static void twl6030_charge_fault_work(struct work_struct *work) { struct twl6030_bci_device_info *di = container_of(work, struct twl6030_bci_device_info, charge_fault_work); if (is_charging(di)) twl6030_start_usb_charger(di, 500); msleep(10); twl6030_determine_charge_state(di); }
static int __init twl6030_bci_battery_probe(struct platform_device *pdev) { struct twl6030_bci_platform_data *pdata = pdev->dev.platform_data; struct twl6030_bci_device_info *di; int irq; int ret; u8 rd_reg = 0, controller_stat = 0; di = kzalloc(sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; if (!pdata) { dev_dbg(&pdev->dev, "platform_data not available\n"); ret = -EINVAL; goto err_pdata; } di->dev = &pdev->dev; di->bat.name = "twl6030_bci_battery"; di->bat.supplied_to = twl6030_bci_supplied_to; di->bat.num_supplicants = ARRAY_SIZE(twl6030_bci_supplied_to); di->bat.type = POWER_SUPPLY_TYPE_BATTERY; di->bat.properties = twl6030_bci_battery_props; di->bat.num_properties = ARRAY_SIZE(twl6030_bci_battery_props); di->bat.get_property = twl6030_bci_battery_get_property; di->bat.external_power_changed = twl6030_bci_battery_external_power_changed; di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; di->bk_bat.name = "twl6030_bci_bk_battery"; di->bk_bat.type = POWER_SUPPLY_TYPE_BATTERY; di->bk_bat.properties = twl6030_bk_bci_battery_props; di->bk_bat.num_properties = ARRAY_SIZE(twl6030_bk_bci_battery_props); di->bk_bat.get_property = twl6030_bk_bci_battery_get_property; di->bk_bat.external_power_changed = NULL; /* * Android expects a battery type POWER_SUPPLY_TYPE_USB * as a usb charger battery. This battery * and its "online" property are used to determine if the * usb cable is plugged in or not. */ di->usb_bat.name = "twl6030_bci_usb_battery"; di->usb_bat.supplied_to = twl6030_bci_supplied_to; di->usb_bat.type = POWER_SUPPLY_TYPE_USB; di->usb_bat.properties = twl6030_usb_battery_props; di->usb_bat.num_properties = ARRAY_SIZE(twl6030_usb_battery_props); di->usb_bat.get_property = twl6030_usb_battery_get_property; di->usb_bat.external_power_changed = NULL; di->vac_priority = 1; platform_set_drvdata(pdev, di); /* settings for temperature sensing */ ret = twl6030battery_temp_setup(); if (ret) goto temp_setup_fail; /* request charger fault interruption */ irq = platform_get_irq(pdev, 1); ret = request_irq(irq, twl6030charger_fault_interrupt, 0, "twl_bci_fault", di); if (ret) { dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", irq, ret); goto batt_irq_fail; } /* request charger ctrl interruption */ irq = platform_get_irq(pdev, 0); ret = request_irq(irq, twl6030charger_ctrl_interrupt, 0, "twl_bci_ctrl", di); if (ret) { dev_dbg(&pdev->dev, "could not request irq %d, status %d\n", irq, ret); goto chg_irq_fail; } twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, REG_INT_MSK_LINE_C); twl6030_interrupt_unmask(TWL6030_CHARGER_CTRL_INT_MASK, REG_INT_MSK_STS_C); twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, REG_INT_MSK_LINE_C); twl6030_interrupt_unmask(TWL6030_CHARGER_FAULT_INT_MASK, REG_INT_MSK_STS_C); ret = power_supply_register(&pdev->dev, &di->bat); if (ret) { dev_dbg(&pdev->dev, "failed to register main battery\n"); goto batt_failed; } INIT_DELAYED_WORK_DEFERRABLE(&di->twl6030_bci_monitor_work, twl6030_bci_battery_work); schedule_delayed_work(&di->twl6030_bci_monitor_work, 0); ret = power_supply_register(&pdev->dev, &di->bk_bat); if (ret) { dev_dbg(&pdev->dev, "failed to register backup battery\n"); goto bk_batt_failed; } ret = power_supply_register(&pdev->dev, &di->usb_bat); if (ret) { dev_dbg(&pdev->dev, "failed to register usb source\n"); goto usb_batt_failed; } INIT_DELAYED_WORK_DEFERRABLE(&di->twl6030_bk_bci_monitor_work, twl6030_bk_bci_battery_work); schedule_delayed_work(&di->twl6030_bk_bci_monitor_work, 500); twl_i2c_read_u8(TWL6030_MODULE_ID0, &rd_reg, REG_MISC1); rd_reg = rd_reg | VAC_MEAS | VBAT_MEAS | BB_MEAS; twl_i2c_write_u8(TWL6030_MODULE_ID0, rd_reg, REG_MISC1); twl_i2c_read_u8(TWL6030_MODULE_ID1, &rd_reg, REG_TOGGLE1); rd_reg = rd_reg | ENABLE_FUELGUAGE; twl_i2c_write_u8(TWL6030_MODULE_ID1, rd_reg, REG_TOGGLE1); twl_i2c_read_u8(TWL_MODULE_MADC, &rd_reg, TWL6030_GPADC_CTRL); rd_reg = rd_reg | ENABLE_ISOURCE; twl_i2c_write_u8(TWL_MODULE_MADC, rd_reg, TWL6030_GPADC_CTRL); twl_i2c_read_u8(TWL_MODULE_USB, &rd_reg, REG_USB_VBUS_CTRL_SET); rd_reg = rd_reg | VBUS_MEAS; twl_i2c_write_u8(TWL_MODULE_USB, rd_reg, REG_USB_VBUS_CTRL_SET); twl_i2c_read_u8(TWL_MODULE_USB, &rd_reg, REG_USB_ID_CTRL_SET); rd_reg = rd_reg | ID_MEAS; twl_i2c_write_u8(TWL_MODULE_USB, rd_reg, REG_USB_ID_CTRL_SET); /* initialize for USB charging */ twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MBAT_TEMP, CONTROLLER_INT_MASK); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, MASK_MCHARGERUSB_THMREG, CHARGERUSB_INT_MASK); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CHARGERUSB_VOREG_4P2, CHARGERUSB_VOREG); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CHARGERUSB_VICHRG_1500, CHARGERUSB_VICHRG); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CHARGERUSB_CTRL2_VITERM_100, CHARGERUSB_CTRL2); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CHARGERUSB_CIN_LIMIT_NONE, CHARGERUSB_CINLIMIT); twl_i2c_write_u8(TWL6030_MODULE_CHARGER, CHARGERUSB_CTRLLIMIT2_1500, CHARGERUSB_CTRLLIMIT2); twl_i2c_write_u8(TWL6030_MODULE_BQ, 0xa0, REG_SAFETY_LIMIT); twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &controller_stat, CONTROLLER_STAT1); if (controller_stat & VBUS_DET) twl6030_start_usb_charger(); if (controller_stat & VAC_DET) twl6030_start_ac_charger(); return 0; usb_batt_failed: power_supply_unregister(&di->bk_bat); bk_batt_failed: power_supply_unregister(&di->bat); batt_failed: free_irq(irq, di); chg_irq_fail: irq = platform_get_irq(pdev, 1); free_irq(irq, NULL); err_pdata: batt_irq_fail: temp_setup_fail: kfree(di); return ret; }
/* * Interrupt service routine * * Attends to TWL 6030 power module interruptions events, specifically * USB_PRES (USB charger presence) CHG_PRES (AC charger presence) events * */ static irqreturn_t twl6030charger_ctrl_interrupt(int irq, void *_di) { struct twl6030_bci_device_info *di = _di; int ret; u8 stat_toggle, stat_reset, stat_set = 0; u8 present_charge_state = 0; u8 ac_or_vbus, no_ac_and_vbus; #ifdef CONFIG_LOCKDEP /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which * we don't want and can't tolerate. Although it might be * friendlier not to borrow this thread context... */ local_irq_enable(); #endif /* read charger controller_stat1 */ ret = twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &present_charge_state, CONTROLLER_STAT1); if (ret) return IRQ_NONE; stat_toggle = charge_state ^ present_charge_state; stat_set = stat_toggle & present_charge_state; stat_reset = stat_toggle & charge_state; no_ac_and_vbus = !((present_charge_state) & (VBUS_DET | VAC_DET)); ac_or_vbus = charge_state & (VBUS_DET | VAC_DET); if (no_ac_and_vbus && ac_or_vbus) { pr_debug("No Charging source\n"); /* disable charging when no source present */ } charge_state = present_charge_state; if (stat_reset & VBUS_DET) { pr_debug("usb removed\n"); usb_connected = 0; if (present_charge_state & VAC_DET) twl6030_start_ac_charger(); } if (stat_set & VBUS_DET) { if ((present_charge_state & VAC_DET) && (di->vac_priority)) pr_debug("USB charger detected, continue with VAC\n"); else twl6030_start_usb_charger(); } if (stat_reset & VAC_DET) { pr_debug("vac removed\n"); if (present_charge_state & VBUS_DET) twl6030_start_usb_charger(); } if (stat_set & VAC_DET) { if ((present_charge_state & VBUS_DET) && !(di->vac_priority)) pr_debug("AC charger detected, continue with VBUS\n"); else twl6030_start_ac_charger(); } if (stat_set & CONTROLLER_STAT1_FAULT_WDG) pr_debug("Fault watchdog fired\n"); if (stat_reset & CONTROLLER_STAT1_FAULT_WDG) pr_debug("Fault watchdog recovered\n"); if (stat_set & CONTROLLER_STAT1_BAT_REMOVED) pr_debug("Battery removed\n"); if (stat_reset & CONTROLLER_STAT1_BAT_REMOVED) pr_debug("Battery inserted\n"); if (stat_set & CONTROLLER_STAT1_BAT_TEMP_OVRANGE) pr_debug("Battery temperature overrange\n"); if (stat_reset & CONTROLLER_STAT1_BAT_TEMP_OVRANGE) pr_debug("Battery temperature within range\n"); twl6030_bci_battery_update_status(di); power_supply_changed(&di->bat); return IRQ_HANDLED; }
static void twl6030_read_fuelgauge(struct twl6030_bci_device_info *di) { s32 samples; int ret, newcap; s64 cap, cur; u64 tmp; int statechanged = 0; /* save last reading before taking a new reading */ di->charge_n2 = di->charge_n1; di->timer_n2 = di->timer_n1; /* update timer_n1 and charge_n1 */ ret = twl6030_read_gasguage_regs(di); if (ret < 0) goto err; samples = di->timer_n1 - di->timer_n2; /* check for timer overflow */ if (di->timer_n1 < di->timer_n2) samples = samples + (1 << 24); /* IACC (As) = ((ACCUM - OFFSET * COUNT) * 62 [mV] ) / (10 [Mohm] * 32768 [HZ] ) */ /* (As * 62000) / 3600 -> mAh */ /* TODO: ensure we do this once an hour (3600*4 samples) to avoid overflow */ cap = (di->charge_n1 - ((s64) di->cc_offset) * ((s64) di->timer_n1)) * 62000LL; tmp = (cap < 0) ? -cap : cap; do_div(tmp, 1179648); cap = (cap < 0) ? -tmp : tmp; cur = di->charge_n1 - di->charge_n2 - (di->cc_offset * samples); cur *= (62LL * 4LL * 100000LL); tmp = (cur < 0) ? -cur : cur; do_div(tmp, samples); tmp >>= 15; /* / 32768 */ cur = (cur < 0) ? -tmp : tmp; di->current_avg_uA = (int) cur; twl6030_update_voltage(di); /* detect charge termination */ /* TODO: make configurable */ /* TODO: termination after X s even if other conditions not met */ if (is_charging(di) && (di->voltage_mV > 4100) && (di->current_avg_uA < 50000)) { if (di->trust_capacity && (di->capacity_uAh < di->capacity_max_uAh) && (di->current_avg_uA > 25000)) { /* if we are charging back to a full state with the CC, * be a bit more aggressive than if we only have voltage * to go by */ /* bump the full time out until the aggressive conditions are not met */ di->full_jiffies = msecs_to_jiffies(120 * 1000) + jiffies; } else if (time_after_eq(jiffies, di->full_jiffies)) { di->full_jiffies = msecs_to_jiffies(120 * 1000) + jiffies; di->trust_capacity = 1; printk("battery: full state detected\n"); /* calibration will zero the cc accumulator */ twl6030_calibrate_fuelgauge(di); di->capacity_offset = di->capacity_max_uAh; cap = 0; di->state = STATE_FULL; twl6030_stop_usb_charger(di); statechanged = 1; } } else { /* bump the full time out as long as we aren't charging */ di->full_jiffies = msecs_to_jiffies(120 * 1000) + jiffies; } cap += di->capacity_offset; /* limit capacity range to reasonable values */ if (cap > ((s64) di->capacity_max_uAh)) cap = di->capacity_max_uAh; if (cap < 0) cap = 0; di->capacity_uAh = cap; /* scale to percentage */ newcap = cap; newcap = newcap / (di->capacity_max_uAh / 100); if (!di->trust_capacity) { /* if we haven't hit a known full charge state, we may * have more capacity than measured by the CC, so use the * CC measured capacity as the floor, but allow higher * estimated capacities based on voltage */ ret = twl6030_estimate_capacity(di); if (ret > newcap) newcap = ret; } printk("battery: %lld uA %lld uAh %d mV %d s (%d%%) %s%s%s\n", cur, cap, di->voltage_mV, samples / 4, newcap, twl6030_state[di->state], is_charging(di) ? " CHG" : "", di->trust_capacity ? " CC" : " EST"); if ((newcap != di->capacity) || statechanged) { di->capacity = newcap; power_supply_changed(&di->bat); } if ((di->state == STATE_FULL) && (di->capacity < 95)) { printk("battery: drained from full to %d%%, charging again\n", di->capacity); di->state = STATE_USB; twl6030_start_usb_charger(di, 500); statechanged = 1; } return; err: pr_err("%s: Error access to TWL6030 (%d)\n", __func__, ret); }
static void twl6030_update_voltage(struct twl6030_bci_device_info *di) { int i; int ret; int index, q; long long total, denom; int curr_voltage; if (is_charging(di)) { if (time_after_eq(jiffies, di->vbat_jiffies)) { di->vbat_jiffies = msecs_to_jiffies(60 * 1000) + jiffies; twl6030_stop_usb_charger(di); msleep(200); ret = twl6030_get_gpadc_conversion(di, di->gpadc_vbat_chnl); if (ret > 0) curr_voltage = ret; twl6030_start_usb_charger(di, 500); } else { /* if no sample is taken don't bother recalculating the weighted average */ return; } } else { di->vbat_jiffies = jiffies; ret = twl6030_get_gpadc_conversion(di, di->gpadc_vbat_chnl); if (ret > 0) curr_voltage = ret; } /* * store the measured voltage in the history table. the index points * to the most recent sample. */ di->voltage_index = (di->voltage_index + 1) & (VOLTAGE_HISTORY_LENGTH - 1); di->voltage_history[di->voltage_index] = curr_voltage; /* filter the cached voltage using a weighted average */ q = VOLTAGE_HISTORY_LENGTH; // we need this many bits in the fraction index = di->voltage_index; total = 0; denom = 0; for (i=0; i < VOLTAGE_HISTORY_LENGTH; i++) { total += (long long) di->voltage_history[index] << (q - i); // convert to q, divide by 2^i denom += 1LL << (q - i); // denom += 1/(2^i), in q format index = (index + 1) & (VOLTAGE_HISTORY_LENGTH - 1); printk("battery voltage history[%d] = %d %s\n", i, di->voltage_history[i], i == di->voltage_index ? "*" : ""); } /* divide by the sum of the weights to get the weighted average */ total <<= q; total += denom >> 1; // round up mid value do_div(total, denom); /* convert back from q format and store value */ di->voltage_mV = total >> q; }
static void twl6030_determine_charge_state(struct twl6030_bci_device_info *di) { u8 stat1; int newstate = STATE_BATTERY; /* TODO: i2c error -> fault? */ twl_i2c_read_u8(TWL6030_MODULE_CHARGER, &stat1, CONTROLLER_STAT1); /* TODO: why is STAT1.0 (BAT_TEMP_OVRANGE) always set? */ /* printk("battery: determine_charge_state() stat1=%02x int1=%02x\n", stat1, int1); */ if (stat1 & VBUS_DET) { /* dedicated charger detected by PHY? */ if (di->usb_event == USB_EVENT_CHARGER) newstate = STATE_AC; else newstate = STATE_USB; if (!di->vbus_online) { di->vbus_online = 1; wake_lock(&usb_wake_lock); } } else { /* ensure we don't have a stale USB_EVENT_CHARGER should detect bounce */ di->usb_event = USB_EVENT_NONE; if (di->vbus_online) { di->vbus_online = 0; /* give USB and userspace some time to react before suspending */ wake_lock_timeout(&usb_wake_lock, HZ / 2); } } if (di->state == newstate) return; switch (newstate) { case STATE_FAULT: case STATE_BATTERY: if (is_charging(di)) twl6030_stop_usb_charger(di); break; case STATE_USB: case STATE_AC: /* moving out of STATE_FULL should only happen on unplug * or if we actually run down the battery capacity */ if (di->state == STATE_FULL) { newstate = STATE_FULL; break; } /* TODO: high current? */ if (!is_charging(di)) twl6030_start_usb_charger(di, 500); break; } if (di->state != newstate) { printk("battery: state %s -> %s\n", twl6030_state[di->state], twl6030_state[newstate]); di->state = newstate; power_supply_changed(&di->bat); power_supply_changed(&di->usb); } }