mtd: spi: spi-nor-core: Add 4 Byte addressing support
authorVignesh R <vigneshr@ti.com>
Tue, 5 Feb 2019 05:59:19 +0000 (11:29 +0530)
committerJagan Teki <jagan@amarulasolutions.com>
Thu, 7 Feb 2019 10:03:21 +0000 (15:33 +0530)
Sync changes from Linux SPI NOR framework to add 4 byte addressing
support. This is required in order to support flashes like MT35x
that no longer support legacy Bank Address Register(BAR) way of accessing
>16MB region.

Signed-off-by: Vignesh R <vigneshr@ti.com>
Tested-by: Simon Goldschmidt <simon.k.r.goldschmidt@gmail.com>
Tested-by: Stefan Roese <sr@denx.de>
Tested-by: Horatiu Vultur <horatiu.vultur@microchip.com>
Reviewed-by: Jagan Teki <jagan@openedev.com>
Tested-by: Jagan Teki <jagan@amarulasolutions.com> #zynq-microzed
drivers/mtd/spi/spi-nor-core.c

index 66e94258b6c5ba699ced5efe0fa2d162e9899d83..523e44cbba2a8e97ec1ccc3fa488b927b616e304 100644 (file)
@@ -290,6 +290,126 @@ static struct spi_nor *mtd_to_spi_nor(struct mtd_info *mtd)
        return mtd->priv;
 }
 
