// SPDX-License-Identifier: GPL-2.0 #include #include #include #include #include #include #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); static struct nvmem_layout sl28vpd_layout = { .name = "sl28-vpd", .of_match_table = sl28vpd_of_match_table, .add_cells = sl28vpd_add_cells, }; module_nvmem_layout_driver(sl28vpd_layout); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Michael Walle "); MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");