# NVMEM (Non-Volatile Memory) Framework ## Overview ### What is NVMEM? Non-Volatile Memory (NVMEM) refers to computer memory that can retain stored information even when power is removed. Common examples include: - **EEPROM** (Electrically Erasable Programmable Read-Only Memory) - **OTP** (One-Time Programmable memory) - **eFuse** (Electronic fuse) - **Battery-backed SRAM** - **Flash memory regions** (for configuration storage) NVMEM devices are commonly used to store: - Device configuration and calibration data - MAC addresses and serial numbers - Hardware identifiers and unique IDs - Manufacturing test data - Secure key storage ### NVMEM Framework in RT-Thread RT-Thread's NVMEM framework provides a unified abstraction layer for accessing various types of non-volatile memory devices. The framework supports: - **Unified Access Interface**: Common APIs for reading/writing NVMEM regardless of underlying hardware - **Cell-Based Organization**: NVMEM data organized into named cells (regions) - **Device Tree Integration**: Hardware description through device tree - **Write Protection**: Hardware write-protect pin support - **Bit-Level Access**: Support for bit-addressable memory regions - **Reference Counting**: Automatic resource management The framework is located in: - Header: `components/drivers/include/drivers/nvmem.h` - Implementation: `components/drivers/nvmem/nvmem.c` ## Kconfig Configuration ### Enable NVMEM Framework ``` RT-Thread Components → Device Drivers → Using Device Driver Model (RT_USING_DM) → Using Open Firmware (OF) API (RT_USING_OFW) → Using Non Volatile Memory (NVMEM) device drivers (RT_USING_NVMEM) ``` ### Configuration Options #### RT_USING_NVMEM ```kconfig menuconfig RT_USING_NVMEM bool "Using Non Volatile Memory (NVMEM) device drivers" depends on RT_USING_DM depends on RT_USING_OFW depends on RT_USING_PIN select RT_USING_ADT select RT_USING_ADT_REF default n ``` **Dependencies**: - `RT_USING_DM`: Device Driver Model must be enabled - `RT_USING_OFW`: Open Firmware (Device Tree) support required - `RT_USING_PIN`: PIN driver framework for write-protect pins **Description**: Enables the NVMEM framework providing unified access to non-volatile memory devices like EEPROM, OTP, eFuse, etc. #### SOC-Specific NVMEM Drivers ```kconfig if RT_USING_NVMEM osource "$(SOC_DM_NVMEM_DIR)/Kconfig" endif ``` SoC vendors can provide their specific NVMEM driver configurations through the `SOC_DM_NVMEM_DIR` directory. ## Device Tree Bindings ### NVMEM Provider Node ```dts eeprom: eeprom@50 { compatible = "atmel,24c256"; reg = <0x50>; #address-cells = <1>; #size-cells = <1>; /* Optional properties */ read-only; /* Memory is read-only */ wp-gpios = <&gpio 10 GPIO_ACTIVE_HIGH>; /* Write protect pin */ /* Define memory cells */ mac_address: mac@0 { reg = <0x00 6>; /* Offset 0x00, 6 bytes */ }; serial_number: serial@10 { reg = <0x10 16>; /* Offset 0x10, 16 bytes */ }; calibration: calib@100 { reg = <0x100 32>; /* Offset 0x100, 32 bytes */ bits = <4 12>; /* Bit offset 4, 12 bits wide */ }; }; ``` ### NVMEM Consumer Node ```dts ethernet@40000000 { compatible = "vendor,ethernet"; reg = <0x40000000 0x10000>; /* Reference NVMEM cells */ nvmem-cells = <&mac_address>; nvmem-cell-names = "mac-address"; }; device@50000000 { compatible = "vendor,device"; reg = <0x50000000 0x1000>; /* Multiple NVMEM references */ nvmem-cells = <&serial_number>, <&calibration>; nvmem-cell-names = "serial", "calibration-data"; }; ``` ### Device Tree Properties #### NVMEM Provider Properties | Property | Type | Description | |----------|------|-------------| | `#address-cells` | u32 | Number of cells for cell address (usually 1) | | `#size-cells` | u32 | Number of cells for cell size (usually 1) | | `read-only` | bool | Memory is read-only, writes not allowed | | `wp-gpios` | phandle | GPIO for hardware write protection | #### NVMEM Cell Properties | Property | Type | Description | |----------|------|-------------| | `reg` | u32 array | `` - Memory region for this cell | | `bits` | u32 array | `` - Bit-level addressing | #### NVMEM Consumer Properties | Property | Type | Description | |----------|------|-------------| | `nvmem-cells` | phandle | Reference to NVMEM cells | | `nvmem-cell-names` | string array | Names for referenced cells | ## Application Layer API ### Data Structures #### struct rt_nvmem_device ```c struct rt_nvmem_device { struct rt_device parent; /* Parent device */ int cells_nr; /* Number of cells */ rt_list_t cell_nodes; /* List of cells */ /* Read/Write callbacks */ rt_ssize_t (*reg_read)(struct rt_nvmem_device *, int offset, void *val, rt_size_t bytes); rt_ssize_t (*reg_write)(struct rt_nvmem_device *, int offset, void *val, rt_size_t bytes); rt_ssize_t size; /* Total size in bytes */ int word_size; /* Word size (1, 2, 4 bytes) */ int stride; /* Minimum access stride */ rt_bool_t read_only; /* Read-only flag */ rt_bool_t ignore_wp; /* Ignore WP pin */ rt_base_t wp_pin; /* Write protect GPIO */ rt_uint8_t wp_pin_active; /* WP active level */ struct rt_ref ref; /* Reference counter */ struct rt_spinlock spinlock; /* Spinlock for protection */ void *priv; /* Private data */ }; ``` #### struct rt_nvmem_cell ```c struct rt_nvmem_cell { rt_list_t list; /* List node */ int index; /* Cell index */ const char *id; /* Cell identifier */ const rt_bool_t free_able; /* Can be freed */ rt_uint32_t offset; /* Offset in bytes */ rt_uint32_t bytes; /* Size in bytes */ rt_uint32_t bit_offset; /* Bit offset within byte */ rt_uint32_t nbits; /* Number of bits */ struct rt_ref ref; /* Reference counter */ struct rt_ofw_node *np; /* Device tree node */ struct rt_nvmem_device *nvmem; /* Parent NVMEM device */ }; ``` ### Consumer APIs #### Get NVMEM Cell ```c struct rt_nvmem_cell *rt_nvmem_get_cell_by_name(struct rt_device *dev, const char *id); struct rt_nvmem_cell *rt_nvmem_get_cell_by_index(struct rt_device *dev, int index); ``` **Parameters**: - `dev`: Consumer device - `id`: Cell name from device tree `nvmem-cell-names` - `index`: Cell index (0-based) **Returns**: Pointer to `rt_nvmem_cell` on success, NULL on failure **Description**: Obtain a reference to an NVMEM cell for reading/writing. Must be released with `rt_nvmem_put_cell()`. **Example**: ```c struct rt_nvmem_cell *cell; /* Get by name */ cell = rt_nvmem_get_cell_by_name(dev, "mac-address"); if (!cell) { rt_kprintf("Failed to get MAC address cell\n"); return -RT_ERROR; } /* Get by index */ cell = rt_nvmem_get_cell_by_index(dev, 0); ``` #### Release NVMEM Cell ```c void rt_nvmem_put_cell(struct rt_nvmem_cell *cell); ``` **Parameters**: - `cell`: NVMEM cell to release **Description**: Release a reference to an NVMEM cell obtained with `rt_nvmem_get_cell_*()`. #### Read NVMEM Cell ```c rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len); ``` **Parameters**: - `cell`: NVMEM cell to read - `buffer`: Buffer to store read data - `len`: Number of bytes to read **Returns**: Number of bytes read on success, negative error code on failure **Description**: Read data from an NVMEM cell. Supports bit-level access if configured. #### Write NVMEM Cell ```c rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell, void *buffer, rt_size_t len); ``` **Parameters**: - `cell`: NVMEM cell to write - `buffer`: Data to write - `len`: Number of bytes to write **Returns**: Number of bytes written on success, negative error code on failure **Description**: Write data to an NVMEM cell. Returns error if memory is read-only. #### Typed Read Functions ```c rt_ssize_t rt_nvmem_cell_read_u8(struct rt_nvmem_cell *cell, rt_uint8_t *out_val); rt_ssize_t rt_nvmem_cell_read_u16(struct rt_nvmem_cell *cell, rt_uint16_t *out_val); rt_ssize_t rt_nvmem_cell_read_u32(struct rt_nvmem_cell *cell, rt_uint32_t *out_val); rt_ssize_t rt_nvmem_cell_read_u64(struct rt_nvmem_cell *cell, rt_uint64_t *out_val); ``` **Parameters**: - `cell`: NVMEM cell to read - `out_val`: Pointer to store the value **Returns**: Number of bytes read on success, negative error code on failure **Description**: Convenience functions to read specific integer types from NVMEM cells. ### Provider APIs #### Register NVMEM Device ```c rt_err_t rt_nvmem_device_register(struct rt_nvmem_device *ndev); ``` **Parameters**: - `ndev`: NVMEM device to register **Returns**: `RT_EOK` on success, negative error code on failure **Description**: Register an NVMEM device with the framework. Initializes reference counting, parses device tree, and configures write-protect pin if present. #### Unregister NVMEM Device ```c rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *ndev); ``` **Parameters**: - `ndev`: NVMEM device to unregister **Returns**: `RT_EOK` on success, `-RT_EBUSY` if device is still in use **Description**: Unregister an NVMEM device. Fails if there are outstanding references. #### Append Cell to Device ```c rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *ndev, struct rt_nvmem_cell *cell); ``` **Parameters**: - `ndev`: NVMEM device - `cell`: Cell to append **Returns**: `RT_EOK` on success, negative error code on failure **Description**: Add a cell to an NVMEM device. Used for runtime cell registration. ## Complete Example: Ethernet Driver with MAC Address ### Device Tree ```dts /* EEPROM device with MAC address storage */ i2c0: i2c@40000000 { compatible = "vendor,i2c"; reg = <0x40000000 0x1000>; eeprom@50 { compatible = "atmel,24c32"; reg = <0x50>; #address-cells = <1>; #size-cells = <1>; /* MAC address cell */ eth_mac: mac@0 { reg = <0x00 6>; }; /* Device configuration */ eth_config: config@10 { reg = <0x10 16>; }; }; }; /* Ethernet controller referencing MAC address */ ethernet@50000000 { compatible = "vendor,ethernet"; reg = <0x50000000 0x10000>; interrupts = <32 IRQ_TYPE_LEVEL_HIGH>; clocks = <&cru CLK_EMAC>; clock-names = "stmmaceth"; /* Reference NVMEM for MAC address */ nvmem-cells = <ð_mac>; nvmem-cell-names = "mac-address"; }; ``` ### Ethernet Driver Implementation ```c #include #include #include struct ethernet_device { struct rt_device parent; void *base; int irq; rt_uint8_t mac_addr[6]; /* Other fields... */ }; static rt_err_t ethernet_load_mac_address(struct ethernet_device *eth_dev) { struct rt_nvmem_cell *cell; rt_ssize_t ret; /* Get MAC address cell from device tree */ cell = rt_nvmem_get_cell_by_name(ð_dev->parent, "mac-address"); if (!cell) { rt_kprintf("[ETH] No MAC address in NVMEM, using default\n"); /* Use default or random MAC */ eth_dev->mac_addr[0] = 0x00; eth_dev->mac_addr[1] = 0x11; eth_dev->mac_addr[2] = 0x22; eth_dev->mac_addr[3] = 0x33; eth_dev->mac_addr[4] = 0x44; eth_dev->mac_addr[5] = 0x55; return RT_EOK; } /* Read MAC address from NVMEM */ ret = rt_nvmem_cell_read(cell, eth_dev->mac_addr, 6); if (ret != 6) { rt_kprintf("[ETH] Failed to read MAC address: %d\n", ret); rt_nvmem_put_cell(cell); return -RT_ERROR; } /* Release cell */ rt_nvmem_put_cell(cell); rt_kprintf("[ETH] MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\n", eth_dev->mac_addr[0], eth_dev->mac_addr[1], eth_dev->mac_addr[2], eth_dev->mac_addr[3], eth_dev->mac_addr[4], eth_dev->mac_addr[5]); return RT_EOK; } static rt_err_t ethernet_write_mac_address(struct ethernet_device *eth_dev, const rt_uint8_t *mac) { struct rt_nvmem_cell *cell; rt_ssize_t ret; /* Get MAC address cell */ cell = rt_nvmem_get_cell_by_name(ð_dev->parent, "mac-address"); if (!cell) { rt_kprintf("[ETH] No MAC address cell available\n"); return -RT_ERROR; } /* Write new MAC address */ ret = rt_nvmem_cell_write(cell, (void *)mac, 6); if (ret != 6) { rt_kprintf("[ETH] Failed to write MAC address: %d\n", ret); rt_nvmem_put_cell(cell); return -RT_ERROR; } rt_nvmem_put_cell(cell); /* Update local copy */ rt_memcpy(eth_dev->mac_addr, mac, 6); rt_kprintf("[ETH] MAC Address updated successfully\n"); return RT_EOK; } static rt_err_t ethernet_probe(struct rt_platform_device *pdev) { struct ethernet_device *eth_dev; rt_err_t ret; eth_dev = rt_calloc(1, sizeof(*eth_dev)); if (!eth_dev) { return -RT_ENOMEM; } /* Initialize device */ eth_dev->parent.ofw_node = pdev->parent.ofw_node; /* Get hardware resources */ eth_dev->base = rt_dm_dev_get_address(&pdev->parent); if (!eth_dev->base) { rt_free(eth_dev); return -RT_ERROR; } /* Load MAC address from NVMEM */ ret = ethernet_load_mac_address(eth_dev); if (ret != RT_EOK) { rt_kprintf("[ETH] Warning: MAC address load failed\n"); } /* Configure hardware with MAC address */ /* ... hardware initialization ... */ rt_kprintf("[ETH] Ethernet initialized\n"); return RT_EOK; } static const struct rt_ofw_node_id ethernet_ofw_match[] = { { .compatible = "vendor,ethernet" }, { /* sentinel */ } }; static struct rt_platform_driver ethernet_driver = { .name = "vendor-ethernet", .ids = ethernet_ofw_match, .probe = ethernet_probe, }; RT_PLATFORM_DRIVER_EXPORT(ethernet_driver); ``` ## NVMEM Provider Driver Example ### I2C EEPROM Driver ```c #include #include #include #include struct eeprom_device { struct rt_nvmem_device nvmem; struct rt_i2c_bus_device *i2c_bus; rt_uint16_t i2c_addr; rt_size_t size; rt_uint32_t page_size; }; static rt_ssize_t eeprom_reg_read(struct rt_nvmem_device *ndev, int offset, void *val, rt_size_t bytes) { struct eeprom_device *eeprom; struct rt_i2c_msg msgs[2]; rt_uint8_t addr_buf[2]; rt_ssize_t ret; eeprom = rt_container_of(ndev, struct eeprom_device, nvmem); /* Check bounds */ if (offset + bytes > eeprom->size) { return -RT_EINVAL; } /* Prepare address (big-endian) */ addr_buf[0] = (offset >> 8) & 0xFF; addr_buf[1] = offset & 0xFF; /* Write address */ msgs[0].addr = eeprom->i2c_addr; msgs[0].flags = RT_I2C_WR; msgs[0].buf = addr_buf; msgs[0].len = 2; /* Read data */ msgs[1].addr = eeprom->i2c_addr; msgs[1].flags = RT_I2C_RD; msgs[1].buf = val; msgs[1].len = bytes; ret = rt_i2c_transfer(eeprom->i2c_bus, msgs, 2); if (ret != 2) { return -RT_ERROR; } return bytes; } static rt_ssize_t eeprom_reg_write(struct rt_nvmem_device *ndev, int offset, void *val, rt_size_t bytes) { struct eeprom_device *eeprom; struct rt_i2c_msg msg; rt_uint8_t *write_buf; rt_size_t written = 0; rt_ssize_t ret; eeprom = rt_container_of(ndev, struct eeprom_device, nvmem); /* Check bounds */ if (offset + bytes > eeprom->size) { return -RT_EINVAL; } write_buf = rt_malloc(eeprom->page_size + 2); if (!write_buf) { return -RT_ENOMEM; } /* Write page by page */ while (written < bytes) { rt_uint32_t page_offset = offset % eeprom->page_size; rt_size_t chunk = RT_MIN(bytes - written, eeprom->page_size - page_offset); /* Prepare write buffer: address + data */ write_buf[0] = (offset >> 8) & 0xFF; write_buf[1] = offset & 0xFF; rt_memcpy(&write_buf[2], (rt_uint8_t *)val + written, chunk); msg.addr = eeprom->i2c_addr; msg.flags = RT_I2C_WR; msg.buf = write_buf; msg.len = chunk + 2; ret = rt_i2c_transfer(eeprom->i2c_bus, &msg, 1); if (ret != 1) { rt_free(write_buf); return -RT_ERROR; } /* Wait for write cycle to complete (typ. 5ms for EEPROM) */ rt_thread_mdelay(5); written += chunk; offset += chunk; } rt_free(write_buf); return written; } static rt_err_t eeprom_probe(struct rt_platform_device *pdev) { struct eeprom_device *eeprom; struct rt_device *dev = &pdev->parent; rt_err_t ret; rt_uint32_t size; eeprom = rt_calloc(1, sizeof(*eeprom)); if (!eeprom) { return -RT_ENOMEM; } /* Get I2C bus */ eeprom->i2c_bus = rt_i2c_bus_device_find( rt_dm_dev_get_name_id(dev, NULL, 0, "i2c-bus")); if (!eeprom->i2c_bus) { rt_kprintf("[EEPROM] I2C bus not found\n"); rt_free(eeprom); return -RT_ERROR; } /* Get I2C address */ if (rt_dm_dev_prop_read_u32(dev, "reg", &eeprom->i2c_addr) != RT_EOK) { rt_kprintf("[EEPROM] No I2C address specified\n"); rt_free(eeprom); return -RT_ERROR; } /* Get EEPROM size (default 4KB for AT24C32) */ if (rt_dm_dev_prop_read_u32(dev, "size", &size) != RT_EOK) { size = 4096; /* Default 4KB */ } eeprom->size = size; /* Get page size (default 32 bytes) */ if (rt_dm_dev_prop_read_u32(dev, "pagesize", &eeprom->page_size) != RT_EOK) { eeprom->page_size = 32; } /* Initialize NVMEM device */ eeprom->nvmem.parent.ofw_node = dev->ofw_node; eeprom->nvmem.reg_read = eeprom_reg_read; eeprom->nvmem.reg_write = eeprom_reg_write; eeprom->nvmem.size = eeprom->size; eeprom->nvmem.word_size = 1; eeprom->nvmem.stride = 1; eeprom->nvmem.priv = eeprom; /* Register NVMEM device */ ret = rt_nvmem_device_register(&eeprom->nvmem); if (ret != RT_EOK) { rt_kprintf("[EEPROM] Failed to register NVMEM device\n"); rt_free(eeprom); return ret; } rt_kprintf("[EEPROM] Registered %d bytes at I2C address 0x%02X\n", eeprom->size, eeprom->i2c_addr); return RT_EOK; } static const struct rt_ofw_node_id eeprom_ofw_match[] = { { .compatible = "atmel,24c32" }, { .compatible = "atmel,24c64" }, { .compatible = "atmel,24c256" }, { /* sentinel */ } }; static struct rt_platform_driver eeprom_driver = { .name = "atmel-eeprom", .ids = eeprom_ofw_match, .probe = eeprom_probe, }; RT_PLATFORM_DRIVER_EXPORT(eeprom_driver); ``` ## Best Practices ### For Consumers 1. **Always Check Return Values** ```c cell = rt_nvmem_get_cell_by_name(dev, "calibration"); if (!cell) { /* Handle error - use default values */ } ``` 2. **Release Resources** ```c cell = rt_nvmem_get_cell_by_name(dev, "data"); if (cell) { rt_nvmem_cell_read(cell, buffer, size); rt_nvmem_put_cell(cell); /* Always release */ } ``` 3. **Handle Read-Only Memory** ```c ret = rt_nvmem_cell_write(cell, data, len); if (ret < 0) { if (ret == -RT_ENOSYS) { rt_kprintf("Memory is read-only\n"); } } ``` 4. **Use Typed Reads for Simple Values** ```c rt_uint32_t serial_number; cell = rt_nvmem_get_cell_by_name(dev, "serial"); if (cell) { rt_nvmem_cell_read_u32(cell, &serial_number); rt_nvmem_put_cell(cell); } ``` ### For Providers 1. **Implement Proper Bounds Checking** ```c if (offset + bytes > nvmem->size) { return -RT_EINVAL; } ``` 2. **Support Write Protection** ```c /* Framework handles wp-gpios automatically */ /* Just don't set ignore_wp flag */ nvmem->ignore_wp = RT_FALSE; ``` 3. **Handle Page-Based Writes** ```c /* For devices with page write limits */ /* Write in page-sized chunks and wait for completion */ rt_thread_mdelay(write_cycle_time_ms); ``` 4. **Initialize All Fields** ```c nvmem->size = total_size; nvmem->word_size = 1; /* Byte-addressable */ nvmem->stride = 1; /* Minimum access unit */ nvmem->reg_read = my_read; nvmem->reg_write = my_write; ``` ## Troubleshooting ### Cell Not Found **Problem**: `rt_nvmem_get_cell_by_name()` returns NULL **Solutions**: 1. Check device tree `nvmem-cells` and `nvmem-cell-names` properties match 2. Verify NVMEM provider device is probed before consumer 3. Check `#address-cells` and `#size-cells` in provider node ### Write Fails **Problem**: `rt_nvmem_cell_write()` returns error **Solutions**: 1. Check if memory is marked `read-only` in device tree 2. Verify write-protect pin is not active 3. Check provider's `reg_write` callback is implemented 4. Verify offset and size are within bounds ### Data Corruption **Problem**: Read data doesn't match written data **Solutions**: 1. Check page-write boundaries for page-based devices 2. Verify write cycle delays are sufficient 3. Add proper synchronization if accessing from multiple threads 4. Check bit-offset calculations for bit-addressable cells ## Performance Considerations ### Read Performance - **Caching**: Cache frequently-read values (e.g., MAC address) after first read - **Batch Reads**: Read multiple cells in one operation if possible - **Async Access**: Use separate thread for slow NVMEM operations ### Write Performance - **Write Caching**: Buffer writes and flush periodically - **Wear Leveling**: Distribute writes across memory for EEPROMs - **Write Coalescing**: Combine multiple small writes into larger operations ### Resource Usage - **Cell References**: Release cells promptly with `rt_nvmem_put_cell()` - **Memory**: Consider memory cost of buffering for page-based devices - **I/O**: EEPROM writes can take several milliseconds per page ## Related Modules - **OFW (Open Firmware)**: Device tree parsing for NVMEM configuration - **PIN**: Write-protect GPIO handling - **I2C/SPI**: Common buses for NVMEM devices ## References - NVMEM header: `components/drivers/include/drivers/nvmem.h` - NVMEM implementation: `components/drivers/nvmem/nvmem.c` - Linux NVMEM framework: Documentation reference for compatible design