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

@ -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 <[email protected]>");
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 <[email protected]>");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("Wilco EC charge control driver");