Linux 内核之电源篇(加载流程)
Linux 内核 regulator子系统(启动流程)
Linux regulator子系统是一个用于管理电源的框架,它允许内核控制和监测系统中的电源。该子系统提供了一种标准化的接口,使得设备驱动程序可以请求和控制电源,从而实现对系统功耗的优化。
在Linux内核中,每个电源都被表示为一个regulator对象。这些对象包含了电源的名称、电压、电流、电源状态等信息。驱动程序可以通过调用regulator API来请求和控制电源。
Linux regulator子系统还提供了一些特殊的regulator类型,例如fixed regulator和linear regulator。fixed regulator是一种固定电压的电源,它的电压是不可调的。而linear regulator则是一种可调电压的电源,它可以根据需要调整输出电压。
在Linux内核中,regulator子系统的实现是由一个核心框架和一系列驱动程序组成的。驱动程序负责与硬件交互,而核心框架则负责管理和调度这些驱动程序。
总的来说,Linux regulator子系统是一个非常重要的电源管理框架,它可以帮助开发人员实现对系统功耗的优化,从而提高系统的性能和稳定性。
提示:上一篇简单讲述内核设备树中电源的注册和引用,本篇讲讲内核电源驱动。
文章目录
- Linux 内核 regulator子系统(启动流程)
- 圈重点 看想学
- 1 注册GPIO控制开关电源
- 2 注册GPIO控制可变压电源
- 3 注册PWM可调压电源
- 4 注册DC-DC电源
- Tips
- 总结
圈重点 看想学
a) 注册GPIO开关电源
b) 注册GPIO可压电源
c) 注册PWM调压电源
d) 注册DC-DC电源
1 注册GPIO控制开关电源
在 Linux 内核中,每个稳压器都需要在系统启动时进行注册。对于固定电压稳压器,注册过程非常简单,只需要调用 regulator_register_fixed_voltage() 函数即可。该函数定义在 drivers/regulator/fixed.c。
struct regulator_dev *regulator_register_fixed_voltage(struct device *dev,const char *supply_name,struct regulator_init_data *init_data,unsigned int uV,unsigned int min_uV,unsigned int max_uV,unsigned int n_voltages);
该函数的参数说明如下:
- dev:指向该稳压器所属设备的指针。
- supply_name:该稳压器的名称。
- init_data:指向 regulator_init_data 结构体的指针,该结构体包含了该稳压器的初始化数据。
- uV:该稳压器的输出电压。
- min_uV:该稳压器的最小输出电压。
- max_uV:该稳压器的最大输出电压。
- n_voltages:该稳压器支持的电压档位数量。
在注册固定电压稳压器时,我们只需要指定 uV、min_uV 和 max_uV 三个参数即可,因为固定电压稳压器只支持一个电压档位。
/ {vcc5v0_otg: vcc5v0-otg-regulator {compatible = "regulator-fixed";regulator-name = "vcc5v0_otg";//regulator-boot-on; //开机是自动开启供电//regulator-always-on; //强制一直开启,不可被关闭regulator-min-microvolt = <5000000>;regulator-max-microvolt = <5000000>;enable-active-high;gpio = <&gpio2 RK_PA2 GPIO_ACTIVE_HIGH>;vin-supply = <&vcc5v0_sys>;pinctrl-names = "default";pinctrl-0 = <&vcc5v0_otg_en>;};
};&pinctrl {usb {vcc5v0_otg_en: vcc5v0-otg-en {rockchip,pins = <2 RK_PA2 RK_FUNC_GPIO &pcfg_pull_none>;};};};
在 Linux 内核中,驱动程序的加载过程是由内核模块机制完成的。
- 驱动程序注册
在驱动程序初始化时,调用 platform_driver_register() 函数来注册驱动程序。这个函数会将驱动程序添加到 platform 驱动程序列表中。 - 设备匹配
当系统启动时,内核会扫描设备树来匹配设备和驱动程序。当匹配到一个与 regulator-fixed 驱动程序匹配的设备时,内核会调用驱动程序的 probe() 函数。 - probe() 函数
probe() 函数会执行以下操作:
- 调用 devm_regulator_register_fixed_voltage() 函数来注册固定电压的电源。
- 设置电源的名称、电压和电流限制等属性。
- 返回 0 表示成功。
- 设备卸载
当系统关闭或者设备被移除时,内核会调用驱动程序的 remove() 函数来卸载设备。 - remove() 函数
remove() 函数会执行以下操作:
- 调用 devm_regulator_unregister() 函数来注销电源。
- 返回 0 表示成功。
2 注册GPIO控制可变压电源
可变压电源是一个 GPIO 控制的电源管理器,它可以通过 GPIO 控制电源的开关。在 Linux 内核中,它的源代码位于 drivers/regulator/gpio-regulator.c。
- 在设备树中定义 regulator-gpio 节点,包括 GPIO 端口号、电源名称、电源类型等信息。
/ {vccio_sd: vccio-sd-regulator {compatible = "regulator-gpio";regulator-name = "vccio_sd";regulator-min-microvolt = <1800000>;regulator-max-microvolt = <3300000>;gpios = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;gpios-states = <0x1>;states = <1800000 0x03300000 0x1>;};
};
- 在内核启动时,通过调用 of_register_platform_driver() 函数注册 platform_driver,将 regulator-gpio 的驱动程序注册到内核中。
- 当系统启动时,内核会自动扫描设备树,找到 regulator-gpio 节点,并将其与驱动程序进行匹配。
- 当匹配成功后,内核会调用 platform_driver 中的 probe() 函数,该函数会初始化 regulator-gpio 并注册到内核的电源管理器中。
- 在 probe() 函数中,首先会获取 regulator-gpio 节点中的 GPIO 端口号,并通过调用 gpio_request() 函数请求该 GPIO 端口。
- 然后,通过调用 regulator_register() 函数将 regulator-gpio 注册到内核的电源管理器中。
- 当需要控制电源时,应用程序可以通过调用 regulator_get() 函数获取 regulator-gpio 的句柄,然后通过调用 regulator_enable() 或 regulator_disable() 函数来控制电源的开关。
- 当不再需要使用 regulator-gpio 时,应用程序可以通过调用 regulator_put() 函数释放该句柄,并通过调用 regulator_unregister() 函数将 regulator-gpio 从内核的电源管理器中注销。
3 注册PWM可调压电源
PWM regulator是一种基于PWM信号的电源调节器,它可以通过调节PWM信号的占空比来控制输出电压。在Linux内核中,PWM regulator的驱动程序位于drivers/regulator/pwm-regulator.c
。下面是PWM regulator的加载流程:
-
驱动程序初始化
在驱动程序初始化时,首先会调用pwm_regulator_probe函数。该函数会注册一个platform_driver结构体,并将其与pwm_regulator_driver结构体关联起来。同时,该函数还会调用pwm_regulator_of_match函数,用于匹配设备树中的节点信息。 -
设备树匹配
当系统启动时,内核会解析设备树,并将设备树中的节点信息传递给驱动程序。驱动程序会通过pwm_regulator_of_match函数匹配设备树中的节点信息,并将匹配到的节点信息保存在pwm_regulator_data结构体中。
/ {vdd_log: vdd-logic {compatible = "pwm-regulator";rockchip,pwm_id = <1>;rockchip,pwm_voltage = <1100000>;pwms = <&pwm1 0 25000 1>;regulator-name = "vcc_log";regulator-min-microvolt = <860000>;regulator-max-microvolt = <1360000>;regulator-always-on;regulator-boot-on;};
};
-
PWM信号初始化
当驱动程序成功匹配到设备树中的节点信息后,会调用pwm_regulator_probe函数中的pwm_regulator_init函数,用于初始化PWM信号。该函数会调用pwm_get函数获取PWM信号,并设置PWM信号的频率和占空比。 -
电源调节器初始化
当PWM信号初始化完成后,会调用pwm_regulator_probe函数中的regulator_init函数,用于初始化电源调节器。该函数会调用regulator_register函数注册一个regulator_dev结构体,并将其与pwm_regulator_data结构体关联起来。同时,该函数还会调用regulator_set_voltage函数设置输出电压。 -
电源调节器使用
当电源调节器初始化完成后,就可以使用电源调节器了。在使用电源调节器时,可以调用regulator_enable和regulator_disable函数来启用和禁用电源调节器。同时,也可以调用regulator_set_voltage函数来设置输出电压。
以上就是PWM regulator的加载流程。在加载过程中,驱动程序会初始化PWM信号和电源调节器,并将它们关联起来。同时,驱动程序还会通过设备树匹配来获取节点信息。在使用电源调节器时,可以通过调用相关函数来控制输出电压。
4 注册DC-DC电源
以 TCS4525/6为例简单讲解电压调节器注册流程。tcs452x 内核驱动位置:drivers/regulator/fan53555.c。
- 驱动注册
在 Linux 中,驱动程序需要通过注册函数进行注册,以便系统能够识别和加载它们。在 fan53555.c 中,驱动程序使用以下代码进行注册:
static struct i2c_driver fan53555_regulator_driver = {.driver = {.name = "fan53555-regulator",.of_match_table = of_match_ptr(fan53555_dt_ids),},.probe = fan53555_regulator_probe,.shutdown = fan53555_regulator_shutdown,.id_table = fan53555_id,
};module_i2c_driver(fan53555_regulator_driver);
这里使用了 i2c_driver 结构体,其中包含了驱动程序的名称、设备树匹配表、probe 函数、shutdown 函数和 ID 表。最后,使用 module_i2c_driver 函数进行注册。
- 设备树匹配
在 Linux 中,设备树是描述硬件设备的一种标准化方式。驱动程序需要使用设备树匹配来确定哪些设备需要加载该驱动程序。在 fan53555.c 中,使用以下代码进行设备树匹配:
static const struct i2c_device_id fan53555_id[] = {{.name = "fan53555",.driver_data = FAN53555_VENDOR_FAIRCHILD}, {.name = "rk8603",.driver_data = FAN53555_VENDOR_RK}, {.name = "rk8604",.driver_data = FAN53555_VENDOR_RK}, {.name = "syr827",.driver_data = FAN53555_VENDOR_SILERGY}, {.name = "syr828",.driver_data = FAN53555_VENDOR_SILERGY}, {.name = "tcs452x",.driver_data = FAN53555_VENDOR_TCS},{ },
};
MODULE_DEVICE_TABLE(i2c, fan53555_id);
这里使用了 of_device_id 结构体,其中包含了设备的兼容性字符串。最后,使用 MODULE_DEVICE_TABLE 宏进行设备树匹配。
- Probe 函数
在 Linux 中,probe 函数用于初始化驱动程序并将其与硬件设备关联。在 fan53555.c 中,使用以下代码进行 probe 函数:
static int fan53555_regulator_probe(struct i2c_client *client,const struct i2c_device_id *id)
{struct device_node *np = client->dev.of_node;struct fan53555_device_info *di;struct fan53555_platform_data *pdata;struct regulator_config config = { };unsigned int val;int ret;di = devm_kzalloc(&client->dev, sizeof(struct fan53555_device_info),GFP_KERNEL);if (!di)return -ENOMEM;di->desc.of_map_mode = fan53555_map_mode;pdata = dev_get_platdata(&client->dev);if (!pdata)pdata = fan53555_parse_dt(&client->dev, np, &di->desc);//遍历设备树if (!pdata || !pdata->regulator) {dev_err(&client->dev, "Platform data not found!\\n");return -ENODEV;}di->vsel_gpio = pdata->vsel_gpio;di->sleep_vsel_id = pdata->sleep_vsel_id;di->regulator = pdata->regulator;if (client->dev.of_node) {di->vendor =(unsigned long)of_device_get_match_data(&client->dev);} else {/* if no ramp constraint set, get the pdata ramp_delay */if (!di->regulator->constraints.ramp_delay) {int slew_idx = (pdata->slew_rate & 0x7)? pdata->slew_rate : 0;di->regulator->constraints.ramp_delay= slew_rates[slew_idx];}di->vendor = id->driver_data;}di->regmap = devm_regmap_init_i2c(client, &fan53555_regmap_config);if (IS_ERR(di->regmap)) {dev_err(&client->dev, "Failed to allocate regmap!\\n");return PTR_ERR(di->regmap);}di->dev = &client->dev;i2c_set_clientdata(client, di);/* Get chip ID */ret = regmap_read(di->regmap, FAN53555_ID1, &val);if (ret < 0) {dev_err(&client->dev, "Failed to get chip ID!\\n");return ret;}di->chip_id = val & DIE_ID;/* Get chip revision */ret = regmap_read(di->regmap, FAN53555_ID2, &val);if (ret < 0) {dev_err(&client->dev, "Failed to get chip Rev!\\n");return ret;}di->chip_rev = val & DIE_REV;dev_info(&client->dev, "FAN53555 Option[%d] Rev[%d] Detected!\\n",di->chip_id, di->chip_rev);/* Device init */ret = fan53555_device_setup(di, pdata);if (ret < 0) {dev_err(&client->dev, "Failed to setup device!\\n");return ret;}/* Register regulator */config.dev = di->dev;config.init_data = di->regulator;config.regmap = di->regmap;config.driver_data = di;config.of_node = np;ret = fan53555_regulator_register(di, &config);if (ret < 0)dev_err(&client->dev, "Failed to register regulator!\\n");return ret;
}
- 驱动卸载
由于是重要电压调节器,不允许被卸载;关机关联函数替代恰到好处。在 fan53555.c 中,使用以下代码进行 shutdown 函数:
static void fan53555_regulator_shutdown(struct i2c_client *client)
{struct fan53555_device_info *di;int ret;di = i2c_get_clientdata(client);dev_info(di->dev, "fan53555..... reset\\n");switch (di->vendor) {case FAN53555_VENDOR_FAIRCHILD:case FAN53555_VENDOR_RK:case FAN53555_VENDOR_SILERGY:ret = regmap_update_bits(di->regmap, di->slew_reg,CTL_RESET, CTL_RESET);break;case FAN53555_VENDOR_TCS:ret = regmap_update_bits(di->regmap, TCS452X_LIMCONF,CTL_RESET, CTL_RESET);/ the device can't return 'ack' during the reset,* it will return -ENXIO, ignore this error.*/if (ret == -ENXIO)ret = 0;break;default:ret = -EINVAL;break;}if (ret < 0)dev_err(di->dev, "reset: force fan53555_reset error! ret=%d\\n", ret);elsedev_info(di->dev, "reset: force fan53555_reset ok!\\n");
}
Tips
总结
活学活用,做个合格的搬运工。