展讯库仑计驱动

sprd_27xx_fgu.c就是展讯SL8541E 库仑计驱动,用来统计电量的;

还是一样,从static int sprdfgu_2723_probe(struct platform_device *pdev)分析开始:


static int sprdfgu_2723_probe(struct platform_device *pdev)
{
	int ret = 0, irq = 0;
	u32 value = 0;
	struct device_node *np = pdev->dev.of_node;
	const struct of_device_id *of_id;
	struct power_supply *ret_ptr = NULL;
	struct power_supply_desc *sprdfgu_desc = NULL;

	reg_map = dev_get_regmap(pdev->dev.parent, NULL);
	if (!reg_map)
		dev_err(&pdev->dev, "%s :NULL regmap for fgu 2723\n",
			__func__);
	sprdfgu_data.fgu_pdata = devm_kzalloc(&pdev->dev,
		sizeof(struct sprdfgu_platform_data),
		GFP_KERNEL);
	of_id = of_match_node(sprdfgu_2723_of_match,
		pdev->dev.of_node);
	if (!of_id) {
		pr_err("get 27xx fgu of device id failed!\n");
		return -ENODEV;
	}
	sprdfgu_data.fgu_pdata->fgu_type = (enum fgu_type)of_id->data;
	SPRD_FGU_DEBUG("fgu_type =%d\n",
		sprdfgu_data.fgu_pdata->fgu_type);

    //ocv-type 为0 时,会使用ocv表来统计开机的初始化电量
	ret = of_property_read_u32(np, "ocv-type",
		&sprdfgu_data.fgu_pdata->ocv_type);
	if (ret)
		dev_err(&pdev->dev, "%s :no ocv-type\n",
			__func__);
	irq = platform_get_irq(pdev, 0);
	if (unlikely(irq <= 0))
		dev_err(&pdev->dev, "no irq resource specified\n");
	sprdfgu_data.fgu_pdata->fgu_irq = irq;
	
	//reg是指库仑计使用的时候的偏移地址
	ret = of_property_read_u32(np, "reg", &value);
	if (ret)
		dev_err(&pdev->dev, "%s :no reg of property for pmic fgu\n",
			__func__);
	sprdfgu_data.fgu_pdata->base_addr = (unsigned long)value;

	sprdfgu_desc = devm_kzalloc(&pdev->dev,
			sizeof(struct power_supply_desc), GFP_KERNEL);
	if (sprdfgu_desc == NULL)
		return -ENOMEM;
		
	//注册power_supply节点
	sprdfgu_desc->name = "sprdfgu";
	sprdfgu_desc->get_property = sprdfgu_power_get_property;
	ret_ptr = power_supply_register(&pdev->dev, sprdfgu_desc, NULL);
	if (IS_ERR(ret_ptr))
		pr_err("register power supply error!\n");
	else
		sprdfgu_data.sprdfgu = ret_ptr;
	sprdfgu_data.dev = &pdev->dev;
	platform_set_drvdata(pdev, &sprdfgu_data);
	ret = sysfs_create_group(&sprdfgu_data.sprdfgu->dev.kobj,
			&sprd_fgu_group);
	if (ret)
		pr_err("failed to create sprd_fgu sysfs device attributes\n");
	INIT_DELAYED_WORK(&sprdfgu_debug_work, sprdfgu_debug_works);
	mutex_init(&sprdfgu_data.lock);
	mutex_init(&sprdfgu_data.track_lock);
	wake_lock_init(&(sprdfgu_data.low_power_lock), WAKE_LOCK_SUSPEND,
		       "sprdfgu_powerlow_lock");

	sprdfgu_data.is_track = 1;

	INIT_DELAYED_WORK(&sprdfgu_data.fgu_qmax_work,
			  sprdfgu_qmax_works);
	sprdfgu_data.fgu_wqueue =
		create_workqueue("sprdfgu_monitor");
	if (!sprdfgu_data.fgu_wqueue)
		sprdfgu_data.is_track = 0;

	sprdfgu_data.track.vol_buff = devm_kzalloc(&pdev->dev,
		sizeof(int) * CUR_VOL_BUFF_LEN,
		GFP_KERNEL);
	if (!sprdfgu_data.track.vol_buff)
		sprdfgu_data.is_track = 0;

	sprdfgu_data.track.cur_buff = devm_kzalloc(&pdev->dev,
		sizeof(int) * CUR_VOL_BUFF_LEN,
		GFP_KERNEL);
	if (!sprdfgu_data.track.cur_buff)
		sprdfgu_data.is_track = 0;

	SPRD_FGU_DEBUG("sprdfgu_2723_probe ok\n");
	return ret;
}

