/* bit definitions of Measurement Off Time Register */ #define MEAS_OFF_SLEEP_EN (1 << 1)
/* bit definitions of GPADC Bias Current 2 Register */ #define GPBIAS2_GPADC1_SET (2 << 4) /* GPADC1 Bias Current value in uA unit */ #define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1)
/* bit definitions of GPADC Misc 1 Register */ #define GPMISC1_GPADC_EN (1 << 0)
/* bit definitions of Charger Control 6 Register */ #define CC6_BAT_DET_GPADC1 1
/* bit definitions of Coulomb Counter Reading Register */ #define CCNT_AVG_SEL (4 << 3)
/* bit definitions of RTC miscellaneous Register1 */ #define RTC_SOC_5LSB (0x1F << 3)
/* bit definitions of RTC Register1 */ #define RTC_SOC_3MSB (0x7)
/* bit definitions of Power up Log register */ #define BAT_WU_LOG (1<<6)
struct power_supply *battery; struct mutex lock; int status; int irq_cc; int irq_batt; int max_capacity; int resistor; /* Battery Internal Resistor */ int last_capacity; int start_soc; unsigned present:1; unsigned temp_type:1; /* TINT or TBAT */
};
/* * register 1 bit[7:0] -- bit[11:4] of measured value of voltage * register 0 bit[3:0] -- bit[3:0] of measured value of voltage
*/ staticint measure_12bit_voltage(struct pm860x_battery_info *info, int offset, int *data)
{ unsignedchar buf[2]; int ret;
ret = pm860x_bulk_read(info->i2c, offset, 2, buf); if (ret < 0) return ret;
staticint measure_vbatt(struct pm860x_battery_info *info, int state, int *data)
{ unsignedchar buf[5]; int ret;
switch (state) { case OCV_MODE_ACTIVE:
ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data); if (ret) return ret; /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */
*data *= 3; break; case OCV_MODE_SLEEP: /* * voltage value of VBATT in sleep mode is saved in different * registers. * bit[11:10] -- bit[7:6] of LDO9(0x18) * bit[9:8] -- bit[7:6] of LDO8(0x17) * bit[7:6] -- bit[7:6] of LDO7(0x16) * bit[5:4] -- bit[7:6] of LDO6(0x15) * bit[3:0] -- bit[7:4] of LDO5(0x14)
*/
ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf); if (ret < 0) return ret;
ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8)
| ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4)
| (buf[0] >> 4); /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */
*data = ((*data & 0xff) * 27 * 25) >> 9; break; default: return -EINVAL;
} return 0;
}
/* * Return value is signed data. * Negative value means discharging, and positive value means charging.
*/ staticint measure_current(struct pm860x_battery_info *info, int *data)
{ unsignedchar buf[2]; short s; int ret;
ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf); if (ret < 0) return ret;
s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); /* current(mA) = value * 0.125 */
*data = s >> 3; return 0;
}
staticint set_charger_current(struct pm860x_battery_info *info, int data, int *old)
{ int ret;
if (data < 50 || data > 1600 || !old) return -EINVAL;
staticint calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt)
{ unsignedint sum; int ret; int data;
ret = read_ccnt(info, CCNT_POS1, &data); if (ret) goto out;
sum = data & 0xffff;
ret = read_ccnt(info, CCNT_POS2, &data); if (ret) goto out;
sum |= (data & 0xffff) << 16;
ccnt->pos += sum;
ret = read_ccnt(info, CCNT_NEG1, &data); if (ret) goto out;
sum = data & 0xffff;
ret = read_ccnt(info, CCNT_NEG2, &data); if (ret) goto out;
sum |= (data & 0xffff) << 16;
sum = ~sum + 1; /* since it's negative */
ccnt->neg += sum;
ret = read_ccnt(info, CCNT_SPOS, &data); if (ret) goto out;
ccnt->spos += data;
ret = read_ccnt(info, CCNT_SNEG, &data); if (ret) goto out;
/* Calculate Open Circuit Voltage */ staticint calc_ocv(struct pm860x_battery_info *info, int *ocv)
{ int ret; int i; int data; int vbatt_avg; int vbatt_sum; int ibatt_avg; int ibatt_sum;
if (!ocv) return -EINVAL;
for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) {
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) goto out;
vbatt_sum += data;
ret = measure_current(info, &data); if (ret) goto out;
ibatt_sum += data;
}
vbatt_avg = vbatt_sum / 10;
ibatt_avg = ibatt_sum / 10;
/* Calculate State of Charge (percent points) */ staticint calc_soc(struct pm860x_battery_info *info, int state, int *soc)
{ int i; int ocv; int count; int ret = -EINVAL;
if (!soc) return -EINVAL;
switch (state) { case OCV_MODE_ACTIVE:
ret = calc_ocv(info, &ocv); break; case OCV_MODE_SLEEP:
ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv); break;
} if (ret) return ret;
mutex_lock(&info->lock);
ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); if (ret & STATUS2_BAT) {
info->present = 1;
info->temp_type = PM860X_TEMP_TBAT;
} else {
info->present = 0;
info->temp_type = PM860X_TEMP_TINT;
}
mutex_unlock(&info->lock); /* clear ccnt since battery is attached or detached */
clear_ccnt(info, &ccnt_data); return IRQ_HANDLED;
}
staticvoid pm860x_init_battery(struct pm860x_battery_info *info)
{ unsignedchar buf[2]; int ret; int data; int bat_remove; int soc = 0;
/* measure enable on GPADC1 */
data = MEAS1_GP1; if (info->temp_type == PM860X_TEMP_TINT)
data |= MEAS1_TINT;
ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data); if (ret) goto out;
/* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */
data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC;
ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data); if (ret) goto out;
/* measure disable CC in sleep time */
ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82); if (ret) goto out;
ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c); if (ret) goto out;
/* enable GPADC */
ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1,
GPMISC1_GPADC_EN, GPMISC1_GPADC_EN); if (ret < 0) goto out;
/* detect battery via GPADC1 */
ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6,
CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1); if (ret < 0) goto out;
ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3,
CCNT_AVG_SEL); if (ret < 0) goto out;
/* set GPADC1 bias */
ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4,
GPBIAS2_GPADC1_SET); if (ret < 0) goto out;
staticint calc_resistor(struct pm860x_battery_info *info)
{ int vbatt_sum1; int vbatt_sum2; int chg_current; int ibatt_sum1; int ibatt_sum2; int data; int ret; int i;
ret = measure_current(info, &data); /* make sure that charging is launched by data > 0 */ if (ret || data < 0) goto out;
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) goto out; /* calculate resistor only in CC charge mode */ if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX) goto out;
/* current is saved */ if (set_charger_current(info, 500, &chg_current)) goto out;
/* * set charge current as 500mA, wait about 500ms till charging * process is launched and stable with the newer charging current.
*/
msleep(500);
for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) {
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) goto out_meas;
vbatt_sum1 += data;
ret = measure_current(info, &data); if (ret) goto out_meas;
if (set_charger_current(info, 100, &ret)) goto out_meas; /* * set charge current as 100mA, wait about 500ms till charging * process is launched and stable with the newer charging current.
*/
msleep(500);
for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) {
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) goto out_meas;
vbatt_sum2 += data;
ret = measure_current(info, &data); if (ret) goto out_meas;
dev_dbg(info->dev, "%s, last cap : %d", __func__,
info->last_capacity);
ret = measure_current(info, &ibat); if (ret) goto out; /* Calculate the capacity when discharging(ibat < 0) */ if (ibat < 0) {
ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv); if (ret)
cap_ocv = info->last_capacity;
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) goto out; if (data <= LOW_BAT_THRESHOLD) { /* choose the lower capacity value to report * between vbat and CC when vbat < 3.6v; * than 3.6v;
*/
*cap = min(cap_ocv, cap_cc);
} else { /* when detect vbat > 3.6v, but cap_cc < 15,and * cap_ocv is 10% larger than cap_cc, we can think * CC have some accumulation error, switch to OCV * to estimate capacity;
* */ if (cap_cc < 15 && cap_ocv - cap_cc > 10)
*cap = cap_ocv; else
*cap = cap_cc;
} /* when discharging, make sure current capacity
* is lower than last*/ if (*cap > info->last_capacity)
*cap = info->last_capacity;
} else {
*cap = cap_cc;
}
info->last_capacity = *cap;
dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n",
(ibat < 0) ? "discharging" : "charging",
cap_ocv, cap_cc, *cap); /* * store the current capacity to RTC domain register, * after next power up , it will be restored.
*/
pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB,
(*cap & 0x1F) << 3);
pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB,
((*cap >> 5) & 0x3)); return 0;
out: return ret;
}
staticint pm860x_batt_get_prop(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent); int data; int ret;
switch (psp) { case POWER_SUPPLY_PROP_PRESENT:
val->intval = info->present; break; case POWER_SUPPLY_PROP_CAPACITY:
ret = calc_capacity(info, &data); if (ret) return ret; if (data < 0)
data = 0; elseif (data > 100)
data = 100; /* return 100 if battery is not attached */ if (!info->present)
data = 100;
val->intval = data; break; case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* return real vbatt Voltage */
ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); if (ret) return ret;
val->intval = data * 1000; break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: /* return Open Circuit Voltage (not measured voltage) */
ret = calc_ocv(info, &data); if (ret) return ret;
val->intval = data * 1000; break; case POWER_SUPPLY_PROP_CURRENT_NOW:
ret = measure_current(info, &data); if (ret) return ret;
val->intval = data; break; case POWER_SUPPLY_PROP_TEMP: if (info->present) {
ret = measure_temp(info, &data); if (ret) return ret;
data *= 10;
} else { /* Fake Temp 25C Without Battery */
data = 250;
}
val->intval = data; break; default: return -ENODEV;
} return 0;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.