From 2f43616873dd88cd417017dc5bc218b3e10deb0d Mon Sep 17 00:00:00 2001 From: Carl-Daniel Hailfinger Date: Wed, 28 Jul 2010 15:08:35 +0000 Subject: Add Nvidia nForce MCP61/MCP65/MCP67/MCP78S/MCP73/MCP79 SPI flashing support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Huge thanks go to Michael Karcher for reverse engineering the interface and to Johannes Sjölund for testing the first iterations of my patch on his hardware until it worked. Thanks to the following testers of the patch: * MCP61, 10de:03e0, LPC OK, ECS Geforce6100SM-M, Andrew Cleveland * MCP61, 10de:03e0, LPC OK, Biostar NF520-A2 NF61D-A2, Vitaliy Buchynskyy * MCP65, 10de:0441, SPI OK, MSI MS-7369 K9N Neo-F v2, Kjell Braden * MCP65, 10de:0441, SPI OK, MSI MS-7369, Wolfgang Schnitker * MCP65, 10de:0441, SPI OK, MSI MS-7369, Johannes Sjölund * MCP65, 10de:0441, SPI OK, MSI MS-7369, Melchior Franz * MCP78S, 10de:075c, SPI OK, Asus M3N78 PRO, Brad Rogers * MCP78S, 10de:075c, SPI OK, Asus M3N78-VM, Marcel Partap * MCP78S, 10de:075c, SPI OK, Asus M4N78 PRO, Kimmo Vuorinen * MCP78S, 10de:075c, SPI OK, Asus M4N78 PRO, Vikram Ambrose * MCP79, 10de:0aad, SPI OK, Acer Aspire R3600, Andrew Morgan * MCP79, 10de:0aae, LPC ??, Lenovo Ideapad S12 laptop, Christian Schmitt * MCP79, 10de:0aae, SPI OK, Apple iMac9,1 Mac-F2218EA9, David "dledson" flashrom will refuse to write/erase for safety reasons if MCP6x/MCP7x SPI is detected. Corresponding to flashrom svn r1113. Signed-off-by: Carl-Daniel Hailfinger Acked-by: Uwe Hermann --- Makefile | 6 +- chipset_enable.c | 185 +++++++++++----------------------------------------- mcp6x_spi.c | 193 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ programmer.h | 13 ++++ spi.c | 7 ++ 5 files changed, 257 insertions(+), 147 deletions(-) create mode 100644 mcp6x_spi.c diff --git a/Makefile b/Makefile index 290b9e6..782ecac 100644 --- a/Makefile +++ b/Makefile @@ -118,8 +118,12 @@ CONFIG_RAYER_SPI ?= yes ifeq ($(CONFIG_RAYER_SPI), yes) override CONFIG_BITBANG_SPI = yes else +ifeq ($(CONFIG_INTERNAL), yes) +override CONFIG_BITBANG_SPI = yes +else CONFIG_BITBANG_SPI ?= no endif +endif # Always enable 3Com NICs for now. CONFIG_NIC3COM ?= yes @@ -162,7 +166,7 @@ ifeq ($(CONFIG_INTERNAL), yes) 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 diff --git a/chipset_enable.c b/chipset_enable.c index 17318c2..d75fc82 100644 --- a/chipset_enable.c +++ b/chipset_enable.c @@ -868,18 +868,16 @@ static int enable_flash_mcp55(struct pci_dev *dev, const char *name) return 0; } -/* This is a shot in the dark. Even if the code is totally bogus for some - * chipsets, users will at least start to send in reports. +/** + * The MCP6x/MCP7x code is based on cleanroom reverse engineering. + * It is assumed that LPC chips need the MCP55 code and SPI chips need the + * code provided in enable_flash_mcp6x_7x_common. */ -static int enable_flash_mcp6x_7x_common(struct pci_dev *dev, const char *name) +static int enable_flash_mcp6x_7x(struct pci_dev *dev, const char *name) { int ret = 0; + int want_spi = 0; uint8_t val; - uint16_t status; - char *busname; - uint32_t mcp_spibaraddr; - void *mcp_spibar; - struct pci_dev *smbusdev; msg_pinfo("This chipset is not really supported yet. Guesswork...\n"); @@ -887,20 +885,31 @@ static int enable_flash_mcp6x_7x_common(struct pci_dev *dev, const char *name) val = pci_read_byte(dev, 0x8a); msg_pdbg("ISA/LPC bridge reg 0x8a contents: 0x%02x, bit 6 is %i, bit 5 " "is %i\n", val, (val >> 6) & 0x1, (val >> 5) & 0x1); + switch ((val >> 5) & 0x3) { case 0x0: + ret = enable_flash_mcp55(dev, name); buses_supported = CHIP_BUSTYPE_LPC; + msg_pdbg("Flash bus type is LPC\n"); break; case 0x2: - buses_supported = CHIP_BUSTYPE_SPI; + want_spi = 1; + /* SPI is added in mcp6x_spi_init if it works. + * Do we really want to disable LPC in this case? + */ + buses_supported = CHIP_BUSTYPE_NONE; + msg_pdbg("Flash bus type is SPI\n"); + msg_perr("SPI on this chipset is WIP. Write is unsupported!\n"); + programmer_may_write = 0; break; default: - buses_supported = CHIP_BUSTYPE_UNKNOWN; + /* Should not happen. */ + buses_supported = CHIP_BUSTYPE_NONE; + msg_pdbg("Flash bus type is unknown (none)\n"); + msg_pinfo("Something went wrong with bus type detection.\n"); + goto out_msg; break; } - busname = flashbuses_to_text(buses_supported); - msg_pdbg("Guessed flash bus type is %s\n", busname); - free(busname); /* Force enable SPI and disable LPC? Not a good idea. */ #if 0 @@ -909,62 +918,8 @@ static int enable_flash_mcp6x_7x_common(struct pci_dev *dev, const char *name) pci_write_byte(dev, 0x8a, val); #endif - /* Look for the SMBus device (SMBus PCI class) */ - smbusdev = pci_dev_find_vendorclass(0x10de, 0x0c05); - if (!smbusdev) { - if (buses_supported & CHIP_BUSTYPE_SPI) { - msg_perr("ERROR: SMBus device not found. Not enabling " - "SPI.\n"); - buses_supported &= ~CHIP_BUSTYPE_SPI; - ret = 1; - } else { - msg_pinfo("Odd. SMBus device not found.\n"); - } - goto out_msg; - } - msg_pdbg("Found SMBus device %04x:%04x at %02x:%02x:%01x\n", - smbusdev->vendor_id, smbusdev->device_id, - 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); - /* 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); - - /* Accessing a NULL pointer BAR is evil. Don't do it. */ - if (mcp_spibaraddr && (buses_supported == CHIP_BUSTYPE_SPI)) { - /* Map the BAR. Bytewise/wordwise access at 0x530 and 0x540. */ - mcp_spibar = physmap("MCP67 SPI", mcp_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); - /* FIXME: Remove the physunmap once the SPI driver exists. */ - physunmap(mcp_spibar, 0x544); - } else if (!mcp_spibaraddr && (buses_supported & CHIP_BUSTYPE_SPI)) { - msg_pdbg("Strange. MCP SPI BAR is invalid.\n"); - buses_supported &= ~CHIP_BUSTYPE_SPI; + if (mcp6x_spi_init(want_spi)) { ret = 1; - } else if (mcp_spibaraddr && !(buses_supported & CHIP_BUSTYPE_SPI)) { - msg_pdbg("Strange. MCP SPI BAR is valid, but chipset apparently" - " doesn't have SPI enabled.\n"); - } else { - msg_pdbg("MCP SPI is not used.\n"); } out_msg: msg_pinfo("Please send the output of \"flashrom -V\" to " @@ -974,68 +929,6 @@ out_msg: return ret; } -/** - * The MCP61/MCP67 code is guesswork based on cleanroom reverse engineering. - * Due to that, it only reads info and doesn't change any settings. - * It is assumed that LPC chips need the MCP55 code and SPI chips need the - * code provided in enable_flash_mcp6x_7x_common. Until we know for sure, call - * enable_flash_mcp55 from this function only if enable_flash_mcp6x_7x_common - * indicates the flash chip is LPC. Warning: enable_flash_mcp55 - * might make SPI flash inaccessible. The same caveat applies to SPI init - * for LPC flash. - */ -static int enable_flash_mcp67(struct pci_dev *dev, const char *name) -{ - int result = 0; - - result = enable_flash_mcp6x_7x_common(dev, name); - if (result) - return result; - - /* Not sure if this is correct. No docs as usual. */ - switch (buses_supported) { - case CHIP_BUSTYPE_LPC: - 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; - break; - default: - msg_pinfo("Something went wrong with bus type detection.\n"); - buses_supported = CHIP_BUSTYPE_NONE; - break; - } - - return result; -} - -static int enable_flash_mcp7x(struct pci_dev *dev, const char *name) -{ - int result = 0; - - result = enable_flash_mcp6x_7x_common(dev, name); - if (result) - return result; - - /* Not sure if this is correct. No docs as usual. */ - switch (buses_supported) { - case CHIP_BUSTYPE_LPC: - 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; - break; - default: - msg_pinfo("Something went wrong with bus type detection.\n"); - buses_supported = CHIP_BUSTYPE_NONE; - break; - } - - return result; -} - static int enable_flash_ht1000(struct pci_dev *dev, const char *name) { uint8_t val; @@ -1187,22 +1080,22 @@ const struct penable chipset_enables[] = { {0x10de, 0x0365, OK, "NVIDIA", "MCP55", enable_flash_mcp55}, /* LPC */ {0x10de, 0x0366, OK, "NVIDIA", "MCP55", enable_flash_mcp55}, /* LPC */ {0x10de, 0x0367, OK, "NVIDIA", "MCP55", enable_flash_mcp55}, /* Pro */ - {0x10de, 0x03e0, NT, "NVIDIA", "MCP61", enable_flash_mcp67}, - {0x10de, 0x03e1, NT, "NVIDIA", "MCP61", enable_flash_mcp67}, - {0x10de, 0x03e2, NT, "NVIDIA", "MCP61", enable_flash_mcp67}, - {0x10de, 0x03e3, NT, "NVIDIA", "MCP61", enable_flash_mcp67}, - {0x10de, 0x0440, NT, "NVIDIA", "MCP65", enable_flash_mcp7x}, - {0x10de, 0x0441, NT, "NVIDIA", "MCP65", enable_flash_mcp7x}, - {0x10de, 0x0442, NT, "NVIDIA", "MCP65", enable_flash_mcp7x}, - {0x10de, 0x0443, NT, "NVIDIA", "MCP65", enable_flash_mcp7x}, - {0x10de, 0x0548, OK, "NVIDIA", "MCP67", enable_flash_mcp67}, - {0x10de, 0x075c, NT, "NVIDIA", "MCP78S", enable_flash_mcp7x}, - {0x10de, 0x075d, NT, "NVIDIA", "MCP78S", enable_flash_mcp7x}, - {0x10de, 0x07d7, NT, "NVIDIA", "MCP73", enable_flash_mcp7x}, - {0x10de, 0x0aac, NT, "NVIDIA", "MCP79", enable_flash_mcp7x}, - {0x10de, 0x0aad, NT, "NVIDIA", "MCP79", enable_flash_mcp7x}, - {0x10de, 0x0aae, NT, "NVIDIA", "MCP79", enable_flash_mcp7x}, - {0x10de, 0x0aaf, NT, "NVIDIA", "MCP79", enable_flash_mcp7x}, + {0x10de, 0x03e0, NT, "NVIDIA", "MCP61", enable_flash_mcp6x_7x}, + {0x10de, 0x03e1, NT, "NVIDIA", "MCP61", enable_flash_mcp6x_7x}, + {0x10de, 0x03e2, NT, "NVIDIA", "MCP61", enable_flash_mcp6x_7x}, + {0x10de, 0x03e3, NT, "NVIDIA", "MCP61", enable_flash_mcp6x_7x}, + {0x10de, 0x0440, NT, "NVIDIA", "MCP65", enable_flash_mcp6x_7x}, + {0x10de, 0x0441, NT, "NVIDIA", "MCP65", enable_flash_mcp6x_7x}, + {0x10de, 0x0442, NT, "NVIDIA", "MCP65", enable_flash_mcp6x_7x}, + {0x10de, 0x0443, NT, "NVIDIA", "MCP65", enable_flash_mcp6x_7x}, + {0x10de, 0x0548, OK, "NVIDIA", "MCP67", enable_flash_mcp6x_7x}, + {0x10de, 0x075c, NT, "NVIDIA", "MCP78S", enable_flash_mcp6x_7x}, + {0x10de, 0x075d, NT, "NVIDIA", "MCP78S", enable_flash_mcp6x_7x}, + {0x10de, 0x07d7, NT, "NVIDIA", "MCP73", enable_flash_mcp6x_7x}, + {0x10de, 0x0aac, NT, "NVIDIA", "MCP79", enable_flash_mcp6x_7x}, + {0x10de, 0x0aad, NT, "NVIDIA", "MCP79", enable_flash_mcp6x_7x}, + {0x10de, 0x0aae, NT, "NVIDIA", "MCP79", enable_flash_mcp6x_7x}, + {0x10de, 0x0aaf, NT, "NVIDIA", "MCP79", enable_flash_mcp6x_7x}, {0x1039, 0x0496, NT, "SiS", "85C496+497", enable_flash_sis85c496}, {0x1039, 0x0406, NT, "SiS", "501/5101/5501", enable_flash_sis501}, {0x1039, 0x5511, NT, "SiS", "5511", enable_flash_sis5511}, diff --git a/mcp6x_spi.c b/mcp6x_spi.c new file mode 100644 index 0000000..f3890be --- /dev/null +++ b/mcp6x_spi.c @@ -0,0 +1,193 @@ +/* + * 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 +#include +#include +#include "flash.h" +#include "programmer.h" + +/* Bit positions for each pin. */ + +#define MCP6X_SPI_CS 1 +#define MCP6X_SPI_SCK 2 +#define MCP6X_SPI_MOSI 3 +#define MCP6X_SPI_MISO 4 +#define MCP6X_SPI_REQUEST 0 +#define MCP6X_SPI_GRANT 8 + +void *mcp6x_spibar = NULL; + +static void mcp6x_request_spibus(void) +{ + uint8_t tmp; + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp |= 1 << MCP6X_SPI_REQUEST; + mmio_writeb(tmp, mcp6x_spibar + 0x530); + + /* Wait until we are allowed to use the SPI bus. */ + while (!(mmio_readw(mcp6x_spibar + 0x530) & (1 << MCP6X_SPI_GRANT))) ; +} + +static void mcp6x_release_spibus(void) +{ + uint8_t tmp; + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp &= ~(1 << MCP6X_SPI_REQUEST); + mmio_writeb(tmp, mcp6x_spibar + 0x530); +} + +static void mcp6x_bitbang_set_cs(int val) +{ + uint8_t tmp; + + /* 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(); + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp &= ~(1 << MCP6X_SPI_CS); + tmp |= (val << MCP6X_SPI_CS); + mmio_writeb(tmp, mcp6x_spibar + 0x530); + + if (val == 1) + mcp6x_release_spibus(); +} + +static void mcp6x_bitbang_set_sck(int val) +{ + uint8_t tmp; + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp &= ~(1 << MCP6X_SPI_SCK); + tmp |= (val << MCP6X_SPI_SCK); + mmio_writeb(tmp, mcp6x_spibar + 0x530); +} + +static void mcp6x_bitbang_set_mosi(int val) +{ + uint8_t tmp; + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp &= ~(1 << MCP6X_SPI_MOSI); + tmp |= (val << MCP6X_SPI_MOSI); + mmio_writeb(tmp, mcp6x_spibar + 0x530); +} + +static int mcp6x_bitbang_get_miso(void) +{ + uint8_t tmp; + + tmp = mmio_readb(mcp6x_spibar + 0x530); + tmp = (tmp >> MCP6X_SPI_MISO) & 0x1; + return tmp; +} + +static const struct bitbang_spi_master bitbang_spi_master_mcp6x = { + .type = BITBANG_SPI_MASTER_MCP, + .set_cs = mcp6x_bitbang_set_cs, + .set_sck = mcp6x_bitbang_set_sck, + .set_mosi = mcp6x_bitbang_set_mosi, + .get_miso = mcp6x_bitbang_get_miso, +}; + +int mcp6x_spi_init(int want_spi) +{ + uint16_t status; + uint32_t mcp6x_spibaraddr; + struct pci_dev *smbusdev; + + /* Look for the SMBus device (SMBus PCI class) */ + smbusdev = pci_dev_find_vendorclass(0x10de, 0x0c05); + if (!smbusdev) { + if (want_spi) { + msg_perr("ERROR: SMBus device not found. Not enabling " + "SPI.\n"); + return 1; + } else { + msg_pinfo("Odd. SMBus device not found.\n"); + return 0; + } + } + msg_pdbg("Found SMBus device %04x:%04x at %02x:%02x:%01x\n", + smbusdev->vendor_id, smbusdev->device_id, + smbusdev->bus, smbusdev->dev, smbusdev->func); + + + /* Locate the BAR where the SPI interface lives. */ + mcp6x_spibaraddr = pci_read_long(smbusdev, 0x74); + /* BAR size is 64k, bits 15..4 are zero, bit 3..0 declare a + * 32-bit non-prefetchable memory BAR. + */ + mcp6x_spibaraddr &= ~0xffff; + msg_pdbg("MCP SPI BAR is at 0x%08x\n", mcp6x_spibaraddr); + + /* Accessing a NULL pointer BAR is evil. Don't do it. */ + if (!mcp6x_spibaraddr && want_spi) { + msg_perr("Error: Chipset is strapped for SPI, but MCP SPI BAR " + "is invalid.\n"); + return 1; + } else if (!mcp6x_spibaraddr && !want_spi) { + msg_pdbg("MCP SPI is not used.\n"); + return 0; + } else if (mcp6x_spibaraddr && !want_spi) { + msg_pdbg("Strange. MCP SPI BAR is valid, but chipset apparently" + " doesn't have SPI enabled.\n"); + /* FIXME: Should we enable SPI anyway? */ + return 0; + } + /* Map the BAR. Bytewise/wordwise access at 0x530 and 0x540. */ + mcp6x_spibar = physmap("Nvidia MCP6x SPI", mcp6x_spibaraddr, 0x544); + +#if 0 + /* FIXME: Run the physunmap in a shutdown function. */ + physunmap(mcp6x_spibar, 0x544); +#endif + + status = mmio_readw(mcp6x_spibar + 0x530); + msg_pdbg("SPI control is 0x%04x, req=%i, gnt=%i\n", + status, (status >> MCP6X_SPI_REQUEST) & 0x1, + (status >> MCP6X_SPI_GRANT) & 0x1); + + /* 1 usec halfperiod delay for now. */ + if (bitbang_spi_init(&bitbang_spi_master_mcp6x, 1)) { + /* This should never happen. */ + msg_perr("MCP6X bitbang SPI master init failed!\n"); + return 1; + } + + buses_supported |= CHIP_BUSTYPE_SPI; + spi_controller = SPI_CONTROLLER_MCP6X_BITBANG; + + return 0; +} + +#endif diff --git a/programmer.h b/programmer.h index 68ad500..226db04 100644 --- a/programmer.h +++ b/programmer.h @@ -110,6 +110,11 @@ enum bitbang_spi_master_type { #if CONFIG_RAYER_SPI == 1 BITBANG_SPI_MASTER_RAYER, #endif +#if CONFIG_INTERNAL == 1 +#if defined(__i386__) || defined(__x86_64__) + BITBANG_SPI_MASTER_MCP, +#endif +#endif }; struct bitbang_spi_master { @@ -404,6 +409,13 @@ int ft2232_spi_write_256(struct flashchip *flash, uint8_t *buf, int start, int l int rayer_spi_init(void); #endif +/* mcp6x_spi.c */ +#if CONFIG_INTERNAL == 1 +#if defined(__i386__) || defined(__x86_64__) +int mcp6x_spi_init(int want_spi); +#endif +#endif + /* bitbang_spi.c */ int bitbang_spi_init(const struct bitbang_spi_master *master, int halfperiod); int bitbang_spi_send_command(unsigned int writecnt, unsigned int readcnt, const unsigned char *writearr, unsigned char *readarr); @@ -455,6 +467,7 @@ enum spi_controller { SPI_CONTROLLER_SB600, SPI_CONTROLLER_VIA, SPI_CONTROLLER_WBSIO, + SPI_CONTROLLER_MCP6X_BITBANG, #endif #endif #if CONFIG_FT2232_SPI == 1 diff --git a/spi.c b/spi.c index 65d43be..b005cdc 100644 --- a/spi.c +++ b/spi.c @@ -83,6 +83,13 @@ const struct spi_programmer spi_programmer[] = { .read = wbsio_spi_read, .write_256 = spi_chip_write_1_new, }, + + { /* 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 -- cgit v1.1