Newer
Older
/*
* (C) Copyright 2000, 2001
* Wolfgang Denk, DENX Software Engineering, wd@denx.de.
*
* SPDX-License-Identifier: GPL-2.0+
/*
* Support for read and write access to EEPROM like memory devices. This
* includes regular EEPROM as well as FRAM (ferroelectic nonvolaile RAM).
* FRAM devices read and write data at bus speed. In particular, there is no
* write delay. Also, there is no limit imposed on the number of bytes that can
* Use the following configuration options to ensure no unneeded performance
* degradation (typical for EEPROM) is incured for FRAM memory:
* #define CONFIG_SYS_I2C_FRAM
* #undef CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS
#include <common.h>
#include <config.h>
#include <command.h>
#include <i2c.h>
#ifndef CONFIG_SYS_I2C_SPEED
#define CONFIG_SYS_I2C_SPEED 50000
#ifndef CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS
#define CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS 0
#endif
#ifndef CONFIG_SYS_EEPROM_PAGE_WRITE_BITS
#define CONFIG_SYS_EEPROM_PAGE_WRITE_BITS 8
#endif
#ifndef I2C_RXTX_LEN
#define I2C_RXTX_LEN 128
#endif
#define EEPROM_PAGE_SIZE (1 << CONFIG_SYS_EEPROM_PAGE_WRITE_BITS)
#define EEPROM_PAGE_OFFSET(x) ((x) & (EEPROM_PAGE_SIZE - 1))
* for CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 2 (16-bit EEPROM address) offset is
* 0x000nxxxx for EEPROM address selectors at n, offset xxxx in EEPROM.
*
* for CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 1 (8-bit EEPROM page address) offset is
* 0x00000nxx for EEPROM address selectors and page number at n.
*/
#if !defined(CONFIG_SPI) || defined(CONFIG_ENV_EEPROM_IS_ON_I2C)
#if !defined(CONFIG_SYS_I2C_EEPROM_ADDR_LEN) || \
(CONFIG_SYS_I2C_EEPROM_ADDR_LEN < 1) || \
(CONFIG_SYS_I2C_EEPROM_ADDR_LEN > 2)
#error CONFIG_SYS_I2C_EEPROM_ADDR_LEN must be 1 or 2
__weak int eeprom_write_enable(unsigned dev_addr, int state)
{
return 0;
}
{
/* SPI EEPROM */
#if defined(CONFIG_SPI) && !defined(CONFIG_ENV_EEPROM_IS_ON_I2C)
#if defined(CONFIG_HARD_I2C) || defined(CONFIG_SYS_I2C)
#if defined(CONFIG_SYS_I2C)
if (bus >= 0)
i2c_set_bus_num(bus);
#endif
i2c_init(CONFIG_SYS_I2C_SPEED, CONFIG_SYS_I2C_SLAVE);
#endif
}
static int eeprom_addr(unsigned dev_addr, unsigned offset, uchar *addr)
{
unsigned blk_off;
int alen;
blk_off = offset & 0xff; /* block offset */
#if CONFIG_SYS_I2C_EEPROM_ADDR_LEN == 1
addr[0] = offset >> 8; /* block number */
addr[1] = blk_off; /* block offset */
alen = 2;
#else
addr[0] = offset >> 16; /* block number */
addr[1] = offset >> 8; /* upper address octet */
addr[2] = blk_off; /* lower address octet */
alen = 3;
#endif /* CONFIG_SYS_I2C_EEPROM_ADDR_LEN */
addr[0] |= dev_addr; /* insert device address */
return alen;
}
static int eeprom_len(unsigned offset, unsigned end)
{
unsigned len = end - offset;
/*
* For a FRAM device there is no limit on the number of the
* bytes that can be ccessed with the single read or write
* operation.
*/
#if !defined(CONFIG_SYS_I2C_FRAM)
unsigned blk_off = offset & 0xff;
unsigned maxlen = EEPROM_PAGE_SIZE - EEPROM_PAGE_OFFSET(blk_off);
if (maxlen > I2C_RXTX_LEN)
maxlen = I2C_RXTX_LEN;
if (len > maxlen)
len = maxlen;
#endif
return len;
}
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
static int eeprom_rw_block(unsigned offset, uchar *addr, unsigned alen,
uchar *buffer, unsigned len, bool read)
{
int ret = 0;
/* SPI */
#if defined(CONFIG_SPI) && !defined(CONFIG_ENV_EEPROM_IS_ON_I2C)
if (read)
spi_read(addr, alen, buffer, len);
else
spi_write(addr, alen, buffer, len);
#else /* I2C */
#if defined(CONFIG_SYS_I2C_EEPROM_BUS)
i2c_set_bus_num(CONFIG_SYS_I2C_EEPROM_BUS);
#endif
if (read)
ret = i2c_read(addr[0], offset, alen - 1, buffer, len);
else
ret = i2c_write(addr[0], offset, alen - 1, buffer, len);
if (ret)
ret = 1;
#endif
return ret;
}
static int eeprom_rw(unsigned dev_addr, unsigned offset, uchar *buffer,
unsigned cnt, bool read)
alen = eeprom_addr(dev_addr, offset, addr);
len = eeprom_len(offset, end);
rcode = eeprom_rw_block(offset, addr, alen, buffer, len, read);
if (!read)
udelay(CONFIG_SYS_EEPROM_PAGE_WRITE_DELAY_MS * 1000);
int eeprom_read(unsigned dev_addr, unsigned offset, uchar *buffer, unsigned cnt)
/*
* Read data until done or would cross a page boundary.
* We must write the address again when changing pages
* because the next page may be in a different device.
*/
return eeprom_rw(dev_addr, offset, buffer, cnt, 1);
}
int eeprom_write(unsigned dev_addr, unsigned offset,
uchar *buffer, unsigned cnt)
eeprom_write_enable(dev_addr, 1);
/*
* Write data until done or would cross a write page boundary.
* We must write the address again when changing pages
* because the address counter only increments within a page.
*/
ret = eeprom_rw(dev_addr, offset, buffer, cnt, 0);
eeprom_write_enable(dev_addr, 0);
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
#ifdef CONFIG_CMD_EEPROM_LAYOUT
#include <eeprom_layout.h>
__weak int eeprom_parse_layout_version(char *str)
{
return LAYOUT_VERSION_UNRECOGNIZED;
}
static unsigned char eeprom_buf[CONFIG_SYS_EEPROM_SIZE];
#ifndef CONFIG_EEPROM_LAYOUT_HELP_STRING
#define CONFIG_EEPROM_LAYOUT_HELP_STRING "<not defined>"
#endif
enum eeprom_action {
EEPROM_PRINT,
EEPROM_UPDATE,
EEPROM_ACTION_INVALID,
};
static enum eeprom_action parse_action(char *cmd)
{
if (!strncmp(cmd, "print", 5))
return EEPROM_PRINT;
if (!strncmp(cmd, "update", 6))
return EEPROM_UPDATE;
return EEPROM_ACTION_INVALID;
}
static int parse_numeric_param(char *str)
{
char *endptr;
int value = simple_strtol(str, &endptr, 16);
return (*endptr != '\0') ? -1 : value;
}
static int eeprom_execute_command(enum eeprom_action action, int i2c_bus,
int i2c_addr, int layout_ver, char *key,
char *value)
{
int rcode;
struct eeprom_layout layout;
if (action == EEPROM_ACTION_INVALID)
return CMD_RET_USAGE;
eeprom_init(i2c_bus);
rcode = eeprom_read(i2c_addr, 0, eeprom_buf, CONFIG_SYS_EEPROM_SIZE);
if (rcode < 0)
return rcode;
eeprom_layout_setup(&layout, eeprom_buf, CONFIG_SYS_EEPROM_SIZE,
layout_ver);
if (action == EEPROM_PRINT) {
layout.print(&layout);
return 0;
}
layout.update(&layout, key, value);
rcode = eeprom_write(i2c_addr, 0, layout.data, CONFIG_SYS_EEPROM_SIZE);
return rcode;
}
#define NEXT_PARAM(argc, index) { (argc)--; (index)++; }
static int do_eeprom_layout(cmd_tbl_t *cmdtp, int flag, int argc,
char * const argv[])
{
int layout_ver = LAYOUT_VERSION_AUTODETECT;
enum eeprom_action action = EEPROM_ACTION_INVALID;
int i2c_bus = -1, i2c_addr = -1, index = 0;
char *field_name = "";
char *field_value = "";
if (argc <= 1)
return CMD_RET_USAGE;
NEXT_PARAM(argc, index); /* Skip program name */
action = parse_action(argv[index]);
NEXT_PARAM(argc, index);
if (argc <= 1)
return CMD_RET_USAGE;
if (!strcmp(argv[index], "-l")) {
NEXT_PARAM(argc, index);
layout_ver = eeprom_parse_layout_version(argv[index]);
NEXT_PARAM(argc, index);
}
if (argc <= 1)
return CMD_RET_USAGE;
i2c_bus = parse_numeric_param(argv[index]);
NEXT_PARAM(argc, index);
i2c_addr = parse_numeric_param(argv[index]);
NEXT_PARAM(argc, index);
if (action == EEPROM_PRINT)
goto done;
if (argc) {
field_name = argv[index];
NEXT_PARAM(argc, index);
}
if (argc) {
field_value = argv[index];
NEXT_PARAM(argc, index);
}
done:
return eeprom_execute_command(action, i2c_bus, i2c_addr, layout_ver,
field_name, field_value);
}
#endif
static int do_eeprom(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
const char *const fmt =
"\nEEPROM @0x%lX %s: addr %08lx off %04lx count %ld ... ";
char * const *args = &argv[2];
int rcode;
ulong dev_addr, addr, off, cnt;
#ifdef CONFIG_CMD_EEPROM_LAYOUT
if (argc >= 2) {
if (!strcmp(argv[1], "update") || !strcmp(argv[1], "print"))
return do_eeprom_layout(cmdtp, flag, argc, argv);
}
#endif
switch (argc) {
#ifdef CONFIG_SYS_DEF_EEPROM_ADDR
case 5:
dev_addr = CONFIG_SYS_DEF_EEPROM_ADDR;
break;
#endif
case 6:
bus_addr = -1;
dev_addr = simple_strtoul(*args++, NULL, 16);
break;
case 7:
bus_addr = simple_strtoul(*args++, NULL, 16);
dev_addr = simple_strtoul(*args++, NULL, 16);
break;
default:
return CMD_RET_USAGE;
}
addr = simple_strtoul(*args++, NULL, 16);
off = simple_strtoul(*args++, NULL, 16);
cnt = simple_strtoul(*args++, NULL, 16);
printf(fmt, dev_addr, argv[1], addr, off, cnt);
rcode = eeprom_read(dev_addr, off, (uchar *)addr, cnt);
} else if (strcmp(argv[1], "write") == 0) {
printf(fmt, dev_addr, argv[1], addr, off, cnt);
rcode = eeprom_write(dev_addr, off, (uchar *)addr, cnt);
eeprom, 8, 1, do_eeprom,
"read <bus> <devaddr> addr off cnt\n"
"eeprom write <bus> <devaddr> addr off cnt\n"
" - read/write `cnt' bytes from `devaddr` EEPROM at offset `off'"
#ifdef CONFIG_CMD_EEPROM_LAYOUT
"\n"
"eeprom print [-l <layout_version>] bus devaddr\n"
" - Print layout fields and their data in human readable format\n"
"eeprom update [-l <layout_version>] bus devaddr <field_name> <field_value>\n"
" - Update a specific eeprom field with new data.\n"
" The new data must be written in the same human readable format as shown by the print command.\n"
"\n"
"LAYOUT VERSIONS\n"
"The -l option can be used to force the command to interpret the EEPROM data using the chosen layout.\n"
"If the -l option is omitted, the command will auto detect the layout based on the data in the EEPROM.\n"
"The values which can be provided with the -l option are:\n"
CONFIG_EEPROM_LAYOUT_HELP_STRING"\n"
#endif