diff options
author | Kamal Dasu <kdasu.kdev@gmail.com> | 2017-03-03 16:16:53 -0500 |
---|---|---|
committer | Boris Brezillon <boris.brezillon@free-electrons.com> | 2017-04-25 08:18:42 -0400 |
commit | 9d2ee0a60b8bd9bef2a0082c533736d6a7b39873 (patch) | |
tree | 08104bffea7783a3211f338b5c7cacdc2aa1cd08 /drivers/mtd/nand | |
parent | 65a2c1caa70f71690dcb5afd8fc6afe67fcde599 (diff) |
mtd: nand: brcmnand: Check flash #WP pin status before nand erase/program
On brcmnand controller v6.x and v7.x, the #WP pin is controlled through
the NAND_WP bit in CS_SELECT register.
The driver currently assumes that toggling the #WP pin is
instantaneously enabling/disabling write-protection, but it actually
takes some time to propagate the new state to the internal NAND chip
logic. This behavior is sometime causing data corruptions when an
erase/program operation is executed before write-protection has really
been disabled.
Fixes: 27c5b17cd1b1 ("mtd: nand: add NAND driver "library" for Broadcom STB NAND controller")
Signed-off-by: Kamal Dasu <kdasu.kdev@gmail.com>
Signed-off-by: Boris Brezillon <boris.brezillon@free-electrons.com>
Diffstat (limited to 'drivers/mtd/nand')
-rw-r--r-- | drivers/mtd/nand/brcmnand/brcmnand.c | 61 |
1 files changed, 58 insertions, 3 deletions
diff --git a/drivers/mtd/nand/brcmnand/brcmnand.c b/drivers/mtd/nand/brcmnand/brcmnand.c index 42ebd73f821d..7419c5ce63f8 100644 --- a/drivers/mtd/nand/brcmnand/brcmnand.c +++ b/drivers/mtd/nand/brcmnand/brcmnand.c | |||
@@ -101,6 +101,9 @@ struct brcm_nand_dma_desc { | |||
101 | #define BRCMNAND_MIN_BLOCKSIZE (8 * 1024) | 101 | #define BRCMNAND_MIN_BLOCKSIZE (8 * 1024) |
102 | #define BRCMNAND_MIN_DEVSIZE (4ULL * 1024 * 1024) | 102 | #define BRCMNAND_MIN_DEVSIZE (4ULL * 1024 * 1024) |
103 | 103 | ||
104 | #define NAND_CTRL_RDY (INTFC_CTLR_READY | INTFC_FLASH_READY) | ||
105 | #define NAND_POLL_STATUS_TIMEOUT_MS 100 | ||
106 | |||
104 | /* Controller feature flags */ | 107 | /* Controller feature flags */ |
105 | enum { | 108 | enum { |
106 | BRCMNAND_HAS_1K_SECTORS = BIT(0), | 109 | BRCMNAND_HAS_1K_SECTORS = BIT(0), |
@@ -765,6 +768,31 @@ enum { | |||
765 | CS_SELECT_AUTO_DEVICE_ID_CFG = BIT(30), | 768 | CS_SELECT_AUTO_DEVICE_ID_CFG = BIT(30), |
766 | }; | 769 | }; |
767 | 770 | ||
771 | static int bcmnand_ctrl_poll_status(struct brcmnand_controller *ctrl, | ||
772 | u32 mask, u32 expected_val, | ||
773 | unsigned long timeout_ms) | ||
774 | { | ||
775 | unsigned long limit; | ||
776 | u32 val; | ||
777 | |||
778 | if (!timeout_ms) | ||
779 | timeout_ms = NAND_POLL_STATUS_TIMEOUT_MS; | ||
780 | |||
781 | limit = jiffies + msecs_to_jiffies(timeout_ms); | ||
782 | do { | ||
783 | val = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); | ||
784 | if ((val & mask) == expected_val) | ||
785 | return 0; | ||
786 | |||
787 | cpu_relax(); | ||
788 | } while (time_after(limit, jiffies)); | ||
789 | |||
790 | dev_warn(ctrl->dev, "timeout on status poll (expected %x got %x)\n", | ||
791 | expected_val, val & mask); | ||
792 | |||
793 | return -ETIMEDOUT; | ||
794 | } | ||
795 | |||
768 | static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en) | 796 | static inline void brcmnand_set_wp(struct brcmnand_controller *ctrl, bool en) |
769 | { | 797 | { |
770 | u32 val = en ? CS_SELECT_NAND_WP : 0; | 798 | u32 val = en ? CS_SELECT_NAND_WP : 0; |
@@ -1024,12 +1052,39 @@ static void brcmnand_wp(struct mtd_info *mtd, int wp) | |||
1024 | 1052 | ||
1025 | if ((ctrl->features & BRCMNAND_HAS_WP) && wp_on == 1) { | 1053 | if ((ctrl->features & BRCMNAND_HAS_WP) && wp_on == 1) { |
1026 | static int old_wp = -1; | 1054 | static int old_wp = -1; |
1055 | int ret; | ||
1027 | 1056 | ||
1028 | if (old_wp != wp) { | 1057 | if (old_wp != wp) { |
1029 | dev_dbg(ctrl->dev, "WP %s\n", wp ? "on" : "off"); | 1058 | dev_dbg(ctrl->dev, "WP %s\n", wp ? "on" : "off"); |
1030 | old_wp = wp; | 1059 | old_wp = wp; |
1031 | } | 1060 | } |
1061 | |||
1062 | /* | ||
1063 | * make sure ctrl/flash ready before and after | ||
1064 | * changing state of #WP pin | ||
1065 | */ | ||
1066 | ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY | | ||
1067 | NAND_STATUS_READY, | ||
1068 | NAND_CTRL_RDY | | ||
1069 | NAND_STATUS_READY, 0); | ||
1070 | if (ret) | ||
1071 | return; | ||
1072 | |||
1032 | brcmnand_set_wp(ctrl, wp); | 1073 | brcmnand_set_wp(ctrl, wp); |
1074 | chip->cmdfunc(mtd, NAND_CMD_STATUS, -1, -1); | ||
1075 | /* NAND_STATUS_WP 0x00 = protected, 0x80 = not protected */ | ||
1076 | ret = bcmnand_ctrl_poll_status(ctrl, | ||
1077 | NAND_CTRL_RDY | | ||
1078 | NAND_STATUS_READY | | ||
1079 | NAND_STATUS_WP, | ||
1080 | NAND_CTRL_RDY | | ||
1081 | NAND_STATUS_READY | | ||
1082 | (wp ? 0 : NAND_STATUS_WP), 0); | ||
1083 | |||
1084 | if (ret) | ||
1085 | dev_err_ratelimited(&host->pdev->dev, | ||
1086 | "nand #WP expected %s\n", | ||
1087 | wp ? "on" : "off"); | ||
1033 | } | 1088 | } |
1034 | } | 1089 | } |
1035 | 1090 | ||
@@ -1157,15 +1212,15 @@ static irqreturn_t brcmnand_dma_irq(int irq, void *data) | |||
1157 | static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd) | 1212 | static void brcmnand_send_cmd(struct brcmnand_host *host, int cmd) |
1158 | { | 1213 | { |
1159 | struct brcmnand_controller *ctrl = host->ctrl; | 1214 | struct brcmnand_controller *ctrl = host->ctrl; |
1160 | u32 intfc; | 1215 | int ret; |
1161 | 1216 | ||
1162 | dev_dbg(ctrl->dev, "send native cmd %d addr_lo 0x%x\n", cmd, | 1217 | dev_dbg(ctrl->dev, "send native cmd %d addr_lo 0x%x\n", cmd, |
1163 | brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS)); | 1218 | brcmnand_read_reg(ctrl, BRCMNAND_CMD_ADDRESS)); |
1164 | BUG_ON(ctrl->cmd_pending != 0); | 1219 | BUG_ON(ctrl->cmd_pending != 0); |
1165 | ctrl->cmd_pending = cmd; | 1220 | ctrl->cmd_pending = cmd; |
1166 | 1221 | ||
1167 | intfc = brcmnand_read_reg(ctrl, BRCMNAND_INTFC_STATUS); | 1222 | ret = bcmnand_ctrl_poll_status(ctrl, NAND_CTRL_RDY, NAND_CTRL_RDY, 0); |
1168 | WARN_ON(!(intfc & INTFC_CTLR_READY)); | 1223 | WARN_ON(ret); |
1169 | 1224 | ||
1170 | mb(); /* flush previous writes */ | 1225 | mb(); /* flush previous writes */ |
1171 | brcmnand_write_reg(ctrl, BRCMNAND_CMD_START, | 1226 | brcmnand_write_reg(ctrl, BRCMNAND_CMD_START, |