4.3.17. PMIC 驱动调试指南

4.3.17.1. PMIC 概述

PMIC(Power Management Integrated Circuit,电源管理集成电路)是一种高度集成的硬件模块,负责电子设备的电源分配、电压调节、充放电管理及系统低功耗控制。在 Linux 系统中,PMIC 通过驱动程序和内核框架实现与操作系统的交互,支持动态电源管理、休眠唤醒等功能。X5 EVB 上使用的 PMIC 是 HPU3501 芯片。

4.3.17.2. PMIC 的功能描述

PMIC 典型应用

PMIC 典型应用简介

  1. 电源转换与分配

    • DC-DC 转换器:高效转换输入电压为多路不同电压输出,供 CPU、外设等使用。

    • LDO(低压差稳压器):适用于低功耗场景,提供稳定电压输出(如传感器、时钟电路)。

  2. 系统电源控制

    • 管理开机/关机流程:响应电源键信号,控制 CPU 上电时序及复位操作。

    • 动态调频调压 (DVFS):根据 CPU/GPU 负载动态调整核心电压,平衡性能与功耗。

  3. 保护机制

    • 集成过压(OVP)、欠压(UVP)、过流(OCP)、过温(OTP)保护电路,确保系统安全运行。

    • PMIC 的典型应用之一就是进行热关断保护,实施热关断是为了防止因过热和功耗而造成损坏。触发热关断时,PMIC 将关闭所有电源轨,直到结温降至设定值以下,然后设备再次开始通电。详细介绍可以参考驱动平台 thermal 系统调试说明章节。

HPU3501 典型应用介绍

当前 X5 EVB 上使用的 PMIC 是 HPU3501,其典型应用如下图所示:

HPU3501_typical_application

上图中 HPU3501 作为核心组件,通过其内部的开关稳压器和低压差线性稳压器,以及外部的 DC/DC 转换器,为 X5 SoC 提供多种电压和电流的电源轨,同时通过 I2C 接口实现对电源进行管理。详细介绍如下:

输入电源

  • 5V IN:外部电源输入,提供给 PMIC 和其他外部 DC/DC 转换器的电源。

PMIC

  • VIN (2.7V-5.5V):PMIC 的主电源输入,接受 2.7V 至 5.5V 的电压范围。

  • I2C:用于配置和监控 PMIC 功能的 I2C 接口。

  • POR (Power-On Reset):上电复位功能,确保系统在上电时处于已知状态。

  • SWx (Switchers):可配置的开关稳压器,用于提供不同电压和电流的电源轨。图中标识了 SW1 至 SW5,每个都有不同的电压和电流规格。

    • SW1:0.6V-1.275V,最大电流 4A

    • SW2:0.6V-1.275V,最大电流 4A

    • SW3:0.6V-1.275V,最大电流 4A

    • SW4:0.6V-1.275V,最大电流 4A

    • SW5:0V-3.875V,最大电流 3A

  • LDOx (Low Dropout Regulators):低压差稳压器,提供稳定的低噪声电源轨。图中标识了 LDO1 至 LDO3,每个都有不同的电压和电流规格。

    • LDO1:0.6V-1.3V,最大电流 0.3A

    • LDO2:1.2V-3.7V,最大电流 0.3A

    • LDO3:1.2V-3.7V,最大电流 0.3A

X5 SoC 的电源轨

  • VDDx 系列:这些是为 X5 SoC 提供的不同电压和电流的电源轨,包括:

    • VDD08_ANON, VDD08_DSP, VDD08_DSP_PLL 等:为 SoC 的不同模块(如模拟、数字信号处理、PLL 等)提供电源。

    • VDD08_DDR, VDDQ_DDR_IV1, VDDA_PLL_DDR 0V8:为 DDR 内存提供电源。

    • VDD08_SOC, VDD08_BPU, VDD08_CPU:为 SoC 的核心部件(如 CPU、BPU 等)提供电源。

    • VDD08_SOC_PLL, VDDAIR_SOC_PLL:为 SoC 的 PLL 提供电源。

    • VP_MIPI_PHY 0V8:为 MIPI 接口提供电源。

    • VDDAIR1_USB, VDDAIR1_USER2_VOOH:为 USB 和用户接口提供电源。

    • VDDAIR8_PHY 1V8, VDDAIR8_PLL_DDR:为 PHY 和 PLL 提供电源。

    • VDD08_GPU:为 GPU 提供电源。

    • VDD08_SOC_1V8:为 SoC 的其他部分提供 1.8V 电源。

    • VDD08_SOC_5V3:为 SoC 提供 5.3V 电源。

在 X5 EVB 中 PMIC 模块的电路原理图如下:

PMIC_Circuit_Schematic

PMIC 功能原理

PMIC 的核心工作原理是通过集成多种电源管理功能来确保系统稳定、高效地运行。它通过提供电压调节、功率分配、电流保护等功能,确保设备在不同工作负载下的稳定性和能效,并在必要时进行电源控制和系统的保护措施。 PMIC 的部分核心功能原理说明如下:

  1. 电压调节

    • 电压转换:PMIC 内部包含多个电压调节器,能够将输入的电源电压转换为不同的稳定输出电压,为系统内的不同模块(如 CPU、GPU、内存、外围设备等)提供所需的电源电压。

    • 电压调整:通过调节输出电压,PMIC 可以根据系统的需求调整功率,降低功耗或者提高电压满足负载需求。这通常与动态调频调压 (DVFS)相关,用来在不同负载下优化功耗。

  2. 功率分配

    • PMIC 管理系统中各个部件的电源轨(电源通道),确保各个模块得到适当的电源。PMIC 会分配电源给 CPU、GPU、内存、外设等,同时根据系统负载调节电压和电流,优化能效。

  3. 电流和功率保护

    • 过电流保护:PMIC 包含过电流保护功能,防止电流超过预定的安全值,从而避免对设备造成损坏。

    • 过热保护:PMIC 会监控温度,如果 CPU 或其他监控的组件温度过高,会采取措施降低功耗或关闭部分功能,以防止设备过热。

4.3.17.3. PMIC 驱动代码

PMIC 驱动框架

在 Linux 内核中,PMIC 驱动负责初始化和管理电源管理集成电路(PMIC)的硬件功能,它利用了 MFD(Multi-Function Device)框架来处理多个功能单元(称为 Cell),并为每个 Cell 创建一个 Platform 设备。PMIC 驱动中,MFD 框架负责注册和注销设备,以及管理 Cell 的操作。Regulator 框架则负责定义和管理电压调节器的行为,包括电压和电流的调节。Regmap 是一个接口,为 PMIC 提供了与寄存器通信的通用方法,通常用于 I2C 或 SPI 通信。下面将分别介绍这几个驱动框架,以及 PMIC 驱动中这几个驱动框架之间的联系。

MFD 框架简介

PMIC 驱动是典型的 MFD 框架,MFD 是 Linux 中用于管理和控制多功能设备的软件框架。它提供了一个统一的接口,使多个设备能够通过单一驱动程序进行管理和控制。

在 MFD 中,每个独立的功能单元被称作一个 “cell”。Linux 内核中的 MFD 子系统负责为这些 “cell” 中的分别创建相应的 platform 设备,进而对其进行独立管理。

MFD 子系统由以下三个主要部分组成:

  1. MFD Core(核心)

    • 提供 MFD 设备的注册和注销功能。当一个 MFD 设备被系统识别时,MFD Core 负责将其添加到系统中;当设备不再被使用或需要从系统中移除时,MFD Core 负责注销设备。

    • 管理对 Cell 的操作。Cell 是 MFD 设备中具有特定功能的单元,例如一个电压调节器。MFD Core 允许对这些 Cell 进行识别和配置。

  2. MFD 设备驱动

    • 这是与 MFD 设备交互的软件组件。它使用 MFD Core 提供的接口来注册设备,创建 Cell,并管理这些 Cell 的行为。设备驱动负责实现与硬件交互的具体细节,如读写寄存器等。

  3. MFD Cell 描述符

    • 描述符是一个数据结构,用于描述 MFD 设备中的每个 Cell。它包含了 Cell 的特性和配置信息,如 Cell 的名称、功能、寄存器映射等。

    • MFD 为每个 Cell 创建一个 Platform 设备。这意味着每个 Cell 都可以被看作是一个独立的设备,可以通过标准的 Platform 设备接口进行访问和控制。

MFD 的工作流程

  • 当一个 MFD 设备被系统识别(例如,通过 I2C 或 SPI 总线),MFD 设备驱动会使用 MFD Core 的接口来注册这个设备。

  • MFD Core 会为设备中的每个 Cell 创建一个 Platform 设备,并为这些 Platform 设备分配资源,如中断。

  • 驱动程序和用户空间程序可以通过 Platform 设备接口来访问和控制每个 Cell,从而实现对 MFD 设备的管理和控制。

该流程如下图所示:

MFD_driver

Regulator 框架简介

PMIC 中的每个电压调节器(如 DC-DC 转换器或 LDO)都会通过 Regulator 框架进行管理。

Regulator 框架是 Linux 内核中用于管理电源调节器的子系统。它提供了一种标准化的方式来管理电源的开关、电压和电流设置,以及动态调整输出。

Linux voltage and current regulator framework 文档中,定义了一系列与电源调节器(Regulator)相关的术语。这些术语用于描述电源管理框架中的各个组件及其功能。以下是这些术语的简介:

1. Regulator(调节器):

  • 定义:为其他设备提供电源的电子设备。

  • 功能:大多数调节器可以启用或禁用其输出,某些调节器还可以控制其输出电压和/或电流。

  • 示例

    Input Voltage -> Regulator -> Output Voltage
    

