#include <linux/clkdev.h>
 #include <linux/clk-provider.h>
 #include <linux/platform_device.h>
+#include <linux/platform_data/i2c-xiic.h>
 #include <linux/ptp_clock_kernel.h>
 #include <linux/spi/spi.h>
 #include <linux/spi/xilinx_spi.h>
 #include <net/devlink.h>
 #include <linux/i2c.h>
 #include <linux/mtd/mtd.h>
+#include <linux/nvmem-consumer.h>
 
 #ifndef PCI_VENDOR_ID_FACEBOOK
 #define PCI_VENDOR_ID_FACEBOOK 0x1d9b
        int                     irq_vec;
 };
 
+#define OCP_BOARD_ID_LEN               13
+#define OCP_SERIAL_LEN                 6
+
 struct ptp_ocp {
        struct pci_dev          *pdev;
        struct device           dev;
        struct platform_device  *spi_flash;
        struct clk_hw           *i2c_clk;
        struct timer_list       watchdog;
+       const struct ptp_ocp_eeprom_map *eeprom_map;
        struct dentry           *debug_root;
        time64_t                gnss_lost;
        int                     id;
        int                     gnss2_port;
        int                     mac_port;       /* miniature atomic clock */
        int                     nmea_port;
-       u8                      serial[6];
-       bool                    has_serial;
+       u8                      board_id[OCP_BOARD_ID_LEN];
+       u8                      serial[OCP_SERIAL_LEN];
+       bool                    has_eeprom_data;
        u32                     pps_req_map;
        int                     flash_start;
        u32                     utc_tai_offset;
 static irqreturn_t ptp_ocp_ts_irq(int irq, void *priv);
 static int ptp_ocp_ts_enable(void *priv, u32 req, bool enable);
 
+struct ptp_ocp_eeprom_map {
+       u16     off;
+       u16     len;
+       u32     bp_offset;
+       const void * const tag;
+};
+
+#define EEPROM_ENTRY(addr, member)                             \
+       .off = addr,                                            \
+       .len = sizeof_field(struct ptp_ocp, member),            \
+       .bp_offset = offsetof(struct ptp_ocp, member)
+
+#define BP_MAP_ENTRY_ADDR(bp, map) ({                          \
+       (void *)((uintptr_t)(bp) + (map)->bp_offset);           \
+})
+
+static struct ptp_ocp_eeprom_map fb_eeprom_map[] = {
+       { EEPROM_ENTRY(0x43, board_id) },
+       { EEPROM_ENTRY(0x00, serial), .tag = "mac" },
+       { }
+};
+
 #define bp_assign_entry(bp, res, val) ({                               \
        uintptr_t addr = (uintptr_t)(bp) + (res)->bp_offset;            \
        *(typeof(val) *)addr = val;                                     \
                .extra = &(struct ptp_ocp_i2c_info) {
                        .name = "xiic-i2c",
                        .fixed_rate = 50000000,
+                       .data_size = sizeof(struct xiic_i2c_platform_data),
+                       .data = &(struct xiic_i2c_platform_data) {
+                               .num_devices = 2,
+                               .devices = (struct i2c_board_info[]) {
+                                       { I2C_BOARD_INFO("24c02", 0x50) },
+                                       { I2C_BOARD_INFO("24mac402", 0x58),
+                                         .platform_data = "mac" },
+                               },
+                       },
                },
        },
        {
        return gnss_name[idx];
 }
 
+struct ptp_ocp_nvmem_match_info {
+       struct ptp_ocp *bp;
+       const void * const tag;
+};
+
 static int
-ptp_ocp_firstchild(struct device *dev, void *data)
+ptp_ocp_nvmem_match(struct device *dev, const void *data)
 {
-       return 1;
+       const struct ptp_ocp_nvmem_match_info *info = data;
+
+       dev = dev->parent;
+       if (!i2c_verify_client(dev) || info->tag != dev->platform_data)
+               return 0;
+
+       while ((dev = dev->parent))
+               if (dev->driver && !strcmp(dev->driver->name, KBUILD_MODNAME))
+                       return info->bp == dev_get_drvdata(dev);
+       return 0;
 }
 
