summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSrinivas Kandagatla <srini@kernel.org>2026-09-25 11:53:41 +0100
committerSrinivas Kandagatla <srini@kernel.org>2026-09-25 11:53:41 +0100
commit41f42ff4ae134eba8dbe3424cf2ca1824c652edd (patch)
treeede4855c911ba5a0ac4ca441155b7a09e8294a04
parent841ea6e56cc88ec2c28826d62fbab88b234524c5 (diff)
parentd30804cbeea7ebb6277badc8d6210af5383ac1a4 (diff)
downloadlinux-next-41f42ff4ae134eba8dbe3424cf2ca1824c652edd.tar.gz
linux-next-41f42ff4ae134eba8dbe3424cf2ca1824c652edd.zip
Merge branches 'nvmem-fixes' and 'nvmem-for-v7.4' into nvmem-for-next
* nvmem-fixes: nvmem: core: Fix OOB read for bit offsets of more than one byte nvmem: layouts: onie-tlv: fix device_node reference leak in error path nvmem: layouts: sl28vpd: fix device_node reference leak in error path nvmem: rockchip-otp: Serialize reads * nvmem-for-v7.4: nvmem: protect nvmem_device::ops with SRCU nvmem: remove duplicated reference counting nvmem: add mt6323 PMIC EFUSE driver dt-bindings: mfd: mediatek: mt6397: add mt6323 PMIC EFUSE nvmem: layouts: Support fixed-layout as the nvmem device node itself nvmem: core: Add const to pattrs allocation type nvmem: uniphier-efuse: Enable for K3 SoCs dt-bindings: nvmem: uniphier-efuse: Add ti,am62p-efuse compatible dt-bindings: nvmem: qfprom: Add compatible for Qualcomm Maili
-rw-r--r--Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml21
-rw-r--r--Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml1
-rw-r--r--Documentation/devicetree/bindings/nvmem/socionext,uniphier-efuse.yaml6
-rw-r--r--MAINTAINERS5
-rw-r--r--drivers/nvmem/Kconfig14
-rw-r--r--drivers/nvmem/Makefile2
-rw-r--r--drivers/nvmem/core.c122
-rw-r--r--drivers/nvmem/internals.h5
-rw-r--r--drivers/nvmem/layouts.c13
-rw-r--r--drivers/nvmem/mt6323-efuse.c83
10 files changed, 216 insertions, 56 deletions
diff --git a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
index 3cbc0dc12c31..d2de227ae7a6 100644
--- a/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
+++ b/Documentation/devicetree/bindings/mfd/mediatek,mt6397.yaml
@@ -159,6 +159,23 @@ properties:
required:
- compatible
+ efuse:
+ type: object
+ unevaluatedProperties: false
+ description:
+ The efuse is responsible for storing calibration data, such as thermal
+ sensor calibration.
+
+ properties:
+ compatible:
+ const: mediatek,mt6323-efuse
+
+ nvmem-layout:
+ $ref: /schemas/nvmem/layouts/nvmem-layout.yaml#
+
+ required:
+ - compatible
+
leds:
type: object
additionalProperties: false
@@ -307,6 +324,10 @@ examples:
interrupt-controller;
#interrupt-cells = <2>;
+ efuse {
+ compatible = "mediatek,mt6323-efuse";
+ };
+
leds {
compatible = "mediatek,mt6323-led";
#address-cells = <1>;
diff --git a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml
index bc0a0898b9e4..566304ed4c78 100644
--- a/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml
+++ b/Documentation/devicetree/bindings/nvmem/qcom,qfprom.yaml
@@ -31,6 +31,7 @@ properties:
- qcom,ipq9574-qfprom
- qcom,ipq9650-qfprom
- qcom,kaanapali-qfprom
+ - qcom,maili-qfprom
- qcom,milos-qfprom
- qcom,msm8226-qfprom
- qcom,msm8916-qfprom
diff --git a/Documentation/devicetree/bindings/nvmem/socionext,uniphier-efuse.yaml b/Documentation/devicetree/bindings/nvmem/socionext,uniphier-efuse.yaml
index e27cbae2d63a..d26b3d2a3eba 100644
--- a/Documentation/devicetree/bindings/nvmem/socionext,uniphier-efuse.yaml
+++ b/Documentation/devicetree/bindings/nvmem/socionext,uniphier-efuse.yaml
@@ -16,7 +16,11 @@ allOf:
properties:
compatible:
- const: socionext,uniphier-efuse
+ oneOf:
+ - const: socionext,uniphier-efuse
+ - items:
+ - const: ti,am62p-efuse
+ - const: socionext,uniphier-efuse
reg:
maxItems: 1
diff --git a/MAINTAINERS b/MAINTAINERS
index 3a19da74d00c..6ec0ed61260c 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16718,6 +16718,11 @@ S: Maintained
F: drivers/iio/adc/mt6323-auxadc.c
F: include/dt-bindings/iio/adc/mediatek,mt6323-auxadc.h
+MEDIATEK MT6323 PMIC NVMEM DRIVER
+M: Roman Vivchar <rva333@protonmail.com>
+S: Maintained
+F: drivers/nvmem/mt6323-efuse.c
+
MEDIATEK MT6735 CLOCK & RESET DRIVERS
M: Yassine Oudjana <y.oudjana@protonmail.com>
L: linux-clk@vger.kernel.org
diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig
index c36c2a4c2a0b..083a5e528960 100644
--- a/drivers/nvmem/Kconfig
+++ b/drivers/nvmem/Kconfig
@@ -328,6 +328,17 @@ config NVMEM_MTK_EFUSE
This driver can also be built as a module. If so, the module
will be called efuse-mtk.
+config NVMEM_MT6323_EFUSE
+ tristate "MediaTek MT6323 PMIC EFUSE support"
+ depends on ARCH_MEDIATEK || COMPILE_TEST
+ depends on MFD_MT6397
+ help
+ This is a driver to access hardware related data like sensor
+ calibration, etc.
+
+ This driver can also be built as a module. If so, the module
+ will be called efuse-mt6323.
+
config NVMEM_MXS_OCOTP
tristate "Freescale MXS On-Chip OTP Memory Support"
depends on ARCH_MXS || COMPILE_TEST
@@ -541,8 +552,9 @@ config NVMEM_U_BOOT_ENV
config NVMEM_UNIPHIER_EFUSE
tristate "UniPhier SoCs eFuse support"
- depends on ARCH_UNIPHIER || COMPILE_TEST
+ depends on ARCH_UNIPHIER || ARCH_K3 || COMPILE_TEST
depends on HAS_IOMEM
+ default y if ARCH_K3
help
This is a simple driver to dump specified values of UniPhier SoC
from eFuse.
diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile
index 2bbfb9ff1885..5e70bad9f0f9 100644
--- a/drivers/nvmem/Makefile
+++ b/drivers/nvmem/Makefile
@@ -53,6 +53,8 @@ obj-$(CONFIG_NVMEM_MESON_MX_EFUSE) += nvmem_meson_mx_efuse.o
nvmem_meson_mx_efuse-y := meson-mx-efuse.o
obj-$(CONFIG_NVMEM_MICROCHIP_OTPC) += nvmem-microchip-otpc.o
nvmem-microchip-otpc-y := microchip-otpc.o
+obj-$(CONFIG_NVMEM_MT6323_EFUSE) += nvmem_mt6323-efuse.o
+nvmem_mt6323-efuse-y := mt6323-efuse.o
obj-$(CONFIG_NVMEM_MTK_EFUSE) += nvmem_mtk-efuse.o
nvmem_mtk-efuse-y := mtk-efuse.o
obj-$(CONFIG_NVMEM_MXS_OCOTP) += nvmem-mxs-ocotp.o
diff --git a/drivers/nvmem/core.c b/drivers/nvmem/core.c
index b4e5069d9e38..7d4cce8e2742 100644
--- a/drivers/nvmem/core.c
+++ b/drivers/nvmem/core.c
@@ -57,7 +57,12 @@ static BLOCKING_NOTIFIER_HEAD(nvmem_notifier);
static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops;
+
+ guard(srcu)(&nvmem->srcu);
+ ops = srcu_dereference(nvmem->ops, &nvmem->srcu);
+ if (!ops)
+ return -ENODEV;
if (!ops->reg_read)
return -EOPNOTSUPP;
@@ -68,9 +73,14 @@ static int __nvmem_reg_read(struct nvmem_device *nvmem, unsigned int offset,
static int __nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
void *val, size_t bytes)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops;
int ret, wr_ok;
+ guard(srcu)(&nvmem->srcu);
+ ops = srcu_dereference(nvmem->ops, &nvmem->srcu);
+ if (!ops)
+ return -ENODEV;
+
if (!ops->reg_write)
return -EOPNOTSUPP;
@@ -289,7 +299,7 @@ static ssize_t bin_attr_nvmem_write(struct file *filp, struct kobject *kobj,
static umode_t nvmem_bin_attr_get_umode(struct nvmem_device *nvmem)
{
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops);
umode_t mode = 0400;
@@ -333,7 +343,7 @@ static umode_t nvmem_attr_is_visible(struct kobject *kobj,
{
struct device *dev = kobj_to_dev(kobj);
struct nvmem_device *nvmem = to_nvmem_device(dev);
- struct nvmem_operations *ops = nvmem->ops;
+ struct nvmem_operations *ops = rcu_dereference_raw(nvmem->ops);
/*
* If the device has no .reg_write operation, do not allow
@@ -485,7 +495,7 @@ static int nvmem_populate_sysfs_cells(struct nvmem_device *nvmem)
/* Allocate an array of attributes with a sentinel */
ncells = list_count_nodes(&nvmem->cells);
pattrs = devm_kcalloc(&nvmem->dev, ncells + 1,
- sizeof(struct bin_attribute *), GFP_KERNEL);
+ sizeof(*pattrs), GFP_KERNEL);
if (!pattrs)
return -ENOMEM;
@@ -535,24 +545,6 @@ static void nvmem_sysfs_remove_compat(struct nvmem_device *nvmem)
#endif /* CONFIG_NVMEM_SYSFS */
-static void nvmem_release(struct device *dev)
-{
- struct nvmem_device *nvmem = to_nvmem_device(dev);
-
- ida_free(&nvmem_ida, nvmem->id);
- gpiod_put(nvmem->wp_gpio);
- kfree(nvmem->ops);
- kfree(nvmem);
-}
-
-static const struct device_type nvmem_provider_type = {
- .release = nvmem_release,
-};
-
-static const struct bus_type nvmem_bus_type = {
- .name = "nvmem",
-};
-
static void nvmem_cell_entry_drop(struct nvmem_cell_entry *cell)
{
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_CELL_REMOVE, cell);
@@ -571,6 +563,25 @@ static void nvmem_device_remove_all_cells(const struct nvmem_device *nvmem)
nvmem_cell_entry_drop(cell);
}
+static void nvmem_release(struct device *dev)
+{
+ struct nvmem_device *nvmem = to_nvmem_device(dev);
+
+ gpiod_put(nvmem->wp_gpio);
+ nvmem_device_remove_all_cells(nvmem);
+ ida_free(&nvmem_ida, nvmem->id);
+ cleanup_srcu_struct(&nvmem->srcu);
+ kfree(nvmem);
+}
+
+static const struct device_type nvmem_provider_type = {
+ .release = nvmem_release,
+};
+
+static const struct bus_type nvmem_bus_type = {
+ .name = "nvmem",
+};
+
static void nvmem_cell_entry_add(struct nvmem_cell_entry *cell)
{
scoped_guard(mutex, &nvmem_mutex)
@@ -918,7 +929,21 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
nvmem->dev.type = &nvmem_provider_type;
nvmem->dev.bus = &nvmem_bus_type;
nvmem->dev.parent = config->dev;
- nvmem->ops = ops;
+ INIT_LIST_HEAD(&nvmem->cells);
+
+ /*
+ * Must happen before we assign the release() callback in
+ * device_initialize().
+ */
+ rval = init_srcu_struct(&nvmem->srcu);
+ if (rval) {
+ ida_free(&nvmem_ida, nvmem->id);
+ kfree(ops);
+ kfree(nvmem);
+ return ERR_PTR(rval);
+ }
+
+ rcu_assign_pointer(nvmem->ops, ops);
device_initialize(&nvmem->dev);
@@ -931,8 +956,6 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
goto err_put_device;
}
- kref_init(&nvmem->refcnt);
- INIT_LIST_HEAD(&nvmem->cells);
nvmem->fixup_dt_cell_info = config->fixup_dt_cell_info;
ops->reg_read = config->reg_read;
@@ -993,20 +1016,20 @@ struct nvmem_device *nvmem_register(const struct nvmem_config *config)
if (config->cells) {
rval = nvmem_add_cells(nvmem, config->cells, config->ncells);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
}
if (config->add_legacy_fixed_of_cells) {
rval = nvmem_add_cells_from_legacy_of(nvmem);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
}
dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);
rval = device_add(&nvmem->dev);
if (rval)
- goto err_remove_cells;
+ goto err_remove_compat;
rval = nvmem_populate_layout(nvmem);
if (rval)
@@ -1032,40 +1055,40 @@ err_destroy_layout:
#endif
err_remove_dev:
device_del(&nvmem->dev);
-err_remove_cells:
- nvmem_device_remove_all_cells(nvmem);
+err_remove_compat:
nvmem_sysfs_remove_compat(nvmem);
err_put_device:
+ ops = rcu_replace_pointer(nvmem->ops, NULL, true);
+ synchronize_srcu(&nvmem->srcu);
put_device(&nvmem->dev);
+ kfree(ops);
return ERR_PTR(rval);
}
EXPORT_SYMBOL_GPL(nvmem_register);
-static void nvmem_device_release(struct kref *kref)
+/**
+ * nvmem_unregister() - Unregister previously registered nvmem device
+ *
+ * @nvmem: Pointer to previously registered nvmem device.
+ */
+void nvmem_unregister(struct nvmem_device *nvmem)
{
- struct nvmem_device *nvmem;
+ struct nvmem_operations *ops;
- nvmem = container_of(kref, struct nvmem_device, refcnt);
+ if (!nvmem)
+ return;
blocking_notifier_call_chain(&nvmem_notifier, NVMEM_REMOVE, nvmem);
- nvmem_sysfs_remove_compat(nvmem);
+ ops = rcu_replace_pointer(nvmem->ops, NULL, true);
+ synchronize_srcu(&nvmem->srcu);
- nvmem_device_remove_all_cells(nvmem);
+ nvmem_sysfs_remove_compat(nvmem);
nvmem_destroy_layout(nvmem);
- device_unregister(&nvmem->dev);
-}
+ kfree(ops);
-/**
- * nvmem_unregister() - Unregister previously registered nvmem device
- *
- * @nvmem: Pointer to previously registered nvmem device.
- */
-void nvmem_unregister(struct nvmem_device *nvmem)
-{
- if (nvmem)
- kref_put(&nvmem->refcnt, nvmem_device_release);
+ device_unregister(&nvmem->dev);
}
EXPORT_SYMBOL_GPL(nvmem_unregister);
@@ -1126,8 +1149,6 @@ static struct nvmem_device *nvmem_device_match(void *data,
return ERR_PTR(-EINVAL);
}
- kref_get(&nvmem->refcnt);
-
return nvmem;
}
@@ -1243,9 +1264,8 @@ EXPORT_SYMBOL_GPL(devm_nvmem_device_put);
*/
void nvmem_device_put(struct nvmem_device *nvmem)
{
- put_device(&nvmem->dev);
module_put(nvmem->owner);
- kref_put(&nvmem->refcnt, nvmem_device_release);
+ put_device(&nvmem->dev);
}
EXPORT_SYMBOL_GPL(nvmem_device_put);
diff --git a/drivers/nvmem/internals.h b/drivers/nvmem/internals.h
index 4e610deeaa7b..bc7a99f5aefb 100644
--- a/drivers/nvmem/internals.h
+++ b/drivers/nvmem/internals.h
@@ -6,6 +6,7 @@
#include <linux/device.h>
#include <linux/nvmem-consumer.h>
#include <linux/nvmem-provider.h>
+#include <linux/srcu.h>
/* Hold pointers to callbacks owned by the nvmem provider module. */
struct nvmem_operations {
@@ -16,10 +17,10 @@ struct nvmem_operations {
struct nvmem_device {
struct module *owner;
struct device dev;
+ struct srcu_struct srcu;
int stride;
int word_size;
int id;
- struct kref refcnt;
size_t size;
bool read_only;
bool root_only;
@@ -34,7 +35,7 @@ struct nvmem_device {
unsigned int nkeepout;
struct gpio_desc *wp_gpio;
struct nvmem_layout *layout;
- struct nvmem_operations *ops;
+ struct nvmem_operations __rcu *ops;
void *priv;
bool sysfs_cells_populated;
};
diff --git a/drivers/nvmem/layouts.c b/drivers/nvmem/layouts.c
index 07a34be9669c..409909e3244f 100644
--- a/drivers/nvmem/layouts.c
+++ b/drivers/nvmem/layouts.c
@@ -156,7 +156,18 @@ static int nvmem_layout_bus_populate(struct nvmem_device *nvmem,
struct device_node *of_nvmem_layout_get_container(struct nvmem_device *nvmem)
{
- return of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+ struct device_node *np;
+
+ /* Search for nvmem-layout child */
+ np = of_get_child_by_name(nvmem->dev.of_node, "nvmem-layout");
+ if (np)
+ return np;
+
+ /* The nvmem of_node is itself a fixed-layout node */
+ if (of_device_is_compatible(nvmem->dev.of_node, "fixed-layout"))
+ return of_node_get(nvmem->dev.of_node);
+
+ return NULL;
}
EXPORT_SYMBOL_GPL(of_nvmem_layout_get_container);
diff --git a/drivers/nvmem/mt6323-efuse.c b/drivers/nvmem/mt6323-efuse.c
new file mode 100644
index 000000000000..de5e30215c22
--- /dev/null
+++ b/drivers/nvmem/mt6323-efuse.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) 2026 Roman Vivchar <rva333@protonmail.com>
+ */
+
+#include <linux/err.h>
+#include <linux/mfd/mt6397/core.h>
+#include <linux/module.h>
+#include <linux/nvmem-provider.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+
+#include <linux/mfd/mt6323/registers.h>
+
+#define MT6323_EFUSE_DOUT_BASE MT6323_EFUSE_DOUT_0_15
+#define MT6323_EFUSE_SIZE 24
+
+static int mt6323_efuse_read(void *context, unsigned int offset, void *val,
+ size_t bytes)
+{
+ struct regmap *map = context;
+ u16 *buf = val;
+ u32 tmp;
+ int ret;
+
+ /*
+ * A manual loop using regmap_read is required because PWRAP is not
+ * a continuous MMIO space, but rather a FSM that doesn't implement the
+ * necessary read callback for the regmap_read_raw and regmap_read_bulk
+ * functions.
+ */
+ for (size_t i = 0; i < bytes; i += sizeof(*buf)) {
+ ret = regmap_read(map, MT6323_EFUSE_DOUT_BASE + offset + i, &tmp);
+ if (ret)
+ return ret;
+
+ *buf++ = tmp;
+ }
+
+ return 0;
+}
+
+static int mt6323_efuse_probe(struct platform_device *pdev)
+{
+ struct mt6397_chip *mt6323 = dev_get_drvdata(pdev->dev.parent);
+ struct device *dev = &pdev->dev;
+ struct nvmem_config config = {
+ .name = "mt6323-efuse",
+ .stride = 2,
+ .word_size = 2,
+ .size = MT6323_EFUSE_SIZE,
+ .reg_read = mt6323_efuse_read,
+ };
+ struct nvmem_device *nvmem;
+
+ if (!mt6323 || !mt6323->regmap)
+ return dev_err_probe(dev, -ENODEV, "failed to get parent\n");
+
+ config.dev = dev;
+ config.priv = mt6323->regmap;
+
+ nvmem = devm_nvmem_register(dev, &config);
+ return PTR_ERR_OR_ZERO(nvmem);
+}
+
+static const struct of_device_id mt6323_efuse_of_match[] = {
+ { .compatible = "mediatek,mt6323-efuse" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, mt6323_efuse_of_match);
+
+static struct platform_driver mt6323_efuse_driver = {
+ .probe = mt6323_efuse_probe,
+ .driver = {
+ .name = "mt6323-efuse",
+ .of_match_table = mt6323_efuse_of_match,
+ },
+};
+module_platform_driver(mt6323_efuse_driver);
+
+MODULE_DESCRIPTION("MediaTek MT6323 PMIC EFUSE driver");
+MODULE_LICENSE("GPL");