高通電源管理qpnp-vm-bms驅動

来源:https://www.cnblogs.com/linhaostudy/archive/2019/09/27/11598331.html
-Advertisement-
Play Games

1. compatible節點: qpnp vm bms.c使用來控制電池曲線的和BMS功能的,其compatible節點是"qcom,qpnp vm bms" 2. probe函數: qpnp_vm_bms_probe函數如下: 2.1 parse_bms_dt_properties()函數 在這 ...


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的值
    rc = bms_get_adc(chip, spmi);
    if (rc < 0) {
        pr_err("Failed to get adc rc=%d\n", rc);
        return rc;
    }

    //指向revision外圍節點的phandle
    revid_dev_node = of_parse_phandle(spmi->dev.of_node,
                        "qcom,pmic-revid", 0);
    if (!revid_dev_node) {
        pr_err("Missing qcom,pmic-revid property\n");
        return -EINVAL;
    }
    
    //返回pmic的修訂信息
    chip->revid_data = get_revid_data(revid_dev_node);
    if (IS_ERR(chip->revid_data)) {
        pr_err("revid error rc = %ld\n", 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=%d\n", rc);
        return rc;
    }
    chip->warm_reset = !!rc;

    //解析spmi設備的內容,並且在其中尋找它的中斷基地址
    rc = parse_spmi_dt_properties(chip, spmi);
    if (rc) {
        pr_err("Error registering spmi resource rc=%d\n", 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 = %d\n", 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=%d\n", 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=%d\n", rc);
        return rc;
    }

    pr_debug("BMS version: %hhu.%hhu\n",
            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 %d\n", rc);
        goto fail_init;
    }

    /* set the battery profile */
    //設置電池的配置文件
    rc = config_battery_data(chip->batt_data);
    if (rc) {
        pr_err("Unable to config battery data %d\n", 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 =%d\n",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=%d\n",
                    rc);
            goto fail_setup;
        }
    }
    
    //請求一些相應的中斷BMS
    rc = bms_request_irqs(chip);
    if (rc) {
        pr_err("error requesting bms irqs, rc = %d\n", 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=%d\n", 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=%d\n",
                    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);

    rc = power_supply_register(chip->dev, &chip->bms_psy);
    if (rc < 0) {
        pr_err("power_supply_register bms failed rc = %d\n", 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=%d\n",
                            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 dir\n");

    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 file\n");

        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 file\n");

        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 file\n");
    }
    
    
    //這裡啟動工作隊列,絕大部分的工作內容都是在這裡完成的
    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=%d\n",
                    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 = %lld\n", 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 batterydata\n");
            return -EINVAL;
    }

    batt_data = devm_kzalloc(chip->dev,
            sizeof(struct bms_battery_data), GFP_KERNEL);
    if (!batt_data) {
        pr_err("Could not alloc battery data\n");
        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 failed\n");
        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 loaded\n");
        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 present\n");
        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;
}

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機制,作為服務一直運行。

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 = %d\n", __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;

2.3.7 工作隊列monitor_soc_work

static void monitor_soc_work(struct work_struct *work)
    calculate_delta_time(&chip->tm_sec, &chip->delta_time_s);
    rc = get_batt_therm(chip, &batt_temp);
    new_soc = lookup_soc_ocv(chip, chip->last_ocv_uv,batt_temp);
    new_soc = clamp_soc_based_on_voltage(chip, new_soc);
    report_vm_bms_soc(chip);//上報事件,上層得到消息,調用qpnp_vm_bms_power_get_property,獲取相關的屬性,計算出
                last_ocv_uv,並通過qpnp_vm_bms_power_set_property方法,設置last_ocv_uv,並啟動monitor_soc_work。

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 復充模式

  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 status\n");
        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=%d\n", rc);
        } else {
            pr_info("soc dropped below resume_soc soc=%d resume_soc=%d, restart charging\n",
                    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, return\n");
        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=%d\n",
                    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=%d\n",
                    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 process\n");
    }
    pr_debug("bms power_supply_changed\n");
    power_supply_changed(&chip->bms_psy);
}

我們如何知道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)));
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Nmap包含四項基本功能: 主機探測 (Host Discovery) 埠掃描 (Port Scanning) 版本檢測 (Version Detection) 操作系統偵測 (Operating System Detection) 支持探測腳本的編寫 Nmap在實際中應用場合如下: 通過對設備或 ...
  • 待補 ...
  • 待補 ...
  • CPU 架構SMP/NUMA,調優 SMP:全稱是“對稱多處理”(Symmetrical Multi Processing)技術 。 是指在一個電腦上彙集了一組處理器(多CPU),各CPU之間共用記憶體以及匯流排。 弱點:CPU變多後,但是記憶體和記憶體控制器只有一個,CPU是通過記憶體控制器訪問記憶體的,所 ...
  • 數據備份[root@Server ~]# /usr/sbin/slapcat > /root/ldapdbak.ldif第二種 ldapsearch -x -b 'dc=com,dc=cn' > ldapbackup.ldif停止服務[root@Server ~]# systemctl stop s ...
  • scp命令是基於ssh命令的,要使scp成功虛擬機必須開啟server scp命令詳解: -1 強制scp命令使用協議ssh1 -2 強制scp命令使用協議ssh2 -4 強制scp命令只使用IPv4定址 -6 強制scp命令只使用IPv6定址 -B 使用批處理模式(傳輸過程中不詢問傳輸口令或短語) ...
  • GPIO(General-purpose input/output 通用目的輸入/輸出埠) 電壓(A模擬量)與電平(D數字量) GPIO 8種工作模式(輸入四種、輸出四種) 1、GPIO_Mode_AIN 模擬輸入 2、GPIO_Mode_IN_FLOATING 浮空輸入 3、GPIO_Mode_ ...
  • vsftpd上傳文件大小為0(主動模式) 主動模式與被動模式 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...