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=%d\n", 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 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;}}//请求一些相应的中断BMSrc = 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);//power_supply注册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;
}

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 OCVrc = read_and_update_ocv(chip, batt_temp, true);..........//读关机保存的soc和last_soc_uvrc = read_shutdown_ocv_soc(chip);//这里判断是使用估计soc还是估值soc。如果chip->warm_reset 为真if (chip->warm_reset) {if (chip->shutdown_soc_invalid) { //这个是dtsi的一个配置选项,若没有配置,//则不使用关机socest_ocv = estimate_ocv(chip); //估值socchip->last_ocv_uv = est_ocv;} else {chip->last_ocv_uv = chip->shutdown_ocv;//使用关机的soc和ocvpr_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 OCVrc = 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_uvrc = qpnp_vbat_sns_comp_result(chip->vadc_dev, &uv, is_pon_ocv); //根据IC的类型,进行温度补偿//从寄存器中读到储存的soc和ocvread_shutdown_ocv_socrc = 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_cutoffsoc_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);elseiavg_ma = chip->current_now / 1000;//查表得到FCC,ACCfcc = interpolate_fcc(chip->batt_data->fcc_temp_lut,batt_temp);acc = interpolate_acc(chip->batt_data->ibat_acc_lut,batt_temp, iavg_ma);//计算出UUCsoc_uuc = ((fcc - acc) * 100) / fcc;if (batt_temp > chip->dt.cfg_low_temp_threshold)soc_uuc = adjust_uuc(chip, soc_uuc);//得到soc_accsoc_acc = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_uuc),(100 - soc_uuc));soc_final = soc_acc;   //这个为上报的socchip->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=%d\n", 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 100\n");chip->last_soc_invalid = true;chip->last_soc = -EINVAL;new_soc = 100;} else {//检测电池电压battery_voltage_check(chip);//假设这个qcom,use-voltage-soc节点打开,就使用电压来计算socif (chip->dt.cfg_use_voltage_soc) {//通过电压计算soccalculate_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=%d\n",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=%d\n",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_psy\n");power_supply_changed(&chip->bms_psy);} else if (chip->last_soc != chip->calculated_soc) {pr_debug("update bms_psy\n");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=%d\n",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

last_soc其实与calcaulte_soc差不多,但是last_soc也包括了上次开机前的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=%d\n",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之后就会改变了,这里是初始化时间//这段大概是BMS算法机制的东西,说明充电时间-将其限制在SOC_catchup_sec,以避免充电持续较长时间时溢出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;elsechip->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=%d\n",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;//如果这次的soc改变变大。并且不处于充电模式,soc跟着last_soc一样,就是排除不充电且电量变大的情况if (chip->last_soc < soc && !charging)soc = chip->last_soc;else if (chip->last_soc < soc && soc != 100)//缩小计算的soc与last_soc的进度soc = scale_soc_while_chg(chip, charge_time_sec,chip->catch_up_time_sec,soc, chip->chg_start_soc);//如果电池接近切断,或者电池温度低于低温阈值,允许更大的变化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);elsesoc_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=%d\n",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-socif (chip->reported_soc_in_use)//设置reported_soc为100return prepare_reported_soc(chip);pr_debug("Reported SOC=%d\n", 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 复充模式

以下这些函数都只可能在达到100%的时候才会进入的:

  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%;

  1. check_eoc_condition函数中:
