[flashrom] [PATCH 3/4] sbxxx: Add support for new AMD SPI controller.

Stefan Tauner stefan.tauner at student.tuwien.ac.at
Fri Aug 9 00:55:01 CEST 2013


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 at aristanetworks.com>
Signed-off-by: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006 at gmx.net>
Signed-off-by: Stefan Tauner <stefan.tauner at 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 at amd.com>
  * Copyright (C) 2008 Joe Bao <Zheng.Bao at 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;
 }
-- 
Kind regards, Stefan Tauner





More information about the flashrom mailing list