OpenWrt – Rev 1

Subversion Repositories:
Rev:
mtd: spi-nor: add support for switching between 3-byte and 4-byte addressing on w25q256 flash

On some devices the flash chip needs to be in 3-byte addressing mode during
reboot, otherwise the boot loader will fail to start.
This mode however does not allow regular reads/writes onto the upper 16M
half. W25Q256 has separate read commands for reading from >16M, however
it does not have any separate write commands.
This patch changes the code to leave the chip in 3-byte mode most of the
time and only switch during erase/write cycles that go to >16M
addresses.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
---
--- a/drivers/mtd/spi-nor/spi-nor.c
+++ b/drivers/mtd/spi-nor/spi-nor.c
@@ -89,6 +89,10 @@ struct flash_info {
 #define NO_CHIP_ERASE          BIT(12) /* Chip does not support chip erase */
 #define SPI_NOR_SKIP_SFDP      BIT(13) /* Skip parsing of SFDP tables */
 #define USE_CLSR               BIT(14) /* use CLSR command */
+#define SPI_NOR_4B_READ_OP     BIT(15) /*
+                                        * Like SPI_NOR_4B_OPCODES, but for read
+                                        * op code only.
+                                        */
 };
 
 #define JEDEC_MFR(info)        ((info)->id[0])
@@ -240,6 +244,15 @@ static inline u8 spi_nor_convert_3to4_er
                                      ARRAY_SIZE(spi_nor_3to4_erase));
 }
 
