Commit | Line | Data |
---|---|---|
f4150724 BH |
1 | /**************************************************************************** |
2 | * Driver for Solarflare Solarstorm network controllers and boards | |
3 | * Copyright 2005-2006 Fen Systems Ltd. | |
4 | * Copyright 2006-2008 Solarflare Communications Inc. | |
5 | * | |
6 | * This program is free software; you can redistribute it and/or modify it | |
7 | * under the terms of the GNU General Public License version 2 as published | |
8 | * by the Free Software Foundation, incorporated herein by reference. | |
9 | */ | |
10 | ||
11 | #include <linux/module.h> | |
12 | #include <linux/mtd/mtd.h> | |
13 | #include <linux/delay.h> | |
14 | ||
15 | #define EFX_DRIVER_NAME "sfc_mtd" | |
16 | #include "net_driver.h" | |
17 | #include "spi.h" | |
ff2ef902 | 18 | #include "efx.h" |
f4150724 BH |
19 | |
20 | #define EFX_SPI_VERIFY_BUF_LEN 16 | |
21 | ||
22 | struct efx_mtd { | |
23 | const struct efx_spi_device *spi; | |
24 | struct mtd_info mtd; | |
25 | char name[IFNAMSIZ + 20]; | |
26 | }; | |
27 | ||
28 | /* SPI utilities */ | |
29 | ||
30 | static int efx_spi_slow_wait(struct efx_mtd *efx_mtd, bool uninterruptible) | |
31 | { | |
32 | const struct efx_spi_device *spi = efx_mtd->spi; | |
33 | struct efx_nic *efx = spi->efx; | |
34 | u8 status; | |
35 | int rc, i; | |
36 | ||
37 | /* Wait up to 4s for flash/EEPROM to finish a slow operation. */ | |
38 | for (i = 0; i < 40; i++) { | |
39 | __set_current_state(uninterruptible ? | |
40 | TASK_UNINTERRUPTIBLE : TASK_INTERRUPTIBLE); | |
41 | schedule_timeout(HZ / 10); | |
42 | rc = falcon_spi_cmd(spi, SPI_RDSR, -1, NULL, | |
43 | &status, sizeof(status)); | |
44 | if (rc) | |
45 | return rc; | |
46 | if (!(status & SPI_STATUS_NRDY)) | |
47 | return 0; | |
48 | if (signal_pending(current)) | |
49 | return -EINTR; | |
50 | } | |
51 | EFX_ERR(efx, "timed out waiting for %s\n", efx_mtd->name); | |
52 | return -ETIMEDOUT; | |
53 | } | |
54 | ||
55 | static int efx_spi_unlock(const struct efx_spi_device *spi) | |
56 | { | |
57 | const u8 unlock_mask = (SPI_STATUS_BP2 | SPI_STATUS_BP1 | | |
58 | SPI_STATUS_BP0); | |
59 | u8 status; | |
60 | int rc; | |
61 | ||
62 | rc = falcon_spi_cmd(spi, SPI_RDSR, -1, NULL, &status, sizeof(status)); | |
63 | if (rc) | |
64 | return rc; | |
65 | ||
66 | if (!(status & unlock_mask)) | |
67 | return 0; /* already unlocked */ | |
68 | ||
69 | rc = falcon_spi_cmd(spi, SPI_WREN, -1, NULL, NULL, 0); | |
70 | if (rc) | |
71 | return rc; | |
72 | rc = falcon_spi_cmd(spi, SPI_SST_EWSR, -1, NULL, NULL, 0); | |
73 | if (rc) | |
74 | return rc; | |
75 | ||
76 | status &= ~unlock_mask; | |
77 | rc = falcon_spi_cmd(spi, SPI_WRSR, -1, &status, NULL, sizeof(status)); | |
78 | if (rc) | |
79 | return rc; | |
be4ea89c | 80 | rc = falcon_spi_wait_write(spi); |
f4150724 BH |
81 | if (rc) |
82 | return rc; | |
83 | ||
84 | return 0; | |
85 | } | |
86 | ||
87 | static int efx_spi_erase(struct efx_mtd *efx_mtd, loff_t start, size_t len) | |
88 | { | |
89 | const struct efx_spi_device *spi = efx_mtd->spi; | |
90 | unsigned pos, block_len; | |
91 | u8 empty[EFX_SPI_VERIFY_BUF_LEN]; | |
92 | u8 buffer[EFX_SPI_VERIFY_BUF_LEN]; | |
93 | int rc; | |
94 | ||
95 | if (len != spi->erase_size) | |
96 | return -EINVAL; | |
97 | ||
98 | if (spi->erase_command == 0) | |
99 | return -EOPNOTSUPP; | |
100 | ||
101 | rc = efx_spi_unlock(spi); | |
102 | if (rc) | |
103 | return rc; | |
104 | rc = falcon_spi_cmd(spi, SPI_WREN, -1, NULL, NULL, 0); | |
105 | if (rc) | |
106 | return rc; | |
107 | rc = falcon_spi_cmd(spi, spi->erase_command, start, NULL, NULL, 0); | |
108 | if (rc) | |
109 | return rc; | |
110 | rc = efx_spi_slow_wait(efx_mtd, false); | |
111 | ||
112 | /* Verify the entire region has been wiped */ | |
113 | memset(empty, 0xff, sizeof(empty)); | |
114 | for (pos = 0; pos < len; pos += block_len) { | |
115 | block_len = min(len - pos, sizeof(buffer)); | |
116 | rc = falcon_spi_read(spi, start + pos, block_len, NULL, buffer); | |
117 | if (rc) | |
118 | return rc; | |
119 | if (memcmp(empty, buffer, block_len)) | |
120 | return -EIO; | |
121 | ||
122 | /* Avoid locking up the system */ | |
123 | cond_resched(); | |
124 | if (signal_pending(current)) | |
125 | return -EINTR; | |
126 | } | |
127 | ||
128 | return rc; | |
129 | } | |
130 | ||
131 | /* MTD interface */ | |
132 | ||
133 | static int efx_mtd_read(struct mtd_info *mtd, loff_t start, size_t len, | |
134 | size_t *retlen, u8 *buffer) | |
135 | { | |
136 | struct efx_mtd *efx_mtd = mtd->priv; | |
137 | const struct efx_spi_device *spi = efx_mtd->spi; | |
138 | struct efx_nic *efx = spi->efx; | |
139 | int rc; | |
140 | ||
141 | rc = mutex_lock_interruptible(&efx->spi_lock); | |
142 | if (rc) | |
143 | return rc; | |
144 | rc = falcon_spi_read(spi, FALCON_FLASH_BOOTCODE_START + start, | |
145 | len, retlen, buffer); | |
146 | mutex_unlock(&efx->spi_lock); | |
147 | return rc; | |
148 | } | |
149 | ||
150 | static int efx_mtd_erase(struct mtd_info *mtd, struct erase_info *erase) | |
151 | { | |
152 | struct efx_mtd *efx_mtd = mtd->priv; | |
153 | struct efx_nic *efx = efx_mtd->spi->efx; | |
154 | int rc; | |
155 | ||
156 | rc = mutex_lock_interruptible(&efx->spi_lock); | |
157 | if (rc) | |
158 | return rc; | |
159 | rc = efx_spi_erase(efx_mtd, FALCON_FLASH_BOOTCODE_START + erase->addr, | |
160 | erase->len); | |
161 | mutex_unlock(&efx->spi_lock); | |
162 | ||
163 | if (rc == 0) { | |
164 | erase->state = MTD_ERASE_DONE; | |
165 | } else { | |
166 | erase->state = MTD_ERASE_FAILED; | |
167 | erase->fail_addr = 0xffffffff; | |
168 | } | |
169 | mtd_erase_callback(erase); | |
170 | return rc; | |
171 | } | |
172 | ||
173 | static int efx_mtd_write(struct mtd_info *mtd, loff_t start, | |
174 | size_t len, size_t *retlen, const u8 *buffer) | |
175 | { | |
176 | struct efx_mtd *efx_mtd = mtd->priv; | |
177 | const struct efx_spi_device *spi = efx_mtd->spi; | |
178 | struct efx_nic *efx = spi->efx; | |
179 | int rc; | |
180 | ||
181 | rc = mutex_lock_interruptible(&efx->spi_lock); | |
182 | if (rc) | |
183 | return rc; | |
184 | rc = falcon_spi_write(spi, FALCON_FLASH_BOOTCODE_START + start, | |
185 | len, retlen, buffer); | |
186 | mutex_unlock(&efx->spi_lock); | |
187 | return rc; | |
188 | } | |
189 | ||
190 | static void efx_mtd_sync(struct mtd_info *mtd) | |
191 | { | |
192 | struct efx_mtd *efx_mtd = mtd->priv; | |
193 | struct efx_nic *efx = efx_mtd->spi->efx; | |
194 | int rc; | |
195 | ||
196 | mutex_lock(&efx->spi_lock); | |
197 | rc = efx_spi_slow_wait(efx_mtd, true); | |
198 | mutex_unlock(&efx->spi_lock); | |
199 | ||
200 | if (rc) | |
201 | EFX_ERR(efx, "%s sync failed (%d)\n", efx_mtd->name, rc); | |
202 | return; | |
203 | } | |
204 | ||
205 | void efx_mtd_remove(struct efx_nic *efx) | |
206 | { | |
207 | if (efx->spi_flash && efx->spi_flash->mtd) { | |
208 | struct efx_mtd *efx_mtd = efx->spi_flash->mtd; | |
209 | int rc; | |
210 | ||
211 | for (;;) { | |
212 | rc = del_mtd_device(&efx_mtd->mtd); | |
213 | if (rc != -EBUSY) | |
214 | break; | |
215 | ssleep(1); | |
216 | } | |
217 | WARN_ON(rc); | |
218 | kfree(efx_mtd); | |
219 | } | |
220 | } | |
221 | ||
222 | void efx_mtd_rename(struct efx_nic *efx) | |
223 | { | |
224 | if (efx->spi_flash && efx->spi_flash->mtd) { | |
225 | struct efx_mtd *efx_mtd = efx->spi_flash->mtd; | |
226 | snprintf(efx_mtd->name, sizeof(efx_mtd->name), | |
227 | "%s sfc_flash_bootrom", efx->name); | |
228 | } | |
229 | } | |
230 | ||
231 | int efx_mtd_probe(struct efx_nic *efx) | |
232 | { | |
233 | struct efx_spi_device *spi = efx->spi_flash; | |
234 | struct efx_mtd *efx_mtd; | |
235 | ||
236 | if (!spi || spi->size <= FALCON_FLASH_BOOTCODE_START) | |
237 | return -ENODEV; | |
238 | ||
239 | efx_mtd = kzalloc(sizeof(*efx_mtd), GFP_KERNEL); | |
240 | if (!efx_mtd) | |
241 | return -ENOMEM; | |
242 | ||
243 | efx_mtd->spi = spi; | |
244 | spi->mtd = efx_mtd; | |
245 | ||
246 | efx_mtd->mtd.type = MTD_NORFLASH; | |
247 | efx_mtd->mtd.flags = MTD_CAP_NORFLASH; | |
248 | efx_mtd->mtd.size = spi->size - FALCON_FLASH_BOOTCODE_START; | |
249 | efx_mtd->mtd.erasesize = spi->erase_size; | |
250 | efx_mtd->mtd.writesize = 1; | |
251 | efx_mtd_rename(efx); | |
252 | ||
253 | efx_mtd->mtd.owner = THIS_MODULE; | |
254 | efx_mtd->mtd.priv = efx_mtd; | |
255 | efx_mtd->mtd.name = efx_mtd->name; | |
256 | efx_mtd->mtd.erase = efx_mtd_erase; | |
257 | efx_mtd->mtd.read = efx_mtd_read; | |
258 | efx_mtd->mtd.write = efx_mtd_write; | |
259 | efx_mtd->mtd.sync = efx_mtd_sync; | |
260 | ||
261 | if (add_mtd_device(&efx_mtd->mtd)) { | |
262 | kfree(efx_mtd); | |
263 | spi->mtd = NULL; | |
264 | /* add_mtd_device() returns 1 if the MTD table is full */ | |
265 | return -ENOMEM; | |
266 | } | |
267 | ||
268 | return 0; | |
269 | } |