2. PMIC(电源管理集成电路):

  • 定义:包含多个调节器的集成电路,通常还包含其他子系统。

  • 功能:PMIC 用于管理系统的电源分配和电源管理功能。

3. Consumer(消费者):

  • 定义:由调节器供电的电子设备。

  • 分类

    • 静态消费者:不需要改变其供电电压或电流限制,仅需要启用或禁用其电源。其供电电压由硬件、引导程序、固件或内核板初始化代码设置。

    • 动态消费者:需要根据操作需求改变其供电电压或电流限制。

4. Power Domain(电源域):

  • 定义:由调节器、开关或其他电源域的输出电源供电的电子电路。

  • 示例

    Regulator -+-> Switch-1 -+-> Switch-2 --> [Consumer A]
               |             |
               |             +-> [Consumer B], [Consumer C]
               |
               +-> [Consumer D], [Consumer E]
    

    这里描述了一个调节器和三个电源域:

    • 电源域1:Switch-1,消费者 D 和 E。

    • 电源域2:Switch-2,消费者 B 和 C。

    • 电源域3:消费者 A。 这三个电源域之间存在供给(supplies)关系:

    • Domain-1 --> Domain-2 --> Domain-3

5. Constraints(约束):

  • 定义:用于定义性能和硬件保护的电源级别。

  • 分类

    • 调节器级别:由调节器硬件的操作参数定义,通常在调节器数据手册中指定。例如:

      • 输出电压范围为800mV 到3500mV。

      • 调节器电流输出限制为5V 时20mA,10V 时10mA。

    • 电源域级别:在软件中由内核级板初始化代码定义,用于限制电源域的电源范围。例如在上述电源域结构中:

      • 电源域1的电压为3300mV。

      • 电源域2的电压范围为1400mV 到1600mV。

      • 电源域3的电流限制范围为0mA 到20mA。

    • 消费者级别:由消费者驱动程序动态设置电压或电流限制级别。例如:

      • 消费者背光驱动程序请求将电流从5mA 增加到10mA,以提高 LCD 照明。


在 Linux 内核中,Regulator 框架通过一系列的软件接口和硬件抽象层来控制电压输出。Regulator 框架主要由以下几部分组成:

  1. Machine interface(机器层接口)

    • 这个层通常指的是与特定硬件平台相关的配置信息,这些信息通过设备树(Device Tree)来定义。设备树描述了系统中所有的硬件组件,包括调节器的属性,如支持的电压范围、最大电流、默认设置等。

    • 在 Regulator 框架中,Machine 层的信息用于初始化调节器驱动,确保调节器能够按照硬件设计的要求进行配置。

  2. Regulator Driver interface(调节器驱动接口)

    • 调节器驱动是内核的一部分,负责直接与硬件交互,实现对调节器的控制。这包括启用或禁用调节器、设置电压和电流等。

    • 调节器驱动通常通过平台驱动(Platform Driver)模型注册到内核中,它们可以是特定于硬件的,也可以是通用的。

  3. Consumer(消费者)

    • 消费者是指系统中依赖调节器供电的设备或模块。这些设备通过 Regulator 框架请求电源,并在不再需要时释放电源。

    • 消费者可以通过内核提供的 API 与调节器驱动交互,如 regulator_get()regulator_put() 来获取和释放调节器,以及 regulator_enable()regulator_disable() 来控制电源。

  4. Sys-Class-Regulator(用户空间接口)

    • 这个部分提供了用户空间程序与内核调节器框架交互的接口。通过 sysfs 文件系统,用户空间程序可以查询调节器的状态,如当前电压和电流,或者改变调节器的设置。

    • 这种接口使得用户空间程序能够动态地管理系统的电源,例如在需要时调整电压以优化性能或节省能源。

    • 详细可以参考内核文档:kernel/Documentation/ABI/testing/sysfs-class-regulator

Linux Regulator Framework 在具体驱动中如下图所示:

Regulator_framework

说明如下:

  1. 用户空间程序(User_Space):发起对调节器状态的读取或写入请求。

  2. Sysfs 接口(Sysfs_Interface):作为用户空间与内核空间的接口,接收用户空间的请求并转发给相应的 Consumer。

  3. Consumer:代表系统中使用调节器供电的设备或模块,它们通过 Sysfs 接口请求电源管理操作。

  4. Regulator 驱动(Regulator_Driver):内核中的调节器驱动程序,负责处理 Consumer 的请求,并与硬件交互以执行电源管理操作。

  5. Machine:代表与特定硬件平台相关的配置信息,这些信息用于初始化调节器硬件。

Regmap 框架简介

Regmap(寄存器映射)是 Linux 内核中的一个框架,用于简化对硬件寄存器的访问。它提供了一组统一的 API,用于读写硬件寄存器,同时支持缓存机制以提高效率。

基于代码复用的原则,Linux 内核引入了 regmap 模型,该模型将寄存器访问的共通逻辑抽象出来。驱动开发人员不再需要关注使用 SPI 或 I2C 接口的 API 函数,而是统一使用 regmap API 函数。这种做法的优势在于,通过统一的 regmap 接口,减少了代码冗余,并提高了驱动的可移植性。regmap 模型的核心特点包括:

  • 它提供了一组统一的接口函数(如regmap_readregmap_write),用于访问设备寄存器,包括 SOC 内部的寄存器。

  • regmap 是 Linux 内核为了减少慢速 I/O 操作在驱动开发中的冗余开销而设计的,它提供了一种通用的接口来操作硬件寄存器。

  • regmap 在驱动程序和硬件之间引入了缓存机制,从而减少低速 I/O 操作的次数,提高了访问效率,但是可能会降低实时性。

整个 Regmap 分为 3 层,其拓扑结构如下:

Regmap_Topology

该图展示了 Linux 内核中 regmap 子系统的一个简化拓扑结构。regmap 子系统通过提供统一的接口来简化对硬件寄存器的访问,并且支持多种不同的总线类型。以下是对图中各层级的分析:

regmap 层提供通用的读写接口:

  • regmap_readregmap_write:这是 regmap 子系统提供的两个主要接口,用于从硬件寄存器读取数据和向硬件寄存器写入数据。这些接口对上层驱动程序隐藏了具体的总线操作细节。

regcache 层管理缓存以提高效率:

  • regcache:这一层负责管理寄存器的缓存。regmap 可以使用缓存来减少对硬件的访问次数,从而提高性能。图中列出了几种不同的缓存实现方式:

    • flat:数组,简单的扁平缓存,不涉及复杂的数据结构。当设备寄存器很少时,可以用这种类型来缓存寄存器值。

    • lzo:使用 LZO (Lempel–Ziv–Oberhumer) 压缩算法的缓存,适用于需要压缩大量寄存器数据的场景。这个算法有 3 个特性:

      • 压缩快

      • 解压不需要额外内存

      • 压缩比可以自动调节。

      • 可以理解为一个数组缓存,套了一层压缩,以节约内存。当设备寄存器数量中等时,可以考虑这种缓存类型。

    • rbtree:使用红黑树(一种自平衡二叉查找树)实现的缓存,它的特性是索引快,当设备寄存器数量比较大,或者对寄存器操作延时要求低时,就可以用这种缓存类型。

bus 层负责与具体的硬件总线进行交互:

  • bus:这一层定义了与不同总线类型交互的具体方法。每种总线类型都有其特定的实现,以支持 regmap 接口。图中列出了几种常见的总线类型:

    • i2c:用于 I2C 总线的实现。

    • spi:用于 SPI 总线的实现。

    • mmio:用于内存映射 I/O(Memory-Mapped I/O)的实现。

    • irq:用于中断的实现。

对应的 regmap 驱动框架有三个主要组成部分:regmap API 函数、regmap 核心(core)以及物理总线层:

  1. regmap API 函数 这一层提供了一组 API 函数,用于简化寄存器的读写操作。这些函数是 regmap 框架对外的接口,允许驱动程序以统一的方式访问硬件寄存器,而无需关心底层的总线类型或具体的寄存器访问细节。

  2. regmap 核心(core) 这一层是 regmap 框架的核心,负责管理寄存器的缓存和提供对不同缓存策略的支持。缓存策略的选择可以影响性能和资源的使用。

  3. 物理总线层 这一层定义了与不同物理总线交互的具体方法。每种总线类型都有其特定的实现,以支持 regmap API 函数。这些总线包括。

regmap 驱动框架各层之间的交互过程如下图所示:

Regmap_framework

PMIC 驱动框架说明

先说明 PMIC、MFD、Regulator 以及 Regmap 之间的关系:

  1. PMIC 与 MFD

    • PMIC 硬件通过 MFD 框架注册到内核中。MFD 框架将 PMIC 的每个功能抽象为一个 Cell,并为每个 Cell 创建一个 Platform 设备。

    • 例如,PMIC 芯片 hpu3501通过 MFD 框架注册,每个功能(如 LDO 或 DC-DC 转换器)被抽象为一个 Cell。

  2. MFD 与 Regulator

    • MFD 框架作为中间层,将 PMIC 的各个功能模块抽象为独立的设备,MFD 框架为每个 Cell 创建的 Platform 设备可以被 Regulator 框架使用。Regulator 框架通过这些 Platform 设备与 PMIC 硬件通信。

    • 例如,MFD 框架为 hpu3501的每个 LDO 创建一个 Platform 设备,Regulator 框架通过这些设备控制电压和电流。

  3. Regulator 与 Regmap

    • Regulator 框架通过 Regmap 框架与 PMIC 硬件通信。Regmap 框架提供了寄存器访问的 API,Regulator 框架通过这些 API 读写 PMIC 的寄存器。

    • 例如,Regulator 框架通过regmap_write函数设置 PMIC 的电压控制寄存器。

