锂电池的驱动程序要实现以
下五个功能:
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_propertie
s
;
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);
int use_for_apm;
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_pr
oper
ty即
可。另外,我
们写驱
动程序又要给用
户提供接口,内核中的提供给用户的接口即sysfs,通过读取其中的属性就可以得到电源的信息。内核主要通过两个文件power_supply_class.c和power_supply_core.c,我们调用其中的函数就可以把电源(电池,USB power supply
或者AC power supply)的信息展现给用户,有关电源的属性写在/sys/class /powersupp
ly文件夹下。
这样,按照内核提供的接口,驱动程序的书写就
很清晰了,结合锂电池的驱动程序的源代码,我们来看看驱动程序的执行过程。
当一个驱动被编译好并被
挂到内核上
之后
,会首先
执行一个模块的初始化函数,每个驱动都是统一的
,在这里是module_init(stmp3xxx_bat_ini
t);它代表首先执
行stmp3xxx_
bat_ini
t,在驱动里它是这么定义的:
static int __init stmp3xxx_bat_init(void)
{
return platform_driver_register(
&stmp3xxx_batdrv);
}
这个函数执行platform_
driver_r
egi
ster(
&stmp3xxx_batdrv);并将返回值返回。而platform_drive
r_register
()是一个内核函数,它在内核中如下定义:
int platform_driver_regist
er(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_ty
pe;
if (drv->probe)
drv->driver.probe = platform_drv_probe;//platform_drv_probe仍然是一个内核函数,、
//上面的函数的作用就是将device的driver转变
成pl
atform_driver。
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
这个函数所完成的就是:首先将platfo
rm_driver的结
构体变量
driver的bus域
初始化,然后将platform_driver的driver的函数指针probe等初始
化为pla
tform_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上的所有设备,通过比较设备的
名字和驱动的名字来进行匹配,如果名字相同
才能注册成功,当注册成功后接下来会调用platfor
m_driver结构中probe函数
指针,在这里就是stmp3xxx_bat_probe,其函数原型 static int stmp3xxx_bat_probe(
struct platform_d
evice *pdev),而此时 stmp3x
xx_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驱动作为例子来说明:
static struct resource s3c_i2c_resource[] = {
[0] = {
.start = S3C24XX_PA_IIC,
.end = S3C24XX_PA_IIC + S3C24XX_SZ_IIC - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_IIC, //S3C2410_IRQ(27)
.end = IRQ_IIC,
.flags = IORESOURCE_IRQ,
}
};
这里定义了两组resource,它描述了
一个I2C设备的资源,第1组描述了这个I2C设备所占用的总线地址范围,IORESOURCE_MEM表示第1组描述的是内存类型的资源信
息,第2组描述了这个I2C设备的中断号,IORESOURCE_IRQ表示第2组描述的是中断资源信息。设备驱动会根据f
lags来获取相应的资源信息。
保存指向驱动特有信息的指针:platform_set_drvdata(pdev, info);
对电源进行初始化,代码如下:
info->bat.name
= ”battery”;//名字
info->bat.type
= POWER_SUPPLY_TYPE_BATTERY;//类型
info->ba
t.properties
= stmp3xxx_bat_props;//属性
info->bat.num_prope
rties = 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);
接下来将三
种电源注册,即把他们的
属性写到s
ys文件系统里,
以使用户空间可以得到有关
电源的信息,以其中的一个为例:
ret = power_supply_register(&pdev->dev, &info->bat);//将电池注册
power_supply_register调用内核提供
的函数device_create(
)和pow
er_supply_create_attr
s来实现电池的注册。
这里只写出了驱动要完成的基本功
能,至于如何完成的,
即驱动与硬件
之间的交
互,对寄存器的操作,需要参考具体的硬件手册。