|
@@ -0,0 +1,815 @@
|
|
|
+/*
|
|
|
+ * SPI flash driver
|
|
|
+ *
|
|
|
+ * Enter bugs at http://blackfin.uclinux.org/
|
|
|
+ *
|
|
|
+ * Copyright (c) 2005-2007 Analog Devices Inc.
|
|
|
+ *
|
|
|
+ * Licensed under the GPL-2 or later.
|
|
|
+ */
|
|
|
+
|
|
|
+/* Configuration options:
|
|
|
+ * CONFIG_SPI_BAUD - value to load into SPI_BAUD (divisor of SCLK to get SPI CLK)
|
|
|
+ * CONFIG_SPI_FLASH_SLOW_READ - force usage of the slower read
|
|
|
+ * WARNING: make sure your SCLK + SPI_BAUD is slow enough
|
|
|
+ */
|
|
|
+
|
|
|
+#include <common.h>
|
|
|
+#include <malloc.h>
|
|
|
+#include <asm/io.h>
|
|
|
+#include <asm/mach-common/bits/spi.h>
|
|
|
+
|
|
|
+/* Forcibly phase out these */
|
|
|
+#ifdef CONFIG_SPI_FLASH_NUM_SECTORS
|
|
|
+# error do not set CONFIG_SPI_FLASH_NUM_SECTORS
|
|
|
+#endif
|
|
|
+#ifdef CONFIG_SPI_FLASH_SECTOR_SIZE
|
|
|
+# error do not set CONFIG_SPI_FLASH_SECTOR_SIZE
|
|
|
+#endif
|
|
|
+
|
|
|
+#if defined(CONFIG_SPI)
|
|
|
+
|
|
|
+struct flash_info {
|
|
|
+ char *name;
|
|
|
+ uint16_t id;
|
|
|
+ unsigned sector_size;
|
|
|
+ unsigned num_sectors;
|
|
|
+};
|
|
|
+
|
|
|
+/* SPI Speeds: 50 MHz / 33 MHz */
|
|
|
+static struct flash_info flash_spansion_serial_flash[] = {
|
|
|
+ { "S25FL016", 0x0215, 64 * 1024, 32 },
|
|
|
+ { "S25FL032", 0x0216, 64 * 1024, 64 },
|
|
|
+ { "S25FL064", 0x0217, 64 * 1024, 128 },
|
|
|
+ { "S25FL0128", 0x0218, 256 * 1024, 64 },
|
|
|
+ { NULL, 0, 0, 0 }
|
|
|
+};
|
|
|
+
|
|
|
+/* SPI Speeds: 50 MHz / 20 MHz */
|
|
|
+static struct flash_info flash_st_serial_flash[] = {
|
|
|
+ { "m25p05", 0x2010, 32 * 1024, 2 },
|
|
|
+ { "m25p10", 0x2011, 32 * 1024, 4 },
|
|
|
+ { "m25p20", 0x2012, 64 * 1024, 4 },
|
|
|
+ { "m25p40", 0x2013, 64 * 1024, 8 },
|
|
|
+ { "m25p16", 0x2015, 64 * 1024, 32 },
|
|
|
+ { "m25p32", 0x2016, 64 * 1024, 64 },
|
|
|
+ { "m25p64", 0x2017, 64 * 1024, 128 },
|
|
|
+ { "m25p128", 0x2018, 256 * 1024, 64 },
|
|
|
+ { NULL, 0, 0, 0 }
|
|
|
+};
|
|
|
+
|
|
|
+/* SPI Speeds: 66 MHz / 33 MHz */
|
|
|
+static struct flash_info flash_atmel_dataflash[] = {
|
|
|
+ { "AT45DB011x", 0x0c, 264, 512 },
|
|
|
+ { "AT45DB021x", 0x14, 264, 1025 },
|
|
|
+ { "AT45DB041x", 0x1c, 264, 2048 },
|
|
|
+ { "AT45DB081x", 0x24, 264, 4096 },
|
|
|
+ { "AT45DB161x", 0x2c, 528, 4096 },
|
|
|
+ { "AT45DB321x", 0x34, 528, 8192 },
|
|
|
+ { "AT45DB642x", 0x3c, 1056, 8192 },
|
|
|
+ { NULL, 0, 0, 0 }
|
|
|
+};
|
|
|
+
|
|
|
+/* SPI Speed: 50 MHz / 25 MHz or 40 MHz / 20 MHz */
|
|
|
+static struct flash_info flash_winbond_serial_flash[] = {
|
|
|
+ { "W25X10", 0x3011, 16 * 256, 32 },
|
|
|
+ { "W25X20", 0x3012, 16 * 256, 64 },
|
|
|
+ { "W25X40", 0x3013, 16 * 256, 128 },
|
|
|
+ { "W25X80", 0x3014, 16 * 256, 256 },
|
|
|
+ { "W25P80", 0x2014, 256 * 256, 16 },
|
|
|
+ { "W25P16", 0x2015, 256 * 256, 32 },
|
|
|
+ { NULL, 0, 0, 0 }
|
|
|
+};
|
|
|
+
|
|
|
+struct flash_ops {
|
|
|
+ uint8_t read, write, erase, status;
|
|
|
+};
|
|
|
+
|
|
|
+#ifdef CONFIG_SPI_FLASH_SLOW_READ
|
|
|
+# define OP_READ 0x03
|
|
|
+#else
|
|
|
+# define OP_READ 0x0B
|
|
|
+#endif
|
|
|
+static struct flash_ops flash_st_ops = {
|
|
|
+ .read = OP_READ,
|
|
|
+ .write = 0x02,
|
|
|
+ .erase = 0xD8,
|
|
|
+ .status = 0x05,
|
|
|
+};
|
|
|
+
|
|
|
+static struct flash_ops flash_atmel_ops = {
|
|
|
+ .read = OP_READ,
|
|
|
+ .write = 0x82,
|
|
|
+ .erase = 0x81,
|
|
|
+ .status = 0xD7,
|
|
|
+};
|
|
|
+
|
|
|
+static struct flash_ops flash_winbond_ops = {
|
|
|
+ .read = OP_READ,
|
|
|
+ .write = 0x02,
|
|
|
+ .erase = 0x20,
|
|
|
+ .status = 0x05,
|
|
|
+};
|
|
|
+
|
|
|
+struct manufacturer_info {
|
|
|
+ const char *name;
|
|
|
+ uint8_t id;
|
|
|
+ struct flash_info *flashes;
|
|
|
+ struct flash_ops *ops;
|
|
|
+};
|
|
|
+
|
|
|
+static struct {
|
|
|
+ struct manufacturer_info *manufacturer;
|
|
|
+ struct flash_info *flash;
|
|
|
+ struct flash_ops *ops;
|
|
|
+ uint8_t manufacturer_id, device_id1, device_id2;
|
|
|
+ unsigned int write_length;
|
|
|
+ unsigned long sector_size, num_sectors;
|
|
|
+} flash;
|
|
|
+
|
|
|
+enum {
|
|
|
+ JED_MANU_SPANSION = 0x01,
|
|
|
+ JED_MANU_ST = 0x20,
|
|
|
+ JED_MANU_ATMEL = 0x1F,
|
|
|
+ JED_MANU_WINBOND = 0xEF,
|
|
|
+};
|
|
|
+
|
|
|
+static struct manufacturer_info flash_manufacturers[] = {
|
|
|
+ {
|
|
|
+ .name = "Spansion",
|
|
|
+ .id = JED_MANU_SPANSION,
|
|
|
+ .flashes = flash_spansion_serial_flash,
|
|
|
+ .ops = &flash_st_ops,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .name = "ST",
|
|
|
+ .id = JED_MANU_ST,
|
|
|
+ .flashes = flash_st_serial_flash,
|
|
|
+ .ops = &flash_st_ops,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .name = "Atmel",
|
|
|
+ .id = JED_MANU_ATMEL,
|
|
|
+ .flashes = flash_atmel_dataflash,
|
|
|
+ .ops = &flash_atmel_ops,
|
|
|
+ },
|
|
|
+ {
|
|
|
+ .name = "Winbond",
|
|
|
+ .id = JED_MANU_WINBOND,
|
|
|
+ .flashes = flash_winbond_serial_flash,
|
|
|
+ .ops = &flash_winbond_ops,
|
|
|
+ },
|
|
|
+};
|
|
|
+
|
|
|
+#define TIMEOUT 5000 /* timeout of 5 seconds */
|
|
|
+
|
|
|
+/* BF54x support */
|
|
|
+#ifndef pSPI_CTL
|
|
|
+# define pSPI_CTL pSPI0_CTL
|
|
|
+# define pSPI_BAUD pSPI0_BAUD
|
|
|
+# define pSPI_FLG pSPI0_FLG
|
|
|
+# define pSPI_RDBR pSPI0_RDBR
|
|
|
+# define pSPI_STAT pSPI0_STAT
|
|
|
+# define pSPI_TDBR pSPI0_TDBR
|
|
|
+# define SPI0_SCK 0x0001
|
|
|
+# define SPI0_MOSI 0x0004
|
|
|
+# define SPI0_MISO 0x0002
|
|
|
+# define SPI0_SEL1 0x0010
|
|
|
+#endif
|
|
|
+
|
|
|
+/* Default to the SPI SSEL that we boot off of:
|
|
|
+ * BF54x, BF537, (everything new?): SSEL1
|
|
|
+ * BF533, BF561: SSEL2
|
|
|
+ */
|
|
|
+#ifndef CONFIG_SPI_FLASH_SSEL
|
|
|
+# if defined(__ADSPBF531__) || defined(__ADSPBF532__) || \
|
|
|
+ defined(__ADSPBF533__) || defined(__ADSPBF561__)
|
|
|
+# define CONFIG_SPI_FLASH_SSEL 2
|
|
|
+# else
|
|
|
+# define CONFIG_SPI_FLASH_SSEL 1
|
|
|
+# endif
|
|
|
+#endif
|
|
|
+#define SSEL_MASK (1 << CONFIG_SPI_FLASH_SSEL)
|
|
|
+
|
|
|
+static void SPI_INIT(void)
|
|
|
+{
|
|
|
+ /* [#3541] This delay appears to be necessary, but not sure
|
|
|
+ * exactly why as the history behind it is non-existant.
|
|
|
+ */
|
|
|
+ udelay(CONFIG_CCLK_HZ / 25000000);
|
|
|
+
|
|
|
+ /* enable SPI pins: SSEL, MOSI, MISO, SCK */
|
|
|
+#ifdef __ADSPBF54x__
|
|
|
+ *pPORTE_FER |= (SPI0_SCK | SPI0_MOSI | SPI0_MISO | SPI0_SEL1);
|
|
|
+#elif defined(__ADSPBF534__) || defined(__ADSPBF536__) || defined(__ADSPBF537__)
|
|
|
+ *pPORTF_FER |= (PF10 | PF11 | PF12 | PF13);
|
|
|
+#elif defined(__ADSPBF52x__)
|
|
|
+ bfin_write_PORTG_MUX((bfin_read_PORTG_MUX() & ~PORT_x_MUX_0_MASK) | PORT_x_MUX_0_FUNC_3);
|
|
|
+ bfin_write_PORTG_FER(bfin_read_PORTG_FER() | PG1 | PG2 | PG3 | PG4);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* initate communication upon write of TDBR */
|
|
|
+ *pSPI_CTL = (SPE|MSTR|CPHA|CPOL|0x01);
|
|
|
+ *pSPI_BAUD = CONFIG_SPI_BAUD;
|
|
|
+}
|
|
|
+
|
|
|
+static void SPI_DEINIT(void)
|
|
|
+{
|
|
|
+ /* put SPI settings back to reset state */
|
|
|
+ *pSPI_CTL = 0x0400;
|
|
|
+ *pSPI_BAUD = 0;
|
|
|
+ SSYNC();
|
|
|
+}
|
|
|
+
|
|
|
+static void SPI_ON(void)
|
|
|
+{
|
|
|
+ /* toggle SSEL to reset the device so it'll take a new command */
|
|
|
+ *pSPI_FLG = 0xFF00 | SSEL_MASK;
|
|
|
+ SSYNC();
|
|
|
+
|
|
|
+ *pSPI_FLG = ((0xFF & ~SSEL_MASK) << 8) | SSEL_MASK;
|
|
|
+ SSYNC();
|
|
|
+}
|
|
|
+
|
|
|
+static void SPI_OFF(void)
|
|
|
+{
|
|
|
+ /* put SPI settings back to reset state */
|
|
|
+ *pSPI_FLG = 0xFF00;
|
|
|
+ SSYNC();
|
|
|
+}
|
|
|
+
|
|
|
+static uint8_t spi_write_read_byte(uint8_t transmit)
|
|
|
+{
|
|
|
+ *pSPI_TDBR = transmit;
|
|
|
+ SSYNC();
|
|
|
+
|
|
|
+ while ((*pSPI_STAT & TXS))
|
|
|
+ if (ctrlc())
|
|
|
+ break;
|
|
|
+ while (!(*pSPI_STAT & SPIF))
|
|
|
+ if (ctrlc())
|
|
|
+ break;
|
|
|
+ while (!(*pSPI_STAT & RXS))
|
|
|
+ if (ctrlc())
|
|
|
+ break;
|
|
|
+
|
|
|
+ /* Read dummy to empty the receive register */
|
|
|
+ return *pSPI_RDBR;
|
|
|
+}
|
|
|
+
|
|
|
+static uint8_t read_status_register(void)
|
|
|
+{
|
|
|
+ uint8_t status_register;
|
|
|
+
|
|
|
+ /* send instruction to read status register */
|
|
|
+ SPI_ON();
|
|
|
+ spi_write_read_byte(flash.ops->status);
|
|
|
+ /* send dummy to receive the status register */
|
|
|
+ status_register = spi_write_read_byte(0);
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ return status_register;
|
|
|
+}
|
|
|
+
|
|
|
+static int wait_for_ready_status(void)
|
|
|
+{
|
|
|
+ ulong start = get_timer(0);
|
|
|
+
|
|
|
+ while (get_timer(0) - start < TIMEOUT) {
|
|
|
+ switch (flash.manufacturer_id) {
|
|
|
+ case JED_MANU_SPANSION:
|
|
|
+ case JED_MANU_ST:
|
|
|
+ case JED_MANU_WINBOND:
|
|
|
+ if (!(read_status_register() & 0x01))
|
|
|
+ return 0;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case JED_MANU_ATMEL:
|
|
|
+ if (read_status_register() & 0x80)
|
|
|
+ return 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (ctrlc()) {
|
|
|
+ puts("\nAbort\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ puts("Timeout\n");
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+/* Request and read the manufacturer and device id of parts which
|
|
|
+ * are compatible with the JEDEC standard (JEP106) and use that to
|
|
|
+ * setup other operating conditions.
|
|
|
+ */
|
|
|
+static int spi_detect_part(void)
|
|
|
+{
|
|
|
+ uint16_t dev_id;
|
|
|
+ size_t i;
|
|
|
+
|
|
|
+ static char called_init;
|
|
|
+ if (called_init)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ SPI_ON();
|
|
|
+
|
|
|
+ /* Send the request for the part identification */
|
|
|
+ spi_write_read_byte(0x9F);
|
|
|
+
|
|
|
+ /* Now read in the manufacturer id bytes */
|
|
|
+ do {
|
|
|
+ flash.manufacturer_id = spi_write_read_byte(0);
|
|
|
+ if (flash.manufacturer_id == 0x7F)
|
|
|
+ puts("Warning: unhandled manufacturer continuation byte!\n");
|
|
|
+ } while (flash.manufacturer_id == 0x7F);
|
|
|
+
|
|
|
+ /* Now read in the first device id byte */
|
|
|
+ flash.device_id1 = spi_write_read_byte(0);
|
|
|
+
|
|
|
+ /* Now read in the second device id byte */
|
|
|
+ flash.device_id2 = spi_write_read_byte(0);
|
|
|
+
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ dev_id = (flash.device_id1 << 8) | flash.device_id2;
|
|
|
+
|
|
|
+ for (i = 0; i < ARRAY_SIZE(flash_manufacturers); ++i) {
|
|
|
+ if (flash.manufacturer_id == flash_manufacturers[i].id)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (i == ARRAY_SIZE(flash_manufacturers))
|
|
|
+ goto unknown;
|
|
|
+
|
|
|
+ flash.manufacturer = &flash_manufacturers[i];
|
|
|
+ flash.ops = flash_manufacturers[i].ops;
|
|
|
+
|
|
|
+ switch (flash.manufacturer_id) {
|
|
|
+ case JED_MANU_SPANSION:
|
|
|
+ case JED_MANU_ST:
|
|
|
+ case JED_MANU_WINBOND:
|
|
|
+ for (i = 0; flash.manufacturer->flashes[i].name; ++i) {
|
|
|
+ if (dev_id == flash.manufacturer->flashes[i].id)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!flash.manufacturer->flashes[i].name)
|
|
|
+ goto unknown;
|
|
|
+
|
|
|
+ flash.flash = &flash.manufacturer->flashes[i];
|
|
|
+ flash.sector_size = flash.flash->sector_size;
|
|
|
+ flash.num_sectors = flash.flash->num_sectors;
|
|
|
+ flash.write_length = 256;
|
|
|
+ break;
|
|
|
+
|
|
|
+ case JED_MANU_ATMEL: {
|
|
|
+ uint8_t status = read_status_register();
|
|
|
+
|
|
|
+ for (i = 0; flash.manufacturer->flashes[i].name; ++i) {
|
|
|
+ if ((status & 0x3c) == flash.manufacturer->flashes[i].id)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ if (!flash.manufacturer->flashes[i].name)
|
|
|
+ goto unknown;
|
|
|
+
|
|
|
+ flash.flash = &flash.manufacturer->flashes[i];
|
|
|
+ flash.sector_size = flash.flash->sector_size;
|
|
|
+ flash.num_sectors = flash.flash->num_sectors;
|
|
|
+
|
|
|
+ /* see if flash is in "power of 2" mode */
|
|
|
+ if (status & 0x1)
|
|
|
+ flash.sector_size &= ~(1 << (ffs(flash.sector_size) - 1));
|
|
|
+
|
|
|
+ flash.write_length = flash.sector_size;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ called_init = 1;
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ unknown:
|
|
|
+ printf("Unknown SPI device: 0x%02X 0x%02X 0x%02X\n",
|
|
|
+ flash.manufacturer_id, flash.device_id1, flash.device_id2);
|
|
|
+ return 1;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Function: spi_init_f
|
|
|
+ * Description: Init SPI-Controller (ROM part)
|
|
|
+ * return: ---
|
|
|
+ */
|
|
|
+void spi_init_f(void)
|
|
|
+{
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Function: spi_init_r
|
|
|
+ * Description: Init SPI-Controller (RAM part) -
|
|
|
+ * The malloc engine is ready and we can move our buffers to
|
|
|
+ * normal RAM
|
|
|
+ * return: ---
|
|
|
+ */
|
|
|
+void spi_init_r(void)
|
|
|
+{
|
|
|
+#if defined(CONFIG_POST) && (CONFIG_POST & CFG_POST_SPI)
|
|
|
+ /* Our testing strategy here is pretty basic:
|
|
|
+ * - fill src memory with an 8-bit pattern
|
|
|
+ * - write the src memory to the SPI flash
|
|
|
+ * - read the SPI flash into the dst memory
|
|
|
+ * - compare src and dst memory regions
|
|
|
+ * - repeat a few times
|
|
|
+ * The variations we test for:
|
|
|
+ * - change the 8-bit pattern a bit
|
|
|
+ * - change the read/write block size so we know:
|
|
|
+ * - writes smaller/equal/larger than the buffer work
|
|
|
+ * - writes smaller/equal/larger than the sector work
|
|
|
+ * - change the SPI offsets so we know:
|
|
|
+ * - writing partial sectors works
|
|
|
+ */
|
|
|
+ uint8_t *mem_src, *mem_dst;
|
|
|
+ size_t i, c, l, o;
|
|
|
+ size_t test_count, errors;
|
|
|
+ uint8_t pattern;
|
|
|
+
|
|
|
+ SPI_INIT();
|
|
|
+
|
|
|
+ if (spi_detect_part())
|
|
|
+ goto out;
|
|
|
+ eeprom_info();
|
|
|
+
|
|
|
+ ulong lengths[] = {
|
|
|
+ flash.write_length,
|
|
|
+ flash.write_length * 2,
|
|
|
+ flash.write_length / 2,
|
|
|
+ flash.sector_size,
|
|
|
+ flash.sector_size * 2,
|
|
|
+ flash.sector_size / 2
|
|
|
+ };
|
|
|
+ ulong offsets[] = {
|
|
|
+ 0,
|
|
|
+ flash.write_length,
|
|
|
+ flash.write_length * 2,
|
|
|
+ flash.write_length / 2,
|
|
|
+ flash.write_length / 4,
|
|
|
+ flash.sector_size,
|
|
|
+ flash.sector_size * 2,
|
|
|
+ flash.sector_size / 2,
|
|
|
+ flash.sector_size / 4,
|
|
|
+ };
|
|
|
+
|
|
|
+ /* the exact addresses are arbitrary ... they just need to not overlap */
|
|
|
+ mem_src = (void *)(0);
|
|
|
+ mem_dst = (void *)(max(flash.write_length, flash.sector_size) * 2);
|
|
|
+
|
|
|
+ test_count = 0;
|
|
|
+ errors = 0;
|
|
|
+ pattern = 0x00;
|
|
|
+
|
|
|
+ for (i = 0; i < 16; ++i) { /* 16 = 8 bits * 2 iterations */
|
|
|
+ for (l = 0; l < ARRAY_SIZE(lengths); ++l) {
|
|
|
+ for (o = 0; o < ARRAY_SIZE(offsets); ++o) {
|
|
|
+ ulong len = lengths[l];
|
|
|
+ ulong off = offsets[o];
|
|
|
+
|
|
|
+ printf("Testing pattern 0x%02X of length %5lu and offset %5lu: ", pattern, len, off);
|
|
|
+
|
|
|
+ /* setup the source memory region */
|
|
|
+ memset(mem_src, pattern, len);
|
|
|
+
|
|
|
+ test_count += 4;
|
|
|
+ for (c = 0; c < 4; ++c) { /* 4 is just a random repeat count */
|
|
|
+ if (ctrlc()) {
|
|
|
+ puts("\nAbort\n");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* make sure background fill pattern != pattern */
|
|
|
+ memset(mem_dst, pattern ^ 0xFF, len);
|
|
|
+
|
|
|
+ /* write out the source memory and then read it back and compare */
|
|
|
+ eeprom_write(0, off, mem_src, len);
|
|
|
+ eeprom_read(0, off, mem_dst, len);
|
|
|
+
|
|
|
+ if (memcmp(mem_src, mem_dst, len)) {
|
|
|
+ for (c = 0; c < len; ++c)
|
|
|
+ if (mem_src[c] != mem_dst[c])
|
|
|
+ break;
|
|
|
+ printf(" FAIL @ offset %u, skipping repeats ", c);
|
|
|
+ ++errors;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* XXX: should shrink write region here to test with
|
|
|
+ * leading/trailing canaries so we know surrounding
|
|
|
+ * bytes don't get screwed.
|
|
|
+ */
|
|
|
+ }
|
|
|
+ puts("\n");
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /* invert the pattern every other run and shift out bits slowly */
|
|
|
+ pattern ^= 0xFF;
|
|
|
+ if (i % 2)
|
|
|
+ pattern = (pattern | 0x01) << 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (errors)
|
|
|
+ printf("SPI FAIL: Out of %i tests, there were %i errors ;(\n", test_count, errors);
|
|
|
+ else
|
|
|
+ printf("SPI PASS: %i tests worked!\n", test_count);
|
|
|
+
|
|
|
+ out:
|
|
|
+ SPI_DEINIT();
|
|
|
+
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+static void transmit_address(uint32_t addr)
|
|
|
+{
|
|
|
+ /* Send the highest byte of the 24 bit address at first */
|
|
|
+ spi_write_read_byte(addr >> 16);
|
|
|
+ /* Send the middle byte of the 24 bit address at second */
|
|
|
+ spi_write_read_byte(addr >> 8);
|
|
|
+ /* Send the lowest byte of the 24 bit address finally */
|
|
|
+ spi_write_read_byte(addr);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Read a value from flash for verify purpose
|
|
|
+ * Inputs: unsigned long ulStart - holds the SPI start address
|
|
|
+ * int pnData - pointer to store value read from flash
|
|
|
+ * long lCount - number of elements to read
|
|
|
+ */
|
|
|
+static int read_flash(unsigned long address, long count, uchar *buffer)
|
|
|
+{
|
|
|
+ size_t i;
|
|
|
+
|
|
|
+ /* Send the read command to SPI device */
|
|
|
+ SPI_ON();
|
|
|
+ spi_write_read_byte(flash.ops->read);
|
|
|
+ transmit_address(address);
|
|
|
+
|
|
|
+#ifndef CONFIG_SPI_FLASH_SLOW_READ
|
|
|
+ /* Send dummy byte when doing SPI fast reads */
|
|
|
+ spi_write_read_byte(0);
|
|
|
+#endif
|
|
|
+
|
|
|
+ /* After the SPI device address has been placed on the MOSI pin the data can be */
|
|
|
+ /* received on the MISO pin. */
|
|
|
+ for (i = 1; i <= count; ++i) {
|
|
|
+ *buffer++ = spi_write_read_byte(0);
|
|
|
+ if (i % flash.sector_size == 0)
|
|
|
+ puts(".");
|
|
|
+ }
|
|
|
+
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+static int enable_writing(void)
|
|
|
+{
|
|
|
+ ulong start;
|
|
|
+
|
|
|
+ if (flash.manufacturer_id == JED_MANU_ATMEL)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ /* A write enable instruction must previously have been executed */
|
|
|
+ SPI_ON();
|
|
|
+ spi_write_read_byte(0x06);
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ /* The status register will be polled to check the write enable latch "WREN" */
|
|
|
+ start = get_timer(0);
|
|
|
+ while (get_timer(0) - start < TIMEOUT) {
|
|
|
+ if (read_status_register() & 0x02)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (ctrlc()) {
|
|
|
+ puts("\nAbort\n");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ puts("Timeout\n");
|
|
|
+ return -1;
|
|
|
+}
|
|
|
+
|
|
|
+static long address_to_sector(unsigned long address)
|
|
|
+{
|
|
|
+ if (address > (flash.num_sectors * flash.sector_size) - 1)
|
|
|
+ return -1;
|
|
|
+ return address / flash.sector_size;
|
|
|
+}
|
|
|
+
|
|
|
+static int erase_sector(int address)
|
|
|
+{
|
|
|
+ /* sector gets checked in higher function, so assume it's valid
|
|
|
+ * here and figure out the offset of the sector in flash
|
|
|
+ */
|
|
|
+ if (enable_writing())
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Send the erase block command to the flash followed by the 24 address
|
|
|
+ * to point to the start of a sector
|
|
|
+ */
|
|
|
+ SPI_ON();
|
|
|
+ spi_write_read_byte(flash.ops->erase);
|
|
|
+ transmit_address(address);
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ return wait_for_ready_status();
|
|
|
+}
|
|
|
+
|
|
|
+/* Write [count] bytes out of [buffer] into the given SPI [address] */
|
|
|
+static long write_flash(unsigned long address, long count, uchar *buffer)
|
|
|
+{
|
|
|
+ long i, write_buffer_size;
|
|
|
+
|
|
|
+ if (enable_writing())
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ /* Send write command followed by the 24 bit address */
|
|
|
+ SPI_ON();
|
|
|
+ spi_write_read_byte(flash.ops->write);
|
|
|
+ transmit_address(address);
|
|
|
+
|
|
|
+ /* Shoot out a single write buffer */
|
|
|
+ write_buffer_size = min(count, flash.write_length);
|
|
|
+ for (i = 0; i < write_buffer_size; ++i)
|
|
|
+ spi_write_read_byte(buffer[i]);
|
|
|
+
|
|
|
+ SPI_OFF();
|
|
|
+
|
|
|
+ /* Wait for the flash to do its thing */
|
|
|
+ if (wait_for_ready_status()) {
|
|
|
+ puts("SPI Program Time out! ");
|
|
|
+ return -1;
|
|
|
+ }
|
|
|
+
|
|
|
+ return i;
|
|
|
+}
|
|
|
+
|
|
|
+/* Write [count] bytes out of [buffer] into the given SPI [address] */
|
|
|
+static int write_sector(unsigned long address, long count, uchar *buffer)
|
|
|
+{
|
|
|
+ long write_cnt;
|
|
|
+
|
|
|
+ while (count != 0) {
|
|
|
+ write_cnt = write_flash(address, count, buffer);
|
|
|
+ if (write_cnt == -1)
|
|
|
+ return -1;
|
|
|
+
|
|
|
+ /* Now that we've sent some bytes out to the flash, update
|
|
|
+ * our counters a bit
|
|
|
+ */
|
|
|
+ count -= write_cnt;
|
|
|
+ address += write_cnt;
|
|
|
+ buffer += write_cnt;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* return the appropriate error code */
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Function: spi_write
|
|
|
+ */
|
|
|
+ssize_t spi_write(uchar *addr, int alen, uchar *buffer, int len)
|
|
|
+{
|
|
|
+ unsigned long offset;
|
|
|
+ int start_sector, end_sector;
|
|
|
+ int start_byte, end_byte;
|
|
|
+ uchar *temp = NULL;
|
|
|
+ int num, ret = 0;
|
|
|
+
|
|
|
+ SPI_INIT();
|
|
|
+
|
|
|
+ if (spi_detect_part())
|
|
|
+ goto out;
|
|
|
+
|
|
|
+ offset = addr[0] << 16 | addr[1] << 8 | addr[2];
|
|
|
+
|
|
|
+ /* Get the start block number */
|
|
|
+ start_sector = address_to_sector(offset);
|
|
|
+ if (start_sector == -1) {
|
|
|
+ puts("Invalid sector! ");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+ end_sector = address_to_sector(offset + len - 1);
|
|
|
+ if (end_sector == -1) {
|
|
|
+ puts("Invalid sector! ");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* Since flashes operate in sector units but the eeprom command
|
|
|
+ * operates as a continuous stream of bytes, we need to emulate
|
|
|
+ * the eeprom behavior. So here we read in the sector, overlay
|
|
|
+ * any bytes we're actually modifying, erase the sector, and
|
|
|
+ * then write back out the new sector.
|
|
|
+ */
|
|
|
+ temp = malloc(flash.sector_size);
|
|
|
+ if (!temp) {
|
|
|
+ puts("Malloc for sector failed! ");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (num = start_sector; num <= end_sector; num++) {
|
|
|
+ unsigned long address = num * flash.sector_size;
|
|
|
+
|
|
|
+ /* XXX: should add an optimization when spanning sectors:
|
|
|
+ * No point in reading in a sector if we're going to be
|
|
|
+ * clobbering the whole thing. Need to also add a test
|
|
|
+ * case to make sure the optimization is correct.
|
|
|
+ */
|
|
|
+ if (read_flash(address, flash.sector_size, temp)) {
|
|
|
+ puts("Read sector failed! ");
|
|
|
+ len = 0;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ start_byte = max(address, offset);
|
|
|
+ end_byte = address + flash.sector_size - 1;
|
|
|
+ if (end_byte > (offset + len))
|
|
|
+ end_byte = (offset + len - 1);
|
|
|
+
|
|
|
+ memcpy(temp + start_byte - address,
|
|
|
+ buffer + start_byte - offset,
|
|
|
+ end_byte - start_byte + 1);
|
|
|
+
|
|
|
+ if (erase_sector(address)) {
|
|
|
+ puts("Erase sector failed! ");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (write_sector(address, flash.sector_size, temp)) {
|
|
|
+ puts("Write sector failed! ");
|
|
|
+ goto out;
|
|
|
+ }
|
|
|
+
|
|
|
+ puts(".");
|
|
|
+ }
|
|
|
+
|
|
|
+ ret = len;
|
|
|
+
|
|
|
+ out:
|
|
|
+ free(temp);
|
|
|
+
|
|
|
+ SPI_DEINIT();
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Function: spi_read
|
|
|
+ */
|
|
|
+ssize_t spi_read(uchar *addr, int alen, uchar *buffer, int len)
|
|
|
+{
|
|
|
+ unsigned long offset;
|
|
|
+
|
|
|
+ SPI_INIT();
|
|
|
+
|
|
|
+ if (spi_detect_part())
|
|
|
+ len = 0;
|
|
|
+ else {
|
|
|
+ offset = addr[0] << 16 | addr[1] << 8 | addr[2];
|
|
|
+ read_flash(offset, len, buffer);
|
|
|
+ }
|
|
|
+
|
|
|
+ SPI_DEINIT();
|
|
|
+
|
|
|
+ return len;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Spit out some useful information about the SPI eeprom
|
|
|
+ */
|
|
|
+int eeprom_info(void)
|
|
|
+{
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ SPI_INIT();
|
|
|
+
|
|
|
+ if (spi_detect_part())
|
|
|
+ ret = 1;
|
|
|
+ else
|
|
|
+ printf("SPI Device: %s 0x%02X (%s) 0x%02X 0x%02X\n"
|
|
|
+ "Parameters: num sectors = %i, sector size = %i, write size = %i\n"
|
|
|
+ "Flash Size: %i mbit (%i mbyte)\n"
|
|
|
+ "Status: 0x%02X\n",
|
|
|
+ flash.flash->name, flash.manufacturer_id, flash.manufacturer->name,
|
|
|
+ flash.device_id1, flash.device_id2, flash.num_sectors,
|
|
|
+ flash.sector_size, flash.write_length,
|
|
|
+ (flash.num_sectors * flash.sector_size) >> 17,
|
|
|
+ (flash.num_sectors * flash.sector_size) >> 20,
|
|
|
+ read_status_register());
|
|
|
+
|
|
|
+ SPI_DEINIT();
|
|
|
+
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+#endif
|