1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
|
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;
@@ -1164,7 +1216,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) },
@@ -1220,6 +1272,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;
@@ -1244,6 +1299,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;
}
@@ -1345,6 +1412,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;
@@ -1392,6 +1463,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;
}
@@ -2808,8 +2880,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);
|