diff options
-rw-r--r-- | drivers/mtd/onenand/Kconfig | 7 | ||||
-rw-r--r-- | drivers/mtd/onenand/Makefile | 3 | ||||
-rw-r--r-- | drivers/mtd/onenand/onenand_sim.c | 464 |
3 files changed, 474 insertions, 0 deletions
diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig index 186ea9dc0942..e5ed081c42cb 100644 --- a/drivers/mtd/onenand/Kconfig +++ b/drivers/mtd/onenand/Kconfig | |||
@@ -35,4 +35,11 @@ config MTD_ONENAND_SYNC_READ | |||
35 | help | 35 | help |
36 | This enables support for Sync. Burst Read. | 36 | This enables support for Sync. Burst Read. |
37 | 37 | ||
38 | config MTD_ONENAND_SIM | ||
39 | tristate "Support for OneNAND flash simulator" | ||
40 | depends on MTD_ONENAND | ||
41 | help | ||
42 | The simulator may simulate verious OneNAND flash chips for the | ||
43 | MTD onenand layer. | ||
44 | |||
38 | endmenu | 45 | endmenu |
diff --git a/drivers/mtd/onenand/Makefile b/drivers/mtd/onenand/Makefile index 243c7592a111..932301a888ed 100644 --- a/drivers/mtd/onenand/Makefile +++ b/drivers/mtd/onenand/Makefile | |||
@@ -8,4 +8,7 @@ obj-$(CONFIG_MTD_ONENAND) += onenand.o | |||
8 | # Board specific. | 8 | # Board specific. |
9 | obj-$(CONFIG_MTD_ONENAND_OMAP) += omap-onenand.o | 9 | obj-$(CONFIG_MTD_ONENAND_OMAP) += omap-onenand.o |
10 | 10 | ||
11 | # Simulator | ||
12 | obj-$(CONFIG_MTD_ONENAND_SIM) += onenand_sim.o | ||
13 | |||
11 | onenand-objs = onenand_base.o onenand_bbt.o | 14 | onenand-objs = onenand_base.o onenand_bbt.o |
diff --git a/drivers/mtd/onenand/onenand_sim.c b/drivers/mtd/onenand/onenand_sim.c new file mode 100644 index 000000000000..cb376b206953 --- /dev/null +++ b/drivers/mtd/onenand/onenand_sim.c | |||
@@ -0,0 +1,464 @@ | |||
1 | /* | ||
2 | * linux/drivers/mtd/onenand/simulator.c | ||
3 | * | ||
4 | * The OneNAND simulator | ||
5 | * | ||
6 | * Copyright(c) 2005 Samsung Electronics | ||
7 | * Kyungmin Park <kyungmin.park@samsung.com> | ||
8 | */ | ||
9 | |||
10 | #include <linux/config.h> | ||
11 | #include <linux/module.h> | ||
12 | #include <linux/init.h> | ||
13 | #include <linux/vmalloc.h> | ||
14 | #include <linux/mtd/mtd.h> | ||
15 | #include <linux/mtd/onenand.h> | ||
16 | |||
17 | #include <asm/io.h> | ||
18 | #include <asm/sizes.h> | ||
19 | |||
20 | #ifndef CONFIG_ONENAND_SIM_MANUFACTURER | ||
21 | #define CONFIG_ONENAND_SIM_MANUFACTURER 0xec | ||
22 | #endif | ||
23 | #ifndef CONFIG_ONENAND_SIM_DEVICE_ID | ||
24 | #define CONFIG_ONENAND_SIM_DEVICE_ID 0x04 | ||
25 | #endif | ||
26 | #ifndef CONFIG_ONENAND_SIM_VERSION_ID | ||
27 | #define CONFIG_ONENAND_SIM_VERSION_ID 0x1e | ||
28 | #endif | ||
29 | |||
30 | static int manuf_id = CONFIG_ONENAND_SIM_MANUFACTURER; | ||
31 | static int device_id = CONFIG_ONENAND_SIM_DEVICE_ID; | ||
32 | static int version_id = CONFIG_ONENAND_SIM_VERSION_ID; | ||
33 | |||
34 | struct onenand_flash { | ||
35 | void __iomem *base; | ||
36 | void __iomem *data; | ||
37 | }; | ||
38 | |||
39 | #define ONENAND_CORE(flash) (flash->data) | ||
40 | |||
41 | #define ONENAND_MAIN_AREA(this, offset) \ | ||
42 | (this->base + ONENAND_DATARAM + offset) | ||
43 | |||
44 | #define ONENAND_SPARE_AREA(this, offset) \ | ||
45 | (this->base + ONENAND_SPARERAM + offset) | ||
46 | |||
47 | #define ONENAND_GET_WP_STATUS(this) \ | ||
48 | (readw(this->base + ONENAND_REG_WP_STATUS)) | ||
49 | |||
50 | #define ONENAND_SET_WP_STATUS(v, this) \ | ||
51 | (writew(v, this->base + ONENAND_REG_WP_STATUS)) | ||
52 | |||
53 | /* It has all 0xff chars */ | ||
54 | static unsigned char *ffchars; | ||
55 | |||
56 | /* | ||
57 | * OneNAND simulator mtd | ||
58 | */ | ||
59 | struct mtd_info *onenand_sim; | ||
60 | |||
61 | |||
62 | /** | ||
63 | * onenand_lock_handle - Handle Lock scheme | ||
64 | * @param this OneNAND device structure | ||
65 | * @param cmd The command to be sent | ||
66 | * | ||
67 | * Send lock command to OneNAND device. | ||
68 | * The lock scheme is depends on chip type. | ||
69 | */ | ||
70 | static void onenand_lock_handle(struct onenand_chip *this, int cmd) | ||
71 | { | ||
72 | int block_lock_scheme; | ||
73 | int status; | ||
74 | |||
75 | status = ONENAND_GET_WP_STATUS(this); | ||
76 | block_lock_scheme = !(this->options & ONENAND_CONT_LOCK); | ||
77 | |||
78 | switch (cmd) { | ||
79 | case ONENAND_CMD_UNLOCK: | ||
80 | if (block_lock_scheme) | ||
81 | ONENAND_SET_WP_STATUS(ONENAND_WP_US, this); | ||
82 | else | ||
83 | ONENAND_SET_WP_STATUS(status | ONENAND_WP_US, this); | ||
84 | break; | ||
85 | |||
86 | case ONENAND_CMD_LOCK: | ||
87 | if (block_lock_scheme) | ||
88 | ONENAND_SET_WP_STATUS(ONENAND_WP_LS, this); | ||
89 | else | ||
90 | ONENAND_SET_WP_STATUS(status | ONENAND_WP_LS, this); | ||
91 | break; | ||
92 | |||
93 | case ONENAND_CMD_LOCK_TIGHT: | ||
94 | if (block_lock_scheme) | ||
95 | ONENAND_SET_WP_STATUS(ONENAND_WP_LTS, this); | ||
96 | else | ||
97 | ONENAND_SET_WP_STATUS(status | ONENAND_WP_LTS, this); | ||
98 | break; | ||
99 | |||
100 | default: | ||
101 | break; | ||
102 | } | ||
103 | } | ||
104 | |||
105 | /** | ||
106 | * onenand_bootram_handle - Handle BootRAM area | ||
107 | * @param this OneNAND device structure | ||
108 | * @param cmd The command to be sent | ||
109 | * | ||
110 | * Emulate BootRAM area. It is possible to do basic operation using BootRAM. | ||
111 | */ | ||
112 | static void onenand_bootram_handle(struct onenand_chip *this, int cmd) | ||
113 | { | ||
114 | switch (cmd) { | ||
115 | case ONENAND_CMD_READID: | ||
116 | writew(manuf_id, this->base); | ||
117 | writew(device_id, this->base + 2); | ||
118 | writew(version_id, this->base + 4); | ||
119 | break; | ||
120 | |||
121 | default: | ||
122 | /* REVIST: Handle other commands */ | ||
123 | break; | ||
124 | } | ||
125 | } | ||
126 | |||
127 | /** | ||
128 | * onenand_update_interrupt - Set interrupt register | ||
129 | * @param this OneNAND device structure | ||
130 | * @param cmd The command to be sent | ||
131 | * | ||
132 | * Update interrupt register. The status is depends on command. | ||
133 | */ | ||
134 | static void onenand_update_interrupt(struct onenand_chip *this, int cmd) | ||
135 | { | ||
136 | int interrupt = ONENAND_INT_MASTER; | ||
137 | |||
138 | switch (cmd) { | ||
139 | case ONENAND_CMD_READ: | ||
140 | case ONENAND_CMD_READOOB: | ||
141 | interrupt |= ONENAND_INT_READ; | ||
142 | break; | ||
143 | |||
144 | case ONENAND_CMD_PROG: | ||
145 | case ONENAND_CMD_PROGOOB: | ||
146 | interrupt |= ONENAND_INT_WRITE; | ||
147 | break; | ||
148 | |||
149 | case ONENAND_CMD_ERASE: | ||
150 | interrupt |= ONENAND_INT_ERASE; | ||
151 | break; | ||
152 | |||
153 | case ONENAND_CMD_RESET: | ||
154 | interrupt |= ONENAND_INT_RESET; | ||
155 | break; | ||
156 | |||
157 | default: | ||
158 | break; | ||
159 | } | ||
160 | |||
161 | writew(interrupt, this->base + ONENAND_REG_INTERRUPT); | ||
162 | } | ||
163 | |||
164 | /** | ||
165 | * onenand_check_overwrite - Check over-write if happend | ||
166 | * @param dest The destination pointer | ||
167 | * @param src The source pointer | ||
168 | * @param count The length to be check | ||
169 | * @return 0 on same, otherwise 1 | ||
170 | * | ||
171 | * Compare the source with destination | ||
172 | */ | ||
173 | static int onenand_check_overwrite(void *dest, void *src, size_t count) | ||
174 | { | ||
175 | unsigned int *s = (unsigned int *) src; | ||
176 | unsigned int *d = (unsigned int *) dest; | ||
177 | int i; | ||
178 | count >>= 2; | ||
179 | |||
180 | for (i = 0; i < count; i++) | ||
181 | if ((*s++ ^ *d++) != 0) | ||
182 | return 1; | ||
183 | |||
184 | return 0; | ||
185 | } | ||
186 | |||
187 | /** | ||
188 | * onenand_data_handle - Handle OneNAND Core and DataRAM | ||
189 | * @param this OneNAND device structure | ||
190 | * @param cmd The command to be sent | ||
191 | * @param dataram Which dataram used | ||
192 | * @param offset The offset to OneNAND Core | ||
193 | * | ||
194 | * Copy data from OneNAND Core to DataRAM (read) | ||
195 | * Copy data from DataRAM to OneNAND Core (write) | ||
196 | * Erase the OneNAND Core (erase) | ||
197 | */ | ||
198 | static void onenand_data_handle(struct onenand_chip *this, int cmd, | ||
199 | int dataram, unsigned int offset) | ||
200 | { | ||
201 | struct onenand_flash *flash = this->priv; | ||
202 | int main_offset, spare_offset; | ||
203 | void __iomem *src; | ||
204 | void __iomem *dest; | ||
205 | |||
206 | if (dataram) { | ||
207 | main_offset = onenand_sim->oobblock; | ||
208 | spare_offset = onenand_sim->oobsize; | ||
209 | } else { | ||
210 | main_offset = 0; | ||
211 | spare_offset = 0; | ||
212 | } | ||
213 | |||
214 | switch (cmd) { | ||
215 | case ONENAND_CMD_READ: | ||
216 | src = ONENAND_CORE(flash) + offset; | ||
217 | dest = ONENAND_MAIN_AREA(this, main_offset); | ||
218 | memcpy(dest, src, onenand_sim->oobblock); | ||
219 | /* Fall through */ | ||
220 | |||
221 | case ONENAND_CMD_READOOB: | ||
222 | src = ONENAND_CORE(flash) + this->chipsize + (offset >> 5); | ||
223 | dest = ONENAND_SPARE_AREA(this, spare_offset); | ||
224 | memcpy(dest, src, onenand_sim->oobsize); | ||
225 | break; | ||
226 | |||
227 | case ONENAND_CMD_PROG: | ||
228 | src = ONENAND_MAIN_AREA(this, main_offset); | ||
229 | dest = ONENAND_CORE(flash) + offset; | ||
230 | if (memcmp(dest, ffchars, onenand_sim->oobblock) && | ||
231 | onenand_check_overwrite(dest, src, onenand_sim->oobblock)) | ||
232 | printk(KERN_ERR "over-write happend at 0x%08x\n", offset); | ||
233 | memcpy(dest, src, onenand_sim->oobblock); | ||
234 | /* Fall through */ | ||
235 | |||
236 | case ONENAND_CMD_PROGOOB: | ||
237 | src = ONENAND_SPARE_AREA(this, spare_offset); | ||
238 | /* Check all data is 0xff chars */ | ||
239 | if (!memcmp(src, ffchars, onenand_sim->oobsize)) | ||
240 | break; | ||
241 | |||
242 | dest = ONENAND_CORE(flash) + this->chipsize + (offset >> 5); | ||
243 | if (memcmp(dest, ffchars, onenand_sim->oobsize) && | ||
244 | onenand_check_overwrite(dest, src, onenand_sim->oobsize)) | ||
245 | printk(KERN_ERR "OOB: over-write happend at 0x%08x\n", offset); | ||
246 | memcpy(dest, src, onenand_sim->oobsize); | ||
247 | break; | ||
248 | |||
249 | case ONENAND_CMD_ERASE: | ||
250 | memset(ONENAND_CORE(flash) + offset, 0xff, (1 << this->erase_shift)); | ||
251 | break; | ||
252 | |||
253 | default: | ||
254 | break; | ||
255 | } | ||
256 | } | ||
257 | |||
258 | /** | ||
259 | * onenand_command_handle - Handle command | ||
260 | * @param this OneNAND device structure | ||
261 | * @param cmd The command to be sent | ||
262 | * | ||
263 | * Emulate OneNAND command. | ||
264 | */ | ||
265 | static void onenand_command_handle(struct onenand_chip *this, int cmd) | ||
266 | { | ||
267 | unsigned long offset = 0; | ||
268 | int block = -1, page = -1, bufferram = -1; | ||
269 | int dataram = 0; | ||
270 | |||
271 | switch (cmd) { | ||
272 | case ONENAND_CMD_UNLOCK: | ||
273 | case ONENAND_CMD_LOCK: | ||
274 | case ONENAND_CMD_LOCK_TIGHT: | ||
275 | onenand_lock_handle(this, cmd); | ||
276 | break; | ||
277 | |||
278 | case ONENAND_CMD_BUFFERRAM: | ||
279 | /* Do nothing */ | ||
280 | return; | ||
281 | |||
282 | default: | ||
283 | block = (int) readw(this->base + ONENAND_REG_START_ADDRESS1); | ||
284 | if (block & (1 << ONENAND_DDP_SHIFT)) { | ||
285 | block &= ~(1 << ONENAND_DDP_SHIFT); | ||
286 | /* The half of chip block */ | ||
287 | block += this->chipsize >> (this->erase_shift + 1); | ||
288 | } | ||
289 | if (cmd == ONENAND_CMD_ERASE) | ||
290 | break; | ||
291 | |||
292 | page = (int) readw(this->base + ONENAND_REG_START_ADDRESS8); | ||
293 | page = (page >> ONENAND_FPA_SHIFT); | ||
294 | bufferram = (int) readw(this->base + ONENAND_REG_START_BUFFER); | ||
295 | bufferram >>= ONENAND_BSA_SHIFT; | ||
296 | bufferram &= ONENAND_BSA_DATARAM1; | ||
297 | dataram = (bufferram == ONENAND_BSA_DATARAM1) ? 1 : 0; | ||
298 | break; | ||
299 | } | ||
300 | |||
301 | if (block != -1) | ||
302 | offset += block << this->erase_shift; | ||
303 | |||
304 | if (page != -1) | ||
305 | offset += page << this->page_shift; | ||
306 | |||
307 | onenand_data_handle(this, cmd, dataram, offset); | ||
308 | |||
309 | onenand_update_interrupt(this, cmd); | ||
310 | } | ||
311 | |||
312 | /** | ||
313 | * onenand_writew - [OneNAND Interface] Emulate write operation | ||
314 | * @param value value to write | ||
315 | * @param addr address to write | ||
316 | * | ||
317 | * Write OneNAND reigser with value | ||
318 | */ | ||
319 | static void onenand_writew(unsigned short value, void __iomem *addr) | ||
320 | { | ||
321 | struct onenand_chip *this = onenand_sim->priv; | ||
322 | |||
323 | /* BootRAM handling */ | ||
324 | if (addr < this->base + ONENAND_DATARAM) { | ||
325 | onenand_bootram_handle(this, value); | ||
326 | return; | ||
327 | } | ||
328 | /* Command handling */ | ||
329 | if (addr == this->base + ONENAND_REG_COMMAND) | ||
330 | onenand_command_handle(this, value); | ||
331 | |||
332 | writew(value, addr); | ||
333 | } | ||
334 | |||
335 | /** | ||
336 | * flash_init - Initialize OneNAND simulator | ||
337 | * @param flash OneNAND simulaotr data strucutres | ||
338 | * | ||
339 | * Initialize OneNAND simulator. | ||
340 | */ | ||
341 | static int __init flash_init(struct onenand_flash *flash) | ||
342 | { | ||
343 | int density, size; | ||
344 | int buffer_size; | ||
345 | |||
346 | flash->base = kmalloc(SZ_128K, GFP_KERNEL); | ||
347 | if (!flash->base) { | ||
348 | printk(KERN_ERR "Unalbe to allocate base address.\n"); | ||
349 | return -ENOMEM; | ||
350 | } | ||
351 | |||
352 | memset(flash->base, 0, SZ_128K); | ||
353 | |||
354 | density = device_id >> ONENAND_DEVICE_DENSITY_SHIFT; | ||
355 | size = ((16 << 20) << density); | ||
356 | |||
357 | ONENAND_CORE(flash) = vmalloc(size + (size >> 5)); | ||
358 | if (!ONENAND_CORE(flash)) { | ||
359 | printk(KERN_ERR "Unalbe to allocate nand core address.\n"); | ||
360 | kfree(flash->base); | ||
361 | return -ENOMEM; | ||
362 | } | ||
363 | |||
364 | memset(ONENAND_CORE(flash), 0xff, size + (size >> 5)); | ||
365 | |||
366 | /* Setup registers */ | ||
367 | writew(manuf_id, flash->base + ONENAND_REG_MANUFACTURER_ID); | ||
368 | writew(device_id, flash->base + ONENAND_REG_DEVICE_ID); | ||
369 | writew(version_id, flash->base + ONENAND_REG_VERSION_ID); | ||
370 | |||
371 | if (density < 2) | ||
372 | buffer_size = 0x0400; /* 1KB page */ | ||
373 | else | ||
374 | buffer_size = 0x0800; /* 2KB page */ | ||
375 | writew(buffer_size, flash->base + ONENAND_REG_DATA_BUFFER_SIZE); | ||
376 | |||
377 | return 0; | ||
378 | } | ||
379 | |||
380 | /** | ||
381 | * flash_exit - Clean up OneNAND simulator | ||
382 | * @param flash OneNAND simulaotr data strucutres | ||
383 | * | ||
384 | * Clean up OneNAND simulator. | ||
385 | */ | ||
386 | static void flash_exit(struct onenand_flash *flash) | ||
387 | { | ||
388 | vfree(ONENAND_CORE(flash)); | ||
389 | kfree(flash->base); | ||
390 | kfree(flash); | ||
391 | } | ||
392 | |||
393 | static int __init onenand_sim_init(void) | ||
394 | { | ||
395 | struct onenand_chip *this; | ||
396 | struct onenand_flash *flash; | ||
397 | int len; | ||
398 | |||
399 | /* Allocate all 0xff chars pointer */ | ||
400 | ffchars = kmalloc(MAX_ONENAND_PAGESIZE, GFP_KERNEL); | ||
401 | if (!ffchars) { | ||
402 | printk(KERN_ERR "Unable to allocate ff chars.\n"); | ||
403 | return -ENOMEM; | ||
404 | } | ||
405 | memset(ffchars, 0xff, MAX_ONENAND_PAGESIZE); | ||
406 | |||
407 | len = sizeof(struct mtd_info) + sizeof(struct onenand_chip) + sizeof (struct onenand_flash); | ||
408 | |||
409 | /* Allocate OneNAND simulator mtd pointer */ | ||
410 | onenand_sim = kmalloc(len, GFP_KERNEL); | ||
411 | if (!onenand_sim) { | ||
412 | printk(KERN_ERR "Unable to allocate core structures.\n"); | ||
413 | kfree(ffchars); | ||
414 | return -ENOMEM; | ||
415 | } | ||
416 | |||
417 | memset(onenand_sim, 0, len); | ||
418 | |||
419 | this = (struct onenand_chip *) (onenand_sim + 1); | ||
420 | /* Override write_word function */ | ||
421 | this->write_word = onenand_writew; | ||
422 | |||
423 | flash = (struct onenand_flash *) (this + 1); | ||
424 | |||
425 | if (flash_init(flash)) { | ||
426 | printk(KERN_ERR "Unable to allocat flash.\n"); | ||
427 | kfree(ffchars); | ||
428 | kfree(onenand_sim); | ||
429 | return -ENOMEM; | ||
430 | } | ||
431 | |||
432 | this->base = flash->base; | ||
433 | this->priv = flash; | ||
434 | onenand_sim->priv = this; | ||
435 | |||
436 | if (onenand_scan(onenand_sim, 1)) { | ||
437 | kfree(ffchars); | ||
438 | kfree(onenand_sim); | ||
439 | flash_exit(flash); | ||
440 | return -ENXIO; | ||
441 | } | ||
442 | |||
443 | add_mtd_device(onenand_sim); | ||
444 | |||
445 | return 0; | ||
446 | } | ||
447 | |||
448 | static void __exit onenand_sim_exit(void) | ||
449 | { | ||
450 | struct onenand_chip *this = onenand_sim->priv; | ||
451 | struct onenand_flash *flash = this->priv; | ||
452 | |||
453 | kfree(ffchars); | ||
454 | onenand_release(onenand_sim); | ||
455 | flash_exit(flash); | ||
456 | kfree(onenand_sim); | ||
457 | } | ||
458 | |||
459 | module_init(onenand_sim_init); | ||
460 | module_exit(onenand_sim_exit); | ||
461 | |||
462 | MODULE_LICENSE("GPL"); | ||
463 | MODULE_AUTHOR("Kyungmin Park <kyungmin.park@samsung.com>"); | ||
464 | MODULE_DESCRIPTION("The OneNAND flash simulator"); | ||