| // SPDX-License-Identifier: GPL-2.0-or-later | |
| /* | |
| * Copyright (C) 2023 PHYTEC Messtechnik GmbH | |
| * Author: Teresa Remmet <t.remmet@phytec.de> | |
| */ | |
| #include <dm/device.h> | |
| #include <dm/uclass.h> | |
| #include <i2c.h> | |
| #include <u-boot/crc.h> | |
| #include <malloc.h> | |
| #include <extension_board.h> | |
| #include "phytec_som_detection.h" | |
| struct phytec_eeprom_data eeprom_data; | |
| #if IS_ENABLED(CONFIG_PHYTEC_SOM_DETECTION) | |
| int phytec_eeprom_data_setup_fallback(struct phytec_eeprom_data *data, | |
| int bus_num, int addr, int addr_fallback) | |
| { | |
| int ret; | |
| ret = phytec_eeprom_data_init(data, bus_num, addr); | |
| if (ret) { | |
| pr_err("%s: init failed. Trying fall back address 0x%x\n", | |
| __func__, addr_fallback); | |
| ret = phytec_eeprom_data_init(data, bus_num, addr_fallback); | |
| } | |
| if (ret) | |
| pr_err("%s: EEPROM data init failed\n", __func__); | |
| return ret; | |
| } | |
| int phytec_eeprom_data_setup(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| int ret; | |
| ret = phytec_eeprom_data_init(data, bus_num, addr); | |
| if (ret) | |
| pr_err("%s: EEPROM data init failed\n", __func__); | |
| return ret; | |
| } | |
| int phytec_eeprom_read(u8 *data, int bus_num, int addr, int size, int offset) | |
| { | |
| int ret; | |
| #if CONFIG_IS_ENABLED(DM_I2C) | |
| struct udevice *dev; | |
| ret = i2c_get_chip_for_busnum(bus_num, addr, 2, &dev); | |
| if (ret) { | |
| pr_err("%s: i2c EEPROM not found: %i.\n", __func__, ret); | |
| return ret; | |
| } | |
| ret = dm_i2c_read(dev, offset, (uint8_t *)data, size); | |
| if (ret) { | |
| pr_err("%s: Unable to read EEPROM data: %i\n", __func__, ret); | |
| return ret; | |
| } | |
| #else | |
| i2c_set_bus_num(bus_num); | |
| ret = i2c_read(addr, offset, 2, (uint8_t *)data, size); | |
| #endif | |
| return ret; | |
| } | |
| int phytec_eeprom_data_init_v2(struct phytec_eeprom_data *data) | |
| { | |
| unsigned int crc; | |
| if (!data) | |
| return -1; | |
| crc = crc8(0, (const unsigned char *)&data->payload, PHYTEC_API2_DATA_LEN); | |
| debug("%s: crc: %x\n", __func__, crc); | |
| if (crc) { | |
| pr_err("%s: CRC mismatch. EEPROM data is not usable.\n", | |
| __func__); | |
| return -EINVAL; | |
| } | |
| return 0; | |
| } | |
| #if IS_ENABLED(CONFIG_PHYTEC_SOM_DETECTION_BLOCKS) | |
| int phytec_eeprom_data_init_v3_block(struct phytec_eeprom_data *data, | |
| struct phytec_api3_block_header *header, | |
| u8 *payload) | |
| { | |
| struct phytec_api3_element *element = NULL; | |
| struct phytec_api3_element *list_iterator; | |
| if (!header) | |
| return -1; | |
| if (!payload) | |
| return -1; | |
| debug("%s: block type: %i\n", __func__, header->block_type); | |
| switch (header->block_type) { | |
| case PHYTEC_API3_BLOCK_MAC: | |
| element = phytec_blocks_init_mac(header, payload); | |
| break; | |
| default: | |
| debug("%s: Unknown block type %i\n", __func__, | |
| header->block_type); | |
| } | |
| if (!element) | |
| return -1; | |
| if (!data->payload.block_head) { | |
| data->payload.block_head = element; | |
| return 0; | |
| } | |
| list_iterator = data->payload.block_head; | |
| while (list_iterator && list_iterator->next) | |
| list_iterator = list_iterator->next; | |
| list_iterator->next = element; | |
| return 0; | |
| } | |
| int phytec_eeprom_data_init_v3(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| int ret, i; | |
| struct phytec_api3_header header; | |
| unsigned int crc; | |
| u8 *payload; | |
| int block_addr; | |
| struct phytec_api3_block_header *block_header; | |
| if (!data) | |
| return -1; | |
| ret = phytec_eeprom_read((uint8_t *)&header, bus_num, addr, | |
| PHYTEC_API3_DATA_HEADER_LEN, | |
| PHYTEC_API2_DATA_LEN); | |
| if (ret) { | |
| pr_err("%s: Failed to read API v3 data header.\n", __func__); | |
| goto err; | |
| } | |
| crc = crc8(0, (const unsigned char *)&header, | |
| PHYTEC_API3_DATA_HEADER_LEN); | |
| debug("%s: crc: %x\n", __func__, crc); | |
| if (crc) { | |
| pr_err("%s: CRC mismatch. API3 header is unusable.\n", | |
| __func__); | |
| goto err; | |
| } | |
| debug("%s: data length: %i\n", __func__, header.data_length); | |
| payload = malloc(header.data_length); | |
| if (!payload) { | |
| pr_err("%s: Unable to allocate memory\n", __func__); | |
| goto err_payload; | |
| } | |
| ret = phytec_eeprom_read(payload, bus_num, addr, header.data_length, | |
| PHYTEC_API3_DATA_HEADER_LEN + | |
| PHYTEC_API2_DATA_LEN); | |
| if (ret) { | |
| pr_err("%s: Failed to read API v3 data payload.\n", __func__); | |
| goto err_payload; | |
| } | |
| block_addr = 0; | |
| debug("%s: block count: %i\n", __func__, header.block_count); | |
| for (i = 0; i < header.block_count; i++) { | |
| debug("%s: block_addr: %i\n", __func__, block_addr); | |
| block_header = (struct phytec_api3_block_header *) | |
| &payload[block_addr]; | |
| crc = crc8(0, (const unsigned char *)block_header, | |
| PHYTEC_API3_BLOCK_HEADER_LEN); | |
| debug("%s: crc: %x\n", __func__, crc); | |
| if (crc) { | |
| pr_err("%s: CRC mismatch. API3 block header is unusable\n", | |
| __func__); | |
| goto err_payload; | |
| } | |
| ret = phytec_eeprom_data_init_v3_block(data, block_header, | |
| &payload[block_addr + PHYTEC_API3_BLOCK_HEADER_LEN]); | |
| /* Ignore failed block initialization and continue. */ | |
| if (ret) | |
| debug("%s: Unable to create block with index %i.\n", | |
| __func__, i); | |
| block_addr = block_header->next_block; | |
| } | |
| free(payload); | |
| return 0; | |
| err_payload: | |
| free(payload); | |
| err: | |
| return -1; | |
| } | |
| #else | |
| inline int phytec_eeprom_data_init_v3(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| return 0; | |
| } | |
| #endif | |
| int phytec_eeprom_data_init(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| int ret, i; | |
| u8 *ptr; | |
| if (!data) | |
| data = &eeprom_data; | |
| ret = phytec_eeprom_read((u8 *)data, bus_num, addr, | |
| PHYTEC_API2_DATA_LEN, 0); | |
| if (ret) | |
| goto err; | |
| data->payload.block_head = NULL; | |
| if (data->payload.api_rev == 0xff) { | |
| pr_err("%s: EEPROM is not flashed. Prototype?\n", __func__); | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| ptr = (u8 *)data; | |
| for (i = 0; i < PHYTEC_API2_DATA_LEN; ++i) | |
| if (ptr[i] != 0x0) | |
| break; | |
| if (i == PHYTEC_API2_DATA_LEN) { | |
| pr_err("%s: EEPROM data is all zero. Erased?\n", __func__); | |
| ret = -EINVAL; | |
| goto err; | |
| } | |
| if (data->payload.api_rev >= PHYTEC_API_REV2) { | |
| ret = phytec_eeprom_data_init_v2(data); | |
| if (ret) | |
| goto err; | |
| } | |
| if (IS_ENABLED(CONFIG_PHYTEC_SOM_DETECTION_BLOCKS)) | |
| if (data->payload.api_rev >= PHYTEC_API_REV3) { | |
| ret = phytec_eeprom_data_init_v3(data, bus_num, addr); | |
| if (ret) | |
| goto err; | |
| } | |
| data->valid = true; | |
| return 0; | |
| err: | |
| data->valid = false; | |
| return ret; | |
| } | |
| void __maybe_unused phytec_print_som_info(struct phytec_eeprom_data *data) | |
| { | |
| struct phytec_api2_data *api2; | |
| char pcb_sub_rev; | |
| unsigned int ksp_no, sub_som_type1, sub_som_type2; | |
| if (!data) | |
| data = &eeprom_data; | |
| if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2) | |
| return; | |
| api2 = &data->payload.data.data_api2; | |
| /* Calculate PCB subrevision */ | |
| pcb_sub_rev = api2->pcb_sub_opt_rev & 0x0f; | |
| pcb_sub_rev = pcb_sub_rev ? ((pcb_sub_rev - 1) + 'a') : ' '; | |
| /* print standard product string */ | |
| if (api2->som_type <= 1) { | |
| printf("SoM: %s-%03u-%s.%s PCB rev: %u%c\n", | |
| phytec_som_type_str[api2->som_type], api2->som_no, | |
| api2->opt, api2->bom_rev, api2->pcb_rev, pcb_sub_rev); | |
| return; | |
| } | |
| /* print KSP/KSM string */ | |
| if (api2->som_type <= 3) { | |
| ksp_no = (api2->ksp_no << 8) | api2->som_no; | |
| printf("SoM: %s-%u ", | |
| phytec_som_type_str[api2->som_type], ksp_no); | |
| /* print standard product based KSP/KSM strings */ | |
| } else { | |
| switch (api2->som_type) { | |
| case 4: | |
| sub_som_type1 = 0; | |
| sub_som_type2 = 3; | |
| break; | |
| case 5: | |
| sub_som_type1 = 0; | |
| sub_som_type2 = 2; | |
| break; | |
| case 6: | |
| sub_som_type1 = 1; | |
| sub_som_type2 = 3; | |
| break; | |
| case 7: | |
| sub_som_type1 = 1; | |
| sub_som_type2 = 2; | |
| break; | |
| default: | |
| pr_err("%s: Invalid SoM type: %i", __func__, api2->som_type); | |
| return; | |
| }; | |
| printf("SoM: %s-%03u-%s-%03u ", | |
| phytec_som_type_str[sub_som_type1], | |
| api2->som_no, phytec_som_type_str[sub_som_type2], | |
| api2->ksp_no); | |
| } | |
| printf("Option: %s BOM rev: %s PCB rev: %u%c\n", api2->opt, | |
| api2->bom_rev, api2->pcb_rev, pcb_sub_rev); | |
| } | |
| char * __maybe_unused phytec_get_opt(struct phytec_eeprom_data *data) | |
| { | |
| char *opt; | |
| if (!data) | |
| data = &eeprom_data; | |
| if (!data->valid) | |
| return NULL; | |
| if (data->payload.api_rev < PHYTEC_API_REV2) | |
| opt = data->payload.data.data_api0.opt; | |
| else | |
| opt = data->payload.data.data_api2.opt; | |
| return opt; | |
| } | |
| u8 __maybe_unused phytec_get_rev(struct phytec_eeprom_data *data) | |
| { | |
| struct phytec_api2_data *api2; | |
| if (!data) | |
| data = &eeprom_data; | |
| if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2) | |
| return PHYTEC_EEPROM_INVAL; | |
| api2 = &data->payload.data.data_api2; | |
| return api2->pcb_rev; | |
| } | |
| u8 __maybe_unused phytec_get_som_type(struct phytec_eeprom_data *data) | |
| { | |
| if (!data) | |
| data = &eeprom_data; | |
| if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2) | |
| return PHYTEC_EEPROM_INVAL; | |
| return data->payload.data.data_api2.som_type; | |
| } | |
| #if IS_ENABLED(CONFIG_CMD_EXTENSION) | |
| struct extension *phytec_add_extension(const char *name, const char *overlay, | |
| const char *other) | |
| { | |
| struct extension *extension; | |
| if (strlen(overlay) > sizeof(extension->overlay)) { | |
| pr_err("Overlay name %s is longer than %lu.\n", overlay, | |
| sizeof(extension->overlay)); | |
| return NULL; | |
| } | |
| extension = calloc(1, sizeof(struct extension)); | |
| snprintf(extension->name, sizeof(extension->name), name); | |
| snprintf(extension->overlay, sizeof(extension->overlay), overlay); | |
| snprintf(extension->other, sizeof(extension->other), other); | |
| snprintf(extension->owner, sizeof(extension->owner), "PHYTEC"); | |
| return extension; | |
| } | |
| #endif /* IS_ENABLED(CONFIG_CMD_EXTENSION) */ | |
| struct phytec_api3_element * | |
| __maybe_unused phytec_get_block_head(struct phytec_eeprom_data *data) | |
| { | |
| if (!data) | |
| data = &eeprom_data; | |
| if (!data->valid) | |
| return NULL; | |
| return data->payload.block_head; | |
| } | |
| #else | |
| inline int phytec_eeprom_data_setup(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| return PHYTEC_EEPROM_INVAL; | |
| } | |
| inline int phytec_eeprom_data_setup_fallback(struct phytec_eeprom_data *data, | |
| int bus_num, int addr, | |
| int addr_fallback) | |
| { | |
| return PHYTEC_EEPROM_INVAL; | |
| } | |
| inline int phytec_eeprom_data_init(struct phytec_eeprom_data *data, | |
| int bus_num, int addr) | |
| { | |
| return PHYTEC_EEPROM_INVAL; | |
| } | |
| inline void __maybe_unused phytec_print_som_info(struct phytec_eeprom_data *data) | |
| { | |
| } | |
| inline char *__maybe_unused phytec_get_opt(struct phytec_eeprom_data *data) | |
| { | |
| return NULL; | |
| } | |
| u8 __maybe_unused phytec_get_rev(struct phytec_eeprom_data *data) | |
| { | |
| return PHYTEC_EEPROM_INVAL; | |
| } | |
| u8 __maybe_unused phytec_get_som_type(struct phytec_eeprom_data *data) | |
| { | |
| return PHYTEC_EEPROM_INVAL; | |
| } | |
| inline struct phytec_api3_element * __maybe_unused | |
| phytec_get_block_head(struct phytec_eeprom_data *data) | |
| { | |
| return NULL; | |
| } | |
| #if IS_ENABLED(CONFIG_CMD_EXTENSION) | |
| inline struct extension *phytec_add_extension(const char *name, | |
| const char *overlay, | |
| const char *other) | |
| { | |
| return NULL; | |
| } | |
| #endif /* IS_ENABLED(CONFIG_CMD_EXTENSION) */ | |
| #endif /* IS_ENABLED(CONFIG_PHYTEC_SOM_DETECTION) */ |