mtd: spi-nor: Recover from Spansion/Cypress errors
authorAlexander Sverdlin <alexander.sverdlin@nokia.com>
Mon, 17 Jul 2017 15:54:07 +0000 (17:54 +0200)
committerCyrille Pitchen <cyrille.pitchen@wedev4u.fr>
Tue, 1 Aug 2017 19:15:33 +0000 (21:15 +0200)
S25FL{128|256|512}S datasheets say:
"When P_ERR or E_ERR bits are set to one, the WIP bit will remain set to
one indicating the device remains busy and unable to receive new operation
commands. A Clear Status Register (CLSR) command must be received to return
the device to standby mode."

Current spi-nor code works until first error occurs, but write/erase errors
are not just rare hardware failures, they also occur if user tries to flash
write-protected areas. After such attempt no SPI command can be executed
any more and even read fails. This patch adds support for P_ERR and E_ERR
bits in Status Register 1 (so that operation fails immediately and not
after a long timeout) and proper recovery from the error condition.

Tested on Spansion S25FS128S, which is supported by S25FL129P entry.

Signed-off-by: Alexander Sverdlin <alexander.sverdlin@nokia.com>
Signed-off-by: Cyrille Pitchen <cyrille.pitchen@wedev4u.fr>
drivers/mtd/spi-nor/spi-nor.c
include/linux/mtd/spi-nor.h

index 196b52f..c70c2eb 100644 (file)
@@ -88,6 +88,7 @@ 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 JEDEC_MFR(info)        ((info)->id[0])
@@ -308,8 +309,18 @@ static inline int spi_nor_sr_ready(struct spi_nor *nor)
        int sr = read_sr(nor);
        if (sr < 0)
                return sr;
-       else
-               return !(sr & SR_WIP);
+
+       if (nor->flags & SNOR_F_USE_CLSR && sr & (SR_E_ERR | SR_P_ERR)) {
+               if (sr & SR_E_ERR)
+                       dev_err(nor->dev, "Erase Error occurred\n");
+               else
+                       dev_err(nor->dev, "Programming Error occurred\n");
+
+               nor->write_reg(nor, SPINOR_OP_CLSR, NULL, 0);
+               return -EIO;
+       }
+
+       return !(sr & SR_WIP);
 }
 
 static inline int spi_nor_fsr_ready(struct spi_nor *nor)
@@ -1043,15 +1054,15 @@ static const struct flash_info spi_nor_ids[] = {
         */
        { "s25sl032p",  INFO(0x010215, 0x4d00,  64 * 1024,  64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
        { "s25sl064p",  INFO(0x010216, 0x4d00,  64 * 1024, 128, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
-       { "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, 0) },
-       { "s25fl256s1", INFO(0x010219, 0x4d01,  64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
-       { "s25fl512s",  INFO(0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+       { "s25fl256s0", INFO(0x010219, 0x4d00, 256 * 1024, 128, USE_CLSR) },
+       { "s25fl256s1", INFO(0x010219, 0x4d01,  64 * 1024, 512, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
+       { "s25fl512s",  INFO(0x010220, 0x4d00, 256 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
        { "s70fl01gs",  INFO(0x010221, 0x4d00, 256 * 1024, 256, 0) },
        { "s25sl12800", INFO(0x012018, 0x0300, 256 * 1024,  64, 0) },
        { "s25sl12801", INFO(0x012018, 0x0301,  64 * 1024, 256, 0) },
-       { "s25fl128s",  INFO6(0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
-       { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024,  64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
-       { "s25fl129p1", INFO(0x012018, 0x4d01,  64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ) },
+       { "s25fl128s",  INFO6(0x012018, 0x4d0180, 64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
+       { "s25fl129p0", INFO(0x012018, 0x4d00, 256 * 1024,  64, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
+       { "s25fl129p1", INFO(0x012018, 0x4d01,  64 * 1024, 256, SPI_NOR_DUAL_READ | SPI_NOR_QUAD_READ | USE_CLSR) },
        { "s25sl004a",  INFO(0x010212,      0,  64 * 1024,   8, 0) },
        { "s25sl008a",  INFO(0x010213,      0,  64 * 1024,  16, 0) },
        { "s25sl016a",  INFO(0x010214,      0,  64 * 1024,  32, 0) },
@@ -2707,6 +2718,8 @@ int spi_nor_scan(struct spi_nor *nor, const char *name,
                nor->flags |= SNOR_F_HAS_SR_TB;
        if (info->flags & NO_CHIP_ERASE)
                nor->flags |= SNOR_F_NO_OP_CHIP_ERASE;
+       if (info->flags & USE_CLSR)
+               nor->flags |= SNOR_F_USE_CLSR;
 
        if (info->flags & SPI_NOR_NO_ERASE)
                mtd->flags |= MTD_NO_ERASE;
index 0df3638..1f0a7fc 100644 (file)
 
 /* Used for Spansion flashes only. */
 #define SPINOR_OP_BRWR         0x17    /* Bank register write */
+#define SPINOR_OP_CLSR         0x30    /* Clear status register 1 */
 
 /* Used for Micron flashes only. */
 #define SPINOR_OP_RD_EVCR      0x65    /* Read EVCR register */
 #define SR_BP2                 BIT(4)  /* Block protect 2 */
 #define SR_TB                  BIT(5)  /* Top/Bottom protect */
 #define SR_SRWD                        BIT(7)  /* SR write protect */
+/* Spansion/Cypress specific status bits */
+#define SR_E_ERR               BIT(5)
+#define SR_P_ERR               BIT(6)
 
 #define SR_QUAD_EN_MX          BIT(6)  /* Macronix Quad I/O */
 
@@ -224,6 +228,7 @@ enum spi_nor_option_flags {
        SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
        SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
        SNOR_F_READY_XSR_RDY    = BIT(4),
+       SNOR_F_USE_CLSR         = BIT(5),
 };
 
 /**