PMBus 核心驱动程序和内部 API

简介

[来自 pmbus.org] 电源管理总线 (PMBus) 是一种开放标准的电源管理协议,具有完全定义的命令语言,可促进与电源系统中的电源转换器和其他设备进行通信。该协议通过行业标准的 SMBus 串行接口实现,并支持符合要求的电源转换产品的编程、控制和实时监控。这种灵活且高度通用的标准允许基于模拟和数字技术的设备之间的通信,并提供真正的互操作性,这将降低设计复杂性并缩短电源系统设计人员的上市时间。这个开放的电源系统标准由领先的电源和半导体公司率先推出,由 PMBus 实施者论坛 (PMBus-IF) 维护和推广,该论坛由 30 多个采用者组成,其目标是为用户提供支持并促进采用。

不幸的是,虽然 PMBus 命令是标准化的,但没有强制性命令,制造商可以根据自己的意愿添加任意数量的非标准命令。此外,如果执行不支持的命令,不同的 PMBus 设备的行为也会不同。一些设备返回错误,一些设备返回 0xff 或 0xffff 并设置状态错误标志,而一些设备可能只是挂起。

尽管存在所有这些困难,但通用的 PMBus 设备驱动程序仍然有用并受到支持,因为内核版本 2.6.39。然而,除了核心 PMBus 驱动程序之外,还必须支持设备特定的扩展,因为 PMBus 设备开发人员接下来会推出什么新的设备特定功能是未知的。

为了使设备特定的扩展尽可能可扩展,并避免必须为新设备重复修改核心 PMBus 驱动程序,PMBus 驱动程序被拆分为核心、通用和设备特定的代码。核心代码(在 pmbus_core.c 中)提供通用功能。通用代码(在 pmbus.c 中)为通用 PMBus 设备提供支持。设备特定的代码负责设备特定的初始化,并在需要时将设备特定的功能映射到通用功能。这在某种程度上与 PCI 代码类似,其中通用代码会根据需要为各种设备添加怪癖。

PMBus 设备功能自动检测

对于通用的 PMBus 设备,pmbus.c 中的代码尝试自动检测所有受支持的 PMBus 命令。自动检测有些受限,因为要考虑的变量实在太多了。例如,几乎不可能自动检测哪些 PMBus 命令已分页,哪些命令在所有页面中复制(有关多页面 PMBus 设备的详细信息,请参阅 PMBus 规范)。

因此,如果无法自动检测所有命令,则通常提供设备特定的驱动程序是有意义的。此驱动程序中的数据结构可用于告知核心驱动程序各个芯片支持的功能。

一些命令始终会自动检测。这适用于所有限制命令(lcrit、min、max 和 crit 属性)以及相关的警报属性。限制和警报属性会自动检测,因为可能的组合实在太多,无法提供手动配置界面。

PMBus 内部 API

核心和设备特定的 PMBus 代码之间的 API 在 drivers/hwmon/pmbus/pmbus.h 中定义。除了内部 API 之外,pmbus.h 还定义了标准的 PMBus 命令和虚拟 PMBus 命令。

标准 PMBus 命令

标准 PMBus 命令(命令值 0x00 到 0xff)在 PMBUs 规范中定义。

虚拟 PMBus 命令

提供虚拟 PMBus 命令是为了支持多个芯片供应商已实施的非标准功能,因此值得支持。

虚拟 PMBus 命令以命令值 0x100 开头,因此可以很容易地与标准 PMBus 命令(其值不能大于 0xff)区分开来。对虚拟 PMBus 命令的支持是设备特定的,因此必须在设备特定的代码中实现。

虚拟命令命名为 PMBUS_VIRT_xxx,并以 PMBUS_VIRT_BASE 开头。所有虚拟命令都是字大小的。

目前有两种类型的虚拟命令。

  • READ 命令是只读的;写入会被忽略或返回错误。

  • RESET 命令是读/写。读取重置寄存器返回零(用于检测),写入任何值都会导致关联的历史记录重置。

虚拟命令必须在设备特定的驱动程序代码中处理。如果支持虚拟命令,则芯片驱动程序代码返回非负值,如果不支持,则返回负错误代码。在这种情况下,芯片驱动程序可以返回 -ENODATA 或任何其他 Linux 错误代码,但 -ENODATA 以外的错误代码的处理效率更高,因此首选。无论哪种情况,如果芯片驱动程序在读取或写入虚拟寄存器时返回错误代码,则调用的 PMBus 核心代码将中止(换句话说,PMBus 核心代码永远不会向芯片发送虚拟命令)。