简而言之,MFD 框架管理 PMIC 的多功能单元,Regulator 框架控制电压调节器,而 Regmap 负责与 PMIC 的寄存器进行通信。

以 X5 EVB 中 hpu3501 为例,上述联系可以用下图表示:

PMIC_framework

PMIC 驱动框架可以描述如下:

hpu3501

hpu3501 硬件支持多种的功能,比如同时支持 RTC 和 Regulator,所以这里采用了 Linux 的 MFD 架构作为 Regulator 和 RTC 的整体架构。 如上图所示,MFD 层主要的作用是注册 I2C 设备,向下通过 I2C 总线与硬件 HPU3501进行通信,向上利用 regmap 方式为 Regulator 和 RTC 提供统一的读写寄存器的接口。
RTC 主要功能是对时间和闹钟的设置和读写。
Regulator 主要功能是对各路电的控制。

MFD 驱动代码简介

MFD config 开关

CONFIG_MFD_CORE=y

CONFIG_MFD_CORE 配置项启用了 MFD(Multi-Function Device)核心框架。

MFD 代码路径

kernel/drivers/mfd/mfd-core.c
kernel/include/linux/mfd/core.h

MFD 关键代码简介

MFD 驱动代码中关键数据结构如下:

// kernel/include/linux/mfd/core.h
/*
 * This struct describes the MFD part ("cell").
 * After registration the copy of this structure will become the platform data
 * of the resulting platform_device
 */
struct mfd_cell {
	const char		*name;
	int			id;
	int			level;

	int			(*enable)(struct platform_device *dev);
	int			(*disable)(struct platform_device *dev);

	int			(*suspend)(struct platform_device *dev);
	int			(*resume)(struct platform_device *dev);

	/* platform data passed to the sub devices drivers */
	void			*platform_data;
	size_t			pdata_size;

	/* Software node for the device. */
	const struct software_node *swnode;

	/*
	 * Device Tree compatible string
	 * See: Documentation/devicetree/usage-model.rst Chapter 2.2 for details
	 */
	const char		*of_compatible;

	/*
	 * Address as defined in Device Tree.  Used to compement 'of_compatible'
	 * (above) when matching OF nodes with devices that have identical
	 * compatible strings
	 */
	u64 of_reg;

	/* Set to 'true' to use 'of_reg' (above) - allows for of_reg=0 */
	bool use_of_reg;

	/* Matches ACPI */
	const struct mfd_cell_acpi_match	*acpi_match;

	/*
	 * These resources can be specified relative to the parent device.
	 * For accessing hardware you should use resources from the platform dev
	 */
	int			num_resources;
	const struct resource	*resources;

	/* don't check for resource conflicts */
	bool			ignore_resource_conflicts;

	/*
	 * Disable runtime PM callbacks for this subdevice - see
	 * pm_runtime_no_callbacks().
	 */
	bool			pm_runtime_no_callbacks;

	/* A list of regulator supplies that should be mapped to the MFD
	 * device rather than the child device when requested
	 */
	const char * const	*parent_supplies;
	int			num_parent_supplies;
};

struct mfd_cell 结构体在 MFD 子系统中用于描述 MFD 设备的一个功能单元(称为“cell”)。这个结构体包含了创建 platform 设备所需的各种信息,并且当 MFD 设备注册后,这个结构体的一个副本会成为生成的 platform_device 的平台数据(platform data)。


在 Linux 内核中提供了一组函数接口用于启用和禁用 MFD 设备中的单元(cell),添加和移除 MFD 设备,以及获取与平台设备(platform device)关联的 MFD 单元。下面是对这些函数接口的简介:

  1. mfd_cell_enablemfd_cell_disable

    这两个函数用于启用和禁用 MFD 设备中的单元。它们会自动处理引用计数(refcounting),确保只有在设备第一次被启用或没有其他客户端使用时,才会调用单元的启用(enable)或禁用(disable)回调函数。

    extern int mfd_cell_enable(struct platform_device *pdev);
    extern int mfd_cell_disable(struct platform_device *pdev);
    
    • mfd_cell_enable(pdev):启用指定的 platform 设备(pdev)所对应的 MFD 单元。

    • mfd_cell_disable(pdev):禁用指定的 platform 设备(pdev)所对应的 MFD 单元。

  2. mfd_get_cell

    这个内联函数用于获取创建特定 platform 设备的 MFD 单元。

    static inline const struct mfd_cell *mfd_get_cell(struct platform_device *pdev)
    {
    	return pdev->mfd_cell;
    }
    
    • mfd_get_cell(pdev):返回与给定的 platform 设备(pdev)关联的 MFD 单元的指针。

  3. mfd_add_devices

    这个函数用于添加一组 MFD 设备。它接受父设备、设备 ID、MFD 单元数组、设备数量、内存资源基地址、中断基地址和中断域等参数。

    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);
    
    • mfd_add_devices(parent, id, cells, n_devs, mem_base, irq_base, irq_domain):在指定的父设备下添加多个 MFD 设备。

  4. mfd_add_hotplug_devices

    这个内联函数是 mfd_add_devices 的一个特例,用于添加热插拔 MFD 设备。它自动设置设备 ID 为自动分配(PLATFORM_DEVID_AUTO),并忽略内存资源基地址、中断基地址和中断域。

    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);
    }
    
    • mfd_add_hotplug_devices(parent, cells, n_devs):在指定的父设备下添加多个热插拔 MFD 设备。

  5. mfd_remove_devicesmfd_remove_devices_late

    这两个函数用于移除之前添加的 MFD 设备。

    extern void mfd_remove_devices(struct device *parent);
    extern void mfd_remove_devices_late(struct device *parent);
    
    • mfd_remove_devices(parent):移除指定父设备下的所有 MFD 设备。

    • mfd_remove_devices_late(parent):在系统关闭时移除指定父设备下的所有 MFD 设备。

  6. devm_mfd_add_devices

    这个函数用于添加一组 MFD 设备,并在设备释放时自动移除它们。它与 mfd_add_devices 类似,但提供了设备模型(devm)支持,以确保资源的自动管理。

    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);
    
    • devm_mfd_add_devices(dev, id, cells, n_devs, mem_base, irq_base, irq_domain):在指定的设备下添加多个 MFD 设备,并在设备释放时自动移除它们。

Regulator 驱动代码简介

Regulator config 开关

CONFIG_REGULATOR=y
CONFIG_REGULATOR_DEBUG=y
CONFIG_REGULATOR_FIXED_VOLTAGE=y
CONFIG_REGULATOR_GPIO=y
  • CONFIG_REGULATOR 配置项启用了调节器框架。调节器框架用于管理电压和电流调节器,这些调节器负责为系统中的不同组件提供稳定的电源。

  • CONFIG_REGULATOR_DEBUG 配置项启用了调节器框架的调试功能。

  • CONFIG_REGULATOR_FIXED_VOLTAGE 配置项启用了固定电压调节器的支持。固定电压调节器是一种简单的调节器,它提供恒定的输出电压。

  • CONFIG_REGULATOR_GPIO 配置项启用了基于 GPIO(通用输入输出)的调节器支持。

Regulator 代码路径

kernel/include/linux/regulator/driver.h
kernel/drivers/regulator/core.c
kernel/drivers/regulator/devres.c
kernel/drivers/regulator/irq_helpers.c

Regulator 关键代码简介

Regulator 驱动代码中关键数据结构如下:

// kernel/include/linux/regulator/driver.h
struct regulator_desc {
	const char *name;
	const char *supply_name;
	const char *of_match;
	bool of_match_full_name;
	const char *regulators_node;
	int (*of_parse_cb)(struct device_node *,
			    const struct regulator_desc *,
			    struct regulator_config *);
	int id;
	unsigned int continuous_voltage_range:1;
	unsigned n_voltages;
	unsigned int n_current_limits;
	const struct regulator_ops *ops;
	int irq;
	enum regulator_type type;
	struct module *owner;

	unsigned int min_uV;
	unsigned int uV_step;
	unsigned int linear_min_sel;
	int fixed_uV;
	unsigned int ramp_delay;
	int min_dropout_uV;

	const struct linear_range *linear_ranges;
	const unsigned int *linear_range_selectors;

	int n_linear_ranges;

	const unsigned int *volt_table;
	const unsigned int *curr_table;

	......

	unsigned int n_ramp_values;

	unsigned int enable_time;

	unsigned int off_on_delay;

	unsigned int poll_enabled_time;

	unsigned int (*of_map_mode)(unsigned int mode);
};

结构体 struct regulator_desc 是 Linux 内核中用于描述电压或电流调节器(regulator)的静态信息。它包含了调节器的核心属性和操作接口,这些信息在调节器驱动注册时提供给调节器框架。


// kernel/include/linux/regulator/driver.h
struct regulator_dev {
	const struct regulator_desc *desc;
	int exclusive;
	u32 use_count;
	u32 open_count;
	u32 bypass_count;

	/* lists we belong to */
	struct list_head list; /* list of all regulators */

	/* lists we own */
	struct list_head consumer_list; /* consumers we supply */

	struct coupling_desc coupling_desc;

	struct blocking_notifier_head notifier;
	struct ww_mutex mutex; /* consumer lock */
	struct task_struct *mutex_owner;
	int ref_cnt;
	struct module *owner;
	struct device dev;
	struct regulation_constraints *constraints;
	struct regulator *supply;	/* for tree */
	const char *supply_name;
	struct regmap *regmap;

	struct delayed_work disable_work;

	void *reg_data;		/* regulator_dev data */

	struct dentry *debugfs;

	struct regulator_enable_gpio *ena_pin;
	unsigned int ena_gpio_state:1;

	unsigned int is_switch:1;

	/* time when this regulator was disabled last time */
	ktime_t last_off;
	int cached_err;
	bool use_cached_err;
	spinlock_t err_lock;
};

结构体 struct regulator_dev 定义了 Linux 内核中电压/电流调节器类设备的核心数据结构,它包含了调节器的状态信息、操作接口、消费者列表等。通过这个结构体,调节器框架可以管理和控制调节器的行为,同时提供统一的接口给其他内核子系统或用户空间程序,每个调节器实例都会有一个这样的结构体。


// kernel/include/linux/regulator/driver.h
struct regulator_ops {

	/* enumerate supported voltages */
	int (*list_voltage) (struct regulator_dev *, unsigned selector);

	/* get/set regulator voltage */
	int (*set_voltage) (struct regulator_dev *, int min_uV, int max_uV,
			    unsigned *selector);
        ......

	/* get/set regulator current  */
	int (*set_current_limit) (struct regulator_dev *,
				 int min_uA, int max_uA);
	int (*get_current_limit) (struct regulator_dev *);

	......

	/* enable/disable regulator */
	int (*enable) (struct regulator_dev *);
	int (*disable) (struct regulator_dev *);
	
        ......

	/* report regulator status ... most other accessors report
	 * control inputs, this reports results of combining inputs
	 * from Linux (and other sources) with the actual load.
	 * returns REGULATOR_STATUS_* or negative errno.
	 */
	int (*get_status)(struct regulator_dev *);

	/* get most efficient regulator operating mode for load */
	unsigned int (*get_optimum_mode) (struct regulator_dev *, int input_uV,
					  int output_uV, int load_uA);
	/* set the load on the regulator */
	int (*set_load)(struct regulator_dev *, int load_uA);

	/* control and report on bypass mode */
	int (*set_bypass)(struct regulator_dev *dev, bool enable);
	int (*get_bypass)(struct regulator_dev *dev, bool *enable);

	/* the operations below are for configuration of regulator state when
	 * its parent PMIC enters a global STANDBY/HIBERNATE state */

	/* set regulator suspend voltage */
	int (*set_suspend_voltage) (struct regulator_dev *, int uV);

	/* enable/disable regulator in suspend state */
	int (*set_suspend_enable) (struct regulator_dev *);
	int (*set_suspend_disable) (struct regulator_dev *);

	/* set regulator suspend operating mode (defined in consumer.h) */
	int (*set_suspend_mode) (struct regulator_dev *, unsigned int mode);

	int (*resume)(struct regulator_dev *rdev);

	int (*set_pull_down) (struct regulator_dev *);
};

结构体 struct regulator_ops 定义了 Linux 内核中电压/电流调节器(regulator)的操作接口集合。这些操作由调节器芯片驱动程序实现,以控制调节器的行为。


Linux 内核中调节器(Regulator)子系统提供了调节器的注册、注销、事件通知、中断处理以及电压映射等一系列操作的函数接口。下面是对这些函数接口的简要说明:

  1. 调节器注册与注销

    下面这些函数用于在内核中注册和注销调节器设备:

    // kernel/drivers/regulator/core.c
    struct regulator_dev *regulator_register(struct device *dev,
                                        const struct regulator_desc *regulator_desc,
                                        const struct regulator_config *config);
    // kernel/drivers/regulator/devres.c
    struct regulator_dev *devm_regulator_register(struct device *dev,
                                                const struct regulator_desc *regulator_desc,
                                                const struct regulator_config *config);
    // kernel/drivers/regulator/devres.c
    void regulator_unregister(struct regulator_dev *rdev);
    
    • regulator_register:注册单个调节器设备。

    • devm_regulator_register:注册调节器设备,并在设备销毁时自动注销。

    • regulator_unregister:注销已注册的调节器设备。

  2. 事件通知

    用于处理调节器事件的函数:

    // kernel/drivers/regulator/core.c
    int regulator_notifier_call_chain(struct regulator_dev *rdev,
                                    unsigned long event, void *data);
    
    • regulator_notifier_call_chain:触发调节器事件,通知所有注册的监听器。

  3. 中断处理

    下面这些函数用于处理调节器相关的中断:

    // kernel/drivers/regulator/devres.c
    void *devm_regulator_irq_helper(struct device *dev,
                                    const struct regulator_irq_desc *d, int irq,
                                    int irq_flags, int common_errs,
                                    int *per_rdev_errs, struct regulator_dev **rdev,
                                    int rdev_amount);
    // kernel/drivers/regulator/irq_helpers.c
    void *regulator_irq_helper(struct device *dev,
                            const struct regulator_irq_desc *d, int irq,
                            int irq_flags, int common_errs, int *per_rdev_errs,
                            struct regulator_dev **rdev, int rdev_amount);
    // kernel/drivers/regulator/irq_helpers.c
    void regulator_irq_helper_cancel(void **handle);
    
    • devm_regulator_irq_helper:使用设备管理器帮助处理调节器中断。

    • regulator_irq_helper:帮助处理调节器中断。

    • regulator_irq_helper_cancel:取消之前注册的中断处理程序。

  4. 调节器操作

    下面这些函数用于获取调节器设备的信息和状态:

    // kernel/drivers/regulator/core.c
    void *rdev_get_drvdata(struct regulator_dev *rdev);
    // kernel/drivers/regulator/core.c
    struct device *rdev_get_dev(struct regulator_dev *rdev);
    // kernel/drivers/regulator/core.c
    struct regmap *rdev_get_regmap(struct regulator_dev *rdev);
    // kernel/drivers/regulator/core.c
    int rdev_get_id(struct regulator_dev *rdev);
    
    • rdev_get_drvdata:获取调节器的私有数据。

    • rdev_get_dev:获取调节器的设备结构体。

    • rdev_get_regmap:获取调节器的寄存器映射。

    • rdev_get_id:获取调节器的 ID。

  5. 电压映射与控制

    下面这些函数用于处理电压映射和调节器控制操作:

    // kernel/drivers/regulator/irq_helpers.c
    int regulator_list_voltage_linear(struct regulator_dev *rdev,
                                    unsigned int selector);
    // kernel/drivers/regulator/irq_helpers.c
    int regulator_map_voltage_linear(struct regulator_dev *rdev,
                                    int min_uV, int max_uV);
    // kernel/drivers/regulator/irq_helpers.c
    int regulator_set_voltage_sel_regmap(struct regulator_dev *rdev, unsigned sel);
    // kernel/drivers/regulator/irq_helpers.c
    int regulator_enable_regmap(struct regulator_dev *rdev);
    // kernel/drivers/regulator/irq_helpers.c
    int regulator_disable_regmap(struct regulator_dev *rdev);
    
    • regulator_list_voltage_linear:列出调节器支持的电压值(线性映射)。

    • regulator_map_voltage_linear:将电压值映射到调节器的选择器值(线性映射)。

    • regulator_set_voltage_sel_regmap:通过 regmap 接口设置调节器的电压选择器值。

    • regulator_enable_regmap:通过 regmap 接口启用调节器。

    • regulator_disable_regmap:通过 regmap 接口禁用调节器。

  6. 辅助函数

    下面这些函数用于在注册调节器之前进行一些辅助操作:

    // kernel/drivers/regulator/irq_helpers.c
    int regulator_desc_list_voltage_linear_range(const struct regulator_desc *desc,
                                                unsigned int selector);
    
    • regulator_desc_list_voltage_linear_range:根据调节器描述符列出支持的电压值(线性范围)。

    这些分类涵盖了调节器子系统中的主要操作和功能,使得内核开发者可以方便地管理和操作电压/电流调节器。

Regmap 驱动代码简介

Regmap config 开关

CONFIG_REGMAP=y
CONFIG_REGMAP_I2C=y
CONFIG_REGMAP_SPI=y
CONFIG_REGMAP_MMIO=y
CONFIG_REGMAP_TEE=y
  1. CONFIG_REGMAP=y

    • 这个选项启用了 regmap 子系统。y 表示该选项被编译进内核。regmap 是一个通用的寄存器映射和访问框架,它为不同的总线(如 I2C、SPI 等)提供了统一的寄存器访问接口。

  2. CONFIG_REGMAP_I2C=y

    • 这个选项启用了对 I2C 总线的支持。当启用此选项时,regmap 子系统将能够通过 I2C 总线与设备进行通信。

  3. CONFIG_REGMAP_SPI=y

    • 这个选项启用了对 SPI 总线的支持。启用后,regmap 子系统可以通过 SPI 总线访问设备寄存器。

  4. CONFIG_REGMAP_MMIO=y

    • 这个选项启用了对内存映射 I/O(Memory-Mapped I/O)的支持。这允许 regmap 子系统直接通过内存地址访问硬件寄存器。

  5. CONFIG_REGMAP_TEE=y

    • 这个选项启用了对可信执行环境(Trusted Execution Environment,TEE)的支持。TEE 是一种安全机制,用于保护敏感操作免受恶意软件的攻击。当启用此选项时,regmap 子系统将能够与 TEE 进行交互,一般用于安全相关的寄存器访问。

Regmap 代码路径

kernel/include/linux/regmap.h
kernel/drivers/base/regmap/internal.h

Regmap 关键代码简介

Regmap 驱动中关键数据结构如下:

// kernel/include/linux/regmap.h
struct regmap_config {
	const char *name;

	int reg_bits;
	int reg_stride;
	int reg_downshift;
	unsigned int reg_base;
	int pad_bits;
	int val_bits;

