mini/nand.c

357 lines
9.7 KiB
C
Raw Normal View History

/*
mini - a Free Software replacement for the Nintendo/BroadOn IOS.
2009-04-13 03:06:53 +02:00
low-level NAND support
2009-04-13 22:13:45 +02:00
Copyright (C) 2008, 2009 Haxx Enterprises <bushing@gmail.com>
Copyright (C) 2008, 2009 Sven Peter <svenpeter@gmail.com>
Copyright (C) 2008, 2009 Hector Martin "marcan" <marcan@marcansoft.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, version 2.
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.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "hollywood.h"
#include "nand.h"
#include "utils.h"
#include "string.h"
#include "start.h"
#include "memory.h"
#include "crypto.h"
#include "irq.h"
#include "ipc.h"
#include "gecko.h"
#include "types.h"
// #define NAND_DEBUG 1
2009-03-27 11:59:13 +01:00
#define NAND_SUPPORT_WRITE 1
#define NAND_SUPPORT_ERASE 1
#ifdef ALLOW_BOOT2_WRITES
#define NAND_MIN_PAGE 0x40
#else
#define NAND_MIN_PAGE 0x200
#endif
#ifdef NAND_DEBUG
# include "gecko.h"
# define NAND_debug(f, arg...) gecko_printf("NAND: " f, ##arg);
#else
2009-03-07 06:50:42 +01:00
# define NAND_debug(f, arg...)
#endif
2009-04-13 03:06:53 +02:00
#define NAND_RESET 0xff
#define NAND_CHIPID 0x90
#define NAND_GETSTATUS 0x70
#define NAND_ERASE_PRE 0x60
#define NAND_ERASE_POST 0xd0
2009-04-13 03:06:53 +02:00
#define NAND_READ_PRE 0x00
#define NAND_READ_POST 0x30
#define NAND_WRITE_PRE 0x80
#define NAND_WRITE_POST 0x10
2009-04-13 03:06:53 +02:00
#define NAND_BUSY_MASK 0x80000000
#define NAND_ERROR 0x20000000
#define NAND_FLAGS_IRQ 0x40000000
#define NAND_FLAGS_WAIT 0x8000
#define NAND_FLAGS_WR 0x4000
#define NAND_FLAGS_RD 0x2000
#define NAND_FLAGS_ECC 0x1000
2009-03-18 18:18:56 +01:00
static ipc_request current_request;
2009-03-18 18:18:56 +01:00
static u8 ipc_data[PAGE_SIZE] MEM2_BSS ALIGNED(32);
static u8 ipc_ecc[ECC_BUFFER_ALLOC] MEM2_BSS ALIGNED(128); //128 alignment REQUIRED
static volatile int irq_flag;
2009-05-02 13:33:40 +02:00
static u32 last_page_read = 0;
void nand_irq(void)
{
int code, tag, err = 0;
2009-05-02 12:58:53 +02:00
if(read32(NAND_CMD) & NAND_ERROR) {
2009-03-07 06:51:00 +01:00
gecko_printf("NAND: Error on IRQ\n");
err = -1;
}
2009-03-07 20:15:07 +01:00
ahb_flush_from(AHB_NAND);
ahb_flush_to(AHB_STARLET);
if (current_request.code != 0) {
switch (current_request.req) {
case IPC_NAND_GETID:
memcpy32((void*)current_request.args[0], ipc_data, 0x40);
dc_flushrange((void*)current_request.args[0], 0x40);
break;
2009-03-08 08:17:48 +01:00
case IPC_NAND_STATUS:
memcpy32((void*)current_request.args[0], ipc_data, 0x40);
dc_flushrange((void*)current_request.args[0], 0x40);
break;
case IPC_NAND_READ:
2009-05-02 13:33:40 +02:00
err = nand_correct(last_page_read, ipc_data, ipc_ecc);
if (current_request.args[1] != 0xFFFFFFFF) {
memcpy32((void*)current_request.args[1], ipc_data, PAGE_SIZE);
dc_flushrange((void*)current_request.args[1], PAGE_SIZE);
}
if (current_request.args[2] != 0xFFFFFFFF) {
memcpy32((void*)current_request.args[2], ipc_ecc, PAGE_SPARE_SIZE);
dc_flushrange((void*)current_request.args[2], PAGE_SPARE_SIZE);
}
break;
case IPC_NAND_ERASE:
// no action needed upon erase completion
break;
case IPC_NAND_WRITE:
// no action needed upon write completion
break;
default:
gecko_printf("Got IRQ for unknown NAND req %d\n", current_request.req);
}
code = current_request.code;
tag = current_request.tag;
current_request.code = 0;
ipc_post(code, tag, 1, err);
}
irq_flag = 1;
}
inline void __nand_wait(void) {
2009-05-02 12:58:53 +02:00
while(read32(NAND_CMD) & NAND_BUSY_MASK);
if(read32(NAND_CMD) & NAND_ERROR)
2009-03-07 06:51:00 +01:00
gecko_printf("NAND: Error on wait\n");
2009-03-07 20:15:07 +01:00
ahb_flush_from(AHB_NAND);
ahb_flush_to(AHB_STARLET);
}
void nand_send_command(u32 command, u32 bitmask, u32 flags, u32 num_bytes) {
u32 cmd = NAND_BUSY_MASK | (bitmask << 24) | (command << 16) | flags | num_bytes;
2009-03-07 06:50:42 +01:00
NAND_debug("nand_send_command(%x, %x, %x, %x) -> %x\n",
command, bitmask, flags, num_bytes, cmd);
2009-03-07 06:50:42 +01:00
2009-05-02 12:58:53 +02:00
write32(NAND_CMD, 0x7fffffff);
write32(NAND_CMD, 0);
write32(NAND_CMD, cmd);
}
void __nand_set_address(s32 page_off, s32 pageno) {
2009-05-02 12:58:53 +02:00
if (page_off != -1) write32(NAND_ADDR0, page_off);
if (pageno != -1) write32(NAND_ADDR1, pageno);
}
void __nand_setup_dma(u8 *data, u8 *spare) {
if (((s32)data) != -1) {
2009-05-02 12:58:53 +02:00
write32(NAND_DATA, dma_addr(data));
}
if (((s32)spare) != -1) {
u32 addr = dma_addr(spare);
if(addr & 0x7f)
gecko_printf("NAND: Spare buffer 0x%08x is not aligned, data will be corrupted\n", addr);
2009-05-02 12:58:53 +02:00
write32(NAND_ECC, addr);
}
}
2009-04-13 03:06:53 +02:00
int nand_reset(void) {
NAND_debug("nand_reset()\n");
2009-04-13 03:06:53 +02:00
// IOS actually uses NAND_FLAGS_IRQ | NAND_FLAGS_WAIT here
nand_send_command(NAND_RESET, 0, NAND_FLAGS_WAIT, 0);
__nand_wait();
// enable NAND controller
2009-05-02 12:58:53 +02:00
write32(NAND_CONF, 0x08000000);
// set configuration parameters for 512MB flash chips
2009-05-02 12:58:53 +02:00
write32(NAND_CONF, 0x4b3e0e7f);
return 0;
}
void nand_get_id(u8 *idbuf) {
irq_flag = 0;
__nand_set_address(0,0);
2009-03-07 06:50:42 +01:00
dc_invalidaterange(idbuf, 0x40);
__nand_setup_dma(idbuf, (u8 *)-1);
nand_send_command(NAND_CHIPID, 1, NAND_FLAGS_IRQ | NAND_FLAGS_RD, 0x40);
}
void nand_get_status(u8 *status_buf) {
irq_flag = 0;
status_buf[0]=0;
dc_invalidaterange(status_buf, 0x40);
__nand_setup_dma(status_buf, (u8 *)-1);
nand_send_command(NAND_GETSTATUS, 0, NAND_FLAGS_IRQ | NAND_FLAGS_RD, 0x40);
}
void nand_read_page(u32 pageno, void *data, void *ecc) {
irq_flag = 0;
2009-05-02 13:33:40 +02:00
last_page_read = pageno; // needed for error reporting
2009-03-07 06:50:42 +01:00
__nand_set_address(0, pageno);
nand_send_command(NAND_READ_PRE, 0x1f, 0, 0);
if (((s32)data) != -1) dc_invalidaterange(data, PAGE_SIZE);
if (((s32)ecc) != -1) dc_invalidaterange(ecc, ECC_BUFFER_SIZE);
__nand_wait();
2009-03-07 06:50:42 +01:00
__nand_setup_dma(data, ecc);
nand_send_command(NAND_READ_POST, 0, NAND_FLAGS_IRQ | NAND_FLAGS_WAIT | NAND_FLAGS_RD | NAND_FLAGS_ECC, 0x840);
}
2009-04-13 03:06:53 +02:00
void nand_wait(void) {
// power-saving IRQ wait
while(!irq_flag) {
u32 cookie = irq_kill();
if(!irq_flag)
irq_wait();
irq_restore(cookie);
}
}
#ifdef NAND_SUPPORT_WRITE
void nand_write_page(u32 pageno, void *data, void *ecc) {
irq_flag = 0;
NAND_debug("nand_write_page(%u, %p, %p)\n", pageno, data, ecc);
2009-04-13 03:06:53 +02:00
// this is a safety check to prevent you from accidentally wiping out boot1 or boot2.
if ((pageno < NAND_MIN_PAGE) || (pageno >= NAND_MAX_PAGE)) {
gecko_printf("Error: nand_write to page %d forbidden\n", pageno);
2009-03-07 06:50:42 +01:00
return;
2009-02-16 13:49:11 +01:00
}
if (((s32)data) != -1) dc_flushrange(data, PAGE_SIZE);
if (((s32)ecc) != -1) dc_flushrange(ecc, PAGE_SPARE_SIZE);
2009-03-07 20:15:07 +01:00
ahb_flush_to(AHB_NAND);
__nand_set_address(0, pageno);
__nand_setup_dma(data, ecc);
nand_send_command(NAND_WRITE_PRE, 0x1f, NAND_FLAGS_WR, 0x840);
__nand_wait();
nand_send_command(NAND_WRITE_POST, 0, NAND_FLAGS_IRQ | NAND_FLAGS_WAIT, 0);
}
#endif
#ifdef NAND_SUPPORT_ERASE
void nand_erase_block(u32 pageno) {
irq_flag = 0;
2009-03-07 06:50:42 +01:00
NAND_debug("nand_erase_block(%d)\n", pageno);
2009-04-13 03:06:53 +02:00
// this is a safety check to prevent you from accidentally wiping out boot1 or boot2.
if ((pageno < NAND_MIN_PAGE) || (pageno >= NAND_MAX_PAGE)) {
gecko_printf("Error: nand_erase to page %d forbidden\n", pageno);
2009-03-07 06:50:42 +01:00
return;
2009-02-16 13:49:11 +01:00
}
2009-03-07 06:50:42 +01:00
__nand_set_address(0, pageno);
nand_send_command(NAND_ERASE_PRE, 0x1c, 0, 0);
__nand_wait();
nand_send_command(NAND_ERASE_POST, 0, NAND_FLAGS_IRQ | NAND_FLAGS_WAIT, 0);
NAND_debug("nand_erase_block(%d) done\n", pageno);
}
#endif
void nand_initialize(void)
{
current_request.code = 0;
nand_reset();
irq_enable(IRQ_NAND);
}
int nand_correct(u32 pageno, void *data, void *ecc)
{
u8 *dp = (u8*)data;
2009-04-15 21:11:33 +02:00
u32 *ecc_read = (u32*)((u8*)ecc+0x30);
u32 *ecc_calc = (u32*)((u8*)ecc+0x40);
int i;
int uncorrectable = 0;
int corrected = 0;
for(i=0;i<4;i++) {
u32 syndrome = *ecc_read ^ *ecc_calc; //calculate ECC syncrome
// don't try to correct unformatted pages (all FF)
if ((*ecc_read != 0xFFFFFFFF) && syndrome) {
if(!((syndrome-1)&syndrome)) {
// single-bit error in ECC
corrected++;
} else {
// byteswap and extract odd and even halves
u16 even = (syndrome >> 24) | ((syndrome >> 8) & 0xf00);
u16 odd = ((syndrome << 8) & 0xf00) | ((syndrome >> 8) & 0x0ff);
if((even ^ odd) != 0xfff) {
// oops, can't fix this one
uncorrectable++;
} else {
// fix the bad bit
dp[odd >> 3] ^= 1<<(odd&7);
corrected++;
}
}
}
dp += 0x200;
ecc_read++;
ecc_calc++;
}
if(uncorrectable || corrected)
2009-03-07 20:13:00 +01:00
gecko_printf("ECC stats for NAND page 0x%x: %d uncorrectable, %d corrected\n", pageno, uncorrectable, corrected);
if(uncorrectable)
return NAND_ECC_UNCORRECTABLE;
if(corrected)
return NAND_ECC_CORRECTED;
return NAND_ECC_OK;
}
void nand_ipc(volatile ipc_request *req)
{
if (current_request.code != 0) {
gecko_printf("NAND: previous IPC request is not done yet.");
ipc_post(req->code, req->tag, 1, -1);
return;
}
switch (req->req) {
case IPC_NAND_RESET:
nand_reset();
2009-01-30 13:43:35 +01:00
ipc_post(req->code, req->tag, 0);
break;
case IPC_NAND_GETID:
current_request = *req;
nand_get_id(ipc_data);
break;
case IPC_NAND_STATUS:
2009-03-08 08:17:48 +01:00
current_request = *req;
nand_get_status(ipc_data);
break;
case IPC_NAND_READ:
current_request = *req;
nand_read_page(req->args[0], ipc_data, ipc_ecc);
break;
#ifdef NAND_SUPPORT_WRITE
case IPC_NAND_WRITE:
current_request = *req;
dc_invalidaterange((void*)req->args[1], PAGE_SIZE);
dc_invalidaterange((void*)req->args[2], PAGE_SPARE_SIZE);
memcpy(ipc_data, (void*)req->args[1], PAGE_SIZE);
memcpy(ipc_ecc, (void*)req->args[2], PAGE_SPARE_SIZE);
nand_write_page(req->args[0], ipc_data, ipc_ecc);
break;
#endif
#ifdef NAND_SUPPORT_ERASE
case IPC_NAND_ERASE:
current_request = *req;
nand_erase_block(req->args[0]);
break;
#endif
default:
gecko_printf("IPC: unknown SLOW NAND request %04x\n",
req->req);
}
}
2009-04-13 22:13:45 +02:00