Skip to content

Commit 22d982e

Browse files
committed
ramips: 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>
1 parent f90f94d commit 22d982e

File tree

2 files changed

+224
-9
lines changed

2 files changed

+224
-9
lines changed
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
mtd: spi-nor: add support for switching between 3-byte and 4-byte addressing on w25q256 flash
2+
3+
On some devices the flash chip needs to be in 3-byte addressing mode during
4+
reboot, otherwise the boot loader will fail to start.
5+
This mode however does not allow regular reads/writes onto the upper 16M
6+
half. W25Q256 has separate read commands for reading from >16M, however
7+
it does not have any separate write commands.
8+
This patch changes the code to leave the chip in 3-byte mode most of the
9+
time and only switch during erase/write cycles that go to >16M
10+
addresses.
11+
12+
Signed-off-by: Felix Fietkau <nbd@nbd.name>
13+
---
14+
--- a/drivers/mtd/spi-nor/spi-nor.c
15+
+++ b/drivers/mtd/spi-nor/spi-nor.c
16+
@@ -85,6 +85,10 @@ struct flash_info {
17+
* Use dedicated 4byte address op codes
18+
* to support memory size above 128Mib.
19+
*/
20+
+#define SPI_NOR_4B_READ_OP BIT(12) /*
21+
+ * Like SPI_NOR_4B_OPCODES, but for read
22+
+ * op code only.
23+
+ */
24+
};
25+
26+
#define JEDEC_MFR(info) ((info)->id[0])
27+
@@ -250,6 +254,15 @@ static inline u8 spi_nor_convert_3to4_er
28+
ARRAY_SIZE(spi_nor_3to4_erase));
29+
}
30+
31+
+static void spi_nor_set_4byte_read(struct spi_nor *nor,
32+
+ const struct flash_info *info)
33+
+{
34+
+ nor->addr_width = 3;
35+
+ nor->ext_addr = 0;
36+
+ nor->read_opcode = spi_nor_convert_3to4_read(nor->read_opcode);
37+
+ nor->flags |= SNOR_F_4B_EXT_ADDR;
38+
+}
39+
+
40+
static void spi_nor_set_4byte_opcodes(struct spi_nor *nor,
41+
const struct flash_info *info)
42+
{
43+
@@ -467,6 +480,36 @@ static int spi_nor_erase_sector(struct s
44+
return nor->write_reg(nor, nor->erase_opcode, buf, nor->addr_width);
45+
}
46+
47+
+static int spi_nor_check_ext_addr(struct spi_nor *nor, u32 addr)
48+
+{
49+
+ bool ext_addr;
50+
+ int ret;
51+
+ u8 cmd;
52+
+
53+
+ if (!(nor->flags & SNOR_F_4B_EXT_ADDR))
54+
+ return 0;
55+
+
56+
+ ext_addr = !!(addr & 0xff000000);
57+
+ if (nor->ext_addr == ext_addr)
58+
+ return 0;
59+
+
60+
+ cmd = ext_addr ? SPINOR_OP_EN4B : SPINOR_OP_EX4B;
61+
+ write_enable(nor);
62+
+ ret = nor->write_reg(nor, cmd, NULL, 0);
63+
+ if (ret)
64+
+ return ret;
65+
+
66+
+ cmd = 0;
67+
+ ret = nor->write_reg(nor, SPINOR_OP_WREAR, &cmd, 1);
68+
+ if (ret)
69+
+ return ret;
70+
+
71+
+ nor->addr_width = 3 + ext_addr;
72+
+ nor->ext_addr = ext_addr;
73+
+ write_disable(nor);
74+
+ return 0;
75+
+}
76+
+
77+
/*
78+
* Erase an address range on the nor chip. The address range may extend
79+
* one or more erase sectors. Return an error is there is a problem erasing.
80+
@@ -492,6 +535,10 @@ static int spi_nor_erase(struct mtd_info
81+
if (ret)
82+
return ret;
83+
84+
+ ret = spi_nor_check_ext_addr(nor, addr + len);
85+
+ if (ret)
86+
+ return ret;
87+
+
88+
/* whole-chip erase? */
89+
if (len == mtd->size && !(nor->flags & SNOR_F_NO_OP_CHIP_ERASE)) {
90+
unsigned long timeout;
91+
@@ -542,6 +589,7 @@ static int spi_nor_erase(struct mtd_info
92+
write_disable(nor);
93+
94+
erase_err:
95+
+ spi_nor_check_ext_addr(nor, 0);
96+
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_ERASE);
97+
98+
instr->state = ret ? MTD_ERASE_FAILED : MTD_ERASE_DONE;
99+
@@ -834,7 +882,9 @@ static int spi_nor_lock(struct mtd_info
100+
if (ret)
101+
return ret;
102+
103+
+ spi_nor_check_ext_addr(nor, ofs + len);
104+
ret = nor->flash_lock(nor, ofs, len);
105+
+ spi_nor_check_ext_addr(nor, 0);
106+
107+
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_UNLOCK);
108+
return ret;
109+
@@ -849,7 +899,9 @@ static int spi_nor_unlock(struct mtd_inf
110+
if (ret)
111+
return ret;
112+
113+
+ spi_nor_check_ext_addr(nor, ofs + len);
114+
ret = nor->flash_unlock(nor, ofs, len);
115+
+ spi_nor_check_ext_addr(nor, 0);
116+
117+
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_LOCK);
118+
return ret;
119+
@@ -1151,7 +1203,7 @@ static const struct flash_info spi_nor_i
120+
{ "w25q80", INFO(0xef5014, 0, 64 * 1024, 16, SECT_4K) },
121+
{ "w25q80bl", INFO(0xef4014, 0, 64 * 1024, 16, SECT_4K) },
122+
{ "w25q128", INFO(0xef4018, 0, 64 * 1024, 256, SECT_4K) },
123+
- { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K) },
124+
+ { "w25q256", INFO(0xef4019, 0, 64 * 1024, 512, SECT_4K | SPI_NOR_4B_READ_OP) },
125+
126+
/* Catalyst / On Semiconductor -- non-JEDEC */
127+
{ "cat25c11", CAT25_INFO( 16, 8, 16, 1, SPI_NOR_NO_ERASE | SPI_NOR_NO_FR) },
128+
@@ -1205,6 +1257,9 @@ static int spi_nor_read(struct mtd_info
129+
if (ret)
130+
return ret;
131+
132+
+ if (nor->flags & SNOR_F_4B_EXT_ADDR)
133+
+ nor->addr_width = 4;
134+
+
135+
while (len) {
136+
loff_t addr = from;
137+
138+
@@ -1229,6 +1284,18 @@ static int spi_nor_read(struct mtd_info
139+
ret = 0;
140+
141+
read_err:
142+
+ if (nor->flags & SNOR_F_4B_EXT_ADDR) {
143+
+ u8 val = 0;
144+
+
145+
+ if ((from + len) & 0xff000000) {
146+
+ write_enable(nor);
147+
+ nor->write_reg(nor, SPINOR_OP_WREAR, &val, 1);
148+
+ write_disable(nor);
149+
+ }
150+
+
151+
+ nor->addr_width = 3;
152+
+ }
153+
+
154+
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_READ);
155+
return ret;
156+
}
157+
@@ -1330,6 +1397,10 @@ static int spi_nor_write(struct mtd_info
158+
if (ret)
159+
return ret;
160+
161+
+ ret = spi_nor_check_ext_addr(nor, to + len);
162+
+ if (ret < 0)
163+
+ return ret;
164+
+
165+
for (i = 0; i < len; ) {
166+
ssize_t written;
167+
loff_t addr = to + i;
168+
@@ -1377,6 +1448,7 @@ static int spi_nor_write(struct mtd_info
169+
}
170+
171+
write_err:
172+
+ spi_nor_check_ext_addr(nor, 0);
173+
spi_nor_unlock_and_unprep(nor, SPI_NOR_OPS_WRITE);
174+
return ret;
175+
}
176+
@@ -1715,8 +1787,10 @@ int spi_nor_scan(struct spi_nor *nor, co
177+
else if (mtd->size > 0x1000000) {
178+
/* enable 4-byte addressing if the device exceeds 16MiB */
179+
nor->addr_width = 4;
180+
- if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
181+
- info->flags & SPI_NOR_4B_OPCODES)
182+
+ if (info->flags & SPI_NOR_4B_READ_OP)
183+
+ spi_nor_set_4byte_read(nor, info);
184+
+ else if (JEDEC_MFR(info) == SNOR_MFR_SPANSION ||
185+
+ info->flags & SPI_NOR_4B_OPCODES)
186+
spi_nor_set_4byte_opcodes(nor, info);
187+
else
188+
set_4byte(nor, info, 1);
189+
--- a/include/linux/mtd/spi-nor.h
190+
+++ b/include/linux/mtd/spi-nor.h
191+
@@ -90,6 +90,7 @@
192+
/* Used for Macronix and Winbond flashes. */
193+
#define SPINOR_OP_EN4B 0xb7 /* Enter 4-byte mode */
194+
#define SPINOR_OP_EX4B 0xe9 /* Exit 4-byte mode */
195+
+#define SPINOR_OP_WREAR 0xc5 /* Write extended address register */
196+
197+
/* Used for Spansion flashes only. */
198+
#define SPINOR_OP_BRWR 0x17 /* Bank register write */
199+
@@ -141,6 +142,7 @@ enum spi_nor_option_flags {
200+
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
201+
SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
202+
SNOR_F_READY_XSR_RDY = BIT(4),
203+
+ SNOR_F_4B_EXT_ADDR = BIT(5),
204+
};
205+
206+
/**
207+
@@ -188,6 +190,7 @@ struct spi_nor {
208+
enum read_mode flash_read;
209+
bool sst_write_second;
210+
u32 flags;
211+
+ u8 ext_addr;
212+
u8 cmd_buf[SPI_NOR_MAX_CMD_SIZE];
213+
214+
int (*prepare)(struct spi_nor *nor, enum spi_nor_ops ops);

target/linux/ramips/patches-4.9/0054-mtd-add-chunked-read-io-to-m25p80.patch

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
--- a/drivers/mtd/spi-nor/spi-nor.c
22
+++ b/drivers/mtd/spi-nor/spi-nor.c
3-
@@ -1381,6 +1381,66 @@ write_err:
3+
@@ -1453,6 +1453,67 @@ write_err:
44
return ret;
55
}
66

77
+static int spi_nor_chunked_write(struct mtd_info *mtd, loff_t _to, size_t _len,
88
+ size_t *_retlen, const u_char *_buf)
99
+{
1010
+ struct spi_nor *nor = mtd_to_spi_nor(mtd);
11+
+ u32 addr_width = nor->addr_width + !!(nor->flags & SNOR_F_4B_EXT_ADDR);
1112
+ int chunk_size;
1213
+ int retlen = 0;
1314
+ int ret;
@@ -16,8 +17,8 @@
1617
+ if (!chunk_size)
1718
+ chunk_size = _len;
1819
+
19-
+ if (nor->addr_width > 3)
20-
+ chunk_size -= nor->addr_width - 3;
20+
+ if (addr_width > 3)
21+
+ chunk_size -= addr_width - 3;
2122
+
2223
+ while (retlen < _len) {
2324
+ size_t len = min_t(int, chunk_size, _len - retlen);
@@ -67,7 +68,7 @@
6768
static int macronix_quad_enable(struct spi_nor *nor)
6869
{
6970
int ret, val;
70-
@@ -1630,10 +1690,12 @@ int spi_nor_scan(struct spi_nor *nor, co
71+
@@ -1702,10 +1763,12 @@ int spi_nor_scan(struct spi_nor *nor, co
7172
}
7273

7374
/* sst nor chips use AAI word program */
@@ -82,7 +83,7 @@
8283

8384
if (info->flags & USE_FSR)
8485
nor->flags |= SNOR_F_USE_FSR;
85-
@@ -1663,11 +1725,20 @@ int spi_nor_scan(struct spi_nor *nor, co
86+
@@ -1735,11 +1798,20 @@ int spi_nor_scan(struct spi_nor *nor, co
8687
mtd->writebufsize = nor->page_size;
8788

8889
if (np) {
@@ -105,15 +106,15 @@
105106
nor->flash_read = SPI_NOR_FAST;
106107
--- a/include/linux/mtd/spi-nor.h
107108
+++ b/include/linux/mtd/spi-nor.h
108-
@@ -141,6 +141,7 @@ enum spi_nor_option_flags {
109-
SNOR_F_NO_OP_CHIP_ERASE = BIT(2),
109+
@@ -143,6 +143,7 @@ enum spi_nor_option_flags {
110110
SNOR_F_S3AN_ADDR_DEFAULT = BIT(3),
111111
SNOR_F_READY_XSR_RDY = BIT(4),
112-
+ SNOR_F_SST = BIT(5),
112+
SNOR_F_4B_EXT_ADDR = BIT(5),
113+
+ SNOR_F_SST = BIT(6),
113114
};
114115

115116
/**
116-
@@ -180,6 +181,7 @@ struct spi_nor {
117+
@@ -182,6 +183,7 @@ struct spi_nor {
117118
struct mutex lock;
118119
struct device *dev;
119120
u32 page_size;

0 commit comments

Comments
 (0)