This patch adds support for the "SPI 100" SPI engine in Yangtze FCHs (found in Kabini and Temash). BTW it also rewrites sb600spi.c completely.
Tested reading/writing on ASRock IMB-A180.
Signed-off-by: Wei Hu wei@aristanetworks.com Signed-off-by: Carl-Daniel Hailfinger c-d.hailfinger.devel.2006@gmx.net Signed-off-by: Stefan Tauner stefan.tauner@student.tuwien.ac.at --- sb600spi.c | 349 ++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 208 insertions(+), 141 deletions(-)
diff --git a/sb600spi.c b/sb600spi.c index 9ab7a58..98238c2 100644 --- a/sb600spi.c +++ b/sb600spi.c @@ -4,7 +4,8 @@ * Copyright (C) 2008 Wang Qingpei Qingpei.Wang@amd.com * Copyright (C) 2008 Joe Bao Zheng.Bao@amd.com * Copyright (C) 2008 Advanced Micro Devices, Inc. - * Copyright (C) 2009, 2010 Carl-Daniel Hailfinger + * Copyright (C) 2009, 2010, 2013 Carl-Daniel Hailfinger + * Copyright (C) 2013 Stefan Tauner * * 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 @@ -131,40 +132,148 @@ static void reset_internal_fifo_pointer(void) msg_pspew("reset\n"); }
+static int check_internal_fifo_pointer(uint8_t have, uint8_t want) +{ + if (have != want) { + msg_perr("AMD SPI FIFO pointer corruption! Pointer is %d, wanted %d\n", have, want); + msg_perr("Something else is accessing the flash chip and causes random corruption.\n" + "Please stop all applications and drivers and IPMI which access the flash chip.\n"); + return 1; + } + msg_pspew("AMD SPI FIFO pointer is %d, wanted %d\n", have, want); + return 0; +} + + static int compare_internal_fifo_pointer(uint8_t want) { - uint8_t tmp; + uint8_t tmp = mmio_readb(sb600_spibar + 0xd) & 0x7; + want &= 0x7; /* Modulo FIFO length */ + return check_internal_fifo_pointer(tmp, want); +}
- tmp = mmio_readb(sb600_spibar + 0xd) & 0x07; - want &= 0x7; - if (want != tmp) { - msg_perr("FIFO pointer corruption! Pointer is %d, wanted %d\n", tmp, want); - msg_perr("Something else is accessing the flash chip and " - "causes random corruption.\nPlease stop all " - "applications and drivers and IPMI which access the " - "flash chip.\n"); - return 1; +/* Check and set the number of bytes to be transmitted. */ +static int set_bytecounts(unsigned int writecnt, unsigned int readcnt) +{ + unsigned int maxwritecnt, maxreadcnt; + + if (amd_gen >= CHIPSET_YANGTZE) + maxreadcnt = maxwritecnt = 70; + else + maxreadcnt = maxwritecnt = 8; + + if (writecnt > maxwritecnt) { + msg_pinfo("%s: SPI controller can not send %d bytes, it is limited to %d bytes\n", + __func__, writecnt, maxwritecnt); + return SPI_INVALID_LENGTH; + } + + if (readcnt > maxreadcnt) { + msg_pinfo("%s: SPI controller can not receive %d bytes, it is limited to %d bytes\n", + __func__, readcnt, maxreadcnt); + return SPI_INVALID_LENGTH; + } + + if (amd_gen >= CHIPSET_YANGTZE) { + /* Use the extended TxByteCount and RxByteCount register (and the bigger FIFO later). */ + mmio_writeb(writecnt, sb600_spibar + 0x48); + mmio_writeb(readcnt, sb600_spibar + 0x4b); } else { - msg_pspew("SB600 FIFO pointer is %d, wanted %d\n", tmp, want); - return 0; + /* This is a workaround for a bug in SB600 and SB700. If we only send + * an opcode and no additional data/address, the SPI controller will + * read one byte too few from the chip. Basically, the last byte of + * the chip response is discarded and will not end up in the FIFO. + * It is unclear if the CS# line is set high too early as well. + */ + unsigned int readoffby1 = (writecnt) ? 0 : 1; + uint8_t readwrite = (readcnt + readoffby1) << 4 | (writecnt); + mmio_writeb(readwrite, sb600_spibar + 1); } + return 0; }
-static int reset_compare_internal_fifo_pointer(uint8_t want) +static int fill_fifo(const uint8_t *writearr, unsigned int writecnt) { - int ret; - - ret = compare_internal_fifo_pointer(want); - reset_internal_fifo_pointer(); - return ret; + int count; + if (amd_gen >= CHIPSET_YANGTZE) { + msg_pspew("Filling FIFO: "); + for (count = 0; count < writecnt; count++) { + msg_pspew("[%02x]", writearr[count]); + mmio_writeb(writearr[count], sb600_spibar + 0x80 + count); + } + msg_pspew("\n"); + } else { + reset_internal_fifo_pointer(); + msg_pspew("Filling FIFO: "); + for (count = 0; count < writecnt; count++) { + msg_pspew("[%02x]", writearr[count]); + mmio_writeb(writearr[count], sb600_spibar + 0xC); + } + msg_pspew("\n"); + if (compare_internal_fifo_pointer(writecnt)) + return SPI_PROGRAMMER_ERROR; + } + return 0; }
-static void execute_command(void) +static int execute_command(unsigned int writecnt, unsigned int readcnt) { + /* + * We should send the data in sequence, which means we need to reset + * the FIFO pointer to the first byte we want to send. + */ + reset_internal_fifo_pointer(); + msg_pspew("Executing... \n"); mmio_writeb(mmio_readb(sb600_spibar + 2) | 1, sb600_spibar + 2); - while (mmio_readb(sb600_spibar + 2) & 1) ; + msg_pspew("done\n"); + if (compare_internal_fifo_pointer(writecnt + readcnt)) + return SPI_PROGRAMMER_ERROR; + return 0; +} + +static int read_fifo(int skip, uint8_t *data, int len) +{ + int count; + if (amd_gen >= CHIPSET_YANGTZE) { + msg_pspew("Reading: "); + for (count = 0; count < len; count++) { + data[count] = mmio_readb(sb600_spibar + 0x80 + (skip + count) % 70); + msg_pspew("[%02x]", data[count]); + } + } else { + /* + * After the command executed, we should find out the index of the + * received byte. Here we just reset the FIFO pointer and skip the + * writecnt. + * It would be possible to increase the FIFO pointer by one instead + * of reading and discarding one byte from the FIFO. + * The FIFO is implemented on top of an 8 byte ring buffer and the + * buffer is never cleared. For every byte that is shifted out after + * the opcode, the FIFO already stores the response from the chip. + * Usually, the chip will respond with 0x00 or 0xff. + */ + reset_internal_fifo_pointer(); + + msg_pspew("Skipping: "); + for (count = 0; count < skip; count++) { + msg_pspew("[%02x]", mmio_readb(sb600_spibar + 0xC)); + } + msg_pspew("\n"); + if (compare_internal_fifo_pointer(skip)) + return SPI_PROGRAMMER_ERROR; + + msg_pspew("Reading: "); + for (count = 0; count < len; count++) { + data[count] = mmio_readb(sb600_spibar + 0xC); + msg_pspew("[%02x]", data[count]); + } + msg_pspew("\n"); + if (compare_internal_fifo_pointer(skip+len)) + return SPI_PROGRAMMER_ERROR; + } + return 0; }
static int sb600_spi_send_command(struct flashctx *flash, unsigned int writecnt, @@ -172,102 +281,27 @@ static int sb600_spi_send_command(struct flashctx *flash, unsigned int writecnt, const unsigned char *writearr, unsigned char *readarr) { - int count; /* First byte is cmd which can not being sent through FIFO. */ unsigned char cmd = *writearr++; - unsigned int readoffby1; - unsigned char readwrite; - writecnt--; - - msg_pspew("%s, cmd=%x, writecnt=%x, readcnt=%x\n", - __func__, cmd, writecnt, readcnt); - - if (readcnt > 8) { - msg_pinfo("%s, SB600 SPI controller can not receive %d bytes, " - "it is limited to 8 bytes\n", __func__, readcnt); - return SPI_INVALID_LENGTH; - } - - if (writecnt > 8) { - msg_pinfo("%s, SB600 SPI controller can not send %d bytes, " - "it is limited to 8 bytes\n", __func__, writecnt); - return SPI_INVALID_LENGTH; - } - - /* This is a workaround for a bug in SB600 and SB700. If we only send - * an opcode and no additional data/address, the SPI controller will - * read one byte too few from the chip. Basically, the last byte of - * the chip response is discarded and will not end up in the FIFO. - * It is unclear if the CS# line is set high too early as well. - */ - readoffby1 = (writecnt) ? 0 : 1; - readwrite = (readcnt + readoffby1) << 4 | (writecnt); - mmio_writeb(readwrite, sb600_spibar + 1); + msg_pspew("%s, cmd=%x, writecnt=%x, readcnt=%x\n", __func__, cmd, writecnt, readcnt); mmio_writeb(cmd, sb600_spibar + 0);
- /* Before we use the FIFO, reset it first. */ - reset_internal_fifo_pointer(); + int ret = set_bytecounts(writecnt, readcnt); + if (ret != 0) + return ret;
- /* Send the write byte to FIFO. */ - msg_pspew("Writing: "); - for (count = 0; count < writecnt; count++, writearr++) { - msg_pspew("[%02x]", *writearr); - mmio_writeb(*writearr, sb600_spibar + 0xC); - } - msg_pspew("\n"); + ret = fill_fifo(writearr, writecnt); + if (ret != 0) + return ret;
- /* - * We should send the data by sequence, which means we need to reset - * the FIFO pointer to the first byte we want to send. - */ - if (reset_compare_internal_fifo_pointer(writecnt)) - return SPI_PROGRAMMER_ERROR; - - msg_pspew("Executing: \n"); - execute_command(); - - /* - * After the command executed, we should find out the index of the - * received byte. Here we just reset the FIFO pointer and skip the - * writecnt. - * It would be possible to increase the FIFO pointer by one instead - * of reading and discarding one byte from the FIFO. - * The FIFO is implemented on top of an 8 byte ring buffer and the - * buffer is never cleared. For every byte that is shifted out after - * the opcode, the FIFO already stores the response from the chip. - * Usually, the chip will respond with 0x00 or 0xff. - */ - if (reset_compare_internal_fifo_pointer(writecnt + readcnt)) - return SPI_PROGRAMMER_ERROR; + ret = execute_command(writecnt, readcnt); + if (ret != 0) + return ret;
- /* Skip the bytes we sent. */ - msg_pspew("Skipping: "); - for (count = 0; count < writecnt; count++) { - cmd = mmio_readb(sb600_spibar + 0xC); - msg_pspew("[%02x]", cmd); - } - msg_pspew("\n"); - if (compare_internal_fifo_pointer(writecnt)) - return SPI_PROGRAMMER_ERROR; - - msg_pspew("Reading: "); - for (count = 0; count < readcnt; count++, readarr++) { - *readarr = mmio_readb(sb600_spibar + 0xC); - msg_pspew("[%02x]", *readarr); - } - msg_pspew("\n"); - if (reset_compare_internal_fifo_pointer(readcnt + writecnt)) - return SPI_PROGRAMMER_ERROR; - - if (mmio_readb(sb600_spibar + 1) != readwrite) { - msg_perr("Unexpected change in SB600 read/write count!\n"); - msg_perr("Something else is accessing the flash chip and " - "causes random corruption.\nPlease stop all " - "applications and drivers and IPMI which access the " - "flash chip.\n"); - return SPI_PROGRAMMER_ERROR; - } + ret = read_fifo(writecnt, readarr, readcnt); + if (ret != 0) + return ret;
return 0; } @@ -282,6 +316,10 @@ static const struct spispeed spispeeds[] = { { "33 MHz", 0x01 }, { "22 MHz", 0x02 }, { "16.5 MHz", 0x03 }, + { "100 MHz", 0x04 }, + { "Reserved", 0x05 }, + { "Reserved", 0x06 }, + { "800 kHz", 0x07 }, };
static int set_speed(struct pci_dev *dev, const struct spispeed *spispeed) @@ -290,7 +328,12 @@ static int set_speed(struct pci_dev *dev, const struct spispeed *spispeed) uint8_t speed = spispeed->speed;
msg_pdbg("Setting SPI clock to %s (0x%x).\n", spispeed->name, speed); - if (amd_gen != CHIPSET_YANGTZE) { + if (amd_gen >= CHIPSET_YANGTZE) { + rmmio_writew((speed << 12) | (speed << 8) | (speed << 4) | speed, sb600_spibar + 0x22); + uint8_t tmp = (mmio_readb(sb600_spibar + 0x22) >> 4); + success = (((tmp >> 12) & 0xf) == speed && ((tmp >> 8) & 0xf) == speed && + ((tmp >> 4) & 0xf) == speed && ((tmp >> 0) & 0xf) == speed); + } else { rmmio_writeb((mmio_readb(sb600_spibar + 0xd) & ~(0x3 << 4)) | (speed << 4), sb600_spibar + 0xd); success = (speed == ((mmio_readb(sb600_spibar + 0xd) >> 4) & 0x3)); } @@ -311,7 +354,28 @@ static int handle_speed(struct pci_dev *dev) * 18 rsvd <- fastReadEnable ? <- ? SpiReadMode[0] * 29:30 rsvd <- <- ? <- ? SpiReadMode[2:1] */ - if (amd_gen != CHIPSET_YANGTZE) { + if (amd_gen >= CHIPSET_YANGTZE) { + static const char *spireadmodes[] = { + "Normal (up to 33 MHz)", + "Reserved", + "Dual IO (1-1-2)", + "Quad IO (1-1-4)", + "Dual IO (1-2-2)", + "Quad IO (1-4-4)", + "Normal (up to 66 MHz)", + "Fast Read", + }; + tmp = mmio_readb(sb600_spibar + 0x00); + uint8_t read_mode = ((tmp >> 28) & 0x6) | ((tmp >> 18) & 0x1); + msg_pdbg("SpiReadMode=%s (%i)\n", spireadmodes[read_mode], read_mode); + tmp = mmio_readb(sb600_spibar + 0x20); + msg_pdbg("UseSpi100 is %sabled\n", (tmp & 0x1) ? "en" : "dis"); + tmp = mmio_readw(sb600_spibar + 0x22); /* SPI100 Speed Config */ + msg_pdbg("NormSpeedNew is %s\n", spispeeds[(tmp >> 12) & 0xf].name); + msg_pdbg("FastSpeedNew is %s\n", spispeeds[(tmp >> 8) & 0xf].name); + msg_pdbg("AltSpeedNew is %s\n", spispeeds[(tmp >> 4) & 0xf].name); + msg_pdbg("TpmSpeedNew is %s\n", spispeeds[(tmp >> 0) & 0xf].name); + } else { if (amd_gen >= CHIPSET_SB89XX && amd_gen <= CHIPSET_HUDSON234) { bool fast_read = (mmio_readl(sb600_spibar + 0x00) >> 18) & 0x1; msg_pdbg("Fast Reads are %sabled\n", fast_read ? "en" : "dis"); @@ -327,13 +391,28 @@ static int handle_speed(struct pci_dev *dev) return set_speed(dev, &spispeeds[spispeed_idx]); }
- -static int sb600_handle_imc(struct pci_dev *dev, bool amd_imc_force) +static int handle_imc(struct pci_dev *dev) { /* Handle IMC everywhere but sb600 which does not have one. */ - if (dev->device_id == 0x438d) + if (amd_gen == CHIPSET_SB6XX) return 0;
+ bool amd_imc_force = false; + char *arg = extract_programmer_param("amd_imc_force"); + if (arg && !strcmp(arg, "yes")) { + amd_imc_force = true; + msg_pspew("amd_imc_force enabled.\n"); + } else if (arg && !strlen(arg)) { + msg_perr("Missing argument for amd_imc_force.\n"); + free(arg); + return 1; + } else if (arg) { + msg_perr("Unknown argument for amd_imc_force: "%s" (not "yes").\n", arg); + free(arg); + return 1; + } + free(arg); + /* TODO: we should not only look at IntegratedImcPresent (LPC Dev 20, Func 3, 40h) but also at * IMCEnable(Strap) and Override EcEnable(Strap) (sb8xx, sb9xx?, a50: Misc_Reg: 80h-87h; * sb7xx, sp5100: PM_Reg: B0h-B1h) etc. */ @@ -361,7 +440,7 @@ static int sb600_handle_imc(struct pci_dev *dev, bool amd_imc_force) return amd_imc_shutdown(dev); }
-static const struct spi_programmer spi_programmer_sb600 = { +static struct spi_programmer spi_programmer_sb600 = { .type = SPI_CONTROLLER_SB600, .max_data_read = 8, .max_data_write = 5, @@ -377,22 +456,6 @@ int sb600_probe_spi(struct pci_dev *dev) struct pci_dev *smbus_dev; uint32_t tmp; uint8_t reg; - bool amd_imc_force = false; - - char *arg = extract_programmer_param("amd_imc_force"); - if (arg && !strcmp(arg, "yes")) { - amd_imc_force = true; - msg_pspew("amd_imc_force enabled.\n"); - } else if (arg && !strlen(arg)) { - msg_perr("Missing argument for amd_imc_force.\n"); - free(arg); - return ERROR_FATAL; - } else if (arg) { - msg_perr("Unknown argument for amd_imc_force: "%s" (not "yes").\n", arg); - free(arg); - return ERROR_FATAL; - } - free(arg);
/* Read SPI_BaseAddr */ tmp = pci_read_long(dev, 0xa0); @@ -419,12 +482,6 @@ int sb600_probe_spi(struct pci_dev *dev) amd_gen = CHIPSET_SB6XX; }
- if (amd_gen == CHIPSET_YANGTZE) { - msg_perr("SPI on Kabini/Temash and newer chipsets are not yet supported.\n" - "Please try a newer version of flashrom.\n"); - return ERROR_NONFATAL; - } - /* Chipset support matrix for SPI Base_Addr (LPC PCI reg 0xa0) * bit 6xx 7xx/SP5100 8xx 9xx hudson1 hudson234 yangtze * 3 rsvd <- <- ? <- ? RouteTpm2Spi @@ -437,6 +494,8 @@ int sb600_probe_spi(struct pci_dev *dev) msg_pdbg("SpiRomEnable=%i", (tmp >> 1) & 0x1); if (amd_gen == CHIPSET_SB7XX) msg_pdbg(", AltSpiCSEnable=%i, AbortEnable=%i", tmp & 0x1, (tmp >> 2) & 0x1); + else if (amd_gen == CHIPSET_YANGTZE) + msg_pdbg(", RouteTpm2Sp=%i", (tmp >> 3) & 0x1);
tmp = pci_read_byte(dev, 0xba); msg_pdbg(", PrefetchEnSPIFromIMC=%i", (tmp & 0x4) >> 2); @@ -470,15 +529,21 @@ int sb600_probe_spi(struct pci_dev *dev) */ tmp = mmio_readl(sb600_spibar + 0x00); msg_pdbg("(0x%08" PRIx32 ") SpiArbEnable=%i", tmp, (tmp >> 19) & 0x1); + if (amd_gen == CHIPSET_YANGTZE) + msg_pdbg(", IllegalAccess=%i", (tmp >> 21) & 0x1);
msg_pdbg(", SpiAccessMacRomEn=%i, SpiHostAccessRomEn=%i, ArbWaitCount=%i", (tmp >> 22) & 0x1, (tmp >> 23) & 0x1, (tmp >> 24) & 0x7);
+ if (amd_gen != CHIPSET_YANGTZE) + msg_pdbg(", SpiBridgeDisable=%i", (tmp >> 27) & 0x1); + switch (amd_gen) { case CHIPSET_SB7XX: msg_pdbg(", DropOneClkOnRd/SpiClkGate=%i", (tmp >> 28) & 0x1); case CHIPSET_SB89XX: case CHIPSET_HUDSON234: + case CHIPSET_YANGTZE: msg_pdbg(", SpiBusy=%i", (tmp >> 31) & 0x1); default: break; } @@ -531,12 +596,14 @@ int sb600_probe_spi(struct pci_dev *dev) if (handle_speed(dev) != 0) return ERROR_FATAL;
- if (sb600_handle_imc(dev, amd_imc_force) != 0) + if (handle_imc(dev) != 0) return ERROR_FATAL;
- /* Bring the FIFO to a clean state. */ - reset_internal_fifo_pointer(); - + /* Starting with Yangtze the SPI controller got a bigger FIFO */ + if (amd_gen == CHIPSET_YANGTZE) { + spi_programmer_sb600.max_data_read = 71; + spi_programmer_sb600.max_data_read = 71 - 3; + } register_spi_programmer(&spi_programmer_sb600); return 0; }