其中这里有三个工作队列:

  1. sprdfgu_debug_works工作队列
  2. sprdfgu_qmax_works工作队列,电量自学习工作队列
  3. sprdfgu_int_init函数中的sprdfgu_irq_works函数

1、 函数为空函数,则不予以处理和分析

2、电容自适应函数:

主要在sprdfgu_read_soc函数中读取:

并且在sprdfgu_qmax_update_monitor函数中当

当电池工作电流足够小的时候电池工作电压近似等于电池开路电压,如果有准确的电量表我们就能准确的定位电池容量我们记录为 C1,将这时库仑计计数值容量记录为Q1。充电满后电池真实容量接近 100%,这里我们将库仑计计数值记录为 Q2。根据以上几个数据我们就可以得到电池真实有效容量:

Qmax = (Q2 – Q1) * 100/(100 –C1)

我们一般采用低电量开始作为初始采集点,并严格规定了环境温度即电流、电压条件,只有符合规定的容量自学习我们才认为是有效的自学习。 自学习功能被抽象为 qmax,结构如下, 各种限制条件需要在初始化的时候写入。

struct sprdfgu_qmax {
    int state; //自学习状态
    int cur_cnom; //当前容量
    int s_clbcnt; //开始自学习的库仑计计数
    int s_soc; //自学习初始点百分比
    int s_vol; //自学习初始电压限制
    int s_cur; //自学习初始电流限制
    int e_vol; //自学习结束电压条件
    int e_cur; //自学习结束电流条件
    int force_s_flag; //应用层控制启动标志位
    int force_s_soc; //应用层直接写入容量值
    uint32_t timeout; //设置的超时门限
    __s64 s_time; //开始自学习的时间点
};

2、 电量保存与回读

自学习状态 State 有 5 种, DONE 状态是学习完成状态, 在 DONE 状态会将学习完成的容量值写入/productinfo/.battery_file 这个文件。 productinfo 分区掉电不擦除,重启后再 INIT状态会回读这个数据作为容量基数进行电量积分,并将自学习状态切换到 IDLE 状态。 如果满足了自学习条件就进入了 QMAX_UPDATING, UPDATING 状态下会检查自学习后容量, 如果容量检查失效则恢复 IDLE状态,需要重新开始学习。 如果手机单体没有完成过自学习则使用默认的容量配置。

enum QMAX_STATE {
QMAX_INIT, //开机默认状态
QMAX_IDLE, //自学习准备状态
QMAX_UPDATING, //电量需要更新
QMAX_DONE, //自学习完成
QMAX_ERR, //异常
};

展讯库仑计驱动_第1张图片

sys/class/power_supply/sprdfgu/ qmax_force_start
应用层主动开启自学习功能
sys/class/power_supply/sprdfgu/ qmax_force_s_soc
应用层设置开启自学习时的容量
sys/class/power_supply/sprdfgu/ qmax_state
读取当前自学习状态
sys/class/power_supply/sprdfgu/ qmax_s_vol
设置开启自学习的电压条件
sys/class/power_supply/sprdfgu/ qmax_s_cur10
设置开启自学习的电流条件
sys/class/power_supply/sprdfgu/ qmax_e_vol
设置自学习停止电压条件
sys/class/power_supply/sprdfgu/ qmax_e_cur
设置自学习停止电流条件
sys/class/power_supply/sprdfgu/ cnom
读取当前积分容量基数
sys/class/power_supply/sprdfgu/ saved_cnom
应用层直接写数保存容量

3. 本质上就是用sprdfgu_irq_works计算电量的


static void sprdfgu_irq_works(struct work_struct *work)
{
	uint32_t cur_ocv;
	uint32_t adc;
	int cur_soc;

	wake_lock_timeout(&(sprdfgu_data.low_power_lock), 2 * HZ);

	SPRD_FGU_DEBUG("%s......0x%x.cur vol = %d\n",
		__func__, sprdfgu_data.int_status,
		sprdfgu_read_vbat_vol());

	if (sprdfgu_data.int_status & BIT_VOLT_HIGH_INT)
		power_supply_changed(sprdfgu_data.sprdfgu);

	if (sprdfgu_data.int_status & BIT_VOLT_LOW_INT) {
		cur_soc = sprdfgu_read_soc();
		mutex_lock(&sprdfgu_data.lock);

		cur_ocv = sprdfgu_read_vbat_ocv();
		if (cur_ocv <= sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("shutdown_vol .\n");
			sprdfgu_soc_adjust(0);
		} else if (cur_ocv <= sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("alm_vol %d.\n", cur_soc);
			if (cur_soc > sprdfgu_data.warning_cap) {
				sprdfgu_soc_adjust(sprdfgu_data.warning_cap);
			} else if (cur_soc <= 0) {
				sprdfgu_soc_adjust(sprdfgu_vol2capacity
						   (cur_ocv));
			}
			if (!sprdfgu_data.adp_status) {
				adc = sprdfgu_vol2adc_mv
					(sprdfgu_data.shutdown_vol);
				fgu_adi_write(REG_FGU_LOW_OVER, adc & 0xFFFF,
					      ~0);
			}
		} else {
			SPRD_FGU_DEBUG("need add\n");
		}
		mutex_unlock(&sprdfgu_data.lock);
	}
}