PMBus 驱动程序信息

在 struct pmbus_driver_info 中定义的 PMBus 驱动程序信息是设备特定驱动程序向核心 PMBus 驱动程序传递信息的主要手段。具体来说,它提供以下信息。

  • 对于支持以直接数据格式提供其数据的设备,它提供用于将寄存器值转换为规范化数据的系数。此数据通常由芯片制造商在设备数据手册中提供。

  • 可以将支持的芯片功能提供给核心驱动程序。对于在执行不支持的命令时反应不良的芯片,和/或加速设备检测和初始化,这可能是必要的。

  • 提供多个函数入口点,以支持覆盖和/或增强通用命令执行。此功能可用于将非标准 PMBus 命令映射到标准命令,或使用设备特定信息来增强标准命令返回值。

PEC 支持

许多 PMBus 设备支持 SMBus PEC(数据包错误检查)。如果 I2C 适配器和 PMBus 芯片都支持,则默认启用它。如果支持 PEC,则 PMBus 核心驱动程序会将名为“pec”的属性添加到 I2C 设备。此属性可用于控制与 PMBus 芯片通信中的 PEC 支持。

API 函数

芯片驱动程序提供的函数

如果成功,所有函数都会返回命令返回值(读取)或零(写入)。-ENODATA 的返回值表示没有制造商特定的命令,但可能存在标准的 PMBus 命令。任何其他负返回值都表示该芯片不存在该命令,并且不应尝试读取或写入该标准命令。

如上所述,此规则的例外情况适用于虚拟命令,该命令必须在特定于驱动程序的代码中处理。有关更多详细信息,请参阅上面的“虚拟 PMBus 命令”。

核心 PMBus 驱动程序代码中的命令执行如下

if (chip_access_function) {
        status = chip_access_function();
        if (status != -ENODATA)
                return status;
}
if (command >= PMBUS_VIRT_BASE) /* For word commands/registers only */
        return -EINVAL;
return generic_access();

芯片驱动程序可能会在 struct pmbus_driver_info 中提供指向以下函数的指针。所有函数都是可选的。

int (*read_byte_data)(struct i2c_client *client, int page, int reg);

从页面 <page>、寄存器 <reg> 读取字节。<page> 可以是 -1,表示“当前页面”。

int (*read_word_data)(struct i2c_client *client, int page, int phase,
                      int reg);

从页面 <page>、相位 <phase>、寄存器 <reg> 读取字。如果芯片不支持多个相位,则可以忽略相位参数。如果芯片支持多个相位,则相位值 0xff 表示所有相位。

int (*write_word_data)(struct i2c_client *client, int page, int reg,
                       u16 word);

将字写入页面 <page>、寄存器 <reg>。

int (*write_byte)(struct i2c_client *client, int page, u8 value);

将字节写入页面 <page>、寄存器 <reg>。<page> 可以是 -1,表示“当前页面”。

int (*identify)(struct i2c_client *client, struct pmbus_driver_info *info);

确定支持的 PMBus 功能。仅当芯片驱动程序支持多个芯片并且芯片功能未预先确定时,此函数才是必需的。它目前仅由通用 pmbus 驱动程序 (pmbus.c) 使用。

核心驱动程序导出的函数

芯片驱动程序应使用以下函数来读取或写入 PMBus 寄存器。芯片驱动程序也可以使用直接 I2C 命令。如果使用直接 I2C 命令,则芯片驱动程序代码不得直接修改当前页面,因为选定的页面缓存在核心驱动程序中,并且核心驱动程序会假定它已被选中。必须使用 pmbus_set_page() 来选择新页面。

int pmbus_set_page(struct i2c_client *client, u8 page, u8 phase);

将 PMBus 页面寄存器设置为 <page> 和 <phase> 以供后续命令使用。如果芯片不支持多个相位,则忽略相位参数。否则,相位值 0xff 将选择所有相位。

int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 phase,
                         u8 reg);

从 <page>、<phase>、<reg> 读取字数据。类似于 i2c_smbus_read_word_data(),但首先选择页和相位。如果芯片不支持多个相位,则忽略相位参数。否则,相位值 0xff 选择所有相位。

int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg,
                          u16 word);

将字数据写入 <page>、<reg>。类似于 i2c_smbus_write_word_data(),但首先选择页。

int pmbus_read_byte_data(struct i2c_client *client, int page, u8 reg);

从 <page>、<reg> 读取字节数据。类似于 i2c_smbus_read_byte_data(),但首先选择页。<page> 可以为 -1,表示“当前页”。

