aboutsummaryrefslogtreecommitdiff
path: root/drivers
diff options
context:
space:
mode:
authorLinus Torvalds2020-01-30 07:51:24 -0800
committerLinus Torvalds2020-01-30 07:51:24 -0800
commit4cadc60d6bcfee9c626d4b55e9dc1475d21ad3bb (patch)
treec9c7247df09e0a44f94e95155fb1a2ce1588c3c6 /drivers
parent893e591b59036f9bc629f55bce715d67bdd266a2 (diff)
parent3d32a8437c051013352baf5cbd0844fcf5d2f409 (diff)
Merge tag 'for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel: "Core: - Add battery internal resistance temperature table support Drivers: - sc27xx: Optimize the battery resistance with measuring temperature - max17042-battery: Add MAX17055 support - bq25890-charger: Add support of BQ25892 and BQ25896 chips - misc fixes" * tag 'for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (44 commits) power: supply: ipaq_micro_battery: remove unneeded semicolon power: supply: bq25890_charger: fix incorrect error return when bq25890_field_read fails power: supply: axp20x_usb_power: Only poll while offline power: supply: axp20x_usb_power: Add wakeup control power: supply: axp20x_usb_power: Allow offlining power: supply: axp20x_usb_power: Use a match structure power: suppy: ucs1002: Make the symbol 'ucs1002_regulator_enable' static power: reset: at91-poweroff: use proper master clock register offset power: reset: at91-poweroff: introduce struct shdwc_reg_config power: supply: bq25890_charger: Add DT and I2C ids for all supported chips dt-bindings: Add new chips to bq25890 binding documentation power: supply: bq25890_charger: Add support of BQ25892 and BQ25896 chips power: supply: core: Update sysfs-class-power ABI document power: supply: sbs-battery: Fix a signedness bug in sbs_get_battery_capacity() power: supply: ltc2941-battery-gauge: fix use-after-free power: supply: max17040: Correct IRQ wake handling power: supply: axp20x_usb_power: Remove unused device_node power: supply: axp20x_ac_power: Add wakeup control power: supply: axp20x_ac_power: Allow offlining power: supply: axp20x_ac_power: Fix reporting online status ...
Diffstat (limited to 'drivers')
-rw-r--r--drivers/power/reset/Kconfig16
-rw-r--r--drivers/power/reset/at91-sama5d2_shdwc.c72
-rw-r--r--drivers/power/reset/gpio-restart.c8
-rw-r--r--drivers/power/supply/Kconfig30
-rw-r--r--drivers/power/supply/ab8500_charger.c6
-rw-r--r--drivers/power/supply/ab8500_fg.c14
-rw-r--r--drivers/power/supply/abx500_chargalg.c2
-rw-r--r--drivers/power/supply/axp20x_ac_power.c131
-rw-r--r--drivers/power/supply/axp20x_usb_power.c219
-rw-r--r--drivers/power/supply/bq25890_charger.c103
-rw-r--r--drivers/power/supply/cros_usbpd-charger.c10
-rw-r--r--drivers/power/supply/ingenic-battery.c15
-rw-r--r--drivers/power/supply/ipaq_micro_battery.c6
-rw-r--r--drivers/power/supply/ltc2941-battery-gauge.c2
-rw-r--r--drivers/power/supply/max17040_battery.c122
-rw-r--r--drivers/power/supply/max17042_battery.c17
-rw-r--r--drivers/power/supply/max77650-charger.c7
-rw-r--r--drivers/power/supply/pda_power.c4
-rw-r--r--drivers/power/supply/power_supply_core.c67
-rw-r--r--drivers/power/supply/sbs-battery.c35
-rw-r--r--drivers/power/supply/sc27xx_fuel_gauge.c49
-rw-r--r--drivers/power/supply/ucs1002_power.c42
22 files changed, 772 insertions, 205 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index c721939767eb..0498363203e8 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -141,14 +141,14 @@ config POWER_RESET_LTC2952
down via the LTC2952. Bindings are made in the device tree.
config POWER_RESET_MT6323
- bool "MediaTek MT6323 power-off driver"
- depends on MFD_MT6397
- help
- The power-off driver is responsible for externally shutdown down
- the power of a remote MediaTek SoC MT6323 is connected to through
- controlling a tiny circuit BBPU inside MT6323 RTC.
-
- Say Y if you have a board where MT6323 could be found.
+ bool "MediaTek MT6323 power-off driver"
+ depends on MFD_MT6397
+ help
+ The power-off driver is responsible for externally shutdown down
+ the power of a remote MediaTek SoC MT6323 is connected to through
+ controlling a tiny circuit BBPU inside MT6323 RTC.
+
+ Say Y if you have a board where MT6323 could be found.
config POWER_RESET_QNAP
bool "QNAP power-off driver"
diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c
index 1c18f465a245..2fe3a627cb53 100644
--- a/drivers/power/reset/at91-sama5d2_shdwc.c
+++ b/drivers/power/reset/at91-sama5d2_shdwc.c
@@ -66,7 +66,7 @@
#define SHDW_CFG_NOT_USED (32)
-struct shdwc_config {
+struct shdwc_reg_config {
u8 wkup_pin_input;
u8 mr_rtcwk_shift;
u8 mr_rttwk_shift;
@@ -74,8 +74,17 @@ struct shdwc_config {
u8 sr_rttwk_shift;
};
+struct pmc_reg_config {
+ u8 mckr;
+};
+
+struct reg_config {
+ struct shdwc_reg_config shdwc;
+ struct pmc_reg_config pmc;
+};
+
struct shdwc {
- const struct shdwc_config *cfg;
+ const struct reg_config *rcfg;
struct clk *sclk;
void __iomem *shdwc_base;
void __iomem *mpddrc_base;
@@ -95,6 +104,7 @@ static const unsigned long long sdwc_dbc_period[] = {
static void __init at91_wakeup_status(struct platform_device *pdev)
{
struct shdwc *shdw = platform_get_drvdata(pdev);
+ const struct reg_config *rcfg = shdw->rcfg;
u32 reg;
char *reason = "unknown";
@@ -106,11 +116,11 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
if (!reg)
return;
- if (SHDW_WK_PIN(reg, shdw->cfg))
+ if (SHDW_WK_PIN(reg, &rcfg->shdwc))
reason = "WKUP pin";
- else if (SHDW_RTCWK(reg, shdw->cfg))
+ else if (SHDW_RTCWK(reg, &rcfg->shdwc))
reason = "RTC";
- else if (SHDW_RTTWK(reg, shdw->cfg))
+ else if (SHDW_RTTWK(reg, &rcfg->shdwc))
reason = "RTT";
pr_info("AT91: Wake-Up source: %s\n", reason);
@@ -131,9 +141,9 @@ static void at91_poweroff(void)
" str %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
/* Switch the master clock source to slow clock. */
- "1: ldr r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+ "1: ldr r6, [%4, %5]\n\t"
" bic r6, r6, #" __stringify(AT91_PMC_CSS) "\n\t"
- " str r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+ " str r6, [%4, %5]\n\t"
/* Wait for clock switch. */
"2: ldr r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t"
" tst r6, #" __stringify(AT91_PMC_MCKRDY) "\n\t"
@@ -148,7 +158,8 @@ static void at91_poweroff(void)
"r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
"r" (at91_shdwc->shdwc_base),
"r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW),
- "r" (at91_shdwc->pmc_base)
+ "r" (at91_shdwc->pmc_base),
+ "r" (at91_shdwc->rcfg->pmc.mckr)
: "r6");
}
@@ -215,6 +226,7 @@ static u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev,
static void at91_shdwc_dt_configure(struct platform_device *pdev)
{
struct shdwc *shdw = platform_get_drvdata(pdev);
+ const struct reg_config *rcfg = shdw->rcfg;
struct device_node *np = pdev->dev.of_node;
u32 mode = 0, tmp, input;
@@ -227,10 +239,10 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp));
if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
- mode |= SHDW_RTCWKEN(shdw->cfg);
+ mode |= SHDW_RTCWKEN(&rcfg->shdwc);
if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
- mode |= SHDW_RTTWKEN(shdw->cfg);
+ mode |= SHDW_RTTWKEN(&rcfg->shdwc);
dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
writel(mode, shdw->shdwc_base + AT91_SHDW_MR);
@@ -239,30 +251,40 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
writel(input, shdw->shdwc_base + AT91_SHDW_WUIR);
}
-static const struct shdwc_config sama5d2_shdwc_config = {
- .wkup_pin_input = 0,
- .mr_rtcwk_shift = 17,
- .mr_rttwk_shift = SHDW_CFG_NOT_USED,
- .sr_rtcwk_shift = 5,
- .sr_rttwk_shift = SHDW_CFG_NOT_USED,
+static const struct reg_config sama5d2_reg_config = {
+ .shdwc = {
+ .wkup_pin_input = 0,
+ .mr_rtcwk_shift = 17,
+ .mr_rttwk_shift = SHDW_CFG_NOT_USED,
+ .sr_rtcwk_shift = 5,
+ .sr_rttwk_shift = SHDW_CFG_NOT_USED,
+ },
+ .pmc = {
+ .mckr = 0x30,
+ },
};
-static const struct shdwc_config sam9x60_shdwc_config = {
- .wkup_pin_input = 0,
- .mr_rtcwk_shift = 17,
- .mr_rttwk_shift = 16,
- .sr_rtcwk_shift = 5,
- .sr_rttwk_shift = 4,
+static const struct reg_config sam9x60_reg_config = {
+ .shdwc = {
+ .wkup_pin_input = 0,
+ .mr_rtcwk_shift = 17,
+ .mr_rttwk_shift = 16,
+ .sr_rtcwk_shift = 5,
+ .sr_rttwk_shift = 4,
+ },
+ .pmc = {
+ .mckr = 0x28,
+ },
};
static const struct of_device_id at91_shdwc_of_match[] = {
{
.compatible = "atmel,sama5d2-shdwc",
- .data = &sama5d2_shdwc_config,
+ .data = &sama5d2_reg_config,
},
{
.compatible = "microchip,sam9x60-shdwc",
- .data = &sam9x60_shdwc_config,
+ .data = &sam9x60_reg_config,
}, {
/*sentinel*/
}
@@ -303,7 +325,7 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
}
match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);
- at91_shdwc->cfg = match->data;
+ at91_shdwc->rcfg = match->data;
at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL);
if (IS_ERR(at91_shdwc->sclk))
diff --git a/drivers/power/reset/gpio-restart.c b/drivers/power/reset/gpio-restart.c
index 308ca9d9d276..5466eeea261c 100644
--- a/drivers/power/reset/gpio-restart.c
+++ b/drivers/power/reset/gpio-restart.c
@@ -64,9 +64,11 @@ static int gpio_restart_probe(struct platform_device *pdev)
gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL,
open_source ? GPIOD_IN : GPIOD_OUT_LOW);
- if (IS_ERR(gpio_restart->reset_gpio)) {
- dev_err(&pdev->dev, "Could not get reset GPIO\n");
- return PTR_ERR(gpio_restart->reset_gpio);
+ ret = PTR_ERR_OR_ZERO(gpio_restart->reset_gpio);
+ if (ret) {
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Could not get reset GPIO\n");
+ return ret;
}
gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 27164a1d3c7c..9a5591ab90d0 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -73,10 +73,10 @@ config WM831X_POWER
provided by Wolfson Microelectronics WM831x PMICs.
config WM8350_POWER
- tristate "WM8350 PMU support"
- depends on MFD_WM8350
- help
- Say Y here to enable support for the power management unit
+ tristate "WM8350 PMU support"
+ depends on MFD_WM8350
+ help
+ Say Y here to enable support for the power management unit
provided by the Wolfson Microelectronics WM8350 PMIC.
config TEST_POWER
@@ -209,16 +209,16 @@ config BATTERY_WM97XX
Say Y to enable support for battery measured by WM97xx aux port.
config BATTERY_SBS
- tristate "SBS Compliant gas gauge"
- depends on I2C
- help
+ tristate "SBS Compliant gas gauge"
+ depends on I2C
+ help
Say Y to include support for SBS battery driver for SBS-compliant
gas gauges.
config CHARGER_SBS
- tristate "SBS Compliant charger"
- depends on I2C
- help
+ tristate "SBS Compliant charger"
+ depends on I2C
+ help
Say Y to include support for SBS compliant battery chargers.
config MANAGER_SBS
@@ -484,11 +484,11 @@ config CHARGER_MANAGER
depends on REGULATOR
select EXTCON
help
- Say Y to enable charger-manager support, which allows multiple
- chargers attached to a battery and multiple batteries attached to a
- system. The charger-manager also can monitor charging status in
- runtime and in suspend-to-RAM by waking up the system periodically
- with help of suspend_again support.
+ Say Y to enable charger-manager support, which allows multiple
+ chargers attached to a battery and multiple batteries attached to a
+ system. The charger-manager also can monitor charging status in
+ runtime and in suspend-to-RAM by waking up the system periodically
+ with help of suspend_again support.
config CHARGER_LT3651
tristate "Analog Devices LT3651 charger"
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
index 8a0f9d769690..f69550d64f09 100644
--- a/drivers/power/supply/ab8500_charger.c
+++ b/drivers/power/supply/ab8500_charger.c
@@ -789,7 +789,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
ret = -ENXIO;
break;
- };
+ }
di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
@@ -1079,7 +1079,7 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
ret = -EPERM;
break;
- };
+ }
di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
return ret;
}
@@ -2427,7 +2427,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
default:
break;
- };
+ }
}
/**
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index c3912ee9eb99..b96f90a82ecf 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -2221,10 +2221,10 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
ab8500_fg_update_cap_scalers(di);
queue_work(di->fg_wq, &di->fg_work);
break;
- };
+ }
default:
break;
- };
+ }
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
switch (ext->desc->type) {
@@ -2331,7 +2331,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__);
goto out;
- };
+ }
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
@@ -2339,7 +2339,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
goto out;
- };
+ }
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
@@ -2347,7 +2347,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__);
goto out;
- };
+ }
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time);
@@ -2355,7 +2355,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__);
goto out;
- };
+ }
ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable);
@@ -2363,7 +2363,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
if (ret) {
dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__);
goto out;
- };
+ }
}
out:
return ret;
diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c
index e6e37d4f20e4..2fb33a07879a 100644
--- a/drivers/power/supply/abx500_chargalg.c
+++ b/drivers/power/supply/abx500_chargalg.c
@@ -1823,7 +1823,7 @@ static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di,
"Enter 0. Disable AC/USB Charging\n"
"1. Enable AC charging\n"
"2. Enable USB Charging\n");
- };
+ }
return strlen(buf);
}
diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c
index 0d34a932b6d5..ac360016b08a 100644
--- a/drivers/power/supply/axp20x_ac_power.c
+++ b/drivers/power/supply/axp20x_ac_power.c
@@ -15,6 +15,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -23,6 +24,9 @@
#define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
#define AXP20X_PWR_STATUS_ACIN_AVAIL BIT(6)
+#define AXP813_ACIN_PATH_SEL BIT(7)
+#define AXP813_ACIN_PATH_SEL_TO_BIT(x) (!!(x) << 7)
+
#define AXP813_VHOLD_MASK GENMASK(5, 3)
#define AXP813_VHOLD_UV_TO_BIT(x) ((((x) / 100000) - 40) << 3)
#define AXP813_VHOLD_REG_TO_UV(x) \
@@ -40,6 +44,9 @@ struct axp20x_ac_power {
struct power_supply *supply;
struct iio_channel *acin_v;
struct iio_channel *acin_i;
+ bool has_acin_path_sel;
+ unsigned int num_irqs;
+ unsigned int irqs[];
};
static irqreturn_t axp20x_ac_power_irq(int irq, void *devid)
@@ -86,6 +93,17 @@ static int axp20x_ac_power_get_property(struct power_supply *psy,
return ret;
val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL);
+
+ /* ACIN_PATH_SEL disables ACIN even if ACIN_AVAIL is set. */
+ if (val->intval && power->has_acin_path_sel) {
+ ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL,
+ &reg);
+ if (ret)
+ return ret;
+
+ val->intval = !!(reg & AXP813_ACIN_PATH_SEL);
+ }
+
return 0;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
@@ -143,6 +161,11 @@ static int axp813_ac_power_set_property(struct power_supply *psy,
struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL,
+ AXP813_ACIN_PATH_SEL,
+ AXP813_ACIN_PATH_SEL_TO_BIT(val->intval));
+
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
if (val->intval < 4000000 || val->intval > 4700000)
return -EINVAL;
@@ -169,7 +192,8 @@ static int axp813_ac_power_set_property(struct power_supply *psy,
static int axp813_ac_power_prop_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
- return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
+ return psp == POWER_SUPPLY_PROP_ONLINE ||
+ psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
}
@@ -221,34 +245,86 @@ static const struct power_supply_desc axp813_ac_power_desc = {
.set_property = axp813_ac_power_set_property,
};
+static const char * const axp20x_irq_names[] = {
+ "ACIN_PLUGIN",
+ "ACIN_REMOVAL",
+};
+
struct axp_data {
const struct power_supply_desc *power_desc;
+ const char * const *irq_names;
+ unsigned int num_irq_names;
bool acin_adc;
+ bool acin_path_sel;
};
static const struct axp_data axp20x_data = {
- .power_desc = &axp20x_ac_power_desc,
- .acin_adc = true,
+ .power_desc = &axp20x_ac_power_desc,
+ .irq_names = axp20x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
+ .acin_adc = true,
+ .acin_path_sel = false,
};
static const struct axp_data axp22x_data = {
- .power_desc = &axp22x_ac_power_desc,
- .acin_adc = false,
+ .power_desc = &axp22x_ac_power_desc,
+ .irq_names = axp20x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
+ .acin_adc = false,
+ .acin_path_sel = false,
};
static const struct axp_data axp813_data = {
- .power_desc = &axp813_ac_power_desc,
- .acin_adc = false,
+ .power_desc = &axp813_ac_power_desc,
+ .irq_names = axp20x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
+ .acin_adc = false,
+ .acin_path_sel = true,
};
+#ifdef CONFIG_PM_SLEEP
+static int axp20x_ac_power_suspend(struct device *dev)
+{
+ struct axp20x_ac_power *power = dev_get_drvdata(dev);
+ int i = 0;
+
+ /*
+ * Allow wake via ACIN_PLUGIN only.
+ *
+ * As nested threaded IRQs are not automatically disabled during
+ * suspend, we must explicitly disable the remainder of the IRQs.
+ */
+ if (device_may_wakeup(&power->supply->dev))
+ enable_irq_wake(power->irqs[i++]);
+ while (i < power->num_irqs)
+ disable_irq(power->irqs[i++]);
+
+ return 0;
+}
+
+static int axp20x_ac_power_resume(struct device *dev)
+{
+ struct axp20x_ac_power *power = dev_get_drvdata(dev);
+ int i = 0;
+
+ if (device_may_wakeup(&power->supply->dev))
+ disable_irq_wake(power->irqs[i++]);
+ while (i < power->num_irqs)
+ enable_irq(power->irqs[i++]);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend,
+ axp20x_ac_power_resume);
+
static int axp20x_ac_power_probe(struct platform_device *pdev)
{
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct axp20x_ac_power *power;
const struct axp_data *axp_data;
- static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL",
- NULL };
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
@@ -259,12 +335,14 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
return -EINVAL;
}
- power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+ axp_data = of_device_get_match_data(&pdev->dev);
+
+ power = devm_kzalloc(&pdev->dev,
+ struct_size(power, irqs, axp_data->num_irq_names),
+ GFP_KERNEL);
if (!power)
return -ENOMEM;
- axp_data = of_device_get_match_data(&pdev->dev);
-
if (axp_data->acin_adc) {
power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v");
if (IS_ERR(power->acin_v)) {
@@ -282,6 +360,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
}
power->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ power->has_acin_path_sel = axp_data->acin_path_sel;
+ power->num_irqs = axp_data->num_irq_names;
platform_set_drvdata(pdev, power);
@@ -295,20 +375,22 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
return PTR_ERR(power->supply);
/* Request irqs after registering, as irqs may trigger immediately */
- for (i = 0; irq_names[i]; i++) {
- irq = platform_get_irq_byname(pdev, irq_names[i]);
+ for (i = 0; i < axp_data->num_irq_names; i++) {
+ irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
if (irq < 0) {
- dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
- irq_names[i], irq);
- continue;
+ dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+ axp_data->irq_names[i], irq);
+ return irq;
}
- irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
- ret = devm_request_any_context_irq(&pdev->dev, irq,
+ power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+ ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
axp20x_ac_power_irq, 0,
DRVNAME, power);
- if (ret < 0)
- dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
- irq_names[i], ret);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
+ axp_data->irq_names[i], ret);
+ return ret;
+ }
}
return 0;
@@ -331,8 +413,9 @@ MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
static struct platform_driver axp20x_ac_power_driver = {
.probe = axp20x_ac_power_probe,
.driver = {
- .name = DRVNAME,
- .of_match_table = axp20x_ac_power_match,
+ .name = DRVNAME,
+ .of_match_table = axp20x_ac_power_match,
+ .pm = &axp20x_ac_power_pm_ops,
},
};
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index 5f0a5722b19e..4fde24b5f35a 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -16,6 +16,7 @@
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
+#include <linux/pm.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/slab.h>
@@ -29,6 +30,9 @@
#define AXP20X_USB_STATUS_VBUS_VALID BIT(2)
+#define AXP20X_VBUS_PATH_SEL BIT(7)
+#define AXP20X_VBUS_PATH_SEL_OFFSET 7
+
#define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000)
#define AXP20X_VBUS_VHOLD_MASK GENMASK(5, 3)
#define AXP20X_VBUS_VHOLD_OFFSET 3
@@ -57,7 +61,6 @@
#define DEBOUNCE_TIME msecs_to_jiffies(50)
struct axp20x_usb_power {
- struct device_node *np;
struct regmap *regmap;
struct power_supply *supply;
enum axp20x_variants axp20x_id;
@@ -65,14 +68,32 @@ struct axp20x_usb_power {
struct iio_channel *vbus_i;
struct delayed_work vbus_detect;
unsigned int old_status;
+ unsigned int online;
+ unsigned int num_irqs;
+ unsigned int irqs[];
};
+static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
+{
+ /*
+ * Polling is only necessary while VBUS is offline. While online, a
+ * present->absent transition implies an online->offline transition
+ * and will triger the VBUS_REMOVAL IRQ.
+ */
+ if (power->axp20x_id >= AXP221_ID && !power->online)
+ return true;
+
+ return false;
+}
+
static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
{
struct axp20x_usb_power *power = devid;
power_supply_changed(power->supply);
+ mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
+
return IRQ_HANDLED;
}
@@ -92,17 +113,11 @@ static void axp20x_usb_power_poll_vbus(struct work_struct *work)
power_supply_changed(power->supply);
power->old_status = val;
+ power->online = val & AXP20X_PWR_STATUS_VBUS_USED;
out:
- mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
-}
-
-static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
-{
- if (power->axp20x_id >= AXP221_ID)
- return true;
-
- return false;
+ if (axp20x_usb_vbus_needs_polling(power))
+ mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
}
static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val)
@@ -264,6 +279,16 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
return 0;
}
+static int axp813_usb_power_set_online(struct axp20x_usb_power *power,
+ int intval)
+{
+ int val = !intval << AXP20X_VBUS_PATH_SEL_OFFSET;
+
+ return regmap_update_bits(power->regmap,
+ AXP20X_VBUS_IPSOUT_MGMT,
+ AXP20X_VBUS_PATH_SEL, val);
+}
+
static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
int intval)
{
@@ -345,6 +370,11 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (power->axp20x_id != AXP813_ID)
+ return -EINVAL;
+ return axp813_usb_power_set_online(power, val->intval);
+
case POWER_SUPPLY_PROP_VOLTAGE_MIN:
return axp20x_usb_power_set_voltage_min(power, val->intval);
@@ -364,6 +394,18 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
static int axp20x_usb_power_prop_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
+ struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
+
+ /*
+ * The VBUS path select flag works differently on on AXP288 and newer:
+ * - On AXP20x and AXP22x, the flag enables VBUS (ignoring N_VBUSEN).
+ * - On AXP288 and AXP8xx, the flag disables VBUS (ignoring N_VBUSEN).
+ * We only expose the control on variants where it can be used to force
+ * the VBUS input offline.
+ */
+ if (psp == POWER_SUPPLY_PROP_ONLINE)
+ return power->axp20x_id == AXP813_ID;
+
return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
psp == POWER_SUPPLY_PROP_CURRENT_MAX;
}
@@ -406,6 +448,92 @@ static const struct power_supply_desc axp22x_usb_power_desc = {
.set_property = axp20x_usb_power_set_property,
};
+static const char * const axp20x_irq_names[] = {
+ "VBUS_PLUGIN",
+ "VBUS_REMOVAL",
+ "VBUS_VALID",
+ "VBUS_NOT_VALID",
+};
+
+static const char * const axp22x_irq_names[] = {
+ "VBUS_PLUGIN",
+ "VBUS_REMOVAL",
+};
+
+struct axp_data {
+ const struct power_supply_desc *power_desc;
+ const char * const *irq_names;
+ unsigned int num_irq_names;
+ enum axp20x_variants axp20x_id;
+};
+
+static const struct axp_data axp202_data = {
+ .power_desc = &axp20x_usb_power_desc,
+ .irq_names = axp20x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp20x_irq_names),
+ .axp20x_id = AXP202_ID,
+};
+
+static const struct axp_data axp221_data = {
+ .power_desc = &axp22x_usb_power_desc,
+ .irq_names = axp22x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp22x_irq_names),
+ .axp20x_id = AXP221_ID,
+};
+
+static const struct axp_data axp223_data = {
+ .power_desc = &axp22x_usb_power_desc,
+ .irq_names = axp22x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp22x_irq_names),
+ .axp20x_id = AXP223_ID,
+};
+
+static const struct axp_data axp813_data = {
+ .power_desc = &axp22x_usb_power_desc,
+ .irq_names = axp22x_irq_names,
+ .num_irq_names = ARRAY_SIZE(axp22x_irq_names),
+ .axp20x_id = AXP813_ID,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int axp20x_usb_power_suspend(struct device *dev)
+{
+ struct axp20x_usb_power *power = dev_get_drvdata(dev);
+ int i = 0;
+
+ /*
+ * Allow wake via VBUS_PLUGIN only.
+ *
+ * As nested threaded IRQs are not automatically disabled during
+ * suspend, we must explicitly disable the remainder of the IRQs.
+ */
+ if (device_may_wakeup(&power->supply->dev))
+ enable_irq_wake(power->irqs[i++]);
+ while (i < power->num_irqs)
+ disable_irq(power->irqs[i++]);
+
+ return 0;
+}
+
+static int axp20x_usb_power_resume(struct device *dev)
+{
+ struct axp20x_usb_power *power = dev_get_drvdata(dev);
+ int i = 0;
+
+ if (device_may_wakeup(&power->supply->dev))
+ disable_irq_wake(power->irqs[i++]);
+ while (i < power->num_irqs)
+ enable_irq(power->irqs[i++]);
+
+ mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend,
+ axp20x_usb_power_resume);
+
static int configure_iio_channels(struct platform_device *pdev,
struct axp20x_usb_power *power)
{
@@ -441,12 +569,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
struct power_supply_config psy_cfg = {};
struct axp20x_usb_power *power;
- static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN",
- "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
- static const char * const axp22x_irq_names[] = {
- "VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
- const char * const *irq_names;
- const struct power_supply_desc *usb_power_desc;
+ const struct axp_data *axp_data;
int i, irq, ret;
if (!of_device_is_available(pdev->dev.of_node))
@@ -457,16 +580,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
return -EINVAL;
}
- power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+ axp_data = of_device_get_match_data(&pdev->dev);
+
+ power = devm_kzalloc(&pdev->dev,
+ struct_size(power, irqs, axp_data->num_irq_names),
+ GFP_KERNEL);
if (!power)
return -ENOMEM;
platform_set_drvdata(pdev, power);
- power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
- &pdev->dev);
- power->np = pdev->dev.of_node;
+ power->axp20x_id = axp_data->axp20x_id;
power->regmap = axp20x->regmap;
+ power->num_irqs = axp_data->num_irq_names;
if (power->axp20x_id == AXP202_ID) {
/* Enable vbus valid checking */
@@ -483,18 +609,6 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
if (ret)
return ret;
-
- usb_power_desc = &axp20x_usb_power_desc;
- irq_names = axp20x_irq_names;
- } else if (power->axp20x_id == AXP221_ID ||
- power->axp20x_id == AXP223_ID ||
- power->axp20x_id == AXP813_ID) {
- usb_power_desc = &axp22x_usb_power_desc;
- irq_names = axp22x_irq_names;
- } else {
- dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n",
- axp20x->variant);
- return -EINVAL;
}
if (power->axp20x_id == AXP813_ID) {
@@ -506,25 +620,29 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = power;
- power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc,
+ power->supply = devm_power_supply_register(&pdev->dev,
+ axp_data->power_desc,
&psy_cfg);
if (IS_ERR(power->supply))
return PTR_ERR(power->supply);
/* Request irqs after registering, as irqs may trigger immediately */
- for (i = 0; irq_names[i]; i++) {
- irq = platform_get_irq_byname(pdev, irq_names[i]);
+ for (i = 0; i < axp_data->num_irq_names; i++) {
+ irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
if (irq < 0) {
- dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
- irq_names[i], irq);
- continue;
+ dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+ axp_data->irq_names[i], irq);
+ return irq;
+ }
+ power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+ ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
+ axp20x_usb_power_irq, 0,
+ DRVNAME, power);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
+ axp_data->irq_names[i], ret);
+ return ret;
}
- irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
- ret = devm_request_any_context_irq(&pdev->dev, irq,
- axp20x_usb_power_irq, 0, DRVNAME, power);
- if (ret < 0)
- dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
- irq_names[i], ret);
}
INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
@@ -546,16 +664,16 @@ static int axp20x_usb_power_remove(struct platform_device *pdev)
static const struct of_device_id axp20x_usb_power_match[] = {
{
.compatible = "x-powers,axp202-usb-power-supply",
- .data = (void *)AXP202_ID,
+ .data = &axp202_data,
}, {
.compatible = "x-powers,axp221-usb-power-supply",
- .data = (void *)AXP221_ID,
+ .data = &axp221_data,
}, {
.compatible = "x-powers,axp223-usb-power-supply",
- .data = (void *)AXP223_ID,
+ .data = &axp223_data,
}, {
.compatible = "x-powers,axp813-usb-power-supply",
- .data = (void *)AXP813_ID,
+ .data = &axp813_data,
}, { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
@@ -564,8 +682,9 @@ static struct platform_driver axp20x_usb_power_driver = {
.probe = axp20x_usb_power_probe,
.remove = axp20x_usb_power_remove,
.driver = {
- .name = DRVNAME,
- .of_match_table = axp20x_usb_power_match,
+ .name = DRVNAME,
+ .of_match_table = axp20x_usb_power_match,
+ .pm = &axp20x_usb_power_pm_ops,
},
};
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
index 9d1ec8d677de..aebd1253dbc9 100644
--- a/drivers/power/supply/bq25890_charger.c
+++ b/drivers/power/supply/bq25890_charger.c
@@ -25,12 +25,20 @@
#define BQ25895_ID 7
#define BQ25896_ID 0
+enum bq25890_chip_version {
+ BQ25890,
+ BQ25892,
+ BQ25895,
+ BQ25896,
+};
+
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_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN,
+ F_MIN_VBAT_SEL, /* Reg03 */
F_PUMPX_EN, F_ICHG, /* Reg04 */
F_IPRECHG, F_ITERM, /* Reg05 */
F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */
@@ -39,8 +47,9 @@ enum bq25890_fields {
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_BOOSTV, F_PFM_OTG_DIS, F_BOOSTI, /* Reg0A */
+ F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_0B_RSVD,
+ 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 */
@@ -91,7 +100,7 @@ struct bq25890_device {
struct regmap *rmap;
struct regmap_field *rmap_fields[F_MAX_FIELDS];
- int chip_id;
+ enum bq25890_chip_version chip_version;
struct bq25890_init_data init_data;
struct bq25890_state state;
@@ -111,8 +120,7 @@ static const struct regmap_access_table bq25890_writeable_regs = {
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),
+ regmap_reg_range(0x0b, 0x14),
};
static const struct regmap_access_table bq25890_volatile_regs = {
@@ -155,7 +163,7 @@ static const struct reg_field bq25890_reg_fields[] = {
[F_OTG_CFG] = REG_FIELD(0x03, 5, 5),
[F_CHG_CFG] = REG_FIELD(0x03, 4, 4),
[F_SYSVMIN] = REG_FIELD(0x03, 1, 3),
- /* MIN_VBAT_SEL on BQ25896 */
+ [F_MIN_VBAT_SEL] = REG_FIELD(0x03, 0, 0), // BQ25896 only
/* REG04 */
[F_PUMPX_EN] = REG_FIELD(0x04, 7, 7),
[F_ICHG] = REG_FIELD(0x04, 0, 6),
@@ -188,8 +196,8 @@ static const struct reg_field bq25890_reg_fields[] = {
[F_PUMPX_DN] = REG_FIELD(0x09, 0, 0),
/* REG0A */
[F_BOOSTV] = REG_FIELD(0x0A, 4, 7),
- /* PFM_OTG_DIS 3 on BQ25896 */
[F_BOOSTI] = REG_FIELD(0x0A, 0, 2), // reserved on BQ25895
+ [F_PFM_OTG_DIS] = REG_FIELD(0x0A, 3, 3), // BQ25896 only
/* REG0B */
[F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7),
[F_CHG_STAT] = REG_FIELD(0x0B, 3, 4),
@@ -275,6 +283,7 @@ static const union {
struct bq25890_lookup lt;
} bq25890_tables[] = {
/* range tables */
+ /* TODO: BQ25896 has max ICHG 3008 mA */
[TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
[TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
[TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
@@ -391,11 +400,13 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
- if (bq->chip_id == BQ25890_ID)
+ if (bq->chip_version == BQ25890)
val->strval = "BQ25890";
- else if (bq->chip_id == BQ25895_ID)
+ else if (bq->chip_version == BQ25892)
+ val->strval = "BQ25892";
+ else if (bq->chip_version == BQ25895)
val->strval = "BQ25895";
- else if (bq->chip_id == BQ25896_ID)
+ else if (bq->chip_version == BQ25896)
val->strval = "BQ25896";
else
val->strval = "UNKNOWN";
@@ -741,6 +752,56 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
return NOTIFY_OK;
}
+static int bq25890_get_chip_version(struct bq25890_device *bq)
+{
+ int id, rev;
+
+ id = bq25890_field_read(bq, F_PN);
+ if (id < 0) {
+ dev_err(bq->dev, "Cannot read chip ID.\n");
+ return id;
+ }
+
+ rev = bq25890_field_read(bq, F_DEV_REV);
+ if (rev < 0) {
+ dev_err(bq->dev, "Cannot read chip revision.\n");
+ return rev;
+ }
+
+ switch (id) {
+ case BQ25890_ID:
+ bq->chip_version = BQ25890;
+ break;
+
+ /* BQ25892 and BQ25896 share same ID 0 */
+ case BQ25896_ID:
+ switch (rev) {
+ case 2:
+ bq->chip_version = BQ25896;
+ break;
+ case 1:
+ bq->chip_version = BQ25892;
+ break;
+ default:
+ dev_err(bq->dev,
+ "Unknown device revision %d, assume BQ25892\n",
+ rev);
+ bq->chip_version = BQ25892;
+ }
+ break;
+
+ case BQ25895_ID:
+ bq->chip_version = BQ25895;
+ break;
+
+ default:
+ dev_err(bq->dev, "Unknown chip ID %d\n", id);
+ return -ENODEV;
+ }
+
+ return 0;
+}
+
static int bq25890_irq_probe(struct bq25890_device *bq)
{
struct gpio_desc *irq;
@@ -859,16 +920,10 @@ static int bq25890_probe(struct i2c_client *client,
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 bq->chip_id;
- }
-
- if ((bq->chip_id != BQ25890_ID) && (bq->chip_id != BQ25895_ID)
- && (bq->chip_id != BQ25896_ID)) {
- dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
- return -ENODEV;
+ ret = bq25890_get_chip_version(bq);
+ if (ret) {
+ dev_err(dev, "Cannot read chip ID or unknown chip.\n");
+ return ret;
}
if (!dev->platform_data) {
@@ -986,12 +1041,18 @@ static const struct dev_pm_ops bq25890_pm = {
static const struct i2c_device_id bq25890_i2c_ids[] = {
{ "bq25890", 0 },
+ { "bq25892", 0 },
+ { "bq25895", 0 },
+ { "bq25896", 0 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
static const struct of_device_id bq25890_of_match[] = {
{ .compatible = "ti,bq25890", },
+ { .compatible = "ti,bq25892", },
+ { .compatible = "ti,bq25895", },
+ { .compatible = "ti,bq25896", },
{ },
};
MODULE_DEVICE_TABLE(of, bq25890_of_match);
diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c
index 6cc7c3910e09..ffad9ee03a68 100644
--- a/drivers/power/supply/cros_usbpd-charger.c
+++ b/drivers/power/supply/cros_usbpd-charger.c
@@ -132,11 +132,8 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger)
ret = cros_usbpd_charger_ec_command(charger, 0,
EC_CMD_CHARGE_PORT_COUNT,
NULL, 0, &resp, sizeof(resp));
- if (ret < 0) {
- dev_err(charger->dev,
- "Unable to get the number of ports (err:0x%x)\n", ret);
+ if (ret < 0)
return ret;
- }
return resp.port_count;
}
@@ -148,11 +145,8 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger)
ret = cros_usbpd_charger_ec_command(charger, 0, EC_CMD_USB_PD_PORTS,
NULL, 0, &resp, sizeof(resp));
- if (ret < 0) {
- dev_err(charger->dev,
- "Unable to get the number or ports (err:0x%x)\n", ret);
+ if (ret < 0)
return ret;
- }
return resp.num_ports;
}
diff --git a/drivers/power/supply/ingenic-battery.c b/drivers/power/supply/ingenic-battery.c
index 35816d4b3012..2748715c4c75 100644
--- a/drivers/power/supply/ingenic-battery.c
+++ b/drivers/power/supply/ingenic-battery.c
@@ -100,10 +100,17 @@ static int ingenic_battery_set_scale(struct ingenic_battery *bat)
return -EINVAL;
}
- return iio_write_channel_attribute(bat->channel,
- scale_raw[best_idx],
- scale_raw[best_idx + 1],
- IIO_CHAN_INFO_SCALE);
+ /* Only set scale if there is more than one (fractional) entry */
+ if (scale_len > 2) {
+ ret = iio_write_channel_attribute(bat->channel,
+ scale_raw[best_idx],
+ scale_raw[best_idx + 1],
+ IIO_CHAN_INFO_SCALE);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
}
static enum power_supply_property ingenic_battery_properties[] = {
diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c
index 03592ceaca88..192d9db0fb00 100644
--- a/drivers/power/supply/ipaq_micro_battery.c
+++ b/drivers/power/supply/ipaq_micro_battery.c
@@ -149,7 +149,7 @@ static int micro_batt_get_property(struct power_supply *b,
default:
val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
break;
- };
+ }
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = get_status(b);
@@ -168,7 +168,7 @@ static int micro_batt_get_property(struct power_supply *b,
break;
default:
return -EINVAL;
- };
+ }
return 0;
}
@@ -185,7 +185,7 @@ static int micro_ac_get_property(struct power_supply *b,
break;
default:
return -EINVAL;
- };
+ }
return 0;
}
diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c
index da49436176cd..30a9014b2f95 100644
--- a/drivers/power/supply/ltc2941-battery-gauge.c
+++ b/drivers/power/supply/ltc2941-battery-gauge.c
@@ -449,7 +449,7 @@ static int ltc294x_i2c_remove(struct i2c_client *client)
{
struct ltc294x_info *info = i2c_get_clientdata(client);
- cancel_delayed_work(&info->work);
+ cancel_delayed_work_sync(&info->work);
power_supply_unregister(info->supply);
return 0;
}
diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
index 62499018e68b..8a1f0ee493aa 100644
--- a/drivers/power/supply/max17040_battery.c
+++ b/drivers/power/supply/max17040_battery.c
@@ -13,6 +13,7 @@
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/delay.h>
+#include <linux/interrupt.h>
#include <linux/power_supply.h>
#include <linux/max17040_battery.h>
#include <linux/slab.h>
@@ -28,6 +29,9 @@
#define MAX17040_DELAY 1000
#define MAX17040_BATTERY_FULL 95
+#define MAX17040_ATHD_MASK 0xFFC0
+#define MAX17040_ATHD_DEFAULT_POWER_UP 4
+
struct max17040_chip {
struct i2c_client *client;
struct delayed_work work;
@@ -42,6 +46,8 @@ struct max17040_chip {
int soc;
/* State Of Charge */
int status;
+ /* Low alert threshold from 32% to 1% of the State of Charge */
+ u32 low_soc_alert;
};
static int max17040_get_property(struct power_supply *psy,
@@ -98,6 +104,21 @@ static void max17040_reset(struct i2c_client *client)
max17040_write_reg(client, MAX17040_CMD, 0x0054);
}
+static int max17040_set_low_soc_alert(struct i2c_client *client, u32 level)
+{
+ int ret;
+ u16 data;
+
+ level = 32 - level;
+ data = max17040_read_reg(client, MAX17040_RCOMP);
+ /* clear the alrt bit and set LSb 5 bits */
+ data &= MAX17040_ATHD_MASK;
+ data |= level;
+ ret = max17040_write_reg(client, MAX17040_RCOMP, data);
+
+ return ret;
+}
+
static void max17040_get_vcell(struct i2c_client *client)
{
struct max17040_chip *chip = i2c_get_clientdata(client);
@@ -160,21 +181,81 @@ static void max17040_get_status(struct i2c_client *client)
chip->status = POWER_SUPPLY_STATUS_FULL;
}
+static int max17040_get_of_data(struct max17040_chip *chip)
+{
+ struct device *dev = &chip->client->dev;
+
+ chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP;
+ device_property_read_u32(dev,
+ "maxim,alert-low-soc-level",
+ &chip->low_soc_alert);
+
+ if (chip->low_soc_alert <= 0 || chip->low_soc_alert >= 33)
+ return -EINVAL;
+
+ return 0;
+}
+
+static void max17040_check_changes(struct i2c_client *client)
+{
+ max17040_get_vcell(client);
+ max17040_get_soc(client);
+ max17040_get_online(client);
+ max17040_get_status(client);
+}
+
static void max17040_work(struct work_struct *work)
{
struct max17040_chip *chip;
+ int last_soc, last_status;
chip = container_of(work, struct max17040_chip, work.work);
- max17040_get_vcell(chip->client);
- max17040_get_soc(chip->client);
- max17040_get_online(chip->client);
- max17040_get_status(chip->client);
+ /* store SOC and status to check changes */
+ last_soc = chip->soc;
+ last_status = chip->status;
+ max17040_check_changes(chip->client);
+
+ /* check changes and send uevent */
+ if (last_soc != chip->soc || last_status != chip->status)
+ power_supply_changed(chip->battery);
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
}
+static irqreturn_t max17040_thread_handler(int id, void *dev)
+{
+ struct max17040_chip *chip = dev;
+ struct i2c_client *client = chip->client;
+
+ dev_warn(&client->dev, "IRQ: Alert battery low level");
+ /* read registers */
+ max17040_check_changes(chip->client);
+
+ /* send uevent */
+ power_supply_changed(chip->battery);
+
+ /* reset alert bit */
+ max17040_set_low_soc_alert(client, chip->low_soc_alert);
+
+ return IRQ_HANDLED;
+}
+
+static int max17040_enable_alert_irq(struct max17040_chip *chip)
+{
+ struct i2c_client *client = chip->client;
+ unsigned int flags;
+ int ret;
+
+ flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+ ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+ max17040_thread_handler, flags,
+ chip->battery->desc->name, chip);
+
+ return ret;
+}
+
static enum power_supply_property max17040_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
@@ -196,6 +277,7 @@ static int max17040_probe(struct i2c_client *client,
struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct max17040_chip *chip;
+ int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
return -EIO;
@@ -206,6 +288,12 @@ static int max17040_probe(struct i2c_client *client,
chip->client = client;
chip->pdata = client->dev.platform_data;
+ ret = max17040_get_of_data(chip);
+ if (ret) {
+ dev_err(&client->dev,
+ "failed: low SOC alert OF data out of bounds\n");
+ return ret;
+ }
i2c_set_clientdata(client, chip);
psy_cfg.drv_data = chip;
@@ -220,6 +308,24 @@ static int max17040_probe(struct i2c_client *client,
max17040_reset(client);
max17040_get_version(client);
+ /* check interrupt */
+ if (client->irq && of_device_is_compatible(client->dev.of_node,
+ "maxim,max77836-battery")) {
+ ret = max17040_set_low_soc_alert(client, chip->low_soc_alert);
+ if (ret) {
+ dev_err(&client->dev,
+ "Failed to set low SOC alert: err %d\n", ret);
+ return ret;
+ }
+
+ ret = max17040_enable_alert_irq(chip);
+ if (ret) {
+ client->irq = 0;
+ dev_warn(&client->dev,
+ "Failed to get IRQ err %d\n", ret);
+ }
+ }
+
INIT_DEFERRABLE_WORK(&chip->work, max17040_work);
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
@@ -244,6 +350,10 @@ static int max17040_suspend(struct device *dev)
struct max17040_chip *chip = i2c_get_clientdata(client);
cancel_delayed_work(&chip->work);
+
+ if (client->irq && device_may_wakeup(dev))
+ enable_irq_wake(client->irq);
+
return 0;
}
@@ -254,6 +364,10 @@ static int max17040_resume(struct device *dev)
queue_delayed_work(system_power_efficient_wq, &chip->work,
MAX17040_DELAY);
+
+ if (client->irq && device_may_wakeup(dev))
+ disable_irq_wake(client->irq);
+
return 0;
}
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 0dfad2cf13fe..69ec4295d55d 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -282,6 +282,8 @@ static int max17042_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
ret = regmap_read(map, MAX17042_V_empty, &data);
+ else if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ ret = regmap_read(map, MAX17055_V_empty, &data);
else
ret = regmap_read(map, MAX17047_V_empty, &data);
if (ret < 0)
@@ -627,7 +629,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
config->filter_cfg);
regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
- chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 ||
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
regmap_write(map, MAX17047_FullSOCThr,
config->full_soc_thresh);
}
@@ -758,6 +761,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
max17042_override_por(map, MAX17042_V_empty, config->vempty);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+ max17042_override_por(map, MAX17055_V_empty, config->vempty);
else
max17042_override_por(map, MAX17047_V_empty, config->vempty);
max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
@@ -765,7 +770,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
max17042_override_por(map, MAX17042_FCTC, config->fctc);
max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
- if (chip->chip_type) {
+ if (chip->chip_type &&
+ ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))) {
max17042_override_por(map, MAX17042_EmptyTempCo,
config->empty_tempco);
max17042_override_por(map, MAX17042_K_empty0,
@@ -929,7 +937,8 @@ max17042_get_default_pdata(struct max17042_chip *chip)
if (!pdata)
return pdata;
- if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17042) {
+ if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+ (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
pdata->init_data = max17047_default_pdata_init_regs;
pdata->num_init_data =
ARRAY_SIZE(max17047_default_pdata_init_regs);
@@ -1167,6 +1176,7 @@ static const struct of_device_id max17042_dt_match[] = {
{ .compatible = "maxim,max17042" },
{ .compatible = "maxim,max17047" },
{ .compatible = "maxim,max17050" },
+ { .compatible = "maxim,max17055" },
{ },
};
MODULE_DEVICE_TABLE(of, max17042_dt_match);
@@ -1176,6 +1186,7 @@ static const struct i2c_device_id max17042_id[] = {
{ "max17042", MAXIM_DEVICE_TYPE_MAX17042 },
{ "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
{ "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
+ { "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
{ }
};
MODULE_DEVICE_TABLE(i2c, max17042_id);
diff --git a/drivers/power/supply/max77650-charger.c b/drivers/power/supply/max77650-charger.c
index 5f9477c5cf5a..d913428bedc0 100644
--- a/drivers/power/supply/max77650-charger.c
+++ b/drivers/power/supply/max77650-charger.c
@@ -354,9 +354,16 @@ static int max77650_charger_remove(struct platform_device *pdev)
return max77650_charger_disable(chg);
}
+static const struct of_device_id max77650_charger_of_match[] = {
+ { .compatible = "maxim,max77650-charger" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, max77650_charger_of_match);
+
static struct platform_driver max77650_charger_driver = {
.driver = {
.name = "max77650-charger",
+ .of_match_table = max77650_charger_of_match,
},
.probe = max77650_charger_probe,
.remove = max77650_charger_remove,
diff --git a/drivers/power/supply/pda_power.c b/drivers/power/supply/pda_power.c
index 3ae5707d39fa..03a37fd6be27 100644
--- a/drivers/power/supply/pda_power.c
+++ b/drivers/power/supply/pda_power.c
@@ -429,6 +429,10 @@ wrongid:
static int pda_power_remove(struct platform_device *pdev)
{
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier)
+ usb_unregister_notifier(transceiver, &otg_nb);
+#endif
if (pdata->is_usb_online && usb_irq)
free_irq(usb_irq->start, pda_psy_usb);
if (pdata->is_ac_online && ac_irq)
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 5c36c430ce8b..1a9a9fae73d3 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -565,9 +565,11 @@ EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
int power_supply_get_battery_info(struct power_supply *psy,
struct power_supply_battery_info *info)
{
+ struct power_supply_resistance_temp_table *resist_table;
struct device_node *battery_np;
const char *value;
int err, len, index;
+ const __be32 *list;
info->energy_full_design_uwh = -EINVAL;
info->charge_full_design_uah = -EINVAL;
@@ -578,6 +580,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
info->constant_charge_current_max_ua = -EINVAL;
info->constant_charge_voltage_max_uv = -EINVAL;
info->factory_internal_resistance_uohm = -EINVAL;
+ info->resist_table = NULL;
for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
info->ocv_table[index] = NULL;
@@ -644,7 +647,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
for (index = 0; index < len; index++) {
struct power_supply_battery_ocv_table *table;
char *propname;
- const __be32 *list;
int i, tab_len, size;
propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index);
@@ -677,6 +679,26 @@ int power_supply_get_battery_info(struct power_supply *psy,
}
}
+ list = of_get_property(battery_np, "resistance-temp-table", &len);
+ if (!list || !len)
+ goto out_put_node;
+
+ info->resist_table_size = len / (2 * sizeof(__be32));
+ resist_table = info->resist_table = devm_kcalloc(&psy->dev,
+ info->resist_table_size,
+ sizeof(*resist_table),
+ GFP_KERNEL);
+ if (!info->resist_table) {
+ power_supply_put_battery_info(psy, info);
+ err = -ENOMEM;
+ goto out_put_node;
+ }
+
+ for (index = 0; index < info->resist_table_size; index++) {
+ resist_table[index].temp = be32_to_cpu(*list++);
+ resist_table[index].resistance = be32_to_cpu(*list++);
+ }
+
out_put_node:
of_node_put(battery_np);
return err;
@@ -692,10 +714,53 @@ void power_supply_put_battery_info(struct power_supply *psy,
if (info->ocv_table[i])
devm_kfree(&psy->dev, info->ocv_table[i]);
}
+
+ if (info->resist_table)
+ devm_kfree(&psy->dev, info->resist_table);
}
EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
/**
+ * power_supply_temp2resist_simple() - find the battery internal resistance
+ * percent
+ * @table: Pointer to battery resistance temperature table
+ * @table_len: The table length
+ * @ocv: Current temperature
+ *
+ * This helper function is used to look up battery internal resistance percent
+ * according to current temperature value from the resistance temperature table,
+ * and the table must be ordered descending. Then the actual battery internal
+ * resistance = the ideal battery internal resistance * percent / 100.
+ *
+ * Return: the battery internal resistance percent
+ */
+int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
+ int table_len, int temp)
+{
+ int i, resist;
+
+ for (i = 0; i < table_len; i++)
+ if (temp > table[i].temp)
+ break;
+
+ if (i > 0 && i < table_len) {
+ int tmp;
+
+ tmp = (table[i - 1].resistance - table[i].resistance) *
+ (temp - table[i].temp);
+ tmp /= table[i - 1].temp - table[i].temp;
+ resist = tmp + table[i].resistance;
+ } else if (i == 0) {
+ resist = table[0].resistance;
+ } else {
+ resist = table[table_len - 1].resistance;
+ }
+
+ return resist;
+}
+EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
+
+/**
* power_supply_ocv2cap_simple() - find the battery capacity
* @table: Pointer to battery OCV lookup table
* @table_len: OCV table length
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c
index f8d74e9f7931..6acd242eed48 100644
--- a/drivers/power/supply/sbs-battery.c
+++ b/drivers/power/supply/sbs-battery.c
@@ -5,6 +5,7 @@
* Copyright (c) 2010, NVIDIA Corporation.
*/
+#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/gpio/consumer.h>
@@ -46,10 +47,10 @@ enum {
/* Battery Mode defines */
#define BATTERY_MODE_OFFSET 0x03
-#define BATTERY_MODE_MASK 0x8000
-enum sbs_battery_mode {
- BATTERY_MODE_AMPS = 0,
- BATTERY_MODE_WATTS = 0x8000
+#define BATTERY_MODE_CAPACITY_MASK BIT(15)
+enum sbs_capacity_mode {
+ CAPACITY_MODE_AMPS = 0,
+ CAPACITY_MODE_WATTS = BATTERY_MODE_CAPACITY_MASK
};
/* manufacturer access defines */
@@ -518,8 +519,8 @@ static void sbs_unit_adjustment(struct i2c_client *client,
}
}
-static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
- enum sbs_battery_mode mode)
+static enum sbs_capacity_mode sbs_set_capacity_mode(struct i2c_client *client,
+ enum sbs_capacity_mode mode)
{
int ret, original_val;
@@ -527,13 +528,13 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
if (original_val < 0)
return original_val;
- if ((original_val & BATTERY_MODE_MASK) == mode)
+ if ((original_val & BATTERY_MODE_CAPACITY_MASK) == mode)
return mode;
- if (mode == BATTERY_MODE_AMPS)
- ret = original_val & ~BATTERY_MODE_MASK;
+ if (mode == CAPACITY_MODE_AMPS)
+ ret = original_val & ~BATTERY_MODE_CAPACITY_MASK;
else
- ret = original_val | BATTERY_MODE_MASK;
+ ret = original_val | BATTERY_MODE_CAPACITY_MASK;
ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret);
if (ret < 0)
@@ -541,7 +542,7 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
usleep_range(1000, 2000);
- return original_val & BATTERY_MODE_MASK;
+ return original_val & BATTERY_MODE_CAPACITY_MASK;
}
static int sbs_get_battery_capacity(struct i2c_client *client,
@@ -549,13 +550,13 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
union power_supply_propval *val)
{
s32 ret;
- enum sbs_battery_mode mode = BATTERY_MODE_WATTS;
+ enum sbs_capacity_mode mode = CAPACITY_MODE_WATTS;
if (power_supply_is_amp_property(psp))
- mode = BATTERY_MODE_AMPS;
+ mode = CAPACITY_MODE_AMPS;
- mode = sbs_set_battery_mode(client, mode);
- if (mode < 0)
+ mode = sbs_set_capacity_mode(client, mode);
+ if ((int)mode < 0)
return mode;
ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
@@ -564,7 +565,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
val->intval = ret;
- ret = sbs_set_battery_mode(client, mode);
+ ret = sbs_set_capacity_mode(client, mode);
if (ret < 0)
return ret;
@@ -1001,6 +1002,6 @@ 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_param(force_load, bool, 0444);
MODULE_PARM_DESC(force_load,
"Attempt to load the driver even if no battery is connected");
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
index bc8f5bda5762..469c83fdaa8e 100644
--- a/drivers/power/supply/sc27xx_fuel_gauge.c
+++ b/drivers/power/supply/sc27xx_fuel_gauge.c
@@ -62,6 +62,8 @@
#define SC27XX_FGU_CUR_BASIC_ADC 8192
#define SC27XX_FGU_SAMPLE_HZ 2
+/* micro Ohms */
+#define SC27XX_FGU_IDEAL_RESISTANCE 20000
/*
* struct sc27xx_fgu_data: describe the FGU device
@@ -81,9 +83,12 @@
* @max_volt: the maximum constant input voltage in millivolt
* @min_volt: the minimum drained battery voltage in microvolt
* @table_len: the capacity table length
+ * @resist_table_len: the resistance table length
* @cur_1000ma_adc: ADC value corresponding to 1000 mA
* @vol_1000mv_adc: ADC value corresponding to 1000 mV
+ * @calib_resist: the real resistance of coulomb counter chip in uOhm
* @cap_table: capacity table with corresponding ocv
+ * @resist_table: resistance percent table with corresponding temperature
*/
struct sc27xx_fgu_data {
struct regmap *regmap;
@@ -103,15 +108,19 @@ struct sc27xx_fgu_data {
int max_volt;
int min_volt;
int table_len;
+ int resist_table_len;
int cur_1000ma_adc;
int vol_1000mv_adc;
+ int calib_resist;
struct power_supply_battery_ocv_table *cap_table;
+ struct power_supply_resistance_temp_table *resist_table;
};
static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity);
static void sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data,
int cap, bool int_mode);
static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap);
+static int sc27xx_fgu_get_temp(struct sc27xx_fgu_data *data, int *temp);
static const char * const sc27xx_charger_supply_name[] = {
"sc2731_charger",
@@ -434,7 +443,7 @@ static int sc27xx_fgu_get_current(struct sc27xx_fgu_data *data, int *val)
static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
{
- int vol, cur, ret;
+ int vol, cur, ret, temp, resistance;
ret = sc27xx_fgu_get_vbat_vol(data, &vol);
if (ret)
@@ -444,8 +453,19 @@ static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
if (ret)
return ret;
+ resistance = data->internal_resist;
+ if (data->resist_table_len > 0) {
+ ret = sc27xx_fgu_get_temp(data, &temp);
+ if (ret)
+ return ret;
+
+ resistance = power_supply_temp2resist_simple(data->resist_table,
+ data->resist_table_len, temp);
+ resistance = data->internal_resist * resistance / 100;
+ }
+
/* Return the battery OCV in micro volts. */
- *val = vol * 1000 - cur * data->internal_resist;
+ *val = vol * 1000 - cur * resistance;
return 0;
}
@@ -884,7 +904,9 @@ static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data)
*/
cal_4200mv = (calib_data & 0x1ff) + 6963 - 4096 - 256;
data->vol_1000mv_adc = DIV_ROUND_CLOSEST(cal_4200mv * 10, 42);
- data->cur_1000ma_adc = data->vol_1000mv_adc * 4;
+ data->cur_1000ma_adc =
+ DIV_ROUND_CLOSEST(data->vol_1000mv_adc * 4 * data->calib_resist,
+ SC27XX_FGU_IDEAL_RESISTANCE);
kfree(buf);
return 0;
@@ -929,6 +951,18 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data)
if (!data->alarm_cap)
data->alarm_cap += 1;
+ data->resist_table_len = info.resist_table_size;
+ if (data->resist_table_len > 0) {
+ data->resist_table = devm_kmemdup(data->dev, info.resist_table,
+ data->resist_table_len *
+ sizeof(struct power_supply_resistance_temp_table),
+ GFP_KERNEL);
+ if (!data->resist_table) {
+ power_supply_put_battery_info(data->battery, &info);
+ return -ENOMEM;
+ }
+ }
+
power_supply_put_battery_info(data->battery, &info);
ret = sc27xx_fgu_calibration(data);
@@ -1051,6 +1085,15 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
return ret;
}
+ ret = device_property_read_u32(&pdev->dev,
+ "sprd,calib-resistance-micro-ohms",
+ &data->calib_resist);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "failed to get fgu calibration resistance\n");
+ return ret;
+ }
+
data->channel = devm_iio_channel_get(dev, "bat-temp");
if (IS_ERR(data->channel)) {
dev_err(dev, "failed to get IIO channel\n");
diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c
index 1b80ae479e7d..cdb9a23d825f 100644
--- a/drivers/power/supply/ucs1002_power.c
+++ b/drivers/power/supply/ucs1002_power.c
@@ -100,7 +100,9 @@ struct ucs1002_info {
struct i2c_client *client;
struct regmap *regmap;
struct regulator_desc *regulator_descriptor;
+ struct regulator_dev *rdev;
bool present;
+ bool output_disable;
};
static enum power_supply_property ucs1002_props[] = {
@@ -233,6 +235,11 @@ static int ucs1002_get_max_current(struct ucs1002_info *info,
unsigned int reg;
int ret;
+ if (info->output_disable) {
+ val->intval = 0;
+ return 0;
+ }
+
ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, &reg);
if (ret)
return ret;
@@ -247,6 +254,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val)
unsigned int reg;
int ret, idx;
+ if (val == 0) {
+ info->output_disable = true;
+ regulator_disable_regmap(info->rdev);
+ return 0;
+ }
+
for (idx = 0; idx < ARRAY_SIZE(ucs1002_current_limit_uA); idx++) {
if (val == ucs1002_current_limit_uA[idx])
break;
@@ -270,6 +283,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val)
if (reg != idx)
return -EINVAL;
+ info->output_disable = false;
+
+ if (info->rdev && info->rdev->use_count &&
+ !regulator_is_enabled_regmap(info->rdev))
+ regulator_enable_regmap(info->rdev);
+
return 0;
}
@@ -470,9 +489,24 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data)
return IRQ_HANDLED;
}
+static int ucs1002_regulator_enable(struct regulator_dev *rdev)
+{
+ struct ucs1002_info *info = rdev_get_drvdata(rdev);
+
+ /*
+ * If the output is disabled due to 0 maximum current, just pretend the
+ * enable did work. The regulator will be enabled as soon as we get a
+ * a non-zero maximum current budget.
+ */
+ if (info->output_disable)
+ return 0;
+
+ return regulator_enable_regmap(rdev);
+}
+
static const struct regulator_ops ucs1002_regulator_ops = {
.is_enabled = regulator_is_enabled_regmap,
- .enable = regulator_enable_regmap,
+ .enable = ucs1002_regulator_enable,
.disable = regulator_disable_regmap,
};
@@ -499,7 +533,6 @@ static int ucs1002_probe(struct i2c_client *client,
};
struct regulator_config regulator_config = {};
int irq_a_det, irq_alert, ret;
- struct regulator_dev *rdev;
struct ucs1002_info *info;
unsigned int regval;
@@ -589,10 +622,11 @@ static int ucs1002_probe(struct i2c_client *client,
regulator_config.dev = dev;
regulator_config.of_node = dev->of_node;
regulator_config.regmap = info->regmap;
+ regulator_config.driver_data = info;
- rdev = devm_regulator_register(dev, info->regulator_descriptor,
+ info->rdev = devm_regulator_register(dev, info->regulator_descriptor,
&regulator_config);
- ret = PTR_ERR_OR_ZERO(rdev);
+ ret = PTR_ERR_OR_ZERO(info->rdev);
if (ret) {
dev_err(dev, "Failed to register VBUS regulator: %d\n", ret);
return ret;