# PMIC 驱动调试指南

## PMIC 概述

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

## PMIC 的功能描述

### PMIC 典型应用

#### PMIC 典型应用简介

1. **电源转换与分配**  
   - **DC-DC 转换器**：高效转换输入电压为多路不同电压输出，供 CPU、外设等使用。  
   - **LDO（低压差稳压器）**：适用于低功耗场景，提供稳定电压输出（如传感器、时钟电路）。  

2. **系统电源控制**  
   - 管理开机/关机流程：响应电源键信号，控制 CPU 上电时序及复位操作。  
   - **[动态调频调压 (DVFS)](../system_component_development/37-Low-power-user-guide.html#span-id-dvfs-explanation)**：根据 CPU/GPU 负载动态调整核心电压，平衡性能与功耗。  

3. **保护机制**  
   - 集成过压（OVP）、欠压（UVP）、过流（OCP）、过温（OTP）保护电路，确保系统安全运行。
   - PMIC 的典型应用之一就是进行热关断保护，实施热关断是为了防止因过热和功耗而造成损坏。触发热关断时，PMIC 将关闭所有电源轨，直到结温降至设定值以下，然后设备再次开始通电。详细介绍可以参考[驱动平台 thermal 系统调试说明](./17-thermal_Usage.html#span-id-thermal-debugging-instructions-thermal)章节。

#### HPU3501 典型应用介绍

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

![HPU3501_typical_application](_static/_images/32-PMIC_Driver_Debug_Guide/HPU3501_typical_application.png)

上图中 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 模块的电路原理图如下：

<a id="PMIC_Circuit_Schematic"></a>
![PMIC_Circuit_Schematic](_static/_images/32-PMIC_Driver_Debug_Guide/PMIC_Circuit_Schematic.png)

### PMIC 功能原理

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

1. **电压调节**
   - **电压转换**：PMIC 内部包含多个电压调节器，能够将输入的电源电压转换为不同的稳定输出电压，为系统内的不同模块（如 CPU、GPU、内存、外围设备等）提供所需的电源电压。
   - **电压调整**：通过调节输出电压，PMIC 可以根据系统的需求调整功率，降低功耗或者提高电压满足负载需求。这通常与[动态调频调压 (DVFS)](../system_component_development/37-Low-power-user-guide.html#span-id-dvfs-explanation)相关，用来在不同负载下优化功耗。

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

3. **电流和功率保护**
   - **过电流保护**：PMIC 包含过电流保护功能，防止电流超过预定的安全值，从而避免对设备造成损坏。
   - **过热保护**：PMIC 会监控温度，如果 CPU 或其他监控的组件温度过高，会采取措施降低功耗或关闭部分功能，以防止设备过热。

## 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](_static/_images/32-PMIC_Driver_Debug_Guide/MFD_driver.png)

#### Regulator 框架简介

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

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

在 [*Linux voltage and current regulator framework*](https://docs.kernel.org/power/regulator/overview.html) 文档中，定义了一系列与电源调节器（Regulator）相关的术语。这些术语用于描述电源管理框架中的各个组件及其功能。以下是这些术语的简介：

**1. Regulator（调节器）：**

- **定义**：为其他设备提供电源的电子设备。
- **功能**：大多数调节器可以启用或禁用其输出，某些调节器还可以控制其输出电压和/或电流。
- **示例**：

  ```bash
  Input Voltage -> Regulator -> Output Voltage
  ```

**2. PMIC（电源管理集成电路）：**

- **定义**：包含多个调节器的集成电路，通常还包含其他子系统。
- **功能**：PMIC 用于管理系统的电源分配和电源管理功能。

**3. Consumer（消费者）：**

- **定义**：由调节器供电的电子设备。
- **分类**：
  - **静态消费者**：不需要改变其供电电压或电流限制，仅需要启用或禁用其电源。其供电电压由硬件、引导程序、固件或内核板初始化代码设置。
  - **动态消费者**：需要根据操作需求改变其供电电压或电流限制。

**4. Power Domain（电源域）：**

- **定义**：由调节器、开关或其他电源域的输出电源供电的电子电路。
- **示例**：
<a id="Power_Domain"></a>

  ```bash
  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。
  - **电源域级别**：在软件中由内核级板初始化代码定义，用于限制电源域的电源范围。例如在上述[电源域结构](#Power_Domain)中：
    - 电源域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](_static/_images/32-PMIC_Driver_Debug_Guide/Regulator_framework.png)

说明如下：

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_read`和`regmap_write`），用于访问设备寄存器，包括 SOC 内部的寄存器。
- regmap 是 Linux 内核为了减少慢速 I/O 操作在驱动开发中的冗余开销而设计的，它提供了一种通用的接口来操作硬件寄存器。
- regmap 在驱动程序和硬件之间引入了缓存机制，从而减少低速 I/O 操作的次数，提高了访问效率，但是可能会降低实时性。
  
整个 Regmap 分为 3 层，其拓扑结构如下：

![Regmap_Topology](_static/_images/32-PMIC_Driver_Debug_Guide/Regmap_Topology.png)

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

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

- **regmap_read** 和 **regmap_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](_static/_images/32-PMIC_Driver_Debug_Guide/Regmap_framework.png)

#### 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 为例，上述联系可以用下图表示：

<a id="hpu3501_pmic_framework"></a>
![PMIC_framework](_static/_images/32-PMIC_Driver_Debug_Guide/PMIC_framework.png)

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

![hpu3501](_static/_images/32-PMIC_Driver_Debug_Guide/hpu-3501.jpg)

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

### MFD 驱动代码简介

#### MFD config 开关

```bash
CONFIG_MFD_CORE=y
```

`CONFIG_MFD_CORE` 配置项启用了 MFD（Multi-Function Device）核心框架。

#### MFD 代码路径

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

#### MFD 关键代码简介

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

```c
// 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_enable` 和 `mfd_cell_disable`

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

    ```c
    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 单元。

    ```c
    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 单元数组、设备数量、内存资源基地址、中断基地址和中断域等参数。

    ```c
    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），并忽略内存资源基地址、中断基地址和中断域。

    ```c
    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_devices` 和 `mfd_remove_devices_late`

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

    ```c
    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）支持，以确保资源的自动管理。

   ```c
   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 开关

```bash
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 代码路径

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

#### Regulator 关键代码简介

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

```c
// 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）的静态信息。它包含了调节器的核心属性和操作接口，这些信息在调节器驱动注册时提供给调节器框架。

---

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

---

```c
// 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. **调节器注册与注销**

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

    ```c
    // 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. **事件通知**

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

    ```c
    // kernel/drivers/regulator/core.c
    int regulator_notifier_call_chain(struct regulator_dev *rdev,
                                    unsigned long event, void *data);
    ```

    - `regulator_notifier_call_chain`：触发调节器事件，通知所有注册的监听器。

3. **中断处理**

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

    ```c
    // 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. **调节器操作**

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

    ```c
    // 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. **电压映射与控制**

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

    ```c
    // 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. **辅助函数**

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

    ```c
    // 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 开关

```bash
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 代码路径

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

```

#### Regmap 关键代码简介

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

```c
// 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 子系统。

---

```c
// 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 框架中，寄存器缓存是一种可选的机制，用于存储设备寄存器的值，以减少对硬件的访问次数，从而提高性能。

---

```c
// 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**
   - 功能：尝试从指定的寄存器地址读取数据。
   - 函数原型：

     ```c
     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**
   - 功能：尝试从指定的寄存器地址开始，批量读取多个寄存器的数据。
   - 函数原型：

     ```c
     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**
   - 功能：尝试更新指定寄存器的特定位。
   - 函数原型：

     ```c
     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**
   - 功能：尝试向指定的寄存器地址写入数据。
   - 函数原型：

     ```c
     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**
   - 功能：尝试从指定的寄存器地址开始，批量写入多个寄存器的数据。
   - 函数原型：

     ```c
     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`，并发出警告。

## PMIC 使用说明

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

### hpu3501 MFD 层驱动代码

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

#### 代码路径

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

#### config 开关

```bash
CONFIG_MFD_HPU3501
```

#### MFD 设备树描述

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

```bash
# 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 设备

```c
// 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);
}
```

**读写寄存器的接口：**

```c
// 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 代码路径

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

#### hpu3501 Regulator CONFIG

```bash
  CONFIG_REGULATOR_HPU3501
```

依赖于

```bash
CONFIG_MFD_HPU3501
```

#### Regulator 设备树描述

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

```bash
# 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: BUCK2`** 和 **`vdd08_cpu_reg: BUCK3`**：定义了两个调节器，分别命名为`VCC_BPU`和`VDD08_CPU`。
- **`regulator-name`**：提供了调节器的名称，用于在系统中引用。
- **`regulator-min-microvolt` 和 `regulator-max-microvolt`**：定义了调节器可以设置的最小和最大电压。
- **`regulator-enable-ramp-delay` 和 `regulator-ramp-delay`**：定义了电压调整时的延迟，以微秒为单位。
- **`regulator-always-on` 和 `regulator-boot-on`**：这些属性表明`VDD08_CPU`调节器应该始终开启，并且在系统启动时自动开启。

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

---

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

#### hpu3501 Regulator 驱动代码说明

**关键数据结构：**

```c
/**
 * 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 函数操作集：**

```c
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` 是一个内核提供的宏，用于定义一个线性电压范围。它的参数包括：

```c
// 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）：

```c
// 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.<br>   <br>00000000: 0V<br>   <br>00000001~01111000:   0.6V<br>   <br>01111001: 0.605V<br>   <br>……<br>   <br>11111111: 1.275V    |

---

举例说明计算过程：

```c
REGULATOR_LINEAR_RANGE(600000, 40, 255, 15000)
```

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

- **`600000`** 是范围的最小电压值，单位是微伏（μV），即 0.6V。
- **`40`** 是最小电压对应的寄存器选择值。
- **`255`** 是最大寄存器选择值。
- **`15000`** 是相邻寄存器选择值之间的电压步长，单位是微伏（μV），即 15mV。

**计算过程**：

1. **最小电压**：600,000μV（即 0.6V）。
2. **最大电压**：可以通过以下计算得出：
   最大电压=最小电压+(最大寄存器选择值−最小寄存器选择值)×步长

   代入具体数值：

    ```bash
    最大电压=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），电压随着寄存器选择值的增加而线性增加。

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

```c
#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 代码路径

```bash
  kernel/drivers/rtc/rtc-hpu3501.c
```

#### hpu3501 RTC CONFIG 开关

```bash
CONFIG_RTC_DRV_HPU3501
```

依赖于

```bash
CONFIG_MFD_HPU3501
```

#### hpu3501 RTC 关键代码简介

**重要数据结构：**

```c
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 号

**操作函数集：**

```c
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寄存器时，可能会引起睡眠，不能在中断处理函数中使用，因此添加了工作队列的形式来处理

```c
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;
}
```

## PMIC 测试

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

```c
#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;
}
```

编译测试：

```bash
arm_gcc pmic_only_get.c -o pmic_only_get
```

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

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

该测例的使用方式如下：

**命令格式：**

```bash
Usage: ./pmic_only_get <get|info|h> [domain]
```

- **`./pmic_only_get`**：这是程序的执行文件名。
- **`<get|info|h>`**：这是必须提供的第一个参数，用于指定要执行的操作。它有三种可能的值：
  - `get`：用于读取电压。
  - `info`：用于打印电压范围和步进值。
  - `h`：用于打印帮助信息。
- **`[domain]`**：这是一个可选参数，仅在使用`get`命令时需要提供。它指定要操作的电源域名称，或者使用`all`来表示所有电源域。

**具体命令及其功能：**

**`get <domain>|all`**

```bash
get <domain>|all: Get the current voltage for the specified power domain or all domains.
```

- **功能**：读取指定电源域或所有电源域的当前电压。
- **参数**：
  - `<domain>`：电源域的名称（如`b1`、`b2`、`l2`等）。必须是程序支持的电源域之一。
  - `all`：表示读取所有电源域的当前电压。
- **示例**：
  - `./pmic_only_get get b1`：读取电源域`b1`的当前电压。
  - `./pmic_only_get get all`：读取所有电源域的当前电压。

**`info`**

```bash
info: Print voltage ranges and step sizes for all power domains.
```

- **功能**：打印所有电源域的电压范围和步进值。
- **参数**：无。
- **示例**：
  - `./pmic_only_get info`：打印所有电源域的电压范围和步进值。

**`h`**

```bash
h: Print this help message.
```

- **功能**：打印帮助信息，说明程序的用法。
- **参数**：无。
- **示例**：
  - `./pmic_only_get h`：打印帮助信息。

测试日志如下：

```bash
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 测试可以参考[系统休眠和唤醒测试](../system_component_development/37-Low-power-user-guide.html#span-id-system-suspend-test)章节。

关于 PMIC 配合 Thermal 系统调整关机温度的相关内容可以参考 [驱动平台 thermal 系统调试说明](./17-thermal_Usage.html#span-id-thermal-debugging-instructions-thermal)，比如调整关机温度为105摄氏度，可以执行下面命令：

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

## PMIC 常见问题

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

### 电压输出异常

- **问题表现**：部分电源输出电压高于或低于正常范围，可能导致设备不稳定或无法启动。
- **解决方案**：
  - 检查电源负载是否超出设计范围。
  - 确认电容是否有足够的滤波作用。
  - 检查动态电压调整参数设置是否正确。
  - 增加去耦电容以改善电源稳定性。

### 通信故障

- **问题表现**：设备无法与 PMIC 正常通信，通常表现为 I2C 总线通信错误。
- **解决方案**：
  - 检查 I2C 总线的连接是否正确，包括设备地址是否配置正确。
  - 确保通信线路没有电气噪声干扰。
  - 使用滤波器减少电磁干扰。

### 噪声干扰或电源质量问题

- **问题描述**：PMIC 输出电源质量差，产生较大的噪声或纹波，影响系统稳定性，尤其在高性能设备中尤为明显。
- **解决方案**：
  - 增加适当的滤波电容和电感，以减少输出纹波和噪声。
  - 在设计时选择适当的开关频率，避免与其他敏感电路发生频率干扰。
  - 优化 PCB 布局，减少电源线长距离和交叉，增加良好的接地设计。
  - 使用适当的屏蔽材料和电磁兼容性（EMC）设计以降低噪声。

## 参考文档

[Power Management](https://docs.kernel.org/power/index.html)

[Linux voltage and current regulator framework](https://docs.kernel.org/power/regulator/overview.html)

[Regulator Consumer Driver Interface](https://docs.kernel.org/power/regulator/consumer.html)

内核文档：

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

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

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