X-Git-Url: https://git.openpandora.org/cgi-bin/gitweb.cgi?p=pandora-kernel.git;a=blobdiff_plain;f=drivers%2Fpower%2Ftwl4030_charger.c;h=ecb7274b086c358fbe0bcd67582965da6b35ad27;hp=54b9198fa576a12b1d7f59a2ecc1463786cb0196;hb=af37d866c8aa1b8b61650629a53c6fab2a17e867;hpb=094daf7db7c47861009899ce23f9177d761e20b0 diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 54b9198fa576..ecb7274b086c 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -21,22 +21,32 @@ #include #include #include +#include +#include +#include #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_BCIMFTH8 0x1d #define TWL4030_BCICTL1 0x23 +#define TWL4030_BCIIREF1 0x27 +#define TWL4030_BCIIREF2 0x28 #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 */ @@ -61,7 +71,20 @@ #define TWL4030_MSTATEC_COMPLETE1 0x0b #define TWL4030_MSTATEC_COMPLETE4 0x0e -static bool allow_usb; +#define TWL4030_KEY_IIREF 0xe7 +#define TWL4030_KEY_MFTH8 0xf4 +#define TWL4030_BATSTSMCHG BIT(6) + +#define IRQ_CHECK_PERIOD (3 * HZ) +#define IRQ_CHECK_THRESHOLD 4 + +/* By default, 80mA */ +static unsigned end_of_charge_current = 200000; +module_param(end_of_charge_current, uint, 0); +MODULE_PARM_DESC(end_of_charge_current, + "Stop charging when the charger current goes below this threshold"); + +static bool allow_usb = 1; module_param(allow_usb, bool, 0644); MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current"); @@ -74,8 +97,27 @@ struct twl4030_bci { struct work_struct work; int irq_chg; int irq_bci; + bool ac_charge_enable; + bool usb_charge_enable; + int usb_current; + int ac_current; + enum power_supply_type current_supply; + struct regulator *usb_reg; + int usb_enabled; + int irq_had_charger; + + unsigned charge_current; + unsigned eoc_current; + + 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; }; /* @@ -101,9 +143,14 @@ static int twl4030_bci_read(u8 reg, u8 *val) return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg); } +static int twl4030_bci_write(u8 reg, u8 val) +{ + return twl_i2c_write_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg); +} + static int twl4030_clear_set_boot_bci(u8 clear, u8 set) { - return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, 0, + return twl4030_clear_set(TWL4030_MODULE_PM_MASTER, clear, TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, TWL4030_PM_MASTER_BOOT_BCI); } @@ -128,6 +175,20 @@ static int twl4030bci_read_adc_val(u8 reg) return temp | val; } +static int twl4030bci_get_cgain(struct twl4030_bci *bci, int *cgain) { + int ret; + u8 reg; + + ret = twl4030_bci_read(TWL4030_BCICTL1, ®); + if (ret < 0) { + dev_warn(bci->dev, "error fetching CGAIN value from BCICTL1 register"); + return ret; + } + *cgain = (reg & TWL4030_CGAIN) ? 1 : 0; + + return 0; +} + /* * Check if VBUS power is present */ @@ -151,13 +212,16 @@ static int twl4030_bci_have_vbus(struct twl4030_bci *bci) } /* - * Enable/Disable USB Charge funtionality. + * Enable/Disable USB Charge functionality. */ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) { int ret; if (enable) { + if (!bci->usb_charge_enable) + return -EACCES; + /* Check for USB charger conneted */ if (!twl4030_bci_have_vbus(bci)) return -ENODEV; @@ -171,6 +235,12 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) return -EACCES; } + /* Need to keep regulator on */ + if (!bci->usb_enabled && + bci->usb_reg && + regulator_enable(bci->usb_reg) == 0) + bci->usb_enabled = 1; + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); if (ret < 0) @@ -181,6 +251,9 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); } else { ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + if (bci->usb_enabled && + regulator_disable(bci->usb_reg) == 0) + bci->usb_enabled = 0; } return ret; @@ -201,17 +274,184 @@ static int twl4030_charger_enable_ac(bool enable) return ret; } +static int update_charge_parameters(struct twl4030_bci *bci, + int charge_current, int eoc_current) +{ + int old_charge_current = bci->charge_current; + int old_eoc_current = bci->eoc_current; + int ret = 0; + int ret2; + u16 hw_charge_current; + u8 hw_eoc_current, boot_bci_prev, reg, cgain_set, cgain_clear; + bool need_cgain = false; + bool restore_boot_reg = false; + + if (charge_current < 0 && eoc_current < 0) + return -EINVAL; + + ret = twl4030_bci_read(TWL4030_BCIMFSTS3, ®); + if (ret) + goto fail_norestore; + if (!(reg & TWL4030_BATSTSMCHG)) { + dev_err(bci->dev, "missing battery, can't change charging parameters\n"); + goto fail_norestore; + } + + if (charge_current >= 0) + bci->charge_current = charge_current; + if (eoc_current >= 0) + bci->eoc_current = eoc_current; + + hw_eoc_current = bci->eoc_current / 13310; + if (hw_eoc_current > 15) + need_cgain = true; + + hw_charge_current = (bci->charge_current * 1000) / 1666666; + if (hw_charge_current > 511) + need_cgain = true; + + if (need_cgain) { + hw_eoc_current /= 2; + hw_charge_current /= 2; + } + + if (hw_eoc_current > 15) { + ret = -EINVAL; + goto fail; + } + if (hw_charge_current > 511) { + ret = -EINVAL; + goto fail; + } + + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci_prev, + TWL4030_PM_MASTER_BOOT_BCI); + if (ret) + goto fail; + + /* + * Stop automatic charging here, because charge current change + * requires multiple register writes and CGAIN change requires + * automatic charge to be stopped (and CV mode disabled too). + */ + ret = twl4030_clear_set_boot_bci( + TWL4030_CVENAC | TWL4030_BCIAUTOAC | TWL4030_BCIAUTOUSB, 0); + if (ret) + goto fail; + else + restore_boot_reg = true; + + ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_MFTH8); + if (ret) + goto fail; + ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0xf0, + hw_eoc_current << 4, TWL4030_BCIMFTH8); + if (ret) + goto fail; + + ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_IIREF); + if (ret) + goto fail; + ret = twl4030_bci_write(TWL4030_BCIIREF1, hw_charge_current & 0xff); + if (ret) + goto fail; + ret = twl4030_bci_write(TWL4030_BCIMFKEY, TWL4030_KEY_IIREF); + if (ret) + goto fail; + ret = twl4030_bci_write(TWL4030_BCIIREF2, (hw_charge_current >> 8) & 0x1); + if (ret) + goto fail; + + if (need_cgain) { + cgain_set = TWL4030_CGAIN; + cgain_clear = 0; + } else { + cgain_set = 0; + cgain_clear = TWL4030_CGAIN; + } + + ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, + cgain_clear, cgain_set, TWL4030_BCICTL1); + if (ret) + goto fail; + + ret = twl4030_bci_read(TWL4030_BCICTL1, ®); + if (ret != 0 || (reg & TWL4030_CGAIN) != cgain_set) { + dev_err(bci->dev, "CGAIN change failed\n"); + goto fail; + } + +fail: + if (restore_boot_reg && (ret2 = twl_i2c_write_u8(TWL4030_MODULE_PM_MASTER, + boot_bci_prev, TWL4030_PM_MASTER_BOOT_BCI))) { + dev_err(bci->dev, "failed boot_bci restore: %d\n", ret2); + if (!ret) + ret = ret2; + } + + if (ret) { + if (charge_current >= 0) + bci->charge_current = old_charge_current; + if (eoc_current >= 0) + bci->eoc_current = old_eoc_current; + } + +fail_norestore: + if (ret) + dev_err(bci->dev, "failed to change charging parameters\n"); + + return ret; +} + /* * TWL4030 CHG_PRES (AC charger presence) events */ 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; } @@ -243,21 +483,30 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) } /* various monitoring events, for now we just log them here */ - if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1)) + if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1) && + __ratelimit(&bci->ratelimit)) dev_warn(bci->dev, "battery temperature out of range\n"); - if (irqs1 & TWL4030_BATSTS) + if (irqs1 & TWL4030_BATSTS && __ratelimit(&bci->ratelimit)) dev_crit(bci->dev, "battery disconnected\n"); - if (irqs2 & TWL4030_VBATOV) + if (irqs2 & TWL4030_VBATOV && __ratelimit(&bci->ratelimit)) dev_crit(bci->dev, "VBAT overvoltage\n"); - if (irqs2 & TWL4030_VBUSOV) + if (irqs2 & TWL4030_VBUSOV && __ratelimit(&bci->ratelimit)) dev_crit(bci->dev, "VBUS overvoltage\n"); - if (irqs2 & TWL4030_ACCHGOV) + 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; } @@ -297,27 +546,255 @@ static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val, * CGAIN == 0: val * 1.6618 - 0.85 * CGAIN == 1: (val * 1.6618 - 0.85) * 2 */ -static int twl4030_charger_get_current(void) +static int twl4030_charger_get_current(struct twl4030_bci *bci) { int curr; int ret; - u8 bcictl1; + int cgain; curr = twl4030bci_read_adc_val(TWL4030_BCIICHG); if (curr < 0) return curr; - ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (ret) + if ((ret = twl4030bci_get_cgain(bci, &cgain))) return ret; ret = (curr * 16618 - 850 * 10000) / 10; - if (bcictl1 & TWL4030_CGAIN) + if (cgain) ret *= 2; return ret; } +static int twl4030bci_get_charging_current(struct twl4030_bci *bci, int *curr) +{ + int ret, val, cgain; + + val = twl4030bci_read_adc_val(TWL4030_BCIIREF1); + if (val < 0) + return val; + if ((ret = twl4030bci_get_cgain(bci, &cgain))) + return ret; + + val &= 0x1ff; + val *= 1666; + if (cgain) + val *= 2; + + *curr = val; + + return 0; +} + +static int twl4030bci_get_eoc_current(struct twl4030_bci *bci, int *curr) +{ + int ret, cgain, ichgeocth; + u8 reg; + + if ((ret = twl4030bci_get_cgain(bci, &cgain))) + return ret; + + ret = twl4030_bci_read(TWL4030_BCIMFTH8, ®); + if (ret < 0) { + dev_warn(bci->dev, "error fetching BCIMFTH8 register"); + return ret; + } + ichgeocth = reg >> 4; + + *curr = (ichgeocth * 13310) * ((cgain) ? 2 : 1); + + return 0; +} + +static ssize_t twl4030_bci_ac_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 boot_bci; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci, + TWL4030_PM_MASTER_BOOT_BCI); + if (ret) + return ret; + + return sprintf(buf, "%d\n", (boot_bci & TWL4030_BCIAUTOAC) ? 1 : 0); +} + +static ssize_t twl4030_bci_ac_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = container_of(psy, struct twl4030_bci, ac); + unsigned long enable; + int ret; + + ret = strict_strtoul(buf, 10, &enable); + if (ret || enable > 1) + return -EINVAL; + + bci->ac_charge_enable = enable; + twl4030_charger_enable_ac(enable); + + return count; +} +static struct device_attribute dev_attr_enable_ac = + __ATTR(enable, S_IRUGO | S_IWUSR, twl4030_bci_ac_show_enable, + twl4030_bci_ac_store_enable); + +static ssize_t twl4030_bci_usb_show_enable(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + u8 boot_bci; + int ret; + + ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &boot_bci, + TWL4030_PM_MASTER_BOOT_BCI); + if (ret) + return ret; + + return sprintf(buf, "%d\n", (boot_bci & TWL4030_BCIAUTOUSB) ? 1 : 0); +} + +static ssize_t twl4030_bci_usb_store_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = container_of(psy, struct twl4030_bci, usb); + unsigned long enable; + int ret; + + ret = strict_strtoul(buf, 10, &enable); + if (ret || enable > 1) + return -EINVAL; + + bci->usb_charge_enable = enable; + twl4030_charger_enable_usb(bci, enable); + + return count; +} +static struct device_attribute dev_attr_enable_usb = + __ATTR(enable, S_IRUGO | S_IWUSR, twl4030_bci_usb_show_enable, + twl4030_bci_usb_store_enable); + +static ssize_t show_charge_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + int ret, val; + + if ((ret = twl4030bci_get_charging_current(bci, &val))) + return ret; + + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_charge_current(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + unsigned long new_current; + int ret; + + ret = strict_strtoul(buf, 10, &new_current); + if (ret) + return -EINVAL; + + /* + * Previously, this sysfs parameter used a different raw-register + * format. All legal values in this format fall numerically into the + * range [0, 1023]. Since in the new format, these encode values so + * low as to be meaningless, reject them here so that anybody trying + * to use the old format will have at least a chance of figuring out + * why it isn't working any more. Note that in both formats, the value + * 0 has the same meaning, so it is allowed where values in the range + * [1, 1023] are not. + */ + if (new_current < 1024 && new_current != 0) + return -EINVAL; + + ret = update_charge_parameters(bci, new_current, -1); + if (ret) + return ret; + + if (psy->type == POWER_SUPPLY_TYPE_MAINS) + bci->ac_current = new_current; + else + bci->usb_current = new_current; + + return count; +} +static DEVICE_ATTR(charge_current, S_IRUGO | S_IWUSR, show_charge_current, + store_charge_current); + +static ssize_t show_end_of_charge_current(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + int ret, eoc_current; + + if ((ret = twl4030bci_get_eoc_current(bci, &eoc_current))) + return ret; + + return sprintf(buf, "%d\n", eoc_current); +} + +static ssize_t store_end_of_charge_current(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct twl4030_bci *bci = dev_get_drvdata(psy->dev->parent); + unsigned long new_eoc_current; + int eoc_current, ret; + + ret = strict_strtoul(buf, 10, &new_eoc_current); + if (ret) + return -EINVAL; + + eoc_current = new_eoc_current; + /* check for overflow */ + if (eoc_current != new_eoc_current) + return -EINVAL; + + if ((ret = update_charge_parameters(bci, -1, eoc_current))) + return ret; + + return count; +} + +static DEVICE_ATTR(end_of_charge_current, S_IRUGO | S_IWUSR, + show_end_of_charge_current, store_end_of_charge_current); + +static struct attribute *bci_ac_attrs[] = { + &dev_attr_enable_ac.attr, + &dev_attr_charge_current.attr, + &dev_attr_end_of_charge_current.attr, + NULL, +}; + +static struct attribute *bci_usb_attrs[] = { + &dev_attr_enable_usb.attr, + &dev_attr_charge_current.attr, + &dev_attr_end_of_charge_current.attr, + NULL, +}; + +static const struct attribute_group bci_ac_attr_group = { + .attrs = bci_ac_attrs, +}; + +static const struct attribute_group bci_usb_attr_group = { + .attrs = bci_usb_attrs, +}; + /* * Returns the main charge FSM state * Or < 0 on failure. @@ -355,7 +832,8 @@ static int twl4030_bci_get_property(struct power_supply *psy, 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; @@ -363,10 +841,29 @@ static int twl4030_bci_get_property(struct power_supply *psy, 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) + update_charge_parameters(bci, bci->usb_current, -1); + else + update_charge_parameters(bci, bci->ac_current, -1); + bci->current_supply = psy->type; + } switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -397,7 +894,7 @@ static int twl4030_bci_get_property(struct power_supply *psy, if (!is_charging) return -ENODATA; /* current measurement is shared between AC and USB */ - ret = twl4030_charger_get_current(); + ret = twl4030_charger_get_current(bci); if (ret < 0) return ret; val->intval = ret; @@ -423,10 +920,16 @@ static enum power_supply_property twl4030_charger_props[] = { static int __init twl4030_bci_probe(struct platform_device *pdev) { + const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; struct twl4030_bci *bci; int ret; u32 reg; + if (pdata == NULL) { + dev_err(&pdev->dev, "No platform data\n"); + return -EINVAL; + } + bci = kzalloc(sizeof(*bci), GFP_KERNEL); if (bci == NULL) return -ENOMEM; @@ -434,14 +937,40 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) bci->dev = &pdev->dev; bci->irq_chg = platform_get_irq(pdev, 0); bci->irq_bci = platform_get_irq(pdev, 1); + bci->ac_current = 1200000; /* ~1.2A */ + bci->usb_current = 530000; /* ~530mA */ + + /* + * Make sure to set sane charging settings + * + * charge_current: + * ~500mA (doesn't really get used, overridden by ac_current or + * usb_current as soon as a charger is plugged in). + * + * end_of_charge_current: + * taken from the module parameter. + */ + if ((ret = update_charge_parameters(bci, + 500000, end_of_charge_current))) + goto fail_set_charge_params; + + 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; bci->ac.num_properties = ARRAY_SIZE(twl4030_charger_props); bci->ac.get_property = twl4030_bci_get_property; + bci->ac.supplied_to = pdata->supplied_to; + bci->ac.num_supplicants = pdata->num_supplicants; ret = power_supply_register(&pdev->dev, &bci->ac); if (ret) { @@ -454,6 +983,14 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) bci->usb.properties = twl4030_charger_props; bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props); bci->usb.get_property = twl4030_bci_get_property; + bci->usb.supplied_to = pdata->supplied_to; + bci->usb.num_supplicants = pdata->num_supplicants; + + bci->usb_reg = regulator_get(bci->dev, "bci3v1"); + if (IS_ERR(bci->usb_reg)) { + dev_warn(&pdev->dev, "regulator get bci3v1 failed\n"); + bci->usb_reg = NULL; + } ret = power_supply_register(&pdev->dev, &bci->usb); if (ret) { @@ -485,6 +1022,18 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) otg_register_notifier(bci->transceiver, &bci->otg_nb); } + ret = sysfs_create_group(&bci->ac.dev->kobj, &bci_ac_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs group: %d\n", ret); + goto fail_sysfs1; + } + + ret = sysfs_create_group(&bci->usb.dev->kobj, &bci_usb_attr_group); + if (ret) { + dev_err(&pdev->dev, "failed to create sysfs group: %d\n", ret); + goto fail_sysfs2; + } + /* Enable interrupts now. */ reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | TWL4030_TBATOR1 | TWL4030_BATSTS); @@ -501,12 +1050,18 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) if (ret < 0) dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); + bci->ac_charge_enable = true; + bci->usb_charge_enable = true; twl4030_charger_enable_ac(true); twl4030_charger_enable_usb(bci, true); return 0; fail_unmask_interrupts: + sysfs_remove_group(&bci->usb.dev->kobj, &bci_usb_attr_group); +fail_sysfs2: + sysfs_remove_group(&bci->ac.dev->kobj, &bci_ac_attr_group); +fail_sysfs1: if (bci->transceiver != NULL) { otg_unregister_notifier(bci->transceiver, &bci->otg_nb); otg_put_transceiver(bci->transceiver); @@ -518,8 +1073,18 @@ fail_chg_irq: 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); + +fail_set_charge_params: kfree(bci); return ret; @@ -529,6 +1094,9 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) { struct twl4030_bci *bci = platform_get_drvdata(pdev); + sysfs_remove_group(&bci->usb.dev->kobj, &bci_usb_attr_group); + sysfs_remove_group(&bci->ac.dev->kobj, &bci_ac_attr_group); + twl4030_charger_enable_ac(false); twl4030_charger_enable_usb(bci, false); @@ -546,6 +1114,14 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) 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);