本文共 13118 字,大约阅读时间需要 43 分钟。
驱动书写指南系列会提供另一个角度的驱动分析,linux内核把各驱动共同的部分抽象出来,做在一起称为框架。就比如说文件系统,linux内核定义好了文件系统中最通用的打开文件、读写文件等公共接口,但是并没有实现函数。这些定义好的接口,可以认为是框架。等到了真正的文件系统实现的时候 ,才会填充这些open、read等函数。对于实现文件系统的程序员来说,就是填充框架外的其他内容,一般都是和硬件相关性比较大。
在本文中,主要介绍怎么注册自己的ec驱动。ec驱动的框架部分,power supply都是实现过了,这里有一个介绍power supply core中,蜗窝科技有一个文章介绍power supply core的,这是连接:http://www.wowotech.net/pm_subsystem/psy_class_overview.html
在上文的 介绍里补充一点内容,power supply 硬件属性分别是什么意思,在写ec驱动的途中,一大部分时间花在研究这几个属性分别是描述什么的以及我需要什么属性,大部分还是蜗窝科技写的对几个属性的解释,能找到的中文资料非常非常少,还是挺值得记录一下的。
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 */};
一般来讲笔记本都是这两个充电类型:mains类型和battery类型,移动设备需要定义usb的充电类型。先定义两个最常见的psy设备:适配器和电池,适配器的充电类型是mains,电池是battery。
static const struct power_supply_desc shiwen_ac_desc = { .name = "shiwen_ac", .type = POWER_SUPPLY_TYPE_MAINS, .properties = shiwen_power_ac_props, .num_properties = ARRAY_SIZE(shiwen_power_ac_props), .get_property = shiwen_power_get_ac_property,};static const struct power_supply_desc shiwen_bat_desc = { .name = "shiwen_battery", .type = POWER_SUPPLY_TYPE_BATTERY, .properties = shiwen_power_battery_props, .num_properties = ARRAY_SIZE(shiwen_power_battery_props), .get_property = shiwen_power_get_battery_property,};
定义好之后,再选择每个psy设备的硬件属性,这部分要看ec芯片提供了什么数据然后选择定义什么属性。上一节提到的属性是框架定义的全部属性,ec芯片不可能提供这全部的数据。这里写的示例,就简单选择几个上层需要的属性吧。适配器就一个是否在线属性,电池桌面需要的属性有电池存在标志、充满时间、放空时间、电池状态、电池容量百分比、电池容量等级、模块名、制造商。
static enum power_supply_property shiwen_power_ac_props[] = { POWER_SUPPLY_PROP_ONLINE,};static enum power_supply_property shiwen_power_battery_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CAPACITY, /* in percents! */ POWER_SUPPLY_PROP_CAPACITY_LEVEL, POWER_SUPPLY_PROP_TIME_TO_EMPTY, POWER_SUPPLY_PROP_TIME_TO_FULL, POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER,};
接着要告诉内核如何获取前面定义的psy属性,psy core留了一个get_property接口,需要驱动工程师把方法填充在接口里。这部分就完全和硬件相关了,不同的ec芯片,读取psy属性方法不同,比如说本文的示例代码里仅仅实现了一个固定值。
struct int shiwen_power_get_battery_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val){ case POWER_SUPPLY_PROP_ONLINE: val->intval = 1; //always assume ac online break; default: val->intval = -1; pr_err("property error\n");}struct int shiwen_power_get_battery_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val){ switch (psp) { case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = "Shiwen example driver"; break; case POWER_SUPPLY_PROP_MANUFACTURER: val->strval = "Shiwen example"; break; case POWER_SUPPLY_PROP_STATUS: val->intval = Charging; //always assume battery charging break; case POWER_SUPPLY_PROP_PRESENT: val->intval = 1; //always assume battery present break; case POWER_SUPPLY_PROP_CAPACITY: val->intval = 100; //always assume battery capacity 100% break; case POWER_SUPPLY_PROP_CAPACITY_LEVEL: val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY: val->intval = 7200; //always assume battery need 2 hours to empty break; case POWER_SUPPLY_PROP_TIME_TO_FULL: val->intval = 0; break; default: val->intval =-1; pr_err("property error\n"); break;}
到这一步,两个psy设备定义完成了。接下来只需要把注册设备,在sys下就能看到接口了。
设备注册,psy core提供了接口,只需要调用接口并做好错误处理即可。
static int __init shiwen_power_init(void){ shiwen_ac = power_supply_register(NULL, &shiwen_ac_desc, NULL); if (IS_ERR(shiwen_ac)) { pr_err("%s: failed to register\n",__func__, shiwen_ac_desc.name); ret = PTR_ERR(shiwen_ac); goto failed_ac; } shiwen_bat = power_supply_register(NULL, &shiwen_bat_desc, NULL); if (IS_ERR(shiwen_ac)) { pr_err("%s: failed to register %s\n", __func__, shiwen_bat_desc.name); ret = PTR_ERR(shiwen_bat); goto failed_bat; } return 0;failed_bat: power_supply_unregister(shiwen_bat);failed_ac: power_supply_unregister(shiwen_ac); return ret;}
到这里,非常简单的ec示例驱动注册就完成了,在sys下查看一下是否正确注册了设备,并且获取到正确的内容。
deepin@deepin-PC:~$ ls /sys/class/power_supply/shiwen_ac shiwen_batterydeepin@deepin-PC:~$ deepin@deepin-PC:~$ ls /sys/class/power_supply/shiwen_ac/online power subsystem type ueventdeepin@deepin-PC:~$ cat /sys/class/power_supply/shiwen_ac/uevent POWER_SUPPLY_NAME=shiwen_acPOWER_SUPPLY_ONLINE=1deepin@deepin-PC:~$ ls /sys/class/power_supply/shiwen_batterycapacity capacity_level manufacturer mcu_time_effect model_name power present status subsystem time_to_empty_avg time_to_full_avg type ueventdeepin@deepin-PC:~$ cat /sys/class/power_supply/shiwen_battery/uevent POWER_SUPPLY_NAME=shiwen_batteryPOWER_SUPPLY_STATUS=ChargingPOWER_SUPPLY_PRESENT=1POWER_SUPPLY_CAPACITY=100POWER_SUPPLY_CAPACITY_LEVEL=POWER_SUPPLY_CAPACITY_LEVEL_FULLPOWER_SUPPLY_TIME_TO_EMPTY_AVG=7200POWER_SUPPLY_TIME_TO_FULL_AVG=0POWER_SUPPLY_MODEL_NAME=Shiwen example driverPOWER_SUPPLY_MANUFACTURER=Shiwen example
到这,就大功告成啦。sys下文件创建、sys下文件的读写都有sysfs框架和psy core框架帮我们实现。上文是一个非常非常简单的psy驱动实现流程,ec状态的改变驱动并没有关注,都是依赖上层daemon或者读取 sys下的文件来感知,对于用户来说相当于是轮询读取。实际上日常我们遇到的ec芯片都没有这么原始,ec事件基本上都是中断通知,所以一个完成的ec驱动还要在上面的流程里面加上中断的注册和处理代码。
关注过我之前写过两个中断介绍文章的童鞋肯定知道驱动注册中断的第一步,肯定是实现中断处理函数。处理函数也和硬件有关系,有的ec芯片ac插拔事件和电池事件的中断是分开的,假设我们虚构的ec芯片只有ac和电池的插拔才能触发中断(看内核psy实现的芯片,这样的中断也是最常见的)。假设ac事件和电池事件共享155号中断(随意选的,没有任何理论依据),那么中断处理函数处理第一步肯定是确定中断源。接着根据中断源,更新一下设备状态即可。
static irqreturn_t shiwen_ec_interrupt(int irq, void *dev_id){ int status; int source; source = SHIWEN_READ_INTSOURCE_REG(); if (source == SHIWEN_BATTERY) power_supply_changed(shiwen_battery); else if(source == SHIWEN_AC) power_supply_changed(shiwen_ac); else //there are no psy event return IRQ_NONE; return IRQ_HANDLED;}
更新整个psy设备状态的函数是psy core框架提供的,很好用吧。psy会根据参数传进去的设备,调用获取属性函数,更新psy设备属性。这个处理函数非常简单,因为ec芯片的中断就很简单。如果是比较复杂的ec芯片的话,可能中断源分的比较多,比如说电压变化、容量变化、电流变化等等都会有一种中断的产生。注意:为了避免读到的状态有问题,需要在中断产生后等一段时间再去读ec状态,所以中断处理函数一般都需要有延时读取ec状态。power_supply_changed
函数内部实现是采用work queue方式读取状态的,我们自己实现的话,为了简单且保险,可以用delay work。假设说,需要实现一个电压变化中断的处理函数:
static irqreturn_t shiwen_voltage_interrupt(int irq, void *dev_id){ int status; schedule_delayed_work(&ec_work, JIFFIES_NUM); return IRQ_HANDLED;}static void ec_work_func(struct work_struct *work){ shiwen_update_voltage();}
在 中断处理函数里面,仅有调用delay work内容,延时过了之后函数会被加载工作队列里,借此达到延时的目的。接下来指定一下延时工作进程需要执行的函数,这是工作进程要求的。这就是一个基本的读取某一个属性的中断函数实现,没实现真正电压读取是因为,电压读取是真正和硬件相关的,每款芯片都不一样,我也实在是虚构不出来了orz…
处理函数写完了之后,中断注册到内核里就可以使用啦。剩下的中断触发是设备的事,中断触发之后的感知是cpu的事,感知到中断之后在调到处理函数之前是内核中断子系统的事,前面中断的两篇文章介绍过内核已经做好了,驱动并不关心。中断的注册一般放在psy设备注册之后,修改过得psy设备注册代码如下:
static int __init shiwen_power_init(void){ shiwen_ac = power_supply_register(NULL, &shiwen_ac_desc, NULL); ...... shiwen_bat = power_supply_register(NULL, &shiwen_bat_desc, NULL); ...... //shiwen_ac and shiwen_battery called shiwen_battery_ac ret =request_irq(155, shiwen_ec_interrupt, IRQF_SHARED, shiwen_battery_ac); INIT_DELAYED_WORK(&ec_work, ec_work_func); return 0; ......}
增加了中断注册个delay work初始化的代码(如果需要自己使用delay work读取某一个属性的时候),代码写到这里,一个带基本功能的ec驱动就做完了。看吧,还是很简单的吧。
对于虚拟的电池或者其他规范的电池芯片,硬件认为驱动只要读到正确的值就可以了,电池芯片把状态送到某个位置,任何电池事件并不会触发中断,包括适配器插拔和电池插拔。这时候就需要驱动工程师想个办法把电池驱动套到psy框架下了,最简单的办法肯定是——定时器。既然不支持中断,那我定时读取总可以了吧,虽然不能保证状态及时更新。
首先定义一个定时器和定时器超时处理函数:struct timer_list power_timer;#define TIMER_COUNT 60 /*设置超时时间为1分钟*/void power_timer_handler(struct timer_list *unused){ mod_timer(&power_timer, jiffies+HZ * TIMER_COUNT); power_supply_changed(shiwen_ac); power_supply_changed(shiwen_bat);}
设置超时时间是60s,定时器超时处理函数就做了俩事,再次设置定时器超时时间和更新shiwen_ac和shiwen_bat状态。这里强调一点,尽量调用psy core提供的更新状态函数,而不要自己实现。因为这个函数的功能并不仅仅是调用get_properties
接口,还有触发 内核uevent等,不要问为啥强调。orz…
timer_setup(&power_timer, power_timer_handler, 0);mod_timer(&power_timer, jiffies+HZ * TIMER_COUNT);
到这,就算设备没有电池,我也能写一个电池驱动出来!就这么霸道,快去试试看。
先说结论:当然可以做,但不推荐做。对于ec芯片来说,他需要做的就是传递电池信息,这个信息包括:电池是否在线、电压值、电流值、容量温度事件等等,这些值难道不能模拟吗?当然不是,只要经过多次实验,得到一个放电曲线和充电曲线,在充电放电途中电压、电流、容量等值都可以通过计算得来。至于电池是否在线,可以写一个历程轮询查看,虽然代价有点大,也算是可以做。
为什么不推荐做呢?原因有下面几条:欢迎大家踊跃提问,一起交流!
转载地址:http://esbsi.baihongyu.com/