board: ge: bx50v3: set eth0 MAC address
authorIan Ray <ian.ray@ge.com>
Tue, 22 Aug 2017 06:03:54 +0000 (09:03 +0300)
committerStefano Babic <sbabic@denx.de>
Wed, 20 Sep 2017 13:34:59 +0000 (15:34 +0200)
Define i2c mux configuration.  Add new vpd_reader which is used to read
vital product data.  Read VPD from EEPROM and set eth0 MAC address.

Signed-off-by: Ian Ray <ian.ray@ge.com>
Signed-off-by: Jose Alarcon <jose.alarcon@ge.com>
board/ge/bx50v3/Makefile
board/ge/bx50v3/bx50v3.c
board/ge/bx50v3/vpd_reader.c [new file with mode: 0644]
board/ge/bx50v3/vpd_reader.h [new file with mode: 0644]
include/configs/ge_bx50v3.h

index bcd149f..2fff27b 100644 (file)
@@ -5,4 +5,4 @@
 # SPDX-License-Identifier:     GPL-2.0+
 #
 
-obj-y  := bx50v3.o
+obj-y  := bx50v3.o vpd_reader.o
index b25c634..c7df4ce 100644 (file)
 #include <asm/arch/sys_proto.h>
 #include <i2c.h>
 #include <pwm.h>
+#include <stdlib.h>
+#include "vpd_reader.h"
 DECLARE_GLOBAL_DATA_PTR;
 
+#ifndef CONFIG_SYS_I2C_EEPROM_ADDR
+# define CONFIG_SYS_I2C_EEPROM_ADDR     0x50
+# define CONFIG_SYS_I2C_EEPROM_ADDR_LEN 1
+#endif
+
+#ifndef CONFIG_SYS_I2C_EEPROM_BUS
+#define CONFIG_SYS_I2C_EEPROM_BUS       2
+#endif
+
 #define NC_PAD_CTRL (PAD_CTL_PUS_100K_UP |     \
        PAD_CTL_SPEED_MED | PAD_CTL_DSE_40ohm | \
        PAD_CTL_HYS)
@@ -528,6 +539,102 @@ int overwrite_console(void)
        return 1;
 }
 
+#define VPD_TYPE_INVALID 0x00
+#define VPD_BLOCK_NETWORK 0x20
+#define VPD_BLOCK_HWID 0x44
+#define VPD_PRODUCT_B850 1
+#define VPD_PRODUCT_B650 2
+#define VPD_PRODUCT_B450 3
+
+struct vpd_cache {
+       uint8_t product_id;
+       uint8_t macbits;
+       unsigned char mac1[6];
+};
+
+/*
+ * Extracts MAC and product information from the VPD.
+ */
+static int vpd_callback(
+       void *userdata,
+       uint8_t id,
+       uint8_t version,
+       uint8_t type,
+       size_t size,
+       uint8_t const *data)
+{
+       struct vpd_cache *vpd = (struct vpd_cache *)userdata;
+
+       if (   id == VPD_BLOCK_HWID
+           && version == 1
+           && type != VPD_TYPE_INVALID
+           && size >= 1) {
+               vpd->product_id = data[0];
+
+       } else if (   id == VPD_BLOCK_NETWORK
+                  && version == 1
+                  && type != VPD_TYPE_INVALID
+                  && size >= 6) {
+               vpd->macbits |= 1;
+               memcpy(vpd->mac1, data, 6);
+       }
+
+       return 0;
+}
+
+static void set_eth0_mac_address(unsigned char * mac)
+{
+       uint32_t *ENET_TCR = (uint32_t*)0x21880c4;
+       uint32_t *ENET_PALR = (uint32_t*)0x21880e4;
+       uint32_t *ENET_PAUR = (uint32_t*)0x21880e8;
+
+       *ENET_TCR |= 0x100;  /* ADDINS */
+       *ENET_PALR |= (mac[0] << 24) | (mac[1] << 16) | (mac[2] << 8) | mac[3];
+       *ENET_PAUR |= (mac[4] << 24) | (mac[5] << 16);
+}
+
+static void process_vpd(struct vpd_cache *vpd)
+{
+       if (   vpd->product_id == VPD_PRODUCT_B850
+           || vpd->product_id == VPD_PRODUCT_B650
+           || vpd->product_id == VPD_PRODUCT_B450) {
+               if (vpd->macbits & 1) {
+                       set_eth0_mac_address(vpd->mac1);
+               }
+       }
+}
+
+static int read_vpd(uint eeprom_bus)
+{
+       struct vpd_cache vpd;
+       int res;
+       int size = 1024;
+       uint8_t *data;
+       unsigned int current_i2c_bus = i2c_get_bus_num();
+
+       res = i2c_set_bus_num(eeprom_bus);
+       if (res < 0)
+               return res;
+
+       data = (uint8_t *)malloc(size);
+       if (!data)
+               return -ENOMEM;
+
+       res = i2c_read(CONFIG_SYS_I2C_EEPROM_ADDR, 0,
+                       CONFIG_SYS_I2C_EEPROM_ADDR_LEN, data, size);
+
+       if (res == 0) {
+               memset(&vpd, 0, sizeof(vpd));
+               vpd_reader(size, data, &vpd, vpd_callback);
+               process_vpd(&vpd);
+       }
+
+       free(data);
+
+       i2c_set_bus_num(current_i2c_bus);
+       return res;
+}
+
 int board_eth_init(bd_t *bis)
 {
        setup_iomux_enet();
@@ -586,6 +693,8 @@ int board_init(void)
        setup_i2c(2, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info2);
        setup_i2c(3, CONFIG_SYS_I2C_SPEED, 0x7f, &i2c_pad_info3);
 
+       read_vpd(CONFIG_SYS_I2C_EEPROM_BUS);
+
        return 0;
 }
 