+static u8 spi_nor_convert_opcode(u8 opcode, const u8 table[][2], size_t size)
+{
+       size_t i;
+
+       for (i = 0; i < size; i++)
+               if (table[i][0] == opcode)
+                       return table[i][1];
+
+       /* No conversion found, keep input op code. */
+       return opcode;
+}
+
+static u8 spi_nor_convert_3to4_read(u8 opcode)
+{
+       static const u8 spi_nor_3to4_read[][2] = {
+               { SPINOR_OP_READ,       SPINOR_OP_READ_4B },
+               { SPINOR_OP_READ_FAST,  SPINOR_OP_READ_FAST_4B },
+               { SPINOR_OP_READ_1_1_2, SPINOR_OP_READ_1_1_2_4B },
+               { SPINOR_OP_READ_1_2_2, SPINOR_OP_READ_1_2_2_4B },
+               { SPINOR_OP_READ_1_1_4, SPINOR_OP_READ_1_1_4_4B },
+               { SPINOR_OP_READ_1_4_4, SPINOR_OP_READ_1_4_4_4B },
+
+               { SPINOR_OP_READ_1_1_1_DTR,     SPINOR_OP_READ_1_1_1_DTR_4B },
+               { SPINOR_OP_READ_1_2_2_DTR,     SPINOR_OP_READ_1_2_2_DTR_4B },
+               { SPINOR_OP_READ_1_4_4_DTR,     SPINOR_OP_READ_1_4_4_DTR_4B },
+       };
+
+       return spi_nor_convert_opcode(opcode, spi_nor_3to4_read,
+                                     ARRAY_SIZE(spi_nor_3to4_read));
+}
+
+static u8 spi_nor_convert_3to4_program(u8 opcode)
+{
+       static const u8 spi_nor_3to4_program[][2] = {
+               { SPINOR_OP_PP,         SPINOR_OP_PP_4B },
+               { SPINOR_OP_PP_1_1_4,   SPINOR_OP_PP_1_1_4_4B },
+               { SPINOR_OP_PP_1_4_4,   SPINOR_OP_PP_1_4_4_4B },
+       };
+
+       return spi_nor_convert_opcode(opcode, spi_nor_3to4_program,
+                                     ARRAY_SIZE(spi_nor_3to4_program));
+}
+
+static u8 spi_nor_convert_3to4_erase(u8 opcode)
+{
+       static const u8 spi_nor_3to4_erase[][2] = {
+               { SPINOR_OP_BE_4K,      SPINOR_OP_BE_4K_4B },
+               { SPINOR_OP_BE_32K,     SPINOR_OP_BE_32K_4B },
+               { SPINOR_OP_SE,         SPINOR_OP_SE_4B },
+       };
+
+       return spi_nor_convert_opcode(opcode, spi_nor_3to4_erase,
+                                     ARRAY_SIZE(spi_nor_3to4_erase));
+}
+
+static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
+                                     const struct flash_info *info)
+{
+       /* Do some manufacturer fixups first */
+       switch (JEDEC_MFR(info)) {
+       case SNOR_MFR_SPANSION:
+               /* No small sector erase for 4-byte command set */
+               nor->erase_opcode = SPINOR_OP_SE;
+               nor->mtd.erasesize = info->sector_size;
+               break;
+
+       default:
+               break;
+       }
+
+       nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
+       nor->program_opcode = spi_nor_convert_3to4_program(nor->program_opcode);
+       nor->erase_opcode = spi_nor_convert_3to4_erase(nor->erase_opcode);
+}
+
+/* Enable/disable 4-byte addressing mode. */
+static int set_4byte(struct spi_nor *nor, const struct flash_info *info,
+                    int enable)
+{
+       int status;
+       bool need_wren = false;
+       u8 cmd;
+
+       switch (JEDEC_MFR(info)) {
+       case SNOR_MFR_ST:
+       case SNOR_MFR_MICRON:
+               /* Some Micron need WREN command; all will accept it */
+               need_wren = true;
+       case SNOR_MFR_MACRONIX:
+       case SNOR_MFR_WINBOND:
+               if (need_wren)
+                       write_enable(nor);
+
+               cmd = enable ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
+               status = nor->write_reg(nor, cmd, NULL, 0);
+               if (need_wren)
+                       write_disable(nor);
+
+               if (!status && !enable &&
+                   JEDEC_MFR(info) == SNOR_MFR_WINBOND) {
+                       /*
+                        * On Winbond W25Q256FV, leaving 4byte mode causes
+                        * the Extended Address Register to be set to 1, so all
+                        * 3-byte-address reads come from the second 16M.
+                        * We must clear the register to enable normal behavior.
+                        */
+                       write_enable(nor);
+                       nor->cmd_buf[0] = 0;
+                       nor->write_reg(nor, SPINOR_OP_WREAR, nor->cmd_buf, 1);
+                       write_disable(nor);
+               }
+
+               return status;
+       default:
+               /* Spansion style */
+               nor->cmd_buf[0] = enable << 7;
+               return nor->write_reg(nor, SPINOR_OP_BRWR, nor->cmd_buf, 1);
+       }
+}
+
 static int spi_nor_sr_ready(struct spi_nor *nor)
 {
        int sr = read_sr(nor);
@@ -1663,6 +1783,21 @@ static int spi_nor_init(struct spi_nor *nor)
                }
        }
 
+       if (nor->addr_width == 4 &&
+           (JEDEC_MFR(nor->info) != SNOR_MFR_SPANSION) &&
+           !(nor->info->flags & SPI_NOR_4B_OPCODES)) {
+               /*
+                * If the RESET# pin isn't hooked up properly, or the system
+                * otherwise doesn't perform a reset command in the boot
+                * sequence, it's impossible to 100% protect against unexpected
+                * reboots (e.g., crashes). Warn the user (or hopefully, system
+                * designer) that this is bad.
+                */
+               if (nor->flags & SNOR_F_BROKEN_RESET)
+                       printf("enabling reset hack; may not recover from unexpected reboots\n");
+               set_4byte(nor, nor->info, 1);
+       }
+
        return 0;
 }
 
@@ -1772,6 +1907,12 @@ int spi_nor_scan(struct spi_nor *nor)
 
        if (info->addr_width) {
                nor->addr_width = info->addr_width;
+       } 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)
+                       spi_nor_set_4byte_opcodes(nor, info);
        } else {
                nor->addr_width = 3;
        }