From 37669d9ff5cf116bb9b6b5968386df9aca4e680a Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:55:55 +0200 Subject: [PATCH 01/19] power: supply: bq24190_charger: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24190_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index cc0dfdc9e85a..ec03521c46d1 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -1700,7 +1700,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; From 1c9427be2a9c04e2c56ddcc3c60e5af3a4d67ffc Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:55:56 +0200 Subject: [PATCH 02/19] power: supply: bq24257_charger: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24257_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 673c7d6ff1a7..ec0a0f83302d 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -959,7 +959,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; From 124db1f91b00c1076a20d616ee007762323e7fab Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:55:57 +0200 Subject: [PATCH 03/19] power: supply: bq25890_charger: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq25890_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 66991e6f75d9..221548997e15 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -827,7 +827,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; From 71d7ffb29b6b9b492b2b14348dabc3c144456788 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:55:58 +0200 Subject: [PATCH 04/19] power: supply: max14656_charger_detector: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/max14656_charger_detector.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index 9e6472834e37..f27b780d2c02 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -251,7 +251,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; From 4e9c406dbea8c9b28a7ac38c5fa2bd1d006240be Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:55:59 +0200 Subject: [PATCH 05/19] power: supply: max17040_battery: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17040_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From dee2f3cf9499848f137cea23dcc13da1a6f921e0 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:56:00 +0200 Subject: [PATCH 06/19] power: supply: max17042_battery: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/max17042_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From df324c606aafa16045542b3129de577d7a4ca3b6 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:56:01 +0200 Subject: [PATCH 07/19] power: supply: rt5033_battery: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt5033_battery.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c index bcdd83048492..508f825ff190 100644 --- a/drivers/power/supply/rt5033_battery.c +++ b/drivers/power/supply/rt5033_battery.c @@ -118,7 +118,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; From bf0e482aefe1dad509f6f2b843bf7461c53ce052 Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:56:02 +0200 Subject: [PATCH 08/19] power: supply: rt9455_charger: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/rt9455_charger.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index cfdbde9daf94..eaa9d6faa874 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1593,7 +1593,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 = {}; From 22ee8384dc688cbcb8ac03112ce81c43d55a961b Mon Sep 17 00:00:00 2001 From: Wolfram Sang Date: Sat, 8 Jun 2019 12:56:03 +0200 Subject: [PATCH 09/19] power: supply: sbs-manager: simplify getting the adapter of a client We have a dedicated pointer for that, so use it. Much easier to read and less computation involved. Signed-off-by: Wolfram Sang Reviewed-by: Simon Horman Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-manager.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/power/supply/sbs-manager.c b/drivers/power/supply/sbs-manager.c index cb6e8f66c7a2..4c29a89df968 100644 --- a/drivers/power/supply/sbs-manager.c +++ b/drivers/power/supply/sbs-manager.c @@ -317,7 +317,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; From e67d4dfc9ff19dbe74b29617cf2592ccc50c3920 Mon Sep 17 00:00:00 2001 From: Andrey Smirnov Date: Wed, 12 Jun 2019 01:44:04 -0700 Subject: [PATCH 10/19] power: supply: Add HWMON compatibility layer Add code implementing HWMON adapter/compatibility layer to allow expositing various sensors present on power supply devices via HWMON subsystem. This is done in order to allow userspace to use single ABI/library(libsensors) to access/manipulate all of the sensors of the system. Signed-off-by: Andrey Smirnov Reviewed-by: Guenter Roeck Tested-by: Chris Healy Cc: Chris Healy Cc: Cory Tusar Cc: Lucas Stach Cc: Fabio Estevam Cc: Guenter Roeck Cc: Sebastian Reichel Cc: linux-kernel@vger.kernel.org Cc: linux-pm@vger.kernel.org Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 14 + drivers/power/supply/Makefile | 1 + drivers/power/supply/power_supply_core.c | 7 + drivers/power/supply/power_supply_hwmon.c | 355 ++++++++++++++++++++++ include/linux/power_supply.h | 13 + 5 files changed, 390 insertions(+) create mode 100644 drivers/power/supply/power_supply_hwmon.c diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 26dacdab03cc..1f2252cb95fd 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -14,6 +14,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 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index f208273f9686..c47e88ba16b9 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 diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index f7033ecf6d0b..35624193a346 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -1072,6 +1072,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 @@ -1090,6 +1094,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: @@ -1242,6 +1248,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 +#include +#include +#include + +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/include/linux/power_supply.h b/include/linux/power_supply.h index d9c0c094f8a0..d5b15e039f4f 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -481,4 +481,17 @@ static inline bool power_supply_is_watt_property(enum power_supply_property psp) return 0; } +#ifdef CONFIG_POWER_SUPPLY_HWMON +int power_supply_add_hwmon_sysfs(struct power_supply *psy); +void power_supply_remove_hwmon_sysfs(struct power_supply *psy); +#else +static inline int power_supply_add_hwmon_sysfs(struct power_supply *psy) +{ + return 0; +} + +static inline +void power_supply_remove_hwmon_sysfs(struct power_supply *psy) {} +#endif + #endif /* __LINUX_POWER_SUPPLY_H__ */ From 9faf929608f52b907d8d41a455f3bee852f3dc85 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Mon, 3 Jun 2019 22:23:17 +0000 Subject: [PATCH 11/19] dt-bindings: power: reset: qcom: Add qcom,pm8998-pon compatibility line Update bindings to support for qcom,pm8998-pon which uses gen2 pon Cc: Andy Gross Cc: David Brown Cc: Bjorn Andersson Cc: Amit Pundir Cc: Rob Herring Cc: Mark Rutland Cc: Sebastian Reichel Cc: linux-arm-msm@vger.kernel.org Cc: devicetree@vger.kernel.org Reviewed-by: Bjorn Andersson Signed-off-by: John Stultz Signed-off-by: Sebastian Reichel --- Documentation/devicetree/bindings/power/reset/qcom,pon.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/Documentation/devicetree/bindings/power/reset/qcom,pon.txt b/Documentation/devicetree/bindings/power/reset/qcom,pon.txt index 5705f575862d..0c0dc3a1e693 100644 --- a/Documentation/devicetree/bindings/power/reset/qcom,pon.txt +++ b/Documentation/devicetree/bindings/power/reset/qcom,pon.txt @@ -9,6 +9,7 @@ Required Properties: -compatible: Must be one of: "qcom,pm8916-pon" "qcom,pms405-pon" + "qcom,pm8998-pon" -reg: Specifies the physical address of the pon register From fce5430f6a86911ba3b4c8343ab04a88d7ad7795 Mon Sep 17 00:00:00 2001 From: John Stultz Date: Mon, 3 Jun 2019 22:23:18 +0000 Subject: [PATCH 12/19] reset: qcom-pon: Add support for gen2 pon Add support for gen2 pon register so "reboot bootloader" can work on pixel3 and db845. Cc: Andy Gross Cc: David Brown Cc: Bjorn Andersson Cc: Amit Pundir Cc: Rob Herring Cc: Mark Rutland Cc: Sebastian Reichel Cc: linux-arm-msm@vger.kernel.org Cc: devicetree@vger.kernel.org Reviewed-by: Bjorn Andersson Signed-off-by: John Stultz v2: * Split out dts changes into separate path * Minor cleanups and remove unused variables Signed-off-by: Sebastian Reichel --- drivers/power/reset/qcom-pon.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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); From cba155e50a117465b64e197e197aedc18770c4cd Mon Sep 17 00:00:00 2001 From: Han Nandor Date: Wed, 15 May 2019 10:47:15 +0000 Subject: [PATCH 13/19] dt-bindings: power: reset: add document for NVMEM based reboot-mode Add the device tree bindings document for the NVMEM based reboot-mode driver. Signed-off-by: Nandor Han Reviewed-by: Rob Herring Signed-off-by: Sebastian Reichel --- .../power/reset/nvmem-reboot-mode.txt | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 Documentation/devicetree/bindings/power/reset/nvmem-reboot-mode.txt diff --git a/Documentation/devicetree/bindings/power/reset/nvmem-reboot-mode.txt b/Documentation/devicetree/bindings/power/reset/nvmem-reboot-mode.txt new file mode 100644 index 000000000000..752d6126d5da --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/nvmem-reboot-mode.txt @@ -0,0 +1,26 @@ +NVMEM reboot mode driver + +This driver gets reboot mode magic value from reboot-mode driver +and stores it in a NVMEM cell named "reboot-mode". Then the bootloader +can read it and take different action according to the magic +value stored. + +Required properties: +- compatible: should be "nvmem-reboot-mode". +- nvmem-cells: A phandle to the reboot mode provided by a nvmem device. +- nvmem-cell-names: Should be "reboot-mode". + +The rest of the properties should follow the generic reboot-mode description +found in reboot-mode.txt + +Example: + reboot-mode { + compatible = "nvmem-reboot-mode"; + nvmem-cells = <&reboot_mode>; + nvmem-cell-names = "reboot-mode"; + + mode-normal = <0xAAAA5501>; + mode-bootloader = <0xBBBB5500>; + mode-recovery = <0xCCCC5502>; + mode-test = <0xDDDD5503>; + }; From 7a78a7f7695bf9ef9cef3c06fbc5fa4573fd0eef Mon Sep 17 00:00:00 2001 From: Han Nandor Date: Wed, 15 May 2019 10:47:14 +0000 Subject: [PATCH 14/19] power: reset: nvmem-reboot-mode: use NVMEM as reboot mode write interface Add a new reboot mode write interface that is using an NVMEM cell to store the reboot mode magic. Signed-off-by: Nandor Han Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 9 +++ drivers/power/reset/Makefile | 1 + drivers/power/reset/nvmem-reboot-mode.c | 76 +++++++++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100644 drivers/power/reset/nvmem-reboot-mode.c diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 6533aa560aa1..bb4a4e854f96 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -245,5 +245,14 @@ config POWER_RESET_SC27XX PMICs includes the SC2720, SC2721, SC2723, SC2730 and SC2731 chips. +config NVMEM_REBOOT_MODE + tristate "Generic NVMEM reboot mode driver" + 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 +#include +#include +#include +#include +#include +#include + +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 "); +MODULE_DESCRIPTION("NVMEM reboot mode driver"); +MODULE_LICENSE("GPL"); From 89e7854fcd5a9d9030faf47cf0244ecc4d097628 Mon Sep 17 00:00:00 2001 From: kbuild test robot Date: Sat, 11 May 2019 02:03:47 +0800 Subject: [PATCH 15/19] power: supply: fix semicolon.cocci warnings drivers/power/supply/ucs1002_power.c:339:2-3: Unneeded semicolon Remove unneeded semicolon. Generated by: scripts/coccinelle/misc/semicolon.cocci Fixes: 9a2688e42638 ("power: supply: Add driver for Microchip UCS1002") CC: Andrey Smirnov Signed-off-by: kbuild test robot Signed-off-by: Sebastian Reichel --- drivers/power/supply/ucs1002_power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; From a4496d52b3430cb3c4c16d03cdd5f4ee97ad1241 Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Tue, 7 May 2019 11:52:47 +0200 Subject: [PATCH 16/19] power: supply: add input power and voltage limit properties For thermal management strategy you might be interested on limit the input power for a power supply. We already have current limit but basically what we probably want is to limit power. So, introduce the input_power_limit property. Although the common use case is limit the input power, in some specific cases it is the voltage that is problematic (i.e some regulators have different efficiencies at higher voltage resulting in more heat). So introduce also the input_voltage_limit property. This happens in one Chromebook and is used on the Pixel C's thermal management strategy to effectively limit the input power to 5V 3A when the screen is on. When the screen is on, the display, the CPU, and the GPU all contribute more heat to the system than while the screen is off, and we made a tradeoff to throttle the charger in order to give more of the thermal budget to those other components. So there's nothing fundamentally broken about the hardware that would cause the Pixel C to malfunction if we were charging at 9V or 12V instead of 5V when the screen is on, i.e. if userspace doesn't change this. What would happen is that you wouldn't meet Google's skin temperature targets on the system if the charger was allowed to run at 9V or 12V with the screen on. For folks hacking on Pixel Cs (which is now outside of Google's official support window for Android) and customizing their own kernel and userspace this would be acceptable, but we wanted to expose this feature in the power supply properties because the feature does exist in the Emedded Controller firmware of the Pixel C and all of Google's Chromebooks with USB-C made since 2015 in case someone running an up to date kernel wanted to limit the charging power for thermal or other reasons. This patch exposes a new property, similar to input current limit, to re-configure the maximum voltage from the external supply at runtime based on system-level knowledge or user input. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Guenter Roeck Acked-by: Adam Thomson Reviewed-by: Benson Leung Signed-off-by: Sebastian Reichel --- Documentation/ABI/testing/sysfs-class-power | 32 +++++++++++++++++++++ Documentation/power/power_supply_class.txt | 4 +++ drivers/power/supply/power_supply_sysfs.c | 2 ++ include/linux/power_supply.h | 2 ++ 4 files changed, 40 insertions(+) diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index b77e30b9014e..27edc06e2495 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -376,10 +376,42 @@ Description: supply. Normally this is configured based on the type of connection made (e.g. A configured SDP should output a maximum of 500mA so the input current limit is set to the same value). + Use preferably input_power_limit, and for problems that can be + solved using power limit use input_current_limit. Access: Read, Write Valid values: Represented in microamps +What: /sys/class/power_supply//input_voltage_limit +Date: May 2019 +Contact: linux-pm@vger.kernel.org +Description: + This entry configures the incoming VBUS voltage limit currently + set in the supply. Normally this is configured based on + system-level knowledge or user input (e.g. This is part of the + Pixel C's thermal management strategy to effectively limit the + input power to 5V when the screen is on to meet Google's skin + temperature targets). Note that this feature should not be + used for safety critical things. + Use preferably input_power_limit, and for problems that can be + solved using power limit use input_voltage_limit. + + Access: Read, Write + Valid values: Represented in microvolts + +What: /sys/class/power_supply//input_power_limit +Date: May 2019 +Contact: linux-pm@vger.kernel.org +Description: + This entry configures the incoming power limit currently set + in the supply. Normally this is configured based on + system-level knowledge or user input. Use preferably this + feature to limit the incoming power and use current/voltage + limit only for problems that can be solved using power limit. + + Access: Read, Write + Valid values: Represented in microwatts + What: /sys/class/power_supply//online, Date: May 2007 Contact: linux-pm@vger.kernel.org diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt index 300d37896e51..1e3c705111db 100644 --- a/Documentation/power/power_supply_class.txt +++ b/Documentation/power/power_supply_class.txt @@ -137,6 +137,10 @@ power supply object. INPUT_CURRENT_LIMIT - input current limit programmed by charger. Indicates the current drawn from a charging source. +INPUT_VOLTAGE_LIMIT - input voltage limit programmed by charger. Indicates +the voltage limit from a charging source. +INPUT_POWER_LIMIT - input power limit programmed by charger. Indicates +the power limit from a charging source. CHARGE_CONTROL_LIMIT - current charge control limit setting CHARGE_CONTROL_LIMIT_MAX - maximum charge control limit setting diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index a704a76d7529..829e12c800e5 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -277,6 +277,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/include/linux/power_supply.h b/include/linux/power_supply.h index d5b15e039f4f..cbb708b57b11 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -129,6 +129,8 @@ enum power_supply_property { POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD, /* in percents! */ POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD, /* in percents! */ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_POWER_LIMIT, POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN, POWER_SUPPLY_PROP_ENERGY_FULL, From 2ffb500d824bbe6535c64d3e7e9971cca0db0a3e Mon Sep 17 00:00:00 2001 From: Enric Balletbo i Serra Date: Tue, 7 May 2019 11:52:48 +0200 Subject: [PATCH 17/19] power: supply: cros: allow to set input voltage and current limit This patch allows reading and writing the input voltage and current limit through the POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT and POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT sysfs properties. This allows userspace to see current values and to re-configure these values at runtime based on system-level knowledge or user input. By default there is no limit, this is reported as a -1 when reading from userspace. Writing a value will set the current or voltage limit in uA or uV, and writing any negative value will remove that limit. Signed-off-by: Enric Balletbo i Serra Reviewed-by: Guenter Roeck Signed-off-by: Sebastian Reichel --- drivers/power/supply/cros_usbpd-charger.c | 116 ++++++++++++++++++++++ 1 file changed, 116 insertions(+) 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; From 3f57fe28f84d636ca6238f33afe8b88d27c34d1b Mon Sep 17 00:00:00 2001 From: Nick Crews Date: Wed, 8 May 2019 14:38:25 -0600 Subject: [PATCH 18/19] power_supply: wilco_ec: Add charging config driver Add a driver to control the charging algorithm used on Wilco devices. See Documentation/ABI/testing/sysfs-class-power-wilco for the userspace interface and other info. Signed-off-by: Nick Crews Reviewed-by: Enric Balletbo i Serra Signed-off-by: Sebastian Reichel --- .../ABI/testing/sysfs-class-power-wilco | 30 +++ drivers/power/supply/Kconfig | 9 + drivers/power/supply/Makefile | 1 + drivers/power/supply/wilco-charger.c | 187 ++++++++++++++++++ 4 files changed, 227 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-class-power-wilco create mode 100644 drivers/power/supply/wilco-charger.c diff --git a/Documentation/ABI/testing/sysfs-class-power-wilco b/Documentation/ABI/testing/sysfs-class-power-wilco new file mode 100644 index 000000000000..da1d6ffe5e3c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-power-wilco @@ -0,0 +1,30 @@ +What: /sys/class/power_supply/wilco-charger/charge_type +Date: April 2019 +KernelVersion: 5.2 +Description: + What charging algorithm to use: + + Standard: Fully charges battery at a standard rate. + Adaptive: Battery settings adaptively optimized based on + typical battery usage pattern. + Fast: Battery charges over a shorter period. + Trickle: Extends battery lifespan, intended for users who + primarily use their Chromebook while connected to AC. + Custom: A low and high threshold percentage is specified. + Charging begins when level drops below + charge_control_start_threshold, and ceases when + level is above charge_control_end_threshold. + +What: /sys/class/power_supply/wilco-charger/charge_control_start_threshold +Date: April 2019 +KernelVersion: 5.2 +Description: + Used when charge_type="Custom", as described above. Measured in + percentages. The valid range is [50, 95]. + +What: /sys/class/power_supply/wilco-charger/charge_control_end_threshold +Date: April 2019 +KernelVersion: 5.2 +Description: + Used when charge_type="Custom", as described above. Measured in + percentages. The valid range is [55, 100]. diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 1f2252cb95fd..4c01598f5ccb 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -702,4 +702,13 @@ config CHARGER_UCS1002 Say Y to enable support for Microchip UCS1002 Programmable USB Port Power Controller with Charger Emulation. +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 c47e88ba16b9..d2263e1e2b6f 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -91,3 +91,4 @@ 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_WILCO) += wilco-charger.o 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 +#include +#include +#include + +#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 "); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Wilco EC charge control driver"); From caa2b557841c8cedc1d4862cd31cf76ee940d105 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Mon, 8 Jul 2019 14:52:39 +0200 Subject: [PATCH 19/19] power: reset: nvmem-reboot-mode: add CONFIG_OF dependency Without CONFIG_OF, we get a build failure in the reboot-mode implementation: drivers/power/reset/reboot-mode.c: In function 'reboot_mode_register': drivers/power/reset/reboot-mode.c:72:2: error: implicit declaration of function 'for_each_property_of_node'; did you mean 'for_each_child_of_node'? [-Werror=implicit-function-declaration] for_each_property_of_node(np, prop) { Add a Kconfig dependency like we have for the other users of CONFIG_REBOOT_MODE. Fixes: 7a78a7f7695b ("power: reset: nvmem-reboot-mode: use NVMEM as reboot mode write interface") Signed-off-by: Arnd Bergmann Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index bb4a4e854f96..736dac97cbdd 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -247,6 +247,7 @@ config POWER_RESET_SC27XX 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