mirror of
https://github.com/linuxboot/heads.git
synced 2024-12-20 05:28:08 +00:00
2686 lines
88 KiB
Diff
2686 lines
88 KiB
Diff
diff --git ./Makefile ./Makefile
|
|
index 4ebde1e..4257e82 100644
|
|
--- ./Makefile
|
|
+++ ./Makefile
|
|
@@ -214,6 +214,16 @@ UNSUPPORTED_FEATURES += CONFIG_GFXNVIDIA=yes
|
|
else
|
|
override CONFIG_GFXNVIDIA = no
|
|
endif
|
|
+ifeq ($(CONFIG_AST1100), yes)
|
|
+UNSUPPORTED_FEATURES += CONFIG_AST1100=yes
|
|
+else
|
|
+override CONFIG_AST1100 = no
|
|
+endif
|
|
+ifeq ($(CONFIG_AST2400), yes)
|
|
+UNSUPPORTED_FEATURES += CONFIG_AST2400=yes
|
|
+else
|
|
+override CONFIG_AST2400 = no
|
|
+endif
|
|
ifeq ($(CONFIG_SATASII), yes)
|
|
UNSUPPORTED_FEATURES += CONFIG_SATASII=yes
|
|
else
|
|
@@ -441,6 +451,16 @@ UNSUPPORTED_FEATURES += CONFIG_GFXNVIDIA=yes
|
|
else
|
|
override CONFIG_GFXNVIDIA = no
|
|
endif
|
|
+ifeq ($(CONFIG_AST1100), yes)
|
|
+UNSUPPORTED_FEATURES += CONFIG_AST1100=yes
|
|
+else
|
|
+override CONFIG_AST1100 = no
|
|
+endif
|
|
+ifeq ($(CONFIG_AST2400), yes)
|
|
+UNSUPPORTED_FEATURES += CONFIG_AST2400=yes
|
|
+else
|
|
+override CONFIG_AST2400 = no
|
|
+endif
|
|
ifeq ($(CONFIG_SATASII), yes)
|
|
UNSUPPORTED_FEATURES += CONFIG_SATASII=yes
|
|
else
|
|
@@ -514,7 +534,7 @@ endif
|
|
CHIP_OBJS = jedec.o stm50.o w39.o w29ee011.o \
|
|
sst28sf040.o 82802ab.o \
|
|
sst49lfxxxc.o sst_fwhub.o flashchips.o spi.o spi25.o spi25_statusreg.o \
|
|
- opaque.o sfdp.o en29lv640b.o at45db.o
|
|
+ spi4ba.o opaque.o sfdp.o en29lv640b.o at45db.o
|
|
|
|
###############################################################################
|
|
# Library code.
|
|
@@ -565,6 +585,12 @@ CONFIG_NIC3COM ?= yes
|
|
# Enable NVIDIA graphics cards. Note: write and erase do not work properly.
|
|
CONFIG_GFXNVIDIA ?= yes
|
|
|
|
+# Enable AST1100 BMC SoCs.
|
|
+CONFIG_AST1100 ?= yes
|
|
+
|
|
+# Enable AST2400 BMC SoCs.
|
|
+CONFIG_AST2400 ?= yes
|
|
+
|
|
# Always enable SiI SATA controllers for now.
|
|
CONFIG_SATASII ?= yes
|
|
|
|
@@ -664,6 +690,8 @@ ifeq ($(CONFIG_ENABLE_LIBPCI_PROGRAMMERS), no)
|
|
override CONFIG_INTERNAL = no
|
|
override CONFIG_NIC3COM = no
|
|
override CONFIG_GFXNVIDIA = no
|
|
+override CONFIG_AST1100 = no
|
|
+override CONFIG_AST2400 = no
|
|
override CONFIG_SATASII = no
|
|
override CONFIG_ATAHPT = no
|
|
override CONFIG_ATAVIA = no
|
|
@@ -776,6 +804,18 @@ PROGRAMMER_OBJS += gfxnvidia.o
|
|
NEED_LIBPCI += CONFIG_GFXNVIDIA
|
|
endif
|
|
|
|
+ifeq ($(CONFIG_AST1100), yes)
|
|
+FEATURE_CFLAGS += -D'CONFIG_AST1100=1'
|
|
+PROGRAMMER_OBJS += ast1100.o
|
|
+NEED_LIBPCI += CONFIG_AST1100
|
|
+endif
|
|
+
|
|
+ifeq ($(CONFIG_AST2400), yes)
|
|
+FEATURE_CFLAGS += -D'CONFIG_AST2400=1'
|
|
+PROGRAMMER_OBJS += ast2400.o
|
|
+NEED_LIBPCI += CONFIG_AST2400
|
|
+endif
|
|
+
|
|
ifeq ($(CONFIG_SATASII), yes)
|
|
FEATURE_CFLAGS += -D'CONFIG_SATASII=1'
|
|
PROGRAMMER_OBJS += satasii.o
|
|
diff --git ./ast1100.c ./ast1100.c
|
|
new file mode 100644
|
|
index 0000000..f661271
|
|
--- /dev/null
|
|
+++ ./ast1100.c
|
|
@@ -0,0 +1,421 @@
|
|
+/*
|
|
+ * This file is part of the flashrom project.
|
|
+ *
|
|
+ * Copyright (C) 2017 Raptor Engineering, LLC
|
|
+ *
|
|
+ * 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; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * 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
|
|
+ */
|
|
+
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include "flash.h"
|
|
+#include "programmer.h"
|
|
+#include "hwaccess.h"
|
|
+
|
|
+#define PCI_VENDOR_ID_ASPEED 0x1a03
|
|
+
|
|
+#define ASPEED_MEMMAP_SIZE (128 * 1024)
|
|
+#define ASPEED_P2A_OFFSET 0x10000
|
|
+
|
|
+#define AST1100_SCU_APB_ADDR 0x1e6e2000
|
|
+#define AST1100_SCU_APB_BRIDGE_OFFSET (AST1100_SCU_APB_ADDR & 0xffff)
|
|
+#define AST1100_SCU_PROT_KEY 0x00
|
|
+#define AST1100_SCU_HW_STRAP 0x70
|
|
+
|
|
+#define AST1100_SCU_PASSWORD 0x1688a8a8
|
|
+#define AST1100_SCU_BOOT_SRC_MASK 0x3
|
|
+#define AST1100_SCU_BOOT_SPI 0x2
|
|
+#define AST1100_SCU_BOOT_NONE 0x3
|
|
+
|
|
+#define AST1100_SMC_APB_ADDR 0x16000000
|
|
+#define AST1100_SMC_SMC00 0x00
|
|
+#define AST1100_SMC_CE_CTL(N) (0x4 + (N * 4))
|
|
+
|
|
+#define AST1100_SMC_SEGMENT_SIZE_MASK 0x3
|
|
+#define AST1100_SMC_SEGMENT_SIZE_32M 0x0
|
|
+#define AST1100_SMC_SEGMENT_SIZE_16M 0x1
|
|
+#define AST1100_SMC_SEGMENT_SIZE_8M 0x2
|
|
+#define AST1100_SMC_SEGMENT_SIZE_4M 0x3
|
|
+
|
|
+#define AST1100_SMC_FLASH_MMIO_ADDR 0x10000000
|
|
+
|
|
+#define AST1100_SPI_CMD_FAST_R_MODE 0x1
|
|
+#define AST1100_SPI_CMD_USER_MODE 0x3
|
|
+#define AST1100_SPI_CMD_MASK 0x3
|
|
+#define AST1100_SPI_STOP_CE_ACTIVE (0x1 << 2)
|
|
+#define AST1100_SPI_SPEED_SHIFT 8
|
|
+#define AST1100_SPI_SPEED_MASK (0x7 << AST1100_SPI_SPEED_SHIFT)
|
|
+
|
|
+#define AST1100_SPI_FLASH_MMIO_ADDR 0x30000000
|
|
+
|
|
+#define AST1100_WDT_APB_ADDR 0x1e785000
|
|
+#define AST1100_WDT_APB_BRIDGE_OFFSET (AST1100_WDT_APB_ADDR & 0xffff)
|
|
+
|
|
+#define AST1100_WDT1_CTR 0x00
|
|
+#define AST1100_WDT1_CTR_RELOAD 0x04
|
|
+#define AST1100_WDT1_CTR_RESTART 0x08
|
|
+#define AST1100_WDT1_CTL 0x0c
|
|
+
|
|
+#define AST1100_WDT_SET_CLOCK (0x1 << 4)
|
|
+#define AST1100_WDT_RESET_SYSTEM (0x1 << 1)
|
|
+#define AST1100_WDT_ENABLE (0x1 << 0)
|
|
+
|
|
+uint8_t *ast1100_device_bar = 0;
|
|
+uint8_t ast1100_device_spi_bus = 0;
|
|
+uint8_t ast1100_device_spi_speed = 0;
|
|
+uint8_t ast1100_device_halt_cpu = 0;
|
|
+uint8_t ast1100_device_reset_cpu = 0;
|
|
+uint8_t ast1100_device_resume_cpu = 0;
|
|
+uint8_t ast1100_device_tickle_fw = 0;
|
|
+uint32_t ast1100_device_flash_mmio_offset = 0;
|
|
+uint32_t ast1100_original_wdt_conf = 0;
|
|
+
|
|
+const struct dev_entry bmc_aspeed_ast1100[] = {
|
|
+ {PCI_VENDOR_ID_ASPEED, 0x2000, OK, "ASPEED", "AST1100" },
|
|
+
|
|
+ {0},
|
|
+};
|
|
+
|
|
+static int ast1100_spi_send_command(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr);
|
|
+
|
|
+static const struct spi_master spi_master_ast1100 = {
|
|
+ .type = SPI_CONTROLLER_AST1100,
|
|
+ .max_data_read = 256,
|
|
+ .max_data_write = 256,
|
|
+ .command = ast1100_spi_send_command,
|
|
+ .multicommand = default_spi_send_multicommand,
|
|
+ .read = default_spi_read,
|
|
+ .write_256 = default_spi_write_256,
|
|
+ .write_aai = default_spi_write_aai,
|
|
+};
|
|
+
|
|
+static int ast1100_set_a2b_bridge_scu(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST1100_SCU_APB_ADDR & 0xffff0000, ast1100_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast1100_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_set_a2b_bridge_wdt(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST1100_WDT_APB_ADDR & 0xffff0000, ast1100_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast1100_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_set_a2b_bridge_smc(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST1100_SMC_APB_ADDR, ast1100_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast1100_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_set_a2b_bridge_smc_flash(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST1100_SMC_FLASH_MMIO_ADDR + ast1100_device_flash_mmio_offset, ast1100_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast1100_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_disable_cpu(void) {
|
|
+ uint32_t dword;
|
|
+
|
|
+ if (ast1100_device_halt_cpu) {
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_HW_STRAP);
|
|
+ if (((dword & AST1100_SCU_BOOT_SRC_MASK) != AST1100_SCU_BOOT_SPI)
|
|
+ && ((dword & AST1100_SCU_BOOT_SRC_MASK) != AST1100_SCU_BOOT_NONE)) { /* NONE permitted to allow for BMC recovery after Ctrl+C or crash */
|
|
+ msg_perr("CPU halt requested but CPU firmware source is not SPI.\n");
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_PROT_KEY);
|
|
+ ast1100_device_halt_cpu = 0;
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ /* Disable CPU */
|
|
+ ast1100_set_a2b_bridge_scu();
|
|
+ pci_mmio_writel((dword & ~AST1100_SCU_BOOT_SRC_MASK) | AST1100_SCU_BOOT_NONE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_HW_STRAP);
|
|
+ ast1100_original_wdt_conf = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+ pci_mmio_writel(ast1100_original_wdt_conf & 0xffff0, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_enable_cpu(void) {
|
|
+ uint32_t dword;
|
|
+
|
|
+ if (ast1100_device_halt_cpu && ast1100_device_resume_cpu) {
|
|
+ /* Re-enable CPU */
|
|
+ ast1100_set_a2b_bridge_scu();
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_HW_STRAP);
|
|
+ pci_mmio_writel((dword & ~AST1100_SCU_BOOT_SRC_MASK) | AST1100_SCU_BOOT_SPI, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_HW_STRAP);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_reset_cpu(void) {
|
|
+ if (ast1100_device_reset_cpu) {
|
|
+ /* Disable WDT from issuing full SoC reset
|
|
+ * Without this, OpenPOWER systems will crash when the GPIO blocks are reset on WDT timeout
|
|
+ */
|
|
+ msg_pinfo("Configuring P2A bridge for WDT access\n");
|
|
+ ast1100_set_a2b_bridge_wdt();
|
|
+ ast1100_original_wdt_conf = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+
|
|
+ /* Initiate reset */
|
|
+ msg_pinfo("Setting WDT to reset CPU immediately\n");
|
|
+ pci_mmio_writel(ast1100_original_wdt_conf & 0xffff0, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+ pci_mmio_writel(0xec08ce00, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTR_RELOAD);
|
|
+ pci_mmio_writel(0x4755, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTR_RESTART);
|
|
+ pci_mmio_writel(AST1100_WDT_SET_CLOCK, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+ pci_mmio_writel(AST1100_WDT_RESET_SYSTEM | AST1100_WDT_ENABLE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_WDT_APB_BRIDGE_OFFSET + AST1100_WDT1_CTL);
|
|
+
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast1100_shutdown(void *data) {
|
|
+ /* Reactivate CPU if previously deactivated */
|
|
+ ast1100_enable_cpu();
|
|
+
|
|
+ /* Reset CPU if requested */
|
|
+ ast1100_reset_cpu();
|
|
+
|
|
+ /* Disable backdoor APB access */
|
|
+ pci_mmio_writel(0x0, ast1100_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int ast1100_init(void)
|
|
+{
|
|
+ struct pci_dev *dev = NULL;
|
|
+ uint32_t dword;
|
|
+
|
|
+ char *arg;
|
|
+
|
|
+ ast1100_device_spi_bus = 0;
|
|
+ arg = extract_programmer_param("spibus");
|
|
+ if (arg)
|
|
+ ast1100_device_spi_bus = strtol(arg, NULL, 0);
|
|
+ free(arg);
|
|
+
|
|
+ ast1100_device_spi_speed = 0;
|
|
+ arg = extract_programmer_param("spispeed");
|
|
+ if (arg)
|
|
+ ast1100_device_spi_speed = strtol(arg, NULL, 0);
|
|
+ free(arg);
|
|
+
|
|
+ ast1100_device_halt_cpu = 0;
|
|
+ arg = extract_programmer_param("cpu");
|
|
+ if (arg && !strcmp(arg,"pause")) {
|
|
+ ast1100_device_halt_cpu = 1;
|
|
+ ast1100_device_resume_cpu = 1;
|
|
+ ast1100_device_reset_cpu = 0;
|
|
+ }
|
|
+ else if (arg && !strcmp(arg,"halt")) {
|
|
+ ast1100_device_halt_cpu = 1;
|
|
+ ast1100_device_resume_cpu = 0;
|
|
+ ast1100_device_reset_cpu = 0;
|
|
+ }
|
|
+ else if (arg && !strcmp(arg,"reset")) {
|
|
+ ast1100_device_halt_cpu = 1;
|
|
+ ast1100_device_resume_cpu = 1;
|
|
+ ast1100_device_reset_cpu = 1;
|
|
+ }
|
|
+ else if (arg) {
|
|
+ msg_perr("Invalid CPU option! Valid values are: pause | halt | reset\n");
|
|
+ return 1;
|
|
+ }
|
|
+ arg = extract_programmer_param("tickle");
|
|
+ if (arg && !strcmp(arg,"true"))
|
|
+ ast1100_device_tickle_fw = 1;
|
|
+ free(arg);
|
|
+
|
|
+ if ((ast1100_device_spi_bus < 0) || (ast1100_device_spi_bus > 2)) {
|
|
+ msg_perr("SPI bus number out of range! Valid values are 0 - 2.\n");
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ if (rget_io_perms())
|
|
+ return 1;
|
|
+
|
|
+ dev = pcidev_init(bmc_aspeed_ast1100, PCI_BASE_ADDRESS_1);
|
|
+ if (!dev)
|
|
+ return 1;
|
|
+
|
|
+ uintptr_t io_base_addr = pcidev_readbar(dev, PCI_BASE_ADDRESS_1);
|
|
+ if (!io_base_addr)
|
|
+ return 1;
|
|
+
|
|
+ msg_pinfo("Detected ASPEED MMIO base address: %p.\n", (void*)io_base_addr);
|
|
+
|
|
+ ast1100_device_bar = rphysmap("ASPEED", io_base_addr, ASPEED_MEMMAP_SIZE);
|
|
+ if (ast1100_device_bar == ERROR_PTR)
|
|
+ return 1;
|
|
+
|
|
+ if (register_shutdown(ast1100_shutdown, dev))
|
|
+ return 1;
|
|
+
|
|
+ io_base_addr += ASPEED_P2A_OFFSET;
|
|
+ msg_pinfo("ASPEED P2A base address: %p.\n", (void*)io_base_addr);
|
|
+
|
|
+ msg_pinfo("Configuring P2A bridge for SCU access\n");
|
|
+ ast1100_set_a2b_bridge_scu();
|
|
+ pci_mmio_writel(AST1100_SCU_PASSWORD, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SCU_APB_BRIDGE_OFFSET + AST1100_SCU_PROT_KEY);
|
|
+
|
|
+ /* Halt CPU if requested */
|
|
+ if (ast1100_disable_cpu())
|
|
+ return 1;
|
|
+
|
|
+ msg_pinfo("Configuring P2A bridge for SMC access\n");
|
|
+ ast1100_set_a2b_bridge_smc();
|
|
+
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_SMC00);
|
|
+ if (((dword >> ((ast1100_device_spi_bus * 2) + 4)) & 0x3) != 0x2) {
|
|
+ msg_perr("CE%01x Flash type is not SPI!\n", ast1100_device_spi_bus);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ msg_pinfo("Setting CE%01x SPI relative clock speed to %d\n", ast1100_device_spi_bus, ast1100_device_spi_speed);
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ dword &= ~AST1100_SPI_SPEED_MASK;
|
|
+ pci_mmio_writel(dword | ((ast1100_device_spi_speed << AST1100_SPI_SPEED_SHIFT) & AST1100_SPI_SPEED_MASK), ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+
|
|
+ msg_pinfo("Enabling CE%01x write\n", ast1100_device_spi_bus);
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_SMC00);
|
|
+ pci_mmio_writel(dword | (0x1 << (10 + ast1100_device_spi_bus)), ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_SMC00);
|
|
+
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_SMC00);
|
|
+ dword &= AST1100_SMC_SEGMENT_SIZE_MASK;
|
|
+ switch (dword & AST1100_SMC_SEGMENT_SIZE_MASK) {
|
|
+ case AST1100_SMC_SEGMENT_SIZE_32M:
|
|
+ ast1100_device_flash_mmio_offset = 0x2000000;
|
|
+ break;
|
|
+ case AST1100_SMC_SEGMENT_SIZE_16M:
|
|
+ ast1100_device_flash_mmio_offset = 0x1000000;
|
|
+ break;
|
|
+ case AST1100_SMC_SEGMENT_SIZE_8M:
|
|
+ ast1100_device_flash_mmio_offset = 0x800000;
|
|
+ break;
|
|
+ case AST1100_SMC_SEGMENT_SIZE_4M:
|
|
+ ast1100_device_flash_mmio_offset = 0x400000;
|
|
+ break;
|
|
+ default:
|
|
+ ast1100_device_flash_mmio_offset = 0x2000000;
|
|
+ }
|
|
+ msg_pinfo("Segment size: 0x%08x\n", ast1100_device_flash_mmio_offset);
|
|
+
|
|
+ ast1100_device_flash_mmio_offset = ast1100_device_flash_mmio_offset * ast1100_device_spi_bus;
|
|
+ msg_pinfo("Using CE%01x offset 0x%08x\n", ast1100_device_spi_bus, ast1100_device_flash_mmio_offset);
|
|
+
|
|
+ register_spi_master(&spi_master_ast1100);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ast1100_spi_xfer_data(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr)
|
|
+{
|
|
+ int i;
|
|
+ uint32_t dword;
|
|
+
|
|
+ for (i = 0; i < writecnt; i++)
|
|
+ msg_pspew("[%02x]", writearr[i]);
|
|
+ msg_pspew("\n");
|
|
+
|
|
+ for (i = 0; i < writecnt; i=i+4) {
|
|
+ if ((writecnt - i) < 4)
|
|
+ break;
|
|
+ dword = writearr[i];
|
|
+ dword |= writearr[i + 1] << 8;
|
|
+ dword |= writearr[i + 2] << 16;
|
|
+ dword |= writearr[i + 3] << 24;
|
|
+ pci_mmio_writel(dword, ast1100_device_bar + ASPEED_P2A_OFFSET);
|
|
+ }
|
|
+ for (; i < writecnt; i++)
|
|
+ pci_mmio_writeb(writearr[i], ast1100_device_bar + ASPEED_P2A_OFFSET);
|
|
+ programmer_delay(1);
|
|
+ for (i = 0; i < readcnt;) {
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET);
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = dword & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 8) & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 16) & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 24) & 0xff;
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < readcnt; i++)
|
|
+ msg_pspew("[%02x]", readarr[i]);
|
|
+ msg_pspew("\n");
|
|
+}
|
|
+
|
|
+/* Returns 0 upon success, a negative number upon errors. */
|
|
+static int ast1100_spi_send_command(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr)
|
|
+{
|
|
+ uint32_t dword;
|
|
+
|
|
+ msg_pspew("%s, cmd=0x%02x, writecnt=%d, readcnt=%d\n", __func__, *writearr, writecnt, readcnt);
|
|
+
|
|
+ /* Set up user command mode */
|
|
+ ast1100_set_a2b_bridge_smc();
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ pci_mmio_writel(dword | AST1100_SPI_CMD_USER_MODE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ pci_mmio_writel(dword & ~AST1100_SPI_STOP_CE_ACTIVE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+
|
|
+ /* Transfer data */
|
|
+ ast1100_set_a2b_bridge_smc_flash();
|
|
+ ast1100_spi_xfer_data(flash, writecnt, readcnt, writearr, readarr);
|
|
+
|
|
+ /* Tear down user command mode */
|
|
+ ast1100_set_a2b_bridge_smc();
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ pci_mmio_writel(dword | AST1100_SPI_STOP_CE_ACTIVE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ dword = pci_mmio_readl(ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+ pci_mmio_writel((dword & ~AST1100_SPI_CMD_MASK) | AST1100_SPI_CMD_FAST_R_MODE, ast1100_device_bar + ASPEED_P2A_OFFSET + AST1100_SMC_CE_CTL(ast1100_device_spi_bus));
|
|
+
|
|
+ if (ast1100_device_tickle_fw) {
|
|
+ ast1100_enable_cpu();
|
|
+ programmer_delay(100);
|
|
+ ast1100_disable_cpu();
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git ./ast2400.c ./ast2400.c
|
|
new file mode 100644
|
|
index 0000000..01cee76
|
|
--- /dev/null
|
|
+++ ./ast2400.c
|
|
@@ -0,0 +1,426 @@
|
|
+/*
|
|
+ * This file is part of the flashrom project.
|
|
+ *
|
|
+ * Copyright (C) 2016 - 2017 Raptor Engineering, LLC
|
|
+ *
|
|
+ * 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; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * 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
|
|
+ */
|
|
+
|
|
+#include <stdlib.h>
|
|
+#include <string.h>
|
|
+#include "flash.h"
|
|
+#include "programmer.h"
|
|
+#include "hwaccess.h"
|
|
+
|
|
+#define PCI_VENDOR_ID_ASPEED 0x1a03
|
|
+
|
|
+#define ASPEED_MEMMAP_SIZE (128 * 1024)
|
|
+#define ASPEED_P2A_OFFSET 0x10000
|
|
+
|
|
+#define AST2400_SCU_APB_ADDR 0x1e6e2000
|
|
+#define AST2400_SCU_APB_BRIDGE_OFFSET (AST2400_SCU_APB_ADDR & 0xffff)
|
|
+#define AST2400_SCU_PROT_KEY 0x00
|
|
+#define AST2400_SCU_MISC_CTL 0x2c
|
|
+#define AST2400_SCU_HW_STRAP 0x70
|
|
+
|
|
+#define AST2400_SCU_PASSWORD 0x1688a8a8
|
|
+#define AST2400_SCU_BOOT_SRC_MASK 0x3
|
|
+#define AST2400_SCU_BOOT_SPI 0x2
|
|
+#define AST2400_SCU_BOOT_NONE 0x3
|
|
+
|
|
+#define AST2400_SMC_APB_ADDR 0x1e620000
|
|
+#define AST2400_SMC_FMC00 0x00
|
|
+#define AST2400_SMC_CE_CTL(N) (0x10 + (N * 4))
|
|
+#define AST2400_SMC_CE_SEG(N) (0x30 + (N * 4))
|
|
+
|
|
+#define AST2400_SMC_FLASH_MMIO_ADDR 0x20000000
|
|
+
|
|
+#define AST2400_SPI_APB_ADDR 0x1e630000
|
|
+#define AST2400_SPI_CFG 0x00
|
|
+#define AST2400_SPI_CTL 0x04
|
|
+
|
|
+#define AST2400_SPI_CFG_WRITE_EN 0x1
|
|
+#define AST2400_SPI_CMD_FAST_R_MODE 0x1
|
|
+#define AST2400_SPI_CMD_USER_MODE 0x3
|
|
+#define AST2400_SPI_CMD_MASK 0x3
|
|
+#define AST2400_SPI_STOP_CE_ACTIVE (0x1 << 2)
|
|
+#define AST2400_SPI_CPOL_1 (0x1 << 4)
|
|
+#define AST2400_SPI_LSB_FIRST_CTRL (0x1 << 5)
|
|
+#define AST2400_SPI_SPEED_MASK (0xf << 8)
|
|
+#define AST2400_SPI_IO_MODE_MASK (0x3 << 28)
|
|
+
|
|
+#define AST2400_SPI_FLASH_MMIO_ADDR 0x30000000
|
|
+
|
|
+#define AST2400_WDT_APB_ADDR 0x1e785000
|
|
+#define AST2400_WDT_APB_BRIDGE_OFFSET (AST2400_WDT_APB_ADDR & 0xffff)
|
|
+
|
|
+#define AST2400_WDT1_CTL 0x0c
|
|
+
|
|
+#define AST2400_WDT_RESET_MODE_MASK (0x3 << 5)
|
|
+#define AST2400_WDT_RESET_CPU_ONLY (0x2 << 5)
|
|
+
|
|
+uint8_t *ast2400_device_bar = 0;
|
|
+uint8_t ast2400_device_spi_bus = 0;
|
|
+uint8_t ast2400_device_halt_cpu = 0;
|
|
+uint8_t ast2400_device_resume_cpu = 0;
|
|
+uint8_t ast2400_device_tickle_fw = 0;
|
|
+uint32_t ast2400_device_flash_mmio_offset = 0;
|
|
+uint32_t ast2400_device_host_mode = 0;
|
|
+uint32_t ast2400_original_wdt_conf = 0;
|
|
+
|
|
+const struct dev_entry bmc_aspeed_ast2400[] = {
|
|
+ {PCI_VENDOR_ID_ASPEED, 0x2000, OK, "ASPEED", "AST2400" },
|
|
+
|
|
+ {0},
|
|
+};
|
|
+
|
|
+static int ast2400_spi_send_command(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr);
|
|
+
|
|
+static const struct spi_master spi_master_ast2400 = {
|
|
+ .type = SPI_CONTROLLER_AST2400,
|
|
+ .max_data_read = 256,
|
|
+ .max_data_write = 256,
|
|
+ .command = ast2400_spi_send_command,
|
|
+ .multicommand = default_spi_send_multicommand,
|
|
+ .read = default_spi_read,
|
|
+ .write_256 = default_spi_write_256,
|
|
+ .write_aai = default_spi_write_aai,
|
|
+};
|
|
+
|
|
+static int ast2400_set_a2b_bridge_scu(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_SCU_APB_ADDR & 0xffff0000, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_set_a2b_bridge_wdt(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_WDT_APB_ADDR & 0xffff0000, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_set_a2b_bridge_smc(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_SMC_APB_ADDR, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_set_a2b_bridge_spi(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_SPI_APB_ADDR, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_set_a2b_bridge_smc_flash(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_SMC_FLASH_MMIO_ADDR + ast2400_device_flash_mmio_offset, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_set_a2b_bridge_spi_flash(void)
|
|
+{
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+ pci_mmio_writel(AST2400_SPI_FLASH_MMIO_ADDR, ast2400_device_bar + 0xf004);
|
|
+ pci_mmio_writel(0x1, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_disable_cpu(void) {
|
|
+ uint32_t dword;
|
|
+
|
|
+ if (ast2400_device_halt_cpu) {
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_HW_STRAP);
|
|
+ if (((dword & AST2400_SCU_BOOT_SRC_MASK) != AST2400_SCU_BOOT_SPI)
|
|
+ && ((dword & AST2400_SCU_BOOT_SRC_MASK) != AST2400_SCU_BOOT_NONE)) { /* NONE permitted to allow for BMC recovery after Ctrl+C or crash */
|
|
+ msg_perr("CPU halt requested but CPU firmware source is not SPI.\n");
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_PROT_KEY);
|
|
+ ast2400_device_halt_cpu = 0;
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ /* Disable WDT from issuing full SoC reset
|
|
+ * Without this, OpenPOWER systems will crash when the GPIO blocks are reset on WDT timeout
|
|
+ */
|
|
+ msg_pinfo("Configuring P2A bridge for WDT access\n");
|
|
+ ast2400_set_a2b_bridge_wdt();
|
|
+ ast2400_original_wdt_conf = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_WDT_APB_BRIDGE_OFFSET + AST2400_WDT1_CTL);
|
|
+ pci_mmio_writel((ast2400_original_wdt_conf & ~AST2400_WDT_RESET_MODE_MASK) | AST2400_WDT_RESET_CPU_ONLY, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_WDT_APB_BRIDGE_OFFSET + AST2400_WDT1_CTL);
|
|
+
|
|
+ /* Disable CPU */
|
|
+ ast2400_set_a2b_bridge_scu();
|
|
+ pci_mmio_writel((dword & ~AST2400_SCU_BOOT_SRC_MASK) | AST2400_SCU_BOOT_NONE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_HW_STRAP);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_enable_cpu(void) {
|
|
+ uint32_t dword;
|
|
+
|
|
+ if (ast2400_device_halt_cpu && ast2400_device_resume_cpu) {
|
|
+ /* Re-enable CPU */
|
|
+ ast2400_set_a2b_bridge_scu();
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_HW_STRAP);
|
|
+ pci_mmio_writel((dword & ~AST2400_SCU_BOOT_SRC_MASK) | AST2400_SCU_BOOT_SPI, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_HW_STRAP);
|
|
+
|
|
+ /* Reset WDT configuration */
|
|
+ ast2400_set_a2b_bridge_wdt();
|
|
+ pci_mmio_writel((ast2400_original_wdt_conf & ~AST2400_WDT_RESET_MODE_MASK) | AST2400_WDT_RESET_CPU_ONLY, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_WDT_APB_BRIDGE_OFFSET + AST2400_WDT1_CTL);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int ast2400_shutdown(void *data) {
|
|
+ /* Reactivate CPU if previously deactivated */
|
|
+ ast2400_enable_cpu();
|
|
+
|
|
+ /* Disable backdoor APB access */
|
|
+ pci_mmio_writel(0x0, ast2400_device_bar + 0xf000);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+int ast2400_init(void)
|
|
+{
|
|
+ struct pci_dev *dev = NULL;
|
|
+ uint32_t dword;
|
|
+ uint8_t divisor;
|
|
+
|
|
+ char *arg;
|
|
+
|
|
+ ast2400_device_spi_bus = 0;
|
|
+ arg = extract_programmer_param("spibus");
|
|
+ if (arg) {
|
|
+ if (!strcmp(arg,"host"))
|
|
+ ast2400_device_host_mode = 1;
|
|
+ else
|
|
+ ast2400_device_spi_bus = strtol(arg, NULL, 0);
|
|
+ }
|
|
+ free(arg);
|
|
+
|
|
+ ast2400_device_halt_cpu = 0;
|
|
+ arg = extract_programmer_param("cpu");
|
|
+ if (arg && !strcmp(arg,"pause")) {
|
|
+ ast2400_device_halt_cpu = 1;
|
|
+ ast2400_device_resume_cpu = 1;
|
|
+ }
|
|
+ if (arg && !strcmp(arg,"halt")) {
|
|
+ ast2400_device_halt_cpu = 1;
|
|
+ ast2400_device_resume_cpu = 0;
|
|
+ }
|
|
+ arg = extract_programmer_param("tickle");
|
|
+ if (arg && !strcmp(arg,"true"))
|
|
+ ast2400_device_tickle_fw = 1;
|
|
+ free(arg);
|
|
+
|
|
+ if ((ast2400_device_host_mode == 0) && ((ast2400_device_spi_bus < 0) || (ast2400_device_spi_bus > 4))) {
|
|
+ msg_perr("SPI bus number out of range! Valid values are 0 - 4.\n");
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ if (rget_io_perms())
|
|
+ return 1;
|
|
+
|
|
+ dev = pcidev_init(bmc_aspeed_ast2400, PCI_BASE_ADDRESS_1);
|
|
+ if (!dev)
|
|
+ return 1;
|
|
+
|
|
+ uintptr_t io_base_addr = pcidev_readbar(dev, PCI_BASE_ADDRESS_1);
|
|
+ if (!io_base_addr)
|
|
+ return 1;
|
|
+
|
|
+ msg_pinfo("Detected ASPEED MMIO base address: %p.\n", (void*)io_base_addr);
|
|
+
|
|
+ ast2400_device_bar = rphysmap("ASPEED", io_base_addr, ASPEED_MEMMAP_SIZE);
|
|
+ if (ast2400_device_bar == ERROR_PTR)
|
|
+ return 1;
|
|
+
|
|
+ if (register_shutdown(ast2400_shutdown, dev))
|
|
+ return 1;
|
|
+
|
|
+ io_base_addr += ASPEED_P2A_OFFSET;
|
|
+ msg_pinfo("ASPEED P2A base address: %p.\n", (void*)io_base_addr);
|
|
+
|
|
+ msg_pinfo("Configuring P2A bridge for SCU access\n");
|
|
+ ast2400_set_a2b_bridge_scu();
|
|
+ pci_mmio_writel(AST2400_SCU_PASSWORD, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_PROT_KEY);
|
|
+
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_MISC_CTL);
|
|
+ pci_mmio_writel(dword & ~((0x1 << 24) | (0x2 << 22)), ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SCU_APB_BRIDGE_OFFSET + AST2400_SCU_MISC_CTL);
|
|
+
|
|
+ /* Halt CPU if requested */
|
|
+ if (ast2400_disable_cpu())
|
|
+ return 1;
|
|
+
|
|
+ msg_pinfo("Configuring P2A bridge for SMC access\n");
|
|
+ ast2400_set_a2b_bridge_smc();
|
|
+
|
|
+ if (ast2400_device_host_mode) {
|
|
+ msg_pinfo("Configuring P2A bridge for SPI access\n");
|
|
+ ast2400_set_a2b_bridge_spi();
|
|
+
|
|
+ divisor = 0; /* Slowest speed for now */
|
|
+
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+ dword &= ~AST2400_SPI_SPEED_MASK;
|
|
+ dword |= (divisor << 8);
|
|
+ dword &= ~AST2400_SPI_CPOL_1;
|
|
+ dword &= ~AST2400_SPI_LSB_FIRST_CTRL; /* MSB first */
|
|
+ dword &= ~AST2400_SPI_IO_MODE_MASK; /* Single bit I/O mode */
|
|
+ pci_mmio_writel(dword, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+ }
|
|
+ else {
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_FMC00);
|
|
+ if (((dword >> (ast2400_device_spi_bus * 2)) & 0x3) != 0x2) {
|
|
+ msg_perr("CE%01x Flash type is not SPI!\n", ast2400_device_spi_bus);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ msg_pinfo("Enabling CE%01x write\n", ast2400_device_spi_bus);
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_FMC00);
|
|
+ pci_mmio_writel(dword | (0x1 << (16 + ast2400_device_spi_bus)), ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_FMC00);
|
|
+
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_SEG(ast2400_device_spi_bus));
|
|
+ ast2400_device_flash_mmio_offset = ((dword >> 16) & 0x3f) * 0x800000;
|
|
+ msg_pinfo("Using CE%01x offset 0x%08x\n", ast2400_device_spi_bus, ast2400_device_flash_mmio_offset);
|
|
+ }
|
|
+
|
|
+ register_spi_master(&spi_master_ast2400);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void ast2400_spi_xfer_data(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr)
|
|
+{
|
|
+ int i;
|
|
+ uint32_t dword;
|
|
+
|
|
+ for (i = 0; i < writecnt; i++)
|
|
+ msg_pspew("[%02x]", writearr[i]);
|
|
+ msg_pspew("\n");
|
|
+
|
|
+ for (i = 0; i < writecnt; i=i+4) {
|
|
+ if ((writecnt - i) < 4)
|
|
+ break;
|
|
+ dword = writearr[i];
|
|
+ dword |= writearr[i + 1] << 8;
|
|
+ dword |= writearr[i + 2] << 16;
|
|
+ dword |= writearr[i + 3] << 24;
|
|
+ pci_mmio_writel(dword, ast2400_device_bar + ASPEED_P2A_OFFSET);
|
|
+ }
|
|
+ for (; i < writecnt; i++)
|
|
+ pci_mmio_writeb(writearr[i], ast2400_device_bar + ASPEED_P2A_OFFSET);
|
|
+ programmer_delay(1);
|
|
+ for (i = 0; i < readcnt;) {
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET);
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = dword & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 8) & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 16) & 0xff;
|
|
+ i++;
|
|
+ if (i < readcnt)
|
|
+ readarr[i] = (dword >> 24) & 0xff;
|
|
+ i++;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < readcnt; i++)
|
|
+ msg_pspew("[%02x]", readarr[i]);
|
|
+ msg_pspew("\n");
|
|
+}
|
|
+
|
|
+/* Returns 0 upon success, a negative number upon errors. */
|
|
+static int ast2400_spi_send_command(struct flashctx *flash,
|
|
+ unsigned int writecnt, unsigned int readcnt,
|
|
+ const unsigned char *writearr,
|
|
+ unsigned char *readarr)
|
|
+{
|
|
+ uint32_t dword;
|
|
+
|
|
+ msg_pspew("%s, cmd=0x%02x, writecnt=%d, readcnt=%d\n", __func__, *writearr, writecnt, readcnt);
|
|
+
|
|
+ if (ast2400_device_host_mode) {
|
|
+ /* Set up user command mode */
|
|
+ ast2400_set_a2b_bridge_spi();
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CFG);
|
|
+ pci_mmio_writel(dword | AST2400_SPI_CFG_WRITE_EN, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CFG);
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+ pci_mmio_writel(dword | AST2400_SPI_CMD_USER_MODE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+
|
|
+ /* Transfer data */
|
|
+ ast2400_set_a2b_bridge_spi_flash();
|
|
+ ast2400_spi_xfer_data(flash, writecnt, readcnt, writearr, readarr);
|
|
+
|
|
+ /* Tear down user command mode */
|
|
+ ast2400_set_a2b_bridge_spi();
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+ pci_mmio_writel((dword & ~AST2400_SPI_CMD_MASK) | AST2400_SPI_CMD_FAST_R_MODE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CTL);
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CFG);
|
|
+ pci_mmio_writel(dword & ~AST2400_SPI_CFG_WRITE_EN, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SPI_CFG);
|
|
+ }
|
|
+ else {
|
|
+ /* Set up user command mode */
|
|
+ ast2400_set_a2b_bridge_smc();
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ pci_mmio_writel(dword | AST2400_SPI_CMD_USER_MODE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ pci_mmio_writel(dword & ~AST2400_SPI_STOP_CE_ACTIVE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+
|
|
+ /* Transfer data */
|
|
+ ast2400_set_a2b_bridge_smc_flash();
|
|
+ ast2400_spi_xfer_data(flash, writecnt, readcnt, writearr, readarr);
|
|
+
|
|
+ /* Tear down user command mode */
|
|
+ ast2400_set_a2b_bridge_smc();
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ pci_mmio_writel(dword | AST2400_SPI_STOP_CE_ACTIVE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ dword = pci_mmio_readl(ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ pci_mmio_writel((dword & ~AST2400_SPI_CMD_MASK) | AST2400_SPI_CMD_FAST_R_MODE, ast2400_device_bar + ASPEED_P2A_OFFSET + AST2400_SMC_CE_CTL(ast2400_device_spi_bus));
|
|
+ }
|
|
+
|
|
+ if (ast2400_device_tickle_fw) {
|
|
+ ast2400_enable_cpu();
|
|
+ programmer_delay(100);
|
|
+ ast2400_disable_cpu();
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
diff --git ./board_enable.c ./board_enable.c
|
|
index 2f0c1c0..758aaf4 100644
|
|
--- ./board_enable.c
|
|
+++ ./board_enable.c
|
|
@@ -2433,6 +2433,7 @@ const struct board_match board_matches[] = {
|
|
{0x8086, 0x27a0, 0x17aa, 0x2017, 0x8086, 0x27b9, 0x17aa, 0x2009, "^ThinkPad T60", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad T60(s)", 0, OK, p2_whitelist_laptop},
|
|
{0x8086, 0x2917, 0x17AA, 0x20F5, 0x8086, 0x2930, 0x17AA, 0x20F9, "^ThinkPad X200", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad X200", 0, OK, p2_whitelist_laptop},
|
|
{0x8086, 0x3B07, 0x17AA, 0x2166, 0x8086, 0x3B30, 0x17AA, 0x2167, "^Lenovo X201", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad X201", 0, OK, p2_whitelist_laptop},
|
|
+ {0x8086, 0x1C22, 0x17AA, 0x21DB, 0x8086, 0x1C4F, 0x17AA, 0x21DB, "^ThinkPad X220", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad X220", 0, OK, p2_whitelist_laptop},
|
|
{0x8086, 0x1E22, 0x17AA, 0x21FA, 0x8086, 0x1E55, 0x17AA, 0x21FA, "^ThinkPad X230", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad X230", 0, OK, p2_whitelist_laptop},
|
|
{0x8086, 0x27A0, 0x17AA, 0x2017, 0x8086, 0x27B9, 0x17AA, 0x2009, "^ThinkPad X60", NULL, NULL, P2, "IBM/Lenovo", "ThinkPad X60(s)", 0, OK, p2_whitelist_laptop},
|
|
{0x8086, 0x2411, 0x8086, 0x2411, 0x8086, 0x7125, 0x0e11, 0xb165, NULL, NULL, NULL, P3, "Mitac", "6513WU", 0, OK, board_mitac_6513wu},
|
|
diff --git ./chipdrivers.h ./chipdrivers.h
|
|
index c85eac9..20529d5 100644
|
|
--- ./chipdrivers.h
|
|
+++ ./chipdrivers.h
|
|
@@ -195,4 +195,26 @@ int erase_sector_stm50(struct flashctx *flash, unsigned int block, unsigned int
|
|
int probe_en29lv640b(struct flashctx *flash);
|
|
int write_en29lv640b(struct flashctx *flash, const uint8_t *buf, unsigned int start, unsigned int len);
|
|
|
|
+/* spi4ba.c */
|
|
+int spi_enter_4ba_b7(struct flashctx *flash);
|
|
+int spi_enter_4ba_b7_we(struct flashctx *flash);
|
|
+int spi_byte_program_4ba(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+int spi_block_erase_20_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_52_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_d8_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_byte_program_4ba_ereg(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba_ereg(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba_ereg(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+int spi_block_erase_20_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_52_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_d8_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_byte_program_4ba_direct(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba_direct(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba_direct(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+int spi_block_erase_21_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_5c_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_dc_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+
|
|
#endif /* !__CHIPDRIVERS_H__ */
|
|
diff --git ./cli_output.c ./cli_output.c
|
|
index feafbd2..cb82fa1 100644
|
|
--- ./cli_output.c
|
|
+++ ./cli_output.c
|
|
@@ -90,7 +90,8 @@ int print(enum msglevel level, const char *fmt, ...)
|
|
fflush(output_type);
|
|
}
|
|
#ifndef STANDALONE
|
|
- if ((level <= verbose_logfile) && logfile) {
|
|
+ /* skip of msgs starting from '\b' added to skip progress percents */
|
|
+ if ((level <= verbose_logfile) && logfile && (!fmt || fmt[0] != '\b')) {
|
|
va_start(ap, fmt);
|
|
ret = vfprintf(logfile, fmt, ap);
|
|
va_end(ap);
|
|
diff --git ./flash.h ./flash.h
|
|
index da049d1..0b72439 100644
|
|
--- ./flash.h
|
|
+++ ./flash.h
|
|
@@ -123,6 +123,14 @@ enum write_granularity {
|
|
#define FEATURE_WRSR_EITHER (FEATURE_WRSR_EWSR | FEATURE_WRSR_WREN)
|
|
#define FEATURE_OTP (1 << 8)
|
|
#define FEATURE_QPI (1 << 9)
|
|
+/* Feature bits used for 4-bytes addressing mode */
|
|
+#define FEATURE_4BA_SUPPORT (1 << 10)
|
|
+#define FEATURE_4BA_ONLY (1 << 11)
|
|
+#define FEATURE_4BA_EXTENDED_ADDR_REG (1 << 12)
|
|
+#define FEATURE_4BA_DIRECT_READ (1 << 13)
|
|
+#define FEATURE_4BA_DIRECT_WRITE (1 << 14)
|
|
+#define FEATURE_4BA_ALL_ERASERS_DIRECT (1 << 15)
|
|
+#define FEATURE_4BA_ALL_DIRECT (FEATURE_4BA_DIRECT_READ | FEATURE_4BA_DIRECT_WRITE | FEATURE_4BA_ALL_ERASERS_DIRECT)
|
|
|
|
enum test_state {
|
|
OK = 0,
|
|
@@ -167,6 +175,14 @@ struct flashchip {
|
|
unsigned int page_size;
|
|
int feature_bits;
|
|
|
|
+ /* set of function pointers to use in 4-bytes addressing mode */
|
|
+ struct four_bytes_addr_funcs_set {
|
|
+ int (*enter_4ba) (struct flashctx *flash);
|
|
+ int (*read_nbyte) (struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+ int (*program_byte) (struct flashctx *flash, unsigned int addr, const uint8_t databyte);
|
|
+ int (*program_nbyte) (struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+ } four_bytes_addr_funcs;
|
|
+
|
|
/* Indicate how well flashrom supports different operations of this flash chip. */
|
|
struct tested {
|
|
enum test_state probe;
|
|
@@ -344,6 +360,11 @@ __attribute__((format(printf, 2, 3)));
|
|
#define msg_pspew(...) print(MSG_SPEW, __VA_ARGS__) /* programmer debug spew */
|
|
#define msg_cspew(...) print(MSG_SPEW, __VA_ARGS__) /* chip debug spew */
|
|
|
|
+/* Read progress will be shown for reads more than 256KB */
|
|
+#define MIN_LENGTH_TO_SHOW_READ_PROGRESS 256 * 1024
|
|
+/* Read progress will be shown for erases and writes more than 64KB */
|
|
+#define MIN_LENGTH_TO_SHOW_ERASE_AND_WRITE_PROGRESS 64 * 1024
|
|
+
|
|
/* layout.c */
|
|
int register_include_arg(char *name);
|
|
int process_include_args(void);
|
|
diff --git ./flashchips.c ./flashchips.c
|
|
index 40b6b8e..60baa73 100644
|
|
--- ./flashchips.c
|
|
+++ ./flashchips.c
|
|
@@ -8045,6 +8045,100 @@ const struct flashchip flashchips[] = {
|
|
|
|
{
|
|
.vendor = "Macronix",
|
|
+ .name = "MX25L25635F/MX25L25645E/MX25L25665E",
|
|
+ .bustype = BUS_SPI,
|
|
+ .manufacture_id = MACRONIX_ID,
|
|
+ .model_id = MACRONIX_MX25L25635F,
|
|
+ .total_size = 32768,
|
|
+ .page_size = 256,
|
|
+ /* OTP: 512B total; enter 0xB1, exit 0xC1 */
|
|
+ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP | FEATURE_4BA_SUPPORT,
|
|
+ .four_bytes_addr_funcs =
|
|
+ {
|
|
+ .enter_4ba = spi_enter_4ba_b7, /* enter 4-bytes addressing mode by CMD B7 */
|
|
+ .read_nbyte = spi_nbyte_read_4ba, /* read from 4-bytes addressing mode */
|
|
+ .program_byte = spi_byte_program_4ba, /* write from 4-bytes addressing mode */
|
|
+ .program_nbyte = spi_nbyte_program_4ba /* write from 4-bytes addressing mode */
|
|
+ },
|
|
+ .tested = TEST_OK_PREW,
|
|
+ .probe = probe_spi_rdid,
|
|
+ .probe_timing = TIMING_ZERO,
|
|
+ .block_erasers =
|
|
+ {
|
|
+ {
|
|
+ .eraseblocks = { {4 * 1024, 8192} },
|
|
+ .block_erase = spi_block_erase_20_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024, 1024} },
|
|
+ .block_erase = spi_block_erase_52_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {64 * 1024, 512} },
|
|
+ .block_erase = spi_block_erase_d8_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_60,
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_c7,
|
|
+ }
|
|
+ },
|
|
+ /* TODO: security register and SBLK/SBULK; MX25L12835F: configuration register */
|
|
+ .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6 is quad enable */
|
|
+ .unlock = spi_disable_blockprotect_bp3_srwd,
|
|
+ .write = spi_chip_write_256,
|
|
+ .read = spi_chip_read, /* Fast read (0x0B) supported */
|
|
+ .voltage = {2700, 3600},
|
|
+ },
|
|
+
|
|
+ {
|
|
+ .vendor = "Macronix",
|
|
+ .name = "MX66L51235F",
|
|
+ .bustype = BUS_SPI,
|
|
+ .manufacture_id = MACRONIX_ID,
|
|
+ .model_id = MACRONIX_MX66L51235F,
|
|
+ .total_size = 65536,
|
|
+ .page_size = 256,
|
|
+ /* OTP: 512B total; enter 0xB1, exit 0xC1 */
|
|
+ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP | FEATURE_4BA_SUPPORT,
|
|
+ .four_bytes_addr_funcs =
|
|
+ {
|
|
+ .enter_4ba = spi_enter_4ba_b7, /* enter 4-bytes addressing mode by CMD B7 */
|
|
+ .read_nbyte = spi_nbyte_read_4ba, /* read from 4-bytes addressing mode */
|
|
+ .program_byte = spi_byte_program_4ba, /* write from 4-bytes addressing mode */
|
|
+ .program_nbyte = spi_nbyte_program_4ba /* write from 4-bytes addressing mode */
|
|
+ },
|
|
+ .tested = TEST_OK_PREW,
|
|
+ .probe = probe_spi_rdid,
|
|
+ .probe_timing = TIMING_ZERO,
|
|
+ .block_erasers =
|
|
+ {
|
|
+ {
|
|
+ .eraseblocks = { {4 * 1024, 16384} },
|
|
+ .block_erase = spi_block_erase_20_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024, 2048} },
|
|
+ .block_erase = spi_block_erase_52_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {64 * 1024, 1024} },
|
|
+ .block_erase = spi_block_erase_d8_4ba,
|
|
+ }, {
|
|
+ .eraseblocks = { {64 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_60,
|
|
+ }, {
|
|
+ .eraseblocks = { {64 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_c7,
|
|
+ }
|
|
+ },
|
|
+ /* TODO: security register and SBLK/SBULK; MX25L12835F: configuration register */
|
|
+ .printlock = spi_prettyprint_status_register_bp3_srwd, /* bit6 is quad enable */
|
|
+ .unlock = spi_disable_blockprotect_bp3_srwd,
|
|
+ .write = spi_chip_write_256,
|
|
+ .read = spi_chip_read, /* Fast read (0x0B) supported */
|
|
+ .voltage = {2700, 3600},
|
|
+ },
|
|
+
|
|
+ {
|
|
+ .vendor = "Macronix",
|
|
.name = "MX25U1635E",
|
|
.bustype = BUS_SPI,
|
|
.manufacture_id = MACRONIX_ID,
|
|
@@ -11747,7 +11841,7 @@ const struct flashchip flashchips[] = {
|
|
.total_size = 16384,
|
|
.page_size = 256,
|
|
.feature_bits = FEATURE_WRSR_WREN,
|
|
- .tested = TEST_UNTESTED,
|
|
+ .tested = TEST_OK_PREW,
|
|
.probe = probe_spi_rdid,
|
|
.probe_timing = TIMING_ZERO,
|
|
.block_erasers = {
|
|
@@ -14588,6 +14682,54 @@ const struct flashchip flashchips[] = {
|
|
|
|
{
|
|
.vendor = "Winbond",
|
|
+ .name = "W25Q256.V",
|
|
+ .bustype = BUS_SPI,
|
|
+ .manufacture_id = WINBOND_NEX_ID,
|
|
+ .model_id = WINBOND_NEX_W25Q256_V,
|
|
+ .total_size = 32768,
|
|
+ .page_size = 256,
|
|
+ /* supports SFDP */
|
|
+ /* OTP: 1024B total, 256B reserved; read 0x48; write 0x42, erase 0x44, read ID 0x4B */
|
|
+ /* FOUR_BYTE_ADDR: supports 4-bytes addressing mode */
|
|
+ .feature_bits = FEATURE_WRSR_WREN | FEATURE_OTP | FEATURE_4BA_SUPPORT | FEATURE_4BA_DIRECT_READ,
|
|
+ .four_bytes_addr_funcs =
|
|
+ {
|
|
+ .enter_4ba = spi_enter_4ba_b7_we, /* enter 4-bytes addressing mode by CMD B7 + WREN */
|
|
+ .read_nbyte = spi_nbyte_read_4ba_direct, /* read directly from any mode, no need to enter 4ba */
|
|
+ .program_byte = spi_byte_program_4ba, /* write from 4-bytes addressing mode */
|
|
+ .program_nbyte = spi_nbyte_program_4ba /* write from 4-bytes addressing mode */
|
|
+ },
|
|
+ .tested = TEST_OK_PREW,
|
|
+ .probe = probe_spi_rdid,
|
|
+ .probe_timing = TIMING_ZERO,
|
|
+ .block_erasers =
|
|
+ {
|
|
+ {
|
|
+ .eraseblocks = { {4 * 1024, 8192} },
|
|
+ .block_erase = spi_block_erase_20_4ba, /* erases 4k from 4-bytes addressing mode */
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024, 1024} },
|
|
+ .block_erase = spi_block_erase_52_4ba, /* erases 32k from 4-bytes addressing mode */
|
|
+ }, {
|
|
+ .eraseblocks = { {64 * 1024, 512} },
|
|
+ .block_erase = spi_block_erase_d8_4ba, /* erases 64k from 4-bytes addressing mode */
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_60,
|
|
+ }, {
|
|
+ .eraseblocks = { {32 * 1024 * 1024, 1} },
|
|
+ .block_erase = spi_block_erase_c7,
|
|
+ }
|
|
+ },
|
|
+ .printlock = spi_prettyprint_status_register_plain, /* TODO: improve */
|
|
+ .unlock = spi_disable_blockprotect,
|
|
+ .write = spi_chip_write_256,
|
|
+ .read = spi_chip_read,
|
|
+ .voltage = {2700, 3600},
|
|
+ },
|
|
+
|
|
+ {
|
|
+ .vendor = "Winbond",
|
|
.name = "W25Q20.W",
|
|
.bustype = BUS_SPI,
|
|
.manufacture_id = WINBOND_NEX_ID,
|
|
diff --git ./flashchips.h ./flashchips.h
|
|
index 9ffb30f..7171a62 100644
|
|
--- ./flashchips.h
|
|
+++ ./flashchips.h
|
|
@@ -482,6 +482,7 @@
|
|
#define MACRONIX_MX25L25635F 0x2019 /* Same as MX25L25639F, but the latter seems to not support REMS */
|
|
#define MACRONIX_MX25L1635D 0x2415
|
|
#define MACRONIX_MX25L1635E 0x2515 /* MX25L1635{E} */
|
|
+#define MACRONIX_MX66L51235F 0x201a /* MX66L51235F */
|
|
#define MACRONIX_MX25U1635E 0x2535
|
|
#define MACRONIX_MX25U3235E 0x2536 /* Same as MX25U6435F */
|
|
#define MACRONIX_MX25U6435E 0x2537 /* Same as MX25U6435F */
|
|
diff --git ./flashrom.c ./flashrom.c
|
|
index 25e53f2..d642de8 100644
|
|
--- ./flashrom.c
|
|
+++ ./flashrom.c
|
|
@@ -5,6 +5,7 @@
|
|
* Copyright (C) 2004 Tyan Corp <yhlu@tyan.com>
|
|
* Copyright (C) 2005-2008 coresystems GmbH
|
|
* Copyright (C) 2008,2009 Carl-Daniel Hailfinger
|
|
+ * Copyright (C) 2016-2017 Raptor Engineering, LLC
|
|
*
|
|
* 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
|
|
@@ -134,6 +135,30 @@ const struct programmer_entry programmer_table[] = {
|
|
},
|
|
#endif
|
|
|
|
+#if CONFIG_AST1100 == 1
|
|
+ {
|
|
+ .name = "ast1100",
|
|
+ .type = PCI,
|
|
+ .devs.dev = bmc_aspeed_ast1100,
|
|
+ .init = ast1100_init,
|
|
+ .map_flash_region = fallback_map,
|
|
+ .unmap_flash_region = fallback_unmap,
|
|
+ .delay = internal_delay,
|
|
+ },
|
|
+#endif
|
|
+
|
|
+#if CONFIG_AST2400 == 1
|
|
+ {
|
|
+ .name = "ast2400",
|
|
+ .type = PCI,
|
|
+ .devs.dev = bmc_aspeed_ast2400,
|
|
+ .init = ast2400_init,
|
|
+ .map_flash_region = fallback_map,
|
|
+ .unmap_flash_region = fallback_unmap,
|
|
+ .delay = internal_delay,
|
|
+ },
|
|
+#endif
|
|
+
|
|
#if CONFIG_DRKAISER == 1
|
|
{
|
|
.name = "drkaiser",
|
|
@@ -1527,6 +1552,17 @@ static int walk_eraseregions(struct flashctx *flash, int erasefunction,
|
|
unsigned int start = 0;
|
|
unsigned int len;
|
|
struct block_eraser eraser = flash->chip->block_erasers[erasefunction];
|
|
+ int show_progress = 0;
|
|
+ unsigned int percent_last, percent_current;
|
|
+ unsigned long size = flash->chip->total_size * 1024;
|
|
+
|
|
+ /* progress visualizaion init */
|
|
+ if(size >= MIN_LENGTH_TO_SHOW_ERASE_AND_WRITE_PROGRESS) {
|
|
+ msg_cinfo(" "); /* only this space will go to logfile but all strings with \b wont. */
|
|
+ msg_cinfo("\b 0%%");
|
|
+ percent_last = percent_current = 0;
|
|
+ show_progress = 1; /* enable progress visualizaion */
|
|
+ }
|
|
|
|
for (i = 0; i < NUM_ERASEREGIONS; i++) {
|
|
/* count==0 for all automatically initialized array
|
|
@@ -1544,8 +1580,20 @@ static int walk_eraseregions(struct flashctx *flash, int erasefunction,
|
|
return 1;
|
|
}
|
|
start += len;
|
|
+
|
|
+ if(show_progress) {
|
|
+ percent_current = (unsigned int) ((unsigned long long)start * 100 / size);
|
|
+ if(percent_current != percent_last) {
|
|
+ msg_cinfo("\b\b\b%2d%%", percent_current);
|
|
+ percent_last = percent_current;
|
|
+ }
|
|
+ }
|
|
}
|
|
}
|
|
+
|
|
+ if(show_progress)
|
|
+ msg_cinfo("\b\b\b\b"); /* remove progress percents from the screen */
|
|
+
|
|
msg_cdbg("\n");
|
|
return 0;
|
|
}
|
|
@@ -1801,7 +1849,7 @@ void print_buildinfo(void)
|
|
|
|
void print_version(void)
|
|
{
|
|
- msg_ginfo("flashrom v%s", flashrom_version);
|
|
+ msg_ginfo("flashrom %s", flashrom_version);
|
|
print_sysinfo();
|
|
msg_ginfo("\n");
|
|
}
|
|
@@ -1983,7 +2031,7 @@ int doit(struct flashctx *flash, int force, const char *filename, int read_it,
|
|
uint8_t *newcontents;
|
|
int ret = 0;
|
|
unsigned long size = flash->chip->total_size * 1024;
|
|
- int read_all_first = 1; /* FIXME: Make this configurable. */
|
|
+ int read_all_first = 0; /* FIXME: Make this configurable. */
|
|
|
|
if (chip_safety_check(flash, force, read_it, write_it, erase_it, verify_it)) {
|
|
msg_cerr("Aborting.\n");
|
|
@@ -2001,6 +2049,44 @@ int doit(struct flashctx *flash, int force, const char *filename, int read_it,
|
|
if (flash->chip->unlock)
|
|
flash->chip->unlock(flash);
|
|
|
|
+ /* Switching to 4-Bytes Addressing mode if flash chip supports it */
|
|
+ if(flash->chip->feature_bits & FEATURE_4BA_SUPPORT) {
|
|
+ /* Do not switch if chip is already in 4-bytes addressing mode */
|
|
+ if (flash->chip->feature_bits & FEATURE_4BA_ONLY) {
|
|
+ msg_cdbg("Flash chip is already in 4-bytes addressing mode.\n");
|
|
+ }
|
|
+ /* Do not switch to 4-Bytes Addressing mode if using Extended Address Register */
|
|
+ else if(flash->chip->feature_bits & FEATURE_4BA_EXTENDED_ADDR_REG) {
|
|
+ msg_cdbg("Using 4-bytes addressing with extended address register.\n");
|
|
+ }
|
|
+ /* Go to 4-Bytes Addressing mode if selected
|
|
+ operation requires 4-Bytes Addressing mode
|
|
+ (no need if functions are direct-4BA) */
|
|
+ else if(((read_it || verify_it)
|
|
+ && (!(flash->chip->feature_bits & FEATURE_4BA_DIRECT_READ)))
|
|
+ || ((erase_it || write_it)
|
|
+ && ((flash->chip->feature_bits & FEATURE_4BA_ALL_DIRECT) != FEATURE_4BA_ALL_DIRECT))) {
|
|
+
|
|
+ if (!flash->chip->four_bytes_addr_funcs.enter_4ba) {
|
|
+ msg_cerr("No function for Enter 4-bytes addressing mode for this flash chip.\n"
|
|
+ "Please report to flashrom@flashrom.org\n");
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ if(flash->chip->four_bytes_addr_funcs.enter_4ba(flash)) {
|
|
+ msg_cerr("Switching to 4-bytes addressing mode failed!\n");
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ msg_cdbg("Switched to 4-bytes addressing mode.\n");
|
|
+ }
|
|
+ /* Do not switch to 4-Bytes Addressing mode if all instructions are direct-4BA
|
|
+ or if the flash chip is 4-Bytes Addressing Only and always in 4BA-mode */
|
|
+ else {
|
|
+ msg_cdbg2("No need to switch to 4-bytes addressing mode.\n");
|
|
+ }
|
|
+ }
|
|
+
|
|
if (read_it) {
|
|
return read_flash_to_file(flash, filename);
|
|
}
|
|
diff --git ./layout.c ./layout.c
|
|
index f71eeaa..2b8ce3c 100644
|
|
--- ./layout.c
|
|
+++ ./layout.c
|
|
@@ -259,6 +259,7 @@ int normalize_romentries(const struct flashctx *flash)
|
|
|
|
static int copy_old_content(struct flashctx *flash, int oldcontents_valid, uint8_t *oldcontents, uint8_t *newcontents, unsigned int start, unsigned int size)
|
|
{
|
|
+#if 0
|
|
if (!oldcontents_valid) {
|
|
/* oldcontents is a zero-filled buffer. By reading the current data into oldcontents here, we
|
|
* avoid a rewrite of identical regions even if an initial full chip read didn't happen. */
|
|
@@ -269,6 +270,7 @@ static int copy_old_content(struct flashctx *flash, int oldcontents_valid, uint8
|
|
return 1;
|
|
}
|
|
}
|
|
+#endif
|
|
memcpy(newcontents + start, oldcontents + start, size);
|
|
return 0;
|
|
}
|
|
diff --git ./pcidev.c ./pcidev.c
|
|
index 2c78063..34b948b 100644
|
|
--- ./pcidev.c
|
|
+++ ./pcidev.c
|
|
@@ -37,11 +37,13 @@ enum pci_bartype {
|
|
uintptr_t pcidev_readbar(struct pci_dev *dev, int bar)
|
|
{
|
|
uint64_t addr;
|
|
- uint32_t upperaddr;
|
|
uint8_t headertype;
|
|
uint16_t supported_cycles;
|
|
enum pci_bartype bartype = TYPE_UNKNOWN;
|
|
|
|
+#ifndef __PPC64__
|
|
+ uint32_t upperaddr;
|
|
+#endif
|
|
|
|
headertype = pci_read_byte(dev, PCI_HEADER_TYPE) & 0x7f;
|
|
msg_pspew("PCI header type 0x%02x\n", headertype);
|
|
@@ -97,6 +99,12 @@ uintptr_t pcidev_readbar(struct pci_dev *dev, int bar)
|
|
switch (bartype) {
|
|
case TYPE_MEMBAR:
|
|
msg_pdbg("MEM");
|
|
+#ifdef __PPC64__
|
|
+ /* PowerPC is able to translate 32-bit BARs into 64-bit host windows.
|
|
+ * Use the dev->base_addr[x] mechanism to handle mapping.
|
|
+ */
|
|
+ addr = dev->base_addr[(bar - 0x10) / 0x4] & PCI_BASE_ADDRESS_MEM_MASK;
|
|
+#else
|
|
if (!(supported_cycles & PCI_COMMAND_MEMORY)) {
|
|
msg_perr("MEM BAR access requested, but device has MEM space accesses disabled.\n");
|
|
/* TODO: Abort here? */
|
|
@@ -122,6 +130,7 @@ uintptr_t pcidev_readbar(struct pci_dev *dev, int bar)
|
|
}
|
|
}
|
|
addr &= PCI_BASE_ADDRESS_MEM_MASK;
|
|
+#endif
|
|
break;
|
|
case TYPE_IOBAR:
|
|
msg_pdbg("I/O\n");
|
|
diff --git ./programmer.h ./programmer.h
|
|
index bd8e98d..754f3cc 100644
|
|
--- ./programmer.h
|
|
+++ ./programmer.h
|
|
@@ -5,6 +5,7 @@
|
|
* Copyright (C) 2000 Ronald G. Minnich <rminnich@gmail.com>
|
|
* Copyright (C) 2005-2009 coresystems GmbH
|
|
* Copyright (C) 2006-2009 Carl-Daniel Hailfinger
|
|
+ * Copyright (C) 2016-2017 Raptor Engineering, LLC
|
|
*
|
|
* 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
|
|
@@ -45,6 +46,12 @@ enum programmer {
|
|
#if CONFIG_GFXNVIDIA == 1
|
|
PROGRAMMER_GFXNVIDIA,
|
|
#endif
|
|
+#if CONFIG_AST1100 == 1
|
|
+ PROGRAMMER_AST1100,
|
|
+#endif
|
|
+#if CONFIG_AST2400 == 1
|
|
+ PROGRAMMER_AST2400,
|
|
+#endif
|
|
#if CONFIG_DRKAISER == 1
|
|
PROGRAMMER_DRKAISER,
|
|
#endif
|
|
@@ -399,6 +406,18 @@ int gfxnvidia_init(void);
|
|
extern const struct dev_entry gfx_nvidia[];
|
|
#endif
|
|
|
|
+/* ast1100.c */
|
|
+#if CONFIG_AST1100 == 1
|
|
+int ast1100_init(void);
|
|
+extern const struct dev_entry bmc_aspeed_ast1100[];
|
|
+#endif
|
|
+
|
|
+/* ast2400.c */
|
|
+#if CONFIG_AST2400 == 1
|
|
+int ast2400_init(void);
|
|
+extern const struct dev_entry bmc_aspeed_ast2400[];
|
|
+#endif
|
|
+
|
|
/* drkaiser.c */
|
|
#if CONFIG_DRKAISER == 1
|
|
int drkaiser_init(void);
|
|
@@ -600,6 +619,14 @@ enum spi_controller {
|
|
#if CONFIG_CH341A_SPI == 1
|
|
SPI_CONTROLLER_CH341A_SPI,
|
|
#endif
|
|
+
|
|
+#if CONFIG_AST1100 == 1
|
|
+ SPI_CONTROLLER_AST1100,
|
|
+#endif
|
|
+
|
|
+#if CONFIG_AST2400 == 1
|
|
+ SPI_CONTROLLER_AST2400,
|
|
+#endif
|
|
};
|
|
|
|
#define MAX_DATA_UNSPECIFIED 0
|
|
diff --git ./serprog.c ./serprog.c
|
|
index 98aac83..c9d98bf 100644
|
|
--- ./serprog.c
|
|
+++ ./serprog.c
|
|
@@ -945,7 +945,10 @@ static int serprog_spi_read(struct flashctx *flash, uint8_t *buf,
|
|
for (i = 0; i < len; i += cur_len) {
|
|
int ret;
|
|
cur_len = min(max_read, (len - i));
|
|
- ret = spi_nbyte_read(flash, start + i, buf + i, cur_len);
|
|
+ ret = (flash->chip->feature_bits & FEATURE_4BA_SUPPORT) == 0
|
|
+ ? spi_nbyte_read(flash, start + i, buf + i, cur_len)
|
|
+ : flash->chip->four_bytes_addr_funcs.read_nbyte(flash,
|
|
+ start + i, buf + i, cur_len);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
diff --git ./spi.c ./spi.c
|
|
index 894f73f..895b6a3 100644
|
|
--- ./spi.c
|
|
+++ ./spi.c
|
|
@@ -100,6 +100,20 @@ int default_spi_write_256(struct flashctx *flash, const uint8_t *buf, unsigned i
|
|
return spi_write_chunked(flash, buf, start, len, max_data);
|
|
}
|
|
|
|
+static int my_ffs(int x)
|
|
+{
|
|
+ int rc = 0;
|
|
+ while(x)
|
|
+ {
|
|
+ if (x & 1)
|
|
+ return rc;
|
|
+ rc++;
|
|
+ x >>= 1;
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
int spi_chip_read(struct flashctx *flash, uint8_t *buf, unsigned int start,
|
|
unsigned int len)
|
|
{
|
|
@@ -110,7 +124,10 @@ int spi_chip_read(struct flashctx *flash, uint8_t *buf, unsigned int start,
|
|
* means 0xffffff, the highest unsigned 24bit number.
|
|
*/
|
|
addrbase = spi_get_valid_read_addr(flash);
|
|
- if (addrbase + flash->chip->total_size * 1024 > (1 << 24)) {
|
|
+ /* Show flash chip size warning if flash chip doesn't support
|
|
+ 4-Bytes Addressing mode and last address excedes 24 bits */
|
|
+ if (!(flash->chip->feature_bits & FEATURE_4BA_SUPPORT) &&
|
|
+ addrbase + flash->chip->total_size * 1024 > (1 << 24)) {
|
|
msg_perr("Flash chip size exceeds the allowed access window. ");
|
|
msg_perr("Read will probably fail.\n");
|
|
/* Try to get the best alignment subject to constraints. */
|
|
@@ -119,7 +136,7 @@ int spi_chip_read(struct flashctx *flash, uint8_t *buf, unsigned int start,
|
|
/* Check if alignment is native (at least the largest power of two which
|
|
* is a factor of the mapped size of the chip).
|
|
*/
|
|
- if (ffs(flash->chip->total_size * 1024) > (ffs(addrbase) ? : 33)) {
|
|
+ if (my_ffs(flash->chip->total_size * 1024) > (my_ffs(addrbase) ? : 33)) {
|
|
msg_perr("Flash chip is not aligned natively in the allowed "
|
|
"access window.\n");
|
|
msg_perr("Read will probably return garbage.\n");
|
|
diff --git ./spi25.c ./spi25.c
|
|
index af4b6db..ae5e51d 100644
|
|
--- ./spi25.c
|
|
+++ ./spi25.c
|
|
@@ -28,6 +28,7 @@
|
|
#include "chipdrivers.h"
|
|
#include "programmer.h"
|
|
#include "spi.h"
|
|
+#include "spi4ba.h"
|
|
|
|
static int spi_rdid(struct flashctx *flash, unsigned char *readarr, int bytes)
|
|
{
|
|
@@ -948,6 +949,16 @@ int spi_read_chunked(struct flashctx *flash, uint8_t *buf, unsigned int start,
|
|
int rc = 0;
|
|
unsigned int i, j, starthere, lenhere, toread;
|
|
unsigned int page_size = flash->chip->page_size;
|
|
+ int show_progress = 0;
|
|
+ unsigned int percent_last, percent_current;
|
|
+
|
|
+ /* progress visualizaion init */
|
|
+ if(len >= MIN_LENGTH_TO_SHOW_READ_PROGRESS) {
|
|
+ msg_cinfo(" "); /* only this space will go to logfile but all strings with \b wont. */
|
|
+ msg_cinfo("\b 0%%");
|
|
+ percent_last = percent_current = 0;
|
|
+ show_progress = 1; /* enable progress visualizaion */
|
|
+ }
|
|
|
|
/* Warning: This loop has a very unusual condition and body.
|
|
* The loop needs to go through each page with at least one affected
|
|
@@ -966,13 +977,28 @@ int spi_read_chunked(struct flashctx *flash, uint8_t *buf, unsigned int start,
|
|
lenhere = min(start + len, (i + 1) * page_size) - starthere;
|
|
for (j = 0; j < lenhere; j += chunksize) {
|
|
toread = min(chunksize, lenhere - j);
|
|
- rc = spi_nbyte_read(flash, starthere + j, buf + starthere - start + j, toread);
|
|
+ rc = (flash->chip->feature_bits & FEATURE_4BA_SUPPORT) == 0
|
|
+ ? spi_nbyte_read(flash, starthere + j, buf + starthere - start + j, toread)
|
|
+ : flash->chip->four_bytes_addr_funcs.read_nbyte(flash, starthere + j,
|
|
+ buf + starthere - start + j, toread);
|
|
if (rc)
|
|
break;
|
|
}
|
|
if (rc)
|
|
break;
|
|
+
|
|
+ if(show_progress) {
|
|
+ percent_current = (unsigned int) ((unsigned long long)(starthere +
|
|
+ lenhere - start) * 100 / len);
|
|
+ if(percent_current != percent_last) {
|
|
+ msg_cinfo("\b\b\b%2d%%", percent_current);
|
|
+ percent_last = percent_current;
|
|
}
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if(show_progress && !rc)
|
|
+ msg_cinfo("\b\b\b\b"); /* remove progress percents from the screen */
|
|
|
|
return rc;
|
|
}
|
|
@@ -1011,7 +1037,10 @@ int spi_write_chunked(struct flashctx *flash, const uint8_t *buf, unsigned int s
|
|
lenhere = min(start + len, (i + 1) * page_size) - starthere;
|
|
for (j = 0; j < lenhere; j += chunksize) {
|
|
towrite = min(chunksize, lenhere - j);
|
|
- rc = spi_nbyte_program(flash, starthere + j, buf + starthere - start + j, towrite);
|
|
+ rc = (flash->chip->feature_bits & FEATURE_4BA_SUPPORT) == 0
|
|
+ ? spi_nbyte_program(flash, starthere + j, buf + starthere - start + j, towrite)
|
|
+ : flash->chip->four_bytes_addr_funcs.program_nbyte(flash, starthere + j,
|
|
+ buf + starthere - start + j, towrite);
|
|
if (rc)
|
|
break;
|
|
while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
@@ -1037,7 +1066,9 @@ int spi_chip_write_1(struct flashctx *flash, const uint8_t *buf, unsigned int st
|
|
int result = 0;
|
|
|
|
for (i = start; i < start + len; i++) {
|
|
- result = spi_byte_program(flash, i, buf[i - start]);
|
|
+ result = (flash->chip->feature_bits & FEATURE_4BA_SUPPORT) == 0
|
|
+ ? spi_byte_program(flash, i, buf[i - start])
|
|
+ : flash->chip->four_bytes_addr_funcs.program_byte(flash, i, buf[i - start]);
|
|
if (result)
|
|
return 1;
|
|
while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
diff --git ./spi4ba.c ./spi4ba.c
|
|
new file mode 100644
|
|
index 0000000..6e1cc9b
|
|
--- /dev/null
|
|
+++ ./spi4ba.c
|
|
@@ -0,0 +1,920 @@
|
|
+/*
|
|
+ * This file is part of the flashrom project.
|
|
+ *
|
|
+ * Copyright (C) 2014 Boris Baykov
|
|
+ *
|
|
+ * 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
|
|
+ */
|
|
+
|
|
+/*
|
|
+ * SPI chip driver functions for 4-bytes addressing
|
|
+ */
|
|
+
|
|
+#include <string.h>
|
|
+#include "flash.h"
|
|
+#include "chipdrivers.h"
|
|
+#include "spi.h"
|
|
+#include "programmer.h"
|
|
+#include "spi4ba.h"
|
|
+
|
|
+/* #define MSG_TRACE_4BA_FUNCS 1 */
|
|
+
|
|
+#ifdef MSG_TRACE_4BA_FUNCS
|
|
+#define msg_trace(...) print(MSG_DEBUG, __VA_ARGS__)
|
|
+#else
|
|
+#define msg_trace(...)
|
|
+#endif
|
|
+
|
|
+/* Enter 4-bytes addressing mode (without sending WREN before) */
|
|
+int spi_enter_4ba_b7(struct flashctx *flash)
|
|
+{
|
|
+ const unsigned char cmd[JEDEC_ENTER_4_BYTE_ADDR_MODE_OUTSIZE] = { JEDEC_ENTER_4_BYTE_ADDR_MODE };
|
|
+
|
|
+ msg_trace("-> %s\n", __func__);
|
|
+
|
|
+ /* Switch to 4-bytes addressing mode */
|
|
+ return spi_send_command(flash, sizeof(cmd), 0, cmd, NULL);
|
|
+}
|
|
+
|
|
+/* Enter 4-bytes addressing mode with sending WREN before */
|
|
+int spi_enter_4ba_b7_we(struct flashctx *flash)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_ENTER_4_BYTE_ADDR_MODE_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_ENTER_4_BYTE_ADDR_MODE },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s\n", __func__);
|
|
+
|
|
+ /* Switch to 4-bytes addressing mode */
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution\n", __func__);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Program one flash byte from 4-bytes addressing mode */
|
|
+int spi_byte_program_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t databyte)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BYTE_PROGRAM_OUTSIZE + 1,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BYTE_PROGRAM,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff),
|
|
+ databyte
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X)\n", __func__, addr);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Program flash bytes from 4-bytes addressing mode */
|
|
+int spi_nbyte_program_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ const uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ int result;
|
|
+ unsigned char cmd[(JEDEC_BYTE_PROGRAM_OUTSIZE + 1) - 1 + 256] = {
|
|
+ JEDEC_BYTE_PROGRAM,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = (JEDEC_BYTE_PROGRAM_OUTSIZE + 1) - 1 + len,
|
|
+ .writearr = cmd,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ if (!len) {
|
|
+ msg_cerr("%s called for zero-length write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+ if (len > 256) {
|
|
+ msg_cerr("%s called for too long a write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ memcpy(&cmd[(JEDEC_BYTE_PROGRAM_OUTSIZE + 1) - 1], bytes, len);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Read flash bytes from 4-bytes addressing mode */
|
|
+int spi_nbyte_read_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ const unsigned char cmd[JEDEC_READ_OUTSIZE + 1] = {
|
|
+ JEDEC_READ,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ /* Send Read */
|
|
+ return spi_send_command(flash, sizeof(cmd), len, cmd, bytes);
|
|
+}
|
|
+
|
|
+/* Erases 4 KB of flash from 4-bytes addressing mode */
|
|
+int spi_block_erase_20_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_SE_OUTSIZE + 1,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_SE,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 15-800 ms, so wait in 10 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(10 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erases 32 KB of flash from 4-bytes addressing mode */
|
|
+int spi_block_erase_52_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_52_OUTSIZE + 1,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_52,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erases 64 KB of flash from 4-bytes addressing mode */
|
|
+int spi_block_erase_d8_4ba(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_D8_OUTSIZE + 1,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_D8,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Write Extended Address Register value */
|
|
+int spi_write_extended_address_register(struct flashctx *flash, uint8_t regdata)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_WRITE_EXT_ADDR_REG_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_WRITE_EXT_ADDR_REG,
|
|
+ regdata
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (%02X)\n", __func__, regdata);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution\n", __func__);
|
|
+ return result;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Assign required value of Extended Address Register. This function
|
|
+ keeps last value of the register and writes the register if the
|
|
+ value has to be changed only. */
|
|
+int set_extended_address_register(struct flashctx *flash, uint8_t data)
|
|
+{
|
|
+ static uint8_t ext_addr_reg_state; /* memory for last register state */
|
|
+ static int ext_addr_reg_state_valid = 0;
|
|
+ int result;
|
|
+
|
|
+ if (ext_addr_reg_state_valid == 0 || data != ext_addr_reg_state) {
|
|
+ result = spi_write_extended_address_register(flash, data);
|
|
+ if (result) {
|
|
+ ext_addr_reg_state_valid = 0;
|
|
+ return result;
|
|
+ }
|
|
+ ext_addr_reg_state = data;
|
|
+ ext_addr_reg_state_valid = 1;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Program one flash byte using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_byte_program_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t databyte)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BYTE_PROGRAM_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BYTE_PROGRAM,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff),
|
|
+ databyte
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X)\n", __func__, addr);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Program flash bytes using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_nbyte_program_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ const uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ int result;
|
|
+ unsigned char cmd[JEDEC_BYTE_PROGRAM_OUTSIZE - 1 + 256] = {
|
|
+ JEDEC_BYTE_PROGRAM,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BYTE_PROGRAM_OUTSIZE - 1 + len,
|
|
+ .writearr = cmd,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ if (!len) {
|
|
+ msg_cerr("%s called for zero-length write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+ if (len > 256) {
|
|
+ msg_cerr("%s called for too long a write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ memcpy(&cmd[JEDEC_BYTE_PROGRAM_OUTSIZE - 1], bytes, len);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Read flash bytes using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_nbyte_read_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ int result;
|
|
+ const unsigned char cmd[JEDEC_READ_OUTSIZE] = {
|
|
+ JEDEC_READ,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ /* Send Read */
|
|
+ return spi_send_command(flash, sizeof(cmd), len, cmd, bytes);
|
|
+}
|
|
+
|
|
+/* Erases 4 KB of flash using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_block_erase_20_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_SE_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_SE,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 15-800 ms, so wait in 10 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(10 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erases 32 KB of flash using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_block_erase_52_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_52_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_52,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erases 64 KB of flash using Extended Address Register
|
|
+ from 3-bytes addressing mode */
|
|
+int spi_block_erase_d8_4ba_ereg(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_D8_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_D8,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = set_extended_address_register(flash, (addr >> 24) & 0xff);
|
|
+ if (result)
|
|
+ return result;
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Program one flash byte with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_BYTE_PROGRAM_4BA (12h) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_byte_program_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t databyte)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BYTE_PROGRAM_4BA_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BYTE_PROGRAM_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff),
|
|
+ databyte
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X)\n", __func__, addr);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Program flash bytes with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_BYTE_PROGRAM_4BA (12h) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_nbyte_program_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ const uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ int result;
|
|
+ unsigned char cmd[JEDEC_BYTE_PROGRAM_4BA_OUTSIZE - 1 + 256] = {
|
|
+ JEDEC_BYTE_PROGRAM_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BYTE_PROGRAM_4BA_OUTSIZE - 1 + len,
|
|
+ .writearr = cmd,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ if (!len) {
|
|
+ msg_cerr("%s called for zero-length write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+ if (len > 256) {
|
|
+ msg_cerr("%s called for too long a write\n", __func__);
|
|
+ return 1;
|
|
+ }
|
|
+
|
|
+ memcpy(&cmd[JEDEC_BYTE_PROGRAM_4BA_OUTSIZE - 1], bytes, len);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+/* Read flash bytes with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_READ_4BA (13h) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_nbyte_read_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ uint8_t *bytes, unsigned int len)
|
|
+{
|
|
+ const unsigned char cmd[JEDEC_READ_4BA_OUTSIZE] = {
|
|
+ JEDEC_READ_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr >> 0) & 0xff
|
|
+ };
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + len - 1);
|
|
+
|
|
+ /* Send Read */
|
|
+ return spi_send_command(flash, sizeof(cmd), len, cmd, bytes);
|
|
+}
|
|
+
|
|
+/* Erase 4 KB of flash with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_SE_4BA (21h) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_block_erase_21_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_SE_4BA_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_SE_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 15-800 ms, so wait in 10 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(10 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erase 32 KB of flash with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_BE_5C_4BA (5Ch) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_block_erase_5c_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_5C_4BA_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_5C_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+/* Erase 64 KB of flash with 4-bytes address from ANY mode (3-bytes or 4-bytes)
|
|
+ JEDEC_BE_DC_4BA (DCh) instruction is new for 4-bytes addressing flash chips.
|
|
+ The presence of this instruction for an exact chip should be checked
|
|
+ by its datasheet or from SFDP 4-Bytes Address Instruction Table (JESD216B). */
|
|
+int spi_block_erase_dc_4ba_direct(struct flashctx *flash, unsigned int addr,
|
|
+ unsigned int blocklen)
|
|
+{
|
|
+ int result;
|
|
+ struct spi_command cmds[] = {
|
|
+ {
|
|
+ .writecnt = JEDEC_WREN_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){ JEDEC_WREN },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = JEDEC_BE_DC_4BA_OUTSIZE,
|
|
+ .writearr = (const unsigned char[]){
|
|
+ JEDEC_BE_DC_4BA,
|
|
+ (addr >> 24) & 0xff,
|
|
+ (addr >> 16) & 0xff,
|
|
+ (addr >> 8) & 0xff,
|
|
+ (addr & 0xff)
|
|
+ },
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }, {
|
|
+ .writecnt = 0,
|
|
+ .writearr = NULL,
|
|
+ .readcnt = 0,
|
|
+ .readarr = NULL,
|
|
+ }};
|
|
+
|
|
+ msg_trace("-> %s (0x%08X-0x%08X)\n", __func__, addr, addr + blocklen - 1);
|
|
+
|
|
+ result = spi_send_multicommand(flash, cmds);
|
|
+ if (result) {
|
|
+ msg_cerr("%s failed during command execution at address 0x%x\n",
|
|
+ __func__, addr);
|
|
+ return result;
|
|
+ }
|
|
+ /* Wait until the Write-In-Progress bit is cleared.
|
|
+ * This usually takes 100-4000 ms, so wait in 100 ms steps.
|
|
+ */
|
|
+ while (spi_read_status_register(flash) & SPI_SR_WIP)
|
|
+ programmer_delay(100 * 1000);
|
|
+ /* FIXME: Check the status register for errors. */
|
|
+ return 0;
|
|
+}
|
|
diff --git ./spi4ba.h ./spi4ba.h
|
|
new file mode 100644
|
|
index 0000000..8e500d1
|
|
--- /dev/null
|
|
+++ ./spi4ba.h
|
|
@@ -0,0 +1,114 @@
|
|
+/*
|
|
+ * This file is part of the flashrom project.
|
|
+ *
|
|
+ * Copyright (C) 2014 Boris Baykov
|
|
+ *
|
|
+ * 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; either version 2 of the License, or
|
|
+ * (at your option) any later version.
|
|
+ *
|
|
+ * 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
|
|
+*/
|
|
+
|
|
+/*
|
|
+ * JEDEC flash chips instructions for 4-bytes addressing
|
|
+ * SPI chip driver functions for 4-bytes addressing
|
|
+ */
|
|
+
|
|
+#ifndef __SPI_4BA_H__
|
|
+#define __SPI_4BA_H__ 1
|
|
+
|
|
+/* Enter 4-byte Address Mode */
|
|
+#define JEDEC_ENTER_4_BYTE_ADDR_MODE 0xB7
|
|
+#define JEDEC_ENTER_4_BYTE_ADDR_MODE_OUTSIZE 0x01
|
|
+#define JEDEC_ENTER_4_BYTE_ADDR_MODE_INSIZE 0x00
|
|
+
|
|
+/* Exit 4-byte Address Mode */
|
|
+#define JEDEC_EXIT_4_BYTE_ADDR_MODE 0xE9
|
|
+#define JEDEC_EXIT_4_BYTE_ADDR_MODE_OUTSIZE 0x01
|
|
+#define JEDEC_EXIT_4_BYTE_ADDR_MODE_INSIZE 0x00
|
|
+
|
|
+/* Write Extended Address Register */
|
|
+#define JEDEC_WRITE_EXT_ADDR_REG 0xC5
|
|
+#define JEDEC_WRITE_EXT_ADDR_REG_OUTSIZE 0x02
|
|
+#define JEDEC_WRITE_EXT_ADDR_REG_INSIZE 0x00
|
|
+
|
|
+/* Read Extended Address Register */
|
|
+#define JEDEC_READ_EXT_ADDR_REG 0xC8
|
|
+#define JEDEC_READ_EXT_ADDR_REG_OUTSIZE 0x01
|
|
+#define JEDEC_READ_EXT_ADDR_REG_INSIZE 0x01
|
|
+
|
|
+/* Read the memory with 4-byte address
|
|
+ From ANY mode (3-bytes or 4-bytes) it works with 4-byte address */
|
|
+#define JEDEC_READ_4BA 0x13
|
|
+#define JEDEC_READ_4BA_OUTSIZE 0x05
|
|
+/* JEDEC_READ_4BA_INSIZE : any length */
|
|
+
|
|
+/* Write memory byte with 4-byte address
|
|
+ From ANY mode (3-bytes or 4-bytes) it works with 4-byte address */
|
|
+#define JEDEC_BYTE_PROGRAM_4BA 0x12
|
|
+#define JEDEC_BYTE_PROGRAM_4BA_OUTSIZE 0x06
|
|
+#define JEDEC_BYTE_PROGRAM_4BA_INSIZE 0x00
|
|
+
|
|
+/* Sector Erase 0x21 (with 4-byte address), usually 4k size.
|
|
+ From ANY mode (3-bytes or 4-bytes) it works with 4-byte address */
|
|
+#define JEDEC_SE_4BA 0x21
|
|
+#define JEDEC_SE_4BA_OUTSIZE 0x05
|
|
+#define JEDEC_SE_4BA_INSIZE 0x00
|
|
+
|
|
+/* Block Erase 0x5C (with 4-byte address), usually 32k size.
|
|
+ From ANY mode (3-bytes or 4-bytes) it works with 4-byte address */
|
|
+#define JEDEC_BE_5C_4BA 0x5C
|
|
+#define JEDEC_BE_5C_4BA_OUTSIZE 0x05
|
|
+#define JEDEC_BE_5C_4BA_INSIZE 0x00
|
|
+
|
|
+/* Block Erase 0xDC (with 4-byte address), usually 64k size.
|
|
+ From ANY mode (3-bytes or 4-bytes) it works with 4-byte address */
|
|
+#define JEDEC_BE_DC_4BA 0xdc
|
|
+#define JEDEC_BE_DC_4BA_OUTSIZE 0x05
|
|
+#define JEDEC_BE_DC_4BA_INSIZE 0x00
|
|
+
|
|
+/* enter 4-bytes addressing mode */
|
|
+int spi_enter_4ba_b7(struct flashctx *flash);
|
|
+int spi_enter_4ba_b7_we(struct flashctx *flash);
|
|
+
|
|
+/* read/write flash bytes in 4-bytes addressing mode */
|
|
+int spi_byte_program_4ba(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+
|
|
+/* erase flash bytes in 4-bytes addressing mode */
|
|
+int spi_block_erase_20_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_52_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_d8_4ba(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+
|
|
+/* read/write flash bytes from 3-bytes addressing mode using extended address register */
|
|
+int spi_byte_program_4ba_ereg(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba_ereg(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba_ereg(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+
|
|
+/* erase flash bytes from 3-bytes addressing mode using extended address register */
|
|
+int spi_block_erase_20_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_52_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_d8_4ba_ereg(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+
|
|
+/* read/write flash bytes with 4-bytes address from any mode (3-byte or 4-byte) */
|
|
+int spi_byte_program_4ba_direct(struct flashctx *flash, unsigned int addr, uint8_t databyte);
|
|
+int spi_nbyte_program_4ba_direct(struct flashctx *flash, unsigned int addr, const uint8_t *bytes, unsigned int len);
|
|
+int spi_nbyte_read_4ba_direct(struct flashctx *flash, unsigned int addr, uint8_t *bytes, unsigned int len);
|
|
+
|
|
+/* erase flash bytes with 4-bytes address from any mode (3-byte or 4-byte) */
|
|
+int spi_block_erase_21_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_5c_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+int spi_block_erase_dc_4ba_direct(struct flashctx *flash, unsigned int addr, unsigned int blocklen);
|
|
+
|
|
+
|
|
+#endif /* __SPI_4BA_H__ */
|