b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | From ea92cbb50a78404e29de2cc3999a240615ffb1c8 Mon Sep 17 00:00:00 2001 |
| 2 | From: Chuanhong Guo <gch981213@gmail.com> |
| 3 | Date: Mon, 6 Apr 2020 17:58:48 +0800 |
| 4 | Subject: [PATCH] mtd: spi-nor: rework broken-flash-reset support |
| 5 | |
| 6 | Instead of resetting flash to 3B address on remove hook, this |
| 7 | implementation only enters 4B mode when needed, which prevents |
| 8 | more unexpected reboot stuck. This implementation makes it only |
| 9 | break when a kernel panic happens during flash operation on 16M+ |
| 10 | areas. |
| 11 | *OpenWrt only*: silent broken-flash-reset warning. We are not dealing |
| 12 | with vendors and it's unpleasant for users to se that unnecessary |
| 13 | and long WARN_ON print. |
| 14 | |
| 15 | Signed-off-by: Chuanhong Guo <gch981213@gmail.com> |
| 16 | --- |
| 17 | drivers/mtd/spi-nor/spi-nor.c | 52 +++++++++++++++++++++++++++++++++-- |
| 18 | 1 file changed, 49 insertions(+), 3 deletions(-) |
| 19 | |
| 20 | --- a/drivers/mtd/spi-nor/spi-nor.c |
| 21 | +++ b/drivers/mtd/spi-nor/spi-nor.c |
| 22 | @@ -616,6 +616,22 @@ static void spi_nor_set_4byte_opcodes(st |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | +static int spi_nor_check_set_addr_width(struct spi_nor *nor, loff_t addr) |
| 27 | +{ |
| 28 | + u8 addr_width; |
| 29 | + |
| 30 | + if ((nor->flags & (SNOR_F_4B_OPCODES | SNOR_F_BROKEN_RESET)) != |
| 31 | + SNOR_F_BROKEN_RESET) |
| 32 | + return 0; |
| 33 | + |
| 34 | + addr_width = addr & 0xff000000 ? 4 : 3; |
| 35 | + if (nor->addr_width == addr_width) |
| 36 | + return 0; |
| 37 | + |
| 38 | + nor->addr_width = addr_width; |
| 39 | + return nor->params.set_4byte(nor, addr_width == 4); |
| 40 | +} |
| 41 | + |
| 42 | static int macronix_set_4byte(struct spi_nor *nor, bool enable) |
| 43 | { |
| 44 | if (nor->spimem) { |
| 45 | @@ -1263,6 +1279,10 @@ static int spi_nor_erase(struct mtd_info |
| 46 | if (ret) |
| 47 | return ret; |
| 48 | |
| 49 | + ret = spi_nor_check_set_addr_width(nor, instr->addr + instr->len); |
| 50 | + if (ret < 0) |
| 51 | + return ret; |
| 52 | + |
| 53 | /* whole-chip erase? */ |
| 54 | if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) { |
| 55 | unsigned long timeout; |
| 56 | @@ -1319,6 +1339,7 @@ static int spi_nor_erase(struct mtd_info |
| 57 | write_disable(nor); |
| 58 | |
| 59 | erase_err: |
| 60 | + spi_nor_check_set_addr_width(nor, 0); |
| 61 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE); |
| 62 | |
| 63 | return ret; |
| 64 | @@ -1625,7 +1646,9 @@ static int spi_nor_lock(struct mtd_info |
| 65 | if (ret) |
| 66 | return ret; |
| 67 | |
| 68 | + spi_nor_check_set_addr_width(nor, ofs + len); |
| 69 | ret = nor->params.locking_ops->lock(nor, ofs, len); |
| 70 | + spi_nor_check_set_addr_width(nor, 0); |
| 71 | |
| 72 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK); |
| 73 | return ret; |
| 74 | @@ -1640,7 +1663,9 @@ static int spi_nor_unlock(struct mtd_inf |
| 75 | if (ret) |
| 76 | return ret; |
| 77 | |
| 78 | + spi_nor_check_set_addr_width(nor, ofs + len); |
| 79 | ret = nor->params.locking_ops->unlock(nor, ofs, len); |
| 80 | + spi_nor_check_set_addr_width(nor, 0); |
| 81 | |
| 82 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); |
| 83 | return ret; |
| 84 | @@ -1655,7 +1680,9 @@ static int spi_nor_is_locked(struct mtd_ |
| 85 | if (ret) |
| 86 | return ret; |
| 87 | |
| 88 | + spi_nor_check_set_addr_width(nor, ofs + len); |
| 89 | ret = nor->params.locking_ops->is_locked(nor, ofs, len); |
| 90 | + spi_nor_check_set_addr_width(nor, 0); |
| 91 | |
| 92 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK); |
| 93 | return ret; |
| 94 | @@ -2562,6 +2589,10 @@ static int spi_nor_read(struct mtd_info |
| 95 | if (ret) |
| 96 | return ret; |
| 97 | |
| 98 | + ret = spi_nor_check_set_addr_width(nor, from + len); |
| 99 | + if (ret < 0) |
| 100 | + return ret; |
| 101 | + |
| 102 | while (len) { |
| 103 | loff_t addr = from; |
| 104 | |
| 105 | @@ -2585,6 +2616,7 @@ static int spi_nor_read(struct mtd_info |
| 106 | ret = 0; |
| 107 | |
| 108 | read_err: |
| 109 | + spi_nor_check_set_addr_width(nor, 0); |
| 110 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ); |
| 111 | return ret; |
| 112 | } |
| 113 | @@ -2602,6 +2634,10 @@ static int sst_write(struct mtd_info *mt |
| 114 | if (ret) |
| 115 | return ret; |
| 116 | |
| 117 | + ret = spi_nor_check_set_addr_width(nor, to + len); |
| 118 | + if (ret < 0) |
| 119 | + return ret; |
| 120 | + |
| 121 | write_enable(nor); |
| 122 | |
| 123 | nor->sst_write_second = false; |
| 124 | @@ -2664,6 +2700,7 @@ static int sst_write(struct mtd_info *mt |
| 125 | } |
| 126 | sst_write_err: |
| 127 | *retlen += actual; |
| 128 | + spi_nor_check_set_addr_width(nor, 0); |
| 129 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); |
| 130 | return ret; |
| 131 | } |
| 132 | @@ -2686,6 +2723,10 @@ static int spi_nor_write(struct mtd_info |
| 133 | if (ret) |
| 134 | return ret; |
| 135 | |
| 136 | + ret = spi_nor_check_set_addr_width(nor, to + len); |
| 137 | + if (ret < 0) |
| 138 | + return ret; |
| 139 | + |
| 140 | for (i = 0; i < len; ) { |
| 141 | ssize_t written; |
| 142 | loff_t addr = to + i; |
| 143 | @@ -2725,6 +2766,7 @@ static int spi_nor_write(struct mtd_info |
| 144 | } |
| 145 | |
| 146 | write_err: |
| 147 | + spi_nor_check_set_addr_width(nor, 0); |
| 148 | spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE); |
| 149 | return ret; |
| 150 | } |
| 151 | @@ -4729,9 +4771,13 @@ static int spi_nor_init(struct spi_nor * |
| 152 | * reboots (e.g., crashes). Warn the user (or hopefully, system |
| 153 | * designer) that this is bad. |
| 154 | */ |
| 155 | - WARN_ONCE(nor->flags & SNOR_F_BROKEN_RESET, |
| 156 | - "enabling reset hack; may not recover from unexpected reboots\n"); |
| 157 | - nor->params.set_4byte(nor, true); |
| 158 | + if (nor->flags & SNOR_F_BROKEN_RESET) { |
| 159 | + dev_warn(nor->dev, |
| 160 | + "enabling reset hack; may not recover from unexpected reboots\n"); |
| 161 | + nor->addr_width = 3; |
| 162 | + } else { |
| 163 | + nor->params.set_4byte(nor, true); |
| 164 | + } |
| 165 | } |
| 166 | |
| 167 | return 0; |