+static void spi_nor_set_4byte_read(struct spi_nor *nor,
+                                  const struct flash_info *info)
+{
+       nor->addr_width = 3;
+       nor->ext_addr = 0;
+       nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
+       nor->flags |= SNOR_F_4B_EXT_ADDR;
+}
+
 static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
                                      const struct flash_info *info)
 {
@@ -467,6 +480,36 @@ static int spi_nor_erase_sector(struct s
        return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
 }
 
+static int spi_nor_check_ext_addr(struct spi_nor *nor, u32 addr)
+{
+       bool ext_addr;
+       int ret;
+       u8 cmd;
+
+       if (!(nor->flags & SNOR_F_4B_EXT_ADDR))
+               return 0;
+
+       ext_addr = !!(addr & 0xff000000);
+       if (nor->ext_addr == ext_addr)
+               return 0;
+
+       cmd = ext_addr ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
+       write_enable(nor);
+       ret = nor->write_reg(nor, cmd, NULL, 0);
+       if (ret)
+               return ret;
+
+       cmd = 0;
+       ret = nor->write_reg(nor, SPINOR_OP_WREAR, &cmd, 1);
+       if (ret)
+               return ret;
+
+       nor->addr_width = 3 + ext_addr;
+       nor->ext_addr = ext_addr;
+       write_disable(nor);
+       return 0;
+}
+
 /*
  * Erase an address range on the nor chip.  The address range may extend
  * one or more erase sectors.  Return an error is there is a problem erasing.
@@ -492,6 +535,10 @@ static int spi_nor_erase(struct mtd_info
        if (ret)
                return ret;
 
+       ret = spi_nor_check_ext_addr(nor, addr + len);
+       if (ret)
+               return ret;
+
        /* whole-chip erase? */
        if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
                unsigned long timeout;
@@ -542,6 +589,7 @@ static int spi_nor_erase(struct mtd_info
        write_disable(nor);
 
 erase_err:
+       spi_nor_check_ext_addr(nor, 0);
        spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
 
        instr->state = ret ? MTD_ERASE_FAILED : MTD_ERASE_DONE;
@@ -834,7 +882,9 @@ static int spi_nor_lock(struct mtd_info
        if (ret)
                return ret;
 
+       spi_nor_check_ext_addr(nor, ofs + len);
        ret = nor->flash_lock(nor, ofs, len);
+       spi_nor_check_ext_addr(nor, 0);
 
        spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK);
        return ret;
@@ -849,7 +899,9 @@ static int spi_nor_unlock(struct mtd_inf
        if (ret)
                return ret;
 
+       spi_nor_check_ext_addr(nor, ofs + len);
        ret = nor->flash_unlock(nor, ofs, len);
+       spi_nor_check_ext_addr(nor, 0);
 
        spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK);
        return ret;
@@ -1177,7 +1229,7 @@ static const struct flash_info spi_nor_i
        { "w25q80", INFO(0xef5014, 0, 64 * 1024,  16, SECT_4K) },
        { "w25q80bl", INFO(0xef4014, 0, 64 * 1024,  16, SECT_4K) },
        { "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
-       { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+       { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | SPI_NOR_4B_READ_OP) },
        { "w25m512jv", INFO(0xef7119, 0, 64 * 1024, 1024,
                        SECT_4K | SPI_NOR_QUAD_READ | SPI_NOR_DUAL_READ) },
 
@@ -1237,6 +1289,9 @@ static int spi_nor_read(struct mtd_info
        if (ret)
                return ret;
 
+       if (nor->flags & SNOR_F_4B_EXT_ADDR)
+               nor->addr_width = 4;
+
        while (len) {
                loff_t addr = from;
 
@@ -1261,6 +1316,18 @@ static int spi_nor_read(struct mtd_info
        ret = 0;
 
 read_err:
+       if (nor->flags & SNOR_F_4B_EXT_ADDR) {
+               u8 val = 0;
+
+               if ((from + len) & 0xff000000) {
+                       write_enable(nor);
+                       nor->write_reg(nor, SPINOR_OP_WREAR, &val, 1);
+                       write_disable(nor);
+               }
+
+               nor->addr_width = 3;
+       }
+
        spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
        return ret;
 }
@@ -1362,6 +1429,10 @@ static int spi_nor_write(struct mtd_info
        if (ret)
                return ret;
 
+       ret = spi_nor_check_ext_addr(nor, to + len);
+       if (ret < 0)
+               return ret;
+
        for (i = 0; i < len; ) {
                ssize_t written;
                loff_t addr = to + i;
@@ -1402,6 +1473,7 @@ static int spi_nor_write(struct mtd_info
        }
 
 write_err:
+       spi_nor_check_ext_addr(nor, 0);
        spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
        return ret;
 }
@@ -2818,8 +2890,10 @@ int spi_nor_scan(struct spi_nor *nor, co
        } else if (mtd->size > 0x1000000) {
                /* enable 4-byte addressing if the device exceeds 16MiB */
                nor->addr_width = 4;
-               if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
-                   info->flags & SPI_NOR_4B_OPCODES)
+               if (info->flags & SPI_NOR_4B_READ_OP)
+                       spi_nor_set_4byte_read(nor, info);
+               else if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
+                        info->flags & SPI_NOR_4B_OPCODES)
                        spi_nor_set_4byte_opcodes(nor, info);
                else
                        set_4byte(nor, info, 1);
--- a/include/linux/mtd/spi-nor.h
+++ b/include/linux/mtd/spi-nor.h
@@ -102,6 +102,7 @@
 /* Used for Macronix and Winbond flashes. */
 #define SPINOR_OP_EN4B         0xb7    /* Enter 4-byte mode */
 #define SPINOR_OP_EX4B         0xe9    /* Exit 4-byte mode */
+#define SPINOR_OP_WREAR                0xc5    /* Write extended address register */
 
 /* Used for Spansion flashes only. */
 #define SPINOR_OP_BRWR         0x17    /* Bank register write */
@@ -229,6 +230,7 @@ enum spi_nor_option_flags {
        SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
        SNOR_F_READY_XSR_RDY    = BIT(4),
        SNOR_F_USE_CLSR         = BIT(5),
+       SNOR_F_4B_EXT_ADDR      = BIT(6),
 };
 
 /**
@@ -280,6 +282,7 @@ struct spi_nor {
        enum spi_nor_protocol   reg_proto;
        bool                    sst_write_second;
        u32                     flags;
+       u8                      ext_addr;
        u8                      cmd_buf[SPI_NOR_MAX_CMD_SIZE];
 
        int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);