aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/power
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power')
-rw-r--r--drivers/power/avs/smartreflex.c41
-rw-r--r--drivers/power/reset/Kconfig10
-rw-r--r--drivers/power/reset/Makefile1
-rw-r--r--drivers/power/reset/nvmem-reboot-mode.c76
-rw-r--r--drivers/power/reset/qcom-pon.c12
-rw-r--r--drivers/power/supply/Kconfig34
-rw-r--r--drivers/power/supply/Makefile3
-rw-r--r--drivers/power/supply/bd70528-charger.c743
-rw-r--r--drivers/power/supply/bq24190_charger.c2
-rw-r--r--drivers/power/supply/bq24257_charger.c2
-rw-r--r--drivers/power/supply/bq25890_charger.c2
-rw-r--r--drivers/power/supply/cros_usbpd-charger.c116
-rw-r--r--drivers/power/supply/max14656_charger_detector.c2
-rw-r--r--drivers/power/supply/max17040_battery.c2
-rw-r--r--drivers/power/supply/max17042_battery.c2
-rw-r--r--drivers/power/supply/olpc_battery.c1
-rw-r--r--drivers/power/supply/power_supply_core.c9
-rw-r--r--drivers/power/supply/power_supply_hwmon.c355
-rw-r--r--drivers/power/supply/power_supply_sysfs.c2
-rw-r--r--drivers/power/supply/rt5033_battery.c2
-rw-r--r--drivers/power/supply/rt9455_charger.c2
-rw-r--r--drivers/power/supply/sbs-manager.c2
-rw-r--r--drivers/power/supply/ucs1002_power.c2
-rw-r--r--drivers/power/supply/wilco-charger.c187
24 files changed, 1564 insertions, 46 deletions
diff --git a/drivers/power/avs/smartreflex.c b/drivers/power/avs/smartreflex.c
index c96c01e09740..4684e7df833a 100644
--- a/drivers/power/avs/smartreflex.c
+++ b/drivers/power/avs/smartreflex.c
@@ -899,38 +899,19 @@ static int omap_sr_probe(struct platform_device *pdev)
}
dev_info(&pdev->dev, "%s: SmartReflex driver initialized\n", __func__);
- if (!sr_dbg_dir) {
+ if (!sr_dbg_dir)
sr_dbg_dir = debugfs_create_dir("smartreflex", NULL);
- if (IS_ERR_OR_NULL(sr_dbg_dir)) {
- ret = PTR_ERR(sr_dbg_dir);
- pr_err("%s:sr debugfs dir creation failed(%d)\n",
- __func__, ret);
- goto err_list_del;
- }
- }
sr_info->dbg_dir = debugfs_create_dir(sr_info->name, sr_dbg_dir);
- if (IS_ERR_OR_NULL(sr_info->dbg_dir)) {
- dev_err(&pdev->dev, "%s: Unable to create debugfs directory\n",
- __func__);
- ret = PTR_ERR(sr_info->dbg_dir);
- goto err_debugfs;
- }
- (void) debugfs_create_file("autocomp", S_IRUGO | S_IWUSR,
- sr_info->dbg_dir, (void *)sr_info, &pm_sr_fops);
- (void) debugfs_create_x32("errweight", S_IRUGO, sr_info->dbg_dir,
- &sr_info->err_weight);
- (void) debugfs_create_x32("errmaxlimit", S_IRUGO, sr_info->dbg_dir,
- &sr_info->err_maxlimit);
+ debugfs_create_file("autocomp", S_IRUGO | S_IWUSR, sr_info->dbg_dir,
+ (void *)sr_info, &pm_sr_fops);
+ debugfs_create_x32("errweight", S_IRUGO, sr_info->dbg_dir,
+ &sr_info->err_weight);
+ debugfs_create_x32("errmaxlimit", S_IRUGO, sr_info->dbg_dir,
+ &sr_info->err_maxlimit);
nvalue_dir = debugfs_create_dir("nvalue", sr_info->dbg_dir);
- if (IS_ERR_OR_NULL(nvalue_dir)) {
- dev_err(&pdev->dev, "%s: Unable to create debugfs directory for n-values\n",
- __func__);
- ret = PTR_ERR(nvalue_dir);
- goto err_debugfs;
- }
if (sr_info->nvalue_count == 0 || !sr_info->nvalue_table) {
dev_warn(&pdev->dev, "%s: %s: No Voltage table for the corresponding vdd. Cannot create debugfs entries for n-values\n",
@@ -945,12 +926,12 @@ static int omap_sr_probe(struct platform_device *pdev)
snprintf(name, sizeof(name), "volt_%lu",
sr_info->nvalue_table[i].volt_nominal);
- (void) debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir,
- &(sr_info->nvalue_table[i].nvalue));
+ debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir,
+ &(sr_info->nvalue_table[i].nvalue));
snprintf(name, sizeof(name), "errminlimit_%lu",
sr_info->nvalue_table[i].volt_nominal);
- (void) debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir,
- &(sr_info->nvalue_table[i].errminlimit));
+ debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir,
+ &(sr_info->nvalue_table[i].errminlimit));
}
diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig
index 980951dff834..a564237278ff 100644
--- a/drivers/power/reset/Kconfig
+++ b/drivers/power/reset/Kconfig
@@ -246,5 +246,15 @@ config POWER_RESET_SC27XX
PMICs includes the SC2720, SC2721, SC2723, SC2730
and SC2731 chips.
+config NVMEM_REBOOT_MODE
+ tristate "Generic NVMEM reboot mode driver"
+ depends on OF
+ select REBOOT_MODE
+ help
+ Say y here will enable reboot mode driver. This will
+ get reboot mode arguments and store it in a NVMEM cell,
+ then the bootloader can read it and take different
+ action according to the mode.
+
endif
diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile
index 0aebee954ac1..85da3198e4e0 100644
--- a/drivers/power/reset/Makefile
+++ b/drivers/power/reset/Makefile
@@ -29,3 +29,4 @@ obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o
+obj-$(CONFIG_NVMEM_REBOOT_MODE) += nvmem-reboot-mode.o
diff --git a/drivers/power/reset/nvmem-reboot-mode.c b/drivers/power/reset/nvmem-reboot-mode.c
new file mode 100644
index 000000000000..e229308d43e2
--- /dev/null
+++ b/drivers/power/reset/nvmem-reboot-mode.c
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (c) Vaisala Oyj. All rights reserved.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/of.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/platform_device.h>
+#include <linux/reboot-mode.h>
+
+struct nvmem_reboot_mode {
+ struct reboot_mode_driver reboot;
+ struct nvmem_cell *cell;
+};
+
+static int nvmem_reboot_mode_write(struct reboot_mode_driver *reboot,
+ unsigned int magic)
+{
+ int ret;
+ struct nvmem_reboot_mode *nvmem_rbm;
+
+ nvmem_rbm = container_of(reboot, struct nvmem_reboot_mode, reboot);
+
+ ret = nvmem_cell_write(nvmem_rbm->cell, &magic, sizeof(magic));
+ if (ret < 0)
+ dev_err(reboot->dev, "update reboot mode bits failed\n");
+
+ return ret;
+}
+
+static int nvmem_reboot_mode_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct nvmem_reboot_mode *nvmem_rbm;
+
+ nvmem_rbm = devm_kzalloc(&pdev->dev, sizeof(*nvmem_rbm), GFP_KERNEL);
+ if (!nvmem_rbm)
+ return -ENOMEM;
+
+ nvmem_rbm->reboot.dev = &pdev->dev;
+ nvmem_rbm->reboot.write = nvmem_reboot_mode_write;
+
+ nvmem_rbm->cell = devm_nvmem_cell_get(&pdev->dev, "reboot-mode");
+ if (IS_ERR(nvmem_rbm->cell)) {
+ dev_err(&pdev->dev, "failed to get the nvmem cell reboot-mode\n");
+ return PTR_ERR(nvmem_rbm->cell);
+ }
+
+ ret = devm_reboot_mode_register(&pdev->dev, &nvmem_rbm->reboot);
+ if (ret)
+ dev_err(&pdev->dev, "can't register reboot mode\n");
+
+ return ret;
+}
+
+static const struct of_device_id nvmem_reboot_mode_of_match[] = {
+ { .compatible = "nvmem-reboot-mode" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, nvmem_reboot_mode_of_match);
+
+static struct platform_driver nvmem_reboot_mode_driver = {
+ .probe = nvmem_reboot_mode_probe,
+ .driver = {
+ .name = "nvmem-reboot-mode",
+ .of_match_table = nvmem_reboot_mode_of_match,
+ },
+};
+module_platform_driver(nvmem_reboot_mode_driver);
+
+MODULE_AUTHOR("Nandor Han <nandor.han@vaisala.com>");
+MODULE_DESCRIPTION("NVMEM reboot mode driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/reset/qcom-pon.c b/drivers/power/reset/qcom-pon.c
index 3fa1642d4c54..22a743a0bf28 100644
--- a/drivers/power/reset/qcom-pon.c
+++ b/drivers/power/reset/qcom-pon.c
@@ -14,11 +14,15 @@
#define PON_SOFT_RB_SPARE 0x8f
+#define GEN1_REASON_SHIFT 2
+#define GEN2_REASON_SHIFT 1
+
struct pm8916_pon {
struct device *dev;
struct regmap *regmap;
u32 baseaddr;
struct reboot_mode_driver reboot_mode;
+ long reason_shift;
};
static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
@@ -30,7 +34,7 @@ static int pm8916_reboot_mode_write(struct reboot_mode_driver *reboot,
ret = regmap_update_bits(pon->regmap,
pon->baseaddr + PON_SOFT_RB_SPARE,
- 0xfc, magic << 2);
+ 0xfc, magic << pon->reason_shift);
if (ret < 0)
dev_err(pon->dev, "update reboot mode bits failed\n");
@@ -60,6 +64,7 @@ static int pm8916_pon_probe(struct platform_device *pdev)
return error;
pon->reboot_mode.dev = &pdev->dev;
+ pon->reason_shift = (long)of_device_get_match_data(&pdev->dev);
pon->reboot_mode.write = pm8916_reboot_mode_write;
error = devm_reboot_mode_register(&pdev->dev, &pon->reboot_mode);
if (error) {
@@ -73,8 +78,9 @@ static int pm8916_pon_probe(struct platform_device *pdev)
}
static const struct of_device_id pm8916_pon_id_table[] = {
- { .compatible = "qcom,pm8916-pon" },
- { .compatible = "qcom,pms405-pon" },
+ { .compatible = "qcom,pm8916-pon", .data = (void *)GEN1_REASON_SHIFT },
+ { .compatible = "qcom,pms405-pon", .data = (void *)GEN1_REASON_SHIFT },
+ { .compatible = "qcom,pm8998-pon", .data = (void *)GEN2_REASON_SHIFT },
{ }
};
MODULE_DEVICE_TABLE(of, pm8916_pon_id_table);
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index dd7da41f230c..5d91b5160b41 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -15,6 +15,20 @@ config POWER_SUPPLY_DEBUG
Say Y here to enable debugging messages for power supply class
and drivers.
+config POWER_SUPPLY_HWMON
+ bool
+ prompt "Expose power supply sensors as hwmon device"
+ depends on HWMON=y || HWMON=POWER_SUPPLY
+ default y
+ help
+ This options enables API that allows sensors found on a
+ power supply device (current, voltage, temperature) to be
+ exposed as a hwmon device.
+
+ Say 'Y' here if you want power supplies to
+ have hwmon sysfs interface too.
+
+
config PDA_POWER
tristate "Generic PDA/phone power driver"
depends on !S390
@@ -152,7 +166,7 @@ config BATTERY_PMU
config BATTERY_OLPC
tristate "One Laptop Per Child battery"
- depends on X86_32 && OLPC
+ depends on OLPC_EC
help
Say Y to enable support for the battery on the OLPC laptop.
@@ -689,4 +703,22 @@ config CHARGER_UCS1002
Say Y to enable support for Microchip UCS1002 Programmable
USB Port Power Controller with Charger Emulation.
+config CHARGER_BD70528
+ tristate "ROHM bd70528 charger driver"
+ depends on MFD_ROHM_BD70528
+ default n
+ help
+ Say Y here to enable support for getting battery status
+ information and altering charger configurations from charger
+ block of the ROHM BD70528 Power Management IC.
+
+config CHARGER_WILCO
+ tristate "Wilco EC based charger for ChromeOS"
+ depends on WILCO_EC
+ help
+ Say Y here to enable control of the charging routines performed
+ by the Embedded Controller on the Chromebook named Wilco. Further
+ information can be found in
+ Documentation/ABI/testing/sysfs-class-power-wilco
+
endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index f208273f9686..96c2b74b36bf 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -6,6 +6,7 @@ power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o
power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
+obj-$(CONFIG_POWER_SUPPLY_HWMON) += power_supply_hwmon.o
obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
obj-$(CONFIG_PDA_POWER) += pda_power.o
@@ -90,3 +91,5 @@ obj-$(CONFIG_CHARGER_CROS_USBPD) += cros_usbpd-charger.o
obj-$(CONFIG_CHARGER_SC2731) += sc2731_charger.o
obj-$(CONFIG_FUEL_GAUGE_SC27XX) += sc27xx_fuel_gauge.o
obj-$(CONFIG_CHARGER_UCS1002) += ucs1002_power.o
+obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
+obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
diff --git a/drivers/power/supply/bd70528-charger.c b/drivers/power/supply/bd70528-charger.c
new file mode 100644
index 000000000000..1bb32b7226d7
--- /dev/null
+++ b/drivers/power/supply/bd70528-charger.c
@@ -0,0 +1,743 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+//
+// Copyright (C) 2018 ROHM Semiconductors
+//
+// power-supply driver for ROHM BD70528 PMIC
+
+/*
+ * BD70528 charger HW state machine.
+ *
+ * The thermal shutdown state is not drawn. From any other state but
+ * battery error and suspend it is possible to go to TSD/TMP states
+ * if temperature is out of bounds.
+ *
+ * CHG_RST = H
+ * or CHG_EN=L
+ * or (DCIN2_UVLO=L && DCIN1_UVLO=L)
+ * or (DCIN2_OVLO=H & DCIN1_UVKLO=L)
+ *
+ * +--------------+ +--------------+
+ * | | | |
+ * | Any state +-------> | Suspend |
+ * | | | |
+ * +--------------+ +------+-------+
+ * |
+ * CHG_EN = H && BAT_DET = H && |
+ * No errors (temp, bat_ov, UVLO, |
+ * OVLO...) |
+ * |
+ * BAT_OV or +---------v----------+
+ * (DBAT && TTRI) | |
+ * +-----------------+ Trickle Charge | <---------------+
+ * | | | |
+ * | +-------+------------+ |
+ * | | |
+ * | | ^ |
+ * | V_BAT > VTRI_TH | | VBAT < VTRI_TH - 50mV |
+ * | | | |
+ * | v | |
+ * | | |
+ * | BAT_OV or +----------+----+ |
+ * | (DBAT && TFST) | | |
+ * | +----------------+ Fast Charge | |
+ * | | | | |
+ * v v +----+----------+ |
+ * | |
+ *+----------------+ ILIM_DET=L | ^ ILIM_DET |
+ *| | & CV_DET=H | | or CV_DET=L |
+ *| Battery Error | & VBAT > | | or VBAT < VRECHG_TH |
+ *| | VRECHG_TH | | or IBAT > IFST/x |
+ *+----------------+ & IBAT < | | |
+ * IFST/x v | |
+ * ^ | |
+ * | +---------+-+ |
+ * | | | |
+ * +-------------------+ Top OFF | |
+ * BAT_OV = H or | | |
+ * (DBAT && TFST) +-----+-----+ |
+ * | |
+ * Stay top-off for 15s | |
+ * v |
+ * |
+ * +--------+ |
+ * | | |
+ * | Done +-------------------------+
+ * | |
+ * +--------+ VBAT < VRECHG_TH
+ */
+
+#include <linux/kernel.h>
+#include <linux/interrupt.h>
+#include <linux/mfd/rohm-bd70528.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#define CHG_STAT_SUSPEND 0x0
+#define CHG_STAT_TRICKLE 0x1
+#define CHG_STAT_FAST 0x3
+#define CHG_STAT_TOPOFF 0xe
+#define CHG_STAT_DONE 0xf
+#define CHG_STAT_OTP_TRICKLE 0x10
+#define CHG_STAT_OTP_FAST 0x11
+#define CHG_STAT_OTP_DONE 0x12
+#define CHG_STAT_TSD_TRICKLE 0x20
+#define CHG_STAT_TSD_FAST 0x21
+#define CHG_STAT_TSD_TOPOFF 0x22
+#define CHG_STAT_BAT_ERR 0x7f
+
+static const char *bd70528_charger_model = "BD70528";
+static const char *bd70528_charger_manufacturer = "ROHM Semiconductors";
+
+#define BD_ERR_IRQ_HND(_name_, _wrn_) \
+static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \
+{ \
+ struct power_supply *psy = (struct power_supply *)arg; \
+ \
+ power_supply_changed(psy); \
+ dev_err(&psy->dev, (_wrn_)); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_INFO_IRQ_HND(_name_, _wrn_) \
+static irqreturn_t bd0528_##_name_##_interrupt(int irq, void *arg) \
+{ \
+ struct power_supply *psy = (struct power_supply *)arg; \
+ \
+ power_supply_changed(psy); \
+ dev_dbg(&psy->dev, (_wrn_)); \
+ \
+ return IRQ_HANDLED; \
+}
+
+#define BD_IRQ_HND(_name_) bd0528_##_name_##_interrupt
+
+struct bd70528_psy {
+ struct regmap *regmap;
+ struct device *dev;
+ struct power_supply *psy;
+};
+
+BD_ERR_IRQ_HND(BAT_OV_DET, "Battery overvoltage detected\n");
+BD_ERR_IRQ_HND(DBAT_DET, "Dead battery detected\n");
+BD_ERR_IRQ_HND(COLD_DET, "Battery cold\n");
+BD_ERR_IRQ_HND(HOT_DET, "Battery hot\n");
+BD_ERR_IRQ_HND(CHG_TSD, "Charger thermal shutdown\n");
+BD_ERR_IRQ_HND(DCIN2_OV_DET, "DCIN2 overvoltage detected\n");
+
+BD_INFO_IRQ_HND(BAT_OV_RES, "Battery voltage back to normal\n");
+BD_INFO_IRQ_HND(COLD_RES, "Battery temperature back to normal\n");
+BD_INFO_IRQ_HND(HOT_RES, "Battery temperature back to normal\n");
+BD_INFO_IRQ_HND(BAT_RMV, "Battery removed\n");
+BD_INFO_IRQ_HND(BAT_DET, "Battery detected\n");
+BD_INFO_IRQ_HND(DCIN2_OV_RES, "DCIN2 voltage back to normal\n");
+BD_INFO_IRQ_HND(DCIN2_RMV, "DCIN2 removed\n");
+BD_INFO_IRQ_HND(DCIN2_DET, "DCIN2 detected\n");
+BD_INFO_IRQ_HND(DCIN1_RMV, "DCIN1 removed\n");
+BD_INFO_IRQ_HND(DCIN1_DET, "DCIN1 detected\n");
+
+struct irq_name_pair {
+ const char *n;
+ irqreturn_t (*h)(int irq, void *arg);
+};
+
+static int bd70528_get_irqs(struct platform_device *pdev,
+ struct bd70528_psy *bdpsy)
+{
+ int irq, i, ret;
+ unsigned int mask;
+ static const struct irq_name_pair bd70528_chg_irqs[] = {
+ { .n = "bd70528-bat-ov-res", .h = BD_IRQ_HND(BAT_OV_RES) },
+ { .n = "bd70528-bat-ov-det", .h = BD_IRQ_HND(BAT_OV_DET) },
+ { .n = "bd70528-bat-dead", .h = BD_IRQ_HND(DBAT_DET) },
+ { .n = "bd70528-bat-warmed", .h = BD_IRQ_HND(COLD_RES) },
+ { .n = "bd70528-bat-cold", .h = BD_IRQ_HND(COLD_DET) },
+ { .n = "bd70528-bat-cooled", .h = BD_IRQ_HND(HOT_RES) },
+ { .n = "bd70528-bat-hot", .h = BD_IRQ_HND(HOT_DET) },
+ { .n = "bd70528-chg-tshd", .h = BD_IRQ_HND(CHG_TSD) },
+ { .n = "bd70528-bat-removed", .h = BD_IRQ_HND(BAT_RMV) },
+ { .n = "bd70528-bat-detected", .h = BD_IRQ_HND(BAT_DET) },
+ { .n = "bd70528-dcin2-ov-res", .h = BD_IRQ_HND(DCIN2_OV_RES) },
+ { .n = "bd70528-dcin2-ov-det", .h = BD_IRQ_HND(DCIN2_OV_DET) },
+ { .n = "bd70528-dcin2-removed", .h = BD_IRQ_HND(DCIN2_RMV) },
+ { .n = "bd70528-dcin2-detected", .h = BD_IRQ_HND(DCIN2_DET) },
+ { .n = "bd70528-dcin1-removed", .h = BD_IRQ_HND(DCIN1_RMV) },
+ { .n = "bd70528-dcin1-detected", .h = BD_IRQ_HND(DCIN1_DET) },
+ };
+
+ for (i = 0; i < ARRAY_SIZE(bd70528_chg_irqs); i++) {
+ irq = platform_get_irq_byname(pdev, bd70528_chg_irqs[i].n);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "Bad IRQ information for %s (%d)\n",
+ bd70528_chg_irqs[i].n, irq);
+ return irq;
+ }
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ bd70528_chg_irqs[i].h,
+ IRQF_ONESHOT,
+ bd70528_chg_irqs[i].n,
+ bdpsy->psy);
+
+ if (ret)
+ return ret;
+ }
+ /*
+ * BD70528 irq controller is not touching the main mask register.
+ * So enable the charger block interrupts at main level. We can just
+ * leave them enabled as irq-controller should disable irqs
+ * from sub-registers when IRQ is disabled or freed.
+ */
+ mask = BD70528_REG_INT_BAT1_MASK | BD70528_REG_INT_BAT2_MASK;
+ ret = regmap_update_bits(bdpsy->regmap,
+ BD70528_REG_INT_MAIN_MASK, mask, 0);
+ if (ret)
+ dev_err(&pdev->dev, "Failed to enable charger IRQs\n");
+
+ return ret;
+}
+
+static int bd70528_get_charger_status(struct bd70528_psy *bdpsy, int *val)
+{
+ int ret;
+ unsigned int v;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v);
+ if (ret) {
+ dev_err(bdpsy->dev, "Charger state read failure %d\n",
+ ret);
+ return ret;
+ }
+
+ switch (v & BD70528_MASK_CHG_STAT) {
+ case CHG_STAT_SUSPEND:
+ /* Maybe we should check the CHG_TTRI_EN? */
+ case CHG_STAT_OTP_TRICKLE:
+ case CHG_STAT_OTP_FAST:
+ case CHG_STAT_OTP_DONE:
+ case CHG_STAT_TSD_TRICKLE:
+ case CHG_STAT_TSD_FAST:
+ case CHG_STAT_TSD_TOPOFF:
+ case CHG_STAT_BAT_ERR:
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case CHG_STAT_DONE:
+ *val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case CHG_STAT_TRICKLE:
+ case CHG_STAT_FAST:
+ case CHG_STAT_TOPOFF:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ default:
+ *val = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int bd70528_get_charge_type(struct bd70528_psy *bdpsy, int *val)
+{
+ int ret;
+ unsigned int v;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CURR_STAT, &v);
+ if (ret) {
+ dev_err(bdpsy->dev, "Charger state read failure %d\n",
+ ret);
+ return ret;
+ }
+
+ switch (v & BD70528_MASK_CHG_STAT) {
+ case CHG_STAT_TRICKLE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case CHG_STAT_FAST:
+ case CHG_STAT_TOPOFF:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case CHG_STAT_DONE:
+ case CHG_STAT_SUSPEND:
+ /* Maybe we should check the CHG_TTRI_EN? */
+ case CHG_STAT_OTP_TRICKLE:
+ case CHG_STAT_OTP_FAST:
+ case CHG_STAT_OTP_DONE:
+ case CHG_STAT_TSD_TRICKLE:
+ case CHG_STAT_TSD_FAST:
+ case CHG_STAT_TSD_TOPOFF:
+ case CHG_STAT_BAT_ERR:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ default:
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int bd70528_get_battery_health(struct bd70528_psy *bdpsy, int *val)
+{
+ int ret;
+ unsigned int v;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v);
+ if (ret) {
+ dev_err(bdpsy->dev, "Battery state read failure %d\n",
+ ret);
+ return ret;
+ }
+ /* No battery? */
+ if (!(v & BD70528_MASK_CHG_BAT_DETECT))
+ *val = POWER_SUPPLY_HEALTH_DEAD;
+ else if (v & BD70528_MASK_CHG_BAT_OVERVOLT)
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (v & BD70528_MASK_CHG_BAT_TIMER)
+ *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ else
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+
+ return 0;
+}
+
+static int bd70528_get_online(struct bd70528_psy *bdpsy, int *val)
+{
+ int ret;
+ unsigned int v;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_IN_STAT, &v);
+ if (ret) {
+ dev_err(bdpsy->dev, "DC1 IN state read failure %d\n",
+ ret);
+ return ret;
+ }
+
+ *val = (v & BD70528_MASK_CHG_DCIN1_UVLO) ? 1 : 0;
+
+ return 0;
+}
+
+static int bd70528_get_present(struct bd70528_psy *bdpsy, int *val)
+{
+ int ret;
+ unsigned int v;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_BAT_STAT, &v);
+ if (ret) {
+ dev_err(bdpsy->dev, "Battery state read failure %d\n",
+ ret);
+ return ret;
+ }
+
+ *val = (v & BD70528_MASK_CHG_BAT_DETECT) ? 1 : 0;
+
+ return 0;
+}
+
+struct linear_range {
+ int min;
+ int step;
+ int vals;
+ int low_sel;
+};
+
+static const struct linear_range current_limit_ranges[] = {
+ {
+ .min = 5,
+ .step = 1,
+ .vals = 36,
+ .low_sel = 0,
+ },
+ {
+ .min = 40,
+ .step = 5,
+ .vals = 5,
+ .low_sel = 0x23,
+ },
+ {
+ .min = 60,
+ .step = 20,
+ .vals = 8,
+ .low_sel = 0x27,
+ },
+ {
+ .min = 200,
+ .step = 50,
+ .vals = 7,
+ .low_sel = 0x2e,
+ }
+};
+
+/*
+ * BD70528 would support setting and getting own charge current/
+ * voltage for low temperatures. The driver currently only reads
+ * the charge current at room temperature. We do set both though.
+ */
+static const struct linear_range warm_charge_curr[] = {
+ {
+ .min = 10,
+ .step = 10,
+ .vals = 20,
+ .low_sel = 0,
+ },
+ {
+ .min = 200,
+ .step = 25,
+ .vals = 13,
+ .low_sel = 0x13,
+ },
+};
+
+/*
+ * Cold charge current selectors are identical to warm charge current
+ * selectors. The difference is that only smaller currents are available
+ * at cold charge range.
+ */
+#define MAX_COLD_CHG_CURR_SEL 0x15
+#define MAX_WARM_CHG_CURR_SEL 0x1f
+#define MIN_CHG_CURR_SEL 0x0
+
+static int find_value_for_selector_low(const struct linear_range *r,
+ int selectors, unsigned int sel,
+ unsigned int *val)
+{
+ int i;
+
+ for (i = 0; i < selectors; i++) {
+ if (r[i].low_sel <= sel && r[i].low_sel + r[i].vals >= sel) {
+ *val = r[i].min + (sel - r[i].low_sel) * r[i].step;
+ return 0;
+ }
+ }
+ return -EINVAL;
+}
+
+/*
+ * For BD70528 voltage/current limits we happily accept any value which
+ * belongs the range. We could check if value matching the selector is
+ * desired by computing the range min + (sel - sel_low) * range step - but
+ * I guess it is enough if we use voltage/current which is closest (below)
+ * the requested?
+ */
+static int find_selector_for_value_low(const struct linear_range *r,
+ int selectors, unsigned int val,
+ unsigned int *sel, bool *found)
+{
+ int i;
+ int ret = -EINVAL;
+
+ *found = false;
+ for (i = 0; i < selectors; i++) {
+ if (r[i].min <= val) {
+ if (r[i].min + r[i].step * r[i].vals >= val) {
+ *found = true;
+ *sel = r[i].low_sel + (val - r[i].min) /
+ r[i].step;
+ ret = 0;
+ break;
+ }
+ /*
+ * If the range max is smaller than requested
+ * we can set the max supported value from range
+ */
+ *sel = r[i].low_sel + r[i].vals;
+ ret = 0;
+ }
+ }
+ return ret;
+}
+
+static int get_charge_current(struct bd70528_psy *bdpsy, int *ma)
+{
+ unsigned int sel;
+ int ret;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_CHG_CURR_WARM,
+ &sel);
+ if (ret) {
+ dev_err(bdpsy->dev,
+ "Charge current reading failed (%d)\n", ret);
+ return ret;
+ }
+
+ sel &= BD70528_MASK_CHG_CHG_CURR;
+
+ ret = find_value_for_selector_low(&warm_charge_curr[0],
+ ARRAY_SIZE(warm_charge_curr), sel,
+ ma);
+ if (ret) {
+ dev_err(bdpsy->dev,
+ "Unknown charge current value 0x%x\n",
+ sel);
+ }
+
+ return ret;
+}
+
+static int get_current_limit(struct bd70528_psy *bdpsy, int *ma)
+{
+ unsigned int sel;
+ int ret;
+
+ ret = regmap_read(bdpsy->regmap, BD70528_REG_CHG_DCIN_ILIM,
+ &sel);
+
+ if (ret) {
+ dev_err(bdpsy->dev,
+ "Input current limit reading failed (%d)\n", ret);
+ return ret;
+ }
+
+ sel &= BD70528_MASK_CHG_DCIN_ILIM;
+
+ ret = find_value_for_selector_low(&current_limit_ranges[0],
+ ARRAY_SIZE(current_limit_ranges), sel,
+ ma);
+
+ if (ret) {
+ /* Unspecified values mean 500 mA */
+ *ma = 500;
+ }
+ return 0;
+}
+
+static enum power_supply_property bd70528_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int bd70528_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return bd70528_get_charger_status(bdpsy, &val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ return bd70528_get_charge_type(bdpsy, &val->intval);
+ case POWER_SUPPLY_PROP_HEALTH:
+ return bd70528_get_battery_health(bdpsy, &val->intval);
+ case POWER_SUPPLY_PROP_PRESENT:
+ return bd70528_get_present(bdpsy, &val->intval);
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = get_current_limit(bdpsy, &val->intval);
+ val->intval *= 1000;
+ return ret;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = get_charge_current(bdpsy, &val->intval);
+ val->intval *= 1000;
+ return ret;
+ case POWER_SUPPLY_PROP_ONLINE:
+ return bd70528_get_online(bdpsy, &val->intval);
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = bd70528_charger_model;
+ return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = bd70528_charger_manufacturer;
+ return 0;
+ default:
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static int bd70528_prop_is_writable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return 1;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int set_charge_current(struct bd70528_psy *bdpsy, int ma)
+{
+ unsigned int reg;
+ int ret = 0, tmpret;
+ bool found;
+
+ if (ma > 500) {
+ dev_warn(bdpsy->dev,
+ "Requested charge current %u exceed maximum (500mA)\n",
+ ma);
+ reg = MAX_WARM_CHG_CURR_SEL;
+ goto set;
+ }
+ if (ma < 10) {
+ dev_err(bdpsy->dev,
+ "Requested charge current %u smaller than min (10mA)\n",
+ ma);
+ reg = MIN_CHG_CURR_SEL;
+ ret = -EINVAL;
+ goto set;
+ }
+
+ ret = find_selector_for_value_low(&warm_charge_curr[0],
+ ARRAY_SIZE(warm_charge_curr), ma,
+ &reg, &found);
+ if (ret) {
+ reg = MIN_CHG_CURR_SEL;
+ goto set;
+ }
+ if (!found) {
+ /* There was a gap in supported values and we hit it */
+ dev_warn(bdpsy->dev,
+ "Unsupported charge current %u mA\n", ma);
+ }
+set:
+
+ tmpret = regmap_update_bits(bdpsy->regmap,
+ BD70528_REG_CHG_CHG_CURR_WARM,
+ BD70528_MASK_CHG_CHG_CURR, reg);
+ if (tmpret)
+ dev_err(bdpsy->dev,
+ "Charge current write failure (%d)\n", tmpret);
+
+ if (reg > MAX_COLD_CHG_CURR_SEL)
+ reg = MAX_COLD_CHG_CURR_SEL;
+
+ if (!tmpret)
+ tmpret = regmap_update_bits(bdpsy->regmap,
+ BD70528_REG_CHG_CHG_CURR_COLD,
+ BD70528_MASK_CHG_CHG_CURR, reg);
+
+ if (!ret)
+ ret = tmpret;
+
+ return ret;
+}
+
+#define MAX_CURR_LIMIT_SEL 0x34
+#define MIN_CURR_LIMIT_SEL 0x0
+
+static int set_current_limit(struct bd70528_psy *bdpsy, int ma)
+{
+ unsigned int reg;
+ int ret = 0, tmpret;
+ bool found;
+
+ if (ma > 500) {
+ dev_warn(bdpsy->dev,
+ "Requested current limit %u exceed maximum (500mA)\n",
+ ma);
+ reg = MAX_CURR_LIMIT_SEL;
+ goto set;
+ }
+ if (ma < 5) {
+ dev_err(bdpsy->dev,
+ "Requested current limit %u smaller than min (5mA)\n",
+ ma);
+ reg = MIN_CURR_LIMIT_SEL;
+ ret = -EINVAL;
+ goto set;
+ }
+
+ ret = find_selector_for_value_low(&current_limit_ranges[0],
+ ARRAY_SIZE(current_limit_ranges), ma,
+ &reg, &found);
+ if (ret) {
+ reg = MIN_CURR_LIMIT_SEL;
+ goto set;
+ }
+ if (!found) {
+ /* There was a gap in supported values and we hit it ?*/
+ dev_warn(bdpsy->dev, "Unsupported current limit %umA\n",
+ ma);
+ }
+
+set:
+ tmpret = regmap_update_bits(bdpsy->regmap,
+ BD70528_REG_CHG_DCIN_ILIM,
+ BD70528_MASK_CHG_DCIN_ILIM, reg);
+
+ if (!ret)
+ ret = tmpret;
+
+ return ret;
+}
+
+static int bd70528_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bd70528_psy *bdpsy = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return set_current_limit(bdpsy, val->intval / 1000);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return set_charge_current(bdpsy, val->intval / 1000);
+ default:
+ break;
+ }
+ return -EINVAL;
+}
+
+static const struct power_supply_desc bd70528_charger_desc = {
+ .name = "bd70528-charger",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = bd70528_charger_props,
+ .num_properties = ARRAY_SIZE(bd70528_charger_props),
+ .get_property = bd70528_charger_get_property,
+ .set_property = bd70528_charger_set_property,
+ .property_is_writeable = bd70528_prop_is_writable,
+};
+
+static int bd70528_power_probe(struct platform_device *pdev)
+{
+ struct bd70528_psy *bdpsy;
+ struct power_supply_config cfg = {};
+
+ bdpsy = devm_kzalloc(&pdev->dev, sizeof(*bdpsy), GFP_KERNEL);
+ if (!bdpsy)
+ return -ENOMEM;
+
+ bdpsy->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!bdpsy->regmap) {
+ dev_err(&pdev->dev, "No regmap found for chip\n");
+ return -EINVAL;
+ }
+ bdpsy->dev = &pdev->dev;
+
+ platform_set_drvdata(pdev, bdpsy);
+ cfg.drv_data = bdpsy;
+ cfg.of_node = pdev->dev.parent->of_node;
+
+ bdpsy->psy = devm_power_supply_register(&pdev->dev,
+ &bd70528_charger_desc, &cfg);
+ if (IS_ERR(bdpsy->psy)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return PTR_ERR(bdpsy->psy);
+ }
+
+ return bd70528_get_irqs(pdev, bdpsy);
+}
+
+static struct platform_driver bd70528_power = {
+ .driver = {
+ .name = "bd70528-power"
+ },
+ .probe = bd70528_power_probe,
+};
+
+module_platform_driver(bd70528_power);
+
+MODULE_AUTHOR("Matti Vaittinen <matti.vaittinen@fi.rohmeurope.com>");
+MODULE_DESCRIPTION("BD70528 power-supply driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c
index a3dd1cfcfa8b..453d6332d43a 100644
--- a/drivers/power/supply/bq24190_charger.c
+++ b/drivers/power/supply/bq24190_charger.c
@@ -1697,7 +1697,7 @@ static int bq24190_get_config(struct bq24190_dev_info *bdi)
static int bq24190_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct power_supply_config charger_cfg = {}, battery_cfg = {};
struct bq24190_dev_info *bdi;
diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c
index 7eb58f10e092..eb151687beb3 100644
--- a/drivers/power/supply/bq24257_charger.c
+++ b/drivers/power/supply/bq24257_charger.c
@@ -950,7 +950,7 @@ static int bq24257_fw_probe(struct bq24257_device *bq)
static int bq24257_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
const struct acpi_device_id *acpi_id;
struct bq24257_device *bq;
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
index b2ff82b4707a..d333f2b321b9 100644
--- a/drivers/power/supply/bq25890_charger.c
+++ b/drivers/power/supply/bq25890_charger.c
@@ -817,7 +817,7 @@ static int bq25890_fw_probe(struct bq25890_device *bq)
static int bq25890_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct bq25890_device *bq;
int ret;
diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c
index 7e9c3984ef6a..3a9ea94c3de3 100644
--- a/drivers/power/supply/cros_usbpd-charger.c
+++ b/drivers/power/supply/cros_usbpd-charger.c
@@ -53,6 +53,8 @@ struct charger_data {
};
static enum power_supply_property cros_usbpd_charger_props[] = {
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+ POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CURRENT_MAX,
@@ -80,6 +82,10 @@ static enum power_supply_usb_type cros_usbpd_charger_usb_types[] = {
POWER_SUPPLY_USB_TYPE_APPLE_BRICK_ID
};
+/* Input voltage/current limit in mV/mA. Default to none. */
+static u16 input_voltage_limit = EC_POWER_LIMIT_NONE;
+static u16 input_current_limit = EC_POWER_LIMIT_NONE;
+
static bool cros_usbpd_charger_port_is_dedicated(struct port_data *port)
{
return port->port_number >= port->charger->num_usbpd_ports;
@@ -324,6 +330,26 @@ static int cros_usbpd_charger_get_port_status(struct port_data *port,
return ret;
}
+static int cros_usbpd_charger_set_ext_power_limit(struct charger_data *charger,
+ u16 current_lim,
+ u16 voltage_lim)
+{
+ struct ec_params_external_power_limit_v1 req;
+ int ret;
+
+ req.current_lim = current_lim;
+ req.voltage_lim = voltage_lim;
+
+ ret = cros_usbpd_charger_ec_command(charger, 0,
+ EC_CMD_EXTERNAL_POWER_LIMIT,
+ &req, sizeof(req), NULL, 0);
+ if (ret < 0)
+ dev_err(charger->dev,
+ "Unable to set the 'External Power Limit': %d\n", ret);
+
+ return ret;
+}
+
static void cros_usbpd_charger_power_changed(struct power_supply *psy)
{
struct port_data *port = power_supply_get_drvdata(psy);
@@ -396,6 +422,18 @@ static int cros_usbpd_charger_get_prop(struct power_supply *psy,
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = port->psy_usb_type;
break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ if (input_current_limit == EC_POWER_LIMIT_NONE)
+ val->intval = -1;
+ else
+ val->intval = input_current_limit * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ if (input_voltage_limit == EC_POWER_LIMIT_NONE)
+ val->intval = -1;
+ else
+ val->intval = input_voltage_limit * 1000;
+ break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = port->model_name;
break;
@@ -409,6 +447,81 @@ static int cros_usbpd_charger_get_prop(struct power_supply *psy,
return 0;
}
+static int cros_usbpd_charger_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct port_data *port = power_supply_get_drvdata(psy);
+ struct charger_data *charger = port->charger;
+ struct device *dev = charger->dev;
+ u16 intval;
+ int ret;
+
+ /* U16_MAX in mV/mA is the maximum supported value */
+ if (val->intval >= U16_MAX * 1000)
+ return -EINVAL;
+ /* A negative number is used to clear the limit */
+ if (val->intval < 0)
+ intval = EC_POWER_LIMIT_NONE;
+ else /* Convert from uA/uV to mA/mV */
+ intval = val->intval / 1000;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ ret = cros_usbpd_charger_set_ext_power_limit(charger, intval,
+ input_voltage_limit);
+ if (ret < 0)
+ break;
+
+ input_current_limit = intval;
+ if (input_current_limit == EC_POWER_LIMIT_NONE)
+ dev_info(dev,
+ "External Current Limit cleared for all ports\n");
+ else
+ dev_info(dev,
+ "External Current Limit set to %dmA for all ports\n",
+ input_current_limit);
+ break;
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ ret = cros_usbpd_charger_set_ext_power_limit(charger,
+ input_current_limit,
+ intval);
+ if (ret < 0)
+ break;
+
+ input_voltage_limit = intval;
+ if (input_voltage_limit == EC_POWER_LIMIT_NONE)
+ dev_info(dev,
+ "External Voltage Limit cleared for all ports\n");
+ else
+ dev_info(dev,
+ "External Voltage Limit set to %dmV for all ports\n",
+ input_voltage_limit);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int cros_usbpd_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
static int cros_usbpd_charger_ec_event(struct notifier_block *nb,
unsigned long queued_during_suspend,
void *_notify)
@@ -525,6 +638,9 @@ static int cros_usbpd_charger_probe(struct platform_device *pd)
psy_desc = &port->psy_desc;
psy_desc->get_property = cros_usbpd_charger_get_prop;
+ psy_desc->set_property = cros_usbpd_charger_set_prop;
+ psy_desc->property_is_writeable =
+ cros_usbpd_charger_property_is_writeable;
psy_desc->external_power_changed =
cros_usbpd_charger_power_changed;
psy_cfg.drv_data = port;
diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c
index 68cfde4be632..3bbb8b4c8ae7 100644
--- a/drivers/power/supply/max14656_charger_detector.c
+++ b/drivers/power/supply/max14656_charger_detector.c
@@ -247,7 +247,7 @@ static void stop_irq_work(void *data)
static int max14656_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct power_supply_config psy_cfg = {};
struct max14656_chip *chip;
diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
index 91cafc7bed30..62499018e68b 100644
--- a/drivers/power/supply/max17040_battery.c
+++ b/drivers/power/supply/max17040_battery.c
@@ -193,7 +193,7 @@ static const struct power_supply_desc max17040_battery_desc = {
static int max17040_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct max17040_chip *chip;
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
index 581c6bd23388..64f3358eaa3c 100644
--- a/drivers/power/supply/max17042_battery.c
+++ b/drivers/power/supply/max17042_battery.c
@@ -1005,7 +1005,7 @@ static void max17042_stop_work(void *data)
static int max17042_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
struct power_supply_config psy_cfg = {};
const struct acpi_device_id *acpi_id = NULL;
diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c
index 9f9430ac8887..ad0e9e0edb3f 100644
--- a/drivers/power/supply/olpc_battery.c
+++ b/drivers/power/supply/olpc_battery.c
@@ -17,7 +17,6 @@
#include <linux/jiffies.h>
#include <linux/sched.h>
#include <linux/olpc-ec.h>
-#include <asm/olpc.h>
#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index 136e8f64848a..82e84801264c 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -606,7 +606,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
/* The property and field names below must correspond to elements
* in enum power_supply_property. For reasoning, see
- * Documentation/power/power_supply_class.txt.
+ * Documentation/power/power_supply_class.rst.
*/
of_property_read_u32(battery_np, "energy-full-design-microwatt-hours",
@@ -1071,6 +1071,10 @@ __power_supply_register(struct device *parent,
if (rc)
goto create_triggers_failed;
+ rc = power_supply_add_hwmon_sysfs(psy);
+ if (rc)
+ goto add_hwmon_sysfs_failed;
+
/*
* Update use_cnt after any uevents (most notably from device_add()).
* We are here still during driver's probe but
@@ -1089,6 +1093,8 @@ __power_supply_register(struct device *parent,
return psy;
+add_hwmon_sysfs_failed:
+ power_supply_remove_triggers(psy);
create_triggers_failed:
psy_unregister_cooler(psy);
register_cooler_failed:
@@ -1241,6 +1247,7 @@ void power_supply_unregister(struct power_supply *psy)
cancel_work_sync(&psy->changed_work);
cancel_delayed_work_sync(&psy->deferred_register_work);
sysfs_remove_link(&psy->dev.kobj, "powers");
+ power_supply_remove_hwmon_sysfs(psy);
power_supply_remove_triggers(psy);
psy_unregister_cooler(psy);
psy_unregister_thermal(psy);
diff --git a/drivers/power/supply/power_supply_hwmon.c b/drivers/power/supply/power_supply_hwmon.c
new file mode 100644
index 000000000000..51fe60440d12
--- /dev/null
+++ b/drivers/power/supply/power_supply_hwmon.c
@@ -0,0 +1,355 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * power_supply_hwmon.c - power supply hwmon support.
+ */
+
+#include <linux/err.h>
+#include <linux/hwmon.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+struct power_supply_hwmon {
+ struct power_supply *psy;
+ unsigned long *props;
+};
+
+static int power_supply_hwmon_in_to_property(u32 attr)
+{
+ switch (attr) {
+ case hwmon_in_average:
+ return POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ case hwmon_in_min:
+ return POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ case hwmon_in_max:
+ return POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ case hwmon_in_input:
+ return POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int power_supply_hwmon_curr_to_property(u32 attr)
+{
+ switch (attr) {
+ case hwmon_curr_average:
+ return POWER_SUPPLY_PROP_CURRENT_AVG;
+ case hwmon_curr_max:
+ return POWER_SUPPLY_PROP_CURRENT_MAX;
+ case hwmon_curr_input:
+ return POWER_SUPPLY_PROP_CURRENT_NOW;
+ default:
+ return -EINVAL;
+ }
+}
+
+static int power_supply_hwmon_temp_to_property(u32 attr, int channel)
+{
+ if (channel) {
+ switch (attr) {
+ case hwmon_temp_input:
+ return POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ case hwmon_temp_min_alarm:
+ return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN;
+ case hwmon_temp_max_alarm:
+ return POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX;
+ default:
+ break;
+ }
+ } else {
+ switch (attr) {
+ case hwmon_temp_input:
+ return POWER_SUPPLY_PROP_TEMP;
+ case hwmon_temp_max:
+ return POWER_SUPPLY_PROP_TEMP_MAX;
+ case hwmon_temp_min:
+ return POWER_SUPPLY_PROP_TEMP_MIN;
+ case hwmon_temp_min_alarm:
+ return POWER_SUPPLY_PROP_TEMP_ALERT_MIN;
+ case hwmon_temp_max_alarm:
+ return POWER_SUPPLY_PROP_TEMP_ALERT_MAX;
+ default:
+ break;
+ }
+ }
+
+ return -EINVAL;
+}
+
+static int
+power_supply_hwmon_to_property(enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ switch (type) {
+ case hwmon_in:
+ return power_supply_hwmon_in_to_property(attr);
+ case hwmon_curr:
+ return power_supply_hwmon_curr_to_property(attr);
+ case hwmon_temp:
+ return power_supply_hwmon_temp_to_property(attr, channel);
+ default:
+ return -EINVAL;
+ }
+}
+
+static bool power_supply_hwmon_is_a_label(enum hwmon_sensor_types type,
+ u32 attr)
+{
+ return type == hwmon_temp && attr == hwmon_temp_label;
+}
+
+static bool power_supply_hwmon_is_writable(enum hwmon_sensor_types type,
+ u32 attr)
+{
+ switch (type) {
+ case hwmon_in:
+ return attr == hwmon_in_min ||
+ attr == hwmon_in_max;
+ case hwmon_curr:
+ return attr == hwmon_curr_max;
+ case hwmon_temp:
+ return attr == hwmon_temp_max ||
+ attr == hwmon_temp_min ||
+ attr == hwmon_temp_min_alarm ||
+ attr == hwmon_temp_max_alarm;
+ default:
+ return false;
+ }
+}
+
+static umode_t power_supply_hwmon_is_visible(const void *data,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel)
+{
+ const struct power_supply_hwmon *psyhw = data;
+ int prop;
+
+
+ if (power_supply_hwmon_is_a_label(type, attr))
+ return 0444;
+
+ prop = power_supply_hwmon_to_property(type, attr, channel);
+ if (prop < 0 || !test_bit(prop, psyhw->props))
+ return 0;
+
+ if (power_supply_property_is_writeable(psyhw->psy, prop) > 0 &&
+ power_supply_hwmon_is_writable(type, attr))
+ return 0644;
+
+ return 0444;
+}
+
+static int power_supply_hwmon_read_string(struct device *dev,
+ enum hwmon_sensor_types type,
+ u32 attr, int channel,
+ const char **str)
+{
+ *str = channel ? "temp" : "temp ambient";
+ return 0;
+}
+
+static int
+power_supply_hwmon_read(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long *val)
+{
+ struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
+ struct power_supply *psy = psyhw->psy;
+ union power_supply_propval pspval;
+ int ret, prop;
+
+ prop = power_supply_hwmon_to_property(type, attr, channel);
+ if (prop < 0)
+ return prop;
+
+ ret = power_supply_get_property(psy, prop, &pspval);
+ if (ret)
+ return ret;
+
+ switch (type) {
+ /*
+ * Both voltage and current is reported in units of
+ * microvolts/microamps, so we need to adjust it to
+ * milliamps(volts)
+ */
+ case hwmon_curr:
+ case hwmon_in:
+ pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 1000);
+ break;
+ /*
+ * Temp needs to be converted from 1/10 C to milli-C
+ */
+ case hwmon_temp:
+ if (check_mul_overflow(pspval.intval, 100,
+ &pspval.intval))
+ return -EOVERFLOW;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ *val = pspval.intval;
+
+ return 0;
+}
+
+static int
+power_supply_hwmon_write(struct device *dev, enum hwmon_sensor_types type,
+ u32 attr, int channel, long val)
+{
+ struct power_supply_hwmon *psyhw = dev_get_drvdata(dev);
+ struct power_supply *psy = psyhw->psy;
+ union power_supply_propval pspval;
+ int prop;
+
+ prop = power_supply_hwmon_to_property(type, attr, channel);
+ if (prop < 0)
+ return prop;
+
+ pspval.intval = val;
+
+ switch (type) {
+ /*
+ * Both voltage and current is reported in units of
+ * microvolts/microamps, so we need to adjust it to
+ * milliamps(volts)
+ */
+ case hwmon_curr:
+ case hwmon_in:
+ if (check_mul_overflow(pspval.intval, 1000,
+ &pspval.intval))
+ return -EOVERFLOW;
+ break;
+ /*
+ * Temp needs to be converted from 1/10 C to milli-C
+ */
+ case hwmon_temp:
+ pspval.intval = DIV_ROUND_CLOSEST(pspval.intval, 100);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return power_supply_set_property(psy, prop, &pspval);
+}
+
+static const struct hwmon_ops power_supply_hwmon_ops = {
+ .is_visible = power_supply_hwmon_is_visible,
+ .read = power_supply_hwmon_read,
+ .write = power_supply_hwmon_write,
+ .read_string = power_supply_hwmon_read_string,
+};
+
+static const struct hwmon_channel_info *power_supply_hwmon_info[] = {
+ HWMON_CHANNEL_INFO(temp,
+ HWMON_T_LABEL |
+ HWMON_T_INPUT |
+ HWMON_T_MAX |
+ HWMON_T_MIN |
+ HWMON_T_MIN_ALARM |
+ HWMON_T_MIN_ALARM,
+
+ HWMON_T_LABEL |
+ HWMON_T_INPUT |
+ HWMON_T_MIN_ALARM |
+ HWMON_T_LABEL |
+ HWMON_T_MAX_ALARM),
+
+ HWMON_CHANNEL_INFO(curr,
+ HWMON_C_AVERAGE |
+ HWMON_C_MAX |
+ HWMON_C_INPUT),
+
+ HWMON_CHANNEL_INFO(in,
+ HWMON_I_AVERAGE |
+ HWMON_I_MIN |
+ HWMON_I_MAX |
+ HWMON_I_INPUT),
+ NULL
+};
+
+static const struct hwmon_chip_info power_supply_hwmon_chip_info = {
+ .ops = &power_supply_hwmon_ops,
+ .info = power_supply_hwmon_info,
+};
+
+static void power_supply_hwmon_bitmap_free(void *data)
+{
+ bitmap_free(data);
+}
+
+int power_supply_add_hwmon_sysfs(struct power_supply *psy)
+{
+ const struct power_supply_desc *desc = psy->desc;
+ struct power_supply_hwmon *psyhw;
+ struct device *dev = &psy->dev;
+ struct device *hwmon;
+ int ret, i;
+
+ if (!devres_open_group(dev, power_supply_add_hwmon_sysfs,
+ GFP_KERNEL))
+ return -ENOMEM;
+
+ psyhw = devm_kzalloc(dev, sizeof(*psyhw), GFP_KERNEL);
+ if (!psyhw) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ psyhw->psy = psy;
+ psyhw->props = bitmap_zalloc(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG + 1,
+ GFP_KERNEL);
+ if (!psyhw->props) {
+ ret = -ENOMEM;
+ goto error;
+ }
+
+ ret = devm_add_action(dev, power_supply_hwmon_bitmap_free,
+ psyhw->props);
+ if (ret)
+ goto error;
+
+ for (i = 0; i < desc->num_properties; i++) {
+ const enum power_supply_property prop = desc->properties[i];
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT_ALERT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ set_bit(prop, psyhw->props);
+ break;
+ default:
+ break;
+ }
+ }
+
+ hwmon = devm_hwmon_device_register_with_info(dev, psy->desc->name,
+ psyhw,
+ &power_supply_hwmon_chip_info,
+ NULL);
+ ret = PTR_ERR_OR_ZERO(hwmon);
+ if (ret)
+ goto error;
+
+ devres_close_group(dev, power_supply_add_hwmon_sysfs);
+ return 0;
+error:
+ devres_release_group(dev, NULL);
+ return ret;
+}
+
+void power_supply_remove_hwmon_sysfs(struct power_supply *psy)
+{
+ devres_release_group(&psy->dev, power_supply_add_hwmon_sysfs);
+}
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 61430ae878d3..f37ad4eae60b 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -276,6 +276,8 @@ static struct device_attribute power_supply_attrs[] = {
POWER_SUPPLY_ATTR(charge_control_start_threshold),
POWER_SUPPLY_ATTR(charge_control_end_threshold),
POWER_SUPPLY_ATTR(input_current_limit),
+ POWER_SUPPLY_ATTR(input_voltage_limit),
+ POWER_SUPPLY_ATTR(input_power_limit),
POWER_SUPPLY_ATTR(energy_full_design),
POWER_SUPPLY_ATTR(energy_empty_design),
POWER_SUPPLY_ATTR(energy_full),
diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c
index 9f0d70648e41..d8667a9fc49b 100644
--- a/drivers/power/supply/rt5033_battery.c
+++ b/drivers/power/supply/rt5033_battery.c
@@ -115,7 +115,7 @@ static const struct power_supply_desc rt5033_battery_desc = {
static int rt5033_battery_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct power_supply_config psy_cfg = {};
struct rt5033_battery *battery;
u32 ret;
diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c
index 40a9d329418a..29161ae90245 100644
--- a/drivers/power/supply/rt9455_charger.c
+++ b/drivers/power/supply/rt9455_charger.c
@@ -1584,7 +1584,7 @@ static const struct regmap_config rt9455_regmap_config = {
static int rt9455_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct rt9455_info *info;
struct power_supply_config rt9455_charger_config = {};
diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c
index 63173068a1ab..666243d9dd59 100644
--- a/drivers/power/supply/sbs-manager.c
+++ b/drivers/power/supply/sbs-manager.c
@@ -314,7 +314,7 @@ static const struct power_supply_desc sbsm_default_psy_desc = {
static int sbsm_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
- struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct i2c_adapter *adapter = client->adapter;
struct sbsm_data *data;
struct device *dev = &client->dev;
struct power_supply_desc *psy_desc;
diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c
index 1c89d030c045..1b80ae479e7d 100644
--- a/drivers/power/supply/ucs1002_power.c
+++ b/drivers/power/supply/ucs1002_power.c
@@ -336,7 +336,7 @@ static int ucs1002_get_usb_type(struct ucs1002_info *info,
case F_ACTIVE_MODE_BC12_CDP:
type = POWER_SUPPLY_USB_TYPE_CDP;
break;
- };
+ }
val->intval = type;
diff --git a/drivers/power/supply/wilco-charger.c b/drivers/power/supply/wilco-charger.c
new file mode 100644
index 000000000000..b3c6d7cdd731
--- /dev/null
+++ b/drivers/power/supply/wilco-charger.c
@@ -0,0 +1,187 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Charging control driver for the Wilco EC
+ *
+ * Copyright 2019 Google LLC
+ *
+ * See Documentation/ABI/testing/sysfs-class-power and
+ * Documentation/ABI/testing/sysfs-class-power-wilco for userspace interface
+ * and other info.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/platform_data/wilco-ec.h>
+#include <linux/power_supply.h>
+
+#define DRV_NAME "wilco-charger"
+
+/* Property IDs and related EC constants */
+#define PID_CHARGE_MODE 0x0710
+#define PID_CHARGE_LOWER_LIMIT 0x0711
+#define PID_CHARGE_UPPER_LIMIT 0x0712
+
+enum charge_mode {
+ CHARGE_MODE_STD = 1, /* Used for Standard */
+ CHARGE_MODE_EXP = 2, /* Express Charge, used for Fast */
+ CHARGE_MODE_AC = 3, /* Mostly AC use, used for Trickle */
+ CHARGE_MODE_AUTO = 4, /* Used for Adaptive */
+ CHARGE_MODE_CUSTOM = 5, /* Used for Custom */
+};
+
+#define CHARGE_LOWER_LIMIT_MIN 50
+#define CHARGE_LOWER_LIMIT_MAX 95
+#define CHARGE_UPPER_LIMIT_MIN 55
+#define CHARGE_UPPER_LIMIT_MAX 100
+
+/* Convert from POWER_SUPPLY_PROP_CHARGE_TYPE value to the EC's charge mode */
+static int psp_val_to_charge_mode(int psp_val)
+{
+ switch (psp_val) {
+ case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
+ return CHARGE_MODE_AC;
+ case POWER_SUPPLY_CHARGE_TYPE_FAST:
+ return CHARGE_MODE_EXP;
+ case POWER_SUPPLY_CHARGE_TYPE_STANDARD:
+ return CHARGE_MODE_STD;
+ case POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE:
+ return CHARGE_MODE_AUTO;
+ case POWER_SUPPLY_CHARGE_TYPE_CUSTOM:
+ return CHARGE_MODE_CUSTOM;
+ default:
+ return -EINVAL;
+ }
+}
+
+/* Convert from EC's charge mode to POWER_SUPPLY_PROP_CHARGE_TYPE value */
+static int charge_mode_to_psp_val(enum charge_mode mode)
+{
+ switch (mode) {
+ case CHARGE_MODE_AC:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case CHARGE_MODE_EXP:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ case CHARGE_MODE_STD:
+ return POWER_SUPPLY_CHARGE_TYPE_STANDARD;
+ case CHARGE_MODE_AUTO:
+ return POWER_SUPPLY_CHARGE_TYPE_ADAPTIVE;
+ case CHARGE_MODE_CUSTOM:
+ return POWER_SUPPLY_CHARGE_TYPE_CUSTOM;
+ default:
+ return -EINVAL;
+ }
+}
+
+static enum power_supply_property wilco_charge_props[] = {
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
+};
+
+static int wilco_charge_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
+ u32 property_id;
+ int ret;
+ u8 raw;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ property_id = PID_CHARGE_MODE;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ property_id = PID_CHARGE_LOWER_LIMIT;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ property_id = PID_CHARGE_UPPER_LIMIT;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ret = wilco_ec_get_byte_property(ec, property_id, &raw);
+ if (ret < 0)
+ return ret;
+ if (property_id == PID_CHARGE_MODE) {
+ ret = charge_mode_to_psp_val(raw);
+ if (ret < 0)
+ return -EBADMSG;
+ raw = ret;
+ }
+ val->intval = raw;
+
+ return 0;
+}
+
+static int wilco_charge_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct wilco_ec_device *ec = power_supply_get_drvdata(psy);
+ int mode;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ mode = psp_val_to_charge_mode(val->intval);
+ if (mode < 0)
+ return -EINVAL;
+ return wilco_ec_set_byte_property(ec, PID_CHARGE_MODE, mode);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
+ if (val->intval < CHARGE_LOWER_LIMIT_MIN ||
+ val->intval > CHARGE_LOWER_LIMIT_MAX)
+ return -EINVAL;
+ return wilco_ec_set_byte_property(ec, PID_CHARGE_LOWER_LIMIT,
+ val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
+ if (val->intval < CHARGE_UPPER_LIMIT_MIN ||
+ val->intval > CHARGE_UPPER_LIMIT_MAX)
+ return -EINVAL;
+ return wilco_ec_set_byte_property(ec, PID_CHARGE_UPPER_LIMIT,
+ val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int wilco_charge_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return 1;
+}
+
+static const struct power_supply_desc wilco_ps_desc = {
+ .properties = wilco_charge_props,
+ .num_properties = ARRAY_SIZE(wilco_charge_props),
+ .get_property = wilco_charge_get_property,
+ .set_property = wilco_charge_set_property,
+ .property_is_writeable = wilco_charge_property_is_writeable,
+ .name = DRV_NAME,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+};
+
+static int wilco_charge_probe(struct platform_device *pdev)
+{
+ struct wilco_ec_device *ec = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct power_supply *psy;
+
+ psy_cfg.drv_data = ec;
+ psy = devm_power_supply_register(&pdev->dev, &wilco_ps_desc, &psy_cfg);
+
+ return PTR_ERR_OR_ZERO(psy);
+}
+
+static struct platform_driver wilco_charge_driver = {
+ .probe = wilco_charge_probe,
+ .driver = {
+ .name = DRV_NAME,
+ }
+};
+module_platform_driver(wilco_charge_driver);
+
+MODULE_ALIAS("platform:" DRV_NAME);
+MODULE_AUTHOR("Nick Crews <ncrews@chromium.org>");
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("Wilco EC charge control driver");