[flashrom] [PATCH] Add Nvidia nForce MCP6x/MCP7x series SPI flashing support

Carl-Daniel Hailfinger c-d.hailfinger.devel.2006 at gmx.net
Thu Jun 17 04:37:35 CEST 2010


Add Nvidia nForce MCP61/MCP65/MCP67/MCP78S/MCP73/MCP79 SPI flashing support.
Fix a few problems in the previously unused SPI bitbanging core.

This code is untested and may fry your flash chip, explode mysteriously
and abduct your dog.
Do NOT try to read/write/erase with this code until we know that it
behaves correctly.

Logs from "flashrom -V" on all newer Nvidia nForce chipsets appreciated.

Huge thanks go to Michael Karcher for reverse engineering the interface
and to Johannes Sjolund for testing multiple iterations of my patch on
his hardware until it worked.

Signed-off-by: Carl-Daniel Hailfinger <c-d.hailfinger.devel.2006 at gmx.net>

Johannes, you can also find this patch near the top at
http://patchwork.coreboot.org/project/flashrom/list/ in case your mailer
corrupts the patch. It applies against latest clean svn.

Index: flashrom-bitbang_spi_nvidia_mcp/flash.h
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/flash.h	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/flash.h	(Arbeitskopie)
@@ -128,13 +128,16 @@
 void programmer_delay(int usecs);
 
 enum bitbang_spi_master {
+#if CONFIG_INTERNAL == 1
+#if defined(__i386__) || defined(__x86_64__)
+	BITBANG_SPI_MASTER_MCP,
+#endif
+#endif
 	BITBANG_SPI_INVALID /* This must always be the last entry. */
 };
 
 extern const int bitbang_spi_master_count;
 
