diff options
Diffstat (limited to 'drivers/nvmem')
-rw-r--r-- | drivers/nvmem/Kconfig | 7 | ||||
-rw-r--r-- | drivers/nvmem/Makefile | 1 | ||||
-rw-r--r-- | drivers/nvmem/bcm-ocotp.c | 4 | ||||
-rw-r--r-- | drivers/nvmem/core.c | 170 | ||||
-rw-r--r-- | drivers/nvmem/imx-ocotp.c | 30 | ||||
-rw-r--r-- | drivers/nvmem/layouts/Kconfig | 23 | ||||
-rw-r--r-- | drivers/nvmem/layouts/Makefile | 7 | ||||
-rw-r--r-- | drivers/nvmem/layouts/onie-tlv.c | 244 | ||||
-rw-r--r-- | drivers/nvmem/layouts/sl28vpd.c | 153 | ||||
-rw-r--r-- | drivers/nvmem/mtk-efuse.c | 53 | ||||
-rw-r--r-- | drivers/nvmem/nintendo-otp.c | 4 | ||||
-rw-r--r-- | drivers/nvmem/stm32-romem.c | 2 | ||||
-rw-r--r-- | drivers/nvmem/u-boot-env.c | 26 | ||||
-rw-r--r-- | drivers/nvmem/vf610-ocotp.c | 3 |
14 files changed, 695 insertions, 32 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 6dec38805041..b291b27048c7 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -21,6 +21,10 @@ config NVMEM_SYSFS This interface is mostly used by userspace applications to read/write directly into nvmem. +# Layouts + +source "drivers/nvmem/layouts/Kconfig" + # Devices config NVMEM_APPLE_EFUSES @@ -336,6 +340,7 @@ config NVMEM_U_BOOT_ENV tristate "U-Boot environment variables support" depends on OF && MTD select CRC32 + select GENERIC_NET_UTILS help U-Boot stores its setup as environment variables. This driver adds support for verifying & exporting such data. It also exposes variables @@ -368,7 +373,7 @@ config NVMEM_VF610_OCOTP be called nvmem-vf610-ocotp. config NVMEM_ZYNQMP - bool "Xilinx ZYNQMP SoC nvmem firmware support" + tristate "Xilinx ZYNQMP SoC nvmem firmware support" depends on ARCH_ZYNQMP help This is a driver to access hardware related data like diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 6a1efffa88f0..f82431ec8aef 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -5,6 +5,7 @@ obj-$(CONFIG_NVMEM) += nvmem_core.o nvmem_core-y := core.o +obj-y += layouts/ # Devices obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o diff --git a/drivers/nvmem/bcm-ocotp.c b/drivers/nvmem/bcm-ocotp.c index a128c7f5e351..0c1fa0c4feb2 100644 --- a/drivers/nvmem/bcm-ocotp.c +++ b/drivers/nvmem/bcm-ocotp.c @@ -244,7 +244,6 @@ MODULE_DEVICE_TABLE(acpi, bcm_otpc_acpi_ids); static int bcm_otpc_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct resource *res; struct otpc_priv *priv; struct nvmem_device *nvmem; int err; @@ -259,8 +258,7 @@ static int bcm_otpc_probe(struct platform_device *pdev) return -ENODEV; /* Get OTP base address register. */ - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->base = devm_ioremap_resource(dev, res); + priv->base = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->base)) { dev_err(dev, "unable to map I/O memory\n"); return PTR_ERR(priv->base); diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 22024b830788..342cd380b420 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -17,6 +17,7 @@ #include <linux/nvmem-provider.h> #include <linux/gpio/consumer.h> #include <linux/of.h> +#include <linux/of_device.h> #include <linux/slab.h> struct nvmem_device { @@ -38,8 +39,8 @@ struct nvmem_device { unsigned int nkeepout; nvmem_reg_read_t reg_read; nvmem_reg_write_t reg_write; - nvmem_cell_post_process_t cell_post_process; struct gpio_desc *wp_gpio; + struct nvmem_layout *layout; void *priv; }; @@ -49,9 +50,12 @@ struct nvmem_device { struct nvmem_cell_entry { const char *name; int offset; + size_t raw_len; int bytes; int bit_offset; int nbits; + nvmem_cell_post_process_t read_post_process; + void *priv; struct device_node *np; struct nvmem_device *nvmem; struct list_head node; @@ -74,6 +78,9 @@ static LIST_HEAD(nvmem_lookup_list); static BLOCKING_NOTIFIER_HEAD(nvmem_notifier); +static DEFINE_SPINLOCK(nvmem_layout_lock); +static LIST_HEAD(nvmem_layouts); + static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset, void *val, size_t bytes) { @@ -463,8 +470,11 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem, { cell->nvmem = nvmem; cell->offset = info->offset; + cell->raw_len = info->raw_len ?: info->bytes; cell->bytes = info->bytes; cell->name = info->name; + cell->read_post_process = info->read_post_process; + cell->priv = info->priv; cell->bit_offset = info->bit_offset; cell->nbits = info->nbits; @@ -688,6 +698,7 @@ static int nvmem_validate_keepouts(struct nvmem_device *nvmem) static int nvmem_add_cells_from_of(struct nvmem_device *nvmem) { + struct nvmem_layout *layout = nvmem->layout; struct device *dev = &nvmem->dev; struct device_node *child; const __be32 *addr; @@ -717,6 +728,9 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem) info.np = of_node_get(child); + if (layout && layout->fixup_cell_info) + layout->fixup_cell_info(nvmem, layout, &info); + ret = nvmem_add_one_cell(nvmem, &info); kfree(info.name); if (ret) { @@ -728,6 +742,108 @@ static int nvmem_add_cells_from_of(struct nvmem_device *nvmem) return 0; } +int __nvmem_layout_register(struct nvmem_layout *layout, struct module *owner) +{ + layout->owner = owner; + + spin_lock(&nvmem_layout_lock); + list_add(&layout->node, &nvmem_layouts); + spin_unlock(&nvmem_layout_lock); + + return 0; +} +EXPORT_SYMBOL_GPL(__nvmem_layout_register); + +void nvmem_layout_unregister(struct nvmem_layout *layout) +{ + spin_lock(&nvmem_layout_lock); + list_del(&layout->node); + spin_unlock(&nvmem_layout_lock); +} +EXPORT_SYMBOL_GPL(nvmem_layout_unregister); + +static struct nvmem_layout *nvmem_layout_get(struct nvmem_device *nvmem) +{ + struct device_node *layout_np, *np = nvmem->dev.of_node; + struct nvmem_layout *l, *layout = ERR_PTR(-EPROBE_DEFER); + + layout_np = of_get_child_by_name(np, "nvmem-layout"); + if (!layout_np) + return NULL; + + /* + * In case the nvmem device was built-in while the layout was built as a + * module, we shall manually request the layout driver loading otherwise + * we'll never have any match. + */ + of_request_module(layout_np); + + spin_lock(&nvmem_layout_lock); + + list_for_each_entry(l, &nvmem_layouts, node) { + if (of_match_node(l->of_match_table, layout_np)) { + if (try_module_get(l->owner)) + layout = l; + + break; + } + } + + spin_unlock(&nvmem_layout_lock); + of_node_put(layout_np); + + return layout; +} + +static void nvmem_layout_put(struct nvmem_layout *layout) +{ + if (layout) + module_put(layout->owner); +} + +static int nvmem_add_cells_from_layout(struct nvmem_device *nvmem) +{ + struct nvmem_layout *layout = nvmem->layout; + int ret; + + if (layout && layout->add_cells) { + ret = layout->add_cells(&nvmem->dev, nvmem, layout); + if (ret) + return ret; + } + + return 0; +} + +#if IS_ENABLED(CONFIG_OF) +/** + * of_nvmem_layout_get_container() - Get OF node to layout container. + * + * @nvmem: nvmem device. + * + * Return: a node pointer with refcount incremented or NULL if no + * container exists. Use of_node_put() on it when done. + */ +struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem) +{ + return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout"); +} +EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container); +#endif + +const void *nvmem_layout_get_match_data(struct nvmem_device *nvmem, + struct nvmem_layout *layout) +{ + struct device_node __maybe_unused *layout_np; + const struct of_device_id *match; + + layout_np = of_nvmem_layout_get_container(nvmem); + match = of_match_node(layout->of_match_table, layout_np); + + return match ? match->data : NULL; +} +EXPORT_SYMBOL_GPL(nvmem_layout_get_match_data); + /** * nvmem_register() - Register a nvmem device for given nvmem_config. * Also creates a binary entry in /sys/bus/nvmem/devices/dev-name/nvmem @@ -790,7 +906,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) nvmem->type = config->type; nvmem->reg_read = config->reg_read; nvmem->reg_write = config->reg_write; - nvmem->cell_post_process = config->cell_post_process; nvmem->keepout = config->keepout; nvmem->nkeepout = config->nkeepout; if (config->of_node) @@ -834,6 +949,19 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) goto err_put_device; } + /* + * If the driver supplied a layout by config->layout, the module + * pointer will be NULL and nvmem_layout_put() will be a noop. + */ + nvmem->layout = config->layout ?: nvmem_layout_get(nvmem); + if (IS_ERR(nvmem->layout)) { + rval = PTR_ERR(nvmem->layout); + nvmem->layout = NULL; + + if (rval == -EPROBE_DEFER) + goto err_teardown_compat; + } + if (config->cells) { rval = nvmem_add_cells(nvmem, config->cells, config->ncells); if (rval) @@ -854,12 +982,18 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) if (rval) goto err_remove_cells; + rval = nvmem_add_cells_from_layout(nvmem); + if (rval) + goto err_remove_cells; + blocking_notifier_call_chain(&nvmem_notifier, NVMEM_ADD, nvmem); return nvmem; err_remove_cells: nvmem_device_remove_all_cells(nvmem); + nvmem_layout_put(nvmem->layout); +err_teardown_compat: if (config->compat) nvmem_sysfs_remove_compat(nvmem, config); err_put_device: @@ -881,6 +1015,7 @@ static void nvmem_device_release(struct kref *kref) device_remove_bin_file(nvmem->base_dev, &nvmem->eeprom); nvmem_device_remove_all_cells(nvmem); + nvmem_layout_put(nvmem->layout); device_unregister(&nvmem->dev); } @@ -1246,6 +1381,15 @@ struct nvmem_cell *of_nvmem_cell_get(struct device_node *np, const char *id) return ERR_PTR(-EINVAL); } + /* nvmem layouts produce cells within the nvmem-layout container */ + if (of_node_name_eq(nvmem_np, "nvmem-layout")) { + nvmem_np = of_get_next_parent(nvmem_np); + if (!nvmem_np) { + of_node_put(cell_np); + return ERR_PTR(-EINVAL); + } + } + nvmem = __nvmem_device_get(nvmem_np, device_match_of_node); of_node_put(nvmem_np); if (IS_ERR(nvmem)) { @@ -1418,7 +1562,7 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem, { int rc; - rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->bytes); + rc = nvmem_reg_read(nvmem, cell->offset, buf, cell->raw_len); if (rc) return rc; @@ -1427,9 +1571,9 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem, if (cell->bit_offset || cell->nbits) nvmem_shift_read_buffer_in_place(cell, buf); - if (nvmem->cell_post_process) { - rc = nvmem->cell_post_process(nvmem->priv, id, index, - cell->offset, buf, cell->bytes); + if (cell->read_post_process) { + rc = cell->read_post_process(cell->priv, id, index, + cell->offset, buf, cell->raw_len); if (rc) return rc; } @@ -1452,14 +1596,15 @@ static int __nvmem_cell_read(struct nvmem_device *nvmem, */ void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len) { - struct nvmem_device *nvmem = cell->entry->nvmem; + struct nvmem_cell_entry *entry = cell->entry; + struct nvmem_device *nvmem = entry->nvmem; u8 *buf; int rc; if (!nvmem) return ERR_PTR(-EINVAL); - buf = kzalloc(cell->entry->bytes, GFP_KERNEL); + buf = kzalloc(max_t(size_t, entry->raw_len, entry->bytes), GFP_KERNEL); if (!buf) return ERR_PTR(-ENOMEM); @@ -1535,6 +1680,14 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si (cell->bit_offset == 0 && len != cell->bytes)) return -EINVAL; + /* + * Any cells which have a read_post_process hook are read-only because + * we cannot reverse the operation and it might affect other cells, + * too. + */ + if (cell->read_post_process) + return -EINVAL; + if (cell->bit_offset || cell->nbits) { buf = nvmem_cell_prepare_write_buffer(cell, buf, len); if (IS_ERR(buf)) @@ -1958,4 +2111,3 @@ module_exit(nvmem_exit); MODULE_AUTHOR("Srinivas Kandagatla <srinivas.kandagatla@linaro.org"); MODULE_AUTHOR("Maxime Ripard <maxime.ripard@free-electrons.com"); MODULE_DESCRIPTION("nvmem Driver Core"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/nvmem/imx-ocotp.c b/drivers/nvmem/imx-ocotp.c index e9b52ecb3f72..ac0edb6398f1 100644 --- a/drivers/nvmem/imx-ocotp.c +++ b/drivers/nvmem/imx-ocotp.c @@ -225,18 +225,13 @@ read_end: static int imx_ocotp_cell_pp(void *context, const char *id, int index, unsigned int offset, void *data, size_t bytes) { - struct ocotp_priv *priv = context; + u8 *buf = data; + int i; /* Deal with some post processing of nvmem cell data */ - if (id && !strcmp(id, "mac-address")) { - if (priv->params->reverse_mac_address) { - u8 *buf = data; - int i; - - for (i = 0; i < bytes/2; i++) - swap(buf[i], buf[bytes - i - 1]); - } - } + if (id && !strcmp(id, "mac-address")) + for (i = 0; i < bytes / 2; i++) + swap(buf[i], buf[bytes - i - 1]); return 0; } @@ -488,7 +483,6 @@ static struct nvmem_config imx_ocotp_nvmem_config = { .stride = 1, .reg_read = imx_ocotp_read, .reg_write = imx_ocotp_write, - .cell_post_process = imx_ocotp_cell_pp, }; static const struct ocotp_params imx6q_params = { @@ -595,6 +589,17 @@ static const struct of_device_id imx_ocotp_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, imx_ocotp_dt_ids); +static void imx_ocotp_fixup_cell_info(struct nvmem_device *nvmem, + struct nvmem_layout *layout, + struct nvmem_cell_info *cell) +{ + cell->read_post_process = imx_ocotp_cell_pp; +} + +struct nvmem_layout imx_ocotp_layout = { + .fixup_cell_info = imx_ocotp_fixup_cell_info, +}; + static int imx_ocotp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -619,6 +624,9 @@ static int imx_ocotp_probe(struct platform_device *pdev) imx_ocotp_nvmem_config.size = 4 * priv->params->nregs; imx_ocotp_nvmem_config.dev = dev; imx_ocotp_nvmem_config.priv = priv; + if (priv->params->reverse_mac_address) + imx_ocotp_nvmem_config.layout = &imx_ocotp_layout; + priv->config = &imx_ocotp_nvmem_config; clk_prepare_enable(priv->clk); diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig new file mode 100644 index 000000000000..7ff1ee1c1f05 --- /dev/null +++ b/drivers/nvmem/layouts/Kconfig @@ -0,0 +1,23 @@ +# SPDX-License-Identifier: GPL-2.0 + +menu "Layout Types" + +config NVMEM_LAYOUT_SL28_VPD + tristate "Kontron sl28 VPD layout support" + select CRC8 + help + Say Y here if you want to support the VPD layout of the Kontron + SMARC-sAL28 boards. + + If unsure, say N. + +config NVMEM_LAYOUT_ONIE_TLV + tristate "ONIE tlv support" + select CRC32 + help + Say Y here if you want to support the Open Compute Project ONIE + Type-Length-Value standard table. + + If unsure, say N. + +endmenu diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile new file mode 100644 index 000000000000..2974bd7d33ed --- /dev/null +++ b/drivers/nvmem/layouts/Makefile @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for nvmem layouts. +# + +obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o +obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c new file mode 100644 index 000000000000..59fc87ccfcff --- /dev/null +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -0,0 +1,244 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * ONIE tlv NVMEM cells provider + * + * Copyright (C) 2022 Open Compute Group ONIE + * Author: Miquel Raynal <miquel.raynal@bootlin.com> + * Based on the nvmem driver written by: Vadym Kochan <vadym.kochan@plvision.eu> + * Inspired by the first layout written by: Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> + +#define ONIE_TLV_MAX_LEN 2048 +#define ONIE_TLV_CRC_FIELD_SZ 6 +#define ONIE_TLV_CRC_SZ 4 +#define ONIE_TLV_HDR_ID "TlvInfo" + +struct onie_tlv_hdr { + u8 id[8]; + u8 version; + __be16 data_len; +} __packed; + +struct onie_tlv { + u8 type; + u8 len; +} __packed; + +static const char *onie_tlv_cell_name(u8 type) +{ + switch (type) { + case 0x21: + return "product-name"; + case 0x22: + return "part-number"; + case 0x23: + return "serial-number"; + case 0x24: + return "mac-address"; + case 0x25: + return "manufacture-date"; + case 0x26: + return "device-version"; + case 0x27: + return "label-revision"; + case 0x28: + return "platform-name"; + case 0x29: + return "onie-version"; + case 0x2A: + return "num-macs"; + case 0x2B: + return "manufacturer"; + case 0x2C: + return "country-code"; + case 0x2D: + return "vendor"; + case 0x2E: + return "diag-version"; + case 0x2F: + return "service-tag"; + case 0xFD: + return "vendor-extension"; + case 0xFE: + return "crc32"; + default: + break; + } + + return NULL; +} + +static int onie_tlv_mac_read_cb(void *priv, const char *id, int index, + unsigned int offset, void *buf, + size_t bytes) +{ + eth_addr_add(buf, index); + + return 0; +} + +static nvmem_cell_post_process_t onie_tlv_read_cb(u8 type, u8 *buf) +{ + switch (type) { + case 0x24: + return &onie_tlv_mac_read_cb; + default: + break; + } + + return NULL; +} + +static int onie_tlv_add_cells(struct device *dev, struct nvmem_device *nvmem, + size_t data_len, u8 *data) +{ + struct nvmem_cell_info cell = {}; + struct device_node *layout; + struct onie_tlv tlv; + unsigned int hdr_len = sizeof(struct onie_tlv_hdr); + unsigned int offset = 0; + int ret; + + layout = of_nvmem_layout_get_container(nvmem); + if (!layout) + return -ENOENT; + + while (offset < data_len) { + memcpy(&tlv, data + offset, sizeof(tlv)); + if (offset + tlv.len >= data_len) { + dev_err(dev, "Out of bounds field (0x%x bytes at 0x%x)\n", + tlv.len, hdr_len + offset); + break; + } + + cell.name = onie_tlv_cell_name(tlv.type); + if (!cell.name) + continue; + + cell.offset = hdr_len + offset + sizeof(tlv.type) + sizeof(tlv.len); + cell.bytes = tlv.len; + cell.np = of_get_child_by_name(layout, cell.name); + cell.read_post_process = onie_tlv_read_cb(tlv.type, data + offset + sizeof(tlv)); + + ret = nvmem_add_one_cell(nvmem, &cell); + if (ret) { + of_node_put(layout); + return ret; + } + + offset += sizeof(tlv) + tlv.len; + } + + of_node_put(layout); + + return 0; +} + +static bool onie_tlv_hdr_is_valid(struct device *dev, struct onie_tlv_hdr *hdr) +{ + if (memcmp(hdr->id, ONIE_TLV_HDR_ID, sizeof(hdr->id))) { + dev_err(dev, "Invalid header\n"); + return false; + } + + if (hdr->version != 0x1) { + dev_err(dev, "Invalid version number\n"); + return false; + } + + return true; +} + +static bool onie_tlv_crc_is_valid(struct device *dev, size_t table_len, u8 *table) +{ + struct onie_tlv crc_hdr; + u32 read_crc, calc_crc; + __be32 crc_be; + + memcpy(&crc_hdr, table + table_len - ONIE_TLV_CRC_FIELD_SZ, sizeof(crc_hdr)); + if (crc_hdr.type != 0xfe || crc_hdr.len != ONIE_TLV_CRC_SZ) { + dev_err(dev, "Invalid CRC field\n"); + return false; + } + + /* The table contains a JAMCRC, which is XOR'ed compared to the original + * CRC32 implementation as known in the Ethernet world. + */ + memcpy(&crc_be, table + table_len - ONIE_TLV_CRC_SZ, ONIE_TLV_CRC_SZ); + read_crc = be32_to_cpu(crc_be); + calc_crc = crc32(~0, table, table_len - ONIE_TLV_CRC_SZ) ^ 0xFFFFFFFF; + if (read_crc != calc_crc) { + dev_err(dev, "Invalid CRC read: 0x%08x, expected: 0x%08x\n", + read_crc, calc_crc); + return false; + } + + return true; +} + +static int onie_tlv_parse_table(struct device *dev, struct nvmem_device *nvmem, + struct nvmem_layout *layout) +{ + struct onie_tlv_hdr hdr; + size_t table_len, data_len, hdr_len; + u8 *table, *data; + int ret; + + ret = nvmem_device_read(nvmem, 0, sizeof(hdr), &hdr); + if (ret < 0) + return ret; + + if (!onie_tlv_hdr_is_valid(dev, &hdr)) { + dev_err(dev, "Invalid ONIE TLV header\n"); + return -EINVAL; + } + + hdr_len = sizeof(hdr.id) + sizeof(hdr.version) + sizeof(hdr.data_len); + data_len = be16_to_cpu(hdr.data_len); + table_len = hdr_len + data_len; + if (table_len > ONIE_TLV_MAX_LEN) { + dev_err(dev, "Invalid ONIE TLV data length\n"); + return -EINVAL; + } + + table = devm_kmalloc(dev, table_len, GFP_KERNEL); + if (!table) + return -ENOMEM; + + ret = nvmem_device_read(nvmem, 0, table_len, table); + if (ret != table_len) + return ret; + + if (!onie_tlv_crc_is_valid(dev, table_len, table)) + return -EINVAL; + + data = table + hdr_len; + ret = onie_tlv_add_cells(dev, nvmem, data_len, data); + if (ret) + return ret; + + return 0; +} + +static const struct of_device_id onie_tlv_of_match_table[] = { + { .compatible = "onie,tlv-layout", }, + {}, +}; +MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table); + +static struct nvmem_layout onie_tlv_layout = { + .name = "ONIE tlv layout", + .of_match_table = onie_tlv_of_match_table, + .add_cells = onie_tlv_parse_table, +}; +module_nvmem_layout_driver(onie_tlv_layout); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Miquel Raynal <miquel.raynal@bootlin.com>"); +MODULE_DESCRIPTION("NVMEM layout driver for Onie TLV table parsing"); diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c new file mode 100644 index 000000000000..05671371f631 --- /dev/null +++ b/drivers/nvmem/layouts/sl28vpd.c @@ -0,0 +1,153 @@ +// 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 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 <michael@walle.cc>"); +MODULE_DESCRIPTION("NVMEM layout driver for the VPD of Kontron sl28 boards"); diff --git a/drivers/nvmem/mtk-efuse.c b/drivers/nvmem/mtk-efuse.c index a08e0aedd21c..b36cd0dcc8c7 100644 --- a/drivers/nvmem/mtk-efuse.c +++ b/drivers/nvmem/mtk-efuse.c @@ -10,6 +10,11 @@ #include <linux/io.h> #include <linux/nvmem-provider.h> #include <linux/platform_device.h> +#include <linux/property.h> + +struct mtk_efuse_pdata { + bool uses_post_processing; +}; struct mtk_efuse_priv { void __iomem *base; @@ -29,6 +34,37 @@ static int mtk_reg_read(void *context, return 0; } +static int mtk_efuse_gpu_speedbin_pp(void *context, const char *id, int index, + unsigned int offset, void *data, size_t bytes) +{ + u8 *val = data; + + if (val[0] < 8) + val[0] = BIT(val[0]); + + return 0; +} + +static void mtk_efuse_fixup_cell_info(struct nvmem_device *nvmem, + struct nvmem_layout *layout, + struct nvmem_cell_info *cell) +{ + size_t sz = strlen(cell->name); + + /* + * On some SoCs, the GPU speedbin is not read as bitmask but as + * a number with range [0-7] (max 3 bits): post process to use + * it in OPP tables to describe supported-hw. + */ + if (cell->nbits <= 3 && + strncmp(cell->name, "gpu-speedbin", min(sz, strlen("gpu-speedbin"))) == 0) + cell->read_post_process = mtk_efuse_gpu_speedbin_pp; +} + +static struct nvmem_layout mtk_efuse_layout = { + .fixup_cell_info = mtk_efuse_fixup_cell_info, +}; + static int mtk_efuse_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -36,6 +72,7 @@ static int mtk_efuse_probe(struct platform_device *pdev) struct nvmem_device *nvmem; struct nvmem_config econfig = {}; struct mtk_efuse_priv *priv; + const struct mtk_efuse_pdata *pdata; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -45,20 +82,32 @@ static int mtk_efuse_probe(struct platform_device *pdev) if (IS_ERR(priv->base)) return PTR_ERR(priv->base); + pdata = device_get_match_data(dev); econfig.stride = 1; econfig.word_size = 1; econfig.reg_read = mtk_reg_read; econfig.size = resource_size(res); econfig.priv = priv; econfig.dev = dev; + if (pdata->uses_post_processing) + econfig.layout = &mtk_efuse_layout; nvmem = devm_nvmem_register(dev, &econfig); return PTR_ERR_OR_ZERO(nvmem); } +static const struct mtk_efuse_pdata mtk_mt8186_efuse_pdata = { + .uses_post_processing = true, +}; + +static const struct mtk_efuse_pdata mtk_efuse_pdata = { + .uses_post_processing = false, +}; + static const struct of_device_id mtk_efuse_of_match[] = { - { .compatible = "mediatek,mt8173-efuse",}, - { .compatible = "mediatek,efuse",}, + { .compatible = "mediatek,mt8173-efuse", .data = &mtk_efuse_pdata }, + { .compatible = "mediatek,mt8186-efuse", .data = &mtk_mt8186_efuse_pdata }, + { .compatible = "mediatek,efuse", .data = &mtk_efuse_pdata }, {/* sentinel */}, }; MODULE_DEVICE_TABLE(of, mtk_efuse_of_match); diff --git a/drivers/nvmem/nintendo-otp.c b/drivers/nvmem/nintendo-otp.c index 33961b17f9f1..355e7f1fc6d5 100644 --- a/drivers/nvmem/nintendo-otp.c +++ b/drivers/nvmem/nintendo-otp.c @@ -76,7 +76,6 @@ static int nintendo_otp_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; const struct of_device_id *of_id = of_match_device(nintendo_otp_of_table, dev); - struct resource *res; struct nvmem_device *nvmem; struct nintendo_otp_priv *priv; @@ -92,8 +91,7 @@ static int nintendo_otp_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - priv->regs = devm_ioremap_resource(dev, res); + priv->regs = devm_platform_ioremap_resource(pdev, 0); if (IS_ERR(priv->regs)) return PTR_ERR(priv->regs); diff --git a/drivers/nvmem/stm32-romem.c b/drivers/nvmem/stm32-romem.c index ba779e26937a..38d0bf557129 100644 --- a/drivers/nvmem/stm32-romem.c +++ b/drivers/nvmem/stm32-romem.c @@ -268,7 +268,7 @@ static const struct stm32_romem_cfg stm32mp13_bsec_cfg = { .ta = true, }; -static const struct of_device_id stm32_romem_of_match[] = { +static const struct of_device_id stm32_romem_of_match[] __maybe_unused = { { .compatible = "st,stm32f4-otp", }, { .compatible = "st,stm32mp15-bsec", .data = (void *)&stm32mp15_bsec_cfg, diff --git a/drivers/nvmem/u-boot-env.c b/drivers/nvmem/u-boot-env.c index 29b1d87a3c51..ee9fd9989b6e 100644 --- a/drivers/nvmem/u-boot-env.c +++ b/drivers/nvmem/u-boot-env.c @@ -4,6 +4,8 @@ */ #include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/if_ether.h> #include <linux/mod_devicetable.h> #include <linux/module.h> #include <linux/mtd/mtd.h> @@ -70,6 +72,25 @@ static int u_boot_env_read(void *context, unsigned int offset, void *val, return 0; } +static int u_boot_env_read_post_process_ethaddr(void *context, const char *id, int index, + unsigned int offset, void *buf, size_t bytes) +{ + u8 mac[ETH_ALEN]; + + if (bytes != 3 * ETH_ALEN - 1) + return -EINVAL; + + if (!mac_pton(buf, mac)) + return -EINVAL; + + if (index) + eth_addr_add(mac, index); + + ether_addr_copy(buf, mac); + + return 0; +} + static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf, size_t data_offset, size_t data_len) { @@ -101,6 +122,11 @@ static int u_boot_env_add_cells(struct u_boot_env *priv, uint8_t *buf, priv->cells[idx].offset = data_offset + value - data; priv->cells[idx].bytes = strlen(value); priv->cells[idx].np = of_get_child_by_name(dev->of_node, priv->cells[idx].name); + if (!strcmp(var, "ethaddr")) { + priv->cells[idx].raw_len = strlen(value); + priv->cells[idx].bytes = ETH_ALEN; + priv->cells[idx].read_post_process = u_boot_env_read_post_process_ethaddr; + } } if (WARN_ON(idx != priv->ncells)) diff --git a/drivers/nvmem/vf610-ocotp.c b/drivers/nvmem/vf610-ocotp.c index 5b6cad16892f..ee9c61ae727d 100644 --- a/drivers/nvmem/vf610-ocotp.c +++ b/drivers/nvmem/vf610-ocotp.c @@ -219,8 +219,7 @@ static int vf610_ocotp_probe(struct platform_device *pdev) if (!ocotp_dev) return -ENOMEM; - res = platform_get_resource(pdev, IORESOURCE_MEM, 0); - ocotp_dev->base = devm_ioremap_resource(dev, res); + ocotp_dev->base = devm_platform_get_and_ioremap_resource(pdev, 0, &res); if (IS_ERR(ocotp_dev->base)) return PTR_ERR(ocotp_dev->base); |