[PATCH v3 08/13] misc: Add support for nvmem cells
Sean Anderson
sean.anderson at seco.com
Fri Apr 29 21:40:27 CEST 2022
Hi Simon,
On 4/25/22 11:24 AM, Sean Anderson wrote:
>
>
> On 4/25/22 1:48 AM, Simon Glass wrote:
>> Hi Sean,
>>
>> On Mon, 18 Apr 2022 at 13:37, Sean Anderson <sean.anderson at seco.com> wrote:
>>>
>>> This adds support for "nvmem cells" as seen in Linux. The nvmem device
>>> class in Linux is used for various assorted ROMs and EEPROMs. In this
>>> sense, it is similar to UCLASS_MISC, but also includes
>>> UCLASS_I2C_EEPROM, UCLASS_RTC, and UCLASS_MTD. New drivers corresponding
>>> to a Linux-style nvmem device should be implemented as one of the
>>> previously-mentioned uclasses. The nvmem API acts as a compatibility
>>> layer to adapt the (slightly different) APIs of these uclasses. It also
>>> handles the lookup of nvmem cells.
>>>
>>> While nvmem devices can be accessed directly, they are most often used
>>> by reading/writing contiguous values called "cells". Cells typically
>>> hold information like calibration, versions, or configuration (such as
>>> mac addresses).
>>>
>>> nvmem devices can specify "cells" in their device tree:
>>>
>>> qfprom: eeprom at 700000 {
>>> #address-cells = <1>;
>>> #size-cells = <1>;
>>> reg = <0x00700000 0x100000>;
>>>
>>> /* ... */
>>>
>>> tsens_calibration: calib at 404 {
>>> reg = <0x404 0x10>;
>>> };
>>> };
>>>
>>> which can then be referenced like:
>>>
>>> tsens {
>>> /* ... */
>>> nvmem-cells = <&tsens_calibration>;
>>> nvmem-cell-names = "calibration";
>>> };
>>>
>>> The tsens driver could then read the calibration value like:
>>>
>>> struct nvmem_cell cal_cell;
>>> u8 cal[16];
>>> nvmem_cell_get_by_name(dev, "calibration", &cal_cell);
>>> nvmem_cell_read(&cal_cell, cal, sizeof(cal));
>>>
>>> Because nvmem devices are not all of the same uclass, supported uclasses
>>> must register a nvmem_interface struct. This allows CONFIG_NVMEM to be
>>> enabled without depending on specific uclasses. At the moment,
>>> nvmem_interface is very bare-bones, and assumes that no initialization
>>> is necessary. However, this could be amended in the future.
>>>
>>> Although I2C_EEPROM and MISC are quite similar (and could likely be
>>> unified), they present different read/write function signatures. To
>>> abstract over this, NVMEM uses the same read/write signature as Linux.
>>> In particular, short read/writes are not allowed, which is allowed by
>>> MISC.
>>>
>>> The functionality implemented by nvmem cells is very similar to that
>>> provided by i2c_eeprom_partition. "fixed-partition"s for eeproms does
>>> not seem to have made its way into Linux or into any device tree other
>>> than sandbox. It is possible that with the introduction of this API it
>>> would be possible to remove it.
>>
>> I still think this would be better as a separate uclass, with child
>> devices created at bind time in each of the respective uclasses, like
>> mmc_bind() does. Then you will see the nvmem devices in the DM tree.
>> Wouldn't we want to add a command to access the nvmem devices?
>
> We already do. E.g. the misc/rtc/eeprom commands. The problem is that
> for software to access them, they would have to use misc_read/dm_rtc_read/
> i2c_eeprom_read.
>
>> This patch feels like a shortcut to me and I'm not sure of the
>> benefit of that shortcut.
> Well, I suppose it's because "nvmem" devices are strict subsets of
> existing devices. There is no new functionality here (except adapting
> between semantics like for misc). We should always be able to use the
> existing API to implement support for a new underlying uclass. There
> should never be device-specific read/write methods, because we can
> use the existing read/write uclass methods.
>
> What I'm trying to get at is that we sort of already have an nvmem
> uclass with nvmem devices, they're just not accessible in a uniform
> way. This series is trying to address the uniformity aspect. But I
> don't think we need new devices for each nvmem interface, because
> all they would do would take up ram/rom.
>
> --Sean
>
> PS. In an ideal world we'd have something like
>
> struct nvmem_ops {
> read();
> write();
> };
>
> struct dm_rtc_ops {
> nvmem_ops nvmem;
> /* the other ops minus read/write */
> };
>
> int nvmem_read (...) {
> struct nvmem_ops *ops = cell->nvmem->ops;
> /* ... */
>
> return ops->read(...);
> }
>
> but unfortunately, we already have fragmented implementations.
>
To follow up on this, I've conducted some size experiments. The
following is the bloat caused by applying the current series on
sandbox64_defconfig:
add/remove: 8/0 grow/shrink: 7/2 up/down: 1069/-170 (899)
Function old new delta
nvmem_cell_get_by_index - 216 +216
dm_test_ethaddr - 192 +192
nvmem_cell_write - 125 +125
nvmem_cell_read - 125 +125
nvmem_cell_get_by_name - 65 +65
addr - 64 +64
sandbox_i2c_rtc_probe - 54 +54
sb_eth_write_hwaddr 14 57 +43
sandbox_i2c_eeprom_probe 70 112 +42
misc_sandbox_probe 21 61 +40
eth_post_probe 444 484 +40
_u_boot_list_2_ut_dm_test_2_dm_test_ethaddr - 32 +32
__func__ 15147 15163 +16
data_gz 18327 18338 +11
dsa_pre_probe 181 185 +4
sb_eth_of_to_plat 126 64 -62
default_environment 553 445 -108
Total: Before=1765267, After=1766166, chg +0.05%
And here is the difference (from baseline) when using your
suggested approach:
add/remove: 26/0 grow/shrink: 8/2 up/down: 2030/-170 (1860)
Function old new delta
dm_test_ethaddr - 192 +192
nvmem_cell_get_by_index - 152 +152
nvmem_register - 137 +137
_u_boot_list_2_driver_2_rtc_nvmem - 128 +128
_u_boot_list_2_driver_2_misc_nvmem - 128 +128
_u_boot_list_2_driver_2_i2c_eeprom_nvmem - 128 +128
_u_boot_list_2_uclass_driver_2_nvmem - 120 +120
misc_nvmem_write - 68 +68
misc_nvmem_read - 68 +68
nvmem_cell_write - 66 +66
nvmem_cell_read - 65 +65
nvmem_cell_get_by_name - 65 +65
addr - 64 +64
sandbox_i2c_rtc_probe - 54 +54
rtc_post_bind - 48 +48
nvmem_rtc_write - 48 +48
nvmem_rtc_read - 48 +48
misc_post_bind - 48 +48
i2c_eeprom_nvmem_write - 48 +48
i2c_eeprom_nvmem_read - 48 +48
sb_eth_write_hwaddr 14 57 +43
sandbox_i2c_eeprom_probe 70 112 +42
misc_sandbox_probe 21 61 +40
eth_post_probe 444 484 +40
_u_boot_list_2_ut_dm_test_2_dm_test_ethaddr - 32 +32
rtc_nvmem_ops - 16 +16
misc_nvmem_ops - 16 +16
i2c_eeprom_post_bind - 16 +16
i2c_eeprom_nvmem_ops - 16 +16
__func__ 15147 15163 +16
data_gz 18327 18338 +11
fmt - 9 +9
version_string 68 74 +6
dsa_pre_probe 181 185 +4
sb_eth_of_to_plat 126 64 -62
default_environment 553 445 -108
Total: Before=1765267, After=1767127, chg +0.11%
As you can see, adding a second driver for each nvmem device
doubles the size of this feature. The patch I used for this follows
(it does not apply cleanly to v3 because the base contains some
changes fixing bugs pointed out by Tom).
>From 958bc25e3bbe78b0a861d1f54b277f63e826b830 Mon Sep 17 00:00:00 2001
From: Sean Anderson <sean.anderson at seco.com>
Date: Fri, 29 Apr 2022 15:33:38 -0400
Subject: [PATCH] misc: nvmem: Convert to using udevices
Instead of calling uclass methods directly, instead create some nvmem
devices solely for the purpose of holding nvmem ops. This is primarily
to illustrate the size difference between these approaches.
Signed-off-by: Sean Anderson <sean.anderson at seco.com>
---
drivers/misc/i2c_eeprom.c | 37 ++++++++++++++
drivers/misc/misc-uclass.c | 58 +++++++++++++++++++--
drivers/misc/nvmem.c | 100 ++++++++++++-------------------------
drivers/rtc/rtc-uclass.c | 46 +++++++++++++++--
include/dm/uclass-id.h | 1 +
include/nvmem.h | 96 +++++++++++++++++++++++------------
6 files changed, 233 insertions(+), 105 deletions(-)
diff --git a/drivers/misc/i2c_eeprom.c b/drivers/misc/i2c_eeprom.c
index 4302e180ac..3f6c7ebf4a 100644
--- a/drivers/misc/i2c_eeprom.c
+++ b/drivers/misc/i2c_eeprom.c
@@ -14,6 +14,7 @@
#include <dm/device-internal.h>
#include <i2c.h>
#include <i2c_eeprom.h>
+#include <nvmem.h>
struct i2c_eeprom_drv_data {
u32 size; /* size in bytes */
@@ -374,7 +375,43 @@ U_BOOT_DRIVER(i2c_eeprom_partition) = {
.ops = &i2c_eeprom_partition_ops,
};
+static int i2c_eeprom_nvmem_read(struct udevice *dev, unsigned int offset,
+ void *buf, size_t size)
+{
+ return i2c_eeprom_read(dev_get_parent(dev), offset, buf, size);
+}
+
+static int i2c_eeprom_nvmem_write(struct udevice *dev, unsigned int offset,
+ const void *buf, size_t size)
+{
+ return i2c_eeprom_write(dev_get_parent(dev), offset, buf, size);
+}
+
+static struct __maybe_unused nvmem_ops i2c_eeprom_nvmem_ops = {
+ .read = i2c_eeprom_nvmem_read,
+ .write = i2c_eeprom_nvmem_write,
+};
+
+#if CONFIG_IS_ENABLED(NVMEM)
+U_BOOT_DRIVER(i2c_eeprom_nvmem) = {
+ .name = "i2c_eeprom_nvmem",
+ .id = UCLASS_NVMEM,
+ .ops = &i2c_eeprom_nvmem_ops,
+ .flags = DM_FLAG_NAME_ALLOCED,
+};
+#endif
+
+#if CONFIG_IS_ENABLED(NVMEM)
+static int i2c_eeprom_post_bind(struct udevice *dev)
+{
+ return nvmem_register(dev, DM_DRIVER_GET(misc_nvmem));
+}
+#endif
+
UCLASS_DRIVER(i2c_eeprom) = {
.id = UCLASS_I2C_EEPROM,
.name = "i2c_eeprom",
+#if CONFIG_IS_ENABLED(NVMEM)
+ .post_bind = i2c_eeprom_post_bind,
+#endif
};
diff --git a/drivers/misc/misc-uclass.c b/drivers/misc/misc-uclass.c
index cfe9d562fa..e9b58d3abe 100644
--- a/drivers/misc/misc-uclass.c
+++ b/drivers/misc/misc-uclass.c
@@ -9,6 +9,7 @@
#include <dm.h>
#include <errno.h>
#include <misc.h>
+#include <nvmem.h>
/*
* Implement a miscellaneous uclass for those do not fit other more
@@ -67,10 +68,61 @@ int misc_set_enabled(struct udevice *dev, bool val)
return ops->set_enabled(dev, val);
}
+static int misc_nvmem_read(struct udevice *dev, unsigned int offset, void *buf,
+ size_t size)
+{
+ int ret = misc_read(dev_get_parent(dev), offset, buf, size);
+
+ if (ret < 0)
+ return ret;
+ if (ret != size)
+ return -EIO;
+ return 0;
+}
+
+static int misc_nvmem_write(struct udevice *dev, unsigned int offset,
+ const void *buf, size_t size)
+{
+ int ret = misc_write(dev_get_parent(dev), offset, buf, size);
+
+ if (ret < 0)
+ return ret;
+ if (ret != size)
+ return -EIO;
+ return 0;
+}
+
+static struct __maybe_unused nvmem_ops misc_nvmem_ops = {
+ .read = misc_nvmem_read,
+ .write = misc_nvmem_write,
+};
+
+#if CONFIG_IS_ENABLED(NVMEM)
+U_BOOT_DRIVER(misc_nvmem) = {
+ .name = "misc_nvmem",
+ .id = UCLASS_NVMEM,
+ .ops = &misc_nvmem_ops,
+ .flags = DM_FLAG_NAME_ALLOCED,
+};
+#endif
+
+static int misc_post_bind(struct udevice *dev)
+{
+#if CONFIG_IS_ENABLED(OF_REAL)
+ int ret = dm_scan_fdt_dev(dev);
+
+ if (ret)
+ return ret;
+#endif
+#if CONFIG_IS_ENABLED(NVMEM)
+ return nvmem_register(dev, DM_DRIVER_GET(misc_nvmem));
+#else
+ return 0;
+#endif
+}
+
UCLASS_DRIVER(misc) = {
.id = UCLASS_MISC,
.name = "misc",
-#if CONFIG_IS_ENABLED(OF_REAL)
- .post_bind = dm_scan_fdt_dev,
-#endif
+ .post_bind = misc_post_bind,
};
diff --git a/drivers/misc/nvmem.c b/drivers/misc/nvmem.c
index 5a2bd1f9f7..afd8c7b5ab 100644
--- a/drivers/misc/nvmem.c
+++ b/drivers/misc/nvmem.c
@@ -10,90 +10,48 @@
#include <nvmem.h>
#include <rtc.h>
#include <dm/device_compat.h>
+#include <dm/device-internal.h>
#include <dm/ofnode.h>
#include <dm/read.h>
#include <dm/uclass.h>
+int nvmem_register(struct udevice *dev, const struct driver* drv)
+{
+ char *name;
+ int name_size;
+ static const char fmt[] = "%s.nvmem";
+
+ assert(drv->flags & DM_FLAG_NAME_ALLOCED);
+
+ name_size = snprintf(NULL, 0, fmt, dev->name) + 1;
+ name = malloc(name_size);
+ if (!name)
+ return -ENOMEM;
+ snprintf(name, name_size, fmt, dev->name);
+
+ return device_bind(dev, drv, name, NULL, dev_ofnode(dev), NULL);
+}
+
int nvmem_cell_read(struct nvmem_cell *cell, void *buf, size_t size)
-{
+{
+ const struct nvmem_ops *ops = dev_get_driver_ops(cell->nvmem);
+
dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size);
if (size != cell->size)
return -EINVAL;
- switch (cell->nvmem->driver->id) {
- case UCLASS_I2C_EEPROM:
- return i2c_eeprom_read(cell->nvmem, cell->offset, buf, size);
- case UCLASS_MISC: {
- int ret = misc_read(cell->nvmem, cell->offset, buf, size);
-
- if (ret < 0)
- return ret;
- if (ret != size)
- return -EIO;
- return 0;
- }
- case UCLASS_RTC:
- return dm_rtc_read(cell->nvmem, cell->offset, buf, size);
- default:
- return -ENOSYS;
- }
+ return ops->read(cell->nvmem, cell->offset, buf, size);
}
int nvmem_cell_write(struct nvmem_cell *cell, const void *buf, size_t size)
{
+ const struct nvmem_ops *ops = dev_get_driver_ops(cell->nvmem);
+
dev_dbg(cell->nvmem, "%s: off=%u size=%zu\n", __func__, cell->offset, size);
if (size != cell->size)
return -EINVAL;
- switch (cell->nvmem->driver->id) {
- case UCLASS_I2C_EEPROM:
- return i2c_eeprom_write(cell->nvmem, cell->offset, buf, size);
- case UCLASS_MISC: {
- int ret = misc_write(cell->nvmem, cell->offset, buf, size);
-
- if (ret < 0)
- return ret;
- if (ret != size)
- return -EIO;
- return 0;
- }
- case UCLASS_RTC:
- return dm_rtc_write(cell->nvmem, cell->offset, buf, size);
- default:
- return -ENOSYS;
- }
-}
-
-/**
- * nvmem_get_device() - Get an nvmem device for a cell
- * @node: ofnode of the nvmem device
- * @cell: Cell to look up
- *
- * Try to find a nvmem-compatible device by going through the nvmem interfaces.
- *
- * Return:
- * * 0 on success
- * * -ENODEV if we didn't find anything
- * * A negative error if there was a problem looking up the device
- */
-static int nvmem_get_device(ofnode node, struct nvmem_cell *cell)
-{
- int i, ret;
- enum uclass_id ids[] = {
- UCLASS_I2C_EEPROM,
- UCLASS_MISC,
- UCLASS_RTC,
- };
-
- for (i = 0; i < ARRAY_SIZE(ids); i++) {
- ret = uclass_get_device_by_ofnode(ids[i], node, &cell->nvmem);
- if (!ret)
- return 0;
- if (ret != -ENODEV && ret != -EPFNOSUPPORT)
- return ret;
- }
-
- return -ENODEV;
+ return ops->write(cell->nvmem, cell->offset, buf, size);
}
int nvmem_cell_get_by_index(struct udevice *dev, int index,
@@ -111,7 +69,8 @@ int nvmem_cell_get_by_index(struct udevice *dev, int index,
if (ret)
return ret;
- ret = nvmem_get_device(ofnode_get_parent(args.node), cell);
+ ret = uclass_get_device_by_ofnode(UCLASS_NVMEM, ofnode_get_parent(args.node),
+ &cell->nvmem);
if (ret)
return ret;
@@ -140,3 +99,8 @@ int nvmem_cell_get_by_name(struct udevice *dev, const char *name,
return nvmem_cell_get_by_index(dev, index, cell);
}
+
+UCLASS_DRIVER(nvmem) = {
+ .id = UCLASS_NVMEM,
+ .name = "nvmem",
+};
diff --git a/drivers/rtc/rtc-uclass.c b/drivers/rtc/rtc-uclass.c
index e5ae6ea4d5..bef2eefecf 100644
--- a/drivers/rtc/rtc-uclass.c
+++ b/drivers/rtc/rtc-uclass.c
@@ -10,6 +10,7 @@
#include <dm.h>
#include <errno.h>
#include <log.h>
+#include <nvmem.h>
#include <rtc.h>
int dm_rtc_get(struct udevice *dev, struct rtc_time *time)
@@ -173,11 +174,50 @@ int rtc_write32(struct udevice *dev, unsigned int reg, u32 value)
return 0;
}
+static int nvmem_rtc_read(struct udevice *dev, unsigned int offset, void *buf,
+ size_t size)
+{
+ return dm_rtc_read(dev_get_parent(dev), offset, buf, size);
+}
+
+static int nvmem_rtc_write(struct udevice *dev, unsigned int offset,
+ const void *buf, size_t size)
+{
+ return dm_rtc_write(dev_get_parent(dev), offset, buf, size);
+}
+
+static struct __maybe_unused nvmem_ops rtc_nvmem_ops = {
+ .read = nvmem_rtc_read,
+ .write = nvmem_rtc_write,
+};
+
+#if CONFIG_IS_ENABLED(NVMEM)
+U_BOOT_DRIVER(rtc_nvmem) = {
+ .name = "rtc_nvmem",
+ .id = UCLASS_NVMEM,
+ .ops = &rtc_nvmem_ops,
+ .flags = DM_FLAG_NAME_ALLOCED,
+};
+#endif
+
+static int rtc_post_bind(struct udevice *dev)
+{
+#if CONFIG_IS_ENABLED(OF_REAL)
+ int ret = dm_scan_fdt_dev(dev);
+
+ if (ret)
+ return ret;
+#endif
+#if CONFIG_IS_ENABLED(NVMEM)
+ return nvmem_register(dev, DM_DRIVER_GET(rtc_nvmem));
+#else
+ return 0;
+#endif
+}
+
UCLASS_DRIVER(rtc) = {
.name = "rtc",
.id = UCLASS_RTC,
.flags = DM_UC_FLAG_SEQ_ALIAS,
-#if CONFIG_IS_ENABLED(OF_REAL)
- .post_bind = dm_scan_fdt_dev,
-#endif
+ .post_bind = rtc_post_bind,
};
diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h
index 3ba69ad9a0..50877890b3 100644
--- a/include/dm/uclass-id.h
+++ b/include/dm/uclass-id.h
@@ -83,6 +83,7 @@ enum uclass_id {
UCLASS_NOP, /* No-op devices */
UCLASS_NORTHBRIDGE, /* Intel Northbridge / SDRAM controller */
UCLASS_NVME, /* NVM Express device */
+ UCLASS_NVMEM, /* Non-volatile memory devices */
UCLASS_P2SB, /* (x86) Primary-to-Sideband Bus */
UCLASS_PANEL, /* Display panel, such as an LCD */
UCLASS_PANEL_BACKLIGHT, /* Backlight controller for panel */
diff --git a/include/nvmem.h b/include/nvmem.h
index 2751713a68..230841499d 100644
--- a/include/nvmem.h
+++ b/include/nvmem.h
@@ -6,35 +6,8 @@
#ifndef NVMEM_H
#define NVMEM_H
-/**
- * typedef nvmem_reg_read_t - Read a register from an nvmem device
- *
- * @dev: The device to read from
- * @offset: The offset of the register from the beginning of @dev
- * @buf: The buffer to read into
- * @size: The size of @buf, in bytes
- *
- * Return:
- * * 0 on success
- * * A negative error on failure
- */
-typedef int (*nvmem_reg_read_t)(struct udevice *dev, unsigned int offset,
- void *buf, size_t size);
-
-/**
- * typedef nvmem_reg_write_t - Write a register to an nvmem device
- * @dev: The device to write
- * @offset: The offset of the register from the beginning of @dev
- * @buf: The buffer to write
- * @size: The size of @buf, in bytes
- *
- * Return:
- * * 0 on success
- * * -ENOSYS if the device is read-only
- * * A negative error on other failures
- */
-typedef int (*nvmem_reg_write_t)(struct udevice *dev, unsigned int offset,
- const void *buf, size_t size);
+struct driver;
+struct udevice;
/**
* struct nvmem_cell - One datum within non-volatile memory
@@ -48,8 +21,6 @@ struct nvmem_cell {
size_t size;
};
-struct udevice;
-
#if CONFIG_IS_ENABLED(NVMEM)
/**
@@ -121,6 +92,22 @@ int nvmem_cell_get_by_index(struct udevice *dev, int index,
int nvmem_cell_get_by_name(struct udevice *dev, const char *name,
struct nvmem_cell *cell);
+/**
+ * nvmem_register() - Register a new nvmem device
+ * @dev: The nvmem device proper
+ * @drv: The nvmem adapter driver
+ *
+ * This registers a child device of @dev as an nvmem device using @drv. The
+ * child device's name will be @dev's name with ".nvmem" appended. @drv should
+ * contain %DM_FLAG_NAME_ALLOCED in its flags.
+ *
+ * Return:
+ * * 0 on success
+ * * -ENOMEM if no memory could be allocated for the name
+ * * A negative error if device_bind() failed
+ */
+int nvmem_register(struct udevice *dev, const struct driver* drv);
+
#else /* CONFIG_NVMEM */
static inline int nvmem_cell_read(struct nvmem_cell *cell, void *buf, int size)
@@ -146,6 +133,53 @@ static inline int nvmem_cell_get_by_name(struct udevice *dev, const char *name,
return -ENOSYS;
}
+static inline int nvmem_register(struct udevice *dev, const struct driver* drv)
+{
+ return 0;
+}
#endif /* CONFIG_NVMEM */
+/**
+ * struct nvmem_ops - Ops implemented by nvmem devices
+ * read: Read a register
+ * write: Write a register
+ */
+struct nvmem_ops {
+ int (*read)(struct udevice *dev, unsigned int offset, void *buf,
+ size_t size);
+ int (*write)(struct udevice *dev, unsigned int offset, const void *buf,
+ size_t size);
+};
+
+#if 0 /* for docs only */
+/**
+ * read() - Read a register from an nvmem device
+ *
+ * @dev: The device to read from
+ * @offset: The offset of the register from the beginning of @dev
+ * @buf: The buffer to read into
+ * @size: The size of @buf, in bytes
+ *
+ * Return:
+ * * 0 on success
+ * * A negative error on failure
+ */
+int read(struct udevice *dev, unsigned int offset, void *buf, size_t size);
+
+/**
+ * write() - Write a register to an nvmem device
+ * @dev: The device to write
+ * @offset: The offset of the register from the beginning of @dev
+ * @buf: The buffer to write
+ * @size: The size of @buf, in bytes
+ *
+ * Return:
+ * * 0 on success
+ * * -ENOSYS if the device is read-only
+ * * A negative error on other failures
+ */
+int write(struct udevice *dev, unsigned int offset, const void *buf,
+ size_t size);
+#endif
+
#endif /* NVMEM_H */
--
2.35.1.1320.gc452695387.dirty
More information about the U-Boot
mailing list