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