diff --git a/board/ge/bx50v3/vpd_reader.c b/board/ge/bx50v3/vpd_reader.c
new file mode 100644 (file)
index 0000000..98da893
--- /dev/null
@@ -0,0 +1,228 @@
+/*
+ * Copyright 2016 General Electric Company
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include "vpd_reader.h"
+
+#include <linux/bch.h>
+#include <stdlib.h>
+
+
+/* BCH configuration */
+
+const struct {
+       int header_ecc_capability_bits;
+       int data_ecc_capability_bits;
+       unsigned int prim_poly;
+       struct {
+               int min;
+               int max;
+       } galois_field_order;
+} bch_configuration = {
+       .header_ecc_capability_bits = 4,
+       .data_ecc_capability_bits = 16,
+       .prim_poly = 0,
+       .galois_field_order = {
+               .min = 5,
+               .max = 15,
+       },
+};
+
+static int calculate_galois_field_order(size_t source_length)
+{
+       int gfo = bch_configuration.galois_field_order.min;
+
+       for (; gfo < bch_configuration.galois_field_order.max &&
+            ((((1 << gfo) - 1) - ((int)source_length * 8)) < 0);
+            gfo++) {
+       }
+
+       if (gfo == bch_configuration.galois_field_order.max) {
+               return -1;
+       }
+
+       return gfo + 1;
+}
+
+static int verify_bch(int ecc_bits, unsigned int prim_poly,
+       uint8_t * data, size_t data_length,
+       const uint8_t * ecc, size_t ecc_length)
+{
+       int gfo = calculate_galois_field_order(data_length);
+       if (gfo < 0) {
+               return -1;
+       }
+
+       struct bch_control * bch = init_bch(gfo, ecc_bits, prim_poly);
+       if (!bch) {
+               return -1;
+       }
+
+       if (bch->ecc_bytes != ecc_length) {
+               free_bch(bch);
+               return -1;
+       }
+
+       unsigned * errloc = (unsigned *)calloc(data_length, sizeof(unsigned));
+       int errors = decode_bch(
+                       bch, data, data_length, ecc, NULL, NULL, errloc);
+       free_bch(bch);
+       if (errors < 0) {
+               free(errloc);
+               return -1;
+       }
+
+       if (errors > 0) {
+               for (int n = 0; n < errors; n++) {
+                       if (errloc[n] >= 8 * data_length) {
+                               /* n-th error located in ecc (no need for data correction) */
+                       } else {
+                               /* n-th error located in data */
+                               data[errloc[n] / 8] ^= 1 << (errloc[n] % 8);
+                       }
+               }
+       }
+
+       free(errloc);
+       return 0;
+}
+
+
+static const int ID = 0;
+static const int LEN = 1;
+static const int VER = 2;
+static const int TYP = 3;
+static const int BLOCK_SIZE = 4;
+
+static const uint8_t HEADER_BLOCK_ID = 0x00;
+static const uint8_t HEADER_BLOCK_LEN = 18;
+static const uint32_t HEADER_BLOCK_MAGIC = 0xca53ca53;
+static const size_t HEADER_BLOCK_VERIFY_LEN = 14;
+static const size_t HEADER_BLOCK_ECC_OFF = 14;
+static const size_t HEADER_BLOCK_ECC_LEN = 4;
+
+static const uint8_t ECC_BLOCK_ID = 0xFF;
+
+int vpd_reader(
+       size_t size,
+       uint8_t * data,
+       void * userdata,
+       int (*fn)(
+           void * userdata,
+           uint8_t id,
+           uint8_t version,
+           uint8_t type,
+           size_t size,
+           uint8_t const * data))
+{
+       if (   size < HEADER_BLOCK_LEN
+           || data == NULL
+           || fn == NULL) {
+               return -EINVAL;
+       }
+
+       /*
+        * +--------------------+--------------------+--//--+--------------------+
+        * | header block       | data block         | ...  | ecc block          |
+        * +--------------------+--------------------+--//--+--------------------+
+        * :                    :                           :
+        * +------+-------+-----+                           +------+-------------+
+        * | id   | magic | ecc |                           | ...  | ecc         |
+        * | len  | off   |     |                           +------+-------------+
+        * | ver  | size  |     |                           :
+        * | type |       |     |                           :
+        * +------+-------+-----+                           :
+        * :              :     :                           :
+        * <----- [1] ---->     <----------- [2] ----------->
+        *
+        * Repair (if necessary) the contents of header block [1] by using a
+        * 4 byte ECC located at the end of the header block.  A successful
+        * return value means that we can trust the header.
+        */
+       int ret = verify_bch(
+               bch_configuration.header_ecc_capability_bits,
+               bch_configuration.prim_poly,
+               data,
+               HEADER_BLOCK_VERIFY_LEN,
+               &data[HEADER_BLOCK_ECC_OFF],
+               HEADER_BLOCK_ECC_LEN);
+       if (ret < 0) {
+               return ret;
+       }
+
+       /* Validate header block { id, length, version, type }. */
+       if (   data[ID] != HEADER_BLOCK_ID
+           || data[LEN] != HEADER_BLOCK_LEN
+           || data[VER] != 0
+           || data[TYP] != 0
+           || ntohl(*(uint32_t *)(&data[4])) != HEADER_BLOCK_MAGIC) {
+               return -EINVAL;
+       }
+
+       uint32_t offset = ntohl(*(uint32_t *)(&data[8]));
+       uint16_t size_bits = ntohs(*(uint16_t *)(&data[12]));
+
+       /* Check that ECC header fits. */
+       if (offset + 3 >= size) {
+               return -EINVAL;
+       }
+
+       /* Validate ECC block. */
+       uint8_t * ecc = &data[offset];
+       if (   ecc[ID] != ECC_BLOCK_ID
+           || ecc[LEN] < BLOCK_SIZE
+           || ecc[LEN] + offset > size
+           || ecc[LEN] - BLOCK_SIZE != size_bits / 8
+           || ecc[VER] != 1
+           || ecc[TYP] != 1) {
+               return -EINVAL;
+       }
+
+       /*
+        * Use the header block to locate the ECC block and verify the data
+        * blocks [2] against the ecc block ECC.
+        */
+       ret = verify_bch(
+               bch_configuration.data_ecc_capability_bits,
+               bch_configuration.prim_poly,
+               &data[data[LEN]],
+               offset - data[LEN],
+               &data[offset + BLOCK_SIZE],
+               ecc[LEN] - BLOCK_SIZE);
+       if (ret < 0) {
+               return ret;
+       }
+
+       /* Stop after ECC.  Ignore possible zero padding. */
+       size = offset;
+
+       for (;;) {
+               /* Move to next block. */
+               size -= data[LEN];
+               data += data[LEN];
+
+               if (size == 0) {
+                       /* Finished iterating through blocks. */
+                       return 0;
+               }
+
+               if (   size < BLOCK_SIZE
+                   || data[LEN] < BLOCK_SIZE) {
+                       /* Not enough data for a header, or short header. */
+                       return -EINVAL;
+               }
+
+               ret = fn(
+                       userdata,
+                       data[ID],
+                       data[VER],
+                       data[TYP],
+                       data[LEN] - BLOCK_SIZE,
+                       &data[BLOCK_SIZE]);
+               if (ret) {
+                       return ret;
+               }
+       }
+}
diff --git a/board/ge/bx50v3/vpd_reader.h b/board/ge/bx50v3/vpd_reader.h
new file mode 100644 (file)
index 0000000..efa172a
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2016 General Electric Company
+ *
+ * SPDX-License-Identifier:    GPL-2.0+
+ */
+
+#include "common.h"
+
+/*
+ * Read VPD from given data, verify content, and call callback
+ * for each vital product data block.
+ *
+ * Returns Non-zero on error.  Negative numbers encode errno.
+ */
+int vpd_reader(
+       size_t size,
+       uint8_t * data,
+       void * userdata,
+       int (*fn)(
+           void * userdata,
+           uint8_t id,
+           uint8_t version,
+           uint8_t type,
+           size_t size,
+           uint8_t const * data));
index a0468d2..33f5101 100644 (file)
 #define CONFIG_SYS_I2C_MXC_I2C2
 #define CONFIG_SYS_I2C_MXC_I2C3
 
+#define CONFIG_SYS_NUM_I2C_BUSES        9
+#define CONFIG_SYS_I2C_MAX_HOPS         1
+#define CONFIG_SYS_I2C_BUSES   {       {0, {I2C_NULL_HOP} }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 0} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 1} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 2} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 3} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 4} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 5} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 6} } }, \
+                                       {0, {{I2C_MUX_PCA9547, 0x70, 7} } }, \
+                               }
+
+#define CONFIG_BCH
+
 #endif /* __GE_BX50V3_CONFIG_H */