Linux 4.19.111 供电(power supply )子系统

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

Linux 内核中为了方便对 battery 的管理专门提供了power supply framework。battery 管理分开为两个部分一个是电池监控fuelgauge另一个是充放电管理charger。

fuelgauge 驱动主要负责向上层 android 系统提供当前电池的电量以及健康状态信息等另外它也向 charger 驱动提供电池的相关信息;

charger 驱动主要负责电源线的插拔检测以及充放电的过程管理。对于 battery 管理硬件上有电量计 IC 和充放电 IC。

power supply 设备它的目的很单纯就是为系统供电但由于供电设备可以来自于电池也可能来自于USB 供电、电源适配器供电、无线供电等等这就涉及到充放电管理。另外内核还需要导出必要的信息到用户空间进而可告知用户程序比如电池电量变化、充放电状态、电池类型等等。因此内核抽象出了 power supply 作为基础组件支撑。

所以 power supply 设备主要功能就是向用户空间汇报各类状态信息因此 power supply framework 的核心思路就是:将这些状态信息抽象为properties——属性。由于状态信息类型是有限的所以属性的个数也是有限的。

power supply 设备驱动只需要负责该 power supply 设备具备有哪些属性?这些属性的值是什么?当属性值发生改变时通知 power supply framework。将某个power supply 设备支持的属性和对应值以 sysfs 的形式提供给用户空间当属性值改变时以 uevent 的形式广播给用户空间程序。另外power supply framework 也会协助处理power supply 设备级联的情况。

一、Power Supply 软件框架

power supply framework 源码存放在 drivers/power/ 路径下。内核抽象出来 power supply 子系统为驱动提供了统一的框架。功能包括:

  1. 抽象 power supply 设备的共性向用户空间提供统一的 API;

  2. 为 power supply 设备驱动的编写提供简单、统一的方式。

power supply framework 主要由3部分组成:

  1. power supply core通用电源监控类用于抽象核心数据结构、实现公共逻辑。位于 drivers/power/power_supply_core.c 中。

  2. power supply sysfs通用电源监控类的 Sysfs 接口实现 Sysfs 以及 uevent 功能。位于 drivers/power/power_supply_sysfs.c 中。

  3. power supply leds基于 Linux LED class提供 power supply 设备状态指示的通用实现。位于 drivers/power/power_suppply_leds.c 中。

最后驱动工程师可以基于 power supply framework实现具体的 power supply 设备驱动程序用来处理平台相关、硬件相关的逻辑。这些驱动都位于 drivers/power/ 目录下。

二、Power Supply 关键数据结构

power_supply_config 结构体代表运行时特定的 power supply 配置。

of_node:严格说此数据结构不是设备模型中的它是一个 DTS 中节点对应的内存中设备描述一般此对象代表一个设备。这里代表 power supply 设备。

fwnode:fwnode 指的是一个固件节点通常代表设备树或 ACPI通常是 DSDT 表中的一个条目。设备树和 ACPI 是定义设备及其属性和设备之间互连的两种不同方式。它们都使用树形结构来编码这一信息。

在一个给定的结构设备上的 fwnode 成员是该设备对应的固件表中的节点。ACPI 在基于 x86/UEFI 的系统中很常见而设备树在 ARM 系统中很常见。

fwnode 可以与接受 fwnode 句柄的内核 api 一起使用。

supplied_to:一个字符串数组保存了由该 power supply 设备供电的 power supply 设备列表以此可将 power supply 设备组织成相互级联的 power supply 设备链。这些“被供电”的 power supply 设备称作 supplicant客户端、乞求者。

num_supplicants:supplicant 数量。


power_supply_desc 结构体代表 power supply 说明详细。

name:设备名称。

type:设备类型。

usb_types:支持的 USB 类型TYPE C 接口、专用的充电端口、充电下游端口等。

num_usb_types:支持的 USB 类型数量。

properties:设备的属性列表。

num_properties:属性的个数。

get_property/set_property:用于驱动程序实现 power supply class 的函数。这些不应该被其他驱动程序直接调用来访问这个 power supply。取而代之的是使用 power_supply_*() 函数例如 power_supply_get_property()。

property_is_writeable:返回指定的属性值是否可写用于 Sysfs它将在注册 power supply 设备时被调用。如果在设备探测期间发生这种情况那么它一定不能访问设备的内部数据因为探测没有结束。

external_power_changed:当一个 power supply 设备存在 supply 设备且该 power supply 设备的属性发生改变如online、offline时power supply core 会调用该回调函数通知 power supply 设备 driver以便让它做出相应的处理。

set_charged:外部模块通知 power supply 设备 driver该 power supply 设备的状态改变了。

no_thermal:是否为 power supply 设备创建 thermal zone。

use_for_apm:For APM emulation。


power_supply 结构体是 power supply class 的核心数据结构用于抽象 power supply 设备。

desc:power_supply_desc 结构。

supplied_to:一个字符串数组保存了由该 power supply 设备供电的 power supply 设备列表以此可将 power supply 设备组织成相互级联的 power supply 设备链。这些“被供电”的 power supply 设备称作 supplicant客户端、乞求者。

num_supplicants:supplicant 个数。

supplied_from:一个字符串数组保存了向该 power supply 设备供电的 power supply 设备列表也称作 supply提供者。从另一个方向组织 power supply 设备之间的级联关系。

num_supplies:supply 的个数。

of_node:power supply 设备它是 DTS 中的一个节点。

dev:power supply 设备。

changed_work/changed_lock/changed:用于处理状态改变的 workqueue主要思路是当该 power supply 设备的状态发生改变提交 changed_work 到 workqueue进而查询并通知所有的 supplicants。

deferred_register_work:power supply 注册结束时调用将任务推迟执行。该工作队列里拥有一个 timer 定时器结构体从而实现延时工作。

initialized:power supply 设备是否初始化完毕。

removing:power supply 设备正在移除中。

use_cnt:使用者计数。

tzd/tcd:如果该 power supply 设备具有温度等属性则需要借助 Linux Generic Thermal Sysfs Drivers温控子系统的框架注册相应的 Thermal 设备。

led_trigger:如果配置了 CONFIG_LEDS_TRIGGERS则调用 Linux Led Class 的接口注册相应的 LED 设备用于power supply 设备状态指示。

include/linux/power_supply.h

/* Run-time specific power supply configuration */
struct power_supply_config {
	struct device_node *of_node;
	struct fwnode_handle *fwnode;

	/* Driver private data */
	void *drv_data;

	char **supplied_to;
	size_t num_supplicants;
};

/* Description of power supply */
struct power_supply_desc {
	const char *name;
	enum power_supply_type type;
	enum power_supply_usb_type *usb_types;
	size_t num_usb_types;
	enum power_supply_property *properties;
	size_t num_properties;

