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

以PMIC为例简析Linux MFD/Regmap/Regulator的使用

 关键词:ADI、SPI、Regmap、MFD、Regulator、PMIC等等。

 以SC27XX为例,梳理一个PMIC用到的内核模块。

1. MFD框架

MFD是Multi-Function Device,MFD子系统是Linux下一种用于管理和控制多功能设备的软件框架。他提供一种统一接口,使得多个设备可以通过一个驱动程序进行管理和控制。

Kernel使能MFD:

Device Drivers
    ->Multifunction device drivers

MFD是多个具有类似功能设备的集合,每一个功能称为一个cell。MFD为每个cell创建一个platform设备。

整个MFD包括3部分:

  • MFD Core提供MFD设备的注册去注册,以及对Cell的操作。
  • MFD设备驱动。
  • MFD Cell描述符,一个描述符对应一个MFD设备单元。MFD为每个Cell创建一个Platform设备。

MFD主要功能包括:

  • MFD对所属设备进行统一关键,包括使能和去使能,以及suspend/resume。
  • MFD进行统一中断处理。
  • MFD负责设备注册和注销。

MFD常用于PMIC设备。

1.1 MFD Core

MFD Core设备注册和去注册API如下:

extern int mfd_add_devices(struct device *parent, int id,
               const struct mfd_cell *cells, int n_devs,
               struct resource *mem_base,
               int irq_base, struct irq_domain *irq_domain);

static inline int mfd_add_hotplug_devices(struct device *parent,
        const struct mfd_cell *cells, int n_devs)
{
    return mfd_add_devices(parent, PLATFORM_DEVID_AUTO, cells, n_devs,
            NULL, 0, NULL);
}

extern void mfd_remove_devices(struct device *parent);

extern int devm_mfd_add_devices(struct device *dev, int id,
                const struct mfd_cell *cells, int n_devs,
                struct resource *mem_base,
                int irq_base, struct irq_domain *irq_domain);

mfd_add_devices()注册一个MFD主设备,devm_mfd_add_devices是带设备资源管理的API变种。

mfd_remove_devices()是将一个MFD去注册接口。

parent:MFD主设备,后面Cell创建的设备挂载在下面。

id:表示后续创建Cell Platform设备时ID如何创建,包括PLATFORM_DEVID_NONE和PLATFORM_DEVID_AUTO两种方式。

cells:struct mfd_cell结构体数组,每一个成员表示一个Cell。

n_devs:Cell数量。

mem_base:内存地址基准。

irq_base:irq号基准。

irq_domain:当前MFD设备的struct irq_domain。

关于Cell的操作:

extern int mfd_cell_enable(struct platform_device *pdev);--调用Cell的enable回调函数。
extern int mfd_cell_disable(struct platform_device *pdev);--调用Cell的disable回调函数。
extern int mfd_clone_cell(const char *cell, const char **clones,
        size_t n_clones);--一个Cell被多个驱动使用,创建一系列别名。
static inline const struct mfd_cell *mfd_get_cell(struct platform_device *pdev)
{
    return pdev->mfd_cell;
}

MFD中使用struct mfd_cell表示:

struct mfd_cell {
    const char        *name;--名称。
    int            id;--Cell ID号。
    atomic_t        *usage_count;
    int            (*enable)(struct platform_device *dev);
    int            (*disable)(struct platform_device *dev);
    int            (*suspend)(struct platform_device *dev);
    int            (*resume)(struct platform_device *dev);
    void            *platform_data;--平台私有数据指针。
    size_t            pdata_size;
    struct property_entry *properties;
    const char        *of_compatible;--和DTS中Device匹配关键词。
    const struct mfd_cell_acpi_match    *acpi_match;
    int            num_resources;
    const struct resource    *resources;
    bool            ignore_resource_conflicts;
    bool            pm_runtime_no_callbacks;
    const char * const    *parent_supplies;
    int            num_parent_supplies;
};

2 Regmap框架

对于I2C/SPI类型的外设,对其进行读写有很多重复逻辑。Linux引入Regmap模型,Regmap将寄存器访问共同逻辑抽象出来,只需关注寄存器和值,屏蔽接口差异。

Regmap提供统一接口函数来访问外设寄存器,减少I2C/SPI等外设驱动开发难度。

Regmap在驱动和硬件之间加了一层cache,降低了低俗IO操作次数,提高了访问效率,但是降低了实时性。

Regmap可以分为3层:

Regmap Core:提供Regmap API接口,实现Regmap机制。包括regmap.c。

regcache:提供Regmap API和底层物理总线之间的Cache层,包括flat、rbtree、lzo共3中方式。包括regcache.c、regcache-flag.c、regcache-lzo.c、regcache-rbtree.c。

底层总线接口:执行数据读写的底层物理总线封装,包括I2C、SPI、IRQ等等。包括regmap-spi.c、regmap-irq.c等。

另外regmap debugfs在/sys/kernel/debug/regmap下提供对regmap的调试接口。

 2.1 Regmap数据结构

struct regmap_bus是底层物理总线接口接入到Regmap框架结构体。一个struct regmap_bus表示一个底层物理总线接口,在注册时调用。

struct regmap_bus {
    bool fast_io;
    regmap_hw_write write;--写一定大小数据到外设。
    regmap_hw_gather_write gather_write;
    regmap_hw_async_write async_write;
    regmap_hw_reg_write reg_write;--写值到单个寄存器。
    regmap_hw_reg_update_bits reg_update_bits;
    regmap_hw_read read;--读一定大小数据到buffer。
    regmap_hw_reg_read reg_read;--从单个寄存器读取值。
    regmap_hw_free_context free_context;
    regmap_hw_async_alloc async_alloc;
    u8 read_flag_mask;
    enum regmap_endian reg_format_endian_default;--寄存器地址大小端设置。
    enum regmap_endian val_format_endian_default;--寄存器值大小端设置。
    size_t max_raw_read;
    size_t max_raw_write;
};

struct regmap_bus是描述物理总线在Regmap中的特性。

struct regmap_config {
    const char *name;

    int reg_bits;--寄存器地址位宽。
    int reg_stride;--寄存器地址步进。
    int pad_bits;
    int val_bits;--寄存器数据位宽。
...
    bool disable_locking;
    regmap_lock lock;
    regmap_unlock unlock;
    void *lock_arg;

    int (*reg_read)(void *context, unsigned int reg, unsigned int *val);
    int (*reg_write)(void *context, unsigned int reg, unsigned int val);

    bool fast_io;

    unsigned int max_register;--寄存器地址最大值。
...
};

2.2 Regmap接口函数

regmap_init是Regmap的注册接口函数,可以适配各种总线接口。regmap_init_i2c以及类似函数,是注册特定总线接口到Regmap的函数。

devm_开头的注册函数是带Device资源管理接口。

regmap_init(dev, bus, bus_context, config)
regmap_init_i2c(i2c, config)
...
devm_regmap_init(dev, bus, bus_context, config)
devm_regmap_init_i2c(i2c, config)

regmap_exit()是Regmap的注销函数。

regmap_read()/regmap_write()是Regmap读写函数。

其中map是Regmap注册返回的strcut regmap结构体,在Regmap内部表示一个已经注册的Regmap。

int regmap_write(struct regmap *map, unsigned int reg, unsigned int val)
int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val)

2.3 Regmap IRQ

struct regmap_irq_chip用于描述Regmap下一个irq_chip。

通过regmap_add_irq_chip()将struct regmap_irq_chip加入到Regmap,返回strcut regmap_irq_chip_data,其中包括struct irq_domain。

int regmap_add_irq_chip(struct regmap *map, int irq, int irq_flags,
            int irq_base, const struct regmap_irq_chip *chip,
            struct regmap_irq_chip_data **data);
void regmap_del_irq_chip(int irq, struct regmap_irq_chip_data *data);

int devm_regmap_add_irq_chip(struct device *dev, struct regmap *map, int irq,
                 int irq_flags, int irq_base,
                 const struct regmap_irq_chip *chip,
                 struct regmap_irq_chip_data **data);
void devm_regmap_del_irq_chip(struct device *dev, int irq,
                  struct regmap_irq_chip_data *data);

int regmap_irq_chip_get_base(struct regmap_irq_chip_data *data);
int regmap_irq_get_virq(struct regmap_irq_chip_data *data, int irq);
struct irq_domain *regmap_irq_get_domain(struct regmap_irq_chip_data *data);

3 Regulator框架

Regulator Driver将Regulator注册到Regulator框架,Regulator Consumer根据名称获取到Regulator。然后对Regulator进行开关或者电压设置。

 Regulator注册和注销接口:

struct regulator_dev *
regulator_register(const struct regulator_desc *regulator_desc,
           const struct regulator_config *config);
