资讯详情

Regulator 框架(一): PMIC /生产者 驱动接口

转载来源:https://www.cnblogs.com/wanglouxiaozi/p/15089298.html

调节器(regulator)它是一种为其他设备供电的电子设备。由调节器供电的设备被称为消费者。它们消耗调节器提供的电力。大多数调节器可以启用和禁止其输出,有些调节器也可以控制其输出电压或电流。这些功能应该通过特定的函数和数据结构向消费者据结构向消费者披露。 提供物理调节的芯片称为电源管理集成电路(PMIC):

Linux接口和控制电压和电流调节器设计了调节器框架。分为四个单独的接口,如下:

PMIC 调节器驱动接口。接口的结构可在include/linux/regulator/driver.h中找到。 消费者接口的设备驱动程序。 机器接口用于板级配置。 用户空间sysfs接口。 本文将涵盖以下主题:

介绍PMIC/producer驱动接口、驱动方法和数据结构 研究ISL6271A MIC用于测试目的的驱动程序和虚拟稳压器 调节器消费者接口及其API 调节器(生产者/消费者)DT(device tree)结合 PMIC /生产者 驱动接口 生产者是产生调节电压或电流的设备。该设备的名称是PMIC,可用于功率排序、电池管理dc - dc转换或简单的电源开关(开/关)。它通过软件控制输入功率来调节输出功率。

它处理调节器驱动程序,特别是制造商PMIC这需要几个头文件:

#include <linux/platform_device.h> #include <linux/regulator/driver.h> #include <linux/regulator/of_regulator.h> 

驱动程序的数据结构 我们将首先简要介绍调节器架使用的数据结构。只介绍生产者接口。

描述结构 内核通过一个 struct regulator_desc 结构来描述PMIC提供的每个调节器,该结构描述了调节器的特征。所谓调节器,我指的是任何独立的调节输出。例如,Intersil 的ISL6271A三个独立调整输出是一个PMIC。其驱动程序应该有三个 regulator_desc 实例。该结构包含调节器的固定属性,如下所示:

 1 struct regulator_desc { 
         2     const char *name;  3     const char *of_match;  4   5     int id;  6     unsigned n_voltages;  7     const struct regulator_ops *ops;  8     int irq;  9     enum regulator_type type; 10     struct module *owner; 11  12     unsigned int min_uV; 13     unsigned int uV_step; 14 }; 

为了简单起见,省略一些字段。可以定义完整的结构 include/linux/regulator/driver.h 中找到:

name:调节器的名称。 of_match: 保存用于识别DT中间调节器的名称。 id: 调节器的数字标识符。 owner: 代表提供调节器的模块。将字段设置为 THIS_MODULE。 type: 指示调节器是电压调节器还是电流调节器 REGULATOR_VOLTAGE 或 REGULATOR_CURRENT。任何其他值都会导致调节器注册失败。 n_voltage: 表示调节器可用的选择器数量。表示调节器能输出值的数量。当输出电压固定时,n_voltage 应设为1。 min_uV:表示稳压器能提供的最小电压值。它是由最低选择器给出的电压。 uV_step :表示每个选择器增加的电压。 ops:ops表示调节器操作表。它是指向调节器支持的一组操作回调结构。 irq: 调节器中断号。 约束结构 当PMIC向消费者公开调节器时,必须使用结构体regulation_constraints该结构对该调节器施加了一些名义上的限制。它是一种收集调节器安全限制并定义消费者无法跨越的边界的结构。这是调节器与消费者之间的合同:

 1 struct regulation_constrants { 
       
 2   const char *name;
 3 
 4   /* 电压输出范围(包括)-用于电压控制 */
 5   int min_uV;
 6   int max_uV;
 7 
 8   int uV_offset;
 9 
10   /* 电流输出范围(包括)-用于电流控制 */
11   int min_uA;
12   int max_uA;
13 
14   /* 这台机器有效的调节器操作模式 */
15   unsigned int valid_modes_mask;
16 
17   /* 调节器在本机上的有效操作 */
18   unsigned int valid_ops_mask;
19 
20   struct regulator_state state_disk;
21   struct regulator_state state_mem;
22   struct regulator_state state_standby;
23   suspend_state_t initial_state; /* 在init时设置挂起状态 */
24 
25   /* 启动时设置的模式 */
26   unsigned int initial_mode;
27 
28   /* 约束的标志 */
29   unsigned always_on:1; /* 当系统开启时,调节器永不关闭 */
30   unsigned boot_on:1; /* bootloader/firmware 使能调节器 */
31   unsigned apply_uV:1; /* 当min == max时,应用uV约束 */
32 };

