From 1e45330bd29067f1d0a1319e06798f2b284dbfe6 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 3 May 2017 17:26:13 -0700 Subject: power: supply: cpcap-charger: Update charge current table and add comments Turns out a similar battery charger hardware is documented for NXP MC13783 PMIC in "MC13783 Power Management and Audio Circuit Users's Guide" named MC13783UG.pdf. Looks like the CPCAP charge current table matches that, so let's start using the nominal values from it. While at it, let's also add comments to some of the mystery CPCAP charger registers based on the MC13783UG.pdf documentation. Note that this patch does not contain any functional changes, the register values being used stay the same. Cc: Marcel Partap Cc: Michael Scott Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 59 +++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 24 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index 26a2dc7ac9a2..adb9767adc8a 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -38,20 +38,27 @@ #include #include -/* CPCAP_REG_CRM register bits */ +/* + * CPCAP_REG_CRM register bits. For documentation of somewhat similar hardware, + * see NXP "MC13783 Power Management and Audio Circuit Users's Guide" + * MC13783UG.pdf chapter "8.5 Battery Interface Register Summary". The registers + * and values for CPCAP are different, but some of the internal components seem + * similar. Also see the Motorola Linux kernel cpcap-regbits.h. CPCAP_REG_CHRGR_1 + * bits that seem to describe the CRM register. + */ #define CPCAP_REG_CRM_UNUSED_641_15 BIT(15) /* 641 = register number */ #define CPCAP_REG_CRM_UNUSED_641_14 BIT(14) /* 641 = register number */ -#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) -#define CPCAP_REG_CRM_RVRSMODE BIT(12) -#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) +#define CPCAP_REG_CRM_CHRG_LED_EN BIT(13) /* Charger LED */ +#define CPCAP_REG_CRM_RVRSMODE BIT(12) /* USB VBUS output enable */ +#define CPCAP_REG_CRM_ICHRG_TR1 BIT(11) /* Trickle charge current */ #define CPCAP_REG_CRM_ICHRG_TR0 BIT(10) -#define CPCAP_REG_CRM_FET_OVRD BIT(9) -#define CPCAP_REG_CRM_FET_CTRL BIT(8) -#define CPCAP_REG_CRM_VCHRG3 BIT(7) +#define CPCAP_REG_CRM_FET_OVRD BIT(9) /* 0 = hardware, 1 = FET_CTRL */ +#define CPCAP_REG_CRM_FET_CTRL BIT(8) /* BPFET 1 if FET_OVRD set */ +#define CPCAP_REG_CRM_VCHRG3 BIT(7) /* Charge voltage bits */ #define CPCAP_REG_CRM_VCHRG2 BIT(6) #define CPCAP_REG_CRM_VCHRG1 BIT(5) #define CPCAP_REG_CRM_VCHRG0 BIT(4) -#define CPCAP_REG_CRM_ICHRG3 BIT(3) +#define CPCAP_REG_CRM_ICHRG3 BIT(3) /* Charge current bits */ #define CPCAP_REG_CRM_ICHRG2 BIT(2) #define CPCAP_REG_CRM_ICHRG1 BIT(1) #define CPCAP_REG_CRM_ICHRG0 BIT(0) @@ -82,23 +89,27 @@ #define CPCAP_REG_CRM_VCHRG_4V42 CPCAP_REG_CRM_VCHRG(0xe) #define CPCAP_REG_CRM_VCHRG_4V44 CPCAP_REG_CRM_VCHRG(0xf) -/* CPCAP_REG_CRM charge currents */ +/* + * CPCAP_REG_CRM charge currents. These seem to match MC13783UG.pdf + * values in "Table 8-3. Charge Path Regulator Current Limit + * Characteristics" for the nominal values. + */ #define CPCAP_REG_CRM_ICHRG(val) (((val) & 0xf) << 0) #define CPCAP_REG_CRM_ICHRG_0A000 CPCAP_REG_CRM_ICHRG(0x0) #define CPCAP_REG_CRM_ICHRG_0A070 CPCAP_REG_CRM_ICHRG(0x1) -#define CPCAP_REG_CRM_ICHRG_0A176 CPCAP_REG_CRM_ICHRG(0x2) -#define CPCAP_REG_CRM_ICHRG_0A264 CPCAP_REG_CRM_ICHRG(0x3) -#define CPCAP_REG_CRM_ICHRG_0A352 CPCAP_REG_CRM_ICHRG(0x4) -#define CPCAP_REG_CRM_ICHRG_0A440 CPCAP_REG_CRM_ICHRG(0x5) -#define CPCAP_REG_CRM_ICHRG_0A528 CPCAP_REG_CRM_ICHRG(0x6) -#define CPCAP_REG_CRM_ICHRG_0A616 CPCAP_REG_CRM_ICHRG(0x7) -#define CPCAP_REG_CRM_ICHRG_0A704 CPCAP_REG_CRM_ICHRG(0x8) -#define CPCAP_REG_CRM_ICHRG_0A792 CPCAP_REG_CRM_ICHRG(0x9) -#define CPCAP_REG_CRM_ICHRG_0A880 CPCAP_REG_CRM_ICHRG(0xa) -#define CPCAP_REG_CRM_ICHRG_0A968 CPCAP_REG_CRM_ICHRG(0xb) -#define CPCAP_REG_CRM_ICHRG_1A056 CPCAP_REG_CRM_ICHRG(0xc) -#define CPCAP_REG_CRM_ICHRG_1A144 CPCAP_REG_CRM_ICHRG(0xd) -#define CPCAP_REG_CRM_ICHRG_1A584 CPCAP_REG_CRM_ICHRG(0xe) +#define CPCAP_REG_CRM_ICHRG_0A177 CPCAP_REG_CRM_ICHRG(0x2) +#define CPCAP_REG_CRM_ICHRG_0A266 CPCAP_REG_CRM_ICHRG(0x3) +#define CPCAP_REG_CRM_ICHRG_0A355 CPCAP_REG_CRM_ICHRG(0x4) +#define CPCAP_REG_CRM_ICHRG_0A443 CPCAP_REG_CRM_ICHRG(0x5) +#define CPCAP_REG_CRM_ICHRG_0A532 CPCAP_REG_CRM_ICHRG(0x6) +#define CPCAP_REG_CRM_ICHRG_0A621 CPCAP_REG_CRM_ICHRG(0x7) +#define CPCAP_REG_CRM_ICHRG_0A709 CPCAP_REG_CRM_ICHRG(0x8) +#define CPCAP_REG_CRM_ICHRG_0A798 CPCAP_REG_CRM_ICHRG(0x9) +#define CPCAP_REG_CRM_ICHRG_0A886 CPCAP_REG_CRM_ICHRG(0xa) +#define CPCAP_REG_CRM_ICHRG_0A975 CPCAP_REG_CRM_ICHRG(0xb) +#define CPCAP_REG_CRM_ICHRG_1A064 CPCAP_REG_CRM_ICHRG(0xc) +#define CPCAP_REG_CRM_ICHRG_1A152 CPCAP_REG_CRM_ICHRG(0xd) +#define CPCAP_REG_CRM_ICHRG_1A596 CPCAP_REG_CRM_ICHRG(0xe) #define CPCAP_REG_CRM_ICHRG_NO_LIMIT CPCAP_REG_CRM_ICHRG(0xf) enum { @@ -428,9 +439,9 @@ static void cpcap_usb_detect(struct work_struct *work) int max_current; if (cpcap_charger_battery_found(ddata)) - max_current = CPCAP_REG_CRM_ICHRG_1A584; + max_current = CPCAP_REG_CRM_ICHRG_1A596; else - max_current = CPCAP_REG_CRM_ICHRG_0A528; + max_current = CPCAP_REG_CRM_ICHRG_0A532; error = cpcap_charger_set_state(ddata, CPCAP_REG_CRM_VCHRG_4V35, -- cgit v1.2.3 From 70c9fc9a5605b747ff4d2c276d277833560e09fe Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 3 May 2017 17:26:14 -0700 Subject: power: supply: cpcap-charger: Fix charger voltages based on ADC values With the ADC driver working, we can now fix the voltage table based on the values read from the ADC. Note that unlike the ICHRG registers, the VCHRG register bits don't match the MC13783UG.pdf. Cc: Marcel Partap Cc: Michael Scott Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index adb9767adc8a..a798af4471d7 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -70,19 +70,23 @@ #define CPCAP_REG_CRM_TR_0A48 CPCAP_REG_CRM_TR(0x2) #define CPCAP_REG_CRM_TR_0A72 CPCAP_REG_CRM_TR(0x4) -/* CPCAP_REG_CRM charge voltages */ +/* + * CPCAP_REG_CRM charge voltages based on the ADC channel 1 values. + * Note that these register bits don't match MC13783UG.pdf VCHRG + * register bits. + */ #define CPCAP_REG_CRM_VCHRG(val) (((val) & 0xf) << 4) #define CPCAP_REG_CRM_VCHRG_3V80 CPCAP_REG_CRM_VCHRG(0x0) #define CPCAP_REG_CRM_VCHRG_4V10 CPCAP_REG_CRM_VCHRG(0x1) -#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x2) -#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x3) -#define CPCAP_REG_CRM_VCHRG_4V22 CPCAP_REG_CRM_VCHRG(0x4) -#define CPCAP_REG_CRM_VCHRG_4V24 CPCAP_REG_CRM_VCHRG(0x5) -#define CPCAP_REG_CRM_VCHRG_4V26 CPCAP_REG_CRM_VCHRG(0x6) -#define CPCAP_REG_CRM_VCHRG_4V28 CPCAP_REG_CRM_VCHRG(0x7) -#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x8) -#define CPCAP_REG_CRM_VCHRG_4V32 CPCAP_REG_CRM_VCHRG(0x9) -#define CPCAP_REG_CRM_VCHRG_4V34 CPCAP_REG_CRM_VCHRG(0xa) +#define CPCAP_REG_CRM_VCHRG_4V12 CPCAP_REG_CRM_VCHRG(0x2) +#define CPCAP_REG_CRM_VCHRG_4V15 CPCAP_REG_CRM_VCHRG(0x3) +#define CPCAP_REG_CRM_VCHRG_4V17 CPCAP_REG_CRM_VCHRG(0x4) +#define CPCAP_REG_CRM_VCHRG_4V20 CPCAP_REG_CRM_VCHRG(0x5) +#define CPCAP_REG_CRM_VCHRG_4V23 CPCAP_REG_CRM_VCHRG(0x6) +#define CPCAP_REG_CRM_VCHRG_4V25 CPCAP_REG_CRM_VCHRG(0x7) +#define CPCAP_REG_CRM_VCHRG_4V27 CPCAP_REG_CRM_VCHRG(0x8) +#define CPCAP_REG_CRM_VCHRG_4V30 CPCAP_REG_CRM_VCHRG(0x9) +#define CPCAP_REG_CRM_VCHRG_4V33 CPCAP_REG_CRM_VCHRG(0xa) #define CPCAP_REG_CRM_VCHRG_4V35 CPCAP_REG_CRM_VCHRG(0xb) #define CPCAP_REG_CRM_VCHRG_4V38 CPCAP_REG_CRM_VCHRG(0xc) #define CPCAP_REG_CRM_VCHRG_4V40 CPCAP_REG_CRM_VCHRG(0xd) -- cgit v1.2.3 From c94d4ed017ae6e2630647b917e775ab2fd8ea0f3 Mon Sep 17 00:00:00 2001 From: Mike Looijmans Date: Tue, 9 May 2017 07:44:17 +0200 Subject: power: supply: Add ltc3651-charger driver The LTC3651 reports its status via GPIO lines. This driver translates the GPIO levels to battery charger status information via sysfs. It relies on devicetree to supply the IO configuration. Signed-off-by: Mike Looijmans Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 7 ++ drivers/power/supply/Makefile | 1 + drivers/power/supply/ltc3651-charger.c | 210 +++++++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 drivers/power/supply/ltc3651-charger.c (limited to 'drivers/power') diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 86f40bf37c34..30598aa05e21 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -408,6 +408,13 @@ config CHARGER_MANAGER runtime and in suspend-to-RAM by waking up the system periodically with help of suspend_again support. +config CHARGER_LTC3651 + tristate "LTC3651 charger" + depends on GPIOLIB + help + Say Y to include support for the LTC3651 battery charger which reports + its status via GPIO lines. + config CHARGER_MAX14577 tristate "Maxim MAX14577/77836 battery charger driver" depends on MFD_MAX14577 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index a39126d7a6ce..c5e576f0f0ee 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -61,6 +61,7 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o +obj-$(CONFIG_CHARGER_LTC3651) += ltc3651-charger.o obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o diff --git a/drivers/power/supply/ltc3651-charger.c b/drivers/power/supply/ltc3651-charger.c new file mode 100644 index 000000000000..5f8d5c0b5721 --- /dev/null +++ b/drivers/power/supply/ltc3651-charger.c @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2017, Topic Embedded Products + * Driver for LTC3651 charger IC. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct ltc3651_charger { + struct power_supply *charger; + struct power_supply_desc charger_desc; + struct gpio_desc *acpr_gpio; + struct gpio_desc *fault_gpio; + struct gpio_desc *chrg_gpio; +}; + +static irqreturn_t ltc3651_charger_irq(int irq, void *devid) +{ + struct power_supply *charger = devid; + + power_supply_changed(charger); + + return IRQ_HANDLED; +} + +static inline struct ltc3651_charger *psy_to_ltc3651_charger( + struct power_supply *psy) +{ + return power_supply_get_drvdata(psy); +} + +static int ltc3651_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct ltc3651_charger *ltc3651_charger = psy_to_ltc3651_charger(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!ltc3651_charger->chrg_gpio) { + val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + break; + } + if (gpiod_get_value(ltc3651_charger->chrg_gpio)) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = gpiod_get_value(ltc3651_charger->acpr_gpio); + break; + case POWER_SUPPLY_PROP_HEALTH: + if (!ltc3651_charger->fault_gpio) { + val->intval = POWER_SUPPLY_HEALTH_UNKNOWN; + break; + } + if (!gpiod_get_value(ltc3651_charger->fault_gpio)) { + val->intval = POWER_SUPPLY_HEALTH_GOOD; + break; + } + /* + * If the fault pin is active, the chrg pin explains the type + * of failure. + */ + if (!ltc3651_charger->chrg_gpio) { + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + } + val->intval = gpiod_get_value(ltc3651_charger->chrg_gpio) ? + POWER_SUPPLY_HEALTH_OVERHEAT : + POWER_SUPPLY_HEALTH_DEAD; + break; + default: + return -EINVAL; + } + + return 0; +} + +static enum power_supply_property ltc3651_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, +}; + +static int ltc3651_charger_probe(struct platform_device *pdev) +{ + struct power_supply_config psy_cfg = {}; + struct ltc3651_charger *ltc3651_charger; + struct power_supply_desc *charger_desc; + int ret; + + ltc3651_charger = devm_kzalloc(&pdev->dev, sizeof(*ltc3651_charger), + GFP_KERNEL); + if (!ltc3651_charger) + return -ENOMEM; + + ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, + "lltc,acpr", GPIOD_IN); + if (IS_ERR(ltc3651_charger->acpr_gpio)) { + ret = PTR_ERR(ltc3651_charger->charger); + dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); + return ret; + } + ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, + "lltc,fault", GPIOD_IN); + if (IS_ERR(ltc3651_charger->fault_gpio)) { + ret = PTR_ERR(ltc3651_charger->charger); + dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); + return ret; + } + ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, + "lltc,chrg", GPIOD_IN); + if (IS_ERR(ltc3651_charger->chrg_gpio)) { + ret = PTR_ERR(ltc3651_charger->charger); + dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); + return ret; + } + + charger_desc = <c3651_charger->charger_desc; + charger_desc->name = pdev->dev.of_node->name; + charger_desc->type = POWER_SUPPLY_TYPE_MAINS; + charger_desc->properties = ltc3651_charger_properties; + charger_desc->num_properties = ARRAY_SIZE(ltc3651_charger_properties); + charger_desc->get_property = ltc3651_charger_get_property; + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ltc3651_charger; + + ltc3651_charger->charger = devm_power_supply_register(&pdev->dev, + charger_desc, &psy_cfg); + if (IS_ERR(ltc3651_charger->charger)) { + ret = PTR_ERR(ltc3651_charger->charger); + dev_err(&pdev->dev, "Failed to register power supply: %d\n", + ret); + return ret; + } + + /* + * Acquire IRQs for the GPIO pins if possible. If the system does not + * support IRQs on these pins, userspace will have to poll the sysfs + * files manually. + */ + if (ltc3651_charger->acpr_gpio) { + ret = gpiod_to_irq(ltc3651_charger->acpr_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request acpr irq\n"); + } + if (ltc3651_charger->fault_gpio) { + ret = gpiod_to_irq(ltc3651_charger->fault_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request fault irq\n"); + } + if (ltc3651_charger->chrg_gpio) { + ret = gpiod_to_irq(ltc3651_charger->chrg_gpio); + if (ret >= 0) + ret = devm_request_any_context_irq(&pdev->dev, ret, + ltc3651_charger_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&pdev->dev), ltc3651_charger->charger); + if (ret < 0) + dev_warn(&pdev->dev, "Failed to request chrg irq\n"); + } + + platform_set_drvdata(pdev, ltc3651_charger); + + return 0; +} + +static const struct of_device_id ltc3651_charger_match[] = { + { .compatible = "lltc,ltc3651-charger" }, + { } +}; +MODULE_DEVICE_TABLE(of, ltc3651_charger_match); + +static struct platform_driver ltc3651_charger_driver = { + .probe = ltc3651_charger_probe, + .driver = { + .name = "ltc3651-charger", + .of_match_table = ltc3651_charger_match, + }, +}; + +module_platform_driver(ltc3651_charger_driver); + +MODULE_AUTHOR("Mike Looijmans "); +MODULE_DESCRIPTION("Driver for LTC3651 charger"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ltc3651-charger"); -- cgit v1.2.3 From a463182031c122f91ccebc14f743654f12aa9cec Mon Sep 17 00:00:00 2001 From: Julia Lawall Date: Thu, 4 May 2017 22:10:49 +0200 Subject: power: supply: axp20x_usb_power: Drop unnecessary static Drop static on a local variable, when the variable is either first initialized or never used, on every possible execution path through the function. The static has no benefit, and dropping it reduces the code size. The semantic patch that fixes this problem is as follows: (http://coccinelle.lip6.fr/) // @bad exists@ position p; identifier x; type T; @@ static T x@p; ... x = <+...x...+> @@ identifier x; expression e; type T; position p != bad.p; @@ -static T x@p; ... when != x when strict ?x = e; // The change in code size is indicates by the following output from the size command. before: text data bss dec hex filename 2865 252 8 3125 c35 drivers/power/supply/axp20x_usb_power.o after: text data bss dec hex filename 2822 252 0 3074 c02 drivers/power/supply/axp20x_usb_power.o Signed-off-by: Julia Lawall Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_usb_power.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index 2397c482656e..44f70dcea61e 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -339,7 +339,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL }; static const char * const axp22x_irq_names[] = { "VBUS_PLUGIN", "VBUS_REMOVAL", NULL }; - static const char * const *irq_names; + const char * const *irq_names; const struct power_supply_desc *usb_power_desc; int i, irq, ret; -- cgit v1.2.3 From 58a36bb06891ee779074db6ef84e98347c634d38 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Mon, 8 May 2017 17:13:54 +0200 Subject: power: supply: core: Add support for supplied-from device-property On devicetree using platforms the devicetree can provide info on which power-supplies supply another power-supply through phandles. This commit adds support for providing this info on non devicetree platforms through the platform code setting a supplied-from device-property on the power-supplies parent device. Signed-off-by: Hans de Goede Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 7ec7c7c202bd..0c09144193a6 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -274,8 +274,30 @@ static int power_supply_check_supplies(struct power_supply *psy) return power_supply_populate_supplied_from(psy); } #else -static inline int power_supply_check_supplies(struct power_supply *psy) +static int power_supply_check_supplies(struct power_supply *psy) { + int nval, ret; + + if (!psy->dev.parent) + return 0; + + nval = device_property_read_string_array(psy->dev.parent, + "supplied-from", NULL, 0); + if (nval <= 0) + return 0; + + psy->supplied_from = devm_kmalloc_array(&psy->dev, nval, + sizeof(char *), GFP_KERNEL); + if (!psy->supplied_from) + return -ENOMEM; + + ret = device_property_read_string_array(psy->dev.parent, + "supplied-from", (const char **)psy->supplied_from, nval); + if (ret < 0) + return ret; + + psy->num_supplies = nval; + return 0; } #endif -- cgit v1.2.3 From 71399aa5d68bb3ed8c4caf8bfd71faae39555876 Mon Sep 17 00:00:00 2001 From: Benson Leung Date: Mon, 8 May 2017 15:02:48 -0700 Subject: power: supply: Add Apple Brick ID power supply type Apple currently supports three very common USB chargers: https://www.apple.com/power-adapters/ These chargers implement a proprietary Apple method for advertising 1A, 2.1A, and 2.4A at 5V called "Brick ID". In addition, 3rd parties implement the same charging method in many charging accessories that work with iOS devices. Devices that have charger detection chips such as the Pericom PI3USB9281, eg. Google Chromebook Pixel 2015, are capable of detecting these chargers, so let's add a type to facilicate passing that info up to userspace. This adds a separate power supply type for Apple's proprietary "Brick ID" charging method. Signed-off-by: Benson Leung Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 2 +- include/linux/power_supply.h | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index bcde8d13476a..07b484f995c1 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -46,7 +46,7 @@ static ssize_t power_supply_show_property(struct device *dev, static char *type_text[] = { "Unknown", "Battery", "UPS", "Mains", "USB", "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", - "USB_PD", "USB_PD_DRP" + "USB_PD", "USB_PD_DRP", "BrickID" }; static char *status_text[] = { "Unknown", "Charging", "Discharging", "Not charging", "Full" diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 3965503315ef..4bd34051995e 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -159,13 +159,14 @@ enum power_supply_type { POWER_SUPPLY_TYPE_BATTERY, POWER_SUPPLY_TYPE_UPS, POWER_SUPPLY_TYPE_MAINS, - POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */ - POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */ - POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */ - POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */ - POWER_SUPPLY_TYPE_USB_TYPE_C, /* Type C Port */ - POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */ - POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */ + POWER_SUPPLY_TYPE_USB, /* Standard Downstream Port */ + POWER_SUPPLY_TYPE_USB_DCP, /* Dedicated Charging Port */ + POWER_SUPPLY_TYPE_USB_CDP, /* Charging Downstream Port */ + POWER_SUPPLY_TYPE_USB_ACA, /* Accessory Charger Adapters */ + POWER_SUPPLY_TYPE_USB_TYPE_C, /* Type C Port */ + POWER_SUPPLY_TYPE_USB_PD, /* Power Delivery Port */ + POWER_SUPPLY_TYPE_USB_PD_DRP, /* PD Dual Role Port */ + POWER_SUPPLY_TYPE_APPLE_BRICK_ID, /* Apple Charging Method */ }; enum power_supply_notifier_events { -- cgit v1.2.3 From 105df60f207301631258c966d82d99f826508b41 Mon Sep 17 00:00:00 2001 From: David Lechner Date: Mon, 15 May 2017 16:21:15 -0500 Subject: power: supply: sysfs: parse string as enum when writing property This fixes the TODO to parse strings and convert them to enum values when writing to a power_supply class property sysfs attribute. There is at least one driver that has a writable enum property that previously could only be written as an integer, so a fallback to writing enums as integers instead of strings is provided so we don't break existing userspace programs. Signed-off-by: David Lechner Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_sysfs.c | 124 ++++++++++++++++++++---------- 1 file changed, 85 insertions(+), 39 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index 07b484f995c1..b32c14183773 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -40,35 +40,42 @@ static struct device_attribute power_supply_attrs[]; +static const char * const power_supply_type_text[] = { + "Unknown", "Battery", "UPS", "Mains", "USB", + "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", + "USB_PD", "USB_PD_DRP", "BrickID" +}; + +static const char * const power_supply_status_text[] = { + "Unknown", "Charging", "Discharging", "Not charging", "Full" +}; + +static const char * const power_supply_charge_type_text[] = { + "Unknown", "N/A", "Trickle", "Fast" +}; + +static const char * const power_supply_health_text[] = { + "Unknown", "Good", "Overheat", "Dead", "Over voltage", + "Unspecified failure", "Cold", "Watchdog timer expire", + "Safety timer expire" +}; + +static const char * const power_supply_technology_text[] = { + "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", + "LiMn" +}; + +static const char * const power_supply_capacity_level_text[] = { + "Unknown", "Critical", "Low", "Normal", "High", "Full" +}; + +static const char * const power_supply_scope_text[] = { + "Unknown", "System", "Device" +}; + static ssize_t power_supply_show_property(struct device *dev, struct device_attribute *attr, char *buf) { - static char *type_text[] = { - "Unknown", "Battery", "UPS", "Mains", "USB", - "USB_DCP", "USB_CDP", "USB_ACA", "USB_C", - "USB_PD", "USB_PD_DRP", "BrickID" - }; - static char *status_text[] = { - "Unknown", "Charging", "Discharging", "Not charging", "Full" - }; - static char *charge_type[] = { - "Unknown", "N/A", "Trickle", "Fast" - }; - static char *health_text[] = { - "Unknown", "Good", "Overheat", "Dead", "Over voltage", - "Unspecified failure", "Cold", "Watchdog timer expire", - "Safety timer expire" - }; - static char *technology_text[] = { - "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", - "LiMn" - }; - static char *capacity_level_text[] = { - "Unknown", "Critical", "Low", "Normal", "High", "Full" - }; - static char *scope_text[] = { - "Unknown", "System", "Device" - }; ssize_t ret = 0; struct power_supply *psy = dev_get_drvdata(dev); const ptrdiff_t off = attr - power_supply_attrs; @@ -91,19 +98,26 @@ static ssize_t power_supply_show_property(struct device *dev, } if (off == POWER_SUPPLY_PROP_STATUS) - return sprintf(buf, "%s\n", status_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_status_text[value.intval]); else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE) - return sprintf(buf, "%s\n", charge_type[value.intval]); + return sprintf(buf, "%s\n", + power_supply_charge_type_text[value.intval]); else if (off == POWER_SUPPLY_PROP_HEALTH) - return sprintf(buf, "%s\n", health_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_health_text[value.intval]); else if (off == POWER_SUPPLY_PROP_TECHNOLOGY) - return sprintf(buf, "%s\n", technology_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_technology_text[value.intval]); else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL) - return sprintf(buf, "%s\n", capacity_level_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_capacity_level_text[value.intval]); else if (off == POWER_SUPPLY_PROP_TYPE) - return sprintf(buf, "%s\n", type_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_type_text[value.intval]); else if (off == POWER_SUPPLY_PROP_SCOPE) - return sprintf(buf, "%s\n", scope_text[value.intval]); + return sprintf(buf, "%s\n", + power_supply_scope_text[value.intval]); else if (off >= POWER_SUPPLY_PROP_MODEL_NAME) return sprintf(buf, "%s\n", value.strval); @@ -117,14 +131,46 @@ static ssize_t power_supply_store_property(struct device *dev, struct power_supply *psy = dev_get_drvdata(dev); const ptrdiff_t off = attr - power_supply_attrs; union power_supply_propval value; - long long_val; - /* TODO: support other types than int */ - ret = kstrtol(buf, 10, &long_val); - if (ret < 0) - return ret; + /* maybe it is a enum property? */ + switch (off) { + case POWER_SUPPLY_PROP_STATUS: + ret = sysfs_match_string(power_supply_status_text, buf); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = sysfs_match_string(power_supply_charge_type_text, buf); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = sysfs_match_string(power_supply_health_text, buf); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + ret = sysfs_match_string(power_supply_technology_text, buf); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + ret = sysfs_match_string(power_supply_capacity_level_text, buf); + break; + case POWER_SUPPLY_PROP_SCOPE: + ret = sysfs_match_string(power_supply_scope_text, buf); + break; + default: + ret = -EINVAL; + } + + /* + * If no match was found, then check to see if it is an integer. + * Integer values are valid for enums in addition to the text value. + */ + if (ret < 0) { + long long_val; + + ret = kstrtol(buf, 10, &long_val); + if (ret < 0) + return ret; + + ret = long_val; + } - value.intval = long_val; + value.intval = ret; ret = power_supply_set_property(psy, off, &value); if (ret < 0) -- cgit v1.2.3 From e12854174bbe5bab7b8f3e378e05dc0700112112 Mon Sep 17 00:00:00 2001 From: Dan Carpenter Date: Thu, 18 May 2017 10:43:46 +0300 Subject: power: supply: ltc3651-charger: fix some error codes in probe There are several cut and past bugs here. ltc3651_charger->charger is NULL at this point, so we return success instead of the intended error codes. Fixes: c94d4ed017ae ("power: supply: Add ltc3651-charger driver") Signed-off-by: Dan Carpenter [Wei Yongjun found the same issue independently] Signed-off-by: Wei Yongjun Acked-by: Mike Looijmans Signed-off-by: Sebastian Reichel --- drivers/power/supply/ltc3651-charger.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/ltc3651-charger.c b/drivers/power/supply/ltc3651-charger.c index 5f8d5c0b5721..eea63ff211c4 100644 --- a/drivers/power/supply/ltc3651-charger.c +++ b/drivers/power/supply/ltc3651-charger.c @@ -110,21 +110,21 @@ static int ltc3651_charger_probe(struct platform_device *pdev) ltc3651_charger->acpr_gpio = devm_gpiod_get(&pdev->dev, "lltc,acpr", GPIOD_IN); if (IS_ERR(ltc3651_charger->acpr_gpio)) { - ret = PTR_ERR(ltc3651_charger->charger); + ret = PTR_ERR(ltc3651_charger->acpr_gpio); dev_err(&pdev->dev, "Failed to acquire acpr GPIO: %d\n", ret); return ret; } ltc3651_charger->fault_gpio = devm_gpiod_get_optional(&pdev->dev, "lltc,fault", GPIOD_IN); if (IS_ERR(ltc3651_charger->fault_gpio)) { - ret = PTR_ERR(ltc3651_charger->charger); + ret = PTR_ERR(ltc3651_charger->fault_gpio); dev_err(&pdev->dev, "Failed to acquire fault GPIO: %d\n", ret); return ret; } ltc3651_charger->chrg_gpio = devm_gpiod_get_optional(&pdev->dev, "lltc,chrg", GPIOD_IN); if (IS_ERR(ltc3651_charger->chrg_gpio)) { - ret = PTR_ERR(ltc3651_charger->charger); + ret = PTR_ERR(ltc3651_charger->chrg_gpio); dev_err(&pdev->dev, "Failed to acquire chrg GPIO: %d\n", ret); return ret; } -- cgit v1.2.3 From 49fb384653810167dca26812217d0235bece0367 Mon Sep 17 00:00:00 2001 From: H. Nikolaus Schaller Date: Sun, 21 May 2017 12:38:16 +0200 Subject: power: supply: twl4030-charger: remove nonstandard max_current sysfs attribute Since we now support the standard 'input_current_limit' property by commit 3fb319c2cdcd ("power: supply: twl4030-charger: add writable INPUT_CURRENT_LIMIT property") we can now remove the nonstandard 'max_current' sysfs attribute. See Documentation/power/power_supply_class.txt line 125 Both are functionally equivalent. From ABI point of view it is just a rename of the property. This also removes the entry in Documentation/ABI/testing/sysfs-class-power-twl4030 Signed-off-by: H. Nikolaus Schaller Signed-off-by: Sebastian Reichel --- .../ABI/testing/sysfs-class-power-twl4030 | 17 ------ drivers/power/supply/twl4030_charger.c | 63 ---------------------- 2 files changed, 80 deletions(-) (limited to 'drivers/power') diff --git a/Documentation/ABI/testing/sysfs-class-power-twl4030 b/Documentation/ABI/testing/sysfs-class-power-twl4030 index be26af0f1895..b4fd32d210c5 100644 --- a/Documentation/ABI/testing/sysfs-class-power-twl4030 +++ b/Documentation/ABI/testing/sysfs-class-power-twl4030 @@ -1,20 +1,3 @@ -What: /sys/class/power_supply/twl4030_ac/max_current - /sys/class/power_supply/twl4030_usb/max_current -Description: - Read/Write limit on current which may - be drawn from the ac (Accessory Charger) or - USB port. - - Value is in micro-Amps. - - Value is set automatically to an appropriate - value when a cable is plugged or unplugged. - - Value can the set by writing to the attribute. - The change will only persist until the next - plug event. These event are reported via udev. - - What: /sys/class/power_supply/twl4030_usb/mode Description: Changing mode for USB port. diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 2f82d0e9ec1b..785a07bc4f39 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -624,63 +624,6 @@ static irqreturn_t twl4030_bci_interrupt(int irq, void *arg) return IRQ_HANDLED; } -/* - * Provide "max_current" attribute in sysfs. - */ -static ssize_t -twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr, - const char *buf, size_t n) -{ - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - int cur = 0; - int status = 0; - status = kstrtoint(buf, 10, &cur); - if (status) - return status; - if (cur < 0) - return -EINVAL; - if (dev == &bci->ac->dev) - bci->ac_cur = cur; - else - bci->usb_cur_target = cur; - - twl4030_charger_update_current(bci); - return n; -} - -/* - * sysfs max_current show - */ -static ssize_t twl4030_bci_max_current_show(struct device *dev, - struct device_attribute *attr, char *buf) -{ - int status = 0; - int cur = -1; - u8 bcictl1; - struct twl4030_bci *bci = dev_get_drvdata(dev->parent); - - if (dev == &bci->ac->dev) { - if (!bci->ac_is_active) - cur = bci->ac_cur; - } else { - if (bci->ac_is_active) - cur = bci->usb_cur_target; - } - if (cur < 0) { - cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1); - if (cur < 0) - return cur; - status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1); - if (status < 0) - return status; - cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN); - } - return scnprintf(buf, PAGE_SIZE, "%u\n", cur); -} - -static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show, - twl4030_bci_max_current_store); - static void twl4030_bci_usb_work(struct work_struct *data) { struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work); @@ -1111,14 +1054,10 @@ static int twl4030_bci_probe(struct platform_device *pdev) dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret); twl4030_charger_update_current(bci); - if (device_create_file(&bci->usb->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); if (device_create_file(&bci->usb->dev, &dev_attr_mode)) dev_warn(&pdev->dev, "could not create sysfs file\n"); if (device_create_file(&bci->ac->dev, &dev_attr_mode)) dev_warn(&pdev->dev, "could not create sysfs file\n"); - if (device_create_file(&bci->ac->dev, &dev_attr_max_current)) - dev_warn(&pdev->dev, "could not create sysfs file\n"); twl4030_charger_enable_ac(bci, true); if (!IS_ERR_OR_NULL(bci->transceiver)) @@ -1150,9 +1089,7 @@ static int twl4030_bci_remove(struct platform_device *pdev) iio_channel_release(bci->channel_vac); - device_remove_file(&bci->usb->dev, &dev_attr_max_current); device_remove_file(&bci->usb->dev, &dev_attr_mode); - device_remove_file(&bci->ac->dev, &dev_attr_max_current); device_remove_file(&bci->ac->dev, &dev_attr_mode); /* mask interrupts */ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, -- cgit v1.2.3 From 2e9bbbf694fd9ed4d96a303dadce4d8ccf2a4af3 Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Wed, 31 May 2017 11:49:14 +0200 Subject: power: reset: at91-poweroff: fix clobber list Assembly in at91_lpddr_poweroff has r0 in the clobber list but uses r6. Reported-by: Ben Hutchings Signed-off-by: Alexandre Belloni Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-poweroff.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/reset/at91-poweroff.c b/drivers/power/reset/at91-poweroff.c index c6c3beea72f9..c30c40193aaa 100644 --- a/drivers/power/reset/at91-poweroff.c +++ b/drivers/power/reset/at91-poweroff.c @@ -97,7 +97,7 @@ static void at91_lpddr_poweroff(void) "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), "r" (at91_shdwc_base), "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW) - : "r0"); + : "r6"); } static int at91_poweroff_get_wakeup_mode(struct device_node *np) -- cgit v1.2.3 From be04a0d77eb1896f56eee5ef8f2f5281008c22ec Mon Sep 17 00:00:00 2001 From: Alexandre Belloni Date: Wed, 31 May 2017 11:49:15 +0200 Subject: power: reset: at91-sama5d2_shdwc: fix clobber list Assembly in at91_lpddr_poweroff has r0 in the clobber list but uses r6. Reported-by: Ben Hutchings Signed-off-by: Alexandre Belloni Acked-by: Nicolas Ferre Signed-off-by: Sebastian Reichel --- drivers/power/reset/at91-sama5d2_shdwc.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/reset/at91-sama5d2_shdwc.c b/drivers/power/reset/at91-sama5d2_shdwc.c index 90b0b5a70ce5..55fce8b75245 100644 --- a/drivers/power/reset/at91-sama5d2_shdwc.c +++ b/drivers/power/reset/at91-sama5d2_shdwc.c @@ -132,7 +132,7 @@ static void at91_lpddr_poweroff(void) "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF), "r" (at91_shdwc->at91_shdwc_base), "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW) - : "r0"); + : "r6"); } static u32 at91_shdwc_debouncer_value(struct platform_device *pdev, -- cgit v1.2.3 From 12031fcae9e74f582579c71c3b2a78cc82ad6c3c Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 31 May 2017 13:39:45 -0700 Subject: power: reset: Allow selecting POWER_RESET_BRCMSTB on ARM64 Since commit 37eb56dc79a8 ("arm64: Add Broadcom Set Top Box Kconfig entry point") we have ARCH_BRCMSTB also visible on ARM64 platform, yet this reboot driver was not selectable, so fix that. Signed-off-by: Florian Fainelli Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 13f1714cf6f7..fdad987bed89 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -58,7 +58,7 @@ config POWER_RESET_BRCMKONA config POWER_RESET_BRCMSTB bool "Broadcom STB reset driver" - depends on ARM || MIPS || COMPILE_TEST + depends on ARM || ARM64 || MIPS || COMPILE_TEST depends on MFD_SYSCON default ARCH_BRCMSTB help -- cgit v1.2.3 From 1d2495e8c2d976208e8bb52d52b4e529e3b1ff75 Mon Sep 17 00:00:00 2001 From: Florian Fainelli Date: Wed, 31 May 2017 13:39:46 -0700 Subject: power: reset: Default POWER_RESET_BRCMSTB to BMIPS_GENERIC On Broadcom MIPS STB platforms, BMIPS_GENERIC is the Kconfig symbol that is used, make this reboot driver default to that value to make sure we can reboot a system properly. Signed-off-by: Florian Fainelli Signed-off-by: Sebastian Reichel --- drivers/power/reset/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index fdad987bed89..ca0de1a78e85 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -60,7 +60,7 @@ config POWER_RESET_BRCMSTB bool "Broadcom STB reset driver" depends on ARM || ARM64 || MIPS || COMPILE_TEST depends on MFD_SYSCON - default ARCH_BRCMSTB + default ARCH_BRCMSTB || BMIPS_GENERIC help This driver provides restart support for Broadcom STB boards. -- cgit v1.2.3 From 874b2adbed1253a11549cb9b7b912ab65fea9cf2 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 31 May 2017 17:19:21 -0700 Subject: power: supply: cpcap-battery: Add a battery driver On the CPCAP PMIC we can use the ADCs for monitoring the battery, and there is also a coulomb counter. So let's add basic support for the battery driver. I did not add any capacity prediction as that should probably be done in the user space. Or at least user space should tell the kernel some battery statistics and then the kernel driver could display the capacity based on that. Cc: devicetree@vger.kernel.org Cc: Marcel Partap Cc: Michael Scott Cc: Rob Herring Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 8 + drivers/power/supply/Makefile | 1 + drivers/power/supply/cpcap-battery.c | 808 +++++++++++++++++++++++++++++++++++ 3 files changed, 817 insertions(+) create mode 100644 drivers/power/supply/cpcap-battery.c (limited to 'drivers/power') diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 30598aa05e21..f04c44c6c3f3 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -82,6 +82,14 @@ config BATTERY_ACT8945A Say Y here to enable support for power supply provided by Active-semi ActivePath ACT8945A charger. +config BATTERY_CPCAP + tristate "Motorola CPCAP PMIC battery driver" + depends on MFD_CPCAP && IIO + default MFD_CPCAP + help + Say Y here to enable support for battery on Motorola + phones and tablets such as droid 4. + config BATTERY_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" depends on W1 && W1_SLAVE_DS2760 diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index c5e576f0f0ee..a41f40957847 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -20,6 +20,7 @@ obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o +obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c new file mode 100644 index 000000000000..ee71a2b37b12 --- /dev/null +++ b/drivers/power/supply/cpcap-battery.c @@ -0,0 +1,808 @@ +/* + * Battery driver for CPCAP PMIC + * + * Copyright (C) 2017 Tony Lindgren + * + * Some parts of the code based on earlie Motorola mapphone Linux kernel + * drivers: + * + * Copyright (C) 2009-2010 Motorola, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +/* + * Register bit defines for CPCAP_REG_BPEOL. Some of these seem to + * map to MC13783UG.pdf "Table 5-19. Register 13, Power Control 0" + * to enable BATTDETEN, LOBAT and EOL features. We currently use + * LOBAT interrupts instead of EOL. + */ +#define CPCAP_REG_BPEOL_BIT_EOL9 BIT(9) /* Set for EOL irq */ +#define CPCAP_REG_BPEOL_BIT_EOL8 BIT(8) /* Set for EOL irq */ +#define CPCAP_REG_BPEOL_BIT_UNKNOWN7 BIT(7) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN6 BIT(6) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN5 BIT(5) +#define CPCAP_REG_BPEOL_BIT_EOL_MULTI BIT(4) /* Set for multiple EOL irqs */ +#define CPCAP_REG_BPEOL_BIT_UNKNOWN3 BIT(3) +#define CPCAP_REG_BPEOL_BIT_UNKNOWN2 BIT(2) +#define CPCAP_REG_BPEOL_BIT_BATTDETEN BIT(1) /* Enable battery detect */ +#define CPCAP_REG_BPEOL_BIT_EOLSEL BIT(0) /* BPDET = 0, EOL = 1 */ + +#define CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS 250 + +enum { + CPCAP_BATTERY_IIO_BATTDET, + CPCAP_BATTERY_IIO_VOLTAGE, + CPCAP_BATTERY_IIO_CHRG_CURRENT, + CPCAP_BATTERY_IIO_BATT_CURRENT, + CPCAP_BATTERY_IIO_NR, +}; + +enum cpcap_battery_irq_action { + CPCAP_BATTERY_IRQ_ACTION_NONE, + CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW, + CPCAP_BATTERY_IRQ_ACTION_POWEROFF, +}; + +struct cpcap_interrupt_desc { + const char *name; + struct list_head node; + int irq; + enum cpcap_battery_irq_action action; +}; + +struct cpcap_battery_config { + int ccm; + int cd_factor; + struct power_supply_info info; +}; + +struct cpcap_coulomb_counter_data { + s32 sample; /* 24-bits */ + s32 accumulator; + s16 offset; /* 10-bits */ +}; + +enum cpcap_battery_state { + CPCAP_BATTERY_STATE_PREVIOUS, + CPCAP_BATTERY_STATE_LATEST, + CPCAP_BATTERY_STATE_NR, +}; + +struct cpcap_battery_state_data { + int voltage; + int current_ua; + int counter_uah; + int temperature; + ktime_t time; + struct cpcap_coulomb_counter_data cc; +}; + +struct cpcap_battery_ddata { + struct device *dev; + struct regmap *reg; + struct list_head irq_list; + struct iio_channel *channels[CPCAP_BATTERY_IIO_NR]; + struct power_supply *psy; + struct cpcap_battery_config config; + struct cpcap_battery_state_data state[CPCAP_BATTERY_STATE_NR]; + atomic_t active; + int status; + u16 vendor; +}; + +#define CPCAP_NO_BATTERY -400 + +static struct cpcap_battery_state_data * +cpcap_battery_get_state(struct cpcap_battery_ddata *ddata, + enum cpcap_battery_state state) +{ + if (state >= CPCAP_BATTERY_STATE_NR) + return NULL; + + return &ddata->state[state]; +} + +static struct cpcap_battery_state_data * +cpcap_battery_latest(struct cpcap_battery_ddata *ddata) +{ + return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_LATEST); +} + +static struct cpcap_battery_state_data * +cpcap_battery_previous(struct cpcap_battery_ddata *ddata) +{ + return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_PREVIOUS); +} + +static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata, + int *value) +{ + struct iio_channel *channel; + int error; + + channel = ddata->channels[CPCAP_BATTERY_IIO_BATTDET]; + error = iio_read_channel_processed(channel, value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + *value = CPCAP_NO_BATTERY; + + return error; + } + + *value /= 100; + + return 0; +} + +static int cpcap_battery_get_voltage(struct cpcap_battery_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_BATTERY_IIO_VOLTAGE]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value * 1000; +} + +static int cpcap_battery_get_current(struct cpcap_battery_ddata *ddata) +{ + struct iio_channel *channel; + int error, value = 0; + + channel = ddata->channels[CPCAP_BATTERY_IIO_BATT_CURRENT]; + error = iio_read_channel_processed(channel, &value); + if (error < 0) { + dev_warn(ddata->dev, "%s failed: %i\n", __func__, error); + + return 0; + } + + return value * 1000; +} + +/** + * cpcap_battery_cc_raw_div - calculate and divide coulomb counter μAms values + * @ddata: device driver data + * @sample: coulomb counter sample value + * @accumulator: coulomb counter integrator value + * @offset: coulomb counter offset value + * @divider: conversion divider + * + * Note that cc_lsb and cc_dur values are from Motorola Linux kernel + * function data_get_avg_curr_ua() and seem to be based on measured test + * results. It also has the following comment: + * + * Adjustment factors are applied here as a temp solution per the test + * results. Need to work out a formal solution for this adjustment. + * + * A coulomb counter for similar hardware seems to be documented in + * "TWL6030 Gas Gauging Basics (Rev. A)" swca095a.pdf in chapter + * "10 Calculating Accumulated Current". We however follow what the + * Motorola mapphone Linux kernel is doing as there may be either a + * TI or ST coulomb counter in the PMIC. + */ +static int cpcap_battery_cc_raw_div(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset, u32 divider) +{ + s64 acc; + u64 tmp; + int avg_current; + u32 cc_lsb; + + sample &= 0xffffff; /* 24-bits, unsigned */ + offset &= 0x7ff; /* 10-bits, signed */ + + switch (ddata->vendor) { + case CPCAP_VENDOR_ST: + cc_lsb = 95374; /* μAms per LSB */ + break; + case CPCAP_VENDOR_TI: + cc_lsb = 91501; /* μAms per LSB */ + break; + default: + return -EINVAL; + } + + acc = accumulator; + acc = acc - ((s64)sample * offset); + cc_lsb = (cc_lsb * ddata->config.cd_factor) / 1000; + + if (acc >= 0) + tmp = acc; + else + tmp = acc * -1; + + tmp = tmp * cc_lsb; + do_div(tmp, divider); + avg_current = tmp; + + if (acc >= 0) + return -avg_current; + else + return avg_current; +} + +/* 3600000μAms = 1μAh */ +static int cpcap_battery_cc_to_uah(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset) +{ + return cpcap_battery_cc_raw_div(ddata, sample, + accumulator, offset, + 3600000); +} + +static int cpcap_battery_cc_to_ua(struct cpcap_battery_ddata *ddata, + u32 sample, s32 accumulator, + s16 offset) +{ + return cpcap_battery_cc_raw_div(ddata, sample, + accumulator, offset, + sample * + CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS); +} + +/** + * cpcap_battery_read_accumulated - reads cpcap coulomb counter + * @ddata: device driver data + * @regs: coulomb counter values + * + * Based on Motorola mapphone kernel function data_read_regs(). + * Looking at the registers, the coulomb counter seems similar to + * the coulomb counter in TWL6030. See "TWL6030 Gas Gauging Basics + * (Rev. A) swca095a.pdf for "10 Calculating Accumulated Current". + * + * Note that swca095a.pdf instructs to stop the coulomb counter + * before reading to avoid values changing. Motorola mapphone + * Linux kernel does not do it, so let's assume they've verified + * the data produced is correct. + */ +static int +cpcap_battery_read_accumulated(struct cpcap_battery_ddata *ddata, + struct cpcap_coulomb_counter_data *ccd) +{ + u16 buf[7]; /* CPCAP_REG_CC1 to CCI */ + int error; + + ccd->sample = 0; + ccd->accumulator = 0; + ccd->offset = 0; + + /* Read coulomb counter register range */ + error = regmap_bulk_read(ddata->reg, CPCAP_REG_CCS1, + buf, ARRAY_SIZE(buf)); + if (error) + return 0; + + /* Sample value CPCAP_REG_CCS1 & 2 */ + ccd->sample = (buf[1] & 0x0fff) << 16; + ccd->sample |= buf[0]; + + /* Accumulator value CPCAP_REG_CCA1 & 2 */ + ccd->accumulator = ((s16)buf[3]) << 16; + ccd->accumulator |= buf[2]; + + /* Offset value CPCAP_REG_CCO */ + ccd->offset = buf[5]; + + /* Adjust offset based on mode value CPCAP_REG_CCM? */ + if (buf[4] >= 0x200) + ccd->offset |= 0xfc00; + + return cpcap_battery_cc_to_uah(ddata, + ccd->sample, + ccd->accumulator, + ccd->offset); +} + +/** + * cpcap_battery_cc_get_avg_current - read cpcap coulumb counter + * @ddata: cpcap battery driver device data + */ +static int cpcap_battery_cc_get_avg_current(struct cpcap_battery_ddata *ddata) +{ + int value, acc, error; + s32 sample = 1; + s16 offset; + + if (ddata->vendor == CPCAP_VENDOR_ST) + sample = 4; + + /* Coulomb counter integrator */ + error = regmap_read(ddata->reg, CPCAP_REG_CCI, &value); + if (error) + return error; + + if ((ddata->vendor == CPCAP_VENDOR_TI) && (value > 0x2000)) + value = value | 0xc000; + + acc = (s16)value; + + /* Coulomb counter sample time */ + error = regmap_read(ddata->reg, CPCAP_REG_CCM, &value); + if (error) + return error; + + if (value < 0x200) + offset = value; + else + offset = value | 0xfc00; + + return cpcap_battery_cc_to_ua(ddata, sample, acc, offset); +} + +static bool cpcap_battery_full(struct cpcap_battery_ddata *ddata) +{ + struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata); + + /* Basically anything that measures above 4347000 is full */ + if (state->voltage >= (ddata->config.info.voltage_max_design - 4000)) + return true; + + return false; +} + +static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata) +{ + struct cpcap_battery_state_data state, *latest, *previous; + ktime_t now; + int error; + + memset(&state, 0, sizeof(state)); + now = ktime_get(); + + latest = cpcap_battery_latest(ddata); + if (latest) { + s64 delta_ms = ktime_to_ms(ktime_sub(now, latest->time)); + + if (delta_ms < CPCAP_BATTERY_CC_SAMPLE_PERIOD_MS) + return delta_ms; + } + + state.time = now; + state.voltage = cpcap_battery_get_voltage(ddata); + state.current_ua = cpcap_battery_get_current(ddata); + state.counter_uah = cpcap_battery_read_accumulated(ddata, &state.cc); + + error = cpcap_charger_battery_temperature(ddata, + &state.temperature); + if (error) + return error; + + previous = cpcap_battery_previous(ddata); + memcpy(previous, latest, sizeof(*previous)); + memcpy(latest, &state, sizeof(*latest)); + + return 0; +} + +static enum power_supply_property cpcap_battery_props[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_POWER_NOW, + POWER_SUPPLY_PROP_POWER_AVG, + POWER_SUPPLY_PROP_CAPACITY_LEVEL, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_TEMP, +}; + +static int cpcap_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy); + struct cpcap_battery_state_data *latest, *previous; + u32 sample; + s32 accumulator; + int cached; + s64 tmp; + + cached = cpcap_battery_update_status(ddata); + if (cached < 0) + return cached; + + latest = cpcap_battery_latest(ddata); + previous = cpcap_battery_previous(ddata); + + switch (psp) { + case POWER_SUPPLY_PROP_PRESENT: + if (latest->temperature > CPCAP_NO_BATTERY) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (cpcap_battery_full(ddata)) { + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + } + if (cpcap_battery_cc_get_avg_current(ddata) < 0) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = ddata->config.info.technology; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = cpcap_battery_get_voltage(ddata); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = ddata->config.info.voltage_max_design; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = ddata->config.info.voltage_min_design; + break; + case POWER_SUPPLY_PROP_CURRENT_AVG: + if (cached) { + val->intval = cpcap_battery_cc_get_avg_current(ddata); + break; + } + sample = latest->cc.sample - previous->cc.sample; + accumulator = latest->cc.accumulator - previous->cc.accumulator; + val->intval = cpcap_battery_cc_to_ua(ddata, sample, + accumulator, + latest->cc.offset); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = latest->current_ua; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = latest->counter_uah; + break; + case POWER_SUPPLY_PROP_POWER_NOW: + tmp = (latest->voltage / 10000) * latest->current_ua; + val->intval = div64_s64(tmp, 100); + break; + case POWER_SUPPLY_PROP_POWER_AVG: + if (cached) { + tmp = cpcap_battery_cc_get_avg_current(ddata); + tmp *= (latest->voltage / 10000); + val->intval = div64_s64(tmp, 100); + break; + } + sample = latest->cc.sample - previous->cc.sample; + accumulator = latest->cc.accumulator - previous->cc.accumulator; + tmp = cpcap_battery_cc_to_ua(ddata, sample, accumulator, + latest->cc.offset); + tmp *= ((latest->voltage + previous->voltage) / 20000); + val->intval = div64_s64(tmp, 100); + break; + case POWER_SUPPLY_PROP_CAPACITY_LEVEL: + if (cpcap_battery_full(ddata)) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL; + else if (latest->voltage >= 3750000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_HIGH; + else if (latest->voltage >= 3300000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL; + else if (latest->voltage > 3100000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW; + else if (latest->voltage <= 3100000) + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL; + else + val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = ddata->config.info.charge_full_design; + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = latest->temperature; + break; + default: + return -EINVAL; + } + + return 0; +} + +static irqreturn_t cpcap_battery_irq_thread(int irq, void *data) +{ + struct cpcap_battery_ddata *ddata = data; + struct cpcap_battery_state_data *latest; + struct cpcap_interrupt_desc *d; + + if (!atomic_read(&ddata->active)) + return IRQ_NONE; + + list_for_each_entry(d, &ddata->irq_list, node) { + if (irq == d->irq) + break; + } + + if (!d) + return IRQ_NONE; + + latest = cpcap_battery_latest(ddata); + + switch (d->action) { + case CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW: + if (latest->counter_uah >= 0) + dev_warn(ddata->dev, "Battery low at 3.3V!\n"); + break; + case CPCAP_BATTERY_IRQ_ACTION_POWEROFF: + if (latest->counter_uah >= 0) { + dev_emerg(ddata->dev, + "Battery empty at 3.1V, powering off\n"); + orderly_poweroff(true); + } + break; + default: + break; + } + + power_supply_changed(ddata->psy); + + return IRQ_HANDLED; +} + +static int cpcap_battery_init_irq(struct platform_device *pdev, + struct cpcap_battery_ddata *ddata, + const char *name) +{ + struct cpcap_interrupt_desc *d; + int irq, error; + + irq = platform_get_irq_byname(pdev, name); + if (!irq) + return -ENODEV; + + error = devm_request_threaded_irq(ddata->dev, irq, NULL, + cpcap_battery_irq_thread, + IRQF_SHARED, + name, ddata); + if (error) { + dev_err(ddata->dev, "could not get irq %s: %i\n", + name, error); + + return error; + } + + d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); + if (!d) + return -ENOMEM; + + d->name = name; + d->irq = irq; + + if (!strncmp(name, "lowbph", 6)) + d->action = CPCAP_BATTERY_IRQ_ACTION_BATTERY_LOW; + else if (!strncmp(name, "lowbpl", 6)) + d->action = CPCAP_BATTERY_IRQ_ACTION_POWEROFF; + + list_add(&d->node, &ddata->irq_list); + + return 0; +} + +static int cpcap_battery_init_interrupts(struct platform_device *pdev, + struct cpcap_battery_ddata *ddata) +{ + const char * const cpcap_battery_irqs[] = { + "eol", "lowbph", "lowbpl", + "chrgcurr1", "battdetb" + }; + int i, error; + + for (i = 0; i < ARRAY_SIZE(cpcap_battery_irqs); i++) { + error = cpcap_battery_init_irq(pdev, ddata, + cpcap_battery_irqs[i]); + if (error) + return error; + } + + /* Enable low battery interrupts for 3.3V high and 3.1V low */ + error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, + 0xffff, + CPCAP_REG_BPEOL_BIT_BATTDETEN); + if (error) + return error; + + return 0; +} + +static int cpcap_battery_init_iio(struct cpcap_battery_ddata *ddata) +{ + const char * const names[CPCAP_BATTERY_IIO_NR] = { + "battdetb", "battp", "chg_isense", "batti", + }; + int error, i; + + for (i = 0; i < CPCAP_BATTERY_IIO_NR; i++) { + ddata->channels[i] = devm_iio_channel_get(ddata->dev, + names[i]); + if (IS_ERR(ddata->channels[i])) { + error = PTR_ERR(ddata->channels[i]); + goto out_err; + } + + if (!ddata->channels[i]->indio_dev) { + error = -ENXIO; + goto out_err; + } + } + + return 0; + +out_err: + dev_err(ddata->dev, "could not initialize VBUS or ID IIO: %i\n", + error); + + return error; +} + +/* + * Based on the values from Motorola mapphone Linux kernel. In the + * the Motorola mapphone Linux kernel tree the value for pm_cd_factor + * is passed to the kernel via device tree. If it turns out to be + * something device specific we can consider that too later. + * + * And looking at the battery full and shutdown values for the stock + * kernel on droid 4, full is 4351000 and software initiates shutdown + * at 3078000. The device will die around 2743000. + */ +static const struct cpcap_battery_config cpcap_battery_default_data = { + .ccm = 0x3ff, + .cd_factor = 0x3cc, + .info.technology = POWER_SUPPLY_TECHNOLOGY_LION, + .info.voltage_max_design = 4351000, + .info.voltage_min_design = 3100000, + .info.charge_full_design = 1740000, +}; + +#ifdef CONFIG_OF +static const struct of_device_id cpcap_battery_id_table[] = { + { + .compatible = "motorola,cpcap-battery", + .data = &cpcap_battery_default_data, + }, + {}, +}; +MODULE_DEVICE_TABLE(of, cpcap_battery_id_table); +#endif + +static int cpcap_battery_probe(struct platform_device *pdev) +{ + struct power_supply_desc *psy_desc; + struct cpcap_battery_ddata *ddata; + const struct of_device_id *match; + struct power_supply_config psy_cfg = {}; + int error; + + match = of_match_device(of_match_ptr(cpcap_battery_id_table), + &pdev->dev); + if (!match) + return -EINVAL; + + if (!match->data) { + dev_err(&pdev->dev, "no configuration data found\n"); + + return -ENODEV; + } + + ddata = devm_kzalloc(&pdev->dev, sizeof(*ddata), GFP_KERNEL); + if (!ddata) + return -ENOMEM; + + INIT_LIST_HEAD(&ddata->irq_list); + ddata->dev = &pdev->dev; + memcpy(&ddata->config, match->data, sizeof(ddata->config)); + + ddata->reg = dev_get_regmap(ddata->dev->parent, NULL); + if (!ddata->reg) + return -ENODEV; + + error = cpcap_get_vendor(ddata->dev, ddata->reg, &ddata->vendor); + if (error) + return error; + + platform_set_drvdata(pdev, ddata); + + error = regmap_update_bits(ddata->reg, CPCAP_REG_CCM, + 0xffff, ddata->config.ccm); + if (error) + return error; + + error = cpcap_battery_init_interrupts(pdev, ddata); + if (error) + return error; + + error = cpcap_battery_init_iio(ddata); + if (error) + return error; + + psy_desc = devm_kzalloc(ddata->dev, sizeof(*psy_desc), GFP_KERNEL); + if (!psy_desc) + return -ENOMEM; + + psy_desc->name = "battery", + psy_desc->type = POWER_SUPPLY_TYPE_BATTERY, + psy_desc->properties = cpcap_battery_props, + psy_desc->num_properties = ARRAY_SIZE(cpcap_battery_props), + psy_desc->get_property = cpcap_battery_get_property, + + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ddata; + + ddata->psy = devm_power_supply_register(ddata->dev, psy_desc, + &psy_cfg); + error = PTR_ERR_OR_ZERO(ddata->psy); + if (error) { + dev_err(ddata->dev, "failed to register power supply\n"); + return error; + } + + atomic_set(&ddata->active, 1); + + return 0; +} + +static int cpcap_battery_remove(struct platform_device *pdev) +{ + struct cpcap_battery_ddata *ddata = platform_get_drvdata(pdev); + int error; + + atomic_set(&ddata->active, 0); + error = regmap_update_bits(ddata->reg, CPCAP_REG_BPEOL, + 0xffff, 0); + if (error) + dev_err(&pdev->dev, "could not disable: %i\n", error); + + return 0; +} + +static struct platform_driver cpcap_battery_driver = { + .driver = { + .name = "cpcap_battery", + .of_match_table = of_match_ptr(cpcap_battery_id_table), + }, + .probe = cpcap_battery_probe, + .remove = cpcap_battery_remove, +}; +module_platform_driver(cpcap_battery_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Tony Lindgren "); +MODULE_DESCRIPTION("CPCAP PMIC Battery Driver"); -- cgit v1.2.3 From c08b1f45d7d193b3e6dcbbf30d403cb49b667b8c Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Wed, 7 Jun 2017 11:37:51 -0700 Subject: power: supply: core: Add power_supply_battery_info and API power_supply_get_battery_info() reads battery data from devicetree. struct power_supply_battery_info provides battery data to drivers. Its fields correspond to elements in enum power_supply_property. Drivers may surface battery data in sysfs via corresponding POWER_SUPPLY_PROP_* fields. Signed-off-by: Matt Ranostay Signed-off-by: Liam Breck Signed-off-by: Sebastian Reichel --- Documentation/power/power_supply_class.txt | 12 +++++++ drivers/power/supply/power_supply_core.c | 57 ++++++++++++++++++++++++++++++ include/linux/power_supply.h | 22 ++++++++++++ 3 files changed, 91 insertions(+) (limited to 'drivers/power') diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt index 0c72588bd967..01f007591070 100644 --- a/Documentation/power/power_supply_class.txt +++ b/Documentation/power/power_supply_class.txt @@ -174,6 +174,18 @@ issued by external power supply will notify supplicants via external_power_changed callback. +Devicetree battery characteristics +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +Drivers should call power_supply_get_battery_info() to obtain battery +characteristics from a devicetree battery node, defined in +Documentation/devicetree/bindings/power/supply/battery.txt. This is +implemented in drivers/power/supply/bq27xxx_battery.c. + +Properties in struct power_supply_battery_info and their counterparts in the +battery node have names corresponding to elements in enum power_supply_property, +for naming consistency between sysfs attributes and battery node properties. + + QA ~~ Q: Where is POWER_SUPPLY_PROP_XYZ attribute? diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 0c09144193a6..a4303ed66144 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include #include "power_supply.h" @@ -519,6 +520,62 @@ struct power_supply *devm_power_supply_get_by_phandle(struct device *dev, EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle); #endif /* CONFIG_OF */ +int power_supply_get_battery_info(struct power_supply *psy, + struct power_supply_battery_info *info) +{ + struct device_node *battery_np; + const char *value; + int err; + + info->energy_full_design_uwh = -EINVAL; + info->charge_full_design_uah = -EINVAL; + info->voltage_min_design_uv = -EINVAL; + info->precharge_current_ua = -EINVAL; + info->charge_term_current_ua = -EINVAL; + info->constant_charge_current_max_ua = -EINVAL; + info->constant_charge_voltage_max_uv = -EINVAL; + + if (!psy->of_node) { + dev_warn(&psy->dev, "%s currently only supports devicetree\n", + __func__); + return -ENXIO; + } + + battery_np = of_parse_phandle(psy->of_node, "monitored-battery", 0); + if (!battery_np) + return -ENODEV; + + err = of_property_read_string(battery_np, "compatible", &value); + if (err) + return err; + + if (strcmp("simple-battery", value)) + return -ENODEV; + + /* The property and field names below must correspond to elements + * in enum power_supply_property. For reasoning, see + * Documentation/power/power_supply_class.txt. + */ + + of_property_read_u32(battery_np, "energy-full-design-microwatt-hours", + &info->energy_full_design_uwh); + of_property_read_u32(battery_np, "charge-full-design-microamp-hours", + &info->charge_full_design_uah); + of_property_read_u32(battery_np, "voltage-min-design-microvolt", + &info->voltage_min_design_uv); + of_property_read_u32(battery_np, "precharge-current-microamp", + &info->precharge_current_ua); + of_property_read_u32(battery_np, "charge-term-current-microamp", + &info->charge_term_current_ua); + of_property_read_u32(battery_np, "constant_charge_current_max_microamp", + &info->constant_charge_current_max_ua); + of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt", + &info->constant_charge_voltage_max_uv); + + return 0; +} +EXPORT_SYMBOL_GPL(power_supply_get_battery_info); + int power_supply_get_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 4bd34051995e..34345d716286 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -289,6 +289,25 @@ struct power_supply_info { int use_for_apm; }; +/* + * This is the recommended struct to manage static battery parameters, + * populated by power_supply_get_battery_info(). Most platform drivers should + * use these for consistency. + * Its field names must correspond to elements in enum power_supply_property. + * The default field value is -EINVAL. + * Power supply class itself doesn't use this. + */ + +struct power_supply_battery_info { + int energy_full_design_uwh; /* microWatt-hours */ + int charge_full_design_uah; /* microAmp-hours */ + int voltage_min_design_uv; /* microVolts */ + int precharge_current_ua; /* microAmps */ + int charge_term_current_ua; /* microAmps */ + int constant_charge_current_max_ua; /* microAmps */ + int constant_charge_voltage_max_uv; /* microVolts */ +}; + extern struct atomic_notifier_head power_supply_notifier; extern int power_supply_reg_notifier(struct notifier_block *nb); extern void power_supply_unreg_notifier(struct notifier_block *nb); @@ -307,6 +326,9 @@ static inline struct power_supply * devm_power_supply_get_by_phandle(struct device *dev, const char *property) { return NULL; } #endif /* CONFIG_OF */ + +extern int power_supply_get_battery_info(struct power_supply *psy, + struct power_supply_battery_info *info); extern void power_supply_changed(struct power_supply *psy); extern int power_supply_am_i_supplied(struct power_supply *psy); extern int power_supply_set_battery_charged(struct power_supply *psy); -- cgit v1.2.3 From 413de34ab93edc80ef710c54ceb0987b8496aef3 Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Wed, 7 Jun 2017 11:37:52 -0700 Subject: power: supply: core: Add power_supply_prop_precharge Battery chargers use POWER_SUPPLY_PROP_PRECHARGE_CURRENT Clarify related item POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT Signed-off-by: Liam Breck Signed-off-by: Sebastian Reichel --- Documentation/power/power_supply_class.txt | 19 ++++++++++++------- drivers/power/supply/power_supply_sysfs.c | 1 + include/linux/power_supply.h | 3 +++ 3 files changed, 16 insertions(+), 7 deletions(-) (limited to 'drivers/power') diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt index 01f007591070..300d37896e51 100644 --- a/Documentation/power/power_supply_class.txt +++ b/Documentation/power/power_supply_class.txt @@ -115,28 +115,33 @@ of charge when battery became full/empty". It also could mean "value of charge when battery considered full/empty at given conditions (temperature, age)". I.e. these attributes represents real thresholds, not design values. +ENERGY_FULL, ENERGY_EMPTY - same as above but for energy. + CHARGE_COUNTER - the current charge counter (in µAh). This could easily be negative; there is no empty or full value. It is only useful for relative, time-based measurements. +PRECHARGE_CURRENT - the maximum charge current during precharge phase +of charge cycle (typically 20% of battery capacity). +CHARGE_TERM_CURRENT - Charge termination current. The charge cycle +terminates when battery voltage is above recharge threshold, and charge +current is below this setting (typically 10% of battery capacity). + CONSTANT_CHARGE_CURRENT - constant charge current programmed by charger. CONSTANT_CHARGE_CURRENT_MAX - maximum charge current supported by the power supply object. -INPUT_CURRENT_LIMIT - input current limit programmed by charger. Indicates -the current drawn from a charging source. -CHARGE_TERM_CURRENT - Charge termination current used to detect the end of charge -condition. - -CALIBRATE - battery or coulomb counter calibration status CONSTANT_CHARGE_VOLTAGE - constant charge voltage programmed by charger. CONSTANT_CHARGE_VOLTAGE_MAX - maximum charge voltage supported by the power supply object. +INPUT_CURRENT_LIMIT - input current limit programmed by charger. Indicates +the current drawn from a charging source. + CHARGE_CONTROL_LIMIT - current charge control limit setting CHARGE_CONTROL_LIMIT_MAX - maximum charge control limit setting -ENERGY_FULL, ENERGY_EMPTY - same as above but for energy. +CALIBRATE - battery or coulomb counter calibration status CAPACITY - capacity in percents. CAPACITY_ALERT_MIN - minimum capacity alert value in percents. diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index b32c14183773..5204f115970f 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -242,6 +242,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(time_to_full_avg), POWER_SUPPLY_ATTR(type), POWER_SUPPLY_ATTR(scope), + POWER_SUPPLY_ATTR(precharge_current), POWER_SUPPLY_ATTR(charge_term_current), POWER_SUPPLY_ATTR(calibrate), /* Properties of type `const char *' */ diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 34345d716286..de89066b72b1 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -146,6 +146,7 @@ enum power_supply_property { POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, POWER_SUPPLY_PROP_TYPE, /* use power_supply.type instead */ POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, POWER_SUPPLY_PROP_CALIBRATE, /* Properties of type `const char *' */ @@ -382,6 +383,8 @@ static inline bool power_supply_is_amp_property(enum power_supply_property psp) case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CHARGE_AVG: case POWER_SUPPLY_PROP_CHARGE_COUNTER: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: case POWER_SUPPLY_PROP_CURRENT_MAX: -- cgit v1.2.3 From 14073f6614f62dc7862c83575b042424599cc867 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Wed, 7 Jun 2017 11:37:54 -0700 Subject: power: supply: bq27xxx: Add bulk transfer bus methods Declare bus.write/read_bulk/write_bulk(). Add I2C write/read_bulk/write_bulk() to implement the above. Add bq27xxx_write/read_block/write_block() helpers to call the above. Signed-off-by: Matt Ranostay Signed-off-by: Liam Breck Acked-by: "Andrew F. Davis" Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 67 +++++++++++++++++++++++- drivers/power/supply/bq27xxx_battery_i2c.c | 82 +++++++++++++++++++++++++++++- include/linux/power/bq27xxx_battery.h | 3 ++ 3 files changed, 149 insertions(+), 3 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 398801a21b86..a11dfad76ed4 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -794,11 +794,74 @@ MODULE_PARM_DESC(poll_interval, static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index, bool single) { - /* Reports EINVAL for invalid/missing registers */ + int ret; + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) return -EINVAL; - return di->bus.read(di, di->regs[reg_index], single); + ret = di->bus.read(di, di->regs[reg_index], single); + if (ret < 0) + dev_dbg(di->dev, "failed to read register 0x%02x (index %d)\n", + di->regs[reg_index], reg_index); + + return ret; +} + +static inline int bq27xxx_write(struct bq27xxx_device_info *di, int reg_index, + u16 value, bool single) +{ + int ret; + + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; + + if (!di->bus.write) + return -EPERM; + + ret = di->bus.write(di, di->regs[reg_index], value, single); + if (ret < 0) + dev_dbg(di->dev, "failed to write register 0x%02x (index %d)\n", + di->regs[reg_index], reg_index); + + return ret; +} + +static inline int bq27xxx_read_block(struct bq27xxx_device_info *di, int reg_index, + u8 *data, int len) +{ + int ret; + + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; + + if (!di->bus.read_bulk) + return -EPERM; + + ret = di->bus.read_bulk(di, di->regs[reg_index], data, len); + if (ret < 0) + dev_dbg(di->dev, "failed to read_bulk register 0x%02x (index %d)\n", + di->regs[reg_index], reg_index); + + return ret; +} + +static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_index, + u8 *data, int len) +{ + int ret; + + if (!di || di->regs[reg_index] == INVALID_REG_ADDR) + return -EINVAL; + + if (!di->bus.write_bulk) + return -EPERM; + + ret = di->bus.write_bulk(di, di->regs[reg_index], data, len); + if (ret < 0) + dev_dbg(di->dev, "failed to write_bulk register 0x%02x (index %d)\n", + di->regs[reg_index], reg_index); + + return ret; } /* diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c index c68fbc3fe50a..a5972214f074 100644 --- a/drivers/power/supply/bq27xxx_battery_i2c.c +++ b/drivers/power/supply/bq27xxx_battery_i2c.c @@ -38,7 +38,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, { struct i2c_client *client = to_i2c_client(di->dev); struct i2c_msg msg[2]; - unsigned char data[2]; + u8 data[2]; int ret; if (!client->adapter) @@ -68,6 +68,82 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg, return ret; } +static int bq27xxx_battery_i2c_write(struct bq27xxx_device_info *di, u8 reg, + int value, bool single) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg; + u8 data[4]; + int ret; + + if (!client->adapter) + return -ENODEV; + + data[0] = reg; + if (single) { + data[1] = (u8) value; + msg.len = 2; + } else { + put_unaligned_le16(value, &data[1]); + msg.len = 3; + } + + msg.buf = data; + msg.addr = client->addr; + msg.flags = 0; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EINVAL; + return 0; +} + +static int bq27xxx_battery_i2c_bulk_read(struct bq27xxx_device_info *di, u8 reg, + u8 *data, int len) +{ + struct i2c_client *client = to_i2c_client(di->dev); + int ret; + + if (!client->adapter) + return -ENODEV; + + ret = i2c_smbus_read_i2c_block_data(client, reg, len, data); + if (ret < 0) + return ret; + if (ret != len) + return -EINVAL; + return 0; +} + +static int bq27xxx_battery_i2c_bulk_write(struct bq27xxx_device_info *di, + u8 reg, u8 *data, int len) +{ + struct i2c_client *client = to_i2c_client(di->dev); + struct i2c_msg msg; + u8 buf[33]; + int ret; + + if (!client->adapter) + return -ENODEV; + + buf[0] = reg; + memcpy(&buf[1], data, len); + + msg.buf = buf; + msg.addr = client->addr; + msg.flags = 0; + msg.len = len + 1; + + ret = i2c_transfer(client->adapter, &msg, 1); + if (ret < 0) + return ret; + if (ret != 1) + return -EINVAL; + return 0; +} + static int bq27xxx_battery_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -95,7 +171,11 @@ static int bq27xxx_battery_i2c_probe(struct i2c_client *client, di->dev = &client->dev; di->chip = id->driver_data; di->name = name; + di->bus.read = bq27xxx_battery_i2c_read; + di->bus.write = bq27xxx_battery_i2c_write; + di->bus.read_bulk = bq27xxx_battery_i2c_bulk_read; + di->bus.write_bulk = bq27xxx_battery_i2c_bulk_write; ret = bq27xxx_battery_setup(di); if (ret) diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index b312bcef53da..c3369fa605f9 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -40,6 +40,9 @@ struct bq27xxx_platform_data { struct bq27xxx_device_info; struct bq27xxx_access_methods { int (*read)(struct bq27xxx_device_info *di, u8 reg, bool single); + int (*write)(struct bq27xxx_device_info *di, u8 reg, int value, bool single); + int (*read_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len); + int (*write_bulk)(struct bq27xxx_device_info *di, u8 reg, u8 *data, int len); }; struct bq27xxx_reg_cache { -- cgit v1.2.3 From 0670c9b3588f163cfcfcd8ea532f321ec004e6ad Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Wed, 7 Jun 2017 11:37:55 -0700 Subject: power: supply: bq27xxx: Add chip data memory read/write support Add these to enable read/write of chip data memory RAM/NVM/flash: bq27xxx_battery_seal() bq27xxx_battery_unseal() bq27xxx_battery_set_cfgupdate() bq27xxx_battery_soft_reset() bq27xxx_battery_read_dm_block() bq27xxx_battery_write_dm_block() bq27xxx_battery_checksum_dm_block() Signed-off-by: Matt Ranostay Signed-off-by: Liam Breck Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq27xxx_battery.c | 265 +++++++++++++++++++++++++++++++++ include/linux/power/bq27xxx_battery.h | 1 + 2 files changed, 266 insertions(+) (limited to 'drivers/power') diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index a11dfad76ed4..6a4ac14cb15c 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -5,6 +5,7 @@ * Copyright (C) 2008 Eurotech S.p.A. * Copyright (C) 2010-2011 Lars-Peter Clausen * Copyright (C) 2011 Pali Rohár + * Copyright (C) 2017 Liam Breck * * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc. * @@ -65,6 +66,7 @@ #define BQ27XXX_FLAG_DSC BIT(0) #define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ #define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ +#define BQ27XXX_FLAG_CFGUP BIT(4) #define BQ27XXX_FLAG_FC BIT(9) #define BQ27XXX_FLAG_OTD BIT(14) #define BQ27XXX_FLAG_OTC BIT(15) @@ -78,6 +80,12 @@ #define BQ27000_FLAG_FC BIT(5) #define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */ +/* control register params */ +#define BQ27XXX_SEALED 0x20 +#define BQ27XXX_SET_CFGUPDATE 0x13 +#define BQ27XXX_SOFT_RESET 0x42 +#define BQ27XXX_RESET 0x41 + #define BQ27XXX_RS (20) /* Resistor sense mOhm */ #define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */ #define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */ @@ -108,9 +116,21 @@ enum bq27xxx_reg_index { BQ27XXX_REG_SOC, /* State-of-Charge */ BQ27XXX_REG_DCAP, /* Design Capacity */ BQ27XXX_REG_AP, /* Average Power */ + BQ27XXX_DM_CTRL, /* Block Data Control */ + BQ27XXX_DM_CLASS, /* Data Class */ + BQ27XXX_DM_BLOCK, /* Data Block */ + BQ27XXX_DM_DATA, /* Block Data */ + BQ27XXX_DM_CKSUM, /* Block Data Checksum */ BQ27XXX_REG_MAX, /* sentinel */ }; +#define BQ27XXX_DM_REG_ROWS \ + [BQ27XXX_DM_CTRL] = 0x61, \ + [BQ27XXX_DM_CLASS] = 0x3e, \ + [BQ27XXX_DM_BLOCK] = 0x3f, \ + [BQ27XXX_DM_DATA] = 0x40, \ + [BQ27XXX_DM_CKSUM] = 0x60 + /* Register mappings */ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27000] = { @@ -131,6 +151,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x0b, [BQ27XXX_REG_DCAP] = 0x76, [BQ27XXX_REG_AP] = 0x24, + [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR, + [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR, + [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, + [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, + [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, }, [BQ27010] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -150,6 +175,11 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x0b, [BQ27XXX_REG_DCAP] = 0x76, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + [BQ27XXX_DM_CTRL] = INVALID_REG_ADDR, + [BQ27XXX_DM_CLASS] = INVALID_REG_ADDR, + [BQ27XXX_DM_BLOCK] = INVALID_REG_ADDR, + [BQ27XXX_DM_DATA] = INVALID_REG_ADDR, + [BQ27XXX_DM_CKSUM] = INVALID_REG_ADDR, }, [BQ2750X] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -169,6 +199,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + BQ27XXX_DM_REG_ROWS, }, [BQ2751X] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -188,6 +219,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x20, [BQ27XXX_REG_DCAP] = 0x2e, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + BQ27XXX_DM_REG_ROWS, }, [BQ27500] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -207,6 +239,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27510G1] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -226,6 +259,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27510G2] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -245,6 +279,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27510G3] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -264,6 +299,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x20, [BQ27XXX_REG_DCAP] = 0x2e, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + BQ27XXX_DM_REG_ROWS, }, [BQ27520G1] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -283,6 +319,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27520G2] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -302,6 +339,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27520G3] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -321,6 +359,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27520G4] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -340,6 +379,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x20, [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, [BQ27XXX_REG_AP] = INVALID_REG_ADDR, + BQ27XXX_DM_REG_ROWS, }, [BQ27530] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -359,6 +399,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27541] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -378,6 +419,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27545] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -397,6 +439,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x2c, [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR, [BQ27XXX_REG_AP] = 0x24, + BQ27XXX_DM_REG_ROWS, }, [BQ27421] = { [BQ27XXX_REG_CTRL] = 0x00, @@ -416,6 +459,7 @@ static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = { [BQ27XXX_REG_SOC] = 0x1c, [BQ27XXX_REG_DCAP] = 0x3c, [BQ27XXX_REG_AP] = 0x18, + BQ27XXX_DM_REG_ROWS, }, }; @@ -757,6 +801,28 @@ static struct { static DEFINE_MUTEX(bq27xxx_list_lock); static LIST_HEAD(bq27xxx_battery_devices); +#define BQ27XXX_MSLEEP(i) usleep_range((i)*1000, (i)*1000+500) + +#define BQ27XXX_DM_SZ 32 + +/** + * struct bq27xxx_dm_buf - chip data memory buffer + * @class: data memory subclass_id + * @block: data memory block number + * @data: data from/for the block + * @has_data: true if data has been filled by read + * @dirty: true if data has changed since last read/write + * + * Encapsulates info required to manage chip data memory blocks. + */ +struct bq27xxx_dm_buf { + u8 class; + u8 block; + u8 data[BQ27XXX_DM_SZ]; + bool has_data, dirty; +}; + + static int poll_interval_param_set(const char *val, const struct kernel_param *kp) { struct bq27xxx_device_info *di; @@ -864,6 +930,205 @@ static inline int bq27xxx_write_block(struct bq27xxx_device_info *di, int reg_in return ret; } +static int bq27xxx_battery_seal(struct bq27xxx_device_info *di) +{ + int ret; + + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_SEALED, false); + if (ret < 0) { + dev_err(di->dev, "bus error on seal: %d\n", ret); + return ret; + } + + return 0; +} + +static int bq27xxx_battery_unseal(struct bq27xxx_device_info *di) +{ + int ret; + + if (di->unseal_key == 0) { + dev_err(di->dev, "unseal failed due to missing key\n"); + return -EINVAL; + } + + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)(di->unseal_key >> 16), false); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, (u16)di->unseal_key, false); + if (ret < 0) + goto out; + + return 0; + +out: + dev_err(di->dev, "bus error on unseal: %d\n", ret); + return ret; +} + +static u8 bq27xxx_battery_checksum_dm_block(struct bq27xxx_dm_buf *buf) +{ + u16 sum = 0; + int i; + + for (i = 0; i < BQ27XXX_DM_SZ; i++) + sum += buf->data[i]; + sum &= 0xff; + + return 0xff - sum; +} + +static int bq27xxx_battery_read_dm_block(struct bq27xxx_device_info *di, + struct bq27xxx_dm_buf *buf) +{ + int ret; + + buf->has_data = false; + + ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true); + if (ret < 0) + goto out; + + BQ27XXX_MSLEEP(1); + + ret = bq27xxx_read_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ); + if (ret < 0) + goto out; + + ret = bq27xxx_read(di, BQ27XXX_DM_CKSUM, true); + if (ret < 0) + goto out; + + if ((u8)ret != bq27xxx_battery_checksum_dm_block(buf)) { + ret = -EINVAL; + goto out; + } + + buf->has_data = true; + buf->dirty = false; + + return 0; + +out: + dev_err(di->dev, "bus error reading chip memory: %d\n", ret); + return ret; +} + +static int bq27xxx_battery_cfgupdate_priv(struct bq27xxx_device_info *di, bool active) +{ + const int limit = 100; + u16 cmd = active ? BQ27XXX_SET_CFGUPDATE : BQ27XXX_SOFT_RESET; + int ret, try = limit; + + ret = bq27xxx_write(di, BQ27XXX_REG_CTRL, cmd, false); + if (ret < 0) + return ret; + + do { + BQ27XXX_MSLEEP(25); + ret = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + if (ret < 0) + return ret; + } while (!!(ret & BQ27XXX_FLAG_CFGUP) != active && --try); + + if (!try) { + dev_err(di->dev, "timed out waiting for cfgupdate flag %d\n", active); + return -EINVAL; + } + + if (limit - try > 3) + dev_warn(di->dev, "cfgupdate %d, retries %d\n", active, limit - try); + + return 0; +} + +static inline int bq27xxx_battery_set_cfgupdate(struct bq27xxx_device_info *di) +{ + int ret = bq27xxx_battery_cfgupdate_priv(di, true); + if (ret < 0 && ret != -EINVAL) + dev_err(di->dev, "bus error on set_cfgupdate: %d\n", ret); + + return ret; +} + +static inline int bq27xxx_battery_soft_reset(struct bq27xxx_device_info *di) +{ + int ret = bq27xxx_battery_cfgupdate_priv(di, false); + if (ret < 0 && ret != -EINVAL) + dev_err(di->dev, "bus error on soft_reset: %d\n", ret); + + return ret; +} + +static int bq27xxx_battery_write_dm_block(struct bq27xxx_device_info *di, + struct bq27xxx_dm_buf *buf) +{ + bool cfgup = di->chip == BQ27421; /* assume related chips need cfgupdate */ + int ret; + + if (!buf->dirty) + return 0; + + if (cfgup) { + ret = bq27xxx_battery_set_cfgupdate(di); + if (ret < 0) + return ret; + } + + ret = bq27xxx_write(di, BQ27XXX_DM_CTRL, 0, true); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_DM_CLASS, buf->class, true); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_DM_BLOCK, buf->block, true); + if (ret < 0) + goto out; + + BQ27XXX_MSLEEP(1); + + ret = bq27xxx_write_block(di, BQ27XXX_DM_DATA, buf->data, BQ27XXX_DM_SZ); + if (ret < 0) + goto out; + + ret = bq27xxx_write(di, BQ27XXX_DM_CKSUM, + bq27xxx_battery_checksum_dm_block(buf), true); + if (ret < 0) + goto out; + + /* DO NOT read BQ27XXX_DM_CKSUM here to verify it! That may cause NVM + * corruption on the '425 chip (and perhaps others), which can damage + * the chip. + */ + + if (cfgup) { + BQ27XXX_MSLEEP(1); + ret = bq27xxx_battery_soft_reset(di); + if (ret < 0) + return ret; + } else { + BQ27XXX_MSLEEP(100); /* flash DM updates in <100ms */ + } + + buf->dirty = false; + + return 0; + +out: + if (cfgup) + bq27xxx_battery_soft_reset(di); + + dev_err(di->dev, "bus error writing chip memory: %d\n", ret); + return ret; +} + /* * Return the battery State-of-Charge * Or < 0 if something fails. diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index c3369fa605f9..b1defb86e9b6 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -64,6 +64,7 @@ struct bq27xxx_device_info { int id; enum bq27xxx_chip chip; const char *name; + u32 unseal_key; struct bq27xxx_access_methods bus; struct bq27xxx_reg_cache cache; int charge_design_full; -- cgit v1.2.3 From ccce440956c79343ab3aa1269a4cf57f9cce030f Mon Sep 17 00:00:00 2001 From: Liam Breck Date: Wed, 7 Jun 2017 11:37:56 -0700 Subject: power: supply: bq27xxx: Add power_supply_battery_info support Previously there was no way to configure these chips in the event that the defaults didn't match the battery in question. For chips with RAM data memory (and also those with flash/NVM data memory if CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM is defined and the user has not set module param dt_monitored_battery_updates_nvm=0) we now call power_supply_get_battery_info(), check its values, and write battery properties to chip data memory if there is a dm_regs table for the chip. Signed-off-by: Matt Ranostay Signed-off-by: Liam Breck Signed-off-by: Sebastian Reichel --- drivers/power/supply/Kconfig | 11 ++ drivers/power/supply/bq27xxx_battery.c | 204 ++++++++++++++++++++++++++++++++- include/linux/power/bq27xxx_battery.h | 2 + 3 files changed, 216 insertions(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f04c44c6c3f3..969f5005669c 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -198,6 +198,17 @@ config BATTERY_BQ27XXX_I2C Say Y here to enable support for batteries with BQ27xxx chips connected over an I2C bus. +config BATTERY_BQ27XXX_DT_UPDATES_NVM + bool "BQ27xxx support for update of NVM/flash data memory" + depends on BATTERY_BQ27XXX_I2C + help + Say Y here to enable devicetree monitored-battery config to update + NVM/flash data memory. Only enable this option for devices with a + fuel gauge mounted on the circuit board, and a battery that cannot + easily be replaced with one of a different type. Not for + general-purpose kernels, as this can cause misconfiguration of a + smart battery with embedded NVM/flash. + config BATTERY_DA9030 tristate "DA9030 battery driver" depends on PMIC_DA903X diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 6a4ac14cb15c..ed44439d0112 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -805,6 +805,13 @@ static LIST_HEAD(bq27xxx_battery_devices); #define BQ27XXX_DM_SZ 32 +struct bq27xxx_dm_reg { + u8 subclass_id; + u8 offset; + u8 bytes; + u16 min, max; +}; + /** * struct bq27xxx_dm_buf - chip data memory buffer * @class: data memory subclass_id @@ -822,6 +829,44 @@ struct bq27xxx_dm_buf { bool has_data, dirty; }; +#define BQ27XXX_DM_BUF(di, i) { \ + .class = (di)->dm_regs[i].subclass_id, \ + .block = (di)->dm_regs[i].offset / BQ27XXX_DM_SZ, \ +} + +static inline u16 *bq27xxx_dm_reg_ptr(struct bq27xxx_dm_buf *buf, + struct bq27xxx_dm_reg *reg) +{ + if (buf->class == reg->subclass_id && + buf->block == reg->offset / BQ27XXX_DM_SZ) + return (u16 *) (buf->data + reg->offset % BQ27XXX_DM_SZ); + + return NULL; +} + +enum bq27xxx_dm_reg_id { + BQ27XXX_DM_DESIGN_CAPACITY = 0, + BQ27XXX_DM_DESIGN_ENERGY, + BQ27XXX_DM_TERMINATE_VOLTAGE, +}; + +static const char * const bq27xxx_dm_reg_name[] = { + [BQ27XXX_DM_DESIGN_CAPACITY] = "design-capacity", + [BQ27XXX_DM_DESIGN_ENERGY] = "design-energy", + [BQ27XXX_DM_TERMINATE_VOLTAGE] = "terminate-voltage", +}; + + +static bool bq27xxx_dt_to_nvm = true; +module_param_named(dt_monitored_battery_updates_nvm, bq27xxx_dt_to_nvm, bool, 0444); +MODULE_PARM_DESC(dt_monitored_battery_updates_nvm, + "Devicetree monitored-battery config updates data memory on NVM/flash chips.\n" + "Users must set this =0 when installing a different type of battery!\n" + "Default is =1." +#ifndef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM + "\nSetting this affects future kernel updates, not the current configuration." +#endif +); static int poll_interval_param_set(const char *val, const struct kernel_param *kp) { @@ -1019,6 +1064,55 @@ out: return ret; } +static void bq27xxx_battery_update_dm_block(struct bq27xxx_device_info *di, + struct bq27xxx_dm_buf *buf, + enum bq27xxx_dm_reg_id reg_id, + unsigned int val) +{ + struct bq27xxx_dm_reg *reg = &di->dm_regs[reg_id]; + const char *str = bq27xxx_dm_reg_name[reg_id]; + u16 *prev = bq27xxx_dm_reg_ptr(buf, reg); + + if (prev == NULL) { + dev_warn(di->dev, "buffer does not match %s dm spec\n", str); + return; + } + + if (reg->bytes != 2) { + dev_warn(di->dev, "%s dm spec has unsupported byte size\n", str); + return; + } + + if (!buf->has_data) + return; + + if (be16_to_cpup(prev) == val) { + dev_info(di->dev, "%s has %u\n", str, val); + return; + } + +#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM + if (!di->ram_chip && !bq27xxx_dt_to_nvm) { +#else + if (!di->ram_chip) { +#endif + /* devicetree and NVM differ; defer to NVM */ + dev_warn(di->dev, "%s has %u; update to %u disallowed " +#ifdef CONFIG_BATTERY_BQ27XXX_DT_UPDATES_NVM + "by dt_monitored_battery_updates_nvm=0" +#else + "for flash/NVM data memory" +#endif + "\n", str, be16_to_cpup(prev), val); + return; + } + + dev_info(di->dev, "update %s to %u\n", str, val); + + *prev = cpu_to_be16(val); + buf->dirty = true; +} + static int bq27xxx_battery_cfgupdate_priv(struct bq27xxx_device_info *di, bool active) { const int limit = 100; @@ -1129,6 +1223,103 @@ out: return ret; } +static void bq27xxx_battery_set_config(struct bq27xxx_device_info *di, + struct power_supply_battery_info *info) +{ + struct bq27xxx_dm_buf bd = BQ27XXX_DM_BUF(di, BQ27XXX_DM_DESIGN_CAPACITY); + struct bq27xxx_dm_buf bt = BQ27XXX_DM_BUF(di, BQ27XXX_DM_TERMINATE_VOLTAGE); + bool updated; + + if (bq27xxx_battery_unseal(di) < 0) + return; + + if (info->charge_full_design_uah != -EINVAL && + info->energy_full_design_uwh != -EINVAL) { + bq27xxx_battery_read_dm_block(di, &bd); + /* assume design energy & capacity are in same block */ + bq27xxx_battery_update_dm_block(di, &bd, + BQ27XXX_DM_DESIGN_CAPACITY, + info->charge_full_design_uah / 1000); + bq27xxx_battery_update_dm_block(di, &bd, + BQ27XXX_DM_DESIGN_ENERGY, + info->energy_full_design_uwh / 1000); + } + + if (info->voltage_min_design_uv != -EINVAL) { + bool same = bd.class == bt.class && bd.block == bt.block; + if (!same) + bq27xxx_battery_read_dm_block(di, &bt); + bq27xxx_battery_update_dm_block(di, same ? &bd : &bt, + BQ27XXX_DM_TERMINATE_VOLTAGE, + info->voltage_min_design_uv / 1000); + } + + updated = bd.dirty || bt.dirty; + + bq27xxx_battery_write_dm_block(di, &bd); + bq27xxx_battery_write_dm_block(di, &bt); + + bq27xxx_battery_seal(di); + + if (updated && di->chip != BQ27421) { /* not a cfgupdate chip, so reset */ + bq27xxx_write(di, BQ27XXX_REG_CTRL, BQ27XXX_RESET, false); + BQ27XXX_MSLEEP(300); /* reset time is not documented */ + } + /* assume bq27xxx_battery_update() is called hereafter */ +} + +static void bq27xxx_battery_settings(struct bq27xxx_device_info *di) +{ + struct power_supply_battery_info info = {}; + unsigned int min, max; + + if (power_supply_get_battery_info(di->bat, &info) < 0) + return; + + if (!di->dm_regs) { + dev_warn(di->dev, "data memory update not supported for chip\n"); + return; + } + + if (info.energy_full_design_uwh != info.charge_full_design_uah) { + if (info.energy_full_design_uwh == -EINVAL) + dev_warn(di->dev, "missing battery:energy-full-design-microwatt-hours\n"); + else if (info.charge_full_design_uah == -EINVAL) + dev_warn(di->dev, "missing battery:charge-full-design-microamp-hours\n"); + } + + /* assume min == 0 */ + max = di->dm_regs[BQ27XXX_DM_DESIGN_ENERGY].max; + if (info.energy_full_design_uwh > max * 1000) { + dev_err(di->dev, "invalid battery:energy-full-design-microwatt-hours %d\n", + info.energy_full_design_uwh); + info.energy_full_design_uwh = -EINVAL; + } + + /* assume min == 0 */ + max = di->dm_regs[BQ27XXX_DM_DESIGN_CAPACITY].max; + if (info.charge_full_design_uah > max * 1000) { + dev_err(di->dev, "invalid battery:charge-full-design-microamp-hours %d\n", + info.charge_full_design_uah); + info.charge_full_design_uah = -EINVAL; + } + + min = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].min; + max = di->dm_regs[BQ27XXX_DM_TERMINATE_VOLTAGE].max; + if ((info.voltage_min_design_uv < min * 1000 || + info.voltage_min_design_uv > max * 1000) && + info.voltage_min_design_uv != -EINVAL) { + dev_err(di->dev, "invalid battery:voltage-min-design-microvolt %d\n", + info.voltage_min_design_uv); + info.voltage_min_design_uv = -EINVAL; + } + + if ((info.energy_full_design_uwh != -EINVAL && + info.charge_full_design_uah != -EINVAL) || + info.voltage_min_design_uv != -EINVAL) + bq27xxx_battery_set_config(di, &info); +} + /* * Return the battery State-of-Charge * Or < 0 if something fails. @@ -1646,6 +1837,13 @@ static int bq27xxx_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: ret = bq27xxx_simple_value(di->charge_design_full, val); break; + /* + * TODO: Implement these to make registers set from + * power_supply_battery_info visible in sysfs. + */ + case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN: + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + return -EINVAL; case POWER_SUPPLY_PROP_CYCLE_COUNT: ret = bq27xxx_simple_value(di->cache.cycle_count, val); break; @@ -1679,7 +1877,10 @@ static void bq27xxx_external_power_changed(struct power_supply *psy) int bq27xxx_battery_setup(struct bq27xxx_device_info *di) { struct power_supply_desc *psy_desc; - struct power_supply_config psy_cfg = { .drv_data = di, }; + struct power_supply_config psy_cfg = { + .of_node = di->dev->of_node, + .drv_data = di, + }; INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll); mutex_init(&di->lock); @@ -1704,6 +1905,7 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION); + bq27xxx_battery_settings(di); bq27xxx_battery_update(di); mutex_lock(&bq27xxx_list_lock); diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index b1defb86e9b6..11e11685dd1d 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -63,7 +63,9 @@ struct bq27xxx_device_info { struct device *dev; int id; enum bq27xxx_chip chip; + bool ram_chip; const char *name; + struct bq27xxx_dm_reg *dm_regs; u32 unseal_key; struct bq27xxx_access_methods bus; struct bq27xxx_reg_cache cache; -- cgit v1.2.3 From f8c91bae0c37dfc5aecc49cafc5c1ea9d9227731 Mon Sep 17 00:00:00 2001 From: Quentin Schulz Date: Thu, 11 May 2017 15:42:17 +0200 Subject: power: supply: axp20x_battery: add support for DT battery This adds support in X-Powers AXP20X and AXP22X battery driver for a fixed battery in DT. It will take the minimum supported voltage by the battery as defined in the battery DT node and set the V_OFF register to this value, telling the system to shut down if the supplied power is below this value. Signed-off-by: Quentin Schulz Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'drivers/power') diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 5d29b2eab8fc..66f530541735 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -433,6 +433,7 @@ static int axp20x_power_probe(struct platform_device *pdev) { struct axp20x_batt_ps *axp20x_batt; struct power_supply_config psy_cfg = {}; + struct power_supply_battery_info info; if (!of_device_is_available(pdev->dev.of_node)) return -ENODEV; @@ -484,6 +485,15 @@ static int axp20x_power_probe(struct platform_device *pdev) return PTR_ERR(axp20x_batt->batt); } + if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) { + int vmin = info.voltage_min_design_uv; + + if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt, + vmin)) + dev_err(&pdev->dev, + "couldn't set voltage_min_design\n"); + } + return 0; } -- cgit v1.2.3 From c800384490500cdf4c2409a9052862d1db805499 Mon Sep 17 00:00:00 2001 From: Quentin Schulz Date: Thu, 11 May 2017 15:42:20 +0200 Subject: power: supply: axp20x_battery: add DT support for battery max constant charge current This adds the ability to set the maximum constant charge current, supported by the battery, delivered by this battery power supply to the battery. The maximum constant charge current set in DT will also set the default constant charge current supplied by this supply. The actual user can modify the constant charge current within the range of 0 to maximum constant charge current via sysfs. The user can also modify the maximum constant charge current to widen the range of possible constant charge current. While this seems quite risky, a message is printed on the console to warn the user this might damage the battery. The reason for letting the user change the maximum constant charge current is for letting users change the battery and thus, let them adjust the maximum constant charge current according to what the battery can support. Signed-off-by: Quentin Schulz Signed-off-by: Sebastian Reichel --- drivers/power/supply/axp20x_battery.c | 78 +++++++++++++++++++++++++++++++---- 1 file changed, 70 insertions(+), 8 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/axp20x_battery.c b/drivers/power/supply/axp20x_battery.c index 66f530541735..7494f0f0eadb 100644 --- a/drivers/power/supply/axp20x_battery.c +++ b/drivers/power/supply/axp20x_battery.c @@ -60,6 +60,8 @@ struct axp20x_batt_ps { struct iio_channel *batt_chrg_i; struct iio_channel *batt_dischrg_i; struct iio_channel *batt_v; + /* Maximum constant charge current */ + unsigned int max_ccc; u8 axp_id; }; @@ -129,6 +131,14 @@ static void raw_to_constant_charge_current(struct axp20x_batt_ps *axp, int *val) *val = *val * 150000 + 300000; } +static void constant_charge_current_to_raw(struct axp20x_batt_ps *axp, int *val) +{ + if (axp->axp_id == AXP209_ID) + *val = (*val - 300000) / 100000; + else + *val = (*val - 300000) / 150000; +} + static int axp20x_get_constant_charge_current(struct axp20x_batt_ps *axp, int *val) { @@ -221,9 +231,7 @@ static int axp20x_battery_get_prop(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - val->intval = AXP20X_CHRG_CTRL1_TGT_CURR; - raw_to_constant_charge_current(axp20x_batt, &val->intval); - + val->intval = axp20x_batt->max_ccc; break; case POWER_SUPPLY_PROP_CURRENT_NOW: @@ -340,10 +348,10 @@ static int axp20x_battery_set_max_voltage(struct axp20x_batt_ps *axp20x_batt, static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, int charge_current) { - if (axp_batt->axp_id == AXP209_ID) - charge_current = (charge_current - 300000) / 100000; - else - charge_current = (charge_current - 300000) / 150000; + if (charge_current > axp_batt->max_ccc) + return -EINVAL; + + constant_charge_current_to_raw(axp_batt, &charge_current); if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0) return -EINVAL; @@ -352,6 +360,36 @@ static int axp20x_set_constant_charge_current(struct axp20x_batt_ps *axp_batt, AXP20X_CHRG_CTRL1_TGT_CURR, charge_current); } +static int axp20x_set_max_constant_charge_current(struct axp20x_batt_ps *axp, + int charge_current) +{ + bool lower_max = false; + + constant_charge_current_to_raw(axp, &charge_current); + + if (charge_current > AXP20X_CHRG_CTRL1_TGT_CURR || charge_current < 0) + return -EINVAL; + + raw_to_constant_charge_current(axp, &charge_current); + + if (charge_current > axp->max_ccc) + dev_warn(axp->dev, + "Setting max constant charge current higher than previously defined. Note that increasing the constant charge current may damage your battery.\n"); + else + lower_max = true; + + axp->max_ccc = charge_current; + + if (lower_max) { + int current_cc; + + axp20x_get_constant_charge_current(axp, ¤t_cc); + if (current_cc > charge_current) + axp20x_set_constant_charge_current(axp, charge_current); + } + + return 0; +} static int axp20x_set_voltage_min_design(struct axp20x_batt_ps *axp_batt, int min_voltage) { @@ -380,6 +418,9 @@ static int axp20x_battery_set_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: return axp20x_set_constant_charge_current(axp20x_batt, val->intval); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + return axp20x_set_max_constant_charge_current(axp20x_batt, + val->intval); default: return -EINVAL; @@ -405,7 +446,8 @@ static int axp20x_battery_prop_writeable(struct power_supply *psy, { return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN || psp == POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN || - psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT; + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT || + psp == POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX; } static const struct power_supply_desc axp20x_batt_ps_desc = { @@ -487,13 +529,33 @@ static int axp20x_power_probe(struct platform_device *pdev) if (!power_supply_get_battery_info(axp20x_batt->batt, &info)) { int vmin = info.voltage_min_design_uv; + int ccc = info.constant_charge_current_max_ua; if (vmin > 0 && axp20x_set_voltage_min_design(axp20x_batt, vmin)) dev_err(&pdev->dev, "couldn't set voltage_min_design\n"); + + /* Set max to unverified value to be able to set CCC */ + axp20x_batt->max_ccc = ccc; + + if (ccc <= 0 || axp20x_set_constant_charge_current(axp20x_batt, + ccc)) { + dev_err(&pdev->dev, + "couldn't set constant charge current from DT: fallback to minimum value\n"); + ccc = 300000; + axp20x_batt->max_ccc = ccc; + axp20x_set_constant_charge_current(axp20x_batt, ccc); + } } + /* + * Update max CCC to a valid value if battery info is present or set it + * to current register value by default. + */ + axp20x_get_constant_charge_current(axp20x_batt, + &axp20x_batt->max_ccc); + return 0; } -- cgit v1.2.3 From f1bea8793d939e594afb3407c26d9cec8792d42f Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Fri, 26 May 2017 23:51:29 -0700 Subject: power: reset: reboot-mode: Make include file global Move the reboot-mode.h include file into include/linux to allow drivers outside drivers/power/reset to implement reboot-mode. Signed-off-by: Bjorn Andersson Signed-off-by: Sebastian Reichel --- drivers/power/reset/reboot-mode.c | 2 +- drivers/power/reset/reboot-mode.h | 18 ------------------ drivers/power/reset/syscon-reboot-mode.c | 2 +- include/linux/reboot-mode.h | 18 ++++++++++++++++++ 4 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 drivers/power/reset/reboot-mode.h create mode 100644 include/linux/reboot-mode.h (limited to 'drivers/power') diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index fb512183ace3..8f975ca0a8c4 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -13,7 +13,7 @@ #include #include #include -#include "reboot-mode.h" +#include #define PREFIX "mode-" diff --git a/drivers/power/reset/reboot-mode.h b/drivers/power/reset/reboot-mode.h deleted file mode 100644 index 75f7fe5c881f..000000000000 --- a/drivers/power/reset/reboot-mode.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __REBOOT_MODE_H__ -#define __REBOOT_MODE_H__ - -struct reboot_mode_driver { - struct device *dev; - struct list_head head; - int (*write)(struct reboot_mode_driver *reboot, unsigned int magic); - struct notifier_block reboot_notifier; -}; - -int reboot_mode_register(struct reboot_mode_driver *reboot); -int reboot_mode_unregister(struct reboot_mode_driver *reboot); -int devm_reboot_mode_register(struct device *dev, - struct reboot_mode_driver *reboot); -void devm_reboot_mode_unregister(struct device *dev, - struct reboot_mode_driver *reboot); - -#endif diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c index c8c371b285b1..563a97d7f73e 100644 --- a/drivers/power/reset/syscon-reboot-mode.c +++ b/drivers/power/reset/syscon-reboot-mode.c @@ -15,7 +15,7 @@ #include #include #include -#include "reboot-mode.h" +#include struct syscon_reboot_mode { struct regmap *map; diff --git a/include/linux/reboot-mode.h b/include/linux/reboot-mode.h new file mode 100644 index 000000000000..75f7fe5c881f --- /dev/null +++ b/include/linux/reboot-mode.h @@ -0,0 +1,18 @@ +#ifndef __REBOOT_MODE_H__ +#define __REBOOT_MODE_H__ + +struct reboot_mode_driver { + struct device *dev; + struct list_head head; + int (*write)(struct reboot_mode_driver *reboot, unsigned int magic); + struct notifier_block reboot_notifier; +}; + +int reboot_mode_register(struct reboot_mode_driver *reboot); +int reboot_mode_unregister(struct reboot_mode_driver *reboot); +int devm_reboot_mode_register(struct device *dev, + struct reboot_mode_driver *reboot); +void devm_reboot_mode_unregister(struct device *dev, + struct reboot_mode_driver *reboot); + +#endif -- cgit v1.2.3 From 37853952b9e4234ab6e085231cf5b6d232c40d0a Mon Sep 17 00:00:00 2001 From: Andy Shevchenko Date: Sun, 11 Jun 2017 18:15:48 +0300 Subject: power: supply: twl4030_charger: Use sysfs_match_string() helper Use sysfs_match_string() helper instead of open coded variant. Cc: Sebastian Reichel Signed-off-by: Andy Shevchenko Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 785a07bc4f39..07c70e59f31a 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -153,7 +153,7 @@ struct twl4030_bci { }; /* strings for 'usb_mode' values */ -static char *modes[] = { "off", "auto", "continuous" }; +static const char *modes[] = { "off", "auto", "continuous" }; /* * clear and set bits on an given register on a given module @@ -669,14 +669,10 @@ twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr, int mode; int status; - if (sysfs_streq(buf, modes[0])) - mode = 0; - else if (sysfs_streq(buf, modes[1])) - mode = 1; - else if (sysfs_streq(buf, modes[2])) - mode = 2; - else - return -EINVAL; + mode = sysfs_match_string(modes, buf); + if (mode < 0) + return mode; + if (dev == &bci->ac->dev) { if (mode == 2) return -EINVAL; -- cgit v1.2.3 From 355679b2701b3d38a4681bf922291c6e55a23e46 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Tue, 13 Jun 2017 10:39:43 +0530 Subject: power: supply: core: constify psy_tcd_ops. File size before: text data bss dec hex filename 4240 200 80 4520 11a8 drivers/power/supply/power_supply_core.o File size After adding 'const': text data bss dec hex filename 4296 136 80 4512 11a0 drivers/power/supply/power_supply_core.o Signed-off-by: Arvind Yadav Signed-off-by: Sebastian Reichel --- drivers/power/supply/power_supply_core.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index a4303ed66144..540d3e0aa011 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -748,7 +748,7 @@ static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd, return ret; } -static struct thermal_cooling_device_ops psy_tcd_ops = { +static const struct thermal_cooling_device_ops psy_tcd_ops = { .get_max_state = ps_get_max_charge_cntl_limit, .get_cur_state = ps_get_cur_chrage_cntl_limit, .set_cur_state = ps_set_cur_charge_cntl_limit, -- cgit v1.2.3 From e8847c565421f1c9bc9abcb92cccf33727b6f558 Mon Sep 17 00:00:00 2001 From: H. Nikolaus Schaller Date: Wed, 14 Jun 2017 11:25:53 +0200 Subject: power: supply: twl4030-charger: allocate iio by devm_iio_channel_get() and fix error path Suggested-by: Sebastian Reichel Signed-off-by: H. Nikolaus Schaller Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 07c70e59f31a..33f2415680d9 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -1013,7 +1013,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) return ret; } - bci->channel_vac = iio_channel_get(&pdev->dev, "vac"); + bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); if (IS_ERR(bci->channel_vac)) { bci->channel_vac = NULL; dev_warn(&pdev->dev, "could not request vac iio channel"); @@ -1040,7 +1040,7 @@ static int twl4030_bci_probe(struct platform_device *pdev) TWL4030_INTERRUPTS_BCIIMR1A); if (ret < 0) { dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret); - goto fail; + return ret; } reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV); @@ -1069,10 +1069,6 @@ static int twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_backup(0, 0); return 0; -fail: - iio_channel_release(bci->channel_vac); - - return ret; } static int twl4030_bci_remove(struct platform_device *pdev) @@ -1083,8 +1079,6 @@ static int twl4030_bci_remove(struct platform_device *pdev) twl4030_charger_enable_usb(bci, false); twl4030_charger_enable_backup(0, 0); - iio_channel_release(bci->channel_vac); - device_remove_file(&bci->usb->dev, &dev_attr_mode); device_remove_file(&bci->ac->dev, &dev_attr_mode); /* mask interrupts */ -- cgit v1.2.3 From 5e6eb025b0d7ee721e8d88d632630ba43eeab50b Mon Sep 17 00:00:00 2001 From: H. Nikolaus Schaller Date: Wed, 14 Jun 2017 11:25:54 +0200 Subject: power: supply: twl4030-charger: move allocation of iio channel to the beginning This is in prepraration for EPROBE_DEFER handling because it is quite likely that geting the (madc) iio channel is deferred more often than later steps. Signed-off-by: H. Nikolaus Schaller Signed-off-by: Sebastian Reichel --- drivers/power/supply/twl4030_charger.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 33f2415680d9..3de802f169a1 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -980,6 +980,12 @@ static int twl4030_bci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bci); + bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); + if (IS_ERR(bci->channel_vac)) { + bci->channel_vac = NULL; + dev_warn(&pdev->dev, "could not request vac iio channel"); + } + bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc, NULL); if (IS_ERR(bci->ac)) { @@ -1013,12 +1019,6 @@ static int twl4030_bci_probe(struct platform_device *pdev) return ret; } - bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); - if (IS_ERR(bci->channel_vac)) { - bci->channel_vac = NULL; - dev_warn(&pdev->dev, "could not request vac iio channel"); - } - INIT_WORK(&bci->work, twl4030_bci_usb_work); INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); -- cgit v1.2.3 From fc443138304e31c05df688ee6e318a6ac57d53f2 Mon Sep 17 00:00:00 2001 From: Tony Lindgren Date: Wed, 14 Jun 2017 21:41:08 -0700 Subject: power: supply: cpcap-charger: Add missing power_supply_config Otherwise cpcap-battery won't probe properly with the power-supplies property configured but will fail with "Not all required supplies found, defer probe". Cc: Marcel Partap Cc: Michael Scott Signed-off-by: Tony Lindgren Signed-off-by: Sebastian Reichel --- drivers/power/supply/cpcap-charger.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index a798af4471d7..11a07633de6c 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -601,6 +601,7 @@ static int cpcap_charger_probe(struct platform_device *pdev) { struct cpcap_charger_ddata *ddata; const struct of_device_id *of_id; + struct power_supply_config psy_cfg = {}; int error; of_id = of_match_device(of_match_ptr(cpcap_charger_id_table), @@ -629,9 +630,12 @@ static int cpcap_charger_probe(struct platform_device *pdev) atomic_set(&ddata->active, 1); + psy_cfg.of_node = pdev->dev.of_node; + psy_cfg.drv_data = ddata; + ddata->usb = devm_power_supply_register(ddata->dev, &cpcap_charger_usb_desc, - NULL); + &psy_cfg); if (IS_ERR(ddata->usb)) { error = PTR_ERR(ddata->usb); dev_err(ddata->dev, "failed to register USB charger: %i\n", -- cgit v1.2.3 From a1bbec72f9fef100bb00762e36c20055deacd0e2 Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Thu, 15 Jun 2017 21:59:29 +0800 Subject: power: supply: sbs-battery: remove incorrect le16_to_cpu calls i2c_smbus commands handle the correct byte order for smbus transactions internally. This will currently result in incorrect operation on big endian systems. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index e3a114e60f1a..cf43e38a9443 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -199,7 +199,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) return ret; } - return le16_to_cpu(ret); + return ret; } static int sbs_read_string_data(struct i2c_client *client, u8 address, @@ -265,7 +265,7 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, memcpy(values, block_buffer + 1, block_length); values[block_length] = '\0'; - return le16_to_cpu(ret); + return ret; } static int sbs_write_word_data(struct i2c_client *client, u8 address, @@ -278,8 +278,7 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address, retries = chip->i2c_retry_count; while (retries > 0) { - ret = i2c_smbus_write_word_data(client, address, - le16_to_cpu(value)); + ret = i2c_smbus_write_word_data(client, address, value); if (ret >= 0) break; retries--; -- cgit v1.2.3 From 48f680c0a9cae36e37d91a3dc086143d6047c8a8 Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Thu, 15 Jun 2017 21:59:30 +0800 Subject: power: supply: bq24735: remove incorrect le16_to_cpu calls i2c_smbus commands handle the correct byte order for smbus transactions internally. This will currently result in incorrect operation on big endian systems. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel --- drivers/power/supply/bq24735-charger.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index eb0145380def..6931e1d826f5 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -81,14 +81,12 @@ static int bq24735_charger_property_is_writeable(struct power_supply *psy, static inline int bq24735_write_word(struct i2c_client *client, u8 reg, u16 value) { - return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value)); + return i2c_smbus_write_word_data(client, reg, value); } static inline int bq24735_read_word(struct i2c_client *client, u8 reg) { - s32 ret = i2c_smbus_read_word_data(client, reg); - - return ret < 0 ? ret : le16_to_cpu(ret); + return i2c_smbus_read_word_data(client, reg); } static int bq24735_update_word(struct i2c_client *client, u8 reg, -- cgit v1.2.3 From fe8a6534396807b8a0863cd146b72c0bce0b0c88 Mon Sep 17 00:00:00 2001 From: Shawn Nematbakhsh Date: Tue, 13 Jun 2017 10:53:25 -0700 Subject: power: supply: sbs-battery: Prevent CAPACITY_MODE races A subset of smart battery commands return charge or energy depending on the CAPACITY_MODE bit setting of BatteryMode(). In order to unambiguously read a charge or energy value, it is necessary to ensure that CAPACITY_MODE is set as desired, and not changed for the duration of the attribute read. Signed-off-by: Shawn Nematbakhsh Reviewed-by: Guenter Roeck Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/power') diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index cf43e38a9443..cdc1d71909a5 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -171,6 +171,7 @@ struct sbs_info { u32 i2c_retry_count; u32 poll_retry_count; struct delayed_work work; + struct mutex mode_lock; }; static char model_name[I2C_SMBUS_BLOCK_MAX + 1]; @@ -622,7 +623,13 @@ static int sbs_get_property(struct power_supply *psy, if (ret < 0) break; + /* sbs_get_battery_capacity() will change the battery mode + * temporarily to read the requested attribute. Ensure we stay + * in the desired mode for the duration of the attribute read. + */ + mutex_lock(&chip->mode_lock); ret = sbs_get_battery_capacity(client, ret, psp, val); + mutex_unlock(&chip->mode_lock); break; case POWER_SUPPLY_PROP_SERIAL_NUMBER: @@ -807,6 +814,7 @@ static int sbs_probe(struct i2c_client *client, psy_cfg.of_node = client->dev.of_node; psy_cfg.drv_data = chip; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; + mutex_init(&chip->mode_lock); /* use pdata if available, fall back to DT properties, * or hardcoded defaults if not -- cgit v1.2.3 From bfa953d336cdd713f6968c85ca820ef22333dc35 Mon Sep 17 00:00:00 2001 From: Shawn Nematbakhsh Date: Tue, 13 Jun 2017 10:53:26 -0700 Subject: power: supply: sbs-battery: Don't needlessly set CAPACITY_MODE According to the smart battery spec (1), the CAPACITY_MODE bit does not influence the value read from RelativeStateOfCharge(), so don't bother changing CAPACITY_MODE when doing such a read. (1) - Smart Battery Data Specification, Rev 1.1, Dec. 11, 1998 Signed-off-by: Shawn Nematbakhsh Reviewed-by: Guenter Roeck Signed-off-by: Sebastian Reichel --- drivers/power/supply/sbs-battery.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'drivers/power') diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index cdc1d71909a5..f7059459f0fb 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -438,6 +438,11 @@ static int sbs_get_battery_property(struct i2c_client *client, } else { if (psp == POWER_SUPPLY_PROP_STATUS) val->intval = POWER_SUPPLY_STATUS_UNKNOWN; + else if (psp == POWER_SUPPLY_PROP_CAPACITY) + /* sbs spec says that this can be >100 % + * even if max value is 100 % + */ + val->intval = min(ret, 100); else val->intval = 0; } @@ -548,12 +553,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client, if (ret < 0) return ret; - if (psp == POWER_SUPPLY_PROP_CAPACITY) { - /* sbs spec says that this can be >100 % - * even if max value is 100 % */ - val->intval = min(ret, 100); - } else - val->intval = ret; + val->intval = ret; ret = sbs_set_battery_mode(client, mode); if (ret < 0) @@ -618,7 +618,6 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CHARGE_NOW: case POWER_SUPPLY_PROP_CHARGE_FULL: case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: - case POWER_SUPPLY_PROP_CAPACITY: ret = sbs_get_property_index(client, psp); if (ret < 0) break; @@ -646,6 +645,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG: case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + case POWER_SUPPLY_PROP_CAPACITY: ret = sbs_get_property_index(client, psp); if (ret < 0) break; -- cgit v1.2.3