示例#1
0
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);
}
示例#2
0
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;
}
示例#3
0
/*
 * 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;
}
示例#4
0
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);
}
示例#5
0
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;
}
示例#6
0
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);
	}
}