summaryrefslogtreecommitdiff
path: root/drivers/power/supply
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/power/supply')
-rw-r--r--drivers/power/supply/Kconfig23
-rw-r--r--drivers/power/supply/Makefile2
-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/power_supply_core.c7
-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
17 files changed, 702 insertions, 10 deletions
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index a873a0038cc8..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
@@ -698,4 +712,13 @@ config CHARGER_BD70528
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 346a8ef5f348..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
@@ -91,3 +92,4 @@ 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/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/power_supply_core.c b/drivers/power/supply/power_supply_core.c
index b55cdfe22a2e..82e84801264c 100644
--- a/drivers/power/supply/power_supply_core.c
+++ b/drivers/power/supply/power_supply_core.c
@@ -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");