LInux 鋰電池驅動分析
鋰電池的驅動程序要實現以下五個功能:
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);
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_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仍然是一個內核函數,、
//上面的函數的作用就是將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驅動作爲例子來說明:
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來實現電池的註冊。
這裏只寫出了驅動要完成的基本功能,至於如何完成的,即驅動與硬件之間的交互,對寄存器的操作,需要參考具體的硬件手冊。
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);
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_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仍然是一個內核函數,、
//上面的函數的作用就是將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驅動作爲例子來說明:
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來實現電池的註冊。
這裏只寫出了驅動要完成的基本功能,至於如何完成的,即驅動與硬件之間的交互,對寄存器的操作,需要參考具體的硬件手冊。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.