gerrit
2017-05-31 20:57:37 UTC
This is an automated email from Gerrit.
Andreas Bolsch (***@gmail.com) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/4152
-- gerrit
commit a3dde711dc584cec47b83235d258b79a54b7a5fd
Author: Andreas Bolsch <***@gmail.com>
Date: Sat May 27 16:32:32 2017 +0200
Generic (bitbanging) driver for SPI flash for Cortex-M
Some Cortex-M controllers include a specialized SPI interface
for external serial flash chips. But most do not, so downloading
firmware employing an external flash require a second procedure
for filling that flash.
This driver overcomes this nuisance by emulating an additional
flash bank which looks like an internal bank.
The SPI flash (or EEPROM) is connected via 4 GPIO pins (NCS, SCLK,
MISO, MOSI) only, no hardware SPI is required, therefore the driver
should work on any Cortex-M controller supporting word addressable
GPIO ports.
Of course, speed is not overwhelming, but on an STM32F746 up to
1.5 MByte/s raw read is possible. However, the limiting factor is
the debug interface and its USB attachement: read/write on
STM32f746-disco via ST-Link up to approx. 150kByte/s.
Set-up is somewhat complicated, as GPIOs must be initialized by a
dedicated script (e. g. in reset init hook, two samples included).
Tested on STM32f746-disco, STM32F769-disco and an STM32F103 board.
Signed-off-by: Andreas Bolsch <***@gmail.com>
Change-Id: I08da60b3a2ed9e156f9eb26859fe13dfd973b873
Signed-off-by: Andreas Bolsch <***@gmail.com>
diff --git a/contrib/loaders/erase_check/msoftspi_erase_check.S b/contrib/loaders/erase_check/msoftspi_erase_check.S
new file mode 100644
index 0000000..bb4a2c1
--- /dev/null
+++ b/contrib/loaders/erase_check/msoftspi_erase_check.S
@@ -0,0 +1,112 @@
+
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/erase_check/msoftspi_erase_check.S > msoftspi_erase_check.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_erase_check.o msoftspi_erase_check.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_erase_check.bin
+ */
+
+/* Params:
+ * r0 - sector count
+ * r1 - flash page size
+ * r2 - unused
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "../flash/msoftspi.inc"
+
+start:
+ mov r8, r0 /* save sector count */
+ adr r0, buffer /* pointer to start of buffer */
+ mov r9, r0 /* save pointer */
+ setup_regs
+sector_start:
+ mov r5, r9 /* get pointer to sector info */
+ ldmia r5!, {r6, r7} /* load address offset and length */
+ mov r12, r6 /* save address offset*/
+ mov r11, r7 /* save sector length */
+start_read:
+ movs r4, #SPIFLASH_READ /* read cmd */
+ send_cmd_addr /* send cmd and address */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+read_loop:
+ bl shift_in_byte /* read byte from flash */
+ movs r7, #0xFF /* fill bits 8-15 */
+ lsls r7, #8 /* with ones */
+ orrs r4, r4, r7 /* copy ones to left of read byte */
+ mov r7, r9 /* pointer to result */
+ ldr r6, [r7, #8] /* get previous result */
+ ands r6, r4 /* and read byte to result */
+ lsls r4, r4, #8 /* shift result into higher byte */
+ orrs r6, r6, r4 /* or read byte to result */
+ str r6, [r7, #8] /* save updated result */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save count */
+ beq sector_end /* stop if sector completed */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ b read_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this sector read */
+ b start_read /* then next page */
+sector_end:
+ bl deselect /* finish this sector read */
+ mov r7, r9 /* pointer to result */
+ mov r6, r11 /* get remaining count */
+ str r6, [r7, #4] /* store remaining (zero) count */
+ adds r7, r7, #12 /* three words */
+ mov r9, r7 /* save update pointer */
+ mov r7, r8 /* get sector count */
+ subs r7, r7, #1 /* decrement count */
+ mov r8, r7 /* save updated count */
+ bne sector_start /* next sector if not finished */
+ b exit
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ .align 2 /* align to word, bkpt is 4 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ buffer, .
diff --git a/contrib/loaders/flash/msoftspi.inc b/contrib/loaders/flash/msoftspi.inc
new file mode 100644
index 0000000..bb66618
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi.inc
@@ -0,0 +1,143 @@
+#include "../../../src/flash/nor/spi.h"
+
+/* set NCS */
+ .macro deselect
+deselect:
+ ldr r6, port_pin_ncs /* NCS port address */
+ ldr r7, [r6] /* load port data */
+ ldr r6, port_pin_ncs+4 /* NCS pin bitmask */
+ orrs r7, r7, r6 /* set NCS bit */
+ ldr r6, port_pin_ncs /* NCS port address */
+ str r7, [r6] /* store new contents */
+ nop /* switch to output */
+ bx lr /* return */
+ .endm
+
+/* entry point, initialize registers */
+ .macro setup_regs
+ subs r0, #1 /* decrement count */
+ mov r11, r0 /* save count */
+ mov r12, r2 /* save address offset */
+ subs r1, #1 /* create page size mask */
+ lsls r3, r3, #30 /* flags into bit 30 and 31 */
+ orrs r1, r1, r3 /* copy flags in page size mask */
+ mov r10, r1 /* save mask and flags */
+ ldr r0, port_pin_sclk /* load SCLK port address */
+ ldr r1, port_pin_sclk+4 /* load SCLK pin bitmask */
+ bl deselect /* for a clean start */
+ .endm
+
+/* send cmd and following 2-, 3- or 4-byte address to flash */
+ .macro send_cmd_addr
+ bl shift_out_byte /* send cmd */
+ mov r7, r10 /* get 3/4-byte flags */
+ lsls r7, r7, #1 /* test for 4-byte address */
+ bcc addr_0_23 /* skip if 3-byte address */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #24 /* addr bits 31-24 */
+ bl shift_out_next /* send addr byte */
+addr_0_23:
+ mov r7, r10 /* get 3/4-byte flags */
+ lsls r7, r7, #2 /* test for 3-byte address */
+ bcc addr_0_15 /* skip if 2-byte address */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #16 /* addr bits 23-16 */
+ bl shift_out_next /* send addr byte */
+addr_0_15:
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #8 /* addr bits 15-8 */
+ bl shift_out_next /* send addr byte */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #0 /* addr bits 7-0 */
+ bl shift_out_next /* send addr byte */
+ .endm
+
+/* shift in one byte from MISO pin
+ * r4: data byte (out)
+ * r5: bit no of MISO pin (lowest byte)
+ * clobbered: r2, r3, r6, r7 */
+ .macro shift_in_byte
+shift_in_byte:
+ movs r4, #1 /* set bit 0 only */
+ lsls r4, r4, #24 /* into bit 24 */
+ movs r7, #0 /* clear temp result */
+ ldr r6, [r0] /* load SCLK port data */
+shift_in_loop:
+ orrs r4, r4, r7 /* insert new bit into result */
+ orrs r6, r6, r1 /* set SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ ldr r7, [r2] /* load MISO port data */
+ ands r7, r7, r3 /* mask all but MISO bit */
+ rors r7, r7, r5 /* shift new bit into bit 0 */
+ bics r6, r6, r1 /* clear SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ lsls r4, r4, #1 /* shift result left one */
+ bcc shift_in_loop /* again if not finished */
+ orrs r4, r4, r7 /* insert last bit into result */
+ bx lr /* return */
+ .endm
+
+/* tail of shift_out_byte, MOSI port address parametrized */
+ .macro shift_tail mosi
+ lsls r4, r4, #1 /* original bit 7 into C */
+shift_out_loop_\@:
+ bics r6, r6, r1 /* clear SCLK bit */
+ sbcs r5, r5, r5 /* fill all bits with ~C */
+ ands r5, r5, r3 /* set/clear bit at MOSI position */
+ str r6, [r0] /* store new SCLK port data */
+ orrs \mosi, \mosi, r3 /* set MOSI bit */
+ bics \mosi, \mosi, r5 /* insert new MOSI bit */
+ str \mosi, [r2] /* store new MOSI port data */
+ orrs r6, r6, r1 /* set SCLK bit */
+ lsls r4, r4, #1 /* shift next bit into C */
+ str r6, [r0] /* store new SCLK port data */
+ bne shift_out_loop_\@ /* again if not finished */
+ str r6, [r0] /* store new SCLK port data */
+ bics r6, r6, r1 /* clear SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ bx lr /* return */
+ .endm
+
+/* clear NCS, shift lowest byte of word out via MOSI
+ * depending on whether MOSI and SCLK are located on same port,
+ * algorithm is slightly different
+ *
+ * r4: data word (in)
+ * clobbered: r2, r3, r5, r6, r7 */
+ .macro shift_out_byte
+shift_out_byte:
+ ldr r2, port_pin_ncs /* NCS port address */
+ ldr r3, port_pin_ncs+4 /* NCS pin bitmask */
+ ldr r7, [r2] /* load port data */
+ bics r7, r7, r3 /* clear NCS bit */
+ str r7, [r2] /* store new contents */
+ ldr r2, port_pin_mosi /* MOSI port address */
+ ldr r3, port_pin_mosi+4 /* MOSI pin bitmask */
+shift_out_next:
+ lsls r4, r4, #1 /* insert '1' bit just */
+ adds r4, r4, #1 /* right of data byte */
+ lsls r4, r4, #23 /* original bit 7 into bit 31 */
+ ldr r6, [r0] /* load SCLK port data */
+ bics r6, r6, r1 /* clear SCLK bit */
+ cmp r0, r2 /* SCLK and MOSI on same port? */
+ beq shift_out_eq
+shift_out_ne:
+ ldr r7, [r2] /* load MOSI port data */
+ shift_tail mosi=r7 /* SCLK, MOSI on different ports */
+shift_out_eq:
+ shift_tail mosi=r6 /* SCLk, MOSI on same port */
+ .endm
+
+/* parameter and buffer allocation */
+ .macro param_block
+port_pin_ncs:
+ .space 8 /* port address (output reg) and mask for NCS pin */
+port_pin_sclk:
+ .space 8 /* port address (output reg) and mask for SCLK pin */
+port_pin_miso:
+ .space 8 /* port address (input reg) and mask for MISO pin */
+port_pin_mosi:
+ .space 8 /* port address (output reg) and mask for MOSI pin */
+bits_no:
+ .space 4 /* bit numbers of NCS, SCLK, MISO, MOSI pins */
+ .endm
diff --git a/contrib/loaders/flash/msoftspi_read.S b/contrib/loaders/flash/msoftspi_read.S
new file mode 100644
index 0000000..3f05a14
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi_read.S
@@ -0,0 +1,104 @@
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/flash/msoftspi_read.S > msoftspi_read.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_read.o msoftspi_read.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_read.bin
+ */
+
+/* Params:
+ * r0 - total count (bytes), status (out)
+ * r1 - flash page size
+ * r2 - address offset into flash
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ * r8 - fifo start
+ * r9 - fifo end + 1
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "msoftspi.inc"
+
+start:
+ setup_regs /* initialize registers */
+start_read:
+ movs r4, #SPIFLASH_READ /* read cmd */
+ send_cmd_addr /* send cmd and address */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+read_loop:
+ bl shift_in_byte /* read byte from flash */
+ ldr r6, wp /* get wp */
+ strb r4, [r6] /* write next byte */
+ adds r6, r6, #1 /* increment wp */
+ cmp r6, r9 /* wp beyond end? */
+ blo read_wait /* if no, then ok */
+ mov r6, r8 /* else wrap around */
+read_wait:
+ ldr r7, rp /* get rp */
+ cmp r7, #0 /* if rp equals 0 */
+ beq exit /* then abort */
+ cmp r6, r7 /* check if fifo full */
+ beq read_wait /* wait until not full */
+ adr r7, wp /* get address of wp */
+ str r6, [r7] /* save updated wp */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save count */
+ bmi exit /* stop if no data left */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ b read_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this page read */
+ b start_read /* then next page */
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ bl deselect /* finish this read cmd */
+ mov r0, r11 /* get count */
+ adds r0, r0, #1 /* correct count */
+ mov r2, r12 /* restore offset */
+ .align 2 /* align to word, bkpt is 11 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ wp, . /* wp, uint32_t */
+ .equ rp, wp + 4 /* rp, uint32_t */
+ .equ buffer, rp + 4 /* buffer follows right away */
diff --git a/contrib/loaders/flash/msoftspi_write.S b/contrib/loaders/flash/msoftspi_write.S
new file mode 100644
index 0000000..56bab5e
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi_write.S
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/flash/msoftspi_write.S > msoftspi_write.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_write.o msoftspi_write.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_write.bin
+ */
+
+/* Params:
+ * r0 - total count (bytes), status (out)
+ * r1 - flash page size
+ * r2 - address offset into flash
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ * r8 - fifo start
+ * r9 - fifo end + 1
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "msoftspi.inc"
+
+start:
+ setup_regs /* initialize registers */
+wip_loop:
+ movs r4, #SPIFLASH_READ_STATUS /* read status reg cmd */
+ bl shift_out_byte /* send command */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+ bl shift_in_byte /* read status byte */
+ bl deselect /* end cmd */
+ lsrs r4, r4, #(SPIFLASH_BSY+1) /* if flash busy, */
+ bcs wip_loop /* then poll again */
+ mov r7, r11 /* get residual count */
+ tst r7, r7 /* test residual count */
+ bpl start_write /* if negative, then finished */
+ b exit
+start_write:
+ movs r4, #SPIFLASH_WRITE_ENABLE /* write enable cmd */
+ bl shift_out_byte /* send cmd */
+ bl deselect /* end cmd */
+ movs r4, #SPIFLASH_PAGE_PROGRAM /* page program cmd */
+ send_cmd_addr /* send cmd and address */
+write_loop:
+ ldr r7, wp /* get wp */
+ cmp r7, #0 /* if wp equals 0 */
+ beq exit /* then abort */
+ ldr r6, rp /* get rp */
+ cmp r6, r7 /* check if fifo empty */
+ beq write_loop /* wait till not empty */
+ ldrb r4, [r6, #0] /* read next byte */
+ bl shift_out_next /* send byte to flash */
+ ldr r6, rp /* get rp */
+ adds r6, r6, #1 /* increment internal rp */
+ cmp r6, r9 /* internal rp beyond end? */
+ blo write_loop1 /* if no, then ok */
+ mov r6, r8 /* else wrap around */
+write_loop1:
+ adr r7, rp /* get address of rp */
+ str r6, [r7] /* save updated rp */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save updated count */
+ bmi page_end /* stop if no data left */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ bne write_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this page write */
+ b wip_loop /* then next page */
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ mov r0, r11 /* get count */
+ adds r0, r0, #1 /* correct count */
+ mov r1, r12 /* get address offset */
+ .align 2 /* align to word, bkpt is 11 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ wp, . /* wp, uint32_t */
+ .equ rp, wp + 4 /* rp, uint32_t */
+ .equ buffer, rp + 4 /* buffer follows right away */
diff --git a/doc/openocd.texi b/doc/openocd.texi
index 5ebfaac..ee58474 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -5073,6 +5073,64 @@ flash bank flash2 ath79 0x20000000 0 0 0 $_TARGETNAME cs2
@end deffn
+@deffn {Flash Driver} msoftspi
+@cindex Generic Cortex-M Bitbanging SPI Interface
+@cindex msoftspi
+
+This driver supports common serial SPI flash chips for any little-endian
+ARM-Cortex-M CPU connected via GPIO pins with memory mapped and word accessible
+output and input registers. No special bit set / clear facility is required.
+
+Setup requires the @var{base} parameter in order to identify
+the memory bank and four @var{*_addr}, @var{*_bit} pairs specifying
+word address and bit number of the GPIO ports of NCS (out), SCLK (out),
+MISO (in), MOSI pins (out). Other parameters are ignored:
+
+base 0 0 0 $_TARGETNAME ncs_addr ncs_bit sclk_addr sclk_bit miso_addr miso_bit mosi_addr mosi_bit
+
+@var{base} is used in write_image etc. to map a certain part of the CPU address space
+to this flash, but is arbitrary otherwise. Chip is autodetected via SPI command and
+matched against hardcoded list of types.
+
+The driver automatically uses 2-, 3-, or 4-byte addresses according to capacity.
+However, for most flash chips 4-byte address mode must be enabled separately,
+e. g. by "Enter 4-Byte Mode" command or setting a special control bit.
+
+GPIO clocks must be enabled, pins configured as GPIO output or input pins,
+respectively, in e. g. reset handler. If flash is connected with four I/O lines
+for QPI mode, the lines not used in SPI mode (HOLD, WP) must be taken into
+account, too. Non-volatile settings like QPI mode or block protection have
+to be disabled.
+
+SPI cycle time depends on CPU and peripheral clocks. Bitbanging might seem
+to be quite slow, but page programming time and data transfer via debug
+interface largely dominate anyway.
+
+Example for STM32F746G discovery board (PB6, PB2, PD12, PD11):
+
+@example
+flash bank_id $_FLASHNAME msoftspi 0x90000000 0 0 0 $_TARGETNAME
+ 0x40020414 6 0x40020414 2 0x40020C10 12 0x40020C14 11
+@end example
+
+There are three specific commands
+@deffn Command {msoftspi mass_erase} bank_id
+Performs a mass erase.
+@end deffn
+
+@deffn Command {msoftspi setid} bank_id name total_size page_size mass_erase_cmd sector_size sector_erase_cmd
+Set flash parameters: @var{name} human readable string, @var{total_size} size in bytes,
+@var{page_size} is write page size. @var{mass_erase_cmd}, @var{sector_size} and @var{sector_erase_cmd}
+are optional. Required if chip id not hardcoded yet and e. g. for EEPROMs which don't support an id
+command at all.
+@end deffn
+
+@deffn Command {msoftspi spicmd} bank_id resp_num cmd_byte ...
+Sends command @var{cmd_byte} and following bytes and reads @var{resp_num} bytes afterwards.
+@end deffn
+
+@end deffn
+
@subsection Internal Flash (Microcontrollers)
@deffn {Flash Driver} aduc702x
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am
index 5179a7c..978b532 100644
--- a/src/flash/nor/Makefile.am
+++ b/src/flash/nor/Makefile.am
@@ -34,6 +34,7 @@ NOR_DRIVERS = \
%D%/lpcspifi.c \
%D%/mdr.c \
%D%/mrvlqspi.c \
+ %D%/msoftspi.c \
%D%/niietcm4.c \
%D%/non_cfi.c \
%D%/nrf51.c \
diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c
index 5502ce5..f8d505c 100644
--- a/src/flash/nor/drivers.c
+++ b/src/flash/nor/drivers.c
@@ -47,6 +47,7 @@ extern struct flash_driver lpc2900_flash;
extern struct flash_driver lpcspifi_flash;
extern struct flash_driver mdr_flash;
extern struct flash_driver mrvlqspi_flash;
+extern struct flash_driver msoftspi_flash;
extern struct flash_driver niietcm4_flash;
extern struct flash_driver nrf51_flash;
extern struct flash_driver numicro_flash;
@@ -101,6 +102,7 @@ static struct flash_driver *flash_drivers[] = {
&lpcspifi_flash,
&mdr_flash,
&mrvlqspi_flash,
+ &msoftspi_flash,
&niietcm4_flash,
&nrf51_flash,
&numicro_flash,
diff --git a/src/flash/nor/msoftspi.c b/src/flash/nor/msoftspi.c
new file mode 100644
index 0000000..7fac25f
--- /dev/null
+++ b/src/flash/nor/msoftspi.c
@@ -0,0 +1,1429 @@
+/***************************************************************************
+ * Copyright (C) 2010 by Antonio Borneo <***@gmail.com>, *
+ * 2017 by Andreas Bolsch <***@mni.thm.de *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imp.h"
+#include "spi.h"
+#include <helper/time_support.h>
+#include <target/algorithm.h>
+#include <target/armv7m.h>
+
+#define CLR_PORT_BIT(data, pin) \
+{ \
+ (data) &= ~(msoftspi_info->pin.mask); \
+ retval = target_write_u32(target, msoftspi_info->pin.addr, data); \
+}
+
+#define SET_PORT_BIT(data, pin) \
+{ \
+ (data) |= (msoftspi_info->pin.mask); \
+ retval = target_write_u32(target, msoftspi_info->pin.addr, data); \
+}
+
+/* bit 1: address byte 4 with bits 24-31 required
+ * bit 0: address byte 3 with bits 16-23 required */
+#define ADDR_BYTES \
+ (((msoftspi_info->dev.size_in_bytes > (1<<24)) ? 0x2 : 0x00) | \
+ ((msoftspi_info->dev.size_in_bytes > (1<<16)) ? 0x1 : 0x00))
+
+/* convert uint32_t into 4 uint8_t in target (i. e. little endian)
+ * byte order, re-inventing the wheel ... */
+static inline uint32_t h_to_le_32(uint32_t val)
+{
+ union {
+ uint32_t word;
+ uint8_t byte[sizeof(uint32_t)];
+ } res;
+
+ res.byte[0] = val & 0xFF;
+ res.byte[1] = (val>>8) & 0xFF;
+ res.byte[2] = (val>>16) & 0xFF;
+ res.byte[3] = (val>>24) & 0xFF;
+
+ return res.word;
+}
+
+/* timeout in ms */
+#define MSOFTSPI_CMD_TIMEOUT (100)
+#define MSOFTSPI_PROBE_TIMEOUT (100)
+#define MSOFTSPI_MAX_TIMEOUT (2000)
+#define MSOFTSPI_MASS_ERASE_TIMEOUT (400000)
+
+typedef struct {
+ uint32_t addr;
+ uint32_t mask;
+} port_pin;
+
+struct msoftspi_flash_bank {
+ int probed;
+ uint32_t bank_num;
+ char devname[32];
+ struct flash_device dev;
+ port_pin ncs;
+ port_pin sclk;
+ port_pin miso;
+ port_pin mosi;
+ uint32_t bits_no;
+};
+
+struct sector_info {
+ uint32_t offset;
+ uint32_t size;
+ uint32_t result;
+};
+
+FLASH_BANK_COMMAND_HANDLER(msoftspi_flash_bank_command)
+{
+ struct msoftspi_flash_bank *msoftspi_info;
+ uint8_t bit_no;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 14)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ msoftspi_info = malloc(sizeof(struct msoftspi_flash_bank));
+ if (msoftspi_info == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ memset(&msoftspi_info->dev, 0, sizeof(msoftspi_info->dev));
+ bank->driver_priv = msoftspi_info;
+ msoftspi_info->probed = 0;
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], msoftspi_info->ncs.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[7], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<24)) | (bit_no<<24));
+ msoftspi_info->ncs.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: NCS bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[8], msoftspi_info->sclk.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[9], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<16)) | (bit_no<<16));
+ msoftspi_info->sclk.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: SCLK bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[10], msoftspi_info->miso.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[11], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<8)) | (bit_no<<8));
+ msoftspi_info->miso.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: MISO bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[12], msoftspi_info->mosi.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[13], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<0)) | (bit_no<<0));
+ msoftspi_info->mosi.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: MOSI bit number must be in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ LOG_DEBUG("NCS (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->ncs.addr, msoftspi_info->ncs.mask);
+ LOG_DEBUG("SCLK (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->sclk.addr, msoftspi_info->sclk.mask);
+ LOG_DEBUG("MISO (in): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->miso.addr, msoftspi_info->miso.mask);
+ LOG_DEBUG("MOSI (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->mosi.addr, msoftspi_info->mosi.mask);
+
+ if (msoftspi_info->sclk.addr == msoftspi_info->mosi.addr)
+ LOG_INFO("SCLK and MOSI located on same port - should work anyway");
+ return ERROR_OK;
+}
+
+/* Send and receive one byte via SPI */
+/* bits 7 down to 0 are shifted out, MSB first */
+static int msoftspi_shift_out(struct flash_bank *bank, uint32_t word)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port_sclk, port_mosi;
+ int k, retval;
+
+ LOG_DEBUG("0x%08" PRIx32, word);
+
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_mosi);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift bit 7 into bit 31 */
+ word <<= 24;
+
+ for (k = 0; k < 8; k++) {
+ /* shift out data bit, msb first */
+ if (word & (1<<31))
+ SET_PORT_BIT(port_mosi, mosi)
+ else
+ CLR_PORT_BIT(port_mosi, mosi);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* set SCLK */
+ if (msoftspi_info->sclk.addr == msoftspi_info->mosi.addr)
+ port_sclk = port_mosi;
+ SET_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift next bit into bit 31 */
+ word <<= 1;
+
+ /* clear SCLK */
+ CLR_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ return ERROR_OK;
+}
+
+static int msoftspi_shift_in(struct flash_bank *bank, uint32_t *word)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port_sclk, port_miso;
+ int k, retval;
+
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ for (k = 0; k < 8; k++) {
+ /* set SCLK */
+ SET_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift in data bit, msb first */
+ retval = target_read_u32(target, msoftspi_info->miso.addr, &port_miso);
+ if (retval != ERROR_OK)
+ return retval;
+
+ *word <<= 1;
+ (port_miso & msoftspi_info->miso.mask) && (*word |= 0x1);
+
+ /* clear SCLK */
+ CLR_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ LOG_DEBUG("0x%08" PRIx32, *word);
+ return ERROR_OK;
+}
+
+/* Read the status register of the external SPI flash chip. */
+static int read_status_reg(struct flash_bank *bank, uint32_t *status)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port;
+ int retval, success;
+
+ success = ERROR_FAIL;
+
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* clear NCS */
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, SPIFLASH_READ_STATUS);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* set to input */
+
+ /* get result byte */
+ success = msoftspi_shift_in(bank, status);
+ *status &= 0xFF;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ return success;
+}
+
+/* Check for WIP (write in progress) bit in status register */
+/* timeout in ms */
+static int wait_till_ready(struct flash_bank *bank, int timeout)
+{
+ uint32_t status;
+ int retval;
+ long long endtime;
+
+ endtime = timeval_ms() + timeout;
+ do {
+ /* Read flash status register */
+ retval = read_status_reg(bank, &status);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if ((status & SPIFLASH_BSY_BIT) == 0)
+ return ERROR_OK;
+ alive_sleep(25);
+ } while (timeval_ms() < endtime);
+
+ LOG_ERROR("timeout");
+ return ERROR_FAIL;
+}
+
+/* Send "write enable" command to SPI flash chip */
+static int msoftspi_write_enable(struct flash_bank *bank)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port, status;
+ int retval;
+
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send write enable command */
+ retval = msoftspi_shift_out(bank, SPIFLASH_WRITE_ENABLE);
+
+err:
+ /* set NCS */
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Read flash status register */
+ retval = read_status_reg(bank, &status);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check write enabled */
+ if ((status & SPIFLASH_WE_BIT) == 0) {
+ LOG_ERROR("Cannot enable write to flash. Status=0x%08" PRIx32, status);
+ return ERROR_FAIL;
+ }
+
+ return ERROR_OK;
+}
+
+/* Erase a single sector */
+static int msoftspi_erase_sector(struct flash_bank *bank, int sector)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t addr = bank->sectors[sector].offset;
+ uint32_t port, data;
+ int retval;
+
+ if (msoftspi_info->dev.erase_cmd == 0x00) {
+ LOG_ERROR("No sector erase available");
+ return ERROR_FAIL;
+ }
+
+ retval = msoftspi_write_enable(bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ } else
+ bank->sectors[sector].is_erased = -1;
+
+ /* Send Sector Erase command */
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, msoftspi_info->dev.erase_cmd);
+ if (retval != ERROR_OK)
+ goto err;
+
+ if (ADDR_BYTES & 0x2) {
+ /* bits 24-31 */
+ retval = msoftspi_shift_out(bank, addr >> 24);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ if (ADDR_BYTES & 0x1) {
+ /* bits 16-23 */
+ retval = msoftspi_shift_out(bank, addr >> 16);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ /* bits 8-15 */
+ retval = msoftspi_shift_out(bank, addr >> 8);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* bits 0-7 */
+ retval = msoftspi_shift_out(bank, addr >> 0);
+ if (retval != ERROR_OK)
+ goto err;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* read flash status register */
+ retval = read_status_reg(bank, &data);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check for command in progress for flash */
+ if ((data & SPIFLASH_WE_BIT) == 0) {
+ LOG_DEBUG("Sector erase not accepted by flash or already completed. Status=0x%08" PRIx32, data);
+ /* return ERROR_FAIL; */
+ }
+
+ /* poll WIP for end of self timed Sector Erase cycle */
+ retval = wait_till_ready(bank, MSOFTSPI_MAX_TIMEOUT);
+
+ /* erasure takes a long time, so some sort of progress message is a good idea */
+ LOG_DEBUG("sector %4d erased", sector);
+
+ return retval;
+}
+
+/* Erase range of sectors */
+static int msoftspi_erase(struct flash_bank *bank, int first, int last)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ int retval = ERROR_OK;
+ int sector;
+
+ LOG_DEBUG("%s: from sector %d to sector %d", __func__, first, last);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if ((first < 0) || (last < first) || (last >= bank->num_sectors)) {
+ LOG_ERROR("Flash sector invalid");
+ return ERROR_FLASH_SECTOR_INVALID;
+ }
+
+ for (sector = first; sector <= last; sector++) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ }
+ }
+
+ for (sector = first; sector <= last; sector++) {
+ retval = msoftspi_erase_sector(bank, sector);
+ if (retval != ERROR_OK)
+ break;
+ keep_alive();
+ }
+
+ if (retval != ERROR_OK)
+ LOG_ERROR("Flash sector_erase failed on sector %d", sector);
+
+ return retval;
+}
+
+/* Check whether flash is blank */
+static int msoftspi_blank_check(struct flash_bank *bank)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct duration bench;
+ struct reg_param reg_params[3];
+ struct armv7m_algorithm armv7m_info;
+ struct working_area *erase_check_algorithm;
+ struct sector_info erase_check_info;
+ uint32_t buffer_size, exit_point, result;
+ int num_sectors, sector, index, count, retval;
+ const uint32_t erased = 0x00FF;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ /* see contrib/loaders/erase_check/msoftspi_erase_check.S for src */
+ static const uint8_t msoftspi_erase_check_code[] = {
+ 0x80, 0x46, 0x57, 0xa0, 0x81, 0x46, 0x01, 0x38, 0x83, 0x46, 0x94, 0x46,
+ 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43, 0x8a, 0x46, 0x4b, 0x48, 0x4c, 0x49,
+ 0x00, 0xf0, 0x46, 0xf8, 0x4d, 0x46, 0xc0, 0xcd, 0xb4, 0x46, 0xbb, 0x46,
+ 0x03, 0x24, 0x00, 0xf0, 0x57, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3,
+ 0x64, 0x46, 0x24, 0x0e, 0x00, 0xf0, 0x57, 0xf8, 0x57, 0x46, 0xbf, 0x00,
+ 0x03, 0xd3, 0x64, 0x46, 0x24, 0x0c, 0x00, 0xf0, 0x50, 0xf8, 0x64, 0x46,
+ 0x24, 0x0a, 0x00, 0xf0, 0x4c, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0,
+ 0x48, 0xf8, 0xc0, 0x46, 0x3c, 0x4a, 0x3d, 0x4b, 0x3f, 0x4d, 0x2d, 0x0a,
+ 0x00, 0xf0, 0x2a, 0xf8, 0xff, 0x27, 0x3f, 0x02, 0x3c, 0x43, 0x4f, 0x46,
+ 0xbe, 0x68, 0x26, 0x40, 0x24, 0x02, 0x26, 0x43, 0xbe, 0x60, 0x67, 0x46,
+ 0x01, 0x37, 0xbc, 0x46, 0x5e, 0x46, 0x01, 0x3e, 0xb3, 0x46, 0x05, 0xd0,
+ 0x56, 0x46, 0x3e, 0x42, 0xea, 0xe7, 0x00, 0xf0, 0x0d, 0xf8, 0xc9, 0xe7,
+ 0x00, 0xf0, 0x0a, 0xf8, 0x4f, 0x46, 0x5e, 0x46, 0x7e, 0x60, 0x0c, 0x37,
+ 0xb9, 0x46, 0x47, 0x46, 0x01, 0x3f, 0xb8, 0x46, 0xba, 0xd1, 0x46, 0xe0,
+ 0x24, 0x4e, 0x37, 0x68, 0x24, 0x4e, 0x37, 0x43, 0x22, 0x4e, 0x37, 0x60,
+ 0xc0, 0x46, 0x70, 0x47, 0x01, 0x24, 0x24, 0x06, 0x00, 0x27, 0x06, 0x68,
+ 0x3c, 0x43, 0x0e, 0x43, 0x06, 0x60, 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41,
+ 0x8e, 0x43, 0x06, 0x60, 0x64, 0x00, 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47,
+ 0x18, 0x4a, 0x19, 0x4b, 0x17, 0x68, 0x9f, 0x43, 0x17, 0x60, 0x1c, 0x4a,
+ 0x1c, 0x4b, 0x64, 0x00, 0x01, 0x34, 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43,
+ 0x90, 0x42, 0x10, 0xd0, 0x17, 0x68, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41,
+ 0x1d, 0x40, 0x06, 0x60, 0x1f, 0x43, 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43,
+ 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60,
+ 0x70, 0x47, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60,
+ 0x1e, 0x43, 0xae, 0x43, 0x16, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60,
+ 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0xc0, 0x46,
+ 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* this will overlay the last 9 words of msoftspi_*_code in target */
+ uint32_t port_buffer[] = {
+ h_to_le_32(msoftspi_info->ncs.addr), h_to_le_32(msoftspi_info->ncs.mask),
+ h_to_le_32(msoftspi_info->sclk.addr), h_to_le_32(msoftspi_info->sclk.mask),
+ h_to_le_32(msoftspi_info->miso.addr), h_to_le_32(msoftspi_info->miso.mask),
+ h_to_le_32(msoftspi_info->mosi.addr), h_to_le_32(msoftspi_info->mosi.mask),
+ h_to_le_32(msoftspi_info->bits_no)
+ };
+
+ num_sectors = bank->num_sectors;
+ while (buffer_size = sizeof(msoftspi_erase_check_code) + num_sectors * sizeof(erase_check_info),
+ target_alloc_working_area_try(target, buffer_size, &erase_check_algorithm) != ERROR_OK) {
+ num_sectors /= 2;
+ if (num_sectors <= 2) {
+ LOG_WARNING("not enough working area, can't do SPI blank check");
+ return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+ }
+ }
+
+ /* prepare check code, excluding port_buffer */
+ retval = target_write_buffer(target, erase_check_algorithm->address,
+ sizeof(msoftspi_erase_check_code) - sizeof(port_buffer), msoftspi_erase_check_code);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* prepare port_buffer values */
+ retval = target_write_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) - sizeof(port_buffer),
+ sizeof(port_buffer), (uint8_t *) port_buffer);
+ if (retval != ERROR_OK)
+ goto err;
+
+ duration_start(&bench);
+
+ /* after breakpoint instruction (halfword) one nop (halfword) and
+ * port_buffer till end of code */
+ exit_point = erase_check_algorithm->address + sizeof(msoftspi_erase_check_code)
+ - sizeof(uint32_t) - sizeof(port_buffer);
+
+ init_reg_param(®_params[0], "r0", 32, PARAM_OUT); /* sector count */
+ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* flash page_size */
+ init_reg_param(®_params[2], "r3", 32, PARAM_OUT); /* 2/3/4-byte address mode */
+
+ sector = 0;
+ while (sector < bank->num_sectors) {
+ /* at most num_sectors sectors to handle in one run */
+ count = bank->num_sectors - sector;
+ if (count > num_sectors)
+ count = num_sectors;
+
+ for (index = 0; index < count; index++) {
+ erase_check_info.offset = h_to_le_32(bank->sectors[sector + index].offset);
+ erase_check_info.size = h_to_le_32(bank->sectors[sector + index].size);
+ erase_check_info.result = h_to_le_32(erased);
+
+ retval = target_write_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) + index * sizeof(erase_check_info),
+ sizeof(erase_check_info), (uint8_t *) &erase_check_info);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ buf_set_u32(reg_params[0].value, 0, 32, count);
+ buf_set_u32(reg_params[1].value, 0, 32, msoftspi_info->dev.pagesize);
+ buf_set_u32(reg_params[2].value, 0, 32, ADDR_BYTES);
+
+ armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
+ armv7m_info.core_mode = ARM_MODE_THREAD;
+
+ LOG_DEBUG("checking sectors %d to %d", sector, sector + count - 1);
+ /* check a block of sectors */
+ retval = target_run_algorithm(target,
+ 0, NULL,
+ 3, reg_params,
+ erase_check_algorithm->address, exit_point,
+ count * MSOFTSPI_MAX_TIMEOUT,
+ &armv7m_info);
+ if (retval != ERROR_OK)
+ break;
+
+ for (index = 0; index < count; index++) {
+ retval = target_read_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) + index * sizeof(erase_check_info),
+ sizeof(erase_check_info), (uint8_t *) &erase_check_info);
+ if (retval != ERROR_OK)
+ goto err;
+
+ if ((erase_check_info.offset != h_to_le_32(bank->sectors[sector + index].offset)) ||
+ (erase_check_info.size != 0)) {
+ LOG_ERROR("corrupted blank check info");
+ goto err;
+ }
+
+ result = h_to_le_32(erase_check_info.result);
+ bank->sectors[sector + index].is_erased = ((result & 0xFF) == 0xFF);
+ LOG_DEBUG("Flash sector %d checked: %04x", sector + index, result & 0xFFFF);
+ }
+ keep_alive();
+ sector += count;
+ }
+
+ destroy_reg_param(®_params[0]);
+ destroy_reg_param(®_params[1]);
+ destroy_reg_param(®_params[2]);
+
+ duration_measure(&bench);
+ LOG_INFO("msoftspi blank checked in"
+ " %fs (%0.3f KiB/s)", duration_elapsed(&bench),
+ duration_kbps(&bench, bank->size));
+
+err:
+ target_free_working_area(target, erase_check_algorithm);
+
+ return retval;
+}
+
+static int msoftspi_protect(struct flash_bank *bank, int set,
+ int first, int last)
+{
+ int sector;
+
+ for (sector = first; sector <= last; sector++)
+ bank->sectors[sector].is_protected = set;
+ return ERROR_OK;
+}
+
+/* Read a block of data from flash or write a block of data to flash */
+static int msoftspi_read_write_block(struct flash_bank *bank, uint8_t *buffer,
+ uint32_t offset, uint32_t count, int write)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct reg_param reg_params[6];
+ struct armv7m_algorithm armv7m_info;
+ struct working_area *write_algorithm;
+ static const uint8_t *code;
+ uint32_t page_size, fifo_start, fifo_size, buffer_size;
+ uint32_t exit_point, remaining;
+ int code_size, retval = ERROR_OK;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " len=0x%08" PRIx32,
+ __func__, offset, count);
+
+ /* see contrib/loaders/flash/msoftspi_read.S for src */
+ static const uint8_t msoftspi_read_code[] = {
+ 0x01, 0x38, 0x83, 0x46, 0x94, 0x46, 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43,
+ 0x8a, 0x46, 0x48, 0x48, 0x48, 0x49, 0x00, 0xf0, 0x3a, 0xf8, 0x03, 0x24,
+ 0x00, 0xf0, 0x4f, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3, 0x64, 0x46,
+ 0x24, 0x0e, 0x00, 0xf0, 0x4f, 0xf8, 0x57, 0x46, 0xbf, 0x00, 0x03, 0xd3,
+ 0x64, 0x46, 0x24, 0x0c, 0x00, 0xf0, 0x48, 0xf8, 0x64, 0x46, 0x24, 0x0a,
+ 0x00, 0xf0, 0x44, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0, 0x40, 0xf8,
+ 0xc0, 0x46, 0x3b, 0x4a, 0x3b, 0x4b, 0x3e, 0x4d, 0x2d, 0x0a, 0x00, 0xf0,
+ 0x22, 0xf8, 0x3d, 0x4e, 0x34, 0x70, 0x01, 0x36, 0x4e, 0x45, 0x00, 0xd3,
+ 0x46, 0x46, 0x3b, 0x4f, 0x00, 0x2f, 0x57, 0xd0, 0xbe, 0x42, 0xfa, 0xd0,
+ 0x37, 0xa7, 0x3e, 0x60, 0x67, 0x46, 0x01, 0x37, 0xbc, 0x46, 0x5e, 0x46,
+ 0x01, 0x3e, 0xb3, 0x46, 0x4c, 0xd4, 0x56, 0x46, 0x3e, 0x42, 0xe6, 0xe7,
+ 0x00, 0xf0, 0x01, 0xf8, 0xc5, 0xe7, 0x27, 0x4e, 0x37, 0x68, 0x27, 0x4e,
+ 0x37, 0x43, 0x25, 0x4e, 0x37, 0x60, 0xc0, 0x46, 0x70, 0x47, 0x01, 0x24,
+ 0x24, 0x06, 0x00, 0x27, 0x06, 0x68, 0x3c, 0x43, 0x0e, 0x43, 0x06, 0x60,
+ 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41, 0x8e, 0x43, 0x06, 0x60, 0x64, 0x00,
+ 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47, 0x1b, 0x4a, 0x1b, 0x4b, 0x17, 0x68,
+ 0x9f, 0x43, 0x17, 0x60, 0x1e, 0x4a, 0x1f, 0x4b, 0x64, 0x00, 0x01, 0x34,
+ 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43, 0x90, 0x42, 0x10, 0xd0, 0x17, 0x68,
+ 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1f, 0x43,
+ 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1,
+ 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x64, 0x00, 0x8e, 0x43,
+ 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1e, 0x43, 0xae, 0x43, 0x16, 0x60,
+ 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43,
+ 0x06, 0x60, 0x70, 0x47, 0xff, 0xf7, 0xb7, 0xff, 0x58, 0x46, 0x01, 0x30,
+ 0x62, 0x46, 0xc0, 0x46, 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* see contrib/loaders/flash/msoftspi_write.S for src */
+ static const uint8_t msoftspi_write_code[] = {
+ 0x01, 0x38, 0x83, 0x46, 0x94, 0x46, 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43,
+ 0x8a, 0x46, 0x50, 0x48, 0x50, 0x49, 0x00, 0xf0, 0x4d, 0xf8, 0x05, 0x24,
+ 0x00, 0xf0, 0x62, 0xf8, 0xc0, 0x46, 0x4e, 0x4a, 0x4e, 0x4b, 0x51, 0x4d,
+ 0x2d, 0x0a, 0x00, 0xf0, 0x4b, 0xf8, 0x00, 0xf0, 0x41, 0xf8, 0x64, 0x08,
+ 0xf1, 0xd2, 0x5f, 0x46, 0x3f, 0x42, 0x00, 0xd5, 0x81, 0xe0, 0x06, 0x24,
+ 0x00, 0xf0, 0x50, 0xf8, 0x00, 0xf0, 0x36, 0xf8, 0x02, 0x24, 0x00, 0xf0,
+ 0x4b, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3, 0x64, 0x46, 0x24, 0x0e,
+ 0x00, 0xf0, 0x4b, 0xf8, 0x57, 0x46, 0xbf, 0x00, 0x03, 0xd3, 0x64, 0x46,
+ 0x24, 0x0c, 0x00, 0xf0, 0x44, 0xf8, 0x64, 0x46, 0x24, 0x0a, 0x00, 0xf0,
+ 0x40, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0, 0x3c, 0xf8, 0x3d, 0x4f,
+ 0x00, 0x2f, 0x60, 0xd0, 0x3c, 0x4e, 0xbe, 0x42, 0xf9, 0xd0, 0x34, 0x78,
+ 0x00, 0xf0, 0x33, 0xf8, 0x39, 0x4e, 0x01, 0x36, 0x4e, 0x45, 0x00, 0xd3,
+ 0x46, 0x46, 0x37, 0xa7, 0x3e, 0x60, 0x67, 0x46, 0x01, 0x37, 0xbc, 0x46,
+ 0x5e, 0x46, 0x01, 0x3e, 0xb3, 0x46, 0x02, 0xd4, 0x56, 0x46, 0x3e, 0x42,
+ 0xe5, 0xd1, 0x00, 0xf0, 0x01, 0xf8, 0xb2, 0xe7, 0x25, 0x4e, 0x37, 0x68,
+ 0x25, 0x4e, 0x37, 0x43, 0x23, 0x4e, 0x37, 0x60, 0xc0, 0x46, 0x70, 0x47,
+ 0x01, 0x24, 0x24, 0x06, 0x00, 0x27, 0x06, 0x68, 0x3c, 0x43, 0x0e, 0x43,
+ 0x06, 0x60, 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41, 0x8e, 0x43, 0x06, 0x60,
+ 0x64, 0x00, 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47, 0x19, 0x4a, 0x1a, 0x4b,
+ 0x17, 0x68, 0x9f, 0x43, 0x17, 0x60, 0x1d, 0x4a, 0x1d, 0x4b, 0x64, 0x00,
+ 0x01, 0x34, 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43, 0x90, 0x42, 0x10, 0xd0,
+ 0x17, 0x68, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60,
+ 0x1f, 0x43, 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60,
+ 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x64, 0x00,
+ 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1e, 0x43, 0xae, 0x43,
+ 0x16, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60,
+ 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x58, 0x46, 0x01, 0x30, 0x61, 0x46,
+ 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+
+ code = write ? msoftspi_write_code : msoftspi_read_code;
+ code_size = write ? sizeof(msoftspi_write_code) : sizeof(msoftspi_read_code);
+
+ /* this will overlay the last 9 words of msoftspi_*_code in target */
+ uint32_t port_buffer[] = {
+ h_to_le_32(msoftspi_info->ncs.addr), h_to_le_32(msoftspi_info->ncs.mask),
+ h_to_le_32(msoftspi_info->sclk.addr), h_to_le_32(msoftspi_info->sclk.mask),
+ h_to_le_32(msoftspi_info->miso.addr), h_to_le_32(msoftspi_info->miso.mask),
+ h_to_le_32(msoftspi_info->mosi.addr), h_to_le_32(msoftspi_info->mosi.mask),
+ h_to_le_32(msoftspi_info->bits_no)
+ };
+
+ /* memory buffer, we assume sectorsize to be a power of 2 times page_size */
+ page_size = msoftspi_info->dev.pagesize;
+ fifo_size = msoftspi_info->dev.sectorsize;
+ while (buffer_size = code_size + 2 * sizeof(uint32_t) + fifo_size,
+ target_alloc_working_area_try(target, buffer_size, &write_algorithm) != ERROR_OK) {
+ fifo_size /= 2;
+ if (fifo_size < page_size) {
+ /* we already allocated the reading/writing code, but failed to get a
+ * buffer, free the algorithm */
+ target_free_working_area(target, write_algorithm);
+
+ LOG_WARNING("not enough working area, can't do SPI %s",
+ write ? "page writes" : "reads");
+ return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+ }
+ };
+
+ /* prepare read/write code, excluding port_buffer */
+ retval = target_write_buffer(target, write_algorithm->address,
+ code_size - sizeof(port_buffer), code);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* prepare port_buffer values */
+ retval = target_write_buffer(target, write_algorithm->address
+ + code_size - sizeof(port_buffer),
+ sizeof(port_buffer), (uint8_t *) port_buffer);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* target buffer starts right after flash_write_code, i. e.
+ * wp and rp are implicitly included in buffer!!! */
+ fifo_start = write_algorithm->address + code_size + 2 * sizeof(uint32_t);
+
+ init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* count (in), status (out) */
+ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* flash page_size */
+ init_reg_param(®_params[2], "r2", 32, PARAM_IN_OUT); /* offset into flash address */
+ init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* 2/3/4-byte address mode */
+ init_reg_param(®_params[4], "r8", 32, PARAM_OUT); /* fifo start */
+ init_reg_param(®_params[5], "r9", 32, PARAM_OUT); /* fifo end + 1 */
+
+ buf_set_u32(reg_params[0].value, 0, 32, count);
+ buf_set_u32(reg_params[1].value, 0, 32,
+ write ? page_size : msoftspi_info->dev.sectorsize);
+ buf_set_u32(reg_params[2].value, 0, 32, offset);
+ buf_set_u32(reg_params[3].value, 0, 32, ADDR_BYTES);
+ buf_set_u32(reg_params[4].value, 0, 32, fifo_start);
+ buf_set_u32(reg_params[5].value, 0, 32, fifo_start + fifo_size);
+
+ armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
+ armv7m_info.core_mode = ARM_MODE_THREAD;
+
+ /* after breakpoint instruction (halfword) one nop (halfword) and
+ * port_buffer till end of code */
+ exit_point = write_algorithm->address + code_size
+ - sizeof(uint32_t) - sizeof(port_buffer);
+
+ if (write) {
+ retval = target_run_flash_async_algorithm(target, buffer, count, 1,
+ 0, NULL,
+ 6, reg_params,
+ write_algorithm->address + code_size,
+ fifo_size + 2 * sizeof(uint32_t),
+ write_algorithm->address, exit_point,
+ &armv7m_info);
+ } else {
+ retval = target_run_read_async_algorithm(target, buffer, count, 1,
+ 0, NULL,
+ 6, reg_params,
+ write_algorithm->address + code_size,
+ fifo_size + 2 * sizeof(uint32_t),
+ write_algorithm->address, exit_point,
+ &armv7m_info);
+ }
+
+ remaining = buf_get_u32(reg_params[0].value, 0, 32);
+ if ((retval == ERROR_OK) && remaining)
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ if (retval != ERROR_OK) {
+ offset = buf_get_u32(reg_params[2].value, 0, 32);
+ LOG_ERROR("flash %s failed at address 0x%" PRIx32 ", remaining 0x%" PRIx32,
+ write ? "write" : "read", offset, remaining);
+ }
+
+ destroy_reg_param(®_params[0]);
+ destroy_reg_param(®_params[1]);
+ destroy_reg_param(®_params[2]);
+ destroy_reg_param(®_params[3]);
+ destroy_reg_param(®_params[4]);
+ destroy_reg_param(®_params[5]);
+
+err:
+ target_free_working_area(target, write_algorithm);
+
+ return retval;
+}
+
+static int msoftspi_write(struct flash_bank *bank, const uint8_t *buffer,
+ uint32_t offset, uint32_t count)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ int sector;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32,
+ __func__, offset, count);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (offset + count > msoftspi_info->dev.size_in_bytes) {
+ LOG_WARNING("Write beyond end of flash. Extra data discarded.");
+ count = msoftspi_info->dev.size_in_bytes - offset;
+ }
+
+ /* check sector protection */
+ for (sector = 0; sector < bank->num_sectors; sector++) {
+ /* Start offset in or before this sector? */
+ /* End offset in or behind this sector? */
+ if ((offset < (bank->sectors[sector].offset + bank->sectors[sector].size))
+ && ((offset + count - 1) >= bank->sectors[sector].offset)) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ } else
+ bank->sectors[sector].is_erased = -1;
+ }
+ }
+
+ return msoftspi_read_write_block(bank, (uint8_t *) buffer, offset, count, 1);
+}
+
+static int msoftspi_read(struct flash_bank *bank, uint8_t *buffer,
+ uint32_t offset, uint32_t count)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32,
+ __func__, offset, count);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if (offset + count > msoftspi_info->dev.size_in_bytes) {
+ LOG_WARNING("Read beyond end of flash. Extra data to be ignored.");
+ count = msoftspi_info->dev.size_in_bytes - offset;
+ }
+
+ return msoftspi_read_write_block(bank, buffer, offset, count, 0);
+}
+
+/* Return ID of flash device */
+static int read_flash_id(struct flash_bank *bank, uint32_t *id)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port, data;
+ int k, retval, success;
+
+ success = ERROR_FAIL;
+
+ if ((target->state != TARGET_HALTED) && (target->state != TARGET_RESET)) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* clear SCLK */
+ retval = target_read_u32(target, msoftspi_info->sclk.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ port &= ~msoftspi_info->sclk.mask;
+ retval = target_write_u32(target, msoftspi_info->sclk.addr, port);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* poll WIP */
+ retval = wait_till_ready(bank, MSOFTSPI_PROBE_TIMEOUT);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* clear NCS */
+ CLR_PORT_BIT(port, ncs);
+ retval = target_write_u32(target, msoftspi_info->ncs.addr, port);
+ if (retval != ERROR_OK)
+ goto err;
+
+ success = msoftspi_shift_out(bank, SPIFLASH_READ_ID);
+ if (success != ERROR_OK)
+ goto err;
+
+ /* set to input */
+
+ for (k = 0; k < 3; k++) {
+ success = msoftspi_shift_in(bank, &data);
+ if (success != ERROR_OK)
+ goto err;
+ }
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ /* three bytes received, placed in bits 0 to 23, byte reversed */
+ *id = ((data & 0xFF) << 16) | (data & 0xFF00) | ((data & 0xFF0000) >> 16);
+
+ if ((*id == 0x000000) || (*id == 0xFFFFFF)) {
+ LOG_INFO("No response from flash");
+ success = ERROR_TARGET_NOT_EXAMINED;
+ }
+
+ return success;
+}
+
+/* Read id from flash chip */
+static int msoftspi_probe(struct flash_bank *bank)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct flash_sector *sectors;
+ const struct flash_device *p;
+ uint32_t id = 0;
+ int retval;
+
+ if (msoftspi_info->probed)
+ free(bank->sectors);
+ msoftspi_info->probed = 0;
+
+ /* read and decode flash ID */
+ retval = read_flash_id(bank, &id);
+ LOG_DEBUG("id 0x%06" PRIx32, id);
+ if (retval == ERROR_TARGET_NOT_EXAMINED) {
+ /* no id retrieved, so id must be set manually */
+ LOG_INFO("No id - set flash parameters manually");
+ return ERROR_OK;
+ }
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* identify flash */
+ msoftspi_info->dev.name = NULL;
+ for (p = flash_devices; id && p->name ; p++) {
+ if (p->device_id == id) {
+ memcpy(&msoftspi_info->dev, p, sizeof(msoftspi_info->dev));
+ LOG_INFO("flash \'%s\' id = 0x%06" PRIx32
+ "\nflash size = %lukbytes",
+ p->name, id, p->size_in_bytes>>10);
+ break;
+ }
+ }
+
+ if (id && !p->name) {
+ LOG_ERROR("Unknown flash device id = 0x%06" PRIx32, id);
+ return ERROR_FAIL;
+ }
+
+ /* set correct size value */
+ bank->size = msoftspi_info->dev.size_in_bytes;
+
+ /* create and fill sectors array */
+ bank->num_sectors =
+ msoftspi_info->dev.size_in_bytes / msoftspi_info->dev.sectorsize;
+ sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
+ if (sectors == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ for (int sector = 0; sector < bank->num_sectors; sector++) {
+ sectors[sector].offset = sector * (msoftspi_info->dev.sectorsize);
+ sectors[sector].size = msoftspi_info->dev.sectorsize;
+ sectors[sector].is_erased = -1;
+ sectors[sector].is_protected = 0;
+ }
+
+ bank->sectors = sectors;
+ msoftspi_info->probed = 1;
+
+ return ERROR_OK;
+}
+
+static int msoftspi_auto_probe(struct flash_bank *bank)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ if (msoftspi_info->probed)
+ return ERROR_OK;
+ return msoftspi_probe(bank);
+}
+
+static int msoftspi_protect_check(struct flash_bank *bank)
+{
+ /* nothing to do. Protection is only handled in SW. */
+ return ERROR_OK;
+}
+
+static int get_msoftspi_info(struct flash_bank *bank, char *buf, int buf_size)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ if (!(msoftspi_info->probed)) {
+ snprintf(buf, buf_size, "\nmsoftspi flash bank not probed yet\n");
+ return ERROR_OK;
+ }
+
+ snprintf(buf, buf_size, "flash \'%s\' device id = 0x%06" PRIx32
+ "\nflash size = %dkBytes\npage size = %d"
+ ", mass_erase = 0x%02x, sector_erase = 0x%02x",
+ msoftspi_info->dev.name, msoftspi_info->dev.device_id,
+ bank->size>>10, msoftspi_info->dev.pagesize,
+ msoftspi_info->dev.chip_erase_cmd, msoftspi_info->dev.erase_cmd);
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(msoftspi_handle_mass_erase_command)
+{
+ struct target *target = NULL;
+ struct flash_bank *bank;
+ struct msoftspi_flash_bank *msoftspi_info;
+ struct duration bench;
+ uint32_t port, data;
+ int retval, sector;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC != 1)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+
+ msoftspi_info = bank->driver_priv;
+ target = bank->target;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if (msoftspi_info->dev.chip_erase_cmd == 0x00) {
+ LOG_ERROR("No mass erase available");
+ return ERROR_FAIL;
+ }
+
+ for (sector = 0; sector < bank->num_sectors; sector++) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ }
+ }
+
+ duration_start(&bench);
+
+ retval = msoftspi_write_enable(bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Send Mass Erase command */
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, msoftspi_info->dev.chip_erase_cmd);
+ if (retval != ERROR_OK)
+ goto err;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* read flash status register */
+ retval = read_status_reg(bank, &data);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check for command in progress for flash */
+ if ((data & SPIFLASH_WE_BIT) == 0) {
+ LOG_ERROR("Mass erase command not accepted by flash. Status=0x%08" PRIx32, data);
+ return ERROR_FAIL;
+ }
+
+ /* poll WIP for end of self timed Sector Erase cycle */
+ retval = wait_till_ready(bank, MSOFTSPI_MASS_ERASE_TIMEOUT);
+
+ duration_measure(&bench);
+ if (retval == ERROR_OK) {
+ /* set all sectors as erased */
+ for (sector = 0; sector < bank->num_sectors; sector++)
+ bank->sectors[sector].is_erased = 1;
+
+ command_print(CMD_CTX, "msoftspi mass erase completed in"
+ " %fs (%0.3f KiB/s)", duration_elapsed(&bench),
+ duration_kbps(&bench, bank->size));
+ } else {
+ command_print(CMD_CTX, "msoftspi mass erase failed after %fs",
+ duration_elapsed(&bench));
+ }
+
+ return retval;
+}
+
+static int log2u(unsigned long word)
+{
+ int result;
+
+ for (result = 0; (unsigned int) result < sizeof(unsigned long) * 8; result++)
+ if (word == (1UL<<result))
+ return result;
+
+ return -1;
+}
+
+COMMAND_HANDLER(msoftspi_handle_setid)
+{
+ struct flash_bank *bank = NULL;
+ struct msoftspi_flash_bank *msoftspi_info = NULL;
+ struct flash_sector *sectors = NULL;
+ int retval;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 2 || CMD_ARGC > 7)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+ msoftspi_info = bank->driver_priv;
+
+ /* invalidate all old info */
+ if (msoftspi_info->probed)
+ free(bank->sectors);
+ msoftspi_info->probed = 0;
+ msoftspi_info->dev.name = NULL;
+ msoftspi_info->dev.device_id = 0;
+
+ strncpy(msoftspi_info->devname, CMD_ARGV[1], sizeof(msoftspi_info->devname) - 1);
+ msoftspi_info->devname[sizeof(msoftspi_info->devname) - 1] = '\0';
+
+ COMMAND_PARSE_NUMBER(u64, CMD_ARGV[2], msoftspi_info->dev.size_in_bytes);
+ if (log2u(msoftspi_info->dev.size_in_bytes) < 8) {
+ command_print(CMD_CTX, "msoftspi: device size must be 2^n with n >= 8");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], msoftspi_info->dev.pagesize);
+ if ((log2u(msoftspi_info->dev.pagesize) > log2u(msoftspi_info->dev.size_in_bytes)) ||
+ (log2u(msoftspi_info->dev.pagesize) < 0)) {
+ command_print(CMD_CTX, "msoftspi: page size must be 2^n and <= device size");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ if (CMD_ARGC > 4)
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[4], msoftspi_info->dev.chip_erase_cmd);
+ else
+ msoftspi_info->dev.chip_erase_cmd = 0x00;
+
+ if (CMD_ARGC > 5) {
+ COMMAND_PARSE_NUMBER(u64, CMD_ARGV[5], msoftspi_info->dev.sectorsize);
+ if ((log2u(msoftspi_info->dev.sectorsize) > log2u(msoftspi_info->dev.size_in_bytes)) ||
+ (log2u(msoftspi_info->dev.sectorsize) < 0)) {
+ command_print(CMD_CTX, "msoftspi: sector size must be 2^n and <= device size");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ if (CMD_ARGC > 6)
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[6], msoftspi_info->dev.erase_cmd);
+ else
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ } else {
+ /* no sector size / sector erase cmd given, treat whole bank as a single sector */
+ msoftspi_info->dev.erase_cmd = 0x00;
+ msoftspi_info->dev.sectorsize = msoftspi_info->dev.size_in_bytes;
+ }
+
+ /* set correct size value */
+ bank->size = msoftspi_info->dev.size_in_bytes;
+
+ /* create and fill sectors array */
+ bank->num_sectors =
+ msoftspi_info->dev.size_in_bytes / msoftspi_info->dev.sectorsize;
+ sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
+ if (sectors == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ for (int sector = 0; sector < bank->num_sectors; sector++) {
+ sectors[sector].offset = sector * (msoftspi_info->dev.sectorsize);
+ sectors[sector].size = msoftspi_info->dev.sectorsize;
+ sectors[sector].is_erased = -1;
+ sectors[sector].is_protected = 0;
+ }
+
+ bank->sectors = sectors;
+ msoftspi_info->dev.name = msoftspi_info->devname;
+ msoftspi_info->probed = 1;
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(msoftspi_handle_spicmd)
+{
+ struct target *target = NULL;
+ struct flash_bank *bank;
+ struct msoftspi_flash_bank *msoftspi_info;
+ uint32_t port, data;
+ uint8_t num_bytes, cmd_byte;
+ const int max = 16;
+ unsigned int count;
+ char temp[4], output[(1 + max + 256) * 3 + 4];
+ int retval;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 3 || CMD_ARGC > max + 3)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+
+ msoftspi_info = bank->driver_priv;
+ target = bank->target;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[1], num_bytes);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[2], cmd_byte);
+
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ snprintf(output, sizeof(output), "spicmd: %02x ", cmd_byte & 0xFF);
+ retval = msoftspi_shift_out(bank, cmd_byte);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send additional bytes */
+ for (count = 3; count < CMD_ARGC; count++) {
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[count], cmd_byte);
+ snprintf(temp, sizeof(temp), "%02x ", cmd_byte & 0xFF);
+ retval = msoftspi_shift_out(bank, cmd_byte);
+ if (retval != ERROR_OK)
+ goto err;
+ strncat(output, temp, sizeof(output));
+ }
+
+ /* set to input */
+ strncat(output, "-> ", sizeof(output));
+
+ for ( ; num_bytes > 0; num_bytes--) {
+ retval = msoftspi_shift_in(bank, &data);
+ if (retval != ERROR_OK)
+ goto err;
+ snprintf(temp, sizeof(temp), "%02x ", data & 0xFF);
+ strncat(output, temp, sizeof(output));
+ }
+ command_print(CMD_CTX, "%s", output);
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ return retval;
+}
+
+static const struct command_registration msoftspi_exec_command_handlers[] = {
+ {
+ .name = "mass_erase",
+ .handler = msoftspi_handle_mass_erase_command,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id",
+ .help = "Mass erase entire flash device.",
+ },
+ {
+ .name = "setid",
+ .handler = msoftspi_handle_setid,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id flash_id",
+ .help = "Set id of flash chip to flash_id (uint24)",
+ },
+ {
+ .name = "spicmd",
+ .handler = msoftspi_handle_spicmd,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id num_resp cmd_byte ...",
+ .help = "Send low-level command cmd_byte and following bytes, read num_bytes.",
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration msoftspi_command_handlers[] = {
+ {
+ .name = "msoftspi",
+ .mode = COMMAND_ANY,
+ .help = "msoftspi flash command group",
+ .usage = "",
+ .chain = msoftspi_exec_command_handlers,
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+struct flash_driver msoftspi_flash = {
+ .name = "msoftspi",
+ .commands = msoftspi_command_handlers,
+ .flash_bank_command = msoftspi_flash_bank_command,
+ .erase = msoftspi_erase,
+ .protect = msoftspi_protect,
+ .write = msoftspi_write,
+ .read = msoftspi_read,
+ .probe = msoftspi_probe,
+ .auto_probe = msoftspi_auto_probe,
+ .erase_check = msoftspi_blank_check,
+ .protect_check = msoftspi_protect_check,
+ .info = get_msoftspi_info,
+};
diff --git a/src/flash/nor/spi.c b/src/flash/nor/spi.c
index fe1be86..62f1868 100644
--- a/src/flash/nor/spi.c
+++ b/src/flash/nor/spi.c
@@ -54,6 +54,7 @@ const struct flash_device flash_devices[] = {
FLASH_ID("sp s25fl164k", 0xd8, 0xc7, 0x00174001, 0x100, 0x10000, 0x800000),
FLASH_ID("sp s25fl128", 0xd8, 0xc7, 0x00182001, 0x100, 0x10000, 0x1000000),
FLASH_ID("sp s25fl256", 0xd8, 0xc7, 0x00190201, 0x100, 0x10000, 0x2000000),
+ FLASH_ID("sp s25fl512s", 0xd8, 0xc7, 0x00200201, 0x200, 0x40000, 0x4000000),
FLASH_ID("atmel 25f512", 0x52, 0xc7, 0x0065001f, 0x80, 0x8000, 0x10000),
FLASH_ID("atmel 25f1024", 0x52, 0x62, 0x0060001f, 0x100, 0x8000, 0x20000),
FLASH_ID("atmel 25f2048", 0x52, 0x62, 0x0063001f, 0x100, 0x10000, 0x40000),
@@ -79,9 +80,10 @@ const struct flash_device flash_devices[] = {
FLASH_ID("win w25q32dw", 0xd8, 0xc7, 0x001660ef, 0x100, 0x10000, 0x400000),
FLASH_ID("win w25q64cv", 0xd8, 0xc7, 0x001740ef, 0x100, 0x10000, 0x800000),
FLASH_ID("win w25q128fv", 0xd8, 0xc7, 0x001840ef, 0x100, 0x10000, 0x1000000),
+ FLASH_ID("win w25q256fv", 0xd8, 0xc7, 0x001940ef, 0x100, 0x10000, 0x2000000),
FLASH_ID("gd gd25q20", 0x20, 0xc7, 0x00c84012, 0x100, 0x1000, 0x80000),
FLASH_ID("gd gd25q16c", 0xd8, 0xc7, 0x001540c8, 0x100, 0x10000, 0x200000),
FLASH_ID("gd gd25q32c", 0xd8, 0xc7, 0x001640c8, 0x100, 0x10000, 0x400000),
FLASH_ID("gd gd25q128c", 0xd8, 0xc7, 0x001840c8, 0x100, 0x10000, 0x1000000),
- FLASH_ID(NULL, 0, 0, 0, 0, 0, 0)
+ FLASH_ID(NULL, 0, 0, 0, 0, 0, 0)
};
diff --git a/src/flash/nor/spi.h b/src/flash/nor/spi.h
index a184998..b3d8778 100644
--- a/src/flash/nor/spi.h
+++ b/src/flash/nor/spi.h
@@ -22,6 +22,8 @@
#ifndef OPENOCD_FLASH_NOR_SPI_H
#define OPENOCD_FLASH_NOR_SPI_H
+#ifndef __ASSEMBLER__
+
/* data structure to maintain flash ids from different vendors */
struct flash_device {
char *name;
@@ -46,9 +48,13 @@ struct flash_device {
extern const struct flash_device flash_devices[];
+#endif
+
/* fields in SPI flash status register */
-#define SPIFLASH_BSY_BIT 0x00000001 /* WIP Bit of SPI SR on SMI SR */
-#define SPIFLASH_WE_BIT 0x00000002 /* WEL Bit of SPI SR on SMI SR */
+#define SPIFLASH_BSY 0
+#define SPIFLASH_BSY_BIT (1 << SPIFLASH_BSY) /* WIP Bit of SPI SR on SMI SR */
+#define SPIFLASH_WE 1
+#define SPIFLASH_WE_BIT (1 << SPIFLASH_WE) /* WEL Bit of SPI SR on SMI SR */
/* SPI Flash Commands */
#define SPIFLASH_READ_ID 0x9F /* Read Flash Identification */
diff --git a/src/target/target.c b/src/target/target.c
index e04ecc4..9501f1a 100644
--- a/src/target/target.c
+++ b/src/target/target.c
@@ -1038,6 +1038,148 @@ int target_run_flash_async_algorithm(struct target *target,
return retval;
}
+int target_run_read_async_algorithm(struct target *target,
+ uint8_t *buffer, uint32_t count, int block_size,
+ int num_mem_params, struct mem_param *mem_params,
+ int num_reg_params, struct reg_param *reg_params,
+ uint32_t buffer_start, uint32_t buffer_size,
+ uint32_t entry_point, uint32_t exit_point, void *arch_info)
+{
+ int retval;
+ int timeout = 0;
+
+ const uint8_t *buffer_orig = buffer;
+
+ /* Set up working area. First word is write pointer, second word is read pointer,
+ * rest is fifo data area. */
+ uint32_t wp_addr = buffer_start;
+ uint32_t rp_addr = buffer_start + 4;
+ uint32_t fifo_start_addr = buffer_start + 8;
+ uint32_t fifo_end_addr = buffer_start + buffer_size;
+
+ uint32_t wp = fifo_start_addr;
+ uint32_t rp = fifo_start_addr;
+
+ /* validate block_size is 2^n */
+ assert(!block_size || !(block_size & (block_size - 1)));
+
+ retval = target_write_u32(target, wp_addr, wp);
+ if (retval != ERROR_OK)
+ return retval;
+ retval = target_write_u32(target, rp_addr, rp);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Start up algorithm on target */
+ retval = target_start_algorithm(target, num_mem_params, mem_params,
+ num_reg_params, reg_params,
+ entry_point,
+ exit_point,
+ arch_info);
+
+ if (retval != ERROR_OK) {
+ LOG_ERROR("error starting target flash read algorithm");
+ return retval;
+ }
+
+ while (count > 0) {
+ retval = target_read_u32(target, wp_addr, &wp);
+ if (retval != ERROR_OK) {
+ LOG_ERROR("failed to get write pointer");
+ break;
+ }
+
+ LOG_DEBUG("offs 0x%zx count 0x%" PRIx32 " wp 0x%" PRIx32 " rp 0x%" PRIx32,
+ (size_t) (buffer - buffer_orig), count, wp, rp);
+
+ if (wp == 0) {
+ LOG_ERROR("flash read algorithm aborted by target");
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ break;
+ }
+
+ if (((wp - fifo_start_addr) & (block_size - 1)) || wp < fifo_start_addr || wp >= fifo_end_addr) {
+ LOG_ERROR("corrupted fifo write pointer 0x%" PRIx32, wp);
+ break;
+ }
+
+ /* Count the number of bytes available in the fifo without
+ * crossing the wrap around. */
+ uint32_t thisrun_bytes;
+ if (wp > rp)
+ thisrun_bytes = wp - rp;
+ else
+ thisrun_bytes = fifo_end_addr - rp;
+
+ if (thisrun_bytes == 0) {
+ /* Throttle polling a bit if transfer is (much) faster than flash
+ * reading. The exact delay shouldn't matter as long as it's
+ * less than buffer size / flash speed. This is very unlikely to
+ * run when using high latency connections such as USB. */
+ alive_sleep(10);
+
+ /* to stop an infinite loop on some targets check and increment a timeout
+ * this issue was observed on a stellaris using the new ICDI interface */
+ if (timeout++ >= 500) {
+ LOG_ERROR("timeout waiting for algorithm, a target reset is recommended");
+ return ERROR_FLASH_OPERATION_FAILED;
+ }
+ continue;
+ }
+
+ /* reset our timeout */
+ timeout = 0;
+
+ /* Limit to the amount of data we actually want to read */
+ if (thisrun_bytes > count * block_size)
+ thisrun_bytes = count * block_size;
+
+ /* Read data from fifo */
+ retval = target_read_buffer(target, rp, thisrun_bytes, buffer);
+ if (retval != ERROR_OK)
+ break;
+
+ /* Update counters and wrap write pointer */
+ buffer += thisrun_bytes;
+ count -= thisrun_bytes / block_size;
+ rp += thisrun_bytes;
+ if (rp >= fifo_end_addr)
+ rp = fifo_start_addr;
+
+ /* Store updated write pointer to target */
+ retval = target_write_u32(target, rp_addr, rp);
+ if (retval != ERROR_OK)
+ break;
+ }
+
+ if (retval != ERROR_OK) {
+ /* abort flash write algorithm on target */
+ target_write_u32(target, rp_addr, 0);
+ }
+
+ int retval2 = target_wait_algorithm(target, num_mem_params, mem_params,
+ num_reg_params, reg_params,
+ exit_point,
+ 10000,
+ arch_info);
+
+ if (retval2 != ERROR_OK) {
+ LOG_ERROR("error waiting for target flash write algorithm");
+ retval = retval2;
+ }
+
+ if (retval == ERROR_OK) {
+ /* check if algorithm set wp = 0 after fifo writer loop finished */
+ retval = target_read_u32(target, wp_addr, &wp);
+ if (retval == ERROR_OK && wp == 0) {
+ LOG_ERROR("flash read algorithm aborted by target");
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ }
+ }
+
+ return retval;
+}
+
int target_read_memory(struct target *target,
target_addr_t address, uint32_t size, uint32_t count, uint8_t *buffer)
{
diff --git a/src/target/target.h b/src/target/target.h
index 53f9e26..98d29f7 100644
--- a/src/target/target.h
+++ b/src/target/target.h
@@ -526,6 +526,18 @@ int target_run_flash_async_algorithm(struct target *target,
void *arch_info);
/**
+ * This routine is a wrapper for asynchronous algorithms.
+ *
+ */
+int target_run_read_async_algorithm(struct target *target,
+ uint8_t *buffer, uint32_t count, int block_size,
+ int num_mem_params, struct mem_param *mem_params,
+ int num_reg_params, struct reg_param *reg_params,
+ uint32_t buffer_start, uint32_t buffer_size,
+ uint32_t entry_point, uint32_t exit_point,
+ void *arch_info);
+
+/**
* Read @a count items of @a size bytes from the memory of @a target at
* the @a address given.
*
diff --git a/tcl/board/msoftspi_examples.cfg b/tcl/board/msoftspi_examples.cfg
new file mode 100644
index 0000000..83077b4
--- /dev/null
+++ b/tcl/board/msoftspi_examples.cfg
@@ -0,0 +1,119 @@
+--------------------------------------------------------------------------------
+for STM32F746G-disco:
+--------------------------------------------------------------------------------
+
+set _MSOFTSPINAME $_CHIPNAME.msoftspi
+flash bank $_MSOFTSPINAME msoftspi 0x90000000 0 0 0 $_TARGETNAME 0x40020414 6 0x40020414 2 0x40020C10 12 0x40020C14 11
+
+$_TARGETNAME configure -event reset-init {
+ mmw 0x40023830 0x000007FF 0 ;# RCC_AHB1ENR |= GPIOA-GPIOK (enable clocks)
+
+ mww 0x40023C00 0x00000006 ;# 6 WS for 192 MHz HCLK
+ sleep 1
+ mww 0x40023804 0x24003008 ;# 192MHz: PLLM=8, PLLN=192, PLLP=2
+ mww 0x40023808 0x00009400 ;# APB1: /4, APB2: /2
+ mmw 0x40023800 0x01000000 0x00000000 ;# PLL on
+ sleep 1
+ mmw 0x40023808 0x00000002 0x00000000 ;# switch to PLL
+
+ adapter_khz 4000
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PD12: BK1_IO1 (MISO), PD11: BK1_IO0 (MOSI)
+ mmw 0x40020400 0x00001010 0x00002020 ;# PB6, PB2 output
+ mmw 0x40020404 0x00000000 0x00000044 ;# PB6, PB2 push-pull
+ mmw 0x40020408 0x00003030 0x00000000 ;# very high speed
+ mmw 0x4002040C 0x00000000 0x00003030 ;# PB6, PB2 no pull-up
+
+ mmw 0x40020C00 0x00400000 0x03800000 ;# PD13 input, PD12 input, PD11 output
+ mmw 0x40020C04 0x00000000 0x08000000 ;# PD11 push-pull
+ mmw 0x40020C08 0x03C00000 0x00000000 ;# PD12, PD11 very high speed
+ mmw 0x40020C0C 0x05000000 0x0A000000 ;# PD13, PD12 pull-up, PD11 no pull-up
+
+ mmw 0x40021000 0x00000000 0x00000300 ;# PE2 input
+ mmw 0x40021004 0x00000000 0x00000000 ;#
+ mmw 0x40021008 0x00000000 0x00000000 ;# very high speed
+ mmw 0x4002100C 0x05000000 0x0A000000 ;# PE2 pull-up
+}
+
+--------------------------------------------------------------------------------
+for STM32F746G-disco:
+--------------------------------------------------------------------------------
+
+set _MSOFTSPINAME $_CHIPNAME.msoftspi
+flash bank $_MSOFTSPINAME msoftspi 0x90000000 0 0 0 $_TARGETNAME 0x40020414 6 0x40020414 2 0x40020810 10 0x40020814 9
+
+$_TARGETNAME configure -event reset-init {
+ mmw 0x40023830 0x000007FF 0 ;# RCC_AHB1ENR |= GPIOA-GPIOK (enable clocks)
+ mmw 0x40023838 0x00000002 0 ;# RCC_AHB3ENR |= QSPIEN (enable clock)
+ sleep 1
+
+ # if demo firmware is installed, it will switch the SPI flash to QPI mode,
+ # which will remain active until power-cycling
+ # hence QPI mode must be turned off ...
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PC10: BK1_IO1, PC9: BK1_IO0
+ mmw 0x40020400 0x00002020 0x00001010 ;# PB6, PB2 alternate
+ mmw 0x40020408 0x00003030 0x00000000 ;# high speed
+ mmw 0x40020420 0x0A000900 0x05000600 ;# AF10, AF9
+
+ mmw 0x40020800 0x00280000 0x00140000 ;# PC10, PC9 alternate
+ mmw 0x40020808 0x003C0000 0x00000000 ;# high speed
+ mmw 0x40020824 0x00000990 0x00000660 ;# AF9, AF9
+
+ mmw 0x40020C00 0x08000000 0x04000000 ;# PD13 alternate
+ mmw 0x40020C08 0x0C000000 0x00000000 ;# high speed
+ mmw 0x40020C24 0x00900000 0x00600000 ;# AF9
+
+ mmw 0x40021000 0x00000020 0x00000010 ;# PE2 alternate
+ mmw 0x40021008 0x00000030 0x00000000 ;# high speed
+ mmw 0x40021020 0x00000900 0x00000600 ;# AF9
+
+ mww 0xA0001000 0x01500318 ;# QUADSPI_CR: PRESCALER=1, APMS=1, FTHRES=3, SSHIFT=1, TCEN=1
+ mww 0xA0001004 0x00190100 ;# QUADSPI_DCR: FSIZE=0x19, CSHT=0x01, CKMODE=0
+ mmw 0xA0001000 0x00000001 0 ;# QUADSPI_CR: EN=1
+
+ # 1-line spi mode
+ mww 0xA0001014 0x000003F5 ;# QUADSPI_CCR: FMODE=0x0, DMODE=0x0, DCYC=0x0, ADSIZE=0x0, ADMODE=0x0, IMODE=0x3, INSTR=RS
+ sleep 1
+
+ # 4-byte address mode
+ mww 0xA0001014 0x000001B7 ;# QUADSPI_CCR: FMODE=0x0, DMODE=0x0, DCYC=0x0, ADSIZE=0x0, ADMODE=0x0, IMODE=0x1, INSTR=EN
+ sleep 1
+
+ # memory-mapped read mode with 4-byte addresses
+ mww 0xA0001014 0x0D003503 ;# QUADSPI_CCR: FMODE=0x3, DMODE=0x1, DCYC=0x0, ADSIZE=0x3, ADMODE=0x1, IMODE=0x1, INSTR=RE
+
+
+ mww 0x40023C00 0x00000006 ;# 6 WS for 192 MHz HCLK
+ sleep 1
+ mww 0x40023804 0x24003008 ;# 192MHz: PLLM=8, PLLN=192, PLLP=2
+ mww 0x40023808 0x00009400 ;# APB1: /4, APB2: /2
+ mmw 0x40023800 0x01000000 0x00000000 ;# PLL on
+ sleep 1
+ mmw 0x40023808 0x00000002 0x00000000 ;# switch to PLL
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PC10: BK1_IO1 (MISO), PC9: BK1_IO0 (MOSI)
+ mmw 0x40020400 0x00001010 0x00002020 ;# PB6, PB2 output
+ mmw 0x40020404 0x00000000 0x00000044 ;# PB6, PB2 push-pull
+ mmw 0x40020408 0x00003030 0x00000000 ;# very high speed
+ mmw 0x4002040C 0x00000000 0x00003030 ;# PB6, PB2 no pull-up
+
+ mmw 0x40020800 0x00040000 0x00380000 ;# PC10 input, PC9 output
+ mmw 0x40020804 0x00000000 0x00000200 ;# PC9 push-pull
+ mmw 0x40020808 0x000C0000 0x00000000 ;# very high speed
+ mmw 0x4002080C 0x00100000 0x002C0000 ;# PC10 pull-up, PC9 no-pullup
+
+ mmw 0x40020C00 0x00000000 0x0C000000 ;# PD13 input
+ mmw 0x40020C04 0x00000000 0x00000000 ;#
+ mmw 0x40020C08 0x00000000 0x00000000 ;# very high speed
+ mmw 0x40020C0C 0x04000000 0x08000000 ;# PD13 pull-up
+
+ mmw 0x40021000 0x00000000 0x00000030 ;# PE2 input
+ mmw 0x40021004 0x00000000 0x00000000 ;#
+ mmw 0x40021008 0x00000000 0x00000000 ;# very high speed
+ mmw 0x4002100C 0x00000010 0x00000020 ;# PE2 pull-up
+
+ sleep 1
+ msoftspi spicmd 1 0 0xB7 ;# EN4B: switch to 4-byte address mode
+ msoftspi spicmd 1 1 0x15 ;# RDCR: read configuration register
+}
--
Andreas Bolsch (***@gmail.com) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/4152
-- gerrit
commit a3dde711dc584cec47b83235d258b79a54b7a5fd
Author: Andreas Bolsch <***@gmail.com>
Date: Sat May 27 16:32:32 2017 +0200
Generic (bitbanging) driver for SPI flash for Cortex-M
Some Cortex-M controllers include a specialized SPI interface
for external serial flash chips. But most do not, so downloading
firmware employing an external flash require a second procedure
for filling that flash.
This driver overcomes this nuisance by emulating an additional
flash bank which looks like an internal bank.
The SPI flash (or EEPROM) is connected via 4 GPIO pins (NCS, SCLK,
MISO, MOSI) only, no hardware SPI is required, therefore the driver
should work on any Cortex-M controller supporting word addressable
GPIO ports.
Of course, speed is not overwhelming, but on an STM32F746 up to
1.5 MByte/s raw read is possible. However, the limiting factor is
the debug interface and its USB attachement: read/write on
STM32f746-disco via ST-Link up to approx. 150kByte/s.
Set-up is somewhat complicated, as GPIOs must be initialized by a
dedicated script (e. g. in reset init hook, two samples included).
Tested on STM32f746-disco, STM32F769-disco and an STM32F103 board.
Signed-off-by: Andreas Bolsch <***@gmail.com>
Change-Id: I08da60b3a2ed9e156f9eb26859fe13dfd973b873
Signed-off-by: Andreas Bolsch <***@gmail.com>
diff --git a/contrib/loaders/erase_check/msoftspi_erase_check.S b/contrib/loaders/erase_check/msoftspi_erase_check.S
new file mode 100644
index 0000000..bb4a2c1
--- /dev/null
+++ b/contrib/loaders/erase_check/msoftspi_erase_check.S
@@ -0,0 +1,112 @@
+
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/erase_check/msoftspi_erase_check.S > msoftspi_erase_check.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_erase_check.o msoftspi_erase_check.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_erase_check.bin
+ */
+
+/* Params:
+ * r0 - sector count
+ * r1 - flash page size
+ * r2 - unused
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "../flash/msoftspi.inc"
+
+start:
+ mov r8, r0 /* save sector count */
+ adr r0, buffer /* pointer to start of buffer */
+ mov r9, r0 /* save pointer */
+ setup_regs
+sector_start:
+ mov r5, r9 /* get pointer to sector info */
+ ldmia r5!, {r6, r7} /* load address offset and length */
+ mov r12, r6 /* save address offset*/
+ mov r11, r7 /* save sector length */
+start_read:
+ movs r4, #SPIFLASH_READ /* read cmd */
+ send_cmd_addr /* send cmd and address */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+read_loop:
+ bl shift_in_byte /* read byte from flash */
+ movs r7, #0xFF /* fill bits 8-15 */
+ lsls r7, #8 /* with ones */
+ orrs r4, r4, r7 /* copy ones to left of read byte */
+ mov r7, r9 /* pointer to result */
+ ldr r6, [r7, #8] /* get previous result */
+ ands r6, r4 /* and read byte to result */
+ lsls r4, r4, #8 /* shift result into higher byte */
+ orrs r6, r6, r4 /* or read byte to result */
+ str r6, [r7, #8] /* save updated result */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save count */
+ beq sector_end /* stop if sector completed */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ b read_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this sector read */
+ b start_read /* then next page */
+sector_end:
+ bl deselect /* finish this sector read */
+ mov r7, r9 /* pointer to result */
+ mov r6, r11 /* get remaining count */
+ str r6, [r7, #4] /* store remaining (zero) count */
+ adds r7, r7, #12 /* three words */
+ mov r9, r7 /* save update pointer */
+ mov r7, r8 /* get sector count */
+ subs r7, r7, #1 /* decrement count */
+ mov r8, r7 /* save updated count */
+ bne sector_start /* next sector if not finished */
+ b exit
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ .align 2 /* align to word, bkpt is 4 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ buffer, .
diff --git a/contrib/loaders/flash/msoftspi.inc b/contrib/loaders/flash/msoftspi.inc
new file mode 100644
index 0000000..bb66618
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi.inc
@@ -0,0 +1,143 @@
+#include "../../../src/flash/nor/spi.h"
+
+/* set NCS */
+ .macro deselect
+deselect:
+ ldr r6, port_pin_ncs /* NCS port address */
+ ldr r7, [r6] /* load port data */
+ ldr r6, port_pin_ncs+4 /* NCS pin bitmask */
+ orrs r7, r7, r6 /* set NCS bit */
+ ldr r6, port_pin_ncs /* NCS port address */
+ str r7, [r6] /* store new contents */
+ nop /* switch to output */
+ bx lr /* return */
+ .endm
+
+/* entry point, initialize registers */
+ .macro setup_regs
+ subs r0, #1 /* decrement count */
+ mov r11, r0 /* save count */
+ mov r12, r2 /* save address offset */
+ subs r1, #1 /* create page size mask */
+ lsls r3, r3, #30 /* flags into bit 30 and 31 */
+ orrs r1, r1, r3 /* copy flags in page size mask */
+ mov r10, r1 /* save mask and flags */
+ ldr r0, port_pin_sclk /* load SCLK port address */
+ ldr r1, port_pin_sclk+4 /* load SCLK pin bitmask */
+ bl deselect /* for a clean start */
+ .endm
+
+/* send cmd and following 2-, 3- or 4-byte address to flash */
+ .macro send_cmd_addr
+ bl shift_out_byte /* send cmd */
+ mov r7, r10 /* get 3/4-byte flags */
+ lsls r7, r7, #1 /* test for 4-byte address */
+ bcc addr_0_23 /* skip if 3-byte address */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #24 /* addr bits 31-24 */
+ bl shift_out_next /* send addr byte */
+addr_0_23:
+ mov r7, r10 /* get 3/4-byte flags */
+ lsls r7, r7, #2 /* test for 3-byte address */
+ bcc addr_0_15 /* skip if 2-byte address */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #16 /* addr bits 23-16 */
+ bl shift_out_next /* send addr byte */
+addr_0_15:
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #8 /* addr bits 15-8 */
+ bl shift_out_next /* send addr byte */
+ mov r4, r12 /* get address offset */
+ lsrs r4, r4, #0 /* addr bits 7-0 */
+ bl shift_out_next /* send addr byte */
+ .endm
+
+/* shift in one byte from MISO pin
+ * r4: data byte (out)
+ * r5: bit no of MISO pin (lowest byte)
+ * clobbered: r2, r3, r6, r7 */
+ .macro shift_in_byte
+shift_in_byte:
+ movs r4, #1 /* set bit 0 only */
+ lsls r4, r4, #24 /* into bit 24 */
+ movs r7, #0 /* clear temp result */
+ ldr r6, [r0] /* load SCLK port data */
+shift_in_loop:
+ orrs r4, r4, r7 /* insert new bit into result */
+ orrs r6, r6, r1 /* set SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ ldr r7, [r2] /* load MISO port data */
+ ands r7, r7, r3 /* mask all but MISO bit */
+ rors r7, r7, r5 /* shift new bit into bit 0 */
+ bics r6, r6, r1 /* clear SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ lsls r4, r4, #1 /* shift result left one */
+ bcc shift_in_loop /* again if not finished */
+ orrs r4, r4, r7 /* insert last bit into result */
+ bx lr /* return */
+ .endm
+
+/* tail of shift_out_byte, MOSI port address parametrized */
+ .macro shift_tail mosi
+ lsls r4, r4, #1 /* original bit 7 into C */
+shift_out_loop_\@:
+ bics r6, r6, r1 /* clear SCLK bit */
+ sbcs r5, r5, r5 /* fill all bits with ~C */
+ ands r5, r5, r3 /* set/clear bit at MOSI position */
+ str r6, [r0] /* store new SCLK port data */
+ orrs \mosi, \mosi, r3 /* set MOSI bit */
+ bics \mosi, \mosi, r5 /* insert new MOSI bit */
+ str \mosi, [r2] /* store new MOSI port data */
+ orrs r6, r6, r1 /* set SCLK bit */
+ lsls r4, r4, #1 /* shift next bit into C */
+ str r6, [r0] /* store new SCLK port data */
+ bne shift_out_loop_\@ /* again if not finished */
+ str r6, [r0] /* store new SCLK port data */
+ bics r6, r6, r1 /* clear SCLK bit */
+ str r6, [r0] /* store new SCLK port data */
+ bx lr /* return */
+ .endm
+
+/* clear NCS, shift lowest byte of word out via MOSI
+ * depending on whether MOSI and SCLK are located on same port,
+ * algorithm is slightly different
+ *
+ * r4: data word (in)
+ * clobbered: r2, r3, r5, r6, r7 */
+ .macro shift_out_byte
+shift_out_byte:
+ ldr r2, port_pin_ncs /* NCS port address */
+ ldr r3, port_pin_ncs+4 /* NCS pin bitmask */
+ ldr r7, [r2] /* load port data */
+ bics r7, r7, r3 /* clear NCS bit */
+ str r7, [r2] /* store new contents */
+ ldr r2, port_pin_mosi /* MOSI port address */
+ ldr r3, port_pin_mosi+4 /* MOSI pin bitmask */
+shift_out_next:
+ lsls r4, r4, #1 /* insert '1' bit just */
+ adds r4, r4, #1 /* right of data byte */
+ lsls r4, r4, #23 /* original bit 7 into bit 31 */
+ ldr r6, [r0] /* load SCLK port data */
+ bics r6, r6, r1 /* clear SCLK bit */
+ cmp r0, r2 /* SCLK and MOSI on same port? */
+ beq shift_out_eq
+shift_out_ne:
+ ldr r7, [r2] /* load MOSI port data */
+ shift_tail mosi=r7 /* SCLK, MOSI on different ports */
+shift_out_eq:
+ shift_tail mosi=r6 /* SCLk, MOSI on same port */
+ .endm
+
+/* parameter and buffer allocation */
+ .macro param_block
+port_pin_ncs:
+ .space 8 /* port address (output reg) and mask for NCS pin */
+port_pin_sclk:
+ .space 8 /* port address (output reg) and mask for SCLK pin */
+port_pin_miso:
+ .space 8 /* port address (input reg) and mask for MISO pin */
+port_pin_mosi:
+ .space 8 /* port address (output reg) and mask for MOSI pin */
+bits_no:
+ .space 4 /* bit numbers of NCS, SCLK, MISO, MOSI pins */
+ .endm
diff --git a/contrib/loaders/flash/msoftspi_read.S b/contrib/loaders/flash/msoftspi_read.S
new file mode 100644
index 0000000..3f05a14
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi_read.S
@@ -0,0 +1,104 @@
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/flash/msoftspi_read.S > msoftspi_read.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_read.o msoftspi_read.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_read.bin
+ */
+
+/* Params:
+ * r0 - total count (bytes), status (out)
+ * r1 - flash page size
+ * r2 - address offset into flash
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ * r8 - fifo start
+ * r9 - fifo end + 1
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "msoftspi.inc"
+
+start:
+ setup_regs /* initialize registers */
+start_read:
+ movs r4, #SPIFLASH_READ /* read cmd */
+ send_cmd_addr /* send cmd and address */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+read_loop:
+ bl shift_in_byte /* read byte from flash */
+ ldr r6, wp /* get wp */
+ strb r4, [r6] /* write next byte */
+ adds r6, r6, #1 /* increment wp */
+ cmp r6, r9 /* wp beyond end? */
+ blo read_wait /* if no, then ok */
+ mov r6, r8 /* else wrap around */
+read_wait:
+ ldr r7, rp /* get rp */
+ cmp r7, #0 /* if rp equals 0 */
+ beq exit /* then abort */
+ cmp r6, r7 /* check if fifo full */
+ beq read_wait /* wait until not full */
+ adr r7, wp /* get address of wp */
+ str r6, [r7] /* save updated wp */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save count */
+ bmi exit /* stop if no data left */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ b read_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this page read */
+ b start_read /* then next page */
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ bl deselect /* finish this read cmd */
+ mov r0, r11 /* get count */
+ adds r0, r0, #1 /* correct count */
+ mov r2, r12 /* restore offset */
+ .align 2 /* align to word, bkpt is 11 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ wp, . /* wp, uint32_t */
+ .equ rp, wp + 4 /* rp, uint32_t */
+ .equ buffer, rp + 4 /* buffer follows right away */
diff --git a/contrib/loaders/flash/msoftspi_write.S b/contrib/loaders/flash/msoftspi_write.S
new file mode 100644
index 0000000..56bab5e
--- /dev/null
+++ b/contrib/loaders/flash/msoftspi_write.S
@@ -0,0 +1,118 @@
+/***************************************************************************
+ * Copyright (C) 2017 by Andreas Bolsch *
+ * ***@mni.thm.de *
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+ .text
+ .syntax unified
+ .cpu cortex-m0
+ .thumb
+ .thumb_func
+
+/* To assemble:
+ * arm-none-eabi-gcc -Wa,-adlmn contrib/loaders/flash/msoftspi_write.S > msoftspi_write.lst
+ *
+ * To generate binary file:
+ * arm-none-eabi-objcopy -O binary msoftspi_write.o msoftspi_write.bin
+ *
+ * To generate C array definition:
+ * xxd -i msoftspi_write.bin
+ */
+
+/* Params:
+ * r0 - total count (bytes), status (out)
+ * r1 - flash page size
+ * r2 - address offset into flash
+ * r3 - 2/3/4-byte (0x0: 2 byte, 0x1: 3 byte, 0x3: 4-byte)
+ * r8 - fifo start
+ * r9 - fifo end + 1
+ *
+ * Clobbered:
+ * r4 - r7, r10 - r12 tmp */
+
+#include "msoftspi.inc"
+
+start:
+ setup_regs /* initialize registers */
+wip_loop:
+ movs r4, #SPIFLASH_READ_STATUS /* read status reg cmd */
+ bl shift_out_byte /* send command */
+ nop /* switch to input */
+ ldr r2, port_pin_miso /* MISO port address */
+ ldr r3, port_pin_miso+4 /* MISO pin bitmask */
+ ldr r5, bits_no /* get bit numbers */
+ lsrs r5, r5, #8 /* MISO bit no. in lowest byte */
+ bl shift_in_byte /* read status byte */
+ bl deselect /* end cmd */
+ lsrs r4, r4, #(SPIFLASH_BSY+1) /* if flash busy, */
+ bcs wip_loop /* then poll again */
+ mov r7, r11 /* get residual count */
+ tst r7, r7 /* test residual count */
+ bpl start_write /* if negative, then finished */
+ b exit
+start_write:
+ movs r4, #SPIFLASH_WRITE_ENABLE /* write enable cmd */
+ bl shift_out_byte /* send cmd */
+ bl deselect /* end cmd */
+ movs r4, #SPIFLASH_PAGE_PROGRAM /* page program cmd */
+ send_cmd_addr /* send cmd and address */
+write_loop:
+ ldr r7, wp /* get wp */
+ cmp r7, #0 /* if wp equals 0 */
+ beq exit /* then abort */
+ ldr r6, rp /* get rp */
+ cmp r6, r7 /* check if fifo empty */
+ beq write_loop /* wait till not empty */
+ ldrb r4, [r6, #0] /* read next byte */
+ bl shift_out_next /* send byte to flash */
+ ldr r6, rp /* get rp */
+ adds r6, r6, #1 /* increment internal rp */
+ cmp r6, r9 /* internal rp beyond end? */
+ blo write_loop1 /* if no, then ok */
+ mov r6, r8 /* else wrap around */
+write_loop1:
+ adr r7, rp /* get address of rp */
+ str r6, [r7] /* save updated rp */
+ mov r7, r12 /* get address offset */
+ adds r7, r7, #1 /* increment address */
+ mov r12, r7 /* save address offset */
+ mov r6, r11 /* get count */
+ subs r6, r6, #1 /* decrement count */
+ mov r11, r6 /* save updated count */
+ bmi page_end /* stop if no data left */
+ mov r6, r10 /* get page size mask */
+ tst r6, r7 /* page end ? */
+ bne write_loop /* if not, then next byte */
+page_end:
+ bl deselect /* finish this page write */
+ b wip_loop /* then next page */
+
+ deselect
+ shift_in_byte
+ shift_out_byte
+
+exit:
+ mov r0, r11 /* get count */
+ adds r0, r0, #1 /* correct count */
+ mov r1, r12 /* get address offset */
+ .align 2 /* align to word, bkpt is 11 words */
+ bkpt #0 /* before code end for exit_point */
+ .align 2 /* align to word */
+
+ param_block
+ .equ wp, . /* wp, uint32_t */
+ .equ rp, wp + 4 /* rp, uint32_t */
+ .equ buffer, rp + 4 /* buffer follows right away */
diff --git a/doc/openocd.texi b/doc/openocd.texi
index 5ebfaac..ee58474 100644
--- a/doc/openocd.texi
+++ b/doc/openocd.texi
@@ -5073,6 +5073,64 @@ flash bank flash2 ath79 0x20000000 0 0 0 $_TARGETNAME cs2
@end deffn
+@deffn {Flash Driver} msoftspi
+@cindex Generic Cortex-M Bitbanging SPI Interface
+@cindex msoftspi
+
+This driver supports common serial SPI flash chips for any little-endian
+ARM-Cortex-M CPU connected via GPIO pins with memory mapped and word accessible
+output and input registers. No special bit set / clear facility is required.
+
+Setup requires the @var{base} parameter in order to identify
+the memory bank and four @var{*_addr}, @var{*_bit} pairs specifying
+word address and bit number of the GPIO ports of NCS (out), SCLK (out),
+MISO (in), MOSI pins (out). Other parameters are ignored:
+
+base 0 0 0 $_TARGETNAME ncs_addr ncs_bit sclk_addr sclk_bit miso_addr miso_bit mosi_addr mosi_bit
+
+@var{base} is used in write_image etc. to map a certain part of the CPU address space
+to this flash, but is arbitrary otherwise. Chip is autodetected via SPI command and
+matched against hardcoded list of types.
+
+The driver automatically uses 2-, 3-, or 4-byte addresses according to capacity.
+However, for most flash chips 4-byte address mode must be enabled separately,
+e. g. by "Enter 4-Byte Mode" command or setting a special control bit.
+
+GPIO clocks must be enabled, pins configured as GPIO output or input pins,
+respectively, in e. g. reset handler. If flash is connected with four I/O lines
+for QPI mode, the lines not used in SPI mode (HOLD, WP) must be taken into
+account, too. Non-volatile settings like QPI mode or block protection have
+to be disabled.
+
+SPI cycle time depends on CPU and peripheral clocks. Bitbanging might seem
+to be quite slow, but page programming time and data transfer via debug
+interface largely dominate anyway.
+
+Example for STM32F746G discovery board (PB6, PB2, PD12, PD11):
+
+@example
+flash bank_id $_FLASHNAME msoftspi 0x90000000 0 0 0 $_TARGETNAME
+ 0x40020414 6 0x40020414 2 0x40020C10 12 0x40020C14 11
+@end example
+
+There are three specific commands
+@deffn Command {msoftspi mass_erase} bank_id
+Performs a mass erase.
+@end deffn
+
+@deffn Command {msoftspi setid} bank_id name total_size page_size mass_erase_cmd sector_size sector_erase_cmd
+Set flash parameters: @var{name} human readable string, @var{total_size} size in bytes,
+@var{page_size} is write page size. @var{mass_erase_cmd}, @var{sector_size} and @var{sector_erase_cmd}
+are optional. Required if chip id not hardcoded yet and e. g. for EEPROMs which don't support an id
+command at all.
+@end deffn
+
+@deffn Command {msoftspi spicmd} bank_id resp_num cmd_byte ...
+Sends command @var{cmd_byte} and following bytes and reads @var{resp_num} bytes afterwards.
+@end deffn
+
+@end deffn
+
@subsection Internal Flash (Microcontrollers)
@deffn {Flash Driver} aduc702x
diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am
index 5179a7c..978b532 100644
--- a/src/flash/nor/Makefile.am
+++ b/src/flash/nor/Makefile.am
@@ -34,6 +34,7 @@ NOR_DRIVERS = \
%D%/lpcspifi.c \
%D%/mdr.c \
%D%/mrvlqspi.c \
+ %D%/msoftspi.c \
%D%/niietcm4.c \
%D%/non_cfi.c \
%D%/nrf51.c \
diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c
index 5502ce5..f8d505c 100644
--- a/src/flash/nor/drivers.c
+++ b/src/flash/nor/drivers.c
@@ -47,6 +47,7 @@ extern struct flash_driver lpc2900_flash;
extern struct flash_driver lpcspifi_flash;
extern struct flash_driver mdr_flash;
extern struct flash_driver mrvlqspi_flash;
+extern struct flash_driver msoftspi_flash;
extern struct flash_driver niietcm4_flash;
extern struct flash_driver nrf51_flash;
extern struct flash_driver numicro_flash;
@@ -101,6 +102,7 @@ static struct flash_driver *flash_drivers[] = {
&lpcspifi_flash,
&mdr_flash,
&mrvlqspi_flash,
+ &msoftspi_flash,
&niietcm4_flash,
&nrf51_flash,
&numicro_flash,
diff --git a/src/flash/nor/msoftspi.c b/src/flash/nor/msoftspi.c
new file mode 100644
index 0000000..7fac25f
--- /dev/null
+++ b/src/flash/nor/msoftspi.c
@@ -0,0 +1,1429 @@
+/***************************************************************************
+ * Copyright (C) 2010 by Antonio Borneo <***@gmail.com>, *
+ * 2017 by Andreas Bolsch <***@mni.thm.de *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ * This program is distributed in the hope that it will be useful, *
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of *
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
+ * GNU General Public License for more details. *
+ * *
+ * You should have received a copy of the GNU General Public License *
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. *
+ ***************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include "imp.h"
+#include "spi.h"
+#include <helper/time_support.h>
+#include <target/algorithm.h>
+#include <target/armv7m.h>
+
+#define CLR_PORT_BIT(data, pin) \
+{ \
+ (data) &= ~(msoftspi_info->pin.mask); \
+ retval = target_write_u32(target, msoftspi_info->pin.addr, data); \
+}
+
+#define SET_PORT_BIT(data, pin) \
+{ \
+ (data) |= (msoftspi_info->pin.mask); \
+ retval = target_write_u32(target, msoftspi_info->pin.addr, data); \
+}
+
+/* bit 1: address byte 4 with bits 24-31 required
+ * bit 0: address byte 3 with bits 16-23 required */
+#define ADDR_BYTES \
+ (((msoftspi_info->dev.size_in_bytes > (1<<24)) ? 0x2 : 0x00) | \
+ ((msoftspi_info->dev.size_in_bytes > (1<<16)) ? 0x1 : 0x00))
+
+/* convert uint32_t into 4 uint8_t in target (i. e. little endian)
+ * byte order, re-inventing the wheel ... */
+static inline uint32_t h_to_le_32(uint32_t val)
+{
+ union {
+ uint32_t word;
+ uint8_t byte[sizeof(uint32_t)];
+ } res;
+
+ res.byte[0] = val & 0xFF;
+ res.byte[1] = (val>>8) & 0xFF;
+ res.byte[2] = (val>>16) & 0xFF;
+ res.byte[3] = (val>>24) & 0xFF;
+
+ return res.word;
+}
+
+/* timeout in ms */
+#define MSOFTSPI_CMD_TIMEOUT (100)
+#define MSOFTSPI_PROBE_TIMEOUT (100)
+#define MSOFTSPI_MAX_TIMEOUT (2000)
+#define MSOFTSPI_MASS_ERASE_TIMEOUT (400000)
+
+typedef struct {
+ uint32_t addr;
+ uint32_t mask;
+} port_pin;
+
+struct msoftspi_flash_bank {
+ int probed;
+ uint32_t bank_num;
+ char devname[32];
+ struct flash_device dev;
+ port_pin ncs;
+ port_pin sclk;
+ port_pin miso;
+ port_pin mosi;
+ uint32_t bits_no;
+};
+
+struct sector_info {
+ uint32_t offset;
+ uint32_t size;
+ uint32_t result;
+};
+
+FLASH_BANK_COMMAND_HANDLER(msoftspi_flash_bank_command)
+{
+ struct msoftspi_flash_bank *msoftspi_info;
+ uint8_t bit_no;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 14)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ msoftspi_info = malloc(sizeof(struct msoftspi_flash_bank));
+ if (msoftspi_info == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ memset(&msoftspi_info->dev, 0, sizeof(msoftspi_info->dev));
+ bank->driver_priv = msoftspi_info;
+ msoftspi_info->probed = 0;
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[6], msoftspi_info->ncs.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[7], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<24)) | (bit_no<<24));
+ msoftspi_info->ncs.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: NCS bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[8], msoftspi_info->sclk.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[9], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<16)) | (bit_no<<16));
+ msoftspi_info->sclk.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: SCLK bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[10], msoftspi_info->miso.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[11], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<8)) | (bit_no<<8));
+ msoftspi_info->miso.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: MISO bit number in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[12], msoftspi_info->mosi.addr);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[13], bit_no);
+ if (bit_no < 32) {
+ msoftspi_info->bits_no = ((msoftspi_info->bits_no & ~(0xFF<<0)) | (bit_no<<0));
+ msoftspi_info->mosi.mask = 1<<bit_no;
+ } else {
+ command_print(CMD_CTX, "msoftspi: MOSI bit number must be in 0 ... 31");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ LOG_DEBUG("NCS (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->ncs.addr, msoftspi_info->ncs.mask);
+ LOG_DEBUG("SCLK (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->sclk.addr, msoftspi_info->sclk.mask);
+ LOG_DEBUG("MISO (in): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->miso.addr, msoftspi_info->miso.mask);
+ LOG_DEBUG("MOSI (out): 0x%" PRIx32 ", 0x%" PRIx32, msoftspi_info->mosi.addr, msoftspi_info->mosi.mask);
+
+ if (msoftspi_info->sclk.addr == msoftspi_info->mosi.addr)
+ LOG_INFO("SCLK and MOSI located on same port - should work anyway");
+ return ERROR_OK;
+}
+
+/* Send and receive one byte via SPI */
+/* bits 7 down to 0 are shifted out, MSB first */
+static int msoftspi_shift_out(struct flash_bank *bank, uint32_t word)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port_sclk, port_mosi;
+ int k, retval;
+
+ LOG_DEBUG("0x%08" PRIx32, word);
+
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_mosi);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift bit 7 into bit 31 */
+ word <<= 24;
+
+ for (k = 0; k < 8; k++) {
+ /* shift out data bit, msb first */
+ if (word & (1<<31))
+ SET_PORT_BIT(port_mosi, mosi)
+ else
+ CLR_PORT_BIT(port_mosi, mosi);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* set SCLK */
+ if (msoftspi_info->sclk.addr == msoftspi_info->mosi.addr)
+ port_sclk = port_mosi;
+ SET_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift next bit into bit 31 */
+ word <<= 1;
+
+ /* clear SCLK */
+ CLR_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ return ERROR_OK;
+}
+
+static int msoftspi_shift_in(struct flash_bank *bank, uint32_t *word)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port_sclk, port_miso;
+ int k, retval;
+
+ retval = target_read_u32(target, msoftspi_info->mosi.addr, &port_sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ for (k = 0; k < 8; k++) {
+ /* set SCLK */
+ SET_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* shift in data bit, msb first */
+ retval = target_read_u32(target, msoftspi_info->miso.addr, &port_miso);
+ if (retval != ERROR_OK)
+ return retval;
+
+ *word <<= 1;
+ (port_miso & msoftspi_info->miso.mask) && (*word |= 0x1);
+
+ /* clear SCLK */
+ CLR_PORT_BIT(port_sclk, sclk);
+ if (retval != ERROR_OK)
+ return retval;
+ }
+
+ LOG_DEBUG("0x%08" PRIx32, *word);
+ return ERROR_OK;
+}
+
+/* Read the status register of the external SPI flash chip. */
+static int read_status_reg(struct flash_bank *bank, uint32_t *status)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port;
+ int retval, success;
+
+ success = ERROR_FAIL;
+
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* clear NCS */
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, SPIFLASH_READ_STATUS);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* set to input */
+
+ /* get result byte */
+ success = msoftspi_shift_in(bank, status);
+ *status &= 0xFF;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ return success;
+}
+
+/* Check for WIP (write in progress) bit in status register */
+/* timeout in ms */
+static int wait_till_ready(struct flash_bank *bank, int timeout)
+{
+ uint32_t status;
+ int retval;
+ long long endtime;
+
+ endtime = timeval_ms() + timeout;
+ do {
+ /* Read flash status register */
+ retval = read_status_reg(bank, &status);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if ((status & SPIFLASH_BSY_BIT) == 0)
+ return ERROR_OK;
+ alive_sleep(25);
+ } while (timeval_ms() < endtime);
+
+ LOG_ERROR("timeout");
+ return ERROR_FAIL;
+}
+
+/* Send "write enable" command to SPI flash chip */
+static int msoftspi_write_enable(struct flash_bank *bank)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port, status;
+ int retval;
+
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send write enable command */
+ retval = msoftspi_shift_out(bank, SPIFLASH_WRITE_ENABLE);
+
+err:
+ /* set NCS */
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Read flash status register */
+ retval = read_status_reg(bank, &status);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check write enabled */
+ if ((status & SPIFLASH_WE_BIT) == 0) {
+ LOG_ERROR("Cannot enable write to flash. Status=0x%08" PRIx32, status);
+ return ERROR_FAIL;
+ }
+
+ return ERROR_OK;
+}
+
+/* Erase a single sector */
+static int msoftspi_erase_sector(struct flash_bank *bank, int sector)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t addr = bank->sectors[sector].offset;
+ uint32_t port, data;
+ int retval;
+
+ if (msoftspi_info->dev.erase_cmd == 0x00) {
+ LOG_ERROR("No sector erase available");
+ return ERROR_FAIL;
+ }
+
+ retval = msoftspi_write_enable(bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ } else
+ bank->sectors[sector].is_erased = -1;
+
+ /* Send Sector Erase command */
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, msoftspi_info->dev.erase_cmd);
+ if (retval != ERROR_OK)
+ goto err;
+
+ if (ADDR_BYTES & 0x2) {
+ /* bits 24-31 */
+ retval = msoftspi_shift_out(bank, addr >> 24);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ if (ADDR_BYTES & 0x1) {
+ /* bits 16-23 */
+ retval = msoftspi_shift_out(bank, addr >> 16);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ /* bits 8-15 */
+ retval = msoftspi_shift_out(bank, addr >> 8);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* bits 0-7 */
+ retval = msoftspi_shift_out(bank, addr >> 0);
+ if (retval != ERROR_OK)
+ goto err;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* read flash status register */
+ retval = read_status_reg(bank, &data);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check for command in progress for flash */
+ if ((data & SPIFLASH_WE_BIT) == 0) {
+ LOG_DEBUG("Sector erase not accepted by flash or already completed. Status=0x%08" PRIx32, data);
+ /* return ERROR_FAIL; */
+ }
+
+ /* poll WIP for end of self timed Sector Erase cycle */
+ retval = wait_till_ready(bank, MSOFTSPI_MAX_TIMEOUT);
+
+ /* erasure takes a long time, so some sort of progress message is a good idea */
+ LOG_DEBUG("sector %4d erased", sector);
+
+ return retval;
+}
+
+/* Erase range of sectors */
+static int msoftspi_erase(struct flash_bank *bank, int first, int last)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ int retval = ERROR_OK;
+ int sector;
+
+ LOG_DEBUG("%s: from sector %d to sector %d", __func__, first, last);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if ((first < 0) || (last < first) || (last >= bank->num_sectors)) {
+ LOG_ERROR("Flash sector invalid");
+ return ERROR_FLASH_SECTOR_INVALID;
+ }
+
+ for (sector = first; sector <= last; sector++) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ }
+ }
+
+ for (sector = first; sector <= last; sector++) {
+ retval = msoftspi_erase_sector(bank, sector);
+ if (retval != ERROR_OK)
+ break;
+ keep_alive();
+ }
+
+ if (retval != ERROR_OK)
+ LOG_ERROR("Flash sector_erase failed on sector %d", sector);
+
+ return retval;
+}
+
+/* Check whether flash is blank */
+static int msoftspi_blank_check(struct flash_bank *bank)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct duration bench;
+ struct reg_param reg_params[3];
+ struct armv7m_algorithm armv7m_info;
+ struct working_area *erase_check_algorithm;
+ struct sector_info erase_check_info;
+ uint32_t buffer_size, exit_point, result;
+ int num_sectors, sector, index, count, retval;
+ const uint32_t erased = 0x00FF;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ /* see contrib/loaders/erase_check/msoftspi_erase_check.S for src */
+ static const uint8_t msoftspi_erase_check_code[] = {
+ 0x80, 0x46, 0x57, 0xa0, 0x81, 0x46, 0x01, 0x38, 0x83, 0x46, 0x94, 0x46,
+ 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43, 0x8a, 0x46, 0x4b, 0x48, 0x4c, 0x49,
+ 0x00, 0xf0, 0x46, 0xf8, 0x4d, 0x46, 0xc0, 0xcd, 0xb4, 0x46, 0xbb, 0x46,
+ 0x03, 0x24, 0x00, 0xf0, 0x57, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3,
+ 0x64, 0x46, 0x24, 0x0e, 0x00, 0xf0, 0x57, 0xf8, 0x57, 0x46, 0xbf, 0x00,
+ 0x03, 0xd3, 0x64, 0x46, 0x24, 0x0c, 0x00, 0xf0, 0x50, 0xf8, 0x64, 0x46,
+ 0x24, 0x0a, 0x00, 0xf0, 0x4c, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0,
+ 0x48, 0xf8, 0xc0, 0x46, 0x3c, 0x4a, 0x3d, 0x4b, 0x3f, 0x4d, 0x2d, 0x0a,
+ 0x00, 0xf0, 0x2a, 0xf8, 0xff, 0x27, 0x3f, 0x02, 0x3c, 0x43, 0x4f, 0x46,
+ 0xbe, 0x68, 0x26, 0x40, 0x24, 0x02, 0x26, 0x43, 0xbe, 0x60, 0x67, 0x46,
+ 0x01, 0x37, 0xbc, 0x46, 0x5e, 0x46, 0x01, 0x3e, 0xb3, 0x46, 0x05, 0xd0,
+ 0x56, 0x46, 0x3e, 0x42, 0xea, 0xe7, 0x00, 0xf0, 0x0d, 0xf8, 0xc9, 0xe7,
+ 0x00, 0xf0, 0x0a, 0xf8, 0x4f, 0x46, 0x5e, 0x46, 0x7e, 0x60, 0x0c, 0x37,
+ 0xb9, 0x46, 0x47, 0x46, 0x01, 0x3f, 0xb8, 0x46, 0xba, 0xd1, 0x46, 0xe0,
+ 0x24, 0x4e, 0x37, 0x68, 0x24, 0x4e, 0x37, 0x43, 0x22, 0x4e, 0x37, 0x60,
+ 0xc0, 0x46, 0x70, 0x47, 0x01, 0x24, 0x24, 0x06, 0x00, 0x27, 0x06, 0x68,
+ 0x3c, 0x43, 0x0e, 0x43, 0x06, 0x60, 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41,
+ 0x8e, 0x43, 0x06, 0x60, 0x64, 0x00, 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47,
+ 0x18, 0x4a, 0x19, 0x4b, 0x17, 0x68, 0x9f, 0x43, 0x17, 0x60, 0x1c, 0x4a,
+ 0x1c, 0x4b, 0x64, 0x00, 0x01, 0x34, 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43,
+ 0x90, 0x42, 0x10, 0xd0, 0x17, 0x68, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41,
+ 0x1d, 0x40, 0x06, 0x60, 0x1f, 0x43, 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43,
+ 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60,
+ 0x70, 0x47, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60,
+ 0x1e, 0x43, 0xae, 0x43, 0x16, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60,
+ 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0xc0, 0x46,
+ 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* this will overlay the last 9 words of msoftspi_*_code in target */
+ uint32_t port_buffer[] = {
+ h_to_le_32(msoftspi_info->ncs.addr), h_to_le_32(msoftspi_info->ncs.mask),
+ h_to_le_32(msoftspi_info->sclk.addr), h_to_le_32(msoftspi_info->sclk.mask),
+ h_to_le_32(msoftspi_info->miso.addr), h_to_le_32(msoftspi_info->miso.mask),
+ h_to_le_32(msoftspi_info->mosi.addr), h_to_le_32(msoftspi_info->mosi.mask),
+ h_to_le_32(msoftspi_info->bits_no)
+ };
+
+ num_sectors = bank->num_sectors;
+ while (buffer_size = sizeof(msoftspi_erase_check_code) + num_sectors * sizeof(erase_check_info),
+ target_alloc_working_area_try(target, buffer_size, &erase_check_algorithm) != ERROR_OK) {
+ num_sectors /= 2;
+ if (num_sectors <= 2) {
+ LOG_WARNING("not enough working area, can't do SPI blank check");
+ return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+ }
+ }
+
+ /* prepare check code, excluding port_buffer */
+ retval = target_write_buffer(target, erase_check_algorithm->address,
+ sizeof(msoftspi_erase_check_code) - sizeof(port_buffer), msoftspi_erase_check_code);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* prepare port_buffer values */
+ retval = target_write_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) - sizeof(port_buffer),
+ sizeof(port_buffer), (uint8_t *) port_buffer);
+ if (retval != ERROR_OK)
+ goto err;
+
+ duration_start(&bench);
+
+ /* after breakpoint instruction (halfword) one nop (halfword) and
+ * port_buffer till end of code */
+ exit_point = erase_check_algorithm->address + sizeof(msoftspi_erase_check_code)
+ - sizeof(uint32_t) - sizeof(port_buffer);
+
+ init_reg_param(®_params[0], "r0", 32, PARAM_OUT); /* sector count */
+ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* flash page_size */
+ init_reg_param(®_params[2], "r3", 32, PARAM_OUT); /* 2/3/4-byte address mode */
+
+ sector = 0;
+ while (sector < bank->num_sectors) {
+ /* at most num_sectors sectors to handle in one run */
+ count = bank->num_sectors - sector;
+ if (count > num_sectors)
+ count = num_sectors;
+
+ for (index = 0; index < count; index++) {
+ erase_check_info.offset = h_to_le_32(bank->sectors[sector + index].offset);
+ erase_check_info.size = h_to_le_32(bank->sectors[sector + index].size);
+ erase_check_info.result = h_to_le_32(erased);
+
+ retval = target_write_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) + index * sizeof(erase_check_info),
+ sizeof(erase_check_info), (uint8_t *) &erase_check_info);
+ if (retval != ERROR_OK)
+ goto err;
+ }
+
+ buf_set_u32(reg_params[0].value, 0, 32, count);
+ buf_set_u32(reg_params[1].value, 0, 32, msoftspi_info->dev.pagesize);
+ buf_set_u32(reg_params[2].value, 0, 32, ADDR_BYTES);
+
+ armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
+ armv7m_info.core_mode = ARM_MODE_THREAD;
+
+ LOG_DEBUG("checking sectors %d to %d", sector, sector + count - 1);
+ /* check a block of sectors */
+ retval = target_run_algorithm(target,
+ 0, NULL,
+ 3, reg_params,
+ erase_check_algorithm->address, exit_point,
+ count * MSOFTSPI_MAX_TIMEOUT,
+ &armv7m_info);
+ if (retval != ERROR_OK)
+ break;
+
+ for (index = 0; index < count; index++) {
+ retval = target_read_buffer(target, erase_check_algorithm->address
+ + sizeof(msoftspi_erase_check_code) + index * sizeof(erase_check_info),
+ sizeof(erase_check_info), (uint8_t *) &erase_check_info);
+ if (retval != ERROR_OK)
+ goto err;
+
+ if ((erase_check_info.offset != h_to_le_32(bank->sectors[sector + index].offset)) ||
+ (erase_check_info.size != 0)) {
+ LOG_ERROR("corrupted blank check info");
+ goto err;
+ }
+
+ result = h_to_le_32(erase_check_info.result);
+ bank->sectors[sector + index].is_erased = ((result & 0xFF) == 0xFF);
+ LOG_DEBUG("Flash sector %d checked: %04x", sector + index, result & 0xFFFF);
+ }
+ keep_alive();
+ sector += count;
+ }
+
+ destroy_reg_param(®_params[0]);
+ destroy_reg_param(®_params[1]);
+ destroy_reg_param(®_params[2]);
+
+ duration_measure(&bench);
+ LOG_INFO("msoftspi blank checked in"
+ " %fs (%0.3f KiB/s)", duration_elapsed(&bench),
+ duration_kbps(&bench, bank->size));
+
+err:
+ target_free_working_area(target, erase_check_algorithm);
+
+ return retval;
+}
+
+static int msoftspi_protect(struct flash_bank *bank, int set,
+ int first, int last)
+{
+ int sector;
+
+ for (sector = first; sector <= last; sector++)
+ bank->sectors[sector].is_protected = set;
+ return ERROR_OK;
+}
+
+/* Read a block of data from flash or write a block of data to flash */
+static int msoftspi_read_write_block(struct flash_bank *bank, uint8_t *buffer,
+ uint32_t offset, uint32_t count, int write)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct reg_param reg_params[6];
+ struct armv7m_algorithm armv7m_info;
+ struct working_area *write_algorithm;
+ static const uint8_t *code;
+ uint32_t page_size, fifo_start, fifo_size, buffer_size;
+ uint32_t exit_point, remaining;
+ int code_size, retval = ERROR_OK;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " len=0x%08" PRIx32,
+ __func__, offset, count);
+
+ /* see contrib/loaders/flash/msoftspi_read.S for src */
+ static const uint8_t msoftspi_read_code[] = {
+ 0x01, 0x38, 0x83, 0x46, 0x94, 0x46, 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43,
+ 0x8a, 0x46, 0x48, 0x48, 0x48, 0x49, 0x00, 0xf0, 0x3a, 0xf8, 0x03, 0x24,
+ 0x00, 0xf0, 0x4f, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3, 0x64, 0x46,
+ 0x24, 0x0e, 0x00, 0xf0, 0x4f, 0xf8, 0x57, 0x46, 0xbf, 0x00, 0x03, 0xd3,
+ 0x64, 0x46, 0x24, 0x0c, 0x00, 0xf0, 0x48, 0xf8, 0x64, 0x46, 0x24, 0x0a,
+ 0x00, 0xf0, 0x44, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0, 0x40, 0xf8,
+ 0xc0, 0x46, 0x3b, 0x4a, 0x3b, 0x4b, 0x3e, 0x4d, 0x2d, 0x0a, 0x00, 0xf0,
+ 0x22, 0xf8, 0x3d, 0x4e, 0x34, 0x70, 0x01, 0x36, 0x4e, 0x45, 0x00, 0xd3,
+ 0x46, 0x46, 0x3b, 0x4f, 0x00, 0x2f, 0x57, 0xd0, 0xbe, 0x42, 0xfa, 0xd0,
+ 0x37, 0xa7, 0x3e, 0x60, 0x67, 0x46, 0x01, 0x37, 0xbc, 0x46, 0x5e, 0x46,
+ 0x01, 0x3e, 0xb3, 0x46, 0x4c, 0xd4, 0x56, 0x46, 0x3e, 0x42, 0xe6, 0xe7,
+ 0x00, 0xf0, 0x01, 0xf8, 0xc5, 0xe7, 0x27, 0x4e, 0x37, 0x68, 0x27, 0x4e,
+ 0x37, 0x43, 0x25, 0x4e, 0x37, 0x60, 0xc0, 0x46, 0x70, 0x47, 0x01, 0x24,
+ 0x24, 0x06, 0x00, 0x27, 0x06, 0x68, 0x3c, 0x43, 0x0e, 0x43, 0x06, 0x60,
+ 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41, 0x8e, 0x43, 0x06, 0x60, 0x64, 0x00,
+ 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47, 0x1b, 0x4a, 0x1b, 0x4b, 0x17, 0x68,
+ 0x9f, 0x43, 0x17, 0x60, 0x1e, 0x4a, 0x1f, 0x4b, 0x64, 0x00, 0x01, 0x34,
+ 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43, 0x90, 0x42, 0x10, 0xd0, 0x17, 0x68,
+ 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1f, 0x43,
+ 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1,
+ 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x64, 0x00, 0x8e, 0x43,
+ 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1e, 0x43, 0xae, 0x43, 0x16, 0x60,
+ 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43,
+ 0x06, 0x60, 0x70, 0x47, 0xff, 0xf7, 0xb7, 0xff, 0x58, 0x46, 0x01, 0x30,
+ 0x62, 0x46, 0xc0, 0x46, 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ /* see contrib/loaders/flash/msoftspi_write.S for src */
+ static const uint8_t msoftspi_write_code[] = {
+ 0x01, 0x38, 0x83, 0x46, 0x94, 0x46, 0x01, 0x39, 0x9b, 0x07, 0x19, 0x43,
+ 0x8a, 0x46, 0x50, 0x48, 0x50, 0x49, 0x00, 0xf0, 0x4d, 0xf8, 0x05, 0x24,
+ 0x00, 0xf0, 0x62, 0xf8, 0xc0, 0x46, 0x4e, 0x4a, 0x4e, 0x4b, 0x51, 0x4d,
+ 0x2d, 0x0a, 0x00, 0xf0, 0x4b, 0xf8, 0x00, 0xf0, 0x41, 0xf8, 0x64, 0x08,
+ 0xf1, 0xd2, 0x5f, 0x46, 0x3f, 0x42, 0x00, 0xd5, 0x81, 0xe0, 0x06, 0x24,
+ 0x00, 0xf0, 0x50, 0xf8, 0x00, 0xf0, 0x36, 0xf8, 0x02, 0x24, 0x00, 0xf0,
+ 0x4b, 0xf8, 0x57, 0x46, 0x7f, 0x00, 0x03, 0xd3, 0x64, 0x46, 0x24, 0x0e,
+ 0x00, 0xf0, 0x4b, 0xf8, 0x57, 0x46, 0xbf, 0x00, 0x03, 0xd3, 0x64, 0x46,
+ 0x24, 0x0c, 0x00, 0xf0, 0x44, 0xf8, 0x64, 0x46, 0x24, 0x0a, 0x00, 0xf0,
+ 0x40, 0xf8, 0x64, 0x46, 0x24, 0x00, 0x00, 0xf0, 0x3c, 0xf8, 0x3d, 0x4f,
+ 0x00, 0x2f, 0x60, 0xd0, 0x3c, 0x4e, 0xbe, 0x42, 0xf9, 0xd0, 0x34, 0x78,
+ 0x00, 0xf0, 0x33, 0xf8, 0x39, 0x4e, 0x01, 0x36, 0x4e, 0x45, 0x00, 0xd3,
+ 0x46, 0x46, 0x37, 0xa7, 0x3e, 0x60, 0x67, 0x46, 0x01, 0x37, 0xbc, 0x46,
+ 0x5e, 0x46, 0x01, 0x3e, 0xb3, 0x46, 0x02, 0xd4, 0x56, 0x46, 0x3e, 0x42,
+ 0xe5, 0xd1, 0x00, 0xf0, 0x01, 0xf8, 0xb2, 0xe7, 0x25, 0x4e, 0x37, 0x68,
+ 0x25, 0x4e, 0x37, 0x43, 0x23, 0x4e, 0x37, 0x60, 0xc0, 0x46, 0x70, 0x47,
+ 0x01, 0x24, 0x24, 0x06, 0x00, 0x27, 0x06, 0x68, 0x3c, 0x43, 0x0e, 0x43,
+ 0x06, 0x60, 0x17, 0x68, 0x1f, 0x40, 0xef, 0x41, 0x8e, 0x43, 0x06, 0x60,
+ 0x64, 0x00, 0xf5, 0xd3, 0x3c, 0x43, 0x70, 0x47, 0x19, 0x4a, 0x1a, 0x4b,
+ 0x17, 0x68, 0x9f, 0x43, 0x17, 0x60, 0x1d, 0x4a, 0x1d, 0x4b, 0x64, 0x00,
+ 0x01, 0x34, 0xe4, 0x05, 0x06, 0x68, 0x8e, 0x43, 0x90, 0x42, 0x10, 0xd0,
+ 0x17, 0x68, 0x64, 0x00, 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60,
+ 0x1f, 0x43, 0xaf, 0x43, 0x17, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60,
+ 0xf4, 0xd1, 0x06, 0x60, 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x64, 0x00,
+ 0x8e, 0x43, 0xad, 0x41, 0x1d, 0x40, 0x06, 0x60, 0x1e, 0x43, 0xae, 0x43,
+ 0x16, 0x60, 0x0e, 0x43, 0x64, 0x00, 0x06, 0x60, 0xf4, 0xd1, 0x06, 0x60,
+ 0x8e, 0x43, 0x06, 0x60, 0x70, 0x47, 0x58, 0x46, 0x01, 0x30, 0x61, 0x46,
+ 0x00, 0xbe, 0xc0, 0x46, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00
+ };
+
+ code = write ? msoftspi_write_code : msoftspi_read_code;
+ code_size = write ? sizeof(msoftspi_write_code) : sizeof(msoftspi_read_code);
+
+ /* this will overlay the last 9 words of msoftspi_*_code in target */
+ uint32_t port_buffer[] = {
+ h_to_le_32(msoftspi_info->ncs.addr), h_to_le_32(msoftspi_info->ncs.mask),
+ h_to_le_32(msoftspi_info->sclk.addr), h_to_le_32(msoftspi_info->sclk.mask),
+ h_to_le_32(msoftspi_info->miso.addr), h_to_le_32(msoftspi_info->miso.mask),
+ h_to_le_32(msoftspi_info->mosi.addr), h_to_le_32(msoftspi_info->mosi.mask),
+ h_to_le_32(msoftspi_info->bits_no)
+ };
+
+ /* memory buffer, we assume sectorsize to be a power of 2 times page_size */
+ page_size = msoftspi_info->dev.pagesize;
+ fifo_size = msoftspi_info->dev.sectorsize;
+ while (buffer_size = code_size + 2 * sizeof(uint32_t) + fifo_size,
+ target_alloc_working_area_try(target, buffer_size, &write_algorithm) != ERROR_OK) {
+ fifo_size /= 2;
+ if (fifo_size < page_size) {
+ /* we already allocated the reading/writing code, but failed to get a
+ * buffer, free the algorithm */
+ target_free_working_area(target, write_algorithm);
+
+ LOG_WARNING("not enough working area, can't do SPI %s",
+ write ? "page writes" : "reads");
+ return ERROR_TARGET_RESOURCE_NOT_AVAILABLE;
+ }
+ };
+
+ /* prepare read/write code, excluding port_buffer */
+ retval = target_write_buffer(target, write_algorithm->address,
+ code_size - sizeof(port_buffer), code);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* prepare port_buffer values */
+ retval = target_write_buffer(target, write_algorithm->address
+ + code_size - sizeof(port_buffer),
+ sizeof(port_buffer), (uint8_t *) port_buffer);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* target buffer starts right after flash_write_code, i. e.
+ * wp and rp are implicitly included in buffer!!! */
+ fifo_start = write_algorithm->address + code_size + 2 * sizeof(uint32_t);
+
+ init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* count (in), status (out) */
+ init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* flash page_size */
+ init_reg_param(®_params[2], "r2", 32, PARAM_IN_OUT); /* offset into flash address */
+ init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* 2/3/4-byte address mode */
+ init_reg_param(®_params[4], "r8", 32, PARAM_OUT); /* fifo start */
+ init_reg_param(®_params[5], "r9", 32, PARAM_OUT); /* fifo end + 1 */
+
+ buf_set_u32(reg_params[0].value, 0, 32, count);
+ buf_set_u32(reg_params[1].value, 0, 32,
+ write ? page_size : msoftspi_info->dev.sectorsize);
+ buf_set_u32(reg_params[2].value, 0, 32, offset);
+ buf_set_u32(reg_params[3].value, 0, 32, ADDR_BYTES);
+ buf_set_u32(reg_params[4].value, 0, 32, fifo_start);
+ buf_set_u32(reg_params[5].value, 0, 32, fifo_start + fifo_size);
+
+ armv7m_info.common_magic = ARMV7M_COMMON_MAGIC;
+ armv7m_info.core_mode = ARM_MODE_THREAD;
+
+ /* after breakpoint instruction (halfword) one nop (halfword) and
+ * port_buffer till end of code */
+ exit_point = write_algorithm->address + code_size
+ - sizeof(uint32_t) - sizeof(port_buffer);
+
+ if (write) {
+ retval = target_run_flash_async_algorithm(target, buffer, count, 1,
+ 0, NULL,
+ 6, reg_params,
+ write_algorithm->address + code_size,
+ fifo_size + 2 * sizeof(uint32_t),
+ write_algorithm->address, exit_point,
+ &armv7m_info);
+ } else {
+ retval = target_run_read_async_algorithm(target, buffer, count, 1,
+ 0, NULL,
+ 6, reg_params,
+ write_algorithm->address + code_size,
+ fifo_size + 2 * sizeof(uint32_t),
+ write_algorithm->address, exit_point,
+ &armv7m_info);
+ }
+
+ remaining = buf_get_u32(reg_params[0].value, 0, 32);
+ if ((retval == ERROR_OK) && remaining)
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ if (retval != ERROR_OK) {
+ offset = buf_get_u32(reg_params[2].value, 0, 32);
+ LOG_ERROR("flash %s failed at address 0x%" PRIx32 ", remaining 0x%" PRIx32,
+ write ? "write" : "read", offset, remaining);
+ }
+
+ destroy_reg_param(®_params[0]);
+ destroy_reg_param(®_params[1]);
+ destroy_reg_param(®_params[2]);
+ destroy_reg_param(®_params[3]);
+ destroy_reg_param(®_params[4]);
+ destroy_reg_param(®_params[5]);
+
+err:
+ target_free_working_area(target, write_algorithm);
+
+ return retval;
+}
+
+static int msoftspi_write(struct flash_bank *bank, const uint8_t *buffer,
+ uint32_t offset, uint32_t count)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ int sector;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32,
+ __func__, offset, count);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (offset + count > msoftspi_info->dev.size_in_bytes) {
+ LOG_WARNING("Write beyond end of flash. Extra data discarded.");
+ count = msoftspi_info->dev.size_in_bytes - offset;
+ }
+
+ /* check sector protection */
+ for (sector = 0; sector < bank->num_sectors; sector++) {
+ /* Start offset in or before this sector? */
+ /* End offset in or behind this sector? */
+ if ((offset < (bank->sectors[sector].offset + bank->sectors[sector].size))
+ && ((offset + count - 1) >= bank->sectors[sector].offset)) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ } else
+ bank->sectors[sector].is_erased = -1;
+ }
+ }
+
+ return msoftspi_read_write_block(bank, (uint8_t *) buffer, offset, count, 1);
+}
+
+static int msoftspi_read(struct flash_bank *bank, uint8_t *buffer,
+ uint32_t offset, uint32_t count)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ LOG_DEBUG("%s: offset=0x%08" PRIx32 " count=0x%08" PRIx32,
+ __func__, offset, count);
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if (offset + count > msoftspi_info->dev.size_in_bytes) {
+ LOG_WARNING("Read beyond end of flash. Extra data to be ignored.");
+ count = msoftspi_info->dev.size_in_bytes - offset;
+ }
+
+ return msoftspi_read_write_block(bank, buffer, offset, count, 0);
+}
+
+/* Return ID of flash device */
+static int read_flash_id(struct flash_bank *bank, uint32_t *id)
+{
+ struct target *target = bank->target;
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ uint32_t port, data;
+ int k, retval, success;
+
+ success = ERROR_FAIL;
+
+ if ((target->state != TARGET_HALTED) && (target->state != TARGET_RESET)) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ /* clear SCLK */
+ retval = target_read_u32(target, msoftspi_info->sclk.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ port &= ~msoftspi_info->sclk.mask;
+ retval = target_write_u32(target, msoftspi_info->sclk.addr, port);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* poll WIP */
+ retval = wait_till_ready(bank, MSOFTSPI_PROBE_TIMEOUT);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* clear NCS */
+ CLR_PORT_BIT(port, ncs);
+ retval = target_write_u32(target, msoftspi_info->ncs.addr, port);
+ if (retval != ERROR_OK)
+ goto err;
+
+ success = msoftspi_shift_out(bank, SPIFLASH_READ_ID);
+ if (success != ERROR_OK)
+ goto err;
+
+ /* set to input */
+
+ for (k = 0; k < 3; k++) {
+ success = msoftspi_shift_in(bank, &data);
+ if (success != ERROR_OK)
+ goto err;
+ }
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ /* three bytes received, placed in bits 0 to 23, byte reversed */
+ *id = ((data & 0xFF) << 16) | (data & 0xFF00) | ((data & 0xFF0000) >> 16);
+
+ if ((*id == 0x000000) || (*id == 0xFFFFFF)) {
+ LOG_INFO("No response from flash");
+ success = ERROR_TARGET_NOT_EXAMINED;
+ }
+
+ return success;
+}
+
+/* Read id from flash chip */
+static int msoftspi_probe(struct flash_bank *bank)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+ struct flash_sector *sectors;
+ const struct flash_device *p;
+ uint32_t id = 0;
+ int retval;
+
+ if (msoftspi_info->probed)
+ free(bank->sectors);
+ msoftspi_info->probed = 0;
+
+ /* read and decode flash ID */
+ retval = read_flash_id(bank, &id);
+ LOG_DEBUG("id 0x%06" PRIx32, id);
+ if (retval == ERROR_TARGET_NOT_EXAMINED) {
+ /* no id retrieved, so id must be set manually */
+ LOG_INFO("No id - set flash parameters manually");
+ return ERROR_OK;
+ }
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* identify flash */
+ msoftspi_info->dev.name = NULL;
+ for (p = flash_devices; id && p->name ; p++) {
+ if (p->device_id == id) {
+ memcpy(&msoftspi_info->dev, p, sizeof(msoftspi_info->dev));
+ LOG_INFO("flash \'%s\' id = 0x%06" PRIx32
+ "\nflash size = %lukbytes",
+ p->name, id, p->size_in_bytes>>10);
+ break;
+ }
+ }
+
+ if (id && !p->name) {
+ LOG_ERROR("Unknown flash device id = 0x%06" PRIx32, id);
+ return ERROR_FAIL;
+ }
+
+ /* set correct size value */
+ bank->size = msoftspi_info->dev.size_in_bytes;
+
+ /* create and fill sectors array */
+ bank->num_sectors =
+ msoftspi_info->dev.size_in_bytes / msoftspi_info->dev.sectorsize;
+ sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
+ if (sectors == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ for (int sector = 0; sector < bank->num_sectors; sector++) {
+ sectors[sector].offset = sector * (msoftspi_info->dev.sectorsize);
+ sectors[sector].size = msoftspi_info->dev.sectorsize;
+ sectors[sector].is_erased = -1;
+ sectors[sector].is_protected = 0;
+ }
+
+ bank->sectors = sectors;
+ msoftspi_info->probed = 1;
+
+ return ERROR_OK;
+}
+
+static int msoftspi_auto_probe(struct flash_bank *bank)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ if (msoftspi_info->probed)
+ return ERROR_OK;
+ return msoftspi_probe(bank);
+}
+
+static int msoftspi_protect_check(struct flash_bank *bank)
+{
+ /* nothing to do. Protection is only handled in SW. */
+ return ERROR_OK;
+}
+
+static int get_msoftspi_info(struct flash_bank *bank, char *buf, int buf_size)
+{
+ struct msoftspi_flash_bank *msoftspi_info = bank->driver_priv;
+
+ if (!(msoftspi_info->probed)) {
+ snprintf(buf, buf_size, "\nmsoftspi flash bank not probed yet\n");
+ return ERROR_OK;
+ }
+
+ snprintf(buf, buf_size, "flash \'%s\' device id = 0x%06" PRIx32
+ "\nflash size = %dkBytes\npage size = %d"
+ ", mass_erase = 0x%02x, sector_erase = 0x%02x",
+ msoftspi_info->dev.name, msoftspi_info->dev.device_id,
+ bank->size>>10, msoftspi_info->dev.pagesize,
+ msoftspi_info->dev.chip_erase_cmd, msoftspi_info->dev.erase_cmd);
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(msoftspi_handle_mass_erase_command)
+{
+ struct target *target = NULL;
+ struct flash_bank *bank;
+ struct msoftspi_flash_bank *msoftspi_info;
+ struct duration bench;
+ uint32_t port, data;
+ int retval, sector;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC != 1)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+
+ msoftspi_info = bank->driver_priv;
+ target = bank->target;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ if (!(msoftspi_info->probed)) {
+ LOG_ERROR("Flash bank not probed");
+ return ERROR_FLASH_BANK_NOT_PROBED;
+ }
+
+ if (msoftspi_info->dev.chip_erase_cmd == 0x00) {
+ LOG_ERROR("No mass erase available");
+ return ERROR_FAIL;
+ }
+
+ for (sector = 0; sector < bank->num_sectors; sector++) {
+ if (bank->sectors[sector].is_protected) {
+ LOG_ERROR("Flash sector %d protected", sector);
+ return ERROR_FAIL;
+ }
+ }
+
+ duration_start(&bench);
+
+ retval = msoftspi_write_enable(bank);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Send Mass Erase command */
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ retval = msoftspi_shift_out(bank, msoftspi_info->dev.chip_erase_cmd);
+ if (retval != ERROR_OK)
+ goto err;
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* read flash status register */
+ retval = read_status_reg(bank, &data);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* check for command in progress for flash */
+ if ((data & SPIFLASH_WE_BIT) == 0) {
+ LOG_ERROR("Mass erase command not accepted by flash. Status=0x%08" PRIx32, data);
+ return ERROR_FAIL;
+ }
+
+ /* poll WIP for end of self timed Sector Erase cycle */
+ retval = wait_till_ready(bank, MSOFTSPI_MASS_ERASE_TIMEOUT);
+
+ duration_measure(&bench);
+ if (retval == ERROR_OK) {
+ /* set all sectors as erased */
+ for (sector = 0; sector < bank->num_sectors; sector++)
+ bank->sectors[sector].is_erased = 1;
+
+ command_print(CMD_CTX, "msoftspi mass erase completed in"
+ " %fs (%0.3f KiB/s)", duration_elapsed(&bench),
+ duration_kbps(&bench, bank->size));
+ } else {
+ command_print(CMD_CTX, "msoftspi mass erase failed after %fs",
+ duration_elapsed(&bench));
+ }
+
+ return retval;
+}
+
+static int log2u(unsigned long word)
+{
+ int result;
+
+ for (result = 0; (unsigned int) result < sizeof(unsigned long) * 8; result++)
+ if (word == (1UL<<result))
+ return result;
+
+ return -1;
+}
+
+COMMAND_HANDLER(msoftspi_handle_setid)
+{
+ struct flash_bank *bank = NULL;
+ struct msoftspi_flash_bank *msoftspi_info = NULL;
+ struct flash_sector *sectors = NULL;
+ int retval;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 2 || CMD_ARGC > 7)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+ msoftspi_info = bank->driver_priv;
+
+ /* invalidate all old info */
+ if (msoftspi_info->probed)
+ free(bank->sectors);
+ msoftspi_info->probed = 0;
+ msoftspi_info->dev.name = NULL;
+ msoftspi_info->dev.device_id = 0;
+
+ strncpy(msoftspi_info->devname, CMD_ARGV[1], sizeof(msoftspi_info->devname) - 1);
+ msoftspi_info->devname[sizeof(msoftspi_info->devname) - 1] = '\0';
+
+ COMMAND_PARSE_NUMBER(u64, CMD_ARGV[2], msoftspi_info->dev.size_in_bytes);
+ if (log2u(msoftspi_info->dev.size_in_bytes) < 8) {
+ command_print(CMD_CTX, "msoftspi: device size must be 2^n with n >= 8");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ COMMAND_PARSE_NUMBER(u32, CMD_ARGV[3], msoftspi_info->dev.pagesize);
+ if ((log2u(msoftspi_info->dev.pagesize) > log2u(msoftspi_info->dev.size_in_bytes)) ||
+ (log2u(msoftspi_info->dev.pagesize) < 0)) {
+ command_print(CMD_CTX, "msoftspi: page size must be 2^n and <= device size");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ if (CMD_ARGC > 4)
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[4], msoftspi_info->dev.chip_erase_cmd);
+ else
+ msoftspi_info->dev.chip_erase_cmd = 0x00;
+
+ if (CMD_ARGC > 5) {
+ COMMAND_PARSE_NUMBER(u64, CMD_ARGV[5], msoftspi_info->dev.sectorsize);
+ if ((log2u(msoftspi_info->dev.sectorsize) > log2u(msoftspi_info->dev.size_in_bytes)) ||
+ (log2u(msoftspi_info->dev.sectorsize) < 0)) {
+ command_print(CMD_CTX, "msoftspi: sector size must be 2^n and <= device size");
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ }
+
+ if (CMD_ARGC > 6)
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[6], msoftspi_info->dev.erase_cmd);
+ else
+ return ERROR_COMMAND_SYNTAX_ERROR;
+ } else {
+ /* no sector size / sector erase cmd given, treat whole bank as a single sector */
+ msoftspi_info->dev.erase_cmd = 0x00;
+ msoftspi_info->dev.sectorsize = msoftspi_info->dev.size_in_bytes;
+ }
+
+ /* set correct size value */
+ bank->size = msoftspi_info->dev.size_in_bytes;
+
+ /* create and fill sectors array */
+ bank->num_sectors =
+ msoftspi_info->dev.size_in_bytes / msoftspi_info->dev.sectorsize;
+ sectors = malloc(sizeof(struct flash_sector) * bank->num_sectors);
+ if (sectors == NULL) {
+ LOG_ERROR("not enough memory");
+ return ERROR_FAIL;
+ }
+
+ for (int sector = 0; sector < bank->num_sectors; sector++) {
+ sectors[sector].offset = sector * (msoftspi_info->dev.sectorsize);
+ sectors[sector].size = msoftspi_info->dev.sectorsize;
+ sectors[sector].is_erased = -1;
+ sectors[sector].is_protected = 0;
+ }
+
+ bank->sectors = sectors;
+ msoftspi_info->dev.name = msoftspi_info->devname;
+ msoftspi_info->probed = 1;
+
+ return ERROR_OK;
+}
+
+COMMAND_HANDLER(msoftspi_handle_spicmd)
+{
+ struct target *target = NULL;
+ struct flash_bank *bank;
+ struct msoftspi_flash_bank *msoftspi_info;
+ uint32_t port, data;
+ uint8_t num_bytes, cmd_byte;
+ const int max = 16;
+ unsigned int count;
+ char temp[4], output[(1 + max + 256) * 3 + 4];
+ int retval;
+
+ LOG_DEBUG("%s", __func__);
+
+ if (CMD_ARGC < 3 || CMD_ARGC > max + 3)
+ return ERROR_COMMAND_SYNTAX_ERROR;
+
+ retval = CALL_COMMAND_HANDLER(flash_command_get_bank, 0, &bank);
+ if (ERROR_OK != retval)
+ return retval;
+
+ msoftspi_info = bank->driver_priv;
+ target = bank->target;
+
+ if (target->state != TARGET_HALTED) {
+ LOG_ERROR("Target not halted");
+ return ERROR_TARGET_NOT_HALTED;
+ }
+
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[1], num_bytes);
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[2], cmd_byte);
+
+ /* clear NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ if (retval != ERROR_OK)
+ goto err;
+ CLR_PORT_BIT(port, ncs);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send command byte */
+ snprintf(output, sizeof(output), "spicmd: %02x ", cmd_byte & 0xFF);
+ retval = msoftspi_shift_out(bank, cmd_byte);
+ if (retval != ERROR_OK)
+ goto err;
+
+ /* send additional bytes */
+ for (count = 3; count < CMD_ARGC; count++) {
+ COMMAND_PARSE_NUMBER(u8, CMD_ARGV[count], cmd_byte);
+ snprintf(temp, sizeof(temp), "%02x ", cmd_byte & 0xFF);
+ retval = msoftspi_shift_out(bank, cmd_byte);
+ if (retval != ERROR_OK)
+ goto err;
+ strncat(output, temp, sizeof(output));
+ }
+
+ /* set to input */
+ strncat(output, "-> ", sizeof(output));
+
+ for ( ; num_bytes > 0; num_bytes--) {
+ retval = msoftspi_shift_in(bank, &data);
+ if (retval != ERROR_OK)
+ goto err;
+ snprintf(temp, sizeof(temp), "%02x ", data & 0xFF);
+ strncat(output, temp, sizeof(output));
+ }
+ command_print(CMD_CTX, "%s", output);
+
+err:
+ /* set NCS */
+ retval = target_read_u32(target, msoftspi_info->ncs.addr, &port);
+ SET_PORT_BIT(port, ncs);
+
+ /* set to output */
+
+ return retval;
+}
+
+static const struct command_registration msoftspi_exec_command_handlers[] = {
+ {
+ .name = "mass_erase",
+ .handler = msoftspi_handle_mass_erase_command,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id",
+ .help = "Mass erase entire flash device.",
+ },
+ {
+ .name = "setid",
+ .handler = msoftspi_handle_setid,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id flash_id",
+ .help = "Set id of flash chip to flash_id (uint24)",
+ },
+ {
+ .name = "spicmd",
+ .handler = msoftspi_handle_spicmd,
+ .mode = COMMAND_EXEC,
+ .usage = "bank_id num_resp cmd_byte ...",
+ .help = "Send low-level command cmd_byte and following bytes, read num_bytes.",
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+static const struct command_registration msoftspi_command_handlers[] = {
+ {
+ .name = "msoftspi",
+ .mode = COMMAND_ANY,
+ .help = "msoftspi flash command group",
+ .usage = "",
+ .chain = msoftspi_exec_command_handlers,
+ },
+ COMMAND_REGISTRATION_DONE
+};
+
+struct flash_driver msoftspi_flash = {
+ .name = "msoftspi",
+ .commands = msoftspi_command_handlers,
+ .flash_bank_command = msoftspi_flash_bank_command,
+ .erase = msoftspi_erase,
+ .protect = msoftspi_protect,
+ .write = msoftspi_write,
+ .read = msoftspi_read,
+ .probe = msoftspi_probe,
+ .auto_probe = msoftspi_auto_probe,
+ .erase_check = msoftspi_blank_check,
+ .protect_check = msoftspi_protect_check,
+ .info = get_msoftspi_info,
+};
diff --git a/src/flash/nor/spi.c b/src/flash/nor/spi.c
index fe1be86..62f1868 100644
--- a/src/flash/nor/spi.c
+++ b/src/flash/nor/spi.c
@@ -54,6 +54,7 @@ const struct flash_device flash_devices[] = {
FLASH_ID("sp s25fl164k", 0xd8, 0xc7, 0x00174001, 0x100, 0x10000, 0x800000),
FLASH_ID("sp s25fl128", 0xd8, 0xc7, 0x00182001, 0x100, 0x10000, 0x1000000),
FLASH_ID("sp s25fl256", 0xd8, 0xc7, 0x00190201, 0x100, 0x10000, 0x2000000),
+ FLASH_ID("sp s25fl512s", 0xd8, 0xc7, 0x00200201, 0x200, 0x40000, 0x4000000),
FLASH_ID("atmel 25f512", 0x52, 0xc7, 0x0065001f, 0x80, 0x8000, 0x10000),
FLASH_ID("atmel 25f1024", 0x52, 0x62, 0x0060001f, 0x100, 0x8000, 0x20000),
FLASH_ID("atmel 25f2048", 0x52, 0x62, 0x0063001f, 0x100, 0x10000, 0x40000),
@@ -79,9 +80,10 @@ const struct flash_device flash_devices[] = {
FLASH_ID("win w25q32dw", 0xd8, 0xc7, 0x001660ef, 0x100, 0x10000, 0x400000),
FLASH_ID("win w25q64cv", 0xd8, 0xc7, 0x001740ef, 0x100, 0x10000, 0x800000),
FLASH_ID("win w25q128fv", 0xd8, 0xc7, 0x001840ef, 0x100, 0x10000, 0x1000000),
+ FLASH_ID("win w25q256fv", 0xd8, 0xc7, 0x001940ef, 0x100, 0x10000, 0x2000000),
FLASH_ID("gd gd25q20", 0x20, 0xc7, 0x00c84012, 0x100, 0x1000, 0x80000),
FLASH_ID("gd gd25q16c", 0xd8, 0xc7, 0x001540c8, 0x100, 0x10000, 0x200000),
FLASH_ID("gd gd25q32c", 0xd8, 0xc7, 0x001640c8, 0x100, 0x10000, 0x400000),
FLASH_ID("gd gd25q128c", 0xd8, 0xc7, 0x001840c8, 0x100, 0x10000, 0x1000000),
- FLASH_ID(NULL, 0, 0, 0, 0, 0, 0)
+ FLASH_ID(NULL, 0, 0, 0, 0, 0, 0)
};
diff --git a/src/flash/nor/spi.h b/src/flash/nor/spi.h
index a184998..b3d8778 100644
--- a/src/flash/nor/spi.h
+++ b/src/flash/nor/spi.h
@@ -22,6 +22,8 @@
#ifndef OPENOCD_FLASH_NOR_SPI_H
#define OPENOCD_FLASH_NOR_SPI_H
+#ifndef __ASSEMBLER__
+
/* data structure to maintain flash ids from different vendors */
struct flash_device {
char *name;
@@ -46,9 +48,13 @@ struct flash_device {
extern const struct flash_device flash_devices[];
+#endif
+
/* fields in SPI flash status register */
-#define SPIFLASH_BSY_BIT 0x00000001 /* WIP Bit of SPI SR on SMI SR */
-#define SPIFLASH_WE_BIT 0x00000002 /* WEL Bit of SPI SR on SMI SR */
+#define SPIFLASH_BSY 0
+#define SPIFLASH_BSY_BIT (1 << SPIFLASH_BSY) /* WIP Bit of SPI SR on SMI SR */
+#define SPIFLASH_WE 1
+#define SPIFLASH_WE_BIT (1 << SPIFLASH_WE) /* WEL Bit of SPI SR on SMI SR */
/* SPI Flash Commands */
#define SPIFLASH_READ_ID 0x9F /* Read Flash Identification */
diff --git a/src/target/target.c b/src/target/target.c
index e04ecc4..9501f1a 100644
--- a/src/target/target.c
+++ b/src/target/target.c
@@ -1038,6 +1038,148 @@ int target_run_flash_async_algorithm(struct target *target,
return retval;
}
+int target_run_read_async_algorithm(struct target *target,
+ uint8_t *buffer, uint32_t count, int block_size,
+ int num_mem_params, struct mem_param *mem_params,
+ int num_reg_params, struct reg_param *reg_params,
+ uint32_t buffer_start, uint32_t buffer_size,
+ uint32_t entry_point, uint32_t exit_point, void *arch_info)
+{
+ int retval;
+ int timeout = 0;
+
+ const uint8_t *buffer_orig = buffer;
+
+ /* Set up working area. First word is write pointer, second word is read pointer,
+ * rest is fifo data area. */
+ uint32_t wp_addr = buffer_start;
+ uint32_t rp_addr = buffer_start + 4;
+ uint32_t fifo_start_addr = buffer_start + 8;
+ uint32_t fifo_end_addr = buffer_start + buffer_size;
+
+ uint32_t wp = fifo_start_addr;
+ uint32_t rp = fifo_start_addr;
+
+ /* validate block_size is 2^n */
+ assert(!block_size || !(block_size & (block_size - 1)));
+
+ retval = target_write_u32(target, wp_addr, wp);
+ if (retval != ERROR_OK)
+ return retval;
+ retval = target_write_u32(target, rp_addr, rp);
+ if (retval != ERROR_OK)
+ return retval;
+
+ /* Start up algorithm on target */
+ retval = target_start_algorithm(target, num_mem_params, mem_params,
+ num_reg_params, reg_params,
+ entry_point,
+ exit_point,
+ arch_info);
+
+ if (retval != ERROR_OK) {
+ LOG_ERROR("error starting target flash read algorithm");
+ return retval;
+ }
+
+ while (count > 0) {
+ retval = target_read_u32(target, wp_addr, &wp);
+ if (retval != ERROR_OK) {
+ LOG_ERROR("failed to get write pointer");
+ break;
+ }
+
+ LOG_DEBUG("offs 0x%zx count 0x%" PRIx32 " wp 0x%" PRIx32 " rp 0x%" PRIx32,
+ (size_t) (buffer - buffer_orig), count, wp, rp);
+
+ if (wp == 0) {
+ LOG_ERROR("flash read algorithm aborted by target");
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ break;
+ }
+
+ if (((wp - fifo_start_addr) & (block_size - 1)) || wp < fifo_start_addr || wp >= fifo_end_addr) {
+ LOG_ERROR("corrupted fifo write pointer 0x%" PRIx32, wp);
+ break;
+ }
+
+ /* Count the number of bytes available in the fifo without
+ * crossing the wrap around. */
+ uint32_t thisrun_bytes;
+ if (wp > rp)
+ thisrun_bytes = wp - rp;
+ else
+ thisrun_bytes = fifo_end_addr - rp;
+
+ if (thisrun_bytes == 0) {
+ /* Throttle polling a bit if transfer is (much) faster than flash
+ * reading. The exact delay shouldn't matter as long as it's
+ * less than buffer size / flash speed. This is very unlikely to
+ * run when using high latency connections such as USB. */
+ alive_sleep(10);
+
+ /* to stop an infinite loop on some targets check and increment a timeout
+ * this issue was observed on a stellaris using the new ICDI interface */
+ if (timeout++ >= 500) {
+ LOG_ERROR("timeout waiting for algorithm, a target reset is recommended");
+ return ERROR_FLASH_OPERATION_FAILED;
+ }
+ continue;
+ }
+
+ /* reset our timeout */
+ timeout = 0;
+
+ /* Limit to the amount of data we actually want to read */
+ if (thisrun_bytes > count * block_size)
+ thisrun_bytes = count * block_size;
+
+ /* Read data from fifo */
+ retval = target_read_buffer(target, rp, thisrun_bytes, buffer);
+ if (retval != ERROR_OK)
+ break;
+
+ /* Update counters and wrap write pointer */
+ buffer += thisrun_bytes;
+ count -= thisrun_bytes / block_size;
+ rp += thisrun_bytes;
+ if (rp >= fifo_end_addr)
+ rp = fifo_start_addr;
+
+ /* Store updated write pointer to target */
+ retval = target_write_u32(target, rp_addr, rp);
+ if (retval != ERROR_OK)
+ break;
+ }
+
+ if (retval != ERROR_OK) {
+ /* abort flash write algorithm on target */
+ target_write_u32(target, rp_addr, 0);
+ }
+
+ int retval2 = target_wait_algorithm(target, num_mem_params, mem_params,
+ num_reg_params, reg_params,
+ exit_point,
+ 10000,
+ arch_info);
+
+ if (retval2 != ERROR_OK) {
+ LOG_ERROR("error waiting for target flash write algorithm");
+ retval = retval2;
+ }
+
+ if (retval == ERROR_OK) {
+ /* check if algorithm set wp = 0 after fifo writer loop finished */
+ retval = target_read_u32(target, wp_addr, &wp);
+ if (retval == ERROR_OK && wp == 0) {
+ LOG_ERROR("flash read algorithm aborted by target");
+ retval = ERROR_FLASH_OPERATION_FAILED;
+ }
+ }
+
+ return retval;
+}
+
int target_read_memory(struct target *target,
target_addr_t address, uint32_t size, uint32_t count, uint8_t *buffer)
{
diff --git a/src/target/target.h b/src/target/target.h
index 53f9e26..98d29f7 100644
--- a/src/target/target.h
+++ b/src/target/target.h
@@ -526,6 +526,18 @@ int target_run_flash_async_algorithm(struct target *target,
void *arch_info);
/**
+ * This routine is a wrapper for asynchronous algorithms.
+ *
+ */
+int target_run_read_async_algorithm(struct target *target,
+ uint8_t *buffer, uint32_t count, int block_size,
+ int num_mem_params, struct mem_param *mem_params,
+ int num_reg_params, struct reg_param *reg_params,
+ uint32_t buffer_start, uint32_t buffer_size,
+ uint32_t entry_point, uint32_t exit_point,
+ void *arch_info);
+
+/**
* Read @a count items of @a size bytes from the memory of @a target at
* the @a address given.
*
diff --git a/tcl/board/msoftspi_examples.cfg b/tcl/board/msoftspi_examples.cfg
new file mode 100644
index 0000000..83077b4
--- /dev/null
+++ b/tcl/board/msoftspi_examples.cfg
@@ -0,0 +1,119 @@
+--------------------------------------------------------------------------------
+for STM32F746G-disco:
+--------------------------------------------------------------------------------
+
+set _MSOFTSPINAME $_CHIPNAME.msoftspi
+flash bank $_MSOFTSPINAME msoftspi 0x90000000 0 0 0 $_TARGETNAME 0x40020414 6 0x40020414 2 0x40020C10 12 0x40020C14 11
+
+$_TARGETNAME configure -event reset-init {
+ mmw 0x40023830 0x000007FF 0 ;# RCC_AHB1ENR |= GPIOA-GPIOK (enable clocks)
+
+ mww 0x40023C00 0x00000006 ;# 6 WS for 192 MHz HCLK
+ sleep 1
+ mww 0x40023804 0x24003008 ;# 192MHz: PLLM=8, PLLN=192, PLLP=2
+ mww 0x40023808 0x00009400 ;# APB1: /4, APB2: /2
+ mmw 0x40023800 0x01000000 0x00000000 ;# PLL on
+ sleep 1
+ mmw 0x40023808 0x00000002 0x00000000 ;# switch to PLL
+
+ adapter_khz 4000
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PD12: BK1_IO1 (MISO), PD11: BK1_IO0 (MOSI)
+ mmw 0x40020400 0x00001010 0x00002020 ;# PB6, PB2 output
+ mmw 0x40020404 0x00000000 0x00000044 ;# PB6, PB2 push-pull
+ mmw 0x40020408 0x00003030 0x00000000 ;# very high speed
+ mmw 0x4002040C 0x00000000 0x00003030 ;# PB6, PB2 no pull-up
+
+ mmw 0x40020C00 0x00400000 0x03800000 ;# PD13 input, PD12 input, PD11 output
+ mmw 0x40020C04 0x00000000 0x08000000 ;# PD11 push-pull
+ mmw 0x40020C08 0x03C00000 0x00000000 ;# PD12, PD11 very high speed
+ mmw 0x40020C0C 0x05000000 0x0A000000 ;# PD13, PD12 pull-up, PD11 no pull-up
+
+ mmw 0x40021000 0x00000000 0x00000300 ;# PE2 input
+ mmw 0x40021004 0x00000000 0x00000000 ;#
+ mmw 0x40021008 0x00000000 0x00000000 ;# very high speed
+ mmw 0x4002100C 0x05000000 0x0A000000 ;# PE2 pull-up
+}
+
+--------------------------------------------------------------------------------
+for STM32F746G-disco:
+--------------------------------------------------------------------------------
+
+set _MSOFTSPINAME $_CHIPNAME.msoftspi
+flash bank $_MSOFTSPINAME msoftspi 0x90000000 0 0 0 $_TARGETNAME 0x40020414 6 0x40020414 2 0x40020810 10 0x40020814 9
+
+$_TARGETNAME configure -event reset-init {
+ mmw 0x40023830 0x000007FF 0 ;# RCC_AHB1ENR |= GPIOA-GPIOK (enable clocks)
+ mmw 0x40023838 0x00000002 0 ;# RCC_AHB3ENR |= QSPIEN (enable clock)
+ sleep 1
+
+ # if demo firmware is installed, it will switch the SPI flash to QPI mode,
+ # which will remain active until power-cycling
+ # hence QPI mode must be turned off ...
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PC10: BK1_IO1, PC9: BK1_IO0
+ mmw 0x40020400 0x00002020 0x00001010 ;# PB6, PB2 alternate
+ mmw 0x40020408 0x00003030 0x00000000 ;# high speed
+ mmw 0x40020420 0x0A000900 0x05000600 ;# AF10, AF9
+
+ mmw 0x40020800 0x00280000 0x00140000 ;# PC10, PC9 alternate
+ mmw 0x40020808 0x003C0000 0x00000000 ;# high speed
+ mmw 0x40020824 0x00000990 0x00000660 ;# AF9, AF9
+
+ mmw 0x40020C00 0x08000000 0x04000000 ;# PD13 alternate
+ mmw 0x40020C08 0x0C000000 0x00000000 ;# high speed
+ mmw 0x40020C24 0x00900000 0x00600000 ;# AF9
+
+ mmw 0x40021000 0x00000020 0x00000010 ;# PE2 alternate
+ mmw 0x40021008 0x00000030 0x00000000 ;# high speed
+ mmw 0x40021020 0x00000900 0x00000600 ;# AF9
+
+ mww 0xA0001000 0x01500318 ;# QUADSPI_CR: PRESCALER=1, APMS=1, FTHRES=3, SSHIFT=1, TCEN=1
+ mww 0xA0001004 0x00190100 ;# QUADSPI_DCR: FSIZE=0x19, CSHT=0x01, CKMODE=0
+ mmw 0xA0001000 0x00000001 0 ;# QUADSPI_CR: EN=1
+
+ # 1-line spi mode
+ mww 0xA0001014 0x000003F5 ;# QUADSPI_CCR: FMODE=0x0, DMODE=0x0, DCYC=0x0, ADSIZE=0x0, ADMODE=0x0, IMODE=0x3, INSTR=RS
+ sleep 1
+
+ # 4-byte address mode
+ mww 0xA0001014 0x000001B7 ;# QUADSPI_CCR: FMODE=0x0, DMODE=0x0, DCYC=0x0, ADSIZE=0x0, ADMODE=0x0, IMODE=0x1, INSTR=EN
+ sleep 1
+
+ # memory-mapped read mode with 4-byte addresses
+ mww 0xA0001014 0x0D003503 ;# QUADSPI_CCR: FMODE=0x3, DMODE=0x1, DCYC=0x0, ADSIZE=0x3, ADMODE=0x1, IMODE=0x1, INSTR=RE
+
+
+ mww 0x40023C00 0x00000006 ;# 6 WS for 192 MHz HCLK
+ sleep 1
+ mww 0x40023804 0x24003008 ;# 192MHz: PLLM=8, PLLN=192, PLLP=2
+ mww 0x40023808 0x00009400 ;# APB1: /4, APB2: /2
+ mmw 0x40023800 0x01000000 0x00000000 ;# PLL on
+ sleep 1
+ mmw 0x40023808 0x00000002 0x00000000 ;# switch to PLL
+
+ # PB2: CLK, PB6: BK1_nCS, PD13: BK1_IO3, PE2: BK1_IO2, PC10: BK1_IO1 (MISO), PC9: BK1_IO0 (MOSI)
+ mmw 0x40020400 0x00001010 0x00002020 ;# PB6, PB2 output
+ mmw 0x40020404 0x00000000 0x00000044 ;# PB6, PB2 push-pull
+ mmw 0x40020408 0x00003030 0x00000000 ;# very high speed
+ mmw 0x4002040C 0x00000000 0x00003030 ;# PB6, PB2 no pull-up
+
+ mmw 0x40020800 0x00040000 0x00380000 ;# PC10 input, PC9 output
+ mmw 0x40020804 0x00000000 0x00000200 ;# PC9 push-pull
+ mmw 0x40020808 0x000C0000 0x00000000 ;# very high speed
+ mmw 0x4002080C 0x00100000 0x002C0000 ;# PC10 pull-up, PC9 no-pullup
+
+ mmw 0x40020C00 0x00000000 0x0C000000 ;# PD13 input
+ mmw 0x40020C04 0x00000000 0x00000000 ;#
+ mmw 0x40020C08 0x00000000 0x00000000 ;# very high speed
+ mmw 0x40020C0C 0x04000000 0x08000000 ;# PD13 pull-up
+
+ mmw 0x40021000 0x00000000 0x00000030 ;# PE2 input
+ mmw 0x40021004 0x00000000 0x00000000 ;#
+ mmw 0x40021008 0x00000000 0x00000000 ;# very high speed
+ mmw 0x4002100C 0x00000010 0x00000020 ;# PE2 pull-up
+
+ sleep 1
+ msoftspi spicmd 1 0 0xB7 ;# EN4B: switch to 4-byte address mode
+ msoftspi spicmd 1 1 0x15 ;# RDCR: read configuration register
+}
--