power supply and reset changes for the v5.3 series

Core:
  * Add HWMON compat layer
  * New properties
   - input power limit
   - input voltage limit
 
 Drivers:
  * qcom-pon: add gen2 support
  * New driver for storing reboot move in NVMEM
  * New driver for Wilco EC charger configuration
  * simplify getting the adapter of a client
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAl0s0xgACgkQ2O7X88g7
 +pqxFQ/9GmfgHpzZ+qQmpBR5zyw1+yrhls3BXYEgHtGM+3YZ6n1sF8Yl1eUYpviC
 ldvN3vHXaxRlG5eDBwMl3ScWZnaxMpZssigO3lL4o+kYng0c0xqaPZZYxA9oJNgn
 0ertZrYcQZWmT82aRnjt2/p+8n+Hld6bv89PodWdLvsDvId1qQPXu5ILV0JL/QNK
 FMQepuaiRu9VXlyPCuWYwoOmKruZjLF7SOyis+I4e55U7lHeyCOySH/tZTTFgd+n
 hUpWm4ekc7YCAJVVJUQcdBtfNvQm1KtGkLSnSockH/636kP2fh5ESj76z8i5I6/6
 yl7OrkCyhespqS9hGCKCPU95s8MQe8HurlGR8aIWHLJJMiv1hIVOq7n9Uj+mmdRS
 OkKQHo/RUxXn5ioCUF3F3NcB94/95f0AWrx3RXjeXd2kYlUmVKCHyaGjPT9WfSOe
 MUcLZwM+GsG+3SWBhPGqjuIhIGfBBuQk+mcYLPLP/j3emNeLByYEtEDhvoQbEooU
 TCyJGR+FGIAyjXcW/uZzxx8MiZPybSXo7a4j837Cx6sRNwZJ4V9Ve/7XdUy7DKD0
 kOBH/ndJhoKJQkup+HEGmv/8os4K8gyW/kaiu718mS0oLDfQGDy0C0Y8BNoJnw4k
 /jo/1q0KY+8Hd6bxqbommA2ORAw7XsDZB7eWWC4gDqMXVcF1S6k=
 =fmGg
 -----END PGP SIGNATURE-----

Merge tag 'for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply

Pull power supply and reset updates from Sebastian Reichel:
 "Core:
   - add HWMON compat layer
   - new properties:
       - input power limit
       - input voltage limit

  Drivers:
   - qcom-pon: add gen2 support
   - new driver for storing reboot move in NVMEM
   - new driver for Wilco EC charger configuration
   - simplify getting the adapter of a client"

* tag 'for-v5.3' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply:
  power: reset: nvmem-reboot-mode: add CONFIG_OF dependency
  power_supply: wilco_ec: Add charging config driver
  power: supply: cros: allow to set input voltage and current limit
  power: supply: add input power and voltage limit properties
  power: supply: fix semicolon.cocci warnings
  power: reset: nvmem-reboot-mode: use NVMEM as reboot mode write interface
  dt-bindings: power: reset: add document for NVMEM based reboot-mode
  reset: qcom-pon: Add support for gen2 pon
  dt-bindings: power: reset: qcom: Add qcom,pm8998-pon compatibility line
  power: supply: Add HWMON compatibility layer
  power: supply: sbs-manager: simplify getting the adapter of a client
  power: supply: rt9455_charger: simplify getting the adapter of a client
  power: supply: rt5033_battery: simplify getting the adapter of a client
  power: supply: max17042_battery: simplify getting the adapter of a client
  power: supply: max17040_battery: simplify getting the adapter of a client
  power: supply: max14656_charger_detector: simplify getting the adapter of a client
  power: supply: bq25890_charger: simplify getting the adapter of a client
  power: supply: bq24257_charger: simplify getting the adapter of a client
  power: supply: bq24190_charger: simplify getting the adapter of a client
This commit is contained in:
Linus Torvalds 2019-07-15 21:06:15 -07:00
commit 5fe7b600a1
27 changed files with 908 additions and 13 deletions

View file

@ -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/<supply_name>/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/<supply_name>/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/<supply_name>/online,
Date: May 2007
Contact: linux-pm@vger.kernel.org

View file

@ -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].

View file

@ -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>;
};

View file

@ -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

View file

@ -165,6 +165,12 @@ CONSTANT_CHARGE_VOLTAGE_MAX
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

View file

@ -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

View file

@ -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

View file

@ -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");

View file

@ -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);

View file

@ -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

View file

@ -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

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View file

@ -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),

View file

@ -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;

View file

@ -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 = {};

View file

@ -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;

View file

@ -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;

View file

@ -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");

View file

@ -128,6 +128,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,
@ -480,4 +482,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__ */