学习要点:1、电池驱动的架构;
2、电池电压的获取,百分比的转换(包括不同用电情况下的分析);
3、充电管理;
当我们要写一个驱动的时候,首先要知道内核提供给驱动的接口,就是当驱动挂载到内核上的时候,内核怎么知道驱动中的信息的,如何来控制驱动。而内核提供给电池驱动的接口就是结构体power_supply。
Battery驱动程序需要通过sys文件系统向用户空间提供接口,sys文件系统的路径是由上层的程序指定的。
Linux标准的Power Supply驱动程序所使用的文件系统路径问/sys/class/power_supply,其中的每个子目录表示一种能源供应设备的名称。
Power Supply驱动程序的头文件在include/linux/power_supply.h中定义,注册和注销驱动程序的函数如下所示:
int power_supply_register(struct device *parent,struct power_supply *psy);
void power_supply_unregister(struct power_supply *psy);
其中power_supply结构体为驱动程序需要实现的部分,
struct power_supply{
const chat *name;/*设备名字*/
enum power_supply_type type;/*类型*/
enum power_supply_property *properties;/*属性指针*/
size_t num_properties;/*属性数目*/
int (*get_property)(struct power_supply *psy,/*获得属性*/
enum power_supply_property psp,
union power_supply_propval *val);
.........../*省略部分,其他属性可以查看include/linux/power_supply.h中struct power_supply*/
}
内核主要通过get_property这个函数指针来获得驱动中的有关电池的信息,而这个函数在内核中只给出了声明,我们在写驱动的时候要自己实现这个函数,即将自己写的函数赋值给这个函数指针,当内核需要驱动中电源信息的时候就回调这个get_property函数。另外,我们写驱动程序的时候又要给用户提供接口,内核中提供给用户的接口就是sysfs,通过读取sysfs文件系统中文件内容,就可以得到电源的信息。内核主要通过两个文件power_supply_class.c和power_supply_core.c,我们调用其中的函数就可以把电源(电池,USB或AC)的信息展现给用户,有关电源的属性写在/sys/class/powersupply文件夹下(此文件夹为程序运行后所生成的)。
这样,按照内核提供的接口,驱动程序的书写就很清晰了,结合电池的驱动程序代码,我们来看看驱动程序的执行过程。
当一个驱动被编译好并挂到内核上之后,会首先执行一个模块的初始化函数,每个驱动都是统一的,在这里是module_init(cvt_mid_power_init);它代表首先执行cvt_mid_power_init这个函数,在驱动里它是这么定义的:
static int __init cvt_mid_power_init(void)
{
int i;
int ret;
for (i = 0; i < ARRAY_SIZE(cvt_mid_power_supplies); i++) {
ret = power_supply_register(NULL, &cvt_mid_power_supplies[i]);
if (ret) {
pr_err("%s: failed to register %s\n", __func__,
cvt_mid_power_supplies[i].name);
goto failed;
}
}
platform_device_register(&cvt_mid_power_dev);
return platform_driver_register(&cvt_mid_power_driver);
failed:
while (--i >= 0)
power_supply_unregister(&cvt_mid_power_supplies[i]);
return ret;
}
首先是通过power_supply_register将cvt_mid_power_supplis[]数组中所提供的电源进行注册,即把他们的属性写到sys文件系统里,以使用户空间可以得到有关电源的信息,
power_supply_register调用内核提供的函数device_create()和power_supply_create_attrs来实现电池的注册.
这个函数执行platform_driver_register(&cvt_mid_driver);并将返回值返回。而platform_driver_register()是一个内核函数,它在内核中如下定义:
int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if(drv->probe)
drv->driver.probe = platform_drv_probe;//platform_drv_probe仍然是一个内核函数,他在内核里定义如下:
/*
static int platform_drv_probe(struct device *_dev)
{
struct paltform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}
*/
//上面的函数作用就是将device的driver转变成platform_drive。
if(drv->remove)
drv->driver.remove = platform_drv_remove;
if(drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
这个函数的功能就是:首先将platform_driver的结构体变量driver的bus域初始化,然后将platform_driver的函数指针probe等初始化为platform_driver的probe。然后执行driver_register(&drv->driver).
platform_driver_register()在我们驱动中,他的参数是一个结构体指针&cvt_mid_power_driver,在我们的驱动里它是如下定义的:
static struct platform_driver cvt_mid_power_driver = {
.probe = cvt_mid_power_probe,
.remove = __devexit_p(cvt_mid_power_remove),
#ifdef CONFIG_PM
.suspend = NULL,
.resume = NULL,
#endif
.driver = {
.name = "cvt_mid_power",
.pm = &cvt_mid_battery_pm_ops,
},
};
下面讲下driver_register(&drv->driver),在这里就不贴出其中的代码了,比较复杂,可以用Source Insighe跟踪其中的调用过程,在这里我就大致的介绍下它的主要过程,一些不重要的东西就省略掉,首先它会遍历在BUS上的所有设备,通过比较设备的名字和驱动的名字来进行匹配,如果名字相同才能注册成功,当注册成功后接下来就会调用platform_driver 结构中的probe函数指针,在这里就是cvt_mid_power_probe,其函数原型static int cvr_mid_power_probe(struct platform_device *pdev),而此时cvt_mid_power_probe的参数就是我们在总线上找到的和驱动相匹配的设备,它是在驱动注册的时候,找到和驱动匹配的设备后给pdev初始化的。
下面我们说下cvt_mid_power_probe所完成的主要功能:获取电源设备的中断资源,代码实现如下:
static __devinit int cvt_mid_power_probe(struct platform_device *pdev)
{
int irq;
int irq_flag;
int i;
int ret;
g_adc_client = adc_register(0, adc_battery_callback, NULL);
dbg_printk("%s,%d,g_adc_client=%d\n", __func__, __LINE__, (int)g_adc_client);
if(g_adc_client == 0)
{
printk("cvt_mid_power_probe error: can not registe adc!\n");
return -1;
}
gpio_request(CHG_OK_PIN, NULL);
gpio_pull_updown(CHG_OK_PIN, GPIOPullUp);
gpio_direction_input(CHG_OK_PIN);
gpio_request(DC_DET_PIN, NULL);
gpio_pull_updown(DC_DET_PIN, GPIOPullUp);
gpio_direction_input(DC_DET_PIN);
g_workqueue = create_singlethread_workqueue("cvt_mid_power_adcwq");
INIT_WORK(&g_work, adc_work_func);
alarm_init(&g_alarm, ANDROID_ALARM_ELAPSED_REALTIME_WAKEUP, cvt_mid_battery_alarm);
wake_lock_init(&g_work_wake_lock, WAKE_LOCK_SUSPEND, "cvt-mid-charger");
initFilter(batteryCapcityFilterBuffer, FILTER_DEPTH);
for(i = 0; i < TABLE_SIZE; i ++)
{
//For 3.7V battery, but use 7.4V battery table...
battery_step_table[i] /= 2;
battery_charging_step_table[i] /= 2;
}
for(i = 0; i < FILTER_DEPTH; i ++)
{
va7882_cal_battery_capacity();
}
wake_lock(&g_work_wake_lock);
queue_work(g_workqueue, &g_work);
cvt_mid_program_alarm(1);
irq = gpio_to_irq(DC_DET_PIN);
irq_flag = gpio_get_value (DC_DET_PIN) ? IRQF_TRIGGER_FALLING : IRQF_TRIGGER_RISING;
// irq_flag = IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING;
ret = request_irq(irq, battery_dc_wakeup, irq_flag, "ac_charge_irq", NULL);
if (ret) {
printk("failed to request dc det irq\n");
}
enable_irq_wake(irq);
return 0;
}
下面说下resource,该元素存入了最为重要的设备资源信息,例如设备的地址,中断号等,其定义如下:struct resource{
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent,*sibling,*child;
};
保存有电池驱动特有信息的指针:cvt_mid_power_supplies;
对电源进行初始化:
static struct power_supply cvt_mid_power_supplies[] = {
{
.name = "ac",//设备名称
.type = POWER_SUPPLY_TYPE_MAINS,//类型
.supplied_to = cvt_mid_power_ac_supplied_to,//供电电池
.num_supplicants = ARRAY_SIZE(cvt_mid_power_ac_supplied_to),//供电电池个数
.properties = cvt_mid_power_ac_props,//属性
.num_properties = ARRAY_SIZE(cvt_mid_power_ac_props),//属性数目
.get_property = cvt_mid_power_get_ac_property,//得到属性的函数
}, {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = cvt_mid_power_battery_props,
.num_properties = ARRAY_SIZE(cvt_mid_power_battery_props),
.get_property = cvt_mid_power_get_battery_property,
},
};
这里主要是实现给电源名字类型等赋初值,最主要是将get_property函数指向我们写好的可以得到电源的属性的函数的起始地址,以便当内核需要用到驱动的信息的时候进行回调。
接下来初始化timer,mutex,代码如下:
init_timer(&info->sm_timer);
info->sm_timer.data = (unsigned long)info;
info->sm_timer.function = state_machine_timer;
mutex_init(&info->sm_lock);