	/*
	 * Functions for drivers implementing power supply class.
	 * These shouldn't be called directly by other drivers for accessing
	 * this power supply. Instead use power_supply_*() functions (for
	 * example power_supply_get_property()).
	 */
	int (*get_property)(struct power_supply *psy,
			    enum power_supply_property psp,
			    union power_supply_propval *val);
	int (*set_property)(struct power_supply *psy,
			    enum power_supply_property psp,
			    const union power_supply_propval *val);
	/*
	 * property_is_writeable() will be called during registration
	 * of power supply. If this happens during device probe then it must
	 * not access internal data of device (because probe did not end).
	 */
	int (*property_is_writeable)(struct power_supply *psy,
				     enum power_supply_property psp);
	void (*external_power_changed)(struct power_supply *psy);
	void (*set_charged)(struct power_supply *psy);

	/*
	 * Set if thermal zone should not be created for this power supply.
	 * For example for virtual supplies forwarding calls to actual
	 * sensors or other supplies.
	 */
	bool no_thermal;
	/* For APM emulation, think legacy userspace. */
	int use_for_apm;
};

struct power_supply {
	const struct power_supply_desc *desc;

	char **supplied_to;
	size_t num_supplicants;

	char **supplied_from;
	size_t num_supplies;
	struct device_node *of_node;

	/* Driver private data */
	void *drv_data;

	/* private */
	struct device dev;
	struct work_struct changed_work;
	struct delayed_work deferred_register_work;
	spinlock_t changed_lock;
	bool changed;
	bool initialized;
	bool removing;
	atomic_t use_cnt;
#ifdef CONFIG_THERMAL
	struct thermal_zone_device *tzd;
	struct thermal_cooling_device *tcd;
#endif

#ifdef CONFIG_LEDS_TRIGGERS
	struct led_trigger *charging_full_trig;
	char *charging_full_trig_name;
	struct led_trigger *charging_trig;
	char *charging_trig_name;
	struct led_trigger *full_trig;
	char *full_trig_name;
	struct led_trigger *online_trig;
	char *online_trig_name;
	struct led_trigger *charging_blink_full_solid_trig;
	char *charging_blink_full_solid_trig_name;
#endif
};

power supply framework 将所有可能 power supply 设备属性以枚举enum power_supply_property 的形式抽象出来power supply 设备 driver 可以根据设备的实际情况从中选取一些。

POWER_SUPPLY_PROP_STATUS该 power supply 设备的 status主要是充电状态包括“Unknown” “Charging”“Discharging” “Not charging”“Full”由枚举变量POWER_SUPPLY_STATUS_*定义。根据设计方案的不同充电类型的 power supply 设备或者 battery 类型的 power supply 设备都可能具备该属性。

POWER_SUPPLY_PROP_CHARGE_TYPE充电类型包括:“Unknown” “N/A” “Trickle”“Fast”由枚举型变量POWER_SUPPLY_CHARGE_TYPE_*定义同理根据设计方案的不同充电类型的 power supply 设备或者 battery 类型的 power supply 设备都可能具备该属性。

POWER_SUPPLY_PROP_HEALTH“健康”情况包括“Unknown”“Good”“Overheat”“Dead”“Over voltage”等由枚举型变量POWER_SUPPLY_HEALTH_*定义。一般用于 battery 类型的 power supply 设备。

POWER_SUPPLY_PROP_TECHNOLOGY采用的技术包括“Unknown”“NiMH”“Li-ion”“Li-poly” “LiFe” “NiCd”“LiMn”由枚举型变量POWER_SUPPLY_TECHNOLOGY_*定义。一般用于 battery 类型的 power supply 设备。

POWER_SUPPLY_PROP_CAPACITY_LEVEL容量包括“Unknown”“Critical”“Low”“Normal”“High”“Full”由枚举型变量POWER_SUPPLY_CAPACITY_LEVEL_*定义。一般用于 battery 类型的 power supply 设备。


power supply 设备类型由 enum power_supply_type 枚举定义。

POWER_SUPPLY_TYPE_UNKOWN未知。

POWER_SUPPLY_TYPE_BATTERY电池嵌入式设备、手持式智能设备常用的供电形式。

POWER_SUPPLY_TYPE_UPSUninterruptible Power System/Uninterruptible Power Supply不间断式供电设备通过将交流电和蓄电池连接正常情况下由交流电供电同时向蓄电池充电。当交流电断电时由蓄电池紧急供电。一般用于服务器等设备。

POWER_SUPPLY_TYPE_MAINS主供电设备如笔记本电脑的适配器其特点是可以单独供电当其断电时再由辅助供电设备供电如 battery。

POWER_SUPPLY_TYPE_USB/POWER_SUPPLY_TYPE_USB_DCP/POWER_SUPPLY_TYPE_USB_CDP/POWER_SUPPLY_TYPE_USB_ACA/POWER_SUPPLY_TYPE_USB_PD/POWER_SUPPLY_TYPE_USB_PD_DRPUSB 类型的供电不同点在于充电电流的限制由 USB Battery Charge Spec 规定。

POWER_SUPPLY_TYPE_USB_TYPE_C USB Type C 接口供电。

POWER_SUPPLY_TYPE_APPLE_BRICK_ID 苹果供电。


power supply USB 类型由 power_supply_usb_type 枚举定义。

标准下行端口SDP

这种端口的D+和D-线上具有15kΩ下拉电阻。限流值为:挂起时2.5mA连接时为100mA连接并配置为较高功率时为500mA。它其实就是一种普通的USB模式当USB处于这种模式时既可以为外部设备手机充电、充电宝充电也可以起到数据连接的作用U盘、手机上传/下载。

专用充电端口DCP

这种端口不支持任何数据传输但能够提供1.5A以上的电流。端口的D+和D-线之间短路。这种类型的端口支持较高充电能力的墙上充电器和车载充电器无需枚举。它其实就是简单的充电器当USB处于这种模式时只能进行充电而不能进行数据连接。

充电下行端口CDP

这种端口既支持大电流充电也支持完全兼容USB 2.0的数据传输。端口具有D+和D-通信所必需的15kΩ下拉电阻也具有充电器检测阶段切换的内部电路。内部电路允许便携设备将CDP与其它类型端口区分开来。它其实就是带有快充功能1.5A的USB接口当USB处于这种模式时既可以进行快充也可以起到数据连接的作用。

USB-PDPower Delivery

基于USB Type-C的一种电源供电标准最大供电功率可达100瓦W;随着USB Type-C的普及越来越多的设备手机、平板、显示器、工作站、充电器等使用USB-PD快速充电方案。

USB PD 1.0

PD1.0把传输功率从之前的7.5W提高到了最大100W输出电压最高达到20V最大电流达到5A。当一个用电设备连接到主机的USB口初始功率为10W (5V / 2A)在最终的功率规格选定之后传输功率相应的转换到18W36W60W或者100W。

USB PD 2.0

随着Type-C接口规范的发布USB-IF将USB PD升级到了2.0版本。BUS电压根据需求在5V9V15V和20V之间切换。

