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


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;
		}
	}
	
	//請求一些相應的中斷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);

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

在這里獲取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節點打開,就使用電壓來計算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=%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;
			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=%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);
		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=%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-soc
	if (chip->reported_soc_in_use)
	    //設置reported_soc為100
		return 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)
{   
    //因為剛剛標志位被設置為false
	if (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--;
		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);
}

現在我們想一想如何保持將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. 流程圖


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM