nvmem 架构

nvmem是针对非易失性,例如eeprom, efuses等的一个抽象层,nvmem分为consumer和provide,其在kernel中的位置为drivers/nvmem
其中provide 最终要的就是调用nvmem_register,其一般的模板如下:
static struct nvmem_config econfig = {
    .name = "qfprom",
    .owner = THIS_MODULE,
};

static int qfprom_probe(struct platform_device *pdev)
{
    ...
    econfig.dev = &pdev->dev;
    nvmem = nvmem_register(&econfig);
    ...
}
而consumer 可以用下面这些API 使用provide提供的设备例如eeprom, efuses等
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);
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);
int 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);

obj-$(CONFIG_NVMEM)        += nvmem_core.o
nvmem_core-y            := core.o

# Devices
obj-$(CONFIG_NVMEM_IMX_OCOTP)    += nvmem-imx-ocotp.o
nvmem-imx-ocotp-y        := imx-ocotp.o
obj-$(CONFIG_NVMEM_LPC18XX_EEPROM)    += nvmem_lpc18xx_eeprom.o
nvmem_lpc18xx_eeprom-y    := lpc18xx_eeprom.o
obj-$(CONFIG_NVMEM_MXS_OCOTP)    += nvmem-mxs-ocotp.o
nvmem-mxs-ocotp-y        := mxs-ocotp.o
obj-$(CONFIG_MTK_EFUSE)        += nvmem_mtk-efuse.o
nvmem_mtk-efuse-y        := mtk-efuse.o
obj-$(CONFIG_QCOM_QFPROM)    += nvmem_qfprom.o
nvmem_qfprom-y            := qfprom.o
obj-$(CONFIG_ROCKCHIP_EFUSE)    += nvmem_rockchip_efuse.o
nvmem_rockchip_efuse-y        := rockchip-efuse.o
obj-$(CONFIG_NVMEM_SUNXI_SID)    += nvmem_sunxi_sid.o
nvmem_sunxi_sid-y        := sunxi_sid.o
obj-$(CONFIG_NVMEM_VF610_OCOTP)    += nvmem-vf610-ocotp.o
nvmem-vf610-ocotp-y        := vf610-ocotp.o
obj-$(CONFIG_MESON_EFUSE)    += nvmem_meson_efuse.o
nvmem_meson_efuse-y        := meson-efuse.o
从drivers/nvmem里面的makefile中可以看到核心文件就一个core.c 这个文件通过provide和consumer 使用的API。这里以mtk-efuse.c为例

static int mtk_reg_read(void *context,
            unsigned int reg, void *_val, size_t bytes)
{
    void __iomem *base = context;
    u32 *val = _val;
    int i = 0, words = bytes / 4;

    while (words--)
        *val++ = readl(base + reg + (i++ * 4));

    return 0;
}

static int mtk_reg_write(void *context,
             unsigned int reg, void *_val, size_t bytes)
{
    void __iomem *base = context;
    u32 *val = _val;
    int i = 0, words = bytes / 4;

    while (words--)
        writel(*val++, base + reg + (i++ * 4));

    return 0;
}

static int mtk_efuse_probe(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev;
    struct resource *res;
    struct nvmem_device *nvmem;
    struct nvmem_config *econfig;
    void __iomem *base;

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);
// 申请一个nvmem_config结构
    econfig = devm_kzalloc(dev, sizeof(*econfig), GFP_KERNEL);
    if (!econfig)
        return -ENOMEM;
//给nvmem_config *econfig 赋值
    econfig->stride = 4;
    econfig->word_size = 4;
    econfig->reg_read = mtk_reg_read;
    econfig->reg_write = mtk_reg_write;
    econfig->size = resource_size(res);
    econfig->priv = base;
    econfig->dev = dev;
    econfig->owner = THIS_MODULE;
//调用提供provide的注册函数
    nvmem = nvmem_register(econfig);
    if (IS_ERR(nvmem))
        return PTR_ERR(nvmem);

    platform_set_drvdata(pdev, nvmem);

    return 0;
}

static int mtk_efuse_remove(struct platform_device *pdev)
{
    struct nvmem_device *nvmem = platform_get_drvdata(pdev);

    return nvmem_unregister(nvmem);
}

static const struct of_device_id mtk_efuse_of_match[] = {
    { .compatible = "mediatek,mt8173-efuse",},
    { .compatible = "mediatek,efuse",},
    {/* sentinel */},
};
MODULE_DEVICE_TABLE(of, mtk_efuse_of_match);

