#include <linux/usb/otg.h>
#include <linux/ratelimit.h>
#include <linux/regulator/machine.h>
+#include <linux/leds.h>
#define TWL4030_BCIMSTATEC 0x02
#define TWL4030_BCIICHG 0x08
#define TWL4030_BCIVAC 0x0a
#define TWL4030_BCIVBUS 0x0c
+#define TWL4030_BCIMFSTS3 0x0f
#define TWL4030_BCIMFSTS4 0x10
#define TWL4030_BCIMFKEY 0x11
#define TWL4030_BCICTL1 0x23
#define TWL4030_BCIAUTOWEN BIT(5)
#define TWL4030_CONFIG_DONE BIT(4)
+#define TWL4030_CVENAC BIT(2)
#define TWL4030_BCIAUTOUSB BIT(1)
#define TWL4030_BCIAUTOAC BIT(0)
#define TWL4030_CGAIN BIT(5)
#define TWL4030_USBFASTMCHG BIT(2)
#define TWL4030_STS_VBUS BIT(7)
#define TWL4030_STS_USB_ID BIT(2)
+#define TWL4030_STS_CHG BIT(1)
/* BCI interrupts */
#define TWL4030_WOVF BIT(0) /* Watchdog overflow */
#define TWL4030_MSTATEC_COMPLETE4 0x0e
#define TWL4030_KEY_IIREF 0xe7
+#define TWL4030_BATSTSMCHG BIT(6)
+
+#define IRQ_CHECK_PERIOD (3 * HZ)
+#define IRQ_CHECK_THRESHOLD 4
static bool allow_usb = 1;
module_param(allow_usb, bool, 0644);
enum power_supply_type current_supply;
struct regulator *usb_reg;
int usb_enabled;
+ int irq_had_charger;
+
+ unsigned long irq_check_count_time;
+ int irq_check_count;
+ int irq_check_ac_disabled;
+
+ struct led_trigger *charging_any_trig;
+ int was_charging_any;
unsigned long event;
struct ratelimit_state ratelimit;
static int set_charge_current(struct twl4030_bci *bci, int new_current)
{
- u8 tmp, boot_bci_prev, cgain_set, cgain_clear;
- int ret;
+ u8 val, boot_bci_prev, cgain_set, cgain_clear;
+ int ret, ret2;
+
+ ret = twl4030_bci_read(TWL4030_BCIMFSTS3, &val);
+ if (ret)
+ goto out_norestore;
+
+ if (!(val & TWL4030_BATSTSMCHG)) {
+ dev_err(bci->dev, "missing battery, can't change charge_current\n");
+ goto out_norestore;
+ }
ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci_prev,
TWL4030_PM_MASTER_BOOT_BCI);
if (ret)
- goto out;
+ goto out_norestore;
/*
* Stop automatic charging here, because charge current change
* requires multiple register writes and CGAIN change requires
- * automatic charge to be stopped.
+ * automatic charge to be stopped (and CV mode disabled too).
*/
ret = twl4030_clear_set_boot_bci(
- TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB | 4, 0);
+ TWL4030_CVENAC | TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB, 0);
if (ret)
goto out;
if (ret)
goto out;
- ret = twl4030_bci_read(TWL4030_BCICTL1, &tmp);
- if (ret != 0 || (tmp & TWL4030_CGAIN) != cgain_set) {
+ ret = twl4030_bci_read(TWL4030_BCICTL1, &val);
+ if (ret != 0 || (val & TWL4030_CGAIN) != cgain_set) {
dev_err(bci->dev, "CGAIN change failed\n");
goto out;
}
- ret = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, boot_bci_prev,
- TWL4030_PM_MASTER_BOOT_BCI);
out:
+ ret2 = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, boot_bci_prev,
+ TWL4030_PM_MASTER_BOOT_BCI);
+ if (ret2 != 0)
+ dev_err(bci->dev, "failed boot_bci restore: %d\n", ret2);
+
+out_norestore:
if (ret != 0)
- dev_err(bci->dev, "charge current change failed\n");
+ dev_err(bci->dev, "charge current change failed: %d\n", ret);
return ret;
}
static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
{
struct twl4030_bci *bci = arg;
+ int have_charger;
+ u8 hw_cond;
+ int ret;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hw_cond,
+ TWL4030_PM_MASTER_STS_HW_CONDITIONS);
+ if (ret < 0) {
+ dev_err(bci->dev, "HW_CONDITIONS read failed: %d\n", ret);
+ goto out;
+ }
+
+ have_charger = (hw_cond & TWL4030_STS_CHG) ? 1 : 0;
+ if (have_charger == bci->irq_had_charger)
+ goto out;
+ bci->irq_had_charger = have_charger;
+
+ dev_dbg(bci->dev, "CHG_PRES irq, hw_cond %02x\n", hw_cond);
+
+ /*
+ * deal with rare mysterious issue of CHG_PRES changing states at ~4Hz
+ * without any charger connected or anything
+ */
+ if (time_before(jiffies, bci->irq_check_count_time + IRQ_CHECK_PERIOD)) {
+ bci->irq_check_count++;
+ if (have_charger && bci->irq_check_count > IRQ_CHECK_THRESHOLD) {
+ dev_err(bci->dev, "spurious CHG_PRES irqs detected (%d), disabling charger\n",
+ bci->irq_check_count);
+ twl4030_charger_enable_ac(false);
+ bci->irq_check_ac_disabled = true;
+ }
+ } else {
+ bci->irq_check_count_time = jiffies;
+ bci->irq_check_count = 1;
+ if (have_charger && bci->irq_check_ac_disabled) {
+ twl4030_charger_enable_ac(true);
+ bci->irq_check_ac_disabled = false;
+ }
+ }
- dev_dbg(bci->dev, "CHG_PRES irq\n");
power_supply_changed(&bci->ac);
power_supply_changed(&bci->usb);
+out:
return IRQ_HANDLED;
}
if (irqs2 & TWL4030_ACCHGOV && __ratelimit(&bci->ratelimit))
dev_crit(bci->dev, "Ac charger overvoltage\n");
+#if 0
/* ack the interrupts */
twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs1,
TWL4030_INTERRUPTS_BCIISR1A);
twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, irqs2,
TWL4030_INTERRUPTS_BCIISR2A);
+#endif
return IRQ_HANDLED;
}
if (ret)
return ret;
- if (bci->current_supply == POWER_SUPPLY_TYPE_MAINS)
+ if (psy->type == POWER_SUPPLY_TYPE_MAINS)
bci->ac_current = new_current;
else
bci->usb_current = new_current;
union power_supply_propval *val)
{
struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent);
- int is_charging;
+ int is_charging_any = 0;
+ int is_charging = 0;
int state;
int ret;
if (state < 0)
return state;
- if (psy->type == POWER_SUPPLY_TYPE_USB)
- is_charging = state & TWL4030_MSTATEC_USB;
- else
- is_charging = state & TWL4030_MSTATEC_AC;
+ if (twl4030_bci_state_to_status(state) ==
+ POWER_SUPPLY_STATUS_CHARGING) {
+ is_charging_any =
+ state & (TWL4030_MSTATEC_USB | TWL4030_MSTATEC_AC);
+ if (psy->type == POWER_SUPPLY_TYPE_USB)
+ is_charging = state & TWL4030_MSTATEC_USB;
+ else
+ is_charging = state & TWL4030_MSTATEC_AC;
+ }
+
+ if (is_charging_any != bci->was_charging_any) {
+ led_trigger_event(bci->charging_any_trig,
+ is_charging_any ? LED_FULL : LED_OFF);
+ bci->was_charging_any = is_charging_any;
+ }
+
if (is_charging && psy->type != bci->current_supply) {
if (psy->type == POWER_SUPPLY_TYPE_USB)
set_charge_current(bci, bci->usb_current);
bci->irq_chg = platform_get_irq(pdev, 0);
bci->irq_bci = platform_get_irq(pdev, 1);
bci->ac_current = 860; /* ~1.2A */
- bci->usb_current = 360; /* ~600mA */
+ bci->usb_current = 330; /* ~560mA */
+ bci->irq_had_charger = -1;
+ bci->irq_check_count_time = jiffies;
platform_set_drvdata(pdev, bci);
ratelimit_state_init(&bci->ratelimit, HZ, 2);
+ led_trigger_register_simple("twl4030_bci-charging",
+ &bci->charging_any_trig);
+
bci->ac.name = "twl4030_ac";
bci->ac.type = POWER_SUPPLY_TYPE_MAINS;
bci->ac.properties = twl4030_charger_props;
power_supply_unregister(&bci->usb);
fail_register_usb:
power_supply_unregister(&bci->ac);
+
+ if (bci->usb_reg) {
+ if (bci->usb_enabled)
+ regulator_disable(bci->usb_reg);
+ regulator_put(bci->usb_reg);
+ }
+
fail_register_ac:
+ led_trigger_unregister_simple(bci->charging_any_trig);
platform_set_drvdata(pdev, NULL);
kfree(bci);
free_irq(bci->irq_chg, bci);
power_supply_unregister(&bci->usb);
power_supply_unregister(&bci->ac);
+
+ if (bci->usb_reg) {
+ if (bci->usb_enabled)
+ regulator_disable(bci->usb_reg);
+ regulator_put(bci->usb_reg);
+ }
+
+ led_trigger_unregister_simple(bci->charging_any_trig);
platform_set_drvdata(pdev, NULL);
kfree(bci);