结构中的每个元素描述:

min_uV, min_uA, max_uA和max_uV是用户可以设置的最小电压/电流值。 uV_offset 是应用于电压从消费者补偿电压下降的偏移量。 valid_modes_mask 和 valid_ops_mask 分别是模式和操作的掩码,可以由消费者配置/执行。 always_on 应该被设置, 如果调节器不被禁用。 boot_on 应该被设置如果在系统初始启动时启用了调节器。如果调节器没有被硬件或引导加载程序启用,那么它将在应用约束时启用。 name 是用于显示目的的约束的描述性名称。 apply_uV 在初始化时应用电压约束。 input_uV 表示该调节器由另一个调节器提供时的输入电压。 state_disk, state_mem 和 state_standby 定义了当系统在磁盘模式、mem模式或备用模式下挂起时调节器的状态。 initial_state 表示默认设置挂起状态。 initial_mode 是在启动时设置的模式。 初始化数据的结构 有两种方法可以将 regulator_init_data 传递给驱动程序,可以通过板级初始化文件中的平台数据或通过 of_get_regulator_init_data 函数的设备树中的节点来实现:

1 struct regulator_init_data { 
       
2     struct regulation_constraints constraints;
3     /* optional regulator machine specific init */
4     int (*regulator_init)(void *driver_data);
5     void *driver_data; /* core does not touch this */
6 };

结构中各元素的含义如下:

constraints 表示调节器的约束。 regulator_init 是一个可选的回调函数,在核心注册调节器时被调用。 driver_data 表示传递给 regulator_init 函数的数据。 struct regulation_constraints 结构是 init data 的一部分。这可以用以下事实来解释: 在调节器初始化时,它的约束直接应用于它,远远早于任何消费者可以使用它。

将初始化数据填充到板级文件中 该方法包括从驱动程序或板级文件中填充约束数组,并将其用作平台数据的一部分。以下是基于 Intersil 公司 ISL6271A 的样例,:

 1 static struct regulator_init_data isl_init_data[] = { 
       
 2   [0] = { 
       
 3     .constraints = { 
       
 4       .name = "Core Buck",
 5       .min_uV = 850000,
 6       .max_uV = 1600000,
 7       .valid_modes_mask = REGULATOR_MODE_NORMAL
 8                 | REGULATOR_MODE_STANDBY,
 9       .valid_ops_mask = REGULATOR_CHANGE_MODE
10                 | REGULATOR_CHANGE_STATUS,
11     },
12   },
13   [1] = { 
       
14     .constraints = { 
       
15       .name = "LDO1",
16       .min_uV = 1100000,
17       .max_uV = 1100000,
18       .always_on = true,
19       .valid_modes_mask = REGULATOR_MODE_NORMAL
20                 | REGULATOR_MODE_STANDBY,
21       .valid_ops_mask = REGULATOR_CHANGE_MODE
22                 | REGULATOR_CHANGE_STATUS,
23     },
24   },
25   [2] = { 
       
26     .constraints = { 
       
27       .name = "LDO2",
28       .min_uV = 1300000,
29       .max_uV = 1300000,
30       .always_on = true,
31       .valid_modes_mask = REGULATOR_MODE_NORMAL
32                 | REGULATOR_MODE_STANDBY,
33       .valid_ops_mask = REGULATOR_CHANGE_MODE
34                 | REGULATOR_CHANGE_STATUS,
35     },
36   },
37 };

这个方法现在过时了,尽管在这里详细描述了一下。新的推荐方法是DT,将在下面介绍。

向DT提供初始化数据 为了提取从DT内部传递的init数据,我们需要引入一个新的数据类型,struct of_regulator_match,它看起来像这样:

struct of_regulator_match { 
       
