/* * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) * then AC is available.
*/ staticinlineint ac_available(struct iio_channel *channel_vac)
{ int val, err;
if (!channel_vac) return 0;
err = iio_read_channel_processed(channel_vac, &val); if (err < 0) return 0; return val > 4500;
}
struct twl4030_bci { struct device *dev; struct power_supply *ac; struct power_supply *usb; struct usb_phy *transceiver; struct notifier_block usb_nb; struct work_struct work; int irq_chg; int irq_bci; int usb_enabled;
/* * ichg_* and *_cur values in uA. If any are 'large', we set * CGAIN to '1' which doubles the range for half the * precision.
*/ unsignedint ichg_eoc, ichg_lo, ichg_hi; unsignedint usb_cur, ac_cur; struct iio_channel *channel_vac; bool ac_is_active; int usb_mode, ac_mode; /* charging mode requested */ #define CHARGE_OFF 0 #define CHARGE_AUTO 1 #define CHARGE_LINEAR 2
/* When setting the USB current we slowly increase the * requested current until target is reached or the voltage * drops below 4.75V. In the latter case we step back one * step.
*/ unsignedint usb_cur_target; struct delayed_work current_worker; #define USB_CUR_STEP 20000 /* 20mA at a time */ #define USB_MIN_VOLT 4750000 /* 4.75V */ #define USB_CUR_DELAY msecs_to_jiffies(100) #define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */
/* * clear and set bits on an given register on a given module
*/ staticint twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg)
{
u8 val = 0; int ret;
ret = twl_i2c_read_u8(mod_no, &val, reg); if (ret) return ret;
/* * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11) * and AC is enabled, set current for 'ac'
*/ if (ac_available(bci->channel_vac)) {
cur = bci->ac_cur;
bci->ac_is_active = true;
} else {
cur = bci->usb_cur;
bci->ac_is_active = false; if (cur > bci->usb_cur_target) {
cur = bci->usb_cur_target;
bci->usb_cur = cur;
} if (cur < bci->usb_cur_target)
schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
}
/* First, check thresholds and see if cgain is needed */ if (bci->ichg_eoc >= 200000)
cgain = true; if (bci->ichg_lo >= 400000)
cgain = true; if (bci->ichg_hi >= 820000)
cgain = true; if (cur > 852000)
cgain = true;
status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); if (status < 0) return status; if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci,
TWL4030_PM_MASTER_BOOT_BCI) < 0)
boot_bci = 0;
boot_bci &= 7;
if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) /* Need to turn for charging while we change the * CGAIN bit. Leave it off while everything is * updated.
*/
twl4030_clear_set_boot_bci(boot_bci, 0);
/* * For ichg_eoc, the hardware only supports reg values matching * 100XXXX000, and requires the XXXX be stored in the high nibble * of TWL4030_BCIMFTH8.
*/
reg = ua2regval(bci->ichg_eoc, cgain); if (reg > 0x278)
reg = 0x278; if (reg < 0x200)
reg = 0x200;
reg = (reg >> 3) & 0xf;
fullreg = reg << 4;
/* * For ichg_lo, reg value must match 10XXXX0000. * XXXX is stored in low nibble of TWL4030_BCIMFTH8.
*/
reg = ua2regval(bci->ichg_lo, cgain); if (reg > 0x2F0)
reg = 0x2F0; if (reg < 0x200)
reg = 0x200;
reg = (reg >> 4) & 0xf;
fullreg |= reg;
/* ichg_eoc and ichg_lo live in same register */
status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg); if (status < 0) return status; if (oldreg != fullreg) {
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4,
TWL4030_BCIMFKEY); if (status < 0) return status;
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
fullreg, TWL4030_BCIMFTH8);
}
/* ichg_hi threshold must be 1XXXX01100 (I think) */
reg = ua2regval(bci->ichg_hi, cgain); if (reg > 0x3E0)
reg = 0x3E0; if (reg < 0x200)
reg = 0x200;
fullreg = (reg >> 5) & 0xF;
fullreg <<= 4;
status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg); if (status < 0) return status; if ((oldreg & 0xF0) != fullreg) {
fullreg |= (oldreg & 0x0F);
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
TWL4030_BCIMFKEY); if (status < 0) return status;
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
fullreg, TWL4030_BCIMFTH9);
}
/* * And finally, set the current. This is stored in * two registers.
*/
reg = ua2regval(cur, cgain); /* we have only 10 bits */ if (reg > 0x3ff)
reg = 0x3ff;
status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg); if (status < 0) return status;
cur_reg = oldreg;
status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg); if (status < 0) return status;
cur_reg |= oldreg << 8; if (reg != cur_reg) { /* disable write protection for one write access for
* BCIIREF */
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
TWL4030_BCIMFKEY); if (status < 0) return status;
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
(reg & 0x100) ? 3 : 2,
TWL4030_BCIIREF2); if (status < 0) return status; /* disable write protection for one write access for
* BCIIREF */
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
TWL4030_BCIMFKEY); if (status < 0) return status;
status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
reg & 0xff,
TWL4030_BCIIREF1);
} if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) { /* Flip CGAIN and re-enable charging */
bcictl1 ^= TWL4030_CGAIN;
twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
bcictl1, TWL4030_BCICTL1);
twl4030_clear_set_boot_bci(0, boot_bci);
} return 0;
}
staticint twl4030_charger_get_current(void);
staticvoid twl4030_current_worker(struct work_struct *data)
{ int v, curr; int res; struct twl4030_bci *bci = container_of(data, struct twl4030_bci,
current_worker.work);
res = twl4030bci_read_adc_val(TWL4030_BCIVBUS); if (res < 0)
v = 0; else /* BCIVBUS uses ADCIN8, 7/1023 V/step */
v = res * 6843;
curr = twl4030_charger_get_current();
if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) { /* charger state change, inform the core */
power_supply_changed(bci->ac);
power_supply_changed(bci->usb);
}
twl4030_charger_update_current(bci);
/* various monitoring events, for now we just log them here */ if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
dev_warn(bci->dev, "battery temperature out of range\n");
if (irqs1 & TWL4030_BATSTS)
dev_crit(bci->dev, "battery disconnected\n");
if (irqs2 & TWL4030_VBATOV)
dev_crit(bci->dev, "VBAT overvoltage\n");
if (irqs2 & TWL4030_VBUSOV)
dev_crit(bci->dev, "VBUS overvoltage\n");
if (irqs2 & TWL4030_ACCHGOV)
dev_crit(bci->dev, "Ac charger overvoltage\n");
/* * Returns the main charge FSM state * Or < 0 on failure.
*/ staticint twl4030bci_state(struct twl4030_bci *bci)
{ int ret;
u8 state;
ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state); if (ret) {
dev_err(bci->dev, "error reading BCIMSTATEC\n"); return ret;
}
dev_dbg(bci->dev, "state: %02x\n", state);
return state;
}
staticint twl4030_bci_state_to_status(int state)
{
state &= TWL4030_MSTATEC_MASK; if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7) return POWER_SUPPLY_STATUS_CHARGING; elseif (TWL4030_MSTATEC_COMPLETE1 <= state &&
state <= TWL4030_MSTATEC_COMPLETE4) return POWER_SUPPLY_STATUS_FULL; else return POWER_SUPPLY_STATUS_NOT_CHARGING;
}
staticint twl4030_bci_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val)
{ struct twl4030_bci *bci = dev_get_drvdata(psy->dev.parent); int is_charging; int state; int ret;
state = twl4030bci_state(bci); if (state < 0) return state;
if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
is_charging = state & TWL4030_MSTATEC_USB; else
is_charging = state & TWL4030_MSTATEC_AC; if (!is_charging) {
u8 s;
ret = twl4030_bci_read(TWL4030_BCIMDEN, &s); if (ret < 0) return ret; if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
is_charging = s & 1; else
is_charging = s & 2; if (is_charging) /* A little white lie */
state = TWL4030_MSTATEC_QUICK1;
}
switch (psp) { case POWER_SUPPLY_PROP_STATUS: if (is_charging)
val->intval = twl4030_bci_state_to_status(state); else
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: /* charging must be active for meaningful result */ if (!is_charging) return -ENODATA; if (psy->desc->type == POWER_SUPPLY_TYPE_USB) {
ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS); if (ret < 0) return ret; /* BCIVBUS uses ADCIN8, 7/1023 V/step */
val->intval = ret * 6843;
} else {
ret = twl4030bci_read_adc_val(TWL4030_BCIVAC); if (ret < 0) return ret; /* BCIVAC uses ADCIN11, 10/1023 V/step */
val->intval = ret * 9775;
} break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (!is_charging) return -ENODATA; /* current measurement is shared between AC and USB */
ret = twl4030_charger_get_current(); if (ret < 0) return ret;
val->intval = ret; break; case POWER_SUPPLY_PROP_ONLINE:
val->intval = is_charging &&
twl4030_bci_state_to_status(state) !=
POWER_SUPPLY_STATUS_NOT_CHARGING; break; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = -1; if (psy->desc->type != POWER_SUPPLY_TYPE_USB) { if (!bci->ac_is_active)
val->intval = bci->ac_cur;
} else { if (bci->ac_is_active)
val->intval = bci->usb_cur_target;
} if (val->intval < 0) {
u8 bcictl1;
reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
TWL4030_INTERRUPTS_BCIIMR2A); if (ret < 0)
dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
twl4030_charger_update_current(bci); if (device_create_file(&bci->usb->dev, &dev_attr_mode))
dev_warn(&pdev->dev, "could not create sysfs file\n"); if (device_create_file(&bci->ac->dev, &dev_attr_mode))
dev_warn(&pdev->dev, "could not create sysfs file\n");
twl4030_charger_enable_ac(bci, true); if (!IS_ERR_OR_NULL(bci->transceiver))
twl4030_bci_usb_ncb(&bci->usb_nb,
bci->transceiver->last_event,
NULL); else
twl4030_charger_enable_usb(bci, false); if (pdata)
twl4030_charger_enable_backup(pdata->bb_uvolt,
pdata->bb_uamp); else
twl4030_charger_enable_backup(0, 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.