驅動書寫指南系列會提供另一個角度的驅動分析,linux內核把各驅動共同的部分抽象出來,做在一起稱為框架。就比如說文件系統,linux內核定義好了文件系統中最通用的打開文件、讀寫文件等公共接口,但是并沒有實現函數。這些定義好的接口,可以認為是框架。等到了真正的文件系統實現的時候 ,才會填充這些open、read等函數。對于實現文件系統的程序員來說,就是填充框架外的其他內容,一般都是和硬件相關性比較大。
power supply core介紹
在本文中,主要介紹怎么注冊自己的ec驅動。ec驅動的框架部分,power supply都是實現過了。
在上文的 介紹里補充一點內容,power supply 硬件屬性分別是什么意思,在寫ec驅動的途中,一大部分時間花在研究這幾個屬性分別是描述什么的以及我需要什么屬性,大部分還是蝸窩科技寫的對幾個屬性的解釋,能找到的中文資料非常非常少,還是挺值得記錄一下的。
enum power_supply_property {
/* Properties of type `int' */
POWER_SUPPLY_PROP_STATUS = 0,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_AUTHENTIC,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_VOLTAGE_MIN,
POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_AVG,
POWER_SUPPLY_PROP_VOLTAGE_OCV,
POWER_SUPPLY_PROP_VOLTAGE_BOOT,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CURRENT_BOOT,
POWER_SUPPLY_PROP_POWER_NOW,
POWER_SUPPLY_PROP_POWER_AVG,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_EMPTY,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_AVG,
POWER_SUPPLY_PROP_CHARGE_COUNTER,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN,
POWER_SUPPLY_PROP_ENERGY_FULL,
POWER_SUPPLY_PROP_ENERGY_EMPTY,
POWER_SUPPLY_PROP_ENERGY_NOW,
POWER_SUPPLY_PROP_ENERGY_AVG,
POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_TEMP_MAX,
POWER_SUPPLY_PROP_TEMP_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
POWER_SUPPLY_PROP_TEMP_AMBIENT,
POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN,
POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
POWER_SUPPLY_PROP_CALIBRATE,
/* Properties of type `const char *' */
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
};
- status:表示電池狀態,充電中、放電、未充電和電池電量已滿
- charge type:表示充電類型,快充、點滴式充電器(trikle charge)
- health:表示電池健康屬性,主要用來表示電池健康、過熱、損壞、過壓等
- present:表示電池或者適配器是否存在
- online:表示電池或者適配器是否在線,有時候可能電池存在但是并沒有接線。有的ec芯片這兩個狀態都是可以讀到的。
- authentic:真實參數,暫時在內核里沒發現按個ec驅動使用了這個屬性。這個屬性是為了給用戶看,移動設備的適配器 或者電池是不是非標準、不合格的。
- technology:表示電池采用的技術,lion(鋰離子電池)、nimh(鎳氫電池)、lipo(鋰聚合物電池)、nicd(鎳鉻電池)、limn(錳酸鋰電池)
- voltage:電壓參數,框架定義了最大、最小、設計最大、設計最小等,框架定義的單位是μV
- current:電流參數,框架定義了最大、目前和平均等,放電為負值,單位是μA,
- power:電流參數,和current不同的是,這個電流參數是智能電池導出的。智能電池使用功率作為工作單位和充電放電測量單位,所以不適合再導出在current屬性里了。
- charge:容量參數,框架定義了設計滿、設計空、充滿、空等等,框架定義的很多設計值都是用來計算電池的健康屬性的,比如說設計滿容量是3000,但是目前充滿只有1500了,那就說明電池的健康系數是50%。只是舉個例子,實際上電池健康系數不可能用僅僅一個屬性來計算,單位是μAh
- energy:功率參數,用來計算功耗,單位是μWh
- capacity:容量百分比,常被桌面用來顯示電池圖標選中后,出現的提示框里顯示電池容量多少。
- temp:溫度參數,注意這個問題是電池溫度,并不是cpu溫度,單位是攝氏度
- time:時間參數,充滿時間、放空時間等等,單位是s
最后還有幾個字符串類型的屬性,模塊名稱、生產商和序列號。
實現自己的ec驅動
定義psy設備
第一步是明確設備的充電類型,框架定義的充電類型有:
enum power_supply_type {
POWER_SUPPLY_TYPE_UNKNOWN = 0,
POWER_SUPPLY_TYPE_BATTERY,
POWER_SUPPLY_TYPE_UPS,
POWER_SUPPLY_TYPE_MAINS,
POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */
POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */
POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */
POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */
POWER_SUPPLY_TYPE_USB_TYPE_C, /* Type C Port */
POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */
POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */
POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */
};
一般來講筆記本都是這兩個充電類型:mains類型和battery類型,移動設備需要定義usb的充電類型。先定義兩個最常見的psy設備:適配器和電池,適配器的充電類型是mains,電池是battery。
static const struct power_supply_desc shiwen_ac_desc = {
.name = "shiwen_ac",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = shiwen_power_ac_props,
.num_properties = ARRAY_SIZE(shiwen_power_ac_props),
.get_property = shiwen_power_get_ac_property,
};
static const struct power_supply_desc shiwen_bat_desc = {
.name = "shiwen_battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = shiwen_power_battery_props,
.num_properties = ARRAY_SIZE(shiwen_power_battery_props),
.get_property = shiwen_power_get_battery_property,
};
選擇psy設備屬性
定義好之后,再選擇每個psy設備的硬件屬性,這部分要看ec芯片提供了什么數據然后選擇定義什么屬性。上一節提到的屬性是框架定義的全部屬性,ec芯片不可能提供這全部的數據。這里寫的示例,就簡單選擇幾個上層需要的屬性吧。適配器就一個是否在線屬性,電池桌面需要的屬性有電池存在標志、充滿時間、放空時間、電池狀態、電池容量百分比、電池容量等級、模塊名、制造商。
static enum power_supply_property shiwen_power_ac_props[] = {
POWER_SUPPLY_PROP_ONLINE,
};
static enum power_supply_property shiwen_power_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CAPACITY, /* in percents! */
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_TIME_TO_EMPTY,
POWER_SUPPLY_PROP_TIME_TO_FULL,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
填充獲取psy屬性的方法
接著要告訴內核如何獲取前面定義的psy屬性,psy core留了一個get_property接口,需要驅動工程師把方法填充在接口里。這部分就完全和硬件相關了,不同的ec芯片,讀取psy屬性方法不同,比如說本文的示例代碼里僅僅實現了一個固定值。
struct int shiwen_power_get_battery_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
case POWER_SUPPLY_PROP_ONLINE:
val- >intval = 1; //always assume ac online
break;
default:
val- >intval = -1;
pr_err("property error
");
}
struct int shiwen_power_get_battery_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
switch (psp) {
case POWER_SUPPLY_PROP_MODEL_NAME:
val- >strval = "Shiwen example driver";
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val- >strval = "Shiwen example";
break;
case POWER_SUPPLY_PROP_STATUS:
val- >intval = Charging; //always assume battery charging
break;
case POWER_SUPPLY_PROP_PRESENT:
val- >intval = 1; //always assume battery present
break;
case POWER_SUPPLY_PROP_CAPACITY:
val- >intval = 100; //always assume battery capacity 100%
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
val- >intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
break;
case POWER_SUPPLY_PROP_TIME_TO_EMPTY:
val- >intval = 7200; //always assume battery need 2 hours to empty
break;
case POWER_SUPPLY_PROP_TIME_TO_FULL:
val- >intval = 0;
break;
default:
val- >intval =-1;
pr_err("property error
");
break;
}
到這一步,兩個psy設備定義完成了。接下來只需要把注冊設備,在sys下就能看到接口了。
注冊設備
設備注冊,psy core提供了接口,只需要調用接口并做好錯誤處理即可。
static int __init shiwen_power_init(void)
{
shiwen_ac = power_supply_register(NULL, &shiwen_ac_desc, NULL);
if (IS_ERR(shiwen_ac)) {
pr_err("%s: failed to register
",__func__, shiwen_ac_desc.name);
ret = PTR_ERR(shiwen_ac);
goto failed_ac;
}
shiwen_bat = power_supply_register(NULL, &shiwen_bat_desc, NULL);
if (IS_ERR(shiwen_ac)) {
pr_err("%s: failed to register %s
", __func__, shiwen_bat_desc.name);
ret = PTR_ERR(shiwen_bat);
goto failed_bat;
}
return 0;
failed_bat:
power_supply_unregister(shiwen_bat);
failed_ac:
power_supply_unregister(shiwen_ac);
return ret;
}
查看結果
到這里,非常簡單的ec示例驅動注冊就完成了,在sys下查看一下是否正確注冊了設備,并且獲取到正確的內容。
deepin@deepin-PC:~$ ls /sys/class/power_supply/
shiwen_ac shiwen_battery
deepin@deepin-PC:~$
deepin@deepin-PC:~$ ls /sys/class/power_supply/shiwen_ac/
online power subsystem type uevent
deepin@deepin-PC:~$ cat /sys/class/power_supply/shiwen_ac/uevent
POWER_SUPPLY_NAME=shiwen_ac
POWER_SUPPLY_ONLINE=1
deepin@deepin-PC:~$ ls /sys/class/power_supply/shiwen_battery
capacity capacity_level manufacturer mcu_time_effect model_name power present status subsystem time_to_empty_avg time_to_full_avg type uevent
deepin@deepin-PC:~$ cat /sys/class/power_supply/shiwen_battery/uevent
POWER_SUPPLY_NAME=shiwen_battery
POWER_SUPPLY_STATUS=Charging
POWER_SUPPLY_PRESENT=1
POWER_SUPPLY_CAPACITY=100
POWER_SUPPLY_CAPACITY_LEVEL=POWER_SUPPLY_CAPACITY_LEVEL_FULL
POWER_SUPPLY_TIME_TO_EMPTY_AVG=7200
POWER_SUPPLY_TIME_TO_FULL_AVG=0
POWER_SUPPLY_MODEL_NAME=Shiwen example driver
POWER_SUPPLY_MANUFACTURER=Shiwen example
到這,就大功告成啦。sys下文件創建、sys下文件的讀寫都有sysfs框架和psy core框架幫我們實現。上文是一個非常非常簡單的psy驅動實現流程,ec狀態的改變驅動并沒有關注,都是依賴上層daemon或者讀取 sys下的文件來感知,對于用戶來說相當于是輪詢讀取。實際上日常我們遇到的ec芯片都沒有這么原始,ec事件基本上都是中斷通知,所以一個完成的ec驅動還要在上面的流程里面加上中斷的注冊和處理代碼。
增加中斷處理
關注過我之前寫過兩個中斷介紹文章的童鞋肯定知道驅動注冊中斷的第一步,肯定是實現中斷處理函數。處理函數也和硬件有關系,有的ec芯片ac插拔事件和電池事件的中斷是分開的,假設我們虛構的ec芯片只有ac和電池的插拔才能觸發中斷(看內核psy實現的芯片,這樣的中斷也是最常見的)。假設ac事件和電池事件共享155號中斷(隨意選的,沒有任何理論依據),那么中斷處理函數處理第一步肯定是確定中斷源。接著根據中斷源,更新一下設備狀態即可。
static irqreturn_t shiwen_ec_interrupt(int irq, void *dev_id)
{
int status;
int source;
source = SHIWEN_READ_INTSOURCE_REG();
if (source = SHIWEN_BATTERY)
power_supply_changed(shiwen_battery);
else if(source = SHIWEN_AC)
power_supply_changed(shiwen_ac);
else
//there are no psy event
return IRQ_NONE;
return IRQ_HANDLED;
}
更新整個psy設備狀態的函數是psy core框架提供的,很好用吧。psy會根據參數傳進去的設備,調用獲取屬性函數,更新psy設備屬性。這個處理函數非常簡單,因為ec芯片的中斷就很簡單。如果是比較復雜的ec芯片的話,可能中斷源分的比較多,比如說電壓變化、容量變化、電流變化等等都會有一種中斷的產生。相對的,中斷處理函數就不能簡單的使用power_supply_changed更新整個psy的狀態了,需要自己實現一個。注意:為了避免讀到的狀態有問題,需要在中斷產生后等一段時間再去讀ec狀態,所以中斷處理函數一般都需要有延時讀取ec狀態。power_supply_changed函數內部實現是采用work queue方式讀取狀態的,我們自己實現的話,為了簡單且保險,可以用delay work。假設說,需要實現一個電壓變化中斷的處理函數:
static irqreturn_t shiwen_voltage_interrupt(int irq, void *dev_id)
{
int status;
schedule_delayed_work(&ec_work, JIFFIES_NUM);
return IRQ_HANDLED;
}
static void ec_work_func(struct work_struct *work)
{
shiwen_update_voltage();
}
在 中斷處理函數里面,僅有調用delay work內容,延時過了之后函數會被加載工作隊列里,借此達到延時的目的。接下來指定一下延時工作進程需要執行的函數,這是工作進程要求的。這就是一個基本的讀取某一個屬性的中斷函數實現,沒實現真正電壓讀取是因為,電壓讀取是真正和硬件相關的,每款芯片都不一樣,我也實在是虛構不出來了orz…
處理函數寫完了之后,中斷注冊到內核里就可以使用啦。剩下的中斷觸發是設備的事,中斷觸發之后的感知是cpu的事,感知到中斷之后在調到處理函數之前是內核中斷子系統的事,前面中斷的兩篇文章介紹過內核已經做好了,驅動并不關心。中斷的注冊一般放在psy設備注冊之后,修改過的psy設備注冊代碼如下:
static int __init shiwen_power_init(void)
{
shiwen_ac = power_supply_register(NULL, &shiwen_ac_desc, NULL);
......
shiwen_bat = power_supply_register(NULL, &shiwen_bat_desc, NULL);
......
//shiwen_ac and shiwen_battery called shiwen_battery_ac
ret =request_irq(155, shiwen_ec_interrupt, IRQF_SHARED, shiwen_battery_ac);
INIT_DELAYED_WORK(&ec_work, ec_work_func);
return 0;
......
}
增加了中斷注冊個delay work初始化的代碼(如果需要自己使用delay work讀取某一個屬性的時候),代碼寫到這里,一個帶基本功能的ec驅動就做完了。看吧,還是很簡單的吧。
-
鋰離子電池
+關注
關注
85文章
3215瀏覽量
77550 -
充電器
+關注
關注
100文章
4073瀏覽量
114654 -
適配器
+關注
關注
8文章
1932瀏覽量
67918 -
中斷處理
+關注
關注
0文章
94瀏覽量
10961 -
LINUX內核
+關注
關注
1文章
316瀏覽量
21618
發布評論請先 登錄
相關推薦
評論