linux/drivers/nvmem/layouts/sl28vpd.c
Rafał Miłecki 401df0d4f4 nvmem: layouts: refactor .add_cells() callback arguments
Simply pass whole "struct nvmem_layout" instead of single variables.
There is nothing in "struct nvmem_layout" that we have to hide from
layout drivers. They also access it during .probe() and .remove().

Thanks to this change:

1. API gets more consistent
   All layouts drivers callbacks get the same argument

2. Layouts get correct device
   Before this change NVMEM core code was passing NVMEM device instead
   of layout device. That resulted in:
   * Confusing prints
   * Calling devm_*() helpers on wrong device
   * Helpers like of_device_get_match_data() dereferencing NULLs

3. It gets possible to get match data
   First of all nvmem_layout_get_match_data() requires passing "struct
   nvmem_layout" which .add_cells() callback didn't have before this. It
   doesn't matter much as it's rather useless now anyway (and will be
   dropped).
   What's more important however is that of_device_get_match_data() can
   be used now thanks to owning a proper device pointer.

Signed-off-by: Rafał Miłecki <rafal@milecki.pl>
Reviewed-by: Miquel Raynal <miquel.raynal@bootlin.com>
Reviewed-by: Michael Walle <michael@walle.cc>
Link: https://lore.kernel.org/r/20231219120104.3422-1-zajec5@gmail.com
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
2024-01-04 17:01:13 +01:00

171 lines
3.7 KiB
C

// 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 nvmem_layout *layout)
{
struct nvmem_device *nvmem = layout->nvmem;
struct device *dev = &layout->dev;
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 int sl28vpd_probe(struct nvmem_layout *layout)
{
layout->add_cells = sl28vpd_add_cells;
return nvmem_layout_register(layout);
}
static void sl28vpd_remove(struct nvmem_layout *layout)
{
nvmem_layout_unregister(layout);
}
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_driver sl28vpd_layout = {
.driver = {
.owner = THIS_MODULE,
.name = "kontron-sl28vpd-layout",
.of_match_table = sl28vpd_of_match_table,
},
.probe = sl28vpd_probe,
.remove = sl28vpd_remove,
};
module_nvmem_layout_driver(sl28vpd_layout);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Michael Walle <michael@walle.cc>");
MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards");