以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);