174 lines
3.8 KiB
C
Raw Normal View History

/*
* This file is part of GodMode9
* Copyright (C) 2019 Wolfvak
*
* 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 of the License, or
* (at your option) any later version.
*
* 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, see <http://www.gnu.org/licenses/>.
*/
#include <types.h>
#include <spi.h>
#include "arm/timer.h"
#include "hw/nvram.h"
// returns manuf id, memory type and size
// size = (1 << id[2]) ?
// apparently unreliable on some Sanyo chips?
#define CMD_RDID 0x9F
#define CMD_READ 0x03
#define CMD_WREN 0x06
#define CMD_WRDI 0x04
#define CMD_WRITE 0x0A
#define CMD_RDSR 0x05
#define NVRAM_SR_WIP BIT(0) // work in progress / busy
#define NVRAM_SR_WEL BIT(1) // write enable latch
#define CMD_DPD 0xB9 // deep power down
#define CMD_RDP 0xAB // release from deep power down
#define NVRAM_PAGE_SIZE 0x100 // 256 byte pages
#define NVRAM_ADDR_MASK 0xFFFFFF // 24bit address
#define NVRAM_ADDR_MAX (NVRAM_ADDR_MASK + 1)
static u32 NVRAM_SendStatusCommand(u32 cmd, u32 width)
{
u32 ret;
SPI_XferInfo xfer[2];
xfer[0].buf = &cmd;
xfer[0].len = 1;
xfer[0].read = false;
xfer[1].buf = &ret;
xfer[1].len = width;
xfer[1].read = true;
ret = 0;
SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, true);
return ret;
}
static void NVRAM_SetWriteEnable(bool write_en)
{
u8 cmd = write_en ? CMD_WREN : CMD_WRDI;
SPI_XferInfo xfer;
xfer.buf = &cmd;
xfer.len = 1;
xfer.read = false;
SPI_DoXfer(SPI_DEV_NVRAM, &xfer, 1, true);
}
u32 NVRAM_Status(void)
{
return NVRAM_SendStatusCommand(CMD_RDSR, 1);
}
void NVRAM_DeepStandby(void)
{
NVRAM_SendStatusCommand(CMD_DPD, 0);
}
void NVRAM_Wakeup(void)
{
NVRAM_SendStatusCommand(CMD_RDP, 0);
}
u32 NVRAM_ReadID(void)
{
return NVRAM_SendStatusCommand(CMD_RDID, 3);
}
int NVRAM_Read(u32 address, u32 *buffer, u32 len)
{
SPI_XferInfo xfer[2];
u32 cmd;
if (address >= NVRAM_ADDR_MAX || len > NVRAM_ADDR_MAX || (address + len) > NVRAM_ADDR_MAX)
return -1;
cmd = __builtin_bswap32(address) | CMD_READ;
xfer[0].buf = &cmd;
xfer[0].len = 4;
xfer[0].read = false;
xfer[1].buf = buffer;
xfer[1].len = len;
xfer[1].read = true;
SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, true);
return 0;
}
static int NVRAM_WritePage(u32 address, const u32 *buffer, u32 len)
{
SPI_XferInfo xfer[2];
u32 cmd, i;
if (address >= NVRAM_ADDR_MAX || len > NVRAM_PAGE_SIZE || (address + len) > NVRAM_ADDR_MAX)
return -1;
cmd = __builtin_bswap32(address) | CMD_WRITE;
xfer[0].buf = &cmd;
xfer[0].len = 4;
xfer[0].read = false;
xfer[1].buf = (void*)buffer;
xfer[1].len = len;
xfer[1].read = false;
// enable the write latch
NVRAM_SetWriteEnable(true);
// make sure it's enabled
for (i = 0; i <= 1000; i++) {
if (i == 1000) return -2;
if (NVRAM_Status() & NVRAM_SR_WEL) break;
TIMER_WaitMS(1);
}
// do the write transfer
SPI_DoXfer(SPI_DEV_NVRAM, xfer, 2, true);
// wait until it's done, disable the write latch
for (i = 0; i <= 1000; i++) {
if (i == 1000) return -3;
if (!(NVRAM_Status() & NVRAM_SR_WIP)) break;
TIMER_WaitMS(1);
}
NVRAM_SetWriteEnable(false);
return 0;
}
int NVRAM_Write(u32 address, const u32 *buffer, u32 len)
{
while(len > 0) {
u32 blksz = len < NVRAM_PAGE_SIZE ? len : NVRAM_PAGE_SIZE;
int result = NVRAM_WritePage(address, buffer, blksz);
if (result != 0)
return result;
address += blksz;
buffer += (blksz / 4);
len -= blksz;
}
return 0;
}