1d9fae023SMichael Walle // SPDX-License-Identifier: GPL-2.0
2d9fae023SMichael Walle
3d9fae023SMichael Walle #include <linux/crc8.h>
4d9fae023SMichael Walle #include <linux/etherdevice.h>
5d9fae023SMichael Walle #include <linux/nvmem-consumer.h>
6d9fae023SMichael Walle #include <linux/nvmem-provider.h>
7d9fae023SMichael Walle #include <linux/of.h>
8d9fae023SMichael Walle #include <uapi/linux/if_ether.h>
9d9fae023SMichael Walle
10d9fae023SMichael Walle #define SL28VPD_MAGIC 'V'
11d9fae023SMichael Walle
12d9fae023SMichael Walle struct sl28vpd_header {
13d9fae023SMichael Walle u8 magic;
14d9fae023SMichael Walle u8 version;
15d9fae023SMichael Walle } __packed;
16d9fae023SMichael Walle
17d9fae023SMichael Walle struct sl28vpd_v1 {
18d9fae023SMichael Walle struct sl28vpd_header header;
19d9fae023SMichael Walle char serial_number[15];
20d9fae023SMichael Walle u8 base_mac_address[ETH_ALEN];
21d9fae023SMichael Walle u8 crc8;
22d9fae023SMichael Walle } __packed;
23d9fae023SMichael Walle
sl28vpd_mac_address_pp(void * priv,const char * id,int index,unsigned int offset,void * buf,size_t bytes)24d9fae023SMichael Walle static int sl28vpd_mac_address_pp(void *priv, const char *id, int index,
25d9fae023SMichael Walle unsigned int offset, void *buf,
26d9fae023SMichael Walle size_t bytes)
27d9fae023SMichael Walle {
28d9fae023SMichael Walle if (bytes != ETH_ALEN)
29d9fae023SMichael Walle return -EINVAL;
30d9fae023SMichael Walle
31d9fae023SMichael Walle if (index < 0)
32d9fae023SMichael Walle return -EINVAL;
33d9fae023SMichael Walle
34d9fae023SMichael Walle if (!is_valid_ether_addr(buf))
35d9fae023SMichael Walle return -EINVAL;
36d9fae023SMichael Walle
37d9fae023SMichael Walle eth_addr_add(buf, index);
38d9fae023SMichael Walle
39d9fae023SMichael Walle return 0;
40d9fae023SMichael Walle }
41d9fae023SMichael Walle
42d9fae023SMichael Walle static const struct nvmem_cell_info sl28vpd_v1_entries[] = {
43d9fae023SMichael Walle {
44d9fae023SMichael Walle .name = "serial-number",
45d9fae023SMichael Walle .offset = offsetof(struct sl28vpd_v1, serial_number),
46d9fae023SMichael Walle .bytes = sizeof_field(struct sl28vpd_v1, serial_number),
47d9fae023SMichael Walle },
48d9fae023SMichael Walle {
49d9fae023SMichael Walle .name = "base-mac-address",
50d9fae023SMichael Walle .offset = offsetof(struct sl28vpd_v1, base_mac_address),
51d9fae023SMichael Walle .bytes = sizeof_field(struct sl28vpd_v1, base_mac_address),
52d9fae023SMichael Walle .read_post_process = sl28vpd_mac_address_pp,
53d9fae023SMichael Walle },
54d9fae023SMichael Walle };
55d9fae023SMichael Walle
sl28vpd_v1_check_crc(struct device * dev,struct nvmem_device * nvmem)56d9fae023SMichael Walle static int sl28vpd_v1_check_crc(struct device *dev, struct nvmem_device *nvmem)
57d9fae023SMichael Walle {
58d9fae023SMichael Walle struct sl28vpd_v1 data_v1;
59d9fae023SMichael Walle u8 table[CRC8_TABLE_SIZE];
60d9fae023SMichael Walle int ret;
61d9fae023SMichael Walle u8 crc;
62d9fae023SMichael Walle
63d9fae023SMichael Walle crc8_populate_msb(table, 0x07);
64d9fae023SMichael Walle
65d9fae023SMichael Walle ret = nvmem_device_read(nvmem, 0, sizeof(data_v1), &data_v1);
66d9fae023SMichael Walle if (ret < 0)
67d9fae023SMichael Walle return ret;
68d9fae023SMichael Walle else if (ret != sizeof(data_v1))
69d9fae023SMichael Walle return -EIO;
70d9fae023SMichael Walle
71d9fae023SMichael Walle crc = crc8(table, (void *)&data_v1, sizeof(data_v1) - 1, 0);
72d9fae023SMichael Walle
73d9fae023SMichael Walle if (crc != data_v1.crc8) {
74d9fae023SMichael Walle dev_err(dev,
75d9fae023SMichael Walle "Checksum is invalid (got %02x, expected %02x).\n",
76d9fae023SMichael Walle crc, data_v1.crc8);
77d9fae023SMichael Walle return -EINVAL;
78d9fae023SMichael Walle }
79d9fae023SMichael Walle
80d9fae023SMichael Walle return 0;
81d9fae023SMichael Walle }
82d9fae023SMichael Walle
sl28vpd_add_cells(struct device * dev,struct nvmem_device * nvmem)83*276dae17SMiquel Raynal static int sl28vpd_add_cells(struct device *dev, struct nvmem_device *nvmem)
84d9fae023SMichael Walle {
85d9fae023SMichael Walle const struct nvmem_cell_info *pinfo;
86d9fae023SMichael Walle struct nvmem_cell_info info = {0};
87d9fae023SMichael Walle struct device_node *layout_np;
88d9fae023SMichael Walle struct sl28vpd_header hdr;
89d9fae023SMichael Walle int ret, i;
90d9fae023SMichael Walle
91d9fae023SMichael Walle /* check header */
92d9fae023SMichael Walle ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr);
93d9fae023SMichael Walle if (ret < 0)
94d9fae023SMichael Walle return ret;
95d9fae023SMichael Walle else if (ret != sizeof(hdr))
96d9fae023SMichael Walle return -EIO;
97d9fae023SMichael Walle
98d9fae023SMichael Walle if (hdr.magic != SL28VPD_MAGIC) {
99d9fae023SMichael Walle dev_err(dev, "Invalid magic value (%02x)\n", hdr.magic);
100d9fae023SMichael Walle return -EINVAL;
101d9fae023SMichael Walle }
102d9fae023SMichael Walle
103d9fae023SMichael Walle if (hdr.version != 1) {
104d9fae023SMichael Walle dev_err(dev, "Version %d is unsupported.\n", hdr.version);
105d9fae023SMichael Walle return -EINVAL;
106d9fae023SMichael Walle }
107d9fae023SMichael Walle
108d9fae023SMichael Walle ret = sl28vpd_v1_check_crc(dev, nvmem);
109d9fae023SMichael Walle if (ret)
110d9fae023SMichael Walle return ret;
111d9fae023SMichael Walle
112d9fae023SMichael Walle layout_np = of_nvmem_layout_get_container(nvmem);
113d9fae023SMichael Walle if (!layout_np)
114d9fae023SMichael Walle return -ENOENT;
115d9fae023SMichael Walle
116d9fae023SMichael Walle for (i = 0; i < ARRAY_SIZE(sl28vpd_v1_entries); i++) {
117d9fae023SMichael Walle pinfo = &sl28vpd_v1_entries[i];
118d9fae023SMichael Walle
119d9fae023SMichael Walle info.name = pinfo->name;
120d9fae023SMichael Walle info.offset = pinfo->offset;
121d9fae023SMichael Walle info.bytes = pinfo->bytes;
122d9fae023SMichael Walle info.read_post_process = pinfo->read_post_process;
123d9fae023SMichael Walle info.np = of_get_child_by_name(layout_np, pinfo->name);
124d9fae023SMichael Walle
125d9fae023SMichael Walle ret = nvmem_add_one_cell(nvmem, &info);
126d9fae023SMichael Walle if (ret) {
127d9fae023SMichael Walle of_node_put(layout_np);
128d9fae023SMichael Walle return ret;
129d9fae023SMichael Walle }
130d9fae023SMichael Walle }
131d9fae023SMichael Walle
132d9fae023SMichael Walle of_node_put(layout_np);
133d9fae023SMichael Walle
134d9fae023SMichael Walle return 0;
135d9fae023SMichael Walle }
136d9fae023SMichael Walle
137d9fae023SMichael Walle static const struct of_device_id sl28vpd_of_match_table[] = {
138d9fae023SMichael Walle { .compatible = "kontron,sl28-vpd" },
139d9fae023SMichael Walle {},
140d9fae023SMichael Walle };
141d9fae023SMichael Walle MODULE_DEVICE_TABLE(of, sl28vpd_of_match_table);
142d9fae023SMichael Walle
143a8642cd1STom Rix static struct nvmem_layout sl28vpd_layout = {
144d9fae023SMichael Walle .name = "sl28-vpd",
145d9fae023SMichael Walle .of_match_table = sl28vpd_of_match_table,
146d9fae023SMichael Walle .add_cells = sl28vpd_add_cells,
147d9fae023SMichael Walle };
1480abdf99fSMiquel Raynal module_nvmem_layout_driver(sl28vpd_layout);
149d9fae023SMichael Walle
150d9fae023SMichael Walle MODULE_LICENSE("GPL");
151d9fae023SMichael Walle MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
152d9fae023SMichael Walle MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");
153