/* * 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 . */ #include #include #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; }