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 <george@luminairecoffee.com> Reviewed-on: http://openocd.zylin.com/783 Tested-by: jenkins Reviewed-by: Aurelien Jacobs <aurel@gnuage.org> Reviewed-by: Freddie Chopin <freddie.chopin@gmail.com>
This commit is contained in:
parent
15e19011ea
commit
516719b6b8
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -4652,6 +4652,30 @@ flash bank $_FLASHNAME cfi 0x00000000 0x02000000 2 4 $_TARGETNAME
|
||||||
@c "cfi part_id" disabled
|
@c "cfi part_id" disabled
|
||||||
@end deffn
|
@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
|
@deffn {Flash Driver} stmsmi
|
||||||
@cindex STMicroelectronics Serial Memory Interface
|
@cindex STMicroelectronics Serial Memory Interface
|
||||||
@cindex SMI
|
@cindex SMI
|
||||||
|
|
|
@ -19,6 +19,7 @@ NOR_DRIVERS = \
|
||||||
lpc2000.c \
|
lpc2000.c \
|
||||||
lpc288x.c \
|
lpc288x.c \
|
||||||
lpc2900.c \
|
lpc2900.c \
|
||||||
|
lpcspifi.c \
|
||||||
non_cfi.c \
|
non_cfi.c \
|
||||||
ocl.c \
|
ocl.c \
|
||||||
pic32mx.c \
|
pic32mx.c \
|
||||||
|
|
|
@ -25,6 +25,7 @@
|
||||||
extern struct flash_driver lpc2000_flash;
|
extern struct flash_driver lpc2000_flash;
|
||||||
extern struct flash_driver lpc288x_flash;
|
extern struct flash_driver lpc288x_flash;
|
||||||
extern struct flash_driver lpc2900_flash;
|
extern struct flash_driver lpc2900_flash;
|
||||||
|
extern struct flash_driver lpcspifi_flash;
|
||||||
extern struct flash_driver cfi_flash;
|
extern struct flash_driver cfi_flash;
|
||||||
extern struct flash_driver at91sam3_flash;
|
extern struct flash_driver at91sam3_flash;
|
||||||
extern struct flash_driver at91sam4_flash;
|
extern struct flash_driver at91sam4_flash;
|
||||||
|
@ -57,6 +58,7 @@ static struct flash_driver *flash_drivers[] = {
|
||||||
&lpc2000_flash,
|
&lpc2000_flash,
|
||||||
&lpc288x_flash,
|
&lpc288x_flash,
|
||||||
&lpc2900_flash,
|
&lpc2900_flash,
|
||||||
|
&lpcspifi_flash,
|
||||||
&cfi_flash,
|
&cfi_flash,
|
||||||
&at91sam7_flash,
|
&at91sam7_flash,
|
||||||
&at91sam3_flash,
|
&at91sam3_flash,
|
||||||
|
|
|
@ -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 <jtag/jtag.h>
|
||||||
|
#include <helper/time_support.h>
|
||||||
|
#include <target/algorithm.h>
|
||||||
|
#include <target/armv7m.h>
|
||||||
|
|
||||||
|
/* 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 <base> <size> <chip_width> <bus_width> <target>
|
||||||
|
*/
|
||||||
|
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,
|
||||||
|
};
|
|
@ -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
|
Loading…
Reference in New Issue