Linux 驱动之 Regulator 子系统

概述

该框架旨在提供标准内核接口来控制电压和电流 Regulator。其目的是允许系统动态控制 Regulator 功率输出,以节省功率并延长电池寿命。 这适用于电压 Regulator(电压输出可控)和电流 Regulator(电流限制可控)。

命名法

  • Regulator:
    为其他设备供电的电子设备。大多数稳压器可以启用和禁用其输出,而有些可以控制其输出电压和/或电流。输入电压 -> 稳压器 -> 输出电压

  • PMIC:
    电源管理 IC。 包含多个稳压器且通常包含其他子系统的 IC

  • Consumer:
    由稳压器供电的电子设备。 消费者可分为两类:

    静态:消费者不会改变其电源电压或电流限制。 它只需要启用或禁用其电源。 其电源电压由硬件、引导加载程序、固件或内核板初始化代码设置 动态:消费者需要改变其电源电压或电流限制以满足操作需求

  • Power Domain:
    由稳压器、开关或其他电源域的输出电源提供其输入电源的电子电路。电源 Regulator 可能位于一个或多个开关后面,例如:

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

    这是一个稳压器和三个电源域:

      Domain 1: Switch-1, Consumers D & E.
      Domain 2: Switch-2, Consumers B & C.
      Domain 3: Consumer A.

    这代表了一种"supplies"关系:

      Domain-1 --> Domain-2 --> Domain-3.
  • Constraints:
    约束用于定义性能和硬件保护的功率级别。约束存在于三个层面: 1、稳压器级别:这由稳压器硬件操作参数定义,并在稳压器数据表中指定,例如:

      电压输出范围为 800mV -> 3500mV
      稳压器电流输出限制为 20mA @ 5V,但为 10mA @ 10V

    2、功率域级别:这是由内核级板初始化代码在软件中定义的。 它用于将功率域限制在特定的功率范围内,例如:

      Domain-1 电压为 3300mV
      Domain-2 电压为 1400mV -> 1600mV
      Domain-3 电流限制为 0mA -> 20mA

    3、Consumer 级别:这是由 Consumer 驱动程序动态设置电压或电流限制级别定义的。例如 消费类背光驱动器要求将电流从 5mA 增加到 10mA,以增加 LCD 亮度。 这将通过以下级别:

      Consumer:需要增加 LCD 亮度。 在亮度表中查找并请求下一个当前 mA 值(Consumer 驱动程序可用于基于相同参考设备的多个不同个性)
      电源域:是此域和系统状态(例如电池电源、USB 电源)的域操作限制内的新电流限制
      稳压器域:是输入/输出电压的稳压器操作参数内的新电流限制。

    如果 Regulator 请求通过所有约束测试,则应用新的 Regulator 值

Design

Regulator 框架专为基于 SoC 的设备而设计,但也可能与非 SoC 设备相关,并分为以下四个接口:

  • Consumer driver interface:
    这使用与内核时钟接口类似的 API,因为 Consumer 驱动程序可以获取和放置一个 Regulator(就像他们可以使用时钟 atm)并获取/设置电压、电流限制、启用和禁用。 这应该允许 Consumer 完全控制他们的电源电压和电流限制

  • Regulator driver interface:
    这允许 Regulator 驱动程序注册其 Regulator 并向内核提供操作。 它还具有用于向客户端传播事件的通知程序调用链

  • Machine interface:
    此接口用于机器特定代码,并允许为每个稳压器创建电压/电流域(带约束)。 它可以提供稳压器约束,以防止由于有缺陷的客户端驱动程序引起的过压或过流而损坏设备。 它还允许创建 regulators 树,其中一些 regulators 由其他 regulators 提供(类似于时钟树)

  • Userspace ABI:
    该框架还通过 sysfs 将许多有用的电压/电流/操作模式数据导出到用户空间。 这可用于帮助监控设备功耗和状态

Regulator Consumer Driver Interface

本节描述了消费类设备驱动程序的调节器接口。

Consumer Regulator Access (static & dynamic drivers)

消费者驱动程序可以通过调用 regulator_get 访问其供应调节器:

1
regulator = regulator_get(dev, "Vcc");

消费者传入其结构设备指针和电源 ID。 然后内核通过查询特定于机器的查找表找到正确的调节器。 如果查找成功,则此调用将返回一个指向提供此使用者的结构调节器的指针。

要释放调节器,消费者驱动程序应调用:

1
regulator_put(regulator);

消费者可以由多个调节器供电,例如 具有模拟和数字电源的编解码器消费者:

1
2
digital = regulator_get(dev, "Vcc");  /* digital core */
analog = regulator_get(dev, "Avdd"); /* analog */

调节器访问函数 regulator_get() 和 regulator_put() 通常会分别在您的设备驱动程序 probe() 和 remove() 中调用。

Regulator Output Enable & Disable (static & dynamic drivers)

消费者可以通过调用 regulator_enable 启用调节器:

1
int regulator_enable(regulator);

在调用 regulator_enabled() 之前,电源可能已经启用。 如果消费者共享调节器或调节器先前已由引导加载程序或内核板初始化代码启用,则可能会发生这种情况。

消费者可以通过调用 regulator_is_enabled 判断是否启用了调节器:

1
int regulator_is_enabled(regulator);

当调节器启用时,这将返回大于零。

消费者可以在不再需要时通过调用禁用其供应:

1
int regulator_disable(regulator);

如果它与其他消费者共享,这可能不会禁用供应。 仅当启用的参考计数为零时,才会禁用调节器。

最后,在紧急情况下可以强制禁用调节器:

1
int regulator_force_disable(regulator);

这将立即强制关闭稳压器输出。 所有消费者都将断电。

Regulator Voltage Control & Status (dynamic drivers)

一些消费类驱动器需要能够动态改变其电源电压以匹配系统工作点。 例如 CPUfreq 驱动程序可以随频率调整电压以节省电量,SD 驱动程序可能需要选择正确的卡电压等。

消费者可以通过调用来控制他们的电源电压:

1
int regulator_set_voltage(regulator, min_uV, max_uV);

其中 min_uV 和 max_uV 是以微伏为单位的最小和最大可接受电压。

这可以在调节器启用或禁用时调用。如果在已启用 regulator 时调用,则电压会立即更改,否则电压配置会更改,并且在下一次启用稳压器时会物理设置电压。

调节器配置的电压输出可以通过调用找到:

1
int regulator_get_voltage(regulator);

无论调节器是启用还是禁用,get_voltage() 都将返回配置的输出电压,并且不应用于确定调节器输出状态。 然而,这可以与 is_enabled() 结合使用来确定稳压器物理输出电压。

Regulator Current Limit Control & Status (dynamic drivers)

一些消费类驱动程序需要能够动态更改其电源电流限制以匹配系统工作点。 例如 LCD 背光驱动程序可以更改电流限制以改变背光亮度,USB 驱动程序可能希望在供电时将限制设置为 500mA。

消费者可以通过调用来控制他们的电源电流限制:

1
int regulator_set_current_limit(regulator, min_uA, max_uA);

其中 min_uA 和 max_uA 是以微安为单位的最小和最大可接受电流限制。

这可以在调节器启用或禁用时调用。如果在已启用电流限制时调用,则电流限制会立即更改,否则电流限制配置会更改,并且在下一次启用调节器时会设置电流限制。

通过调用可以找到调节器电流限制:

1
int regulator_get_current_limit(regulator);

无论调节器是启用还是禁用,get_current_limit() 都将返回电流限制,并且不应用于确定调节器电流负载。

Regulator Operating Mode Control & Status (dynamic drivers)

一些消费者可以通过改变其电源调节器的工作模式来进一步节省系统功率,以便在消费者工作状态发生变化时提高效率。 例如 消费者驱动程序空闲,随后消耗较少的电流。调节器操作模式可以间接或直接改变。

  • 间接操作模式控制: 消费者驱动程序可以通过以下调用请求更改其电源调节器操作模式:

      int regulator_set_load(struct regulator *regulator, int load_uA);

    这将导致 core 重新计算调节器上的总负载(基于其所有消费者)并更改操作模式(如果必要和允许)以最佳匹配当前操作负载。load_uA 值可以从消费者的数据表中确定。 例如 大多数数据表都有表格显示在某些情况下消耗的最大电流。大多数消费者将使用间接操作模式控制,因为他们不了解调节器或调节器是否与其他消费者共享

  • 直接操作模式控制: 定制或紧密耦合的驱动器可能希望根据其工作点直接控制调节器的工作模式,这可以通过调用:

      int regulator_set_mode(struct regulator *regulator, unsigned int mode);
      unsigned int regulator_get_mode(struct regulator *regulator);

    直接模式将仅由了解有关调节器且不与其他消费者共享调节器的消费者使用

Regulator Events

监管机构可以将外部事件通知消费者,在监管机构压力或故障条件下,消费者可能会收到事件。

消费者调用以下接口注册感兴趣的事件:

1
2
int regulator_register_notifier(struct regulator *regulator,
struct notifier_block *nb);

消费者调用以下接口反注册感兴趣的事件:

1
2
int regulator_unregister_notifier(struct regulator *regulator,
struct notifier_block *nb);

监管机构使用内核通知程序框架向感兴趣的消费者发送事件。

Regulator Direct Register Access

某些类型的电源管理硬件或固件被设计为需要对调节器进行低级硬件访问,而无需内核参与,此类设备的示例有:

  • 带有压控振荡器和控制逻辑的时钟源,可通过 I2C 改变电源电压,以实现所需的输出时钟速率。
  • 热管理固件,可发出任意 I2C 事务以在过热条件下执行系统断电。

要设置这样的设备/固件,需要为其配置各种参数,例如调节器的 I2C 地址、各种调节器寄存器的地址等。 监管者框架提供了以下查询这些详细信息的帮助程序。

特定于总线的详细信息,例如 I2C 地址或传输速率,由 regmap 框架处理。 要获取监管机构的 regmap(如果支持),请使用:

1
struct regmap *regulator_get_regmap(struct regulator *regulator);

要获取稳压器电压选择器寄存器的硬件寄存器偏移量和位掩码,请使用:

1
2
3
int regulator_get_hardware_vsel_register(struct regulator *regulator,
unsigned *vsel_reg,
unsigned *vsel_mask);

要将调节器框架电压选择器代码(由调节器列表电压使用)转换为可直接写入电压选择器寄存器的特定于硬件的电压选择器,请使用:

1
2
int regulator_list_hardware_vsel(struct regulator *regulator,
unsigned selector);

Regulator Driver Interface

Registration

驱动程序可以通过调用以下接口注册 Regulator:

1
2
struct regulator_dev *regulator_register(struct regulator_desc *regulator_desc,
const struct regulator_config *config);

这会将 regulator 的能力和操作注册到 regulator 核心。

注销接口如下:

1
void regulator_unregister(struct regulator_dev *rdev);

Regulator Events

调节器可以通过调用以下方式向消费者驱动程序发送事件(例如过热、欠压等):

1
2
int regulator_notifier_call_chain(struct regulator_dev *rdev,
unsigned long event, void *data);

Regulator API design notes

本节对影响调节器 API 设计的一些设计注意事项进行了简要的、部分结构化的概述。

Safety

  • 调节器配置中的错误会对系统产生非常严重的后果,可能包括持久的硬件损坏。
  • 无法自动确定系统的电源配置 - 同一芯片的软件等效变体可能具有不同的电源要求,并且并非所有具有电源要求的组件对软件都是可见的。

API 不应更改硬件状态,除非它知道在此特定系统上执行这些更改是安全的。

Consumer use cases

  • 系统中的绝大多数设备除了能够打开或关闭电源外,不需要对其电源进行任何运行时配置。
  • 系统中的许多电源将在许多不同的消费者之间共享。

消费者 API 的结构应使这些用例非常易于处理,并且消费者无需任何额外工作即可使用共享的 supplies。

Regulator Machine Driver Interface

调节器机器驱动程序接口用于板/机器特定的初始化代码以配置调节器子系统。

Regulator-1 -+-> Regulator-2 --> [Consumer A @ 1.8 - 2.0V]
             |
             +-> [Consumer B @ 3.3V]

消费者 A 和 B 的驱动器必须映射到正确的调节器,以控制它们的电源。 这种映射可以在机器初始化代码中通过为每个调节器创建一个结构体 regulator_consumer_supply 来实现:

1
2
3
4
struct regulator_consumer_supply {
const char *dev_name; /* consumer dev_name() */
const char *supply; /* consumer supply - e.g. "vcc" */
};

对于上面的电源:

1
2
3
4
5
6
7
static struct regulator_consumer_supply regulator1_consumers[] = {
REGULATOR_SUPPLY("Vcc", "consumer B"),
};

static struct regulator_consumer_supply regulator2_consumers[] = {
REGULATOR_SUPPLY("Vcc", "consumer A"),
};

这将 Regulator-1 映射到消费者 B 的“Vcc”电源,并将 Regulator-2 映射到消费者 A 的“Vcc”电源。

现在可以通过为每个稳压器电源域定义一个结构体 regulator_init_data 来注册。这种结构还将消费者映射到他们的 supply regulators:

1
2
3
4
5
6
7
8
9
10
static struct regulator_init_data regulator1_data = {
.constraints = {
.name = "Regulator-1",
.min_uV = 3300000,
.max_uV = 3300000,
.valid_modes_mask = REGULATOR_MODE_NORMAL,
},
.num_consumer_supplies = ARRAY_SIZE(regulator1_consumers),
.consumer_supplies = regulator1_consumers,
};

