From 8e59c7f23410d5ca6b350a178b861a3d68c49edf Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 19 May 2015 16:13:01 +0900 Subject: power_supply: Fix NULL pointer dereference during bq27x00_battery probe Power supply is often registered during probe of a driver. The power_supply_register() returns pointer to newly allocated structure as return value. However before returning the power_supply_register() calls back the get_property() method provided by the driver through uevent. In that time the driver probe is still in progress and driver did not assigned pointer to power supply to its local variables. This leads to NULL pointer dereference from get_property() function. Starting from bq27x00_battery_probe(): di->bat = power_supply_register() device_add() kobject_uevent() power_supply_uevent() power_supply_show_property() power_supply_get_property() bq27x00_battery_get_property() dereference of (di->bat) which is NULL here The first uevent of power supply (the one coming from device creation) should not call back to the driver. To prevent that from happening, increment the atomic use counter at the end of power_supply_register(). This means that power_supply_get_property() will return -ENODEV. IMPORTANT: The patch has impact on this first uevent sent from power supply because it will not contain properties from power supply. The uevent with properties will be sent later after indicating that power supply has changed. This also has a race now, but will be fixed in other patches. Reported-by: H. Nikolaus Schaller Signed-off-by: Krzysztof Kozlowski Fixes: 297d716f6260 ("power_supply: Change ownership from driver to core") Tested-By: Dr. H. Nikolaus Schaller Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_core.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 2ed4a4a6b3c5..15da277e0e8d 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -659,7 +659,6 @@ __power_supply_register(struct device *parent, dev->release = power_supply_dev_release; dev_set_drvdata(dev, psy); psy->desc = desc; - atomic_inc(&psy->use_cnt); if (cfg) { psy->drv_data = cfg->drv_data; psy->of_node = cfg->of_node; @@ -700,6 +699,16 @@ __power_supply_register(struct device *parent, if (rc) goto create_triggers_failed; + /* + * Update use_cnt after any uevents (most notably from device_add()). + * We are here still during driver's probe but + * the power_supply_uevent() calls back driver's get_property + * method so: + * 1. Driver did not assigned the returned struct power_supply, + * 2. Driver could not finish initialization (anything in its probe + * after calling power_supply_register()). + */ + atomic_inc(&psy->use_cnt); power_supply_changed(psy); return psy; -- cgit v1.2.3-58-ga151 From 7f1a57fdd6cb6e7be2ed31878a34655df38e1861 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 19 May 2015 16:13:02 +0900 Subject: power_supply: Fix possible NULL pointer dereference on early uevent Don't call the power_supply_changed() from power_supply_register() when parent is still probing because it may lead to accessing parent too early. In bq27x00_battery this caused NULL pointer exception because uevent of power_supply_changed called back the the get_property() method provided by the driver. The get_property() method accessed pointer which should be returned by power_supply_register(). Starting from bq27x00_battery_probe(): di->bat = power_supply_register() power_supply_changed() kobject_uevent() power_supply_uevent() power_supply_show_property() power_supply_get_property() bq27x00_battery_get_property() dereference of di->bat which is NULL here The dereference of di->bat (value returned by power_supply_register()) is the currently visible problem. However calling back the methods provided by driver before ending the probe may lead to accessing other driver-related data which is not yet initialized. The call to power_supply_changed() is postponed till probing ends - mutex of parent device is released. Reported-by: H. Nikolaus Schaller Signed-off-by: Krzysztof Kozlowski Fixes: 297d716f6260 ("power_supply: Change ownership from driver to core") Tested-By: Dr. H. Nikolaus Schaller Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_core.c | 50 +++++++++++++++++++++++++++++++++++---- include/linux/power_supply.h | 1 + 2 files changed, 46 insertions(+), 5 deletions(-) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 15da277e0e8d..4bc0c7f459a5 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -30,6 +30,8 @@ EXPORT_SYMBOL_GPL(power_supply_notifier); static struct device_type power_supply_dev_type; +#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10) + static bool __power_supply_is_supplied_by(struct power_supply *supplier, struct power_supply *supply) { @@ -121,6 +123,30 @@ void power_supply_changed(struct power_supply *psy) } EXPORT_SYMBOL_GPL(power_supply_changed); +/* + * Notify that power supply was registered after parent finished the probing. + * + * Often power supply is registered from driver's probe function. However + * calling power_supply_changed() directly from power_supply_register() + * would lead to execution of get_property() function provided by the driver + * too early - before the probe ends. + * + * Avoid that by waiting on parent's mutex. + */ +static void power_supply_deferred_register_work(struct work_struct *work) +{ + struct power_supply *psy = container_of(work, struct power_supply, + deferred_register_work.work); + + if (psy->dev.parent) + mutex_lock(&psy->dev.parent->mutex); + + power_supply_changed(psy); + + if (psy->dev.parent) + mutex_unlock(&psy->dev.parent->mutex); +} + #ifdef CONFIG_OF #include @@ -645,6 +671,10 @@ __power_supply_register(struct device *parent, struct power_supply *psy; int rc; + if (!parent) + pr_warn("%s: Expected proper parent device for '%s'\n", + __func__, desc->name); + psy = kzalloc(sizeof(*psy), GFP_KERNEL); if (!psy) return ERR_PTR(-ENOMEM); @@ -671,6 +701,8 @@ __power_supply_register(struct device *parent, goto dev_set_name_failed; INIT_WORK(&psy->changed_work, power_supply_changed_work); + INIT_DELAYED_WORK(&psy->deferred_register_work, + power_supply_deferred_register_work); rc = power_supply_check_supplies(psy); if (rc) { @@ -709,7 +741,10 @@ __power_supply_register(struct device *parent, * after calling power_supply_register()). */ atomic_inc(&psy->use_cnt); - power_supply_changed(psy); + + queue_delayed_work(system_power_efficient_wq, + &psy->deferred_register_work, + POWER_SUPPLY_DEFERRED_REGISTER_TIME); return psy; @@ -729,7 +764,8 @@ dev_set_name_failed: /** * power_supply_register() - Register new power supply - * @parent: Device to be a parent of power supply's device + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this * @desc: Description of power supply, must be valid through whole * lifetime of this power supply * @cfg: Run-time specific configuration accessed during registering, @@ -750,7 +786,8 @@ EXPORT_SYMBOL_GPL(power_supply_register); /** * power_supply_register() - Register new non-waking-source power supply - * @parent: Device to be a parent of power supply's device + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this * @desc: Description of power supply, must be valid through whole * lifetime of this power supply * @cfg: Run-time specific configuration accessed during registering, @@ -779,7 +816,8 @@ static void devm_power_supply_release(struct device *dev, void *res) /** * power_supply_register() - Register managed power supply - * @parent: Device to be a parent of power supply's device + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this * @desc: Description of power supply, must be valid through whole * lifetime of this power supply * @cfg: Run-time specific configuration accessed during registering, @@ -814,7 +852,8 @@ EXPORT_SYMBOL_GPL(devm_power_supply_register); /** * power_supply_register() - Register managed non-waking-source power supply - * @parent: Device to be a parent of power supply's device + * @parent: Device to be a parent of power supply's device, usually + * the device which probe function calls this * @desc: Description of power supply, must be valid through whole * lifetime of this power supply * @cfg: Run-time specific configuration accessed during registering, @@ -858,6 +897,7 @@ void power_supply_unregister(struct power_supply *psy) { WARN_ON(atomic_dec_return(&psy->use_cnt)); cancel_work_sync(&psy->changed_work); + cancel_delayed_work_sync(&psy->deferred_register_work); sysfs_remove_link(&psy->dev.kobj, "powers"); power_supply_remove_triggers(psy); psy_unregister_cooler(psy); diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 75a1dd8dc56e..a80f1fd01ddb 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -237,6 +237,7 @@ struct power_supply { /* private */ struct device dev; struct work_struct changed_work; + struct delayed_work deferred_register_work; spinlock_t changed_lock; bool changed; atomic_t use_cnt; -- cgit v1.2.3-58-ga151 From 2219a935963e5eeb19e5abfb99475fcc06bbb804 Mon Sep 17 00:00:00 2001 From: Laurentiu Palcu Date: Thu, 16 Apr 2015 12:31:16 +0300 Subject: power_supply: Add TI BQ24257 charger driver Based on the datasheet found here: http://www.ti.com/lit/ds/symlink/bq24257.pdf Signed-off-by: Laurentiu Palcu Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power/bq24257.txt | 21 + drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/bq24257_charger.c | 863 +++++++++++++++++++++ 4 files changed, 892 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/bq24257.txt create mode 100644 drivers/power/bq24257_charger.c (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/power/bq24257.txt b/Documentation/devicetree/bindings/power/bq24257.txt new file mode 100644 index 000000000000..5c9d3940d07c --- /dev/null +++ b/Documentation/devicetree/bindings/power/bq24257.txt @@ -0,0 +1,21 @@ +Binding for TI bq24257 Li-Ion Charger + +Required properties: +- compatible: Should contain one of the following: + * "ti,bq24257" +- reg: integer, i2c address of the device. +- ti,battery-regulation-voltage: integer, maximum charging voltage in uV. +- ti,charge-current: integer, maximum charging current in uA. +- ti,termination-current: integer, charge will be terminated when current in + constant-voltage phase drops below this value (in uA). + +Example: + +bq24257 { + compatible = "ti,bq24257"; + reg = <0x6a>; + + ti,battery-regulation-voltage = <4200000>; + ti,charge-current = <1000000>; + ti,termination-current = <50000>; +}; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 4091fb092d06..20352a7a0081 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -388,6 +388,13 @@ config CHARGER_BQ24190 help Say Y to enable support for the TI BQ24190 battery charger. +config CHARGER_BQ24257 + tristate "TI BQ24257 battery charger driver" + depends on I2C && GPIOLIB + depends on REGMAP_I2C + help + Say Y to enable support for the TI BQ24257 battery charger. + config CHARGER_BQ24735 tristate "TI BQ24735 battery charger support" depends on I2C && GPIOLIB diff --git a/drivers/power/Makefile b/drivers/power/Makefile index b7b0181c95e5..19a1f4186d4d 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c new file mode 100644 index 000000000000..ce7f5bbfd5e3 --- /dev/null +++ b/drivers/power/bq24257_charger.c @@ -0,0 +1,863 @@ +/* + * TI BQ24257 charger driver + * + * Copyright (C) 2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BQ24257_REG_1 0x00 +#define BQ24257_REG_2 0x01 +#define BQ24257_REG_3 0x02 +#define BQ24257_REG_4 0x03 +#define BQ24257_REG_5 0x04 +#define BQ24257_REG_6 0x05 +#define BQ24257_REG_7 0x06 + +#define BQ24257_MANUFACTURER "Texas Instruments" +#define BQ24257_STAT_IRQ "stat" +#define BQ24257_PG_GPIO "pg" + +#define BQ24257_ILIM_SET_DELAY 1000 /* msec */ + +enum bq24257_fields { + F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT, /* REG 1 */ + F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE, /* REG 2 */ + F_VBAT, F_USB_DET, /* REG 3 */ + F_ICHG, F_ITERM, /* REG 4 */ + F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */ + F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_STAT, /* REG 6 */ + F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */ + + F_MAX_FIELDS +}; + +/* initial field values, converted from uV/uA */ +struct bq24257_init_data { + u8 ichg; /* charge current */ + u8 vbat; /* regulation voltage */ + u8 iterm; /* termination current */ +}; + +struct bq24257_state { + u8 status; + u8 fault; + bool power_good; +}; + +struct bq24257_device { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + + struct regmap *rmap; + struct regmap_field *rmap_fields[F_MAX_FIELDS]; + + struct gpio_desc *pg; + + struct delayed_work iilimit_setup_work; + + struct bq24257_init_data init_data; + struct bq24257_state state; + + struct mutex lock; /* protect state data */ +}; + +static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case BQ24257_REG_2: + case BQ24257_REG_4: + return false; + + default: + return true; + } +} + +static const struct regmap_config bq24257_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = BQ24257_REG_7, + .cache_type = REGCACHE_RBTREE, + + .volatile_reg = bq24257_is_volatile_reg, +}; + +static const struct reg_field bq24257_reg_fields[] = { + /* REG 1 */ + [F_WD_FAULT] = REG_FIELD(BQ24257_REG_1, 7, 7), + [F_WD_EN] = REG_FIELD(BQ24257_REG_1, 6, 6), + [F_STAT] = REG_FIELD(BQ24257_REG_1, 4, 5), + [F_FAULT] = REG_FIELD(BQ24257_REG_1, 0, 3), + /* REG 2 */ + [F_RESET] = REG_FIELD(BQ24257_REG_2, 7, 7), + [F_IILIMIT] = REG_FIELD(BQ24257_REG_2, 4, 6), + [F_EN_STAT] = REG_FIELD(BQ24257_REG_2, 3, 3), + [F_EN_TERM] = REG_FIELD(BQ24257_REG_2, 2, 2), + [F_CE] = REG_FIELD(BQ24257_REG_2, 1, 1), + [F_HZ_MODE] = REG_FIELD(BQ24257_REG_2, 0, 0), + /* REG 3 */ + [F_VBAT] = REG_FIELD(BQ24257_REG_3, 2, 7), + [F_USB_DET] = REG_FIELD(BQ24257_REG_3, 0, 1), + /* REG 4 */ + [F_ICHG] = REG_FIELD(BQ24257_REG_4, 3, 7), + [F_ITERM] = REG_FIELD(BQ24257_REG_4, 0, 2), + /* REG 5 */ + [F_LOOP_STATUS] = REG_FIELD(BQ24257_REG_5, 6, 7), + [F_LOW_CHG] = REG_FIELD(BQ24257_REG_5, 5, 5), + [F_DPDM_EN] = REG_FIELD(BQ24257_REG_5, 4, 4), + [F_CE_STATUS] = REG_FIELD(BQ24257_REG_5, 3, 3), + [F_VINDPM] = REG_FIELD(BQ24257_REG_5, 0, 2), + /* REG 6 */ + [F_X2_TMR_EN] = REG_FIELD(BQ24257_REG_6, 7, 7), + [F_TMR] = REG_FIELD(BQ24257_REG_6, 5, 6), + [F_SYSOFF] = REG_FIELD(BQ24257_REG_6, 4, 4), + [F_TS_STAT] = REG_FIELD(BQ24257_REG_6, 0, 2), + /* REG 7 */ + [F_VOVP] = REG_FIELD(BQ24257_REG_7, 5, 7), + [F_CLR_VDP] = REG_FIELD(BQ24257_REG_7, 4, 4), + [F_FORCE_BATDET] = REG_FIELD(BQ24257_REG_7, 3, 3), + [F_FORCE_PTM] = REG_FIELD(BQ24257_REG_7, 2, 2) +}; + +static const u32 bq24257_vbat_map[] = { + 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, + 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, + 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, + 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, + 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, + 4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000 +}; + +#define BQ24257_VBAT_MAP_SIZE ARRAY_SIZE(bq24257_vbat_map) + +static const u32 bq24257_ichg_map[] = { + 500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000, + 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000, + 1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000, + 1750000, 1800000, 1850000, 1900000, 1950000, 2000000 +}; + +#define BQ24257_ICHG_MAP_SIZE ARRAY_SIZE(bq24257_ichg_map) + +static const u32 bq24257_iterm_map[] = { + 50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000 +}; + +#define BQ24257_ITERM_MAP_SIZE ARRAY_SIZE(bq24257_iterm_map) + +static int bq24257_field_read(struct bq24257_device *bq, + enum bq24257_fields field_id) +{ + int ret; + int val; + + ret = regmap_field_read(bq->rmap_fields[field_id], &val); + if (ret < 0) + return ret; + + return val; +} + +static int bq24257_field_write(struct bq24257_device *bq, + enum bq24257_fields field_id, u8 val) +{ + return regmap_field_write(bq->rmap_fields[field_id], val); +} + +static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size) +{ + u8 idx; + + for (idx = 1; idx < map_size; idx++) + if (value < map[idx]) + break; + + return idx - 1; +} + +enum bq24257_status { + STATUS_READY, + STATUS_CHARGE_IN_PROGRESS, + STATUS_CHARGE_DONE, + STATUS_FAULT, +}; + +enum bq24257_fault { + FAULT_NORMAL, + FAULT_INPUT_OVP, + FAULT_INPUT_UVLO, + FAULT_SLEEP, + FAULT_BAT_TS, + FAULT_BAT_OVP, + FAULT_TS, + FAULT_TIMER, + FAULT_NO_BAT, + FAULT_ISET, + FAULT_INPUT_LDO_LOW, +}; + +static int bq24257_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq24257_device *bq = power_supply_get_drvdata(psy); + struct bq24257_state state; + + mutex_lock(&bq->lock); + state = bq->state; + mutex_unlock(&bq->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.power_good) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state.status == STATUS_READY) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.status == STATUS_CHARGE_IN_PROGRESS) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (state.status == STATUS_CHARGE_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ24257_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.power_good; + break; + + case POWER_SUPPLY_PROP_HEALTH: + switch (state.fault) { + case FAULT_NORMAL: + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + + case FAULT_INPUT_OVP: + case FAULT_BAT_OVP: + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + break; + + case FAULT_TS: + case FAULT_BAT_TS: + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + + case FAULT_TIMER: + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + + default: + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = bq24257_ichg_map[bq->init_data.ichg]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = bq24257_vbat_map[bq->init_data.vbat]; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1]; + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + val->intval = bq24257_iterm_map[bq->init_data.iterm]; + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int bq24257_get_chip_state(struct bq24257_device *bq, + struct bq24257_state *state) +{ + int ret; + + ret = bq24257_field_read(bq, F_STAT); + if (ret < 0) + return ret; + + state->status = ret; + + ret = bq24257_field_read(bq, F_FAULT); + if (ret < 0) + return ret; + + state->fault = ret; + + state->power_good = !gpiod_get_value_cansleep(bq->pg); + + return 0; +} + +static bool bq24257_state_changed(struct bq24257_device *bq, + struct bq24257_state *new_state) +{ + int ret; + + mutex_lock(&bq->lock); + ret = (bq->state.status != new_state->status || + bq->state.fault != new_state->fault || + bq->state.power_good != new_state->power_good); + mutex_unlock(&bq->lock); + + return ret; +} + +enum bq24257_loop_status { + LOOP_STATUS_NONE, + LOOP_STATUS_IN_DPM, + LOOP_STATUS_IN_CURRENT_LIMIT, + LOOP_STATUS_THERMAL, +}; + +enum bq24257_in_ilimit { + IILIMIT_100, + IILIMIT_150, + IILIMIT_500, + IILIMIT_900, + IILIMIT_1500, + IILIMIT_2000, + IILIMIT_EXT, + IILIMIT_NONE, +}; + +enum bq24257_port_type { + PORT_TYPE_DCP, /* Dedicated Charging Port */ + PORT_TYPE_CDP, /* Charging Downstream Port */ + PORT_TYPE_SDP, /* Standard Downstream Port */ + PORT_TYPE_NON_STANDARD, +}; + +enum bq24257_safety_timer { + SAFETY_TIMER_45, + SAFETY_TIMER_360, + SAFETY_TIMER_540, + SAFETY_TIMER_NONE, +}; + +static int bq24257_iilimit_autoset(struct bq24257_device *bq) +{ + int loop_status; + int iilimit; + int port_type; + int ret; + const u8 new_iilimit[] = { + [PORT_TYPE_DCP] = IILIMIT_2000, + [PORT_TYPE_CDP] = IILIMIT_2000, + [PORT_TYPE_SDP] = IILIMIT_500, + [PORT_TYPE_NON_STANDARD] = IILIMIT_500 + }; + + ret = bq24257_field_read(bq, F_LOOP_STATUS); + if (ret < 0) + goto error; + + loop_status = ret; + + ret = bq24257_field_read(bq, F_IILIMIT); + if (ret < 0) + goto error; + + iilimit = ret; + + /* + * All USB ports should be able to handle 500mA. If not, DPM will lower + * the charging current to accommodate the power source. No need to set + * a lower IILIMIT value. + */ + if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500) + return 0; + + ret = bq24257_field_read(bq, F_USB_DET); + if (ret < 0) + goto error; + + port_type = ret; + + ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]); + if (ret < 0) + goto error; + + ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360); + if (ret < 0) + goto error; + + ret = bq24257_field_write(bq, F_CLR_VDP, 1); + if (ret < 0) + goto error; + + dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n", + port_type, loop_status, new_iilimit[port_type]); + + return 0; + +error: + dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); + return ret; +} + +static void bq24257_iilimit_setup_work(struct work_struct *work) +{ + struct bq24257_device *bq = container_of(work, struct bq24257_device, + iilimit_setup_work.work); + + bq24257_iilimit_autoset(bq); +} + +static void bq24257_handle_state_change(struct bq24257_device *bq, + struct bq24257_state *new_state) +{ + int ret; + struct bq24257_state old_state; + bool reset_iilimit = false; + bool config_iilimit = false; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + if (!new_state->power_good) { /* power removed */ + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + /* activate D+/D- port detection algorithm */ + ret = bq24257_field_write(bq, F_DPDM_EN, 1); + if (ret < 0) + goto error; + + reset_iilimit = true; + } else if (!old_state.power_good) { /* power inserted */ + config_iilimit = true; + } else if (new_state->fault == FAULT_NO_BAT) { /* battery removed */ + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + reset_iilimit = true; + } else if (old_state.fault == FAULT_NO_BAT) { /* battery connected */ + config_iilimit = true; + } else if (new_state->fault == FAULT_TIMER) { /* safety timer expired */ + dev_err(bq->dev, "Safety timer expired! Battery dead?\n"); + } + + if (reset_iilimit) { + ret = bq24257_field_write(bq, F_IILIMIT, IILIMIT_500); + if (ret < 0) + goto error; + } else if (config_iilimit) { + schedule_delayed_work(&bq->iilimit_setup_work, + msecs_to_jiffies(BQ24257_ILIM_SET_DELAY)); + } + + return; + +error: + dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__); +} + +static irqreturn_t bq24257_irq_handler_thread(int irq, void *private) +{ + int ret; + struct bq24257_device *bq = private; + struct bq24257_state state; + + ret = bq24257_get_chip_state(bq, &state); + if (ret < 0) + return IRQ_HANDLED; + + if (!bq24257_state_changed(bq, &state)) + return IRQ_HANDLED; + + dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n", + state.status, state.fault, state.power_good); + + bq24257_handle_state_change(bq, &state); + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + power_supply_changed(bq->charger); + + return IRQ_HANDLED; +} + +static int bq24257_hw_init(struct bq24257_device *bq) +{ + int ret; + int i; + struct bq24257_state state; + + const struct { + int field; + u32 value; + } init_data[] = { + {F_ICHG, bq->init_data.ichg}, + {F_VBAT, bq->init_data.vbat}, + {F_ITERM, bq->init_data.iterm} + }; + + /* + * Disable the watchdog timer to prevent the IC from going back to + * default settings after 50 seconds of I2C inactivity. + */ + ret = bq24257_field_write(bq, F_WD_EN, 0); + if (ret < 0) + return ret; + + /* configure the charge currents and voltages */ + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = bq24257_field_write(bq, init_data[i].field, + init_data[i].value); + if (ret < 0) + return ret; + } + + ret = bq24257_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + if (!state.power_good) + /* activate D+/D- detection algorithm */ + ret = bq24257_field_write(bq, F_DPDM_EN, 1); + else if (state.fault != FAULT_NO_BAT) + ret = bq24257_iilimit_autoset(bq); + + return ret; +} + +static enum power_supply_property bq24257_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + +static char *bq24257_charger_supplied_to[] = { + "main-battery", +}; + +static const struct power_supply_desc bq24257_power_supply_desc = { + .name = "bq24257-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = bq24257_power_supply_props, + .num_properties = ARRAY_SIZE(bq24257_power_supply_props), + .get_property = bq24257_power_supply_get_property, +}; + +static int bq24257_power_supply_init(struct bq24257_device *bq) +{ + struct power_supply_config psy_cfg = { .drv_data = bq, }; + + psy_cfg.supplied_to = bq24257_charger_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to); + + bq->charger = power_supply_register(bq->dev, &bq24257_power_supply_desc, + &psy_cfg); + if (IS_ERR(bq->charger)) + return PTR_ERR(bq->charger); + + return 0; +} + +static int bq24257_irq_probe(struct bq24257_device *bq) +{ + int ret; + struct gpio_desc *stat_irq; + + stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0); + if (IS_ERR(stat_irq)) { + dev_err(bq->dev, "could not probe stat_irq pin\n"); + return PTR_ERR(stat_irq); + } + + ret = gpiod_direction_input(stat_irq); + if (ret < 0) + return ret; + + return gpiod_to_irq(stat_irq); +} + +static int bq24257_pg_gpio_probe(struct bq24257_device *bq) +{ + bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0); + if (IS_ERR(bq->pg)) { + dev_err(bq->dev, "could not probe PG pin\n"); + return PTR_ERR(bq->pg); + } + + return gpiod_direction_input(bq->pg); +} + +static int bq24257_fw_probe(struct bq24257_device *bq) +{ + int ret; + u32 property; + + ret = device_property_read_u32(bq->dev, "ti,charge-current", &property); + if (ret < 0) + return ret; + + bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map, + BQ24257_ICHG_MAP_SIZE); + + ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage", + &property); + if (ret < 0) + return ret; + + bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map, + BQ24257_VBAT_MAP_SIZE); + + ret = device_property_read_u32(bq->dev, "ti,termination-current", + &property); + if (ret < 0) + return ret; + + bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map, + BQ24257_ITERM_MAP_SIZE); + + return 0; +} + +static int bq24257_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct device *dev = &client->dev; + struct bq24257_device *bq; + int ret; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); + if (!bq) + return -ENOMEM; + + bq->client = client; + bq->dev = dev; + + mutex_init(&bq->lock); + + bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config); + if (IS_ERR(bq->rmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(bq->rmap); + } + + for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) { + const struct reg_field *reg_fields = bq24257_reg_fields; + + bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, + reg_fields[i]); + if (IS_ERR(bq->rmap_fields[i])) { + dev_err(dev, "cannot allocate regmap field\n"); + return PTR_ERR(bq->rmap_fields[i]); + } + } + + i2c_set_clientdata(client, bq); + + INIT_DELAYED_WORK(&bq->iilimit_setup_work, bq24257_iilimit_setup_work); + + if (!dev->platform_data) { + ret = bq24257_fw_probe(bq); + if (ret < 0) { + dev_err(dev, "Cannot read device properties.\n"); + return ret; + } + } else { + return -ENODEV; + } + + /* we can only check Power Good status by probing the PG pin */ + ret = bq24257_pg_gpio_probe(bq); + if (ret < 0) + return ret; + + /* reset all registers to defaults */ + ret = bq24257_field_write(bq, F_RESET, 1); + if (ret < 0) + return ret; + + /* + * Put the RESET bit back to 0, in cache. For some reason the HW always + * returns 1 on this bit, so this is the only way to avoid resetting the + * chip every time we update another field in this register. + */ + ret = bq24257_field_write(bq, F_RESET, 0); + if (ret < 0) + return ret; + + ret = bq24257_hw_init(bq); + if (ret < 0) { + dev_err(dev, "Cannot initialize the chip.\n"); + return ret; + } + + if (client->irq <= 0) + client->irq = bq24257_irq_probe(bq); + + if (client->irq < 0) { + dev_err(dev, "no irq resource found\n"); + return client->irq; + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + bq24257_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + BQ24257_STAT_IRQ, bq); + if (ret) + return ret; + + ret = bq24257_power_supply_init(bq); + if (ret < 0) + dev_err(dev, "Failed to register power supply\n"); + + return ret; +} + +static int bq24257_remove(struct i2c_client *client) +{ + struct bq24257_device *bq = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + power_supply_unregister(bq->charger); + + bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */ + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq24257_suspend(struct device *dev) +{ + struct bq24257_device *bq = dev_get_drvdata(dev); + int ret = 0; + + cancel_delayed_work_sync(&bq->iilimit_setup_work); + + /* reset all registers to default (and activate standalone mode) */ + ret = bq24257_field_write(bq, F_RESET, 1); + if (ret < 0) + dev_err(bq->dev, "Cannot reset chip to standalone mode.\n"); + + return ret; +} + +static int bq24257_resume(struct device *dev) +{ + int ret; + struct bq24257_device *bq = dev_get_drvdata(dev); + + ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7); + if (ret < 0) + return ret; + + ret = bq24257_field_write(bq, F_RESET, 0); + if (ret < 0) + return ret; + + ret = bq24257_hw_init(bq); + if (ret < 0) { + dev_err(bq->dev, "Cannot init chip after resume.\n"); + return ret; + } + + /* signal userspace, maybe state changed while suspended */ + power_supply_changed(bq->charger); + + return 0; +} +#endif + +static const struct dev_pm_ops bq24257_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume) +}; + +static const struct i2c_device_id bq24257_i2c_ids[] = { + { "bq24257", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids); + +static const struct of_device_id bq24257_of_match[] = { + { .compatible = "ti,bq24257", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq24257_of_match); + +static const struct acpi_device_id bq24257_acpi_match[] = { + {"BQ242570", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match); + +static struct i2c_driver bq24257_driver = { + .driver = { + .name = "bq24257-charger", + .of_match_table = of_match_ptr(bq24257_of_match), + .acpi_match_table = ACPI_PTR(bq24257_acpi_match), + .pm = &bq24257_pm, + }, + .probe = bq24257_probe, + .remove = bq24257_remove, + .id_table = bq24257_i2c_ids, +}; +module_i2c_driver(bq24257_driver); + +MODULE_AUTHOR("Laurentiu Palcu "); +MODULE_DESCRIPTION("bq24257 charger driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-58-ga151 From f1f27a4acf7c5be41dccf3ce2e509e2e9fcc6e6f Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 2 May 2015 00:46:43 +0900 Subject: power: axp288_fuel_gauge: Constify platform_device_id The platform_device_id is not modified by the driver and core uses it as const. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/axp288_fuel_gauge.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c index bd1dbfee2515..50c0110d6b58 100644 --- a/drivers/power/axp288_fuel_gauge.c +++ b/drivers/power/axp288_fuel_gauge.c @@ -1117,7 +1117,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) return ret; } -static struct platform_device_id axp288_fg_id_table[] = { +static const struct platform_device_id axp288_fg_id_table[] = { { .name = DEV_NAME }, {}, }; -- cgit v1.2.3-58-ga151 From 323667209223368b50e16994e0fa8cf833940ce6 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Sat, 2 May 2015 00:46:44 +0900 Subject: power: at91-reset: Constify platform_device_id The platform_device_id is not modified by the driver and core uses it as const. Signed-off-by: Krzysztof Kozlowski Acked-by: Alexandre Belloni Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-reset.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/reset/at91-reset.c b/drivers/power/reset/at91-reset.c index ca461ebc7ae8..36dc52fb2ec8 100644 --- a/drivers/power/reset/at91-reset.c +++ b/drivers/power/reset/at91-reset.c @@ -243,7 +243,7 @@ static int at91_reset_probe(struct platform_device *pdev) return 0; } -static struct platform_device_id at91_reset_plat_match[] = { +static const struct platform_device_id at91_reset_plat_match[] = { { "at91-sam9260-reset", (unsigned long)at91sam9260_restart }, { "at91-sam9g45-reset", (unsigned long)at91sam9g45_restart }, { /* sentinel */ } -- cgit v1.2.3-58-ga151 From f6d8b7744f692def009b9b354a39daf503bc9e00 Mon Sep 17 00:00:00 2001 From: Anda-Maria Nicolae Date: Tue, 12 May 2015 12:11:32 +0300 Subject: power_supply: bq2415x_charger: Fix coding style issues This patch fixes the following issues reported by checkpatch.pl: - use -EINVAL instead of -ENOSYS, to fix warning message: "ENOSYS means 'invalid syscall nr' and nothing else" - remove unnecessary log message - split lines whose length is greater than 80 characters - if an arm statement uses braces, add braces to the other arms of the respective statement, too - match alignment with open parenthesis Signed-off-by: Anda-Maria Nicolae Signed-off-by: Sebastian Reichel --- drivers/power/bq2415x_charger.c | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index 6c534dcbc19c..2cf8ec747fc2 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -631,7 +631,7 @@ static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA) int val; if (bq->init_data.resistor_sense <= 0) - return -ENOSYS; + return -EINVAL; val = (mA * bq->init_data.resistor_sense - 37400) / 6800; if (val < 0) @@ -650,7 +650,7 @@ static int bq2415x_get_charge_current(struct bq2415x_device *bq) int ret; if (bq->init_data.resistor_sense <= 0) - return -ENOSYS; + return -EINVAL; ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG); @@ -665,7 +665,7 @@ static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA) int val; if (bq->init_data.resistor_sense <= 0) - return -ENOSYS; + return -EINVAL; val = (mA * bq->init_data.resistor_sense - 3400) / 3400; if (val < 0) @@ -684,7 +684,7 @@ static int bq2415x_get_termination_current(struct bq2415x_device *bq) int ret; if (bq->init_data.resistor_sense <= 0) - return -ENOSYS; + return -EINVAL; ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT, BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM); @@ -1166,7 +1166,7 @@ static ssize_t bq2415x_sysfs_set_mode(struct device *dev, if (strncmp(buf, "auto", 4) == 0) { if (bq->automode < 0) - return -ENOSYS; + return -EINVAL; bq->automode = 1; mode = bq->reported_mode; } else if (strncmp(buf, "off", 3) == 0) { @@ -1556,28 +1556,28 @@ static int bq2415x_probe(struct i2c_client *client, bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL); if (!bq) { - dev_err(&client->dev, "failed to allocate device data\n"); ret = -ENOMEM; goto error_2; } if (np) { - bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection"); + bq->notify_psy = power_supply_get_by_phandle(np, + "ti,usb-charger-detection"); if (IS_ERR(bq->notify_psy)) { dev_info(&client->dev, - "no 'ti,usb-charger-detection' property (err=%ld)\n", + "no 'ti,usb-charger-detection' property (err=%ld)\n", PTR_ERR(bq->notify_psy)); bq->notify_psy = NULL; } else if (!bq->notify_psy) { ret = -EPROBE_DEFER; goto error_2; } - } - else if (pdata->notify_device) + } else if (pdata->notify_device) { bq->notify_psy = power_supply_get_by_name(pdata->notify_device); - else + } else { bq->notify_psy = NULL; + } i2c_set_clientdata(client, bq); @@ -1592,11 +1592,11 @@ static int bq2415x_probe(struct i2c_client *client, if (np) { ret = of_property_read_u32(np, "ti,current-limit", - &bq->init_data.current_limit); + &bq->init_data.current_limit); if (ret) goto error_3; ret = of_property_read_u32(np, "ti,weak-battery-voltage", - &bq->init_data.weak_battery_voltage); + &bq->init_data.weak_battery_voltage); if (ret) goto error_3; ret = of_property_read_u32(np, "ti,battery-regulation-voltage", @@ -1604,15 +1604,15 @@ static int bq2415x_probe(struct i2c_client *client, if (ret) goto error_3; ret = of_property_read_u32(np, "ti,charge-current", - &bq->init_data.charge_current); + &bq->init_data.charge_current); if (ret) goto error_3; ret = of_property_read_u32(np, "ti,termination-current", - &bq->init_data.termination_current); + &bq->init_data.termination_current); if (ret) goto error_3; ret = of_property_read_u32(np, "ti,resistor-sense", - &bq->init_data.resistor_sense); + &bq->init_data.resistor_sense); if (ret) goto error_3; } else { @@ -1648,7 +1648,8 @@ static int bq2415x_probe(struct i2c_client *client, } /* Query for initial reported_mode and set it */ - bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy); + bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, + bq->notify_psy); bq2415x_set_mode(bq, bq->reported_mode); bq->automode = 1; -- cgit v1.2.3-58-ga151 From b01e7c3b803e6f0498d4db0b124e3b4efb5ac728 Mon Sep 17 00:00:00 2001 From: Anda-Maria Nicolae Date: Tue, 12 May 2015 12:11:33 +0300 Subject: power_supply: bq2415x_charger: Add ACPI support Replace of_property_read_u32() with device_property_read_u32(), which is a wrapper over ACPI and device tree enumeration methods. When ACPI enumeration is used, automode is not supported. Therefore, bq2415x_charger does not update its input current automatically, depending on the USB port type that is connected to. Input current may be updated via sysfs. Signed-off-by: Anda-Maria Nicolae Signed-off-by: Sebastian Reichel --- drivers/power/bq2415x_charger.c | 73 +++++++++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 18 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c index 2cf8ec747fc2..e98dcb661cc9 100644 --- a/drivers/power/bq2415x_charger.c +++ b/drivers/power/bq2415x_charger.c @@ -35,6 +35,7 @@ #include #include #include +#include #include @@ -1530,13 +1531,14 @@ static int bq2415x_probe(struct i2c_client *client, { int ret; int num; - char *name; + char *name = NULL; struct bq2415x_device *bq; struct device_node *np = client->dev.of_node; struct bq2415x_platform_data *pdata = client->dev.platform_data; + const struct acpi_device_id *acpi_id = NULL; - if (!np && !pdata) { - dev_err(&client->dev, "platform data missing\n"); + if (!np && !pdata && !ACPI_HANDLE(&client->dev)) { + dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n"); return -ENODEV; } @@ -1547,7 +1549,14 @@ static int bq2415x_probe(struct i2c_client *client, if (num < 0) return num; - name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + if (id) { + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); + } else if (ACPI_HANDLE(&client->dev)) { + acpi_id = + acpi_match_device(client->dev.driver->acpi_match_table, + &client->dev); + name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num); + } if (!name) { dev_err(&client->dev, "failed to allocate device name\n"); ret = -ENOMEM; @@ -1573,7 +1582,7 @@ static int bq2415x_probe(struct i2c_client *client, ret = -EPROBE_DEFER; goto error_2; } - } else if (pdata->notify_device) { + } else if (pdata && pdata->notify_device) { bq->notify_psy = power_supply_get_by_name(pdata->notify_device); } else { bq->notify_psy = NULL; @@ -1583,36 +1592,45 @@ static int bq2415x_probe(struct i2c_client *client, bq->id = num; bq->dev = &client->dev; - bq->chip = id->driver_data; + if (id) + bq->chip = id->driver_data; + else if (ACPI_HANDLE(bq->dev)) + bq->chip = acpi_id->driver_data; bq->name = name; bq->mode = BQ2415X_MODE_OFF; bq->reported_mode = BQ2415X_MODE_OFF; bq->autotimer = 0; bq->automode = 0; - if (np) { - ret = of_property_read_u32(np, "ti,current-limit", - &bq->init_data.current_limit); + if (np || ACPI_HANDLE(bq->dev)) { + ret = device_property_read_u32(bq->dev, + "ti,current-limit", + &bq->init_data.current_limit); if (ret) goto error_3; - ret = of_property_read_u32(np, "ti,weak-battery-voltage", - &bq->init_data.weak_battery_voltage); + ret = device_property_read_u32(bq->dev, + "ti,weak-battery-voltage", + &bq->init_data.weak_battery_voltage); if (ret) goto error_3; - ret = of_property_read_u32(np, "ti,battery-regulation-voltage", + ret = device_property_read_u32(bq->dev, + "ti,battery-regulation-voltage", &bq->init_data.battery_regulation_voltage); if (ret) goto error_3; - ret = of_property_read_u32(np, "ti,charge-current", - &bq->init_data.charge_current); + ret = device_property_read_u32(bq->dev, + "ti,charge-current", + &bq->init_data.charge_current); if (ret) goto error_3; - ret = of_property_read_u32(np, "ti,termination-current", - &bq->init_data.termination_current); + ret = device_property_read_u32(bq->dev, + "ti,termination-current", + &bq->init_data.termination_current); if (ret) goto error_3; - ret = of_property_read_u32(np, "ti,resistor-sense", - &bq->init_data.resistor_sense); + ret = device_property_read_u32(bq->dev, + "ti,resistor-sense", + &bq->init_data.resistor_sense); if (ret) goto error_3; } else { @@ -1728,9 +1746,28 @@ static const struct i2c_device_id bq2415x_i2c_id_table[] = { }; MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table); +static const struct acpi_device_id bq2415x_i2c_acpi_match[] = { + { "BQ2415X", BQUNKNOWN }, + { "BQ241500", BQ24150 }, + { "BQA24150", BQ24150A }, + { "BQ241510", BQ24151 }, + { "BQA24151", BQ24151A }, + { "BQ241520", BQ24152 }, + { "BQ241530", BQ24153 }, + { "BQA24153", BQ24153A }, + { "BQ241550", BQ24155 }, + { "BQ241560", BQ24156 }, + { "BQA24156", BQ24156A }, + { "BQS24157", BQ24157S }, + { "BQ241580", BQ24158 }, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match); + static struct i2c_driver bq2415x_driver = { .driver = { .name = "bq2415x-charger", + .acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match), }, .probe = bq2415x_probe, .remove = bq2415x_remove, -- cgit v1.2.3-58-ga151 From a34c0a8bf8caae7a82de08dbcca2c7c11907c805 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 18 May 2015 22:45:06 +0200 Subject: power: reset: gpio-poweroff: let devm_gpiod_get set direction of gpio MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 39b2bbe3d715 (gpio: add flags argument to gpiod_get*() functions) which appeared in v3.17-rc1, the gpiod_get* functions take an additional parameter that allows to specify direction and initial value for output. Signed-off-by: Uwe Kleine-König Reviewed-by: Linus Walleij Signed-off-by: Sebastian Reichel --- drivers/power/reset/gpio-poweroff.c | 25 ++++++++----------------- 1 file changed, 8 insertions(+), 17 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c index e5332f1db8a7..be3d81ff51cc 100644 --- a/drivers/power/reset/gpio-poweroff.c +++ b/drivers/power/reset/gpio-poweroff.c @@ -48,6 +48,7 @@ static void gpio_poweroff_do_poweroff(void) static int gpio_poweroff_probe(struct platform_device *pdev) { bool input = false; + enum gpiod_flags flags; /* If a pm_power_off function has already been added, leave it alone */ if (pm_power_off != NULL) { @@ -57,25 +58,15 @@ static int gpio_poweroff_probe(struct platform_device *pdev) return -EBUSY; } - reset_gpio = devm_gpiod_get(&pdev->dev, NULL); - if (IS_ERR(reset_gpio)) - return PTR_ERR(reset_gpio); - input = of_property_read_bool(pdev->dev.of_node, "input"); + if (input) + flags = GPIOD_IN; + else + flags = GPIOD_OUT_LOW; - if (input) { - if (gpiod_direction_input(reset_gpio)) { - dev_err(&pdev->dev, - "Could not set direction of reset GPIO to input\n"); - return -ENODEV; - } - } else { - if (gpiod_direction_output(reset_gpio, 0)) { - dev_err(&pdev->dev, - "Could not set direction of reset GPIO\n"); - return -ENODEV; - } - } + reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags); + if (IS_ERR(reset_gpio)) + return PTR_ERR(reset_gpio); pm_power_off = &gpio_poweroff_do_poweroff; return 0; -- cgit v1.2.3-58-ga151 From 9f6cd98fc3c64ebcebf63c04f16246f79bcc4c70 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Mon, 18 May 2015 22:45:07 +0200 Subject: power: reset: ltc2952: use _optional variant of devm_gpiod_get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit devm_gpiod_get_optional returns NULL if devm_gpiod_get would return an ENOENT error pointer. There is no semantic change intended. Signed-off-by: Uwe Kleine-König Signed-off-by: Sebastian Reichel --- drivers/power/reset/ltc2952-poweroff.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/reset/ltc2952-poweroff.c b/drivers/power/reset/ltc2952-poweroff.c index 1e08195551fe..62c91acd6584 100644 --- a/drivers/power/reset/ltc2952-poweroff.c +++ b/drivers/power/reset/ltc2952-poweroff.c @@ -202,16 +202,15 @@ static int ltc2952_poweroff_init(struct platform_device *pdev) return ret; } - data->gpio_trigger = devm_gpiod_get(&pdev->dev, "trigger", GPIOD_IN); + data->gpio_trigger = devm_gpiod_get_optional(&pdev->dev, "trigger", + GPIOD_IN); if (IS_ERR(data->gpio_trigger)) { /* * It's not a problem if the trigger gpio isn't available, but * it is worth a warning if its use was defined in the device * tree. */ - if (PTR_ERR(data->gpio_trigger) != -ENOENT) - dev_err(&pdev->dev, - "unable to claim gpio \"trigger\"\n"); + dev_err(&pdev->dev, "unable to claim gpio \"trigger\"\n"); data->gpio_trigger = NULL; } -- cgit v1.2.3-58-ga151 From a9f6a19b57c4f37dfd6aa322071948245fecbd91 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 19 May 2015 16:16:29 +0900 Subject: power_supply: Use wrappers to avoid races when registering power supply Use wrappers over get_property() and set_property() internally in power supply and for sysfs interface. The wrappers provide safe access if power supply is not yet registered or t is being destroyed. In case of syfs the theoretical race could happen between ending of driver's probe and parallel sysfs access: some_driver_probe() userspace ==================================== =========================== drv->psy = power_supply_register() device_add() sysfs entries are created atomic_inc(&psy->use_cnt); store on sysfs attributes drv->set_property() dereference of drv->psy drv->psy = returned psy; For leds the race could happen between power supply being destroyed and ongoing power_supply_changed_work(). Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_leds.c | 4 ++-- drivers/power/power_supply_sysfs.c | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c index 2d41a43fc81a..2277ad9c2f68 100644 --- a/drivers/power/power_supply_leds.c +++ b/drivers/power/power_supply_leds.c @@ -25,7 +25,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) unsigned long delay_on = 0; unsigned long delay_off = 0; - if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) + if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status)) return; dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval); @@ -115,7 +115,7 @@ static void power_supply_update_gen_leds(struct power_supply *psy) { union power_supply_propval online; - if (psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online)) + if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online)) return; dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index 9134e3d2d95e..af026806cba5 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -125,7 +125,7 @@ static ssize_t power_supply_store_property(struct device *dev, value.intval = long_val; - ret = psy->desc->set_property(psy, off, &value); + ret = power_supply_set_property(psy, off, &value); if (ret < 0) return ret; -- cgit v1.2.3-58-ga151 From 4970d839aa1574e4e998cdc423864a50743931ff Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Tue, 19 May 2015 16:16:30 +0900 Subject: power_supply: charger-manager: Add parent for power supply The 'parent' argument passed to power_supply_register() is now used to postpone callbacks to the driver until the driver's probe end. Pass current device from charger-manager to utilize that. This will move created power supply from virtual to platform devices. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/charger-manager.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 0aed13f90891..1c202ccbd2a6 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -1768,7 +1768,8 @@ static int charger_manager_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); - cm->charger_psy = power_supply_register(NULL, &cm->charger_psy_desc, + cm->charger_psy = power_supply_register(&pdev->dev, + &cm->charger_psy_desc, &psy_cfg); if (IS_ERR(cm->charger_psy)) { dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", -- cgit v1.2.3-58-ga151 From 4aeae9cb0dad117f055add68c48decaf489aecf3 Mon Sep 17 00:00:00 2001 From: Laurentiu Palcu Date: Tue, 19 May 2015 16:24:40 +0300 Subject: power_supply: Add support for TI BQ25890 charger chip More details about the chip can be found here: http://www.ti.com/product/bq25890 Signed-off-by: Laurentiu Palcu Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/bq25890_charger.c | 999 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1007 insertions(+) create mode 100644 drivers/power/bq25890_charger.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 20352a7a0081..0fe5b08055ba 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -401,6 +401,13 @@ config CHARGER_BQ24735 help Say Y to enable support for the TI BQ24735 battery charger. +config CHARGER_BQ25890 + tristate "TI BQ25890 battery charger driver" + depends on I2C && GPIOLIB + select REGMAP_I2C + help + Say Y to enable support for the TI BQ25890 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB347 Battery Charger" depends on I2C diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 19a1f4186d4d..03942e99776b 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -60,6 +60,7 @@ obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o +obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c new file mode 100644 index 000000000000..c7b4903acd75 --- /dev/null +++ b/drivers/power/bq25890_charger.c @@ -0,0 +1,999 @@ +/* + * TI BQ25890 charger driver + * + * Copyright (C) 2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define BQ25890_MANUFACTURER "Texas Instruments" +#define BQ25890_IRQ_PIN "bq25890_irq" + +#define BQ25890_ID 3 + +enum bq25890_fields { + F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */ + F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */ + F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN, + F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */ + F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */ + F_PUMPX_EN, F_ICHG, /* Reg04 */ + F_IPRECHG, F_ITERM, /* Reg05 */ + F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */ + F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR, + F_JEITA_ISET, /* Reg07 */ + F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */ + F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET, + F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */ + F_BOOSTV, F_BOOSTI, /* Reg0A */ + F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */ + F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT, + F_NTC_FAULT, /* Reg0C */ + F_FORCE_VINDPM, F_VINDPM, /* Reg0D */ + F_THERM_STAT, F_BATV, /* Reg0E */ + F_SYSV, /* Reg0F */ + F_TSPCT, /* Reg10 */ + F_VBUS_GD, F_VBUSV, /* Reg11 */ + F_ICHGR, /* Reg12 */ + F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */ + F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */ + + F_MAX_FIELDS +}; + +/* initial field values, converted to register values */ +struct bq25890_init_data { + u8 ichg; /* charge current */ + u8 vreg; /* regulation voltage */ + u8 iterm; /* termination current */ + u8 iprechg; /* precharge current */ + u8 sysvmin; /* minimum system voltage limit */ + u8 boostv; /* boost regulation voltage */ + u8 boosti; /* boost current limit */ + u8 boostf; /* boost frequency */ + u8 ilim_en; /* enable ILIM pin */ + u8 treg; /* thermal regulation threshold */ +}; + +struct bq25890_state { + u8 online; + u8 chrg_status; + u8 chrg_fault; + u8 vsys_status; + u8 boost_fault; + u8 bat_fault; +}; + +struct bq25890_device { + struct i2c_client *client; + struct device *dev; + struct power_supply *charger; + + struct usb_phy *usb_phy; + struct notifier_block usb_nb; + struct work_struct usb_work; + unsigned long usb_event; + + struct regmap *rmap; + struct regmap_field *rmap_fields[F_MAX_FIELDS]; + + u8 chip_id; + struct bq25890_init_data init_data; + struct bq25890_state state; + + struct mutex lock; /* protect state data */ +}; + +static const struct regmap_range bq25890_readonly_reg_ranges[] = { + regmap_reg_range(0x0b, 0x0c), + regmap_reg_range(0x0e, 0x13), +}; + +static const struct regmap_access_table bq25890_writeable_regs = { + .no_ranges = bq25890_readonly_reg_ranges, + .n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges), +}; + +static const struct regmap_range bq25890_volatile_reg_ranges[] = { + regmap_reg_range(0x00, 0x00), + regmap_reg_range(0x09, 0x09), + regmap_reg_range(0x0b, 0x0c), + regmap_reg_range(0x0e, 0x14), +}; + +static const struct regmap_access_table bq25890_volatile_regs = { + .yes_ranges = bq25890_volatile_reg_ranges, + .n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges), +}; + +static const struct regmap_config bq25890_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + + .max_register = 0x14, + .cache_type = REGCACHE_RBTREE, + + .wr_table = &bq25890_writeable_regs, + .volatile_table = &bq25890_volatile_regs, +}; + +static const struct reg_field bq25890_reg_fields[] = { + /* REG00 */ + [F_EN_HIZ] = REG_FIELD(0x00, 7, 7), + [F_EN_ILIM] = REG_FIELD(0x00, 6, 6), + [F_IILIM] = REG_FIELD(0x00, 0, 5), + /* REG01 */ + [F_BHOT] = REG_FIELD(0x01, 6, 7), + [F_BCOLD] = REG_FIELD(0x01, 5, 5), + [F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4), + /* REG02 */ + [F_CONV_START] = REG_FIELD(0x02, 7, 7), + [F_CONV_RATE] = REG_FIELD(0x02, 6, 6), + [F_BOOSTF] = REG_FIELD(0x02, 5, 5), + [F_ICO_EN] = REG_FIELD(0x02, 4, 4), + [F_HVDCP_EN] = REG_FIELD(0x02, 3, 3), + [F_MAXC_EN] = REG_FIELD(0x02, 2, 2), + [F_FORCE_DPM] = REG_FIELD(0x02, 1, 1), + [F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0), + /* REG03 */ + [F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7), + [F_WD_RST] = REG_FIELD(0x03, 6, 6), + [F_OTG_CFG] = REG_FIELD(0x03, 5, 5), + [F_CHG_CFG] = REG_FIELD(0x03, 4, 4), + [F_SYSVMIN] = REG_FIELD(0x03, 1, 3), + /* REG04 */ + [F_PUMPX_EN] = REG_FIELD(0x04, 7, 7), + [F_ICHG] = REG_FIELD(0x04, 0, 6), + /* REG05 */ + [F_IPRECHG] = REG_FIELD(0x05, 4, 7), + [F_ITERM] = REG_FIELD(0x05, 0, 3), + /* REG06 */ + [F_VREG] = REG_FIELD(0x06, 2, 7), + [F_BATLOWV] = REG_FIELD(0x06, 1, 1), + [F_VRECHG] = REG_FIELD(0x06, 0, 0), + /* REG07 */ + [F_TERM_EN] = REG_FIELD(0x07, 7, 7), + [F_STAT_DIS] = REG_FIELD(0x07, 6, 6), + [F_WD] = REG_FIELD(0x07, 4, 5), + [F_TMR_EN] = REG_FIELD(0x07, 3, 3), + [F_CHG_TMR] = REG_FIELD(0x07, 1, 2), + [F_JEITA_ISET] = REG_FIELD(0x07, 0, 0), + /* REG08 */ + [F_BATCMP] = REG_FIELD(0x08, 6, 7), + [F_VCLAMP] = REG_FIELD(0x08, 2, 4), + [F_TREG] = REG_FIELD(0x08, 0, 1), + /* REG09 */ + [F_FORCE_ICO] = REG_FIELD(0x09, 7, 7), + [F_TMR2X_EN] = REG_FIELD(0x09, 6, 6), + [F_BATFET_DIS] = REG_FIELD(0x09, 5, 5), + [F_JEITA_VSET] = REG_FIELD(0x09, 4, 4), + [F_BATFET_DLY] = REG_FIELD(0x09, 3, 3), + [F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2), + [F_PUMPX_UP] = REG_FIELD(0x09, 1, 1), + [F_PUMPX_DN] = REG_FIELD(0x09, 0, 0), + /* REG0A */ + [F_BOOSTV] = REG_FIELD(0x0A, 4, 7), + [F_BOOSTI] = REG_FIELD(0x0A, 0, 2), + /* REG0B */ + [F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7), + [F_CHG_STAT] = REG_FIELD(0x0B, 3, 4), + [F_PG_STAT] = REG_FIELD(0x0B, 2, 2), + [F_SDP_STAT] = REG_FIELD(0x0B, 1, 1), + [F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0), + /* REG0C */ + [F_WD_FAULT] = REG_FIELD(0x0C, 7, 7), + [F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6), + [F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5), + [F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3), + [F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2), + /* REG0D */ + [F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7), + [F_VINDPM] = REG_FIELD(0x0D, 0, 6), + /* REG0E */ + [F_THERM_STAT] = REG_FIELD(0x0E, 7, 7), + [F_BATV] = REG_FIELD(0x0E, 0, 6), + /* REG0F */ + [F_SYSV] = REG_FIELD(0x0F, 0, 6), + /* REG10 */ + [F_TSPCT] = REG_FIELD(0x10, 0, 6), + /* REG11 */ + [F_VBUS_GD] = REG_FIELD(0x11, 7, 7), + [F_VBUSV] = REG_FIELD(0x11, 0, 6), + /* REG12 */ + [F_ICHGR] = REG_FIELD(0x12, 0, 6), + /* REG13 */ + [F_VDPM_STAT] = REG_FIELD(0x13, 7, 7), + [F_IDPM_STAT] = REG_FIELD(0x13, 6, 6), + [F_IDPM_LIM] = REG_FIELD(0x13, 0, 5), + /* REG14 */ + [F_REG_RST] = REG_FIELD(0x14, 7, 7), + [F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6), + [F_PN] = REG_FIELD(0x14, 3, 5), + [F_TS_PROFILE] = REG_FIELD(0x14, 2, 2), + [F_DEV_REV] = REG_FIELD(0x14, 0, 1) +}; + +/* + * Most of the val -> idx conversions can be computed, given the minimum, + * maximum and the step between values. For the rest of conversions, we use + * lookup tables. + */ +enum bq25890_table_ids { + /* range tables */ + TBL_ICHG, + TBL_ITERM, + TBL_IPRECHG, + TBL_VREG, + TBL_BATCMP, + TBL_VCLAMP, + TBL_BOOSTV, + TBL_SYSVMIN, + + /* lookup tables */ + TBL_TREG, + TBL_BOOSTI, +}; + +/* Thermal Regulation Threshold lookup table, in degrees Celsius */ +static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 }; + +#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl) + +/* Boost mode current limit lookup table, in uA */ +static const u32 bq25890_boosti_tbl[] = { + 500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000 +}; + +#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl) + +struct bq25890_range { + u32 min; + u32 max; + u32 step; +}; + +struct bq25890_lookup { + const u32 *tbl; + u32 size; +}; + +static const union { + struct bq25890_range rt; + struct bq25890_lookup lt; +} bq25890_tables[] = { + /* range tables */ + [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */ + [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */ + [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */ + [TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */ + [TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */ + [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */ + [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */ + + /* lookup tables */ + [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} }, + [TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} } +}; + +static int bq25890_field_read(struct bq25890_device *bq, + enum bq25890_fields field_id) +{ + int ret; + int val; + + ret = regmap_field_read(bq->rmap_fields[field_id], &val); + if (ret < 0) + return ret; + + return val; +} + +static int bq25890_field_write(struct bq25890_device *bq, + enum bq25890_fields field_id, u8 val) +{ + return regmap_field_write(bq->rmap_fields[field_id], val); +} + +static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id) +{ + u8 idx; + + if (id >= TBL_TREG) { + const u32 *tbl = bq25890_tables[id].lt.tbl; + u32 tbl_size = bq25890_tables[id].lt.size; + + for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++) + ; + } else { + const struct bq25890_range *rtbl = &bq25890_tables[id].rt; + u8 rtbl_size; + + rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1; + + for (idx = 1; + idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value); + idx++) + ; + } + + return idx - 1; +} + +static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id) +{ + const struct bq25890_range *rtbl; + + /* lookup table? */ + if (id >= TBL_TREG) + return bq25890_tables[id].lt.tbl[idx]; + + /* range table */ + rtbl = &bq25890_tables[id].rt; + + return (rtbl->min + idx * rtbl->step); +} + +enum bq25890_status { + STATUS_NOT_CHARGING, + STATUS_PRE_CHARGING, + STATUS_FAST_CHARGING, + STATUS_TERMINATION_DONE, +}; + +enum bq25890_chrg_fault { + CHRG_FAULT_NORMAL, + CHRG_FAULT_INPUT, + CHRG_FAULT_THERMAL_SHUTDOWN, + CHRG_FAULT_TIMER_EXPIRED, +}; + +static int bq25890_power_supply_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int ret; + struct bq25890_device *bq = power_supply_get_drvdata(psy); + struct bq25890_state state; + + mutex_lock(&bq->lock); + state = bq->state; + mutex_unlock(&bq->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.online) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (state.chrg_status == STATUS_NOT_CHARGING) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_status == STATUS_PRE_CHARGING || + state.chrg_status == STATUS_FAST_CHARGING) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else if (state.chrg_status == STATUS_TERMINATION_DONE) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + + break; + + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ25890_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + + case POWER_SUPPLY_PROP_HEALTH: + if (!state.chrg_fault && !state.bat_fault && !state.boost_fault) + val->intval = POWER_SUPPLY_HEALTH_GOOD; + else if (state.bat_fault) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED) + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN) + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */ + if (ret < 0) + return ret; + + /* converted_val = ADC_val * 50mA (table 10.3.19) */ + val->intval = ret * 50000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = bq25890_tables[TBL_ICHG].rt.max; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + if (!state.online) { + val->intval = 0; + break; + } + + ret = bq25890_field_read(bq, F_BATV); /* read measured value */ + if (ret < 0) + return ret; + + /* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */ + val->intval = 2304000 + ret * 20000; + break; + + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = bq25890_tables[TBL_VREG].rt.max; + break; + + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM); + break; + + default: + return -EINVAL; + } + + return 0; +} + +static int bq25890_get_chip_state(struct bq25890_device *bq, + struct bq25890_state *state) +{ + int i, ret; + + struct { + enum bq25890_fields id; + u8 *data; + } state_fields[] = { + {F_CHG_STAT, &state->chrg_status}, + {F_PG_STAT, &state->online}, + {F_VSYS_STAT, &state->vsys_status}, + {F_BOOST_FAULT, &state->boost_fault}, + {F_BAT_FAULT, &state->bat_fault}, + {F_CHG_FAULT, &state->chrg_fault} + }; + + for (i = 0; i < ARRAY_SIZE(state_fields); i++) { + ret = bq25890_field_read(bq, state_fields[i].id); + if (ret < 0) + return ret; + + *state_fields[i].data = ret; + } + + dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n", + state->chrg_status, state->online, state->vsys_status, + state->chrg_fault, state->boost_fault, state->bat_fault); + + return 0; +} + +static bool bq25890_state_changed(struct bq25890_device *bq, + struct bq25890_state *new_state) +{ + struct bq25890_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + return (old_state.chrg_status != new_state->chrg_status || + old_state.chrg_fault != new_state->chrg_fault || + old_state.online != new_state->online || + old_state.bat_fault != new_state->bat_fault || + old_state.boost_fault != new_state->boost_fault || + old_state.vsys_status != new_state->vsys_status); +} + +static void bq25890_handle_state_change(struct bq25890_device *bq, + struct bq25890_state *new_state) +{ + int ret; + struct bq25890_state old_state; + + mutex_lock(&bq->lock); + old_state = bq->state; + mutex_unlock(&bq->lock); + + if (!new_state->online) { /* power removed */ + /* disable ADC */ + ret = bq25890_field_write(bq, F_CONV_START, 0); + if (ret < 0) + goto error; + } else if (!old_state.online) { /* power inserted */ + /* enable ADC, to have control of charge current/voltage */ + ret = bq25890_field_write(bq, F_CONV_START, 1); + if (ret < 0) + goto error; + } + + return; + +error: + dev_err(bq->dev, "Error communicating with the chip.\n"); +} + +static irqreturn_t bq25890_irq_handler_thread(int irq, void *private) +{ + struct bq25890_device *bq = private; + int ret; + struct bq25890_state state; + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + goto handled; + + if (!bq25890_state_changed(bq, &state)) + goto handled; + + bq25890_handle_state_change(bq, &state); + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + power_supply_changed(bq->charger); + +handled: + return IRQ_HANDLED; +} + +static int bq25890_chip_reset(struct bq25890_device *bq) +{ + int ret; + int rst_check_counter = 10; + + ret = bq25890_field_write(bq, F_REG_RST, 1); + if (ret < 0) + return ret; + + do { + ret = bq25890_field_read(bq, F_REG_RST); + if (ret < 0) + return ret; + + usleep_range(5, 10); + } while (ret == 1 && --rst_check_counter); + + if (!rst_check_counter) + return -ETIMEDOUT; + + return 0; +} + +static int bq25890_hw_init(struct bq25890_device *bq) +{ + int ret; + int i; + struct bq25890_state state; + + const struct { + enum bq25890_fields id; + u32 value; + } init_data[] = { + {F_ICHG, bq->init_data.ichg}, + {F_VREG, bq->init_data.vreg}, + {F_ITERM, bq->init_data.iterm}, + {F_IPRECHG, bq->init_data.iprechg}, + {F_SYSVMIN, bq->init_data.sysvmin}, + {F_BOOSTV, bq->init_data.boostv}, + {F_BOOSTI, bq->init_data.boosti}, + {F_BOOSTF, bq->init_data.boostf}, + {F_EN_ILIM, bq->init_data.ilim_en}, + {F_TREG, bq->init_data.treg} + }; + + ret = bq25890_chip_reset(bq); + if (ret < 0) + return ret; + + /* disable watchdog */ + ret = bq25890_field_write(bq, F_WD, 0); + if (ret < 0) + return ret; + + /* initialize currents/voltages and other parameters */ + for (i = 0; i < ARRAY_SIZE(init_data); i++) { + ret = bq25890_field_write(bq, init_data[i].id, + init_data[i].value); + if (ret < 0) + return ret; + } + + /* Configure ADC for continuous conversions. This does not enable it. */ + ret = bq25890_field_write(bq, F_CONV_RATE, 1); + if (ret < 0) + return ret; + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + return 0; +} + +static enum power_supply_property bq25890_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, +}; + +static char *bq25890_charger_supplied_to[] = { + "main-battery", +}; + +static const struct power_supply_desc bq25890_power_supply_desc = { + .name = "bq25890-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = bq25890_power_supply_props, + .num_properties = ARRAY_SIZE(bq25890_power_supply_props), + .get_property = bq25890_power_supply_get_property, +}; + +static int bq25890_power_supply_init(struct bq25890_device *bq) +{ + struct power_supply_config psy_cfg = { .drv_data = bq, }; + + psy_cfg.supplied_to = bq25890_charger_supplied_to; + psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to); + + bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc, + &psy_cfg); + + return PTR_ERR_OR_ZERO(bq->charger); +} + +static void bq25890_usb_work(struct work_struct *data) +{ + int ret; + struct bq25890_device *bq = + container_of(data, struct bq25890_device, usb_work); + + switch (bq->usb_event) { + case USB_EVENT_ID: + /* Enable boost mode */ + ret = bq25890_field_write(bq, F_OTG_CFG, 1); + if (ret < 0) + goto error; + break; + + case USB_EVENT_NONE: + /* Disable boost mode */ + ret = bq25890_field_write(bq, F_OTG_CFG, 0); + if (ret < 0) + goto error; + + power_supply_changed(bq->charger); + break; + } + + return; + +error: + dev_err(bq->dev, "Error switching to boost/charger mode.\n"); +} + +static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, + void *priv) +{ + struct bq25890_device *bq = + container_of(nb, struct bq25890_device, usb_nb); + + bq->usb_event = val; + queue_work(system_power_efficient_wq, &bq->usb_work); + + return NOTIFY_OK; +} + +static int bq25890_irq_probe(struct bq25890_device *bq) +{ + int ret; + struct gpio_desc *irq; + + irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0); + if (IS_ERR(irq)) { + dev_err(bq->dev, "Could not probe irq pin.\n"); + return PTR_ERR(irq); + } + + ret = gpiod_direction_input(irq); + if (ret < 0) + return ret; + + return gpiod_to_irq(irq); +} + +static int bq25890_fw_read_u32_props(struct bq25890_device *bq) +{ + int ret; + u32 property; + int i; + struct bq25890_init_data *init = &bq->init_data; + struct { + char *name; + bool optional; + enum bq25890_table_ids tbl_id; + u8 *conv_data; /* holds converted value from given property */ + } props[] = { + /* required properties */ + {"ti,charge-current", false, TBL_ICHG, &init->ichg}, + {"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg}, + {"ti,termination-current", false, TBL_ITERM, &init->iterm}, + {"ti,precharge-current", false, TBL_ITERM, &init->iprechg}, + {"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin}, + {"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv}, + {"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti}, + + /* optional properties */ + {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg} + }; + + /* initialize data for optional properties */ + init->treg = 3; /* 120 degrees Celsius */ + + for (i = 0; i < ARRAY_SIZE(props); i++) { + ret = device_property_read_u32(bq->dev, props[i].name, + &property); + if (ret < 0) { + if (props[i].optional) + continue; + + return ret; + } + + *props[i].conv_data = bq25890_find_idx(property, + props[i].tbl_id); + } + + return 0; +} + +static int bq25890_fw_probe(struct bq25890_device *bq) +{ + int ret; + struct bq25890_init_data *init = &bq->init_data; + + ret = bq25890_fw_read_u32_props(bq); + if (ret < 0) + return ret; + + init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin"); + init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq"); + + return 0; +} + +static int bq25890_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct device *dev = &client->dev; + struct bq25890_device *bq; + int ret; + int i; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL); + if (!bq) + return -ENOMEM; + + bq->client = client; + bq->dev = dev; + + mutex_init(&bq->lock); + + bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config); + if (IS_ERR(bq->rmap)) { + dev_err(dev, "failed to allocate register map\n"); + return PTR_ERR(bq->rmap); + } + + for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) { + const struct reg_field *reg_fields = bq25890_reg_fields; + + bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap, + reg_fields[i]); + if (IS_ERR(bq->rmap_fields[i])) { + dev_err(dev, "cannot allocate regmap field\n"); + return PTR_ERR(bq->rmap_fields[i]); + } + } + + i2c_set_clientdata(client, bq); + + bq->chip_id = bq25890_field_read(bq, F_PN); + if (bq->chip_id < 0) { + dev_err(dev, "Cannot read chip ID.\n"); + return ret; + } + + if (bq->chip_id != BQ25890_ID) { + dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id); + return -ENODEV; + } + + if (!dev->platform_data) { + ret = bq25890_fw_probe(bq); + if (ret < 0) { + dev_err(dev, "Cannot read device properties.\n"); + return ret; + } + } else { + return -ENODEV; + } + + ret = bq25890_hw_init(bq); + if (ret < 0) { + dev_err(dev, "Cannot initialize the chip.\n"); + return ret; + } + + if (client->irq <= 0) + client->irq = bq25890_irq_probe(bq); + + if (client->irq < 0) { + dev_err(dev, "No irq resource found.\n"); + return client->irq; + } + + /* OTG reporting */ + bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(bq->usb_phy)) { + INIT_WORK(&bq->usb_work, bq25890_usb_work); + bq->usb_nb.notifier_call = bq25890_usb_notifier; + usb_register_notifier(bq->usb_phy, &bq->usb_nb); + } + + ret = devm_request_threaded_irq(dev, client->irq, NULL, + bq25890_irq_handler_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + BQ25890_IRQ_PIN, bq); + if (ret) + goto irq_fail; + + ret = bq25890_power_supply_init(bq); + if (ret < 0) { + dev_err(dev, "Failed to register power supply\n"); + goto irq_fail; + } + + return 0; + +irq_fail: + if (!IS_ERR_OR_NULL(bq->usb_phy)) + usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); + + return ret; +} + +static int bq25890_remove(struct i2c_client *client) +{ + struct bq25890_device *bq = i2c_get_clientdata(client); + + power_supply_unregister(bq->charger); + + if (!IS_ERR_OR_NULL(bq->usb_phy)) + usb_unregister_notifier(bq->usb_phy, &bq->usb_nb); + + /* reset all registers to default values */ + bq25890_chip_reset(bq); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq25890_suspend(struct device *dev) +{ + struct bq25890_device *bq = dev_get_drvdata(dev); + + /* + * If charger is removed, while in suspend, make sure ADC is diabled + * since it consumes slightly more power. + */ + return bq25890_field_write(bq, F_CONV_START, 0); +} + +static int bq25890_resume(struct device *dev) +{ + int ret; + struct bq25890_state state; + struct bq25890_device *bq = dev_get_drvdata(dev); + + ret = bq25890_get_chip_state(bq, &state); + if (ret < 0) + return ret; + + mutex_lock(&bq->lock); + bq->state = state; + mutex_unlock(&bq->lock); + + /* Re-enable ADC only if charger is plugged in. */ + if (state.online) { + ret = bq25890_field_write(bq, F_CONV_START, 1); + if (ret < 0) + return ret; + } + + /* signal userspace, maybe state changed while suspended */ + power_supply_changed(bq->charger); + + return 0; +} +#endif + +static const struct dev_pm_ops bq25890_pm = { + SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume) +}; + +static const struct i2c_device_id bq25890_i2c_ids[] = { + { "bq25890", 0 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids); + +static const struct of_device_id bq25890_of_match[] = { + { .compatible = "ti,bq25890", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq25890_of_match); + +static const struct acpi_device_id bq25890_acpi_match[] = { + {"BQ258900", 0}, + {}, +}; +MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match); + +static struct i2c_driver bq25890_driver = { + .driver = { + .name = "bq25890-charger", + .of_match_table = of_match_ptr(bq25890_of_match), + .acpi_match_table = ACPI_PTR(bq25890_acpi_match), + .pm = &bq25890_pm, + }, + .probe = bq25890_probe, + .remove = bq25890_remove, + .id_table = bq25890_i2c_ids, +}; +module_i2c_driver(bq25890_driver); + +MODULE_AUTHOR("Laurentiu Palcu "); +MODULE_DESCRIPTION("bq25890 charger driver"); +MODULE_LICENSE("GPL"); -- cgit v1.2.3-58-ga151 From edd4ab0559316a1efe0881a4e2ccaeb4fec73142 Mon Sep 17 00:00:00 2001 From: Ramakrishna Pallala Date: Sun, 24 May 2015 09:11:58 +0530 Subject: power: max17042_battery: add HEALTH and TEMP_* properties support This patch adds the support for following battery properties to max17042 fuel gauge driver. POWER_SUPPLY_PROP_TEMP_ALERT_MIN POWER_SUPPLY_PROP_TEMP_ALERT_MAX POWER_SUPPLY_PROP_TEMP_MIN POWER_SUPPLY_PROP_TEMP_MAX POWER_SUPPLY_PROP_HEALTH Signed-off-by: Ramakrishna Pallala Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/max17042_battery.c | 190 +++++++++++++++++++++++++++++++-- include/linux/power/max17042_battery.h | 4 + 2 files changed, 183 insertions(+), 11 deletions(-) (limited to 'drivers') diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 6cc5e87ec031..748d9762e89b 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -63,6 +63,8 @@ #define dP_ACC_100 0x1900 #define dP_ACC_200 0x3200 +#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */ + struct max17042_chip { struct i2c_client *client; struct regmap *regmap; @@ -85,10 +87,94 @@ static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_CHARGE_FULL, POWER_SUPPLY_PROP_CHARGE_COUNTER, POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TEMP_ALERT_MIN, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_TEMP_MIN, + POWER_SUPPLY_PROP_TEMP_MAX, + POWER_SUPPLY_PROP_HEALTH, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, }; +static int max17042_get_temperature(struct max17042_chip *chip, int *temp) +{ + int ret; + u32 data; + struct regmap *map = chip->regmap; + + ret = regmap_read(map, MAX17042_TEMP, &data); + if (ret < 0) + return ret; + + *temp = data; + /* The value is signed. */ + if (*temp & 0x8000) { + *temp = (0x7fff & ~*temp) + 1; + *temp *= -1; + } + + /* The value is converted into deci-centigrade scale */ + /* Units of LSB = 1 / 256 degree Celsius */ + *temp = *temp * 10 / 256; + return 0; +} + +static int max17042_get_battery_health(struct max17042_chip *chip, int *health) +{ + int temp, vavg, vbatt, ret; + u32 val; + + ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vavg = val * 625 / 8; + /* Convert to millivolts */ + vavg /= 1000; + + ret = regmap_read(chip->regmap, MAX17042_VCELL, &val); + if (ret < 0) + goto health_error; + + /* bits [0-3] unused */ + vbatt = val * 625 / 8; + /* Convert to millivolts */ + vbatt /= 1000; + + if (vavg < chip->pdata->vmin) { + *health = POWER_SUPPLY_HEALTH_DEAD; + goto out; + } + + if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) { + *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto out; + } + + ret = max17042_get_temperature(chip, &temp); + if (ret < 0) + goto health_error; + + if (temp <= chip->pdata->temp_min) { + *health = POWER_SUPPLY_HEALTH_COLD; + goto out; + } + + if (temp >= chip->pdata->temp_max) { + *health = POWER_SUPPLY_HEALTH_OVERHEAT; + goto out; + } + + *health = POWER_SUPPLY_HEALTH_GOOD; + +out: + return 0; + +health_error: + return ret; +} + static int max17042_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -181,19 +267,34 @@ static int max17042_get_property(struct power_supply *psy, val->intval = data * 1000 / 2; break; case POWER_SUPPLY_PROP_TEMP: - ret = regmap_read(map, MAX17042_TEMP, &data); + ret = max17042_get_temperature(chip, &val->intval); + if (ret < 0) + return ret; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* LSB is Alert Minimum. In deci-centigrade */ + val->intval = (data & 0xff) * 10; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + /* MSB is Alert Maximum. In deci-centigrade */ + val->intval = (data >> 8) * 10; + break; + case POWER_SUPPLY_PROP_TEMP_MIN: + val->intval = chip->pdata->temp_min; + break; + case POWER_SUPPLY_PROP_TEMP_MAX: + val->intval = chip->pdata->temp_max; + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = max17042_get_battery_health(chip, &val->intval); if (ret < 0) return ret; - - val->intval = data; - /* The value is signed. */ - if (val->intval & 0x8000) { - val->intval = (0x7fff & ~val->intval) + 1; - val->intval *= -1; - } - /* The value is converted into deci-centigrade scale */ - /* Units of LSB = 1 / 256 degree Celsius */ - val->intval = val->intval * 10 / 256; break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (chip->pdata->enable_current_sense) { @@ -237,6 +338,69 @@ static int max17042_get_property(struct power_supply *psy, return 0; } +static int max17042_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max17042_chip *chip = power_supply_get_drvdata(psy); + struct regmap *map = chip->regmap; + int ret = 0; + u32 data; + int8_t temp; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in deci-centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force min < max */ + if (temp >= (int8_t)(data >> 8)) + temp = (int8_t)(data >> 8) - 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff00) + temp; + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = regmap_read(map, MAX17042_TALRT_Th, &data); + if (ret < 0) + return ret; + + /* Input in Deci-Centigrade, convert to centigrade */ + temp = val->intval / 10; + /* force max > min */ + if (temp <= (int8_t)(data & 0xff)) + temp = (int8_t)(data & 0xff) + 1; + /* Write both MAX and MIN ALERT */ + data = (data & 0xff) + (temp << 8); + ret = regmap_write(map, MAX17042_TALRT_Th, data); + break; + default: + ret = -EINVAL; + } + + return ret; +} + +static int max17042_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_TEMP_ALERT_MIN: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) { int retries = 8; @@ -665,6 +829,8 @@ static const struct power_supply_desc max17042_psy_desc = { .name = "max170xx_battery", .type = POWER_SUPPLY_TYPE_BATTERY, .get_property = max17042_get_property, + .set_property = max17042_set_property, + .property_is_writeable = max17042_property_is_writeable, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props), }; @@ -673,6 +839,8 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = { .name = "max170xx_battery", .type = POWER_SUPPLY_TYPE_BATTERY, .get_property = max17042_get_property, + .set_property = max17042_set_property, + .property_is_writeable = max17042_property_is_writeable, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, }; diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index cf112b4075c8..522757ac9cd4 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -215,6 +215,10 @@ struct max17042_platform_data { * the datasheet although it can be changed by board designers. */ unsigned int r_sns; + int vmin; /* in millivolts */ + int vmax; /* in millivolts */ + int temp_min; /* in tenths of degree Celsius */ + int temp_max; /* in tenths of degree Celsius */ }; #endif /* __MAX17042_BATTERY_H_ */ -- cgit v1.2.3-58-ga151 From 843735b788a4e49c453f4aefdae80e6dfbe9ee85 Mon Sep 17 00:00:00 2001 From: Ramakrishna Pallala Date: Mon, 4 May 2015 22:16:07 +0530 Subject: power: axp288_charger: axp288 charger driver This patch adds new power supply charger driver support for X-Power AXP288 PMIC integrated charger. This driver interfaces with the axp20x mfd driver as a cell and listens to extcon cable events for setting up charging. Signed-off-by: Ramakrishna Pallala Acked-by: Lee Jones Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/axp288_charger.c | 941 +++++++++++++++++++++++++++++++++++++++++ include/linux/mfd/axp20x.h | 7 + 4 files changed, 956 insertions(+) create mode 100644 drivers/power/axp288_charger.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 0fe5b08055ba..a848b1a5273e 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -204,6 +204,13 @@ config CHARGER_DA9150 This driver can also be built as a module. If so, the module will be called da9150-charger. +config AXP288_CHARGER + tristate "X-Powers AXP288 Charger" + depends on MFD_AXP20X && EXTCON_AXP288 + help + Say yes here to have support X-Power AXP288 power management IC (PMIC) + integrated charger. + config AXP288_FUEL_GAUGE tristate "X-Powers AXP288 Fuel Gauge" depends on MFD_AXP20X && IIO diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 03942e99776b..3572a72432d0 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -66,3 +66,4 @@ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_POWER_RESET) += reset/ obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o +obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o diff --git a/drivers/power/axp288_charger.c b/drivers/power/axp288_charger.c new file mode 100644 index 000000000000..5680317f4823 --- /dev/null +++ b/drivers/power/axp288_charger.c @@ -0,0 +1,941 @@ +/* + * axp288_charger.c - X-power AXP288 PMIC Charger driver + * + * Copyright (C) 2014 Intel Corporation + * Author: Ramakrishna Pallala + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define PS_STAT_VBUS_TRIGGER (1 << 0) +#define PS_STAT_BAT_CHRG_DIR (1 << 2) +#define PS_STAT_VBAT_ABOVE_VHOLD (1 << 3) +#define PS_STAT_VBUS_VALID (1 << 4) +#define PS_STAT_VBUS_PRESENT (1 << 5) + +#define CHRG_STAT_BAT_SAFE_MODE (1 << 3) +#define CHRG_STAT_BAT_VALID (1 << 4) +#define CHRG_STAT_BAT_PRESENT (1 << 5) +#define CHRG_STAT_CHARGING (1 << 6) +#define CHRG_STAT_PMIC_OTP (1 << 7) + +#define VBUS_ISPOUT_CUR_LIM_MASK 0x03 +#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0 +#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */ +#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */ +#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */ +#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */ +#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31 +#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3 +#define VBUS_ISPOUT_VHOLD_SET_OFFSET 4000 /* 4000mV */ +#define VBUS_ISPOUT_VHOLD_SET_LSB_RES 100 /* 100mV */ +#define VBUS_ISPOUT_VHOLD_SET_4300MV 0x3 /* 4300mV */ +#define VBUS_ISPOUT_VBUS_PATH_DIS (1 << 7) + +#define CHRG_CCCV_CC_MASK 0xf /* 4 bits */ +#define CHRG_CCCV_CC_BIT_POS 0 +#define CHRG_CCCV_CC_OFFSET 200 /* 200mA */ +#define CHRG_CCCV_CC_LSB_RES 200 /* 200mA */ +#define CHRG_CCCV_ITERM_20P (1 << 4) /* 20% of CC */ +#define CHRG_CCCV_CV_MASK 0x60 /* 2 bits */ +#define CHRG_CCCV_CV_BIT_POS 5 +#define CHRG_CCCV_CV_4100MV 0x0 /* 4.10V */ +#define CHRG_CCCV_CV_4150MV 0x1 /* 4.15V */ +#define CHRG_CCCV_CV_4200MV 0x2 /* 4.20V */ +#define CHRG_CCCV_CV_4350MV 0x3 /* 4.35V */ +#define CHRG_CCCV_CHG_EN (1 << 7) + +#define CNTL2_CC_TIMEOUT_MASK 0x3 /* 2 bits */ +#define CNTL2_CC_TIMEOUT_OFFSET 6 /* 6 Hrs */ +#define CNTL2_CC_TIMEOUT_LSB_RES 2 /* 2 Hrs */ +#define CNTL2_CC_TIMEOUT_12HRS 0x3 /* 12 Hrs */ +#define CNTL2_CHGLED_TYPEB (1 << 4) +#define CNTL2_CHG_OUT_TURNON (1 << 5) +#define CNTL2_PC_TIMEOUT_MASK 0xC0 +#define CNTL2_PC_TIMEOUT_OFFSET 40 /* 40 mins */ +#define CNTL2_PC_TIMEOUT_LSB_RES 10 /* 10 mins */ +#define CNTL2_PC_TIMEOUT_70MINS 0x3 + +#define CHRG_ILIM_TEMP_LOOP_EN (1 << 3) +#define CHRG_VBUS_ILIM_MASK 0xf0 +#define CHRG_VBUS_ILIM_BIT_POS 4 +#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */ +#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */ +#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */ +#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */ +#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */ +#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */ +#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */ + +#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */ +#define CHRG_VHTFC_45C 0x1F /* 45 DegC */ + +#define BAT_IRQ_CFG_CHRG_DONE (1 << 2) +#define BAT_IRQ_CFG_CHRG_START (1 << 3) +#define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4) +#define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5) +#define BAT_IRQ_CFG_BAT_DISCON (1 << 6) +#define BAT_IRQ_CFG_BAT_CONN (1 << 7) +#define BAT_IRQ_CFG_BAT_MASK 0xFC + +#define TEMP_IRQ_CFG_QCBTU (1 << 4) +#define TEMP_IRQ_CFG_CBTU (1 << 5) +#define TEMP_IRQ_CFG_QCBTO (1 << 6) +#define TEMP_IRQ_CFG_CBTO (1 << 7) +#define TEMP_IRQ_CFG_MASK 0xF0 + +#define FG_CNTL_OCV_ADJ_EN (1 << 3) + +#define CV_4100MV 4100 /* 4100mV */ +#define CV_4150MV 4150 /* 4150mV */ +#define CV_4200MV 4200 /* 4200mV */ +#define CV_4350MV 4350 /* 4350mV */ + +#define CC_200MA 200 /* 200mA */ +#define CC_600MA 600 /* 600mA */ +#define CC_800MA 800 /* 800mA */ +#define CC_1000MA 1000 /* 1000mA */ +#define CC_1600MA 1600 /* 1600mA */ +#define CC_2000MA 2000 /* 2000mA */ + +#define ILIM_100MA 100 /* 100mA */ +#define ILIM_500MA 500 /* 500mA */ +#define ILIM_900MA 900 /* 900mA */ +#define ILIM_1500MA 1500 /* 1500mA */ +#define ILIM_2000MA 2000 /* 2000mA */ +#define ILIM_2500MA 2500 /* 2500mA */ +#define ILIM_3000MA 3000 /* 3000mA */ + +#define AXP288_EXTCON_DEV_NAME "axp288_extcon" + +#define AXP288_EXTCON_SLOW_CHARGER "SLOW-CHARGER" +#define AXP288_EXTCON_DOWNSTREAM_CHARGER "CHARGE-DOWNSTREAM" +#define AXP288_EXTCON_FAST_CHARGER "FAST-CHARGER" + +enum { + VBUS_OV_IRQ = 0, + CHARGE_DONE_IRQ, + CHARGE_CHARGING_IRQ, + BAT_SAFE_QUIT_IRQ, + BAT_SAFE_ENTER_IRQ, + QCBTU_IRQ, + CBTU_IRQ, + QCBTO_IRQ, + CBTO_IRQ, + CHRG_INTR_END, +}; + +struct axp288_chrg_info { + struct platform_device *pdev; + struct axp20x_chrg_pdata *pdata; + struct regmap *regmap; + struct regmap_irq_chip_data *regmap_irqc; + int irq[CHRG_INTR_END]; + struct power_supply *psy_usb; + struct mutex lock; + + /* OTG/Host mode */ + struct { + struct work_struct work; + struct extcon_specific_cable_nb cable; + struct notifier_block id_nb; + bool id_short; + } otg; + + /* SDP/CDP/DCP USB charging cable notifications */ + struct { + struct extcon_dev *edev; + bool connected; + enum power_supply_type chg_type; + struct notifier_block nb; + struct work_struct work; + } cable; + + int health; + int inlmt; + int cc; + int cv; + int max_cc; + int max_cv; + bool online; + bool present; + bool enable_charger; + bool is_charger_enabled; +}; + +static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc) +{ + u8 reg_val; + int ret; + + if (cc < CHRG_CCCV_CC_OFFSET) + cc = CHRG_CCCV_CC_OFFSET; + else if (cc > info->max_cc) + cc = info->max_cc; + + reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES; + cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; + reg_val = reg_val << CHRG_CCCV_CC_BIT_POS; + + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL1, + CHRG_CCCV_CC_MASK, reg_val); + if (ret >= 0) + info->cc = cc; + + return ret; +} + +static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv) +{ + u8 reg_val; + int ret; + + if (cv <= CV_4100MV) { + reg_val = CHRG_CCCV_CV_4100MV; + cv = CV_4100MV; + } else if (cv <= CV_4150MV) { + reg_val = CHRG_CCCV_CV_4150MV; + cv = CV_4150MV; + } else if (cv <= CV_4200MV) { + reg_val = CHRG_CCCV_CV_4200MV; + cv = CV_4200MV; + } else { + reg_val = CHRG_CCCV_CV_4350MV; + cv = CV_4350MV; + } + + reg_val = reg_val << CHRG_CCCV_CV_BIT_POS; + + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL1, + CHRG_CCCV_CV_MASK, reg_val); + + if (ret >= 0) + info->cv = cv; + + return ret; +} + +static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info, + int inlmt) +{ + int ret; + unsigned int val; + u8 reg_val; + + /* Read in limit register */ + ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val); + if (ret < 0) + goto set_inlmt_fail; + + if (inlmt <= ILIM_100MA) { + reg_val = CHRG_VBUS_ILIM_100MA; + inlmt = ILIM_100MA; + } else if (inlmt <= ILIM_500MA) { + reg_val = CHRG_VBUS_ILIM_500MA; + inlmt = ILIM_500MA; + } else if (inlmt <= ILIM_900MA) { + reg_val = CHRG_VBUS_ILIM_900MA; + inlmt = ILIM_900MA; + } else if (inlmt <= ILIM_1500MA) { + reg_val = CHRG_VBUS_ILIM_1500MA; + inlmt = ILIM_1500MA; + } else if (inlmt <= ILIM_2000MA) { + reg_val = CHRG_VBUS_ILIM_2000MA; + inlmt = ILIM_2000MA; + } else if (inlmt <= ILIM_2500MA) { + reg_val = CHRG_VBUS_ILIM_2500MA; + inlmt = ILIM_2500MA; + } else { + reg_val = CHRG_VBUS_ILIM_3000MA; + inlmt = ILIM_3000MA; + } + + reg_val = (val & ~CHRG_VBUS_ILIM_MASK) + | (reg_val << CHRG_VBUS_ILIM_BIT_POS); + ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val); + if (ret >= 0) + info->inlmt = inlmt; + else + dev_err(&info->pdev->dev, "charger BAK control %d\n", ret); + + +set_inlmt_fail: + return ret; +} + +static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info, + bool enable) +{ + int ret; + + if (enable) + ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, + VBUS_ISPOUT_VBUS_PATH_DIS, 0); + else + ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT, + VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS); + + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret); + + + return ret; +} + +static int axp288_charger_enable_charger(struct axp288_chrg_info *info, + bool enable) +{ + int ret; + + if (enable) + ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, + CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN); + else + ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1, + CHRG_CCCV_CHG_EN, 0); + if (ret < 0) + dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret); + else + info->is_charger_enabled = enable; + + return ret; +} + +static int axp288_charger_is_present(struct axp288_chrg_info *info) +{ + int ret, present = 0; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if (ret < 0) + return ret; + + if (val & PS_STAT_VBUS_PRESENT) + present = 1; + return present; +} + +static int axp288_charger_is_online(struct axp288_chrg_info *info) +{ + int ret, online = 0; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if (ret < 0) + return ret; + + if (val & PS_STAT_VBUS_VALID) + online = 1; + return online; +} + +static int axp288_get_charger_health(struct axp288_chrg_info *info) +{ + int ret, pwr_stat, chrg_stat; + int health = POWER_SUPPLY_HEALTH_UNKNOWN; + unsigned int val; + + ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val); + if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT)) + goto health_read_fail; + else + pwr_stat = val; + + ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val); + if (ret < 0) + goto health_read_fail; + else + chrg_stat = val; + + if (!(pwr_stat & PS_STAT_VBUS_VALID)) + health = POWER_SUPPLY_HEALTH_DEAD; + else if (chrg_stat & CHRG_STAT_PMIC_OTP) + health = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE) + health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else + health = POWER_SUPPLY_HEALTH_GOOD; + +health_read_fail: + return health; +} + +static int axp288_charger_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct axp288_chrg_info *info = power_supply_get_drvdata(psy); + int ret = 0; + int scaled_val; + + mutex_lock(&info->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + scaled_val = min(val->intval, info->max_cc); + scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); + ret = axp288_charger_set_cc(info, scaled_val); + if (ret < 0) + dev_warn(&info->pdev->dev, "set charge current failed\n"); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + scaled_val = min(val->intval, info->max_cv); + scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000); + ret = axp288_charger_set_cv(info, scaled_val); + if (ret < 0) + dev_warn(&info->pdev->dev, "set charge voltage failed\n"); + break; + default: + ret = -EINVAL; + } + + mutex_unlock(&info->lock); + return ret; +} + +static int axp288_charger_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct axp288_chrg_info *info = power_supply_get_drvdata(psy); + int ret = 0; + + mutex_lock(&info->lock); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + /* Check for OTG case first */ + if (info->otg.id_short) { + val->intval = 0; + break; + } + ret = axp288_charger_is_present(info); + if (ret < 0) + goto psy_get_prop_fail; + info->present = ret; + val->intval = info->present; + break; + case POWER_SUPPLY_PROP_ONLINE: + /* Check for OTG case first */ + if (info->otg.id_short) { + val->intval = 0; + break; + } + ret = axp288_charger_is_online(info); + if (ret < 0) + goto psy_get_prop_fail; + info->online = ret; + val->intval = info->online; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = axp288_get_charger_health(info); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = info->cc * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = info->max_cc * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = info->cv * 1000; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = info->max_cv * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = info->inlmt * 1000; + break; + default: + ret = -EINVAL; + goto psy_get_prop_fail; + } + +psy_get_prop_fail: + mutex_unlock(&info->lock); + return ret; +} + +static int axp288_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property axp288_usb_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, +}; + +static const struct power_supply_desc axp288_charger_desc = { + .name = "axp288_charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = axp288_usb_props, + .num_properties = ARRAY_SIZE(axp288_usb_props), + .get_property = axp288_charger_usb_get_property, + .set_property = axp288_charger_usb_set_property, + .property_is_writeable = axp288_charger_property_is_writeable, +}; + +static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev) +{ + struct axp288_chrg_info *info = dev; + int i; + + for (i = 0; i < CHRG_INTR_END; i++) { + if (info->irq[i] == irq) + break; + } + + if (i >= CHRG_INTR_END) { + dev_warn(&info->pdev->dev, "spurious interrupt!!\n"); + return IRQ_NONE; + } + + switch (i) { + case VBUS_OV_IRQ: + dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n"); + break; + case CHARGE_DONE_IRQ: + dev_dbg(&info->pdev->dev, "Charging Done INTR\n"); + break; + case CHARGE_CHARGING_IRQ: + dev_dbg(&info->pdev->dev, "Start Charging IRQ\n"); + break; + case BAT_SAFE_QUIT_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Safe Mode(restart timer) Charging IRQ\n"); + break; + case BAT_SAFE_ENTER_IRQ: + dev_dbg(&info->pdev->dev, + "Enter Safe Mode(timer expire) Charging IRQ\n"); + break; + case QCBTU_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Battery Under Temperature(CHRG) INTR\n"); + break; + case CBTU_IRQ: + dev_dbg(&info->pdev->dev, + "Hit Battery Under Temperature(CHRG) INTR\n"); + break; + case QCBTO_IRQ: + dev_dbg(&info->pdev->dev, + "Quit Battery Over Temperature(CHRG) INTR\n"); + break; + case CBTO_IRQ: + dev_dbg(&info->pdev->dev, + "Hit Battery Over Temperature(CHRG) INTR\n"); + break; + default: + dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n"); + goto out; + } + + power_supply_changed(info->psy_usb); +out: + return IRQ_HANDLED; +} + +static void axp288_charger_extcon_evt_worker(struct work_struct *work) +{ + struct axp288_chrg_info *info = + container_of(work, struct axp288_chrg_info, cable.work); + int ret, current_limit; + bool changed = false; + struct extcon_dev *edev = info->cable.edev; + bool old_connected = info->cable.connected; + + /* Determine cable/charger type */ + if (extcon_get_cable_state(edev, AXP288_EXTCON_SLOW_CHARGER) > 0) { + dev_dbg(&info->pdev->dev, "USB SDP charger is connected"); + info->cable.connected = true; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB; + } else if (extcon_get_cable_state(edev, + AXP288_EXTCON_DOWNSTREAM_CHARGER) > 0) { + dev_dbg(&info->pdev->dev, "USB CDP charger is connected"); + info->cable.connected = true; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP; + } else if (extcon_get_cable_state(edev, + AXP288_EXTCON_FAST_CHARGER) > 0) { + dev_dbg(&info->pdev->dev, "USB DCP charger is connected"); + info->cable.connected = true; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP; + } else { + if (old_connected) + dev_dbg(&info->pdev->dev, "USB charger disconnected"); + info->cable.connected = false; + info->cable.chg_type = POWER_SUPPLY_TYPE_USB; + } + + /* Cable status changed */ + if (old_connected != info->cable.connected) + changed = true; + + if (!changed) + return; + + mutex_lock(&info->lock); + + if (info->is_charger_enabled && !info->cable.connected) { + info->enable_charger = false; + ret = axp288_charger_enable_charger(info, info->enable_charger); + if (ret < 0) + dev_err(&info->pdev->dev, + "cannot disable charger (%d)", ret); + + } else if (!info->is_charger_enabled && info->cable.connected) { + switch (info->cable.chg_type) { + case POWER_SUPPLY_TYPE_USB: + current_limit = ILIM_500MA; + break; + case POWER_SUPPLY_TYPE_USB_CDP: + current_limit = ILIM_1500MA; + break; + case POWER_SUPPLY_TYPE_USB_DCP: + current_limit = ILIM_2000MA; + break; + default: + /* Unknown */ + current_limit = 0; + break; + } + + /* Set vbus current limit first, then enable charger */ + ret = axp288_charger_set_vbus_inlmt(info, current_limit); + if (ret < 0) { + dev_err(&info->pdev->dev, + "error setting current limit (%d)", ret); + } else { + info->enable_charger = (current_limit > 0); + ret = axp288_charger_enable_charger(info, + info->enable_charger); + if (ret < 0) + dev_err(&info->pdev->dev, + "cannot enable charger (%d)", ret); + } + } + + if (changed) + info->health = axp288_get_charger_health(info); + + mutex_unlock(&info->lock); + + if (changed) + power_supply_changed(info->psy_usb); +} + +static int axp288_charger_handle_cable_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct axp288_chrg_info *info = + container_of(nb, struct axp288_chrg_info, cable.nb); + + schedule_work(&info->cable.work); + + return NOTIFY_OK; +} + +static void axp288_charger_otg_evt_worker(struct work_struct *work) +{ + struct axp288_chrg_info *info = + container_of(work, struct axp288_chrg_info, otg.work); + int ret; + + /* Disable VBUS path before enabling the 5V boost */ + ret = axp288_charger_vbus_path_select(info, !info->otg.id_short); + if (ret < 0) + dev_warn(&info->pdev->dev, "vbus path disable failed\n"); +} + +static int axp288_charger_handle_otg_evt(struct notifier_block *nb, + unsigned long event, void *param) +{ + struct axp288_chrg_info *info = + container_of(nb, struct axp288_chrg_info, otg.id_nb); + struct extcon_dev *edev = param; + int usb_host = extcon_get_cable_state(edev, "USB-Host"); + + dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n", + usb_host ? "attached" : "detached"); + + /* + * Set usb_id_short flag to avoid running charger detection logic + * in case usb host. + */ + info->otg.id_short = usb_host; + schedule_work(&info->otg.work); + + return NOTIFY_OK; +} + +static void charger_init_hw_regs(struct axp288_chrg_info *info) +{ + int ret, cc, cv; + unsigned int val; + + /* Program temperature thresholds */ + ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_V_LTF_CHRG, ret); + + ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_V_HTF_CHRG, ret); + + /* Do not turn-off charger o/p after charge cycle ends */ + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL2, + CNTL2_CHG_OUT_TURNON, 1); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_CHRG_CTRL2, ret); + + /* Enable interrupts */ + ret = regmap_update_bits(info->regmap, + AXP20X_IRQ2_EN, + BAT_IRQ_CFG_BAT_MASK, 1); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_IRQ2_EN, ret); + + ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN, + TEMP_IRQ_CFG_MASK, 1); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_IRQ3_EN, ret); + + /* Setup ending condition for charging to be 10% of I(chrg) */ + ret = regmap_update_bits(info->regmap, + AXP20X_CHRG_CTRL1, + CHRG_CCCV_ITERM_20P, 0); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_CHRG_CTRL1, ret); + + /* Disable OCV-SOC curve calibration */ + ret = regmap_update_bits(info->regmap, + AXP20X_CC_CTRL, + FG_CNTL_OCV_ADJ_EN, 0); + if (ret < 0) + dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n", + AXP20X_CC_CTRL, ret); + + /* Init charging current and voltage */ + info->max_cc = info->pdata->max_cc; + info->max_cv = info->pdata->max_cv; + + /* Read current charge voltage and current limit */ + ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val); + if (ret < 0) { + /* Assume default if cannot read */ + info->cc = info->pdata->def_cc; + info->cv = info->pdata->def_cv; + } else { + /* Determine charge voltage */ + cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS; + switch (cv) { + case CHRG_CCCV_CV_4100MV: + info->cv = CV_4100MV; + break; + case CHRG_CCCV_CV_4150MV: + info->cv = CV_4150MV; + break; + case CHRG_CCCV_CV_4200MV: + info->cv = CV_4200MV; + break; + case CHRG_CCCV_CV_4350MV: + info->cv = CV_4350MV; + break; + default: + info->cv = INT_MAX; + break; + } + + /* Determine charge current limit */ + cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS; + cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET; + info->cc = cc; + + /* Program default charging voltage and current */ + cc = min(info->pdata->def_cc, info->max_cc); + cv = min(info->pdata->def_cv, info->max_cv); + + ret = axp288_charger_set_cc(info, cc); + if (ret < 0) + dev_warn(&info->pdev->dev, + "error(%d) in setting CC\n", ret); + + ret = axp288_charger_set_cv(info, cv); + if (ret < 0) + dev_warn(&info->pdev->dev, + "error(%d) in setting CV\n", ret); + } +} + +static int axp288_charger_probe(struct platform_device *pdev) +{ + int ret, i, pirq; + struct axp288_chrg_info *info; + struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent); + struct power_supply_config charger_cfg = {}; + + info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pdev = pdev; + info->regmap = axp20x->regmap; + info->regmap_irqc = axp20x->regmap_irqc; + info->pdata = pdev->dev.platform_data; + + if (!info->pdata) { + /* Try ACPI provided pdata via device properties */ + if (!device_property_present(&pdev->dev, + "axp288_charger_data\n")) + dev_err(&pdev->dev, "failed to get platform data\n"); + return -ENODEV; + } + + info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME); + if (info->cable.edev == NULL) { + dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n", + AXP288_EXTCON_DEV_NAME); + return -EPROBE_DEFER; + } + + /* Register for extcon notification */ + INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker); + info->cable.nb.notifier_call = axp288_charger_handle_cable_evt; + ret = extcon_register_notifier(info->cable.edev, &info->cable.nb); + if (ret) { + dev_err(&info->pdev->dev, + "failed to register extcon notifier %d\n", ret); + return ret; + } + + platform_set_drvdata(pdev, info); + mutex_init(&info->lock); + + /* Register with power supply class */ + charger_cfg.drv_data = info; + info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc, + &charger_cfg); + if (IS_ERR(info->psy_usb)) { + dev_err(&pdev->dev, "failed to register power supply charger\n"); + ret = PTR_ERR(info->psy_usb); + goto psy_reg_failed; + } + + /* Register for OTG notification */ + INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker); + info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt; + ret = extcon_register_interest(&info->otg.cable, NULL, "USB-Host", + &info->otg.id_nb); + if (ret) + dev_warn(&pdev->dev, "failed to register otg notifier\n"); + + if (info->otg.cable.edev) + info->otg.id_short = extcon_get_cable_state( + info->otg.cable.edev, "USB-Host"); + + /* Register charger interrupts */ + for (i = 0; i < CHRG_INTR_END; i++) { + pirq = platform_get_irq(info->pdev, i); + info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq); + if (info->irq[i] < 0) { + dev_warn(&info->pdev->dev, + "failed to get virtual interrupt=%d\n", pirq); + ret = info->irq[i]; + goto intr_reg_failed; + } + ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i], + NULL, axp288_charger_irq_thread_handler, + IRQF_ONESHOT, info->pdev->name, info); + if (ret) { + dev_err(&pdev->dev, "failed to request interrupt=%d\n", + info->irq[i]); + goto intr_reg_failed; + } + } + + charger_init_hw_regs(info); + + return 0; + +intr_reg_failed: + if (info->otg.cable.edev) + extcon_unregister_interest(&info->otg.cable); + power_supply_unregister(info->psy_usb); +psy_reg_failed: + extcon_unregister_notifier(info->cable.edev, &info->cable.nb); + return ret; +} + +static int axp288_charger_remove(struct platform_device *pdev) +{ + struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev); + + if (info->otg.cable.edev) + extcon_unregister_interest(&info->otg.cable); + + extcon_unregister_notifier(info->cable.edev, &info->cable.nb); + power_supply_unregister(info->psy_usb); + + return 0; +} + +static struct platform_driver axp288_charger_driver = { + .probe = axp288_charger_probe, + .remove = axp288_charger_remove, + .driver = { + .name = "axp288_charger", + }, +}; + +module_platform_driver(axp288_charger_driver); + +MODULE_AUTHOR("Ramakrishna Pallala "); +MODULE_DESCRIPTION("X-power AXP288 Charger Driver"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/mfd/axp20x.h b/include/linux/mfd/axp20x.h index dfabd6db7ddf..f9030df5acd1 100644 --- a/include/linux/mfd/axp20x.h +++ b/include/linux/mfd/axp20x.h @@ -275,4 +275,11 @@ struct axp20x_fg_pdata { int thermistor_curve[MAX_THERM_CURVE_SIZE][2]; }; +struct axp20x_chrg_pdata { + int max_cc; + int max_cv; + int def_cc; + int def_cv; +}; + #endif /* __LINUX_MFD_AXP20X_H */ -- cgit v1.2.3-58-ga151 From cd054ee16580690830297dbda0abe9c184276d6a Mon Sep 17 00:00:00 2001 From: Thomas Elste Date: Thu, 28 May 2015 11:05:32 +0200 Subject: power: bq24190_charger: Change first_time flag reset condition The initial register reset of BQ24190 generates a charger status change whose propagation via power_supply_changed is prevented using a flag. This flag gets never reset so all following events are ignored as well leading for example to userspace not detecting charger connects/disconnects. Therefor change the reset condition of first_time flag, so only the propagation of the first charger status change is prevented. Signed-off-by: Thomas Elste Signed-off-by: Sebastian Reichel --- drivers/power/bq24190_charger.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c index 407c4af83891..052db78c3736 100644 --- a/drivers/power/bq24190_charger.c +++ b/drivers/power/bq24190_charger.c @@ -1258,10 +1258,13 @@ static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) * register reset so we should ignore that one (the very first * interrupt received). */ - if (alert_userspace && !bdi->first_time) { - power_supply_changed(bdi->charger); - power_supply_changed(bdi->battery); - bdi->first_time = false; + if (alert_userspace) { + if (!bdi->first_time) { + power_supply_changed(bdi->charger); + power_supply_changed(bdi->battery); + } else { + bdi->first_time = false; + } } out: -- cgit v1.2.3-58-ga151 From a1cab5bb872507cb8594eb4010450fed7c48cdf2 Mon Sep 17 00:00:00 2001 From: Takeshi Yoshimura Date: Fri, 29 May 2015 13:16:43 +0000 Subject: power_supply: 88pm860x_charger: Do not call free_irq() twice My static checker detected double free_irq() in pm860x_charger_remove(). Unloading this module always causes a warning. This patch removes the first redundant free_irq() call. Signed-off-by: Takeshi Yoshimura Signed-off-by: Sebastian Reichel --- drivers/power/88pm860x_charger.c | 1 - 1 file changed, 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c index 0e448c68c02b..297e72dc70e6 100644 --- a/drivers/power/88pm860x_charger.c +++ b/drivers/power/88pm860x_charger.c @@ -742,7 +742,6 @@ static int pm860x_charger_remove(struct platform_device *pdev) int i; power_supply_unregister(info->usb); - free_irq(info->irq[0], info); for (i = 0; i < info->irq_nums; i++) free_irq(info->irq[i], info); return 0; -- cgit v1.2.3-58-ga151 From e86d69dd786e94046b8f5be7df1b9a8226a40b2a Mon Sep 17 00:00:00 2001 From: Anda-Maria Nicolae Date: Fri, 29 May 2015 17:25:45 +0300 Subject: power_supply: Add support for Richtek RT9455 battery charger Based on the datasheet found here: http://www.richtek.com/download_ds.jsp?p=RT9455 Signed-off-by: Anda-Maria Nicolae Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/Kconfig | 7 + drivers/power/Makefile | 1 + drivers/power/rt9455_charger.c | 1751 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1759 insertions(+) create mode 100644 drivers/power/rt9455_charger.c (limited to 'drivers') diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index a848b1a5273e..08beeed5485d 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -460,6 +460,13 @@ config BATTERY_RT5033 The fuelgauge calculates and determines the battery state of charge according to battery open circuit voltage. +config CHARGER_RT9455 + tristate "Richtek RT9455 battery charger driver" + depends on I2C && GPIOLIB + select REGMAP_I2C + help + Say Y to enable support for Richtek RT9455 battery charger. + source "drivers/power/reset/Kconfig" endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 3572a72432d0..5752ce818f51 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -37,6 +37,7 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o +obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.o obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c new file mode 100644 index 000000000000..7c0c1ffa84ec --- /dev/null +++ b/drivers/power/rt9455_charger.c @@ -0,0 +1,1751 @@ +/* + * Driver for Richtek RT9455WSC battery charger. + * + * Copyright (C) 2015 Intel Corporation + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define RT9455_MANUFACTURER "Richtek" +#define RT9455_MODEL_NAME "RT9455" +#define RT9455_DRIVER_NAME "rt9455-charger" + +#define RT9455_IRQ_NAME "interrupt" + +#define RT9455_PWR_RDY_DELAY 1 /* 1 second */ +#define RT9455_MAX_CHARGING_TIME 21600 /* 6 hrs */ +#define RT9455_BATT_PRESENCE_DELAY 60 /* 60 seconds */ + +#define RT9455_CHARGE_MODE 0x00 +#define RT9455_BOOST_MODE 0x01 + +#define RT9455_FAULT 0x03 + +#define RT9455_IAICR_100MA 0x00 +#define RT9455_IAICR_500MA 0x01 +#define RT9455_IAICR_NO_LIMIT 0x03 + +#define RT9455_CHARGE_DISABLE 0x00 +#define RT9455_CHARGE_ENABLE 0x01 + +#define RT9455_PWR_FAULT 0x00 +#define RT9455_PWR_GOOD 0x01 + +#define RT9455_REG_CTRL1 0x00 /* CTRL1 reg address */ +#define RT9455_REG_CTRL2 0x01 /* CTRL2 reg address */ +#define RT9455_REG_CTRL3 0x02 /* CTRL3 reg address */ +#define RT9455_REG_DEV_ID 0x03 /* DEV_ID reg address */ +#define RT9455_REG_CTRL4 0x04 /* CTRL4 reg address */ +#define RT9455_REG_CTRL5 0x05 /* CTRL5 reg address */ +#define RT9455_REG_CTRL6 0x06 /* CTRL6 reg address */ +#define RT9455_REG_CTRL7 0x07 /* CTRL7 reg address */ +#define RT9455_REG_IRQ1 0x08 /* IRQ1 reg address */ +#define RT9455_REG_IRQ2 0x09 /* IRQ2 reg address */ +#define RT9455_REG_IRQ3 0x0A /* IRQ3 reg address */ +#define RT9455_REG_MASK1 0x0B /* MASK1 reg address */ +#define RT9455_REG_MASK2 0x0C /* MASK2 reg address */ +#define RT9455_REG_MASK3 0x0D /* MASK3 reg address */ + +enum rt9455_fields { + F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */ + + F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ, + F_OPA_MODE, /* CTRL2 reg fields */ + + F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */ + + F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */ + + F_RST, /* CTRL4 reg fields */ + + F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/ + + F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */ + + F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */ + + F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */ + + F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI, + F_CHMIVRI, /* IRQ2 reg fields */ + + F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */ + + F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */ + + F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM, + F_CHMIVRIM, /* MASK2 reg fields */ + + F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */ + + F_MAX_FIELDS +}; + +static const struct reg_field rt9455_reg_fields[] = { + [F_STAT] = REG_FIELD(RT9455_REG_CTRL1, 4, 5), + [F_BOOST] = REG_FIELD(RT9455_REG_CTRL1, 3, 3), + [F_PWR_RDY] = REG_FIELD(RT9455_REG_CTRL1, 2, 2), + [F_OTG_PIN_POLARITY] = REG_FIELD(RT9455_REG_CTRL1, 1, 1), + + [F_IAICR] = REG_FIELD(RT9455_REG_CTRL2, 6, 7), + [F_TE_SHDN_EN] = REG_FIELD(RT9455_REG_CTRL2, 5, 5), + [F_HIGHER_OCP] = REG_FIELD(RT9455_REG_CTRL2, 4, 4), + [F_TE] = REG_FIELD(RT9455_REG_CTRL2, 3, 3), + [F_IAICR_INT] = REG_FIELD(RT9455_REG_CTRL2, 2, 2), + [F_HIZ] = REG_FIELD(RT9455_REG_CTRL2, 1, 1), + [F_OPA_MODE] = REG_FIELD(RT9455_REG_CTRL2, 0, 0), + + [F_VOREG] = REG_FIELD(RT9455_REG_CTRL3, 2, 7), + [F_OTG_PL] = REG_FIELD(RT9455_REG_CTRL3, 1, 1), + [F_OTG_EN] = REG_FIELD(RT9455_REG_CTRL3, 0, 0), + + [F_VENDOR_ID] = REG_FIELD(RT9455_REG_DEV_ID, 4, 7), + [F_CHIP_REV] = REG_FIELD(RT9455_REG_DEV_ID, 0, 3), + + [F_RST] = REG_FIELD(RT9455_REG_CTRL4, 7, 7), + + [F_TMR_EN] = REG_FIELD(RT9455_REG_CTRL5, 7, 7), + [F_MIVR] = REG_FIELD(RT9455_REG_CTRL5, 4, 5), + [F_IPREC] = REG_FIELD(RT9455_REG_CTRL5, 2, 3), + [F_IEOC_PERCENTAGE] = REG_FIELD(RT9455_REG_CTRL5, 0, 1), + + [F_IAICR_SEL] = REG_FIELD(RT9455_REG_CTRL6, 7, 7), + [F_ICHRG] = REG_FIELD(RT9455_REG_CTRL6, 4, 6), + [F_VPREC] = REG_FIELD(RT9455_REG_CTRL6, 0, 2), + + [F_BATD_EN] = REG_FIELD(RT9455_REG_CTRL7, 6, 6), + [F_CHG_EN] = REG_FIELD(RT9455_REG_CTRL7, 4, 4), + [F_VMREG] = REG_FIELD(RT9455_REG_CTRL7, 0, 3), + + [F_TSDI] = REG_FIELD(RT9455_REG_IRQ1, 7, 7), + [F_VINOVPI] = REG_FIELD(RT9455_REG_IRQ1, 6, 6), + [F_BATAB] = REG_FIELD(RT9455_REG_IRQ1, 0, 0), + + [F_CHRVPI] = REG_FIELD(RT9455_REG_IRQ2, 7, 7), + [F_CHBATOVI] = REG_FIELD(RT9455_REG_IRQ2, 5, 5), + [F_CHTERMI] = REG_FIELD(RT9455_REG_IRQ2, 4, 4), + [F_CHRCHGI] = REG_FIELD(RT9455_REG_IRQ2, 3, 3), + [F_CH32MI] = REG_FIELD(RT9455_REG_IRQ2, 2, 2), + [F_CHTREGI] = REG_FIELD(RT9455_REG_IRQ2, 1, 1), + [F_CHMIVRI] = REG_FIELD(RT9455_REG_IRQ2, 0, 0), + + [F_BSTBUSOVI] = REG_FIELD(RT9455_REG_IRQ3, 7, 7), + [F_BSTOLI] = REG_FIELD(RT9455_REG_IRQ3, 6, 6), + [F_BSTLOWVI] = REG_FIELD(RT9455_REG_IRQ3, 5, 5), + [F_BST32SI] = REG_FIELD(RT9455_REG_IRQ3, 3, 3), + + [F_TSDM] = REG_FIELD(RT9455_REG_MASK1, 7, 7), + [F_VINOVPIM] = REG_FIELD(RT9455_REG_MASK1, 6, 6), + [F_BATABM] = REG_FIELD(RT9455_REG_MASK1, 0, 0), + + [F_CHRVPIM] = REG_FIELD(RT9455_REG_MASK2, 7, 7), + [F_CHBATOVIM] = REG_FIELD(RT9455_REG_MASK2, 5, 5), + [F_CHTERMIM] = REG_FIELD(RT9455_REG_MASK2, 4, 4), + [F_CHRCHGIM] = REG_FIELD(RT9455_REG_MASK2, 3, 3), + [F_CH32MIM] = REG_FIELD(RT9455_REG_MASK2, 2, 2), + [F_CHTREGIM] = REG_FIELD(RT9455_REG_MASK2, 1, 1), + [F_CHMIVRIM] = REG_FIELD(RT9455_REG_MASK2, 0, 0), + + [F_BSTVINOVIM] = REG_FIELD(RT9455_REG_MASK3, 7, 7), + [F_BSTOLIM] = REG_FIELD(RT9455_REG_MASK3, 6, 6), + [F_BSTLOWVIM] = REG_FIELD(RT9455_REG_MASK3, 5, 5), + [F_BST32SIM] = REG_FIELD(RT9455_REG_MASK3, 3, 3), +}; + +#define GET_MASK(fid) (BIT(rt9455_reg_fields[fid].msb + 1) - \ + BIT(rt9455_reg_fields[fid].lsb)) + +/* + * Each array initialised below shows the possible real-world values for a + * group of bits belonging to RT9455 registers. The arrays are sorted in + * ascending order. The index of each real-world value represents the value + * that is encoded in the group of bits belonging to RT9455 registers. + */ +/* REG06[6:4] (ICHRG) in uAh */ +static const int rt9455_ichrg_values[] = { + 500000, 650000, 800000, 950000, 1100000, 1250000, 1400000, 1550000 +}; + +/* + * When the charger is in charge mode, REG02[7:2] represent battery regulation + * voltage. + */ +/* REG02[7:2] (VOREG) in uV */ +static const int rt9455_voreg_values[] = { + 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000, + 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000, + 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000, + 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000, + 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000, + 4300000, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000, + 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, + 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000 +}; + +/* + * When the charger is in boost mode, REG02[7:2] represent boost output + * voltage. + */ +/* REG02[7:2] (Boost output voltage) in uV */ +static const int rt9455_boost_voltage_values[] = { + 4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000, + 4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000, + 4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000, + 5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000, + 5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000, + 5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000, + 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, + 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, +}; + +/* REG07[3:0] (VMREG) in uV */ +static const int rt9455_vmreg_values[] = { + 4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000, + 4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000 +}; + +/* REG05[5:4] (IEOC_PERCENTAGE) */ +static const int rt9455_ieoc_percentage_values[] = { + 10, 30, 20, 30 +}; + +/* REG05[1:0] (MIVR) in uV */ +static const int rt9455_mivr_values[] = { + 4000000, 4250000, 4500000, 5000000 +}; + +/* REG05[1:0] (IAICR) in uA */ +static const int rt9455_iaicr_values[] = { + 100000, 500000, 1000000, 2000000 +}; + +struct rt9455_info { + struct i2c_client *client; + struct regmap *regmap; + struct regmap_field *regmap_fields[F_MAX_FIELDS]; + struct power_supply *charger; +#if IS_ENABLED(CONFIG_USB_PHY) + struct usb_phy *usb_phy; + struct notifier_block nb; +#endif + struct delayed_work pwr_rdy_work; + struct delayed_work max_charging_time_work; + struct delayed_work batt_presence_work; + u32 voreg; + u32 boost_voltage; +}; + +/* + * Iterate through each element of the 'tbl' array until an element whose value + * is greater than v is found. Return the index of the respective element, + * or the index of the last element in the array, if no such element is found. + */ +static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v) +{ + int i; + + /* + * No need to iterate until the last index in the table because + * if no element greater than v is found in the table, + * or if only the last element is greater than v, + * function returns the index of the last element. + */ + for (i = 0; i < tbl_size - 1; i++) + if (v <= tbl[i]) + return i; + + return (tbl_size - 1); +} + +static int rt9455_get_field_val(struct rt9455_info *info, + enum rt9455_fields field, + const int tbl[], int tbl_size, int *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[field], &v); + if (ret) + return ret; + + v = (v >= tbl_size) ? (tbl_size - 1) : v; + *val = tbl[v]; + + return 0; +} + +static int rt9455_set_field_val(struct rt9455_info *info, + enum rt9455_fields field, + const int tbl[], int tbl_size, int val) +{ + unsigned int idx = rt9455_find_idx(tbl, tbl_size, val); + + return regmap_field_write(info->regmap_fields[field], idx); +} + +static int rt9455_register_reset(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + unsigned int v; + int ret, limit = 100; + + ret = regmap_field_write(info->regmap_fields[F_RST], 0x01); + if (ret) { + dev_err(dev, "Failed to set RST bit\n"); + return ret; + } + + /* + * To make sure that reset operation has finished, loop until RST bit + * is set to 0. + */ + do { + ret = regmap_field_read(info->regmap_fields[F_RST], &v); + if (ret) { + dev_err(dev, "Failed to read RST bit\n"); + return ret; + } + + if (!v) + break; + + usleep_range(10, 100); + } while (--limit); + + if (!limit) + return -EIO; + + return 0; +} + +/* Charger power supply property routines */ +static enum power_supply_property rt9455_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static char *rt9455_charger_supplied_to[] = { + "main-battery", +}; + +static int rt9455_charger_get_status(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v, pwr_rdy; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], + &pwr_rdy); + if (ret) { + dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); + return ret; + } + + /* + * If PWR_RDY bit is unset, the battery is discharging. Otherwise, + * STAT bits value must be checked. + */ + if (!pwr_rdy) { + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read STAT bits\n"); + return ret; + } + + switch (v) { + case 0: + /* + * If PWR_RDY bit is set, but STAT bits value is 0, the charger + * may be in one of the following cases: + * 1. CHG_EN bit is 0. + * 2. CHG_EN bit is 1 but the battery is not connected. + * In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is + * returned. + */ + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + return 0; + case 1: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + return 0; + case 2: + val->intval = POWER_SUPPLY_STATUS_FULL; + return 0; + default: + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + return 0; + } +} + +static int rt9455_charger_get_health(struct rt9455_info *info, + union power_supply_propval *val) +{ + struct device *dev = &info->client->dev; + unsigned int v; + int ret; + + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return ret; + } + + if (v & GET_MASK(F_TSDI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + return 0; + } + if (v & GET_MASK(F_VINOVPI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + if (v & GET_MASK(F_BATAB)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + + ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ2 register\n"); + return ret; + } + + if (v & GET_MASK(F_CHBATOVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_CH32MI)) { + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + return 0; + } + + ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v); + if (ret) { + dev_err(dev, "Failed to read IRQ3 register\n"); + return ret; + } + + if (v & GET_MASK(F_BSTBUSOVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_BSTOLI)) { + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + return 0; + } + if (v & GET_MASK(F_BSTLOWVI)) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + if (v & GET_MASK(F_BST32SI)) { + val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + return 0; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &v); + if (ret) { + dev_err(dev, "Failed to read STAT bits\n"); + return ret; + } + + if (v == RT9455_FAULT) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + return 0; + } + + return 0; +} + +static int rt9455_charger_get_battery_presence(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_BATAB], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read BATAB bit\n"); + return ret; + } + + /* + * Since BATAB is 1 when battery is NOT present and 0 otherwise, + * !BATAB is returned. + */ + val->intval = !v; + + return 0; +} + +static int rt9455_charger_get_online(struct rt9455_info *info, + union power_supply_propval *val) +{ + unsigned int v; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v); + if (ret) { + dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n"); + return ret; + } + + val->intval = (int)v; + + return 0; +} + +static int rt9455_charger_get_current(struct rt9455_info *info, + union power_supply_propval *val) +{ + int curr; + int ret; + + ret = rt9455_get_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), + &curr); + if (ret) { + dev_err(&info->client->dev, "Failed to read ICHRG value\n"); + return ret; + } + + val->intval = curr; + + return 0; +} + +static int rt9455_charger_get_current_max(struct rt9455_info *info, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1; + + val->intval = rt9455_ichrg_values[idx]; + + return 0; +} + +static int rt9455_charger_get_voltage(struct rt9455_info *info, + union power_supply_propval *val) +{ + int voltage; + int ret; + + ret = rt9455_get_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + &voltage); + if (ret) { + dev_err(&info->client->dev, "Failed to read VOREG value\n"); + return ret; + } + + val->intval = voltage; + + return 0; +} + +static int rt9455_charger_get_voltage_max(struct rt9455_info *info, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; + + val->intval = rt9455_vmreg_values[idx]; + + return 0; +} + +static int rt9455_charger_get_term_current(struct rt9455_info *info, + union power_supply_propval *val) +{ + struct device *dev = &info->client->dev; + int ichrg, ieoc_percentage, ret; + + ret = rt9455_get_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), + &ichrg); + if (ret) { + dev_err(dev, "Failed to read ICHRG value\n"); + return ret; + } + + ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE, + rt9455_ieoc_percentage_values, + ARRAY_SIZE(rt9455_ieoc_percentage_values), + &ieoc_percentage); + if (ret) { + dev_err(dev, "Failed to read IEOC value\n"); + return ret; + } + + val->intval = ichrg * ieoc_percentage / 100; + + return 0; +} + +static int rt9455_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct rt9455_info *info = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return rt9455_charger_get_status(info, val); + case POWER_SUPPLY_PROP_HEALTH: + return rt9455_charger_get_health(info, val); + case POWER_SUPPLY_PROP_PRESENT: + return rt9455_charger_get_battery_presence(info, val); + case POWER_SUPPLY_PROP_ONLINE: + return rt9455_charger_get_online(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return rt9455_charger_get_current(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return rt9455_charger_get_current_max(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + return rt9455_charger_get_voltage(info, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return rt9455_charger_get_voltage_max(info, val); + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + return 0; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return rt9455_charger_get_term_current(info, val); + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = RT9455_MODEL_NAME; + return 0; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = RT9455_MANUFACTURER; + return 0; + default: + return -ENODATA; + } +} + +static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg, + u32 ieoc_percentage, + u32 mivr, u32 iaicr) +{ + struct device *dev = &info->client->dev; + int idx, ret; + + ret = rt9455_register_reset(info); + if (ret) { + dev_err(dev, "Power On Reset failed\n"); + return ret; + } + + /* Set TE bit in order to enable end of charge detection */ + ret = regmap_field_write(info->regmap_fields[F_TE], 1); + if (ret) { + dev_err(dev, "Failed to set TE bit\n"); + return ret; + } + + /* Set TE_SHDN_EN bit in order to enable end of charge detection */ + ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1); + if (ret) { + dev_err(dev, "Failed to set TE_SHDN_EN bit\n"); + return ret; + } + + /* + * Set BATD_EN bit in order to enable battery detection + * when charging is done + */ + ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1); + if (ret) { + dev_err(dev, "Failed to set BATD_EN bit\n"); + return ret; + } + + /* + * Disable Safety Timer. In charge mode, this timer terminates charging + * if no read or write via I2C is done within 32 minutes. This timer + * avoids overcharging the baterry when the OS is not loaded and the + * charger is connected to a power source. + * In boost mode, this timer triggers BST32SI interrupt if no read or + * write via I2C is done within 32 seconds. + * When the OS is loaded and the charger driver is inserted, it is used + * delayed_work, named max_charging_time_work, to avoid overcharging + * the battery. + */ + ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00); + if (ret) { + dev_err(dev, "Failed to disable Safety Timer\n"); + return ret; + } + + /* Set ICHRG to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_ICHRG, + rt9455_ichrg_values, + ARRAY_SIZE(rt9455_ichrg_values), ichrg); + if (ret) { + dev_err(dev, "Failed to set ICHRG value\n"); + return ret; + } + + /* Set IEOC Percentage to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE, + rt9455_ieoc_percentage_values, + ARRAY_SIZE(rt9455_ieoc_percentage_values), + ieoc_percentage); + if (ret) { + dev_err(dev, "Failed to set IEOC Percentage value\n"); + return ret; + } + + /* Set VOREG to value retrieved from device-specific data */ + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + info->voreg); + if (ret) { + dev_err(dev, "Failed to set VOREG value\n"); + return ret; + } + + /* Set VMREG value to maximum (4.45V). */ + idx = ARRAY_SIZE(rt9455_vmreg_values) - 1; + ret = rt9455_set_field_val(info, F_VMREG, + rt9455_vmreg_values, + ARRAY_SIZE(rt9455_vmreg_values), + rt9455_vmreg_values[idx]); + if (ret) { + dev_err(dev, "Failed to set VMREG value\n"); + return ret; + } + + /* + * Set MIVR to value retrieved from device-specific data. + * If no value is specified, default value for MIVR is 4.5V. + */ + if (mivr == -1) + mivr = 4500000; + + ret = rt9455_set_field_val(info, F_MIVR, + rt9455_mivr_values, + ARRAY_SIZE(rt9455_mivr_values), mivr); + if (ret) { + dev_err(dev, "Failed to set MIVR value\n"); + return ret; + } + + /* + * Set IAICR to value retrieved from device-specific data. + * If no value is specified, default value for IAICR is 500 mA. + */ + if (iaicr == -1) + iaicr = 500000; + + ret = rt9455_set_field_val(info, F_IAICR, + rt9455_iaicr_values, + ARRAY_SIZE(rt9455_iaicr_values), iaicr); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return ret; + } + + /* + * Set IAICR_INT bit so that IAICR value is determined by IAICR bits + * and not by OTG pin. + */ + ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01); + if (ret) { + dev_err(dev, "Failed to set IAICR_INT bit\n"); + return ret; + } + + /* + * Disable CHMIVRI interrupt. Because the driver sets MIVR value, + * CHMIVRI is triggered, but there is no action to be taken by the + * driver when CHMIVRI is triggered. + */ + ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01); + if (ret) { + dev_err(dev, "Failed to mask CHMIVRI interrupt\n"); + return ret; + } + + return 0; +} + +/* + * Before setting the charger into boost mode, boost output voltage is + * set. This is needed because boost output voltage may differ from battery + * regulation voltage. F_VOREG bits represent either battery regulation voltage + * or boost output voltage, depending on the mode the charger is. Both battery + * regulation voltage and boost output voltage are read from DT/ACPI during + * probe. + */ +static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + int ret; + + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_boost_voltage_values, + ARRAY_SIZE(rt9455_boost_voltage_values), + info->boost_voltage); + if (ret) { + dev_err(dev, "Failed to set boost output voltage value\n"); + return ret; + } + + return 0; +} + +/* + * Before setting the charger into charge mode, battery regulation voltage is + * set. This is needed because boost output voltage may differ from battery + * regulation voltage. F_VOREG bits represent either battery regulation voltage + * or boost output voltage, depending on the mode the charger is. Both battery + * regulation voltage and boost output voltage are read from DT/ACPI during + * probe. + */ +static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info) +{ + struct device *dev = &info->client->dev; + int ret; + + ret = rt9455_set_field_val(info, F_VOREG, + rt9455_voreg_values, + ARRAY_SIZE(rt9455_voreg_values), + info->voreg); + if (ret) { + dev_err(dev, "Failed to set VOREG value\n"); + return ret; + } + + return 0; +} + +static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info, + bool *_is_battery_absent, + bool *_alert_userspace) +{ + unsigned int irq1, mask1, mask2; + struct device *dev = &info->client->dev; + bool is_battery_absent = false; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); + if (ret) { + dev_err(dev, "Failed to read MASK1 register\n"); + return ret; + } + + if (irq1 & GET_MASK(F_TSDI)) { + dev_err(dev, "Thermal shutdown fault occurred\n"); + alert_userspace = true; + } + + if (irq1 & GET_MASK(F_VINOVPI)) { + dev_err(dev, "Overvoltage input occurred\n"); + alert_userspace = true; + } + + if (irq1 & GET_MASK(F_BATAB)) { + dev_err(dev, "Battery absence occurred\n"); + is_battery_absent = true; + alert_userspace = true; + + if ((mask1 & GET_MASK(F_BATABM)) == 0) { + ret = regmap_field_write(info->regmap_fields[F_BATABM], + 0x01); + if (ret) { + dev_err(dev, "Failed to mask BATAB interrupt\n"); + return ret; + } + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); + if (ret) { + dev_err(dev, "Failed to read MASK2 register\n"); + return ret; + } + + if (mask2 & GET_MASK(F_CHTERMIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); + return ret; + } + } + + if (mask2 & GET_MASK(F_CHRCHGIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHRCHGIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHRCHGI interrupt\n"); + return ret; + } + } + + /* + * When the battery is absent, max_charging_time_work is + * cancelled, since no charging is done. + */ + cancel_delayed_work_sync(&info->max_charging_time_work); + /* + * Since no interrupt is triggered when the battery is + * reconnected, max_charging_time_work is not rescheduled. + * Therefore, batt_presence_work is scheduled to check whether + * the battery is still absent or not. + */ + queue_delayed_work(system_power_efficient_wq, + &info->batt_presence_work, + RT9455_BATT_PRESENCE_DELAY * HZ); + } + + *_is_battery_absent = is_battery_absent; + + if (alert_userspace) + *_alert_userspace = alert_userspace; + + return 0; +} + +static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info, + bool is_battery_absent, + bool *_alert_userspace) +{ + unsigned int irq2, mask2; + struct device *dev = &info->client->dev; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2); + if (ret) { + dev_err(dev, "Failed to read IRQ2 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2); + if (ret) { + dev_err(dev, "Failed to read MASK2 register\n"); + return ret; + } + + if (irq2 & GET_MASK(F_CHRVPI)) { + dev_dbg(dev, "Charger fault occurred\n"); + alert_userspace = true; + /* + * CHRVPI bit is set in 2 cases: + * 1. when the power source is connected to the charger. + * 2. when the power source is disconnected from the charger. + * To identify the case, PWR_RDY bit is checked. Because + * PWR_RDY bit is set / cleared after CHRVPI interrupt is + * triggered, it is used delayed_work to later read PWR_RDY bit. + */ + queue_delayed_work(system_power_efficient_wq, + &info->pwr_rdy_work, + RT9455_PWR_RDY_DELAY * HZ); + } + if (irq2 & GET_MASK(F_CHBATOVI)) { + dev_err(dev, "Battery OVP occurred\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHTERMI)) { + dev_dbg(dev, "Charge terminated\n"); + if (!is_battery_absent) { + if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x01); + if (ret) { + dev_err(dev, "Failed to mask CHTERMI interrupt\n"); + return ret; + } + /* + * Update MASK2 value, since CHTERMIM bit is + * set. + */ + mask2 = mask2 | GET_MASK(F_CHTERMIM); + } + cancel_delayed_work_sync(&info->max_charging_time_work); + alert_userspace = true; + } + } + if (irq2 & GET_MASK(F_CHRCHGI)) { + dev_dbg(dev, "Recharge request\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_ENABLE); + if (ret) { + dev_err(dev, "Failed to enable charging\n"); + return ret; + } + if (mask2 & GET_MASK(F_CHTERMIM)) { + ret = regmap_field_write( + info->regmap_fields[F_CHTERMIM], 0x00); + if (ret) { + dev_err(dev, "Failed to unmask CHTERMI interrupt\n"); + return ret; + } + /* Update MASK2 value, since CHTERMIM bit is cleared. */ + mask2 = mask2 & ~GET_MASK(F_CHTERMIM); + } + if (!is_battery_absent) { + /* + * No need to check whether the charger is connected to + * power source when CHRCHGI is received, since CHRCHGI + * is not triggered if the charger is not connected to + * the power source. + */ + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + alert_userspace = true; + } + } + if (irq2 & GET_MASK(F_CH32MI)) { + dev_err(dev, "Charger fault. 32 mins timeout occurred\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHTREGI)) { + dev_warn(dev, + "Charger warning. Thermal regulation loop active\n"); + alert_userspace = true; + } + if (irq2 & GET_MASK(F_CHMIVRI)) { + dev_dbg(dev, + "Charger warning. Input voltage MIVR loop active\n"); + } + + if (alert_userspace) + *_alert_userspace = alert_userspace; + + return 0; +} + +static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info, + bool *_alert_userspace) +{ + unsigned int irq3, mask3; + struct device *dev = &info->client->dev; + bool alert_userspace = false; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3); + if (ret) { + dev_err(dev, "Failed to read IRQ3 register\n"); + return ret; + } + + ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3); + if (ret) { + dev_err(dev, "Failed to read MASK3 register\n"); + return ret; + } + + if (irq3 & GET_MASK(F_BSTBUSOVI)) { + dev_err(dev, "Boost fault. Overvoltage input occurred\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BSTOLI)) { + dev_err(dev, "Boost fault. Overload\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BSTLOWVI)) { + dev_err(dev, "Boost fault. Battery voltage too low\n"); + alert_userspace = true; + } + if (irq3 & GET_MASK(F_BST32SI)) { + dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n"); + alert_userspace = true; + } + + if (alert_userspace) { + dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n"); + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return ret; + } + *_alert_userspace = alert_userspace; + } + + return 0; +} + +static irqreturn_t rt9455_irq_handler_thread(int irq, void *data) +{ + struct rt9455_info *info = data; + struct device *dev; + bool alert_userspace = false; + bool is_battery_absent = false; + unsigned int status; + int ret; + + if (!info) + return IRQ_NONE; + + dev = &info->client->dev; + + if (irq != info->client->irq) { + dev_err(dev, "Interrupt is not for RT9455 charger\n"); + return IRQ_NONE; + } + + ret = regmap_field_read(info->regmap_fields[F_STAT], &status); + if (ret) { + dev_err(dev, "Failed to read STAT bits\n"); + return IRQ_HANDLED; + } + dev_dbg(dev, "Charger status is %d\n", status); + + /* + * Each function that processes an IRQ register receives as output + * parameter alert_userspace pointer. alert_userspace is set to true + * in such a function only if an interrupt has occurred in the + * respective interrupt register. This way, it is avoided the following + * case: interrupt occurs only in IRQ1 register, + * rt9455_irq_handler_check_irq1_register() function sets to true + * alert_userspace, but rt9455_irq_handler_check_irq2_register() + * and rt9455_irq_handler_check_irq3_register() functions set to false + * alert_userspace and power_supply_changed() is never called. + */ + ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent, + &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ1 register\n"); + return IRQ_HANDLED; + } + + ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent, + &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ2 register\n"); + return IRQ_HANDLED; + } + + ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace); + if (ret) { + dev_err(dev, "Failed to handle IRQ3 register\n"); + return IRQ_HANDLED; + } + + if (alert_userspace) { + /* + * Sometimes, an interrupt occurs while rt9455_probe() function + * is executing and power_supply_register() is not yet called. + * Do not call power_supply_charged() in this case. + */ + if (info->charger) + power_supply_changed(info->charger); + } + + return IRQ_HANDLED; +} + +static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg, + u32 *ieoc_percentage, + u32 *mivr, u32 *iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (!dev->of_node && !ACPI_HANDLE(dev)) { + dev_err(dev, "No support for either device tree or ACPI\n"); + return -EINVAL; + } + /* + * ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory + * parameters. + */ + ret = device_property_read_u32(dev, "richtek,output-charge-current", + ichrg); + if (ret) { + dev_err(dev, "Error: missing \"output-charge-current\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage", + ieoc_percentage); + if (ret) { + dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, + "richtek,battery-regulation-voltage", + &info->voreg); + if (ret) { + dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n"); + return ret; + } + + ret = device_property_read_u32(dev, "richtek,boost-output-voltage", + &info->boost_voltage); + if (ret) { + dev_err(dev, "Error: missing \"boost-output-voltage\" property\n"); + return ret; + } + + /* + * MIVR and IAICR are optional parameters. Do not return error if one of + * them is not present in ACPI table or device tree specification. + */ + device_property_read_u32(dev, "richtek,min-input-voltage-regulation", + mivr); + device_property_read_u32(dev, "richtek,avg-input-current-regulation", + iaicr); + + return 0; +} + +static int rt9455_usb_event_none(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_NONE, this means the consumer device powered by the + * charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n"); + if (iaicr != RT9455_IAICR_100MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_100MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_vbus(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_VBUS, this means the consumer device powered by the + * charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n"); + if (iaicr != RT9455_IAICR_500MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_500MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_id(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_CHARGE_MODE) { + ret = rt9455_set_boost_voltage_before_boost_mode(info); + if (ret) { + dev_err(dev, "Failed to set boost output voltage before entering boost mode\n"); + return ret; + } + /* + * If the charger is in charge mode, and it has received + * USB_EVENT_ID, this means a consumer device is connected and + * it should be powered by the charger. + * In this case, the charger goes into boost mode. + */ + dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_BOOST_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in boost mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n"); + if (iaicr != RT9455_IAICR_100MA) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_100MA); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event_charger(struct rt9455_info *info, + u8 opa_mode, u8 iaicr) +{ + struct device *dev = &info->client->dev; + int ret; + + if (opa_mode == RT9455_BOOST_MODE) { + ret = rt9455_set_voreg_before_charge_mode(info); + if (ret) { + dev_err(dev, "Failed to set VOREG before entering charge mode\n"); + return ret; + } + /* + * If the charger is in boost mode, and it has received + * USB_EVENT_CHARGER, this means the consumer device powered by + * the charger is not connected anymore. + * In this case, the charger goes into charge mode. + */ + dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n"); + ret = regmap_field_write(info->regmap_fields[F_OPA_MODE], + RT9455_CHARGE_MODE); + if (ret) { + dev_err(dev, "Failed to set charger in charge mode\n"); + return NOTIFY_DONE; + } + } + + dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n"); + if (iaicr != RT9455_IAICR_NO_LIMIT) { + ret = regmap_field_write(info->regmap_fields[F_IAICR], + RT9455_IAICR_NO_LIMIT); + if (ret) { + dev_err(dev, "Failed to set IAICR value\n"); + return NOTIFY_DONE; + } + } + + return NOTIFY_OK; +} + +static int rt9455_usb_event(struct notifier_block *nb, + unsigned long event, void *power) +{ + struct rt9455_info *info = container_of(nb, struct rt9455_info, nb); + struct device *dev = &info->client->dev; + unsigned int opa_mode, iaicr; + int ret; + + if (!info) + return NOTIFY_DONE; + + /* + * Determine whether the charger is in charge mode + * or in boost mode. + */ + ret = regmap_field_read(info->regmap_fields[F_OPA_MODE], + &opa_mode); + if (ret) { + dev_err(dev, "Failed to read OPA_MODE value\n"); + return NOTIFY_DONE; + } + + ret = regmap_field_read(info->regmap_fields[F_IAICR], + &iaicr); + if (ret) { + dev_err(dev, "Failed to read IAICR value\n"); + return NOTIFY_DONE; + } + + dev_dbg(dev, "Received USB event %lu\n", event); + switch (event) { + case USB_EVENT_NONE: + return rt9455_usb_event_none(info, opa_mode, iaicr); + case USB_EVENT_VBUS: + return rt9455_usb_event_vbus(info, opa_mode, iaicr); + case USB_EVENT_ID: + return rt9455_usb_event_id(info, opa_mode, iaicr); + case USB_EVENT_CHARGER: + return rt9455_usb_event_charger(info, opa_mode, iaicr); + default: + dev_err(dev, "Unknown USB event\n"); + } + return NOTIFY_DONE; +} + +static void rt9455_pwr_rdy_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + pwr_rdy_work.work); + struct device *dev = &info->client->dev; + unsigned int pwr_rdy; + int ret; + + ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy); + if (ret) { + dev_err(dev, "Failed to read PWR_RDY bit\n"); + return; + } + switch (pwr_rdy) { + case RT9455_PWR_FAULT: + dev_dbg(dev, "Charger disconnected from power source\n"); + cancel_delayed_work_sync(&info->max_charging_time_work); + break; + case RT9455_PWR_GOOD: + dev_dbg(dev, "Charger connected to power source\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_ENABLE); + if (ret) { + dev_err(dev, "Failed to enable charging\n"); + return; + } + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + break; + } +} + +static void rt9455_max_charging_time_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + max_charging_time_work.work); + struct device *dev = &info->client->dev; + int ret; + + dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n"); + ret = regmap_field_write(info->regmap_fields[F_CHG_EN], + RT9455_CHARGE_DISABLE); + if (ret) + dev_err(dev, "Failed to disable charging\n"); +} + +static void rt9455_batt_presence_work_callback(struct work_struct *work) +{ + struct rt9455_info *info = container_of(work, struct rt9455_info, + batt_presence_work.work); + struct device *dev = &info->client->dev; + unsigned int irq1, mask1; + int ret; + + ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1); + if (ret) { + dev_err(dev, "Failed to read IRQ1 register\n"); + return; + } + + /* + * If the battery is still absent, batt_presence_work is rescheduled. + * Otherwise, max_charging_time is scheduled. + */ + if (irq1 & GET_MASK(F_BATAB)) { + queue_delayed_work(system_power_efficient_wq, + &info->batt_presence_work, + RT9455_BATT_PRESENCE_DELAY * HZ); + } else { + queue_delayed_work(system_power_efficient_wq, + &info->max_charging_time_work, + RT9455_MAX_CHARGING_TIME * HZ); + + ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1); + if (ret) { + dev_err(dev, "Failed to read MASK1 register\n"); + return; + } + + if (mask1 & GET_MASK(F_BATABM)) { + ret = regmap_field_write(info->regmap_fields[F_BATABM], + 0x00); + if (ret) + dev_err(dev, "Failed to unmask BATAB interrupt\n"); + } + } +} + +static const struct power_supply_desc rt9455_charger_desc = { + .name = RT9455_DRIVER_NAME, + .type = POWER_SUPPLY_TYPE_USB, + .properties = rt9455_charger_properties, + .num_properties = ARRAY_SIZE(rt9455_charger_properties), + .get_property = rt9455_charger_get_property, +}; + +static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT9455_REG_DEV_ID: + case RT9455_REG_IRQ1: + case RT9455_REG_IRQ2: + case RT9455_REG_IRQ3: + return false; + default: + return true; + } +} + +static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg) +{ + switch (reg) { + case RT9455_REG_DEV_ID: + case RT9455_REG_CTRL5: + case RT9455_REG_CTRL6: + return false; + default: + return true; + } +} + +static const struct regmap_config rt9455_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .writeable_reg = rt9455_is_writeable_reg, + .volatile_reg = rt9455_is_volatile_reg, + .max_register = RT9455_REG_MASK3, + .cache_type = REGCACHE_RBTREE, +}; + +static int rt9455_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct device *dev = &client->dev; + struct rt9455_info *info; + struct power_supply_config rt9455_charger_config = {}; + /* + * Mandatory device-specific data values. Also, VOREG and boost output + * voltage are mandatory values, but they are stored in rt9455_info + * structure. + */ + u32 ichrg, ieoc_percentage; + /* Optional device-specific data values. */ + u32 mivr = -1, iaicr = -1; + int i, ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->client = client; + i2c_set_clientdata(client, info); + + info->regmap = devm_regmap_init_i2c(client, + &rt9455_regmap_config); + if (IS_ERR(info->regmap)) { + dev_err(dev, "Failed to initialize register map\n"); + return -EINVAL; + } + + for (i = 0; i < F_MAX_FIELDS; i++) { + info->regmap_fields[i] = + devm_regmap_field_alloc(dev, info->regmap, + rt9455_reg_fields[i]); + if (IS_ERR(info->regmap_fields[i])) { + dev_err(dev, + "Failed to allocate regmap field = %d\n", i); + return PTR_ERR(info->regmap_fields[i]); + } + } + + ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage, + &mivr, &iaicr); + if (ret) { + dev_err(dev, "Failed to discover charger\n"); + return ret; + } + +#if IS_ENABLED(CONFIG_USB_PHY) + info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2); + if (IS_ERR(info->usb_phy)) { + dev_err(dev, "Failed to get USB transceiver\n"); + } else { + info->nb.notifier_call = rt9455_usb_event; + ret = usb_register_notifier(info->usb_phy, &info->nb); + if (ret) { + dev_err(dev, "Failed to register USB notifier\n"); + /* + * If usb_register_notifier() fails, set notifier_call + * to NULL, to avoid calling usb_unregister_notifier(). + */ + info->nb.notifier_call = NULL; + } + } +#endif + + INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback); + INIT_DEFERRABLE_WORK(&info->max_charging_time_work, + rt9455_max_charging_time_work_callback); + INIT_DEFERRABLE_WORK(&info->batt_presence_work, + rt9455_batt_presence_work_callback); + + rt9455_charger_config.of_node = dev->of_node; + rt9455_charger_config.drv_data = info; + rt9455_charger_config.supplied_to = rt9455_charger_supplied_to; + rt9455_charger_config.num_supplicants = + ARRAY_SIZE(rt9455_charger_supplied_to); + ret = devm_request_threaded_irq(dev, client->irq, NULL, + rt9455_irq_handler_thread, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + RT9455_DRIVER_NAME, info); + if (ret) { + dev_err(dev, "Failed to register IRQ handler\n"); + goto put_usb_notifier; + } + + ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr); + if (ret) { + dev_err(dev, "Failed to set charger to its default values\n"); + goto put_usb_notifier; + } + + info->charger = devm_power_supply_register(dev, &rt9455_charger_desc, + &rt9455_charger_config); + if (IS_ERR(info->charger)) { + dev_err(dev, "Failed to register charger\n"); + ret = PTR_ERR(info->charger); + goto put_usb_notifier; + } + + return 0; + +put_usb_notifier: +#if IS_ENABLED(CONFIG_USB_PHY) + if (info->nb.notifier_call) { + usb_unregister_notifier(info->usb_phy, &info->nb); + info->nb.notifier_call = NULL; + } +#endif + return ret; +} + +static int rt9455_remove(struct i2c_client *client) +{ + int ret; + struct rt9455_info *info = i2c_get_clientdata(client); + + ret = rt9455_register_reset(info); + if (ret) + dev_err(&info->client->dev, "Failed to set charger to its default values\n"); + +#if IS_ENABLED(CONFIG_USB_PHY) + if (info->nb.notifier_call) + usb_unregister_notifier(info->usb_phy, &info->nb); +#endif + + cancel_delayed_work_sync(&info->pwr_rdy_work); + cancel_delayed_work_sync(&info->max_charging_time_work); + cancel_delayed_work_sync(&info->batt_presence_work); + + return ret; +} + +static const struct i2c_device_id rt9455_i2c_id_table[] = { + { RT9455_DRIVER_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table); + +static const struct of_device_id rt9455_of_match[] = { + { .compatible = "richtek,rt9455", }, + { }, +}; +MODULE_DEVICE_TABLE(of, rt9455_of_match); + +static const struct acpi_device_id rt9455_i2c_acpi_match[] = { + { "RT945500", 0 }, + { } +}; +MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match); + +static struct i2c_driver rt9455_driver = { + .probe = rt9455_probe, + .remove = rt9455_remove, + .id_table = rt9455_i2c_id_table, + .driver = { + .name = RT9455_DRIVER_NAME, + .of_match_table = of_match_ptr(rt9455_of_match), + .acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match), + }, +}; +module_i2c_driver(rt9455_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Anda-Maria Nicolae "); +MODULE_ALIAS("i2c:rt9455-charger"); +MODULE_DESCRIPTION("Richtek RT9455 Charger Driver"); -- cgit v1.2.3-58-ga151 From 23fa43a28d00bca79b28e145a34c0c7dcafd72bc Mon Sep 17 00:00:00 2001 From: Laurentiu Palcu Date: Tue, 2 Jun 2015 13:36:51 +0300 Subject: power_supply: bq25890: make chip_id int Smatch static checker correctly detected an impossible condition because chip_id was declared as u8, instead of int: drivers/power/bq25890_charger.c:843 bq25890_probe() warn: impossible condition '(bq->chip_id < 0) => (0-255 < 0)' Also, while at it, fix the return value too. Signed-off-by: Laurentiu Palcu Reported-by: Dan Carpenter Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/bq25890_charger.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c index c7b4903acd75..16b7c7bc987d 100644 --- a/drivers/power/bq25890_charger.c +++ b/drivers/power/bq25890_charger.c @@ -99,7 +99,7 @@ struct bq25890_device { struct regmap *rmap; struct regmap_field *rmap_fields[F_MAX_FIELDS]; - u8 chip_id; + int chip_id; struct bq25890_init_data init_data; struct bq25890_state state; @@ -842,7 +842,7 @@ static int bq25890_probe(struct i2c_client *client, bq->chip_id = bq25890_field_read(bq, F_PN); if (bq->chip_id < 0) { dev_err(dev, "Cannot read chip ID.\n"); - return ret; + return bq->chip_id; } if (bq->chip_id != BQ25890_ID) { -- cgit v1.2.3-58-ga151 From bcd56fe1aa97117f8cfad68280d9aa6d403fd815 Mon Sep 17 00:00:00 2001 From: Heiko Stübner Date: Tue, 2 Jun 2015 16:48:50 +0200 Subject: power: reset: gpio-restart: increase priority slightly gpio-restart uses a priority of 128 and currently most soc-level restart mechanisms use the same - with some exceptions even using 192. But while the soc-level restarts are provided by the soc itself, gpio-restarts will most of the time be board-specfic and be used when some special board condition makes the soc-level restart only a second choice. The problem at hand manifested itself on the rk3288-veyron devices. While the soc-level restart can sucessfully restart all other rockchip boards I have, the veyron devices use an external restart mechanism that seems to not only reset the soc but also some external needed components. With both restart handlers having priority 128 in my tests the soc-specific variant took precedent in all cases. While it could restart the soc sucessfully in all cases, firmware then got an issue when talking to an external component, resulting in the device being put into recovery mode. So, give the board-specific restart handler a slight push and move it to priority 129 to make it more important than the generic soc-specific restart-handler. Signed-off-by: Heiko Stuebner Reviewed-by: Guenter Roeck Signed-off-by: Sebastian Reichel --- drivers/power/reset/gpio-restart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c index edb327efee8b..829b45f42021 100644 --- a/drivers/power/reset/gpio-restart.c +++ b/drivers/power/reset/gpio-restart.c @@ -78,7 +78,7 @@ static int gpio_restart_probe(struct platform_device *pdev) } gpio_restart->restart_handler.notifier_call = gpio_restart_notify; - gpio_restart->restart_handler.priority = 128; + gpio_restart->restart_handler.priority = 129; gpio_restart->active_delay_ms = 100; gpio_restart->inactive_delay_ms = 100; gpio_restart->wait_delay_ms = 3000; -- cgit v1.2.3-58-ga151 From fb323eccbcaff06c1ed21ab989198eb40df6e846 Mon Sep 17 00:00:00 2001 From: Anda-Maria Nicolae Date: Wed, 3 Jun 2015 11:47:03 +0300 Subject: power_supply: rt9455_charger: Check if CONFIG_USB_PHY is enabled If CONFIG_USB_PHY is not enabled, struct notifier_block is not defined and compilation fails. Therefore, the functions that process USB event notifications are defined only if CONFIG_USB_PHY is enabled. There is no need to define these functions if CONFIG_USB_PHY is not enabled, since no USB notifications are received in this case. Also, since rt9455_set_boost_voltage_before_boost_mode() function is called only if USB_EVENT_ID notification is received, this function should also be defined only if CONFIG_USB_PHY is enabled. Signed-off-by: Anda-Maria Nicolae Signed-off-by: Sebastian Reichel --- drivers/power/rt9455_charger.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers') diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c index 7c0c1ffa84ec..de86913037db 100644 --- a/drivers/power/rt9455_charger.c +++ b/drivers/power/rt9455_charger.c @@ -804,6 +804,7 @@ static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg, return 0; } +#if IS_ENABLED(CONFIG_USB_PHY) /* * Before setting the charger into boost mode, boost output voltage is * set. This is needed because boost output voltage may differ from battery @@ -828,6 +829,7 @@ static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info) return 0; } +#endif /* * Before setting the charger into charge mode, battery regulation voltage is @@ -1241,6 +1243,7 @@ static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg, return 0; } +#if IS_ENABLED(CONFIG_USB_PHY) static int rt9455_usb_event_none(struct rt9455_info *info, u8 opa_mode, u8 iaicr) { @@ -1445,6 +1448,7 @@ static int rt9455_usb_event(struct notifier_block *nb, } return NOTIFY_DONE; } +#endif static void rt9455_pwr_rdy_work_callback(struct work_struct *work) { -- cgit v1.2.3-58-ga151 From 5c6e3a97e969e978368df83239583771c936efea Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 8 Jun 2015 10:09:48 +0900 Subject: power_supply: sysfs: Bring back write to writeable properties The fix for NULL pointer exception related to calling uevent for not finished probe caused to set all writeable properties as non-writeable. This was caused by checking if property is writeable before the initial increase of power supply usage counter and in the same time using wrapper over property_is_writeable(). The wrapper returns ENODEV if the usage counter is still 0. The call trace looked like: device probe: power_supply_register() use_cnt = 0; device_add() create sysfs entries power_supply_attr_is_visible() power_supply_property_is_writeable() if (use_cnt == 0) return -ENODEV; use_cnt++; Replace the usage of wrapper with direct call to property_is_writeable() from driver. This should be safe call during device probe because implementations of this callback just return 0/1 for different properties and they do not access any of the driver's internal data. Fixes: 8e59c7f23410 ("power_supply: Fix NULL pointer dereference during bq27x00_battery probe") Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_sysfs.c | 2 +- include/linux/power_supply.h | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) (limited to 'drivers') diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index af026806cba5..ed2d7fd0c734 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -223,7 +223,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj, if (property == attrno) { if (psy->desc->property_is_writeable && - power_supply_property_is_writeable(psy, property) > 0) + psy->desc->property_is_writeable(psy, property) > 0) mode |= S_IWUSR; return mode; diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index a80f1fd01ddb..0395bcb18ddb 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -206,6 +206,11 @@ struct power_supply_desc { int (*set_property)(struct power_supply *psy, enum power_supply_property psp, const union power_supply_propval *val); + /* + * property_is_writeable() will be called during registration + * of power supply. If this happens during device probe then it must + * not access internal data of device (because probe did not end). + */ int (*property_is_writeable)(struct power_supply *psy, enum power_supply_property psp); void (*external_power_changed)(struct power_supply *psy); -- cgit v1.2.3-58-ga151 From a6e6b63ee2798f18e0e786feb407cc7846077ea3 Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Mon, 8 Jun 2015 10:11:38 +0900 Subject: power_supply: max17042: Add OF support for setting thresholds The commit edd4ab055931 ("power: max17042_battery: add HEALTH and TEMP_* properties support") added support for setting voltage and temperature thresholds with platform data. For DeviceTree default of 0 was always used. This caused reporting battery health always as over voltage or over heated. Signed-off-by: Krzysztof Kozlowski Fixes: edd4ab055931 ("power: max17042_battery: add HEALTH and TEMP_* properties support") Signed-off-by: Sebastian Reichel --- .../devicetree/bindings/power_supply/max17042_battery.txt | 13 +++++++++++++ drivers/power/max17042_battery.c | 9 +++++++++ 2 files changed, 22 insertions(+) (limited to 'drivers') diff --git a/Documentation/devicetree/bindings/power_supply/max17042_battery.txt b/Documentation/devicetree/bindings/power_supply/max17042_battery.txt index 5bc9b685cf8a..3f3894aaeebc 100644 --- a/Documentation/devicetree/bindings/power_supply/max17042_battery.txt +++ b/Documentation/devicetree/bindings/power_supply/max17042_battery.txt @@ -9,10 +9,23 @@ Optional properties : (datasheet-recommended value is 10000). Defining this property enables current-sense functionality. +Optional threshold properties : + If skipped the condition won't be reported. + - maxim,cold-temp : Temperature threshold to report battery + as cold (in tenths of degree Celsius). + - maxim,over-heat-temp : Temperature threshold to report battery + as over heated (in tenths of degree Celsius). + - maxim,dead-volt : Voltage threshold to report battery + as dead (in mV). + - maxim,over-volt : Voltage threshold to report battery + as over voltage (in mV). + Example: battery-charger@36 { compatible = "maxim,max17042"; reg = <0x36>; maxim,rsns-microohm = <10000>; + maxim,over-heat-temp = <600>; + maxim,over-volt = <4300>; }; diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 748d9762e89b..e89255764745 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -809,6 +809,15 @@ max17042_get_pdata(struct device *dev) pdata->enable_current_sense = true; } + if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min)) + pdata->temp_min = INT_MIN; + if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max)) + pdata->temp_max = INT_MAX; + if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin)) + pdata->vmin = INT_MIN; + if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax)) + pdata->vmax = INT_MAX; + return pdata; } #else -- cgit v1.2.3-58-ga151 From fe27e1dfe9962b07215ee01445926306ddbb7c25 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Tue, 9 Jun 2015 23:37:56 +0200 Subject: power: Add devm_power_supply_get_by_phandle() helper function This commit adds a resource-managed version of the power_supply_get_by_phandle() function. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_core.c | 39 +++++++++++++++++++++++++++++++++++++++ include/linux/power_supply.h | 5 +++++ 2 files changed, 44 insertions(+) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 4bc0c7f459a5..a44d4554af4f 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -446,6 +446,45 @@ struct power_supply *power_supply_get_by_phandle(struct device_node *np, return psy; } EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); + +static void devm_power_supply_put(struct device *dev, void *res) +{ + struct power_supply **psy = res; + + power_supply_put(*psy); +} + +/** + * devm_power_supply_get_by_phandle() - Resource managed version of + * power_supply_get_by_phandle() + * @dev: Pointer to device holding phandle property + * @phandle_name: Name of property holding a power supply phandle + * + * Return: On success returns a reference to a power supply with + * matching name equals to value under @property, NULL or ERR_PTR otherwise. + */ +struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, + const char *property) +{ + struct power_supply **ptr, *psy; + + if (!dev->of_node) + return ERR_PTR(-ENODEV); + + ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL); + if (!ptr) + return ERR_PTR(-ENOMEM); + + psy = power_supply_get_by_phandle(dev->of_node, property); + if (IS_ERR_OR_NULL(psy)) { + devres_free(ptr); + } else { + *ptr = psy; + devres_add(dev, ptr); + } + return psy; +} +EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); #endif /* CONFIG_OF */ int power_supply_get_property(struct power_supply *psy, diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 0395bcb18ddb..ef9f1592185d 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -292,10 +292,15 @@ extern void power_supply_put(struct power_supply *psy); #ifdef CONFIG_OF extern struct power_supply *power_supply_get_by_phandle(struct device_node *np, const char *property); +extern struct power_supply *devm_power_supply_get_by_phandle( + struct device *dev, const char *property); #else /* !CONFIG_OF */ static inline struct power_supply * power_supply_get_by_phandle(struct device_node *np, const char *property) { return NULL; } +static inline struct power_supply * +devm_power_supply_get_by_phandle(struct device *dev, const char *property) +{ return NULL; } #endif /* CONFIG_OF */ extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); -- cgit v1.2.3-58-ga151 From f4ed950a63fc9590287f0f4fb0698e530b42b8a6 Mon Sep 17 00:00:00 2001 From: Frans Klaver Date: Wed, 10 Jun 2015 14:16:56 +0200 Subject: sbs-battery: add option to always register battery Commit a22b41a31e53 ("sbs-battery: Probe should try talking to the device") introduced a step in probing the SBS battery, that tries to talk to the device before actually registering it, saying: this driver doesn't actually try talking to the device at probe time, so if it's incorrectly configured in the device tree or platform data (or if the battery has been removed from the system), then probe will succeed and every access will sit there and time out. The end result is a possibly laggy system that thinks it has a battery but can never read status, which isn't very useful. Which is of course reasonable. However, it is also very well possible for a device to boot up on wall-power and be connected to a battery later on. This is a scenario that the driver supported before said patch was applied, and even easily achieved by booting up with the battery attached and removing it later on. sbs-battery's 'present' sysfs file can be used to determine if the device is available or not. So with automated device detection lacking for now, in some cases it is possible that one wants to register a battery, even if none are attached at the moment. To facilitate this, add a module parameter that can be used to configure forced loading module loading time. If set, the battery will always be registered without checking the sanity of the connection. Signed-off-by: Frans Klaver Signed-off-by: Sebastian Reichel --- drivers/power/sbs-battery.c | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index de1178659d4b..d6226d68b574 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -170,6 +171,7 @@ struct sbs_info { static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1]; +static bool force_load; static int sbs_read_word_data(struct i2c_client *client, u8 address) { @@ -885,14 +887,17 @@ static int sbs_probe(struct i2c_client *client, skip_gpio: /* - * Before we register, we need to make sure we can actually talk + * Before we register, we might need to make sure we can actually talk * to the battery. */ - rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); - if (rc < 0) { - dev_err(&client->dev, "%s: Failed to get device status\n", - __func__); - goto exit_psupply; + if (!force_load) { + rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); + + if (rc < 0) { + dev_err(&client->dev, "%s: Failed to get device status\n", + __func__); + goto exit_psupply; + } } chip->power_supply = power_supply_register(&client->dev, sbs_desc, @@ -991,3 +996,7 @@ module_i2c_driver(sbs_battery_driver); MODULE_DESCRIPTION("SBS battery monitor driver"); MODULE_LICENSE("GPL"); + +module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH); +MODULE_PARM_DESC(force_load, + "Attempt to load the driver even if no battery is connected"); -- cgit v1.2.3-58-ga151 From 0007fa362a88f09a1b3e073434c83d35cc1e1e1c Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Fri, 12 Jun 2015 09:19:34 +0200 Subject: power_supply: bq25890: use flags argument of devm_gpiod_get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 39b2bbe3d715 (gpio: add flags argument to gpiod_get*() functions) which appeared in v3.17-rc1, the gpiod_get* functions take an additional parameter that allows to specify direction and initial value for output. Simplify driver accordingly. Furthermore this is one caller less that stops us making the flags argument to gpiod_get*() mandatory. Signed-off-by: Uwe Kleine-König Signed-off-by: Sebastian Reichel --- drivers/power/bq25890_charger.c | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c index 16b7c7bc987d..f993a55cde20 100644 --- a/drivers/power/bq25890_charger.c +++ b/drivers/power/bq25890_charger.c @@ -721,19 +721,14 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val, static int bq25890_irq_probe(struct bq25890_device *bq) { - int ret; struct gpio_desc *irq; - irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0); + irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN); if (IS_ERR(irq)) { dev_err(bq->dev, "Could not probe irq pin.\n"); return PTR_ERR(irq); } - ret = gpiod_direction_input(irq); - if (ret < 0) - return ret; - return gpiod_to_irq(irq); } -- cgit v1.2.3-58-ga151 From e31cd7824d054d30ffc9f45550343123bc437e17 Mon Sep 17 00:00:00 2001 From: Uwe Kleine-König Date: Fri, 12 Jun 2015 10:54:04 +0200 Subject: power_supply: bq24257: use flags argument of devm_gpiod_get MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since 39b2bbe3d715 (gpio: add flags argument to gpiod_get*() functions) which appeared in v3.17-rc1, the gpiod_get* functions take an additional parameter that allows to specify direction and initial value for output. Simplify driver accordingly. Furthermore this is one caller less that stops us making the flags argument to gpiod_get*() mandatory. Signed-off-by: Uwe Kleine-König Signed-off-by: Sebastian Reichel --- drivers/power/bq24257_charger.c | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) (limited to 'drivers') diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c index ce7f5bbfd5e3..5859bc7c1616 100644 --- a/drivers/power/bq24257_charger.c +++ b/drivers/power/bq24257_charger.c @@ -608,31 +608,26 @@ static int bq24257_power_supply_init(struct bq24257_device *bq) static int bq24257_irq_probe(struct bq24257_device *bq) { - int ret; struct gpio_desc *stat_irq; - stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0); + stat_irq = devm_gpiod_get_index(bq->dev, BQ24257_STAT_IRQ, 0, GPIOD_IN); if (IS_ERR(stat_irq)) { dev_err(bq->dev, "could not probe stat_irq pin\n"); return PTR_ERR(stat_irq); } - ret = gpiod_direction_input(stat_irq); - if (ret < 0) - return ret; - return gpiod_to_irq(stat_irq); } static int bq24257_pg_gpio_probe(struct bq24257_device *bq) { - bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0); + bq->pg = devm_gpiod_get_index(bq->dev, BQ24257_PG_GPIO, 0, GPIOD_IN); if (IS_ERR(bq->pg)) { dev_err(bq->dev, "could not probe PG pin\n"); return PTR_ERR(bq->pg); } - return gpiod_direction_input(bq->pg); + return 0; } static int bq24257_fw_probe(struct bq24257_device *bq) -- cgit v1.2.3-58-ga151 From 3d7f1800af0e4a781195770644fc1297217ba71d Mon Sep 17 00:00:00 2001 From: Anda-Maria Nicolae Date: Fri, 12 Jun 2015 15:25:51 +0300 Subject: power_supply: rt9455_charger: Fix error reported by static analysis tool The result of container_of macro cannot be NULL, so there is no need to check whether info is NULL. Signed-off-by: Anda-Maria Nicolae Signed-off-by: Sebastian Reichel --- drivers/power/rt9455_charger.c | 3 --- 1 file changed, 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c index de86913037db..08baac6e3ada 100644 --- a/drivers/power/rt9455_charger.c +++ b/drivers/power/rt9455_charger.c @@ -1412,9 +1412,6 @@ static int rt9455_usb_event(struct notifier_block *nb, unsigned int opa_mode, iaicr; int ret; - if (!info) - return NOTIFY_DONE; - /* * Determine whether the charger is in charge mode * or in boost mode. -- cgit v1.2.3-58-ga151 From 85a5965e1dc5473fd2ae5ff0cb071cfc597c3451 Mon Sep 17 00:00:00 2001 From: Takeshi Yoshimura Date: Sun, 14 Jun 2015 20:55:18 +0900 Subject: wm831x_power: Fix off-by-one at free_irq() An error handling in wm831x_power_probe() mistakenly frees a failed-to- request irq as well as other irqs. I added missing decrement of the loop counter. Signed-off-by: Takeshi Yoshimura Acked-by: Charles Keepax Signed-off-by: Sebastian Reichel --- drivers/power/wm831x_power.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers') diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index 0161bdabd5a3..db11ae6599f3 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -609,6 +609,7 @@ static int wm831x_power_probe(struct platform_device *pdev) return ret; err_bat_irq: + --i; for (; i >= 0; i--) { irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); free_irq(irq, power); -- cgit v1.2.3-58-ga151 From 43df61051ab9af985ec439ee9be9c299e7de34e8 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Thu, 18 Jun 2015 12:35:26 -0700 Subject: power_supply: Correct kerneldoc copy paste errors Signed-off-by: Bjorn Andersson Reviewed-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel --- drivers/power/power_supply_core.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers') diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index a44d4554af4f..869284c2e1e8 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -824,7 +824,7 @@ struct power_supply *__must_check power_supply_register(struct device *parent, EXPORT_SYMBOL_GPL(power_supply_register); /** - * power_supply_register() - Register new non-waking-source power supply + * power_supply_register_no_ws() - Register new non-waking-source power supply * @parent: Device to be a parent of power supply's device, usually * the device which probe function calls this * @desc: Description of power supply, must be valid through whole @@ -854,7 +854,7 @@ static void devm_power_supply_release(struct device *dev, void *res) } /** - * power_supply_register() - Register managed power supply + * devm_power_supply_register() - Register managed power supply * @parent: Device to be a parent of power supply's device, usually * the device which probe function calls this * @desc: Description of power supply, must be valid through whole @@ -890,7 +890,7 @@ devm_power_supply_register(struct device *parent, EXPORT_SYMBOL_GPL(devm_power_supply_register); /** - * power_supply_register() - Register managed non-waking-source power supply + * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply * @parent: Device to be a parent of power supply's device, usually * the device which probe function calls this * @desc: Description of power supply, must be valid through whole -- cgit v1.2.3-58-ga151