summaryrefslogtreecommitdiff
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/reset/Kconfig11
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/qcom-pon.c91
-rw-r--r--drivers/power/reset/zx-reboot.c1
-rw-r--r--drivers/power/supply/Kconfig23
-rw-r--r--drivers/power/supply/Makefile2
-rw-r--r--drivers/power/supply/ab8500_fg.c14
-rw-r--r--drivers/power/supply/adp5061.c745
-rw-r--r--drivers/power/supply/axp20x_usb_power.c1
-rw-r--r--drivers/power/supply/bq27xxx_battery.c3
-rw-r--r--drivers/power/supply/cros_usbpd-charger.c545
-rw-r--r--drivers/power/supply/ds2760_battery.c348
-rw-r--r--drivers/power/supply/lego_ev3_battery.c20
-rw-r--r--drivers/power/supply/max1721x_battery.c2
-rw-r--r--drivers/power/supply/sbs-battery.c67
-rw-r--r--drivers/power/supply/tps65217_charger.c22
-rw-r--r--drivers/power/supply/wm8350_power.c3
17 files changed, 1762 insertions, 137 deletions
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index df58fc878b3e..6533aa560aa1 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -104,6 +104,17 @@ config POWER_RESET_MSM
help
Power off and restart support for Qualcomm boards.
+config POWER_RESET_QCOM_PON
+ tristate "Qualcomm power-on driver"
+ depends on ARCH_QCOM
+ depends on MFD_SPMI_PMIC
+ select REBOOT_MODE
+ help
+ Power On support for Qualcomm boards.
+ If you have a Qualcomm platform and need support for
+ power-on and reboot reason, Say Y.
+ If unsure, Say N.
+
config POWER_RESET_OCELOT_RESET
bool "Microsemi Ocelot reset driver"
depends on MSCC_OCELOT || COMPILE_TEST
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 7778c7485cf1..0aebee954ac1 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o
obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o
obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
+obj-$(CONFIG_POWER_RESET_QCOM_PON) += qcom-pon.o
obj-$(CONFIG_POWER_RESET_OCELOT_RESET) += ocelot-reset.o
obj-$(CONFIG_POWER_RESET_PIIX4_POWEROFF) += piix4-poweroff.o
obj-$(CONFIG_POWER_RESET_LTC2952) += ltc2952-poweroff.o
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
new file mode 100644
index 000000000000..0c4caaa7e88f
--- /dev/null
+++ b/drivers/power/reset/qcom-pon.c
@@ -0,0 +1,91 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (c) 2017-18 Linaro Limited
+
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/platform_device.h>
+#include <linux/reboot.h>
+#include <linux/reboot-mode.h>
+#include <linux/regmap.h>
+
+#define PON_SOFT_RB_SPARE 0x8f
+
+struct pm8916_pon {
+ struct device *dev;
+ struct regmap *regmap;
+ u32 baseaddr;
+ struct reboot_mode_driver reboot_mode;
+};
+
+static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ struct pm8916_pon *pon = container_of
+ (reboot, struct pm8916_pon, reboot_mode);
+ int ret;
+
+ ret = regmap_update_bits(pon->regmap,
+ pon->baseaddr + PON_SOFT_RB_SPARE,
+ 0xfc, magic << 2);
+ if (ret < 0)
+ dev_err(pon->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int pm8916_pon_probe(struct platform_device *pdev)
+{
+ struct pm8916_pon *pon;
+ int error;
+
+ pon = devm_kzalloc(&pdev->dev, sizeof(*pon), GFP_KERNEL);
+ if (!pon)
+ return -ENOMEM;
+
+ pon->dev = &pdev->dev;
+
+ pon->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!pon->regmap) {
+ dev_err(&pdev->dev, "failed to locate regmap\n");
+ return -ENODEV;
+ }
+
+ error = of_property_read_u32(pdev->dev.of_node, "reg",
+ &pon->baseaddr);
+ if (error)
+ return error;
+
+ pon->reboot_mode.dev = &pdev->dev;
+ pon->reboot_mode.write = pm8916_reboot_mode_write;
+ error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode);
+ if (error) {
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+ return error;
+ }
+
+ platform_set_drvdata(pdev, pon);
+
+ return devm_of_platform_populate(&pdev->dev);
+}
+
+static const struct of_device_id pm8916_pon_id_table[] = {
+ { .compatible = "qcom,pm8916-pon" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, pm8916_pon_id_table);
+
+static struct platform_driver pm8916_pon_driver = {
+ .probe = pm8916_pon_probe,
+ .driver = {
+ .name = "pm8916-pon",
+ .of_match_table = of_match_ptr(pm8916_pon_id_table),
+ },
+};
+module_platform_driver(pm8916_pon_driver);
+
+MODULE_DESCRIPTION("pm8916 Power On driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/reset/zx-reboot.c b/drivers/power/reset/zx-reboot.c
index c03e96e6a041..186901c96c01 100644
--- a/drivers/power/reset/zx-reboot.c
+++ b/drivers/power/reset/zx-reboot.c
@@ -51,6 +51,7 @@ static int zx_reboot_probe(struct platform_device *pdev)
np = of_find_compatible_node(NULL, NULL, "zte,zx296702-pcu");
pcu_base = of_iomap(np, 0);
+ of_node_put(np);
if (!pcu_base) {
iounmap(base);
WARN(1, "failed to map pcu_base address");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 428b426842f4..ff6dab0bf0dd 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -75,6 +75,17 @@ config BATTERY_88PM860X
help
Say Y here to enable battery monitor for Marvell 88PM860x chip.
+config CHARGER_ADP5061
+ tristate "ADP5061 battery charger driver"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y here to enable support for the ADP5061 standalone battery
+ charger.
+
+ This driver can be built as a module. If so, the module will be
+ called adp5061.
+
config BATTERY_ACT8945A
tristate "Active-semi ACT8945A charger driver"
depends on MFD_ACT8945A || COMPILE_TEST
@@ -92,7 +103,7 @@ config BATTERY_CPCAP
config BATTERY_DS2760
tristate "DS2760 battery driver (HP iPAQ & others)"
- depends on W1 && W1_SLAVE_DS2760
+ depends on W1
help
Say Y here to enable support for batteries with ds2760 chip.
@@ -624,4 +635,14 @@ config CHARGER_RT9455
help
Say Y to enable support for Richtek RT9455 battery charger.
+config CHARGER_CROS_USBPD
+ tristate "ChromeOS EC based USBPD charger"
+ depends on MFD_CROS_EC
+ default n
+ help
+ Say Y here to enable ChromeOS EC based USBPD charger
+ driver. This driver gets various bits of information about
+ what is connected to USB PD ports from the EC and converts
+ that into power_supply properties.
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index e83aa843bcc6..a26b402c45d9 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -18,6 +18,7 @@ obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
obj-$(CONFIG_TEST_POWER) += test_power.o
obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
+obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
@@ -83,3 +84,4 @@ obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
+obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
index d9c6c7bedd85..02356f9b5f22 100644
--- a/drivers/power/supply/ab8500_fg.c
+++ b/drivers/power/supply/ab8500_fg.c
@@ -379,15 +379,13 @@ static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr)
*/
static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample)
{
- struct timespec64 ts64;
+ time64_t now = ktime_get_boottime_seconds();
struct ab8500_fg_avg_cap *avg = &di->avg_cap;
- getnstimeofday64(&ts64);
-
do {
avg->sum += sample - avg->samples[avg->pos];
avg->samples[avg->pos] = sample;
- avg->time_stamps[avg->pos] = ts64.tv_sec;
+ avg->time_stamps[avg->pos] = now;
avg->pos++;
if (avg->pos == NBR_AVG_SAMPLES)
@@ -400,7 +398,7 @@ static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample)
* Check the time stamp for each sample. If too old,
* replace with latest sample
*/
- } while (ts64.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]);
+ } while (now - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]);
avg->avg = avg->sum / avg->nbr_samples;
@@ -439,14 +437,14 @@ static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di)
static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
{
int i;
- struct timespec64 ts64;
+ time64_t now;
struct ab8500_fg_avg_cap *avg = &di->avg_cap;
- getnstimeofday64(&ts64);
+ now = ktime_get_boottime_seconds();
for (i = 0; i < NBR_AVG_SAMPLES; i++) {
avg->samples[i] = sample;
- avg->time_stamps[i] = ts64.tv_sec;
+ avg->time_stamps[i] = now;
}
avg->pos = 0;
diff --git a/drivers/power/supply/adp5061.c b/drivers/power/supply/adp5061.c
new file mode 100644
index 000000000000..939fd3d8fb1a
--- /dev/null
+++ b/drivers/power/supply/adp5061.c
@@ -0,0 +1,745 @@
+/*
+ * ADP5061 I2C Programmable Linear Battery Charger
+ *
+ * Copyright 2018 Analog Devices Inc.
+ *
+ * Licensed under the GPL-2.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/pm.h>
+#include <linux/mod_devicetable.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+/* ADP5061 registers definition */
+#define ADP5061_ID 0x00
+#define ADP5061_REV 0x01
+#define ADP5061_VINX_SET 0x02
+#define ADP5061_TERM_SET 0x03
+#define ADP5061_CHG_CURR 0x04
+#define ADP5061_VOLTAGE_TH 0x05
+#define ADP5061_TIMER_SET 0x06
+#define ADP5061_FUNC_SET_1 0x07
+#define ADP5061_FUNC_SET_2 0x08
+#define ADP5061_INT_EN 0x09
+#define ADP5061_INT_ACT 0x0A
+#define ADP5061_CHG_STATUS_1 0x0B
+#define ADP5061_CHG_STATUS_2 0x0C
+#define ADP5061_FAULT 0x0D
+#define ADP5061_BATTERY_SHORT 0x10
+#define ADP5061_IEND 0x11
+
+/* ADP5061_VINX_SET */
+#define ADP5061_VINX_SET_ILIM_MSK GENMASK(3, 0)
+#define ADP5061_VINX_SET_ILIM_MODE(x) (((x) & 0x0F) << 0)
+
+/* ADP5061_TERM_SET */
+#define ADP5061_TERM_SET_VTRM_MSK GENMASK(7, 2)
+#define ADP5061_TERM_SET_VTRM_MODE(x) (((x) & 0x3F) << 2)
+#define ADP5061_TERM_SET_CHG_VLIM_MSK GENMASK(1, 0)
+#define ADP5061_TERM_SET_CHG_VLIM_MODE(x) (((x) & 0x03) << 0)
+
+/* ADP5061_CHG_CURR */
+#define ADP5061_CHG_CURR_ICHG_MSK GENMASK(6, 2)
+#define ADP5061_CHG_CURR_ICHG_MODE(x) (((x) & 0x1F) << 2)
+#define ADP5061_CHG_CURR_ITRK_DEAD_MSK GENMASK(1, 0)
+#define ADP5061_CHG_CURR_ITRK_DEAD_MODE(x) (((x) & 0x03) << 0)
+
+/* ADP5061_VOLTAGE_TH */
+#define ADP5061_VOLTAGE_TH_DIS_RCH_MSK BIT(7)
+#define ADP5061_VOLTAGE_TH_DIS_RCH_MODE(x) (((x) & 0x01) << 7)
+#define ADP5061_VOLTAGE_TH_VRCH_MSK GENMASK(6, 5)
+#define ADP5061_VOLTAGE_TH_VRCH_MODE(x) (((x) & 0x03) << 5)
+#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK GENMASK(4, 3)
+#define ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(x) (((x) & 0x03) << 3)
+#define ADP5061_VOLTAGE_TH_VWEAK_MSK GENMASK(2, 0)
+#define ADP5061_VOLTAGE_TH_VWEAK_MODE(x) (((x) & 0x07) << 0)
+
+/* ADP5061_CHG_STATUS_1 */
+#define ADP5061_CHG_STATUS_1_VIN_OV(x) (((x) >> 7) & 0x1)
+#define ADP5061_CHG_STATUS_1_VIN_OK(x) (((x) >> 6) & 0x1)
+#define ADP5061_CHG_STATUS_1_VIN_ILIM(x) (((x) >> 5) & 0x1)
+#define ADP5061_CHG_STATUS_1_THERM_LIM(x) (((x) >> 4) & 0x1)
+#define ADP5061_CHG_STATUS_1_CHDONE(x) (((x) >> 3) & 0x1)
+#define ADP5061_CHG_STATUS_1_CHG_STATUS(x) (((x) >> 0) & 0x7)
+
+/* ADP5061_CHG_STATUS_2 */
+#define ADP5061_CHG_STATUS_2_THR_STATUS(x) (((x) >> 5) & 0x7)
+#define ADP5061_CHG_STATUS_2_RCH_LIM_INFO(x) (((x) >> 3) & 0x1)
+#define ADP5061_CHG_STATUS_2_BAT_STATUS(x) (((x) >> 0) & 0x7)
+
+/* ADP5061_IEND */
+#define ADP5061_IEND_IEND_MSK GENMASK(7, 5)
+#define ADP5061_IEND_IEND_MODE(x) (((x) & 0x07) << 5)
+
+#define ADP5061_NO_BATTERY 0x01
+#define ADP5061_ICHG_MAX 1300 // mA
+
+enum adp5061_chg_status {
+ ADP5061_CHG_OFF,
+ ADP5061_CHG_TRICKLE,
+ ADP5061_CHG_FAST_CC,
+ ADP5061_CHG_FAST_CV,
+ ADP5061_CHG_COMPLETE,
+ ADP5061_CHG_LDO_MODE,
+ ADP5061_CHG_TIMER_EXP,
+ ADP5061_CHG_BAT_DET,
+};
+
+static const int adp5061_chg_type[4] = {
+ [ADP5061_CHG_OFF] = POWER_SUPPLY_CHARGE_TYPE_NONE,
+ [ADP5061_CHG_TRICKLE] = POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
+ [ADP5061_CHG_FAST_CC] = POWER_SUPPLY_CHARGE_TYPE_FAST,
+ [ADP5061_CHG_FAST_CV] = POWER_SUPPLY_CHARGE_TYPE_FAST,
+};
+
+static const int adp5061_vweak_th[8] = {
+ 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400,
+};
+
+static const int adp5061_prechg_current[4] = {
+ 5, 10, 20, 80,
+};
+
+static const int adp5061_vmin[4] = {
+ 2000, 2500, 2600, 2900,
+};
+
+static const int adp5061_const_chg_vmax[4] = {
+ 3200, 3400, 3700, 3800,
+};
+
+static const int adp5061_const_ichg[24] = {
+ 50, 100, 150, 200, 250, 300, 350, 400, 450, 500, 550, 600, 650,
+ 700, 750, 800, 850, 900, 950, 1000, 1050, 1100, 1200, 1300,
+};
+
+static const int adp5061_vmax[36] = {
+ 3800, 3820, 3840, 3860, 3880, 3900, 3920, 3940, 3960, 3980,
+ 4000, 4020, 4040, 4060, 4080, 4100, 4120, 4140, 4160, 4180,
+ 4200, 4220, 4240, 4260, 4280, 4300, 4320, 4340, 4360, 4380,
+ 4400, 4420, 4440, 4460, 4480, 4500,
+};
+
+static const int adp5061_in_current_lim[16] = {
+ 100, 150, 200, 250, 300, 400, 500, 600, 700,
+ 800, 900, 1000, 1200, 1500, 1800, 2100,
+};
+
+static const int adp5061_iend[8] = {
+ 12500, 32500, 52500, 72500, 92500, 117500, 142500, 170000,
+};
+
+struct adp5061_state {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct power_supply *psy;
+};
+
+static int adp5061_get_array_index(const int *array, u8 size, int val)
+{
+ int i;
+
+ for (i = 1; i < size; i++) {
+ if (val < array[i])
+ break;
+ }
+
+ return i-1;
+}
+
+static int adp5061_get_status(struct adp5061_state *st,
+ u8 *status1, u8 *status2)
+{
+ u8 buf[2];
+ int ret;
+
+ /* CHG_STATUS1 and CHG_STATUS2 are adjacent regs */
+ ret = regmap_bulk_read(st->regmap, ADP5061_CHG_STATUS_1,
+ &buf[0], 2);
+ if (ret < 0)
+ return ret;
+
+ *status1 = buf[0];
+ *status2 = buf[1];
+
+ return ret;
+}
+
+static int adp5061_get_input_current_limit(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int mode, ret;
+
+ ret = regmap_read(st->regmap, ADP5061_VINX_SET, &regval);
+ if (ret < 0)
+ return ret;
+
+ mode = ADP5061_VINX_SET_ILIM_MODE(regval);
+ val->intval = adp5061_in_current_lim[mode] * 1000;
+
+ return ret;
+}
+
+static int adp5061_set_input_current_limit(struct adp5061_state *st, int val)
+{
+ int index;
+
+ /* Convert from uA to mA */
+ val /= 1000;
+ index = adp5061_get_array_index(adp5061_in_current_lim,
+ ARRAY_SIZE(adp5061_in_current_lim),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_VINX_SET,
+ ADP5061_VINX_SET_ILIM_MSK,
+ ADP5061_VINX_SET_ILIM_MODE(index));
+}
+
+static int adp5061_set_min_voltage(struct adp5061_state *st, int val)
+{
+ int index;
+
+ /* Convert from uV to mV */
+ val /= 1000;
+ index = adp5061_get_array_index(adp5061_vmin,
+ ARRAY_SIZE(adp5061_vmin),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
+ ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK,
+ ADP5061_VOLTAGE_TH_VTRK_DEAD_MODE(index));
+}
+
+static int adp5061_get_min_voltage(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval = ((regval & ADP5061_VOLTAGE_TH_VTRK_DEAD_MSK) >> 3);
+ val->intval = adp5061_vmin[regval] * 1000;
+
+ return ret;
+}
+
+static int adp5061_get_chg_volt_lim(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int mode, ret;
+
+ ret = regmap_read(st->regmap, ADP5061_TERM_SET, &regval);
+ if (ret < 0)
+ return ret;
+
+ mode = ADP5061_TERM_SET_CHG_VLIM_MODE(regval);
+ val->intval = adp5061_const_chg_vmax[mode] * 1000;
+
+ return ret;
+}
+
+static int adp5061_get_max_voltage(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_TERM_SET, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval = ((regval & ADP5061_TERM_SET_VTRM_MSK) >> 2) - 0x0F;
+ if (regval >= ARRAY_SIZE(adp5061_vmax))
+ regval = ARRAY_SIZE(adp5061_vmax) - 1;
+
+ val->intval = adp5061_vmax[regval] * 1000;
+
+ return ret;
+}
+
+static int adp5061_set_max_voltage(struct adp5061_state *st, int val)
+{
+ int vmax_index;
+
+ /* Convert from uV to mV */
+ val /= 1000;
+ if (val > 4500)
+ val = 4500;
+
+ vmax_index = adp5061_get_array_index(adp5061_vmax,
+ ARRAY_SIZE(adp5061_vmax), val);
+ if (vmax_index < 0)
+ return vmax_index;
+
+ vmax_index += 0x0F;
+
+ return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
+ ADP5061_TERM_SET_VTRM_MSK,
+ ADP5061_TERM_SET_VTRM_MODE(vmax_index));
+}
+
+static int adp5061_set_const_chg_vmax(struct adp5061_state *st, int val)
+{
+ int index;
+
+ /* Convert from uV to mV */
+ val /= 1000;
+ index = adp5061_get_array_index(adp5061_const_chg_vmax,
+ ARRAY_SIZE(adp5061_const_chg_vmax),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_TERM_SET,
+ ADP5061_TERM_SET_CHG_VLIM_MSK,
+ ADP5061_TERM_SET_CHG_VLIM_MODE(index));
+}
+
+static int adp5061_set_const_chg_current(struct adp5061_state *st, int val)
+{
+
+ int index;
+
+ /* Convert from uA to mA */
+ val /= 1000;
+ if (val > ADP5061_ICHG_MAX)
+ val = ADP5061_ICHG_MAX;
+
+ index = adp5061_get_array_index(adp5061_const_ichg,
+ ARRAY_SIZE(adp5061_const_ichg),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
+ ADP5061_CHG_CURR_ICHG_MSK,
+ ADP5061_CHG_CURR_ICHG_MODE(index));
+}
+
+static int adp5061_get_const_chg_current(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_CHG_CURR, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval = ((regval & ADP5061_CHG_CURR_ICHG_MSK) >> 2);
+ if (regval >= ARRAY_SIZE(adp5061_const_ichg))
+ regval = ARRAY_SIZE(adp5061_const_ichg) - 1;
+
+ val->intval = adp5061_const_ichg[regval] * 1000;
+
+ return ret;
+}
+
+static int adp5061_get_prechg_current(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_CHG_CURR, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval &= ADP5061_CHG_CURR_ITRK_DEAD_MSK;
+ val->intval = adp5061_prechg_current[regval] * 1000;
+
+ return ret;
+}
+
+static int adp5061_set_prechg_current(struct adp5061_state *st, int val)
+{
+ int index;
+
+ /* Convert from uA to mA */
+ val /= 1000;
+ index = adp5061_get_array_index(adp5061_prechg_current,
+ ARRAY_SIZE(adp5061_prechg_current),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_CHG_CURR,
+ ADP5061_CHG_CURR_ITRK_DEAD_MSK,
+ ADP5061_CHG_CURR_ITRK_DEAD_MODE(index));
+}
+
+static int adp5061_get_vweak_th(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_VOLTAGE_TH, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval &= ADP5061_VOLTAGE_TH_VWEAK_MSK;
+ val->intval = adp5061_vweak_th[regval] * 1000;
+
+ return ret;
+}
+
+static int adp5061_set_vweak_th(struct adp5061_state *st, int val)
+{
+ int index;
+
+ /* Convert from uV to mV */
+ val /= 1000;
+ index = adp5061_get_array_index(adp5061_vweak_th,
+ ARRAY_SIZE(adp5061_vweak_th),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_VOLTAGE_TH,
+ ADP5061_VOLTAGE_TH_VWEAK_MSK,
+ ADP5061_VOLTAGE_TH_VWEAK_MODE(index));
+}
+
+static int adp5061_get_chg_type(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ u8 status1, status2;
+ int chg_type, ret;
+
+ ret = adp5061_get_status(st, &status1, &status2);
+ if (ret < 0)
+ return ret;
+
+ chg_type = adp5061_chg_type[ADP5061_CHG_STATUS_1_CHG_STATUS(status1)];
+ if (chg_type > ADP5061_CHG_FAST_CV)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else
+ val->intval = chg_type;
+
+ return ret;
+}
+
+static int adp5061_get_charger_status(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ u8 status1, status2;
+ int ret;
+
+ ret = adp5061_get_status(st, &status1, &status2);
+ if (ret < 0)
+ return ret;
+
+ switch (ADP5061_CHG_STATUS_1_CHG_STATUS(status1)) {
+ case ADP5061_CHG_OFF:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case ADP5061_CHG_TRICKLE:
+ case ADP5061_CHG_FAST_CC:
+ case ADP5061_CHG_FAST_CV:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case ADP5061_CHG_COMPLETE:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case ADP5061_CHG_TIMER_EXP:
+ /* The battery must be discharging if there is a charge fault */
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ return ret;
+}
+
+static int adp5061_get_battery_status(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ u8 status1, status2;
+ int ret;
+
+ ret = adp5061_get_status(st, &status1, &status2);
+ if (ret < 0)
+ return ret;
+
+ switch (ADP5061_CHG_STATUS_2_BAT_STATUS(status2)) {
+ case 0x0: /* Battery monitor off */
+ case 0x1: /* No battery */
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ break;
+ case 0x2: /* VBAT < VTRK */
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ break;
+ case 0x3: /* VTRK < VBAT_SNS < VWEAK */
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ break;
+ case 0x4: /* VBAT_SNS > VWEAK */
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ }
+
+ return ret;
+}
+
+static int adp5061_get_termination_current(struct adp5061_state *st,
+ union power_supply_propval *val)
+{
+ unsigned int regval;
+ int ret;
+
+ ret = regmap_read(st->regmap, ADP5061_IEND, &regval);
+ if (ret < 0)
+ return ret;
+
+ regval = (regval & ADP5061_IEND_IEND_MSK) >> 5;
+ val->intval = adp5061_iend[regval];
+
+ return ret;
+}
+
+static int adp5061_set_termination_current(struct adp5061_state *st, int val)
+{
+ int index;
+
+ index = adp5061_get_array_index(adp5061_iend,
+ ARRAY_SIZE(adp5061_iend),
+ val);
+ if (index < 0)
+ return index;
+
+ return regmap_update_bits(st->regmap, ADP5061_IEND,
+ ADP5061_IEND_IEND_MSK,
+ ADP5061_IEND_IEND_MODE(index));
+}
+
+static int adp5061_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct adp5061_state *st = power_supply_get_drvdata(psy);
+ u8 status1, status2;
+ int mode, ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = adp5061_get_status(st, &status1, &status2);
+ if (ret < 0)
+ return ret;
+
+ mode = ADP5061_CHG_STATUS_2_BAT_STATUS(status2);
+ if (mode == ADP5061_NO_BATTERY)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ return adp5061_get_chg_type(st, val);
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ /* This property is used to indicate the input current
+ * limit into VINx (ILIM)
+ */
+ return adp5061_get_input_current_limit(st, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ /* This property is used to indicate the termination
+ * voltage (VTRM)
+ */
+ return adp5061_get_max_voltage(st, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ /*
+ * This property is used to indicate the trickle to fast
+ * charge threshold (VTRK_DEAD)
+ */
+ return adp5061_get_min_voltage(st, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ /* This property is used to indicate the charging
+ * voltage limit (CHG_VLIM)
+ */
+ return adp5061_get_chg_volt_lim(st, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ /*
+ * This property is used to indicate the value of the constant
+ * current charge (ICHG)
+ */
+ return adp5061_get_const_chg_current(st, val);
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ /*
+ * This property is used to indicate the value of the trickle
+ * and weak charge currents (ITRK_DEAD)
+ */
+ return adp5061_get_prechg_current(st, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /*
+ * This property is used to set the VWEAK threshold
+ * bellow this value, weak charge mode is entered
+ * above this value, fast chargerge mode is entered
+ */
+ return adp5061_get_vweak_th(st, val);
+ case POWER_SUPPLY_PROP_STATUS:
+ /*
+ * Indicate the charger status in relation to power
+ * supply status property
+ */
+ return adp5061_get_charger_status(st, val);
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ /*
+ * Indicate the battery status in relation to power
+ * supply capacity level property
+ */
+ return adp5061_get_battery_status(st, val);
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ /* Indicate the values of the termination current */
+ return adp5061_get_termination_current(st, val);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int adp5061_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct adp5061_state *st = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return adp5061_set_input_current_limit(st, val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return adp5061_set_max_voltage(st, val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ return adp5061_set_min_voltage(st, val->intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return adp5061_set_const_chg_vmax(st, val->intval);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return adp5061_set_const_chg_current(st, val->intval);
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ return adp5061_set_prechg_current(st, val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ return adp5061_set_vweak_th(st, val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ return adp5061_set_termination_current(st, val->intval);
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int adp5061_prop_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_PRECHARGE_CURRENT:
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static enum power_supply_property adp5061_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_PRECHARGE_CURRENT,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
+};
+
+static const struct regmap_config adp5061_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+};
+
+static const struct power_supply_desc adp5061_desc = {
+ .name = "adp5061",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .get_property = adp5061_get_property,
+ .set_property = adp5061_set_property,
+ .property_is_writeable = adp5061_prop_writeable,
+ .properties = adp5061_props,
+ .num_properties = ARRAY_SIZE(adp5061_props),
+};
+
+static int adp5061_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct power_supply_config psy_cfg = {};
+ struct adp5061_state *st;
+
+ st = devm_kzalloc(&client->dev, sizeof(*st), GFP_KERNEL);
+ if (!st)
+ return -ENOMEM;
+
+ st->client = client;
+ st->regmap = devm_regmap_init_i2c(client,
+ &adp5061_regmap_config);
+ if (IS_ERR(st->regmap)) {
+ dev_err(&client->dev, "Failed to initialize register map\n");
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, st);
+ psy_cfg.drv_data = st;
+
+ st->psy = devm_power_supply_register(&client->dev,
+ &adp5061_desc,
+ &psy_cfg);
+
+ if (IS_ERR(st->psy)) {
+ dev_err(&client->dev, "Failed to register power supply\n");
+ return PTR_ERR(st->psy);
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id adp5061_id[] = {
+ { "adp5061", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, adp5061_id);
+
+static struct i2c_driver adp5061_driver = {
+ .driver = {
+ .name = KBUILD_MODNAME,
+ },
+ .probe = adp5061_probe,
+ .id_table = adp5061_id,
+};
+module_i2c_driver(adp5061_driver);
+
+MODULE_DESCRIPTION("Analog Devices adp5061 battery charger driver");
+MODULE_AUTHOR("Stefan Popa <stefan.popa@analog.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
index 44f70dcea61e..42001df4bd13 100644
--- a/drivers/power/supply/axp20x_usb_power.c
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -222,6 +222,7 @@ static int axp20x_usb_power_set_current_max(struct axp20x_usb_power *power,
case 100000:
if (power->axp20x_id == AXP221_ID)
return -EINVAL;
+ /* fall through */
case 500000:
case 900000:
val = (900000 - intval) / 400000;
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
index d44ed8e17c47..f022e1b550df 100644
--- a/drivers/power/supply/bq27xxx_battery.c
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -26,7 +26,6 @@
* http://www.ti.com/product/bq27510-g1
* http://www.ti.com/product/bq27510-g2
* http://www.ti.com/product/bq27510-g3
- * http://www.ti.com/product/bq27520-g4
* http://www.ti.com/product/bq27520-g1
* http://www.ti.com/product/bq27520-g2
* http://www.ti.com/product/bq27520-g3
@@ -40,7 +39,9 @@
* http://www.ti.com/product/bq27545-g1
* http://www.ti.com/product/bq27421-g1
* http://www.ti.com/product/bq27425-g1
+ * http://www.ti.com/product/bq27426
* http://www.ti.com/product/bq27411-g1
+ * http://www.ti.com/product/bq27441-g1
* http://www.ti.com/product/bq27621-g1
*/
diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c
new file mode 100644
index 000000000000..688a16bacfbb
--- /dev/null
+++ b/drivers/power/supply/cros_usbpd-charger.c
@@ -0,0 +1,545 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Power supply driver for ChromeOS EC based USB PD Charger.
+ *
+ * Copyright (c) 2014 - 2018 Google, Inc
+ */
+
+#include <linux/module.h>
+#include <linux/mfd/cros_ec.h>
+#include <linux/mfd/cros_ec_commands.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#define CHARGER_DIR_NAME "CROS_USBPD_CHARGER%d"
+#define CHARGER_DIR_NAME_LENGTH sizeof(CHARGER_DIR_NAME)
+#define CHARGER_CACHE_UPDATE_DELAY msecs_to_jiffies(500)
+#define CHARGER_MANUFACTURER_MODEL_LENGTH 32
+
+#define DRV_NAME "cros-usbpd-charger"
+
+struct port_data {
+ int port_number;
+ char name[CHARGER_DIR_NAME_LENGTH];
+ char manufacturer[CHARGER_MANUFACTURER_MODEL_LENGTH];
+ char model_name[CHARGER_MANUFACTURER_MODEL_LENGTH];
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ int psy_usb_type;
+ int psy_online;
+ int psy_status;
+ int psy_current_max;
+ int psy_voltage_max_design;
+ int psy_voltage_now;
+ int psy_power_max;
+ struct charger_data *charger;
+ unsigned long last_update;
+};
+
+struct charger_data {
+ struct device *dev;
+ struct cros_ec_dev *ec_dev;
+ struct cros_ec_device *ec_device;
+ int num_charger_ports;
+ int num_registered_psy;
+ struct port_data *ports[EC_USB_PD_MAX_PORTS];
+ struct notifier_block notifier;
+};
+
+static enum power_supply_property cros_usbpd_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_USB_TYPE
+};
+
+static enum power_supply_usb_type cros_usbpd_charger_usb_types[] = {
+ POWER_SUPPLY_USB_TYPE_UNKNOWN,
+ POWER_SUPPLY_USB_TYPE_SDP,
+ POWER_SUPPLY_USB_TYPE_DCP,
+ POWER_SUPPLY_USB_TYPE_CDP,
+ POWER_SUPPLY_USB_TYPE_C,
+ POWER_SUPPLY_USB_TYPE_PD,
+ POWER_SUPPLY_USB_TYPE_PD_DRP,
+ POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID
+};
+
+static int cros_usbpd_charger_ec_command(struct charger_data *charger,
+ unsigned int version,
+ unsigned int command,
+ void *outdata,
+ unsigned int outsize,
+ void *indata,
+ unsigned int insize)
+{
+ struct cros_ec_dev *ec_dev = charger->ec_dev;
+ struct cros_ec_command *msg;
+ int ret;
+
+ msg = kzalloc(sizeof(*msg) + max(outsize, insize), GFP_KERNEL);
+ if (!msg)
+ return -ENOMEM;
+
+ msg->version = version;
+ msg->command = ec_dev->cmd_offset + command;
+ msg->outsize = outsize;
+ msg->insize = insize;
+
+ if (outsize)
+ memcpy(msg->data, outdata, outsize);
+
+ ret = cros_ec_cmd_xfer_status(charger->ec_device, msg);
+ if (ret >= 0 && insize)
+ memcpy(indata, msg->data, insize);
+
+ kfree(msg);
+ return ret;
+}
+
+static int cros_usbpd_charger_get_num_ports(struct charger_data *charger)
+{
+ struct ec_response_usb_pd_ports resp;
+ int ret;
+
+ 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);
+ return ret;
+ }
+
+ return resp.num_ports;
+}
+
+static int cros_usbpd_charger_get_discovery_info(struct port_data *port)
+{
+ struct charger_data *charger = port->charger;
+ struct ec_params_usb_pd_discovery_entry resp;
+ struct ec_params_usb_pd_info_request req;
+ int ret;
+
+ req.port = port->port_number;
+
+ ret = cros_usbpd_charger_ec_command(charger, 0,
+ EC_CMD_USB_PD_DISCOVERY,
+ &req, sizeof(req),
+ &resp, sizeof(resp));
+ if (ret < 0) {
+ dev_err(charger->dev,
+ "Unable to query discovery info (err:0x%x)\n", ret);
+ return ret;
+ }
+
+ dev_dbg(charger->dev, "Port %d: VID = 0x%x, PID=0x%x, PTYPE=0x%x\n",
+ port->port_number, resp.vid, resp.pid, resp.ptype);
+
+ snprintf(port->manufacturer, sizeof(port->manufacturer), "%x",
+ resp.vid);
+ snprintf(port->model_name, sizeof(port->model_name), "%x", resp.pid);
+
+ return 0;
+}
+
+static int cros_usbpd_charger_get_power_info(struct port_data *port)
+{
+ struct charger_data *charger = port->charger;
+ struct ec_response_usb_pd_power_info resp;
+ struct ec_params_usb_pd_power_info req;
+ int last_psy_status, last_psy_usb_type;
+ struct device *dev = charger->dev;
+ int ret;
+
+ req.port = port->port_number;
+ ret = cros_usbpd_charger_ec_command(charger, 0,
+ EC_CMD_USB_PD_POWER_INFO,
+ &req, sizeof(req),
+ &resp, sizeof(resp));
+ if (ret < 0) {
+ dev_err(dev, "Unable to query PD power info (err:0x%x)\n", ret);
+ return ret;
+ }
+
+ last_psy_status = port->psy_status;
+ last_psy_usb_type = port->psy_usb_type;
+
+ switch (resp.role) {
+ case USB_PD_PORT_POWER_DISCONNECTED:
+ port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ port->psy_online = 0;
+ break;
+ case USB_PD_PORT_POWER_SOURCE:
+ port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ port->psy_online = 0;
+ break;
+ case USB_PD_PORT_POWER_SINK:
+ port->psy_status = POWER_SUPPLY_STATUS_CHARGING;
+ port->psy_online = 1;
+ break;
+ case USB_PD_PORT_POWER_SINK_NOT_CHARGING:
+ port->psy_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ port->psy_online = 1;
+ break;
+ default:
+ dev_err(dev, "Unknown role %d\n", resp.role);
+ break;
+ }
+
+ port->psy_voltage_max_design = resp.meas.voltage_max;
+ port->psy_voltage_now = resp.meas.voltage_now;
+ port->psy_current_max = resp.meas.current_max;
+ port->psy_power_max = resp.max_power;
+
+ switch (resp.type) {
+ case USB_CHG_TYPE_BC12_SDP:
+ case USB_CHG_TYPE_VBUS:
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ break;
+ case USB_CHG_TYPE_NONE:
+ /*
+ * For dual-role devices when we are a source, the firmware
+ * reports the type as NONE. Report such chargers as type
+ * USB_PD_DRP.
+ */
+ if (resp.role == USB_PD_PORT_POWER_SOURCE && resp.dualrole)
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD_DRP;
+ else
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ break;
+ case USB_CHG_TYPE_OTHER:
+ case USB_CHG_TYPE_PROPRIETARY:
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID;
+ break;
+ case USB_CHG_TYPE_C:
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_C;
+ break;
+ case USB_CHG_TYPE_BC12_DCP:
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_DCP;
+ break;
+ case USB_CHG_TYPE_BC12_CDP:
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_CDP;
+ break;
+ case USB_CHG_TYPE_PD:
+ if (resp.dualrole)
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD_DRP;
+ else
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_PD;
+ break;
+ case USB_CHG_TYPE_UNKNOWN:
+ /*
+ * While the EC is trying to determine the type of charger that
+ * has been plugged in, it will report the charger type as
+ * unknown. Additionally since the power capabilities are
+ * unknown, report the max current and voltage as zero.
+ */
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
+ port->psy_voltage_max_design = 0;
+ port->psy_current_max = 0;
+ break;
+ default:
+ dev_err(dev, "Port %d: default case!\n", port->port_number);
+ port->psy_usb_type = POWER_SUPPLY_USB_TYPE_SDP;
+ }
+
+ port->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+
+ dev_dbg(dev,
+ "Port %d: type=%d vmax=%d vnow=%d cmax=%d clim=%d pmax=%d\n",
+ port->port_number, resp.type, resp.meas.voltage_max,
+ resp.meas.voltage_now, resp.meas.current_max,
+ resp.meas.current_lim, resp.max_power);
+
+ /*
+ * If power supply type or status changed, explicitly call
+ * power_supply_changed. This results in udev event getting generated
+ * and allows user mode apps to react quicker instead of waiting for
+ * their next poll of power supply status.
+ */
+ if (last_psy_usb_type != port->psy_usb_type ||
+ last_psy_status != port->psy_status)
+ power_supply_changed(port->psy);
+
+ return 0;
+}
+
+static int cros_usbpd_charger_get_port_status(struct port_data *port,
+ bool ratelimit)
+{
+ int ret;
+
+ if (ratelimit &&
+ time_is_after_jiffies(port->last_update +
+ CHARGER_CACHE_UPDATE_DELAY))
+ return 0;
+
+ ret = cros_usbpd_charger_get_power_info(port);
+ if (ret < 0)
+ return ret;
+
+ ret = cros_usbpd_charger_get_discovery_info(port);
+ port->last_update = jiffies;
+
+ return ret;
+}
+
+static void cros_usbpd_charger_power_changed(struct power_supply *psy)
+{
+ struct port_data *port = power_supply_get_drvdata(psy);
+ struct charger_data *charger = port->charger;
+ int i;
+
+ for (i = 0; i < charger->num_registered_psy; i++)
+ cros_usbpd_charger_get_port_status(charger->ports[i], false);
+}
+
+static int cros_usbpd_charger_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct port_data *port = power_supply_get_drvdata(psy);
+ struct charger_data *charger = port->charger;
+ struct cros_ec_device *ec_device = charger->ec_device;
+ struct device *dev = charger->dev;
+ int ret;
+
+ /* Only refresh ec_port_status for dynamic properties */
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ /*
+ * If mkbp_event_supported, then we can be assured that
+ * the driver's state for the online property is consistent
+ * with the hardware. However, if we aren't event driven,
+ * the optimization before to skip an ec_port_status get
+ * and only returned cached values of the online property will
+ * cause a delay in detecting a cable attach until one of the
+ * other properties are read.
+ *
+ * Allow an ec_port_status refresh for online property check
+ * if we're not already online to check for plug events if
+ * not mkbp_event_supported.
+ */
+ if (ec_device->mkbp_event_supported || port->psy_online)
+ break;
+ /* fall through */
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = cros_usbpd_charger_get_port_status(port, true);
+ if (ret < 0) {
+ dev_err(dev, "Failed to get port status (err:0x%x)\n",
+ ret);
+ return -EINVAL;
+ }
+ break;
+ default:
+ break;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = port->psy_online;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = port->psy_status;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = port->psy_current_max * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = port->psy_voltage_max_design * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = port->psy_voltage_now * 1000;
+ break;
+ case POWER_SUPPLY_PROP_USB_TYPE:
+ val->intval = port->psy_usb_type;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = port->model_name;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = port->manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int cros_usbpd_charger_ec_event(struct notifier_block *nb,
+ unsigned long queued_during_suspend,
+ void *_notify)
+{
+ struct cros_ec_device *ec_device;
+ struct charger_data *charger;
+ struct device *dev;
+ u32 host_event;
+
+ charger = container_of(nb, struct charger_data, notifier);
+ ec_device = charger->ec_device;
+ dev = charger->dev;
+
+ host_event = cros_ec_get_host_event(ec_device);
+ if (host_event & EC_HOST_EVENT_MASK(EC_HOST_EVENT_PD_MCU)) {
+ cros_usbpd_charger_power_changed(charger->ports[0]->psy);
+ return NOTIFY_OK;
+ } else {
+ return NOTIFY_DONE;
+ }
+}
+
+static void cros_usbpd_charger_unregister_notifier(void *data)
+{
+ struct charger_data *charger = data;
+ struct cros_ec_device *ec_device = charger->ec_device;
+
+ blocking_notifier_chain_unregister(&ec_device->event_notifier,
+ &charger->notifier);
+}
+
+static int cros_usbpd_charger_probe(struct platform_device *pd)
+{
+ struct cros_ec_dev *ec_dev = dev_get_drvdata(pd->dev.parent);
+ struct cros_ec_device *ec_device = ec_dev->ec_dev;
+ struct power_supply_desc *psy_desc;
+ struct device *dev = &pd->dev;
+ struct charger_data *charger;
+ struct power_supply *psy;
+ struct port_data *port;
+ int ret = -EINVAL;
+ int i;
+
+ charger = devm_kzalloc(dev, sizeof(struct charger_data),
+ GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ charger->dev = dev;
+ charger->ec_dev = ec_dev;
+ charger->ec_device = ec_device;
+
+ platform_set_drvdata(pd, charger);
+
+ charger->num_charger_ports = cros_usbpd_charger_get_num_ports(charger);
+ if (charger->num_charger_ports <= 0) {
+ /*
+ * This can happen on a system that doesn't support USB PD.
+ * Log a message, but no need to warn.
+ */
+ dev_info(dev, "No charging ports found\n");
+ ret = -ENODEV;
+ goto fail_nowarn;
+ }
+
+ for (i = 0; i < charger->num_charger_ports; i++) {
+ struct power_supply_config psy_cfg = {};
+
+ port = devm_kzalloc(dev, sizeof(struct port_data), GFP_KERNEL);
+ if (!port) {
+ ret = -ENOMEM;
+ goto fail;
+ }
+
+ port->charger = charger;
+ port->port_number = i;
+ sprintf(port->name, CHARGER_DIR_NAME, i);
+
+ psy_desc = &port->psy_desc;
+ psy_desc->name = port->name;
+ psy_desc->type = POWER_SUPPLY_TYPE_USB;
+ psy_desc->get_property = cros_usbpd_charger_get_prop;
+ psy_desc->external_power_changed =
+ cros_usbpd_charger_power_changed;
+ psy_desc->properties = cros_usbpd_charger_props;
+ psy_desc->num_properties =
+ ARRAY_SIZE(cros_usbpd_charger_props);
+ psy_desc->usb_types = cros_usbpd_charger_usb_types;
+ psy_desc->num_usb_types =
+ ARRAY_SIZE(cros_usbpd_charger_usb_types);
+ psy_cfg.drv_data = port;
+
+ psy = devm_power_supply_register_no_ws(dev, psy_desc,
+ &psy_cfg);
+ if (IS_ERR(psy)) {
+ dev_err(dev, "Failed to register power supply\n");
+ continue;
+ }
+ port->psy = psy;
+
+ charger->ports[charger->num_registered_psy++] = port;
+ }
+
+ if (!charger->num_registered_psy) {
+ ret = -ENODEV;
+ dev_err(dev, "No power supplies registered\n");
+ goto fail;
+ }
+
+ if (ec_device->mkbp_event_supported) {
+ /* Get PD events from the EC */
+ charger->notifier.notifier_call = cros_usbpd_charger_ec_event;
+ ret = blocking_notifier_chain_register(
+ &ec_device->event_notifier,
+ &charger->notifier);
+ if (ret < 0) {
+ dev_warn(dev, "failed to register notifier\n");
+ } else {
+ ret = devm_add_action_or_reset(dev,
+ cros_usbpd_charger_unregister_notifier,
+ charger);
+ if (ret < 0)
+ goto fail;
+ }
+ }
+
+ return 0;
+
+fail:
+ WARN(1, "%s: Failing probe (err:0x%x)\n", dev_name(dev), ret);
+
+fail_nowarn:
+ dev_info(dev, "Failing probe (err:0x%x)\n", ret);
+ return ret;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int cros_usbpd_charger_resume(struct device *dev)
+{
+ struct charger_data *charger = dev_get_drvdata(dev);
+ int i;
+
+ if (!charger)
+ return 0;
+
+ for (i = 0; i < charger->num_registered_psy; i++) {
+ power_supply_changed(charger->ports[i]->psy);
+ charger->ports[i]->last_update =
+ jiffies - CHARGER_CACHE_UPDATE_DELAY;
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(cros_usbpd_charger_pm_ops, NULL,
+ cros_usbpd_charger_resume);
+
+static struct platform_driver cros_usbpd_charger_driver = {
+ .driver = {
+ .name = DRV_NAME,
+ .pm = &cros_usbpd_charger_pm_ops,
+ },
+ .probe = cros_usbpd_charger_probe
+};
+
+module_platform_driver(cros_usbpd_charger_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("ChromeOS EC USBPD charger");
+MODULE_ALIAS("platform:" DRV_NAME);
diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c
index ae180dc929c9..11bed88a89fa 100644
--- a/drivers/power/supply/ds2760_battery.c
+++ b/drivers/power/supply/ds2760_battery.c
@@ -27,9 +27,64 @@
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
-
+#include <linux/suspend.h>
#include <linux/w1.h>
-#include "../../w1/slaves/w1_ds2760.h"
+#include <linux/of.h>
+
+static unsigned int cache_time = 1000;
+module_param(cache_time, uint, 0644);
+MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+
+static bool pmod_enabled;
+module_param(pmod_enabled, bool, 0644);
+MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit");
+
+static unsigned int rated_capacity;
+module_param(rated_capacity, uint, 0644);
+MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index");
+
+static unsigned int current_accum;
+module_param(current_accum, uint, 0644);
+MODULE_PARM_DESC(current_accum, "current accumulator value");
+
+#define W1_FAMILY_DS2760 0x30
+
+/* Known commands to the DS2760 chip */
+#define W1_DS2760_SWAP 0xAA
+#define W1_DS2760_READ_DATA 0x69
+#define W1_DS2760_WRITE_DATA 0x6C
+#define W1_DS2760_COPY_DATA 0x48
+#define W1_DS2760_RECALL_DATA 0xB8
+#define W1_DS2760_LOCK 0x6A
+
+/* Number of valid register addresses */
+#define DS2760_DATA_SIZE 0x40
+
+#define DS2760_PROTECTION_REG 0x00
+
+#define DS2760_STATUS_REG 0x01
+#define DS2760_STATUS_IE (1 << 2)
+#define DS2760_STATUS_SWEN (1 << 3)
+#define DS2760_STATUS_RNAOP (1 << 4)
+#define DS2760_STATUS_PMOD (1 << 5)
+
+#define DS2760_EEPROM_REG 0x07
+#define DS2760_SPECIAL_FEATURE_REG 0x08
+#define DS2760_VOLTAGE_MSB 0x0c
+#define DS2760_VOLTAGE_LSB 0x0d
+#define DS2760_CURRENT_MSB 0x0e
+#define DS2760_CURRENT_LSB 0x0f
+#define DS2760_CURRENT_ACCUM_MSB 0x10
+#define DS2760_CURRENT_ACCUM_LSB 0x11
+#define DS2760_TEMP_MSB 0x18
+#define DS2760_TEMP_LSB 0x19
+#define DS2760_EEPROM_BLOCK0 0x20
+#define DS2760_ACTIVE_FULL 0x20
+#define DS2760_EEPROM_BLOCK1 0x30
+#define DS2760_STATUS_WRITE_REG 0x31
+#define DS2760_RATED_CAPACITY 0x32
+#define DS2760_CURRENT_OFFSET_BIAS 0x33
+#define DS2760_ACTIVE_EMPTY 0x3b
struct ds2760_device_info {
struct device *dev;
@@ -55,28 +110,113 @@ struct ds2760_device_info {
int full_counter;
struct power_supply *bat;
struct power_supply_desc bat_desc;
- struct device *w1_dev;
struct workqueue_struct *monitor_wqueue;
struct delayed_work monitor_work;
struct delayed_work set_charged_work;
+ struct notifier_block pm_notifier;
};
-static unsigned int cache_time = 1000;
-module_param(cache_time, uint, 0644);
-MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+static int w1_ds2760_io(struct device *dev, char *buf, int addr, size_t count,
+ int io)
+{
+ struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
-static bool pmod_enabled;
-module_param(pmod_enabled, bool, 0644);
-MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit");
+ if (!dev)
+ return 0;
-static unsigned int rated_capacity;
-module_param(rated_capacity, uint, 0644);
-MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index");
+ mutex_lock(&sl->master->bus_mutex);
-static unsigned int current_accum;
-module_param(current_accum, uint, 0644);
-MODULE_PARM_DESC(current_accum, "current accumulator value");
+ if (addr > DS2760_DATA_SIZE || addr < 0) {
+ count = 0;
+ goto out;
+ }
+ if (addr + count > DS2760_DATA_SIZE)
+ count = DS2760_DATA_SIZE - addr;
+
+ if (!w1_reset_select_slave(sl)) {
+ if (!io) {
+ w1_write_8(sl->master, W1_DS2760_READ_DATA);
+ w1_write_8(sl->master, addr);
+ count = w1_read_block(sl->master, buf, count);
+ } else {
+ w1_write_8(sl->master, W1_DS2760_WRITE_DATA);
+ w1_write_8(sl->master, addr);
+ w1_write_block(sl->master, buf, count);
+ /* XXX w1_write_block returns void, not n_written */
+ }
+ }
+
+out:
+ mutex_unlock(&sl->master->bus_mutex);
+
+ return count;
+}
+
+static int w1_ds2760_read(struct device *dev,
+ char *buf, int addr,
+ size_t count)
+{
+ return w1_ds2760_io(dev, buf, addr, count, 0);
+}
+
+static int w1_ds2760_write(struct device *dev,
+ char *buf,
+ int addr, size_t count)
+{
+ return w1_ds2760_io(dev, buf, addr, count, 1);
+}
+
+static int w1_ds2760_eeprom_cmd(struct device *dev, int addr, int cmd)
+{
+ struct w1_slave *sl = container_of(dev, struct w1_slave, dev);
+
+ if (!dev)
+ return -EINVAL;
+
+ mutex_lock(&sl->master->bus_mutex);
+
+ if (w1_reset_select_slave(sl) == 0) {
+ w1_write_8(sl->master, cmd);
+ w1_write_8(sl->master, addr);
+ }
+
+ mutex_unlock(&sl->master->bus_mutex);
+ return 0;
+}
+
+static int w1_ds2760_store_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_COPY_DATA);
+}
+
+static int w1_ds2760_recall_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2760_eeprom_cmd(dev, addr, W1_DS2760_RECALL_DATA);
+}
+
+static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *bin_attr, char *buf,
+ loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ return w1_ds2760_read(dev, buf, off, count);
+}
+
+static BIN_ATTR_RO(w1_slave, DS2760_DATA_SIZE);
+
+static struct bin_attribute *w1_ds2760_bin_attrs[] = {
+ &bin_attr_w1_slave,
+ NULL,
+};
+
+static const struct attribute_group w1_ds2760_group = {
+ .bin_attrs = w1_ds2760_bin_attrs,
+};
+static const struct attribute_group *w1_ds2760_groups[] = {
+ &w1_ds2760_group,
+ NULL,
+};
/* Some batteries have their rated capacity stored a N * 10 mAh, while
* others use an index into this table. */
static int rated_capacities[] = {
@@ -138,10 +278,10 @@ static int ds2760_battery_read_status(struct ds2760_device_info *di)
count = DS2760_TEMP_LSB - start + 1;
}
- ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count);
+ ret = w1_ds2760_read(di->dev, di->raw + start, start, count);
if (ret != count) {
dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n",
- di->w1_dev);
+ di->dev);
return 1;
}
@@ -242,7 +382,7 @@ static void ds2760_battery_set_current_accum(struct ds2760_device_info *di,
acr[0] = acr_val >> 8;
acr[1] = acr_val & 0xff;
- if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2)
+ if (w1_ds2760_write(di->dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2)
dev_warn(di->dev, "ACR write failed\n");
}
@@ -297,9 +437,9 @@ static void ds2760_battery_write_status(struct ds2760_device_info *di,
if (status == di->raw[DS2760_STATUS_REG])
return;
- w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1);
- w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
- w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_write(di->dev, &status, DS2760_STATUS_WRITE_REG, 1);
+ w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
}
static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di,
@@ -308,9 +448,9 @@ static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di,
if (rated_capacity == di->raw[DS2760_RATED_CAPACITY])
return;
- w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1);
- w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
- w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_write(di->dev, &rated_capacity, DS2760_RATED_CAPACITY, 1);
+ w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
}
static void ds2760_battery_write_active_full(struct ds2760_device_info *di,
@@ -325,9 +465,9 @@ static void ds2760_battery_write_active_full(struct ds2760_device_info *di,
tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1])
return;
- w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp));
- w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
- w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
+ w1_ds2760_write(di->dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp));
+ w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK0);
+ w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK0);
/* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL
* values won't be read back by ds2760_battery_read_status() */
@@ -383,9 +523,9 @@ static void ds2760_battery_set_charged_work(struct work_struct *work)
dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias);
- w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1);
- w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
- w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_write(di->dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1);
+ w1_ds2760_store_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->dev, DS2760_EEPROM_BLOCK1);
/* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS
* value won't be read back by ds2760_battery_read_status() */
@@ -504,24 +644,55 @@ static enum power_supply_property ds2760_battery_props[] = {
POWER_SUPPLY_PROP_CAPACITY,
};
-static int ds2760_battery_probe(struct platform_device *pdev)
+static int ds2760_pm_notifier(struct notifier_block *notifier,
+ unsigned long pm_event,
+ void *unused)
+{
+ struct ds2760_device_info *di =
+ container_of(notifier, struct ds2760_device_info, pm_notifier);
+
+ switch (pm_event) {
+ case PM_HIBERNATION_PREPARE:
+ case PM_SUSPEND_PREPARE:
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+
+ case PM_POST_RESTORE:
+ case PM_POST_HIBERNATION:
+ case PM_POST_SUSPEND:
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ power_supply_changed(di->bat);
+ mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ);
+
+ break;
+
+ case PM_RESTORE_PREPARE:
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static int w1_ds2760_add_slave(struct w1_slave *sl)
{
struct power_supply_config psy_cfg = {};
- char status;
- int retval = 0;
struct ds2760_device_info *di;
+ struct device *dev = &sl->dev;
+ int retval = 0;
+ char name[32];
+ char status;
- di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ di = devm_kzalloc(dev, sizeof(*di), GFP_KERNEL);
if (!di) {
retval = -ENOMEM;
goto di_alloc_failed;
}
- platform_set_drvdata(pdev, di);
+ snprintf(name, sizeof(name), "ds2760-battery.%d", dev->id);
- di->dev = &pdev->dev;
- di->w1_dev = pdev->dev.parent;
- di->bat_desc.name = dev_name(&pdev->dev);
+ di->dev = dev;
+ di->bat_desc.name = name;
di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
di->bat_desc.properties = ds2760_battery_props;
di->bat_desc.num_properties = ARRAY_SIZE(ds2760_battery_props);
@@ -533,10 +704,30 @@ static int ds2760_battery_probe(struct platform_device *pdev)
di->bat_desc.external_power_changed =
ds2760_battery_external_power_changed;
- psy_cfg.drv_data = di;
+ psy_cfg.drv_data = di;
+
+ if (dev->of_node) {
+ u32 tmp;
+
+ psy_cfg.of_node = dev->of_node;
+
+ if (!of_property_read_bool(dev->of_node, "maxim,pmod-enabled"))
+ pmod_enabled = true;
+
+ if (!of_property_read_u32(dev->of_node,
+ "maxim,cache-time-ms", &tmp))
+ cache_time = tmp;
+
+ if (!of_property_read_u32(dev->of_node,
+ "rated-capacity-microamp-hours",
+ &tmp))
+ rated_capacity = tmp / 10; /* property is in mAh */
+ }
di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ sl->family_data = di;
+
/* enable sleep mode feature */
ds2760_battery_read_status(di);
status = di->raw[DS2760_STATUS_REG];
@@ -547,7 +738,7 @@ static int ds2760_battery_probe(struct platform_device *pdev)
ds2760_battery_write_status(di, status);
- /* set rated capacity from module param */
+ /* set rated capacity from module param or device tree */
if (rated_capacity)
ds2760_battery_write_rated_capacity(di, rated_capacity);
@@ -556,7 +747,7 @@ static int ds2760_battery_probe(struct platform_device *pdev)
if (current_accum)
ds2760_battery_set_current_accum(di, current_accum);
- di->bat = power_supply_register(&pdev->dev, &di->bat_desc, &psy_cfg);
+ di->bat = power_supply_register(dev, &di->bat_desc, &psy_cfg);
if (IS_ERR(di->bat)) {
dev_err(di->dev, "failed to register battery\n");
retval = PTR_ERR(di->bat);
@@ -566,14 +757,16 @@ static int ds2760_battery_probe(struct platform_device *pdev)
INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work);
INIT_DELAYED_WORK(&di->set_charged_work,
ds2760_battery_set_charged_work);
- di->monitor_wqueue = alloc_ordered_workqueue(dev_name(&pdev->dev),
- WQ_MEM_RECLAIM);
+ di->monitor_wqueue = alloc_ordered_workqueue(name, WQ_MEM_RECLAIM);
if (!di->monitor_wqueue) {
retval = -ESRCH;
goto workqueue_failed;
}
queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1);
+ di->pm_notifier.notifier_call = ds2760_pm_notifier;
+ register_pm_notifier(&di->pm_notifier);
+
goto success;
workqueue_failed:
@@ -584,65 +777,40 @@ success:
return retval;
}
-static int ds2760_battery_remove(struct platform_device *pdev)
+static void w1_ds2760_remove_slave(struct w1_slave *sl)
{
- struct ds2760_device_info *di = platform_get_drvdata(pdev);
+ struct ds2760_device_info *di = sl->family_data;
+ unregister_pm_notifier(&di->pm_notifier);
cancel_delayed_work_sync(&di->monitor_work);
cancel_delayed_work_sync(&di->set_charged_work);
destroy_workqueue(di->monitor_wqueue);
power_supply_unregister(di->bat);
-
- return 0;
-}
-
-#ifdef CONFIG_PM
-
-static int ds2760_battery_suspend(struct platform_device *pdev,
- pm_message_t state)
-{
- struct ds2760_device_info *di = platform_get_drvdata(pdev);
-
- di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
-
- return 0;
-}
-
-static int ds2760_battery_resume(struct platform_device *pdev)
-{
- struct ds2760_device_info *di = platform_get_drvdata(pdev);
-
- di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
- power_supply_changed(di->bat);
-
- mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ);
-
- return 0;
}
-#else
-
-#define ds2760_battery_suspend NULL
-#define ds2760_battery_resume NULL
-
-#endif /* CONFIG_PM */
-
-MODULE_ALIAS("platform:ds2760-battery");
+#ifdef CONFIG_OF
+static const struct of_device_id w1_ds2760_of_ids[] = {
+ { .compatible = "maxim,ds2760" },
+ {}
+};
+#endif
-static struct platform_driver ds2760_battery_driver = {
- .driver = {
- .name = "ds2760-battery",
- },
- .probe = ds2760_battery_probe,
- .remove = ds2760_battery_remove,
- .suspend = ds2760_battery_suspend,
- .resume = ds2760_battery_resume,
+static struct w1_family_ops w1_ds2760_fops = {
+ .add_slave = w1_ds2760_add_slave,
+ .remove_slave = w1_ds2760_remove_slave,
+ .groups = w1_ds2760_groups,
};
-module_platform_driver(ds2760_battery_driver);
+static struct w1_family w1_ds2760_family = {
+ .fid = W1_FAMILY_DS2760,
+ .fops = &w1_ds2760_fops,
+ .of_match_table = of_match_ptr(w1_ds2760_of_ids),
+};
+module_w1_family(w1_ds2760_family);
-MODULE_LICENSE("GPL");
MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, "
"Matt Reimer <mreimer@vpop.net>, "
"Anton Vorontsov <cbou@mail.ru>");
-MODULE_DESCRIPTION("ds2760 battery driver");
+MODULE_DESCRIPTION("1-wire Driver Dallas 2760 battery monitor chip");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("w1-family-" __stringify(W1_FAMILY_DS2760));
diff --git a/drivers/power/supply/lego_ev3_battery.c b/drivers/power/supply/lego_ev3_battery.c
index 7b993d669f7f..1ae3710909b7 100644
--- a/drivers/power/supply/lego_ev3_battery.c
+++ b/drivers/power/supply/lego_ev3_battery.c
@@ -39,7 +39,7 @@ static int lego_ev3_battery_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct lego_ev3_battery *batt = power_supply_get_drvdata(psy);
- int val2;
+ int ret, val2;
switch (psp) {
case POWER_SUPPLY_PROP_TECHNOLOGY:
@@ -47,11 +47,18 @@ static int lego_ev3_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
/* battery voltage is iio channel * 2 + Vce of transistor */
- iio_read_channel_processed(batt->iio_v, &val->intval);
+ ret = iio_read_channel_processed(batt->iio_v, &val->intval);
+ if (ret)
+ return ret;
+
val->intval *= 2000;
- val->intval += 200000;
+ val->intval += 50000;
+
/* plus adjust for shunt resistor drop */
- iio_read_channel_processed(batt->iio_i, &val2);
+ ret = iio_read_channel_processed(batt->iio_i, &val2);
+ if (ret)
+ return ret;
+
val2 *= 1000;
val2 /= 15;
val->intval += val2;
@@ -64,7 +71,10 @@ static int lego_ev3_battery_get_property(struct power_supply *psy,
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
/* battery current is iio channel / 15 / 0.05 ohms */
- iio_read_channel_processed(batt->iio_i, &val->intval);
+ ret = iio_read_channel_processed(batt->iio_i, &val->intval);
+ if (ret)
+ return ret;
+
val->intval *= 20000;
val->intval /= 15;
break;
diff --git a/drivers/power/supply/max1721x_battery.c b/drivers/power/supply/max1721x_battery.c
index 9ee601a03d9b..9ca895b0dabb 100644
--- a/drivers/power/supply/max1721x_battery.c
+++ b/drivers/power/supply/max1721x_battery.c
@@ -372,7 +372,7 @@ static int devm_w1_max1721x_add_device(struct w1_slave *sl)
}
if (!info->rsense) {
- dev_warn(info->w1_dev, "RSenese not calibrated, set 10 mOhms!\n");
+ dev_warn(info->w1_dev, "RSense not calibrated, set 10 mOhms!\n");
info->rsense = 1000; /* in regs in 10^-5 */
}
dev_info(info->w1_dev, "RSense: %d mOhms.\n", info->rsense / 100);
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c
index 83d7b4115857..8ba6abf584de 100644
--- a/drivers/power/supply/sbs-battery.c
+++ b/drivers/power/supply/sbs-battery.c
@@ -23,6 +23,7 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
+#include <linux/of_device.h>
#include <linux/power/sbs-battery.h>
#include <linux/power_supply.h>
#include <linux/slab.h>
@@ -156,6 +157,9 @@ static enum power_supply_property sbs_properties[] = {
POWER_SUPPLY_PROP_MODEL_NAME
};
+/* Supports special manufacturer commands from TI BQ20Z75 IC. */
+#define SBS_FLAGS_TI_BQ20Z75 BIT(0)
+
struct sbs_info {
struct i2c_client *client;
struct power_supply *power_supply;
@@ -168,6 +172,7 @@ struct sbs_info {
u32 poll_retry_count;
struct delayed_work work;
struct mutex mode_lock;
+ u32 flags;
};
static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
@@ -316,16 +321,40 @@ static int sbs_get_battery_presence_and_health(
struct i2c_client *client, enum power_supply_property psp,
union power_supply_propval *val)
{
+ int ret;
+
+ if (psp == POWER_SUPPLY_PROP_PRESENT) {
+ /* Dummy command; if it succeeds, battery is present. */
+ ret = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
+ if (ret < 0)
+ val->intval = 0; /* battery disconnected */
+ else
+ val->intval = 1; /* battery present */
+ } else { /* POWER_SUPPLY_PROP_HEALTH */
+ /* SBS spec doesn't have a general health command. */
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int sbs_get_ti_battery_presence_and_health(
+ struct i2c_client *client, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
s32 ret;
/*
* Write to ManufacturerAccess with ManufacturerAccess command
- * and then read the status. Do not check for error on the write
- * since not all batteries implement write access to this command,
- * while others mandate it.
+ * and then read the status.
*/
- sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
- MANUFACTURER_ACCESS_STATUS);
+ ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_STATUS);
+ if (ret < 0) {
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ val->intval = 0; /* battery removed */
+ return ret;
+ }
ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr);
if (ret < 0) {
@@ -600,7 +629,12 @@ static int sbs_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_HEALTH:
- ret = sbs_get_battery_presence_and_health(client, psp, val);
+ if (client->flags & SBS_FLAGS_TI_BQ20Z75)
+ ret = sbs_get_ti_battery_presence_and_health(client,
+ psp, val);
+ else
+ ret = sbs_get_battery_presence_and_health(client, psp,
+ val);
if (psp == POWER_SUPPLY_PROP_PRESENT)
return 0;
break;
@@ -806,6 +840,7 @@ static int sbs_probe(struct i2c_client *client,
if (!chip)
return -ENOMEM;
+ chip->flags = (u32)(uintptr_t)of_device_get_match_data(&client->dev);
chip->client = client;
chip->enable_detection = false;
psy_cfg.of_node = client->dev.of_node;
@@ -911,16 +946,19 @@ static int sbs_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct sbs_info *chip = i2c_get_clientdata(client);
+ int ret;
if (chip->poll_time > 0)
cancel_delayed_work_sync(&chip->work);
- /*
- * Write to manufacturer access with sleep command.
- * Support is manufacturer dependend, so ignore errors.
- */
- sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
- MANUFACTURER_ACCESS_SLEEP);
+ if (chip->flags & SBS_FLAGS_TI_BQ20Z75) {
+ /* Write to manufacturer access with sleep command. */
+ ret = sbs_write_word_data(client,
+ sbs_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_SLEEP);
+ if (chip->is_present && ret < 0)
+ return ret;
+ }
return 0;
}
@@ -941,7 +979,10 @@ MODULE_DEVICE_TABLE(i2c, sbs_id);
static const struct of_device_id sbs_dt_ids[] = {
{ .compatible = "sbs,sbs-battery" },
- { .compatible = "ti,bq20z75" },
+ {
+ .compatible = "ti,bq20z75",
+ .data = (void *)SBS_FLAGS_TI_BQ20Z75,
+ },
{ }
};
MODULE_DEVICE_TABLE(of, sbs_dt_ids);
diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c
index 1f5234098aaf..814c2b81fdfe 100644
--- a/drivers/power/supply/tps65217_charger.c
+++ b/drivers/power/supply/tps65217_charger.c
@@ -1,20 +1,8 @@
-/*
- * Battery charger driver for TI's tps65217
- *
- * Copyright (c) 2015, Collabora Ltd.
-
- * This program is free software; you can redistribute it and/or modify it
- * under the terms and conditions of the GNU General Public License,
- * version 2, as published by the Free Software Foundation.
-
- * This program is distributed in the hope 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.
-
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
+// SPDX-License-Identifier: GPL-2.0
+// Battery charger driver for TI's tps65217
+//
+// Copyright (C) 2015 Collabora Ltd.
+// Author: Enric Balletbo i Serra <enric.balletbo@collabora.com>
/*
* Battery charger driver for TI's tps65217
diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c
index a2740cf57ad3..15c0ca15e2aa 100644
--- a/drivers/power/supply/wm8350_power.c
+++ b/drivers/power/supply/wm8350_power.c
@@ -230,7 +230,8 @@ static irqreturn_t wm8350_charger_handler(int irq, void *data)
case WM8350_IRQ_EXT_USB_FB:
case WM8350_IRQ_EXT_WALL_FB:
wm8350_charger_config(wm8350, policy);
- case WM8350_IRQ_EXT_BAT_FB: /* Fall through */
+ /* Fall through */
+ case WM8350_IRQ_EXT_BAT_FB:
power_supply_changed(power->battery);
power_supply_changed(power->usb);
power_supply_changed(power->ac);