162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci
362306a36Sopenharmony_ci#include <linux/crc8.h>
462306a36Sopenharmony_ci#include <linux/etherdevice.h>
562306a36Sopenharmony_ci#include <linux/nvmem-consumer.h>
662306a36Sopenharmony_ci#include <linux/nvmem-provider.h>
762306a36Sopenharmony_ci#include <linux/of.h>
862306a36Sopenharmony_ci#include <uapi/linux/if_ether.h>
962306a36Sopenharmony_ci
1062306a36Sopenharmony_ci#define SL28VPD_MAGIC 'V'
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct sl28vpd_header {
1362306a36Sopenharmony_ci	u8 magic;
1462306a36Sopenharmony_ci	u8 version;
1562306a36Sopenharmony_ci} __packed;
1662306a36Sopenharmony_ci
1762306a36Sopenharmony_cistruct sl28vpd_v1 {
1862306a36Sopenharmony_ci	struct sl28vpd_header header;
1962306a36Sopenharmony_ci	char serial_number[15];
2062306a36Sopenharmony_ci	u8 base_mac_address[ETH_ALEN];
2162306a36Sopenharmony_ci	u8 crc8;
2262306a36Sopenharmony_ci} __packed;
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_cistatic int sl28vpd_mac_address_pp(void *priv, const char *id, int index,
2562306a36Sopenharmony_ci				  unsigned int offset, void *buf,
2662306a36Sopenharmony_ci				  size_t bytes)
2762306a36Sopenharmony_ci{
2862306a36Sopenharmony_ci	if (bytes != ETH_ALEN)
2962306a36Sopenharmony_ci		return -EINVAL;
3062306a36Sopenharmony_ci
3162306a36Sopenharmony_ci	if (index < 0)
3262306a36Sopenharmony_ci		return -EINVAL;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	if (!is_valid_ether_addr(buf))
3562306a36Sopenharmony_ci		return -EINVAL;
3662306a36Sopenharmony_ci
3762306a36Sopenharmony_ci	eth_addr_add(buf, index);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_ci	return 0;
4062306a36Sopenharmony_ci}
4162306a36Sopenharmony_ci
4262306a36Sopenharmony_cistatic const struct nvmem_cell_info sl28vpd_v1_entries[] = {
4362306a36Sopenharmony_ci	{
4462306a36Sopenharmony_ci		.name = "serial-number",
4562306a36Sopenharmony_ci		.offset = offsetof(struct sl28vpd_v1, serial_number),
4662306a36Sopenharmony_ci		.bytes = sizeof_field(struct sl28vpd_v1, serial_number),
4762306a36Sopenharmony_ci	},
4862306a36Sopenharmony_ci	{
4962306a36Sopenharmony_ci		.name = "base-mac-address",
5062306a36Sopenharmony_ci		.offset = offsetof(struct sl28vpd_v1, base_mac_address),
5162306a36Sopenharmony_ci		.bytes = sizeof_field(struct sl28vpd_v1, base_mac_address),
5262306a36Sopenharmony_ci		.read_post_process = sl28vpd_mac_address_pp,
5362306a36Sopenharmony_ci	},
5462306a36Sopenharmony_ci};
5562306a36Sopenharmony_ci
5662306a36Sopenharmony_cistatic int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem)
5762306a36Sopenharmony_ci{
5862306a36Sopenharmony_ci	struct sl28vpd_v1 data_v1;
5962306a36Sopenharmony_ci	u8 table[CRC8_TABLE_SIZE];
6062306a36Sopenharmony_ci	int ret;
6162306a36Sopenharmony_ci	u8 crc;
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	crc8_populate_msb(table, 0x07);
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1);
6662306a36Sopenharmony_ci	if (ret < 0)
6762306a36Sopenharmony_ci		return ret;
6862306a36Sopenharmony_ci	else if (ret != sizeof(data_v1))
6962306a36Sopenharmony_ci		return -EIO;
7062306a36Sopenharmony_ci
7162306a36Sopenharmony_ci	crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0);
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_ci	if (crc != data_v1.crc8) {
7462306a36Sopenharmony_ci		dev_err(dev,
7562306a36Sopenharmony_ci			"Checksum is invalid (got %02x, expected %02x).\n",
7662306a36Sopenharmony_ci			crc, data_v1.crc8);
7762306a36Sopenharmony_ci		return -EINVAL;
7862306a36Sopenharmony_ci	}
7962306a36Sopenharmony_ci
8062306a36Sopenharmony_ci	return 0;
8162306a36Sopenharmony_ci}
8262306a36Sopenharmony_ci
8362306a36Sopenharmony_cistatic int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem,
8462306a36Sopenharmony_ci			     struct nvmem_layout *layout)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	const struct nvmem_cell_info *pinfo;
8762306a36Sopenharmony_ci	struct nvmem_cell_info info = {0};
8862306a36Sopenharmony_ci	struct device_node *layout_np;
8962306a36Sopenharmony_ci	struct sl28vpd_header hdr;
9062306a36Sopenharmony_ci	int ret, i;
9162306a36Sopenharmony_ci
9262306a36Sopenharmony_ci	/* check header */
9362306a36Sopenharmony_ci	ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
9462306a36Sopenharmony_ci	if (ret < 0)
9562306a36Sopenharmony_ci		return ret;
9662306a36Sopenharmony_ci	else if (ret != sizeof(hdr))
9762306a36Sopenharmony_ci		return -EIO;
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_ci	if (hdr.magic != SL28VPD_MAGIC) {
10062306a36Sopenharmony_ci		dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic);
10162306a36Sopenharmony_ci		return -EINVAL;
10262306a36Sopenharmony_ci	}
10362306a36Sopenharmony_ci
10462306a36Sopenharmony_ci	if (hdr.version != 1) {
10562306a36Sopenharmony_ci		dev_err(dev, "Version %d is unsupported.\n", hdr.version);
10662306a36Sopenharmony_ci		return -EINVAL;
10762306a36Sopenharmony_ci	}
10862306a36Sopenharmony_ci
10962306a36Sopenharmony_ci	ret = sl28vpd_v1_check_crc(dev, nvmem);
11062306a36Sopenharmony_ci	if (ret)
11162306a36Sopenharmony_ci		return ret;
11262306a36Sopenharmony_ci
11362306a36Sopenharmony_ci	layout_np = of_nvmem_layout_get_container(nvmem);
11462306a36Sopenharmony_ci	if (!layout_np)
11562306a36Sopenharmony_ci		return -ENOENT;
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) {
11862306a36Sopenharmony_ci		pinfo = &sl28vpd_v1_entries[i];
11962306a36Sopenharmony_ci
12062306a36Sopenharmony_ci		info.name = pinfo->name;
12162306a36Sopenharmony_ci		info.offset = pinfo->offset;
12262306a36Sopenharmony_ci		info.bytes = pinfo->bytes;
12362306a36Sopenharmony_ci		info.read_post_process = pinfo->read_post_process;
12462306a36Sopenharmony_ci		info.np = of_get_child_by_name(layout_np, pinfo->name);
12562306a36Sopenharmony_ci
12662306a36Sopenharmony_ci		ret = nvmem_add_one_cell(nvmem, &info);
12762306a36Sopenharmony_ci		if (ret) {
12862306a36Sopenharmony_ci			of_node_put(layout_np);
12962306a36Sopenharmony_ci			return ret;
13062306a36Sopenharmony_ci		}
13162306a36Sopenharmony_ci	}
13262306a36Sopenharmony_ci
13362306a36Sopenharmony_ci	of_node_put(layout_np);
13462306a36Sopenharmony_ci
13562306a36Sopenharmony_ci	return 0;
13662306a36Sopenharmony_ci}
13762306a36Sopenharmony_ci
13862306a36Sopenharmony_cistatic const struct of_device_id sl28vpd_of_match_table[] = {
13962306a36Sopenharmony_ci	{ .compatible = "kontron,sl28-vpd" },
14062306a36Sopenharmony_ci	{},
14162306a36Sopenharmony_ci};
14262306a36Sopenharmony_ciMODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);
14362306a36Sopenharmony_ci
14462306a36Sopenharmony_cistatic struct nvmem_layout sl28vpd_layout = {
14562306a36Sopenharmony_ci	.name = "sl28-vpd",
14662306a36Sopenharmony_ci	.of_match_table = sl28vpd_of_match_table,
14762306a36Sopenharmony_ci	.add_cells = sl28vpd_add_cells,
14862306a36Sopenharmony_ci};
14962306a36Sopenharmony_cimodule_nvmem_layout_driver(sl28vpd_layout);
15062306a36Sopenharmony_ci
15162306a36Sopenharmony_ciMODULE_LICENSE("GPL");
15262306a36Sopenharmony_ciMODULE_AUTHOR("Michael Walle <michael@walle.cc>");
15362306a36Sopenharmony_ciMODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");
154