|
From: <ge...@op...> - 2017-05-31 20:57:49
|
This is an automated email from Gerrit. Andreas Bolsch (hyp...@gm...) just uploaded a new patch set to Gerrit, which you can find at http://openocd.zylin.com/4152 -- gerrit commit a3dde711dc584cec47b83235d258b79a54b7a5fd Author: Andreas Bolsch <hyp...@gm...> 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 <hyp...@gm...> Change-Id: I08da60b3a2ed9e156f9eb26859fe13dfd973b873 Signed-off-by: Andreas Bolsch <hyp...@gm...> 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 * + * and...@mn... * + * * + * 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 * + * and...@mn... * + * * + * 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 * + * and...@mn... * + * * + * 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 <bor...@gm...>, * + * 2017 by Andreas Bolsch <and...@mn... * + * 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 siz... [truncated message content] |