锂电池驱动分析
鋰電池的驅動程序要實現以下五個功能:
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來實現電池的注冊。
這里只寫出了驅動要完成的基本功能,至于如何完成的,即驅動與硬件之間的交互,對寄存器的操作,需要參考具體的硬件手冊。
?
這個文檔還有很多需要完善的地方,希望大家多提意見,我再繼續修改。
轉載于:https://www.cnblogs.com/armlinux/archive/2010/10/09/2396907.html
總結
- 上一篇: if test project can'
- 下一篇: 拖动改变Table的列宽度