1// SPDX-License-Identifier: GPL-2.0+ 2/* 3 * Copyright (C) 2023 StarFive Technology Co., Ltd. 4 * Author: Yanhong Wang<yanhong.wang@starfivetech.com> 5 */ 6 7#include <command.h> 8#include <env.h> 9#include <i2c.h> 10#include <init.h> 11#include <u-boot/crc.h> 12#include <linux/delay.h> 13 14#define FORMAT_VERSION 0x2 15#define PCB_VERSION 0xB1 16#define BOM_VERSION 'A' 17/* 18 * BYTES_PER_EEPROM_PAGE: the 24FC04H datasheet says that data can 19 * only be written in page mode, which means 16 bytes at a time: 20 * 16-Byte Page Write Buffer 21 */ 22#define BYTES_PER_EEPROM_PAGE 16 23 24/* 25 * EEPROM_WRITE_DELAY_MS: the 24FC04H datasheet says it takes up to 26 * 5ms to complete a given write: 27 * Write Cycle Time (byte or page) ro Page Write Time 5 ms, Maximum 28 */ 29#define EEPROM_WRITE_DELAY_MS 5000 30/* 31 * StarFive OUI. Registration Date is 20xx-xx-xx 32 */ 33#define STARFIVE_OUI_PREFIX "6C:CF:39:" 34#define STARFIVE_DEFAULT_MAC0 "6C:CF:39:6C:DE:AD" 35#define STARFIVE_DEFAULT_MAC1 "6C:CF:39:6C:DE:AE" 36 37/* Magic number at the first four bytes of EEPROM HATs */ 38#define STARFIVE_EEPROM_HATS_SIG "SFVF" /* StarFive VisionFive */ 39 40#define STARFIVE_EEPROM_HATS_SIZE_MAX 256 /* Header + Atom1&4(v1) */ 41#define STARFIVE_EEPROM_WP_OFFSET 0 /* Read only field */ 42#define STARFIVE_EEPROM_ATOM1_PSTR "VF7110A1-2228-D008E000-00000001\0" 43#define STARFIVE_EEPROM_ATOM1_PSTR_SIZE 32 44#define STARFIVE_EEPROM_ATOM1_SN_OFFSET 23 45#define STARFIVE_EEPROM_ATOM1_VSTR "StarFive Technology Co., Ltd.\0\0\0" 46#define STARFIVE_EEPROM_ATOM1_VSTR_SIZE 32 47 48#define MAGIC_NUMBER_BYTES 4 49#define MAC_ADDR_BYTES 6 50#define MAC_ADDR_STRLEN 17 51 52/* 53 * Atom Types 54 * 0x0000 = invalid 55 * 0x0001 = vendor info 56 * 0x0002 = GPIO map 57 * 0x0003 = Linux device tree blob 58 * 0x0004 = manufacturer custom data 59 * 0x0005-0xfffe = reserved for future use 60 * 0xffff = invalid 61 */ 62 63#define HATS_ATOM_INVALID 0x0000 64#define HATS_ATOM_VENDOR 0x0001 65#define HATS_ATOM_GPIO 0x0002 66#define HATS_ATOM_DTB 0x0003 67#define HATS_ATOM_CUSTOM 0x0004 68#define HATS_ATOM_INVALID_END 0xffff 69 70struct eeprom_header { 71 char signature[MAGIC_NUMBER_BYTES]; /* ASCII table signature */ 72 u8 version; /* EEPROM data format version */ 73 /* (0x00 reserved, 0x01 = first version) */ 74 u8 reversed; /* 0x00, Reserved field */ 75 u16 numatoms; /* total atoms in EEPROM */ 76 u32 eeplen; /* total length in bytes of all eeprom data */ 77 /* (including this header) */ 78}; 79 80struct eeprom_atom_header { 81 u16 type; 82 u16 count; 83 u32 dlen; 84}; 85 86struct eeprom_atom1_data { 87 u8 uuid[16]; 88 u16 pid; 89 u16 pver; 90 u8 vslen; 91 u8 pslen; 92 uchar vstr[STARFIVE_EEPROM_ATOM1_VSTR_SIZE]; 93 uchar pstr[STARFIVE_EEPROM_ATOM1_PSTR_SIZE]; /* product SN */ 94}; 95 96struct starfive_eeprom_atom1 { 97 struct eeprom_atom_header header; 98 struct eeprom_atom1_data data; 99 u16 crc; 100}; 101 102struct eeprom_atom4_data { 103 u16 version; 104 u8 pcb_revision; /* PCB version */ 105 u8 bom_revision; /* BOM version */ 106 u8 mac0_addr[MAC_ADDR_BYTES]; /* Ethernet0 MAC */ 107 u8 mac1_addr[MAC_ADDR_BYTES]; /* Ethernet1 MAC */ 108 u8 reserved[2]; 109}; 110 111struct starfive_eeprom_atom4 { 112 struct eeprom_atom_header header; 113 struct eeprom_atom4_data data; 114 u16 crc; 115}; 116 117struct starfive_eeprom { 118 struct eeprom_header header; 119 struct starfive_eeprom_atom1 atom1; 120 struct starfive_eeprom_atom4 atom4; 121}; 122 123static union { 124 struct starfive_eeprom eeprom; 125 uchar buf[STARFIVE_EEPROM_HATS_SIZE_MAX]; 126} pbuf __section(".data"); 127 128/* Set to 1 if we've read EEPROM into memory */ 129static int has_been_read __section(".data"); 130 131static inline int is_match_magic(void) 132{ 133 return strncmp(pbuf.eeprom.header.signature, STARFIVE_EEPROM_HATS_SIG, 134 MAGIC_NUMBER_BYTES); 135} 136 137/* Calculate the current CRC */ 138static inline u32 calculate_crc16(struct eeprom_atom_header *head) 139{ 140 uint len = sizeof(struct eeprom_atom_header) + head->dlen - sizeof(u16); 141 142 return crc16(0, (void *)head, len); 143} 144 145/* This function should be called after each update to the EEPROM structure */ 146static inline void update_crc(void) 147{ 148 pbuf.eeprom.atom1.crc = calculate_crc16(&pbuf.eeprom.atom1.header); 149 pbuf.eeprom.atom4.crc = calculate_crc16(&pbuf.eeprom.atom4.header); 150} 151 152static void dump_raw_eeprom(void) 153{ 154 unsigned int i; 155 u32 len; 156 157 len = sizeof(struct starfive_eeprom); 158 for (i = 0; i < len; i++) { 159 if ((i % 16) == 0) 160 printf("%02X: ", i); 161 printf("%02X ", ((u8 *)pbuf.buf)[i]); 162 if (((i % 16) == 15) || (i == len - 1)) 163 printf("\n"); 164 } 165} 166 167/** 168 * show_eeprom - display the contents of the EEPROM 169 */ 170static void show_eeprom(void) 171{ 172 if (has_been_read != 1) 173 return; 174 175 printf("\n--------EEPROM INFO--------\n"); 176 printf("Vendor : %s\n", pbuf.eeprom.atom1.data.vstr); 177 printf("Product full SN: %s\n", pbuf.eeprom.atom1.data.pstr); 178 printf("data version: 0x%x\n", pbuf.eeprom.atom4.data.version); 179 if (pbuf.eeprom.atom4.data.version == 2) { 180 printf("PCB revision: 0x%x\n", pbuf.eeprom.atom4.data.pcb_revision); 181 printf("BOM revision: %c\n", pbuf.eeprom.atom4.data.bom_revision); 182 printf("Ethernet MAC0 address: %02x:%02x:%02x:%02x:%02x:%02x\n", 183 pbuf.eeprom.atom4.data.mac0_addr[0], pbuf.eeprom.atom4.data.mac0_addr[1], 184 pbuf.eeprom.atom4.data.mac0_addr[2], pbuf.eeprom.atom4.data.mac0_addr[3], 185 pbuf.eeprom.atom4.data.mac0_addr[4], pbuf.eeprom.atom4.data.mac0_addr[5]); 186 printf("Ethernet MAC1 address: %02x:%02x:%02x:%02x:%02x:%02x\n", 187 pbuf.eeprom.atom4.data.mac1_addr[0], pbuf.eeprom.atom4.data.mac1_addr[1], 188 pbuf.eeprom.atom4.data.mac1_addr[2], pbuf.eeprom.atom4.data.mac1_addr[3], 189 pbuf.eeprom.atom4.data.mac1_addr[4], pbuf.eeprom.atom4.data.mac1_addr[5]); 190 } else { 191 printf("Custom data v%d is not Supported\n", pbuf.eeprom.atom4.data.version); 192 dump_raw_eeprom(); 193 } 194 printf("--------EEPROM INFO--------\n\n"); 195} 196 197/** 198 * set_mac_address() - stores a MAC address into the local EEPROM copy 199 * 200 * This function takes a pointer to MAC address string 201 * (i.e."XX:XX:XX:XX:XX:XX", where "XX" is a two-digit hex number), 202 * stores it in the MAC address field of the EEPROM local copy, and 203 * updates the local copy of the CRC. 204 */ 205static void set_mac_address(char *string, int index) 206{ 207 u8 i; 208 u8 *mac; 209 210 if (strncasecmp(STARFIVE_OUI_PREFIX, string, 211 strlen(STARFIVE_OUI_PREFIX))) { 212 printf("The MAC address doesn't match StarFive OUI %s\n", 213 STARFIVE_OUI_PREFIX); 214 return; 215 } 216 mac = (index == 0) ? pbuf.eeprom.atom4.data.mac0_addr : 217 pbuf.eeprom.atom4.data.mac1_addr; 218 219 for (i = 0; *string && (i < MAC_ADDR_BYTES); i++) { 220 mac[i] = hextoul(string, &string); 221 222 if (*string == ':') 223 string++; 224 } 225 226 update_crc(); 227} 228 229/** 230 * init_local_copy() - initialize the in-memory EEPROM copy 231 * 232 * Initialize the in-memory EEPROM copy with the magic number. Must 233 * be done when preparing to initialize a blank EEPROM, or overwrite 234 * one with a corrupted magic number. 235 */ 236static void init_local_copy(void) 237{ 238 memset((void *)pbuf.buf, 0, sizeof(struct starfive_eeprom)); 239 memcpy(pbuf.eeprom.header.signature, STARFIVE_EEPROM_HATS_SIG, 240 strlen(STARFIVE_EEPROM_HATS_SIG)); 241 pbuf.eeprom.header.version = FORMAT_VERSION; 242 pbuf.eeprom.header.numatoms = 2; 243 pbuf.eeprom.header.eeplen = sizeof(struct starfive_eeprom); 244 245 pbuf.eeprom.atom1.header.type = HATS_ATOM_VENDOR; 246 pbuf.eeprom.atom1.header.count = 1; 247 pbuf.eeprom.atom1.header.dlen = sizeof(struct eeprom_atom1_data) + sizeof(u16); 248 pbuf.eeprom.atom1.data.vslen = STARFIVE_EEPROM_ATOM1_VSTR_SIZE; 249 pbuf.eeprom.atom1.data.pslen = STARFIVE_EEPROM_ATOM1_PSTR_SIZE; 250 memcpy(pbuf.eeprom.atom1.data.vstr, STARFIVE_EEPROM_ATOM1_VSTR, 251 strlen(STARFIVE_EEPROM_ATOM1_VSTR)); 252 memcpy(pbuf.eeprom.atom1.data.pstr, STARFIVE_EEPROM_ATOM1_PSTR, 253 strlen(STARFIVE_EEPROM_ATOM1_PSTR)); 254 255 pbuf.eeprom.atom4.header.type = HATS_ATOM_CUSTOM; 256 pbuf.eeprom.atom4.header.count = 2; 257 pbuf.eeprom.atom4.header.dlen = sizeof(struct eeprom_atom4_data) + sizeof(u16); 258 pbuf.eeprom.atom4.data.version = FORMAT_VERSION; 259 pbuf.eeprom.atom4.data.pcb_revision = PCB_VERSION; 260 pbuf.eeprom.atom4.data.bom_revision = BOM_VERSION; 261 set_mac_address(STARFIVE_DEFAULT_MAC0, 0); 262 set_mac_address(STARFIVE_DEFAULT_MAC1, 1); 263} 264 265/** 266 * prog_eeprom() - write the EEPROM from memory 267 */ 268static int prog_eeprom(unsigned int size) 269{ 270 unsigned int i; 271 void *p; 272 uchar tmp_buff[STARFIVE_EEPROM_HATS_SIZE_MAX]; 273 struct udevice *dev; 274 int ret; 275 276 if (is_match_magic()) { 277 printf("MAGIC ERROR, Please check the data@%p.\n", pbuf.buf); 278 return -1; 279 } 280 281 ret = i2c_get_chip_for_busnum(CONFIG_SYS_EEPROM_BUS_NUM, 282 CONFIG_SYS_I2C_EEPROM_ADDR, 283 CONFIG_SYS_I2C_EEPROM_ADDR_LEN, 284 &dev); 285 if (ret) { 286 printf("Get i2c bus:%d addr:%d fail.\n", CONFIG_SYS_EEPROM_BUS_NUM, 287 CONFIG_SYS_I2C_EEPROM_ADDR); 288 return ret; 289 } 290 291 for (i = 0, p = (u8 *)pbuf.buf; i < size; ) { 292 if (!ret) 293 ret = dm_i2c_write(dev, i, p, min((int)(size - i), 294 BYTES_PER_EEPROM_PAGE)); 295 if (ret) 296 break; 297 298 udelay(EEPROM_WRITE_DELAY_MS); 299 i += BYTES_PER_EEPROM_PAGE; 300 p += BYTES_PER_EEPROM_PAGE; 301 } 302 303 if (!ret) { 304 /* Verify the write by reading back the EEPROM and comparing */ 305 ret = dm_i2c_read(dev, 306 STARFIVE_EEPROM_WP_OFFSET, 307 tmp_buff, 308 STARFIVE_EEPROM_HATS_SIZE_MAX); 309 if (!ret && memcmp((void *)pbuf.buf, (void *)tmp_buff, 310 STARFIVE_EEPROM_HATS_SIZE_MAX)) 311 ret = -1; 312 } 313 314 if (ret) { 315 has_been_read = -1; 316 printf("Programming failed.\n"); 317 return -1; 318 } 319 320 printf("Programming passed.\n"); 321 return 0; 322} 323 324/** 325 * read_eeprom() - read the EEPROM into memory, if it hasn't been read already 326 */ 327static int read_eeprom(void) 328{ 329 int ret; 330 struct udevice *dev; 331 332 if (has_been_read == 1) 333 return 0; 334 335 ret = i2c_get_chip_for_busnum(CONFIG_SYS_EEPROM_BUS_NUM, 336 CONFIG_SYS_I2C_EEPROM_ADDR, 1, &dev); 337 if (!ret) 338 ret = dm_i2c_read(dev, 0, (u8 *)pbuf.buf, 339 STARFIVE_EEPROM_HATS_SIZE_MAX); 340 341 has_been_read = (ret == 0) ? 1 : 0; 342 343 return ret; 344} 345 346/** 347 * set_pcb_revision() - stores a StarFive PCB revision into the local EEPROM copy 348 * 349 * Takes a pointer to a string representing the numeric PCB revision in 350 * decimal ("0" - "255"), stores it in the pcb_revision field of the 351 * EEPROM local copy, and updates the CRC of the local copy. 352 */ 353static void set_pcb_revision(char *string) 354{ 355 u32 p; 356 357 p = simple_strtoul(string, &string, 16); 358 if (p > U8_MAX) { 359 printf("%s must not be greater than %d\n", "PCB revision", 360 U8_MAX); 361 return; 362 } 363 364 pbuf.eeprom.atom4.data.pcb_revision = p; 365 366 update_crc(); 367} 368 369/** 370 * set_bom_revision() - stores a StarFive BOM revision into the local EEPROM copy 371 * 372 * Takes a pointer to a uppercase ASCII character representing the BOM 373 * revision ("A" - "Z"), stores it in the bom_revision field of the 374 * EEPROM local copy, and updates the CRC of the local copy. 375 */ 376static void set_bom_revision(char *string) 377{ 378 if (string[0] < 'A' || string[0] > 'Z') { 379 printf("BOM revision must be an uppercase letter between A and Z\n"); 380 return; 381 } 382 383 pbuf.eeprom.atom4.data.bom_revision = string[0]; 384 385 update_crc(); 386} 387 388/** 389 * set_product_id() - stores a StarFive product ID into the local EEPROM copy 390 * 391 * Takes a pointer to a string representing the numeric product ID in 392 * string ("VF7100A1-2150-D008E000-00000001\0"), stores it in the product string 393 * field of the EEPROM local copy, and updates the CRC of the local copy. 394 */ 395static void set_product_id(char *string) 396{ 397 u32 len; 398 399 len = (strlen(string) > STARFIVE_EEPROM_ATOM1_PSTR_SIZE) ? 400 STARFIVE_EEPROM_ATOM1_PSTR_SIZE : strlen(string); 401 402 memcpy((void *)pbuf.eeprom.atom1.data.pstr, (void *)string, len); 403 404 update_crc(); 405} 406 407/** 408 * set_vendor() - set vendor name 409 * 410 * Takes a pointer to a string representing the vendor name, e.g. 411 * "StarFive Technology Co., Ltd.", stores it in the vendor field 412 * of the EEPROM local copy, and updates the CRC of the local copy. 413 */ 414static void set_vendor(char *string) 415{ 416 memset(pbuf.eeprom.atom1.data.vstr, 0, 417 sizeof(pbuf.eeprom.atom1.data.vstr)); 418 419 strncpy(pbuf.eeprom.atom1.data.vstr, 420 string, sizeof(pbuf.eeprom.atom1.data.vstr) - 1); 421 422 update_crc(); 423} 424 425const char *get_product_id_from_eeprom(void) 426{ 427 if (read_eeprom()) 428 return NULL; 429 430 return pbuf.eeprom.atom1.data.pstr; 431} 432 433int do_mac(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) 434{ 435 char *cmd; 436 437 if (argc == 1) { 438 show_eeprom(); 439 return 0; 440 } 441 442 if (argc > 3) 443 return CMD_RET_USAGE; 444 445 cmd = argv[1]; 446 447 /* Commands with no argument */ 448 if (!strcmp(cmd, "read_eeprom")) { 449 has_been_read = 0; 450 return read_eeprom(); 451 } else if (!strcmp(cmd, "initialize")) { 452 init_local_copy(); 453 return 0; 454 } else if (!strcmp(cmd, "write_eeprom")) { 455 return prog_eeprom(STARFIVE_EEPROM_HATS_SIZE_MAX); 456 } else if (!strcmp(cmd, "raw")) { 457 dump_raw_eeprom(); 458 return 0; 459 } 460 461 if (argc != 3) 462 return CMD_RET_USAGE; 463 464 if (is_match_magic()) { 465 printf("Please read the EEPROM ('read_eeprom') and/or initialize the EEPROM ('initialize') first.\n"); 466 return 0; 467 } 468 469 if (!strcmp(cmd, "mac0_address")) { 470 set_mac_address(argv[2], 0); 471 return 0; 472 } else if (!strcmp(cmd, "mac1_address")) { 473 set_mac_address(argv[2], 1); 474 return 0; 475 } else if (!strcmp(cmd, "pcb_revision")) { 476 set_pcb_revision(argv[2]); 477 return 0; 478 } else if (!strcmp(cmd, "bom_revision")) { 479 set_bom_revision(argv[2]); 480 return 0; 481 } else if (!strcmp(cmd, "product_id")) { 482 set_product_id(argv[2]); 483 return 0; 484 } else if (!strcmp(cmd, "vendor")) { 485 set_vendor(argv[2]); 486 return 0; 487 } 488 489 return CMD_RET_USAGE; 490} 491 492/** 493 * mac_read_from_eeprom() - read the MAC address & the serial number in EEPROM 494 * 495 * This function reads the MAC address and the serial number from EEPROM and 496 * sets the appropriate environment variables for each one read. 497 * 498 * The environment variables are only set if they haven't been set already. 499 * This ensures that any user-saved variables are never overwritten. 500 * 501 * If CONFIG_ID_EEPROM is enabled, this function will be called in 502 * "static init_fnc_t init_sequence_r[]" of u-boot/common/board_r.c. 503 */ 504int mac_read_from_eeprom(void) 505{ 506 /** 507 * try to fill the buff from EEPROM, 508 * always return SUCCESS, even some error happens. 509 */ 510 if (read_eeprom()) { 511 dump_raw_eeprom(); 512 return 0; 513 } 514 515 // 1, setup ethaddr env 516 eth_env_set_enetaddr("ethaddr", pbuf.eeprom.atom4.data.mac0_addr); 517 eth_env_set_enetaddr("eth1addr", pbuf.eeprom.atom4.data.mac1_addr); 518 519 /** 520 * 2, setup serial# env, reference to hifive-platform-i2c-eeprom.c, 521 * serial# can be a ASCII string, but not just a hex number, so we 522 * setup serial# in the 32Byte format: 523 * "VF7100A1-2201-D008E000-00000001;" 524 * "<product>-<date>-<DDR&eMMC>-<serial_number>" 525 * <date>: 4Byte, should be the output of `date +%y%W` 526 * <DDR&eMMC>: 8Byte, "D008" means 8GB, "D01T" means 1TB; 527 * "E000" means no eMMC, "E032" means 32GB, "E01T" means 1TB. 528 * <serial_number>: 8Byte, the Unique Identifier of board in hex. 529 */ 530 if (!env_get("serial#")) 531 env_set("serial#", pbuf.eeprom.atom1.data.pstr); 532 533 printf("StarFive EEPROM format v%u\n", pbuf.eeprom.header.version); 534 show_eeprom(); 535 return 0; 536} 537 538/** 539 * get_pcb_revision_from_eeprom - get the PCB revision 540 * 541 * 1.2A return 'A'/'a', 1.3B return 'B'/'b',other values are illegal 542 */ 543u8 get_pcb_revision_from_eeprom(void) 544{ 545 u8 pv = 0xFF; 546 547 if (read_eeprom()) 548 return pv; 549 550 return pbuf.eeprom.atom1.data.pstr[6]; 551} 552 553/** 554 * get_ddr_size_from_eeprom - get the DDR size 555 * pstr: VF7110A1-2228-D008E000-00000001 556 * VF7110A1/VF7110B1 : VisionFive JH7110A /VisionFive JH7110B 557 * D008: 8GB LPDDR4 558 * E000: No emmc device, ECxx: include emmc device, xx: Capacity size[GB] 559 * return: the field of 'D008E000' 560 */ 561 562u32 get_ddr_size_from_eeprom(void) 563{ 564 u32 pv = 0xFFFFFFFF; 565 566 if (read_eeprom()) 567 return pv; 568 569 return hextoul(&pbuf.eeprom.atom1.data.pstr[14], NULL); 570} 571 572u32 get_mmc_size_from_eeprom(void) 573{ 574 u32 size; 575 576 if (IS_ENABLED(CONFIG_STARFIVE_NO_EMMC)) 577 return 0; 578 579 if (read_eeprom()) 580 return 0; 581 582 size = dectoul(&pbuf.eeprom.atom1.data.pstr[19], NULL); 583 584 if (pbuf.eeprom.atom1.data.pstr[21] == 'T') 585 size <<= 10; 586 587 return size; 588} 589 590U_BOOT_LONGHELP(mac, 591 "\n" 592 " - display EEPROM content\n" 593 "mac read_eeprom\n" 594 " - read EEPROM content into memory data structure\n" 595 "mac write_eeprom\n" 596 " - save memory data structure to the EEPROM\n" 597 "mac initialize\n" 598 " - initialize the in-memory EEPROM copy with default data\n" 599 "mac raw\n" 600 " - hexdump memory data structure\n" 601 "mac mac0_address <xx:xx:xx:xx:xx:xx>\n" 602 " - stores a MAC0 address into the local EEPROM copy\n" 603 "mac mac1_address <xx:xx:xx:xx:xx:xx>\n" 604 " - stores a MAC1 address into the local EEPROM copy\n" 605 "mac pcb_revision <?>\n" 606 " - stores a StarFive PCB revision into the local EEPROM copy\n" 607 "mac bom_revision <A>\n" 608 " - stores a StarFive BOM revision into the local EEPROM copy\n" 609 "mac product_id <VF7110A1-2228-D008E000-xxxxxxxx>\n" 610 " - stores a StarFive product ID into the local EEPROM copy\n" 611 "mac vendor <Vendor Name>\n" 612 " - set vendor string\n"); 613 614U_BOOT_CMD( 615 mac, 3, 1, do_mac, 616 "display and program the board revision and MAC address in EEPROM", 617 mac_help_text); 618