From 516719b6b8ac35c14396f9a0cbdc355be4e45c10 Mon Sep 17 00:00:00 2001 From: George Harris Date: Thu, 16 Aug 2012 17:53:32 -0700 Subject: [PATCH] Added SPIFI flash driver, algorithms, and docs Added a flash driver designed to allow program/erase of memory-mapped SPI flash chips for LPC43xx/LPC18xx family micros. This driver includes three algorithms - erase, write, and SPIFI peripheral initialization (to allow memory-mapped access after a reset). The driver has been added to the flash driver table (drivers.c), and the OpenOCD documentation has been updated to include the flash driver configuration command. Change-Id: I79f4ff8f1f07de4e5f2fe4f8c23aeb903f868514 Signed-off-by: George Harris Reviewed-on: http://openocd.zylin.com/783 Tested-by: jenkins Reviewed-by: Aurelien Jacobs Reviewed-by: Freddie Chopin --- contrib/loaders/flash/lpcspifi_erase.S | 176 +++++ contrib/loaders/flash/lpcspifi_init.S | 102 +++ contrib/loaders/flash/lpcspifi_write.S | 210 ++++++ doc/openocd.texi | 24 + src/flash/nor/Makefile.am | 1 + src/flash/nor/drivers.c | 2 + src/flash/nor/lpcspifi.c | 968 +++++++++++++++++++++++++ tcl/board/lpc4350_spifi_generic.cfg | 18 + 8 files changed, 1501 insertions(+) create mode 100644 contrib/loaders/flash/lpcspifi_erase.S create mode 100644 contrib/loaders/flash/lpcspifi_init.S create mode 100644 contrib/loaders/flash/lpcspifi_write.S create mode 100644 src/flash/nor/lpcspifi.c create mode 100644 tcl/board/lpc4350_spifi_generic.cfg diff --git a/contrib/loaders/flash/lpcspifi_erase.S b/contrib/loaders/flash/lpcspifi_erase.S new file mode 100644 index 0000000000..219f645b57 --- /dev/null +++ b/contrib/loaders/flash/lpcspifi_erase.S @@ -0,0 +1,176 @@ +/*************************************************************************** + * Copyright (C) 2012 by George Harris * + * george@luminairecoffee.com * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + + .text + .syntax unified + .cpu cortex-m3 + .thumb + .thumb_func + +/* + * Params : + * r0 = start address, status (out) + * r1 = count + * r2 = erase command + * r3 = block size + */ + +#define SSP_BASE_HIGH 0x4008 +#define SSP_BASE_LOW 0x3000 +#define SSP_CR0_OFFSET 0x00 +#define SSP_CR1_OFFSET 0x04 +#define SSP_DATA_OFFSET 0x08 +#define SSP_CPSR_OFFSET 0x10 +#define SSP_SR_OFFSET 0x0c + +#define SSP_CLOCK_BASE_HIGH 0x4005 +#define SSP_CLOCK_BASE_LOW 0x0000 +#define SSP_BRANCH_CLOCK_BASE_HIGH 0x4005 +#define SSP_BRANCH_CLOCK_BASE_LOW 0x2000 +#define SSP_BASE_CLOCK_OFFSET 0x94 +#define SSP_BRANCH_CLOCK_OFFSET 0x700 + +#define IOCONFIG_BASE_HIGH 0x4008 +#define IOCONFIG_BASE_LOW 0x6000 +#define IOCONFIG_SCK_OFFSET 0x18c +#define IOCONFIG_HOLD_OFFSET 0x190 +#define IOCONFIG_WP_OFFSET 0x194 +#define IOCONFIG_MISO_OFFSET 0x198 +#define IOCONFIG_MOSI_OFFSET 0x19c +#define IOCONFIG_CS_OFFSET 0x1a0 + +#define IO_BASE_HIGH 0x400f +#define IO_BASE_LOW 0x4000 +#define IO_CS_OFFSET 0xab +#define IODIR_BASE_HIGH 0x400f +#define IODIR_BASE_LOW 0x6000 +#define IO_CS_DIR_OFFSET 0x14 + + +setup: /* Initialize SSP pins and module */ + mov.w r10, #IOCONFIG_BASE_LOW + movt r10, #IOCONFIG_BASE_HIGH + mov.w r8, #0xea + str.w r8, [r10, #IOCONFIG_SCK_OFFSET] /* Configure SCK pin function */ + mov.w r8, #0x40 + str.w r8, [r10, #IOCONFIG_HOLD_OFFSET] /* Configure /HOLD pin function */ + mov.w r8, #0x40 + str.w r8, [r10, #IOCONFIG_WP_OFFSET] /* Configure /WP pin function */ + mov.w r8, #0xed + str.w r8, [r10, #IOCONFIG_MISO_OFFSET] /* Configure MISO pin function */ + mov.w r8, #0xed + str.w r8, [r10, #IOCONFIG_MOSI_OFFSET] /* Configure MOSI pin function */ + mov.w r8, #0x44 + str.w r8, [r10, #IOCONFIG_CS_OFFSET] /* Configure CS pin function */ + + mov.w r10, #IODIR_BASE_LOW + movt r10, #IODIR_BASE_HIGH + mov.w r8, #0x800 + str r8, [r10, #IO_CS_DIR_OFFSET] /* Set CS as output */ + mov.w r10, #IO_BASE_LOW + movt r10, #IO_BASE_HIGH + mov.w r8, #0xff + str.w r8, [r10, #IO_CS_OFFSET] /* Set CS high */ + + mov.w r10, #SSP_CLOCK_BASE_LOW + movt r10, #SSP_CLOCK_BASE_HIGH + mov.w r8, #0x0000 + movt r8, #0x0100 + str.w r8, [r10, #SSP_BASE_CLOCK_OFFSET] /* Configure SSP0 base clock (use 12 MHz IRC) */ + + mov.w r10, #SSP_BRANCH_CLOCK_BASE_LOW + movt r10, #SSP_BRANCH_CLOCK_BASE_HIGH + mov.w r8, #0x01 + str.w r8, [r10, #SSP_BRANCH_CLOCK_OFFSET] /* Configure (enable) SSP0 branch clock */ + + mov.w r10, #SSP_BASE_LOW + movt r10, #SSP_BASE_HIGH + mov.w r8, #0x07 + str.w r8, [r10, #SSP_CR0_OFFSET] /* Set clock postscale */ + mov.w r8, #0x02 + str.w r8, [r10, #SSP_CPSR_OFFSET] /* Set clock prescale */ + str.w r8, [r10, #SSP_CR1_OFFSET] /* Enable SSP in SPI mode */ +write_enable: + bl cs_down + mov.w r9, #0x06 /* Send the write enable command */ + bl write_data + bl cs_up + + bl cs_down + mov.w r9, #0x05 /* Get status register */ + bl write_data + mov.w r9, #0x00 /* Dummy data to clock in status */ + bl write_data + bl cs_up + + tst r9, #0x02 /* If the WE bit isn't set, we have a problem. */ + beq error +erase: + bl cs_down + mov.w r9, r2 /* Send the erase command */ + bl write_data +write_address: + lsr r9, r0, #16 /* Send the current 24-bit write address, MSB first */ + bl write_data + lsr r9, r0, #8 + bl write_data + mov.w r9, r0 + bl write_data + bl cs_up +wait_flash_busy: /* Wait for the flash to finish the previous erase */ + bl cs_down + mov.w r9, #0x05 /* Get status register */ + bl write_data + mov.w r9, #0x00 /* Dummy data to clock in status */ + bl write_data + bl cs_up + tst r9, #0x01 /* If it isn't done, keep waiting */ + bne wait_flash_busy + + subs r1, r1, #1 /* decrement count */ + cbz r1, exit /* Exit if we have written everything */ + add r0, r3 /* Move the address up by the block size */ + b write_enable /* Start a new block erase */ +write_data: /* Send/receive 1 byte of data over SSP */ + mov.w r10, #SSP_BASE_LOW + movt r10, #SSP_BASE_HIGH + str.w r9, [r10, #SSP_DATA_OFFSET] /* Write supplied data to the SSP data reg */ +wait_transmit: + ldr r9, [r10, #SSP_SR_OFFSET] /* Check SSP status */ + tst r9, #0x0010 /* Check if BSY bit is set */ + bne wait_transmit /* If still transmitting, keep waiting */ + ldr r9, [r10, #SSP_DATA_OFFSET] /* Load received data */ + bx lr /* Exit subroutine */ +cs_up: + mov.w r8, #0xff + b cs_write +cs_down: + mov.w r8, #0x0000 +cs_write: + mov.w r10, #IO_BASE_LOW + movt r10, #IO_BASE_HIGH + str.w r8, [r10, #IO_CS_OFFSET] + bx lr +error: + movs r0, #0 +exit: + bkpt #0x00 + + .end diff --git a/contrib/loaders/flash/lpcspifi_init.S b/contrib/loaders/flash/lpcspifi_init.S new file mode 100644 index 0000000000..1b373a1b12 --- /dev/null +++ b/contrib/loaders/flash/lpcspifi_init.S @@ -0,0 +1,102 @@ +/*************************************************************************** + * Copyright (C) 2012 by George Harris * + * george@luminairecoffee.com * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +/*************************************************************************** +* This is an algorithm for the LPC43xx family (and probably the LPC18xx * +* family as well, though they have not been tested) that will initialize * +* memory-mapped SPI flash accesses. Unfortunately NXP has published * +* neither the ROM source code that performs this initialization nor the * +* register descriptions necessary to do so, so this code is necessary to * +* call into the ROM SPIFI API. * +***************************************************************************/ + + .text + .syntax unified + .arch armv7-m + .thumb + .thumb_func + + .align 2 + +/* + * Params : + * r0 = spifi clock speed + */ + +#define IOCONFIG_BASE_HIGH 0x4008 +#define IOCONFIG_BASE_LOW 0x6000 +#define IOCONFIG_SCK_OFFSET 0x18c +#define IOCONFIG_HOLD_OFFSET 0x190 +#define IOCONFIG_WP_OFFSET 0x194 +#define IOCONFIG_MISO_OFFSET 0x198 +#define IOCONFIG_MOSI_OFFSET 0x19c +#define IOCONFIG_CS_OFFSET 0x1a0 + +#define SPIFI_ROM_TABLE_BASE_HIGH 0x1040 +#define SPIFI_ROM_TABLE_BASE_LOW 0x0118 + +code: + mov.w r8, r0 + sub sp, #0x84 + add r7, sp, #0x0 + /* Initialize SPIFI pins */ + mov.w r3, #IOCONFIG_BASE_LOW + movt r3, #IOCONFIG_BASE_HIGH + mov.w r2, #0xf3 + str.w r2, [r3, #IOCONFIG_SCK_OFFSET] + mov.w r3, #IOCONFIG_BASE_LOW + movt r3, #IOCONFIG_BASE_HIGH + mov.w r2, #IOCONFIG_BASE_LOW + movt r2, #IOCONFIG_BASE_HIGH + mov.w r1, #IOCONFIG_BASE_LOW + movt r1, #IOCONFIG_BASE_HIGH + mov.w r0, #IOCONFIG_BASE_LOW + movt r0, #IOCONFIG_BASE_HIGH + mov.w r4, #0xd3 + str.w r4, [r0, #IOCONFIG_MOSI_OFFSET] + mov r0, r4 + str.w r0, [r1, #IOCONFIG_MISO_OFFSET] + mov r1, r0 + str.w r1, [r2, #IOCONFIG_WP_OFFSET] + str.w r1, [r3, #IOCONFIG_HOLD_OFFSET] + mov.w r3, #IOCONFIG_BASE_LOW + movt r3, #IOCONFIG_BASE_HIGH + mov.w r2, #0x13 + str.w r2, [r3, #IOCONFIG_CS_OFFSET] + + /* Perform SPIFI init. See spifi_rom_api.h (in NXP lpc43xx driver package) for details */ + /* on initialization arguments. */ + movw r3, #SPIFI_ROM_TABLE_BASE_LOW /* The ROM API table is located @ 0x10400118, and */ + movt r3, #SPIFI_ROM_TABLE_BASE_HIGH /* the first pointer in the struct is to the init function. */ + ldr r3, [r3, #0x0] + ldr r4, [r3, #0x0] /* Grab the init function pointer from the table */ + /* Set up function arguments */ + movw r0, #0x3b4 + movt r0, #0x1000 /* Pointer to a SPIFI data struct that we don't care about */ + mov.w r1, #0x3 /* "csHigh". Not 100% sure what this does. */ + mov.w r2, #0xc0 /* The configuration word: S_RCVCLOCK | S_FULLCLK */ + mov.w r3, r8 /* SPIFI clock speed (12MHz) */ + blx r4 /* Call the init function */ + b done + +done: + bkpt #0 + + .end diff --git a/contrib/loaders/flash/lpcspifi_write.S b/contrib/loaders/flash/lpcspifi_write.S new file mode 100644 index 0000000000..4292a370d9 --- /dev/null +++ b/contrib/loaders/flash/lpcspifi_write.S @@ -0,0 +1,210 @@ +/*************************************************************************** + * Copyright (C) 2012 by George Harris * + * george@luminairecoffee.com * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + + .text + .syntax unified + .cpu cortex-m3 + .thumb + .thumb_func + +/* + * Params : + * r0 = workarea start, status (out) + * r1 = workarea end + * r2 = target address (offset from flash base) + * r3 = count (bytes) + * r4 = page size + * Clobbered: + * r7 - rp + * r8 - wp, tmp + * r9 - send/receive data + * r10 - temp + * r11 - current page end address + */ + +#define SSP_BASE_HIGH 0x4008 +#define SSP_BASE_LOW 0x3000 +#define SSP_CR0_OFFSET 0x00 +#define SSP_CR1_OFFSET 0x04 +#define SSP_DATA_OFFSET 0x08 +#define SSP_CPSR_OFFSET 0x10 +#define SSP_SR_OFFSET 0x0c + +#define SSP_CLOCK_BASE_HIGH 0x4005 +#define SSP_CLOCK_BASE_LOW 0x0000 +#define SSP_BRANCH_CLOCK_BASE_HIGH 0x4005 +#define SSP_BRANCH_CLOCK_BASE_LOW 0x2000 +#define SSP_BASE_CLOCK_OFFSET 0x94 +#define SSP_BRANCH_CLOCK_OFFSET 0x700 + +#define IOCONFIG_BASE_HIGH 0x4008 +#define IOCONFIG_BASE_LOW 0x6000 +#define IOCONFIG_SCK_OFFSET 0x18c +#define IOCONFIG_HOLD_OFFSET 0x190 +#define IOCONFIG_WP_OFFSET 0x194 +#define IOCONFIG_MISO_OFFSET 0x198 +#define IOCONFIG_MOSI_OFFSET 0x19c +#define IOCONFIG_CS_OFFSET 0x1a0 + +#define IO_BASE_HIGH 0x400f +#define IO_BASE_LOW 0x4000 +#define IO_CS_OFFSET 0xab +#define IODIR_BASE_HIGH 0x400f +#define IODIR_BASE_LOW 0x6000 +#define IO_CS_DIR_OFFSET 0x14 + + +setup: /* Initialize SSP pins and module */ + mov.w r10, #IOCONFIG_BASE_LOW + movt r10, #IOCONFIG_BASE_HIGH + mov.w r8, #0xea + str.w r8, [r10, #IOCONFIG_SCK_OFFSET] /* Configure SCK pin function */ + mov.w r8, #0x40 + str.w r8, [r10, #IOCONFIG_HOLD_OFFSET] /* Configure /HOLD pin function */ + mov.w r8, #0x40 + str.w r8, [r10, #IOCONFIG_WP_OFFSET] /* Configure /WP pin function */ + mov.w r8, #0xed + str.w r8, [r10, #IOCONFIG_MISO_OFFSET] /* Configure MISO pin function */ + mov.w r8, #0xed + str.w r8, [r10, #IOCONFIG_MOSI_OFFSET] /* Configure MOSI pin function */ + mov.w r8, #0x44 + str.w r8, [r10, #IOCONFIG_CS_OFFSET] /* Configure CS pin function */ + + mov.w r10, #IODIR_BASE_LOW + movt r10, #IODIR_BASE_HIGH + mov.w r8, #0x800 + str r8, [r10, #IO_CS_DIR_OFFSET] /* Set CS as output */ + mov.w r10, #IO_BASE_LOW + movt r10, #IO_BASE_HIGH + mov.w r8, #0xff + str.w r8, [r10, #IO_CS_OFFSET] /* Set CS high */ + + mov.w r10, #SSP_CLOCK_BASE_LOW + movt r10, #SSP_CLOCK_BASE_HIGH + mov.w r8, #0x0000 + movt r8, #0x0100 + str.w r8, [r10, #SSP_BASE_CLOCK_OFFSET] /* Configure SSP0 base clock (use 12 MHz IRC) */ + + mov.w r10, #SSP_BRANCH_CLOCK_BASE_LOW + movt r10, #SSP_BRANCH_CLOCK_BASE_HIGH + mov.w r8, #0x01 + str.w r8, [r10, #SSP_BRANCH_CLOCK_OFFSET] /* Configure (enable) SSP0 branch clock */ + + mov.w r10, #SSP_BASE_LOW + movt r10, #SSP_BASE_HIGH + mov.w r8, #0x07 + str.w r8, [r10, #SSP_CR0_OFFSET] /* Set clock postscale */ + mov.w r8, #0x02 + str.w r8, [r10, #SSP_CPSR_OFFSET] /* Set clock prescale */ + str.w r8, [r10, #SSP_CR1_OFFSET] /* Enable SSP in SPI mode */ + + mov.w r11, #0x00 +find_next_page_boundary: + add r11, r4 /* Increment to the next page */ + cmp r11, r2 + /* If we have not reached the next page boundary after the target address, keep going */ + bls find_next_page_boundary +write_enable: + bl cs_down + mov.w r9, #0x06 /* Send the write enable command */ + bl write_data + bl cs_up + + bl cs_down + mov.w r9, #0x05 /* Get status register */ + bl write_data + mov.w r9, #0x00 /* Dummy data to clock in status */ + bl write_data + bl cs_up + + tst r9, #0x02 /* If the WE bit isn't set, we have a problem. */ + beq error +page_program: + bl cs_down + mov.w r9, #0x02 /* Send the page program command */ + bl write_data +write_address: + lsr r9, r2, #16 /* Send the current 24-bit write address, MSB first */ + bl write_data + lsr r9, r2, #8 + bl write_data + mov.w r9, r2 + bl write_data +wait_fifo: + ldr r8, [r0] /* read the write pointer */ + cmp r8, #0 /* if it's zero, we're gonzo */ + beq exit + ldr r7, [r0, #4] /* read the read pointer */ + cmp r7, r8 /* wait until they are not equal */ + beq wait_fifo +write: + ldrb r9, [r7], #0x01 /* Load one byte from the FIFO, increment the read pointer by 1 */ + bl write_data /* send the byte to the flash chip */ + + cmp r7, r1 /* wrap the read pointer if it is at the end */ + it cs + addcs r7, r0, #8 /* skip loader args */ + str r7, [r0, #4] /* store the new read pointer */ + subs r3, r3, #1 /* decrement count */ + cbz r3, exit /* Exit if we have written everything */ + + add r2, #1 /* Increment flash address by 1 */ + cmp r11, r2 /* See if we have reached the end of a page */ + bne wait_fifo /* If not, keep writing bytes */ + bl cs_up /* Otherwise, end the command and keep going w/ the next page */ + add r11, r4 /* Move up the end-of-page address by the page size*/ +wait_flash_busy: /* Wait for the flash to finish the previous page write */ + bl cs_down + mov.w r9, #0x05 /* Get status register */ + bl write_data + mov.w r9, #0x00 /* Dummy data to clock in status */ + bl write_data + bl cs_up + tst r9, #0x01 /* If it isn't done, keep waiting */ + bne wait_flash_busy + b write_enable /* If it is done, start a new page write */ +write_data: /* Send/receive 1 byte of data over SSP */ + mov.w r10, #SSP_BASE_LOW + movt r10, #SSP_BASE_HIGH + str.w r9, [r10, #SSP_DATA_OFFSET] /* Write supplied data to the SSP data reg */ +wait_transmit: + ldr r9, [r10, #SSP_SR_OFFSET] /* Check SSP status */ + tst r9, #0x0010 /* Check if BSY bit is set */ + bne wait_transmit /* If still transmitting, keep waiting */ + ldr r9, [r10, #SSP_DATA_OFFSET] /* Load received data */ + bx lr /* Exit subroutine */ +cs_up: + mov.w r8, #0xff + b cs_write +cs_down: + mov.w r8, #0x0000 +cs_write: + mov.w r10, #IO_BASE_LOW + movt r10, #IO_BASE_HIGH + str.w r8, [r10, #IO_CS_OFFSET] + bx lr +error: + movs r0, #0 + str r0, [r2, #4] /* set rp = 0 on error */ +exit: + mov r0, r6 + bkpt #0x00 + + .end diff --git a/doc/openocd.texi b/doc/openocd.texi index 1604e9c6c2..e6635576e4 100644 --- a/doc/openocd.texi +++ b/doc/openocd.texi @@ -4652,6 +4652,30 @@ flash bank $_FLASHNAME cfi 0x00000000 0x02000000 2 4 $_TARGETNAME @c "cfi part_id" disabled @end deffn +@deffn {Flash Driver} lpcspifi +@cindex NXP SPI Flash Interface +@cindex SPIFI +@cindex lpcspifi +NXP's LPC43xx and LPC18xx families include a proprietary SPI +Flash Interface (SPIFI) peripheral that can drive and provide +memory mapped access to external SPI flash devices. + +The lpcspifi driver initializes this interface and provides +program and erase functionality for these serial flash devices. +Use of this driver @b{requires} a working area of at least 1kB +to be configured on the target device; more than this will +significantly reduce flash programming times. + +The setup command only requires the @var{base} parameter. All +other parameters are ignored, and the flash size and layout +are configured by the driver. + +@example +flash bank $_FLASHNAME lpcspifi 0x14000000 0 0 0 $_TARGETNAME +@end example + +@end deffn + @deffn {Flash Driver} stmsmi @cindex STMicroelectronics Serial Memory Interface @cindex SMI diff --git a/src/flash/nor/Makefile.am b/src/flash/nor/Makefile.am index d26aa2037d..c344cd01bd 100644 --- a/src/flash/nor/Makefile.am +++ b/src/flash/nor/Makefile.am @@ -19,6 +19,7 @@ NOR_DRIVERS = \ lpc2000.c \ lpc288x.c \ lpc2900.c \ + lpcspifi.c \ non_cfi.c \ ocl.c \ pic32mx.c \ diff --git a/src/flash/nor/drivers.c b/src/flash/nor/drivers.c index 69c3387e7f..61a5d3394b 100644 --- a/src/flash/nor/drivers.c +++ b/src/flash/nor/drivers.c @@ -25,6 +25,7 @@ extern struct flash_driver lpc2000_flash; extern struct flash_driver lpc288x_flash; extern struct flash_driver lpc2900_flash; +extern struct flash_driver lpcspifi_flash; extern struct flash_driver cfi_flash; extern struct flash_driver at91sam3_flash; extern struct flash_driver at91sam4_flash; @@ -57,6 +58,7 @@ static struct flash_driver *flash_drivers[] = { &lpc2000_flash, &lpc288x_flash, &lpc2900_flash, + &lpcspifi_flash, &cfi_flash, &at91sam7_flash, &at91sam3_flash, diff --git a/src/flash/nor/lpcspifi.c b/src/flash/nor/lpcspifi.c new file mode 100644 index 0000000000..757d6d1953 --- /dev/null +++ b/src/flash/nor/lpcspifi.c @@ -0,0 +1,968 @@ +/*************************************************************************** + * Copyright (C) 2012 by George Harris * + * george@luminairecoffee.com * + * * + * 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, write to the * + * Free Software Foundation, Inc., * + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * + ***************************************************************************/ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "imp.h" +#include "spi.h" +#include +#include +#include +#include + +/* Offsets from ssp_base into config & data registers */ +#define SSP_CR0 (0x00) /* Control register 0 */ +#define SSP_CR1 (0x04) /* Control register 1 */ +#define SSP_DATA (0x08) /* Data register (TX and RX) */ +#define SSP_SR (0x0C) /* Status register */ +#define SSP_CPSR (0x10) /* Clock prescale register */ + +/* Status register fields */ +#define SSP_BSY (0x00000010) + +/* Timeout in ms */ +#define SSP_CMD_TIMEOUT (100) +#define SSP_PROBE_TIMEOUT (100) +#define SSP_MAX_TIMEOUT (3000) + +struct lpcspifi_flash_bank { + int probed; + uint32_t ssp_base; + uint32_t io_base; + uint32_t ioconfig_base; + uint32_t bank_num; + uint32_t max_spi_clock_mhz; + struct flash_device *dev; +}; + +struct lpcspifi_target { + char *name; + uint32_t tap_idcode; + uint32_t spifi_base; + uint32_t ssp_base; + uint32_t io_base; + uint32_t ioconfig_base; /* base address for the port word pin registers */ +}; + +static struct lpcspifi_target target_devices[] = { + /* name, tap_idcode, spifi_base, ssp_base, io_base, ioconfig_base */ + { "LPC43xx/18xx", 0x4ba00477, 0x14000000, 0x40083000, 0x400F4000, 0x40086000 }, + { NULL, 0, 0, 0, 0, 0 } +}; + +/* flash_bank lpcspifi + */ +FLASH_BANK_COMMAND_HANDLER(lpcspifi_flash_bank_command) +{ + struct lpcspifi_flash_bank *lpcspifi_info; + + if (CMD_ARGC < 6) + return ERROR_COMMAND_SYNTAX_ERROR; + + lpcspifi_info = malloc(sizeof(struct lpcspifi_flash_bank)); + if (lpcspifi_info == NULL) { + LOG_ERROR("not enough memory"); + return ERROR_FAIL; + } + + bank->driver_priv = lpcspifi_info; + lpcspifi_info->probed = 0; + + return ERROR_OK; +} + +static inline int ioconfig_write_reg(struct target *target, uint32_t ioconfig_base, uint32_t offset, uint32_t value) +{ + return target_write_u32(target, ioconfig_base + offset, value); +} + +static inline int ssp_write_reg(struct target *target, uint32_t ssp_base, uint32_t offset, uint32_t value) +{ + return target_write_u32(target, ssp_base + offset, value); +} + +static inline int io_write_reg(struct target *target, uint32_t io_base, uint32_t offset, uint32_t value) +{ + return target_write_u32(target, io_base + offset, value); +} + +static inline int ssp_read_reg(struct target *target, uint32_t ssp_base, uint32_t offset, uint32_t *value) +{ + return target_read_u32(target, ssp_base + offset, value); +} + +static int ssp_setcs(struct target *target, uint32_t io_base, unsigned int value) +{ + return io_write_reg(target, io_base, 0x12ac, value ? 0xffffffff : 0x00000000); +} + +/* Poll the SSP busy flag. When this comes back as 0, the transfer is complete + * and the controller is idle. */ +static int poll_ssp_busy(struct target *target, uint32_t ssp_base, int timeout) +{ + long long endtime; + uint32_t value; + int retval; + + retval = ssp_read_reg(target, ssp_base, SSP_SR, &value); + if ((retval == ERROR_OK) && (value & SSP_BSY) == 0) + return ERROR_OK; + else if (retval != ERROR_OK) + return retval; + + endtime = timeval_ms() + timeout; + do { + alive_sleep(1); + retval = ssp_read_reg(target, ssp_base, SSP_SR, &value); + if ((retval == ERROR_OK) && (value & SSP_BSY) == 0) + return ERROR_OK; + else if (retval != ERROR_OK) + return retval; + } while (timeval_ms() < endtime); + + LOG_ERROR("Timeout while polling BSY"); + return ERROR_FLASH_OPERATION_FAILED; +} + +/* Un-initialize the ssp module and initialize the SPIFI module */ +static int lpcspifi_set_hw_mode(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + struct armv7m_algorithm armv7m_info; + struct working_area *spifi_init_algorithm; + struct reg_param reg_params[1]; + int retval = ERROR_OK; + + LOG_DEBUG("Uninitializing LPC43xx SSP"); + /* Turn off the SSP module */ + retval = ssp_write_reg(target, ssp_base, SSP_CR1, 0x00000000); + if (retval != ERROR_OK) + return retval; + + /* see contrib/loaders/flash/lpcspifi_init.S for src */ + static const uint8_t spifi_init_code[] = { + 0x4f, 0xea, 0x00, 0x08, 0xa1, 0xb0, 0x00, 0xaf, + 0x4f, 0xf4, 0xc0, 0x43, 0xc4, 0xf2, 0x08, 0x03, + 0x4f, 0xf0, 0xf3, 0x02, 0xc3, 0xf8, 0x8c, 0x21, + 0x4f, 0xf4, 0xc0, 0x43, 0xc4, 0xf2, 0x08, 0x03, + 0x4f, 0xf4, 0xc0, 0x42, 0xc4, 0xf2, 0x08, 0x02, + 0x4f, 0xf4, 0xc0, 0x41, 0xc4, 0xf2, 0x08, 0x01, + 0x4f, 0xf4, 0xc0, 0x40, 0xc4, 0xf2, 0x08, 0x00, + 0x4f, 0xf0, 0xd3, 0x04, 0xc0, 0xf8, 0x9c, 0x41, + 0x20, 0x46, 0xc1, 0xf8, 0x98, 0x01, 0x01, 0x46, + 0xc2, 0xf8, 0x94, 0x11, 0xc3, 0xf8, 0x90, 0x11, + 0x4f, 0xf4, 0xc0, 0x43, 0xc4, 0xf2, 0x08, 0x03, + 0x4f, 0xf0, 0x13, 0x02, 0xc3, 0xf8, 0xa0, 0x21, + 0x40, 0xf2, 0x18, 0x13, 0xc1, 0xf2, 0x40, 0x03, + 0x1b, 0x68, 0x1c, 0x68, 0x40, 0xf2, 0xb4, 0x30, + 0xc1, 0xf2, 0x00, 0x00, 0x4f, 0xf0, 0x03, 0x01, + 0x4f, 0xf0, 0xc0, 0x02, 0x4f, 0xea, 0x08, 0x03, + 0xa0, 0x47, 0x00, 0xf0, 0x00, 0xb8, 0x00, 0xbe + }; + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARMV7M_MODE_ANY; + + + LOG_DEBUG("Allocating working area for SPIFI init algorithm"); + /* Get memory for spifi initialization algorithm */ + retval = target_alloc_working_area(target, sizeof(spifi_init_code), + &spifi_init_algorithm); + if (retval != ERROR_OK) { + LOG_ERROR("Insufficient working area to initialize SPIFI "\ + "module. You must allocate at least %zdB of working "\ + "area in order to use this driver.", + sizeof(spifi_init_code) + ); + + return retval; + } + + LOG_DEBUG("Writing algorithm to working area at 0x%08x", + spifi_init_algorithm->address); + /* Write algorithm to working area */ + retval = target_write_buffer(target, + spifi_init_algorithm->address, + sizeof(spifi_init_code), + spifi_init_code + ); + + if (retval != ERROR_OK) { + target_free_working_area(target, spifi_init_algorithm); + return retval; + } + + init_reg_param(®_params[0], "r0", 32, PARAM_OUT); /* spifi clk speed */ + + /* For now, the algorithm will set up the SPIFI module + * @ the IRC clock speed. In the future, it could be made + * a bit smarter to use other clock sources if the user has + * already configured them in order to speed up memory- + * mapped reads. */ + buf_set_u32(reg_params[0].value, 0, 32, 12); + + /* Run the algorithm */ + LOG_DEBUG("Running SPIFI init algorithm"); + retval = target_run_algorithm(target, 0 , NULL, 1, reg_params, + spifi_init_algorithm->address, + spifi_init_algorithm->address + sizeof(spifi_init_code) - 2, + 1000, &armv7m_info); + + if (retval != ERROR_OK) + LOG_ERROR("Error executing SPIFI init algorithm"); + + target_free_working_area(target, spifi_init_algorithm); + + destroy_reg_param(®_params[0]); + + return retval; +} + +/* Initialize the ssp module */ +static int lpcspifi_set_sw_mode(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + uint32_t io_base = lpcspifi_info->io_base; + uint32_t ioconfig_base = lpcspifi_info->ioconfig_base; + int retval = ERROR_OK; + + /* Re-initialize SPIFI. There are a couple of errata on this, so this makes + sure that nothing's in an unhappy state. */ + retval = lpcspifi_set_hw_mode(bank); + + /* If we couldn't initialize hardware mode, don't even bother continuing */ + if (retval != ERROR_OK) + return retval; + + /* Initialize the pins */ + retval = ioconfig_write_reg(target, ioconfig_base, 0x194, 0x00000040); + if (retval == ERROR_OK) + retval = ioconfig_write_reg(target, ioconfig_base, 0x1a0, 0x00000044); + if (retval == ERROR_OK) + retval = ioconfig_write_reg(target, ioconfig_base, 0x190, 0x00000040); + if (retval == ERROR_OK) + retval = ioconfig_write_reg(target, ioconfig_base, 0x19c, 0x000000ed); + if (retval == ERROR_OK) + retval = ioconfig_write_reg(target, ioconfig_base, 0x198, 0x000000ed); + if (retval == ERROR_OK) + retval = ioconfig_write_reg(target, ioconfig_base, 0x18c, 0x000000ea); + + /* Set CS high & as an output */ + if (retval == ERROR_OK) + retval = io_write_reg(target, io_base, 0x12ac, 0xffffffff); + if (retval == ERROR_OK) + retval = io_write_reg(target, io_base, 0x2014, 0x00000800); + + /* Initialize the module */ + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_CR0, 0x00000007); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_CR1, 0x00000000); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_CPSR, 0x00000008); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_CR1, 0x00000002); + + /* If something didn't work out, attempt to return SPIFI to HW mode */ + if (retval != ERROR_OK) + lpcspifi_set_hw_mode(bank); + + return retval; +} + +/* 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 lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + uint32_t io_base = lpcspifi_info->io_base; + uint32_t value; + int retval = ERROR_OK; + + retval = ssp_setcs(target, io_base, 0); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, SPIFLASH_READ_STATUS); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + /* Dummy write to clock in the register */ + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, 0x00); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_setcs(target, io_base, 1); + + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + *status = value; + + return retval; +} + +/* check for BSY bit in flash 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(1); + } while (timeval_ms() < endtime); + + LOG_ERROR("timeout waiting for flash to finish write/erase operation"); + return ERROR_FAIL; +} + +/* Send "write enable" command to SPI flash chip. */ +static int lpcspifi_write_enable(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + uint32_t io_base = lpcspifi_info->io_base; + uint32_t status, value; + int retval = ERROR_OK; + + retval = ssp_setcs(target, io_base, 0); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, SPIFLASH_WRITE_ENABLE); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + retval = ssp_setcs(target, io_base, 1); + + /* read flash status register */ + if (retval == ERROR_OK) + 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 retval; +} + +static int lpcspifi_bulk_erase(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + uint32_t io_base = lpcspifi_info->io_base; + uint32_t value; + int retval = ERROR_OK; + + retval = lpcspifi_set_sw_mode(bank); + + if (retval == ERROR_OK) + retval = lpcspifi_write_enable(bank); + + /* send SPI command "bulk erase" */ + if (retval == ERROR_OK) + ssp_setcs(target, io_base, 0); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, lpcspifi_info->dev->chip_erase_cmd); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + retval = ssp_setcs(target, io_base, 1); + + /* poll flash BSY for self-timed bulk erase */ + if (retval == ERROR_OK) + retval = wait_till_ready(bank, bank->num_sectors*SSP_MAX_TIMEOUT); + + return retval; +} + +static int lpcspifi_erase(struct flash_bank *bank, int first, int last) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + struct reg_param reg_params[4]; + struct armv7m_algorithm armv7m_info; + struct working_area *erase_algorithm; + int retval = ERROR_OK; + int sector; + + LOG_DEBUG("erase from sector %d to sector %d", first, last); + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if ((first < 0) || (last < first) || (last >= bank->num_sectors)) { + LOG_ERROR("Flash sector invalid"); + return ERROR_FLASH_SECTOR_INVALID; + } + + if (!(lpcspifi_info->probed)) { + LOG_ERROR("Flash bank not probed"); + return ERROR_FLASH_BANK_NOT_PROBED; + } + + for (sector = first; sector <= last; sector++) { + if (bank->sectors[sector].is_protected) { + LOG_ERROR("Flash sector %d protected", sector); + return ERROR_FAIL; + } + } + + /* If we're erasing the entire chip and the flash supports + * it, use a bulk erase instead of going sector-by-sector. */ + if (first == 0 && last == (bank->num_sectors - 1) + && lpcspifi_info->dev->chip_erase_cmd != lpcspifi_info->dev->erase_cmd) { + LOG_DEBUG("Chip supports the bulk erase command."\ + " Will use bulk erase instead of sector-by-sector erase."); + retval = lpcspifi_bulk_erase(bank); + + if (retval == ERROR_OK) { + retval = lpcspifi_set_hw_mode(bank); + return retval; + } else + LOG_WARNING("Bulk flash erase failed. Falling back to sector-by-sector erase."); + } + + retval = lpcspifi_set_hw_mode(bank); + if (retval != ERROR_OK) + return retval; + + /* see contrib/loaders/flash/lpcspifi_erase.S for src */ + static const uint8_t lpcspifi_flash_erase_code[] = { + 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x08, 0x0a, + 0x4f, 0xf0, 0xea, 0x08, 0xca, 0xf8, 0x8c, 0x81, + 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x90, 0x81, + 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x94, 0x81, + 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x98, 0x81, + 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x9c, 0x81, + 0x4f, 0xf0, 0x44, 0x08, 0xca, 0xf8, 0xa0, 0x81, + 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, + 0x4f, 0xf4, 0x00, 0x68, 0xca, 0xf8, 0x14, 0x80, + 0x4f, 0xf4, 0x80, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, + 0x4f, 0xf0, 0xff, 0x08, 0xca, 0xf8, 0xab, 0x80, + 0x4f, 0xf0, 0x00, 0x0a, 0xc4, 0xf2, 0x05, 0x0a, + 0x4f, 0xf0, 0x00, 0x08, 0xc0, 0xf2, 0x00, 0x18, + 0xca, 0xf8, 0x94, 0x80, 0x4f, 0xf4, 0x00, 0x5a, + 0xc4, 0xf2, 0x05, 0x0a, 0x4f, 0xf0, 0x01, 0x08, + 0xca, 0xf8, 0x00, 0x87, 0x4f, 0xf4, 0x40, 0x5a, + 0xc4, 0xf2, 0x08, 0x0a, 0x4f, 0xf0, 0x07, 0x08, + 0xca, 0xf8, 0x00, 0x80, 0x4f, 0xf0, 0x02, 0x08, + 0xca, 0xf8, 0x10, 0x80, 0xca, 0xf8, 0x04, 0x80, + 0x00, 0xf0, 0x52, 0xf8, 0x4f, 0xf0, 0x06, 0x09, + 0x00, 0xf0, 0x3b, 0xf8, 0x00, 0xf0, 0x48, 0xf8, + 0x00, 0xf0, 0x4a, 0xf8, 0x4f, 0xf0, 0x05, 0x09, + 0x00, 0xf0, 0x33, 0xf8, 0x4f, 0xf0, 0x00, 0x09, + 0x00, 0xf0, 0x2f, 0xf8, 0x00, 0xf0, 0x3c, 0xf8, + 0x19, 0xf0, 0x02, 0x0f, 0x00, 0xf0, 0x45, 0x80, + 0x00, 0xf0, 0x3a, 0xf8, 0x4f, 0xea, 0x02, 0x09, + 0x00, 0xf0, 0x23, 0xf8, 0x4f, 0xea, 0x10, 0x49, + 0x00, 0xf0, 0x1f, 0xf8, 0x4f, 0xea, 0x10, 0x29, + 0x00, 0xf0, 0x1b, 0xf8, 0x4f, 0xea, 0x00, 0x09, + 0x00, 0xf0, 0x17, 0xf8, 0x00, 0xf0, 0x24, 0xf8, + 0x00, 0xf0, 0x26, 0xf8, 0x4f, 0xf0, 0x05, 0x09, + 0x00, 0xf0, 0x0f, 0xf8, 0x4f, 0xf0, 0x00, 0x09, + 0x00, 0xf0, 0x0b, 0xf8, 0x00, 0xf0, 0x18, 0xf8, + 0x19, 0xf0, 0x01, 0x0f, 0x7f, 0xf4, 0xf0, 0xaf, + 0x01, 0x39, 0xf9, 0xb1, 0x18, 0x44, 0xff, 0xf7, + 0xbf, 0xbf, 0x4f, 0xf4, 0x40, 0x5a, 0xc4, 0xf2, + 0x08, 0x0a, 0xca, 0xf8, 0x08, 0x90, 0xda, 0xf8, + 0x0c, 0x90, 0x19, 0xf0, 0x10, 0x0f, 0x7f, 0xf4, + 0xfa, 0xaf, 0xda, 0xf8, 0x08, 0x90, 0x70, 0x47, + 0x4f, 0xf0, 0xff, 0x08, 0x00, 0xf0, 0x02, 0xb8, + 0x4f, 0xf0, 0x00, 0x08, 0x4f, 0xf4, 0x80, 0x4a, + 0xc4, 0xf2, 0x0f, 0x0a, 0xca, 0xf8, 0xab, 0x80, + 0x70, 0x47, 0x00, 0x20, 0x00, 0xbe, 0xff, 0xff + }; + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARMV7M_MODE_ANY; + + + /* Get memory for spifi initialization algorithm */ + retval = target_alloc_working_area(target, sizeof(lpcspifi_flash_erase_code), + &erase_algorithm); + if (retval != ERROR_OK) { + LOG_ERROR("Insufficient working area. You must configure a working"\ + " area of at least %zdB in order to erase SPIFI flash.", + sizeof(lpcspifi_flash_erase_code)); + return retval; + } + + /* Write algorithm to working area */ + retval = target_write_buffer(target, erase_algorithm->address, + sizeof(lpcspifi_flash_erase_code), lpcspifi_flash_erase_code); + if (retval != ERROR_OK) { + target_free_working_area(target, erase_algorithm); + return retval; + } + + init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* Start address */ + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* Sector count */ + init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* Erase command */ + init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* Sector size */ + + buf_set_u32(reg_params[0].value, 0, 32, bank->sectors[first].offset); + buf_set_u32(reg_params[1].value, 0, 32, last - first + 1); + buf_set_u32(reg_params[2].value, 0, 32, lpcspifi_info->dev->erase_cmd); + buf_set_u32(reg_params[3].value, 0, 32, bank->sectors[first].size); + + /* Run the algorithm */ + retval = target_run_algorithm(target, 0 , NULL, 4, reg_params, + erase_algorithm->address, + erase_algorithm->address + sizeof(lpcspifi_flash_erase_code) - 4, + 3000*(last - first + 1), &armv7m_info); + + if (retval != ERROR_OK) + LOG_ERROR("Error executing flash erase algorithm"); + + target_free_working_area(target, erase_algorithm); + + destroy_reg_param(®_params[0]); + destroy_reg_param(®_params[1]); + destroy_reg_param(®_params[2]); + destroy_reg_param(®_params[3]); + + retval = lpcspifi_set_hw_mode(bank); + + return retval; +} + +static int lpcspifi_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; +} + +static int lpcspifi_write(struct flash_bank *bank, uint8_t *buffer, + uint32_t offset, uint32_t count) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t page_size, fifo_size; + struct working_area *fifo; + struct reg_param reg_params[5]; + struct armv7m_algorithm armv7m_info; + struct working_area *write_algorithm; + int sector; + int retval = ERROR_OK; + + LOG_DEBUG("offset=0x%08" PRIx32 " count=0x%08" PRIx32, + offset, count); + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + if (offset + count > lpcspifi_info->dev->size_in_bytes) { + LOG_WARNING("Writes past end of flash. Extra data discarded."); + count = lpcspifi_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) + && bank->sectors[sector].is_protected) { + LOG_ERROR("Flash sector %d protected", sector); + return ERROR_FAIL; + } + } + + page_size = lpcspifi_info->dev->pagesize; + + retval = lpcspifi_set_hw_mode(bank); + if (retval != ERROR_OK) + return retval; + + /* see contrib/loaders/flash/lpcspifi_write.S for src */ + static const uint8_t lpcspifi_flash_write_code[] = { + 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x08, 0x0a, + 0x4f, 0xf0, 0xea, 0x08, 0xca, 0xf8, 0x8c, 0x81, + 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x90, 0x81, + 0x4f, 0xf0, 0x40, 0x08, 0xca, 0xf8, 0x94, 0x81, + 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x98, 0x81, + 0x4f, 0xf0, 0xed, 0x08, 0xca, 0xf8, 0x9c, 0x81, + 0x4f, 0xf0, 0x44, 0x08, 0xca, 0xf8, 0xa0, 0x81, + 0x4f, 0xf4, 0xc0, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, + 0x4f, 0xf4, 0x00, 0x68, 0xca, 0xf8, 0x14, 0x80, + 0x4f, 0xf4, 0x80, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, + 0x4f, 0xf0, 0xff, 0x08, 0xca, 0xf8, 0xab, 0x80, + 0x4f, 0xf0, 0x00, 0x0a, 0xc4, 0xf2, 0x05, 0x0a, + 0x4f, 0xf0, 0x00, 0x08, 0xc0, 0xf2, 0x00, 0x18, + 0xca, 0xf8, 0x94, 0x80, 0x4f, 0xf4, 0x00, 0x5a, + 0xc4, 0xf2, 0x05, 0x0a, 0x4f, 0xf0, 0x01, 0x08, + 0xca, 0xf8, 0x00, 0x87, 0x4f, 0xf4, 0x40, 0x5a, + 0xc4, 0xf2, 0x08, 0x0a, 0x4f, 0xf0, 0x07, 0x08, + 0xca, 0xf8, 0x00, 0x80, 0x4f, 0xf0, 0x02, 0x08, + 0xca, 0xf8, 0x10, 0x80, 0xca, 0xf8, 0x04, 0x80, + 0x4f, 0xf0, 0x00, 0x0b, 0xa3, 0x44, 0x93, 0x45, + 0x7f, 0xf6, 0xfc, 0xaf, 0x00, 0xf0, 0x6a, 0xf8, + 0x4f, 0xf0, 0x06, 0x09, 0x00, 0xf0, 0x53, 0xf8, + 0x00, 0xf0, 0x60, 0xf8, 0x00, 0xf0, 0x62, 0xf8, + 0x4f, 0xf0, 0x05, 0x09, 0x00, 0xf0, 0x4b, 0xf8, + 0x4f, 0xf0, 0x00, 0x09, 0x00, 0xf0, 0x47, 0xf8, + 0x00, 0xf0, 0x54, 0xf8, 0x19, 0xf0, 0x02, 0x0f, + 0x00, 0xf0, 0x5d, 0x80, 0x00, 0xf0, 0x52, 0xf8, + 0x4f, 0xf0, 0x02, 0x09, 0x00, 0xf0, 0x3b, 0xf8, + 0x4f, 0xea, 0x12, 0x49, 0x00, 0xf0, 0x37, 0xf8, + 0x4f, 0xea, 0x12, 0x29, 0x00, 0xf0, 0x33, 0xf8, + 0x4f, 0xea, 0x02, 0x09, 0x00, 0xf0, 0x2f, 0xf8, + 0xd0, 0xf8, 0x00, 0x80, 0xb8, 0xf1, 0x00, 0x0f, + 0x00, 0xf0, 0x47, 0x80, 0x47, 0x68, 0x47, 0x45, + 0x3f, 0xf4, 0xf6, 0xaf, 0x17, 0xf8, 0x01, 0x9b, + 0x00, 0xf0, 0x21, 0xf8, 0x8f, 0x42, 0x28, 0xbf, + 0x00, 0xf1, 0x08, 0x07, 0x47, 0x60, 0x01, 0x3b, + 0xbb, 0xb3, 0x02, 0xf1, 0x01, 0x02, 0x93, 0x45, + 0x7f, 0xf4, 0xe6, 0xaf, 0x00, 0xf0, 0x22, 0xf8, + 0xa3, 0x44, 0x00, 0xf0, 0x23, 0xf8, 0x4f, 0xf0, + 0x05, 0x09, 0x00, 0xf0, 0x0c, 0xf8, 0x4f, 0xf0, + 0x00, 0x09, 0x00, 0xf0, 0x08, 0xf8, 0x00, 0xf0, + 0x15, 0xf8, 0x19, 0xf0, 0x01, 0x0f, 0x7f, 0xf4, + 0xf0, 0xaf, 0xff, 0xf7, 0xa7, 0xbf, 0x4f, 0xf4, + 0x40, 0x5a, 0xc4, 0xf2, 0x08, 0x0a, 0xca, 0xf8, + 0x08, 0x90, 0xda, 0xf8, 0x0c, 0x90, 0x19, 0xf0, + 0x10, 0x0f, 0x7f, 0xf4, 0xfa, 0xaf, 0xda, 0xf8, + 0x08, 0x90, 0x70, 0x47, 0x4f, 0xf0, 0xff, 0x08, + 0x00, 0xf0, 0x02, 0xb8, 0x4f, 0xf0, 0x00, 0x08, + 0x4f, 0xf4, 0x80, 0x4a, 0xc4, 0xf2, 0x0f, 0x0a, + 0xca, 0xf8, 0xab, 0x80, 0x70, 0x47, 0x00, 0x20, + 0x50, 0x60, 0x30, 0x46, 0x00, 0xbe, 0xff, 0xff + }; + + if (target_alloc_working_area(target, sizeof(lpcspifi_flash_write_code), + &write_algorithm) != ERROR_OK) { + LOG_ERROR("Insufficient working area. You must configure"\ + " a working area > %zdB in order to write to SPIFI flash.", + sizeof(lpcspifi_flash_write_code)); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + }; + + retval = target_write_buffer(target, write_algorithm->address, + sizeof(lpcspifi_flash_write_code), + lpcspifi_flash_write_code); + if (retval != ERROR_OK) { + target_free_working_area(target, write_algorithm); + return retval; + } + + /* FIFO allocation */ + fifo_size = target_get_working_area_avail(target); + + if (fifo_size == 0) { + /* if we already allocated the writing code but failed to get fifo + * space, free the algorithm */ + target_free_working_area(target, write_algorithm); + + LOG_ERROR("Insufficient working area. Please allocate at least"\ + " %zdB of working area to enable flash writes.", + sizeof(lpcspifi_flash_write_code) + 1 + ); + + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + } else if (fifo_size < page_size) + LOG_WARNING("Working area size is limited; flash writes may be"\ + " slow. Increase working area size to at least %zdB"\ + " to reduce write times.", + sizeof(lpcspifi_flash_write_code) + page_size + ); + else if (fifo_size > 0x2000) /* Beyond this point, we start to get diminishing returns */ + fifo_size = 0x2000; + + if (target_alloc_working_area(target, fifo_size, &fifo) != ERROR_OK) { + target_free_working_area(target, write_algorithm); + return ERROR_TARGET_RESOURCE_NOT_AVAILABLE; + }; + + armv7m_info.common_magic = ARMV7M_COMMON_MAGIC; + armv7m_info.core_mode = ARMV7M_MODE_ANY; + + init_reg_param(®_params[0], "r0", 32, PARAM_IN_OUT); /* buffer start, status (out) */ + init_reg_param(®_params[1], "r1", 32, PARAM_OUT); /* buffer end */ + init_reg_param(®_params[2], "r2", 32, PARAM_OUT); /* target address */ + init_reg_param(®_params[3], "r3", 32, PARAM_OUT); /* count (halfword-16bit) */ + init_reg_param(®_params[4], "r4", 32, PARAM_OUT); /* page size */ + + buf_set_u32(reg_params[0].value, 0, 32, fifo->address); + buf_set_u32(reg_params[1].value, 0, 32, fifo->address + fifo->size); + buf_set_u32(reg_params[2].value, 0, 32, offset); + buf_set_u32(reg_params[3].value, 0, 32, count); + buf_set_u32(reg_params[4].value, 0, 32, page_size); + + retval = target_run_flash_async_algorithm(target, buffer, count, 1, + 0, NULL, + 5, reg_params, + fifo->address, fifo->size, + write_algorithm->address, 0, + &armv7m_info + ); + + if (retval != ERROR_OK) + LOG_ERROR("Error executing flash write algorithm"); + + target_free_working_area(target, fifo); + target_free_working_area(target, write_algorithm); + + 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]); + + /* Switch to HW mode before return to prompt */ + retval = lpcspifi_set_hw_mode(bank); + return retval; +} + +/* Return ID of flash device */ +/* On exit, SW mode is kept */ +static int lpcspifi_read_flash_id(struct flash_bank *bank, uint32_t *id) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base = lpcspifi_info->ssp_base; + uint32_t io_base = lpcspifi_info->io_base; + uint32_t value; + int retval; + + if (target->state != TARGET_HALTED) { + LOG_ERROR("Target not halted"); + return ERROR_TARGET_NOT_HALTED; + } + + LOG_DEBUG("Getting ID"); + retval = lpcspifi_set_sw_mode(bank); + if (retval != ERROR_OK) + return retval; + + /* poll WIP */ + if (retval == ERROR_OK) + retval = wait_till_ready(bank, SSP_PROBE_TIMEOUT); + + /* Send SPI command "read ID" */ + if (retval == ERROR_OK) + retval = ssp_setcs(target, io_base, 0); + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, SPIFLASH_READ_ID); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + + /* Dummy write to clock in data */ + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, 0x00); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + ((uint8_t *)id)[0] = value; + + /* Dummy write to clock in data */ + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, 0x00); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + ((uint8_t *)id)[1] = value; + + /* Dummy write to clock in data */ + if (retval == ERROR_OK) + retval = ssp_write_reg(target, ssp_base, SSP_DATA, 0x00); + if (retval == ERROR_OK) + retval = poll_ssp_busy(target, ssp_base, SSP_CMD_TIMEOUT); + if (retval == ERROR_OK) + retval = ssp_read_reg(target, ssp_base, SSP_DATA, &value); + if (retval == ERROR_OK) + ((uint8_t *)id)[2] = value; + + if (retval == ERROR_OK) + retval = ssp_setcs(target, io_base, 1); + + return retval; +} + +static int lpcspifi_probe(struct flash_bank *bank) +{ + struct target *target = bank->target; + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + uint32_t ssp_base; + uint32_t io_base; + uint32_t ioconfig_base; + struct flash_sector *sectors; + uint32_t id = 0; /* silence uninitialized warning */ + struct lpcspifi_target *target_device; + int retval; + + /* If we've already probed, we should be fine to skip this time. */ + if (lpcspifi_info->probed) + return ERROR_OK; + lpcspifi_info->probed = 0; + + for (target_device = target_devices ; target_device->name ; ++target_device) + if (target_device->tap_idcode == target->tap->idcode) + break; + if (!target_device->name) { + LOG_ERROR("Device ID 0x%" PRIx32 " is not known as SPIFI capable", + target->tap->idcode); + return ERROR_FAIL; + } + + ssp_base = target_device->ssp_base; + io_base = target_device->io_base; + ioconfig_base = target_device->ioconfig_base; + lpcspifi_info->ssp_base = ssp_base; + lpcspifi_info->io_base = io_base; + lpcspifi_info->ioconfig_base = ioconfig_base; + lpcspifi_info->bank_num = bank->bank_number; + + LOG_DEBUG("Valid SPIFI on device %s at address 0x%" PRIx32, + target_device->name, bank->base); + + /* read and decode flash ID; returns in SW mode */ + retval = lpcspifi_read_flash_id(bank, &id); + if (retval != ERROR_OK) + return retval; + + retval = lpcspifi_set_hw_mode(bank); + if (retval != ERROR_OK) + return retval; + + lpcspifi_info->dev = NULL; + for (struct flash_device *p = flash_devices; p->name ; p++) + if (p->device_id == id) { + lpcspifi_info->dev = p; + break; + } + + if (!lpcspifi_info->dev) { + LOG_ERROR("Unknown flash device (ID 0x%08" PRIx32 ")", id); + return ERROR_FAIL; + } + + LOG_INFO("Found flash device \'%s\' (ID 0x%08" PRIx32 ")", + lpcspifi_info->dev->name, lpcspifi_info->dev->device_id); + + /* Set correct size value */ + bank->size = lpcspifi_info->dev->size_in_bytes; + + /* create and fill sectors array */ + bank->num_sectors = + lpcspifi_info->dev->size_in_bytes / lpcspifi_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 * lpcspifi_info->dev->sectorsize; + sectors[sector].size = lpcspifi_info->dev->sectorsize; + sectors[sector].is_erased = -1; + sectors[sector].is_protected = 1; + } + + bank->sectors = sectors; + + lpcspifi_info->probed = 1; + return ERROR_OK; +} + +static int lpcspifi_auto_probe(struct flash_bank *bank) +{ + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + if (lpcspifi_info->probed) + return ERROR_OK; + return lpcspifi_probe(bank); +} + +static int lpcspifi_protect_check(struct flash_bank *bank) +{ + /* Nothing to do. Protection is only handled in SW. */ + return ERROR_OK; +} + +static int get_lpcspifi_info(struct flash_bank *bank, char *buf, int buf_size) +{ + struct lpcspifi_flash_bank *lpcspifi_info = bank->driver_priv; + + if (!(lpcspifi_info->probed)) { + snprintf(buf, buf_size, + "\nSPIFI flash bank not probed yet\n"); + return ERROR_OK; + } + + snprintf(buf, buf_size, "\nSPIFI flash information:\n" + " Device \'%s\' (ID 0x%08x)\n", + lpcspifi_info->dev->name, lpcspifi_info->dev->device_id); + + return ERROR_OK; +} + +struct flash_driver lpcspifi_flash = { + .name = "lpcspifi", + .flash_bank_command = lpcspifi_flash_bank_command, + .erase = lpcspifi_erase, + .protect = lpcspifi_protect, + .write = lpcspifi_write, + .read = default_flash_read, + .probe = lpcspifi_probe, + .auto_probe = lpcspifi_auto_probe, + .erase_check = default_flash_blank_check, + .protect_check = lpcspifi_protect_check, + .info = get_lpcspifi_info, +}; diff --git a/tcl/board/lpc4350_spifi_generic.cfg b/tcl/board/lpc4350_spifi_generic.cfg new file mode 100644 index 0000000000..a529c19bbc --- /dev/null +++ b/tcl/board/lpc4350_spifi_generic.cfg @@ -0,0 +1,18 @@ +# +# Generic LPC4350 board w/ SPIFI flash. +# This config file is intended as an example of how to +# use the lpcspifi flash driver, but it should be functional +# for most LPC4350 boards utilizing SPIFI flash. + +set CHIPNAME lpc4350 + +source [find target/lpc4350.cfg] + +#A large working area greatly reduces flash write times +set _WORKAREASIZE 0x2000 + +$_CHIPNAME.m4 configure -work-area-phys 0x10000000 -work-area-size $_WORKAREASIZE + +#Configure the flash bank; 0x14000000 is the base address for +#lpc43xx/lpc18xx family micros. +flash bank SPIFI_FLASH lpcspifi 0x14000000 0 0 0 $_CHIPNAME.m4 -- 2.30.2