diff options
Diffstat (limited to 'drivers/nvmem')
33 files changed, 1197 insertions, 409 deletions
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 283134498fbc..d370b2ad11e7 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -40,6 +40,19 @@ config NVMEM_APPLE_EFUSES This driver can also be built as a module. If so, the module will be called nvmem-apple-efuses. +config NVMEM_APPLE_SPMI + tristate "Apple SPMI NVMEM" + depends on ARCH_APPLE || COMPILE_TEST + depends on SPMI + select REGMAP_SPMI + help + Say y here to build a driver to expose NVMEM cells for a set of power + and RTC-related settings on a SPMI-attached PMIC present on Apple + devices, such as Apple Silicon Macs. + + This driver can also be built as a module. If so, the module + will be called apple-nvmem-spmi. + config NVMEM_BCM_OCOTP tristate "Broadcom On-Chip OTP Controller support" depends on ARCH_BCM_IPROC || COMPILE_TEST @@ -154,6 +167,18 @@ config NVMEM_LPC18XX_OTP To compile this driver as a module, choose M here: the module will be called nvmem_lpc18xx_otp. +config NVMEM_MAX77759 + tristate "Maxim Integrated MAX77759 NVMEM Support" + depends on MFD_MAX77759 + default MFD_MAX77759 + help + Say Y here to include support for the user-accessible storage found + in Maxim Integrated MAX77759 PMICs. This IC provides space for 30 + bytes of storage. + + This driver can also be built as a module. If so, the module + will be called nvmem-max77759. + config NVMEM_MESON_EFUSE tristate "Amlogic Meson GX eFuse Support" depends on (ARCH_MESON || COMPILE_TEST) && MESON_SM @@ -246,9 +271,21 @@ config NVMEM_RAVE_SP_EEPROM help Say y here to enable Rave SP EEPROM support. +config NVMEM_RCAR_EFUSE + tristate "Renesas R-Car Gen4 E-FUSE support" + depends on (ARCH_RENESAS && ARM64) || COMPILE_TEST + depends on NVMEM + help + Enable support for reading the fuses in the E-FUSE or OTP + non-volatile memory block on Renesas R-Car Gen4 SoCs. + + This driver can also be built as a module. If so, the module + will be called nvmem-rcar-efuse. + config NVMEM_RMEM tristate "Reserved Memory Based Driver Support" depends on HAS_IOMEM + select CRC32 help This driver maps reserved memory into an nvmem device. It might be useful to expose information left by firmware in memory. @@ -363,8 +400,7 @@ config NVMEM_SUNXI_SID config NVMEM_U_BOOT_ENV tristate "U-Boot environment variables support" depends on OF && MTD - select CRC32 - select GENERIC_NET_UTILS + select NVMEM_LAYOUT_U_BOOT_ENV help U-Boot stores its setup as environment variables. This driver adds support for verifying & exporting such data. It also exposes variables diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index cdd01fbf1313..2021d59688db 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -12,6 +12,8 @@ obj-y += layouts/ # Devices obj-$(CONFIG_NVMEM_APPLE_EFUSES) += nvmem-apple-efuses.o nvmem-apple-efuses-y := apple-efuses.o +obj-$(CONFIG_NVMEM_APPLE_SPMI) += apple_nvmem_spmi.o +apple_nvmem_spmi-y := apple-spmi-nvmem.o obj-$(CONFIG_NVMEM_BCM_OCOTP) += nvmem-bcm-ocotp.o nvmem-bcm-ocotp-y := bcm-ocotp.o obj-$(CONFIG_NVMEM_BRCM_NVRAM) += nvmem_brcm_nvram.o @@ -34,6 +36,8 @@ obj-$(CONFIG_NVMEM_LPC18XX_EEPROM) += nvmem_lpc18xx_eeprom.o nvmem_lpc18xx_eeprom-y := lpc18xx_eeprom.o obj-$(CONFIG_NVMEM_LPC18XX_OTP) += nvmem_lpc18xx_otp.o nvmem_lpc18xx_otp-y := lpc18xx_otp.o +obj-$(CONFIG_NVMEM_MAX77759) += nvmem-max77759.o +nvmem-max77759-y := max77759-nvmem.o obj-$(CONFIG_NVMEM_MESON_EFUSE) += nvmem_meson_efuse.o nvmem_meson_efuse-y := meson-efuse.o obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o @@ -52,6 +56,8 @@ obj-$(CONFIG_NVMEM_QCOM_SEC_QFPROM) += nvmem_sec_qfprom.o nvmem_sec_qfprom-y := sec-qfprom.o obj-$(CONFIG_NVMEM_RAVE_SP_EEPROM) += nvmem-rave-sp-eeprom.o nvmem-rave-sp-eeprom-y := rave-sp-eeprom.o +obj-$(CONFIG_NVMEM_RCAR_EFUSE) += nvmem-rcar-efuse.o +nvmem-rcar-efuse-y := rcar-efuse.o obj-$(CONFIG_NVMEM_RMEM) += nvmem-rmem.o nvmem-rmem-y := rmem.o obj-$(CONFIG_NVMEM_ROCKCHIP_EFUSE) += nvmem_rockchip_efuse.o diff --git a/drivers/nvmem/apple-efuses.c b/drivers/nvmem/apple-efuses.c index d3d49d22338b..1d1bf84a099f 100644 --- a/drivers/nvmem/apple-efuses.c +++ b/drivers/nvmem/apple-efuses.c @@ -78,4 +78,5 @@ static struct platform_driver apple_efuses_driver = { module_platform_driver(apple_efuses_driver); MODULE_AUTHOR("Sven Peter <sven@svenpeter.dev>"); +MODULE_DESCRIPTION("Apple SoC eFuse driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/apple-spmi-nvmem.c b/drivers/nvmem/apple-spmi-nvmem.c new file mode 100644 index 000000000000..88614005d5ce --- /dev/null +++ b/drivers/nvmem/apple-spmi-nvmem.c @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: GPL-2.0-only OR MIT +/* + * Apple SPMI NVMEM driver + * + * Copyright The Asahi Linux Contributors + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/spmi.h> +#include <linux/regmap.h> + +static const struct regmap_config apple_spmi_regmap_config = { + .reg_bits = 16, + .val_bits = 8, + .max_register = 0xffff, +}; + +static int apple_spmi_nvmem_probe(struct spmi_device *sdev) +{ + struct regmap *regmap; + struct nvmem_config nvmem_cfg = { + .dev = &sdev->dev, + .name = "spmi_nvmem", + .id = NVMEM_DEVID_AUTO, + .word_size = 1, + .stride = 1, + .size = 0xffff, + .reg_read = (void *)regmap_bulk_read, + .reg_write = (void *)regmap_bulk_write, + }; + + regmap = devm_regmap_init_spmi_ext(sdev, &apple_spmi_regmap_config); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + + nvmem_cfg.priv = regmap; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(&sdev->dev, &nvmem_cfg)); +} + +static const struct of_device_id apple_spmi_nvmem_id_table[] = { + { .compatible = "apple,spmi-nvmem" }, + { }, +}; +MODULE_DEVICE_TABLE(of, apple_spmi_nvmem_id_table); + +static struct spmi_driver apple_spmi_nvmem_driver = { + .probe = apple_spmi_nvmem_probe, + .driver = { + .name = "apple-spmi-nvmem", + .of_match_table = apple_spmi_nvmem_id_table, + }, +}; + +module_spmi_driver(apple_spmi_nvmem_driver); + +MODULE_LICENSE("Dual MIT/GPL"); +MODULE_AUTHOR("Hector Martin <marcan@marcan.st>"); +MODULE_DESCRIPTION("Apple SPMI NVMEM driver"); diff --git a/drivers/nvmem/brcm_nvram.c b/drivers/nvmem/brcm_nvram.c index 5cdf339cfbec..b4cf245fb246 100644 --- a/drivers/nvmem/brcm_nvram.c +++ b/drivers/nvmem/brcm_nvram.c @@ -22,7 +22,7 @@ * * @dev: NVMEM device pointer * @nvmem_size: Size of the whole space available for NVRAM - * @data: NVRAM data copy stored to avoid poking underlaying flash controller + * @data: NVRAM data copy stored to avoid poking underlying flash controller * @data_len: NVRAM data size * @padding_byte: Padding value used to fill remaining space * @cells: Array of discovered NVMEM cells @@ -100,7 +100,7 @@ static int brcm_nvram_read_post_process_macaddr(void *context, const char *id, i { u8 mac[ETH_ALEN]; - if (bytes != 3 * ETH_ALEN - 1) + if (bytes != MAC_ADDR_STR_LEN) return -EINVAL; if (!mac_pton(buf, mac)) @@ -253,5 +253,6 @@ static int __init brcm_nvram_init(void) subsys_initcall_sync(brcm_nvram_init); MODULE_AUTHOR("Rafał Miłecki"); +MODULE_DESCRIPTION("Broadcom I/O-mapped NVRAM support driver"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(of, brcm_nvram_of_match_table); diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c index 980123fb4dde..fd2a9698d1c9 100644 --- a/drivers/nvmem/core.c +++ b/drivers/nvmem/core.c @@ -47,9 +47,6 @@ struct nvmem_cell { static DEFINE_MUTEX(nvmem_mutex); static DEFINE_IDA(nvmem_ida); -static DEFINE_MUTEX(nvmem_cell_mutex); -static LIST_HEAD(nvmem_cell_tables); - static DEFINE_MUTEX(nvmem_lookup_mutex); static LIST_HEAD(nvmem_lookup_list); @@ -179,18 +176,41 @@ static ssize_t type_show(struct device *dev, { struct nvmem_device *nvmem = to_nvmem_device(dev); - return sprintf(buf, "%s\n", nvmem_type_str[nvmem->type]); + return sysfs_emit(buf, "%s\n", nvmem_type_str[nvmem->type]); } static DEVICE_ATTR_RO(type); +static ssize_t force_ro_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return sysfs_emit(buf, "%d\n", nvmem->read_only); +} + +static ssize_t force_ro_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct nvmem_device *nvmem = to_nvmem_device(dev); + int ret = kstrtobool(buf, &nvmem->read_only); + + if (ret < 0) + return ret; + + return count; +} + +static DEVICE_ATTR_RW(force_ro); + static struct attribute *nvmem_attrs[] = { + &dev_attr_force_ro.attr, &dev_attr_type.attr, NULL, }; static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev; @@ -203,19 +223,12 @@ static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); - /* Stop the user from reading */ - if (pos >= nvmem->size) - return 0; - if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; - if (pos + count > nvmem->size) - count = nvmem->size - pos; - count = round_down(count, nvmem->word_size); if (!nvmem->reg_read) @@ -230,7 +243,7 @@ static ssize_t bin_attr_nvmem_read(struct file *filp, struct kobject *kobj, } static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct device *dev; @@ -243,22 +256,15 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj, dev = kobj_to_dev(kobj); nvmem = to_nvmem_device(dev); - /* Stop the user from writing */ - if (pos >= nvmem->size) - return -EFBIG; - if (!IS_ALIGNED(pos, nvmem->stride)) return -EINVAL; if (count < nvmem->word_size) return -EINVAL; - if (pos + count > nvmem->size) - count = nvmem->size - pos; - count = round_down(count, nvmem->word_size); - if (!nvmem->reg_write) + if (!nvmem->reg_write || nvmem->read_only) return -EPERM; rc = nvmem_reg_write(nvmem, pos, buf, count); @@ -289,21 +295,49 @@ static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem) } static umode_t nvmem_bin_attr_is_visible(struct kobject *kobj, - struct bin_attribute *attr, int i) + const struct bin_attribute *attr, + int i) { struct device *dev = kobj_to_dev(kobj); struct nvmem_device *nvmem = to_nvmem_device(dev); - attr->size = nvmem->size; - return nvmem_bin_attr_get_umode(nvmem); } +static size_t nvmem_bin_attr_size(struct kobject *kobj, + const struct bin_attribute *attr, + int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + + return nvmem->size; +} + +static umode_t nvmem_attr_is_visible(struct kobject *kobj, + struct attribute *attr, int i) +{ + struct device *dev = kobj_to_dev(kobj); + struct nvmem_device *nvmem = to_nvmem_device(dev); + + /* + * If the device has no .reg_write operation, do not allow + * configuration as read-write. + * If the device is set as read-only by configuration, it + * can be forced into read-write mode using the 'force_ro' + * attribute. + */ + if (attr == &dev_attr_force_ro.attr && !nvmem->reg_write) + return 0; /* Attribute not visible */ + + return attr->mode; +} + static struct nvmem_cell *nvmem_create_cell(struct nvmem_cell_entry *entry, const char *id, int index); static ssize_t nvmem_cell_attr_read(struct file *filp, struct kobject *kobj, - struct bin_attribute *attr, char *buf, + const struct bin_attribute *attr, char *buf, loff_t pos, size_t count) { struct nvmem_cell_entry *entry; @@ -337,29 +371,26 @@ destroy_cell: } /* default read/write permissions */ -static struct bin_attribute bin_attr_rw_nvmem = { +static const struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, - .read = bin_attr_nvmem_read, - .write = bin_attr_nvmem_write, + .read_new = bin_attr_nvmem_read, + .write_new = bin_attr_nvmem_write, }; -static struct bin_attribute *nvmem_bin_attributes[] = { +static const struct bin_attribute *const nvmem_bin_attributes[] = { &bin_attr_rw_nvmem, NULL, }; static const struct attribute_group nvmem_bin_group = { - .bin_attrs = nvmem_bin_attributes, + .bin_attrs_new = nvmem_bin_attributes, .attrs = nvmem_attrs, .is_bin_visible = nvmem_bin_attr_is_visible, -}; - -/* Cell attributes will be dynamically allocated */ -static struct attribute_group nvmem_cells_group = { - .name = "cells", + .bin_size = nvmem_bin_attr_size, + .is_visible = nvmem_attr_is_visible, }; static const struct attribute_group *nvmem_dev_groups[] = { @@ -367,17 +398,12 @@ static const struct attribute_group *nvmem_dev_groups[] = { NULL, }; -static const struct attribute_group *nvmem_cells_groups[] = { - &nvmem_cells_group, - NULL, -}; - -static struct bin_attribute bin_attr_nvmem_eeprom_compat = { +static const struct bin_attribute bin_attr_nvmem_eeprom_compat = { .attr = { .name = "eeprom", }, - .read = bin_attr_nvmem_read, - .write = bin_attr_nvmem_write, + .read_new = bin_attr_nvmem_read, + .write_new = bin_attr_nvmem_write, }; /* @@ -396,10 +422,9 @@ static int nvmem_sysfs_setup_compat(struct nvmem_device *nvmem, if (!config->base_dev) return -EINVAL; - if (config->type == NVMEM_TYPE_FRAM) - bin_attr_nvmem_eeprom_compat.attr.name = "fram"; - nvmem->eeprom = bin_attr_nvmem_eeprom_compat; + if (config->type == NVMEM_TYPE_FRAM) + nvmem->eeprom.attr.name = "fram"; nvmem->eeprom.attr.mode = nvmem_bin_attr_get_umode(nvmem); nvmem->eeprom.size = nvmem->size; #ifdef CONFIG_DEBUG_LOCK_ALLOC @@ -429,23 +454,25 @@ static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem, static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) { - struct bin_attribute **cells_attrs, *attrs; + struct attribute_group group = { + .name = "cells", + }; struct nvmem_cell_entry *entry; + const struct bin_attribute **pattrs; + struct bin_attribute *attrs; unsigned int ncells = 0, i = 0; int ret = 0; mutex_lock(&nvmem_mutex); - if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) { - nvmem_cells_group.bin_attrs = NULL; + if (list_empty(&nvmem->cells) || nvmem->sysfs_cells_populated) goto unlock_mutex; - } /* Allocate an array of attributes with a sentinel */ ncells = list_count_nodes(&nvmem->cells); - cells_attrs = devm_kcalloc(&nvmem->dev, ncells + 1, - sizeof(struct bin_attribute *), GFP_KERNEL); - if (!cells_attrs) { + pattrs = devm_kcalloc(&nvmem->dev, ncells + 1, + sizeof(struct bin_attribute *), GFP_KERNEL); + if (!pattrs) { ret = -ENOMEM; goto unlock_mutex; } @@ -460,24 +487,25 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem) list_for_each_entry(entry, &nvmem->cells, node) { sysfs_bin_attr_init(&attrs[i]); attrs[i].attr.name = devm_kasprintf(&nvmem->dev, GFP_KERNEL, - "%s@%x", entry->name, - entry->offset); - attrs[i].attr.mode = 0444; + "%s@%x,%x", entry->name, + entry->offset, + entry->bit_offset); + attrs[i].attr.mode = 0444 & nvmem_bin_attr_get_umode(nvmem); attrs[i].size = entry->bytes; - attrs[i].read = &nvmem_cell_attr_read; + attrs[i].read_new = &nvmem_cell_attr_read; attrs[i].private = entry; if (!attrs[i].attr.name) { ret = -ENOMEM; goto unlock_mutex; } - cells_attrs[i] = &attrs[i]; + pattrs[i] = &attrs[i]; i++; } - nvmem_cells_group.bin_attrs = cells_attrs; + group.bin_attrs_new = pattrs; - ret = devm_device_add_groups(&nvmem->dev, nvmem_cells_groups); + ret = device_add_group(&nvmem->dev, &group); if (ret) goto unlock_mutex; @@ -563,9 +591,11 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem, cell->nbits = info->nbits; cell->np = info->np; - if (cell->nbits) + if (cell->nbits) { cell->bytes = DIV_ROUND_UP(cell->nbits + cell->bit_offset, BITS_PER_BYTE); + cell->raw_len = ALIGN(cell->bytes, nvmem->word_size); + } if (!IS_ALIGNED(cell->offset, nvmem->stride)) { dev_err(&nvmem->dev, @@ -574,6 +604,18 @@ static int nvmem_cell_info_to_nvmem_cell_entry_nodup(struct nvmem_device *nvmem, return -EINVAL; } + if (!IS_ALIGNED(cell->raw_len, nvmem->word_size)) { + dev_err(&nvmem->dev, + "cell %s raw len %zd unaligned to nvmem word size %d\n", + cell->name ?: "<unknown>", cell->raw_len, + nvmem->word_size); + + if (info->raw_len) + return -EINVAL; + + cell->raw_len = ALIGN(cell->raw_len, nvmem->word_size); + } + return 0; } @@ -674,41 +716,6 @@ int nvmem_unregister_notifier(struct notifier_block *nb) } EXPORT_SYMBOL_GPL(nvmem_unregister_notifier); -static int nvmem_add_cells_from_table(struct nvmem_device *nvmem) -{ - const struct nvmem_cell_info *info; - struct nvmem_cell_table *table; - struct nvmem_cell_entry *cell; - int rval = 0, i; - - mutex_lock(&nvmem_cell_mutex); - list_for_each_entry(table, &nvmem_cell_tables, node) { - if (strcmp(nvmem_dev_name(nvmem), table->nvmem_name) == 0) { - for (i = 0; i < table->ncells; i++) { - info = &table->cells[i]; - - cell = kzalloc(sizeof(*cell), GFP_KERNEL); - if (!cell) { - rval = -ENOMEM; - goto out; - } - - rval = nvmem_cell_info_to_nvmem_cell_entry(nvmem, info, cell); - if (rval) { - kfree(cell); - goto out; - } - - nvmem_cell_entry_add(cell); - } - } - } - -out: - mutex_unlock(&nvmem_cell_mutex); - return rval; -} - static struct nvmem_cell_entry * nvmem_find_cell_entry_by_name(struct nvmem_device *nvmem, const char *cell_id) { @@ -806,6 +813,13 @@ static int nvmem_add_cells_from_dt(struct nvmem_device *nvmem, struct device_nod if (addr && len == (2 * sizeof(u32))) { info.bit_offset = be32_to_cpup(addr++); info.nbits = be32_to_cpup(addr); + if (info.bit_offset >= BITS_PER_BYTE * info.bytes || + info.nbits < 1 || + info.bit_offset + info.nbits > BITS_PER_BYTE * info.bytes) { + dev_err(dev, "nvmem: invalid bits on %pOF\n", child); + of_node_put(child); + return -EINVAL; + } } info.np = of_node_get(child); @@ -988,10 +1002,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config) goto err_remove_cells; } - rval = nvmem_add_cells_from_table(nvmem); - if (rval) - goto err_remove_cells; - if (config->add_legacy_fixed_of_cells) { rval = nvmem_add_cells_from_legacy_of(nvmem); if (rval) @@ -1224,7 +1234,7 @@ static void devm_nvmem_device_release(struct device *dev, void *res) } /** - * devm_nvmem_device_put() - put alredy got nvmem device + * devm_nvmem_device_put() - put already got nvmem device * * @dev: Device that uses the nvmem device. * @nvmem: pointer to nvmem device allocated by devm_nvmem_cell_get(), @@ -1242,7 +1252,7 @@ void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem) EXPORT_SYMBOL_GPL(devm_nvmem_device_put); /** - * nvmem_device_put() - put alredy got nvmem device + * nvmem_device_put() - put already got nvmem device * * @nvmem: pointer to nvmem device that needs to be released. */ @@ -1253,13 +1263,13 @@ void nvmem_device_put(struct nvmem_device *nvmem) EXPORT_SYMBOL_GPL(nvmem_device_put); /** - * devm_nvmem_device_get() - Get nvmem cell of device form a given id + * devm_nvmem_device_get() - Get nvmem device of device form a given id * * @dev: Device that requests the nvmem device. * @id: name id for the requested nvmem device. * - * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_cell - * on success. The nvmem_cell will be freed by the automatically once the + * Return: ERR_PTR() on error or a valid pointer to a struct nvmem_device + * on success. The nvmem_device will be freed by the automatically once the * device is freed. */ struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *id) @@ -1594,21 +1604,29 @@ EXPORT_SYMBOL_GPL(nvmem_cell_put); static void nvmem_shift_read_buffer_in_place(struct nvmem_cell_entry *cell, void *buf) { u8 *p, *b; - int i, extra, bit_offset = cell->bit_offset; + int i, extra, bytes_offset; + int bit_offset = cell->bit_offset; p = b = buf; - if (bit_offset) { + + bytes_offset = bit_offset / BITS_PER_BYTE; + b += bytes_offset; + bit_offset %= BITS_PER_BYTE; + + if (bit_offset % BITS_PER_BYTE) { /* First shift */ - *b++ >>= bit_offset; + *p = *b++ >> bit_offset; /* setup rest of the bytes if any */ for (i = 1; i < cell->bytes; i++) { /* Get bits from next byte and shift them towards msb */ - *p |= *b << (BITS_PER_BYTE - bit_offset); + *p++ |= *b << (BITS_PER_BYTE - bit_offset); - p = b; - *b++ >>= bit_offset; + *p = *b++ >> bit_offset; } + } else if (p != b) { + memmove(p, b, cell->bytes - bytes_offset); + p += cell->bytes - 1; } else { /* point to the msb */ p += cell->bytes - 1; @@ -1757,6 +1775,8 @@ static int __nvmem_cell_entry_write(struct nvmem_cell_entry *cell, void *buf, si return -EINVAL; if (cell->bit_offset || cell->nbits) { + if (len != BITS_TO_BYTES(cell->nbits) && len != cell->bytes) + return -EINVAL; buf = nvmem_cell_prepare_write_buffer(cell, buf, len); if (IS_ERR(buf)) return PTR_ERR(buf); @@ -2090,32 +2110,6 @@ int nvmem_device_write(struct nvmem_device *nvmem, EXPORT_SYMBOL_GPL(nvmem_device_write); /** - * nvmem_add_cell_table() - register a table of cell info entries - * - * @table: table of cell info entries - */ -void nvmem_add_cell_table(struct nvmem_cell_table *table) -{ - mutex_lock(&nvmem_cell_mutex); - list_add_tail(&table->node, &nvmem_cell_tables); - mutex_unlock(&nvmem_cell_mutex); -} -EXPORT_SYMBOL_GPL(nvmem_add_cell_table); - -/** - * nvmem_del_cell_table() - remove a previously registered cell info table - * - * @table: table of cell info entries - */ -void nvmem_del_cell_table(struct nvmem_cell_table *table) -{ - mutex_lock(&nvmem_cell_mutex); - list_del(&table->node); - mutex_unlock(&nvmem_cell_mutex); -} -EXPORT_SYMBOL_GPL(nvmem_del_cell_table); - -/** * nvmem_add_cell_lookups() - register a list of cell lookup entries * * @entries: array of cell lookup entries diff --git a/drivers/nvmem/imx-iim.c b/drivers/nvmem/imx-iim.c index f13bbd164086..8cfbe55a56cb 100644 --- a/drivers/nvmem/imx-iim.c +++ b/drivers/nvmem/imx-iim.c @@ -115,11 +115,11 @@ static int imx_iim_probe(struct platform_device *pdev) if (IS_ERR(iim->clk)) return PTR_ERR(iim->clk); - cfg.name = "imx-iim", - cfg.read_only = true, - cfg.word_size = 1, - cfg.stride = 1, - cfg.reg_read = imx_iim_read, + cfg.name = "imx-iim"; + cfg.read_only = true; + cfg.word_size = 1; + cfg.stride = 1; + cfg.reg_read = imx_iim_read; cfg.dev = dev; cfg.size = drvdata->nregs; cfg.priv = iim; diff --git a/drivers/nvmem/imx-ocotp-ele.c b/drivers/nvmem/imx-ocotp-ele.c index cf920542f939..ca6dd71d8a2e 100644 --- a/drivers/nvmem/imx-ocotp-ele.c +++ b/drivers/nvmem/imx-ocotp-ele.c @@ -14,8 +14,9 @@ #include <linux/slab.h> enum fuse_type { - FUSE_FSB = 1, - FUSE_ELE = 2, + FUSE_FSB = BIT(0), + FUSE_ELE = BIT(1), + FUSE_ECC = BIT(2), FUSE_INVALID = -1 }; @@ -70,13 +71,15 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz u32 *buf; void *p; int i; + u8 skipbytes; - index = offset; - num_bytes = round_up(bytes, 4); - count = num_bytes >> 2; + if (offset + bytes > priv->data->size) + bytes = priv->data->size - offset; - if (count > ((priv->data->size >> 2) - index)) - count = (priv->data->size >> 2) - index; + index = offset >> 2; + skipbytes = offset - (index << 2); + num_bytes = round_up(bytes + skipbytes, 4); + count = num_bytes >> 2; p = kzalloc(num_bytes, GFP_KERNEL); if (!p) @@ -93,10 +96,13 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz continue; } - *buf++ = readl_relaxed(reg + (i << 2)); + if (type & FUSE_ECC) + *buf++ = readl_relaxed(reg + (i << 2)) & GENMASK(15, 0); + else + *buf++ = readl_relaxed(reg + (i << 2)); } - memcpy(val, (u8 *)p, bytes); + memcpy(val, ((u8 *)p) + skipbytes, bytes); mutex_unlock(&priv->lock); @@ -105,6 +111,26 @@ static int imx_ocotp_reg_read(void *context, unsigned int offset, void *val, siz return 0; }; +static int imx_ocotp_cell_pp(void *context, const char *id, int index, + unsigned int offset, void *data, size_t bytes) +{ + u8 *buf = data; + int i; + + /* Deal with some post processing of nvmem cell data */ + if (id && !strcmp(id, "mac-address")) + for (i = 0; i < bytes / 2; i++) + swap(buf[i], buf[bytes - i - 1]); + + return 0; +} + +static void imx_ocotp_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + cell->read_post_process = imx_ocotp_cell_pp; +} + static int imx_ele_ocotp_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -127,10 +153,12 @@ static int imx_ele_ocotp_probe(struct platform_device *pdev) priv->config.owner = THIS_MODULE; priv->config.size = priv->data->size; priv->config.reg_read = priv->data->reg_read; - priv->config.word_size = 4; + priv->config.word_size = 1; priv->config.stride = 1; priv->config.priv = priv; priv->config.read_only = true; + priv->config.add_legacy_fixed_of_cells = true; + priv->config.fixup_dt_cell_info = imx_ocotp_fixup_dt_cell_info; mutex_init(&priv->lock); nvmem = devm_nvmem_register(dev, &priv->config); @@ -155,8 +183,30 @@ static const struct ocotp_devtype_data imx93_ocotp_data = { }, }; +static const struct ocotp_devtype_data imx95_ocotp_data = { + .reg_off = 0x8000, + .reg_read = imx_ocotp_reg_read, + .size = 2048, + .num_entry = 12, + .entry = { + { 0, 1, FUSE_FSB | FUSE_ECC }, + { 7, 1, FUSE_FSB | FUSE_ECC }, + { 9, 3, FUSE_FSB | FUSE_ECC }, + { 12, 24, FUSE_FSB }, + { 36, 2, FUSE_FSB | FUSE_ECC }, + { 38, 14, FUSE_FSB }, + { 63, 1, FUSE_ELE }, + { 128, 16, FUSE_ELE }, + { 188, 1, FUSE_ELE }, + { 317, 2, FUSE_FSB | FUSE_ECC }, + { 320, 7, FUSE_FSB }, + { 328, 184, FUSE_FSB } + }, +}; + static const struct of_device_id imx_ele_ocotp_dt_ids[] = { { .compatible = "fsl,imx93-ocotp", .data = &imx93_ocotp_data, }, + { .compatible = "fsl,imx95-ocotp", .data = &imx95_ocotp_data, }, {}, }; MODULE_DEVICE_TABLE(of, imx_ele_ocotp_dt_ids); diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c index 6a6aa58369ff..65d39e19f6ec 100644 --- a/drivers/nvmem/layouts.c +++ b/drivers/nvmem/layouts.c @@ -17,11 +17,11 @@ #include "internals.h" #define to_nvmem_layout_driver(drv) \ - (container_of((drv), struct nvmem_layout_driver, driver)) + (container_of_const((drv), struct nvmem_layout_driver, driver)) #define to_nvmem_layout_device(_dev) \ container_of((_dev), struct nvmem_layout, dev) -static int nvmem_layout_bus_match(struct device *dev, struct device_driver *drv) +static int nvmem_layout_bus_match(struct device *dev, const struct device_driver *drv) { return of_driver_match_device(dev, drv); } @@ -45,20 +45,22 @@ static void nvmem_layout_bus_remove(struct device *dev) return drv->remove(layout); } -static struct bus_type nvmem_layout_bus_type = { +static const struct bus_type nvmem_layout_bus_type = { .name = "nvmem-layout", .match = nvmem_layout_bus_match, .probe = nvmem_layout_bus_probe, .remove = nvmem_layout_bus_remove, }; -int nvmem_layout_driver_register(struct nvmem_layout_driver *drv) +int __nvmem_layout_driver_register(struct nvmem_layout_driver *drv, + struct module *owner) { drv->driver.bus = &nvmem_layout_bus_type; + drv->driver.owner = owner; return driver_register(&drv->driver); } -EXPORT_SYMBOL_GPL(nvmem_layout_driver_register); +EXPORT_SYMBOL_GPL(__nvmem_layout_driver_register); void nvmem_layout_driver_unregister(struct nvmem_layout_driver *drv) { @@ -121,7 +123,7 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem, int ret; /* Make sure it has a compatible property */ - if (!of_get_property(layout_dn, "compatible", NULL)) { + if (!of_property_present(layout_dn, "compatible")) { pr_debug("%s() - skipping %pOF, no compatible prop\n", __func__, layout_dn); return 0; diff --git a/drivers/nvmem/layouts/Kconfig b/drivers/nvmem/layouts/Kconfig index 9c6e672fc350..5e586dfebe47 100644 --- a/drivers/nvmem/layouts/Kconfig +++ b/drivers/nvmem/layouts/Kconfig @@ -26,6 +26,17 @@ config NVMEM_LAYOUT_ONIE_TLV If unsure, say N. +config NVMEM_LAYOUT_U_BOOT_ENV + tristate "U-Boot environment variables layout" + 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 + as NVMEM cells so they can be referenced by other drivers. + + If unsure, say N. + endmenu endif diff --git a/drivers/nvmem/layouts/Makefile b/drivers/nvmem/layouts/Makefile index 2974bd7d33ed..4940c9db0665 100644 --- a/drivers/nvmem/layouts/Makefile +++ b/drivers/nvmem/layouts/Makefile @@ -5,3 +5,4 @@ obj-$(CONFIG_NVMEM_LAYOUT_SL28_VPD) += sl28vpd.o obj-$(CONFIG_NVMEM_LAYOUT_ONIE_TLV) += onie-tlv.o +obj-$(CONFIG_NVMEM_LAYOUT_U_BOOT_ENV) += u-boot-env.o diff --git a/drivers/nvmem/layouts/onie-tlv.c b/drivers/nvmem/layouts/onie-tlv.c index 9d2ad5f2dc10..0967a32319a2 100644 --- a/drivers/nvmem/layouts/onie-tlv.c +++ b/drivers/nvmem/layouts/onie-tlv.c @@ -247,7 +247,6 @@ MODULE_DEVICE_TABLE(of, onie_tlv_of_match_table); static struct nvmem_layout_driver onie_tlv_layout = { .driver = { - .owner = THIS_MODULE, .name = "onie-tlv-layout", .of_match_table = onie_tlv_of_match_table, }, diff --git a/drivers/nvmem/layouts/sl28vpd.c b/drivers/nvmem/layouts/sl28vpd.c index 53fa50f17dca..e93b020b0836 100644 --- a/drivers/nvmem/layouts/sl28vpd.c +++ b/drivers/nvmem/layouts/sl28vpd.c @@ -156,7 +156,6 @@ 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, }, diff --git a/drivers/nvmem/layouts/u-boot-env.c b/drivers/nvmem/layouts/u-boot-env.c new file mode 100644 index 000000000000..436426d4e8f9 --- /dev/null +++ b/drivers/nvmem/layouts/u-boot-env.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (C) 2022 - 2023 Rafał Miłecki <rafal@milecki.pl> + */ + +#include <linux/crc32.h> +#include <linux/etherdevice.h> +#include <linux/export.h> +#include <linux/if_ether.h> +#include <linux/nvmem-consumer.h> +#include <linux/nvmem-provider.h> +#include <linux/of.h> +#include <linux/slab.h> + +#include "u-boot-env.h" + +struct u_boot_env_image_single { + __le32 crc32; + uint8_t data[]; +} __packed; + +struct u_boot_env_image_redundant { + __le32 crc32; + u8 mark; + uint8_t data[]; +} __packed; + +struct u_boot_env_image_broadcom { + __le32 magic; + __le32 len; + __le32 crc32; + DECLARE_FLEX_ARRAY(uint8_t, data); +} __packed; + +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 != MAC_ADDR_STR_LEN) + 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_parse_cells(struct device *dev, struct nvmem_device *nvmem, uint8_t *buf, + size_t data_offset, size_t data_len) +{ + char *data = buf + data_offset; + char *var, *value, *eq; + + for (var = data; + var < data + data_len && *var; + var = value + strlen(value) + 1) { + struct nvmem_cell_info info = {}; + + eq = strchr(var, '='); + if (!eq) + break; + *eq = '\0'; + value = eq + 1; + + info.name = devm_kstrdup(dev, var, GFP_KERNEL); + if (!info.name) + return -ENOMEM; + info.offset = data_offset + value - data; + info.bytes = strlen(value); + info.np = of_get_child_by_name(dev->of_node, info.name); + if (!strcmp(var, "ethaddr")) { + info.raw_len = strlen(value); + info.bytes = ETH_ALEN; + info.read_post_process = u_boot_env_read_post_process_ethaddr; + } + + nvmem_add_one_cell(nvmem, &info); + } + + return 0; +} + +int u_boot_env_parse(struct device *dev, struct nvmem_device *nvmem, + enum u_boot_env_format format) +{ + size_t crc32_data_offset; + size_t crc32_data_len; + size_t crc32_offset; + __le32 *crc32_addr; + size_t data_offset; + size_t data_len; + size_t dev_size; + uint32_t crc32; + uint32_t calc; + uint8_t *buf; + int bytes; + int err; + + dev_size = nvmem_dev_size(nvmem); + + buf = kzalloc(dev_size, GFP_KERNEL); + if (!buf) { + err = -ENOMEM; + goto err_out; + } + + bytes = nvmem_device_read(nvmem, 0, dev_size, buf); + if (bytes < 0) { + err = bytes; + goto err_kfree; + } else if (bytes != dev_size) { + err = -EIO; + goto err_kfree; + } + + switch (format) { + case U_BOOT_FORMAT_SINGLE: + crc32_offset = offsetof(struct u_boot_env_image_single, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_single, data); + data_offset = offsetof(struct u_boot_env_image_single, data); + break; + case U_BOOT_FORMAT_REDUNDANT: + crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data); + data_offset = offsetof(struct u_boot_env_image_redundant, data); + break; + case U_BOOT_FORMAT_BROADCOM: + crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32); + crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data); + data_offset = offsetof(struct u_boot_env_image_broadcom, data); + break; + } + + if (dev_size < data_offset) { + dev_err(dev, "Device too small for u-boot-env\n"); + err = -EIO; + goto err_kfree; + } + + crc32_addr = (__le32 *)(buf + crc32_offset); + crc32 = le32_to_cpu(*crc32_addr); + crc32_data_len = dev_size - crc32_data_offset; + data_len = dev_size - data_offset; + + calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L; + if (calc != crc32) { + dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32); + err = -EINVAL; + goto err_kfree; + } + + buf[dev_size - 1] = '\0'; + err = u_boot_env_parse_cells(dev, nvmem, buf, data_offset, data_len); + +err_kfree: + kfree(buf); +err_out: + return err; +} +EXPORT_SYMBOL_GPL(u_boot_env_parse); + +static int u_boot_env_add_cells(struct nvmem_layout *layout) +{ + struct device *dev = &layout->dev; + enum u_boot_env_format format; + + format = (uintptr_t)device_get_match_data(dev); + + return u_boot_env_parse(dev, layout->nvmem, format); +} + +static int u_boot_env_probe(struct nvmem_layout *layout) +{ + layout->add_cells = u_boot_env_add_cells; + + return nvmem_layout_register(layout); +} + +static void u_boot_env_remove(struct nvmem_layout *layout) +{ + nvmem_layout_unregister(layout); +} + +static const struct of_device_id u_boot_env_of_match_table[] = { + { .compatible = "u-boot,env", .data = (void *)U_BOOT_FORMAT_SINGLE, }, + { .compatible = "u-boot,env-redundant-bool", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "u-boot,env-redundant-count", .data = (void *)U_BOOT_FORMAT_REDUNDANT, }, + { .compatible = "brcm,env", .data = (void *)U_BOOT_FORMAT_BROADCOM, }, + {}, +}; + +static struct nvmem_layout_driver u_boot_env_layout = { + .driver = { + .name = "u-boot-env-layout", + .of_match_table = u_boot_env_of_match_table, + }, + .probe = u_boot_env_probe, + .remove = u_boot_env_remove, +}; +module_nvmem_layout_driver(u_boot_env_layout); + +MODULE_AUTHOR("Rafał Miłecki"); +MODULE_LICENSE("GPL"); +MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table); +MODULE_DESCRIPTION("NVMEM layout driver for U-Boot environment variables"); diff --git a/drivers/nvmem/layouts/u-boot-env.h b/drivers/nvmem/layouts/u-boot-env.h new file mode 100644 index 000000000000..dd5f280ac047 --- /dev/null +++ b/drivers/nvmem/layouts/u-boot-env.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +#ifndef _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H +#define _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H + +enum u_boot_env_format { + U_BOOT_FORMAT_SINGLE, + U_BOOT_FORMAT_REDUNDANT, + U_BOOT_FORMAT_BROADCOM, +}; + +int u_boot_env_parse(struct device *dev, struct nvmem_device *nvmem, + enum u_boot_env_format format); + +#endif /* ifndef _LINUX_NVMEM_LAYOUTS_U_BOOT_ENV_H */ diff --git a/drivers/nvmem/lpc18xx_eeprom.c b/drivers/nvmem/lpc18xx_eeprom.c index a0275b29afd5..aa43f5f612f9 100644 --- a/drivers/nvmem/lpc18xx_eeprom.c +++ b/drivers/nvmem/lpc18xx_eeprom.c @@ -249,13 +249,11 @@ err_clk: return ret; } -static int lpc18xx_eeprom_remove(struct platform_device *pdev) +static void lpc18xx_eeprom_remove(struct platform_device *pdev) { struct lpc18xx_eeprom_dev *eeprom = platform_get_drvdata(pdev); clk_disable_unprepare(eeprom->clk); - - return 0; } static const struct of_device_id lpc18xx_eeprom_of_match[] = { diff --git a/drivers/nvmem/lpc18xx_otp.c b/drivers/nvmem/lpc18xx_otp.c index adc9948e7b2e..c41a0c58bec7 100644 --- a/drivers/nvmem/lpc18xx_otp.c +++ b/drivers/nvmem/lpc18xx_otp.c @@ -21,7 +21,7 @@ * LPC18xx OTP memory contains 4 banks with 4 32-bit words. Bank 0 starts * at offset 0 from the base. * - * Bank 0 contains the part ID for Flashless devices and is reseverd for + * Bank 0 contains the part ID for Flashless devices and is reserved for * devices with Flash. * Bank 1/2 is generale purpose or AES key storage for secure devices. * Bank 3 contains control data, USB ID and generale purpose words. diff --git a/drivers/nvmem/max77759-nvmem.c b/drivers/nvmem/max77759-nvmem.c new file mode 100644 index 000000000000..c9961ad0e232 --- /dev/null +++ b/drivers/nvmem/max77759-nvmem.c @@ -0,0 +1,145 @@ +// SPDX-License-Identifier: GPL-2.0-only +// +// Copyright 2020 Google Inc +// Copyright 2025 Linaro Ltd. +// +// NVMEM driver for Maxim MAX77759 + +#include <linux/dev_printk.h> +#include <linux/device.h> +#include <linux/device/driver.h> +#include <linux/err.h> +#include <linux/mfd/max77759.h> +#include <linux/mod_devicetable.h> +#include <linux/module.h> +#include <linux/nvmem-provider.h> +#include <linux/overflow.h> +#include <linux/platform_device.h> +#include <linux/string.h> + +#define MAX77759_NVMEM_OPCODE_HEADER_LEN 3 +/* + * NVMEM commands have a three byte header (which becomes part of the command), + * so we need to subtract that. + */ +#define MAX77759_NVMEM_SIZE (MAX77759_MAXQ_OPCODE_MAXLENGTH \ + - MAX77759_NVMEM_OPCODE_HEADER_LEN) + +struct max77759_nvmem { + struct device *dev; + struct max77759 *max77759; +}; + +static int max77759_nvmem_reg_read(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_NVMEM_OPCODE_HEADER_LEN); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_READ; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + rsp->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, MAX77759_NVMEM_OPCODE_HEADER_LEN)) { + dev_warn(nvmem->dev, "protocol error (read)\n"); + return -EIO; + } + + memcpy(val, &rsp->rsp[MAX77759_NVMEM_OPCODE_HEADER_LEN], bytes); + + return 0; +} + +static int max77759_nvmem_reg_write(void *priv, unsigned int offset, + void *val, size_t bytes) +{ + struct max77759_nvmem *nvmem = priv; + DEFINE_FLEX(struct max77759_maxq_command, cmd, cmd, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + DEFINE_FLEX(struct max77759_maxq_response, rsp, rsp, length, + MAX77759_MAXQ_OPCODE_MAXLENGTH); + int ret; + + cmd->cmd[0] = MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE; + cmd->cmd[1] = offset; + cmd->cmd[2] = bytes; + memcpy(&cmd->cmd[MAX77759_NVMEM_OPCODE_HEADER_LEN], val, bytes); + cmd->length = bytes + MAX77759_NVMEM_OPCODE_HEADER_LEN; + rsp->length = cmd->length; + + ret = max77759_maxq_command(nvmem->max77759, cmd, rsp); + if (ret < 0) + return ret; + + if (memcmp(cmd->cmd, rsp->rsp, cmd->length)) { + dev_warn(nvmem->dev, "protocol error (write)\n"); + return -EIO; + } + + return 0; +} + +static int max77759_nvmem_probe(struct platform_device *pdev) +{ + struct nvmem_config config = { + .dev = &pdev->dev, + .name = dev_name(&pdev->dev), + .id = NVMEM_DEVID_NONE, + .type = NVMEM_TYPE_EEPROM, + .ignore_wp = true, + .size = MAX77759_NVMEM_SIZE, + .word_size = sizeof(u8), + .stride = sizeof(u8), + .reg_read = max77759_nvmem_reg_read, + .reg_write = max77759_nvmem_reg_write, + }; + struct max77759_nvmem *nvmem; + + nvmem = devm_kzalloc(&pdev->dev, sizeof(*nvmem), GFP_KERNEL); + if (!nvmem) + return -ENOMEM; + + nvmem->dev = &pdev->dev; + nvmem->max77759 = dev_get_drvdata(pdev->dev.parent); + + config.priv = nvmem; + + return PTR_ERR_OR_ZERO(devm_nvmem_register(config.dev, &config)); +} + +static const struct of_device_id max77759_nvmem_of_id[] = { + { .compatible = "maxim,max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(of, max77759_nvmem_of_id); + +static const struct platform_device_id max77759_nvmem_platform_id[] = { + { "max77759-nvmem", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_nvmem_platform_id); + +static struct platform_driver max77759_nvmem_driver = { + .driver = { + .name = "max77759-nvmem", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + .of_match_table = max77759_nvmem_of_id, + }, + .probe = max77759_nvmem_probe, + .id_table = max77759_nvmem_platform_id, +}; + +module_platform_driver(max77759_nvmem_driver); + +MODULE_AUTHOR("André Draszik <andre.draszik@linaro.org>"); +MODULE_DESCRIPTION("NVMEM driver for Maxim MAX77759"); +MODULE_LICENSE("GPL"); diff --git a/drivers/nvmem/meson-efuse.c b/drivers/nvmem/meson-efuse.c index b922df99f9bc..d7f9ac99a212 100644 --- a/drivers/nvmem/meson-efuse.c +++ b/drivers/nvmem/meson-efuse.c @@ -18,18 +18,24 @@ static int meson_efuse_read(void *context, unsigned int offset, void *val, size_t bytes) { struct meson_sm_firmware *fw = context; + int ret; - return meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset, - bytes, 0, 0, 0); + ret = meson_sm_call_read(fw, (u8 *)val, bytes, SM_EFUSE_READ, offset, + bytes, 0, 0, 0); + + return ret < 0 ? ret : 0; } static int meson_efuse_write(void *context, unsigned int offset, void *val, size_t bytes) { struct meson_sm_firmware *fw = context; + int ret; + + ret = meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset, + bytes, 0, 0, 0); - return meson_sm_call_write(fw, (u8 *)val, bytes, SM_EFUSE_WRITE, offset, - bytes, 0, 0, 0); + return ret < 0 ? ret : 0; } static const struct of_device_id meson_efuse_match[] = { @@ -42,45 +48,25 @@ static int meson_efuse_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct meson_sm_firmware *fw; - struct device_node *sm_np; struct nvmem_device *nvmem; struct nvmem_config *econfig; struct clk *clk; unsigned int size; - int ret; + struct device_node *sm_np __free(device_node) = + of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); - sm_np = of_parse_phandle(pdev->dev.of_node, "secure-monitor", 0); if (!sm_np) { dev_err(&pdev->dev, "no secure-monitor node\n"); return -ENODEV; } fw = meson_sm_get(sm_np); - of_node_put(sm_np); if (!fw) return -EPROBE_DEFER; - clk = devm_clk_get(dev, NULL); - if (IS_ERR(clk)) { - ret = PTR_ERR(clk); - if (ret != -EPROBE_DEFER) - dev_err(dev, "failed to get efuse gate"); - return ret; - } - - ret = clk_prepare_enable(clk); - if (ret) { - dev_err(dev, "failed to enable gate"); - return ret; - } - - ret = devm_add_action_or_reset(dev, - (void(*)(void *))clk_disable_unprepare, - clk); - if (ret) { - dev_err(dev, "failed to add disable callback"); - return ret; - } + clk = devm_clk_get_enabled(dev, NULL); + if (IS_ERR(clk)) + return dev_err_probe(dev, PTR_ERR(clk), "failed to get efuse gate"); if (meson_sm_call(fw, SM_EFUSE_USER_MAX, &size, 0, 0, 0, 0, 0) < 0) { dev_err(dev, "failed to get max user"); diff --git a/drivers/nvmem/meson-mx-efuse.c b/drivers/nvmem/meson-mx-efuse.c index 3ff04d5ca8f8..8a16f5f02657 100644 --- a/drivers/nvmem/meson-mx-efuse.c +++ b/drivers/nvmem/meson-mx-efuse.c @@ -43,7 +43,6 @@ struct meson_mx_efuse_platform_data { struct meson_mx_efuse { void __iomem *base; struct clk *core_clk; - struct nvmem_device *nvmem; struct nvmem_config config; }; @@ -193,6 +192,7 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) { const struct meson_mx_efuse_platform_data *drvdata; struct meson_mx_efuse *efuse; + struct nvmem_device *nvmem; drvdata = of_device_get_match_data(&pdev->dev); if (!drvdata) @@ -223,9 +223,9 @@ static int meson_mx_efuse_probe(struct platform_device *pdev) return PTR_ERR(efuse->core_clk); } - efuse->nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); + nvmem = devm_nvmem_register(&pdev->dev, &efuse->config); - return PTR_ERR_OR_ZERO(efuse->nvmem); + return PTR_ERR_OR_ZERO(nvmem); } static struct platform_driver meson_mx_efuse_driver = { diff --git a/drivers/nvmem/microchip-otpc.c b/drivers/nvmem/microchip-otpc.c index 7cf81738a3e0..df979e8549fd 100644 --- a/drivers/nvmem/microchip-otpc.c +++ b/drivers/nvmem/microchip-otpc.c @@ -156,7 +156,7 @@ static int mchp_otpc_read(void *priv, unsigned int off, void *val, /* * We reach this point with off being multiple of stride = 4 to * be able to cross the subsystem. Inside the driver we use continuous - * unsigned integer numbers for packet id, thus devide off by 4 + * unsigned integer numbers for packet id, thus divide off by 4 * before passing it to mchp_otpc_id_to_packet(). */ packet = mchp_otpc_id_to_packet(otpc, off / 4); diff --git a/drivers/nvmem/mtk-efuse.c b/drivers/nvmem/mtk-efuse.c index 84f05b40a411..af953e1d9230 100644 --- a/drivers/nvmem/mtk-efuse.c +++ b/drivers/nvmem/mtk-efuse.c @@ -68,6 +68,7 @@ static int mtk_efuse_probe(struct platform_device *pdev) struct nvmem_config econfig = {}; struct mtk_efuse_priv *priv; const struct mtk_efuse_pdata *pdata; + struct platform_device *socinfo; priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL); if (!priv) @@ -88,8 +89,16 @@ static int mtk_efuse_probe(struct platform_device *pdev) if (pdata->uses_post_processing) econfig.fixup_dt_cell_info = &mtk_efuse_fixup_dt_cell_info; nvmem = devm_nvmem_register(dev, &econfig); + if (IS_ERR(nvmem)) + return PTR_ERR(nvmem); - return PTR_ERR_OR_ZERO(nvmem); + socinfo = platform_device_register_data(&pdev->dev, "mtk-socinfo", + PLATFORM_DEVID_AUTO, NULL, 0); + if (IS_ERR(socinfo)) + dev_info(dev, "MediaTek SoC Information will be unavailable\n"); + + platform_set_drvdata(pdev, socinfo); + return 0; } static const struct mtk_efuse_pdata mtk_mt8186_efuse_pdata = { @@ -108,8 +117,17 @@ static const struct of_device_id mtk_efuse_of_match[] = { }; MODULE_DEVICE_TABLE(of, mtk_efuse_of_match); +static void mtk_efuse_remove(struct platform_device *pdev) +{ + struct platform_device *socinfo = platform_get_drvdata(pdev); + + if (!IS_ERR_OR_NULL(socinfo)) + platform_device_unregister(socinfo); +} + static struct platform_driver mtk_efuse_driver = { .probe = mtk_efuse_probe, + .remove = mtk_efuse_remove, .driver = { .name = "mediatek,efuse", .of_match_table = mtk_efuse_of_match, diff --git a/drivers/nvmem/qcom-spmi-sdam.c b/drivers/nvmem/qcom-spmi-sdam.c index 9aa8f42faa4c..4f1cca6eab71 100644 --- a/drivers/nvmem/qcom-spmi-sdam.c +++ b/drivers/nvmem/qcom-spmi-sdam.c @@ -144,6 +144,7 @@ static int sdam_probe(struct platform_device *pdev) sdam->sdam_config.owner = THIS_MODULE; sdam->sdam_config.add_legacy_fixed_of_cells = true; sdam->sdam_config.stride = 1; + sdam->sdam_config.size = sdam->size; sdam->sdam_config.word_size = 1; sdam->sdam_config.reg_read = sdam_read; sdam->sdam_config.reg_write = sdam_write; diff --git a/drivers/nvmem/qfprom.c b/drivers/nvmem/qfprom.c index 116a39e804c7..a872c640b8c5 100644 --- a/drivers/nvmem/qfprom.c +++ b/drivers/nvmem/qfprom.c @@ -321,19 +321,32 @@ static int qfprom_reg_read(void *context, unsigned int reg, void *_val, size_t bytes) { struct qfprom_priv *priv = context; - u8 *val = _val; - int i = 0, words = bytes; + u32 *val = _val; void __iomem *base = priv->qfpcorrected; + int words = DIV_ROUND_UP(bytes, sizeof(u32)); + int i; if (read_raw_data && priv->qfpraw) base = priv->qfpraw; - while (words--) - *val++ = readb(base + reg + i++); + for (i = 0; i < words; i++) + *val++ = readl(base + reg + i * sizeof(u32)); return 0; } +/* Align reads to word boundary */ +static void qfprom_fixup_dt_cell_info(struct nvmem_device *nvmem, + struct nvmem_cell_info *cell) +{ + unsigned int byte_offset = cell->offset % sizeof(u32); + + cell->bit_offset += byte_offset * BITS_PER_BYTE; + cell->offset -= byte_offset; + if (byte_offset && !cell->nbits) + cell->nbits = cell->bytes * BITS_PER_BYTE; +} + static void qfprom_runtime_disable(void *data) { pm_runtime_disable(data); @@ -358,10 +371,11 @@ static int qfprom_probe(struct platform_device *pdev) struct nvmem_config econfig = { .name = "qfprom", .add_legacy_fixed_of_cells = true, - .stride = 1, - .word_size = 1, + .stride = 4, + .word_size = 4, .id = NVMEM_DEVID_AUTO, .reg_read = qfprom_reg_read, + .fixup_dt_cell_info = qfprom_fixup_dt_cell_info, }; struct device *dev = &pdev->dev; struct resource *res; diff --git a/drivers/nvmem/rcar-efuse.c b/drivers/nvmem/rcar-efuse.c new file mode 100644 index 000000000000..f24bdb9cb5a7 --- /dev/null +++ b/drivers/nvmem/rcar-efuse.c @@ -0,0 +1,142 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Renesas R-Car E-FUSE/OTP Driver + * + * Copyright (C) 2024 Glider bv + */ + +#include <linux/device.h> +#include <linux/export.h> +#include <linux/io.h> +#include <linux/mod_devicetable.h> +#include <linux/nvmem-provider.h> +#include <linux/platform_device.h> +#include <linux/pm_runtime.h> +#include <linux/property.h> + +struct rcar_fuse { + struct nvmem_keepout keepouts[2]; + struct nvmem_device *nvmem; + struct device *dev; + void __iomem *base; +}; + +struct rcar_fuse_data { + unsigned int bank; /* 0: PFC + E-FUSE, 1: OPT_MEM + E-FUSE */ + unsigned int start; /* inclusive */ + unsigned int end; /* exclusive */ +}; + +static int rcar_fuse_reg_read(void *priv, unsigned int offset, void *val, + size_t bytes) +{ + struct rcar_fuse *fuse = priv; + int ret; + + ret = pm_runtime_resume_and_get(fuse->dev); + if (ret < 0) + return ret; + + __ioread32_copy(val, fuse->base + offset, bytes / 4); + + pm_runtime_put(fuse->dev); + + return 0; +} + +static int rcar_fuse_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + const struct rcar_fuse_data *data = device_get_match_data(dev); + struct nvmem_config config = { + .dev = dev, + .name = "rcar-fuse", + .id = NVMEM_DEVID_NONE, + .owner = THIS_MODULE, + .type = NVMEM_TYPE_OTP, + .read_only = true, + .root_only = true, + .reg_read = rcar_fuse_reg_read, + .word_size = 4, + .stride = 4, + }; + struct rcar_fuse *fuse; + struct resource *res; + int ret; + + ret = devm_pm_runtime_enable(dev); + if (ret < 0) + return ret; + + fuse = devm_kzalloc(dev, sizeof(*fuse), GFP_KERNEL); + if (!fuse) + return -ENOMEM; + + fuse->base = devm_platform_get_and_ioremap_resource(pdev, data->bank, + &res); + if (IS_ERR(fuse->base)) + return PTR_ERR(fuse->base); + + fuse->dev = dev; + fuse->keepouts[0].start = 0; + fuse->keepouts[0].end = data->start; + fuse->keepouts[1].start = data->end; + fuse->keepouts[1].end = resource_size(res); + + config.keepout = fuse->keepouts; + config.nkeepout = ARRAY_SIZE(fuse->keepouts); + config.size = resource_size(res); + config.priv = fuse; + + fuse->nvmem = devm_nvmem_register(dev, &config); + if (IS_ERR(fuse->nvmem)) + return dev_err_probe(dev, PTR_ERR(fuse->nvmem), + "Failed to register NVMEM device\n"); + + return 0; +} + +static const struct rcar_fuse_data rcar_fuse_v3u = { + .bank = 0, + .start = 0x0c0, + .end = 0x0e8, +}; + +static const struct rcar_fuse_data rcar_fuse_s4 = { + .bank = 0, + .start = 0x0c0, + .end = 0x14c, +}; + +static const struct rcar_fuse_data rcar_fuse_v4h = { + .bank = 1, + .start = 0x100, + .end = 0x1a0, +}; + +static const struct rcar_fuse_data rcar_fuse_v4m = { + .bank = 1, + .start = 0x100, + .end = 0x110, +}; + +static const struct of_device_id rcar_fuse_match[] = { + { .compatible = "renesas,r8a779a0-efuse", .data = &rcar_fuse_v3u }, + { .compatible = "renesas,r8a779f0-efuse", .data = &rcar_fuse_s4 }, + { .compatible = "renesas,r8a779g0-otp", .data = &rcar_fuse_v4h }, + { .compatible = "renesas,r8a779h0-otp", .data = &rcar_fuse_v4m }, + { /* sentinel */ } +}; + +static struct platform_driver rcar_fuse_driver = { + .probe = rcar_fuse_probe, + .driver = { + .name = "rcar_fuse", + .of_match_table = rcar_fuse_match, + }, +}; +module_platform_driver(rcar_fuse_driver); + +MODULE_DESCRIPTION("Renesas R-Car E-FUSE/OTP driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Geert Uytterhoeven"); diff --git a/drivers/nvmem/rmem.c b/drivers/nvmem/rmem.c index 752d0bf4445e..b39d628cb60a 100644 --- a/drivers/nvmem/rmem.c +++ b/drivers/nvmem/rmem.c @@ -3,28 +3,40 @@ * Copyright (C) 2020 Nicolas Saenz Julienne <nsaenzjulienne@suse.de> */ +#include <linux/crc32.h> #include <linux/io.h> #include <linux/module.h> #include <linux/nvmem-provider.h> #include <linux/of_reserved_mem.h> #include <linux/platform_device.h> +#include <linux/slab.h> struct rmem { struct device *dev; struct nvmem_device *nvmem; struct reserved_mem *mem; +}; + +struct rmem_match_data { + int (*checksum)(struct rmem *priv); +}; - phys_addr_t size; +struct __packed rmem_eyeq5_header { + u32 magic; + u32 version; + u32 size; }; +#define RMEM_EYEQ5_MAGIC ((u32)0xDABBAD00) + static int rmem_read(void *context, unsigned int offset, void *val, size_t bytes) { struct rmem *priv = context; - size_t available = priv->mem->size; - loff_t off = offset; void *addr; - int count; + + if ((phys_addr_t)offset + bytes > priv->mem->size) + return -EIO; /* * Only map the reserved memory at this point to avoid potential rogue @@ -36,23 +48,79 @@ static int rmem_read(void *context, unsigned int offset, * An alternative would be setting the memory as RO, set_memory_ro(), * but as of Dec 2020 this isn't possible on arm64. */ - addr = memremap(priv->mem->base, available, MEMREMAP_WB); + addr = memremap(priv->mem->base, priv->mem->size, MEMREMAP_WB); if (!addr) { dev_err(priv->dev, "Failed to remap memory region\n"); return -ENOMEM; } - count = memory_read_from_buffer(val, bytes, &off, addr, available); + memcpy(val, addr + offset, bytes); memunmap(addr); - return count; + return 0; +} + +static int rmem_eyeq5_checksum(struct rmem *priv) +{ + void *buf __free(kfree) = NULL; + struct rmem_eyeq5_header header; + u32 computed_crc, *target_crc; + size_t data_size; + int ret; + + ret = rmem_read(priv, 0, &header, sizeof(header)); + if (ret) + return ret; + + if (header.magic != RMEM_EYEQ5_MAGIC) + return -EINVAL; + + /* + * Avoid massive kmalloc() if header read is invalid; + * the check would be done by the next rmem_read() anyway. + */ + if (header.size > priv->mem->size) + return -EINVAL; + + /* + * 0 +-------------------+ + * | Header (12 bytes) | \ + * +-------------------+ | + * | | | data to be CRCed + * | ... | | + * | | / + * data_size +-------------------+ + * | CRC (4 bytes) | + * header.size +-------------------+ + */ + + buf = kmalloc(header.size, GFP_KERNEL); + if (!buf) + return -ENOMEM; + + ret = rmem_read(priv, 0, buf, header.size); + if (ret) + return ret; + + data_size = header.size - sizeof(*target_crc); + target_crc = buf + data_size; + computed_crc = crc32(U32_MAX, buf, data_size) ^ U32_MAX; + + if (computed_crc == *target_crc) + return 0; + + dev_err(priv->dev, + "checksum failed: computed %#x, expected %#x, header (%#x, %#x, %#x)\n", + computed_crc, *target_crc, header.magic, header.version, header.size); + return -EINVAL; } static int rmem_probe(struct platform_device *pdev) { struct nvmem_config config = { }; struct device *dev = &pdev->dev; + const struct rmem_match_data *match_data = device_get_match_data(dev); struct reserved_mem *mem; struct rmem *priv; @@ -75,10 +143,22 @@ static int rmem_probe(struct platform_device *pdev) config.size = mem->size; config.reg_read = rmem_read; + if (match_data && match_data->checksum) { + int ret = match_data->checksum(priv); + + if (ret) + return ret; + } + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &config)); } +static const struct rmem_match_data rmem_eyeq5_match_data = { + .checksum = rmem_eyeq5_checksum, +}; + static const struct of_device_id rmem_match[] = { + { .compatible = "mobileye,eyeq5-bootloader-config", .data = &rmem_eyeq5_match_data }, { .compatible = "nvmem-rmem", }, { /* sentinel */ }, }; diff --git a/drivers/nvmem/rockchip-efuse.c b/drivers/nvmem/rockchip-efuse.c index 2b40978ddb18..013e67136f3b 100644 --- a/drivers/nvmem/rockchip-efuse.c +++ b/drivers/nvmem/rockchip-efuse.c @@ -206,6 +206,7 @@ static int rockchip_rk3399_efuse_read(void *context, unsigned int offset, static struct nvmem_config econfig = { .name = "rockchip-efuse", .add_legacy_fixed_of_cells = true, + .type = NVMEM_TYPE_OTP, .stride = 1, .word_size = 1, .read_only = true, diff --git a/drivers/nvmem/rockchip-otp.c b/drivers/nvmem/rockchip-otp.c index cb9aa5428350..d88f12c53242 100644 --- a/drivers/nvmem/rockchip-otp.c +++ b/drivers/nvmem/rockchip-otp.c @@ -59,7 +59,6 @@ #define RK3588_OTPC_AUTO_EN 0x08 #define RK3588_OTPC_INT_ST 0x84 #define RK3588_OTPC_DOUT0 0x20 -#define RK3588_NO_SECURE_OFFSET 0x300 #define RK3588_NBYTES 4 #define RK3588_BURST_NUM 1 #define RK3588_BURST_SHIFT 8 @@ -69,6 +68,7 @@ struct rockchip_data { int size; + int read_offset; const char * const *clks; int num_clks; nvmem_reg_read_t reg_read; @@ -196,7 +196,7 @@ static int rk3588_otp_read(void *context, unsigned int offset, addr_start = round_down(offset, RK3588_NBYTES) / RK3588_NBYTES; addr_end = round_up(offset + bytes, RK3588_NBYTES) / RK3588_NBYTES; addr_len = addr_end - addr_start; - addr_start += RK3588_NO_SECURE_OFFSET; + addr_start += otp->data->read_offset / RK3588_NBYTES; buf = kzalloc(array_size(addr_len, RK3588_NBYTES), GFP_KERNEL); if (!buf) @@ -255,6 +255,8 @@ static int rockchip_otp_read(void *context, unsigned int offset, static struct nvmem_config otp_config = { .name = "rockchip-otp", .owner = THIS_MODULE, + .add_legacy_fixed_of_cells = true, + .type = NVMEM_TYPE_OTP, .read_only = true, .stride = 1, .word_size = 1, @@ -272,12 +274,21 @@ static const struct rockchip_data px30_data = { .reg_read = px30_otp_read, }; +static const struct rockchip_data rk3576_data = { + .size = 0x100, + .read_offset = 0x700, + .clks = px30_otp_clocks, + .num_clks = ARRAY_SIZE(px30_otp_clocks), + .reg_read = rk3588_otp_read, +}; + static const char * const rk3588_otp_clocks[] = { "otp", "apb_pclk", "phy", "arb", }; static const struct rockchip_data rk3588_data = { .size = 0x400, + .read_offset = 0xc00, .clks = rk3588_otp_clocks, .num_clks = ARRAY_SIZE(rk3588_otp_clocks), .reg_read = rk3588_otp_read, @@ -293,6 +304,10 @@ static const struct of_device_id rockchip_otp_match[] = { .data = &px30_data, }, { + .compatible = "rockchip,rk3576-otp", + .data = &rk3576_data, + }, + { .compatible = "rockchip,rk3588-otp", .data = &rk3588_data, }, diff --git a/drivers/nvmem/sc27xx-efuse.c b/drivers/nvmem/sc27xx-efuse.c index bff27011f4ff..4e2ffefac96c 100644 --- a/drivers/nvmem/sc27xx-efuse.c +++ b/drivers/nvmem/sc27xx-efuse.c @@ -262,6 +262,7 @@ static const struct of_device_id sc27xx_efuse_of_match[] = { { .compatible = "sprd,sc2730-efuse", .data = &sc2730_edata}, { } }; +MODULE_DEVICE_TABLE(of, sc27xx_efuse_of_match); static struct platform_driver sc27xx_efuse_driver = { .probe = sc27xx_efuse_probe, diff --git a/drivers/nvmem/sprd-efuse.c b/drivers/nvmem/sprd-efuse.c index bb3105f3291f..1a7e4e5d8b86 100644 --- a/drivers/nvmem/sprd-efuse.c +++ b/drivers/nvmem/sprd-efuse.c @@ -426,6 +426,7 @@ static const struct of_device_id sprd_efuse_of_match[] = { { .compatible = "sprd,ums312-efuse", .data = &ums312_data }, { } }; +MODULE_DEVICE_TABLE(of, sprd_efuse_of_match); static struct platform_driver sprd_efuse_driver = { .probe = sprd_efuse_probe, diff --git a/drivers/nvmem/sunplus-ocotp.c b/drivers/nvmem/sunplus-ocotp.c index 38f5d9df39cd..30d55b111a64 100644 --- a/drivers/nvmem/sunplus-ocotp.c +++ b/drivers/nvmem/sunplus-ocotp.c @@ -159,7 +159,6 @@ static int sp_ocotp_probe(struct platform_device *pdev) struct device *dev = &pdev->dev; struct nvmem_device *nvmem; struct sp_ocotp_priv *otp; - struct resource *res; int ret; otp = devm_kzalloc(dev, sizeof(*otp), GFP_KERNEL); @@ -168,13 +167,11 @@ static int sp_ocotp_probe(struct platform_device *pdev) otp->dev = dev; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hb_gpio"); - otp->base[HB_GPIO] = devm_ioremap_resource(dev, res); + otp->base[HB_GPIO] = devm_platform_ioremap_resource_byname(pdev, "hb_gpio"); if (IS_ERR(otp->base[HB_GPIO])) return PTR_ERR(otp->base[HB_GPIO]); - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "otprx"); - otp->base[OTPRX] = devm_ioremap_resource(dev, res); + otp->base[OTPRX] = devm_platform_ioremap_resource_byname(pdev, "otprx"); if (IS_ERR(otp->base[OTPRX])) return PTR_ERR(otp->base[OTPRX]); diff --git a/drivers/nvmem/u-boot-env.c b/drivers/nvmem/u-boot-env.c index befbab156cda..ced414fc9e60 100644 --- a/drivers/nvmem/u-boot-env.c +++ b/drivers/nvmem/u-boot-env.c @@ -3,23 +3,15 @@ * Copyright (C) 2022 Rafał Miłecki <rafal@milecki.pl> */ -#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> -#include <linux/nvmem-consumer.h> #include <linux/nvmem-provider.h> #include <linux/of.h> #include <linux/platform_device.h> #include <linux/slab.h> -enum u_boot_env_format { - U_BOOT_FORMAT_SINGLE, - U_BOOT_FORMAT_REDUNDANT, - U_BOOT_FORMAT_BROADCOM, -}; +#include "layouts/u-boot-env.h" struct u_boot_env { struct device *dev; @@ -29,24 +21,6 @@ struct u_boot_env { struct mtd_info *mtd; }; -struct u_boot_env_image_single { - __le32 crc32; - uint8_t data[]; -} __packed; - -struct u_boot_env_image_redundant { - __le32 crc32; - u8 mark; - uint8_t data[]; -} __packed; - -struct u_boot_env_image_broadcom { - __le32 magic; - __le32 len; - __le32 crc32; - DECLARE_FLEX_ARRAY(uint8_t, data); -} __packed; - static int u_boot_env_read(void *context, unsigned int offset, void *val, size_t bytes) { @@ -69,134 +43,6 @@ 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) -{ - struct nvmem_device *nvmem = priv->nvmem; - struct device *dev = priv->dev; - char *data = buf + data_offset; - char *var, *value, *eq; - - for (var = data; - var < data + data_len && *var; - var = value + strlen(value) + 1) { - struct nvmem_cell_info info = {}; - - eq = strchr(var, '='); - if (!eq) - break; - *eq = '\0'; - value = eq + 1; - - info.name = devm_kstrdup(dev, var, GFP_KERNEL); - if (!info.name) - return -ENOMEM; - info.offset = data_offset + value - data; - info.bytes = strlen(value); - info.np = of_get_child_by_name(dev->of_node, info.name); - if (!strcmp(var, "ethaddr")) { - info.raw_len = strlen(value); - info.bytes = ETH_ALEN; - info.read_post_process = u_boot_env_read_post_process_ethaddr; - } - - nvmem_add_one_cell(nvmem, &info); - } - - return 0; -} - -static int u_boot_env_parse(struct u_boot_env *priv) -{ - struct nvmem_device *nvmem = priv->nvmem; - struct device *dev = priv->dev; - size_t crc32_data_offset; - size_t crc32_data_len; - size_t crc32_offset; - __le32 *crc32_addr; - size_t data_offset; - size_t data_len; - size_t dev_size; - uint32_t crc32; - uint32_t calc; - uint8_t *buf; - int bytes; - int err; - - dev_size = nvmem_dev_size(nvmem); - - buf = kzalloc(dev_size, GFP_KERNEL); - if (!buf) { - err = -ENOMEM; - goto err_out; - } - - bytes = nvmem_device_read(nvmem, 0, dev_size, buf); - if (bytes < 0) { - err = bytes; - goto err_kfree; - } else if (bytes != dev_size) { - err = -EIO; - goto err_kfree; - } - - switch (priv->format) { - case U_BOOT_FORMAT_SINGLE: - crc32_offset = offsetof(struct u_boot_env_image_single, crc32); - crc32_data_offset = offsetof(struct u_boot_env_image_single, data); - data_offset = offsetof(struct u_boot_env_image_single, data); - break; - case U_BOOT_FORMAT_REDUNDANT: - crc32_offset = offsetof(struct u_boot_env_image_redundant, crc32); - crc32_data_offset = offsetof(struct u_boot_env_image_redundant, data); - data_offset = offsetof(struct u_boot_env_image_redundant, data); - break; - case U_BOOT_FORMAT_BROADCOM: - crc32_offset = offsetof(struct u_boot_env_image_broadcom, crc32); - crc32_data_offset = offsetof(struct u_boot_env_image_broadcom, data); - data_offset = offsetof(struct u_boot_env_image_broadcom, data); - break; - } - crc32_addr = (__le32 *)(buf + crc32_offset); - crc32 = le32_to_cpu(*crc32_addr); - crc32_data_len = dev_size - crc32_data_offset; - data_len = dev_size - data_offset; - - calc = crc32(~0, buf + crc32_data_offset, crc32_data_len) ^ ~0L; - if (calc != crc32) { - dev_err(dev, "Invalid calculated CRC32: 0x%08x (expected: 0x%08x)\n", calc, crc32); - err = -EINVAL; - goto err_kfree; - } - - buf[dev_size - 1] = '\0'; - err = u_boot_env_add_cells(priv, buf, data_offset, data_len); - -err_kfree: - kfree(buf); -err_out: - return err; -} - static int u_boot_env_probe(struct platform_device *pdev) { struct nvmem_config config = { @@ -228,7 +74,7 @@ static int u_boot_env_probe(struct platform_device *pdev) if (IS_ERR(priv->nvmem)) return PTR_ERR(priv->nvmem); - return u_boot_env_parse(priv); + return u_boot_env_parse(dev, priv->nvmem, priv->format); } static const struct of_device_id u_boot_env_of_match_table[] = { @@ -249,5 +95,6 @@ static struct platform_driver u_boot_env_driver = { module_platform_driver(u_boot_env_driver); MODULE_AUTHOR("Rafał Miłecki"); +MODULE_DESCRIPTION("U-Boot environment variables support module"); MODULE_LICENSE("GPL"); MODULE_DEVICE_TABLE(of, u_boot_env_of_match_table); diff --git a/drivers/nvmem/zynqmp_nvmem.c b/drivers/nvmem/zynqmp_nvmem.c index 7f15aa89a9d0..7da717d6c7fa 100644 --- a/drivers/nvmem/zynqmp_nvmem.c +++ b/drivers/nvmem/zynqmp_nvmem.c @@ -1,8 +1,10 @@ // SPDX-License-Identifier: GPL-2.0+ /* * Copyright (C) 2019 Xilinx, Inc. + * Copyright (C) 2022 - 2023, Advanced Micro Devices, Inc. */ +#include <linux/dma-mapping.h> #include <linux/module.h> #include <linux/nvmem-provider.h> #include <linux/of.h> @@ -10,36 +12,190 @@ #include <linux/firmware/xlnx-zynqmp.h> #define SILICON_REVISION_MASK 0xF +#define P_USER_0_64_UPPER_MASK GENMASK(31, 16) +#define P_USER_127_LOWER_4_BIT_MASK GENMASK(3, 0) +#define WORD_INBYTES 4 +#define SOC_VER_SIZE 0x4 +#define EFUSE_MEMORY_SIZE 0x177 +#define UNUSED_SPACE 0x8 +#define ZYNQMP_NVMEM_SIZE (SOC_VER_SIZE + UNUSED_SPACE + \ + EFUSE_MEMORY_SIZE) +#define SOC_VERSION_OFFSET 0x0 +#define EFUSE_START_OFFSET 0xC +#define EFUSE_END_OFFSET 0xFC +#define EFUSE_PUF_START_OFFSET 0x100 +#define EFUSE_PUF_MID_OFFSET 0x140 +#define EFUSE_PUF_END_OFFSET 0x17F +#define EFUSE_NOT_ENABLED 29 -struct zynqmp_nvmem_data { - struct device *dev; - struct nvmem_device *nvmem; +/* + * efuse access type + */ +enum efuse_access { + EFUSE_READ = 0, + EFUSE_WRITE +}; + +/** + * struct xilinx_efuse - the basic structure + * @src: address of the buffer to store the data to be write/read + * @size: read/write word count + * @offset: read/write offset + * @flag: 0 - represents efuse read and 1- represents efuse write + * @pufuserfuse:0 - represents non-puf efuses, offset is used for read/write + * 1 - represents puf user fuse row number. + * + * this structure stores all the required details to + * read/write efuse memory. + */ +struct xilinx_efuse { + u64 src; + u32 size; + u32 offset; + enum efuse_access flag; + u32 pufuserfuse; }; -static int zynqmp_nvmem_read(void *context, unsigned int offset, - void *val, size_t bytes) +static int zynqmp_efuse_access(void *context, unsigned int offset, + void *val, size_t bytes, enum efuse_access flag, + unsigned int pufflag) { + struct device *dev = context; + struct xilinx_efuse *efuse; + dma_addr_t dma_addr; + dma_addr_t dma_buf; + size_t words = bytes / WORD_INBYTES; int ret; - int idcode, version; - struct zynqmp_nvmem_data *priv = context; - - ret = zynqmp_pm_get_chipid(&idcode, &version); - if (ret < 0) - return ret; + int value; + char *data; + + if (bytes % WORD_INBYTES != 0) { + dev_err(dev, "Bytes requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 0 && offset % WORD_INBYTES) { + dev_err(dev, "Offset requested should be word aligned\n"); + return -EOPNOTSUPP; + } + + if (pufflag == 1 && flag == EFUSE_WRITE) { + memcpy(&value, val, bytes); + if ((offset == EFUSE_PUF_START_OFFSET || + offset == EFUSE_PUF_MID_OFFSET) && + value & P_USER_0_64_UPPER_MASK) { + dev_err(dev, "Only lower 4 bytes are allowed to be programmed in P_USER_0 & P_USER_64\n"); + return -EOPNOTSUPP; + } + + if (offset == EFUSE_PUF_END_OFFSET && + (value & P_USER_127_LOWER_4_BIT_MASK)) { + dev_err(dev, "Only MSB 28 bits are allowed to be programmed for P_USER_127\n"); + return -EOPNOTSUPP; + } + } + + efuse = dma_alloc_coherent(dev, sizeof(struct xilinx_efuse), + &dma_addr, GFP_KERNEL); + if (!efuse) + return -ENOMEM; - dev_dbg(priv->dev, "Read chipid val %x %x\n", idcode, version); - *(int *)val = version & SILICON_REVISION_MASK; + data = dma_alloc_coherent(dev, sizeof(bytes), + &dma_buf, GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto efuse_data_fail; + } + + if (flag == EFUSE_WRITE) { + memcpy(data, val, bytes); + efuse->flag = EFUSE_WRITE; + } else { + efuse->flag = EFUSE_READ; + } + + efuse->src = dma_buf; + efuse->size = words; + efuse->offset = offset; + efuse->pufuserfuse = pufflag; + + zynqmp_pm_efuse_access(dma_addr, (u32 *)&ret); + if (ret != 0) { + if (ret == EFUSE_NOT_ENABLED) { + dev_err(dev, "efuse access is not enabled\n"); + ret = -EOPNOTSUPP; + } else { + dev_err(dev, "Error in efuse read %x\n", ret); + ret = -EPERM; + } + goto efuse_access_err; + } + + if (flag == EFUSE_READ) + memcpy(val, data, bytes); +efuse_access_err: + dma_free_coherent(dev, sizeof(bytes), + data, dma_buf); +efuse_data_fail: + dma_free_coherent(dev, sizeof(struct xilinx_efuse), + efuse, dma_addr); + + return ret; +} - return 0; +static int zynqmp_nvmem_read(void *context, unsigned int offset, void *val, size_t bytes) +{ + struct device *dev = context; + int ret; + int pufflag = 0; + int idcode; + int version; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + switch (offset) { + /* Soc version offset is zero */ + case SOC_VERSION_OFFSET: + if (bytes != SOC_VER_SIZE) + return -EOPNOTSUPP; + + ret = zynqmp_pm_get_chipid((u32 *)&idcode, (u32 *)&version); + if (ret < 0) + return ret; + + dev_dbg(dev, "Read chipid val %x %x\n", idcode, version); + *(int *)val = version & SILICON_REVISION_MASK; + break; + /* Efuse offset starts from 0xc */ + case EFUSE_START_OFFSET ... EFUSE_END_OFFSET: + case EFUSE_PUF_START_OFFSET ... EFUSE_PUF_END_OFFSET: + ret = zynqmp_efuse_access(context, offset, val, + bytes, EFUSE_READ, pufflag); + break; + default: + *(u32 *)val = 0xDEADBEEF; + ret = 0; + break; + } + + return ret; } -static struct nvmem_config econfig = { - .name = "zynqmp-nvmem", - .owner = THIS_MODULE, - .word_size = 1, - .size = 1, - .read_only = true, -}; +static int zynqmp_nvmem_write(void *context, + unsigned int offset, void *val, size_t bytes) +{ + int pufflag = 0; + + if (offset < EFUSE_START_OFFSET || offset > EFUSE_PUF_END_OFFSET) + return -EOPNOTSUPP; + + if (offset >= EFUSE_PUF_START_OFFSET && offset <= EFUSE_PUF_END_OFFSET) + pufflag = 1; + + return zynqmp_efuse_access(context, offset, + val, bytes, EFUSE_WRITE, pufflag); +} static const struct of_device_id zynqmp_nvmem_match[] = { { .compatible = "xlnx,zynqmp-nvmem-fw", }, @@ -50,21 +206,19 @@ MODULE_DEVICE_TABLE(of, zynqmp_nvmem_match); static int zynqmp_nvmem_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; - struct zynqmp_nvmem_data *priv; - - priv = devm_kzalloc(dev, sizeof(struct zynqmp_nvmem_data), GFP_KERNEL); - if (!priv) - return -ENOMEM; + struct nvmem_config econfig = {}; - priv->dev = dev; + econfig.name = "zynqmp-nvmem"; + econfig.owner = THIS_MODULE; + econfig.word_size = 1; + econfig.size = ZYNQMP_NVMEM_SIZE; econfig.dev = dev; + econfig.priv = dev; econfig.add_legacy_fixed_of_cells = true; econfig.reg_read = zynqmp_nvmem_read; - econfig.priv = priv; - - priv->nvmem = devm_nvmem_register(dev, &econfig); + econfig.reg_write = zynqmp_nvmem_write; - return PTR_ERR_OR_ZERO(priv->nvmem); + return PTR_ERR_OR_ZERO(devm_nvmem_register(dev, &econfig)); } static struct platform_driver zynqmp_nvmem_driver = { |