Type-C是一种接口规范默认最大支持5V/3A。Type-C接口带有专用的通信线即CC(channel configure)线。CC线可以传输USB PD协议同时支持DRP (dual role port) type C接口可以在电源和负载之间进行角色转换进而支持功率双向传输。

USB PD 3.0

是目前USB PD的最新版本。在PD2.0的基础上PD3.0增加了PPSprogrammable power supply功能BUS电压能够以20mV/step进行调整电流限值能够以50mA/step进行调整。电压电流细分调整可以优化电力传输策略让power变得更加智能化。

在这里插入图片描述

include/linux/power_supply.h

enum {
	POWER_SUPPLY_STATUS_UNKNOWN = 0,
	POWER_SUPPLY_STATUS_CHARGING,
	POWER_SUPPLY_STATUS_DISCHARGING,
	POWER_SUPPLY_STATUS_NOT_CHARGING,
	POWER_SUPPLY_STATUS_FULL,
};

enum {
	POWER_SUPPLY_CHARGE_TYPE_UNKNOWN = 0,
	POWER_SUPPLY_CHARGE_TYPE_NONE,
	POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
	POWER_SUPPLY_CHARGE_TYPE_FAST,
};

enum {
	POWER_SUPPLY_HEALTH_UNKNOWN = 0,
	POWER_SUPPLY_HEALTH_GOOD,
	POWER_SUPPLY_HEALTH_OVERHEAT,
	POWER_SUPPLY_HEALTH_DEAD,
	POWER_SUPPLY_HEALTH_OVERVOLTAGE,
	POWER_SUPPLY_HEALTH_UNSPEC_FAILURE,
	POWER_SUPPLY_HEALTH_COLD,
	POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE,
	POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE,
};

enum {
	POWER_SUPPLY_TECHNOLOGY_UNKNOWN = 0,
	POWER_SUPPLY_TECHNOLOGY_NiMH,
	POWER_SUPPLY_TECHNOLOGY_LION,
	POWER_SUPPLY_TECHNOLOGY_LIPO,
	POWER_SUPPLY_TECHNOLOGY_LiFe,
	POWER_SUPPLY_TECHNOLOGY_NiCd,
	POWER_SUPPLY_TECHNOLOGY_LiMn,
};

enum {
	POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN = 0,
	POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL,
	POWER_SUPPLY_CAPACITY_LEVEL_LOW,
	POWER_SUPPLY_CAPACITY_LEVEL_NORMAL,
	POWER_SUPPLY_CAPACITY_LEVEL_HIGH,
	POWER_SUPPLY_CAPACITY_LEVEL_FULL,
};

enum {
	POWER_SUPPLY_SCOPE_UNKNOWN = 0,
	POWER_SUPPLY_SCOPE_SYSTEM,
	POWER_SUPPLY_SCOPE_DEVICE,
};

enum power_supply_property {
	/* Properties of type `int' */
	POWER_SUPPLY_PROP_STATUS = 0,
	POWER_SUPPLY_PROP_CHARGE_TYPE,
	POWER_SUPPLY_PROP_HEALTH,
	POWER_SUPPLY_PROP_PRESENT,
	POWER_SUPPLY_PROP_ONLINE,
	POWER_SUPPLY_PROP_AUTHENTIC,
	POWER_SUPPLY_PROP_TECHNOLOGY,
	POWER_SUPPLY_PROP_CYCLE_COUNT,
	POWER_SUPPLY_PROP_VOLTAGE_MAX,
	POWER_SUPPLY_PROP_VOLTAGE_MIN,
	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
	POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
	POWER_SUPPLY_PROP_VOLTAGE_NOW,
	POWER_SUPPLY_PROP_VOLTAGE_AVG,
	POWER_SUPPLY_PROP_VOLTAGE_OCV,
	POWER_SUPPLY_PROP_VOLTAGE_BOOT,
	POWER_SUPPLY_PROP_CURRENT_MAX,
	POWER_SUPPLY_PROP_CURRENT_NOW,
	POWER_SUPPLY_PROP_CURRENT_AVG,
	POWER_SUPPLY_PROP_CURRENT_BOOT,
	POWER_SUPPLY_PROP_POWER_NOW,
	POWER_SUPPLY_PROP_POWER_AVG,
	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
	POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
	POWER_SUPPLY_PROP_CHARGE_FULL,
	POWER_SUPPLY_PROP_CHARGE_EMPTY,
	POWER_SUPPLY_PROP_CHARGE_NOW,
	POWER_SUPPLY_PROP_CHARGE_AVG,
	POWER_SUPPLY_PROP_CHARGE_COUNTER,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
	POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
	POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
	POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
	POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
	POWER_SUPPLY_PROP_ENERGY_FULL,
	POWER_SUPPLY_PROP_ENERGY_EMPTY,
	POWER_SUPPLY_PROP_ENERGY_NOW,
	POWER_SUPPLY_PROP_ENERGY_AVG,
	POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
	POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, /* in percents! */
	POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, /* in percents! */
	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
	POWER_SUPPLY_PROP_TEMP,
	POWER_SUPPLY_PROP_TEMP_MAX,
	POWER_SUPPLY_PROP_TEMP_MIN,
	POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
	POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
	POWER_SUPPLY_PROP_TEMP_AMBIENT,
	POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN,
	POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX,
	POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
	POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
	POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
	POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
	POWER_SUPPLY_PROP_USB_TYPE,
	POWER_SUPPLY_PROP_SCOPE,
	POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
	POWER_SUPPLY_PROP_CALIBRATE,
	/* Properties of type `const char *' */
	POWER_SUPPLY_PROP_MODEL_NAME,
	POWER_SUPPLY_PROP_MANUFACTURER,
	POWER_SUPPLY_PROP_SERIAL_NUMBER,
};

enum power_supply_type {
	POWER_SUPPLY_TYPE_UNKNOWN = 0,
	POWER_SUPPLY_TYPE_BATTERY,
	POWER_SUPPLY_TYPE_UPS,
	POWER_SUPPLY_TYPE_MAINS,
	POWER_SUPPLY_TYPE_USB,			/* Standard Downstream Port */
	POWER_SUPPLY_TYPE_USB_DCP,		/* Dedicated Charging Port */
	POWER_SUPPLY_TYPE_USB_CDP,		/* Charging Downstream Port */
	POWER_SUPPLY_TYPE_USB_ACA,		/* Accessory Charger Adapters */
	POWER_SUPPLY_TYPE_USB_TYPE_C,		/* Type C Port */
	POWER_SUPPLY_TYPE_USB_PD,		/* Power Delivery Port */
	POWER_SUPPLY_TYPE_USB_PD_DRP,		/* PD Dual Role Port */
	POWER_SUPPLY_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
};

