| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | /*
 | 
					
						
							|  |  |  |  * OneNAND flash memories emulation. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * Copyright (C) 2008 Nokia Corporation | 
					
						
							|  |  |  |  * Written by Andrzej Zaborowski <andrew@openedhand.com> | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is free software; you can redistribute it and/or | 
					
						
							|  |  |  |  * modify it under the terms of the GNU General Public License as | 
					
						
							|  |  |  |  * published by the Free Software Foundation; either version 2 or | 
					
						
							|  |  |  |  * (at your option) version 3 of the License. | 
					
						
							|  |  |  |  * | 
					
						
							|  |  |  |  * This program is distributed in the hope that it will be useful, | 
					
						
							|  |  |  |  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
					
						
							|  |  |  |  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
					
						
							|  |  |  |  * GNU General Public License for more details. | 
					
						
							|  |  |  |  * | 
					
						
							| 
									
										
										
										
											2009-01-04 22:05:52 +00:00
										 |  |  |  * You should have received a copy of the GNU General Public License along | 
					
						
							| 
									
										
										
										
											2009-07-16 20:47:01 +00:00
										 |  |  |  * with this program; if not, see <http://www.gnu.org/licenses/>.
 | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #include "qemu-common.h"
 | 
					
						
							| 
									
										
										
										
											2011-01-21 13:12:11 +03:00
										 |  |  | #include "hw.h"
 | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | #include "flash.h"
 | 
					
						
							|  |  |  | #include "irq.h"
 | 
					
						
							| 
									
										
										
										
											2010-06-02 18:48:27 +02:00
										 |  |  | #include "blockdev.h"
 | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  | #include "memory.h"
 | 
					
						
							|  |  |  | #include "exec-memory.h"
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | #include "sysbus.h"
 | 
					
						
							| 
									
										
										
										
											2011-10-20 14:53:35 +02:00
										 |  |  | #include "qemu-error.h"
 | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | /* 11 for 2kB-page OneNAND ("2nd generation") and 10 for 1kB-page chips */ | 
					
						
							|  |  |  | #define PAGE_SHIFT	11
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* Fixed */ | 
					
						
							|  |  |  | #define BLOCK_SHIFT	(PAGE_SHIFT + 6)
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | typedef struct { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     SysBusDevice busdev; | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:26 +01:00
										 |  |  |     struct { | 
					
						
							|  |  |  |         uint16_t man; | 
					
						
							|  |  |  |         uint16_t dev; | 
					
						
							|  |  |  |         uint16_t ver; | 
					
						
							|  |  |  |     } id; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     int shift; | 
					
						
							| 
									
										
										
										
											2009-10-01 16:12:16 -05:00
										 |  |  |     target_phys_addr_t base; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     qemu_irq intr; | 
					
						
							|  |  |  |     qemu_irq rdy; | 
					
						
							|  |  |  |     BlockDriverState *bdrv; | 
					
						
							|  |  |  |     BlockDriverState *bdrv_cur; | 
					
						
							|  |  |  |     uint8_t *image; | 
					
						
							|  |  |  |     uint8_t *otp; | 
					
						
							|  |  |  |     uint8_t *current; | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |     MemoryRegion ram; | 
					
						
							|  |  |  |     MemoryRegion mapped_ram; | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     uint8_t current_direction; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     uint8_t *boot[2]; | 
					
						
							|  |  |  |     uint8_t *data[2][2]; | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |     MemoryRegion iomem; | 
					
						
							|  |  |  |     MemoryRegion container; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     int cycle; | 
					
						
							|  |  |  |     int otpmode; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     uint16_t addr[8]; | 
					
						
							|  |  |  |     uint16_t unladdr[8]; | 
					
						
							|  |  |  |     int bufaddr; | 
					
						
							|  |  |  |     int count; | 
					
						
							|  |  |  |     uint16_t command; | 
					
						
							|  |  |  |     uint16_t config[2]; | 
					
						
							|  |  |  |     uint16_t status; | 
					
						
							|  |  |  |     uint16_t intstatus; | 
					
						
							|  |  |  |     uint16_t wpstatus; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  |     ECCState ecc; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     int density_mask; | 
					
						
							|  |  |  |     int secs; | 
					
						
							|  |  |  |     int secs_cur; | 
					
						
							|  |  |  |     int blocks; | 
					
						
							|  |  |  |     uint8_t *blockwp; | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | } OneNANDState; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  |     ONEN_BUF_BLOCK = 0, | 
					
						
							|  |  |  |     ONEN_BUF_BLOCK2 = 1, | 
					
						
							|  |  |  |     ONEN_BUF_DEST_BLOCK = 2, | 
					
						
							|  |  |  |     ONEN_BUF_DEST_PAGE = 3, | 
					
						
							|  |  |  |     ONEN_BUF_PAGE = 7, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  |     ONEN_ERR_CMD = 1 << 10, | 
					
						
							|  |  |  |     ONEN_ERR_ERASE = 1 << 11, | 
					
						
							|  |  |  |     ONEN_ERR_PROG = 1 << 12, | 
					
						
							|  |  |  |     ONEN_ERR_LOAD = 1 << 13, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  |     ONEN_INT_RESET = 1 << 4, | 
					
						
							|  |  |  |     ONEN_INT_ERASE = 1 << 5, | 
					
						
							|  |  |  |     ONEN_INT_PROG = 1 << 6, | 
					
						
							|  |  |  |     ONEN_INT_LOAD = 1 << 7, | 
					
						
							|  |  |  |     ONEN_INT = 1 << 15, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | enum { | 
					
						
							|  |  |  |     ONEN_LOCK_LOCKTIGHTEN = 1 << 0, | 
					
						
							|  |  |  |     ONEN_LOCK_LOCKED = 1 << 1, | 
					
						
							|  |  |  |     ONEN_LOCK_UNLOCKED = 1 << 2, | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  | static void onenand_mem_setup(OneNANDState *s) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     /* XXX: We should use IO_MEM_ROMD but we broke it earlier...
 | 
					
						
							|  |  |  |      * Both 0x0000 ... 0x01ff and 0x8000 ... 0x800f can be used to | 
					
						
							|  |  |  |      * write boot commands.  Also take note of the BWPS bit.  */ | 
					
						
							|  |  |  |     memory_region_init(&s->container, "onenand", 0x10000 << s->shift); | 
					
						
							|  |  |  |     memory_region_add_subregion(&s->container, 0, &s->iomem); | 
					
						
							|  |  |  |     memory_region_init_alias(&s->mapped_ram, "onenand-mapped-ram", | 
					
						
							|  |  |  |                              &s->ram, 0x0200 << s->shift, | 
					
						
							|  |  |  |                              0xbe00 << s->shift); | 
					
						
							|  |  |  |     memory_region_add_subregion_overlap(&s->container, | 
					
						
							|  |  |  |                                         0x0200 << s->shift, | 
					
						
							|  |  |  |                                         &s->mapped_ram, | 
					
						
							|  |  |  |                                         1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static void onenand_intr_update(OneNANDState *s) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     qemu_set_irq(s->intr, ((s->intstatus >> 15) ^ (~s->config[0] >> 6)) & 1); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static void onenand_pre_save(void *opaque) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     OneNANDState *s = opaque; | 
					
						
							|  |  |  |     if (s->current == s->otp) { | 
					
						
							|  |  |  |         s->current_direction = 1; | 
					
						
							|  |  |  |     } else if (s->current == s->image) { | 
					
						
							|  |  |  |         s->current_direction = 2; | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |         s->current_direction = 0; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static int onenand_post_load(void *opaque, int version_id) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     OneNANDState *s = opaque; | 
					
						
							|  |  |  |     switch (s->current_direction) { | 
					
						
							|  |  |  |     case 0: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 1: | 
					
						
							|  |  |  |         s->current = s->otp; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 2: | 
					
						
							|  |  |  |         s->current = s->image; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         return -1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     onenand_intr_update(s); | 
					
						
							|  |  |  |     return 0; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static const VMStateDescription vmstate_onenand = { | 
					
						
							|  |  |  |     .name = "onenand", | 
					
						
							|  |  |  |     .version_id = 1, | 
					
						
							|  |  |  |     .minimum_version_id = 1, | 
					
						
							|  |  |  |     .minimum_version_id_old = 1, | 
					
						
							|  |  |  |     .pre_save = onenand_pre_save, | 
					
						
							|  |  |  |     .post_load = onenand_post_load, | 
					
						
							|  |  |  |     .fields = (VMStateField[]) { | 
					
						
							|  |  |  |         VMSTATE_UINT8(current_direction, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_INT32(cycle, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_INT32(otpmode, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16_ARRAY(addr, OneNANDState, 8), | 
					
						
							|  |  |  |         VMSTATE_UINT16_ARRAY(unladdr, OneNANDState, 8), | 
					
						
							|  |  |  |         VMSTATE_INT32(bufaddr, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_INT32(count, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16(command, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16_ARRAY(config, OneNANDState, 2), | 
					
						
							|  |  |  |         VMSTATE_UINT16(status, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16(intstatus, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16(wpstatus, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_INT32(secs_cur, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_PARTIAL_VBUFFER(blockwp, OneNANDState, blocks), | 
					
						
							|  |  |  |         VMSTATE_UINT8(ecc.cp, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_UINT16_ARRAY(ecc.lp, OneNANDState, 2), | 
					
						
							|  |  |  |         VMSTATE_UINT16(ecc.count, OneNANDState), | 
					
						
							|  |  |  |         VMSTATE_BUFFER_UNSAFE(otp, OneNANDState, 0, ((64 + 2) << PAGE_SHIFT)), | 
					
						
							|  |  |  |         VMSTATE_END_OF_LIST() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | /* Hot reset (Reset OneNAND command) or warm reset (RP pin low) */ | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static void onenand_reset(OneNANDState *s, int cold) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     memset(&s->addr, 0, sizeof(s->addr)); | 
					
						
							|  |  |  |     s->command = 0; | 
					
						
							|  |  |  |     s->count = 1; | 
					
						
							|  |  |  |     s->bufaddr = 0; | 
					
						
							|  |  |  |     s->config[0] = 0x40c0; | 
					
						
							|  |  |  |     s->config[1] = 0x0000; | 
					
						
							|  |  |  |     onenand_intr_update(s); | 
					
						
							|  |  |  |     qemu_irq_raise(s->rdy); | 
					
						
							|  |  |  |     s->status = 0x0000; | 
					
						
							|  |  |  |     s->intstatus = cold ? 0x8080 : 0x8010; | 
					
						
							|  |  |  |     s->unladdr[0] = 0; | 
					
						
							|  |  |  |     s->unladdr[1] = 0; | 
					
						
							|  |  |  |     s->wpstatus = 0x0002; | 
					
						
							|  |  |  |     s->cycle = 0; | 
					
						
							|  |  |  |     s->otpmode = 0; | 
					
						
							|  |  |  |     s->bdrv_cur = s->bdrv; | 
					
						
							|  |  |  |     s->current = s->image; | 
					
						
							|  |  |  |     s->secs_cur = s->secs; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (cold) { | 
					
						
							|  |  |  |         /* Lock the whole flash */ | 
					
						
							|  |  |  |         memset(s->blockwp, ONEN_LOCK_LOCKED, s->blocks); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |         if (s->bdrv_cur && bdrv_read(s->bdrv_cur, 0, s->boot[0], 8) < 0) { | 
					
						
							|  |  |  |             hw_error("%s: Loading the BootRAM failed.\n", __func__); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static void onenand_system_reset(DeviceState *dev) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     onenand_reset(FROM_SYSBUS(OneNANDState, sysbus_from_qdev(dev)), 1); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static inline int onenand_load_main(OneNANDState *s, int sec, int secn, | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                 void *dest) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     if (s->bdrv_cur) | 
					
						
							|  |  |  |         return bdrv_read(s->bdrv_cur, sec, dest, secn) < 0; | 
					
						
							|  |  |  |     else if (sec + secn > s->secs_cur) | 
					
						
							|  |  |  |         return 1; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     memcpy(dest, s->current + (sec << 9), secn << 9); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static inline int onenand_prog_main(OneNANDState *s, int sec, int secn, | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                 void *src) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     int result = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (secn > 0) { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |         uint32_t size = (uint32_t)secn * 512; | 
					
						
							|  |  |  |         const uint8_t *sp = (const uint8_t *)src; | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |         uint8_t *dp = 0; | 
					
						
							|  |  |  |         if (s->bdrv_cur) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |             dp = g_malloc(size); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |             if (!dp || bdrv_read(s->bdrv_cur, sec, dp, secn) < 0) { | 
					
						
							|  |  |  |                 result = 1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             if (sec + secn > s->secs_cur) { | 
					
						
							|  |  |  |                 result = 1; | 
					
						
							|  |  |  |             } else { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |                 dp = (uint8_t *)s->current + (sec << 9); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!result) { | 
					
						
							|  |  |  |             uint32_t i; | 
					
						
							|  |  |  |             for (i = 0; i < size; i++) { | 
					
						
							|  |  |  |                 dp[i] &= sp[i]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->bdrv_cur) { | 
					
						
							|  |  |  |                 result = bdrv_write(s->bdrv_cur, sec, dp, secn) < 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (dp && s->bdrv_cur) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |             g_free(dp); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     return result; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static inline int onenand_load_spare(OneNANDState *s, int sec, int secn, | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                 void *dest) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     uint8_t buf[512]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (s->bdrv_cur) { | 
					
						
							|  |  |  |         if (bdrv_read(s->bdrv_cur, s->secs_cur + (sec >> 5), buf, 1) < 0) | 
					
						
							|  |  |  |             return 1; | 
					
						
							|  |  |  |         memcpy(dest, buf + ((sec & 31) << 4), secn << 4); | 
					
						
							|  |  |  |     } else if (sec + secn > s->secs_cur) | 
					
						
							|  |  |  |         return 1; | 
					
						
							|  |  |  |     else | 
					
						
							|  |  |  |         memcpy(dest, s->current + (s->secs_cur << 9) + (sec << 4), secn << 4); | 
					
						
							|  |  |  |   | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static inline int onenand_prog_spare(OneNANDState *s, int sec, int secn, | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                 void *src) | 
					
						
							|  |  |  | { | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     int result = 0; | 
					
						
							|  |  |  |     if (secn > 0) { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |         const uint8_t *sp = (const uint8_t *)src; | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |         uint8_t *dp = 0, *dpp = 0; | 
					
						
							|  |  |  |         if (s->bdrv_cur) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |             dp = g_malloc(512); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |             if (!dp || bdrv_read(s->bdrv_cur, | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |                                  s->secs_cur + (sec >> 5), | 
					
						
							|  |  |  |                                  dp, 1) < 0) { | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |                 result = 1; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 dpp = dp + ((sec & 31) << 4); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             if (sec + secn > s->secs_cur) { | 
					
						
							|  |  |  |                 result = 1; | 
					
						
							|  |  |  |             } else { | 
					
						
							|  |  |  |                 dpp = s->current + (s->secs_cur << 9) + (sec << 4); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (!result) { | 
					
						
							|  |  |  |             uint32_t i; | 
					
						
							|  |  |  |             for (i = 0; i < (secn << 4); i++) { | 
					
						
							|  |  |  |                 dpp[i] &= sp[i]; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->bdrv_cur) { | 
					
						
							|  |  |  |                 result = bdrv_write(s->bdrv_cur, s->secs_cur + (sec >> 5), | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |                                     dp, 1) < 0; | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         if (dp) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |             g_free(dp); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |         } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return result; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  | static inline int onenand_erase(OneNANDState *s, int sec, int num) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     uint8_t *blankbuf, *tmpbuf; | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     blankbuf = g_malloc(512); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     if (!blankbuf) { | 
					
						
							|  |  |  |         return 1; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     tmpbuf = g_malloc(512); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     if (!tmpbuf) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |         g_free(blankbuf); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |         return 1; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     memset(blankbuf, 0xff, 512); | 
					
						
							|  |  |  |     for (; num > 0; num--, sec++) { | 
					
						
							|  |  |  |         if (s->bdrv_cur) { | 
					
						
							|  |  |  |             int erasesec = s->secs_cur + (sec >> 5); | 
					
						
							|  |  |  |             if (bdrv_write(s->bdrv_cur, sec, blankbuf, 1)) { | 
					
						
							|  |  |  |                 goto fail; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (bdrv_read(s->bdrv_cur, erasesec, tmpbuf, 1) < 0) { | 
					
						
							|  |  |  |                 goto fail; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             memcpy(tmpbuf + ((sec & 31) << 4), blankbuf, 1 << 4); | 
					
						
							|  |  |  |             if (bdrv_write(s->bdrv_cur, erasesec, tmpbuf, 1) < 0) { | 
					
						
							|  |  |  |                 goto fail; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |             if (sec + 1 > s->secs_cur) { | 
					
						
							|  |  |  |                 goto fail; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             memcpy(s->current + (sec << 9), blankbuf, 512); | 
					
						
							|  |  |  |             memcpy(s->current + (s->secs_cur << 9) + (sec << 4), | 
					
						
							|  |  |  |                    blankbuf, 1 << 4); | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     g_free(tmpbuf); | 
					
						
							|  |  |  |     g_free(blankbuf); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     return 0; | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | fail: | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     g_free(tmpbuf); | 
					
						
							|  |  |  |     g_free(blankbuf); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:28 +01:00
										 |  |  |     return 1; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:33:02 +00:00
										 |  |  | static void onenand_command(OneNANDState *s) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							|  |  |  |     int b; | 
					
						
							|  |  |  |     int sec; | 
					
						
							|  |  |  |     void *buf; | 
					
						
							|  |  |  | #define SETADDR(block, page)			\
 | 
					
						
							|  |  |  |     sec = (s->addr[page] & 3) +			\ | 
					
						
							|  |  |  |             ((((s->addr[page] >> 2) & 0x3f) +	\ | 
					
						
							|  |  |  |               (((s->addr[block] & 0xfff) |	\ | 
					
						
							|  |  |  |                 (s->addr[block] >> 15 ?		\ | 
					
						
							|  |  |  |                  s->density_mask : 0)) << 6)) << (PAGE_SHIFT - 9)); | 
					
						
							|  |  |  | #define SETBUF_M()				\
 | 
					
						
							|  |  |  |     buf = (s->bufaddr & 8) ?			\ | 
					
						
							|  |  |  |             s->data[(s->bufaddr >> 2) & 1][0] : s->boot[0];	\ | 
					
						
							|  |  |  |     buf += (s->bufaddr & 3) << 9; | 
					
						
							|  |  |  | #define SETBUF_S()				\
 | 
					
						
							|  |  |  |     buf = (s->bufaddr & 8) ?			\ | 
					
						
							|  |  |  |             s->data[(s->bufaddr >> 2) & 1][1] : s->boot[1];	\ | 
					
						
							|  |  |  |     buf += (s->bufaddr & 3) << 4; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:33:02 +00:00
										 |  |  |     switch (s->command) { | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     case 0x00:	/* Load single/multiple sector data unit into buffer */ | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETBUF_M() | 
					
						
							|  |  |  |         if (onenand_load_main(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if 0
 | 
					
						
							|  |  |  |         SETBUF_S() | 
					
						
							|  |  |  |         if (onenand_load_spare(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
 | 
					
						
							|  |  |  |          * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages) | 
					
						
							|  |  |  |          * then we need two split the read/write into two chunks. | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_LOAD; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x13:	/* Load single/multiple spare sector into buffer */ | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETBUF_S() | 
					
						
							|  |  |  |         if (onenand_load_spare(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_LOAD; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
 | 
					
						
							|  |  |  |          * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages) | 
					
						
							|  |  |  |          * then we need two split the read/write into two chunks. | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_LOAD; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x80:	/* Program single/multiple sector data unit from buffer */ | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETBUF_M() | 
					
						
							|  |  |  |         if (onenand_prog_main(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | #if 0
 | 
					
						
							|  |  |  |         SETBUF_S() | 
					
						
							|  |  |  |         if (onenand_prog_spare(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG; | 
					
						
							|  |  |  | #endif
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
 | 
					
						
							|  |  |  |          * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages) | 
					
						
							|  |  |  |          * then we need two split the read/write into two chunks. | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_PROG; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x1a:	/* Program single/multiple spare area sector from buffer */ | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETBUF_S() | 
					
						
							|  |  |  |         if (onenand_prog_spare(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* TODO: if (s->bufaddr & 3) + s->count was > 4 (2k-pages)
 | 
					
						
							|  |  |  |          * or    if (s->bufaddr & 1) + s->count was > 2 (1k-pages) | 
					
						
							|  |  |  |          * then we need two split the read/write into two chunks. | 
					
						
							|  |  |  |          */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_PROG; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x1b:	/* Copy-back program */ | 
					
						
							|  |  |  |         SETBUF_S() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  |         if (onenand_load_main(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         SETADDR(ONEN_BUF_DEST_BLOCK, ONEN_BUF_DEST_PAGE) | 
					
						
							|  |  |  |         if (onenand_prog_main(s, sec, s->count, buf)) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_PROG; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* TODO: spare areas */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_PROG; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0x23:	/* Unlock NAND array block(s) */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         /* XXX the previous (?) area should be locked automatically */ | 
					
						
							|  |  |  |         for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) { | 
					
						
							|  |  |  |             if (b >= s->blocks) { | 
					
						
							|  |  |  |                 s->status |= ONEN_ERR_CMD; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2008-07-23 16:35:45 +00:00
										 |  |  |     case 0x27:	/* Unlock All NAND array blocks */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (b = 0; b < s->blocks; b ++) { | 
					
						
							|  |  |  |             if (b >= s->blocks) { | 
					
						
							|  |  |  |                 s->status |= ONEN_ERR_CMD; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             s->wpstatus = s->blockwp[b] = ONEN_LOCK_UNLOCKED; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     case 0x2a:	/* Lock NAND array block(s) */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) { | 
					
						
							|  |  |  |             if (b >= s->blocks) { | 
					
						
							|  |  |  |                 s->status |= ONEN_ERR_CMD; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->blockwp[b] == ONEN_LOCK_LOCKTIGHTEN) | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKED; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x2c:	/* Lock-tight NAND array block(s) */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         for (b = s->unladdr[0]; b <= s->unladdr[1]; b ++) { | 
					
						
							|  |  |  |             if (b >= s->blocks) { | 
					
						
							|  |  |  |                 s->status |= ONEN_ERR_CMD; | 
					
						
							|  |  |  |                 break; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             if (s->blockwp[b] == ONEN_LOCK_UNLOCKED) | 
					
						
							|  |  |  |                 continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             s->wpstatus = s->blockwp[b] = ONEN_LOCK_LOCKTIGHTEN; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0x71:	/* Erase-Verify-Read */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x95:	/* Multi-block erase */ | 
					
						
							|  |  |  |         qemu_irq_pulse(s->intr); | 
					
						
							|  |  |  |         /* Fall through.  */ | 
					
						
							|  |  |  |     case 0x94:	/* Block erase */ | 
					
						
							|  |  |  |         sec = ((s->addr[ONEN_BUF_BLOCK] & 0xfff) | | 
					
						
							|  |  |  |                         (s->addr[ONEN_BUF_BLOCK] >> 15 ? s->density_mask : 0)) | 
					
						
							|  |  |  |                 << (BLOCK_SHIFT - 9); | 
					
						
							|  |  |  |         if (onenand_erase(s, sec, 1 << (BLOCK_SHIFT - 9))) | 
					
						
							|  |  |  |             s->status |= ONEN_ERR_CMD | ONEN_ERR_ERASE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_ERASE; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0xb0:	/* Erase suspend */ | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0x30:	/* Erase resume */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT | ONEN_INT_ERASE; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf0:	/* Reset NAND Flash core */ | 
					
						
							|  |  |  |         onenand_reset(s, 0); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0xf3:	/* Reset OneNAND */ | 
					
						
							|  |  |  |         onenand_reset(s, 0); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0x65:	/* OTP Access */ | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							| 
									
										
										
										
											2009-09-21 18:11:34 +00:00
										 |  |  |         s->bdrv_cur = NULL; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |         s->current = s->otp; | 
					
						
							|  |  |  |         s->secs_cur = 1 << (BLOCK_SHIFT - 9); | 
					
						
							|  |  |  |         s->addr[ONEN_BUF_BLOCK] = 0; | 
					
						
							|  |  |  |         s->otpmode = 1; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         s->status |= ONEN_ERR_CMD; | 
					
						
							|  |  |  |         s->intstatus |= ONEN_INT; | 
					
						
							|  |  |  |         fprintf(stderr, "%s: unknown OneNAND command %x\n", | 
					
						
							| 
									
										
										
										
											2011-08-28 16:33:02 +00:00
										 |  |  |                         __func__, s->command); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     onenand_intr_update(s); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  | static uint64_t onenand_read(void *opaque, target_phys_addr_t addr, | 
					
						
							|  |  |  |                              unsigned size) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  |     OneNANDState *s = (OneNANDState *) opaque; | 
					
						
							| 
									
										
										
										
											2008-12-01 18:59:50 +00:00
										 |  |  |     int offset = addr >> s->shift; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     switch (offset) { | 
					
						
							|  |  |  |     case 0x0000 ... 0xc000: | 
					
						
							| 
									
										
										
										
											2008-12-01 18:59:50 +00:00
										 |  |  |         return lduw_le_p(s->boot[0] + addr); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     case 0xf000:	/* Manufacturer ID */ | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:26 +01:00
										 |  |  |         return s->id.man; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     case 0xf001:	/* Device ID */ | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:26 +01:00
										 |  |  |         return s->id.dev; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     case 0xf002:	/* Version ID */ | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:26 +01:00
										 |  |  |         return s->id.ver; | 
					
						
							|  |  |  |     /* TODO: get the following values from a real chip!  */ | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     case 0xf003:	/* Data Buffer size */ | 
					
						
							|  |  |  |         return 1 << PAGE_SHIFT; | 
					
						
							|  |  |  |     case 0xf004:	/* Boot Buffer size */ | 
					
						
							|  |  |  |         return 0x200; | 
					
						
							|  |  |  |     case 0xf005:	/* Amount of buffers */ | 
					
						
							|  |  |  |         return 1 | (2 << 8); | 
					
						
							|  |  |  |     case 0xf006:	/* Technology */ | 
					
						
							|  |  |  |         return 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf100 ... 0xf107:	/* Start addresses */ | 
					
						
							|  |  |  |         return s->addr[offset - 0xf100]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf200:	/* Start buffer */ | 
					
						
							|  |  |  |         return (s->bufaddr << 8) | ((s->count - 1) & (1 << (PAGE_SHIFT - 10))); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf220:	/* Command */ | 
					
						
							|  |  |  |         return s->command; | 
					
						
							|  |  |  |     case 0xf221:	/* System Configuration 1 */ | 
					
						
							|  |  |  |         return s->config[0] & 0xffe0; | 
					
						
							|  |  |  |     case 0xf222:	/* System Configuration 2 */ | 
					
						
							|  |  |  |         return s->config[1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf240:	/* Controller Status */ | 
					
						
							|  |  |  |         return s->status; | 
					
						
							|  |  |  |     case 0xf241:	/* Interrupt */ | 
					
						
							|  |  |  |         return s->intstatus; | 
					
						
							|  |  |  |     case 0xf24c:	/* Unlock Start Block Address */ | 
					
						
							|  |  |  |         return s->unladdr[0]; | 
					
						
							|  |  |  |     case 0xf24d:	/* Unlock End Block Address */ | 
					
						
							|  |  |  |         return s->unladdr[1]; | 
					
						
							|  |  |  |     case 0xf24e:	/* Write Protection Status */ | 
					
						
							|  |  |  |         return s->wpstatus; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xff00:	/* ECC Status */ | 
					
						
							|  |  |  |         return 0x00; | 
					
						
							|  |  |  |     case 0xff01:	/* ECC Result of main area data */ | 
					
						
							|  |  |  |     case 0xff02:	/* ECC Result of spare area data */ | 
					
						
							|  |  |  |     case 0xff03:	/* ECC Result of main area data */ | 
					
						
							|  |  |  |     case 0xff04:	/* ECC Result of spare area data */ | 
					
						
							| 
									
										
										
										
											2009-05-08 02:35:15 +01:00
										 |  |  |         hw_error("%s: imeplement ECC\n", __FUNCTION__); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |         return 0x0000; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     fprintf(stderr, "%s: unknown OneNAND register %x\n", | 
					
						
							|  |  |  |                     __FUNCTION__, offset); | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2009-10-01 16:12:16 -05:00
										 |  |  | static void onenand_write(void *opaque, target_phys_addr_t addr, | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |                           uint64_t value, unsigned size) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2009-05-10 01:44:56 +01:00
										 |  |  |     OneNANDState *s = (OneNANDState *) opaque; | 
					
						
							| 
									
										
										
										
											2008-12-01 18:59:50 +00:00
										 |  |  |     int offset = addr >> s->shift; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     int sec; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     switch (offset) { | 
					
						
							|  |  |  |     case 0x0000 ... 0x01ff: | 
					
						
							|  |  |  |     case 0x8000 ... 0x800f: | 
					
						
							|  |  |  |         if (s->cycle) { | 
					
						
							|  |  |  |             s->cycle = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (value == 0x0000) { | 
					
						
							|  |  |  |                 SETADDR(ONEN_BUF_BLOCK, ONEN_BUF_PAGE) | 
					
						
							|  |  |  |                 onenand_load_main(s, sec, | 
					
						
							|  |  |  |                                 1 << (PAGE_SHIFT - 9), s->data[0][0]); | 
					
						
							|  |  |  |                 s->addr[ONEN_BUF_PAGE] += 4; | 
					
						
							|  |  |  |                 s->addr[ONEN_BUF_PAGE] &= 0xff; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         switch (value) { | 
					
						
							|  |  |  |         case 0x00f0:	/* Reset OneNAND */ | 
					
						
							|  |  |  |             onenand_reset(s, 0); | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         case 0x00e0:	/* Load Data into Buffer */ | 
					
						
							|  |  |  |             s->cycle = 1; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         case 0x0090:	/* Read Identification Data */ | 
					
						
							|  |  |  |             memset(s->boot[0], 0, 3 << s->shift); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:26 +01:00
										 |  |  |             s->boot[0][0 << s->shift] = s->id.man & 0xff; | 
					
						
							|  |  |  |             s->boot[0][1 << s->shift] = s->id.dev & 0xff; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |             s->boot[0][2 << s->shift] = s->wpstatus & 0xff; | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         default: | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |             fprintf(stderr, "%s: unknown OneNAND boot command %"PRIx64"\n", | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                             __FUNCTION__, value); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf100 ... 0xf107:	/* Start addresses */ | 
					
						
							|  |  |  |         s->addr[offset - 0xf100] = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf200:	/* Start buffer */ | 
					
						
							|  |  |  |         s->bufaddr = (value >> 8) & 0xf; | 
					
						
							|  |  |  |         if (PAGE_SHIFT == 11) | 
					
						
							|  |  |  |             s->count = (value & 3) ?: 4; | 
					
						
							|  |  |  |         else if (PAGE_SHIFT == 10) | 
					
						
							|  |  |  |             s->count = (value & 1) ?: 2; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf220:	/* Command */ | 
					
						
							|  |  |  |         if (s->intstatus & (1 << 15)) | 
					
						
							|  |  |  |             break; | 
					
						
							|  |  |  |         s->command = value; | 
					
						
							| 
									
										
										
										
											2011-08-28 16:33:02 +00:00
										 |  |  |         onenand_command(s); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |         break; | 
					
						
							|  |  |  |     case 0xf221:	/* System Configuration 1 */ | 
					
						
							|  |  |  |         s->config[0] = value; | 
					
						
							|  |  |  |         onenand_intr_update(s); | 
					
						
							|  |  |  |         qemu_set_irq(s->rdy, (s->config[0] >> 7) & 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0xf222:	/* System Configuration 2 */ | 
					
						
							|  |  |  |         s->config[1] = value; | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     case 0xf241:	/* Interrupt */ | 
					
						
							|  |  |  |         s->intstatus &= value; | 
					
						
							|  |  |  |         if ((1 << 15) & ~s->intstatus) | 
					
						
							|  |  |  |             s->status &= ~(ONEN_ERR_CMD | ONEN_ERR_ERASE | | 
					
						
							|  |  |  |                             ONEN_ERR_PROG | ONEN_ERR_LOAD); | 
					
						
							|  |  |  |         onenand_intr_update(s); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0xf24c:	/* Unlock Start Block Address */ | 
					
						
							|  |  |  |         s->unladdr[0] = value & (s->blocks - 1); | 
					
						
							|  |  |  |         /* For some reason we have to set the end address to by default
 | 
					
						
							|  |  |  |          * be same as start because the software forgets to write anything | 
					
						
							|  |  |  |          * in there.  */ | 
					
						
							|  |  |  |         s->unladdr[1] = value & (s->blocks - 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |     case 0xf24d:	/* Unlock End Block Address */ | 
					
						
							|  |  |  |         s->unladdr[1] = value & (s->blocks - 1); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     default: | 
					
						
							|  |  |  |         fprintf(stderr, "%s: unknown OneNAND register %x\n", | 
					
						
							|  |  |  |                         __FUNCTION__, offset); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  | static const MemoryRegionOps onenand_ops = { | 
					
						
							|  |  |  |     .read = onenand_read, | 
					
						
							|  |  |  |     .write = onenand_write, | 
					
						
							|  |  |  |     .endianness = DEVICE_NATIVE_ENDIAN, | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static int onenand_initfn(SysBusDevice *dev) | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     OneNANDState *s = (OneNANDState *)dev; | 
					
						
							|  |  |  |     uint32_t size = 1 << (24 + ((s->id.dev >> 4) & 7)); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     void *ram; | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     s->base = (target_phys_addr_t)-1; | 
					
						
							| 
									
										
										
										
											2009-09-21 18:11:34 +00:00
										 |  |  |     s->rdy = NULL; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     s->blocks = size >> BLOCK_SHIFT; | 
					
						
							|  |  |  |     s->secs = size >> 9; | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     s->blockwp = g_malloc(s->blocks); | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     s->density_mask = (s->id.dev & 0x08) | 
					
						
							|  |  |  |         ? (1 << (6 + ((s->id.dev >> 4) & 7))) : 0; | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |     memory_region_init_io(&s->iomem, &onenand_ops, s, "onenand", | 
					
						
							|  |  |  |                           0x10000 << s->shift); | 
					
						
							| 
									
										
										
										
											2011-07-29 16:35:25 +01:00
										 |  |  |     if (!s->bdrv) { | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |         s->image = memset(g_malloc(size + (size >> 5)), | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |                           0xff, size + (size >> 5)); | 
					
						
							|  |  |  |     } else { | 
					
						
							| 
									
										
										
										
											2011-10-20 14:53:35 +02:00
										 |  |  |         if (bdrv_is_read_only(s->bdrv)) { | 
					
						
							|  |  |  |             error_report("Can't use a read-only drive"); | 
					
						
							|  |  |  |             return -1; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |         s->bdrv_cur = s->bdrv; | 
					
						
							| 
									
										
										
										
											2011-07-30 06:53:39 +02:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2011-08-20 22:09:37 -05:00
										 |  |  |     s->otp = memset(g_malloc((64 + 2) << PAGE_SHIFT), | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |                     0xff, (64 + 2) << PAGE_SHIFT); | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |     memory_region_init_ram(&s->ram, NULL, "onenand.ram", 0xc000 << s->shift); | 
					
						
							|  |  |  |     ram = memory_region_get_ram_ptr(&s->ram); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  |     s->boot[0] = ram + (0x0000 << s->shift); | 
					
						
							|  |  |  |     s->boot[1] = ram + (0x8000 << s->shift); | 
					
						
							|  |  |  |     s->data[0][0] = ram + ((0x0200 + (0 << (PAGE_SHIFT - 1))) << s->shift); | 
					
						
							|  |  |  |     s->data[0][1] = ram + ((0x8010 + (0 << (PAGE_SHIFT - 6))) << s->shift); | 
					
						
							|  |  |  |     s->data[1][0] = ram + ((0x0200 + (1 << (PAGE_SHIFT - 1))) << s->shift); | 
					
						
							|  |  |  |     s->data[1][1] = ram + ((0x8010 + (1 << (PAGE_SHIFT - 6))) << s->shift); | 
					
						
							| 
									
										
										
										
											2011-08-15 17:17:24 +03:00
										 |  |  |     onenand_mem_setup(s); | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     sysbus_init_irq(dev, &s->intr); | 
					
						
							| 
									
										
										
										
											2011-11-27 11:38:10 +02:00
										 |  |  |     sysbus_init_mmio(dev, &s->container); | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     vmstate_register(&dev->qdev, | 
					
						
							|  |  |  |                      ((s->shift & 0x7f) << 24) | 
					
						
							|  |  |  |                      | ((s->id.man & 0xff) << 16) | 
					
						
							|  |  |  |                      | ((s->id.dev & 0xff) << 8) | 
					
						
							|  |  |  |                      | (s->id.ver & 0xff), | 
					
						
							|  |  |  |                      &vmstate_onenand, s); | 
					
						
							|  |  |  |     return 0; | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static SysBusDeviceInfo onenand_info = { | 
					
						
							|  |  |  |     .init = onenand_initfn, | 
					
						
							|  |  |  |     .qdev.name = "onenand", | 
					
						
							|  |  |  |     .qdev.size = sizeof(OneNANDState), | 
					
						
							|  |  |  |     .qdev.reset = onenand_system_reset, | 
					
						
							|  |  |  |     .qdev.props = (Property[]) { | 
					
						
							|  |  |  |         DEFINE_PROP_UINT16("manufacturer_id", OneNANDState, id.man, 0), | 
					
						
							|  |  |  |         DEFINE_PROP_UINT16("device_id", OneNANDState, id.dev, 0), | 
					
						
							|  |  |  |         DEFINE_PROP_UINT16("version_id", OneNANDState, id.ver, 0), | 
					
						
							|  |  |  |         DEFINE_PROP_INT32("shift", OneNANDState, shift, 0), | 
					
						
							|  |  |  |         DEFINE_PROP_DRIVE("drive", OneNANDState, bdrv), | 
					
						
							|  |  |  |         DEFINE_PROP_END_OF_LIST() | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | }; | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | static void onenand_register_device(void) | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     sysbus_register_withprop(&onenand_info); | 
					
						
							| 
									
										
										
										
											2008-04-14 21:57:44 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2008-07-29 14:19:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | void *onenand_raw_otp(DeviceState *onenand_device) | 
					
						
							| 
									
										
										
										
											2008-07-29 14:19:16 +00:00
										 |  |  | { | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  |     return FROM_SYSBUS(OneNANDState, sysbus_from_qdev(onenand_device))->otp; | 
					
						
							| 
									
										
										
										
											2008-07-29 14:19:16 +00:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2011-08-28 16:22:17 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | device_init(onenand_register_device) |