struct regulator_dev *
devm_regulator_register(struct device *dev,
            const struct regulator_desc *regulator_desc,
            const struct regulator_config *config);
void regulator_unregister(struct regulator_dev *rdev);
void devm_regulator_unregister(struct device *dev, struct regulator_dev *rdev);

 Regulator获取和释放:

struct regulator *__must_check regulator_get(struct device *dev,
                         const char *id);
struct regulator *__must_check devm_regulator_get(struct device *dev,
                         const char *id);
void regulator_put(struct regulator *regulator);
void devm_regulator_put(struct regulator *regulator);

Regulator开关好和电压设置:

int __must_check regulator_enable(struct regulator *regulator);
int regulator_disable(struct regulator *regulator);
int regulator_force_disable(struct regulator *regulator);
int regulator_set_voltage(struct regulator *regulator, int min_uV, int max_uV);
int regulator_get_voltage(struct regulator *regulator);

关于Regulator更多参考:《Linux Regulator Framework(1)_概述 (wowotech.net)》《Linux Regulator Framework(2)_regulator driver (wowotech.net)》。

4 PMIC(SC27XX)驱动框架

SC27XX芯片框架如下:

SOC借用SPI框架处理和PMIC的ADI接口通信。

PMIC驱动注册MFD设备,为每个功能创建设备;并基于Regmap-SPI建立寄存器通信通道。

内核为每个LDO/DCDC创建Regulator设备,内核其他驱动通过通过Regulator接口进行开关和电压设置。

 4.1 ADI驱动

ADI驱动初始化:

  • 分配一个SPI Master,并注册。
  • 初始化ADI硬件接口。
sprd_adi_init
  ->sprd_adi_probe
    ->spi_alloc_master--SOC ADI作为SPI Master分配。
    ->spi_controller_get_devdata
    ->sprd_adi_hw_init--ADI接口初始化。
    ->sprd_adi_set_wdt_rst_mode
    ->devm_spi_register_controller--将分配的SPI Master注册到SPI Framework。
    ->register_restart_handler--注册一个系统Restart的回调函数到restart_handler_list。
sprd_adi_exit
  ->sprd_adi_remove
    ->unregister_restart_handler

 ADI初始化完成后,挂载在下面的设备就可以通过调用SPI读写函数进行寄存器设置。

4.2 PMIC驱动

PMIC驱动比较简单,首先基于ADI接口SPI总线注册到Regmap;然后将每个功能作为MFD Cell创建设备。

sprd_pmic_init
    ->sprd_pmic_probe
        ->devm_regmap_init--将基于ADI的SPI总线注册到Regmap框架。
        ->devm_regmap_add_irq_chip--将PMIC的irq注册到Regmap,给MFD使用。
        ->devm_mfd_add_devices--注册MFD Cell,创建Platform设备。
sprd_pmic_exit

4.3 Regulator驱动

Regulator驱动获取PMIC创建的struct regmap结构体,后续的寄存器读写都基于Regmap进行。并创建一系列debugfs用于调试。最后将PMIC Regulator注册Regulator框架中。

regu_driver_init
  ->sprd_regulator_probe
    ->dev_get_regmap--获取父设备的struct regmap。
    ->debugfs_create_dir--创建/sys/kernel/debug/regulator下的调试文件。
    ->regulator_register_dt--在当前设备树中找到regulators节点,遍历并注册下面的regulator。
      -->regulator_parse_dt-->解析单个regulator节点,将属性写入struct sprd_regulator_desc中。
      ->regulator_register-->struct regulator_desc是描述regulator结构体,struct regulator_config是对regulator配置。
      ->rdev_init_debugfs-->在debugfs下创建对regulator调试的节点。

4.4 Regulator使用

一个典型的Regulator使用如下:

  • 根据名称获取Regulator。
  • 使能Regulator。
  • 读取或者设置电压。
  • 关闭Regulator。
    st->reg_vdd = devm_regulator_get(&spi->dev, "vdd");
    if (!IS_ERR(st->reg_vdd)) {
        ret = regulator_enable(st->reg_vdd);
        if (ret)
            return ret;

        ret = regulator_get_voltage(st->reg_vdd);
        if (ret < 0)
            goto error_disable_reg_pos;

        pos_voltage_uv = ret;
    }
    regulator_disable(st->reg_vdd);

 

posted on 2023-10-15 23:59  ArnoldLu  阅读(1553)  评论(0编辑  收藏  举报

导航