名称字段应设置为对电路板有用的描述,以便为其他调节器配置电源以及用于日志记录和其他诊断输出。 通常,原理图中用于 supply rail 的名称是一个不错的选择。 如果未提供名称,则子系统将自动选择一个。

Regulator-1 为 Regulator-2 供电。 这种关系必须在核心中注册,以便在消费者 A 启用其供应 (Regulator-2) 时也启用 Regulator-1。 电源调节器由下面的 supply_regulator 字段设置:

1
2
3
4
5
6
7
8
9
10
11
static struct regulator_init_data regulator2_data = {
.supply_regulator = "Regulator-1",
.constraints = {
.min_uV = 1800000,
.max_uV = 2000000,
.valid_ops_mask = REGULATOR_CHANGE_VOLTAGE,
.valid_modes_mask = REGULATOR_MODE_NORMAL,
},
.num_consumer_supplies = ARRAY_SIZE(regulator2_consumers),
.consumer_supplies = regulator2_consumers,
};

最后,必须以通常的方式注册调节器设备:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
static struct platform_device regulator_devices[] = {
{
.name = "regulator",
.id = DCDC_1,
.dev = {
.platform_data = &regulator1_data,
},
},
{
.name = "regulator",
.id = DCDC_2,
.dev = {
.platform_data = &regulator2_data,
},
},
};
/* register regulator 1 device */
platform_device_register(&regulator_devices[0]);

/* register regulator 2 device */
platform_device_register(&regulator_devices[1]);

数据结构

上文中已经将 Regulator、PMIC、Consumer、Power Domain 等概念进行了介绍,同时也将相关接口进行了介绍。在这一章中将介绍 regulator 的内部实现。整体的数据结构如下:原图

  • regulator_map:
    组织一个系统内所有的 pmic 设备。将他们挂载到一个全局链表中

  • regulator_dev:
    struct regulator_dev 是 regulator 设备的抽象,当 driver 以 struct regulator_desc、struct regulator_config 两个类型的参数,调用 regulator_register 将 regulator 注册到 kernel 之后,regulator 就会分配一个 struct regulator_dev 变量,后续所有的 regulator 操作,都将以该变量为对象

  • regulator_desc:
    regulator_desc 用于描述一个调节器(dcdc、ldo 等)。 在注册 regulator 的时候,需要使用 struct regulator_desc 结构提供该 regulator 的静态描述。所谓的静态,是指这些描述不会在运行时改变,代表了设备的一种属性。regulator_desc 描述该 regulator device 的类型(电压、电流、电流和电压)、中断 id、支持的输出电压个数、操作类型(可改变电压等)、输出模式(fast、normal、idle、standby 等);若该 regulator device 在注册的时候支持通过 regmap 进行配置(如该 regulator device 可通过 spi、iic 接口访问,则可以通过 spi/iic 对应的 regmap 接口访问该 regulator device 的寄存器,进行配置操作),则需要定义 enable_reg、enable_mask、apply_reg 等参数的信息,以便通过 regmap 进行配置

  • regulator_ops:
    这个数据结构是描述调节器的操作函数集的数据结构。对于 pmic 里的每一个调节器都需要有自己的操作方法,该数据结构提供对调节器的操作函数集

  • regulator_config:
    regulator_config 保存了 regulator 的动态信息,所谓的动态信息,是指那些会在 driver 运行过程中改变、或者 driver 运行后才会确定的信息

  • regulation_constraints:
    该数据结构描述 regulator device 的约束信息,包括输出电压范围、输出电流范围、regulator device 支持的模式(fast、normal、idle、standby 等)、regulator device 支持的操作模式,包括 change volt、change current、change bypass mode 等、regulator device 支持的 suspend 状态下的输出控制(如在 suspend to disk 状态下的输出控制等)

  • regulator:
    该数据结构表示一个 regulator device 的使用者,包括是否一直使能、是否使用 bypass 模式(bypass 模式指 regulator device 输入电压直接作为输出,不做限制)、电压范围、电流值、设备属性信息、该 regulator 对应的 supply 名称等

参考文献

http://www.wowotech.net/pm_subsystem/regulator_driver.html
https://blog.csdn.net/lickylin/article/details/107128818?spm=1001.2014.3001.5501
https://blog.csdn.net/lickylin/article/details/107128857?spm=1001.2014.3001.5501
《Linux 内核文档》