diff --git a/MAINTAINERS.yml b/MAINTAINERS.yml index 6079a21eacbd2..02765b9b27e87 100644 --- a/MAINTAINERS.yml +++ b/MAINTAINERS.yml @@ -3385,6 +3385,24 @@ NEORV32 platform: tests: - boards.neorv32 +NVMEM: + status: maintained + maintainers: + - pdgendt + collaborators: + - henrikbrixandersen + files: + - doc/services/nvmem/ + - dts/bindings/nvmem/ + - include/zephyr/devicetree/nvmem.h + - include/zephyr/nvmem.h + - subsys/nvmem/ + - tests/subsys/nvmem/ + labels: + - "area: NVMEM" + tests: + - nvmem + NXP Platform Drivers: status: maintained maintainers: diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index d896b4c06c396..af1f82e8d9483 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -193,6 +193,19 @@ New APIs and options * :kconfig:option:`CONFIG_MODEM_DEDICATED_WORKQUEUE` +* NVMEM + + * Introduced :ref:`Non-Volatile Memory (NVMEM)` subsystem + + * :kconfig:option:`CONFIG_NVMEM` + * :kconfig:option:`CONFIG_NVMEM_EEPROM` + * :c:struct:`nvmem_cell` + * :c:func:`nvmem_cell_read` + * :c:func:`nvmem_cell_write` + * :c:func:`nvmem_cell_is_ready` + * :c:macro:`NVMEM_CELL_GET_BY_NAME` - and variants + * :c:macro:`NVMEM_CELL_GET_BY_IDX` - and variants + * Networking * Sockets diff --git a/doc/services/index.rst b/doc/services/index.rst index 7e8a96d320a35..8c1d9a45acf44 100644 --- a/doc/services/index.rst +++ b/doc/services/index.rst @@ -24,6 +24,7 @@ OS Services resource_management/index.rst mem_mgmt/index.rst net_buf/index.rst + nvmem/index.rst modem/index.rst notify.rst pm/index.rst diff --git a/doc/services/nvmem/devicetree_bindings.txt b/doc/services/nvmem/devicetree_bindings.txt new file mode 100644 index 0000000000000..151f1faf97ff3 --- /dev/null +++ b/doc/services/nvmem/devicetree_bindings.txt @@ -0,0 +1,18 @@ +&eeprom0 { + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + mac_address: mac_address@0 { + reg = <0x0 6>; + #nvmem-cell-cells = <0>; + read-only; + }; + + calibration_data: calibration_data@6 { + reg = <0x6 100>; + #nvmem-cell-cells = <0>; + }; + }; +}; diff --git a/doc/services/nvmem/index.rst b/doc/services/nvmem/index.rst new file mode 100644 index 0000000000000..02192dc419785 --- /dev/null +++ b/doc/services/nvmem/index.rst @@ -0,0 +1,68 @@ +.. _nvmem: + +Non-Volatile Memory (NVMEM) +########################### + +The NVMEM subsystem provides a generic interface for accessing non-volatile +memory devices. It abstracts the underlying hardware and provides a unified API +for reading and writing data. + +Key Concepts +************ + +NVMEM Provider +============== + +An NVMEM provider is a driver that exposes NVMEM cells. For example, an EEPROM +driver can be an NVMEM provider. The NVMEM provider is responsible for reading +and writing data to the underlying hardware. + +NVMEM Cell +========== + +An NVMEM cell is a region of non-volatile memory. It is defined in the +devicetree and has properties like offset, size, and read-only status. + +NVMEM Consumer +============== + +An NVMEM consumer is a driver or application that uses NVMEM cells to store or +retrieve data. + +Configuration +************* + +* :kconfig:option:`CONFIG_NVMEM`: Enables the NVMEM subsystem. +* :kconfig:option:`CONFIG_NVMEM_EEPROM`: Enables NVMEM support for EEPROM devices. + +Devicetree Bindings +******************* + +The NVMEM subsystem relies on devicetree bindings to define NVMEM cells. +The following is an example of how to define an NVMEM provider and cells in the +devicetree: + +.. literalinclude:: devicetree_bindings.txt + :language: dts + + +A consumer can then reference the NVMEM cells like this: + +.. literalinclude:: my_consumer.txt + :language: dts + + +Usage Example +************* + +The following is an example of how to use the NVMEM API to read data from an +NVMEM cell: + +.. literalinclude:: usage_example.txt + :language: c + + +API Reference +************* + +.. doxygengroup:: nvmem_interface diff --git a/doc/services/nvmem/my_consumer.txt b/doc/services/nvmem/my_consumer.txt new file mode 100644 index 0000000000000..10a6fb7da07d1 --- /dev/null +++ b/doc/services/nvmem/my_consumer.txt @@ -0,0 +1,5 @@ +my_consumer: my-consumer { + compatible = "my,consumer"; + nvmem-cells = <&mac_address>, <&calibration_data>; + nvmem-cell-names = "mac-address", "calibration-data"; +}; diff --git a/doc/services/nvmem/usage_example.txt b/doc/services/nvmem/usage_example.txt new file mode 100644 index 0000000000000..7714abb419753 --- /dev/null +++ b/doc/services/nvmem/usage_example.txt @@ -0,0 +1,23 @@ +#include + +static const struct nvmem_cell mac_address = + NVMEM_CELL_GET_BY_NAME(DT_NODELABEL(my_consumer), mac_address); + +int main(void) +{ + uint8_t mac[6]; + int ret; + + if (!nvmem_cell_is_ready(&mac_address)) { + printk("NVMEM cell is not ready\n"); + return -ENODEV; + } + + ret = nvmem_cell_read(&mac_address, mac, 0, sizeof(mac)); + if (ret < 0) { + printk("Failed to read MAC address: %d\n", ret); + return ret; + } + + /* ... */ +} diff --git a/dts/bindings/nvmem/fixed-layout.yaml b/dts/bindings/nvmem/fixed-layout.yaml new file mode 100644 index 0000000000000..4f930963bd519 --- /dev/null +++ b/dts/bindings/nvmem/fixed-layout.yaml @@ -0,0 +1,46 @@ +# Copyright (c) 2025 Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +description: | + Fixed layout for Non-Volatile memory. + +compatible: "fixed-layout" + +properties: + "#address-cells": + type: int + const: 1 + description: | + Number of cells required to represent a child node's + reg property address. + + "#size-cells": + type: int + const: 1 + description: | + Number of cells required to represent a child node's size. + +child-binding: + description: | + Each child node of the NVMEM provider node represents + an individual NVMEM cell. These should usually + look like this: + + cell_nodelabel: cell@START_OFFSET { + reg = <0xSTART_OFFSET 0xSIZE>; + #nvmem-cell-cells = <0>; + }; + + properties: + reg: + type: array + required: true + description: | + This should be in the format , where OFFSET + is the offset of the NVMEM cell relative to the base + address of the parent memory, and SIZE is the size of + the cell in bytes. + + read-only: + type: boolean + description: Set this property if the cell is read-only. diff --git a/dts/bindings/nvmem/nvmem-consumer.yaml b/dts/bindings/nvmem/nvmem-consumer.yaml new file mode 100644 index 0000000000000..734348bc5e1a9 --- /dev/null +++ b/dts/bindings/nvmem/nvmem-consumer.yaml @@ -0,0 +1,14 @@ +# Copyright (c) 2024, Andriy Gelman +# Copyright (c) 2025 Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +properties: + nvmem-cell-names: + type: string-array + description: + Names for each nvmem-cells specified. + + nvmem-cells: + type: phandle-array + description: + List of phandles to the nvmem data cells. diff --git a/dts/bindings/test/vnd,nvmem-consumer.yaml b/dts/bindings/test/vnd,nvmem-consumer.yaml new file mode 100644 index 0000000000000..052d350d8899f --- /dev/null +++ b/dts/bindings/test/vnd,nvmem-consumer.yaml @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +description: Test NVMEM consumer node + +compatible: "vnd,nvmem-consumer" + +include: [nvmem-consumer.yaml] diff --git a/include/zephyr/devicetree/nvmem.h b/include/zephyr/devicetree/nvmem.h new file mode 100644 index 0000000000000..00fc3515ea69e --- /dev/null +++ b/include/zephyr/devicetree/nvmem.h @@ -0,0 +1,313 @@ +/** + * @file + * @brief NVMEM Devicetree public API header file. + */ + +/* + * Copyright (c) 2024, Andriy Gelman + * Copyright (c) 2025, Basalte bv + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef INCLUDE_ZEPHYR_DEVICETREE_NVMEM_H_ +#define INCLUDE_ZEPHYR_DEVICETREE_NVMEM_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup devicetree-nvmem Devicetree NVMEM API + * @ingroup devicetree + * @{ + */ + +/** + * @brief Test if a node has an nvmem-cells phandle-array property at a given index + * + * This expands to 1 if the given index is a valid nvmem-cells property phandle-array index. + * Otherwise, it expands to 0. + * + * Example devicetree fragment: + * + * @code{.dts} + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_NVMEM_CELLS_HAS_IDX(DT_NODELABEL(eth), 0) // 1 + * DT_NVMEM_CELLS_HAS_IDX(DT_NODELABEL(eth), 1) // 0 + * @endcode + * + * @param node_id node identifier; may or may not have any nvmem-cells property + * @param idx index of a nvmem-cells property phandle-array whose existence to check + * + * @return 1 if the index exists, 0 otherwise + */ +#define DT_NVMEM_CELLS_HAS_IDX(node_id, idx) DT_PROP_HAS_IDX(node_id, nvmem_cells, idx) + +/** + * @brief Test if a node has an nvmem-cell-names array property hold a given name. + * + * This expands to 1 if the name is available as nvmem-cells-name array property cell. + * Otherwise, it expands to 0. + * + * Example devicetree fragment: + * + * @code{.dts} + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_NVMEM_CELLS_HAS_NAME(DT_NODELABEL(eth), mac_address) // 1 + * DT_NVMEM_CELLS_HAS_NAME(DT_NODELABEL(eth), bogus) // 0 + * @endcode + * + * @param node_id node identifier; may or may not have any nvmem-cell-names property + * @param name lowercase-and-underscores nvmem-cell-names cell value name to check + * + * @return 1 if the index exists, 0 otherwise + */ +#define DT_NVMEM_CELLS_HAS_NAME(node_id, name) DT_PROP_HAS_NAME(node_id, nvmem_cells, name) + +/** + * @brief Get the number of elements in an nvmem-cells property + * + * Example devicetree fragment: + * + * @code{.dts} + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_NUM_NVMEM_CELLS(DT_NODELABEL(eth)) // 1 + * @endcode + * + * @param node_id node identifier with an nvmem-cells property + * + * @return number of elements in the property + */ +#define DT_NUM_NVMEM_CELLS(node_id) DT_PROP_LEN(node_id, nvmem_cells) + +/** + * @brief Get the node identifier for the NVMEM cell from the nvmem-cells property by index. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_NVMEM_CELL_BY_IDX(DT_NODELABEL(eth), 0) // DT_NODELABEL(mac_address) + * @endcode + * + * @param node_id node identifier for a node with a nvmem-cells property + * @param idx index into the nvmem-cells property + * + * @return the node identifier for the NVMEM cell at index idx + */ +#define DT_NVMEM_CELL_BY_IDX(node_id, idx) DT_PHANDLE_BY_IDX(node_id, nvmem_cells, idx) + +/** + * @brief Equivalent to DT_NVMEM_CELL_BY_IDX(node_id, 0) + * + * @param node_id node identifier + * + * @return a node identifier for the NVMEM cell at index 0 + * in "nvmem-cells" + * + * @see DT_NVMEM_CELL_BY_IDX() + */ +#define DT_NVMEM_CELL(node_id) DT_NVMEM_CELL_BY_IDX(node_id, 0) + +/** + * @brief Get the node identifier for the NVMEM cell from the nvmem-cells property by name. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_NVMEM_CELL_BY_NAME(DT_NODELABEL(eth), mac_address) // DT_NODELABEL(mac_address) + * @endcode + * + * @param node_id node identifier for a node with a nvmem-cells property + * @param name lowercase-and-underscores name of an nvmem-cells element + * as defined by the node's nvmem-cell-names property + * + * @return the node identifier for the NVMEM cell by name + */ +#define DT_NVMEM_CELL_BY_NAME(node_id, name) DT_PHANDLE_BY_NAME(node_id, nvmem_cells, name) + +/** + * @brief Equivalent to DT_NVMEM_CELLS_HAS_IDX(DT_DRV_INST(inst), idx) + * + * @param inst DT_DRV_COMPAT instance number; may or may not have any nvmem-cells property + * @param idx index of an nvmem-cells property phandle-array whose existence to check + * + * @return 1 if the index exists, 0 otherwise + */ +#define DT_INST_NVMEM_CELLS_HAS_IDX(inst, idx) DT_NVMEM_CELLS_HAS_IDX(DT_DRV_INST(inst), idx) + +/** + * @brief Equivalent to DT_NVMEM_CELLS_HAS_NAME(DT_DRV_INST(inst), name) + * + * @param inst DT_DRV_COMPAT instance number; may or may not have any nvmem-cell-names property. + * @param name lowercase-and-underscores nvmem-cell-names cell value name to check + * + * @return 1 if the nvmem cell name exists, 0 otherwise + */ +#define DT_INST_NVMEM_CELLS_HAS_NAME(inst, name) DT_NVMEM_CELLS_HAS_NAME(DT_DRV_INST(inst), name) + +/** + * @brief Equivalent to DT_NUM_NVMEM_CELLS(DT_DRV_INST(inst)) + * + * @param inst instance number + * + * @return number of elements in the nvmem-cells property + */ +#define DT_INST_NUM_NVMEM_CELLS(inst) DT_NUM_NVMEM_CELLS(DT_DRV_INST(inst)) + +/** + * @brief Get the node identifier for the controller phandle from an + * nvmem-cells phandle-array property at an index + * + * @param inst instance number + * @param idx logical index into nvmem-cells + * + * @return the node identifier for the nvmem cell referenced at + * index "idx" + * + * @see DT_NVMEM_CELL_CTLR_BY_IDX() + */ +#define DT_INST_NVMEM_CELL_BY_IDX(inst, idx) DT_NVMEM_CELL_BY_IDX(DT_DRV_INST(inst), idx) + +/** + * @brief Equivalent to DT_INST_NVMEM_CELL_BY_IDX(inst, 0) + * + * @param inst instance number + * + * @return a node identifier for the nvmem cell at index 0 + * in nvmem-cells + * + * @see DT_NVMEM_CELL() + */ +#define DT_INST_NVMEM_CELL(inst) DT_INST_NVMEM_CELL_BY_IDX(inst, 0) + +/** + * @brief Get the node identifier for the controller phandle from an + * nvmem-cells phandle-array property by name + * + * @param inst instance number + * @param name lowercase-and-underscores name of an nvmem-cells element + * as defined by the node's nvmem-cell-names property + * + * @return the node identifier for the nvmem cell referenced by + * the named element + * + * @see DT_NVMEM_CELL_BY_NAME() + */ +#define DT_INST_NVMEM_CELL_BY_NAME(inst, name) DT_NVMEM_CELL_BY_NAME(DT_DRV_INST(inst), name) + +/** + * @brief Get the node identifier of the memory controller for an nvmem cell. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * DT_MTD_FROM_NVMEM_CELL(DT_NVMEM_CELL(DT_NODELABEL(eth))) // DT_NODELABEL(mac_eeprom) + * @endcode + * + * @param node_id node identifier for an nvmem cell node + * + * @return the node identifier of the Memory Technology Device (MTD) that + * contains the nvmem cell node. + */ +#define DT_MTD_FROM_NVMEM_CELL(node_id) DT_GPARENT(node_id) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* INCLUDE_ZEPHYR_DEVICETREE_NVMEM_H_ */ diff --git a/include/zephyr/nvmem.h b/include/zephyr/nvmem.h new file mode 100644 index 0000000000000..ae422bc792efb --- /dev/null +++ b/include/zephyr/nvmem.h @@ -0,0 +1,357 @@ +/* + * Copyright (c) 2025 Basalte bv + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Main header file for NVMEM API. + * @ingroup nvmem_interface + */ + +#ifndef ZEPHYR_INCLUDE_NVMEM_H_ +#define ZEPHYR_INCLUDE_NVMEM_H_ + +/** + * @brief Interfaces for NVMEM cells. + * @defgroup nvmem_interface NVMEM + * @since 4.3 + * @version 0.1.0 + * @ingroup io_interfaces + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Non-Volatile Memory cell representation. + */ +struct nvmem_cell { + /** NVMEM parent controller device instance. */ + const struct device *dev; + /** Offset of the NVMEM cell relative to the parent controller's base address */ + off_t offset; + /** Size of the NVMEM cell */ + size_t size; + /** Indicator if the NVMEM cell is read-write or read-only */ + bool read_only; +}; + +/** + * @brief Static initializer for a struct nvmem_cell. + * + * This returns a static initializer for a struct nvmem_cell given a devicetree + * node identifier. + * + * @note This is a helper macro for other NVMEM_CELL_GET macros to initialize the + * nvmem_cell struct. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * const struct nvmem_cell cell = NVMEM_CELL_INIT(DT_NODELABEL(mac_address)); + * + * // Initializes 'cell' to: + * // { + * // .dev = DEVICE_DT_GET(DT_NODELABEL(mac_eeprom)), + * // .offset = 0xfa, + * // .size = 6, + * // .read_only = false, + * // } + * @endcode + * + * @param node_id Devicetree node identifier. + * + * @return Static initializer for a struct nvmem_cell + */ +#define NVMEM_CELL_INIT(node_id) \ + { \ + .dev = DEVICE_DT_GET(DT_MTD_FROM_NVMEM_CELL(node_id)), \ + .offset = DT_REG_ADDR(node_id), \ + .size = DT_REG_SIZE(node_id), \ + .read_only = DT_PROP(node_id, read_only), \ + } + +/** + * @brief Static initializer for a struct nvmem_cell. + * + * This returns a static initializer for a struct nvmem_cell given a devicetree + * node identifier and a name. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * const struct nvmem_cell cell = + * NVMEM_CELL_GET_BY_NAME(DT_NODELABEL(eth), mac_address); + * + * // Initializes 'cell' to: + * // { + * // .dev = DEVICE_DT_GET(DT_NODELABEL(mac_eeprom)), + * // .offset = 0xfa, + * // .size = 6, + * // .read_only = false, + * // } + * @endcode + * + * @param node_id Devicetree node identifier. + * @param name Lowercase-and-underscores name of an nvmem-cells element as defined by + * the node's nvmem-cell-names property. + * + * @return Static initializer for a struct nvmem_cell for the property. + * + * @see NVMEM_CELL_INST_GET_BY_NAME + */ +#define NVMEM_CELL_GET_BY_NAME(node_id, name) NVMEM_CELL_INIT(DT_NVMEM_CELL_BY_NAME(node_id, name)) + +/** + * @brief Static initializer for a struct nvmem_cell from a DT_DRV_COMPAT + * instance. + * + * @param inst DT_DRV_COMPAT instance number + * @param name Lowercase-and-underscores name of an nvmem-cells element as defined by + * the node's nvmem-cell-names property. + * + * @return Static initializer for a struct nvmem_cell for the property. + * + * @see NVMEM_CELL_GET_BY_NAME + */ +#define NVMEM_CELL_INST_GET_BY_NAME(inst, name) NVMEM_CELL_GET_BY_NAME(DT_DRV_INST(inst), name) + +/** + * @brief Like NVMEM_CELL_GET_BY_NAME(), with a fallback to a default value. + * + * If the devicetree node identifier 'node_id' refers to a node with a property + * 'nvmem-cells', this expands to NVMEM_CELL_GET_BY_NAME(node_id, name). The + * @p default_value parameter is not expanded in this case. Otherwise, this + * expands to @p default_value. + * + * @param node_id Devicetree node identifier. + * @param name Lowercase-and-underscores name of an nvmem-cells element as defined by + * the node's nvmem-cell-names property. + * @param default_value Fallback value to expand to. + * + * @return Static initializer for a struct nvmem_cell for the property, + * or @p default_value if the node or property do not exist. + * + * @see NVMEM_CELL_INST_GET_BY_NAME_OR + */ +#define NVMEM_CELL_GET_BY_NAME_OR(node_id, name, default_value) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, nvmem_cells), \ + (NVMEM_CELL_GET_BY_NAME(node_id, name)), \ + (default_value)) + +/** + * @brief Like NVMEM_CELL_INST_GET_BY_NAME(), with a fallback to a default + * value. + * + * @param inst DT_DRV_COMPAT instance number + * @param name Lowercase-and-underscores name of an nvmem-cells element as defined by + * the node's nvmem-cell-names property. + * @param default_value Fallback value to expand to. + * + * @return Static initializer for a struct nvmem_cell for the property, + * or @p default_value if the node or property do not exist. + * + * @see NVMEM_CELL_GET_BY_NAME_OR + */ +#define NVMEM_CELL_INST_GET_BY_NAME_OR(inst, name, default_value) \ + NVMEM_CELL_GET_BY_NAME_OR(DT_DRV_INST(inst), name, default_value) + +/** + * @brief Static initializer for a struct nvmem_cell. + * + * This returns a static initializer for a struct nvmem_cell given a devicetree + * node identifier and an index. + * + * Example devicetree fragment: + * + * @code{.dts} + * mac_eeprom: mac_eeprom@2 { + * nvmem-layout { + * compatible = "fixed-layout"; + * #address-cells = <1>; + * #size-cells = <1>; + * + * mac_address: mac_address@fa { + * reg = <0xfa 0x06>; + * #nvmem-cell-cells = <0>; + * }; + * }; + * }; + * + * eth: ethernet { + * nvmem-cells = <&mac_address>; + * nvmem-cell-names = "mac-address"; + * }; + * @endcode + * + * Example usage: + * + * @code{.c} + * const struct nvmem_cell cell = + * NVMEM_CELL_GET_BY_IDX(DT_NODELABEL(eth), 0); + * + * // Initializes 'cell' to: + * // { + * // .dev = DEVICE_DT_GET(DT_NODELABEL(mac_eeprom)), + * // .offset = 0xfa, + * // .size = 6, + * // .read_only = false, + * // } + * @endcode + * + * @param node_id Devicetree node identifier. + * @param idx Logical index into 'nvmem-cells' property. + * + * @return Static initializer for a struct nvmem_cell for the property. + * + * @see NVMEM_CELL_INST_GET_BY_IDX + */ +#define NVMEM_CELL_GET_BY_IDX(node_id, idx) NVMEM_CELL_INIT(DT_NVMEM_CELL_BY_IDX(node_id, idx)) + +/** + * @brief Static initializer for a struct nvmem_cell from a DT_DRV_COMPAT + * instance. + * + * @param inst DT_DRV_COMPAT instance number + * @param idx Logical index into 'nvmem-cells' property. + * + * @return Static initializer for a struct nvmem_cell for the property. + * + * @see NVMEM_CELL_GET_BY_IDX + */ +#define NVMEM_CELL_INST_GET_BY_IDX(inst, idx) NVMEM_CELL_GET_BY_IDX(DT_DRV_INST(inst), idx) + +/** + * @brief Like NVMEM_CELL_GET_BY_IDX(), with a fallback to a default value. + * + * If the devicetree node identifier 'node_id' refers to a node with a property + * 'nvmem-cells', this expands to NVMEM_CELL_GET_BY_IDX(node_id, idx). The + * @p default_value parameter is not expanded in this case. Otherwise, this + * expands to @p default_value. + * + * @param node_id Devicetree node identifier. + * @param idx Logical index into 'nvmem-cells' property. + * @param default_value Fallback value to expand to. + * + * @return Static initializer for a struct nvmem_cell for the property, + * or @p default_value if the node or property do not exist. + * + * @see NVMEM_CELL_INST_GET_BY_IDX_OR + */ +#define NVMEM_CELL_GET_BY_IDX_OR(node_id, idx, default_value) \ + COND_CODE_1(DT_NODE_HAS_PROP(node_id, nvmem_cells), \ + (NVMEM_CELL_GET_BY_IDX(node_id, idx)), \ + (default_value)) + +/** + * @brief Like NVMEM_CELL_INST_GET_BY_IDX(), with a fallback to a default + * value. + * + * @param inst DT_DRV_COMPAT instance number + * @param idx Logical index into 'nvmem-cells' property. + * @param default_value Fallback value to expand to. + * + * @return Static initializer for a struct nvmem_cell for the property, + * or @p default_value if the node or property do not exist. + * + * @see NVMEM_CELL_GET_BY_IDX_OR + */ +#define NVMEM_CELL_INST_GET_BY_IDX_OR(inst, idx, default_value) \ + NVMEM_CELL_GET_BY_IDX_OR(DT_DRV_INST(inst), idx, default_value) + +/** + * @brief Read data from an NVMEM cell. + * + * @param cell The NVMEM cell. + * @param data Buffer to store read data. + * @param off The offset to start reading from. + * @param len Number of bytes to read. + * + * @kconfig_dep{CONFIG_NVMEM} + * + * @return 0 on success, negative errno code on failure. + */ +int nvmem_cell_read(const struct nvmem_cell *cell, void *data, off_t off, size_t len); + +/** + * @brief Write data to an NVMEM cell. + * + * @param cell The NVMEM cell. + * @param data Buffer with data to write. + * @param off The offset to start writing to. + * @param len Number of bytes to write. + * + * @kconfig_dep{CONFIG_NVMEM} + * + * @return 0 on success, negative errno code on failure. + */ +int nvmem_cell_write(const struct nvmem_cell *cell, const void *data, off_t off, size_t len); + +/** + * @brief Validate that the NVMEM cell is ready. + * + * @param cell The NVMEM cell. + * + * @return true if the NVMEM cell is ready for use and false otherwise. + */ +static inline bool nvmem_cell_is_ready(const struct nvmem_cell *cell) +{ + return cell != NULL && device_is_ready(cell->dev); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_NVMEM_H_ */ diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt index 7a4c45a01ac52..756214ad752d4 100644 --- a/subsys/CMakeLists.txt +++ b/subsys/CMakeLists.txt @@ -54,6 +54,7 @@ add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_LLEXT llext) add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem) add_subdirectory_ifdef(CONFIG_NETWORKING net) +add_subdirectory_ifdef(CONFIG_NVMEM nvmem) add_subdirectory_ifdef(CONFIG_PROFILING profiling) add_subdirectory_ifdef(CONFIG_RETENTION retention) add_subdirectory_ifdef(CONFIG_SECURE_STORAGE secure_storage) diff --git a/subsys/Kconfig b/subsys/Kconfig index f58aca67bc476..f35a0d1c90680 100644 --- a/subsys/Kconfig +++ b/subsys/Kconfig @@ -36,6 +36,7 @@ source "subsys/mgmt/Kconfig" source "subsys/modbus/Kconfig" source "subsys/modem/Kconfig" source "subsys/net/Kconfig" +source "subsys/nvmem/Kconfig" source "subsys/pm/Kconfig" source "subsys/pmci/Kconfig" source "subsys/portability/Kconfig" diff --git a/subsys/nvmem/CMakeLists.txt b/subsys/nvmem/CMakeLists.txt new file mode 100644 index 0000000000000..eacf7cc86f0ba --- /dev/null +++ b/subsys/nvmem/CMakeLists.txt @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(nvmem.c) diff --git a/subsys/nvmem/Kconfig b/subsys/nvmem/Kconfig new file mode 100644 index 0000000000000..7e1ea018e125b --- /dev/null +++ b/subsys/nvmem/Kconfig @@ -0,0 +1,18 @@ +# Copyright (c) 2025 Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +menuconfig NVMEM + bool "Non Volatile Memory (NVMEM)" + +if NVMEM + +config NVMEM_EEPROM + bool "NVMEM support for EEPROM devices" + default y + depends on EEPROM + +module = NVMEM +module-str = nvmem +source "subsys/logging/Kconfig.template.log_config" + +endif # NVMEM diff --git a/subsys/nvmem/nvmem.c b/subsys/nvmem/nvmem.c new file mode 100644 index 0000000000000..d60e64c45d3f1 --- /dev/null +++ b/subsys/nvmem/nvmem.c @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2025 Basalte bv + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +int nvmem_cell_read(const struct nvmem_cell *cell, void *buf, off_t off, size_t len) +{ + __ASSERT_NO_MSG(cell != NULL); + + if (off < 0 || cell->size < off + len) { + return -EINVAL; + } + + if (IS_ENABLED(CONFIG_NVMEM_EEPROM) && DEVICE_API_IS(eeprom, cell->dev)) { + return eeprom_read(cell->dev, cell->offset + off, buf, len); + } + + return -ENXIO; +} + +int nvmem_cell_write(const struct nvmem_cell *cell, const void *buf, off_t off, size_t len) +{ + __ASSERT_NO_MSG(cell != NULL); + + if (off < 0 || cell->size < off + len) { + return -EINVAL; + } + + if (cell->read_only) { + return -EROFS; + } + + if (IS_ENABLED(CONFIG_NVMEM_EEPROM) && DEVICE_API_IS(eeprom, cell->dev)) { + return eeprom_write(cell->dev, cell->offset + off, buf, len); + } + + return -ENXIO; +} diff --git a/tests/lib/devicetree/api/app.overlay b/tests/lib/devicetree/api/app.overlay index 950ca9b403555..0404be3974d41 100644 --- a/tests/lib/devicetree/api/app.overlay +++ b/tests/lib/devicetree/api/app.overlay @@ -903,6 +903,31 @@ }; }; }; + + test_nvmem_consumer: test-nvmem-consumer { + compatible = "vnd,nvmem-consumer"; + nvmem-cells = <&cell0>, <&cell10>; + nvmem-cell-names = "cell0", "cell10"; + }; + + test-nvmem-provider { + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + cell0: cell@0 { + reg = <0x0 0x10>; + #nvmem-cell-cells = <0>; + }; + + cell10: cell@10 { + reg = <0x10 0x10>; + read-only; + #nvmem-cell-cells = <0>; + }; + }; + }; }; test_64 { diff --git a/tests/lib/devicetree/api/src/main.c b/tests/lib/devicetree/api/src/main.c index 98189ebe582cc..54d95555fe694 100644 --- a/tests/lib/devicetree/api/src/main.c +++ b/tests/lib/devicetree/api/src/main.c @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -3832,4 +3833,61 @@ ZTEST(devicetree_api, test_interrupt_controller) zassert_true(DT_SAME_NODE(DT_INST_IRQ_INTC(0), TEST_INTC), ""); } +ZTEST(devicetree_api, test_nvmem_devictree) +{ + zexpect_equal(DT_NVMEM_CELLS_HAS_IDX(DT_NODELABEL(test_nvmem_consumer), 0), 1); + zexpect_equal(DT_NVMEM_CELLS_HAS_IDX(DT_NODELABEL(test_nvmem_consumer), 1), 1); + zexpect_equal(DT_NVMEM_CELLS_HAS_IDX(DT_NODELABEL(test_nvmem_consumer), 2), 0); + + zexpect_equal(DT_NVMEM_CELLS_HAS_NAME(DT_NODELABEL(test_nvmem_consumer), cell0), 1); + zexpect_equal(DT_NVMEM_CELLS_HAS_NAME(DT_NODELABEL(test_nvmem_consumer), cell10), 1); + zexpect_equal(DT_NVMEM_CELLS_HAS_NAME(DT_NODELABEL(test_nvmem_consumer), missing), 0); + + zexpect_equal(DT_NUM_NVMEM_CELLS(DT_NODELABEL(test_nvmem_consumer)), 2); + + zexpect_str_equal(DT_NODE_PATH(DT_NVMEM_CELL_BY_IDX(DT_NODELABEL(test_nvmem_consumer), 0)), + "/test/test-nvmem-provider/nvmem-layout/cell@0"); + zexpect_str_equal(DT_NODE_PATH(DT_NVMEM_CELL_BY_IDX(DT_NODELABEL(test_nvmem_consumer), 1)), + "/test/test-nvmem-provider/nvmem-layout/cell@10"); + + zexpect_str_equal( + DT_NODE_PATH(DT_NVMEM_CELL_BY_NAME(DT_NODELABEL(test_nvmem_consumer), cell0)), + "/test/test-nvmem-provider/nvmem-layout/cell@0"); + zexpect_str_equal( + DT_NODE_PATH(DT_NVMEM_CELL_BY_NAME(DT_NODELABEL(test_nvmem_consumer), cell10)), + "/test/test-nvmem-provider/nvmem-layout/cell@10"); + + zexpect_str_equal(DT_NODE_PATH(DT_MTD_FROM_NVMEM_CELL( + DT_NVMEM_CELL(DT_NODELABEL(test_nvmem_consumer)))), + "/test/test-nvmem-provider"); +} + +#undef DT_DRV_COMPAT +#define DT_DRV_COMPAT vnd_nvmem_consumer +ZTEST(devicetree_api, test_nvmem_devictree_inst) +{ + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_IDX(0, 0), 1); + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_IDX(0, 1), 1); + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_IDX(0, 2), 0); + + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_NAME(0, cell0), 1); + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_NAME(0, cell10), 1); + zexpect_equal(DT_INST_NVMEM_CELLS_HAS_NAME(0, missing), 0); + + zexpect_equal(DT_INST_NUM_NVMEM_CELLS(0), 2); + + zexpect_str_equal(DT_NODE_PATH(DT_INST_NVMEM_CELL_BY_IDX(0, 0)), + "/test/test-nvmem-provider/nvmem-layout/cell@0"); + zexpect_str_equal(DT_NODE_PATH(DT_INST_NVMEM_CELL_BY_IDX(0, 1)), + "/test/test-nvmem-provider/nvmem-layout/cell@10"); + + zexpect_str_equal(DT_NODE_PATH(DT_INST_NVMEM_CELL_BY_NAME(0, cell0)), + "/test/test-nvmem-provider/nvmem-layout/cell@0"); + zexpect_str_equal(DT_NODE_PATH(DT_INST_NVMEM_CELL_BY_NAME(0, cell10)), + "/test/test-nvmem-provider/nvmem-layout/cell@10"); + + zexpect_str_equal(DT_NODE_PATH(DT_MTD_FROM_NVMEM_CELL(DT_INST_NVMEM_CELL(0))), + "/test/test-nvmem-provider"); +} + ZTEST_SUITE(devicetree_api, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/nvmem/api/CMakeLists.txt b/tests/subsys/nvmem/api/CMakeLists.txt new file mode 100644 index 0000000000000..66e04833dcdd7 --- /dev/null +++ b/tests/subsys/nvmem/api/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright (c) 2025 Basalte bv +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(nvmem_eeprom) + +target_sources(app PRIVATE src/main.c) diff --git a/tests/subsys/nvmem/api/eeprom.overlay b/tests/subsys/nvmem/api/eeprom.overlay new file mode 100644 index 0000000000000..62c33ad4a2687 --- /dev/null +++ b/tests/subsys/nvmem/api/eeprom.overlay @@ -0,0 +1,35 @@ +/** + * Copyright (c) 2025, Basalte bv + * SPDX-License-Identifier: Apache-2.0 + */ + +/ { + aliases { + nvmem0 = &eeprom0; + }; + + test_consumer0: test-consumer0 { + compatible = "vnd,nvmem-consumer"; + nvmem-cells = <&cell0>, <&cell10>; + nvmem-cell-names = "cell0", "cell10"; + }; +}; + +&eeprom0 { + nvmem-layout { + compatible = "fixed-layout"; + #address-cells = <1>; + #size-cells = <1>; + + cell0: cell@0 { + reg = <0x0 0x10>; + #nvmem-cell-cells = <0>; + }; + + cell10: cell@10 { + reg = <0x10 0x10>; + read-only; + #nvmem-cell-cells = <0>; + }; + }; +}; diff --git a/tests/subsys/nvmem/api/prj.conf b/tests/subsys/nvmem/api/prj.conf new file mode 100644 index 0000000000000..146d1c8eb3a71 --- /dev/null +++ b/tests/subsys/nvmem/api/prj.conf @@ -0,0 +1,2 @@ +CONFIG_ZTEST=y +CONFIG_NVMEM=y diff --git a/tests/subsys/nvmem/api/src/main.c b/tests/subsys/nvmem/api/src/main.c new file mode 100644 index 0000000000000..c44227d1ae34f --- /dev/null +++ b/tests/subsys/nvmem/api/src/main.c @@ -0,0 +1,50 @@ +/** + * Copyright (c) 2025 Basalte bv + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#define consumer0 DT_NODELABEL(test_consumer0) +#define nvmem0 DT_ALIAS(nvmem0) + +static const struct nvmem_cell cell0 = NVMEM_CELL_GET_BY_IDX(consumer0, 0); +static const struct nvmem_cell cell10 = NVMEM_CELL_GET_BY_NAME(consumer0, cell10); + +ZTEST(nvmem_api, test_nvmem_api) +{ + uint8_t buf[0x10]; + int ret; + + zexpect_equal_ptr(cell0.dev, DEVICE_DT_GET(nvmem0)); + zexpect_equal(cell0.offset, 0); + zexpect_equal(cell0.size, 0x10); + zexpect_false(cell0.read_only); + + zexpect_equal_ptr(cell10.dev, DEVICE_DT_GET(nvmem0)); + zexpect_equal(cell10.offset, 0x10); + zexpect_equal(cell10.size, 0x10); + zexpect_true(cell10.read_only); + + for (size_t i = 0; i < sizeof(buf); ++i) { + buf[i] = 2 * i; + } + + ret = nvmem_cell_write(&cell0, buf, 0, sizeof(buf)); + zassert_ok(ret, "Failed to write NVMEM"); + + memset(buf, 0, sizeof(buf)); + + ret = nvmem_cell_read(&cell0, buf, 0, sizeof(buf)); + zassert_ok(ret, "Failed to read NVMEM"); + + for (size_t i = 0; i < sizeof(buf); ++i) { + zexpect_equal(buf[i], 2 * i); + } + + ret = nvmem_cell_write(&cell10, buf, 0, sizeof(buf)); + zassert_equal(ret, -EROFS, "Expected read-only NVMEM"); +} + +ZTEST_SUITE(nvmem_api, NULL, NULL, NULL, NULL, NULL); diff --git a/tests/subsys/nvmem/api/testcase.yaml b/tests/subsys/nvmem/api/testcase.yaml new file mode 100644 index 0000000000000..adfe3fe58e49b --- /dev/null +++ b/tests/subsys/nvmem/api/testcase.yaml @@ -0,0 +1,13 @@ +common: + platform_allow: + - native_sim + integration_platforms: + - native_sim + tags: + - nvmem +tests: + nvmem.api.eeprom: + extra_configs: + - CONFIG_EEPROM=y + extra_dtc_overlay_files: + - eeprom.overlay