diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2012-08-01 05:08:25 +0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2012-08-01 05:08:25 +0400 |
commit | 4b24ff71108164e047cf2c95990b77651163e315 (patch) | |
tree | 4b478906379adf22ff259fc1f2cce0da796df415 /drivers/power | |
parent | bca1a5c0eabe0f17081760c61e8d08e73dd6b6a6 (diff) | |
parent | 4b1bf5871f7d59de6484cc887e205d6d2f1e6fbd (diff) | |
download | linux-4b24ff71108164e047cf2c95990b77651163e315.tar.xz |
Merge tag 'for-v3.6' of git://git.infradead.org/battery-2.6
Pull battery updates from Anton Vorontsov:
"The tag contains just a few battery-related changes for v3.6. It's is
all pretty straightforward, except one thing.
One of our patches added thermal support for power supply class, but
thermal/ subsystem changed under our feet. We (well, Stephen, that
is) caught the issue and it was decided[1] that I'd just delay the
battery pull request, and then will fix it up by merging upstream back
into battery tree at the specific commit.
That's not all though: another[2] small fixup for thermal subsystem
was needed to get rid of a warning in power supply subsystem (the
warning was not drivers/power's "fault", the thermal registration
function just needed a proper const annotation, which is also done by
a small commit on top of the merge.
So, to sum this up:
- The 'master' branch of the battery tree was in the -next tree for
weeks, was never rebased, altered etc. It should be all OK;
- Although, for-v3.6 tag contains the 'master' branch + merge + the
warning fix.
[1] http://lkml.org/lkml/2012/6/19/23
[2] http://lkml.org/lkml/2012/6/18/28"
* tag 'for-v3.6' of git://git.infradead.org/battery-2.6: (23 commits)
thermal: Constify 'type' argument for the registration routine
olpc-battery: update CHARGE_FULL_DESIGN property for BYD LiFe batteries
olpc-battery: Add VOLTAGE_MAX_DESIGN property
charger-manager: Fix build break related to EXTCON
lp8727_charger: Move header file into platform_data directory
power_supply: Add min/max alert properties for CAPACITY, TEMP, TEMP_AMBIENT
bq27x00_battery: Add support for BQ27425 chip
charger-manager: Set current limit of regulator for over current protection
charger-manager: Use EXTCON Subsystem to detect charger cables for charging
test_power: Add VOLTAGE_NOW and BATTERY_TEMP properties
test_power: Add support for USB AC source
gpio-charger: Use cansleep version of gpio_set_value
bq27x00_battery: Add support for power average and health properties
sbs-battery: Don't trigger false supply_changed event
twl4030_charger: Allow charger to control the regulator that feeds it
twl4030_charger: Add backup-battery charging
twl4030_charger: Fix some typos
max17042_battery: Support CHARGE_COUNTER power supply attribute
smb347-charger: Add constant charge and current properties
power_supply: Add constant charge_current and charge_voltage properties
...
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/Kconfig | 1 | ||||
-rw-r--r-- | drivers/power/bq27x00_battery.c | 155 | ||||
-rw-r--r-- | drivers/power/charger-manager.c | 152 | ||||
-rw-r--r-- | drivers/power/ds2781_battery.c | 2 | ||||
-rw-r--r-- | drivers/power/gpio-charger.c | 2 | ||||
-rw-r--r-- | drivers/power/lp8727_charger.c | 2 | ||||
-rw-r--r-- | drivers/power/max17042_battery.c | 8 | ||||
-rw-r--r-- | drivers/power/olpc_battery.c | 62 | ||||
-rw-r--r-- | drivers/power/pda_power.c | 4 | ||||
-rw-r--r-- | drivers/power/power_supply_core.c | 65 | ||||
-rw-r--r-- | drivers/power/power_supply_sysfs.c | 8 | ||||
-rw-r--r-- | drivers/power/sbs-battery.c | 2 | ||||
-rw-r--r-- | drivers/power/smb347-charger.c | 123 | ||||
-rw-r--r-- | drivers/power/test_power.c | 75 | ||||
-rw-r--r-- | drivers/power/twl4030_charger.c | 80 |
15 files changed, 691 insertions, 50 deletions
diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index aa764ecc4e60..c1892f321c46 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -268,6 +268,7 @@ config CHARGER_GPIO config CHARGER_MANAGER bool "Battery charger manager for multiple chargers" depends on REGULATOR && RTC_CLASS + select EXTCON help Say Y to enable charger-manager support, which allows multiple chargers attached to a battery and multiple batteries attached to a diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index f5d6d379f2fb..181ddece5181 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -22,6 +22,7 @@ * Datasheets: * http://focus.ti.com/docs/prod/folders/print/bq27000.html * http://focus.ti.com/docs/prod/folders/print/bq27500.html + * http://www.ti.com/product/bq27425-g1 */ #include <linux/module.h> @@ -51,6 +52,7 @@ #define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ #define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ #define BQ27x00_REG_AE 0x22 /* Available energy */ +#define BQ27x00_POWER_AVG 0x24 #define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ #define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ @@ -66,15 +68,21 @@ #define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ #define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ #define BQ27500_FLAG_FC BIT(9) +#define BQ27500_FLAG_OTC BIT(15) + +/* bq27425 register addresses are same as bq27x00 addresses minus 4 */ +#define BQ27425_REG_OFFSET 0x04 +#define BQ27425_REG_SOC 0x18 /* Register address plus offset */ #define BQ27000_RS 20 /* Resistor sense */ +#define BQ27x00_POWER_CONSTANT (256 * 29200 / 1000) struct bq27x00_device_info; struct bq27x00_access_methods { int (*read)(struct bq27x00_device_info *di, u8 reg, bool single); }; -enum bq27x00_chip { BQ27000, BQ27500 }; +enum bq27x00_chip { BQ27000, BQ27500, BQ27425}; struct bq27x00_reg_cache { int temperature; @@ -86,6 +94,8 @@ struct bq27x00_reg_cache { int capacity; int energy; int flags; + int power_avg; + int health; }; struct bq27x00_device_info { @@ -123,6 +133,22 @@ static enum power_supply_property bq27x00_battery_props[] = { POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, POWER_SUPPLY_PROP_CYCLE_COUNT, POWER_SUPPLY_PROP_ENERGY_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_HEALTH, +}; + +static enum power_supply_property bq27425_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, }; static unsigned int poll_interval = 360; @@ -137,10 +163,24 @@ MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \ static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg, bool single) { + if (di->chip == BQ27425) + return di->bus.read(di, reg - BQ27425_REG_OFFSET, single); return di->bus.read(di, reg, single); } /* + * Higher versions of the chip like BQ27425 and BQ27500 + * differ from BQ27000 and BQ27200 in calculation of certain + * parameters. Hence we need to check for the chip type. + */ +static bool bq27xxx_is_chip_version_higher(struct bq27x00_device_info *di) +{ + if (di->chip == BQ27425 || di->chip == BQ27500) + return true; + return false; +} + +/* * Return the battery Relative State-of-Charge * Or < 0 if something fails. */ @@ -150,6 +190,8 @@ static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di) if (di->chip == BQ27500) rsoc = bq27x00_read(di, BQ27500_REG_SOC, false); + else if (di->chip == BQ27425) + rsoc = bq27x00_read(di, BQ27425_REG_SOC, false); else rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true); @@ -174,7 +216,7 @@ static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg) return charge; } - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) charge *= 1000; else charge = charge * 3570 / BQ27000_RS; @@ -208,7 +250,7 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) { int ilmd; - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); else ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true); @@ -218,7 +260,7 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) return ilmd; } - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) ilmd *= 1000; else ilmd = ilmd * 256 * 3570 / BQ27000_RS; @@ -262,7 +304,7 @@ static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) return temp; } - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) temp -= 2731; else temp = ((temp * 5) - 5463) / 2; @@ -306,14 +348,70 @@ static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg) return tval * 60; } +/* + * Read a power avg register. + * Return < 0 if something fails. + */ +static int bq27x00_battery_read_pwr_avg(struct bq27x00_device_info *di, u8 reg) +{ + int tval; + + tval = bq27x00_read(di, reg, false); + if (tval < 0) { + dev_err(di->dev, "error reading power avg rgister %02x: %d\n", + reg, tval); + return tval; + } + + if (di->chip == BQ27500) + return tval; + else + return (tval * BQ27x00_POWER_CONSTANT) / BQ27000_RS; +} + +/* + * Read flag register. + * Return < 0 if something fails. + */ +static int bq27x00_battery_read_health(struct bq27x00_device_info *di) +{ + int tval; + + tval = bq27x00_read(di, BQ27x00_REG_FLAGS, false); + if (tval < 0) { + dev_err(di->dev, "error reading flag register:%d\n", tval); + return tval; + } + + if ((di->chip == BQ27500)) { + if (tval & BQ27500_FLAG_SOCF) + tval = POWER_SUPPLY_HEALTH_DEAD; + else if (tval & BQ27500_FLAG_OTC) + tval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } else { + if (tval & BQ27000_FLAG_EDV1) + tval = POWER_SUPPLY_HEALTH_DEAD; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } + + return -1; +} + static void bq27x00_update(struct bq27x00_device_info *di) { struct bq27x00_reg_cache cache = {0, }; bool is_bq27500 = di->chip == BQ27500; + bool is_bq27425 = di->chip == BQ27425; cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); if (cache.flags >= 0) { - if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) { + if (!is_bq27500 && !is_bq27425 + && (cache.flags & BQ27000_FLAG_CI)) { dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); cache.capacity = -ENODATA; cache.energy = -ENODATA; @@ -321,16 +419,30 @@ static void bq27x00_update(struct bq27x00_device_info *di) cache.time_to_empty_avg = -ENODATA; cache.time_to_full = -ENODATA; cache.charge_full = -ENODATA; + cache.health = -ENODATA; } else { cache.capacity = bq27x00_battery_read_rsoc(di); - cache.energy = bq27x00_battery_read_energy(di); - cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE); - cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP); - cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF); + if (!is_bq27425) { + cache.energy = bq27x00_battery_read_energy(di); + cache.time_to_empty = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTE); + cache.time_to_empty_avg = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTECP); + cache.time_to_full = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTF); + } cache.charge_full = bq27x00_battery_read_lmd(di); + cache.health = bq27x00_battery_read_health(di); } cache.temperature = bq27x00_battery_read_temperature(di); + if (!is_bq27425) + cache.cycle_count = bq27x00_battery_read_cyct(di); cache.cycle_count = bq27x00_battery_read_cyct(di); + cache.power_avg = + bq27x00_battery_read_pwr_avg(di, BQ27x00_POWER_AVG); /* We only have to read charge design full once */ if (di->charge_design_full <= 0) @@ -376,7 +488,7 @@ static int bq27x00_battery_current(struct bq27x00_device_info *di, return curr; } - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { /* bq27500 returns signed value */ val->intval = (int)((s16)curr) * 1000; } else { @@ -397,7 +509,7 @@ static int bq27x00_battery_status(struct bq27x00_device_info *di, { int status; - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { if (di->cache.flags & BQ27500_FLAG_FC) status = POWER_SUPPLY_STATUS_FULL; else if (di->cache.flags & BQ27500_FLAG_DSC) @@ -425,7 +537,7 @@ static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, { int level; - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { if (di->cache.flags & BQ27500_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; else if (di->cache.flags & BQ27500_FLAG_SOC1) @@ -550,6 +662,12 @@ static int bq27x00_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ENERGY_NOW: ret = bq27x00_simple_value(di->cache.energy, val); break; + case POWER_SUPPLY_PROP_POWER_AVG: + ret = bq27x00_simple_value(di->cache.power_avg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq27x00_simple_value(di->cache.health, val); + break; default: return -EINVAL; } @@ -570,8 +688,14 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di) int ret; di->bat.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat.properties = bq27x00_battery_props; - di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); + di->chip = BQ27425; + if (di->chip == BQ27425) { + di->bat.properties = bq27425_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props); + } else { + di->bat.properties = bq27x00_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); + } di->bat.get_property = bq27x00_battery_get_property; di->bat.external_power_changed = bq27x00_external_power_changed; @@ -729,6 +853,7 @@ static int bq27x00_battery_remove(struct i2c_client *client) static const struct i2c_device_id bq27x00_id[] = { { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ { "bq27500", BQ27500 }, + { "bq27425", BQ27425 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27x00_id); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 86935ec18954..526e5c931294 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -271,16 +271,13 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) if (enable) { if (cm->emergency_stop) return -EAGAIN; - err = regulator_bulk_enable(desc->num_charger_regulators, - desc->charger_regulators); + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_enable(desc->charger_regulators[i].consumer); } else { /* * Abnormal battery state - Stop charging forcibly, * even if charger was enabled at the other places */ - err = regulator_bulk_disable(desc->num_charger_regulators, - desc->charger_regulators); - for (i = 0; i < desc->num_charger_regulators; i++) { if (regulator_is_enabled( desc->charger_regulators[i].consumer)) { @@ -288,7 +285,7 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) desc->charger_regulators[i].consumer); dev_warn(cm->dev, "Disable regulator(%s) forcibly.\n", - desc->charger_regulators[i].supply); + desc->charger_regulators[i].regulator_name); } } } @@ -994,11 +991,92 @@ int setup_charger_manager(struct charger_global_desc *gd) } EXPORT_SYMBOL_GPL(setup_charger_manager); +/** + * charger_extcon_work - enable/diable charger according to the state + * of charger cable + * + * @work: work_struct of the function charger_extcon_work. + */ +static void charger_extcon_work(struct work_struct *work) +{ + struct charger_cable *cable = + container_of(work, struct charger_cable, wq); + int ret; + + if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) { + ret = regulator_set_current_limit(cable->charger->consumer, + cable->min_uA, cable->max_uA); + if (ret < 0) { + pr_err("Cannot set current limit of %s (%s)\n", + cable->charger->regulator_name, cable->name); + return; + } + + pr_info("Set current limit of %s : %duA ~ %duA\n", + cable->charger->regulator_name, + cable->min_uA, cable->max_uA); + } + + try_charger_enable(cable->cm, cable->attached); +} + +/** + * charger_extcon_notifier - receive the state of charger cable + * when registered cable is attached or detached. + * + * @self: the notifier block of the charger_extcon_notifier. + * @event: the cable state. + * @ptr: the data pointer of notifier block. + */ +static int charger_extcon_notifier(struct notifier_block *self, + unsigned long event, void *ptr) +{ + struct charger_cable *cable = + container_of(self, struct charger_cable, nb); + + cable->attached = event; + schedule_work(&cable->wq); + + return NOTIFY_DONE; +} + +/** + * charger_extcon_init - register external connector to use it + * as the charger cable + * + * @cm: the Charger Manager representing the battery. + * @cable: the Charger cable representing the external connector. + */ +static int charger_extcon_init(struct charger_manager *cm, + struct charger_cable *cable) +{ + int ret = 0; + + /* + * Charger manager use Extcon framework to identify + * the charger cable among various external connector + * cable (e.g., TA, USB, MHL, Dock). + */ + INIT_WORK(&cable->wq, charger_extcon_work); + cable->nb.notifier_call = charger_extcon_notifier; + ret = extcon_register_interest(&cable->extcon_dev, + cable->extcon_name, cable->name, &cable->nb); + if (ret < 0) { + pr_info("Cannot register extcon_dev for %s(cable: %s).\n", + cable->extcon_name, + cable->name); + ret = -EINVAL; + } + + return ret; +} + static int charger_manager_probe(struct platform_device *pdev) { struct charger_desc *desc = dev_get_platdata(&pdev->dev); struct charger_manager *cm; int ret = 0, i = 0; + int j = 0; union power_supply_propval val; if (g_desc && !rtc_dev && g_desc->rtc_name) { @@ -1167,11 +1245,31 @@ static int charger_manager_probe(struct platform_device *pdev) goto err_register; } - ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, - desc->charger_regulators); - if (ret) { - dev_err(&pdev->dev, "Cannot get charger regulators.\n"); - goto err_bulk_get; + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + + charger->consumer = regulator_get(&pdev->dev, + charger->regulator_name); + if (charger->consumer == NULL) { + dev_err(&pdev->dev, "Cannot find charger(%s)n", + charger->regulator_name); + ret = -EINVAL; + goto err_chg_get; + } + + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + + ret = charger_extcon_init(cm, cable); + if (ret < 0) { + dev_err(&pdev->dev, "Cannot find charger(%s)n", + charger->regulator_name); + goto err_extcon; + } + cable->charger = charger; + cable->cm = cm; + } } ret = try_charger_enable(cm, true); @@ -1197,9 +1295,19 @@ static int charger_manager_probe(struct platform_device *pdev) return 0; err_chg_enable: - regulator_bulk_free(desc->num_charger_regulators, - desc->charger_regulators); -err_bulk_get: +err_extcon: + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + extcon_unregister_interest(&cable->extcon_dev); + } + } +err_chg_get: + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); + power_supply_unregister(&cm->charger_psy); err_register: kfree(cm->charger_psy.properties); @@ -1218,6 +1326,8 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) { struct charger_manager *cm = platform_get_drvdata(pdev); struct charger_desc *desc = cm->desc; + int i = 0; + int j = 0; /* Remove from the list */ mutex_lock(&cm_list_mtx); @@ -1229,8 +1339,18 @@ static int __devexit charger_manager_remove(struct platform_device *pdev) if (delayed_work_pending(&cm_monitor_work)) cancel_delayed_work_sync(&cm_monitor_work); - regulator_bulk_free(desc->num_charger_regulators, - desc->charger_regulators); + for (i = 0 ; i < desc->num_charger_regulators ; i++) { + struct charger_regulator *charger + = &desc->charger_regulators[i]; + for (j = 0 ; j < charger->num_cables ; j++) { + struct charger_cable *cable = &charger->cables[j]; + extcon_unregister_interest(&cable->extcon_dev); + } + } + + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); + power_supply_unregister(&cm->charger_psy); try_charger_enable(cm, false); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c index 5f92a4bb33f9..7a1ff4e4cf9a 100644 --- a/drivers/power/ds2781_battery.c +++ b/drivers/power/ds2781_battery.c @@ -64,7 +64,7 @@ static inline int ds2781_battery_io(struct ds2781_device_info *dev_info, return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io); } -int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, +static int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf, int addr, size_t count) { return ds2781_battery_io(dev_info, buf, addr, count, 0); diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index 8672c9177dd7..cb2aa3195687 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -54,7 +54,7 @@ static int gpio_charger_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = gpio_get_value(pdata->gpio); + val->intval = gpio_get_value_cansleep(pdata->gpio); val->intval ^= pdata->gpio_active_low; break; default: diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index d8b75780bfef..6a364f4798f7 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -15,7 +15,7 @@ #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/power_supply.h> -#include <linux/lp8727.h> +#include <linux/platform_data/lp8727.h> #define DEBOUNCE_MSEC 270 diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 140788b309f8..74abc6c755b4 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -113,6 +113,7 @@ static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_VOLTAGE_OCV, POWER_SUPPLY_PROP_CAPACITY, POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_TEMP, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, @@ -201,6 +202,13 @@ static int max17042_get_property(struct power_supply *psy, val->intval = ret * 1000 / 2; break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + ret = max17042_read_reg(chip->client, MAX17042_QH); + if (ret < 0) + return ret; + + val->intval = ret * 1000 / 2; + break; case POWER_SUPPLY_PROP_TEMP: ret = max17042_read_reg(chip->client, MAX17042_TEMP); if (ret < 0) diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c index 7385092f9bc8..55b10b813353 100644 --- a/drivers/power/olpc_battery.c +++ b/drivers/power/olpc_battery.c @@ -231,11 +231,9 @@ static int olpc_bat_get_charge_full_design(union power_supply_propval *val) case POWER_SUPPLY_TECHNOLOGY_LiFe: switch (mfr) { - case 1: /* Gold Peak */ - val->intval = 2800000; - break; + case 1: /* Gold Peak, fall through */ case 2: /* BYD */ - val->intval = 3100000; + val->intval = 2800000; break; default: return -EIO; @@ -267,6 +265,55 @@ static int olpc_bat_get_charge_now(union power_supply_propval *val) return 0; } +static int olpc_bat_get_voltage_max_design(union power_supply_propval *val) +{ + uint8_t ec_byte; + union power_supply_propval tech; + int mfr; + int ret; + + ret = olpc_bat_get_tech(&tech); + if (ret) + return ret; + + ec_byte = BAT_ADDR_MFR_TYPE; + ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1); + if (ret) + return ret; + + mfr = ec_byte >> 4; + + switch (tech.intval) { + case POWER_SUPPLY_TECHNOLOGY_NiMH: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 6000000; + break; + default: + return -EIO; + } + break; + + case POWER_SUPPLY_TECHNOLOGY_LiFe: + switch (mfr) { + case 1: /* Gold Peak */ + val->intval = 6400000; + break; + case 2: /* BYD */ + val->intval = 6500000; + break; + default: + return -EIO; + } + break; + + default: + return -EIO; + } + + return ret; +} + /********************************************************************* * Battery properties *********************************************************************/ @@ -401,6 +448,11 @@ static int olpc_bat_get_property(struct power_supply *psy, sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf)); val->strval = bat_serial; break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + ret = olpc_bat_get_voltage_max_design(val); + if (ret) + return ret; + break; default: ret = -EINVAL; break; @@ -428,6 +480,7 @@ static enum power_supply_property olpc_xo1_bat_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, }; /* XO-1.5 does not have ambient temperature property */ @@ -449,6 +502,7 @@ static enum power_supply_property olpc_xo15_bat_props[] = { POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, }; /* EEPROM reading goes completely around the power_supply API, sadly */ diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c index 8dbcd53c5e67..6a1ca735e935 100644 --- a/drivers/power/pda_power.c +++ b/drivers/power/pda_power.c @@ -134,13 +134,13 @@ static void update_charger(void) regulator_set_current_limit(ac_draw, max_uA, max_uA); if (!regulator_enabled) { dev_dbg(dev, "charger on (AC)\n"); - regulator_enable(ac_draw); + WARN_ON(regulator_enable(ac_draw)); regulator_enabled = 1; } } else { if (regulator_enabled) { dev_dbg(dev, "charger off\n"); - regulator_disable(ac_draw); + WARN_ON(regulator_disable(ac_draw)); regulator_enabled = 0; } } diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 6ad612726785..08cc8a3c15af 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -17,6 +17,7 @@ #include <linux/device.h> #include <linux/err.h> #include <linux/power_supply.h> +#include <linux/thermal.h> #include "power_supply.h" /* exported for the APM Power driver, APM emulation */ @@ -169,6 +170,63 @@ static void power_supply_dev_release(struct device *dev) kfree(dev); } +#ifdef CONFIG_THERMAL +static int power_supply_read_temp(struct thermal_zone_device *tzd, + unsigned long *temp) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + WARN_ON(tzd == NULL); + psy = tzd->devdata; + ret = psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); + + /* Convert tenths of degree Celsius to milli degree Celsius. */ + if (!ret) + *temp = val.intval * 100; + + return ret; +} + +static struct thermal_zone_device_ops psy_tzd_ops = { + .get_temp = power_supply_read_temp, +}; + +static int psy_register_thermal(struct power_supply *psy) +{ + int i; + + /* Register battery zone device psy reports temperature */ + for (i = 0; i < psy->num_properties; i++) { + if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { + psy->tzd = thermal_zone_device_register(psy->name, 0, 0, + psy, &psy_tzd_ops, 0, 0, 0, 0); + if (IS_ERR(psy->tzd)) + return PTR_ERR(psy->tzd); + break; + } + } + return 0; +} + +static void psy_unregister_thermal(struct power_supply *psy) +{ + if (IS_ERR_OR_NULL(psy->tzd)) + return; + thermal_zone_device_unregister(psy->tzd); +} +#else +static int psy_register_thermal(struct power_supply *psy) +{ + return 0; +} + +static void psy_unregister_thermal(struct power_supply *psy) +{ +} +#endif + int power_supply_register(struct device *parent, struct power_supply *psy) { struct device *dev; @@ -197,6 +255,10 @@ int power_supply_register(struct device *parent, struct power_supply *psy) if (rc) goto device_add_failed; + rc = psy_register_thermal(psy); + if (rc) + goto register_thermal_failed; + rc = power_supply_create_triggers(psy); if (rc) goto create_triggers_failed; @@ -206,6 +268,8 @@ int power_supply_register(struct device *parent, struct power_supply *psy) goto success; create_triggers_failed: + psy_unregister_thermal(psy); +register_thermal_failed: device_del(dev); kobject_set_name_failed: device_add_failed: @@ -220,6 +284,7 @@ void power_supply_unregister(struct power_supply *psy) cancel_work_sync(&psy->changed_work); sysfs_remove_link(&psy->dev->kobj, "powers"); power_supply_remove_triggers(psy); + psy_unregister_thermal(psy); device_unregister(psy->dev); } EXPORT_SYMBOL_GPL(power_supply_unregister); diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 4150747f9186..1d96614a17a4 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -159,6 +159,8 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(charge_now), POWER_SUPPLY_ATTR(charge_avg), POWER_SUPPLY_ATTR(charge_counter), + POWER_SUPPLY_ATTR(constant_charge_current), + POWER_SUPPLY_ATTR(constant_charge_voltage), POWER_SUPPLY_ATTR(energy_full_design), POWER_SUPPLY_ATTR(energy_empty_design), POWER_SUPPLY_ATTR(energy_full), @@ -166,9 +168,15 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(energy_now), POWER_SUPPLY_ATTR(energy_avg), POWER_SUPPLY_ATTR(capacity), + POWER_SUPPLY_ATTR(capacity_alert_min), + POWER_SUPPLY_ATTR(capacity_alert_max), POWER_SUPPLY_ATTR(capacity_level), POWER_SUPPLY_ATTR(temp), + POWER_SUPPLY_ATTR(temp_alert_min), + POWER_SUPPLY_ATTR(temp_alert_max), POWER_SUPPLY_ATTR(temp_ambient), + POWER_SUPPLY_ATTR(temp_ambient_alert_min), + POWER_SUPPLY_ATTR(temp_ambient_alert_max), POWER_SUPPLY_ATTR(time_to_empty_now), POWER_SUPPLY_ATTR(time_to_empty_avg), POWER_SUPPLY_ATTR(time_to_full_now), diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index a5b6849d4123..a65e8f54157e 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -469,7 +469,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; + goto done; /* don't trigger power_supply_changed()! */ case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_FULL: diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c index f8eedd8a676f..332dd0110bda 100644 --- a/drivers/power/smb347-charger.c +++ b/drivers/power/smb347-charger.c @@ -196,6 +196,14 @@ static const unsigned int ccc_tbl[] = { 1200000, }; +/* Convert register value to current using lookup table */ +static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) +{ + if (val >= size) + return -EINVAL; + return tbl[val]; +} + /* Convert current to register value using lookup table */ static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) { @@ -841,22 +849,101 @@ fail: return ret; } +/* + * Returns the constant charge current programmed + * into the charger in uA. + */ +static int get_const_charge_current(struct smb347_charger *smb) +{ + int ret, intval; + unsigned int v; + + if (!smb347_is_ps_online(smb)) + return -ENODATA; + + ret = regmap_read(smb->regmap, STAT_B, &v); + if (ret < 0) + return ret; + + /* + * The current value is composition of FCC and PCC values + * and we can detect which table to use from bit 5. + */ + if (v & 0x20) { + intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7); + } else { + v >>= 3; + intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7); + } + + return intval; +} + +/* + * Returns the constant charge voltage programmed + * into the charger in uV. + */ +static int get_const_charge_voltage(struct smb347_charger *smb) +{ + int ret, intval; + unsigned int v; + + if (!smb347_is_ps_online(smb)) + return -ENODATA; + + ret = regmap_read(smb->regmap, STAT_A, &v); + if (ret < 0) + return ret; + + v &= STAT_A_FLOAT_VOLTAGE_MASK; + if (v > 0x3d) + v = 0x3d; + + intval = 3500000 + v * 20000; + + return intval; +} + static int smb347_mains_get_property(struct power_supply *psy, enum power_supply_property prop, union power_supply_propval *val) { struct smb347_charger *smb = container_of(psy, struct smb347_charger, mains); + int ret; - if (prop == POWER_SUPPLY_PROP_ONLINE) { + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: val->intval = smb->mains_online; - return 0; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + default: + return -EINVAL; } - return -EINVAL; + + return 0; } static enum power_supply_property smb347_mains_properties[] = { POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, }; static int smb347_usb_get_property(struct power_supply *psy, @@ -865,16 +952,40 @@ static int smb347_usb_get_property(struct power_supply *psy, { struct smb347_charger *smb = container_of(psy, struct smb347_charger, usb); + int ret; - if (prop == POWER_SUPPLY_PROP_ONLINE) { + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: val->intval = smb->usb_online; - return 0; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = get_const_charge_voltage(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = get_const_charge_current(smb); + if (ret < 0) + return ret; + else + val->intval = ret; + break; + + default: + return -EINVAL; } - return -EINVAL; + + return 0; } static enum power_supply_property smb347_usb_properties[] = { POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, }; static int smb347_battery_get_property(struct power_supply *psy, diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c index b527c93bf2f3..b99a452a4fda 100644 --- a/drivers/power/test_power.c +++ b/drivers/power/test_power.c @@ -22,11 +22,13 @@ #include <linux/vermagic.h> static int ac_online = 1; +static int usb_online = 1; static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING; static int battery_health = POWER_SUPPLY_HEALTH_GOOD; static int battery_present = 1; /* true */ static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; static int battery_capacity = 50; +static int battery_voltage = 3300; static int test_power_get_ac_property(struct power_supply *psy, enum power_supply_property psp, @@ -42,6 +44,20 @@ static int test_power_get_ac_property(struct power_supply *psy, return 0; } +static int test_power_get_usb_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = usb_online; + break; + default: + return -EINVAL; + } + return 0; +} + static int test_power_get_battery_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -86,6 +102,12 @@ static int test_power_get_battery_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW: val->intval = 3600; break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = 26; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = battery_voltage; + break; default: pr_info("%s: some properties deliberately report errors.\n", __func__); @@ -114,6 +136,8 @@ static enum power_supply_property test_power_battery_props[] = { POWER_SUPPLY_PROP_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER, POWER_SUPPLY_PROP_SERIAL_NUMBER, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, }; static char *test_power_ac_supplied_to[] = { @@ -135,6 +159,14 @@ static struct power_supply test_power_supplies[] = { .properties = test_power_battery_props, .num_properties = ARRAY_SIZE(test_power_battery_props), .get_property = test_power_get_battery_property, + }, { + .name = "test_usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = test_power_get_usb_property, }, }; @@ -167,6 +199,7 @@ static void __exit test_power_exit(void) /* Let's see how we handle changes... */ ac_online = 0; + usb_online = 0; battery_status = POWER_SUPPLY_STATUS_DISCHARGING; for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) power_supply_changed(&test_power_supplies[i]); @@ -275,6 +308,19 @@ static int param_get_ac_online(char *buffer, const struct kernel_param *kp) return strlen(buffer); } +static int param_set_usb_online(const char *key, const struct kernel_param *kp) +{ + usb_online = map_get_value(map_ac_online, key, usb_online); + power_supply_changed(&test_power_supplies[2]); + return 0; +} + +static int param_get_usb_online(char *buffer, const struct kernel_param *kp) +{ + strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown")); + return strlen(buffer); +} + static int param_set_battery_status(const char *key, const struct kernel_param *kp) { @@ -350,13 +396,31 @@ static int param_set_battery_capacity(const char *key, #define param_get_battery_capacity param_get_int +static int param_set_battery_voltage(const char *key, + const struct kernel_param *kp) +{ + int tmp; + + if (1 != sscanf(key, "%d", &tmp)) + return -EINVAL; + + battery_voltage = tmp; + power_supply_changed(&test_power_supplies[1]); + return 0; +} +#define param_get_battery_voltage param_get_int static struct kernel_param_ops param_ops_ac_online = { .set = param_set_ac_online, .get = param_get_ac_online, }; +static struct kernel_param_ops param_ops_usb_online = { + .set = param_set_usb_online, + .get = param_get_usb_online, +}; + static struct kernel_param_ops param_ops_battery_status = { .set = param_set_battery_status, .get = param_get_battery_status, @@ -382,18 +446,27 @@ static struct kernel_param_ops param_ops_battery_capacity = { .get = param_get_battery_capacity, }; +static struct kernel_param_ops param_ops_battery_voltage = { + .set = param_set_battery_voltage, + .get = param_get_battery_voltage, +}; #define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_usb_online(name, p) __param_check(name, p, void); #define param_check_battery_status(name, p) __param_check(name, p, void); #define param_check_battery_present(name, p) __param_check(name, p, void); #define param_check_battery_technology(name, p) __param_check(name, p, void); #define param_check_battery_health(name, p) __param_check(name, p, void); #define param_check_battery_capacity(name, p) __param_check(name, p, void); +#define param_check_battery_voltage(name, p) __param_check(name, p, void); module_param(ac_online, ac_online, 0644); MODULE_PARM_DESC(ac_online, "AC charging state <on|off>"); +module_param(usb_online, usb_online, 0644); +MODULE_PARM_DESC(usb_online, "USB charging state <on|off>"); + module_param(battery_status, battery_status, 0644); MODULE_PARM_DESC(battery_status, "battery status <charging|discharging|not-charging|full>"); @@ -413,6 +486,8 @@ MODULE_PARM_DESC(battery_health, module_param(battery_capacity, battery_capacity, 0644); MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); +module_param(battery_voltage, battery_voltage, 0644); +MODULE_PARM_DESC(battery_voltage, "battery voltage (millivolts)"); MODULE_DESCRIPTION("Power supply driver for testing"); MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 7cacbaa68efe..15f4d5d8611b 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -22,6 +22,7 @@ #include <linux/power_supply.h> #include <linux/notifier.h> #include <linux/usb/otg.h> +#include <linux/regulator/machine.h> #define TWL4030_BCIMSTATEC 0x02 #define TWL4030_BCIICHG 0x08 @@ -29,6 +30,7 @@ #define TWL4030_BCIVBUS 0x0c #define TWL4030_BCIMFSTS4 0x10 #define TWL4030_BCICTL1 0x23 +#define TWL4030_BB_CFG 0x12 #define TWL4030_BCIAUTOWEN BIT(5) #define TWL4030_CONFIG_DONE BIT(4) @@ -38,6 +40,17 @@ #define TWL4030_USBFASTMCHG BIT(2) #define TWL4030_STS_VBUS BIT(7) #define TWL4030_STS_USB_ID BIT(2) +#define TWL4030_BBCHEN BIT(4) +#define TWL4030_BBSEL_MASK 0b1100 +#define TWL4030_BBSEL_2V5 0b0000 +#define TWL4030_BBSEL_3V0 0b0100 +#define TWL4030_BBSEL_3V1 0b1000 +#define TWL4030_BBSEL_3V2 0b1100 +#define TWL4030_BBISEL_MASK 0b11 +#define TWL4030_BBISEL_25uA 0b00 +#define TWL4030_BBISEL_150uA 0b01 +#define TWL4030_BBISEL_500uA 0b10 +#define TWL4030_BBISEL_1000uA 0b11 /* BCI interrupts */ #define TWL4030_WOVF BIT(0) /* Watchdog overflow */ @@ -75,6 +88,8 @@ struct twl4030_bci { struct work_struct work; int irq_chg; int irq_bci; + struct regulator *usb_reg; + int usb_enabled; unsigned long event; }; @@ -104,7 +119,7 @@ static int twl4030_bci_read(u8 reg, u8 *val) 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); } @@ -152,14 +167,14 @@ 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) { - /* Check for USB charger conneted */ + /* Check for USB charger connected */ if (!twl4030_bci_have_vbus(bci)) return -ENODEV; @@ -172,6 +187,12 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) return -EACCES; } + /* Need to keep regulator on */ + if (!bci->usb_enabled) { + regulator_enable(bci->usb_reg); + 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) @@ -182,6 +203,10 @@ 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); + bci->usb_enabled = 0; + } } return ret; @@ -203,6 +228,49 @@ static int twl4030_charger_enable_ac(bool enable) } /* + * Enable/Disable charging of Backup Battery. + */ +static int twl4030_charger_enable_backup(int uvolt, int uamp) +{ + int ret; + u8 flags; + + if (uvolt < 2500000 || + uamp < 25) { + /* disable charging of backup battery */ + ret = twl4030_clear_set(TWL4030_MODULE_PM_RECEIVER, + TWL4030_BBCHEN, 0, TWL4030_BB_CFG); + return ret; + } + + flags = TWL4030_BBCHEN; + if (uvolt >= 3200000) + flags |= TWL4030_BBSEL_3V2; + else if (uvolt >= 3100000) + flags |= TWL4030_BBSEL_3V1; + else if (uvolt >= 3000000) + flags |= TWL4030_BBSEL_3V0; + else + flags |= TWL4030_BBSEL_2V5; + + if (uamp >= 1000) + flags |= TWL4030_BBISEL_1000uA; + else if (uamp >= 500) + flags |= TWL4030_BBISEL_500uA; + else if (uamp >= 150) + flags |= TWL4030_BBISEL_150uA; + else + flags |= TWL4030_BBISEL_25uA; + + ret = twl4030_clear_set(TWL4030_MODULE_PM_RECEIVER, + TWL4030_BBSEL_MASK | TWL4030_BBISEL_MASK, + flags, + TWL4030_BB_CFG); + + return ret; +} + +/* * TWL4030 CHG_PRES (AC charger presence) events */ static irqreturn_t twl4030_charger_interrupt(int irq, void *arg) @@ -425,6 +493,7 @@ static enum power_supply_property twl4030_charger_props[] = { static int __init twl4030_bci_probe(struct platform_device *pdev) { struct twl4030_bci *bci; + struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data; int ret; u32 reg; @@ -456,6 +525,8 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props); bci->usb.get_property = twl4030_bci_get_property; + bci->usb_reg = regulator_get(bci->dev, "bci3v1"); + ret = power_supply_register(&pdev->dev, &bci->usb); if (ret) { dev_err(&pdev->dev, "failed to register usb: %d\n", ret); @@ -504,6 +575,8 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_ac(true); twl4030_charger_enable_usb(bci, true); + twl4030_charger_enable_backup(pdata->bb_uvolt, + pdata->bb_uamp); return 0; @@ -532,6 +605,7 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) twl4030_charger_enable_ac(false); twl4030_charger_enable_usb(bci, false); + twl4030_charger_enable_backup(0, 0); /* mask interrupts */ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, |