	bool (*writeable_reg)(struct device *dev, unsigned int reg);
	bool (*readable_reg)(struct device *dev, unsigned int reg);
	bool (*volatile_reg)(struct device *dev, unsigned int reg);
	bool (*precious_reg)(struct device *dev, unsigned int reg);
	bool (*writeable_noinc_reg)(struct device *dev, unsigned int reg);
	bool (*readable_noinc_reg)(struct device *dev, unsigned int reg);

        ......

	unsigned int num_ranges;

	bool use_hwlock;
	bool use_raw_spinlock;
	unsigned int hwlock_id;
	unsigned int hwlock_mode;

	bool can_sleep;
};

struct regmap_config 结构体用于配置 Linux 内核中的 regmap 子系统。


// kernel/drivers/base/regmap/internal.h
struct regcache_ops {
	const char *name;
	enum regcache_type type;
	int (*init)(struct regmap *map);
	int (*exit)(struct regmap *map);
#ifdef CONFIG_DEBUG_FS
	void (*debugfs_init)(struct regmap *map);
#endif
	int (*read)(struct regmap *map, unsigned int reg, unsigned int *value);
	int (*write)(struct regmap *map, unsigned int reg, unsigned int value);
	int (*sync)(struct regmap *map, unsigned int min, unsigned int max);
	int (*drop)(struct regmap *map, unsigned int min, unsigned int max);
};

struct regcache_ops 结构体用于定义寄存器缓存(regcache)的操作集合。在 regmap 框架中,寄存器缓存是一种可选的机制,用于存储设备寄存器的值,以减少对硬件的访问次数,从而提高性能。


// kernel/include/linux/regmap.h
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_noinc_write reg_noinc_write;
	regmap_hw_reg_update_bits reg_update_bits;
	regmap_hw_read read;
	regmap_hw_reg_read reg_read;
	regmap_hw_reg_noinc_read reg_noinc_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;
	bool free_on_exit;
};

regmap_bus 结构体定义了一组函数指针和属性,这些函数指针用于执行不同类型的寄存器访问操作,如读写、批量写入、异步写入等。这些操作通常由具体的总线驱动实现,如 I2C 或 SPI 驱动。


Regmap 中常用的一些函数接口如下:

  1. regmap_read

    • 功能:尝试从指定的寄存器地址读取数据。

    • 函数原型:

      static inline int regmap_read(struct regmap *map, unsigned int reg, unsigned int *val);
      
    • 形参:

      • struct regmap *map:指向 regmap 结构体的指针,该结构体描述了寄存器映射。

      • unsigned int reg:要读取的寄存器地址。

      • unsigned int *val:指向存储读取值的缓冲区的指针。

    • 返回值:如果 regmap API 被禁用,则返回 -EINVAL,并发出警告。

  2. regmap_bulk_read

    • 功能:尝试从指定的寄存器地址开始,批量读取多个寄存器的数据。

    • 函数原型:

      static inline int regmap_bulk_read(struct regmap *map, unsigned int reg, void *val, size_t val_count);
      
    • 形参:

      • struct regmap *map:指向 regmap 结构体的指针。

      • unsigned int reg:要读取的第一个寄存器地址。

      • void *val:指向存储读取数据的缓冲区的指针。

      • size_t val_count:要读取的寄存器数量。

    • 返回值:如果 regmap API 被禁用,则返回 -EINVAL,并发出警告。

  3. regmap_update_bits_base

    • 功能:尝试更新指定寄存器的特定位。

    • 函数原型:

      static inline int regmap_update_bits_base(struct regmap *map, unsigned int reg, unsigned int mask, unsigned int val, bool *change, bool async, bool force);
      
    • 形参:

      • struct regmap *map:指向 regmap 结构体的指针。

      • unsigned int reg:要更新的寄存器地址。

      • unsigned int mask:要更新的位的掩码。

      • unsigned int val:要设置的新值。

      • bool *change:指向一个布尔值的指针,用于指示寄存器值是否实际发生了变化。

      • bool async:指示操作是否应异步执行。

      • bool force:指示是否强制更新位,即使它们没有变化。

    • 返回值:如果 regmap API 被禁用,则返回 -EINVAL,并发出警告。

  4. regmap_write

    • 功能:尝试向指定的寄存器地址写入数据。

    • 函数原型:

      static inline int regmap_write(struct regmap *map, unsigned int reg, unsigned int val);
      
    • 形参:

      • struct regmap *map:指向 regmap 结构体的指针。

      • unsigned int reg:要写入的寄存器地址。

      • unsigned int val:要写入的值。

    • 返回值:如果 regmap API 被禁用,则返回 -EINVAL,并发出警告。

  5. regmap_bulk_write

    • 功能:尝试从指定的寄存器地址开始,批量写入多个寄存器的数据。

    • 函数原型:

      static inline int regmap_bulk_write(struct regmap *map, unsigned int reg, const void *val, size_t val_count);
      
    • 形参:

      • struct regmap *map:指向 regmap 结构体的指针。

      • unsigned int reg:要写入的第一个寄存器地址。

      • const void *val:指向要写入的数据缓冲区的指针。

      • size_t val_count:要写入的寄存器数量。

    • 返回值:如果 regmap API 被禁用,则返回 -EINVAL,并发出警告。

4.3.17.4. PMIC 使用说明

以 hpu3501 的驱动代码为例对 PMIC 的使用进行说明。

hpu3501 MFD 层驱动代码

在前文 MFD 驱动框架 章节中已经说明了 hpu3501 MFD 的框架,当前实际只使用了 hpu3501 的 Regulator 功能,所以只有这一个 Cell,本章具体说明 hpu3501 MFD 层相关代码。

代码路径

kernel/drivers/mfd/hpu3501.c
kernel/include/linux/mfd/hpu3501.h

config 开关

CONFIG_MFD_HPU3501

MFD 设备树描述

目前在 x5-evb.dtsi 中,只用到了一个 PMIC,挂在了 I2C2上,HPU3501 在 dts 中 MTD 驱动需要使用的相关描述如下:

# kernel/scripts/dtc/include-prefixes/arm64/hobot/x5-evb.dtsi
&i2c2 {
	status = "okay";

	hpu3501@1c {
		compatible = "hobot, hpu3501";
		reg = <0x1c>;
		status = "okay";
    ......

该设备树定义了一个 PMIC(hpu3501),它连接在 I2C 总线2上,地址为0x1C。设备树中各个属性说明如下:

  • hpu3501@1c:定义了一个名为hpu3501的节点,@1c表示该设备在 I2C 总线上的地址是0x1C。

  • compatible = "hobot, hpu3501";:指定了设备的兼容性,这通常用于内核中的驱动程序匹配。

  • reg = <0x1c>;:再次明确了 I2C 设备的地址。

  • status = "okay";:表示该设备是可用的。

hpu3501 MFD 关键函数说明

初始化:

  • 注册 I2C 设备

  • 初始化 regmap 接口

  • 为 Regulator 和 RTC 注册 MFD 设备

// kernel/drivers/mfd/hpu3501.c
static const struct mfd_cell hpu3501_devs[] = {
        {
                .name = "hpu3501-regulator",
        },
        {
                .name = "hpu3501-rtc",
        },
};

static int hpu3501_i2c_probe(struct i2c_client *i2c,
                            const struct i2c_device_id *id)
{
        int ret;

        struct hpu3501_dev *hpu3501;

        hpu3501 = devm_kzalloc(&i2c->dev, sizeof(struct hpu3501_dev),
                                GFP_KERNEL);
        if (!hpu3501)
                return -ENOMEM;

        i2c_set_clientdata(i2c, hpu3501);
        hpu3501->dev = &i2c->dev;
        hpu3501->i2c_client = i2c;

        hpu3501->regmap = devm_regmap_init_i2c(i2c, &hpu3501_regmap_config);
        if (IS_ERR(hpu3501->regmap)) {
                ret = PTR_ERR(hpu3501->regmap);
                dev_err(&i2c->dev, "regmap init failed: %d\n", ret);
                return ret;
        }

        ret = devm_mfd_add_devices(hpu3501->dev, PLATFORM_DEVID_AUTO,
                        hpu3501_devs, ARRAY_SIZE(hpu3501_devs), NULL, 0, NULL);

        return ret;
}

static const struct i2c_device_id hpu3501_i2c_id[] = {
        { "hpu3501", 0 },
        { }
};
MODULE_DEVICE_TABLE(i2c, hpu3501_i2c_id);

#ifdef CONFIG_OF
static const struct of_device_id hpu3501_of_match[] = {
        {.compatible = "hobot, hpu3501", },
        {},
};
MODULE_DEVICE_TABLE(of, hpu3501_of_match);
#endif

static struct i2c_driver hpu3501_i2c_driver = {
        .driver = {
                   .name = "hpu3501",
                   .of_match_table = of_match_ptr(hpu3501_of_match),
        },
        .probe = hpu3501_i2c_probe,
        .id_table = hpu3501_i2c_id,
};

static int __init hpu3501_i2c_init(void)
{
        return i2c_add_driver(&hpu3501_i2c_driver);
}

读写寄存器的接口:

// kernel/drivers/mfd/hpu3501.c
//对单个寄存器的写。reg:寄存器地址,val:要写的值
int hpu3501_write(struct device *dev, int reg, uint8_t val)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);

        return regmap_write(hpu3501->regmap, reg, val);
}
EXPORT_SYMBOL_GPL(hpu3501_write);

//对单个寄存器的读。reg:寄存器地址,val:存放数据的指针
int hpu3501_read(struct device *dev, int reg, uint8_t *val)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);
        unsigned int rval;
        int ret;

        ret = regmap_read(hpu3501->regmap, reg, &rval);
        if (!ret)
                *val = rval;
        return ret;
}
EXPORT_SYMBOL_GPL(hpu3501_read);