enum power_supply_usb_type {
	POWER_SUPPLY_USB_TYPE_UNKNOWN = 0,
	POWER_SUPPLY_USB_TYPE_SDP,		/* Standard Downstream Port */
	POWER_SUPPLY_USB_TYPE_DCP,		/* Dedicated Charging Port */
	POWER_SUPPLY_USB_TYPE_CDP,		/* Charging Downstream Port */
	POWER_SUPPLY_USB_TYPE_ACA,		/* Accessory Charger Adapters */
	POWER_SUPPLY_USB_TYPE_C,		/* Type C Port */
	POWER_SUPPLY_USB_TYPE_PD,		/* Power Delivery Port */
	POWER_SUPPLY_USB_TYPE_PD_DRP,		/* PD Dual Role Port */
	POWER_SUPPLY_USB_TYPE_PD_PPS,		/* PD Programmable Power Supply */
	POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID,	/* Apple Charging Method */
};

三、Power Supply 核心实现

power supply framework 首要任务是向 power supply 设备 driver 提供统一的驱动编写接口主要包括:

power supply 设备的 register/unregister API

其中 power_supply_register 和 power_supply_register_no_ws 的区别是 power_supply_register 注册的设备具备 wakeup 系统的能力而 power_supply_register_no_ws 不具备。power_supply_register 和 devm_power_supply_register 的区别是 power_supply_register 需要对返回的 power_supply 指针使用 power_supply_unregister 来释放资源而 devm_power_supply_register 返回的 power_supply 指针将在驱动程序 detach 时自动注销。

include/linux/power_supply.h

extern struct power_supply *__must_check
power_supply_register(struct device *parent,
				 const struct power_supply_desc *desc,
				 const struct power_supply_config *cfg);
extern struct power_supply *__must_check
power_supply_register_no_ws(struct device *parent,
				 const struct power_supply_desc *desc,
				 const struct power_supply_config *cfg);
extern struct power_supply *__must_check
devm_power_supply_register(struct device *parent,
				 const struct power_supply_desc *desc,
				 const struct power_supply_config *cfg);
extern struct power_supply *__must_check
devm_power_supply_register_no_ws(struct device *parent,
				 const struct power_supply_desc *desc,
				 const struct power_supply_config *cfg);
extern void power_supply_unregister(struct power_supply *psy);

power_supply_register、power_supply_register_no_ws、devm_power_supply_register 和 devm_power_supply_register_no_ws内部都调用了 __power_supply_register下面分析它的关键工作流程:

  1. 遍历 desc 指向的 power_supply_desc 结构中的属性字段如果存在 USB 供电属性那么就去查看是否存在 USB 类型不存在的话返回 ERR_PTR(-EINVAL)。
  2. 调用 kzalloc 给 power_supply 结构分配内存并调用 device_initialize 初始化 device 结构接着给 dev 指向的 device 结构字段赋值。
  3. 调用 dev_set_drvdata 给 device 设置私有数据指向刚刚分配的 power_supply 结构。
  4. 形参 cfg 不为空的情况下将 cfg 携带的数据赋给 power_supply 结构中的相应字段。
  5. 调用 dev_set_name 设置 device name。
  6. 调用 INIT_WORK 和 INIT_DELAYED_WORK 宏初始化工作队列并添加任务INIT_DELAYED_WORK 添加的任务会延时执行。
  7. 调用 power_supply_check_supplies 检查给本 power supply 设备供电的 power supply 设备这个函数可能到设备树去检索 supplies。
  8. 调用 spin_lock_init 初始化 power_supply 结构中的自旋锁。
  9. 调用 device_add 添加这个 device。
  10. 调用 device_init_wakeup 赋予设备是否支持唤醒系统和是否使用唤醒系统的特性。
  11. 调用 psy_register_thermal 给 power supply 设备注册 thermal和温控相关。
  12. 调用 psy_register_cooler 给 power supply 设备注册 cooler和温控相关。
  13. 调用 power_supply_create_triggers 创建触发器。
  14. power_supply 结构中的 use_cnt 原子的加 1在任何 uevents 之后更新 use_cnt最明显的来自 device_add() 。
  15. power_supply 结构中的 initialized 字段赋值为 true表明 power_supply 结构已经初始化完成。
  16. 调用 queue_delayed_work 启动 delayed work 执行延迟 POWER_SUPPLY_DEFERRED_REGISTER_TIME 后执行。
  17. 没有异常的情况下现在返回指向 power_supply 结构的指针。

power_supply_unregister 主要工作流程:

  1. 调用 atomic_dec_return 将 power_supply 结构中的 use_cnt 原子地减一。
  2. power_supply 结构中的 removing 字段赋值为 true表征正在移除 power supply 设备。
  3. 调用 cancel_work_sync 和 cancel_delayed_work_sync 取消两个工作队列里的任务。
  4. 调用 sysfs_remove_link 删除 kobj 目录下名为 powers 的软链接文件。
  5. 调用 power_supply_remove_triggers 移除触发器。
  6. 调用 psy_unregister_cooler 和 psy_unregister_thermal 取消注册的 cooler 和 thermal。
  7. 调用 device_init_wakeup 取消 power supply 设备唤醒系统的能力。
  8. 调用 device_unregister 移除设备。

drivers/power/supply/power_supply_core.c

static struct power_supply *__must_check
__power_supply_register(struct device *parent,
				   const struct power_supply_desc *desc,
				   const struct power_supply_config *cfg,
				   bool ws)
{
	struct device *dev;
	struct power_supply *psy;
	int i, rc;

	if (!parent)
		pr_warn("%s: Expected proper parent device for '%s'\n",
			__func__, desc->name);

	if (!desc || !desc->name || !desc->properties || !desc->num_properties)
		return ERR_PTR(-EINVAL);

	for (i = 0; i < desc->num_properties; ++i) {
		if ((desc->properties[i] == POWER_SUPPLY_PROP_USB_TYPE) &&
		    (!desc->usb_types || !desc->num_usb_types))
			return ERR_PTR(-EINVAL);
	}

	psy = kzalloc(sizeof(*psy), GFP_KERNEL);
	if (!psy)
		return ERR_PTR(-ENOMEM);

	dev = &psy->dev;

	device_initialize(dev);

	dev->class = power_supply_class;
	dev->type = &power_supply_dev_type;
	dev->parent = parent;
	dev->release = power_supply_dev_release;
	dev_set_drvdata(dev, psy);
	psy->desc = desc;
	if (cfg) {
		psy->drv_data = cfg->drv_data;
		psy->of_node =
			cfg->fwnode ? to_of_node(cfg->fwnode) : cfg->of_node;
		psy->supplied_to = cfg->supplied_to;
		psy->num_supplicants = cfg->num_supplicants;
	}

	rc = dev_set_name(dev, "%s", desc->name);
	if (rc)
		goto dev_set_name_failed;

	INIT_WORK(&psy->changed_work, power_supply_changed_work);
	INIT_DELAYED_WORK(&psy->deferred_register_work,
			  power_supply_deferred_register_work);

	rc = power_supply_check_supplies(psy);
	if (rc) {
		dev_info(dev, "Not all required supplies found, defer probe\n");
		goto check_supplies_failed;
	}

	spin_lock_init(&psy->changed_lock);
	rc = device_add(dev);
	if (rc)
		goto device_add_failed;

	rc = device_init_wakeup(dev, ws);
	if (rc)
		goto wakeup_init_failed;

	rc = psy_register_thermal(psy);
	if (rc)
		goto register_thermal_failed;

	rc = psy_register_cooler(psy);
	if (rc)
		goto register_cooler_failed;

	rc = power_supply_create_triggers(psy);
	if (rc)
		goto create_triggers_failed;

	/*
	 * Update use_cnt after any uevents (most notably from device_add()).
	 * We are here still during driver's probe but
	 * the power_supply_uevent() calls back driver's get_property
	 * method so:
	 * 1. Driver did not assigned the returned struct power_supply,
	 * 2. Driver could not finish initialization (anything in its probe
	 *    after calling power_supply_register()).
	 */
	atomic_inc(&psy->use_cnt);
	psy->initialized = true;

	queue_delayed_work(system_power_efficient_wq,
			   &psy->deferred_register_work,
			   POWER_SUPPLY_DEFERRED_REGISTER_TIME);

	return psy;

create_triggers_failed:
	psy_unregister_cooler(psy);
register_cooler_failed:
	psy_unregister_thermal(psy);
register_thermal_failed:
	device_del(dev);
wakeup_init_failed:
device_add_failed:
check_supplies_failed:
dev_set_name_failed:
	put_device(dev);
	return ERR_PTR(rc);
}

