1.概述
在上一篇博文中参考窝窝科技的文章分析了linux Power Supply子系统的框架。这篇我们以一个实际的例子来看一下PSY driver的编程方式,比便于更深刻理解power supply 子系统。
2.power supply子系统的引入
以市面上一款常见的的平板方案来看一看,进入sys/class/power_supply/目录下
可以看到这里有三个PSY设备,分别对应USB充电器 DC充电器,和电池。
进入battery目录下,发现下面有各种各样的属性,另外两个atc260x-usb 、atc260x-wall目录下分别也是这样。
那么在内核中肯定有三个驱动对应这个三个PSY设备。
3.PSY设备驱动分析
我们先以battery驱动为例来分析。
static int __init atc260x_gauge_init(void)
{
struct device_node *node =
of_find_compatible_node(NULL, NULL, "actions,atc2603c-battery");//获取设备树中对应的属性节点
if (!node) {
GAUGE_INFO("%s fail to find atc2603c-battery node\n", __func__);
return 0;
}
GAUGE_INFO("atc2603c_battery:version(%s), time stamp(%s)\n",
ATC2603C_BATTERY_DRV_VERSION, ATC2603C_BATTERY_DRV_TIMESTAMP);
return platform_driver_register(&atc260x_gauge_driver); //将PSY设备驱动注册为platform设备驱动
}
看看atc260x_gauge_driver的probe函数。
static int atc260x_gauge_probe(struct platform_device *pdev)
{
struct atc260x_gauge_info *info; //
int ret;
info = kzalloc(sizeof(struct atc260x_gauge_info), GFP_KERNEL); //分配一个atc260x_gauge_info
if (info == NULL)
return -ENOMEM;
info->atc260x = atc260x;
info->node = pdev->dev.of_node; //一些初始化工作
global_gauge_info_ptr = info;
first_store_gauge_info = 1;
mutex_init(&info->lock);
platform_set_drvdata(pdev, info);
/*init battery power supply*/ //这里就是power supply相关的了
info->battery.name = "battery"; //这name “battery”就是对应我们在/sys/class/power_supply/目录下看到的battery
info->battery.use_for_apm = 1;
info->battery.type = POWER_SUPPLY_TYPE_BATTERY; //设备type
info->battery.properties = atc260x_gauge_props; //设置属性列表
info->battery.num_properties = ARRAY_SIZE(atc260x_gauge_props); //设置属性列表的大小
info->battery.get_property = atc260x_gauge_get_props; //设置获取属性值的回调函数。
ret = power_supply_register(&pdev->dev, &info->battery); //将这个power_supply设备注册进内核中。
。。。//其他电量计相关的省略
return ret;
}
从上面可以看出 编写PSY driver相当的简单。就调用了一个power_supply_register函数。
后面再电量计的轮询函数中,如果检测到相关的属性发生了变化。就调用power_supply_changed函数,上报给power supply core。
static void soc_post_process(struct atc260x_gauge_info *info)
{
int soc_last;
if (info->soc_pre != info->soc_show) {
info->soc_pre = info->soc_show;
power_supply_changed(&info->battery); //如果soc发生了变化,则调用power_supply_changed上报变化信息。
}
info->bat_temp = get_battery_temperature();
if (info->pre_temp!= info->bat_temp) {
info->pre_temp = info->bat_temp;
power_supply_changed(&info->battery); //如果温度发生了变化也进行上报
}
//省略其他不相关的。。。
}
对于USB 和WALL 的PSY设备,其编程方法也是类似的,这里不再赘述。
4.power supply framework的分析
先从power_supply_register分析起
int power_supply_register(struct device *parent, struct power_supply *psy)
{
struct device *dev;
int rc;
dev = kzalloc(sizeof(*dev), GFP_KERNEL); //分配一个device 结构
if (!dev)
return -ENOMEM;
device_initialize(dev);
dev->class = power_supply_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->dev = dev;
INIT_WORK(&psy->changed_work, power_supply_changed_work); //初始化一个power_supply_changed_work 工做队列
rc = power_supply_check_supplies(psy);
if (rc) {
dev_info(dev, "Not all required supplies found, defer probe\n");
goto check_supplies_failed;
}
rc = kobject_set_name(&dev->kobj, "%s", psy->name); //设置kobject的name,其实就是“battery”
if (rc)
goto kobject_set_name_failed;
rc = device_add(dev); //将这个设备添加到设备链表中去
if (rc)
goto device_add_failed;
spin_lock_init(&psy->changed_lock);
rc = device_init_wakeup(dev, true);
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;
power_supply_changed(psy); //调用一次changed,上报一次属性值
goto success;
}
接着看看power_supply_changed
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); //将psy->changed_work任务提交到工做队列,这个工做队列就是在power_supply_register中初始化的
}
EXPORT_SYMBOL_GPL(power_supply_changed);
既然如此,那我们就应该继续去看psy->changed_work的工作队列函数。power_supply_changed_work
static void power_supply_changed_work(struct work_struct *work)
{
unsigned long flags;
struct power_supply *psy = container_of(work, struct power_supply, //获取到PSY
changed_work);
dev_dbg(psy->dev, "%s\n", __func__);
spin_lock_irqsave(&psy->changed_lock, flags);
if (psy->changed) { //已经在power_supply_changed中被设置成true了。
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); //更改led指示状态
kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); //发送uevent事件,通知应用层
spin_lock_irqsave(&psy->changed_lock, flags);
}
if (!psy->changed)
pm_relax(psy->dev);
spin_unlock_irqrestore(&psy->changed_lock, flags);
}
至此,每当驱动中计算出battery的电量信息发生了变化,或者充电器的插拔状态发生了变化,驱动都会调用power_supply_changed函数,随后在power_supply_changed中会启动工作队列,上报uevent事件,通知Android层去sysfs中读取电池相关的属性信息,展现给用户空间。