diff options
author | Linus Torvalds <torvalds@linux-foundation.org> | 2015-06-23 16:10:27 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2015-06-23 16:10:27 -0700 |
commit | 36a1624d8844b6c165daf61649e6b68c02d0835f (patch) | |
tree | 70159ac0dfd7d298f2c7abc615a9f5c9f9188b71 /drivers/power | |
parent | 5262f25f0983ff6f2c21e3dadcfd0c809e5aa286 (diff) | |
parent | 43df61051ab9af985ec439ee9be9c299e7de34e8 (diff) |
Merge tag 'for-4.2' of git://git.infradead.org/battery-2.6
Pull power supply and reset updates from Sebastian Reichel:
- new charger drivers: BQ24257, BQ25890, AXP288, RT9455
- MAX17042 battery: add health & temperature support
- BQ2415x charger: add ACPI support
- misc fixes and cleanups
* tag 'for-4.2' of git://git.infradead.org/battery-2.6: (32 commits)
power_supply: Correct kerneldoc copy paste errors
wm831x_power: Fix off-by-one at free_irq()
power_supply: rt9455_charger: Fix error reported by static analysis tool
power_supply: bq24257: use flags argument of devm_gpiod_get
power_supply: bq25890: use flags argument of devm_gpiod_get
sbs-battery: add option to always register battery
power: Add devm_power_supply_get_by_phandle() helper function
power_supply: max17042: Add OF support for setting thresholds
power_supply: sysfs: Bring back write to writeable properties
power_supply: rt9455_charger: Check if CONFIG_USB_PHY is enabled
power: reset: gpio-restart: increase priority slightly
power_supply: bq25890: make chip_id int
power_supply: Add support for Richtek RT9455 battery charger
Documentation: devicetree: Add Richtek RT9455 bindings
of: Add vendor prefix for Richtek Technology Corporation
power_supply: 88pm860x_charger: Do not call free_irq() twice
power: bq24190_charger: Change first_time flag reset condition
power: axp288_charger: axp288 charger driver
power: max17042_battery: add HEALTH and TEMP_* properties support
power_supply: Add support for TI BQ25890 charger chip
...
Diffstat (limited to 'drivers/power')
-rw-r--r-- | drivers/power/88pm860x_charger.c | 1 | ||||
-rw-r--r-- | drivers/power/Kconfig | 28 | ||||
-rw-r--r-- | drivers/power/Makefile | 4 | ||||
-rw-r--r-- | drivers/power/axp288_charger.c | 941 | ||||
-rw-r--r-- | drivers/power/axp288_fuel_gauge.c | 2 | ||||
-rw-r--r-- | drivers/power/bq2415x_charger.c | 94 | ||||
-rw-r--r-- | drivers/power/bq24190_charger.c | 11 | ||||
-rw-r--r-- | drivers/power/bq24257_charger.c | 858 | ||||
-rw-r--r-- | drivers/power/bq25890_charger.c | 994 | ||||
-rw-r--r-- | drivers/power/charger-manager.c | 3 | ||||
-rw-r--r-- | drivers/power/max17042_battery.c | 199 | ||||
-rw-r--r-- | drivers/power/power_supply_core.c | 106 | ||||
-rw-r--r-- | drivers/power/power_supply_leds.c | 4 | ||||
-rw-r--r-- | drivers/power/power_supply_sysfs.c | 4 | ||||
-rw-r--r-- | drivers/power/reset/at91-reset.c | 2 | ||||
-rw-r--r-- | drivers/power/reset/gpio-poweroff.c | 25 | ||||
-rw-r--r-- | drivers/power/reset/gpio-restart.c | 2 | ||||
-rw-r--r-- | drivers/power/reset/ltc2952-poweroff.c | 7 | ||||
-rw-r--r-- | drivers/power/rt9455_charger.c | 1752 | ||||
-rw-r--r-- | drivers/power/sbs-battery.c | 21 | ||||
-rw-r--r-- | drivers/power/wm831x_power.c | 1 |
21 files changed, 4971 insertions, 88 deletions
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; diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index 4091fb092d06..08beeed5485d 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 @@ -388,12 +395,26 @@ 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 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 @@ -439,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 b7b0181c95e5..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 @@ -58,9 +59,12 @@ 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_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_POWER_AVS) += avs/ 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 <ramakrishna.pallala@intel.com> + * + * 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 <linux/module.h> +#include <linux/device.h> +#include <linux/regmap.h> +#include <linux/workqueue.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/usb/otg.h> +#include <linux/notifier.h> +#include <linux/power_supply.h> +#include <linux/notifier.h> +#include <linux/property.h> +#include <linux/mfd/axp20x.h> +#include <linux/extcon.h> + +#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; |