-static int
-ptp_ocp_read_i2c(struct i2c_adapter *adap, u8 addr, u8 reg, u8 sz, u8 *data)
+static inline struct nvmem_device *
+ptp_ocp_nvmem_device_get(struct ptp_ocp *bp, const void * const tag)
 {
-       struct i2c_msg msgs[2] = {
-               {
-                       .addr = addr,
-                       .len = 1,
-                       .buf = ®,
-               },
-               {
-                       .addr = addr,
-                       .flags = I2C_M_RD,
-                       .len = 2,
-                       .buf = data,
-               },
-       };
-       int err;
-       u8 len;
-
-       /* xiic-i2c for some stupid reason only does 2 byte reads. */
-       while (sz) {
-               len = min_t(u8, sz, 2);
-               msgs[1].len = len;
-               err = i2c_transfer(adap, msgs, 2);
-               if (err != msgs[1].len)
-                       return err;
-               msgs[1].buf += len;
-               reg += len;
-               sz -= len;
+       struct ptp_ocp_nvmem_match_info info = { .bp = bp, .tag = tag };
+
+       return nvmem_device_find(&info, ptp_ocp_nvmem_match);
+}
+
+static inline void
+ptp_ocp_nvmem_device_put(struct nvmem_device **nvmemp)
+{
+       if (*nvmemp != NULL) {
+               nvmem_device_put(*nvmemp);
+               *nvmemp = NULL;
        }
-       return 0;
 }
 
 static void
-ptp_ocp_get_serial_number(struct ptp_ocp *bp)
+ptp_ocp_read_eeprom(struct ptp_ocp *bp)
 {
-       struct i2c_adapter *adap;
-       struct device *dev;
-       int err;
+       const struct ptp_ocp_eeprom_map *map;
+       struct nvmem_device *nvmem;
+       const void *tag;
+       int ret;
 
        if (!bp->i2c_ctrl)
                return;
 
-       dev = device_find_child(&bp->i2c_ctrl->dev, NULL, ptp_ocp_firstchild);
-       if (!dev) {
-               dev_err(&bp->pdev->dev, "Can't find I2C adapter\n");
-               return;
-       }
-
-       adap = i2c_verify_adapter(dev);
-       if (!adap) {
-               dev_err(&bp->pdev->dev, "device '%s' isn't an I2C adapter\n",
-                       dev_name(dev));
-               goto out;
-       }
+       tag = NULL;
+       nvmem = NULL;
 
-       err = ptp_ocp_read_i2c(adap, 0x58, 0x9A, 6, bp->serial);
-       if (err) {
-               dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", err);
-               goto out;
+       for (map = bp->eeprom_map; map->len; map++) {
+               if (map->tag != tag) {
+                       tag = map->tag;
+                       ptp_ocp_nvmem_device_put(&nvmem);
+               }
+               if (!nvmem) {
+                       nvmem = ptp_ocp_nvmem_device_get(bp, tag);
+                       if (!nvmem)
+                               goto out;
+               }
+               ret = nvmem_device_read(nvmem, map->off, map->len,
+                                       BP_MAP_ENTRY_ADDR(bp, map));
+               if (ret != map->len)
+                       goto read_fail;
        }
 
-       bp->has_serial = true;
+       bp->has_eeprom_data = true;
 
 out:
-       put_device(dev);
+       ptp_ocp_nvmem_device_put(&nvmem);
+       return;
+
+read_fail:
+       dev_err(&bp->pdev->dev, "could not read eeprom: %d\n", ret);
+       goto out;
+}
+
+static int
+ptp_ocp_firstchild(struct device *dev, void *data)
+{
+       return 1;
 }
 
 static struct device *
                        return err;
        }
 
-       if (!bp->has_serial)
-               ptp_ocp_get_serial_number(bp);
-
-       if (bp->has_serial) {
-               sprintf(buf, "%pM", bp->serial);
-               err = devlink_info_serial_number_put(req, buf);
-               if (err)
-                       return err;
+       if (!bp->has_eeprom_data) {
+               ptp_ocp_read_eeprom(bp);
+               if (!bp->has_eeprom_data)
+                       return 0;
        }
 
+       sprintf(buf, "%pM", bp->serial);
+       err = devlink_info_serial_number_put(req, buf);
+       if (err)
+               return err;
+
+       err = devlink_info_version_fixed_put(req,
+                       DEVLINK_INFO_VERSION_GENERIC_BOARD_ID,
+                       bp->board_id);
+       if (err)
+               return err;
+
        return 0;
 }
 
 ptp_ocp_fb_board_init(struct ptp_ocp *bp, struct ocp_resource *r)
 {
        bp->flash_start = 1024 * 4096;
+       bp->eeprom_map = fb_eeprom_map;
 
        ptp_ocp_tod_init(bp);
        ptp_ocp_nmea_out_init(bp);
 {
        struct ptp_ocp *bp = dev_get_drvdata(dev);
 
-       if (!bp->has_serial)
-               ptp_ocp_get_serial_number(bp);
+       if (!bp->has_eeprom_data)
+               ptp_ocp_read_eeprom(bp);
 
        return sysfs_emit(buf, "%pM\n", bp->serial);
 }