公司的电池又出问题了。对于电池,只是解决问题的话,不会困难。但将整个电池的知识点串连起来,却不是件轻松的事。
电池部分可以分成二块来看待:
1,ECBIOS读取电池,并处理
2,SYSTEMBIOS写ASL代码,提供给驱动
附:驱动和EC沟通,报给内核
嵌入式BIOS部分
嵌入式BIOS工程师做电池,手边的事情一般有:
1,侦测电池插拔动作
一般有两种方式来作这个功能:
(1)通过一根GPIO PIN的电平高低来检测,需要提前去抖;
(2)通过ADC控制器来侦测电池温度,其实在软件的眼中,不管温度还是阻值或者其它什么东西,都是电压。我们可以通过电压是否在一定的范围内来确定电池是否存在。
2,获取电池信息
EC获取电池信息,要通过读SMART BATTERY IC的寄存器来获取。比较重要的寄存器有:
BatteryCurrent | 电池电流 |
BatteryVoltage | 电池电压 |
DesignCapacity | 电池设计容量 |
RemainingCapacity | 电池现有容量 |
FullChargeCapacity | 电池满充容量 |
RSOC | 电池现有容量百分比 |
CycleCount | 电池跑的CYCLE数 |
BatteryStatus | 电池状态 |
CycleCount的值代表了电池使用的程度;RemainingCapacity和FullChargeCapacity的比值就是WINDOWS显示的百分比,由于算法的原因,可能和RSOC不一致;而电池剩余时间可以通过BatteryCurrent和RemainingCapacity的比值得到。
读取这些寄存器需要遵循SMBUS协议。
3,电池充放电
(1)当AC和电池均存在,而batteryStatus的FULLY_CHARGED位没置1时,要充电。充电IC一般接受DAC或SMBUS的通信方式。
(2)其余情况下,不应充电。
(3)电池充放电过程中,要点亮一些指示灯给终端用户使用。
4,通知ACPI驱动更新电池信息
电池信息的传递需要通过62/66端口来实现。EC代码的一般做法是用256 BYTE的内存来作为ECRAM或ECSpace,HOST端驱动或应用会通过约定的时序来读取。
当电池拔出时,我们应该将ECRAM有关电池部分清0,然后通过SCI中断发送Q事件号来通知电池驱动来读取电池信息。
当电池插入时,我们不应立即通知电池驱动来获取电池信息,因为此时还没有读电池信息,我们最好要第一次读完电池信息后,再通知驱动来更新电池信息。如果不这样做,电池图标就会指示错误。SYSTEM BIOS为了避免这种情况出现,也可以利用线程SLEEP的方式来绕过这段时间。
在电池充放电过程中,我们可以实时读取RSOC的值,一旦RSOC值有变化,我们就可以通知电池驱动更新电池信息。
5,电池Learning功能的辅助性设计
一般来讲,主板都会设计留出电源开关来给EC使用,EC利用这些开关来决定当前系统使用交流电或是电池,EC不会主动调用这些开关,而是把它们封装做成接口提供给HOST端使用。
这种灵活的设计主要为电池Learning功能使用。我们可以通过操作系统层面上应用调用EC的接口,或是直接在BIOS层面调用EC的接口来做电池 Learning功能。
6,电池保护功能
电池在充放电过程中,有时会产生过压过流或过温现象,这种现象会对电池造成危害。EC工程师可以在电源工程师的设定规则下,来做一些保护功能,当达到临界点时,要停些充电或关机。
SYSTEM BIOS部分
SystemBIOS工程师做电池,手边的事情一般有:
1, 在DeviceEC ASL中加入电池相产信息的字段。然后在QEvent加三个事件来通知电池驱动更新电池信息。这三个事件通知的时机分别是:
(1) 电池插入
(2) 电池拔出
(3) 电池百分比改变
(4) AC插入和拔出
电池插入和拔出的事件:
Method(_QXX)
{
Sleep(5) //此处防止EC BIOS代码考虑不全面
Notify(BATT,0x80)
Sleep(5) //此处防止读取数据时,时序错乱
Notify(BATT,0x81)
}
电池百分比改变事件:
Method(_QYY)
{
Notify(BATT,0x80)
}
AC插拔事件:
Method(_QZZ)
{
Notify(ACAD,0x80) //通知AC状态改变
Sleep(5)
Notify(BATT,0x80)
Sleep(5)
Notify(BATT,0x81)
Notify(EveryCPU) //_CST,让操作系统重新决定CPU省电策略
}
2, 添加DeviceBattery ASL代码。
里面主要注意两个Method:_BIF和_BST
我们可以看到,_BST的package里有Battery Remaining Capacity字段,根据_QYY事件中的代码,我们可以推断出电池的Notify Value 0x80就是更新_BST的package。
这个需要细心和耐心,对照ACPI SPEC和EC SPACE空间,一点点地给电池驱动报数据。
值得一提的是,和SLEEP一样,为了防止数据读取错误,我们也可以在此添加Mutex。Sleep是通过线程休眠来达到延时的目的,而Mutex则是通过互斥量来阻止两段代码争资源。我也分不清谁优谁劣,不过为了防止错误发生,做项目中尽量两者兼用。
附:驱动部分
为了搞明白我们的接口怎么被操作系统获取的,我特地读了下Linux3.18.2驱动。由于不专业,所以还请各位同仁斧正。
有关battery部分的驱动,有两个比较重要的文件:
Drivers\acpi\sbs.c
Drivers\acpi\battery.c
首先在sbs.c中:
我们可以看到驱动入品函数为:acpi_sbs_init(void)调用acpi_bus_register_driver(&acpi_sbs_driver),Acpi_sbs_driver.ops.add = acpi_sbs_add,Acpi_sbs_add会调用acpi_battery_add(sbs, id),acpi_battery_add调用power_supply_register(&sbs->device->dev,&battery->bat),
battery->bat.get_property= acpi_sbs_battery_get_property;
我们知道,Linux系统中,power_supply_regiter会注册Power设备,而根文件系统显示电池相关信息是由acpi_sbs_battery_get_property()提供的,所以我们就需要在硬件相关部分,将我们读取到的信息提供给acpi_sbs_battery_get_property()所需的变量。而读取硬件信息相关代码就在battery.c中。
在battery.c文件中:
入口函数为acpi_battery_init(),调用async_schedule(acpi_battery_init_async, NULL),acpi_battery_init_async()调用acpi_bus_register_driver(&acpi_battery_driver),acpi_battery.driver.ops.add= acpi_battery_add,acpi_battery_add()中赋值battery->pm_nb.notifier_call =battery_notify,battery_notify()调用acpi_battery_get_info()和
acpi_battery_get_state(),而这两个函数会通过acpi_evaluate_object(battery->device->handle,"_BST",NULL, &buffer)和
acpi_evaluate_object(battery->device->handle,name,NULL, &buffer)将读到的电池信息放到acpi_battery结构体变量中,而该结构体变量会被sbs.c的acpi_sbs_battery_get_property()使用。至于怎么读到电池信息,这部分内容会涉及到ec.c中的内容,就不再展开了。
对于Linux驱动实在太不专业,去描述这些的时候让我想到《圣经》的马太福音第一章前几节,我就用这几节内容来结束battery内容吧。
1:1 亚伯拉罕的后裔,大卫的子孙,耶稣基督的家谱,
1:2 亚伯拉罕生以撒,以撒生雅各,雅各生犹大和他的弟兄,1:3 犹大从她玛氏生法勒斯和谢拉,法勒斯生希斯仑,希斯仑生亚兰,
1:4 亚兰生亚米拿达,亚米拿达生拿顺,拿顺生撒门,
1:5 撒门从喇合氏生波阿斯,波阿斯从路得氏生俄备得,俄备得生耶西,
1:6 耶西生大卫王。大卫从乌利亚的妻子生所罗门,
1:7 所罗门生罗波安,罗波安生亚比雅,亚比雅生亚撒,
1:8 亚撒生约沙法,约沙法生约兰,约兰生乌西亚,
1:9 乌西亚生约坦,约坦生亚哈斯,亚哈斯生希西家,
1:10 希西家生玛拿西,玛拿西生亚们,亚们生约西亚。
1:11 百姓被迁到巴比伦的时候,约西亚生耶哥尼雅和他的弟兄。
1:12 迁到巴比伦之后,耶哥尼雅生撒拉铁,撒拉铁生所罗巴伯,
1:13 所罗巴伯生亚比玉,亚比玉生以利亚敬,以利亚敬生亚所,
1:14 亚所生撒督,撒督生亚金,亚金生以律,
1:15 以律生以利亚撒,以利亚撒生马但,马但生雅各,
1:16 雅各生约瑟,就是马利亚的丈夫。那称为基督的耶稣,是从马利亚生的。