锂电池的驱动程序要实现以下五个功能:
1.可以自动检测到当前给电池充电的是USB还是AC
2.组织过大的充电电流
3.坏电池检测
4.死亡温度的检测
5.电池电压的测量
当我们要写一个锂电池的驱动程序的时候,首先要知道内核提供给驱动的接口,就是当驱动挂载到内核上的时候,内核是怎么知道驱动中的信息的,如何来控制驱动。而这个内核提供给驱动的接口就是一个结构体power_supply.
struct power_supply {
const char *name;
enum power_supply_type type;
enum power_supply_property *properties;//声明了电源的属性
size_t num_properties;
char **supplied_to;
size_t num_supplicants;
int (*get_property)(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val);//得到电源的属性
void (*external_power_changed)(struct power_supply *psy);
void (*set_charged)(struct power_supply *psy);
/* For APM emulation, think legacy userspace. */
int use_for_apm;
/* private */
struct device *dev;
struct work_struct changed_work;
#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;
#endif
};
内核主要通过get_property这个函数指针来获得驱动中的有关电池的信息,而这个函数在内核中只给出了其声明,我们在写驱动的时候要自己实现这个函数,即讲自己写的函数赋值给函数指针,当内核需要驱动中的电源的信息的时候,回调这个get_property即可。另外,我们写驱动程序又要给用户提供接口,内核中的提供给用户的接口即sysfs,通过读取其中的属性就可以得到电源的信息。内核主要通过两个文件power_supply_class.c和power_supply_core.c,我们调用其中的函数就可以把电源(电池,USB power supply或者AC power supply)的信息展现给用户,有关电源的属性写在/sys/class/powersupply文件夹下。
这样,按照内核提供的接口,驱动程序的书写就很清晰了,结合锂电池的驱动程序的源代码,我们来看看驱动程序的执行过程。
当一个驱动被编译好并被挂到内核上之后,会首先执行一个模块的初始化函数,每个驱动都是统一的,在这里是module_init(stmp3xxx_bat_init);它代表首先执行stmp3xxx_bat_init,在驱动里它是这么定义的:
static int __init stmp3xxx_bat_init(void)
{
return platform_driver_register(&stmp3xxx_batdrv);
}
这个函数执行platform_driver_register(&stmp3xxx_batdrv);并将返回值返回。而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 platform_driver *drv = to_platform_driver(_dev->driver);
struct platform_device *dev = to_platform_device(_dev);
return drv->probe(dev);
}*/
//上面的函数的作用就是将device的driver转变成platform_driver。
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的driver的函数指针probe等初始化为platform_driver的probe(如何完成的请看上面代码中给出的注释).然后执行driver_register(&drv->driver)(我们一会再分析driver_register(&drv->driver))。
platform_driver_register()在我们的驱动中,它的参数是一个结构体指针&stmp3xxx_batdrv,在我们的驱动里它是如下这么定义的:
static struct platform_driver stmp3xxx_batdrv = {
.probe = stmp3xxx_bat_probe,
.remove = stmp3xxx_bat_remove,
.shutdown = stmp3xxx_bat_shutdown,
.suspend = stmp3xxx_bat_suspend,
.resume = stmp3xxx_bat_resume,
.driver = {
.name = ”stmp3xxx-battery”,
.owner = THIS_MODULE,
},
};
下面讲一下driver_register(&drv->driver),在这里我就不贴出其中的代码了,它的过程比较复杂,可以用Source Insight跟踪其中的调用过程,在这里我就大致的介绍一下它的主要过程,一些不重要的东西略掉,首先它会遍历在BUS上的所有设备,通过比较设备的名字和驱动的名字来进行匹配,如果名字相同才能注册成功,当注册成功后接下来会调用platform_driver结构中probe函数指针,在这里就是stmp3xxx_bat_probe,其函数原型static int stmp3xxx_bat_probe(struct platform_device *pdev),而此时stmp3xxx_bat_probe的参数就是我们在总线上找到的和驱动相匹配的设备,它是在驱动注册的时候,找到和驱动匹配的设备后给pdev初始化的。
下面我们说一说stmp3xxx_bat_probe所完成的主要功能:获取电源设备的中断资源,代码实现如下:
struct resource *vdd5v_irq;
info->vdd5v_irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0);
下面说一下resource,该元素存入了最为重要的设备资源信息,例如设备的地址,中断号等,其定义如下:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};
下面举s3c2410平台的i2c驱动作为例子来说明:
这里定义了两组resource,它描述了一个I2C设备的资源,第1组描述了这个I2C设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信息,第2组描述了这个I2C设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据flags来获取相应的资源信息。 |
保存指向驱动特有信息的指针:platform_set_drvdata(pdev, info);
对电源进行初始化,代码如下:
info->bat.name = ”battery”;//名字
info->bat.type = POWER_SUPPLY_TYPE_BATTERY;//类型
info->bat.properties = stmp3xxx_bat_props;//属性
info->bat.num_properties = ARRAY_SIZE(stmp3xxx_bat_props);//属性的个数
info->bat.get_property = stmp3xxx_bat_get_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);
接下来将三种电源注册,即把他们的属性写到sys文件系统里,以使用户空间可以得到有关电源的信息,以其中的一个为例:
ret = power_supply_register(&pdev->dev, &info->bat);//将电池注册
power_supply_register调用内核提供的函数device_create()和power_supply_create_attrs来实现电池的注册。
这里只写出了驱动要完成的基本功能,至于如何完成的,即驱动与硬件之间的交互,对寄存器的操作,需要参考具体的硬件手册。
这个文档还有很多需要完善的地方,希望大家多提意见,我再继续修改。