From: Nora Schiffer <[email protected]> Introduce a sysinfo driver that can be instantiated from the device, which will provide information from the EEPROM found on all TQ-Systems SoMs.
Signed-off-by: Nora Schiffer <[email protected]> Signed-off-by: Max Merchel <[email protected]> Signed-off-by: Alexander Feilke <[email protected]> --- drivers/sysinfo/Kconfig | 8 ++ drivers/sysinfo/Makefile | 1 + drivers/sysinfo/tq_eeprom.c | 223 ++++++++++++++++++++++++++++++++++++ include/sysinfo/tq_eeprom.h | 24 ++++ 4 files changed, 256 insertions(+) create mode 100644 drivers/sysinfo/tq_eeprom.c create mode 100644 include/sysinfo/tq_eeprom.h diff --git a/drivers/sysinfo/Kconfig b/drivers/sysinfo/Kconfig index df83df69ffb..6922dac9170 100644 --- a/drivers/sysinfo/Kconfig +++ b/drivers/sysinfo/Kconfig @@ -59,4 +59,12 @@ config SYSINFO_GPIO This ternary number is then mapped to a board revision name using device tree properties. +config SYSINFO_TQ_EEPROM + bool "Enable TQ-Systems EEPROM sysinfo driver" + depends on I2C_EEPROM + depends on SPL_I2C_EEPROM || !SPL_SYSINFO + help + Support querying EEPROM of TQ-Systems SOMs to determine board + information. + endif diff --git a/drivers/sysinfo/Makefile b/drivers/sysinfo/Makefile index 26ca3150999..d21fb3c2270 100644 --- a/drivers/sysinfo/Makefile +++ b/drivers/sysinfo/Makefile @@ -9,3 +9,4 @@ obj-$(CONFIG_SYSINFO_IOT2050) += iot2050.o obj-$(CONFIG_SYSINFO_RCAR3) += rcar3.o obj-$(CONFIG_SYSINFO_SANDBOX) += sandbox.o obj-$(CONFIG_SYSINFO_SMBIOS) += smbios.o +obj-$(CONFIG_SYSINFO_TQ_EEPROM) += tq_eeprom.o diff --git a/drivers/sysinfo/tq_eeprom.c b/drivers/sysinfo/tq_eeprom.c new file mode 100644 index 00000000000..b10803f9b5a --- /dev/null +++ b/drivers/sysinfo/tq_eeprom.c @@ -0,0 +1,223 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (c) 2014-2026 TQ-Systems GmbH <[email protected]>, + * D-82229 Seefeld, Germany. + * Author: Nora Schiffer + */ + +#include <display_options.h> +#include <dm.h> +#include <i2c_eeprom.h> +#include <log.h> +#include <net.h> +#include <dm/device_compat.h> +#include <linux/ctype.h> +#include <linux/sizes.h> +#include <sysinfo/tq_eeprom.h> + +#define TQ_EE_RSV0_BYTES 0x20 +#define TQ_EE_RSV1_BYTES 10 +#define TQ_EE_SERIAL_BYTES 8 +#define TQ_EE_RSV2_BYTES 8 +#define TQ_EE_BDID_BYTES 0x40 + +struct tq_eeprom_data { + u8 rsv0[TQ_EE_RSV0_BYTES]; + u8 mac[ETH_ALEN]; /* 0x20 ... 0x25 */ + u8 rsv1[TQ_EE_RSV1_BYTES]; + u8 serial[TQ_EE_SERIAL_BYTES]; /* 0x30 ... 0x37 */ + u8 rsv2[TQ_EE_RSV2_BYTES]; + u8 id[TQ_EE_BDID_BYTES]; /* 0x40 ... 0x7f */ +}; + +static_assert(sizeof(struct tq_eeprom_data) == 0x80, + "struct tq_eeprom_data has incorrect size"); + +/** + * struct sysinfo_tq_eeprom_priv - sysinfo private data + */ +struct sysinfo_tq_eeprom_priv { + struct udevice *i2c_eeprom; + int offset; + + /* Reserve extra space for \0 in id and serial */ + char id[TQ_EE_BDID_BYTES + 1]; + char serial[TQ_EE_SERIAL_BYTES + 1]; + u8 mac[ETH_ALEN]; +}; + +static void tq_eeprom_parse_id(struct udevice *dev, const struct tq_eeprom_data *data) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + int i; + + for (i = 0; i < sizeof(data->id); i++) { + if (!(isprint(data->id[i]) && isascii(data->id[i]))) + break; + } + + if (i == 0) + dev_warn(dev, "no valid model name in EEPROM\n"); + + snprintf(priv->id, sizeof(priv->id), "%.*s", i, data->id); +} + +static int tq_eeprom_serial_len_old(const struct tq_eeprom_data *data) +{ + int i; + + for (i = 0; i < sizeof(data->serial); i++) { + if (!isdigit(data->serial[i])) + break; + } + + return i; +} + +static int tq_eeprom_serial_len_new(const struct tq_eeprom_data *data) +{ + int i; + + for (i = 0; i < sizeof(data->serial); i++) { + if (!(isdigit(data->serial[i]) || isupper(data->serial[i]))) + break; + } + + return i; +} + +static void tq_eeprom_parse_serial(struct udevice *dev, const struct tq_eeprom_data *data) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + int len; + + if (data->serial[0] == 'T' && data->serial[1] == 'Q') + len = tq_eeprom_serial_len_new(data); + else + len = tq_eeprom_serial_len_old(data); + + /* For now, only serial numbers with the exact size of the field are accepted */ + if (len != sizeof(data->serial)) { + dev_warn(dev, "no valid serial number in EEPROM\n"); + len = 0; + } + + snprintf(priv->serial, sizeof(priv->serial), "%.*s", len, data->serial); +} + +static int tq_eeprom_dump(const struct sysinfo_tq_eeprom_priv *priv) +{ + printf("TQ EEPROM:\n"); + printf(" ID: %s\n", priv->id[0] ? priv->id : "<invalid>"); + printf(" SN: %s\n", priv->serial[0] ? priv->serial : "<invalid>"); + printf(" MAC: "); + if (is_valid_ethaddr(priv->mac)) + printf("%pM\n", priv->mac); + else + printf("<invalid>\n"); + + return 0; +} + +static int sysinfo_tq_eeprom_detect(struct udevice *dev) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + struct tq_eeprom_data data; + int ret; + + ret = i2c_eeprom_read(priv->i2c_eeprom, priv->offset, + (u8 *)&data, sizeof(data)); + if (ret < 0) { + dev_err(dev, "EEPROM read failed: %d\n", ret); + return -EIO; + } + + tq_eeprom_parse_id(dev, &data); + tq_eeprom_parse_serial(dev, &data); + memcpy(priv->mac, data.mac, ETH_ALEN); + + if (!IS_ENABLED(CONFIG_SPL_BUILD)) + tq_eeprom_dump(priv); + + return 0; +} + +static int sysinfo_tq_eeprom_get_str(struct udevice *dev, int id, size_t size, char *val) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + + switch (id) { + case SYSID_TQ_MODEL: + if (!priv->id[0]) + return -ENODATA; + + strlcpy(val, priv->id, size); + return 0; + + case SYSID_TQ_SERIAL: + if (!priv->serial[0]) + return -ENODATA; + + strlcpy(val, priv->serial, size); + return 0; + + default: + return -EINVAL; + } +} + +static int sysinfo_tq_eeprom_get_data(struct udevice *dev, int id, void **data, size_t *size) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + + switch (id) { + case SYSID_TQ_MAC_ADDR: + if (!is_valid_ethaddr(priv->mac)) + return -ENODATA; + + *data = priv->mac; + *size = sizeof(priv->mac); + + return 0; + + default: + return -EINVAL; + } +} + +static const struct sysinfo_ops sysinfo_tq_eeprom_ops = { + .detect = sysinfo_tq_eeprom_detect, + .get_str = sysinfo_tq_eeprom_get_str, + .get_data = sysinfo_tq_eeprom_get_data, +}; + +static int sysinfo_tq_eeprom_probe(struct udevice *dev) +{ + struct sysinfo_tq_eeprom_priv *priv = dev_get_priv(dev); + int ret; + + ret = uclass_get_device_by_phandle(UCLASS_I2C_EEPROM, dev, "i2c-eeprom", + &priv->i2c_eeprom); + if (ret) { + dev_err(dev, "i2c-eeprom backing device not found: %d\n", ret); + return ret; + } + + priv->offset = dev_read_u32_default(dev, "offset", 0); + + return 0; +} + +static const struct udevice_id sysinfo_tq_eeprom_ids[] = { + { .compatible = "tq,eeprom-sysinfo" }, + { /* sentinel */ } +}; + +U_BOOT_DRIVER(sysinfo_tq_eeprom) = { + .name = "sysinfo_tq_eeprom", + .id = UCLASS_SYSINFO, + .of_match = sysinfo_tq_eeprom_ids, + .ops = &sysinfo_tq_eeprom_ops, + .priv_auto = sizeof(struct sysinfo_tq_eeprom_priv), + .probe = sysinfo_tq_eeprom_probe, +}; diff --git a/include/sysinfo/tq_eeprom.h b/include/sysinfo/tq_eeprom.h new file mode 100644 index 00000000000..6b1bddd7ce0 --- /dev/null +++ b/include/sysinfo/tq_eeprom.h @@ -0,0 +1,24 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2023-2026 TQ-Systems GmbH <[email protected]>, + * D-82229 Seefeld, Germany. + * Author: Nora Schiffer + */ + +#ifndef __SYSINFO_TQ_EEPROM_H__ +#define __SYSINFO_TQ_EEPROM_H__ + +#include <sysinfo.h> + +enum { + /* Model string of TQ-Systems SOM. This is different from BOARD_MODEL, + * which usually combines SOM and baseboard names for TQ hardware + */ + SYSID_TQ_MODEL = SYSID_USER, + /* SOM serial number */ + SYSID_TQ_SERIAL, + /* MAC address */ + SYSID_TQ_MAC_ADDR, +}; + +#endif /* __SYSINFO_TQ_EEPROM_H__ */ -- 2.34.1

