高通電源管理qpnp-vm-bms驅動
- 2019 年 10 月 11 日
- 筆記
1. compatible節點:
qpnp-vm-bms.c使用來控制電池曲線的和BMS功能的,其compatible節點是"qcom,qpnp-vm-bms"
2. probe函數:
qpnp_vm_bms_probe函數如下:
static int qpnp_vm_bms_probe(struct spmi_device *spmi) { struct qpnp_bms_chip *chip; struct device_node *revid_dev_node; int rc, vbatt = 0; chip = devm_kzalloc(&spmi->dev, sizeof(*chip), GFP_KERNEL); if (!chip) { pr_err("kzalloc() failed.n"); return -ENOMEM; } //獲取ADC的值,ADC是電流的大小,綁定vadc,並且獲取溫度,設備列表 rc = bms_get_adc(chip, spmi); if (rc < 0) { pr_err("Failed to get adc rc=%dn", rc); return rc; } //指向revision外圍節點的phandle,vm-bus需要配置這個節點 revid_dev_node = of_parse_phandle(spmi->dev.of_node, "qcom,pmic-revid", 0); if (!revid_dev_node) { pr_err("Missing qcom,pmic-revid propertyn"); return -EINVAL; } //返回pmic的修訂資訊 chip->revid_data = get_revid_data(revid_dev_node); if (IS_ERR(chip->revid_data)) { pr_err("revid error rc = %ldn", PTR_ERR(chip->revid_data)); return -EINVAL; } if ((chip->revid_data->pmic_subtype == PM8916_V2P0_SUBTYPE) && chip->revid_data->rev4 == PM8916_V2P0_REV4) chip->workaround_flag |= WRKARND_PON_OCV_COMP; //查看是否是熱啟動的,熱啟動就是在不關閉設備的情況下,重啟電腦 rc = qpnp_pon_is_warm_reset(); if (rc < 0) { pr_err("Error reading warm reset status rc=%dn", rc); return rc; } chip->warm_reset = !!rc; //解析spmi設備的內容,並且在其中尋找它的中斷基地址 rc = parse_spmi_dt_properties(chip, spmi); if (rc) { pr_err("Error registering spmi resource rc=%dn", rc); return rc; } //解析電池的參數,如v-cutoff-uv,關機電壓,它不會讀qcom的內容,會直接讀qcom,後面的內容會有仔細說 rc = parse_bms_dt_properties(chip); if (rc) { pr_err("Unable to read all bms properties, rc = %dn", rc); return rc; } //查詢錯誤的原因 if (chip->dt.cfg_disable_bms) { pr_info("VMBMS disabled (disable-bms = 1)n"); rc = qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, BMS_EN_BIT, 0); if (rc) pr_err("Unable to disable VMBMS rc=%dn", rc); return -ENODEV; } //讀取存在pm?PM里讀出來的未經修正的原始數據? rc = qpnp_read_wrapper(chip, chip->revision, chip->base + REVISION1_REG, 2); if (rc) { pr_err("Error reading version register rc=%dn", rc); return rc; } pr_debug("BMS version: %hhu.%hhun", chip->revision[1], chip->revision[0]); dev_set_drvdata(&spmi->dev, chip); device_init_wakeup(&spmi->dev, 1); mutex_init(&chip->bms_data_mutex); mutex_init(&chip->bms_device_mutex); mutex_init(&chip->last_soc_mutex); mutex_init(&chip->state_change_mutex); init_waitqueue_head(&chip->bms_wait_q); //初始化隊列 /* read battery-id and select the battery profile */ //設置電池數據,也就是電池曲線 rc = set_battery_data(chip); if (rc) { pr_err("Unable to read battery data %dn", rc); goto fail_init; } /* set the battery profile */ //設置電池的配置文件,其實也就是配置剛剛設置好的全局變數了 rc = config_battery_data(chip->batt_data); if (rc) { pr_err("Unable to config battery data %dn", rc); goto fail_init; } //初始化wakeup_source,內核睡眠機制 wakeup_source_init(&chip->vbms_lv_wake_source.source, "vbms_lv_wake"); wakeup_source_init(&chip->vbms_cv_wake_source.source, "vbms_cv_wake"); wakeup_source_init(&chip->vbms_soc_wake_source.source, "vbms_soc_wake"); //初始化工作隊列 INIT_DELAYED_WORK(&chip->monitor_soc_work, monitor_soc_work); INIT_DELAYED_WORK(&chip->voltage_soc_timeout_work, voltage_soc_timeout_work); //初始化配置狀態,各種狀態 bms_init_defaults(chip); //這一句看不懂了,可能是電池BMS演算法用來讀取硬體配置的 bms_load_hw_defaults(chip); //通過判斷power_supply裡面的函數來確定是否是正在充電的狀態 is_bat_pres_ght =(is_battery_present(chip)); pr_err("is_bat_pres_ght =%dn",is_bat_pres_ght); ///if (is_battery_present(chip)) { //如果電池正在充電 if (is_bat_pres_ght) { //設置電池的設置低電(高電,高溫,低溫)的閾值,也就是電池低電關機 rc = setup_vbat_monitoring(chip); if (rc) { pr_err("fail to configure vbat monitoring rc=%dn", rc); goto fail_setup; } } //請求一些相應的中斷BMS rc = bms_request_irqs(chip); if (rc) { pr_err("error requesting bms irqs, rc = %dn", rc); goto fail_irq; } //電池一些常規的檢測,主要從PMIC上讀到的相關資訊 //電池的插入狀態檢測,判斷手段是如果當前狀態和之前狀態不一樣就判斷電池拔出,並且確定電池是否存在,否則重置 battery_insertion_check(chip); //電池狀態檢測 battery_status_check(chip); /* character device to pass data to the userspace */ //向上層註冊字元設備 rc = register_bms_char_device(chip); if (rc) { pr_err("Unable to regiter '/dev/vm_bms' rc=%dn", rc); goto fail_bms_device; } the_chip = chip; //這個也很重要,我們從上節知道,初值last_ocv_soc是非常重要的,決定著後面的soc估值演算法,計算估值電壓 calculate_initial_soc(chip); if (chip->dt.cfg_battery_aging_comp) { rc = calculate_initial_aging_comp(chip); if (rc) pr_err("Unable to calculate initial aging data rc=%dn", rc); } //設置和註冊電池的power supply /* setup & register the battery power supply */ chip->bms_psy.name = "bms"; chip->bms_psy.type = POWER_SUPPLY_TYPE_BMS; chip->bms_psy.properties = bms_power_props; chip->bms_psy.num_properties = ARRAY_SIZE(bms_power_props); chip->bms_psy.get_property = qpnp_vm_bms_power_get_property; chip->bms_psy.set_property = qpnp_vm_bms_power_set_property; chip->bms_psy.external_power_changed = qpnp_vm_bms_ext_power_changed; chip->bms_psy.property_is_writeable = qpnp_vm_bms_property_is_writeable; chip->bms_psy.supplied_to = qpnp_vm_bms_supplicants; chip->bms_psy.num_supplicants = ARRAY_SIZE(qpnp_vm_bms_supplicants); //power_supply註冊 rc = power_supply_register(chip->dev, &chip->bms_psy); if (rc < 0) { pr_err("power_supply_register bms failed rc = %dn", rc); goto fail_psy; } chip->bms_psy_registered = true; rc = get_battery_voltage(chip, &vbatt); if (rc) { pr_err("error reading vbat_sns adc channel=%d, rc=%dn", VBAT_SNS, rc); goto fail_get_vtg; } chip->debug_root = debugfs_create_dir("qpnp_vmbms", NULL); if (!chip->debug_root) pr_err("Couldn't create debug dirn"); if (chip->debug_root) { struct dentry *ent; ent = debugfs_create_file("bms_data", S_IFREG | S_IRUGO, chip->debug_root, chip, &bms_data_debugfs_ops); if (!ent) pr_err("Couldn't create bms_data debug filen"); ent = debugfs_create_file("bms_config", S_IFREG | S_IRUGO, chip->debug_root, chip, &bms_config_debugfs_ops); if (!ent) pr_err("Couldn't create bms_config debug filen"); ent = debugfs_create_file("bms_status", S_IFREG | S_IRUGO, chip->debug_root, chip, &bms_status_debugfs_ops); if (!ent) pr_err("Couldn't create bms_status debug filen"); } //這裡啟動工作隊列,絕大部分的工作內容都是在這裡完成的 schedule_delayed_work(&chip->monitor_soc_work, 0); /* * schedule a work to check if the userspace vmbms module * has registered. Fall-back to voltage-based-soc reporting * if it has not. */ // schedule_delayed_work(&chip->voltage_soc_timeout_work, msecs_to_jiffies(chip->dt.cfg_voltage_soc_timeout_ms)); pr_info("probe success: soc=%d vbatt=%d ocv=%d warm_reset=%dn", get_prop_bms_capacity(chip), vbatt, chip->last_ocv_uv, chip->warm_reset); return rc; fail_get_vtg: power_supply_unregister(&chip->bms_psy); fail_psy: device_destroy(chip->bms_class, chip->dev_no); cdev_del(&chip->bms_cdev); unregister_chrdev_region(chip->dev_no, 1); fail_bms_device: chip->bms_psy_registered = false; fail_irq: reset_vbat_monitoring(chip); fail_setup: wakeup_source_trash(&chip->vbms_lv_wake_source.source); wakeup_source_trash(&chip->vbms_cv_wake_source.source); wakeup_source_trash(&chip->vbms_soc_wake_source.source); fail_init: mutex_destroy(&chip->bms_data_mutex); mutex_destroy(&chip->last_soc_mutex); mutex_destroy(&chip->state_change_mutex); mutex_destroy(&chip->bms_device_mutex); the_chip = NULL; return rc; }
2.1 parse_bms_dt_properties()函數
在這裡我們詳細分析一下各個節點的內容,這裡就挑幾個比較重要的看看:(詳細可以參考設備樹裡面的內容)
- v-cutoff-uv:如修改關機電壓,除了修改這裡,還需要修改電池曲線數據的qcom,v-cutoff-uv,其實最好是用電池曲線數據里的
- max-voltage-uv:電池最大的電壓,單位為毫伏
- qcom,r-conn-mohm :連接器的電阻
- s1-sample-interval-ms:狀態s1下累加器的取樣(毫秒)。(即)累加器充滿vbat樣本的速率。最小值=0最大值=2550ms。
- resume-soc:當充滿的電池百分比低於此值,則重新開始充電。
- volatge-soc-timeout-ms:如果沒有使用VMBMS演算法來計算SOC,模組在此時間後基於SOC來報告電壓。
- low-temp-threshold:當溫度閾值低於此值,禁用IBAT求取平均值和UUC(不可用電量)平滑功能,如沒指定默認為0,我們這裡沒有指定。
- qcom,ignore-shutdown-soc:有些不看翻譯對大家都好;
- qcom,use-voltage-soc :BMS根據此項的值來決定是否採用基於電壓的SOC來替代基於庫倫電量計的方式
- qcom,use-reported-soc :此項使能reported_soc邏輯,而且要定義qcom,resume-soc為一個合適的值,BMS也需要控制充電、停止充電和重新充電。高通給出的程式碼默認是定義qcom,use-reported-soc,但我們核心板廠家注釋掉此項,並增加qcom,report-charger-eoc
- qcom,report-charger-eoc: 指示BMS需要通知EOC(充電結束)給充電器
- qcom,disable-bms :此屬性用於關閉VM BMS硬體模組
2.2 set_battery_data()函數
這一部分內容就是設置電池曲線內容:
下面就是電池曲線的詳細內容,不仔細說了:
static int set_battery_data(struct qpnp_bms_chip *chip) { int64_t battery_id; int rc = 0; struct bms_battery_data *batt_data; struct device_node *node; //裡面的內容通過讀取ADC來獲取ID號 battery_id = read_battery_id(chip); if (battery_id < 0) { pr_err("cannot read battery id err = %lldn", battery_id); return battery_id; } node = of_find_node_by_name(chip->spmi->dev.of_node, "qcom,battery-data"); if (!node) { pr_err("No available batterydatan"); return -EINVAL; } batt_data = devm_kzalloc(chip->dev, sizeof(struct bms_battery_data), GFP_KERNEL); if (!batt_data) { pr_err("Could not alloc battery datan"); return -EINVAL; } batt_data->fcc_temp_lut = devm_kzalloc(chip->dev, sizeof(struct single_row_lut), GFP_KERNEL); batt_data->pc_temp_ocv_lut = devm_kzalloc(chip->dev, sizeof(struct pc_temp_ocv_lut), GFP_KERNEL); batt_data->rbatt_sf_lut = devm_kzalloc(chip->dev, sizeof(struct sf_lut), GFP_KERNEL); batt_data->ibat_acc_lut = devm_kzalloc(chip->dev, sizeof(struct ibat_temp_acc_lut), GFP_KERNEL); batt_data->max_voltage_uv = -1; batt_data->cutoff_uv = -1; batt_data->iterm_ua = -1; /* * if the alloced luts are 0s, of_batterydata_read_data ignores * them. */ rc = of_batterydata_read_data(node, batt_data, battery_id); if (rc || !batt_data->pc_temp_ocv_lut || !batt_data->fcc_temp_lut || !batt_data->rbatt_sf_lut || !batt_data->ibat_acc_lut) { pr_err("battery data load failedn"); devm_kfree(chip->dev, batt_data->fcc_temp_lut); devm_kfree(chip->dev, batt_data->pc_temp_ocv_lut); devm_kfree(chip->dev, batt_data->rbatt_sf_lut); devm_kfree(chip->dev, batt_data->ibat_acc_lut); devm_kfree(chip->dev, batt_data); return rc; } if (batt_data->pc_temp_ocv_lut == NULL) { pr_err("temp ocv lut table has not been loadedn"); devm_kfree(chip->dev, batt_data->fcc_temp_lut); devm_kfree(chip->dev, batt_data->pc_temp_ocv_lut); devm_kfree(chip->dev, batt_data->rbatt_sf_lut); devm_kfree(chip->dev, batt_data->ibat_acc_lut); devm_kfree(chip->dev, batt_data); return -EINVAL; } /* check if ibat_acc_lut is valid */ if (!batt_data->ibat_acc_lut->rows) { pr_info("ibat_acc_lut not presentn"); devm_kfree(chip->dev, batt_data->ibat_acc_lut); batt_data->ibat_acc_lut = NULL; } /* Override battery properties if specified in the battery profile */ if (batt_data->max_voltage_uv >= 0) chip->dt.cfg_max_voltage_uv = batt_data->max_voltage_uv; if (batt_data->cutoff_uv >= 0) chip->dt.cfg_v_cutoff_uv = batt_data->cutoff_uv; chip->batt_data = batt_data; return 0; }
在of_batterydata_read_data
函數中有一個返回值:
of_batterydata_read_data-> of_batterydata_load_battery_data
of_batterydata_load_battery_data函數中有配置電池曲線的東西;
2.3 高通電量計
術語 |
全稱 |
注釋 |
---|---|---|
FCC |
Full-Charge Capacity |
滿電荷電量 |
UC |
Remaining capacity |
RC 剩餘電量 |
CC |
Coulumb counter |
電量計 |
UUC |
Unusable capacity |
不可用電量 |
RUC |
Remaining usable capacity // |
RUC=RC-CC-UUC RUC=RC-CC-UUC,剩餘可用電量 |
SoC |
State of charge |
電量百分比 |
OCV |
Open circuit voltage |
開路電壓,電池在開路狀態下的端電壓稱為開路電壓 |
SOC=(RC-CC-UUC)/(FCC-UUC)
以下是各個變數的計算方法:
2.3.1 FCC:
在校準的電池profile中有定義,會隨溫度有變化;
static struct single_row_lut fcc_temp = { .x = {-20, 0, 25, 40, 60}, .y = {3193, 3190, 3190, 3180, 3183}, .cols = 5 }
對應電池曲線的qcom,fcc-temp-lut;
2.3.2 pc-temp-ocv-lut:
qcom,pc-temp-ocv-lut,為溫度、SOC對應得電壓表,PMU8909獲取的電壓值,通過查該表,在溫度和電壓下,可得到當前的SOC。
對應電池曲線的qcom,pc-temp-ocv-lut
2.3.3 rbatt-sf-lut:
rbatt-sf-lut,為溫度、soc對應的電池內阻表,這裡主要考慮內阻的影響,對OCV的修正,new_ocv=ocv+rbatt(內阻)*current(當前電流)。
對應電池曲線的qcom,rbatt-sf-lut
2.3.3 ibat-acc-luit
ibat-acc-luit,為溫度、電流對應的acc表,這兩個是起到修正SOC的作用
對應電池曲線的qcom, ibat-acc-luit
2.3.4 計算公式
soc_uuc = ((fcc - acc) * 100) / fcc,
//fcc在qcom,fcc-temp-lut查表可知、acc在qcom, ibat-acc-luit查表可知
soc_acc = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_uuc),(100 - soc_uuc));
//最終soc_acc,為上報的SOC.soc_ocv則是在qcom,pc-temp-ocv-lut查表可知
2.3.5 BMS演算法
會上報事件uevent,當HAL層,收到消息,然後調用getprop的方法,獲取相關的參數,如,電阻、電流、fcc、acc等,來估算出last_ocv_uv,然後調用setprop,把該值設下去,並啟動工作執行緒,根據last_ocv_uv,查表得到soc,並經過修正SOC,並再次上報事件,循環下去。這個估值演算法,我猜可能是一套學習演算法,具體的沒有源碼,不清楚,只知道它把演算法變為.bin文件,用了binder機制,作為服務一直運行。
我們如何知道monitor_soc_work
函數不斷的運行呢?
原因在於:
static void monitor_soc_work(struct work_struct *work) { ...... if ((chip->last_soc != chip->calculated_soc) || chip->dt.cfg_use_voltage_soc) schedule_delayed_work(&chip->monitor_soc_work, msecs_to_jiffies(get_calculation_delay_ms(chip))); }
2.3.6 分析如何確定初始的last_ocv_uv:
static int calculate_initial_soc(struct qpnp_bms_chip *chip) { ........ ........ //讀當前電池溫度 rc = get_batt_therm(chip, &batt_temp); ............ //讀PON OCV rc = read_and_update_ocv(chip, batt_temp, true); .......... //讀關機保存的soc和last_soc_uv rc = read_shutdown_ocv_soc(chip); //這裡判斷是使用估計soc還是估值soc。如果chip->warm_reset 為真 if (chip->warm_reset) { if (chip->shutdown_soc_invalid) { //這個是dtsi的一個配置選項,若沒有配置, //則不使用關機soc est_ocv = estimate_ocv(chip); //估值soc chip->last_ocv_uv = est_ocv; } else { chip->last_ocv_uv = chip->shutdown_ocv;//使用關機的soc和ocv pr_err("Hyan %d : set chip->last_ocv_uv = %dn", __LINE__, chip->last_ocv_uv); chip->last_soc = chip->shutdown_soc; chip->calculated_soc = lookup_soc_ocv(chip, chip->shutdown_ocv, batt_temp); } } else { if (chip->workaround_flag & WRKARND_PON_OCV_COMP) adjust_pon_ocv(chip, batt_temp); /* !warm_reset use PON OCV only if shutdown SOC is invalid */ chip->calculated_soc = lookup_soc_ocv(chip, chip->last_ocv_uv, batt_temp); if (!chip->shutdown_soc_invalid && (abs(chip->shutdown_soc - chip->calculated_soc) < chip->dt.cfg_shutdown_soc_valid_limit)) { chip->last_ocv_uv = chip->shutdown_ocv; chip->last_soc = chip->shutdown_soc; chip->calculated_soc = lookup_soc_ocv(chip, chip->shutdown_ocv, batt_temp);//使用估值soc } else { chip->shutdown_soc_invalid = true; //使用關機soc } } ............. ............ } //得到PON OCV rc = read_and_update_ocv(chip, batt_temp, true); ocv_uv = convert_vbatt_raw_to_uv(chip, ocv_data, is_pon_ocv); uv = vadc_reading_to_uv(reading, true); //讀ADC值 uv = adjust_vbatt_reading(chip, uv); //轉化為soc_uv rc = qpnp_vbat_sns_comp_result(chip->vadc_dev, &uv, is_pon_ocv); //根據IC的類型,進行溫度補償 //從暫存器中讀到儲存的soc和ocv read_shutdown_ocv_soc rc = qpnp_read_wrapper(chip, (u8 *)&stored_ocv, chip->base + BMS_OCV_REG, 2); rc = qpnp_read_wrapper(chip, &stored_soc, chip->base + BMS_SOC_REG, 1); adjust_pon_ocv(struct qpnp_bms_chip *chip, int batt_temp) rc = qpnp_vadc_read(chip->vadc_dev, DIE_TEMP, &result); pc = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, batt_temp, chip->last_ocv_uv / 1000); //根據ocv和temp,查表得PC(soc)。 rbatt_mohm = get_rbatt(chip, pc, batt_temp); //根據soc和temp,得電池內阻值 /* convert die_temp to DECIDEGC */ die_temp = (int)result.physical / 100; current_ma = interpolate_current_comp(die_temp); //當前電流 delta_uv = rbatt_mohm * current_ma; chip->last_ocv_uv += delta_uv; //修正last_ocv_uv //這個函數主要根據last_ocv_uv,計算出soc的 lookup_soc_ocv(struct qpnp_bms_chip *chip, int ocv_uv, int batt_temp) //查表得到soc_ocv,soc_cutoff soc_ocv = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, batt_temp, ocv_uv / 1000); soc_cutoff = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, batt_temp, chip->dt.cfg_v_cutoff_uv / 1000); soc_final = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_cutoff), (100 - soc_cutoff)); if (batt_temp > chip->dt.cfg_low_temp_threshold) iavg_ma = calculate_uuc_iavg(chip); else iavg_ma = chip->current_now / 1000; //查表得到FCC,ACC fcc = interpolate_fcc(chip->batt_data->fcc_temp_lut, batt_temp); acc = interpolate_acc(chip->batt_data->ibat_acc_lut, batt_temp, iavg_ma); //計算出UUC soc_uuc = ((fcc - acc) * 100) / fcc; if (batt_temp > chip->dt.cfg_low_temp_threshold) soc_uuc = adjust_uuc(chip, soc_uuc); //得到soc_acc soc_acc = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_uuc), (100 - soc_uuc)); soc_final = soc_acc; //這個為上報的soc chip->last_acc = acc;
在這裡獲取last_ocv_uv,溫度;
2.3.7 工作隊列monitor_soc_work
static void monitor_soc_work(struct work_struct *work) { struct qpnp_bms_chip *chip = container_of(work, struct qpnp_bms_chip, monitor_soc_work.work); int rc, new_soc = 0, batt_temp; bms_stay_awake(&chip->vbms_soc_wake_source); //計算上次工作隊列和這次工作隊列的差值 calculate_delta_time(&chip->tm_sec, &chip->delta_time_s); pr_debug("elapsed_time=%dn", chip->delta_time_s); mutex_lock(&chip->last_soc_mutex); //電池不存在,報100%電量 if (!is_battery_present(chip)) { /* if battery is not preset report 100% SOC */ pr_debug("battery gone, reporting 100n"); chip->last_soc_invalid = true; chip->last_soc = -EINVAL; new_soc = 100; } else { //檢測電池電壓 battery_voltage_check(chip); //假設這個qcom,use-voltage-soc節點打開,就使用電壓來計算soc if (chip->dt.cfg_use_voltage_soc) { //通過電壓計算soc calculate_soc_from_voltage(chip); } else { //獲取電池的溫度 rc = get_batt_therm(chip, &batt_temp); if (rc < 0) { pr_err("Unable to read batt temp rc=%d, using default=%dn", rc, BMS_DEFAULT_TEMP); batt_temp = BMS_DEFAULT_TEMP; } if (chip->last_soc_invalid) { chip->last_soc_invalid = false; chip->last_soc = -EINVAL; } //這裡使用last_ocv_uv算出soc的 new_soc = lookup_soc_ocv(chip, chip->last_ocv_uv, batt_temp); /* clamp soc due to BMS hw/sw immaturities */ new_soc = clamp_soc_based_on_voltage(chip, new_soc); //上次的電壓不等於這次的電壓 if (chip->calculated_soc != new_soc) { pr_debug("SOC changed! new_soc=%d prev_soc=%dn", new_soc, chip->calculated_soc); chip->calculated_soc = new_soc; /* * To recalculate the catch-up time, clear it * when SOC changes. */ chip->catch_up_time_sec = 0; if (chip->calculated_soc == 100) /* update last_soc immediately */ report_vm_bms_soc(chip); pr_debug("update bms_psyn"); power_supply_changed(&chip->bms_psy); } else if (chip->last_soc != chip->calculated_soc) { pr_debug("update bms_psyn"); power_supply_changed(&chip->bms_psy); } else { report_vm_bms_soc(chip); } } /* low SOC configuration */ low_soc_check(chip); } /* * schedule the work only if last_soc has not caught up with * the calculated soc or if we are using voltage based soc */ if ((chip->last_soc != chip->calculated_soc) || chip->dt.cfg_use_voltage_soc) schedule_delayed_work(&chip->monitor_soc_work, msecs_to_jiffies(get_calculation_delay_ms(chip))); //復充標誌位 if (chip->reported_soc_in_use && chip->charger_removed_since_full && !chip->charger_reinserted) { /* record the elapsed time after last reported_soc change */ chip->reported_soc_change_sec += chip->delta_time_s; pr_debug("reported_soc_change_sec=%dn", chip->reported_soc_change_sec); /* above the catch up time, calculate new reported_soc */ if (chip->reported_soc_change_sec > UI_SOC_CATCHUP_TIME) { calculate_reported_soc(chip); chip->reported_soc_change_sec = 0; } } mutex_unlock(&chip->last_soc_mutex); bms_relax(&chip->vbms_soc_wake_source); }
上面注釋已經寫的差不多了;看一下上報函數report_vm_bms_soc
:
static int report_vm_bms_soc(struct qpnp_bms_chip *chip) { int soc, soc_change, batt_temp, rc; int time_since_last_change_sec = 0, charge_time_sec = 0; unsigned long last_change_sec; bool charging; soc = chip->calculated_soc; last_change_sec = chip->last_soc_change_sec; //計算上次電量改變的情況 calculate_delta_time(&last_change_sec, &time_since_last_change_sec); //判斷電量是否正在充電 charging = is_battery_charging(chip); pr_debug("charging=%d last_soc=%d last_soc_unbound=%dn", charging, chip->last_soc, chip->last_soc_unbound); /* * account for charge time - limit it to SOC_CATCHUP_SEC to * avoid overflows when charging continues for extended periods */ //正在充電,last_soc是指上一次的最開始開機的soc,與計算出來的soc不一樣,這是第一次,last_soc之後就會改變了,這裡是初始化時間 if (charging && chip->last_soc != -EINVAL) { if (chip->charge_start_tm_sec == 0 || (chip->catch_up_time_sec == 0 && (abs(soc - chip->last_soc) >= MIN_SOC_UUC))) { /* * calculating soc for the first time * after start of chg. Initialize catchup time */ if (abs(soc - chip->last_soc) < MAX_CATCHUP_SOC) chip->catch_up_time_sec = (soc - chip->last_soc) * SOC_CATCHUP_SEC_PER_PERCENT; else chip->catch_up_time_sec = SOC_CATCHUP_SEC_MAX; chip->chg_start_soc = chip->last_soc; if (chip->catch_up_time_sec < 0) chip->catch_up_time_sec = 0; chip->charge_start_tm_sec = last_change_sec; pr_debug("chg_start_soc=%d charge_start_tm_sec=%d catch_up_time_sec=%dn", chip->chg_start_soc, chip->charge_start_tm_sec, chip->catch_up_time_sec); } charge_time_sec = min(SOC_CATCHUP_SEC_MAX, (int)last_change_sec - chip->charge_start_tm_sec); /* end catchup if calculated soc and last soc are same */ if (chip->last_soc == soc) { chip->catch_up_time_sec = 0; chip->chg_start_soc = chip->last_soc; } } //不充電狀態 if (chip->last_soc != -EINVAL) { /* * last_soc < soc ... if we have not been charging at all * since the last time this was called, report previous SoC. * Otherwise, scale and catch up. */ rc = get_batt_therm(chip, &batt_temp); if (rc) batt_temp = BMS_DEFAULT_TEMP; if (chip->last_soc < soc && !charging) soc = chip->last_soc; else if (chip->last_soc < soc && soc != 100) soc = scale_soc_while_chg(chip, charge_time_sec, chip->catch_up_time_sec, soc, chip->chg_start_soc); /* * if the battery is close to cutoff or if the batt_temp * is under the low-temp threshold allow bigger change */ if (bms_wake_active(&chip->vbms_lv_wake_source) || (batt_temp <= chip->dt.cfg_low_temp_threshold)) soc_change = min((int)abs(chip->last_soc - soc), time_since_last_change_sec); else soc_change = min((int)abs(chip->last_soc - soc), time_since_last_change_sec / SOC_CHANGE_PER_SEC); if (chip->last_soc_unbound) { chip->last_soc_unbound = false; } else { /* * if soc have not been unbound by resume, * only change reported SoC by 1. */ soc_change = min(1, soc_change); } if (soc < chip->last_soc && soc != 0) soc = chip->last_soc - soc_change; if (soc > chip->last_soc && soc != 100) soc = chip->last_soc + soc_change; } if (chip->last_soc != soc && !chip->last_soc_unbound) chip->last_soc_change_sec = last_change_sec; /* * Check/update eoc under following condition: * if there is change in soc: * soc != chip->last_soc * during bootup if soc is 100: */ soc = bound_soc(soc); //當電池改變,或者在開機過程中達到100%的電量 if ((soc != chip->last_soc) || (soc == 100)) { chip->last_soc = soc; //在這個函數裡面,如果report_soc==100的話,還是算是不充電的狀態 //當上一次充電還是100,報告已經充滿電了,假設有這個標誌的話,qcom,use-reported-soc,會設置eoc_reported為true,這個在之後復充標誌的時候有用到 check_eoc_condition(chip); //不充電狀態並且設置的復充電量高於0%,這是必備條件 if ((chip->dt.cfg_soc_resume_limit > 0) && !charging) //裡面的復充條件是 check_recharge_condition(chip); } pr_debug("last_soc=%d calculated_soc=%d soc=%d time_since_last_change=%dn", chip->last_soc, chip->calculated_soc, soc, time_since_last_change_sec); /* * Backup the actual ocv (last_ocv_uv) and not the * last_soc-interpolated ocv. This makes sure that * the BMS algorithm always uses the correct ocv and * can catch up on the last_soc (across reboots). * We do not want the algorithm to be based of a wrong * initial OCV. */ backup_ocv_soc(chip, chip->last_ocv_uv, chip->last_soc); //設備樹中的qcom,use-reported-soc if (chip->reported_soc_in_use) //設置reported_soc為100 return prepare_reported_soc(chip); pr_debug("Reported SOC=%dn", chip->last_soc); return chip->last_soc; }
2.4 復充、充電、停止充電邏輯
通過閱讀設備樹知道resume-soc這個節點來控制:
在probe函數中通過宏定SPMI_PROP_READ_OPTIONAL
義:
SPMI_PROP_READ_OPTIONAL(cfg_soc_resume_limit, "resume-soc", rc);
cfg_soc_resume_limit分別在以下這幾個函數中使用過:
- check_recharge_condition函數,最後也是在report_vm_bms_soc函數中使用的
- report_vm_bms_soc函數:為內核執行緒中上報的函數,主要電池控制也在這個函數裡面
- reported_soc_check_status函數
reported_soc_check_status -> qpnp_vm_bms_ext_power_changed //這個是個對調函數,暫時沒看到哪裡的有調到;
2.4.1 復充模式
check_recharge_condition
函數:
static void check_recharge_condition(struct qpnp_bms_chip *chip) { int rc; union power_supply_propval ret = {0,}; int status = get_battery_status(chip); if (chip->last_soc > chip->dt.cfg_soc_resume_limit) return; if (status == POWER_SUPPLY_STATUS_UNKNOWN) { pr_debug("Unable to read battery statusn"); return; } /* Report recharge to charger for SOC based resume of charging */ if ((status != POWER_SUPPLY_STATUS_CHARGING) && chip->eoc_reported) { ret.intval = POWER_SUPPLY_STATUS_CHARGING; rc = chip->batt_psy->set_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, &ret); if (rc < 0) { pr_err("Unable to set battery property rc=%dn", rc); } else { pr_info("soc dropped below resume_soc soc=%d resume_soc=%d, restart chargingn", chip->last_soc, chip->dt.cfg_soc_resume_limit); chip->eoc_reported = false; } } }
如果chip->last_soc高於設置的resume-soc復沖電壓的話, 那麼就return出來;
如果chip->last_soc低於設置的resume-soc復沖電壓的話,就設置電源的充電狀態,並設置set_property給上層;
我們可以看看這個函數在哪裡使用的:
在函數的report_vm_bms_soc上使用的:
if ((soc != chip->last_soc) || (soc == 100)) { chip->last_soc = soc; check_eoc_condition(chip); if ((chip->dt.cfg_soc_resume_limit > 0) && !charging) check_recharge_condition(chip); }
當電壓改變的時候,判斷不在充電模式且設置的復充電容在95%;
2.4.2 停止充電模式
停止充電模式在函數的calculate_reported_soc
函數中:
monitor_soc_work --> calculate_reported_soc
static void calculate_reported_soc(struct qpnp_bms_chip *chip) { union power_supply_propval ret = {0,}; if (chip->last_soc < 0) { pr_debug("last_soc is not ready, returnn"); return; } //這樣就是處於充電模式 if (chip->reported_soc > chip->last_soc) { /*send DISCHARGING status if the reported_soc drops from 100 */ //當充電到100%的時候,設置停止充電的狀態 if (chip->reported_soc == 100) { ret.intval = POWER_SUPPLY_STATUS_DISCHARGING; chip->batt_psy->set_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS, &ret); pr_debug("Report discharging status, reported_soc=%d, last_soc=%dn", chip->reported_soc, chip->last_soc); } /* * reported_soc_delta is used to prevent * the big change in last_soc, * this is not used in high current mode */ if (chip->reported_soc_delta > 0) chip->reported_soc_delta--; if (chip->reported_soc_high_current) chip->reported_soc--; else chip->reported_soc = chip->last_soc + chip->reported_soc_delta; pr_debug("New reported_soc=%d, last_soc is=%dn", chip->reported_soc, chip->last_soc); } else { chip->reported_soc_in_use = false; chip->reported_soc_high_current = false; pr_debug("reported_soc equals last_soc,stop reported_soc processn"); } pr_debug("bms power_supply_changedn"); power_supply_changed(&chip->bms_psy); }
現在我們想一想如何保持將100%的電壓一直保持到95%到復充的狀態呢?有一個非常重要的標誌位charger_removed_since_full
:
這個標誌位是什麼意思呢?字面意思就是當充電器被拔掉的時候是電量滿電的;也就是說電量滿電的之後(是之後),並且充電器沒有拔掉的時候;看一下這個標誌位是會在什麼時候改變的吧:
static void reported_soc_check_status(struct qpnp_bms_chip *chip) { u8 present; present = is_charger_present(chip); pr_debug("usb_present=%dn", present); //當沒有充電狀態,並且false的狀態 if (!present && !chip->charger_removed_since_full) { chip->charger_removed_since_full = true; pr_debug("reported_soc: charger removed since fulln"); return; } if (chip->reported_soc_high_current) { pr_debug("reported_soc in high current mode, returnn"); return; } if ((chip->reported_soc - chip->last_soc) > (100 - chip->dt.cfg_soc_resume_limit + HIGH_CURRENT_TH)) { chip->reported_soc_high_current = true; chip->charger_removed_since_full = true; chip->charger_reinserted = false; pr_debug("reported_soc enters high current moden"); return; } if (present && chip->charger_removed_since_full) { chip->charger_reinserted = true; pr_debug("reported_soc: charger reinsertedn"); } if (!present && chip->charger_removed_since_full) { chip->charger_reinserted = false; pr_debug("reported_soc: charger removed againn"); } }
但這個函數也要在一定條件下才能進來,同樣也需要reported_soc_in_use
標誌位來使用:
if (chip->reported_soc_in_use) reported_soc_check_status(chip);
最開始的時候reported_soc_in_use
已經是true的狀態了,只有兩種情況會改變它,
- 在重新插入的情況下,充完了電;
- 在
calculate_reported_soc
函數中,屬於放電的狀態;
3. 流程圖
