|
|
1 неделя назад | |
|---|---|---|
| .. | ||
| README.md | 1 неделя назад | |
| README_zh.md | 1 неделя назад | |
Non-Volatile Memory (NVMEM) refers to computer memory that can retain stored information even when power is removed. Common examples include:
NVMEM devices are commonly used to store:
RT-Thread's NVMEM framework provides a unified abstraction layer for accessing various types of non-volatile memory devices. The framework supports:
The framework is located in:
components/drivers/include/drivers/nvmem.hcomponents/drivers/nvmem/nvmem.cRT-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)
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 enabledRT_USING_OFW: Open Firmware (Device Tree) support requiredRT_USING_PIN: PIN driver framework for write-protect pinsDescription: Enables the NVMEM framework providing unified access to non-volatile memory devices like EEPROM, OTP, eFuse, etc.
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.
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 */
};
};
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";
};
| 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 |
| Property | Type | Description |
|---|---|---|
reg |
u32 array | <offset size> - Memory region for this cell |
bits |
u32 array | <bit_offset nbits> - Bit-level addressing |
| Property | Type | Description |
|---|---|---|
nvmem-cells |
phandle | Reference to NVMEM cells |
nvmem-cell-names |
string array | Names for referenced cells |
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 {
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 */
};
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 deviceid: Cell name from device tree nvmem-cell-namesindex: 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:
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);
void rt_nvmem_put_cell(struct rt_nvmem_cell *cell);
Parameters:
cell: NVMEM cell to releaseDescription: Release a reference to an NVMEM cell obtained with rt_nvmem_get_cell_*().
rt_ssize_t rt_nvmem_cell_read(struct rt_nvmem_cell *cell,
void *buffer, rt_size_t len);
Parameters:
cell: NVMEM cell to readbuffer: Buffer to store read datalen: Number of bytes to readReturns: Number of bytes read on success, negative error code on failure
Description: Read data from an NVMEM cell. Supports bit-level access if configured.
rt_ssize_t rt_nvmem_cell_write(struct rt_nvmem_cell *cell,
void *buffer, rt_size_t len);
Parameters:
cell: NVMEM cell to writebuffer: Data to writelen: Number of bytes to writeReturns: 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.
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 readout_val: Pointer to store the valueReturns: Number of bytes read on success, negative error code on failure
Description: Convenience functions to read specific integer types from NVMEM cells.
rt_err_t rt_nvmem_device_register(struct rt_nvmem_device *ndev);
Parameters:
ndev: NVMEM device to registerReturns: 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.
rt_err_t rt_nvmem_device_unregister(struct rt_nvmem_device *ndev);
Parameters:
ndev: NVMEM device to unregisterReturns: RT_EOK on success, -RT_EBUSY if device is still in use
Description: Unregister an NVMEM device. Fails if there are outstanding references.
rt_err_t rt_nvmem_device_append_cell(struct rt_nvmem_device *ndev,
struct rt_nvmem_cell *cell);
Parameters:
ndev: NVMEM devicecell: Cell to appendReturns: RT_EOK on success, negative error code on failure
Description: Add a cell to an NVMEM device. Used for runtime cell registration.
/* 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";
};
#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/nvmem.h>
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);
#include <rtthread.h>
#include <rtdevice.h>
#include <drivers/nvmem.h>
#include <drivers/i2c.h>
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);
Always Check Return Values
cell = rt_nvmem_get_cell_by_name(dev, "calibration");
if (!cell) {
/* Handle error - use default values */
}
Release Resources
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 */
}
Handle Read-Only Memory
ret = rt_nvmem_cell_write(cell, data, len);
if (ret < 0) {
if (ret == -RT_ENOSYS) {
rt_kprintf("Memory is read-only\n");
}
}
Use Typed Reads for Simple Values
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);
}
Implement Proper Bounds Checking
if (offset + bytes > nvmem->size) {
return -RT_EINVAL;
}
Support Write Protection
/* Framework handles wp-gpios automatically */
/* Just don't set ignore_wp flag */
nvmem->ignore_wp = RT_FALSE;
Handle Page-Based Writes
/* For devices with page write limits */
/* Write in page-sized chunks and wait for completion */
rt_thread_mdelay(write_cycle_time_ms);
Initialize All Fields
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;
Problem: rt_nvmem_get_cell_by_name() returns NULL
Solutions:
nvmem-cells and nvmem-cell-names properties match#address-cells and #size-cells in provider nodeProblem: rt_nvmem_cell_write() returns error
Solutions:
read-only in device treereg_write callback is implementedProblem: Read data doesn't match written data
Solutions:
rt_nvmem_put_cell()components/drivers/include/drivers/nvmem.hcomponents/drivers/nvmem/nvmem.c