static void check_eoc_condition(struct qpnp_bms_chip *chip)
{int rc;int status = get_battery_status(chip);union power_supply_propval ret = {0,};if (status == POWER_SUPPLY_STATUS_UNKNOWN) {pr_err("Unable to read battery status\n");return;}/** Check battery status:* if last_soc is 100 and battery status is still charging* reset ocv_at_100 and force reporting of eoc to charger.*///检查电池状态,假设上次电池的last_soc为100,并且还在充电状态强制报告eoc到充电器上if ((chip->last_soc == 100) &&(status == POWER_SUPPLY_STATUS_CHARGING))chip->ocv_at_100 = -EINVAL;/** Store the OCV value at 100. If the new ocv is greater than* ocv_at_100 (battery settles), update ocv_at_100. Else* if the SOC drops, reset ocv_at_100.*/if (chip->ocv_at_100 == -EINVAL) {//假设上次last_soc为100,报告复充条件符合,第二次达到100%进来的if (chip->last_soc == 100) {if (chip->dt.cfg_report_charger_eoc) {//上报充满电的状态rc = report_eoc(chip);if (!rc) {/** update ocv_at_100 only if EOC is* reported successfully.*/chip->ocv_at_100 = chip->last_ocv_uv;pr_debug("Battery FULL\n");} else {pr_err("Unable to report eoc rc=%d\n",rc);chip->ocv_at_100 = -EINVAL;}}if (chip->dt.cfg_use_reported_soc) {/* begin reported_soc process */chip->reported_soc_in_use = true;chip->charger_removed_since_full = false;chip->charger_reinserted = false;chip->reported_soc = 100;pr_debug("Begin reported_soc process\n");}}} else {if (chip->last_ocv_uv >= chip->ocv_at_100) {pr_debug("new_ocv(%d) > ocv_at_100(%d) maintaining SOC to 100\n",chip->last_ocv_uv, chip->ocv_at_100);chip->ocv_at_100 = chip->last_ocv_uv;chip->last_soc = 100;} else if (chip->last_soc != 100) {/** Report that the battery is discharging.* This gets called once when the SOC falls* below 100.*/if (chip->reported_soc_in_use&& chip->reported_soc == 100) {pr_debug("reported_soc=100, last_soc=%d, do not send DISCHARING status\n",chip->last_soc);} else {ret.intval = POWER_SUPPLY_STATUS_DISCHARGING;chip->batt_psy->set_property(chip->batt_psy,POWER_SUPPLY_PROP_STATUS, &ret);}pr_debug("SOC dropped (%d) discarding ocv_at_100\n",chip->last_soc);chip->ocv_at_100 = -EINVAL;}}
}

设置四个标志位:

  • reported_soc_in_use=true
  • charger_removed_since_full=false
  • charger_reinserted=false
  • reported_soc=100 //这个标志位在下文的停止充电中有使用到

这四个标志位之后会在report_vm_bms_soc上的prepare_reported_soc函数上使用;

  1. prepare_reported_soc函数
static int prepare_reported_soc(struct qpnp_bms_chip *chip)
{   //因为刚刚标志位被设置为falseif (chip->charger_removed_since_full == false) {/** charger is not removed since full,* keep reported_soc as 100 and calculate the delta soc* between reported_soc and last_soc*/chip->reported_soc = 100;chip->reported_soc_delta = 100 - chip->last_soc;pr_debug("Keep at reported_soc 100, reported_soc_delta=%d, last_soc=%d\n",chip->reported_soc_delta,chip->last_soc);} else {/* charger is removed since full */if (chip->charger_reinserted) {/** charger reinserted, keep the reported_soc* until it equals to last_soc.*/if (chip->reported_soc == chip->last_soc) {chip->reported_soc_in_use = false;chip->reported_soc_high_current = false;pr_debug("reported_soc equals to last_soc, stop reported_soc process\n");}chip->reported_soc_change_sec = 0;}}pr_debug("Reporting reported_soc=%d, last_soc=%d\n",chip->reported_soc, chip->last_soc);return chip->reported_soc;
}

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--;elsechip->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);
}

现在我们想一想如何保持将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=%d\n", present);//当没有充电状态,并且false的状态if (!present && !chip->charger_removed_since_full) {chip->charger_removed_since_full = true;pr_debug("reported_soc: charger removed since full\n");return;}if (chip->reported_soc_high_current) {pr_debug("reported_soc in high current mode, return\n");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 mode\n");return;}if (present && chip->charger_removed_since_full) {chip->charger_reinserted = true;pr_debug("reported_soc: charger reinserted\n");}if (!present && chip->charger_removed_since_full) {chip->charger_reinserted = false;pr_debug("reported_soc: charger removed again\n");}
}

但这个函数也要在一定条件下才能进来,同样也需要reported_soc_in_use标志位来使用:

if (chip->reported_soc_in_use)reported_soc_check_status(chip);

最开始的时候reported_soc_in_use已经是true的状态了,只有两种情况会改变它,

  1. 在重新插入的情况下,充完了电;
  2. calculate_reported_soc函数中,属于放电的状态;

回到顶部

3. 流程图

高通电源管理qpnp-vm-bms驱动-电量计相关推荐

  1. 高通Audio中ASOC的codec驱动(二)

    继上一篇文章:高通Audio中ASOC的machine驱动(一) ASOC的出现是为了让codec独立于CPU,减少和CPU之间的耦合,这样同一个codec驱动就无需修改就可以匹配任何一款平台. 在M ...

  2. 【高通SDM660平台】(1) --- Camera 驱动 Bringup Guide

    [高通SDM660平台]Camera 驱动 Bringup Guide 一.Kernel 代码移植 1. DTS 文件配置 1.1 sdm660.dtsi 1.2 sdm660-camera.dtsi ...

  3. 高通Audio中ASOC的machine驱动

    高通Audio中ASOC的machine驱动 233333发表于linux驱动个人学习已订阅 1.1K ASoC被分为Machine.Platform和Codec三大部分,其中的Machine驱动负责 ...

  4. 高通Audio中ASOC的machine驱动(一) ---mark 详细条理

    高通Audio中ASOC的machine驱动(一) 转载原文:https://www.cnblogs.com/linhaostudy/p/8419231.html 阅读目录 1. 注册Platform ...

  5. 高通SDX55平台:R8168 PHY驱动适配

    高通SDX55平台 R8168 PHY驱动适配 1. SDX55 CPE应用场景 高通5G平台SDX55支持5G独立组网(SA)和非独立组网(NSA)两种网络架构,同时兼容LTE和WCDMA制式,拥有 ...

  6. TM034XVZP01 mipi屏 高通8909平台lk和kernel驱动 基于ili9881驱动

    效果图如上. 所需的时序文件链接 https://download.csdn.net/download/jxhln/11120468 大家可以下载参考 上述是最终的成果,该文章只着重讲述lk阶段的调试 ...

  7. 高通Audio中ASOC的machine驱动(一)

    ASoC被分为Machine.Platform和Codec三大部分,其中的Machine驱动负责Platform和Codec之间的耦合以及部分和设备或板子特定的代码,再次引用上一节的内容:Machin ...

  8. 电源管理芯片:LED驱动电源芯片的计划及面积

    LED主要分为:LED显示屏.景观LED照明.室内LED照明:不同使用场景的LED驱动芯片有不同的需求.依据电源管理芯片的统计,2018年LED驱动IC商场规模为38亿美元,其间亚洲商场份额最高,占比 ...

  9. android_驱动_qcom_【高通SDM660平台】(1) ---Bringup Guide

    [高通SDM660平台]Camera 驱动 Bringup Guide 一.Kernel 代码移植 1. DTS 文件配置 1.1 sdm660.dtsi 1.2 sdm660-camera.dtsi ...

最新文章

  1. 维护窗口和停机时间 可用率99.99%
  2. 介绍自己的一个Android插桩热修复框架项目QuickPatch
  3. python读取上一级文件夹下的图片
  4. 聚簇索引和非聚簇索引详解
  5. 2019年5G阵营 iPhone可能掉队了!
  6. CString的成员函数用法大全
  7. python库build的那堆事儿之彩笔的划水历程
  8. eclipse画UML图
  9. 观察者模式及其应用场景
  10. html根据出生日期计算星座,星座测算.html
  11. 基于Unity3D的黄金矿工
  12. 8600 系列 VSM 用于磁性微型机器人以及韦根线研究
  13. arduino使用oled代码_【教程】在ESP32上使用E32433T LoRa模块
  14. python之panda模块1
  15. svchost.exe占用cpu100 的解决方法
  16. 存储器PCB布线技巧
  17. DNA存储:这些公司正在开启数据存储的未来
  18. 分享4款非常实用的黑科技APP,使用过后才发现离不开它们了
  19. 小学计算机打字比赛简报,育瑞实验小学打字比赛活动通知
  20. 使用MyBatis实现增删改查遇到的异常解决方法

热门文章

  1. docker-compose安装es、eshead、分词器HanLP v7.3.2
  2. ceph同步数据过程OSD进程异常退出记录
  3. Squid和Apache中的max-age与Expires的分别
  4. tbc自建服务器,魔兽世界:TBC怀旧服要来了?暴雪掏出PTR服务器,却改了个寂寞...
  5. nin神经网络_深度学习基础(三)NIN_Network In Network
  6. 机器人炒菜感想_机器人炒菜 会炒600多道菜
  7. 拼多多自己没货源怎么弄?有哪些渠道?
  8. Java IDEA中输出语句变红报错分析
  9. 【软考笔记】2. 操作系统基本原理
  10. python装饰器使用教学,Python教程|简单上手Python中装饰器的使用