static struct platform_driver mtk_efuse_driver = {
    .probe = mtk_efuse_probe,
    .remove = mtk_efuse_remove,
    .driver = {
        .name = "mediatek,efuse",
        .of_match_table = mtk_efuse_of_match,
    },
};

static int __init mtk_efuse_init(void)
{
    int ret;

    ret = platform_driver_register(&mtk_efuse_driver);
    if (ret) {
        pr_err("Failed to register efuse driver\n");
        return ret;
    }

    return 0;
}
subsys_initcall(mtk_efuse_init);

可以看到mtk-efuse.c 是按照provide的模板写的,其最重要在其probe函数中调用nvmem_register
struct nvmem_device *nvmem_register(const struct nvmem_config *config)
{
    struct nvmem_device *nvmem;
    struct device_node *np;
    int rval;

    if (!config->dev)
        return ERR_PTR(-EINVAL);

    nvmem = kzalloc(sizeof(*nvmem), GFP_KERNEL);
    if (!nvmem)
        return ERR_PTR(-ENOMEM);

    rval  = ida_simple_get(&nvmem_ida, 0, 0, GFP_KERNEL);
    if (rval < 0) {
        kfree(nvmem);
        return ERR_PTR(rval);
    }

    nvmem->id = rval;
    nvmem->owner = config->owner;
    nvmem->stride = config->stride;
    nvmem->word_size = config->word_size;
    nvmem->size = config->size;
    nvmem->dev.type = &nvmem_provider_type;
    nvmem->dev.bus = &nvmem_bus_type;
    nvmem->dev.parent = config->dev;
    nvmem->priv = config->priv;
    nvmem->reg_read = config->reg_read;
    nvmem->reg_write = config->reg_write;
    np = config->dev->of_node;
    nvmem->dev.of_node = np;
    dev_set_name(&nvmem->dev, "%s%d",
             config->name ? : "nvmem", config->id);

    nvmem->read_only = of_property_read_bool(np, "read-only") |
               config->read_only;

    if (config->root_only)
        nvmem->dev.groups = nvmem->read_only ?
            nvmem_ro_root_dev_groups :
            nvmem_rw_root_dev_groups;
    else
        nvmem->dev.groups = nvmem->read_only ?
            nvmem_ro_dev_groups :
            nvmem_rw_dev_groups;

    device_initialize(&nvmem->dev);

    dev_dbg(&nvmem->dev, "Registering nvmem device %s\n", config->name);

    rval = device_add(&nvmem->dev);
    if (rval)
        goto out;

    if (config->compat) {
        rval = nvmem_setup_compat(nvmem, config);
        if (rval)
            goto out;
    }

    if (config->cells)
        nvmem_add_cells(nvmem, config);

    return nvmem;
out:
    ida_simple_remove(&nvmem_ida, nvmem->id);
    kfree(nvmem);
    return ERR_PTR(rval);
}
在nvmem_register 中有申请一个nvmem_device *nvmem; 最终要的就是
    nvmem->reg_read = config->reg_read;
    nvmem->reg_write = config->reg_write;
这样当consumer调用nvmem_device_write 来写/读 provide的时候
int nvmem_device_write(struct nvmem_device *nvmem,
               unsigned int offset,
               size_t bytes, void *buf)
{
    int rc;

    if (!nvmem)
        return -EINVAL;

    rc = nvmem_reg_write(nvmem, offset, buf, bytes);

    if (rc)
        return rc;


    return bytes;
}

static int nvmem_reg_write(struct nvmem_device *nvmem, unsigned int offset,
               void *val, size_t bytes)
{
    if (nvmem->reg_write)
        return nvmem->reg_write(nvmem->priv, offset, val, bytes);

    return -EINVAL;
}
可见在nvmem_reg_write 中最终调用nvmem->reg_write。而nvmem->reg_write 是在nvmem_register 中赋值的
nvmem->reg_read = config->reg_read;
nvmem->reg_write = config->reg_write;
对应本例就是
    econfig->reg_read = mtk_reg_read;
    econfig->reg_write = mtk_reg_write;

对应user space就可以直接读写/sys/bus/nvmem/devices/*/nvmem
Userspace can read/write the raw NVMEM file located at
/sys/bus/nvmem/devices/*/nvmem

ex:

hexdump /sys/bus/nvmem/devices/qfprom0/nvmem

0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
00000a0 db10 2240 0000 e000 0c00 0c00 0000 0c00
0000000 0000 0000 0000 0000 0000 0000 0000 0000
...
*
0001000
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值