int pmbus_write_byte(struct i2c_client *client, int page, u8 value);

将字节数据写入 <page>、<reg>。类似于 i2c_smbus_write_byte(),但首先选择页。<page> 可以为 -1,表示“当前页”。

void pmbus_clear_faults(struct i2c_client *client);

在所有芯片页上执行 PMBus “清除故障” 命令。如果定义了设备特定的 write_byte 函数,则此函数会调用该函数。因此,_不能_ 从该函数中调用此函数。

bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg);

检查字节寄存器是否存在。如果寄存器存在则返回 true,否则返回 false。如果定义了设备特定的 write_byte 函数,则此函数会调用该函数以获取芯片状态。因此,_不能_ 从该函数中调用此函数。

bool pmbus_check_word_register(struct i2c_client *client, int page, int reg);

检查字寄存器是否存在。如果寄存器存在则返回 true,否则返回 false。如果定义了设备特定的 write_byte 函数,则此函数会调用该函数以获取芯片状态。因此,_不能_ 从该函数中调用此函数。

int pmbus_do_probe(struct i2c_client *client, struct pmbus_driver_info *info);

执行探测函数。类似于其他驱动程序的标准探测函数,但额外添加了指向 struct pmbus_driver_info 的指针作为参数。如果支持,则调用 identify 函数。只能从设备探测函数中调用。

const struct pmbus_driver_info
      *pmbus_get_driver_info(struct i2c_client *client);

返回指向传递给 pmbus_do_probe() 的 struct pmbus_driver_info 的指针。

PMBus 驱动程序平台数据

PMBus 平台数据在 include/linux/pmbus.h 中定义。平台数据当前提供一个 flags 字段,其中使用了四个位。

#define PMBUS_SKIP_STATUS_CHECK                 BIT(0)

#define PMBUS_WRITE_PROTECTED                   BIT(1)

#define PMBUS_NO_CAPABILITY                     BIT(2)

#define PMBUS_READ_STATUS_AFTER_FAILED_CHECK    BIT(3)

#define PMBUS_NO_WRITE_PROTECT                  BIT(4)

#define PMBUS_USE_COEFFICIENTS_CMD              BIT(5)

struct pmbus_platform_data {
        u32 flags;              /* Device specific flags */

        /* regulator support */
        int num_regulators;
        struct regulator_init_data *reg_init_data;
};

标志

PMBUS_SKIP_STATUS_CHECK

在寄存器检测期间,跳过检查状态寄存器以查找通信或命令错误。

当尝试读取不支持的寄存器时,某些 PMBus 芯片会响应有效数据。对于此类芯片,在尝试确定芯片寄存器是否存在时,必须检查状态寄存器。其他 PMBus 芯片不支持 STATUS_CML 寄存器,或者由于无法解释的原因报告通信错误。对于此类芯片,必须禁用状态寄存器检查。

某些 i2c 控制器不支持单字节命令(没有数据的写入命令,i2c_smbus_write_byte())。使用此类控制器时,无法清除状态寄存器,必须设置 PMBUS_SKIP_STATUS_CHECK 标志。

PMBUS_WRITE_PROTECTED

如果芯片受写保护且写保护不由标准 WRITE_PROTECT 命令确定,则设置此标志。

PMBUS_NO_CAPABILITY

某些 PMBus 芯片在读取 CAPABILITY 寄存器时不会响应有效数据。对于此类芯片,应设置此标志,以便 PMBus 核心驱动程序不会使用 CAPABILITY 来确定其行为。

PMBUS_READ_STATUS_AFTER_FAILED_CHECK

在每次寄存器检查失败后读取 STATUS 寄存器。

当尝试读取不支持的寄存器时,某些 PMBus 芯片最终会进入未定义状态。对于此类芯片,有必要在寄存器检查失败后将芯片 pmbus 控制器重置为已知状态。这可以通过读取已知寄存器来完成。通过设置此标志,驱动程序将在每次寄存器检查失败后尝试读取 STATUS 寄存器。此读取可能会失败,但它会将芯片置于已知状态。

PMBUS_NO_WRITE_PROTECT

某些 PMBus 芯片在读取 WRITE_PROTECT 寄存器时会响应无效数据。对于此类芯片,应设置此标志,以便 PMBus 核心驱动程序不会使用 WRITE_PROTECT 命令来确定其行为。

PMBUS_USE_COEFFICIENTS_CMD

设置此标志后,PMBus 核心驱动程序将使用 COEFFICIENTS 寄存器初始化直接模式格式的系数。