board: phytec: Add common PHYTEC SoM detection

Recent shipped PHYTEC SoMs come with an i2c  EEPROM containing
information about the hardware such as board revision and variant.
This can be used for RAM detection and loading device tree overlays
during kernel start.

Signed-off-by: Teresa Remmet <t.remmet@phytec.de>
Reviewed-by: Yannic Moog <y.moog@phytec.de>
Tested-by: Yannic Moog <y.moog@phytec.de>
Reviewed-by: Fabio Estevam <festevam@gmail.com>
diff --git a/board/phytec/common/Kconfig b/board/phytec/common/Kconfig
new file mode 100644
index 0000000..d614d45
--- /dev/null
+++ b/board/phytec/common/Kconfig
@@ -0,0 +1,5 @@
+config PHYTEC_SOM_DETECTION
+	bool "Support SoM detection for PHYTEC platforms"
+	select SPL_CRC8 if SPL
+	help
+	   Support of I2C EEPROM based SoM detection.
diff --git a/board/phytec/common/Makefile b/board/phytec/common/Makefile
new file mode 100644
index 0000000..5fe8725
--- /dev/null
+++ b/board/phytec/common/Makefile
@@ -0,0 +1,10 @@
+# SPDX-License-Identifier: GPL-2.0+
+# Copyright (C) 2023 PHYTEC Messtechnik GmbH
+# Author: Teresa Remmet <t.remmet@phytec.de>
+
+ifdef CONFIG_SPL_BUILD
+# necessary to create built-in.o
+obj- := __dummy__.o
+endif
+
+obj-$(CONFIG_PHYTEC_SOM_DETECTION) += phytec_som_detection.o
diff --git a/board/phytec/common/phytec_som_detection.c b/board/phytec/common/phytec_som_detection.c
new file mode 100644
index 0000000..366bdd4
--- /dev/null
+++ b/board/phytec/common/phytec_som_detection.c
@@ -0,0 +1,188 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2023 PHYTEC Messtechnik GmbH
+ * Author: Teresa Remmet <t.remmet@phytec.de>
+ */
+
+#include <common.h>
+#include <asm/mach-imx/mxc_i2c.h>
+#include <asm/arch/sys_proto.h>
+#include <dm/device.h>
+#include <dm/uclass.h>
+#include <i2c.h>
+#include <u-boot/crc.h>
+
+#include "phytec_som_detection.h"
+
+struct phytec_eeprom_data eeprom_data;
+
+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_data_init(struct phytec_eeprom_data *data,
+			    int bus_num, int addr)
+{
+	int ret, i;
+	unsigned int crc;
+	int *ptr;
+
+	if (!data)
+		data = &eeprom_data;
+
+#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, 0, (uint8_t *)data,
+			  sizeof(struct phytec_eeprom_data));
+	if (ret) {
+		pr_err("%s: Unable to read EEPROM data\n", __func__);
+		return ret;
+	}
+#else
+	i2c_set_bus_num(bus_num);
+	ret = i2c_read(addr, 0, 2, (uint8_t *)data,
+		       sizeof(struct phytec_eeprom_data));
+#endif
+
+	if (data->api_rev == 0xff) {
+		pr_err("%s: EEPROM is not flashed. Prototype?\n", __func__);
+		return -EINVAL;
+	}
+
+	ptr = (int *)data;
+	for (i = 0; i < sizeof(struct phytec_eeprom_data); i += sizeof(ptr))
+		if (*ptr != 0x0)
+			break;
+
+	if (i == sizeof(struct phytec_eeprom_data)) {
+		pr_err("%s: EEPROM data is all zero. Erased?\n", __func__);
+		return -EINVAL;
+	}
+
+	/* We are done here for early revisions */
+	if (data->api_rev <= PHYTEC_API_REV1)
+		return 0;
+
+	crc = crc8(0, (const unsigned char *)data,
+		   sizeof(struct phytec_eeprom_data));
+	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;
+}
+
+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->api_rev < PHYTEC_API_REV2)
+		return;
+
+	api2 = &data->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:
+			break;
+		};
+
+		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->api_rev < PHYTEC_API_REV2)
+		opt = data->data.data_api0.opt;
+	else
+		opt = data->data.data_api2.opt;
+
+	return opt;
+}
diff --git a/board/phytec/common/phytec_som_detection.h b/board/phytec/common/phytec_som_detection.h
new file mode 100644
index 0000000..01f7e46
--- /dev/null
+++ b/board/phytec/common/phytec_som_detection.h
@@ -0,0 +1,104 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2023 PHYTEC Messtechnik GmbH
+ * Author: Teresa Remmet <t.remmet@phytec.de>
+ */
+
+#ifndef _PHYTEC_SOM_DETECTION_H
+#define _PHYTEC_SOM_DETECTION_H
+
+#define PHYTEC_MAX_OPTIONS	17
+#define PHYTEC_EEPROM_INVAL	0xff
+
+#define PHYTEC_GET_OPTION(option) \
+	(((option) > '9') ? (option) - 'A' + 10 : (option) - '0')
+
+enum {
+	PHYTEC_API_REV0 = 0,
+	PHYTEC_API_REV1,
+	PHYTEC_API_REV2,
+};
+
+static const char * const phytec_som_type_str[] = {
+	"PCM",
+	"PCL",
+	"KSM",
+	"KSP",
+};
+
+struct phytec_api0_data {
+	u8 pcb_rev;		/* PCB revision of SoM */
+	u8 som_type;		/* SoM type */
+	u8 ksp_no;		/* KSP no */
+	char opt[16];		/* SoM options */
+	u8 mac[6];		/* MAC address (optional) */
+	u8 pad[5];		/* padding */
+	u8 cksum;		/* checksum */
+} __packed;
+
+struct phytec_api2_data {
+	u8 pcb_rev;		/* PCB revision of SoM */
+	u8 pcb_sub_opt_rev;	/* PCB subrevision and opt revision */
+	u8 som_type;		/* SoM type */
+	u8 som_no;		/* SoM number */
+	u8 ksp_no;		/* KSP information */
+	char opt[PHYTEC_MAX_OPTIONS]; /* SoM options */
+	char bom_rev[2];	/* BOM revision */
+	u8 mac[6];		/* MAC address (optional) */
+	u8 crc8;		/* checksum */
+} __packed;
+
+struct phytec_eeprom_data {
+	u8 api_rev;
+	union {
+		struct phytec_api0_data data_api0;
+		struct phytec_api2_data data_api2;
+	} data;
+} __packed;
+
+#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 phytec_eeprom_data_setup(struct phytec_eeprom_data *data,
+			     int bus_num, int addr);
+int phytec_eeprom_data_init(struct phytec_eeprom_data *data,
+			    int bus_num, int addr);
+void __maybe_unused phytec_print_som_info(struct phytec_eeprom_data *data);
+
+char * __maybe_unused phytec_get_opt(struct phytec_eeprom_data *data);
+
+#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;
+}
+
+#endif /* IS_ENABLED(CONFIG_PHYTEC_SOM_DETECTION) */
+
+#endif /* _PHYTEC_SOM_DETECTION_H */