// SPDX-License-Identifier: GPL-2.0+ /* * Library to support early TI EVM EEPROM handling * * Copyright (C) 2015-2016 Texas Instruments Incorporated - http://www.ti.com/ * Lokesh Vutla * Steve Kipisz */ #include #include #include #include #include #include #include #include "board_detect.h" #if !defined(CONFIG_DM_I2C) /** * ti_i2c_eeprom_init - Initialize an i2c bus and probe for a device * @i2c_bus: i2c bus number to initialize * @dev_addr: Device address to probe for * * Return: 0 on success or corresponding error on failure. */ static int __maybe_unused ti_i2c_eeprom_init(int i2c_bus, int dev_addr) { int rc; if (i2c_bus >= 0) { rc = i2c_set_bus_num(i2c_bus); if (rc) return rc; } return i2c_probe(dev_addr); } /** * ti_i2c_eeprom_read - Read data from an EEPROM * @dev_addr: The device address of the EEPROM * @offset: Offset to start reading in the EEPROM * @ep: Pointer to a buffer to read into * @epsize: Size of buffer * * Return: 0 on success or corresponding result of i2c_read */ static int __maybe_unused ti_i2c_eeprom_read(int dev_addr, int offset, uchar *ep, int epsize) { return i2c_read(dev_addr, offset, 2, ep, epsize); } #endif /** * ti_eeprom_string_cleanup() - Handle eeprom programming errors * @s: eeprom string (should be NULL terminated) * * Some Board manufacturers do not add a NULL termination at the * end of string, instead some binary information is kludged in, hence * convert the string to just printable characters of ASCII chart. */ static void __maybe_unused ti_eeprom_string_cleanup(char *s) { int i, l; l = strlen(s); for (i = 0; i < l; i++, s++) if (*s < ' ' || *s > '~') { *s = 0; break; } } __weak void gpi2c_init(void) { } static int __maybe_unused ti_i2c_eeprom_get(int bus_addr, int dev_addr, u32 header, u32 size, uint8_t *ep) { u32 hdr_read; int rc; #if defined(CONFIG_DM_I2C) struct udevice *dev; struct udevice *bus; rc = uclass_get_device_by_seq(UCLASS_I2C, bus_addr, &bus); if (rc) return rc; rc = i2c_get_chip(bus, dev_addr, 1, &dev); if (rc) return rc; /* * Read the header first then only read the other contents. */ rc = i2c_set_chip_offset_len(dev, 2); if (rc) return rc; rc = dm_i2c_read(dev, 0, (uint8_t *)&hdr_read, 4); if (rc) return rc; /* Corrupted data??? */ if (hdr_read != header) { rc = dm_i2c_read(dev, 0, (uint8_t *)&hdr_read, 4); /* * read the eeprom header using i2c again, but use only a * 1 byte address (some legacy boards need this..) */ if (rc) { rc = i2c_set_chip_offset_len(dev, 1); if (rc) return rc; rc = dm_i2c_read(dev, 0, (uint8_t *)&hdr_read, 4); } if (rc) return rc; } if (hdr_read != header) return -1; rc = dm_i2c_read(dev, 0, ep, size); if (rc) return rc; #else u32 byte; gpi2c_init(); rc = ti_i2c_eeprom_init(bus_addr, dev_addr); if (rc) return rc; /* * Read the header first then only read the other contents. */ byte = 2; rc = i2c_read(dev_addr, 0x0, byte, (uint8_t *)&hdr_read, 4); if (rc) return rc; /* Corrupted data??? */ if (hdr_read != header) { rc = i2c_read(dev_addr, 0x0, byte, (uint8_t *)&hdr_read, 4); /* * read the eeprom header using i2c again, but use only a * 1 byte address (some legacy boards need this..) */ byte = 1; if (rc) { rc = i2c_read(dev_addr, 0x0, byte, (uint8_t *)&hdr_read, 4); } if (rc) return rc; } if (hdr_read != header) return -1; rc = i2c_read(dev_addr, 0x0, byte, ep, size); if (rc) return rc; #endif return 0; } int __maybe_unused ti_i2c_eeprom_am_set(const char *name, const char *rev) { struct ti_common_eeprom *ep; if (!name || !rev) return -1; ep = TI_EEPROM_DATA; if (ep->header == TI_EEPROM_HEADER_MAGIC) goto already_set; /* Set to 0 all fields */ memset(ep, 0, sizeof(*ep)); strncpy(ep->name, name, TI_EEPROM_HDR_NAME_LEN); strncpy(ep->version, rev, TI_EEPROM_HDR_REV_LEN); /* Some dummy serial number to identify the platform */ strncpy(ep->serial, "0000", TI_EEPROM_HDR_SERIAL_LEN); /* Mark it with a valid header */ ep->header = TI_EEPROM_HEADER_MAGIC; already_set: return 0; } int __maybe_unused ti_i2c_eeprom_am_get(int bus_addr, int dev_addr) { int rc; struct ti_am_eeprom am_ep; struct ti_common_eeprom *ep; ep = TI_EEPROM_DATA; #ifndef CONFIG_SPL_BUILD if (ep->header == TI_EEPROM_HEADER_MAGIC) return 0; /* EEPROM has already been read */ #endif /* Initialize with a known bad marker for i2c fails.. */ ep->header = TI_DEAD_EEPROM_MAGIC; ep->name[0] = 0x0; ep->version[0] = 0x0; ep->serial[0] = 0x0; ep->config[0] = 0x0; rc = ti_i2c_eeprom_get(bus_addr, dev_addr, TI_EEPROM_HEADER_MAGIC, sizeof(am_ep), (uint8_t *)&am_ep); if (rc) return rc; ep->header = am_ep.header; strlcpy(ep->name, am_ep.name, TI_EEPROM_HDR_NAME_LEN + 1); ti_eeprom_string_cleanup(ep->name); /* BeagleBone Green '1' eeprom, board_rev: 0x1a 0x00 0x00 0x00 */ if (am_ep.version[0] == 0x1a && am_ep.version[1] == 0x00 && am_ep.version[2] == 0x00 && am_ep.version[3] == 0x00) strlcpy(ep->version, "BBG1", TI_EEPROM_HDR_REV_LEN + 1); else strlcpy(ep->version, am_ep.version, TI_EEPROM_HDR_REV_LEN + 1); ti_eeprom_string_cleanup(ep->version); strlcpy(ep->serial, am_ep.serial, TI_EEPROM_HDR_SERIAL_LEN + 1); ti_eeprom_string_cleanup(ep->serial); strlcpy(ep->config, am_ep.config, TI_EEPROM_HDR_CONFIG_LEN + 1); ti_eeprom_string_cleanup(ep->config); memcpy(ep->mac_addr, am_ep.mac_addr, TI_EEPROM_HDR_NO_OF_MAC_ADDR * TI_EEPROM_HDR_ETH_ALEN); return 0; } int __maybe_unused ti_i2c_eeprom_dra7_get(int bus_addr, int dev_addr) { int rc, offset = 0; struct dra7_eeprom dra7_ep; struct ti_common_eeprom *ep; ep = TI_EEPROM_DATA; #ifndef CONFIG_SPL_BUILD if (ep->header == DRA7_EEPROM_HEADER_MAGIC) return 0; /* EEPROM has already been read */ #endif /* Initialize with a known bad marker for i2c fails.. */ ep->header = TI_DEAD_EEPROM_MAGIC; ep->name[0] = 0x0; ep->version[0] = 0x0; ep->serial[0] = 0x0; ep->config[0] = 0x0; ep->emif1_size = 0; ep->emif2_size = 0; rc = ti_i2c_eeprom_get(bus_addr, dev_addr, DRA7_EEPROM_HEADER_MAGIC, sizeof(dra7_ep), (uint8_t *)&dra7_ep); if (rc) return rc; ep->header = dra7_ep.header; strlcpy(ep->name, dra7_ep.name, TI_EEPROM_HDR_NAME_LEN + 1); ti_eeprom_string_cleanup(ep->name); offset = dra7_ep.version_major - 1; /* Rev F is skipped */ if (offset >= 5) offset = offset + 1; snprintf(ep->version, TI_EEPROM_HDR_REV_LEN + 1, "%c.%d", 'A' + offset, dra7_ep.version_minor); ti_eeprom_string_cleanup(ep->version); ep->emif1_size = (u64)dra7_ep.emif1_size; ep->emif2_size = (u64)dra7_ep.emif2_size; strlcpy(ep->config, dra7_ep.config, TI_EEPROM_HDR_CONFIG_LEN + 1); ti_eeprom_string_cleanup(ep->config); return 0; } static int ti_i2c_eeprom_am6_parse_record(struct ti_am6_eeprom_record *record, struct ti_am6_eeprom *ep, char **mac_addr, u8 mac_addr_max_cnt, u8 *mac_addr_cnt) { switch (record->header.id) { case TI_AM6_EEPROM_RECORD_BOARD_INFO: if (record->header.len != sizeof(record->data.board_info)) return -EINVAL; if (!ep) break; /* Populate (and clean, if needed) the board name */ strlcpy(ep->name, record->data.board_info.name, sizeof(ep->name)); ti_eeprom_string_cleanup(ep->name); /* Populate selected other fields from the board info record */ strlcpy(ep->version, record->data.board_info.version, sizeof(ep->version)); strlcpy(ep->software_revision, record->data.board_info.software_revision, sizeof(ep->software_revision)); strlcpy(ep->serial, record->data.board_info.serial, sizeof(ep->serial)); break; case TI_AM6_EEPROM_RECORD_MAC_INFO: if (record->header.len != sizeof(record->data.mac_info)) return -EINVAL; if (!mac_addr || !mac_addr_max_cnt) break; *mac_addr_cnt = ((record->data.mac_info.mac_control & TI_AM6_EEPROM_MAC_ADDR_COUNT_MASK) >> TI_AM6_EEPROM_MAC_ADDR_COUNT_SHIFT) + 1; /* * The EEPROM can (but may not) hold a very large amount * of MAC addresses, by far exceeding what we want/can store * in the common memory array, so only grab what we can fit. * Note that a value of 0 means 1 MAC address, and so on. */ *mac_addr_cnt = min(*mac_addr_cnt, mac_addr_max_cnt); memcpy(mac_addr, record->data.mac_info.mac_addr, *mac_addr_cnt * TI_EEPROM_HDR_ETH_ALEN); break; case 0x00: /* Illegal value... Fall through... */ case 0xFF: /* Illegal value... Something went horribly wrong... */ return -EINVAL; default: pr_warn("%s: Ignoring record id %u\n", __func__, record->header.id); } return 0; } int __maybe_unused ti_i2c_eeprom_am6_get(int bus_addr, int dev_addr, struct ti_am6_eeprom *ep, char **mac_addr, u8 mac_addr_max_cnt, u8 *mac_addr_cnt) { struct udevice *dev; struct udevice *bus; unsigned int eeprom_addr; struct ti_am6_eeprom_record_board_id board_id; struct ti_am6_eeprom_record record; int rc; /* Initialize with a known bad marker for i2c fails.. */ memset(ep, 0, sizeof(*ep)); ep->header = TI_DEAD_EEPROM_MAGIC; /* Read the board ID record which is always the first EEPROM record */ rc = ti_i2c_eeprom_get(bus_addr, dev_addr, TI_EEPROM_HEADER_MAGIC, sizeof(board_id), (uint8_t *)&board_id); if (rc) return rc; if (board_id.header.id != TI_AM6_EEPROM_RECORD_BOARD_ID) { pr_err("%s: Invalid board ID record!\n", __func__); return -EINVAL; } /* Establish DM handle to board config EEPROM */ rc = uclass_get_device_by_seq(UCLASS_I2C, bus_addr, &bus); if (rc) return rc; rc = i2c_get_chip(bus, dev_addr, 1, &dev); if (rc) return rc; ep->header = TI_EEPROM_HEADER_MAGIC; /* Ready to parse TLV structure. Initialize variables... */ *mac_addr_cnt = 0; /* * After the all-encompassing board ID record all other records follow * a TLV-type scheme. Point to the first such record and then start * parsing those one by one. */ eeprom_addr = sizeof(board_id); while (true) { rc = dm_i2c_read(dev, eeprom_addr, (uint8_t *)&record.header, sizeof(record.header)); if (rc) return rc; /* * Check for end of list marker. If we reached it don't go * any further and stop parsing right here. */ if (record.header.id == TI_AM6_EEPROM_RECORD_END_LIST) break; eeprom_addr += sizeof(record.header); debug("%s: dev_addr=0x%02x header.id=%u header.len=%u\n", __func__, dev_addr, record.header.id, record.header.len); /* Read record into memory if it fits */ if (record.header.len <= sizeof(record.data)) { rc = dm_i2c_read(dev, eeprom_addr, (uint8_t *)&record.data, record.header.len); if (rc) return rc; /* Process record */ rc = ti_i2c_eeprom_am6_parse_record(&record, ep, mac_addr, mac_addr_max_cnt, mac_addr_cnt); if (rc) { pr_err("%s: EEPROM parsing error!\n", __func__); return rc; } } else { /* * We may get here in case of larger records which * are not yet understood. */ pr_err("%s: Ignoring record id %u\n", __func__, record.header.id); } eeprom_addr += record.header.len; } return 0; } int __maybe_unused ti_i2c_eeprom_am6_get_base(int bus_addr, int dev_addr) { struct ti_am6_eeprom *ep = TI_AM6_EEPROM_DATA; int ret; /* * Always execute EEPROM read by not allowing to bypass it during the * first invocation of SPL which happens on the R5 core. */ #if !(defined(CONFIG_SPL_BUILD) && defined(CONFIG_CPU_V7R)) if (ep->header == TI_EEPROM_HEADER_MAGIC) { debug("%s: EEPROM has already been read\n", __func__); return 0; } #endif ret = ti_i2c_eeprom_am6_get(bus_addr, dev_addr, ep, (char **)ep->mac_addr, AM6_EEPROM_HDR_NO_OF_MAC_ADDR, &ep->mac_addr_cnt); return ret; } bool __maybe_unused board_ti_is(char *name_tag) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (ep->header == TI_DEAD_EEPROM_MAGIC) return false; return !strncmp(ep->name, name_tag, TI_EEPROM_HDR_NAME_LEN); } bool __maybe_unused board_ti_rev_is(char *rev_tag, int cmp_len) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; int l; if (ep->header == TI_DEAD_EEPROM_MAGIC) return false; l = cmp_len > TI_EEPROM_HDR_REV_LEN ? TI_EEPROM_HDR_REV_LEN : cmp_len; return !strncmp(ep->version, rev_tag, l); } char * __maybe_unused board_ti_get_rev(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; /* if ep->header == TI_DEAD_EEPROM_MAGIC, this is empty already */ return ep->version; } char * __maybe_unused board_ti_get_config(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; /* if ep->header == TI_DEAD_EEPROM_MAGIC, this is empty already */ return ep->config; } char * __maybe_unused board_ti_get_name(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; /* if ep->header == TI_DEAD_EEPROM_MAGIC, this is empty already */ return ep->name; } void __maybe_unused board_ti_get_eth_mac_addr(int index, u8 mac_addr[TI_EEPROM_HDR_ETH_ALEN]) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (ep->header == TI_DEAD_EEPROM_MAGIC) goto fail; if (index < 0 || index >= TI_EEPROM_HDR_NO_OF_MAC_ADDR) goto fail; memcpy(mac_addr, ep->mac_addr[index], TI_EEPROM_HDR_ETH_ALEN); return; fail: memset(mac_addr, 0, TI_EEPROM_HDR_ETH_ALEN); } void __maybe_unused board_ti_am6_get_eth_mac_addr(int index, u8 mac_addr[TI_EEPROM_HDR_ETH_ALEN]) { struct ti_am6_eeprom *ep = TI_AM6_EEPROM_DATA; if (ep->header == TI_DEAD_EEPROM_MAGIC) goto fail; if (index < 0 || index >= ep->mac_addr_cnt) goto fail; memcpy(mac_addr, ep->mac_addr[index], TI_EEPROM_HDR_ETH_ALEN); return; fail: memset(mac_addr, 0, TI_EEPROM_HDR_ETH_ALEN); } u64 __maybe_unused board_ti_get_emif1_size(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (ep->header != DRA7_EEPROM_HEADER_MAGIC) return 0; return ep->emif1_size; } u64 __maybe_unused board_ti_get_emif2_size(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (ep->header != DRA7_EEPROM_HEADER_MAGIC) return 0; return ep->emif2_size; } void __maybe_unused set_board_info_env(char *name) { char *unknown = "unknown"; struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (name) env_set("board_name", name); else if (ep->name) env_set("board_name", ep->name); else env_set("board_name", unknown); if (ep->version) env_set("board_rev", ep->version); else env_set("board_rev", unknown); if (ep->serial) env_set("board_serial", ep->serial); else env_set("board_serial", unknown); } void __maybe_unused set_board_info_env_am6(char *name) { char *unknown = "unknown"; struct ti_am6_eeprom *ep = TI_AM6_EEPROM_DATA; if (name) env_set("board_name", name); else if (ep->name) env_set("board_name", ep->name); else env_set("board_name", unknown); if (ep->version) env_set("board_rev", ep->version); else env_set("board_rev", unknown); if (ep->software_revision) env_set("board_software_revision", ep->software_revision); else env_set("board_software_revision", unknown); if (ep->serial) env_set("board_serial", ep->serial); else env_set("board_serial", unknown); } static u64 mac_to_u64(u8 mac[6]) { int i; u64 addr = 0; for (i = 0; i < 6; i++) { addr <<= 8; addr |= mac[i]; } return addr; } static void u64_to_mac(u64 addr, u8 mac[6]) { mac[5] = addr; mac[4] = addr >> 8; mac[3] = addr >> 16; mac[2] = addr >> 24; mac[1] = addr >> 32; mac[0] = addr >> 40; } void board_ti_set_ethaddr(int index) { uint8_t mac_addr[6]; int i; u64 mac1, mac2; u8 mac_addr1[6], mac_addr2[6]; int num_macs; /* * Export any Ethernet MAC addresses from EEPROM. * The 2 MAC addresses in EEPROM define the address range. */ board_ti_get_eth_mac_addr(0, mac_addr1); board_ti_get_eth_mac_addr(1, mac_addr2); if (is_valid_ethaddr(mac_addr1) && is_valid_ethaddr(mac_addr2)) { mac1 = mac_to_u64(mac_addr1); mac2 = mac_to_u64(mac_addr2); /* must contain an address range */ num_macs = mac2 - mac1 + 1; if (num_macs <= 0) return; if (num_macs > 50) { printf("%s: Too many MAC addresses: %d. Limiting to 50\n", __func__, num_macs); num_macs = 50; } for (i = 0; i < num_macs; i++) { u64_to_mac(mac1 + i, mac_addr); if (is_valid_ethaddr(mac_addr)) { eth_env_set_enetaddr_by_index("eth", i + index, mac_addr); } } } } void board_ti_am6_set_ethaddr(int index, int count) { u8 mac_addr[6]; int i; for (i = 0; i < count; i++) { board_ti_am6_get_eth_mac_addr(i, mac_addr); if (is_valid_ethaddr(mac_addr)) eth_env_set_enetaddr_by_index("eth", i + index, mac_addr); } } bool __maybe_unused board_ti_was_eeprom_read(void) { struct ti_common_eeprom *ep = TI_EEPROM_DATA; if (ep->header == TI_EEPROM_HEADER_MAGIC) return true; else return false; }