-extern enum bitbang_spi_master bitbang_spi_master;
-
 struct bitbang_spi_master_entry {
 	void (*set_cs) (int val);
 	void (*set_sck) (int val);
@@ -529,10 +532,22 @@
 int ft2232_spi_read(struct flashchip *flash, uint8_t *buf, int start, int len);
 int ft2232_spi_write_256(struct flashchip *flash, uint8_t *buf);
 
+/* mcp6x_spi.c */
+#if CONFIG_INTERNAL == 1
+#if defined(__i386__) || defined(__x86_64__)
+extern void *mcp6x_spibar;
+int mcp6x_spi_init(void);
+void mcp6x_bitbang_set_cs(int val);
+void mcp6x_bitbang_set_sck(int val);
+void mcp6x_bitbang_set_mosi(int val);
+int mcp6x_bitbang_get_miso(void);
+#endif
+#endif
+
 /* bitbang_spi.c */
 extern int bitbang_spi_half_period;
 extern const struct bitbang_spi_master_entry bitbang_spi_master_table[];
-int bitbang_spi_init(void);
+int bitbang_spi_init(enum bitbang_spi_master master);
 int bitbang_spi_send_command(unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr);
 int bitbang_spi_read(struct flashchip *flash, uint8_t *buf, int start, int len);
 int bitbang_spi_write_256(struct flashchip *flash, uint8_t *buf);
@@ -635,6 +650,7 @@
 	SPI_CONTROLLER_SB600,
 	SPI_CONTROLLER_VIA,
 	SPI_CONTROLLER_WBSIO,
+	SPI_CONTROLLER_MCP6X_BITBANG,
 #endif
 #endif
 #if CONFIG_FT2232_SPI == 1
Index: flashrom-bitbang_spi_nvidia_mcp/spi25.c
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/spi25.c	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/spi25.c	(Arbeitskopie)
@@ -178,6 +178,7 @@
 	case SPI_CONTROLLER_VIA:
 	case SPI_CONTROLLER_SB600:
 	case SPI_CONTROLLER_WBSIO:
+	case SPI_CONTROLLER_MCP6X_BITBANG:
 #endif
 #endif
 #if CONFIG_FT2232_SPI == 1
Index: flashrom-bitbang_spi_nvidia_mcp/hwaccess.h
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/hwaccess.h	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/hwaccess.h	(Arbeitskopie)
@@ -169,6 +169,10 @@
 #define __DARWIN__
 #endif
 
+/* Clarification about OUTB/OUTW/OUTL argument order:
+ * OUT[BWL](val, port)
+ */
+
 #if defined(__FreeBSD__) || defined(__DragonFly__)
   #include <machine/cpufunc.h>
   #define off64_t off_t
Index: flashrom-bitbang_spi_nvidia_mcp/bitbang_spi.c
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/bitbang_spi.c	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/bitbang_spi.c	(Arbeitskopie)
@@ -26,17 +26,29 @@
 #include "chipdrivers.h"
 #include "spi.h"
 
-/* Length of half a clock period in usecs */
-int bitbang_spi_half_period = 0;
+/* Length of half a clock period in usecs. Default to 1 (500 kHz). */
+int bitbang_spi_half_period = 1;
 
 enum bitbang_spi_master bitbang_spi_master = BITBANG_SPI_INVALID;
 
 const struct bitbang_spi_master_entry bitbang_spi_master_table[] = {
+#if CONFIG_INTERNAL == 1
+#if defined(__i386__) || defined(__x86_64__)
+	{
+		.set_cs = mcp6x_bitbang_set_cs,
+		.set_sck = mcp6x_bitbang_set_sck,
+		.set_mosi = mcp6x_bitbang_set_mosi,
+		.get_miso = mcp6x_bitbang_get_miso,
+	},
+#endif
+#endif
+
 	{}, /* This entry corresponds to BITBANG_SPI_INVALID. */
 };
 
 const int bitbang_spi_master_count = ARRAY_SIZE(bitbang_spi_master_table);
 
+/* Note that CS# is active low, so val=0 means the chip is active. */
 void bitbang_spi_set_cs(int val)
 {
 	bitbang_spi_master_table[bitbang_spi_master].set_cs(val);
@@ -57,10 +69,18 @@
 	return bitbang_spi_master_table[bitbang_spi_master].get_miso();
 }
 
-int bitbang_spi_init(void)
+int bitbang_spi_init(enum bitbang_spi_master master)
 {
+	bitbang_spi_master = master;
+
+	if (bitbang_spi_master == BITBANG_SPI_INVALID) {
+		msg_perr("Invalid bitbang SPI master. \n"
+			 "Please report a bug at flashrom at flashrom.org\n");
+		return 1;
+	}
 	bitbang_spi_set_cs(1);
 	bitbang_spi_set_sck(0);
+	bitbang_spi_set_mosi(0);
 	buses_supported = CHIP_BUSTYPE_SPI;
 	return 0;
 }
@@ -87,6 +107,7 @@
 {
 	static unsigned char *bufout = NULL;
 	static unsigned char *bufin = NULL;
+	unsigned char *tmp;
 	static int oldbufsize = 0;
 	int bufsize;
 	int i;
@@ -98,20 +119,34 @@
 	bufsize = max(writecnt + readcnt, 260);
 	/* Never shrink. realloc() calls are expensive. */
 	if (bufsize > oldbufsize) {
-		bufout = realloc(bufout, bufsize);
-		if (!bufout) {
+		tmp = realloc(bufout, bufsize);
+		if (!tmp) {
 			msg_perr("Out of memory!\n");
+			if (bufout)
+				free(bufout);
+			bufout = NULL;
 			if (bufin)
 				free(bufin);
+			bufin = NULL;
+			oldbufsize = 0;
 			exit(1);
-		}
-		bufin = realloc(bufout, bufsize);
-		if (!bufin) {
+		} else
+			bufout = tmp;
+
+		tmp = realloc(bufin, bufsize);
+		if (!tmp) {
 			msg_perr("Out of memory!\n");
+			if (bufin)
+				free(bufin);
+			bufin = NULL;
 			if (bufout)
 				free(bufout);
+			bufout = NULL;
+			oldbufsize = 0;
 			exit(1);
-		}
+		} else
+			bufin = tmp;
+
 		oldbufsize = bufsize;
 	}
 		
@@ -135,8 +170,13 @@
 
 int bitbang_spi_read(struct flashchip *flash, uint8_t *buf, int start, int len)
 {
-	/* Maximum read length is unlimited, use 64k bytes. */
-	return spi_read_chunked(flash, buf, start, len, 64 * 1024);
+	/* Maximum read length is unlimited in theory.
+	 * The current implementation can handle reads of up to 65536 bytes.
+	 * Please note that you need two buffers of 2n+4 bytes each for a read
+	 * of n bytes, resulting in a total memory requirement of 4n+8 bytes.
+	 * To conserve memory, read in chunks of 256 bytes.
+	 */
+	return spi_read_chunked(flash, buf, start, len, 256);
 }
 
 int bitbang_spi_write_256(struct flashchip *flash, uint8_t *buf)
Index: flashrom-bitbang_spi_nvidia_mcp/spi.c
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/spi.c	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/spi.c	(Arbeitskopie)
@@ -83,6 +83,13 @@
 		.read = wbsio_spi_read,
 		.write_256 = wbsio_spi_write_1,
 	},
+
+	{ /* SPI_CONTROLLER_MCP6X_BITBANG */
+		.command = bitbang_spi_send_command,
+		.multicommand = default_spi_send_multicommand,
+		.read = bitbang_spi_read,
+		.write_256 = bitbang_spi_write_256,
+	},
 #endif
 #endif
 
Index: flashrom-bitbang_spi_nvidia_mcp/Makefile
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/Makefile	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/Makefile	(Arbeitskopie)
@@ -107,8 +107,12 @@
 # Always enable serprog for now. Needs to be disabled on Windows.
 CONFIG_SERPROG ?= yes
 
-# Bitbanging SPI infrastructure is not used yet.
+# Bitbanging SPI infrastructure, default off unless needed.
+ifeq ($(CONFIG_INTERNAL), yes)
+CONFIG_BITBANG_SPI = yes
+else
 CONFIG_BITBANG_SPI ?= no
+endif
 
 # Always enable 3Com NICs for now.
 CONFIG_NIC3COM ?= yes
@@ -151,7 +155,7 @@
 FEATURE_CFLAGS += -D'CONFIG_INTERNAL=1'
 PROGRAMMER_OBJS += processor_enable.o chipset_enable.o board_enable.o cbtable.o dmi.o internal.o
 # FIXME: The PROGRAMMER_OBJS below should only be included on x86.
-PROGRAMMER_OBJS += it87spi.o ichspi.o sb600spi.o wbsio_spi.o
+PROGRAMMER_OBJS += it87spi.o ichspi.o sb600spi.o wbsio_spi.o mcp6x_spi.o
 NEED_PCI := yes
 endif
 
Index: flashrom-bitbang_spi_nvidia_mcp/chipset_enable.c
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/chipset_enable.c	(Revision 1049)
+++ flashrom-bitbang_spi_nvidia_mcp/chipset_enable.c	(Arbeitskopie)
@@ -1092,10 +1092,8 @@
 {
 	int ret = 0;
 	uint8_t val;
-	uint16_t status;
 	char *busname;
-	uint32_t mcp_spibaraddr;
-	void *mcp_spibar;
+	uint32_t mcp6x_spibaraddr;
 	struct pci_dev *smbusdev;
 
 	msg_pinfo("This chipset is not really supported yet. Guesswork...\n");
@@ -1144,40 +1142,33 @@
 		smbusdev->bus, smbusdev->dev, smbusdev->func);
 
 	/* Locate the BAR where the SPI interface lives. */
-	mcp_spibaraddr = pci_read_long(smbusdev, 0x74);
-	msg_pdbg("SPI BAR is at 0x%08x, ", mcp_spibaraddr);
+	mcp6x_spibaraddr = pci_read_long(smbusdev, 0x74);
+	msg_pdbg("SPI BAR is at 0x%08x, ", mcp6x_spibaraddr);
 	/* We hope this has native alignment. We know the SPI interface (well,
 	 * a set of GPIOs that is connected to SPI flash) is at offset 0x530,
 	 * so we expect a size of at least 0x800. Clear the lower bits.
 	 * It is entirely possible that the BAR is 64k big and the low bits are
 	 * reserved for an entirely different purpose.
 	 */
-	mcp_spibaraddr &= ~0x7ff;
-	msg_pdbg("after clearing low bits BAR is at 0x%08x\n", mcp_spibaraddr);
+	mcp6x_spibaraddr &= ~0x7ff;
+	msg_pdbg("after clearing low bits BAR is at 0x%08x\n", mcp6x_spibaraddr);
 
 	/* Accessing a NULL pointer BAR is evil. Don't do it. */
-	if (mcp_spibaraddr && (buses_supported == CHIP_BUSTYPE_SPI)) {
+	if (mcp6x_spibaraddr && (buses_supported == CHIP_BUSTYPE_SPI)) {
 		/* Map the BAR. Bytewise/wordwise access at 0x530 and 0x540. */
-		mcp_spibar = physmap("MCP67 SPI", mcp_spibaraddr, 0x544);
+		mcp6x_spibar = physmap("Nvidia MCP6x SPI", mcp6x_spibaraddr, 0x544);
 
-/* Guessed. If this is correct, migrate to a separate MCP67 SPI driver. */
-#define MCP67_SPI_CS		(1 << 1)
-#define MCP67_SPI_SCK		(1 << 2)
-#define MCP67_SPI_MOSI		(1 << 3)
-#define MCP67_SPI_MISO		(1 << 4)
-#define MCP67_SPI_ENABLE	(1 << 0)
-#define MCP67_SPI_IDLE		(1 << 8)
-
-		status = mmio_readw(mcp_spibar + 0x530);
-		msg_pdbg("SPI control is 0x%04x, enable=%i, idle=%i\n",
-			 status, status & 0x1, (status >> 8) & 0x1);
+		if (mcp6x_spi_init())
+			ret = 1;
+#if 0
 		/* FIXME: Remove the physunmap once the SPI driver exists. */
-		physunmap(mcp_spibar, 0x544);
-	} else if (!mcp_spibaraddr && (buses_supported & CHIP_BUSTYPE_SPI)) {
+		physunmap(mcp6x_spibar, 0x544);
+#endif
+	} else if (!mcp6x_spibaraddr && (buses_supported & CHIP_BUSTYPE_SPI)) {
 		msg_pdbg("Strange. MCP SPI BAR is invalid.\n");
 		buses_supported &= ~CHIP_BUSTYPE_SPI;
 		ret = 1;
-	} else if (mcp_spibaraddr && !(buses_supported & CHIP_BUSTYPE_SPI)) {
+	} else if (mcp6x_spibaraddr && !(buses_supported & CHIP_BUSTYPE_SPI)) {
 		msg_pdbg("Strange. MCP SPI BAR is valid, but chipset apparently"
 			 " doesn't have SPI enabled.\n");
 	} else {
@@ -1215,8 +1206,7 @@
 		result = enable_flash_mcp55(dev, name);
 		break;
 	case CHIP_BUSTYPE_SPI:
-		msg_pinfo("SPI on this chipset is not supported yet.\n");
-		buses_supported = CHIP_BUSTYPE_NONE;
+		msg_perr("SPI on this chipset is WIP. DO NOT USE!\n");
 		break;
 	default:
 		msg_pinfo("Something went wrong with bus type detection.\n");
@@ -1241,8 +1231,7 @@
 		msg_pinfo("LPC on this chipset is not supported yet.\n");
 		break;
 	case CHIP_BUSTYPE_SPI:
-		msg_pinfo("SPI on this chipset is not supported yet.\n");
-		buses_supported = CHIP_BUSTYPE_NONE;
+		msg_perr("SPI on this chipset is WIP. DO NOT USE!\n");
 		break;
 	default:
 		msg_pinfo("Something went wrong with bus type detection.\n");
Index: flashrom-bitbang_spi_nvidia_mcp/mcp6x_spi.c
===================================================================
--- flashrom-bitbang_spi_nvidia_mcp/mcp6x_spi.c	(Revision 0)
+++ flashrom-bitbang_spi_nvidia_mcp/mcp6x_spi.c	(Revision 0)
@@ -0,0 +1,132 @@
+/*
+ * This file is part of the flashrom project.
+ *
+ * Copyright (C) 2010 Carl-Daniel Hailfinger
+ *
+ * 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; version 2 of the License.
+ *
+ * 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
+ */
+
+/* Driver for the Nvidia MCP6x/MCP7x MCP6X_SPI controller.
+ * Based on clean room reverse engineered docs from
+ * http://www.flashrom.org/pipermail/flashrom/2009-December/001180.html
+ * created by Michael Karcher.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdint.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include "flash.h"
+
+/* We have two sets of pins, out and in. The numbers for both sets are
+ * independent and are bitshift values, not real pin numbers.
+ */
+
+/* Guessed. */
+#define MCP6X_SPI_CS		1
+#define MCP6X_SPI_SCK		2
+#define MCP6X_SPI_MOSI		3
+#define MCP6X_SPI_MISO		4
+#define MCP6X_SPI_ENABLE	0
+#define MCP6X_SPI_IDLE		8
+
+void *mcp6x_spibar = NULL;
+
+void mcp6x_request_spibus(void)
+{
+	uint8_t byte;
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte |= 1 << MCP6X_SPI_ENABLE;
+	mmio_writeb(byte, mcp6x_spibar + 0x530);
+
+	/* Wait until we are allowed to use the SPI bus. */
+	while (!(mmio_readw(mcp6x_spibar + 0x530) & (1 << MCP6X_SPI_IDLE))) ;
+}
+
+void mcp6x_release_spibus(void)
+{
+	uint8_t byte;
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte &= ~(1 << MCP6X_SPI_ENABLE);
+	mmio_writeb(byte, mcp6x_spibar + 0x530);
+}
+
+void mcp6x_bitbang_set_cs(int val)
+{
+	uint8_t byte;
+
+	/* Requesting and releasing the SPI bus is handled in here to allow the
+	 * chipset to use its own SPI engine for native reads.
+	 */
+	if (val == 0)
+		mcp6x_request_spibus();
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte &= ~(1 << MCP6X_SPI_CS);
+	byte |= (val << MCP6X_SPI_CS);
+	mmio_writeb(byte, mcp6x_spibar + 0x530);
+
+	if (val == 1)
+		mcp6x_release_spibus();
+}
+
+void mcp6x_bitbang_set_sck(int val)
+{
+	uint8_t byte;
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte &= ~(1 << MCP6X_SPI_SCK);
+	byte |= (val << MCP6X_SPI_SCK);
+	mmio_writeb(byte, mcp6x_spibar + 0x530);
+}
+
+void mcp6x_bitbang_set_mosi(int val)
+{
+	uint8_t byte;
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte &= ~(1 << MCP6X_SPI_MOSI);
+	byte |= (val << MCP6X_SPI_MOSI);
+	mmio_writeb(byte, mcp6x_spibar + 0x530);
+}
+
+int mcp6x_bitbang_get_miso(void)
+{
+	uint8_t byte;
+
+	byte = mmio_readb(mcp6x_spibar + 0x530);
+	byte = (byte >> MCP6X_SPI_MISO) & 0x1;
+	return byte;
+}
+
+int mcp6x_spi_init(void)
+{
+	uint16_t status;
+
+	status = mmio_readw(mcp6x_spibar + 0x530);
+	msg_pdbg("SPI control is 0x%04x, enable=%i, idle=%i\n",
+		 status, (status >> MCP6X_SPI_ENABLE) & 0x1,
+		 (status >> MCP6X_SPI_IDLE) & 0x1);
+
+	if (bitbang_spi_init(BITBANG_SPI_MASTER_MCP))
+		return 1;
+	spi_controller = SPI_CONTROLLER_MCP6X_BITBANG;
+
+	return 0;
+}
+
+#endif


-- 
http://www.hailfinger.org/





More information about the flashrom mailing list