//对连续几个寄存器的写。reg:第一个寄存器的地址,len:寄存器个数,val:要写数据的指针
int hpu3501_writes(struct device *dev, int reg, int len, uint8_t *val)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);

        return regmap_bulk_write(hpu3501->regmap, reg, val, len);
}
EXPORT_SYMBOL_GPL(hpu3501_writes);

//对连续几个寄存器的读。reg:第一个寄存器的地址,len:寄存器个数,val:存放数据的指针
int hpu3501_reads(struct device *dev, int reg, int len, uint8_t *val)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);

        return regmap_bulk_read(hpu3501->regmap, reg, val, len);
}
EXPORT_SYMBOL_GPL(hpu3501_reads);

//将某个寄存器的某各 bit 设置为1。reg:寄存器地址,bit_mask:对应位的 mask,比如要对 bit0
//操作则是0x1
int hpu3501_set_bits(struct device *dev, int reg, uint8_t bit_mask)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);

        return regmap_update_bits(hpu3501->regmap, reg, bit_mask, bit_mask);
}
EXPORT_SYMBOL_GPL(hpu3501_set_bits);

//将某个寄存器的某各 bit 设置为0。reg:寄存器地址,bit_mask:对应位的 mask,比如要对 bit0
//操作则是0x1
int hpu3501_clr_bits(struct device *dev, int reg, uint8_t bit_mask)
{
        struct hpu3501_dev *hpu3501 = dev_to_hpu3501(dev);

        return regmap_update_bits(hpu3501->regmap, reg, bit_mask, 0);
}
EXPORT_SYMBOL_GPL(hpu3501_clr_bits);

hpu3501 Regulator 层驱动代码

hpu3501 Regulator 代码路径

kernel/drivers/regulator/hpu3501-regulator.c
kernel/drivers/regulator/hpu3501-regulator.h
kernel/include/linux/regulator/driver.h

hpu3501 Regulator CONFIG

  CONFIG_REGULATOR_HPU3501

依赖于

CONFIG_MFD_HPU3501

Regulator 设备树描述

HPU3501 Regulator 层的设备树节点挂载在 MFD 节点下,作为一个 Cell,具体描述如下:

