Linux NVMEM子系统:概述以及RK3588 OTP实例
NVMEM子系统为Non-Volatile类型存储提供统一内核处理框架。
1 NVMEM概述
NVMEM子系统为eeprom,otp,efuse类型设备提供统一的访问接口。一般都基于regmap实现读写。
NVMEM子系统初始化:
nvmem_init
bus_register--注册NVMEM总线nvmem_bus_type。
2 NVMEM API和数据结构
2.1 NVMEM Provider
struct nvmem_config表示一个NVMEM设备的配置, 作为nvmem_register()参数注册到NVMEM子系统,返货struct nvmem_device。
struct nvmem_config { struct device *dev; const char *name;--NVMEM设备名称。 int id; struct module *owner; struct gpio_desc *wp_gpio;--写保护GPIO。 const struct nvmem_cell_info *cells;--预定义cell列表。 int ncells; enum nvmem_type type; bool read_only;--只读。 bool root_only;--仅root用户可访问。 bool no_of_node; nvmem_reg_read_t reg_read;--对NVMEM读回调函数。 nvmem_reg_write_t reg_write;--对NVMEM写回调函数。 int size; int word_size; int stride; void *priv; /* To be only used by old driver/misc/eeprom drivers */ bool compat; struct device *base_dev; };
devm_nvmem_register/devm_nvmem_unregister是NVMEM设备的注册和注销函数:
struct nvmem_device *nvmem_register(const struct nvmem_config *cfg); void nvmem_unregister(struct nvmem_device *nvmem); struct nvmem_device *devm_nvmem_register(struct device *dev, const struct nvmem_config *cfg); int devm_nvmem_unregister(struct device *dev, struct nvmem_device *nvmem);
devm_nvmem_register()是带资源管理的NVMEM注册函数:
devm_nvmem_register
nvmem_register
gpiod_get_optional--获取write protect引脚。
device_property_present--判断dts设置是否为read-only。
device_register--注册设备,设备属性位nvmem_dev_groups。创建binary类型的nvmem属性节点,type表示类型的属性节点。
nvmem_sysfs_setup_compat
nvmem_add_cells--根据nvmem_config中的cells创建cell。
nvmem_add_cells_from_table--根据nvmem_cell_tables创建cell。
nvmem_add_cells_from_of--根据dts创建cell。
对nvmem属性读写,最终会调用具体设备的struct nvmem_config的读写接口:
static struct bin_attribute bin_attr_rw_nvmem = { .attr = { .name = "nvmem", .mode = 0644, }, .read = bin_attr_nvmem_read, .write = bin_attr_nvmem_write, };
bin_attr_nvmem_write
nvmem_reg_write
nvmem->reg_write
bin_attr_nvmem_read
nvmem_reg_read
nvmem->reg_read
struct nvmem_cell_table是表示一系列NVMEM Cell的组合列表:
struct nvmem_cell_table { const char *nvmem_name; const struct nvmem_cell_info *cells; size_t ncells; struct list_head node; };
2.2 NVMEM Consumer
struct nvmem_cell_info表示一个cell:
struct nvmem_cell_info { const char *name;--名称。 unsigned int offset;--byte为单位的偏移。 unsigned int bytes;--byte为单位的长度。 unsigned int bit_offset;--bit为单位的偏移。 unsigned int nbits;--bit为单位的长度。 };
获取struct device后,根据cell名称进行操作,获取cell:
struct nvmem_cell *nvmem_cell_get(struct device *dev, const char *id); struct nvmem_cell *devm_nvmem_cell_get(struct device *dev, const char *id); void nvmem_cell_put(struct nvmem_cell *cell); void devm_nvmem_cell_put(struct device *dev, struct nvmem_cell *cell);
对cell进行读写:
void *nvmem_cell_read(struct nvmem_cell *cell, size_t *len); int nvmem_cell_write(struct nvmem_cell *cell, void *buf, size_t len); int nvmem_cell_read_u8(struct device *dev, const char *cell_id, u8 *val); int nvmem_cell_read_u16(struct device *dev, const char *cell_id, u16 *val); int nvmem_cell_read_u32(struct device *dev, const char *cell_id, u32 *val); int nvmem_cell_read_u64(struct device *dev, const char *cell_id, u64 *val);
获取struct device后,根据NVMEM设备名称获取struct nvmem_device后直接对裸设备进行读写:
struct nvmem_device *nvmem_device_get(struct device *dev, const char *name); struct nvmem_device *devm_nvmem_device_get(struct device *dev, const char *name); void nvmem_device_put(struct nvmem_device *nvmem); void devm_nvmem_device_put(struct device *dev, struct nvmem_device *nvmem); int nvmem_device_read(struct nvmem_device *nvmem, unsigned int offset, size_t bytes, void *buf); int nvmem_device_write(struct nvmem_device *nvmem, unsigned int offset, size_t bytes, void *buf); ssize_t nvmem_device_cell_read(struct nvmem_device *nvmem, struct nvmem_cell_info *info, void *buf); int nvmem_device_cell_write(struct nvmem_device *nvmem, struct nvmem_cell_info *info, void *buf); const char *nvmem_dev_name(struct nvmem_device *nvmem); void nvmem_add_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries); void nvmem_del_cell_lookups(struct nvmem_cell_lookup *entries, size_t nentries);
3 NVMEM驱动(RK3588 OTP)
3.1 RK3588 OTP DTS
dts中除了及地址/时钟/复位配置外,还包括一系列NVMEM Cell定义:
otp: otp@fecc0000 { compatible = "rockchip,rk3588-otp"; reg = <0x0 0xfecc0000 0x0 0x400>; #address-cells = <1>; #size-cells = <1>; clocks = <&cru CLK_OTPC_NS>, <&cru PCLK_OTPC_NS>, <&cru CLK_OTPC_ARB>, <&cru CLK_OTP_PHY_G>; clock-names = "otpc", "apb", "arb", "phy"; resets = <&cru SRST_OTPC_NS>, <&cru SRST_P_OTPC_NS>, <&cru SRST_OTPC_ARB>; reset-names = "otpc", "apb", "arb"; /* Data cells */ cpu_code: cpu-code@2 { reg = <0x02 0x2>; }; ... };
3.2 RK3588 OTP驱动
Rockchip OTP作为NVMEM设备初始化如下:
rockchip_otp_init
rockchip_otp_driver
rockchip_otp_probe
devm_nvmem_register--注册NVMEM设备,默认配struct nvmem_config为otp_config。
otp_config中定义了OTP设备的通用配置:
static struct nvmem_config otp_config = { .name = "rockchip-otp", .owner = THIS_MODULE, .read_only = true, .reg_read = rockchip_otp_read,--此接口会在对nvmem属性节点读时调用,再去调用具体设备的读函数。 .reg_write = rockchip_otp_write,--此接口会在对nvmem属性节点写时调用,再去调用具体设备的写函数。 .stride = 1, .word_size = 1, };
不同的设备差异通过of_device_id->data区分:
static const struct of_device_id rockchip_otp_match[] = { #ifdef CONFIG_CPU_RK3588 { .compatible = "rockchip,rk3588-otp", .data = (void *)&rk3588_data, }, #endif { /* sentinel */ }, }; static const struct rockchip_data rk3588_data = { .size = 0x400, .clocks = rk3588_otp_clocks, .num_clks = ARRAY_SIZE(rk3588_otp_clocks), .reg_read = rk3588_otp_read, };
3.3 NVMEM Consumer驱动
cpuinfo作为NVMEM Consumer,调用cpu_code节点:
cpuinfo { compatible = "rockchip,cpuinfo"; nvmem-cells = <&otp_id>, <&otp_cpu_version>, <&cpu_code>;--NVMEM设备提供过的句柄。 nvmem-cell-names = "id", "cpu-version", "cpu-code"; };
在设备驱动的中,通过
rockchip_cpuinfo_init
rockchip_cpuinfo_driver
rockchip_cpuinfo_probe
nvmem_cell_get--获取Cell,然后读取内容。
nvmem_cell_read
nvmem_cell_put
4 用户空间接口
NVMEM设备节点如下:
/sys/devices/platform/fecc0000.otp/rockchip-otp0/ |-- nvmem |-- of_node -> ../../../../firmware/devicetree/base/otp@fecc0000 |-- power | |-- async | |-- autosuspend_delay_ms | |-- control | |-- runtime_active_kids | |-- runtime_active_time | |-- runtime_enabled | |-- runtime_status | |-- runtime_suspended_time | `-- runtime_usage |-- subsystem -> ../../../../bus/nvmem |-- type `-- uevent
通过hexdump访问:
hexdump /sys/devices/platform/fecc0000.otp/rockchip-otp0/nvmem 0000000 4b52 8835 fe12 4121 4e32 4b35 0000 0000 0000010 0000 0000 1202 0b0a 0e0b 1630 0008 0000 0000020 0000 0000 0000 0000 120c 0000 3100 1c08 0000030 6551 5c09 2809 2907 ec07 f703 0003 0000 0000040 0000 0000 0000 0000 0000 0000 0000 0000 * 0000400