diff options
author | Ryan Mallon <ryan@bluewatersys.com> | 2009-09-18 15:51:40 -0400 |
---|---|---|
committer | David Woodhouse <David.Woodhouse@intel.com> | 2009-09-19 13:54:45 -0400 |
commit | ec77e21b91f0393a5201cfd4571a82ab7d64fd29 (patch) | |
tree | d50c9d344ceffc48e2e7ac4cb3c030021bd162bf /drivers/mtd/devices | |
parent | 91e0955b57377578f7555b5d0f2a21040691004b (diff) |
mtd: SST25L (non JEDEC) SPI Flash driver
Add support for the non JEDEC SST25L SPI Flash devices.
[dwmw2: Some cleanups]
Signed-off-by: Andre Renaud <andre@bluewatersys.com>
Signed-off-by: Ryan Mallon <ryan@bluewatersys.com>
Acked-by: Linus Walleij <linus.walleij@stericsson.com>
Cc: Anton Vorontsov <avorontsov@ru.mvista.com>
Cc: "H Hartley Sweeten" <hartleys@visionengravers.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: David Woodhouse <David.Woodhouse@intel.com>
Diffstat (limited to 'drivers/mtd/devices')
-rw-r--r-- | drivers/mtd/devices/Kconfig | 10 | ||||
-rw-r--r-- | drivers/mtd/devices/Makefile | 1 | ||||
-rw-r--r-- | drivers/mtd/devices/sst25l.c | 510 |
3 files changed, 521 insertions, 0 deletions
diff --git a/drivers/mtd/devices/Kconfig b/drivers/mtd/devices/Kconfig index 325fab92a62c..c222514bb70d 100644 --- a/drivers/mtd/devices/Kconfig +++ b/drivers/mtd/devices/Kconfig | |||
@@ -104,6 +104,16 @@ config M25PXX_USE_FAST_READ | |||
104 | help | 104 | help |
105 | This option enables FAST_READ access supported by ST M25Pxx. | 105 | This option enables FAST_READ access supported by ST M25Pxx. |
106 | 106 | ||
107 | config MTD_SST25L | ||
108 | tristate "Support SST25L (non JEDEC) SPI Flash chips" | ||
109 | depends on SPI_MASTER | ||
110 | help | ||
111 | This enables access to the non JEDEC SST25L SPI flash chips, used | ||
112 | for program and data storage. | ||
113 | |||
114 | Set up your spi devices with the right board-specific platform data, | ||
115 | if you want to specify device partitioning. | ||
116 | |||
107 | config MTD_SLRAM | 117 | config MTD_SLRAM |
108 | tristate "Uncached system RAM" | 118 | tristate "Uncached system RAM" |
109 | help | 119 | help |
diff --git a/drivers/mtd/devices/Makefile b/drivers/mtd/devices/Makefile index 0993d5cf3923..ab5c9b92ac82 100644 --- a/drivers/mtd/devices/Makefile +++ b/drivers/mtd/devices/Makefile | |||
@@ -16,3 +16,4 @@ obj-$(CONFIG_MTD_LART) += lart.o | |||
16 | obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o | 16 | obj-$(CONFIG_MTD_BLOCK2MTD) += block2mtd.o |
17 | obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o | 17 | obj-$(CONFIG_MTD_DATAFLASH) += mtd_dataflash.o |
18 | obj-$(CONFIG_MTD_M25P80) += m25p80.o | 18 | obj-$(CONFIG_MTD_M25P80) += m25p80.o |
19 | obj-$(CONFIG_MTD_SST25L) += sst25l.o | ||
diff --git a/drivers/mtd/devices/sst25l.c b/drivers/mtd/devices/sst25l.c new file mode 100644 index 000000000000..ac7d52b420cc --- /dev/null +++ b/drivers/mtd/devices/sst25l.c | |||
@@ -0,0 +1,510 @@ | |||
1 | /* | ||
2 | * sst25l.c | ||
3 | * | ||
4 | * Driver for SST25L SPI Flash chips | ||
5 | * | ||
6 | * Copyright © 2009 Bluewater Systems Ltd | ||
7 | * Author: Andre Renaud <andre@bluewatersys.com> | ||
8 | * Author: Ryan Mallon <ryan@bluewatersys.com> | ||
9 | * | ||
10 | * Based on m25p80.c | ||
11 | * | ||
12 | * This code is free software; you can redistribute it and/or modify | ||
13 | * it under the terms of the GNU General Public License version 2 as | ||
14 | * published by the Free Software Foundation. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/init.h> | ||
19 | #include <linux/module.h> | ||
20 | #include <linux/device.h> | ||
21 | #include <linux/mutex.h> | ||
22 | #include <linux/interrupt.h> | ||
23 | |||
24 | #include <linux/mtd/mtd.h> | ||
25 | #include <linux/mtd/partitions.h> | ||
26 | |||
27 | #include <linux/spi/spi.h> | ||
28 | #include <linux/spi/flash.h> | ||
29 | |||
30 | /* Erases can take up to 3 seconds! */ | ||
31 | #define MAX_READY_WAIT_JIFFIES msecs_to_jiffies(3000) | ||
32 | |||
33 | #define SST25L_CMD_WRSR 0x01 /* Write status register */ | ||
34 | #define SST25L_CMD_WRDI 0x04 /* Write disable */ | ||
35 | #define SST25L_CMD_RDSR 0x05 /* Read status register */ | ||
36 | #define SST25L_CMD_WREN 0x06 /* Write enable */ | ||
37 | #define SST25L_CMD_READ 0x03 /* High speed read */ | ||
38 | |||
39 | #define SST25L_CMD_EWSR 0x50 /* Enable write status register */ | ||
40 | #define SST25L_CMD_SECTOR_ERASE 0x20 /* Erase sector */ | ||
41 | #define SST25L_CMD_READ_ID 0x90 /* Read device ID */ | ||
42 | #define SST25L_CMD_AAI_PROGRAM 0xaf /* Auto address increment */ | ||
43 | |||
44 | #define SST25L_STATUS_BUSY (1 << 0) /* Chip is busy */ | ||
45 | #define SST25L_STATUS_WREN (1 << 1) /* Write enabled */ | ||
46 | #define SST25L_STATUS_BP0 (1 << 2) /* Block protection 0 */ | ||
47 | #define SST25L_STATUS_BP1 (1 << 3) /* Block protection 1 */ | ||
48 | |||
49 | struct sst25l_flash { | ||
50 | struct spi_device *spi; | ||
51 | struct mutex lock; | ||
52 | struct mtd_info mtd; | ||
53 | |||
54 | int partitioned; | ||
55 | }; | ||
56 | |||
57 | struct flash_info { | ||
58 | const char *name; | ||
59 | uint16_t device_id; | ||
60 | unsigned page_size; | ||
61 | unsigned nr_pages; | ||
62 | unsigned erase_size; | ||
63 | }; | ||
64 | |||
65 | #define to_sst25l_flash(x) container_of(x, struct sst25l_flash, mtd) | ||
66 | |||
67 | static struct flash_info __initdata sst25l_flash_info[] = { | ||
68 | {"sst25lf020a", 0xbf43, 256, 1024, 4096}, | ||
69 | {"sst25lf040a", 0xbf44, 256, 2048, 4096}, | ||
70 | }; | ||
71 | |||
72 | static int sst25l_status(struct sst25l_flash *flash, int *status) | ||
73 | { | ||
74 | unsigned char command, response; | ||
75 | int err; | ||
76 | |||
77 | command = SST25L_CMD_RDSR; | ||
78 | err = spi_write_then_read(flash->spi, &command, 1, &response, 1); | ||
79 | if (err < 0) | ||
80 | return err; | ||
81 | |||
82 | *status = response; | ||
83 | return 0; | ||
84 | } | ||
85 | |||
86 | static int sst25l_write_enable(struct sst25l_flash *flash, int enable) | ||
87 | { | ||
88 | unsigned char command[2]; | ||
89 | int status, err; | ||
90 | |||
91 | command[0] = enable ? SST25L_CMD_WREN : SST25L_CMD_WRDI; | ||
92 | err = spi_write(flash->spi, command, 1); | ||
93 | if (err) | ||
94 | return err; | ||
95 | |||
96 | command[0] = SST25L_CMD_EWSR; | ||
97 | err = spi_write(flash->spi, command, 1); | ||
98 | if (err) | ||
99 | return err; | ||
100 | |||
101 | command[0] = SST25L_CMD_WRSR; | ||
102 | command[1] = enable ? 0 : SST25L_STATUS_BP0 | SST25L_STATUS_BP1; | ||
103 | err = spi_write(flash->spi, command, 2); | ||
104 | if (err) | ||
105 | return err; | ||
106 | |||
107 | if (enable) { | ||
108 | err = sst25l_status(flash, &status); | ||
109 | if (err) | ||
110 | return err; | ||
111 | if (!(status & SST25L_STATUS_WREN)) | ||
112 | return -EROFS; | ||
113 | } | ||
114 | |||
115 | return 0; | ||
116 | } | ||
117 | |||
118 | static int sst25l_wait_till_ready(struct sst25l_flash *flash) | ||
119 | { | ||
120 | unsigned long deadline; | ||
121 | int status, err; | ||
122 | |||
123 | deadline = jiffies + MAX_READY_WAIT_JIFFIES; | ||
124 | do { | ||
125 | err = sst25l_status(flash, &status); | ||
126 | if (err) | ||
127 | return err; | ||
128 | if (!(status & SST25L_STATUS_BUSY)) | ||
129 | return 0; | ||
130 | |||
131 | cond_resched(); | ||
132 | } while (!time_after_eq(jiffies, deadline)); | ||
133 | |||
134 | return -ETIMEDOUT; | ||
135 | } | ||
136 | |||
137 | static int sst25l_erase_sector(struct sst25l_flash *flash, uint32_t offset) | ||
138 | { | ||
139 | unsigned char command[4]; | ||
140 | int err; | ||
141 | |||
142 | err = sst25l_write_enable(flash, 1); | ||
143 | if (err) | ||
144 | return err; | ||
145 | |||
146 | command[0] = SST25L_CMD_SECTOR_ERASE; | ||
147 | command[1] = offset >> 16; | ||
148 | command[2] = offset >> 8; | ||
149 | command[3] = offset; | ||
150 | err = spi_write(flash->spi, command, 4); | ||
151 | if (err) | ||
152 | return err; | ||
153 | |||
154 | err = sst25l_wait_till_ready(flash); | ||
155 | if (err) | ||
156 | return err; | ||
157 | |||
158 | return sst25l_write_enable(flash, 0); | ||
159 | } | ||
160 | |||
161 | static int sst25l_erase(struct mtd_info *mtd, struct erase_info *instr) | ||
162 | { | ||
163 | struct sst25l_flash *flash = to_sst25l_flash(mtd); | ||
164 | uint32_t addr, end; | ||
165 | int err; | ||
166 | |||
167 | /* Sanity checks */ | ||
168 | if (instr->addr + instr->len > flash->mtd.size) | ||
169 | return -EINVAL; | ||
170 | |||
171 | if ((uint32_t)instr->len % mtd->erasesize) | ||
172 | return -EINVAL; | ||
173 | |||
174 | if ((uint32_t)instr->addr % mtd->erasesize) | ||
175 | return -EINVAL; | ||
176 | |||
177 | addr = instr->addr; | ||
178 | end = addr + instr->len; | ||
179 | |||
180 | mutex_lock(&flash->lock); | ||
181 | |||
182 | err = sst25l_wait_till_ready(flash); | ||
183 | if (err) | ||
184 | return err; | ||
185 | |||
186 | while (addr < end) { | ||
187 | err = sst25l_erase_sector(flash, addr); | ||
188 | if (err) { | ||
189 | mutex_unlock(&flash->lock); | ||
190 | instr->state = MTD_ERASE_FAILED; | ||
191 | dev_err(&flash->spi->dev, "Erase failed\n"); | ||
192 | return err; | ||
193 | } | ||
194 | |||
195 | addr += mtd->erasesize; | ||
196 | } | ||
197 | |||
198 | mutex_unlock(&flash->lock); | ||
199 | |||
200 | instr->state = MTD_ERASE_DONE; | ||
201 | mtd_erase_callback(instr); | ||
202 | return 0; | ||
203 | } | ||
204 | |||
205 | static int sst25l_read(struct mtd_info *mtd, loff_t from, size_t len, | ||
206 | size_t *retlen, unsigned char *buf) | ||
207 | { | ||
208 | struct sst25l_flash *flash = to_sst25l_flash(mtd); | ||
209 | struct spi_transfer transfer[2]; | ||
210 | struct spi_message message; | ||
211 | unsigned char command[4]; | ||
212 | int ret; | ||
213 | |||
214 | /* Sanity checking */ | ||
215 | if (len == 0) | ||
216 | return 0; | ||
217 | |||
218 | if (from + len > flash->mtd.size) | ||
219 | return -EINVAL; | ||
220 | |||
221 | if (retlen) | ||
222 | *retlen = 0; | ||
223 | |||
224 | spi_message_init(&message); | ||
225 | memset(&transfer, 0, sizeof(transfer)); | ||
226 | |||
227 | command[0] = SST25L_CMD_READ; | ||
228 | command[1] = from >> 16; | ||
229 | command[2] = from >> 8; | ||
230 | command[3] = from; | ||
231 | |||
232 | transfer[0].tx_buf = command; | ||
233 | transfer[0].len = sizeof(command); | ||
234 | spi_message_add_tail(&transfer[0], &message); | ||
235 | |||
236 | transfer[1].rx_buf = buf; | ||
237 | transfer[1].len = len; | ||
238 | spi_message_add_tail(&transfer[1], &message); | ||
239 | |||
240 | mutex_lock(&flash->lock); | ||
241 | |||
242 | /* Wait for previous write/erase to complete */ | ||
243 | ret = sst25l_wait_till_ready(flash); | ||
244 | if (ret) { | ||
245 | mutex_unlock(&flash->lock); | ||
246 | return ret; | ||
247 | } | ||
248 | |||
249 | spi_sync(flash->spi, &message); | ||
250 | |||
251 | if (retlen && message.actual_length > sizeof(command)) | ||
252 | *retlen += message.actual_length - sizeof(command); | ||
253 | |||
254 | mutex_unlock(&flash->lock); | ||
255 | return 0; | ||
256 | } | ||
257 | |||
258 | static int sst25l_write(struct mtd_info *mtd, loff_t to, size_t len, | ||
259 | size_t *retlen, const unsigned char *buf) | ||
260 | { | ||
261 | struct sst25l_flash *flash = to_sst25l_flash(mtd); | ||
262 | int i, j, ret, bytes, copied = 0; | ||
263 | unsigned char command[5]; | ||
264 | |||
265 | /* Sanity checks */ | ||
266 | if (!len) | ||
267 | return 0; | ||
268 | |||
269 | if (to + len > flash->mtd.size) | ||
270 | return -EINVAL; | ||
271 | |||
272 | if ((uint32_t)to % mtd->writesize) | ||
273 | return -EINVAL; | ||
274 | |||
275 | mutex_lock(&flash->lock); | ||
276 | |||
277 | ret = sst25l_write_enable(flash, 1); | ||
278 | if (ret) | ||
279 | goto out; | ||
280 | |||
281 | for (i = 0; i < len; i += mtd->writesize) { | ||
282 | ret = sst25l_wait_till_ready(flash); | ||
283 | if (ret) | ||
284 | goto out; | ||
285 | |||
286 | /* Write the first byte of the page */ | ||
287 | command[0] = SST25L_CMD_AAI_PROGRAM; | ||
288 | command[1] = (to + i) >> 16; | ||
289 | command[2] = (to + i) >> 8; | ||
290 | command[3] = (to + i); | ||
291 | command[4] = buf[i]; | ||
292 | ret = spi_write(flash->spi, command, 5); | ||
293 | if (ret < 0) | ||
294 | goto out; | ||
295 | copied++; | ||
296 | |||
297 | /* | ||
298 | * Write the remaining bytes using auto address | ||
299 | * increment mode | ||
300 | */ | ||
301 | bytes = min_t(uint32_t, mtd->writesize, len - i); | ||
302 | for (j = 1; j < bytes; j++, copied++) { | ||
303 | ret = sst25l_wait_till_ready(flash); | ||
304 | if (ret) | ||
305 | goto out; | ||
306 | |||
307 | command[1] = buf[i + j]; | ||
308 | ret = spi_write(flash->spi, command, 2); | ||
309 | if (ret) | ||
310 | goto out; | ||
311 | } | ||
312 | } | ||
313 | |||
314 | out: | ||
315 | ret = sst25l_write_enable(flash, 0); | ||
316 | |||
317 | if (retlen) | ||
318 | *retlen = copied; | ||
319 | |||
320 | mutex_unlock(&flash->lock); | ||
321 | return ret; | ||
322 | } | ||
323 | |||
324 | static struct flash_info *__init sst25l_match_device(struct spi_device *spi) | ||
325 | { | ||
326 | struct flash_info *flash_info = NULL; | ||
327 | unsigned char command[4], response; | ||
328 | int i, err; | ||
329 | uint16_t id; | ||
330 | |||
331 | command[0] = SST25L_CMD_READ_ID; | ||
332 | command[1] = 0; | ||
333 | command[2] = 0; | ||
334 | command[3] = 0; | ||
335 | err = spi_write_then_read(spi, command, sizeof(command), &response, 1); | ||
336 | if (err < 0) { | ||
337 | dev_err(&spi->dev, "error reading device id msb\n"); | ||
338 | return NULL; | ||
339 | } | ||
340 | |||
341 | id = response << 8; | ||
342 | |||
343 | command[0] = SST25L_CMD_READ_ID; | ||
344 | command[1] = 0; | ||
345 | command[2] = 0; | ||
346 | command[3] = 1; | ||
347 | err = spi_write_then_read(spi, command, sizeof(command), &response, 1); | ||
348 | if (err < 0) { | ||
349 | dev_err(&spi->dev, "error reading device id lsb\n"); | ||
350 | return NULL; | ||
351 | } | ||
352 | |||
353 | id |= response; | ||
354 | |||
355 | for (i = 0; i < ARRAY_SIZE(sst25l_flash_info); i++) | ||
356 | if (sst25l_flash_info[i].device_id == id) | ||
357 | flash_info = &sst25l_flash_info[i]; | ||
358 | |||
359 | if (!flash_info) | ||
360 | dev_err(&spi->dev, "unknown id %.4x\n", id); | ||
361 | |||
362 | return flash_info; | ||
363 | } | ||
364 | |||
365 | static int __init sst25l_probe(struct spi_device *spi) | ||
366 | { | ||
367 | struct flash_info *flash_info; | ||
368 | struct sst25l_flash *flash; | ||
369 | struct flash_platform_data *data; | ||
370 | int ret, i; | ||
371 | |||
372 | flash_info = sst25l_match_device(spi); | ||
373 | if (!flash_info) | ||
374 | return -ENODEV; | ||
375 | |||
376 | flash = kzalloc(sizeof(struct sst25l_flash), GFP_KERNEL); | ||
377 | if (!flash) | ||
378 | return -ENOMEM; | ||
379 | |||
380 | flash->spi = spi; | ||
381 | mutex_init(&flash->lock); | ||
382 | dev_set_drvdata(&spi->dev, flash); | ||
383 | |||
384 | data = spi->dev.platform_data; | ||
385 | if (data && data->name) | ||
386 | flash->mtd.name = data->name; | ||
387 | else | ||
388 | flash->mtd.name = dev_name(&spi->dev); | ||
389 | |||
390 | flash->mtd.type = MTD_NORFLASH; | ||
391 | flash->mtd.flags = MTD_CAP_NORFLASH; | ||
392 | flash->mtd.erasesize = flash_info->erase_size; | ||
393 | flash->mtd.writesize = flash_info->page_size; | ||
394 | flash->mtd.size = flash_info->page_size * flash_info->nr_pages; | ||
395 | flash->mtd.erase = sst25l_erase; | ||
396 | flash->mtd.read = sst25l_read; | ||
397 | flash->mtd.write = sst25l_write; | ||
398 | |||
399 | dev_info(&spi->dev, "%s (%lld KiB)\n", flash_info->name, | ||
400 | (long long)flash->mtd.size >> 10); | ||
401 | |||
402 | DEBUG(MTD_DEBUG_LEVEL2, | ||
403 | "mtd .name = %s, .size = 0x%llx (%lldMiB) " | ||
404 | ".erasesize = 0x%.8x (%uKiB) .numeraseregions = %d\n", | ||
405 | flash->mtd.name, | ||
406 | (long long)flash->mtd.size, (long long)(flash->mtd.size >> 20), | ||
407 | flash->mtd.erasesize, flash->mtd.erasesize / 1024, | ||
408 | flash->mtd.numeraseregions); | ||
409 | |||
410 | if (flash->mtd.numeraseregions) | ||
411 | for (i = 0; i < flash->mtd.numeraseregions; i++) | ||
412 | DEBUG(MTD_DEBUG_LEVEL2, | ||
413 | "mtd.eraseregions[%d] = { .offset = 0x%llx, " | ||
414 | ".erasesize = 0x%.8x (%uKiB), " | ||
415 | ".numblocks = %d }\n", | ||
416 | i, (long long)flash->mtd.eraseregions[i].offset, | ||
417 | flash->mtd.eraseregions[i].erasesize, | ||
418 | flash->mtd.eraseregions[i].erasesize / 1024, | ||
419 | flash->mtd.eraseregions[i].numblocks); | ||
420 | |||
421 | if (mtd_has_partitions()) { | ||
422 | struct mtd_partition *parts = NULL; | ||
423 | int nr_parts = 0; | ||
424 | |||
425 | if (mtd_has_cmdlinepart()) { | ||
426 | static const char *part_probes[] = | ||
427 | {"cmdlinepart", NULL}; | ||
428 | |||
429 | nr_parts = parse_mtd_partitions(&flash->mtd, | ||
430 | part_probes, | ||
431 | &parts, 0); | ||
432 | } | ||
433 | |||
434 | if (nr_parts <= 0 && data && data->parts) { | ||
435 | parts = data->parts; | ||
436 | nr_parts = data->nr_parts; | ||
437 | } | ||
438 | |||
439 | if (nr_parts > 0) { | ||
440 | for (i = 0; i < nr_parts; i++) { | ||
441 | DEBUG(MTD_DEBUG_LEVEL2, "partitions[%d] = " | ||
442 | "{.name = %s, .offset = 0x%llx, " | ||
443 | ".size = 0x%llx (%lldKiB) }\n", | ||
444 | i, parts[i].name, | ||
445 | (long long)parts[i].offset, | ||
446 | (long long)parts[i].size, | ||
447 | (long long)(parts[i].size >> 10)); | ||
448 | } | ||
449 | |||
450 | flash->partitioned = 1; | ||
451 | return add_mtd_partitions(&flash->mtd, | ||
452 | parts, nr_parts); | ||
453 | } | ||
454 | |||
455 | } else if (data->nr_parts) { | ||
456 | dev_warn(&spi->dev, "ignoring %d default partitions on %s\n", | ||
457 | data->nr_parts, data->name); | ||
458 | } | ||
459 | |||
460 | ret = add_mtd_device(&flash->mtd); | ||
461 | if (ret == 1) { | ||
462 | kfree(flash); | ||
463 | dev_set_drvdata(&spi->dev, NULL); | ||
464 | return -ENODEV; | ||
465 | } | ||
466 | |||
467 | return 0; | ||
468 | } | ||
469 | |||
470 | static int __exit sst25l_remove(struct spi_device *spi) | ||
471 | { | ||
472 | struct sst25l_flash *flash = dev_get_drvdata(&spi->dev); | ||
473 | int ret; | ||
474 | |||
475 | if (mtd_has_partitions() && flash->partitioned) | ||
476 | ret = del_mtd_partitions(&flash->mtd); | ||
477 | else | ||
478 | ret = del_mtd_device(&flash->mtd); | ||
479 | if (ret == 0) | ||
480 | kfree(flash); | ||
481 | return ret; | ||
482 | } | ||
483 | |||
484 | static struct spi_driver sst25l_driver = { | ||
485 | .driver = { | ||
486 | .name = "sst25l", | ||
487 | .bus = &spi_bus_type, | ||
488 | .owner = THIS_MODULE, | ||
489 | }, | ||
490 | .probe = sst25l_probe, | ||
491 | .remove = __exit_p(sst25l_remove), | ||
492 | }; | ||
493 | |||
494 | static int __init sst25l_init(void) | ||
495 | { | ||
496 | return spi_register_driver(&sst25l_driver); | ||
497 | } | ||
498 | |||
499 | static void __exit sst25l_exit(void) | ||
500 | { | ||
501 | spi_unregister_driver(&sst25l_driver); | ||
502 | } | ||
503 | |||
504 | module_init(sst25l_init); | ||
505 | module_exit(sst25l_exit); | ||
506 | |||
507 | MODULE_DESCRIPTION("MTD SPI driver for SST25L Flash chips"); | ||
508 | MODULE_AUTHOR("Andre Renaud <andre@bluewatersys.com>, " | ||
509 | "Ryan Mallon <ryan@bluewatersys.com>"); | ||
510 | MODULE_LICENSE("GPL"); | ||