--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2019 Xilinx, Inc.
+ *
+ */
+
+#include <common.h>
+#include <fdtdec.h>
+#include <malloc.h>
+#include <fru.h>
+
+static int do_fru_capture(cmd_tbl_t *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ unsigned long addr;
+ char *endp;
+ int ret;
+
+ if (argc < 3)
+ return CMD_RET_USAGE;
+
+ addr = simple_strtoul(argv[2], &endp, 16);
+ if (*argv[1] == 0 || *endp != 0)
+ return -1;
+
+ ret = fru_capture(addr);
+
+ return ret;
+}
+
+static int do_fru_display(cmd_tbl_t *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ fru_display();
+ return CMD_RET_SUCCESS;
+}
+
+static cmd_tbl_t cmd_fru_sub[] = {
+ U_BOOT_CMD_MKENT(capture, 3, 0, do_fru_capture, "", ""),
+ U_BOOT_CMD_MKENT(display, 2, 0, do_fru_display, "", ""),
+};
+
+static int do_fru(cmd_tbl_t *cmdtp, int flag, int argc,
+ char *const argv[])
+{
+ cmd_tbl_t *c;
+
+ if (argc < 2)
+ return CMD_RET_USAGE;
+
+ c = find_cmd_tbl(argv[1], &cmd_fru_sub[0],
+ ARRAY_SIZE(cmd_fru_sub));
+
+ if (c)
+ return c->cmd(c, flag, argc, argv);
+
+ return CMD_RET_USAGE;
+}
+
+/***************************************************/
+#ifdef CONFIG_SYS_LONGHELP
+static char fru_help_text[] =
+ "capture <addr>- Parse and capture FRU table present at address.\n"
+ "display = Displays content of FRU table that was captured using\n"
+ " fru capture command\n"
+ ;
+#endif
+
+U_BOOT_CMD(
+ fru, 3, 1, do_fru,
+ "FRU table info",
+ fru_help_text
+)
--- /dev/null
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * (C) Copyright 2019 Xilinx, Inc.
+ *
+ */
+
+#include <common.h>
+#include <fdtdec.h>
+#include <malloc.h>
+#include <asm/io.h>
+#include <asm/arch/hardware.h>
+#include "linux/crc8.h"
+
+DECLARE_GLOBAL_DATA_PTR;
+
+struct fru_common_hdr {
+ u8 version;
+ u8 off_internal;
+ u8 off_chassis;
+ u8 off_board;
+ u8 off_product;
+ u8 off_multirec;
+ u8 pad;
+ u8 crc;
+};
+
+#define FRU_BOARD_MAX_LEN 32
+
+struct fru_board_data {
+ u8 ver;
+ u8 len;
+ u8 lang_code;
+ u8 time[3];
+ u8 manuf_type_len;
+ u8 manuf_name[FRU_BOARD_MAX_LEN];
+ u8 prd_name_type_len;
+ u8 product_name[FRU_BOARD_MAX_LEN];
+ u8 prd_part_type_len;
+ u8 product_part[FRU_BOARD_MAX_LEN];
+ u8 prd_ver_type_len;
+ u8 product_ver[FRU_BOARD_MAX_LEN];
+ u8 prd_serial_type_len;
+ u8 product_serial[FRU_BOARD_MAX_LEN];
+ u8 asset_tag_type_len;
+ u8 asset_tag[FRU_BOARD_MAX_LEN];
+};
+
+struct fru_table {
+ bool captured;
+ struct fru_common_hdr hdr;
+ struct fru_board_data brd;
+};
+
+#define FRU_TYPELEN_CODE_MASK 0xC0
+#define FRU_TYPELEN_LEN_MASK 0x3F
+#define FRU_COMMON_HDR_VER_MASK 0xF
+#define FRU_COMMON_HDR_LEN_MULTIPLIER 8
+#define FRU_LANG_CODE_ENGLISH 0
+#define FRU_LANG_CODE_ENGLISH_1 25
+#define FRU_TYPELEN_EOF 0xC1
+
+#define FRU_BOARD_AREA_TOTAL_FIELDS 6
+#define FRU_TYPELEN_TYPE_SHIFT 6
+#define FRU_TYPELEN_TYPE_ASCII8 3
+
+struct fru_table fru_data;
+
+static u16 fru_cal_area_len(u8 len)
+{
+ return len * FRU_COMMON_HDR_LEN_MULTIPLIER;
+}
+
+static u8 fru_version(u8 ver)
+{
+ return ver & FRU_COMMON_HDR_VER_MASK;
+}
+
+static int fru_check_language(u8 code)
+{
+ if (code != FRU_LANG_CODE_ENGLISH && code != FRU_LANG_CODE_ENGLISH_1) {
+ printf("FRU_ERROR: Only English Language is supported\n");
+ return -EINVAL;
+ }
+
+ return code;
+}
+
+static int fru_check_type_len(u8 type_len, u8 language, u8 *type)
+{
+ int len;
+
+ if (type_len == FRU_TYPELEN_EOF)
+ return -EINVAL;
+
+ *type = (type_len & FRU_TYPELEN_CODE_MASK) >> FRU_TYPELEN_TYPE_SHIFT;
+
+ len = type_len & FRU_TYPELEN_LEN_MASK;
+
+ return len;
+}
+
+static int fru_parse_board(unsigned long addr)
+{
+ u8 i, type;
+ int len;
+ u8 *data;
+
+ memcpy(&fru_data.brd.ver, (void *)addr, 6);
+ addr += 6;
+ data = (u8 *)&fru_data.brd.manuf_type_len;
+
+ for (i = 0; i < FRU_BOARD_AREA_TOTAL_FIELDS; i++,
+ data += FRU_BOARD_MAX_LEN) {
+ *data++ = *(u8 *)addr;
+ len = fru_check_type_len(*(u8 *)addr, fru_data.brd.lang_code,
+ &type);
+ /*
+ * Stop cature if it end of fields
+ */
+ if (len == -EINVAL)
+ return 0;
+
+ /*
+ * Dont capture data if type is not ASCII8
+ */
+ if (type != FRU_TYPELEN_TYPE_ASCII8)
+ return 0;
+
+ addr += 1;
+ if (!len)
+ continue;
+ memcpy(data, (u8 *)addr, len);
+ addr += len;
+ }
+
+ return 0;
+}
+
+int fru_capture(unsigned long addr)
+{
+ struct fru_common_hdr *hdr;
+ u8 crc = 0;
+ u8 len;
+ u8 *temp = (u8 *)addr;
+
+ hdr = (struct fru_common_hdr *)addr;
+ len = sizeof(struct fru_common_hdr);
+
+ while(len--) {
+ crc += *temp;
+ temp++;
+ }
+
+ if (crc) {
+ printf("%s Common header CRC error\n", __func__);
+ return -EINVAL;
+ }
+
+ memcpy((void *)&fru_data.hdr, (void *)hdr,
+ sizeof(struct fru_common_hdr));
+
+ fru_data.captured = true;
+
+ if (hdr->off_board) {
+ addr += fru_cal_area_len(hdr->off_board);
+ fru_parse_board(addr);
+ }
+
+ return 0;
+}
+
+static int fru_display_board(void)
+{
+ u32 time = 0;
+ u8 type;
+ int len;
+ u8 *data;
+ const char *typecode[] = {
+ "Binary/Unspecified",
+ "BCD plus",
+ "6-bit ASCII",
+ "8-bit ASCII",
+ "2-byte UNICODE"
+ };
+ const char *boardinfo[] = {
+ "Manufacturer Name",
+ "Product Name",
+ "Product Model/Part No",
+ "Product Version",
+ "Product Serial No",
+ "Asset Tag"
+ };
+
+ printf("*****BOARD INFO*****\n");
+ printf("Version:%d\n", fru_version(fru_data.brd.ver));
+ printf("Board Area Length:%d\n", fru_cal_area_len(fru_data.brd.len));
+
+ if (fru_check_language(fru_data.brd.lang_code))
+ return 0;
+
+ time = fru_data.brd.time[2] << 16 | fru_data.brd.time[1] << 8 |
+ fru_data.brd.time[0];
+ printf("Time in Minutes from 0:00hrs 1/1/96 %d\n", time);
+
+ data = (u8 *)&fru_data.brd.manuf_type_len;
+
+ for (u8 i = 0; i < FRU_BOARD_AREA_TOTAL_FIELDS; i++) {
+ len = fru_check_type_len(*data++, fru_data.brd.lang_code,
+ &type);
+
+ if (type <= FRU_TYPELEN_TYPE_ASCII8 &&
+ (fru_data.brd.lang_code == FRU_LANG_CODE_ENGLISH ||
+ fru_data.brd.lang_code == FRU_LANG_CODE_ENGLISH_1))
+ printf("Type code: %s\n", typecode[type]);
+ else
+ printf("Type code: %s\n", typecode[type + 1]);
+ if (len == -EINVAL) {
+ printf("**** EOF for Board Area ****\n");
+ return 0;
+ }
+ if (type != FRU_TYPELEN_TYPE_ASCII8) {
+ printf("FRU_ERROR: Only ASCII8 type is supported\n");
+ return 0;
+ }
+ if (!len) {
+ printf("%s not found\n", boardinfo[i]);
+ continue;
+ }
+
+ printf("Length: %d\n", len);
+ printf("%s: %s\n", boardinfo[i], data);
+ data += FRU_BOARD_MAX_LEN;
+ }
+
+ return 0;
+}
+
+static void fru_display_common_hdr(void)
+{
+ struct fru_common_hdr *hdr = &fru_data.hdr;
+
+ printf("*****COMMON HEADER*****\n");
+ printf("Version:%d\n", fru_version(hdr->version));
+ if (hdr->off_internal)
+ printf("Internal Use Area Offset:%d\n",
+ fru_cal_area_len(hdr->off_internal));
+ else
+ printf("*** No Internal Area ***\n");
+
+ if (hdr->off_chassis)
+ printf("Chassis Info Area Offset:%d\n",
+ fru_cal_area_len(hdr->off_chassis));
+ else
+ printf("*** No Chassis Info Area ***\n");
+
+ if (hdr->off_board)
+ printf("Board Area Offset:%d\n",
+ fru_cal_area_len(hdr->off_board));
+ else
+ printf("*** No Board Area ***\n");
+
+ if (hdr->off_product)
+ printf("Product Info Area Offset:%d\n",
+ fru_cal_area_len(hdr->off_product));
+ else
+ printf("*** No Product Info Area ***\n");
+
+ if (hdr->off_multirec)
+ printf("MultiRecord Area Offset:%d\n",
+ fru_cal_area_len(hdr->off_multirec));
+ else
+ printf("*** No MultiRecord Area ***\n");
+}
+
+int fru_display(void)
+{
+ if (!fru_data.captured) {
+ printf("FRU data not available please run fru parse\n");
+ return -EINVAL;
+ }
+
+ fru_display_common_hdr();
+ fru_display_board();
+
+ return 0;
+}