struct power_supply *__must_check power_supply_register(struct device *parent,
		const struct power_supply_desc *desc,
		const struct power_supply_config *cfg)
{
	return __power_supply_register(parent, desc, cfg, true);
}
EXPORT_SYMBOL_GPL(power_supply_register);


struct power_supply *__must_check
power_supply_register_no_ws(struct device *parent,
		const struct power_supply_desc *desc,
		const struct power_supply_config *cfg)
{
	return __power_supply_register(parent, desc, cfg, false);
}
EXPORT_SYMBOL_GPL(power_supply_register_no_ws);

static void devm_power_supply_release(struct device *dev, void *res)
{
	struct power_supply **psy = res;

	power_supply_unregister(*psy);
}

struct power_supply *__must_check
devm_power_supply_register(struct device *parent,
		const struct power_supply_desc *desc,
		const struct power_supply_config *cfg)
{
	struct power_supply **ptr, *psy;

	ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);

	if (!ptr)
		return ERR_PTR(-ENOMEM);
	psy = __power_supply_register(parent, desc, cfg, true);
	if (IS_ERR(psy)) {
		devres_free(ptr);
	} else {
		*ptr = psy;
		devres_add(parent, ptr);
	}
	return psy;
}
EXPORT_SYMBOL_GPL(devm_power_supply_register);

struct power_supply *__must_check
devm_power_supply_register_no_ws(struct device *parent,
		const struct power_supply_desc *desc,
		const struct power_supply_config *cfg)
{
	struct power_supply **ptr, *psy;

	ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);

	if (!ptr)
		return ERR_PTR(-ENOMEM);
	psy = __power_supply_register(parent, desc, cfg, false);
	if (IS_ERR(psy)) {
		devres_free(ptr);
	} else {
		*ptr = psy;
		devres_add(parent, ptr);
	}
	return psy;
}
EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws);

void power_supply_unregister(struct power_supply *psy)
{
	WARN_ON(atomic_dec_return(&psy->use_cnt));
	psy->removing = true;
	cancel_work_sync(&psy->changed_work);
	cancel_delayed_work_sync(&psy->deferred_register_work);
	sysfs_remove_link(&psy->dev.kobj, "powers");
	power_supply_remove_triggers(psy);
	psy_unregister_cooler(psy);
	psy_unregister_thermal(psy);
	device_init_wakeup(&psy->dev, false);
	device_unregister(&psy->dev);
}
EXPORT_SYMBOL_GPL(power_supply_unregister);

power supply 设备的 状态改变时通知 power supply core API

当 power supply 设备 driver 检测到该设备某些属性值改变时需要调用这个接口通知 power supply core。

include/linux/power_supply.h

extern void power_supply_changed(struct power_supply *psy);
  1. 调用 spin_lock_irqsave 获取 power_supply 结构中 changed_lock 锁之前关闭本地中断。
  2. 修改 power_supply 结构中 changed 字段为 true。
  3. 调用 pm_stay_awake 通知 PM 核心正在处理一个唤醒事件。
  4. 调用 spin_unlock_irqrestore 解锁并恢复本地中断到没调用 spin_lock_irqsave 之前的状态。
  5. 调用 schedule_work 将 changed_work 任务提交到工作队列。

drivers/power/supply/power_supply_core.c

void power_supply_changed(struct power_supply *psy)
{
	unsigned long flags;

	dev_dbg(&psy->dev, "%s\n", __func__);

	spin_lock_irqsave(&psy->changed_lock, flags);
	psy->changed = true;
	pm_stay_awake(&psy->dev);
	spin_unlock_irqrestore(&psy->changed_lock, flags);
	schedule_work(&psy->changed_work);
}
EXPORT_SYMBOL_GPL(power_supply_changed);

由于注册 power supply 设备的时候工作任务关联的函数是 power_supply_changed_work当工作线程执行到这个任务时会调用 power_supply_changed_work 函数。

power_supply_changed_work 主要工作流程:

  1. 调用 container_of 宏获取 power_supply 结构。
  2. 调用 spin_lock_irqsave 获取 power_supply 结构中 changed_lock 锁之前关闭本地中断。
  3. 当 power_supply 结构中的 changed 字段为 true表明有工作需要进一步处理先将 changed 字段设置为 false接着调用 spin_unlock_irqrestore 解锁并恢复本地中断到没调用 spin_lock_irqsave 之前的状态。然后调用 class_for_each_device 从设备列表的开头开始迭代 power_supply_class并为每个设备调用 __power_supply_changed_work传递 power_supply 结构。接下来调用 power_supply_update_leds 更新 LED 指示灯的状态。调用 atomic_notifier_call_chain 依次调用通知程序链power_supply_notifier中的每个函数函数在原子上下文中运行所以它们不能阻塞。继续调用 kobject_uevent 触发 CHANGE 类型的 uevent。最后继续调用 spin_lock_irqsave 获取 power_supply 结构中 changed_lock 锁之前关闭本地中断。

在这里检查’changed’以避免 power_supply_changed 和这个例程之间的竞争导致的问题。在最坏的情况下power_supply_changed 可以在上锁之前再次调用。在这个例程的第一次调用中我们将把’changed’标记为 false并且在下一次调用中它也将保持为 false。

  1. 运行到这里如果 power_supply 结构中的 changed 字段为 false就调动 pm_relax 通知 PM 核心一个唤醒事件的处理已经结束。最后调用 spin_unlock_irqrestore 解锁并恢复本地中断到没调用 spin_lock_irqsave 之前的状态。