# kernel/scripts/dtc/include-prefixes/arm64/hobot/x5-evb.dtsi
&i2c2 {
	status = "okay";

	hpu3501@1c {
		compatible = "hobot, hpu3501";
		reg = <0x1c>;
		status = "okay";
		hpu3501-regulator {
			master;
			en_pin_map = <0x15>;
			regulators {
				vdd08_bpu_1_reg: BUCK2 {
					regulator-name = "VCC_BPU";
					regulator-min-microvolt = <800000>;
					regulator-max-microvolt = <800000>;
					regulator-enable-ramp-delay = <3000>;
					regulator-ramp-delay = <2000>;
				};
				vdd08_cpu_reg: BUCK3 {
					regulator-name = "VDD08_CPU";
					regulator-min-microvolt = <600000>;
					regulator-max-microvolt = <1000000>;
					regulator-enable-ramp-delay = <3000>;
					regulator-ramp-delay = <2000>;
					regulator-always-on;
					regulator-boot-on;
				};
			};
		};

	};
        ......

该设备树定义了 HPU3501 的两个调节器,其中 BUCK2为 BPU 的电源,BUCK3为 CPU 的电源控制,各个属性说明如下:

  • hpu3501-regulator:定义了 PMIC 的调节器功能。

  • master;:表示这个调节器是主调节器,负责电源管理。

  • en_pin_map = <0x15>;:定义了 PMIC 的 EN_PIN_RMPR 寄存器的值,决定了 EN pin 可以控制哪些 LDO 和 BUCK 的开关。

  • vdd08_bpu_1_reg: BUCK2vdd08_cpu_reg: BUCK3:定义了两个调节器,分别命名为VCC_BPUVDD08_CPU

  • regulator-name:提供了调节器的名称,用于在系统中引用。

  • regulator-min-microvoltregulator-max-microvolt:定义了调节器可以设置的最小和最大电压。

  • regulator-enable-ramp-delayregulator-ramp-delay:定义了电压调整时的延迟,以微秒为单位。

  • regulator-always-onregulator-boot-on:这些属性表明VDD08_CPU调节器应该始终开启,并且在系统启动时自动开启。

关于 Regulator 在设备树中的规范和各个属性的详细说明,可以参考内核文档 kernel/Documentation/devicetree/bindings/regulator/regulator.yaml (BSP 包源码中) 。如果要在设备树中添加 Regulator 的其他属性,需要按照该文档中的描述进行添加,以保证内核能够正确解析和配置。


结合设备树与 PMIC 硬件原理图,可以对 Linux Regulator 框架进一步的理解,结合前文介绍 Regulator 框架的章节中说明了调节器与电源域的组成结构,设备树中描述的这两个调节器,是内核中可以控制的两个电源域,在电路原理图中使用了 VCC_BPU 以及 VDD08_CPU 的所有器件都是这两个调节器的 Consumer。

hpu3501 Regulator 驱动代码说明

关键数据结构:

/**
 * master: ture for master PMIC, false for slave PMIC
 * en_pin_map: rigister 0x06 configuration
 * fault_cfgr: rigister 0x0a configuration
 * ocp_cfg1r: rigister 0x08 configuration
 * ocp_cfg2r: rigister 0x09 configuration
 */
struct hpu3501_regulator {
    bool master;
    u32 en_pin_map;
    u32 fault_cfgr;
    u32 ocp_cfg1r;
    u32 ocp_cfg2r;
};
  • master:表示该 PMIC 是 master 还是 slave

  • en_pin_map:表示 EN PIN 对应控制哪几路电

  • fault_cfgr:异常配置

  • ocp_cfg1r:电流阈值

  • ocp_cfg2r:电流阈值

  • en_pin_map、fault_cfgr、ocp_cfg1r、ocp_cfg2r 分别对应寄存器0x06、0x0a、0x08、0x09,请查看寄存器手册查看具体信息

Regulator 函数操作集:

static const struct regulator_ops hpu3501_ops = {
	.list_voltage = regulator_list_voltage_linear_range,
	.map_voltage = regulator_map_voltage_linear_range,
	.get_voltage_sel = regulator_get_voltage_sel_regmap,
	.set_voltage_sel = dr_regulator_set_voltage_sel_regmap,
	.enable = regulator_enable_regmap,
	.disable = regulator_disable_regmap,
	.is_enabled = regulator_is_enabled_regmap,
};

静态配置信息:

先介绍一个宏:

REGULATOR_LINEAR_RANGE 是一个内核提供的宏,用于定义一个线性电压范围。它的参数包括:

// kernel/include/linux/regulator/driver.h
/* Initialize struct linear_range for regulators */
#define REGULATOR_LINEAR_RANGE(_min_uV, _min_sel, _max_sel, _step_uV)	\
{									\
	.min		= _min_uV,					\
	.min_sel	= _min_sel,					\
	.max_sel	= _max_sel,					\
	.step		= _step_uV,					\
}
  • 起始电压(min_uV:范围的最小电压,单位为微伏(μV)。

  • 起始寄存器值(min_sel:最小电压对应的寄存器选择值。这个值是硬件寄存器中的一个数值,用于设置电压。

  • 结束寄存器值(n_values:范围内的最大寄存器选择值。这个值决定了范围内的最大电压。。

  • 步长(step_uV:相邻寄存器值之间的电压差,单位为微伏(μV)。实际反应的是电压输出可以精确到的最小增量,例如,BUCK1 的步长为 5mV,意味着其电压输出可以精确到 5mV 的增量。

hpu3501 输出8路电,每路电的电路调节步伐都有自己的规律,下面数据结构就是描述的每路电的调节的范围和步长(step):

// kernel/drivers/regulator/hpu3501-regulator.c
/* Buck1-Buck4: step 5mv, range: 0v and [0.6v, 1.275v] */
static const struct linear_range hpu3501_voltage_ranges1[] = {
    REGULATOR_LINEAR_RANGE(0, 0, 0, 0), // 0V
    REGULATOR_LINEAR_RANGE(600000, 1, 120, 0), // 0.6V 固定值
    REGULATOR_LINEAR_RANGE(605000, 121, 255, 5000), // 1.205V 到 1.275V,步长为 5mV
};

/* Buck5: step 15mv, range: 0v and [0.6v, 3.825v] */
static const struct linear_range hpu3501_voltage_ranges2[] = {
    REGULATOR_LINEAR_RANGE(0, 0, 39, 0), // 0V
    REGULATOR_LINEAR_RANGE(600000, 40, 255, 15000), // 0.6V 到 3.825V,步长为 15mV
};

/* Ldo1: step 10mv, range: 0v and [0.6v, 1.3v] */
static const struct linear_range hpu3501_voltage_ranges3[] = {
    REGULATOR_LINEAR_RANGE(0, 0, 0, 0), // 0V
    REGULATOR_LINEAR_RANGE(600000, 1, 60, 0), // 0.6V 固定值
    REGULATOR_LINEAR_RANGE(610000, 61, 129, 10000), // 1.21V 到 1.3V,步长为 10mV
    REGULATOR_LINEAR_RANGE(1300000, 130, 255, 0), //  以上,1.3V 固定值
};

/* Ldo2-Ldo3: step 20mv, range: 0v and [1.2v, 3.7v] */
static const struct linear_range hpu3501_voltage_ranges4[] = {
    REGULATOR_LINEAR_RANGE(0, 0, 0, 0), // 0V
    REGULATOR_LINEAR_RANGE(1200000, 1, 60, 0), // 1.2V 固定值
    REGULATOR_LINEAR_RANGE(1220000, 61, 184, 20000), // 2.42V 到 3.7V,步长为 20mV
    REGULATOR_LINEAR_RANGE(3700000, 185, 255, 0), // 3.7V 以上,固定值
};

注意:上述 linear_range 结构体是根据 hpu3501 的硬件设计定义的,需要严格按照其寄存器说明进行配置,关于各个 BUCK 的电压范围和步长值可以从 hpu3501 寄存器手册中查到相关说明,相关寄存器为 BUCK1_VOUT_CFGR ~ BUCK5_VOUT_CFGR 以及 LDO1_VOUT_CFGR ~ LDO3_VOUT_CFGR ,以 BUCK1_VOUT_CFGR 寄存器为例,相关描述如下:

Buck1 Output Voltage Setting Register (Address=18h)

Bit Field Type Reset Description
7:0 BUCK1_VOUT R/W 10100000 Buck1 output voltage setting from 0.6V to 1.275V with step 5mV.

00000000: 0V

00000001~01111000: 0.6V

01111001: 0.605V

……

11111111: 1.275V

举例说明计算过程:

REGULATOR_LINEAR_RANGE(600000, 40, 255, 15000)

这个示例中定义了一个线性电压范围,其中:

  • 600000 是范围的最小电压值,单位是微伏(μV),即 0.6V。

  • 40 是最小电压对应的寄存器选择值。

  • 255 是最大寄存器选择值。

  • 15000 是相邻寄存器选择值之间的电压步长,单位是微伏(μV),即 15mV。

计算过程

  1. 最小电压:600,000μV(即 0.6V)。

  2. 最大电压:可以通过以下计算得出: 最大电压=最小电压+(最大寄存器选择值−最小寄存器选择值)×步长

    代入具体数值:

    最大电压=600,000μV+(255−40)×15,000μV
            =600,000μV+215×15,000μV
            =600,000μV+3,225,000μV
            =3,825,000μV(即 3.825V)
    

电压计算示例

  • 当寄存器选择值为 40 时,电压为 600,000μV(即 0.6V)。

  • 当寄存器选择值为 41 时,电压为 600,000μV + 15,000μV = 615,000μV(即 0.615V)。

  • 当寄存器选择值为 42 时,电压为 600,000μV + 2 x 15,000μV = 630,000μV(即 0.63V)。

  • 以此类推,直到寄存器选择值为 255,电压为 3,825,000μV(即 3.825V)。

如上,按照在设定的寄存器值范围内(40~255),电压随着寄存器选择值的增加而线性增加。

这样配置好后,就可以在下面代码中通过寄存器进行电压设置:

#define HPU3501_REG(_name, _id, _linear, _step, _vset_mask)                    \
	[ID_##_id] = {                                                         \
		.name = _name,                                                 \
		.id = ID_##_id,                                                \
		.type = REGULATOR_VOLTAGE,                                     \
		.ops = &hpu3501_ops,                                           \
		.n_voltages = HPU3501_VOLTAGE_NUM##_step,                      \
		.linear_ranges = hpu3501_voltage_ranges##_linear,              \
		.n_linear_ranges =                                             \
			ARRAY_SIZE(hpu3501_voltage_ranges##_linear),           \
		.vsel_reg = HPU3501##_##_id##_VSET,                            \
		.vsel_mask = HPU3501_VSET_MASK##_vset_mask,                    \
		.enable_reg = HPU3501_ON_OFF_CTRL,                             \
		.enable_mask = HPU3501##_##_id##_ENA_BIT,                      \
		.disable_val = HPU3501_POWER_OFF,                              \
		.owner = THIS_MODULE,                                          \
	}

/*
 * name:regulator name used
 * id: regulator id, define in hpu3501-regulator.h without prefix "ID_"
 * linear: which linear id to chose, refer to hpu3501_voltage_rangesxx array above
 * step: how many step this regulator have, refer to datasheet
 * vset_mask: refer to datasheet, when set register how many bits are used to control
 */
static const struct regulator_desc hpu3501_regulators[] = {
	HPU3501_REG("BUCK1", BUCK1, 1, 256, 8),
	HPU3501_REG("BUCK2", BUCK2, 1, 256, 8),
	HPU3501_REG("BUCK3", BUCK3, 1, 256, 8),
	HPU3501_REG("BUCK4", BUCK4, 1, 256, 8),
	HPU3501_REG("BUCK5", BUCK5, 2, 256, 8),

	HPU3501_REG("LDO1", LDO1, 3, 256, 8),
	HPU3501_REG("LDO2_CFG1", LDO2_CFG1, 4, 256, 8),
	HPU3501_REG("LDO2_CFG2", LDO2_CFG2, 4, 256, 8),
	HPU3501_REG("LDO3", LDO3, 4, 256, 8),
};

根据当前代码中 hpu3501_regulators 的配置状态,可以得出,hpu3501 的各个电源域的配置状态:

  • BUCK1~BUCK4 使用的是 hpu3501_voltage_ranges1 配置参数。

  • BUCK5 使用的是 hpu3501_voltage_ranges2 配置参数。

  • LDO1 使用的是 hpu3501_voltage_ranges3 配置参数。

  • LDO2_CFG1/LDO2_CFG2 使用的是 hpu3501_voltage_ranges4 配置参数。

  • LDO3 使用的是 hpu3501_voltage_ranges4 配置参数。

hpu3501 RTC 功能简介

说明:X5 EVB 中 hpu3501 RTC 功能当前没有开启。

hpu3501 RTC 代码路径

  kernel/drivers/rtc/rtc-hpu3501.c

hpu3501 RTC CONFIG 开关

CONFIG_RTC_DRV_HPU3501

依赖于

CONFIG_MFD_HPU3501

hpu3501 RTC 关键代码简介

重要数据结构:

struct hpu3501_rtc {
        struct device                *dev;
        struct rtc_device        *rtc;
        unsigned long long        epoch_start;
        u32 opsel;
        u32 pin_alarm_n;
        int irq;
        struct work_struct irq_work;
};
  • epoch_start: 起始时间,在初始化的时候设置了1970年1月1日0时0分0秒。设置的 alarm 时间不得早于这个时间

  • opsel:当 alarm 被触发后 hpu3501的第41引脚输出高电平还是低电平

  • pin_alarm_n: hpu3501的第41个引脚链接的 X5 SOC 的哪个 GPIO

  • irq: 上述 pin_alarm_n 指定 GPIO 的 irq 号

操作函数集:

static const struct rtc_class_ops hpu3501_rtc_ops = {
        .read_time        = hpu3501_rtc_read_time,
        .set_time        = hpu3501_rtc_set_time,
        .set_alarm        = hpu3501_rtc_set_alarm,
        .read_alarm        = hpu3501_rtc_read_alarm,
        .alarm_irq_enable = hpu3501_rtc_alarm_irq_enable,
};

中断处理:

由于在使用 regmap 读取 hpu3501寄存器时,可能会引起睡眠,不能在中断处理函数中使用,因此添加了工作队列的形式来处理

static void hpu3501_alarm_work(struct work_struct *work)
{
        struct hpu3501_rtc *rtc = container_of(work, struct hpu3501_rtc,
                        irq_work);
        struct device *pa_dev = to_hpu3501_dev(rtc->dev);
        int ret;
        u8 status;

        /* check alarm bit status then clear alarm flag */
        ret = hpu3501_read(pa_dev, HPU3501_RTC_ISR, &status);
        if (ret < 0) {
                dev_err(rtc->dev, "read failed with err %d\n", ret);
        }

        if (!(status & HPU3501_ALARM_F)) {
                dev_err(rtc->dev, "alarm flag not set\n");
        } else {
                ret = hpu3501_set_bits(pa_dev, HPU3501_RTC_ISR, HPU3501_ALARM_F);
                if (ret < 0) {
                        dev_err(rtc->dev, "failed to set HPU3501_ALARM_F\n");
                }
        }

        rtc_update_irq(rtc->rtc, 1, RTC_IRQF | RTC_AF);
}

static irqreturn_t hpu3501_alarm_irq_handler(int irq, void *data)
{
        struct hpu3501_rtc *rtc = data;

        schedule_work(&rtc->irq_work);

        return IRQ_HANDLED;
}

4.3.17.5. PMIC 测试

下面给出 X5 EVB 上 PMIC 的测试用例,实现对 HPU3501 各个电源域的电压读取(请不要尝试随意修改各个电源域的输出电压,以免烧毁器件):

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>
#include <linux/i2c-dev.h>

#define I2C_BUS 2 // /dev/i2c-2
#define DEVICE_ADDRESS 0x1C

// Structure to describe a power domain
typedef struct {
    const char *name;          // Name of the power domain (e.g., "b1", "l2")
    uint8_t register_address;  // I2C register address
    int step_mv;               // Voltage step in millivolts
    int min_voltage_mv;        // Minimum voltage in millivolts
    int min_register_value;    // Minimum register value
    int max_register_value;    // Maximum register value
} PowerDomain;

// Array of power domains
const PowerDomain power_domains[] = {
    {"b1", 0x18, 5, 600, 120, 255},  // BUCK1
    {"b2", 0x1A, 5, 600, 120, 255},  // BUCK2
    {"b3", 0x1C, 5, 600, 120, 255},  // BUCK3
    {"b4", 0x1E, 5, 600, 120, 255},  // BUCK4
    {"b5", 0x20, 15, 600, 40, 255}, // BUCK5
    {"l1", 0x22, 10, 600, 60, 130},  // LDO1
    {"l2", 0x24, 20, 1200, 60, 185}, // LDO2
    {"l3", 0x27, 20, 1200, 60, 185}, // LDO3
};

// Function to execute i2cget command
int execute_i2cget(uint8_t register_address) {
    char command[128];
    snprintf(command, sizeof(command), "sudo i2cget -y -f %d 0x%02X 0x%02X", I2C_BUS, DEVICE_ADDRESS, register_address);

    FILE *fp = popen(command, "r");
    if (!fp) {
        fprintf(stderr, "Failed to execute i2cget command: %s\n", command);
        exit(EXIT_FAILURE);
    }

    int register_value;
    fscanf(fp, "0x%x", &register_value);
    pclose(fp);

    return register_value;
}

// Function to print voltage ranges and step sizes for all power domains
void print_voltage_ranges() {
    for (size_t i = 0; i < sizeof(power_domains) / sizeof(power_domains[0]); i++) {
        printf("%s: %.3fV to %.3fV, step %dmV (Register Address: 0x%02X)\n",
               power_domains[i].name,
               power_domains[i].min_voltage_mv / 1000.0,
               (power_domains[i].min_voltage_mv + power_domains[i].step_mv * (power_domains[i].max_register_value - power_domains[i].min_register_value)) / 1000.0,
               power_domains[i].step_mv,
               power_domains[i].register_address);
    }
}

// Function to print help information
void print_help(const char *program_name) {
    printf("Usage: %s <get|info|h> [domain]\n", program_name);
    printf("  get <domain>|all: Get the current voltage for the specified power domain or all domains.\n");
    printf("  info: Print voltage ranges and step sizes for all power domains.\n");
    printf("  h: Print this help message.\n");
}

// Function to find power domain by name
const PowerDomain *find_power_domain(const char *name) {
    for (size_t i = 0; i < sizeof(power_domains) / sizeof(power_domains[0]); i++) {
        if (strcmp(power_domains[i].name, name) == 0) {
            return &power_domains[i];
        }
    }
    return NULL;
}

int main(int argc, char *argv[]) {
    if (argc < 2) {
        print_help(argv[0]);
        exit(EXIT_FAILURE);
    }

    if (strcmp(argv[1], "info") == 0) {
        print_voltage_ranges();
        return 0;
    } else if (strcmp(argv[1], "h") == 0) {
        print_help(argv[0]);
        return 0;
    }

    if (strcmp(argv[1], "get") == 0) {
        if (argc != 3) {
            fprintf(stderr, "Error: Invalid number of arguments for 'get' command.\n");
            exit(EXIT_FAILURE);
        }
        if (strcmp(argv[2], "all") == 0) {
            for (size_t i = 0; i < sizeof(power_domains) / sizeof(power_domains[0]); i++) {
                int register_value = execute_i2cget(power_domains[i].register_address);
                int voltage_mv = power_domains[i].min_voltage_mv + (register_value - power_domains[i].min_register_value) * power_domains[i].step_mv;

                printf("%s current voltage: %d mV (register value: 0x%X)\n", power_domains[i].name, voltage_mv, register_value);
            }
        } else {
            const PowerDomain *domain = find_power_domain(argv[2]);
            if (!domain) {
                fprintf(stderr, "Error: Invalid regulator name. Please use 'b1', 'b2', 'b3', 'b4', 'b5', 'l1', 'l2', 'l3'.\n");
                print_help(argv[0]);
                exit(EXIT_FAILURE);
            }

            int register_value = execute_i2cget(domain->register_address);
            int voltage_mv = domain->min_voltage_mv + (register_value - domain->min_register_value) * domain->step_mv;

            printf("%s current voltage: %d mV (register value: 0x%X)\n", domain->name, voltage_mv, register_value);
        }
    } else {
        fprintf(stderr, "Error: Invalid command. Use 'get', 'info', or 'h'.\n");
        print_help(argv[0]);
        exit(EXIT_FAILURE);
    }

    return 0;
}

编译测试:

arm_gcc pmic_only_get.c -o pmic_only_get

其中 arm_gcc 是在 .bashrc 中创建的编译工具的别名:

# ~/.bashrc
alias arm_gcc='/opt/arm-gnu-toolchain-11.3.rel1-x86_64-aarch64-none-linux-gnu/bin/aarch64-none-linux-gnu-gcc'

该测例的使用方式如下:

命令格式:

Usage: ./pmic_only_get <get|info|h> [domain]
  • ./pmic_only_get:这是程序的执行文件名。

  • <get|info|h>:这是必须提供的第一个参数,用于指定要执行的操作。它有三种可能的值:

    • get:用于读取电压。

    • info:用于打印电压范围和步进值。

    • h:用于打印帮助信息。

  • [domain]:这是一个可选参数,仅在使用get命令时需要提供。它指定要操作的电源域名称,或者使用all来表示所有电源域。

具体命令及其功能:

get <domain>|all

get <domain>|all: Get the current voltage for the specified power domain or all domains.
  • 功能:读取指定电源域或所有电源域的当前电压。

  • 参数

    • <domain>:电源域的名称(如b1b2l2等)。必须是程序支持的电源域之一。

    • all:表示读取所有电源域的当前电压。

  • 示例

    • ./pmic_only_get get b1:读取电源域b1的当前电压。

    • ./pmic_only_get get all:读取所有电源域的当前电压。

info

info: Print voltage ranges and step sizes for all power domains.
  • 功能:打印所有电源域的电压范围和步进值。

  • 参数:无。

  • 示例

    • ./pmic_only_get info:打印所有电源域的电压范围和步进值。

h

h: Print this help message.
  • 功能:打印帮助信息,说明程序的用法。

  • 参数:无。

  • 示例

    • ./pmic_only_get h:打印帮助信息。

测试日志如下:

root@buildroot:/userdata# ./pmic_only_get h 
Usage: ./pmic_only_get <get|info|h> [domain]
  get <domain>|all: Get the current voltage for the specified power domain or all domains.
  info: Print voltage ranges and step sizes for all power domains.
  h: Print this help message.
root@buildroot:/userdata# ./pmic_only_get info
b1: 0.600V to 1.275V, step 5mV (Register Address: 0x18)
b2: 0.600V to 1.275V, step 5mV (Register Address: 0x1A)
b3: 0.600V to 1.275V, step 5mV (Register Address: 0x1C)
b4: 0.600V to 1.275V, step 5mV (Register Address: 0x1E)
b5: 0.600V to 3.825V, step 15mV (Register Address: 0x20)
l1: 0.600V to 1.300V, step 10mV (Register Address: 0x22)
l2: 1.200V to 3.700V, step 20mV (Register Address: 0x24)
l3: 1.200V to 3.700V, step 20mV (Register Address: 0x27)
root@buildroot:/userdata# ./pmic_only_get get b1
b1 current voltage: 800 mV (register value: 0xA0)
root@buildroot:/userdata# ./pmic_only_get get b2
b2 current voltage: 0 mV (register value: 0x0)
root@buildroot:/userdata# ./pmic_only_get get l2
l2 current voltage: 3300 mV (register value: 0xA5)
root@buildroot:/userdata# ./pmic_only_get get all
b1 current voltage: 800 mV (register value: 0xA0)
b2 current voltage: 0 mV (register value: 0x0)
b3 current voltage: 850 mV (register value: 0xAA)
b4 current voltage: 1100 mV (register value: 0xDC)
b5 current voltage: 1800 mV (register value: 0x78)
l1 current voltage: 800 mV (register value: 0x50)
l2 current voltage: 3300 mV (register value: 0xA5)
l3 current voltage: 1800 mV (register value: 0x5A)

注意:上面各个电源域的当前电压值与实际使用的单板实际配置相关,不一定与上面测试结果相同。


另外,利用 PMIC Suspend 测试可以参考系统休眠和唤醒测试章节。

关于 PMIC 配合 Thermal 系统调整关机温度的相关内容可以参考 驱动平台 thermal 系统调试说明,比如调整关机温度为105摄氏度,可以执行下面命令:

echo 105000 > /sys/devices/virtual/thermal/thermal_zone1/trip_point_2_temp

4.3.17.6. PMIC 常见问题

在使用 PMIC 时,可能会遇到一些问题。以下是一些常见问题及其解决方案:

电压输出异常

  • 问题表现:部分电源输出电压高于或低于正常范围,可能导致设备不稳定或无法启动。

  • 解决方案

    • 检查电源负载是否超出设计范围。

    • 确认电容是否有足够的滤波作用。

    • 检查动态电压调整参数设置是否正确。

    • 增加去耦电容以改善电源稳定性。

通信故障

  • 问题表现:设备无法与 PMIC 正常通信,通常表现为 I2C 总线通信错误。

  • 解决方案

    • 检查 I2C 总线的连接是否正确,包括设备地址是否配置正确。

    • 确保通信线路没有电气噪声干扰。

    • 使用滤波器减少电磁干扰。

噪声干扰或电源质量问题

  • 问题描述:PMIC 输出电源质量差,产生较大的噪声或纹波,影响系统稳定性,尤其在高性能设备中尤为明显。

  • 解决方案

    • 增加适当的滤波电容和电感,以减少输出纹波和噪声。

    • 在设计时选择适当的开关频率,避免与其他敏感电路发生频率干扰。

    • 优化 PCB 布局,减少电源线长距离和交叉,增加良好的接地设计。

    • 使用适当的屏蔽材料和电磁兼容性(EMC)设计以降低噪声。

4.3.17.7. 参考文档

Power Management

Linux voltage and current regulator framework

Regulator Consumer Driver Interface

内核文档:

kernel/Documentation/devicetree/bindings/regulator/regulator.yaml

kernel/Documentation/ABI/testing/sysfs-class-mtd

kernel/Documentation/ABI/testing/sysfs-class-regulator