LXR | KVM | PM | Time | Interrupt | Systems Performance | Bootup Optimization

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

posted on 2024-05-26 23:59  ArnoldLu  阅读(788)  评论(0编辑  收藏  举报

导航