    const char *name;
    void *driver_data;
    struct regulator_init_data *init_data;
    struct device_node *of_node;
    const struct regulator_desc *desc;
};

在使用这个数据结构之前,我们需要弄清楚如何实现DT文件的调节器绑定。

DT中的每个PMIC节点都应该有一个名为 regulators 的子节点,在这个子节点中,我们必须声明PMIC提供的每个调节器为专用子节点。换句话说,PMIC的每个调节器都被定义为regulators节点的子节点,而regulators节点又是DT中PMIC节点的子节点。

在调节器节点中可以定义一些标准化的属性:

regulator-name: 这是一个字符串,用作调节器输出的描述性名称。 regulator-min-microvolt: 这是用户可以设定的最小电压。 regulator-max-microvolt: 这是用户可设定的最大电压。 regulator-microvolt-offset: 这是施加在电压上以补偿电压下降的偏移量。 regulator-min-microamp: 这是用户可以设定的最小电流。 regulator-max-microamp: 这是用户可以设定的最大电流。 regulator-always-on: 这是一个布尔值,表示不应该禁用调节器。 regulator-boot-on: 这是一个bootloader/firmware 阶段使能的调节器。 -supply: 这是一个指向父供应/调节节点的phandle。 regulator-ramp-delay: 这是调节器的斜坡延迟(用uV/uS表示) 这些属性实际上很像struct regulator_init_data中的字段。回到ISL6271A驱动程序,它的DT入口应该是这样的:

 1 isl6271a@3c { 
       
 2   compatible = "isl6271a";
 3   reg = <0x3c>;
 4   interrupts = <0 86 0x4>;
 5 
 6   /* supposing our regulator is powered by another regulator */
 7   in-v1-supply = <&some_reg>;
 8   [...]
 9 
10   regulators { 
       
11     reg1: core_buck { 
       
12       regulator-name = "Core Buck";
13       regulator-min-microvolt = <850000>;
14       regulator-max-microvolt = <1600000>;
15     };
16 
17     reg2: ldo1 { 
       
18       regulator-name = "LDO1";
19       regulator-min-microvolt = <1100000>;
20       regulator-max-microvolt = <1100000>;
21       regulator-always-on;
22     };
23 
24     reg3: ldo2 { 
       
25       regulator-name = "LDO2";
26       regulator-min-microvolt = <1300000>;
27       regulator-max-microvolt = <1300000>;
28       regulator-always-on;
29     };
30   };
31 };

使用内核函数 of_regulator_match(),将regulators子节点作为参数,该函数将遍历每个调节器设备节点,并为每个设备构建一个 struct regulator_init_data结构体。

配置结构 调节器设备是通过struct regulator_config结构来配置的,该结构包含调节器描述的可变元素。当涉及到用核心注册一个调节器时,这个结构被传递给框架:

struct regulator_config { 
       
    struct device *dev;
    const struct regulator_init_data *init_data;
    void *driver_data;
    struct device_node *of_node;
};

上述代码可以解释如下:

dev表示调节器所属的 struct device 结构。 init_data是结构中最重要的字段,因为它包含一个包含调节器约束的元素(特定于机器的结构)。 driver_data 持有调节器的私有数据。 of_node用于支持DT的驱动。它是用于解析DT绑定的节点。由开发人员来设置这个字段。也可以是NULL。 设备操作结构 struct regulator_ops结构体是一个回调列表,它表示调节器可以执行的所有操作。这些回调函数是辅助函数,由通用内核函数包装:

 1 struct regulator_ops { 
       
 2   /* enumerate supported voltages */
 3   int (*list_voltage) (struct regulator_dev *,
 4               unsigned selector);
 5 
 6   /* get/set regulator voltage */
 7   int (*set_voltage) (struct regulator_dev *,
 8               int min_uV, int max_uV,
 9               unsigned *selector);
10 
11   int (*map_voltage)(struct regulator_dev *,
12               int min_uV, int max_uV);
13   int (*set_voltage_sel) (struct regulator_dev *,
14               unsigned selector);
15   int (*get_voltage) (struct regulator_dev *);
16   int (*get_voltage_sel) (struct regulator_dev *);
17 
18   /* get/set regulator current */
19   int (*set_current_limit) (struct regulator_dev *,
20                  int min_uA, int max_uA);
21   int (*get_current_limit) (struct regulator_dev *);
22 
23   int (*set_input_current_limit) (struct regulator_dev *,
24                    int lim_uA);
25   int (*set_over_current_protection) (struct regulator_dev *);
26   int (*set_active_discharge) (struct regulator_dev *,
27                   bool enable);
28 
29   /* enable/disable regulator */
30   int (*enable) (struct regulator_dev *);
31   int (*disable) (struct regulator_dev *);
32   int (*is_enabled) (struct regulator_dev *);
33 
34   /* get/set regulator operating mode (defined in consumer.h) */
35   int (*set_mode) (struct regulator_dev *, unsigned int mode);
36   unsigned int (*get_mode) (struct regulator_dev *);
37 };

