`sprd_27xx_fgu.c`就是展訊SL8541E 庫侖計驅動,用來統計電量的; 還是一樣,從 分析開始: 其中這裡有三個工作隊列: 1. sprdfgu_debug_works工作隊列 2. sprdfgu_qmax_works工作隊列,電量自學習工作隊列 3. 在 函數中的sprdfgu_ ...
sprd_27xx_fgu.c
就是展訊SL8541E 庫侖計驅動,用來統計電量的;
還是一樣,從static int sprdfgu_2723_probe(struct platform_device *pdev)
分析開始:
static int sprdfgu_2723_probe(struct platform_device *pdev)
{
int ret = 0, irq = 0;
u32 value = 0;
struct device_node *np = pdev->dev.of_node;
const struct of_device_id *of_id;
struct power_supply *ret_ptr = NULL;
struct power_supply_desc *sprdfgu_desc = NULL;
reg_map = dev_get_regmap(pdev->dev.parent, NULL);
if (!reg_map)
dev_err(&pdev->dev, "%s :NULL regmap for fgu 2723\n",
__func__);
sprdfgu_data.fgu_pdata = devm_kzalloc(&pdev->dev,
sizeof(struct sprdfgu_platform_data),
GFP_KERNEL);
of_id = of_match_node(sprdfgu_2723_of_match,
pdev->dev.of_node);
if (!of_id) {
pr_err("get 27xx fgu of device id failed!\n");
return -ENODEV;
}
sprdfgu_data.fgu_pdata->fgu_type = (enum fgu_type)of_id->data;
SPRD_FGU_DEBUG("fgu_type =%d\n",
sprdfgu_data.fgu_pdata->fgu_type);
//ocv-type 為0 時,會使用ocv表來統計開機的初始化電量
ret = of_property_read_u32(np, "ocv-type",
&sprdfgu_data.fgu_pdata->ocv_type);
if (ret)
dev_err(&pdev->dev, "%s :no ocv-type\n",
__func__);
irq = platform_get_irq(pdev, 0);
if (unlikely(irq <= 0))
dev_err(&pdev->dev, "no irq resource specified\n");
sprdfgu_data.fgu_pdata->fgu_irq = irq;
//reg是指庫侖計使用的時候的偏移地址
ret = of_property_read_u32(np, "reg", &value);
if (ret)
dev_err(&pdev->dev, "%s :no reg of property for pmic fgu\n",
__func__);
sprdfgu_data.fgu_pdata->base_addr = (unsigned long)value;
sprdfgu_desc = devm_kzalloc(&pdev->dev,
sizeof(struct power_supply_desc), GFP_KERNEL);
if (sprdfgu_desc == NULL)
return -ENOMEM;
//註冊power_supply節點
sprdfgu_desc->name = "sprdfgu";
sprdfgu_desc->get_property = sprdfgu_power_get_property;
ret_ptr = power_supply_register(&pdev->dev, sprdfgu_desc, NULL);
if (IS_ERR(ret_ptr))
pr_err("register power supply error!\n");
else
sprdfgu_data.sprdfgu = ret_ptr;
sprdfgu_data.dev = &pdev->dev;
platform_set_drvdata(pdev, &sprdfgu_data);
ret = sysfs_create_group(&sprdfgu_data.sprdfgu->dev.kobj,
&sprd_fgu_group);
if (ret)
pr_err("failed to create sprd_fgu sysfs device attributes\n");
INIT_DELAYED_WORK(&sprdfgu_debug_work, sprdfgu_debug_works);
mutex_init(&sprdfgu_data.lock);
mutex_init(&sprdfgu_data.track_lock);
wake_lock_init(&(sprdfgu_data.low_power_lock), WAKE_LOCK_SUSPEND,
"sprdfgu_powerlow_lock");
sprdfgu_data.is_track = 1;
INIT_DELAYED_WORK(&sprdfgu_data.fgu_qmax_work,
sprdfgu_qmax_works);
sprdfgu_data.fgu_wqueue =
create_workqueue("sprdfgu_monitor");
if (!sprdfgu_data.fgu_wqueue)
sprdfgu_data.is_track = 0;
sprdfgu_data.track.vol_buff = devm_kzalloc(&pdev->dev,
sizeof(int) * CUR_VOL_BUFF_LEN,
GFP_KERNEL);
if (!sprdfgu_data.track.vol_buff)
sprdfgu_data.is_track = 0;
sprdfgu_data.track.cur_buff = devm_kzalloc(&pdev->dev,
sizeof(int) * CUR_VOL_BUFF_LEN,
GFP_KERNEL);
if (!sprdfgu_data.track.cur_buff)
sprdfgu_data.is_track = 0;
SPRD_FGU_DEBUG("sprdfgu_2723_probe ok\n");
return ret;
}
其中這裡有三個工作隊列:
- sprdfgu_debug_works工作隊列
- sprdfgu_qmax_works工作隊列,電量自學習工作隊列
- 在
sprdfgu_int_init
函數中的sprdfgu_irq_works函數
1、 函數為空函數,則不予以處理和分析
2、電容自適應函數:
主要在sprdfgu_read_soc
函數中讀取:
並且在sprdfgu_qmax_update_monitor
函數中當
當電池工作電流足夠小的時候電池工作電壓近似等於電池開路電壓,如果有準確的電量表我們就能準確的定位電池容量我們記錄為 C1,將這時庫侖計計數值容量記錄為Q1。充電滿後電池真實容量接近 100%,這裡我們將庫侖計計數值記錄為 Q2。根據以上幾個數據我們就可以得到電池真實有效容量:
Qmax = (Q2 – Q1) * 100/(100 –C1)
我們一般採用低電量開始作為初始採集點,並嚴格規定了環境溫度即電流、電壓條件,只有符合規定的容量自學習我們才認為是有效的自學習。 自學習功能被抽象為 qmax,結構如下, 各種限制條件需要在初始化的時候寫入。
struct sprdfgu_qmax {
int state; //自學習狀態
int cur_cnom; //當前容量
int s_clbcnt; //開始自學習的庫侖計計數
int s_soc; //自學習初始點百分比
int s_vol; //自學習初始電壓限制
int s_cur; //自學習初始電流限制
int e_vol; //自學習結束電壓條件
int e_cur; //自學習結束電流條件
int force_s_flag; //應用層控制啟動標誌位
int force_s_soc; //應用層直接寫入容量值
uint32_t timeout; //設置的超時門限
__s64 s_time; //開始自學習的時間點
};
2、 電量保存與回讀
自學習狀態 State 有 5 種, DONE 狀態是學習完成狀態, 在 DONE 狀態會將學習完成的容量值寫入/productinfo/.battery_file 這個文件。 productinfo 分區掉電不擦除,重啟後再 INIT狀態會回讀這個數據作為容量基數進行電量積分,並將自學習狀態切換到 IDLE 狀態。 如果滿足了自學習條件就進入了 QMAX_UPDATING, UPDATING 狀態下會檢查自學習後容量, 如果容量檢查失效則恢復 IDLE狀態,需要重新開始學習。 如果手機單體沒有完成過自學習則使用預設的容量配置。
enum QMAX_STATE {
QMAX_INIT, //開機預設狀態
QMAX_IDLE, //自學習準備狀態
QMAX_UPDATING, //電量需要更新
QMAX_DONE, //自學習完成
QMAX_ERR, //異常
};
sys/class/power_supply/sprdfgu/ qmax_force_start
應用層主動開啟自學習功能
sys/class/power_supply/sprdfgu/ qmax_force_s_soc
應用層設置開啟自學習時的容量
sys/class/power_supply/sprdfgu/ qmax_state
讀取當前自學習狀態
sys/class/power_supply/sprdfgu/ qmax_s_vol
設置開啟自學習的電壓條件
sys/class/power_supply/sprdfgu/ qmax_s_cur10
設置開啟自學習的電流條件
sys/class/power_supply/sprdfgu/ qmax_e_vol
設置自學習停止電壓條件
sys/class/power_supply/sprdfgu/ qmax_e_cur
設置自學習停止電流條件
sys/class/power_supply/sprdfgu/ cnom
讀取當前積分容量基數
sys/class/power_supply/sprdfgu/ saved_cnom
應用層直接寫數保存容量
3. 本質上就是用sprdfgu_irq_works計算電量的
static void sprdfgu_irq_works(struct work_struct *work)
{
uint32_t cur_ocv;
uint32_t adc;
int cur_soc;
wake_lock_timeout(&(sprdfgu_data.low_power_lock), 2 * HZ);
SPRD_FGU_DEBUG("%s......0x%x.cur vol = %d\n",
__func__, sprdfgu_data.int_status,
sprdfgu_read_vbat_vol());
if (sprdfgu_data.int_status & BIT_VOLT_HIGH_INT)
power_supply_changed(sprdfgu_data.sprdfgu);
if (sprdfgu_data.int_status & BIT_VOLT_LOW_INT) {
cur_soc = sprdfgu_read_soc();
mutex_lock(&sprdfgu_data.lock);
cur_ocv = sprdfgu_read_vbat_ocv();
if (cur_ocv <= sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("shutdown_vol .\n");
sprdfgu_soc_adjust(0);
} else if (cur_ocv <= sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("alm_vol %d.\n", cur_soc);
if (cur_soc > sprdfgu_data.warning_cap) {
sprdfgu_soc_adjust(sprdfgu_data.warning_cap);
} else if (cur_soc <= 0) {
sprdfgu_soc_adjust(sprdfgu_vol2capacity
(cur_ocv));
}
if (!sprdfgu_data.adp_status) {
adc = sprdfgu_vol2adc_mv
(sprdfgu_data.shutdown_vol);
fgu_adi_write(REG_FGU_LOW_OVER, adc & 0xFFFF,
~0);
}
} else {
SPRD_FGU_DEBUG("need add\n");
}
mutex_unlock(&sprdfgu_data.lock);
}
}
首先第一個函數sprdfgu_read_vbat_vol
:
讀取vbat上的電壓:
uint32_t sprdfgu_read_vbat_vol(void)
{
u32 cur_vol_raw;
uint32_t temp;
cur_vol_raw = sprdfgu_reg_get(REG_FGU_VOLT_VAL);
temp = sprdfgu_adc2vol_mv(cur_vol_raw);
return temp;
}
第二個函數sprdfgu_read_soc
函數,也就是電量計的核心:
int sprdfgu_read_soc(void)
{
int cur_cc, cc_delta, capcity_delta, temp;
uint32_t cur_ocv;
int cur;
union power_supply_propval val;
sprdfgu_track();
mutex_lock(&sprdfgu_data.lock);
sprdfgu_qmax_update_monitor();
cur_cc = sprdfgu_clbcnt_get();
cc_delta = cur_cc - sprdfgu_data.init_clbcnt;
/* 0.1mah */
temp = DIV_ROUND_CLOSEST(cc_delta,
(360 * FGU_CUR_SAMPLE_HZ));
temp = sprdfgu_adc2cur_ma(temp);
capcity_delta = DIV_ROUND_CLOSEST(temp * 100,
sprdfgu_data.cur_cnom);
SPRD_FGU_DEBUG("d cap %d,cnom %d,g %dmAh(0.1mA),init_cc:%d\n",
capcity_delta, sprdfgu_data.cur_cnom,
temp, sprdfgu_data.init_clbcnt);
capcity_delta += sprdfgu_data.init_cap;
SPRD_FGU_DEBUG("soc %d,init_cap %d,cap:%d\n",
(capcity_delta + 9) / 10, sprdfgu_data.init_cap,
capcity_delta);
cur = sprdfgu_read_batcurrent();
cur_ocv = sprdfgu_read_vbat_ocv();
if ((cur_ocv >= sprdfgu_data.bat_full_vol)
&& (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
if (capcity_delta < FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
}
if (capcity_delta > FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
sprdpsy_get_property("battery",
POWER_SUPPLY_PROP_STATUS,
&val);
if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
if (capcity_delta <= sprdfgu_data.warning_cap
&& cur_ocv > sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc low...\n");
capcity_delta = sprdfgu_data.warning_cap + 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta <= 0
&& cur_ocv > sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("soc 0...\n");
capcity_delta = 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta > sprdfgu_data.warning_cap
&& cur_ocv < sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc high...\n");
sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
capcity_delta = sprdfgu_vol2capacity(cur_ocv);
}
}
if (capcity_delta < 0)
capcity_delta = 0;
sprdfgu_record_rawsoc(capcity_delta);
#ifdef SPRDFGU_TEMP_COMP_SOC
{
temp = sprdbat_read_temp();
capcity_delta =
sprdfgu_temp_comp_soc(capcity_delta, temp / 10);
}
#endif
if (sprdfgu_read_vbat_vol() < sprdfgu_data.pdata->soft_vbat_uvlo) {
SPRD_FGU_DEBUG("trigger soft uvlo vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
}
mutex_unlock(&sprdfgu_data.lock);
return capcity_delta;
}
- 首先通過
sprdfgu_track
函數來定位電池開路電壓;
這裡首先介紹電池內阻自適應:
電池開路電壓 OCV 是表徵電池容量的最關鍵因素,在電量顯示、電池容量自學習的時候都可能用到電池開路電壓來定位百分比。因此我們必須找到一個方法定位電池開路電壓。
FGU 可以獲取到電池工作狀態下的電流 I 和電壓 VBAT,根據歐姆定律只要我們得到電池內阻 R 就可以計算出電池開路電壓了: OCV = VBAT – I *R。 電池內阻一般都是動態變化的,不同容量不同溫度都會有不同的電池內阻,所以需要一個動態調整內阻的演算法來保證內阻的準確性, 平臺提供了一套電池內阻自適應的演算法:提取硬體 buff 中的一組電流電壓值(共 8 個)並將這 8 個數據按大小排序,找到最大的壓降 DeltaV 和電流變化DeltaI,根據歐姆定律 R =DeltaV/DeltaI;使用計算出來的 R 就能得到實時 OCV 了。
電池內阻自學習通過 sprdfgu_tracking 來記錄學習狀態,成員包括自學習時的溫度、平均電流、平均電壓、電流 buff、電壓 buff、最小電流、 最小電壓等等。 Chargework 會調用 static void sprdfgu_track(void)來更新 sprdfgu_data.track,使用的時候通過介面 static struct sprdfgu_tracking sprdfgu_get_track_data(void);獲取到實時的電池工作狀態信息sprdfgu_data.track。
struct sprdfgu_tracking {
__s64 r_time;
__s64 query_time; //結構更新時間間隔
int rint; //電池內阻自適應出來的內阻
int r_temp; //獲取電池內阻時候判斷採樣溫度
int c_temp; //容量自適應的時候判斷採樣的溫度
int r_vol; //
int relax_vol; //最接近電池開路電壓
int relax_cur; //最接近零點的電流
int ave_vol; //平均電壓
int ave_cur; //平均電流
int *cur_buff; //電流 buff
int *vol_buff; //電壓 buff
};
sprdfgu_track則是算出上述數據的函數;
-
sprdfgu_qmax_update_monitor
則是電池自學習的過程,所以不再敘述; -
sprdfgu_clbcnt_get
獲取記錄電流積分數據; -
獲取當前 Coulomb Counter 計數量下次輪詢調用的時候可以計算出兩 次 Coulomb Counter 統計的差值從而得到庫侖量變化 cc_delta 。DIV_ROUND_CLOSEST(cc_delta, (3600 * 2));
-
DIV_ROUND_CLOSEST(mah * 100, sprdfgu_data.pdata->cnom);通過變化的 mah 計算出與總容量 cnom 的比值,從而得到變化百分比。
-
capcity_delta += sprdfgu_data.init_cap;
計算出這次電量加上初始化的電量; -
sprdfgu_read_batcurrent
讀取現在電池vbat的容量; -
sprdfgu_read_vbat_ocv
讀取開路電壓的ocv; -
對一些ocv的條件判定,具體看代碼:
//開路電壓ocv大於電池滿電的狀態並且充電電流小於充電截止電流
if ((cur_ocv >= sprdfgu_data.bat_full_vol)
&& (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
if (capcity_delta < FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
}
//大於100,電量則設置100
if (capcity_delta > FULL_CAP) {
capcity_delta = FULL_CAP;
sprdfgu_soc_adjust(FULL_CAP);
}
sprdpsy_get_property("battery",
POWER_SUPPLY_PROP_STATUS,
&val);
//不在充電狀態的話,
if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
if (capcity_delta <= sprdfgu_data.warning_cap
&& cur_ocv > sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc low...\n");
capcity_delta = sprdfgu_data.warning_cap + 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta <= 0
&& cur_ocv > sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("soc 0...\n");
capcity_delta = 10;
sprdfgu_soc_adjust(capcity_delta);
} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
SPRD_FGU_DEBUG("vol 0...\n");
capcity_delta = 0;
sprdfgu_soc_adjust(capcity_delta);
} else if (capcity_delta > sprdfgu_data.warning_cap
&& cur_ocv < sprdfgu_data.pdata->alm_vol) {
SPRD_FGU_DEBUG("soc high...\n");
sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
capcity_delta = sprdfgu_vol2capacity(cur_ocv);
}
}
if (capcity_delta < 0)
capcity_delta = 0;
sprdfgu_record_rawsoc
記錄soc上raw;
另外:
sprdfgu_load_uicap
讀取ui的電量,本身由rtc記錄。sprdfgu_load_rawsoc
則是記錄實際電量;sprdfgu_soc_adjust
調整電量。輸入為電量值