首先第一个函数sprdfgu_read_vbat_vol

读取vbat上的电压:

uint32_t sprdfgu_read_vbat_vol(void)
{
	u32 cur_vol_raw;
	uint32_t temp;

	cur_vol_raw = sprdfgu_reg_get(REG_FGU_VOLT_VAL);
	temp = sprdfgu_adc2vol_mv(cur_vol_raw);
	return temp;
}

第二个函数sprdfgu_read_soc函数,也就是电量计的核心:

int sprdfgu_read_soc(void)
{
	int cur_cc, cc_delta, capcity_delta, temp;
	uint32_t cur_ocv;
	int cur;
	union power_supply_propval val;

	sprdfgu_track();
	mutex_lock(&sprdfgu_data.lock);

	sprdfgu_qmax_update_monitor();

	cur_cc = sprdfgu_clbcnt_get();
	cc_delta = cur_cc - sprdfgu_data.init_clbcnt;

	/* 0.1mah */
	temp = DIV_ROUND_CLOSEST(cc_delta,
		(360 * FGU_CUR_SAMPLE_HZ));
	temp = sprdfgu_adc2cur_ma(temp);

	capcity_delta = DIV_ROUND_CLOSEST(temp * 100,
		sprdfgu_data.cur_cnom);

	SPRD_FGU_DEBUG("d cap %d,cnom %d,g %dmAh(0.1mA),init_cc:%d\n",
		capcity_delta, sprdfgu_data.cur_cnom,
		temp, sprdfgu_data.init_clbcnt);

	capcity_delta += sprdfgu_data.init_cap;

	SPRD_FGU_DEBUG("soc %d,init_cap %d,cap:%d\n",
		  (capcity_delta + 9) / 10, sprdfgu_data.init_cap,
		  capcity_delta);

	cur = sprdfgu_read_batcurrent();
	cur_ocv = sprdfgu_read_vbat_ocv();
	if ((cur_ocv >= sprdfgu_data.bat_full_vol)
	    && (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
		SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
		if (capcity_delta < FULL_CAP) {
			capcity_delta = FULL_CAP;
			sprdfgu_soc_adjust(FULL_CAP);
		}
	}

	if (capcity_delta > FULL_CAP) {
		capcity_delta = FULL_CAP;
		sprdfgu_soc_adjust(FULL_CAP);
	}

	sprdpsy_get_property("battery",
				POWER_SUPPLY_PROP_STATUS,
				&val);
	if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
		if (capcity_delta <= sprdfgu_data.warning_cap
		    && cur_ocv > sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc low...\n");
			capcity_delta = sprdfgu_data.warning_cap + 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta <= 0
		    && cur_ocv > sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("soc 0...\n");
			capcity_delta = 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("vol 0...\n");
			capcity_delta = 0;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta > sprdfgu_data.warning_cap
			   && cur_ocv < sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc high...\n");
			sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
			capcity_delta = sprdfgu_vol2capacity(cur_ocv);
		}
	}

	if (capcity_delta < 0)
		capcity_delta = 0;

	sprdfgu_record_rawsoc(capcity_delta);

#ifdef SPRDFGU_TEMP_COMP_SOC
	{
		temp = sprdbat_read_temp();
		capcity_delta =
			sprdfgu_temp_comp_soc(capcity_delta, temp / 10);
	}
#endif

	if (sprdfgu_read_vbat_vol() < sprdfgu_data.pdata->soft_vbat_uvlo) {
		SPRD_FGU_DEBUG("trigger soft uvlo vol 0...\n");
		capcity_delta = 0;
		sprdfgu_soc_adjust(capcity_delta);
	}

	mutex_unlock(&sprdfgu_data.lock);
	return capcity_delta;
}
  1. 首先通过sprdfgu_track函数来定位电池开路电压;

这里首先介绍电池内阻自适应:

电池开路电压 OCV 是表征电池容量的最关键因素,在电量显示、电池容量自学习的时候都可能用到电池开路电压来定位百分比。因此我们必须找到一个方法定位电池开路电压。