回调名称很好地解释了它们的作用。这里没有列出其他回调函数,对于这些回调函数,您必须在调节器的约束valid_ops_mask或valid_modes_mask中启用适当的掩码,然后消费者才能使用它们。可用的操作掩码标志在 include/linux/regulator/machine.h 中定义。

因此,给定一个 struct regulator_dev 结构体,您可以通过调用 rdev_get_id() 函数来获得相应的调节器ID:

int rdev_get_id(struct regulator_dev *rdev); 驱动方法 驱动程序方法由probe()和remove()函数组成。

probe 函数 PMIC驱动程序的probe函数可以分为几个步骤,具体如下:

为PMIC提供的所有调节器定义一个struct regulator_desc对象数组。在这一步中,您应该定义一个有效的 struct regulator_ops,以链接到适当的regulator_desc。所有的regulator_ops都可以是相同的,假设它们都支持相同的操作。 现在,在probe函数中,对于每个调节器,做以下操作: 从平台数据中获取适当的 struct regulator_init_data 结构体(其中必须已经包含有效的 struct regulator_constraints 结构体),或者从DT中构建一个 struct regulator_constraints 结构体,以便构建一个新的 struct regulator_init_data 对象。 使用前面的 struct regulator_init_data 来设置 struct regulator_config 结构体。如果驱动程序支持 DT,你可以让 regulator_config.of_node 指向用于提取调节器属性的节点。 调用 regulator_register()(或托管版本,devm_regulator_register())将调节器注册到核心,并给出之前的 regulator_desc 和 regulator_config 作为参数。 调节器使用 regulator_register() 函数或 devm_regulator_register() 向内核注册:

struct regulator_dev * regulator_register(const struct regulator_desc *regulator_desc, const struct regulator_config *cfg) 这个函数返回到目前为止我们还没有讨论过的数据类型:一个 struct regulator_dev 对象,在 include/linux/regulator/driver.h 中定义。该结构代表了生产者端调节器设备的实例(在消费者端是不同的)。struct regulator_dev 结构的实例不应该被任何东西直接使用,除了调节器核心和通知注入(它应该接受互斥而不是其他直接访问)。也就是说,要从驱动程序中跟踪已注册的调节器,您应该保存由注册函数返回的每个regulator_dev对象的引用。

remove 函数 remove() 函数是之前在探测函数期间执行的所有操作的倒置。因此,当要从系统中删除一个调节器时,你应该记住的基本函数是 regulator_unregister():

void regulator_unregister(struct regulator_dev *rdev); 这个函数接受一个指向struct regulator_dev 结构体的指针作为参数。这是应保留注册函数返回的每个regualtor_dev对象的引用的另一个原因。下面是ISL6271A驱动的 remove 函数:

 1 static int __devexit isl6271a_remove(struct i2c_client *i2c)
 2 { 
       
 3   struct isl_pmic *pmic = i2c_get_clientdata(i2c);
 4   int i;
 5 
 6   for (i = 0; i < 3; i++)
 7     regulator_unregister(pmic->rdev[i]);
 8 
 9   kfree(pmic);
10   return 0;
11 }

案例研究- Intersil ISL6271A电压调节器 该PMIC提供三种调节器装置,其中只有一种可以改变输出值。另外两个提供固定电压:

1 struct isl_pmic { 
       
2     struct i2c_client *client;
3     struct regulator_dev *rdev[3];
4     struct mutex mtx;
5 };

