锂电池驱动分析

来源:互联网 发布:python最好的书籍推荐 编辑:程序博客网 时间:2024/04/28 17:02

锂电池的驱动程序要实现以下五个功能:

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驱动作为例子来说明: 

 

/* arch/arm/mach-s3c2410/devs.c */
/* 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组描述的是中断资源信息。设备驱动会根据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来实现电池的注册。

这里只写出了驱动要完成的基本功能,至于如何完成的,即驱动与硬件之间的交互,对寄存器的操作,需要参考具体的硬件手册。

 

这个文档还有很多需要完善的地方,希望大家多提意见,我再继续修改。

原创粉丝点击