FGU 可以获取到电池工作状态下的电流 I 和电压 VBAT,根据欧姆定律只要我们得到电池内阻 R 就可以计算出电池开路电压了: OCV = VBAT – I *R。 电池内阻一般都是动态变化的,不同容量不同温度都会有不同的电池内阻,所以需要一个动态调整内阻的算法来保证内阻的准确性, 平台提供了一套电池内阻自适应的算法:提取硬件 buff 中的一组电流电压值(共 8 个)并将这 8 个数据按大小排序,找到最大的压降 DeltaV 和电流变化DeltaI,根据欧姆定律 R =DeltaV/DeltaI;使用计算出来的 R 就能得到实时 OCV 了。

电池内阻自学习通过 sprdfgu_tracking 来记录学习状态,成员包括自学习时的温度、平均电流、平均电压、电流 buff、电压 buff、最小电流、 最小电压等等。 Chargework 会调用 static void sprdfgu_track(void)来更新 sprdfgu_data.track,使用的时候通过接口 static struct sprdfgu_tracking sprdfgu_get_track_data(void);获取到实时的电池工作状态信息sprdfgu_data.track。

struct sprdfgu_tracking {
__s64 r_time;
__s64 query_time; //结构更新时间间隔
int rint; //电池内阻自适应出来的内阻
int r_temp; //获取电池内阻时候判断采样温度
int c_temp; //容量自适应的时候判断采样的温度
int r_vol; //
int relax_vol; //最接近电池开路电压
int relax_cur; //最接近零点的电流
int ave_vol; //平均电压
int ave_cur; //平均电流
int *cur_buff; //电流 buff
int *vol_buff; //电压 buff
};

sprdfgu_track则是算出上述数据的函数;

  1. sprdfgu_qmax_update_monitor则是电池自学习的过程,所以不再叙述;

  2. sprdfgu_clbcnt_get获取记录电流积分数据;

  3. 获取当前 Coulomb Counter 计数量下次轮询调用的时候可以计算出两 次 Coulomb Counter 统计的差值从而得到库仑量变化 cc_delta 。DIV_ROUND_CLOSEST(cc_delta, (3600 * 2));

  4. DIV_ROUND_CLOSEST(mah * 100, sprdfgu_data.pdata->cnom);通过变化的 mah 计算出与总容量 cnom 的比值,从而得到变化百分比。

  5. capcity_delta += sprdfgu_data.init_cap;计算出这次电量加上初始化的电量;

  6. sprdfgu_read_batcurrent读取现在电池vbat的容量;

  7. sprdfgu_read_vbat_ocv读取开路电压的ocv;

  8. 对一些ocv的条件判定,具体看代码:

    //开路电压ocv大于电池满电的状态并且充电电流小于充电截止电流
    if ((cur_ocv >= sprdfgu_data.bat_full_vol)
	    && (abs(cur) <= sprdfgu_data.pdata->chg_end_cur)) {
		SPRD_FGU_DEBUG("cur_ocv %d\n", cur_ocv);
		if (capcity_delta < FULL_CAP) {
			capcity_delta = FULL_CAP;
			sprdfgu_soc_adjust(FULL_CAP);
		}
	}
    
    //大于100,电量则设置100
	if (capcity_delta > FULL_CAP) {
		capcity_delta = FULL_CAP;
		sprdfgu_soc_adjust(FULL_CAP);
	}

	sprdpsy_get_property("battery",
				POWER_SUPPLY_PROP_STATUS,
				&val);
	//不在充电状态的话,
	if (val.intval != POWER_SUPPLY_STATUS_CHARGING) {
		if (capcity_delta <= sprdfgu_data.warning_cap
		    && cur_ocv > sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc low...\n");
			capcity_delta = sprdfgu_data.warning_cap + 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta <= 0
		    && cur_ocv > sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("soc 0...\n");
			capcity_delta = 10;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (cur_ocv < sprdfgu_data.shutdown_vol) {
			SPRD_FGU_DEBUG("vol 0...\n");
			capcity_delta = 0;
			sprdfgu_soc_adjust(capcity_delta);
		} else if (capcity_delta > sprdfgu_data.warning_cap
			   && cur_ocv < sprdfgu_data.pdata->alm_vol) {
			SPRD_FGU_DEBUG("soc high...\n");
			sprdfgu_soc_adjust(sprdfgu_vol2capacity(cur_ocv));
			capcity_delta = sprdfgu_vol2capacity(cur_ocv);
		}
	}

	if (capcity_delta < 0)
		capcity_delta = 0;
  1. sprdfgu_record_rawsoc记录soc上raw;

另外:

  • sprdfgu_load_uicap读取ui的电量,本身由rtc记录。
  • sprdfgu_load_rawsoc则是记录实际电量;
  • sprdfgu_soc_adjust调整电量。输入为电量值

你可能感兴趣的:(展讯库仑计驱动)