首先,我们定义了ops回调函数,来建立一个struct regulator_desc:

处理get_voltage_sel操作的回调函数:

 1 static int isl6271a_get_voltage_sel(struct regulator_dev *rdev)
 2 { 
       
 3   struct isl_pmic *pmic = rdev_get_drvdata(dev);
 4   int idx = rdev_get_id(rdev);
 5   idx = i2c_smbus_read_byte(pmic->client);
 6   if (idx < 0)
 7     [...] /* handle this error */
 8 
 9   return idx;
10 }

下面是处理set_voltage_sel操作的回调函数:

 1 static int isl6271a_set_voltage_sel(
 2               struct regulator_dev *dev, unsigned selector)
 3 { 
       
 4   struct isl_pmic *pmic = rdev_get_drvdata(dev);
 5   int err;
 6 
 7   err = i2c_smbus_write_byte(pmic->client, selector);
 8   if (err < 0)
 9     [...] /* handle this error */
10 
11   return err;
12 }
  1. 既然我们已经完成了回调定义,我们可以构建struct regulator_ops:
 1 tatic struct regulator_ops isl_core_ops = { 
       
 2     .get_voltage_sel = isl6271a_get_voltage_sel,
 3     .set_voltage_sel = isl6271a_set_voltage_sel,
 4     .list_voltage = regulator_list_voltage_linear,
 5     .map_voltage = regulator_map_voltage_linear,
 6 };
 7 
 8 static struct regulator_ops isl_fixed_ops = { 
       
 9     .list_voltage = regulator_list_voltage_linear,
10 };

关于 regulator_list_voltage_linear 和 regulator_list_voltage_linear 函数是从哪里来的。与许多其他调节器助手函数一样,它们也在 drivers/regulator/helpers.c 中定义。内核为线性输出调节器提供辅助函数,就像ISL6271A的情况一样。

现在是时候为所有的调节器构建一个struct regulator_desc数组了:

 1 static const struct regulator_desc isl_rd[] = { 
       
 2   { 
       
 3     .name = "Core Buck",
 4     .id = 0,
 5     .n_voltages = 16,
 6     .ops = &isl_core_ops,
 7     .type = REGULATOR_VOLTAGE,
 8     .owner = THIS_MODULE,
 9     .min_uV = ISL6271A_VOLTAGE_MIN,
10     .uV_step = ISL6271A_VOLTAGE_STEP,
11   }, { 
       
12     .name = "LDO1",
13     .id = 1,
14     .n_voltages = 1,
15     .ops = &isl_fixed_ops,
16     .type = REGULATOR_VOLTAGE,
17     .owner = THIS_MODULE,
18     .min_uV = 1100000,
19   }, { 
       
20     .name = "LDO2",
21     .id = 2,
22     .n_voltages = 1,
23     .ops = &isl_fixed_ops,
24     .type = REGULATOR_VOLTAGE,
25     .owner = THIS_MODULE,
26     .min_uV = 1300000,
27   },
28 };

LDO1和LDO2输出电压固定。这就是为什么它们的n_voltage属性被设置为1,并且它们的操作只提供regulator_list_voltage_linear映射。

3.现在我们在probe函数中,在这里我们需要构建struct init_data结构体。如果你还记得,我们将使用前面介绍的struct of_regulator_match。我们应该声明这种类型的数组,在数组中我们应该设置每个调节器的.name属性,我们需要获取init_data:

1 static struct of_regulator_match isl6271a_matches[] = { 
       
2     { 
        .name = "core_buck", },
3     { 
        .name = "ldo1", },
4     { 
        .name = "ldo2", },
5 };

仔细看一下,您会注意到.name属性的值与设备树中调节器的标签的值完全相同。这是一个你应该关心和尊重的规则。

现在让我们看看 probe 函数。ISL6271A提供了三个调节器输出,这意味着 regulator_register() 函数应该被调用三次:

 1 static int isl6271a_probe(
        标签: 3pmic稳压器线性芯片

锐单商城拥有海量元器件数据手册IC替代型号,打造 电子元器件IC百科大全!

锐单商城 - 一站式电子元器件采购平台