269 lines
5.6 KiB
C
269 lines
5.6 KiB
C
/* Copyright 2012 The Chromium OS Authors. All rights reserved.
|
|
* Use of this source code is governed by a BSD-style license that can be
|
|
* found in the LICENSE file.
|
|
*/
|
|
|
|
/* EEPROM module for Chrome EC */
|
|
|
|
#include "clock.h"
|
|
#include "console.h"
|
|
#include "eeprom.h"
|
|
#include "registers.h"
|
|
#include "timer.h"
|
|
#include "util.h"
|
|
#include "watchdog.h"
|
|
|
|
/* Size of EEPROM block in bytes */
|
|
#define EEPROM_BLOCK_SIZE 64
|
|
|
|
/* Count of EEPROM blocks */
|
|
static int block_count;
|
|
|
|
/*
|
|
* Wait for the current EEPROM operation to finish; all operations but write
|
|
* should normally finish in 4 system clocks, but worst case is up to
|
|
* 1800ms if the EEPROM needs to do an internal page erase/copy. We must
|
|
* spin-wait for this delay, because EEPROM operations will fail if the chip
|
|
* drops to sleep mode.
|
|
*/
|
|
static int wait_for_done(void)
|
|
{
|
|
int j;
|
|
|
|
for (j = 0; j < 20; j++) { /* 20 * 100 ms = 2000 ms */
|
|
uint64_t tstop = get_time().val + 100 * MSEC;
|
|
while (get_time().val < tstop) {
|
|
if (!(LM4_EEPROM_EEDONE & 0x01))
|
|
return EC_SUCCESS;
|
|
}
|
|
watchdog_reload();
|
|
}
|
|
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
|
|
int eeprom_get_block_count(void)
|
|
{
|
|
return block_count;
|
|
}
|
|
|
|
|
|
int eeprom_get_block_size(void)
|
|
{
|
|
return EEPROM_BLOCK_SIZE;
|
|
}
|
|
|
|
|
|
int eeprom_read(int block, int offset, int size, char *data)
|
|
{
|
|
uint32_t *d = (uint32_t *)data;
|
|
int rv;
|
|
|
|
if (block < 0 || block >= block_count ||
|
|
offset < 0 || offset > EEPROM_BLOCK_SIZE || offset & 3 ||
|
|
size < 0 || offset + size > EEPROM_BLOCK_SIZE || size & 3)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
rv = wait_for_done();
|
|
if (rv)
|
|
return rv;
|
|
|
|
LM4_EEPROM_EEBLOCK = block;
|
|
if (LM4_EEPROM_EEBLOCK != block)
|
|
return EC_ERROR_UNKNOWN; /* Error setting block */
|
|
|
|
LM4_EEPROM_EEOFFSET = offset >> 2;
|
|
|
|
for (; size; size -= sizeof(uint32_t))
|
|
*(d++) = LM4_EEPROM_EERDWRINC;
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
int eeprom_write(int block, int offset, int size, const char *data)
|
|
{
|
|
uint32_t *d = (uint32_t *)data;
|
|
int rv;
|
|
|
|
if (block < 0 || block >= block_count ||
|
|
offset < 0 || offset > EEPROM_BLOCK_SIZE || offset & 3 ||
|
|
size < 0 || offset + size > EEPROM_BLOCK_SIZE || size & 3)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
rv = wait_for_done();
|
|
if (rv)
|
|
return rv;
|
|
|
|
LM4_EEPROM_EEBLOCK = block;
|
|
if (LM4_EEPROM_EEBLOCK != block)
|
|
return EC_ERROR_UNKNOWN; /* Error setting block */
|
|
|
|
LM4_EEPROM_EEOFFSET = offset >> 2;
|
|
|
|
/* Write 32 bits at a time; wait for each write to complete */
|
|
for (; size; size -= sizeof(uint32_t)) {
|
|
LM4_EEPROM_EERDWRINC = *(d++);
|
|
|
|
rv = wait_for_done();
|
|
if (rv)
|
|
return rv;
|
|
|
|
if (LM4_EEPROM_EEDONE & 0x10) {
|
|
/* Failed due to write protect */
|
|
return EC_ERROR_ACCESS_DENIED;
|
|
} else if (LM4_EEPROM_EEDONE & 0x100) {
|
|
/* Failed due to program voltage level */
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
int eeprom_hide(int block)
|
|
{
|
|
/* Block 0 can't be hidden */
|
|
if (block <= 0 || block >= block_count)
|
|
return EC_ERROR_UNKNOWN;
|
|
|
|
LM4_EEPROM_EEHIDE |= 1 << block;
|
|
return EC_SUCCESS;
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Console commands */
|
|
|
|
static int command_eeprom_info(int argc, char **argv)
|
|
{
|
|
ccprintf("%d blocks @ %d bytes, hide=0x%08x\n",
|
|
eeprom_get_block_count(), eeprom_get_block_size(),
|
|
LM4_EEPROM_EEHIDE);
|
|
return EC_SUCCESS;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(eeinfo, command_eeprom_info,
|
|
NULL,
|
|
"Print EEPROM info");
|
|
|
|
|
|
static int command_eeprom_read(int argc, char **argv)
|
|
{
|
|
int block = 0;
|
|
int offset = 0;
|
|
char *e;
|
|
int rv;
|
|
uint32_t d;
|
|
|
|
if (argc < 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
block = strtoi(argv[1], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
if (argc > 2) {
|
|
offset = strtoi(argv[2], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
}
|
|
|
|
rv = eeprom_read(block, offset, sizeof(d), (char *)&d);
|
|
if (rv == EC_SUCCESS)
|
|
ccprintf("%d:%d = 0x%08x\n", block, offset, d);
|
|
return rv;
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(eeread, command_eeprom_read,
|
|
"block [offset]",
|
|
"Read a word of EEPROM");
|
|
|
|
|
|
static int command_eeprom_write(int argc, char **argv)
|
|
{
|
|
int block = 0;
|
|
int offset = 0;
|
|
char *e;
|
|
uint32_t d;
|
|
|
|
if (argc < 4)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
block = strtoi(argv[1], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM1;
|
|
offset = strtoi(argv[2], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM2;
|
|
d = strtoi(argv[3], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM3;
|
|
|
|
ccprintf("Writing 0x%08x to %d:%d...\n", d, block, offset);
|
|
return eeprom_write(block, offset, sizeof(d), (char *)&d);
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(eewrite, command_eeprom_write,
|
|
"block offset value",
|
|
"Write a word of EEPROM");
|
|
|
|
|
|
#ifdef CONSOLE_COMMAND_EEHIDE
|
|
static int command_eeprom_hide(int argc, char **argv)
|
|
{
|
|
int block = 0;
|
|
char *e;
|
|
|
|
if (argc < 2)
|
|
return EC_ERROR_PARAM_COUNT;
|
|
|
|
block = strtoi(argv[1], &e, 0);
|
|
if (*e)
|
|
return EC_ERROR_PARAM1;
|
|
|
|
ccprintf("Hiding block %d\n", block);
|
|
return eeprom_hide(block);
|
|
}
|
|
DECLARE_CONSOLE_COMMAND(eehide, command_eeprom_hide,
|
|
"block",
|
|
"Hide a block of EEPROM");
|
|
#endif
|
|
|
|
|
|
/*****************************************************************************/
|
|
/* Initialization */
|
|
|
|
|
|
int eeprom_init(void)
|
|
{
|
|
/* Enable the EEPROM module in run and sleep modes. */
|
|
clock_enable_peripheral(CGC_OFFSET_EEPROM, 0x1,
|
|
CGC_MODE_RUN | CGC_MODE_SLEEP);
|
|
|
|
/* Wait for internal EEPROM init to finish */
|
|
wait_for_done();
|
|
|
|
/* Store block count */
|
|
block_count = LM4_EEPROM_EESIZE >> 16;
|
|
|
|
/*
|
|
* Handle resetting the EEPROM module to clear state from a previous
|
|
* error condition.
|
|
*/
|
|
if (LM4_EEPROM_EESUPP & 0xc0) {
|
|
LM4_SYSTEM_SREEPROM = 1;
|
|
clock_wait_cycles(200);
|
|
LM4_SYSTEM_SREEPROM = 0;
|
|
|
|
/* Wait again for internal init to finish */
|
|
clock_wait_cycles(6);
|
|
wait_for_done();
|
|
|
|
/* Fail if error condition didn't clear */
|
|
if (LM4_EEPROM_EESUPP & 0xc0)
|
|
return EC_ERROR_UNKNOWN;
|
|
}
|
|
|
|
return EC_SUCCESS;
|
|
}
|