保持 wakeup_source 直到所有事件处理完毕。power_supply_changed 可能会再次调用并将’changed’设置为 true。

__power_supply_changed_work 主要工作流程:

  1. 调用目标 power supply 设备 dev_get_drvdata 获取 power_supply struct。
  2. 调用 __power_supply_is_supplied_by 查找是否有其它 power supply 设备给目标设备供电。如果 __power_supply_is_supplied_by 返回 true表明的确有。检查目标设备 external_power_changed 字段是否为空调用目标设备的 external_power_changed 通知给它供电的 power supply 设备。

__power_supply_is_supplied_by 主要工作流程:

  1. 检查目标 power supply 设备的 supplied_from 和可能给目标供电的 power supply 设备 supplied_to 列表如果同时为空直接返回 false。
  2. 查找目标 power supply 设备的 supplied_from 列表如果存在名字和可能给目标供电的 power supply 设备的名字一样说明这个设备的确给目标设备供电直接返回 true。
  3. 如果目标 power supply 设备的 supplied_from 列表为空就去查找可能给目标供电的 power supply 设备 supplied_to 列表如果找到目标 power supply 设备的名字说明的确给它供电了也返回 true。

drivers/power/supply/power_supply_core.c

static bool __power_supply_is_supplied_by(struct power_supply *supplier,
					 struct power_supply *supply)
{
	int i;

	if (!supply->supplied_from && !supplier->supplied_to)
		return false;

	/* Support both supplied_to and supplied_from modes */
	if (supply->supplied_from) {
		if (!supplier->desc->name)
			return false;
		for (i = 0; i < supply->num_supplies; i++)
			if (!strcmp(supplier->desc->name, supply->supplied_from[i]))
				return true;
	} else {
		if (!supply->desc->name)
			return false;
		for (i = 0; i < supplier->num_supplicants; i++)
			if (!strcmp(supplier->supplied_to[i], supply->desc->name))
				return true;
	}

	return false;
}

static int __power_supply_changed_work(struct device *dev, void *data)
{
	struct power_supply *psy = data;
	struct power_supply *pst = dev_get_drvdata(dev);

	if (__power_supply_is_supplied_by(psy, pst)) {
		if (pst->desc->external_power_changed)
			pst->desc->external_power_changed(pst);
	}

	return 0;
}

static void power_supply_changed_work(struct work_struct *work)
{
	unsigned long flags;
	struct power_supply *psy = container_of(work, struct power_supply,
						changed_work);

	dev_dbg(&psy->dev, "%s\n", __func__);

	spin_lock_irqsave(&psy->changed_lock, flags);
	/*
	 * Check 'changed' here to avoid issues due to race between
	 * power_supply_changed() and this routine. In worst case
	 * power_supply_changed() can be called again just before we take above
	 * lock. During the first call of this routine we will mark 'changed' as
	 * false and it will stay false for the next call as well.
	 */
	if (likely(psy->changed)) {
		psy->changed = false;
		spin_unlock_irqrestore(&psy->changed_lock, flags);
		class_for_each_device(power_supply_class, NULL, psy,
				      __power_supply_changed_work);
		power_supply_update_leds(psy);
		atomic_notifier_call_chain(&power_supply_notifier,
				PSY_EVENT_PROP_CHANGED, psy);
		kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);
		spin_lock_irqsave(&psy->changed_lock, flags);
	}

	/*
	 * Hold the wakeup_source until all events are processed.
	 * power_supply_changed() might have called again and have set 'changed'
	 * to true.
	 */
	if (likely(!psy->changed))
		pm_relax(&psy->dev);
	spin_unlock_irqrestore(&psy->changed_lock, flags);
}

power supply 设备的 获取/释放 API

include/linux/power_supply.h

extern struct power_supply *power_supply_get_by_name(const char *name);
extern void power_supply_put(struct power_supply *psy);
#ifdef CONFIG_OF
extern struct power_supply *power_supply_get_by_phandle(struct device_node *np,
							const char *property);
extern struct power_supply *devm_power_supply_get_by_phandle(
				    struct device *dev, const char *property);
#else /* !CONFIG_OF */
...
#endif /* CONFIG_OF */

根据名称搜索匹配的 power_supply 结构如果找到它会增加内部 power supply 设备的参考计数。用户在使用后应该使用 power_supply_put。

power_supply_get_by_name 主要工作流程:

  1. 调用 class_find_device 用于定位设备迭代器中和 name 匹配的设备name 匹配是使用 power_supply_match_device_by_name 实现的此函数内部调用了 strcmp 比较 name 字符串。
  2. 从 device 结构体中调用 dev_get_drvdata 获取其私有数据 power_supply 结构体。
  3. power_supply 结构体 use_cnt 字段原子地加 1。

power_supply_put 解除通过 power_supply_get_by_name 获取的引用。在注销 power supply 设备前应该解除引用。其内部首先原子地将 use_cnt 字段减 1再去调用 put_device 减少对 device 的引用计数。

power_supply_get_by_phandle 通过持有 phandle 属性的设备节点和包含 powe supply 设备名称的属性获取相应的 power_supply 结构。

devm_power_supply_get_by_phandle 则是资源管理版本的 power_supply_get_by_phandle会自动调用
devm_power_supply_put 解除引用。

drivers/power/supply/power_supply_core.c

static int power_supply_match_device_by_name(struct device *dev, const void *data)
{
	const char *name = data;
	struct power_supply *psy = dev_get_drvdata(dev);

	return strcmp(psy->desc->name, name) == 0;
}

struct power_supply *power_supply_get_by_name(const char *name)
{
	struct power_supply *psy = NULL;
	struct device *dev = class_find_device(power_supply_class, NULL, name,
					power_supply_match_device_by_name);

	if (dev) {
		psy = dev_get_drvdata(dev);
		atomic_inc(&psy->use_cnt);
	}

	return psy;
}
EXPORT_SYMBOL_GPL(power_supply_get_by_name);

void power_supply_put(struct power_supply *psy)
{
	might_sleep();

	atomic_dec(&psy->use_cnt);
	put_device(&psy->dev);
}
EXPORT_SYMBOL_GPL(power_supply_put);

#ifdef CONFIG_OF
static int power_supply_match_device_node(struct device *dev, const void *data)
{
	return dev->parent && dev->parent->of_node == data;
}

struct power_supply *power_supply_get_by_phandle(struct device_node *np,
							const char *property)
{
	struct device_node *power_supply_np;
	struct power_supply *psy = NULL;
	struct device *dev;

	power_supply_np = of_parse_phandle(np, property, 0);
	if (!power_supply_np)
		return ERR_PTR(-ENODEV);

	dev = class_find_device(power_supply_class, NULL, power_supply_np,
						power_supply_match_device_node);

	of_node_put(power_supply_np);

	if (dev) {
		psy = dev_get_drvdata(dev);
		atomic_inc(&psy->use_cnt);
	}

	return psy;
}
EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);

