mirror of
https://github.com/RT-Thread/rt-thread.git
synced 2026-02-04 21:11:40 +00:00
[documentation][device_driver_model] Add NVMEM framework documentation (EN) - module 5/26
Co-authored-by: BernardXiong <1241087+BernardXiong@users.noreply.github.com>
This commit is contained in:
@@ -0,0 +1,859 @@
|
||||
# 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 | `<offset size>` - Memory region for this cell |
|
||||
| `bits` | u32 array | `<bit_offset nbits>` - 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 <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);
|
||||
```
|
||||
|
||||
## NVMEM Provider Driver Example
|
||||
|
||||
### I2C EEPROM Driver
|
||||
|
||||
```c
|
||||
#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);
|
||||
```
|
||||
|
||||
## 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
|
||||
Reference in New Issue
Block a user