Commit d9fae023 authored by Michael Walle's avatar Michael Walle Committed by Greg Kroah-Hartman
Browse files

nvmem: layouts: sl28vpd: Add new layout driver



This layout applies to the VPD of the Kontron sl28 boards. The VPD only
contains a base MAC address. Therefore, we have to add an individual
offset to it. This is done by taking the second argument of the nvmem
phandle into account. Also this let us checking the VPD version and the
checksum.

Signed-off-by: default avatarMichael Walle <michael@walle.cc>
Signed-off-by: default avatarMiquel Raynal <miquel.raynal@bootlin.com>
Signed-off-by: default avatarSrinivas Kandagatla <srinivas.kandagatla@linaro.org>
Link: https://lore.kernel.org/r/20230404172148.82422-22-srinivas.kandagatla@linaro.org


Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 8a134fd9
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -2,4 +2,13 @@

menu "Layout Types"

config NVMEM_LAYOUT_SL28_VPD
	tristate "Kontron sl28 VPD layout support"
	select CRC8
	help
	  Say Y here if you want to support the VPD layout of the Kontron
	  SMARC-sAL28 boards.

	  If unsure, say N.

endmenu
+2 −0
Original line number Diff line number Diff line
@@ -2,3 +2,5 @@
#
# Makefile for nvmem layouts.
#

obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o
+165 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0

#include <linux/crc8.h>
#include <linux/etherdevice.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
#include <linux/of.h>
#include <uapi/linux/if_ether.h>

#define SL28VPD_MAGIC 'V'

struct sl28vpd_header {
	u8 magic;
	u8 version;
} __packed;

struct sl28vpd_v1 {
	struct sl28vpd_header header;
	char serial_number[15];
	u8 base_mac_address[ETH_ALEN];
	u8 crc8;
} __packed;

static int sl28vpd_mac_address_pp(void *priv, const char *id, int index,
				  unsigned int offset, void *buf,
				  size_t bytes)
{
	if (bytes != ETH_ALEN)
		return -EINVAL;

	if (index < 0)
		return -EINVAL;

	if (!is_valid_ether_addr(buf))
		return -EINVAL;

	eth_addr_add(buf, index);

	return 0;
}

static const struct nvmem_cell_info sl28vpd_v1_entries[] = {
	{
		.name = "serial-number",
		.offset = offsetof(struct sl28vpd_v1, serial_number),
		.bytes = sizeof_field(struct sl28vpd_v1, serial_number),
	},
	{
		.name = "base-mac-address",
		.offset = offsetof(struct sl28vpd_v1, base_mac_address),
		.bytes = sizeof_field(struct sl28vpd_v1, base_mac_address),
		.read_post_process = sl28vpd_mac_address_pp,
	},
};

static int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem)
{
	struct sl28vpd_v1 data_v1;
	u8 table[CRC8_TABLE_SIZE];
	int ret;
	u8 crc;

	crc8_populate_msb(table, 0x07);

	ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1);
	if (ret < 0)
		return ret;
	else if (ret != sizeof(data_v1))
		return -EIO;

	crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0);

	if (crc != data_v1.crc8) {
		dev_err(dev,
			"Checksum is invalid (got %02x, expected %02x).\n",
			crc, data_v1.crc8);
		return -EINVAL;
	}

	return 0;
}

static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
			     struct nvmem_layout *layout)
{
	const struct nvmem_cell_info *pinfo;
	struct nvmem_cell_info info = {0};
	struct device_node *layout_np;
	struct sl28vpd_header hdr;
	int ret, i;

	/* check header */
	ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
	if (ret < 0)
		return ret;
	else if (ret != sizeof(hdr))
		return -EIO;

	if (hdr.magic != SL28VPD_MAGIC) {
		dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic);
		return -EINVAL;
	}

	if (hdr.version != 1) {
		dev_err(dev, "Version %d is unsupported.\n", hdr.version);
		return -EINVAL;
	}

	ret = sl28vpd_v1_check_crc(dev, nvmem);
	if (ret)
		return ret;

	layout_np = of_nvmem_layout_get_container(nvmem);
	if (!layout_np)
		return -ENOENT;

	for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) {
		pinfo = &sl28vpd_v1_entries[i];

		info.name = pinfo->name;
		info.offset = pinfo->offset;
		info.bytes = pinfo->bytes;
		info.read_post_process = pinfo->read_post_process;
		info.np = of_get_child_by_name(layout_np, pinfo->name);

		ret = nvmem_add_one_cell(nvmem, &info);
		if (ret) {
			of_node_put(layout_np);
			return ret;
		}
	}

	of_node_put(layout_np);

	return 0;
}

static const struct of_device_id sl28vpd_of_match_table[] = {
	{ .compatible = "kontron,sl28-vpd" },
	{},
};
MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);

struct nvmem_layout sl28vpd_layout = {
	.name = "sl28-vpd",
	.of_match_table = sl28vpd_of_match_table,
	.add_cells = sl28vpd_add_cells,
};

static int __init sl28vpd_init(void)
{
	return nvmem_layout_register(&sl28vpd_layout);
}

static void __exit sl28vpd_exit(void)
{
	nvmem_layout_unregister(&sl28vpd_layout);
}

module_init(sl28vpd_init);
module_exit(sl28vpd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");