static void devm_power_supply_put(struct device *dev, void *res)
{
	struct power_supply **psy = res;

	power_supply_put(*psy);
}

struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
						      const char *property)
{
	struct power_supply **ptr, *psy;

	if (!dev->of_node)
		return ERR_PTR(-ENODEV);

	ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
	if (!ptr)
		return ERR_PTR(-ENOMEM);

	psy = power_supply_get_by_phandle(dev->of_node, property);
	if (IS_ERR_OR_NULL(psy)) {
		devres_free(ptr);
	} else {
		*ptr = psy;
		devres_add(dev, ptr);
	}
	return psy;
}
EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
#endif /* CONFIG_OF */

power supply 设备的 向其它 driver 提供的用于接收 power supply 设备状态改变 notifier 的 API

include/linux/power_supply.h

extern struct atomic_notifier_head power_supply_notifier;
extern int power_supply_reg_notifier(struct notifier_block *nb);
extern void power_supply_unreg_notifier(struct notifier_block *nb);

power_supply_reg_notifier 调用 atomic_notifier_chain_register 原子地添加 notifier_block 到通知链。power_supply_unreg_notifier 刚好相反。

drivers/power/supply/power_supply_core.c

int power_supply_reg_notifier(struct notifier_block *nb)
{
	return atomic_notifier_chain_register(&power_supply_notifier, nb);
}
EXPORT_SYMBOL_GPL(power_supply_reg_notifier);

void power_supply_unreg_notifier(struct notifier_block *nb)
{
	atomic_notifier_chain_unregister(&power_supply_notifier, nb);
}

power supply 设备的 属性 API

这些属性相关的 API 只是简单的做了下检查后将工作委托给 power_supply desc 指向的 power_supply_desc 结构体中的具体实现函数处理。

include/linux/power_supply.h

extern int power_supply_get_property(struct power_supply *psy,
			    enum power_supply_property psp,
			    union power_supply_propval *val);
extern int power_supply_set_property(struct power_supply *psy,
			    enum power_supply_property psp,
			    const union power_supply_propval *val);
extern int power_supply_property_is_writeable(struct power_supply *psy,
					enum power_supply_property psp);

drivers/power/supply/power_supply_core.c

int power_supply_get_property(struct power_supply *psy,
			    enum power_supply_property psp,
			    union power_supply_propval *val)
{
	if (atomic_read(&psy->use_cnt) <= 0) {
		if (!psy->initialized)
			return -EAGAIN;
		return -ENODEV;
	}

	return psy->desc->get_property(psy, psp, val);
}
EXPORT_SYMBOL_GPL(power_supply_get_property);

int power_supply_set_property(struct power_supply *psy,
			    enum power_supply_property psp,
			    const union power_supply_propval *val)
{
	if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property)
		return -ENODEV;

	return psy->desc->set_property(psy, psp, val);
}
EXPORT_SYMBOL_GPL(power_supply_set_property);

int power_supply_property_is_writeable(struct power_supply *psy,
					enum power_supply_property psp)
{
	if (atomic_read(&psy->use_cnt) <= 0 ||
			!psy->desc->property_is_writeable)
		return -ENODEV;

	return psy->desc->property_is_writeable(psy, psp);
}
EXPORT_SYMBOL_GPL(power_supply_property_is_writeable);

power supply 设备的 其他 API

power_supply_get_battery_info 获取电池信息power_supply_battery_info 代表。

power_supply_set_input_current_limit_from_supplier 设置来自 power supply 设备的输入电流限制。

power_supply_set_battery_charged 调用指定 power supply 设备的 set_charged 回调。

power_supply_is_system_supplied 查询系统是否有有效的或者处于 online 状态的 power supply 设备如果没有可能为桌面系统。

power_supply_am_i_supplied 查询自己是否由其它 power supply 设备供电。

power_supply_external_power_changed 外部供电设备“变化”调用 external_power_changed 回调函数处理。

power_supply_powers 在指定设备通常是该 power supply 设备的 Sysfs 目录/sys/devices/xxx/下创建指定 power supply 设备的符号链接/sys/devices/xxx/powers。

power_supply_get_drvdata 获取 power_supply 结构的 driver 私有数据。

include/linux/power_supply.h

extern int power_supply_get_battery_info(struct power_supply *psy,
					 struct power_supply_battery_info *info);
					 extern int power_supply_am_i_supplied(struct power_supply *psy);
extern int power_supply_am_i_supplied(struct power_supply *psy);
extern int power_supply_set_input_current_limit_from_supplier(
					 struct power_supply *psy);
extern int power_supply_set_battery_charged(struct power_supply *psy);
extern int power_supply_is_system_supplied(void);
extern void power_supply_external_power_changed(struct power_supply *psy);
extern int power_supply_powers(struct power_supply *psy, struct device *dev);
extern void *power_supply_get_drvdata(struct power_supply *psy);
  1. 判断 power_supply 结构中的 of_node 是否为空如果为空就打印 warn log:当前仅支持设备树。
  2. 调用 of_parse_phandle 通过当前节点的 phandle 属性monitored-battery获得一个 device node 节点battery_np。
  3. 从 battery_np 指向的 device node 中读出各种属性对应的值填充到 power_supply_battery_info 结构体对应字段。

drivers/power/supply/power_supply_core.c

int power_supply_get_battery_info(struct power_supply *psy,
				  struct power_supply_battery_info *info)
{
	struct device_node *battery_np;
	const char *value;
	int err;

	info->energy_full_design_uwh         = -EINVAL;
	info->charge_full_design_uah         = -EINVAL;
	info->voltage_min_design_uv          = -EINVAL;
	info->precharge_current_ua           = -EINVAL;
	info->charge_term_current_ua         = -EINVAL;
	info->constant_charge_current_max_ua = -EINVAL;
	info->constant_charge_voltage_max_uv = -EINVAL;

	if (!psy->of_node) {
		dev_warn(&psy->dev, "%s currently only supports devicetree\n",
			 __func__);
		return -ENXIO;
	}

	battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0);
	if (!battery_np)
		return -ENODEV;

	err = of_property_read_string(battery_np, "compatible", &value);
	if (err)
		return err;

	if (strcmp("simple-battery", value))
		return -ENODEV;

	/* The property and field names below must correspond to elements
	 * in enum power_supply_property. For reasoning, see
	 * Documentation/power/power_supply_class.txt.
	 */

	of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
			     &info->energy_full_design_uwh);
	of_property_read_u32(battery_np, "charge-full-design-microamp-hours",
			     &info->charge_full_design_uah);
	of_property_read_u32(battery_np, "voltage-min-design-microvolt",
			     &info->voltage_min_design_uv);
	of_property_read_u32(battery_np, "precharge-current-microamp",
			     &info->precharge_current_ua);
	of_property_read_u32(battery_np, "charge-term-current-microamp",
			     &info->charge_term_current_ua);
	of_property_read_u32(battery_np, "constant_charge_current_max_microamp",
			     &info->constant_charge_current_max_ua);
	of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt",
			     &info->constant_charge_voltage_max_uv);

	return 0;
}
EXPORT_SYMBOL_GPL(power_supply_get_battery_info);

power_supply_set_input_current_limit_from_supplier 主要工作流程:

  1. 调用 class_for_each_device 遍历 power_supply_class此功能不打算用于具有多个供电源的情况我们只是选择第一个供电源报告非0最大电流。
  2. __power_supply_get_supplier_max_current 函数中获取到给该 power supply 设备供电的 power supply 设备读取最大电流值并返回这会导致 class_for_each_device 迭代退出循环。
  3. 调用 set_property 给该 power supply 设备设置最大电流值限制。

drivers/power/supply/power_supply_core.c

static int __power_supply_get_supplier_max_current(struct device *dev,
						   void *data)
{
	union power_supply_propval ret = {0,};
	struct power_supply *epsy = dev_get_drvdata(dev);
	struct power_supply *psy = data;

	if (__power_supply_is_supplied_by(epsy, psy))
		if (!epsy->desc->get_property(epsy,
					      POWER_SUPPLY_PROP_CURRENT_MAX,
					      &ret))
			return ret.intval;

	return 0;
}

int power_supply_set_input_current_limit_from_supplier(struct power_supply *psy)
{
	union power_supply_propval val = {0,};
	int curr;

	if (!psy->desc->set_property)
		return -EINVAL;

	/*
	 * This function is not intended for use with a supply with multiple
	 * suppliers, we simply pick the first supply to report a non 0
	 * max-current.
	 */
	curr = class_for_each_device(power_supply_class, NULL, psy,
				      __power_supply_get_supplier_max_current);
	if (curr <= 0)
		return (curr == 0) ? -ENODEV : curr;

	val.intval = curr;

	return psy->desc->set_property(psy,
				POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, &val);
}
EXPORT_SYMBOL_GPL(power_supply_set_input_current_limit_from_supplier);

当 power supply 设备读取用户数大于等于 0并且其类型为电池时而且它的 set_charged 不为空就调用 set_charged 指向的实际实现函数。

drivers/power/supply/power_supply_core.c

int power_supply_set_battery_charged(struct power_supply *psy)
{
	if (atomic_read(&psy->use_cnt) >= 0 &&
			psy->desc->type == POWER_SUPPLY_TYPE_BATTERY &&
			psy->desc->set_charged) {
		psy->desc->set_charged(psy);
		return 0;
	}

	return -EINVAL;
}
EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);

调用 class_for_each_device 遍历 power_supply_class查找系统是否有有效的或者处于 online 状态的 power supply 设备非电池供电类型如果遍历完后 count 还为 0说明根本没有发现 power supply 设备那么很可能我们正在桌面系统上运行因此假设我们使用的是主电源供电。

__power_supply_is_system_supplied 主要获取 POWER_SUPPLY_PROP_ONLINE 属性判断 power supply 设备是否在线。

drivers/power/supply/power_supply_core.c

static int __power_supply_is_system_supplied(struct device *dev, void *data)
{
	union power_supply_propval ret = {0,};
	struct power_supply *psy = dev_get_drvdata(dev);
	unsigned int *count = data;

	(*count)++;
	if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY)
		if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE,
					&ret))
			return ret.intval;

	return 0;
}

int power_supply_is_system_supplied(void)
{
	int error;
	unsigned int count = 0;

	error = class_for_each_device(power_supply_class, NULL, &count,
				      __power_supply_is_system_supplied);

	/*
	 * If no power class device was found at all, most probably we are
	 * running on a desktop system, so assume we are on mains power.
	 */
	if (count == 0)
		return 1;

	return error;
}
EXPORT_SYMBOL_GPL(power_supply_is_system_supplied);

调用 class_for_each_device 遍历 power_supply_class当查到存在设备给其供电设备的 POWER_SUPPLY_PROP_ONLINE 属性如果不为 0 就 break 出来。

drivers/power/supply/power_supply_core.c

struct psy_am_i_supplied_data {
	struct power_supply *psy;
	unsigned int count;
};

static int __power_supply_am_i_supplied(struct device *dev, void *_data)
{
	union power_supply_propval ret = {0,};
	struct power_supply *epsy = dev_get_drvdata(dev);
	struct psy_am_i_supplied_data *data = _data;

	if (__power_supply_is_supplied_by(epsy, data->psy)) {
		data->count++;
		if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE,
					&ret))
			return ret.intval;
	}

	return 0;
}

int power_supply_am_i_supplied(struct power_supply *psy)
{
	struct psy_am_i_supplied_data data = { psy, 0 };
	int error;

	error = class_for_each_device(power_supply_class, NULL, &data,
				      __power_supply_am_i_supplied);

	dev_dbg(&psy->dev, "%s count %u err %d\n", __func__, data.count, error);

	if (data.count == 0)
		return -ENODEV;

	return error;
}
EXPORT_SYMBOL_GPL(power_supply_am_i_supplied);

power supply core 调用 power_supply_external_power_changed 函数非常简单判断用户数大于 0 的情况下直接调用 external_power_changed 函数指针指向的实际函数实现。

drivers/power/supply/power_supply_core.c

void power_supply_external_power_changed(struct power_supply *psy)
{
	if (atomic_read(&psy->use_cnt) <= 0 ||
			!psy->desc->external_power_changed)
		return;

	psy->desc->external_power_changed(psy);
}
EXPORT_SYMBOL_GPL(power_supply_external_power_changed);

调用 sysfs_create_link 在 Sysfs 创建一个该 power supply 设备我们要在其目录中创建链接指向的 dev 设备符号链接 powers。

drivers/power/supply/power_supply_core.c

int power_supply_powers(struct power_supply *psy, struct device *dev)
{
	return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers");
}
EXPORT_SYMBOL_GPL(power_supply_powers);

调用 sysfs_create_link 在 Sysfs 创建一个该 power supply 设备我们要在其目录中创建链接指向的 dev 设备符号链接 powers。

drivers/power/supply/power_supply_core.c

int power_supply_powers(struct power_supply *psy, struct device *dev)
{
	return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers");
}
EXPORT_SYMBOL_GPL(power_supply_powers);

power_supply_get_drvdata 实现非常简单仅仅返回了 power_supply 结构的 drv_data 字段。

drivers/power/supply/power_supply_core.c

void *power_supply_get_drvdata(struct power_supply *psy)
{
	return psy->drv_data;
}
EXPORT_SYMBOL_GPL(power_supply_get_drvdata);
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: linux