--- /dev/null
+From c22bc82183c2dea64919f975473ec518738baa3e Mon Sep 17 00:00:00 2001
+From: Daniel Golle <daniel@makrotopia.org>
+Date: Wed, 12 Jul 2023 13:38:35 +0100
+Subject: [PATCH] nvmem: add layout for Adtran devices
+
+Adtran stores unique factory data on GPT partitions on the eMMC.
+Using blk-nvmem the 'mfginfo' partition gets exposes as NVMEM provider.
+
+Add layout driver to parse mfginfo, mainly to provide MAC addresses to
+Ethernet and wireless interfaces.
+
+Variable names are converted to lower-case and '_' is replaced with '-'
+in order to comply with the device tree node naming convention.
+The main MAC address always ends on a 0 and up to 16 addresses are
+alocated for each device to use for various interfaces.
+
+Implement post-processing function for 'MFG_MAC' variable ('mfg-mac'
+node name in device tree) adding the nvmem cell index to the least
+significant digit of the MAC address.
+
+Signed-off-by: Daniel Golle <daniel@makrotopia.org>
+---
+ drivers/nvmem/layouts/Kconfig | 9 +++
+ drivers/nvmem/layouts/Makefile | 1 +
+ drivers/nvmem/layouts/adtran.c | 135 +++++++++++++++++++++++++++++++++
+ 3 files changed, 145 insertions(+)
+ create mode 100644 drivers/nvmem/layouts/adtran.c
+
+--- a/drivers/nvmem/layouts/Kconfig
++++ b/drivers/nvmem/layouts/Kconfig
+@@ -8,6 +8,15 @@ if NVMEM_LAYOUTS
+
+ menu "Layout Types"
+
++config NVMEM_LAYOUT_ADTRAN
++ tristate "Adtran mfginfo layout support"
++ select GENERIC_NET_UTILS
++ help
++ Say Y here if you want to support the layout used by Adtran for
++ mfginfo.
++
++ If unsure, say N.
++
+ config NVMEM_LAYOUT_SL28_VPD
+ tristate "Kontron sl28 VPD layout support"
+ select CRC8
+--- a/drivers/nvmem/layouts/Makefile
++++ b/drivers/nvmem/layouts/Makefile
+@@ -6,3 +6,4 @@
+ obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
+ obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o
+ obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o
++obj-$(CONFIG_NVMEM_LAYOUT_ADTRAN) += adtran.o
+--- /dev/null
++++ b/drivers/nvmem/layouts/adtran.c
+@@ -0,0 +1,135 @@
++// SPDX-License-Identifier: GPL-2.0
++#include <linux/ctype.h>
++#include <linux/etherdevice.h>
++#include <linux/nvmem-consumer.h>
++#include <linux/nvmem-provider.h>
++#include <linux/of.h>
++#include <linux/platform_device.h>
++
++/*
++ * Adtran devices usually come with a main MAC address ending on 0 and
++ * hence may have up to 16 MAC addresses per device.
++ * The main MAC address is stored as variable MFG_MAC in ASCII format.
++ */
++static int adtran_mac_address_pp(void *priv, const char *id, int index,
++ unsigned int offset, void *buf,
++ size_t bytes)
++{
++ u8 mac[ETH_ALEN];
++
++ if (WARN_ON(bytes != 3 * ETH_ALEN - 1))
++ return -EINVAL;
++
++ if (!mac_pton(buf, mac))
++ return -EINVAL;
++
++ if (index)
++ eth_addr_add(mac, index);
++
++ ether_addr_copy(buf, mac);
++
++ return 0;
++}
++
++static int adtran_add_cells(struct nvmem_layout *layout)
++{
++ struct nvmem_device *nvmem = layout->nvmem;
++ struct nvmem_cell_info info;
++ struct device_node *layout_np;
++ char mfginfo[1024], *c, *t, *p;
++ int ret = -EINVAL;
++
++ ret = nvmem_device_read(nvmem, 0, sizeof(mfginfo), mfginfo);
++ if (ret < 0)
++ return ret;
++ else if (ret != sizeof(mfginfo))
++ return -EIO;
++
++ layout_np = of_nvmem_layout_get_container(nvmem);
++ if (!layout_np)
++ return -ENOENT;
++
++ c = mfginfo;
++ while (*c != 0xff) {
++ memset(&info, 0, sizeof(info));
++ if (*c == '#')
++ goto nextline;
++
++ t = strchr(c, '=');
++ if (!t)
++ goto nextline;
++
++ *t = '\0';
++ ++t;
++ info.offset = t - mfginfo;
++ /* process variable name: convert to lower-case, '_' -> '-' */
++ p = c;
++ do {
++ *p = tolower(*p);
++ if (*p == '_')
++ *p = '-';
++ } while (*++p);
++ info.name = c;
++ c = strchr(t, 0xa); /* find newline */
++ if (!c)
++ break;
++
++ info.bytes = c - t;
++ if (!strcmp(info.name, "mfg-mac")) {
++ info.raw_len = info.bytes;
++ info.bytes = ETH_ALEN;
++ info.read_post_process = adtran_mac_address_pp;
++ }
++
++ info.np = of_get_child_by_name(layout_np, info.name);
++ ret = nvmem_add_one_cell(nvmem, &info);
++ if (ret)
++ break;
++
++ ++c;
++ continue;
++
++nextline:
++ c = strchr(c, 0xa); /* find newline */
++ if (!c)
++ break;
++ ++c;
++ }
++
++ of_node_put(layout_np);
++
++ return ret;
++}
++
++static int adtran_probe(struct nvmem_layout *layout)
++{
++ layout->add_cells = adtran_add_cells;
++
++ return nvmem_layout_register(layout);
++}
++
++static void adtran_remove(struct nvmem_layout *layout)
++{
++ nvmem_layout_unregister(layout);
++}
++
++static const struct of_device_id adtran_of_match_table[] = {
++ { .compatible = "adtran,mfginfo" },
++ {},
++};
++MODULE_DEVICE_TABLE(of, adtran_of_match_table);
++
++static struct nvmem_layout_driver adtran_layout = {
++ .driver = {
++ .owner = THIS_MODULE,
++ .name = "adtran-layout",
++ .of_match_table = adtran_of_match_table,
++ },
++ .probe = adtran_probe,
++ .remove = adtran_remove,
++};
++module_nvmem_layout_driver(adtran_layout);
++
++MODULE_LICENSE("GPL");
++MODULE_AUTHOR("Daniel Golle <daniel@makrotopia.org>");
++MODULE_DESCRIPTION("NVMEM layout driver for Adtran mfginfo");