mirror of
https://source.denx.de/u-boot/u-boot.git
synced 2025-08-15 03:36:59 +02:00
ft_board_setup inside the board code allows to alter device-tree during the boot process. Introduce a new function for the PHYTEC SOM detection to read the product name and part number from the EEPROM content and include both into the device-tree as * phytec,som-part-number * phytec,som-product-name This function can be called from the board code when those values should be exposed to Linux. This patch also updates the phytec_print_som_info function and changes the output. Signed-off-by: Daniel Schultz <d.schultz@phytec.de> Reviewed-by: Wadim Egorov <w.egorov@phytec.de> Tested-by: Wadim Egorov <w.egorov@phytec.de>
583 lines
13 KiB
C
583 lines
13 KiB
C
// 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;
|
|
}
|
|
|
|
static int phytec_get_product_name(struct phytec_eeprom_data *data,
|
|
char *product)
|
|
{
|
|
struct phytec_api2_data *api2;
|
|
unsigned int ksp_no, som_type;
|
|
int len;
|
|
|
|
if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2)
|
|
return -EINVAL;
|
|
|
|
api2 = &data->payload.data.data_api2;
|
|
|
|
if (api2->som_type > 1 && api2->som_type <= 3) {
|
|
ksp_no = (api2->ksp_no << 8) | api2->som_no;
|
|
len = snprintf(product, PHYTEC_PRODUCT_NAME_MAX_LEN + 1,
|
|
"%s-%04u", phytec_som_type_str[api2->som_type],
|
|
ksp_no);
|
|
if (len != PHYTEC_PRODUCT_NAME_KSP_LEN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
switch (api2->som_type) {
|
|
case 0:
|
|
som_type = api2->som_type;
|
|
break;
|
|
case 4:
|
|
som_type = 0;
|
|
break;
|
|
case 5:
|
|
som_type = 0;
|
|
break;
|
|
case 6:
|
|
som_type = 1;
|
|
break;
|
|
case 7:
|
|
som_type = 1;
|
|
break;
|
|
default:
|
|
pr_err("%s: Invalid SOM type: %i", __func__, api2->som_type);
|
|
return -EINVAL;
|
|
};
|
|
|
|
len = snprintf(product, PHYTEC_PRODUCT_NAME_MAX_LEN + 1, "%s-%03u",
|
|
phytec_som_type_str[som_type], api2->som_no);
|
|
if (len != PHYTEC_PRODUCT_NAME_STD_LEN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static int phytec_get_part_number(struct phytec_eeprom_data *data,
|
|
char *part)
|
|
{
|
|
char product_name[PHYTEC_PRODUCT_NAME_MAX_LEN + 1] = {'\0'};
|
|
struct phytec_api2_data *api2;
|
|
unsigned int ksp_type;
|
|
int res, len;
|
|
|
|
if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2)
|
|
return -EINVAL;
|
|
|
|
api2 = &data->payload.data.data_api2;
|
|
|
|
res = phytec_get_product_name(data, product_name);
|
|
if (res)
|
|
return res;
|
|
|
|
if (api2->som_type <= 1) {
|
|
len = snprintf(part, PHYTEC_PART_NUMBER_MAX_LEN + 1,
|
|
"%s-%s.%s", product_name, api2->opt,
|
|
api2->bom_rev);
|
|
if (len < PHYTEC_PART_NUMBER_STD_LEN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
if (api2->som_type <= 3) {
|
|
snprintf(part, PHYTEC_PART_NUMBER_MAX_LEN + 1, "%s.%s",
|
|
product_name, api2->bom_rev);
|
|
if (len != PHYTEC_PART_NUMBER_KSP_LEN)
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
switch (api2->som_type) {
|
|
case 4:
|
|
ksp_type = 3;
|
|
break;
|
|
case 5:
|
|
ksp_type = 2;
|
|
break;
|
|
case 6:
|
|
ksp_type = 3;
|
|
break;
|
|
case 7:
|
|
ksp_type = 2;
|
|
break;
|
|
default:
|
|
pr_err("%s: Invalid SOM type: %i", __func__, api2->som_type);
|
|
return -EINVAL;
|
|
};
|
|
|
|
len = snprintf(part, PHYTEC_PART_NUMBER_MAX_LEN + 1, "%s-%s%02u.%s",
|
|
product_name, phytec_som_type_str[ksp_type],
|
|
api2->ksp_no, api2->bom_rev);
|
|
if (len < PHYTEC_PART_NUMBER_STD_KSP_LEN)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void __maybe_unused phytec_print_som_info(struct phytec_eeprom_data *data)
|
|
{
|
|
char part_number[PHYTEC_PART_NUMBER_MAX_LEN + 1] = {'\0'};
|
|
struct phytec_api2_data *api2;
|
|
char pcb_sub_rev;
|
|
int res;
|
|
|
|
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') : ' ';
|
|
|
|
res = phytec_get_part_number(data, part_number);
|
|
if (res)
|
|
return;
|
|
|
|
printf("SOM: %s\n", part_number);
|
|
printf("PCB Rev.: %u%c\n", api2->pcb_rev, pcb_sub_rev);
|
|
if (api2->som_type > 1)
|
|
printf("Options: %s\n", api2->opt);
|
|
}
|
|
|
|
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_OF_LIBFDT)
|
|
int phytec_ft_board_fixup(struct phytec_eeprom_data *data, void *blob)
|
|
{
|
|
char product_name[PHYTEC_PRODUCT_NAME_MAX_LEN + 1] = {'\0'};
|
|
char part_number[PHYTEC_PART_NUMBER_MAX_LEN + 1] = {'\0'};
|
|
int res;
|
|
|
|
if (!data)
|
|
data = &eeprom_data;
|
|
|
|
if (!data->valid || data->payload.api_rev < PHYTEC_API_REV2)
|
|
return -EINVAL;
|
|
|
|
res = phytec_get_product_name(data, product_name);
|
|
if (res)
|
|
return res;
|
|
|
|
fdt_setprop(blob, 0, "phytec,som-product-name", product_name,
|
|
strlen(product_name) + 1);
|
|
|
|
res = phytec_get_part_number(data, part_number);
|
|
if (res)
|
|
return res;
|
|
|
|
fdt_setprop(blob, 0, "phytec,som-part-number", part_number,
|
|
strlen(part_number) + 1);
|
|
|
|
return 0;
|
|
}
|
|
#endif /* IS_ENABLED(CONFIG_OF_LIBFDT) */
|
|
|
|
#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_OF_LIBFDT)
|
|
inline int phytec_ft_board_fixup(struct phytec_eeprom_data *data, void *blob)
|
|
{
|
|
return 0;
|
|
}
|
|
#endif /* IS_ENABLED(CONFIG_OF_LIBFDT) */
|
|
#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) */
|