power supply and reset changes for the v5.12 series

battery/charger driver changes:
  * acer_a500: new fuel gauge driver for Acer Iconia Tab A500
  * bq256xx: new charger driver
  * bq27xxx: Support CHARGE_NOW for bq27z561/bq28z610/bq34z100
  * bq27xxx: Fix inverted CURRENT_NOW sign
  * cpcap: rework fuel gauge and charger drivers
  * ltc4162l: new charger driver
  * max8997-charger: add extcon based current limit configuration
  * max8903, wm97xx, z2: convert to GPIO descriptors (incl. ARM board files)
  * misc. cleanup and fixes
 
 reset drivers:
  * new poweroff driver for ATC260x
  * at91-sama5d2_shdwc: add support for sama7g5
  * drop zte zx driver (SoC support is removed from kernel)
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmAy7hUACgkQ2O7X88g7
 +po3vhAAgwtTzyqb1oVIaRRb4rz/aeEZqTe6TvQxKTXvZaq8S/aCT8QClSsB/FH2
 Pond8LaKOeF/R4CFFIdulA26hq65uCLo0sD1TbOf6mN2h9dsd/7Bqpp4lp69zZVJ
 0MfdBVgFguUwK/Uwzk15JsiEqyFe0TIZWnNGrveMy+NMpL9WqyYY2OoBGhlVsrnj
 c9k5anTHQVR6tlTaPOr03lUdt5Kb0UNZd5lIJNMCjVokYPkAhhnxxhLz6vFTjuyU
 w2oqeyK+p02cGYu+O0GrC7tRlBbwZEYdMxNGCPzgZOTPR1YUtjjxm1No8q8lzF/d
 mrV2IbTToyzjUELyXxjIWl4jYrZxV74MN3D63ZmkQrcvnwg9CggyhpaAbtj3n345
 wVGIp0fd6Hb98KgCXAV+YqslZVeBmC/rdt+5m+k7i+mGb5lGf2S+cPA6Nda58U/C
 Tk/mkL8oMfQ23olTcHywDe7H+rmD1crCsEaAFWu5V5a6eizBDW7fCzUzocxiYD4K
 uPreBfdiXw/hnBW4CMouwzBxbLH3INKtZWLvrvEWdpRgJ3v2c3QTh5gNwkpmDiu8
 pscPz2UeD/Lfe1ozqS97HseITLCc86E4q84B+BmZim0i/nfqRhNlYKD9YjbubawC
 gNP7Vj1u6bRPlhEGZ8QMw8LowMmSTBT6X2aGC8oQkUKbnlspAO0=
 =Efu2
 -----END PGP SIGNATURE-----

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

Pull power supply and reset updates from Sebastian Reichel:
 "Battery/charger driver changes:
   - acer_a500: new fuel gauge driver for Acer Iconia Tab A500
   - bq256xx: new charger driver
   - bq27xxx: Support CHARGE_NOW for bq27z561/bq28z610/bq34z100
   - bq27xxx: Fix inverted CURRENT_NOW sign
   - cpcap: rework fuel gauge and charger drivers
   - ltc4162l: new charger driver
   - max8997-charger: add extcon based current limit configuration
   - max8903, wm97xx, z2: convert to GPIO descriptors (incl. ARM board files)
   - misc cleanup and fixes

  Reset drivers:
   - new poweroff driver for ATC260x
   - at91-sama5d2_shdwc: add support for sama7g5
   - drop zte zx driver (SoC support is removed from kernel)"

* tag 'for-v5.12' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (55 commits)
  power-supply: use kobj_to_dev()
  power: supply: charger-manager: fix typo
  power/reset: remove zte zx driver
  power: supply: bq25980: Fix repetive bq25975 with bq25960
  power: supply: smb347-charger: Fix interrupt usage if interrupt is unavailable
  power: supply: axp20x_usb_power: Init work before enabling IRQs
  power: supply: fix sbs-charger build, needs REGMAP_I2C
  power: supply: bq27xxx: fix polarity of current_now
  power: supply: charger-manager: fix incorrect health status
  power: reset: at91-sama5d2_shdwc: add support for sama7g5
  dt-bindings: atmel-sysreg: add microchip,sama7g5-shdwc
  power: reset: at91-sama5d2_shdwc: fix wkupdbc mask
  dt-bindings: power/supply: Add ltc4162-l-charger
  power: supply: bq24190_charger: convert comma to semicolon
  power: supply: ab8500_fg: convert comma to semicolon
  power: supply: ds2780: Switch to using the new API kobj_to_dev()
  power: supply: bq27xxx: Support CHARGE_NOW for bq27z561/bq28z610/bq34z100
  power: supply: cpcap-charger: Fix power_supply_put on null battery pointer
  power: supply: cpcap-battery: constify psy_desc
  power: supply: cpcap-battery: Fix typo
  ...
This commit is contained in:
Linus Torvalds 2021-02-22 09:49:59 -08:00
commit a7dcf5f235
42 changed files with 4325 additions and 553 deletions

View File

@ -0,0 +1,82 @@
What: /sys/class/power_supply/ltc4162-l/charge_status
Date: Januari 2021
KernelVersion: 5.11
Description:
Detailed charge status information as reported by the chip.
Access: Read
Valid values:
ilim_reg_active
thermal_reg_active
vin_uvcl_active
iin_limit_active
constant_current
constant_voltage
charger_off
What: /sys/class/power_supply/ltc4162-l/ibat
Date: Januari 2021
KernelVersion: 5.11
Description:
Battery input current as measured by the charger. Negative value
means that the battery is discharging.
Access: Read
Valid values: Signed value in microamps
What: /sys/class/power_supply/ltc4162-l/vbat
Date: Januari 2021
KernelVersion: 5.11
Description:
Battery voltage as measured by the charger.
Access: Read
Valid values: In microvolts
What: /sys/class/power_supply/ltc4162-l/vbat_avg
Date: Januari 2021
KernelVersion: 5.11
Description:
Battery voltage, averaged over time, as measured by the charger.
Access: Read
Valid values: In microvolts
What: /sys/class/power_supply/ltc4162-l/force_telemetry
Date: Januari 2021
KernelVersion: 5.11
Description:
To save battery current, the measurement system is disabled if
the battery is the only source of power. This affects all
voltage, current and temperature measurements.
Write a "1" to this to keep performing telemetry once every few
seconds, even when running on battery (as reported by the online
property, which is "1" when external power is available and "0"
when the system runs on battery).
Access: Read, Write
Valid values: 0 (disabled) or 1 (enabled)
What: /sys/class/power_supply/ltc4162-l/arm_ship_mode
Date: Januari 2021
KernelVersion: 5.11
Description:
The charger will normally drain the battery while inactive,
typically drawing about 54 microamps. Write a "1" to this
property to arm a special "ship" mode that extends shelf life
by reducing the leakage to about 2.8 microamps. The chip will
remain in this mode (and no longer respond to I2C commands)
until some external power-supply is attached raising the input
voltage above 1V. It will then automatically revert to "0".
Writing a "0" to the property cancels the "ship" mode request.
The ship mode, when armed, activates once the input voltage
drops below 1V.
Access: Read, Write
Valid values: 0 (disable) or 1 (enable)

View File

@ -91,7 +91,8 @@ SHDWC SAMA5D2-Compatible Shutdown Controller
1) shdwc node
required properties:
- compatible: should be "atmel,sama5d2-shdwc" or "microchip,sam9x60-shdwc".
- compatible: should be "atmel,sama5d2-shdwc", "microchip,sam9x60-shdwc" or
"microchip,sama7g5-shdwc"
- reg: should contain registers location and length
- clocks: phandle to input clock.
- #address-cells: should be one. The cell is the wake-up input index.
@ -103,7 +104,7 @@ optional properties:
microseconds. It's usually a board-related property.
- atmel,wakeup-rtc-timer: boolean to enable Real-Time Clock wake-up.
optional microchip,sam9x60-shdwc properties:
optional microchip,sam9x60-shdwc or microchip,sama7g5-shdwc properties:
- atmel,wakeup-rtt-timer: boolean to enable Real-time Timer Wake-up.
The node contains child nodes for each wake-up input that the platform uses.

View File

@ -0,0 +1,110 @@
# SPDX-License-Identifier: (GPL-2.0-only or BSD-2-Clause)
# Copyright (C) 2020 Texas Instruments Incorporated
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/bq256xx.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: TI bq256xx Switch Mode Buck Charger
maintainers:
- Ricardo Rivera-Matos <r-rivera-matos@ti.com>
description: |
The bq256xx devices are a family of highly-integrated battery charge
management and system power management ICs for single cell Li-ion and Li-
polymer batteries.
Datasheets:
- https://www.ti.com/lit/ds/symlink/bq25600.pdf
- https://www.ti.com/lit/ds/symlink/bq25601.pdf
- https://www.ti.com/lit/ds/symlink/bq25600d.pdf
- https://www.ti.com/lit/ds/symlink/bq25601d.pdf
- https://www.ti.com/lit/ds/symlink/bq25611d.pdf
- https://www.ti.com/lit/ds/symlink/bq25618.pdf
- https://www.ti.com/lit/ds/symlink/bq25619.pdf
properties:
compatible:
enum:
- ti,bq25600
- ti,bq25601
- ti,bq25600d
- ti,bq25601d
- ti,bq25611d
- ti,bq25618
- ti,bq25619
reg:
maxItems: 1
ti,watchdog-timeout-ms:
$ref: /schemas/types.yaml#/definitions/uint32
default: 0
description: |
Watchdog timer in ms. 0 (default) disables the watchdog
minimum: 0
maximum: 160000
enum: [ 0, 40000, 80000, 160000]
input-voltage-limit-microvolt:
description: |
Minimum input voltage limit in µV with a 100000 µV step
minimum: 3900000
maximum: 5400000
input-current-limit-microamp:
description: |
Maximum input current limit in µA with a 100000 µA step
minimum: 100000
maximum: 3200000
monitored-battery:
$ref: /schemas/types.yaml#/definitions/phandle
description: phandle to the battery node being monitored
interrupts:
maxItems: 1
description: |
Interrupt sends an active low, 256 μs pulse to host to report the charger
device status and faults.
required:
- compatible
- reg
- monitored-battery
additionalProperties: false
examples:
- |
bat: battery {
compatible = "simple-battery";
constant-charge-current-max-microamp = <2040000>;
constant-charge-voltage-max-microvolt = <4352000>;
precharge-current-microamp = <180000>;
charge-term-current-microamp = <180000>;
};
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
clock-frequency = <400000>;
#address-cells = <1>;
#size-cells = <0>;
charger@6b {
compatible = "ti,bq25601";
reg = <0x6b>;
monitored-battery = <&bat>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
ti,watchdog-timeout-ms = <40000>;
input-voltage-limit-microvolt = <4500000>;
input-current-limit-microamp = <2400000>;
};
};
...

View File

@ -0,0 +1,69 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
# Copyright (C) 2020 Topic Embedded Products
%YAML 1.2
---
$id: "http://devicetree.org/schemas/power/supply/ltc4162-l.yaml#"
$schema: "http://devicetree.org/meta-schemas/core.yaml#"
title: Linear Technology (Analog Devices) LTC4162-L Charger
maintainers:
- Mike Looijmans <mike.looijmans@topic.nl>
description: |
The LTC ® 4162-L is an advanced monolithic synchronous step-down switching
battery charger and PowerPath (TM) manager that seamlessly manages power
distribution between input sources such as wall adapters, backplanes, solar
panels, etc., and a rechargeable Lithium-Ion/Polymer battery.
Specifications about the charger can be found at:
https://www.analog.com/en/products/ltc4162-s.html
properties:
compatible:
enum:
- lltc,ltc4162-l
reg:
maxItems: 1
description: I2C address of the charger.
lltc,rsnsb-micro-ohms:
$ref: /schemas/types.yaml#/definitions/uint32
description: Battery sense resistor in microohm.
minimum: 1000
lltc,rsnsi-micro-ohms:
$ref: /schemas/types.yaml#/definitions/uint32
description: Input current sense resistor in microohm.
minimum: 1000
lltc,cell-count:
$ref: /schemas/types.yaml#/definitions/uint32
description: |
Number of battery cells. If not provided, will be obtained from the chip
once the external power is applied. Omit this when the number of cells
is somewhat dynamic. Without it, several measurements will return 0 until
the charger is connected to an external supply.
required:
- compatible
- reg
- lltc,rsnsb-micro-ohms
- lltc,rsnsi-micro-ohms
additionalProperties: false
examples:
- |
i2c0 {
#address-cells = <1>;
#size-cells = <0>;
charger: battery-charger@68 {
compatible = "lltc,ltc4162-l";
reg = <0x68>;
lltc,rsnsb-micro-ohms = <10000>;
lltc,rsnsi-micro-ohms = <16000>;
lltc,cell-count = <2>;
};
};

View File

@ -577,7 +577,6 @@ static struct platform_device power_dev = {
static struct wm97xx_batt_pdata mioa701_battery_data = {
.batt_aux = WM97XX_AUX_ID1,
.temp_aux = -1,
.charge_gpio = -1,
.min_voltage = 0xc00,
.max_voltage = 0xfc0,
.batt_tech = POWER_SUPPLY_TECHNOLOGY_LION,

View File

@ -212,7 +212,6 @@ void __init palm27x_irda_init(int pwdn)
static struct wm97xx_batt_pdata palm27x_batt_pdata = {
.batt_aux = WM97XX_AUX_ID3,
.temp_aux = WM97XX_AUX_ID2,
.charge_gpio = -1,
.batt_mult = 1000,
.batt_div = 414,
.temp_mult = 1,

View File

@ -273,7 +273,6 @@ static struct platform_device power_supply = {
static struct wm97xx_batt_pdata palmte2_batt_pdata = {
.batt_aux = WM97XX_AUX_ID3,
.temp_aux = WM97XX_AUX_ID2,
.charge_gpio = -1,
.max_voltage = PALMTE2_BAT_MAX_VOLTAGE,
.min_voltage = PALMTE2_BAT_MIN_VOLTAGE,
.batt_mult = 1000,

View File

@ -487,7 +487,6 @@ static struct z2_battery_info batt_chip_info = {
.batt_I2C_bus = 0,
.batt_I2C_addr = 0x55,
.batt_I2C_reg = 2,
.charge_gpio = GPIO0_ZIPITZ2_AC_DETECT,
.min_voltage = 3475000,
.max_voltage = 4190000,
.batt_div = 59,
@ -496,9 +495,19 @@ static struct z2_battery_info batt_chip_info = {
.batt_name = "Z2",
};
static struct gpiod_lookup_table z2_battery_gpio_table = {
.dev_id = "aer915",
.table = {
GPIO_LOOKUP("gpio-pxa", GPIO0_ZIPITZ2_AC_DETECT,
NULL, GPIO_ACTIVE_HIGH),
{ },
},
};
static struct i2c_board_info __initdata z2_i2c_board_info[] = {
{
I2C_BOARD_INFO("aer915", 0x55),
.dev_name = "aer915",
.platform_data = &batt_chip_info,
}, {
I2C_BOARD_INFO("wm8750", 0x1b),
@ -509,6 +518,7 @@ static struct i2c_board_info __initdata z2_i2c_board_info[] = {
static void __init z2_i2c_init(void)
{
pxa_set_i2c_info(NULL);
gpiod_add_lookup_table(&z2_battery_gpio_table);
i2c_register_board_info(0, ARRAY_AND_SIZE(z2_i2c_board_info));
}
#else

View File

@ -39,6 +39,13 @@ config POWER_RESET_AT91_SAMA5D2_SHDWC
This driver supports the alternate shutdown controller for some Atmel
SAMA5 SoCs. It is present for example on SAMA5D2 SoC.
config POWER_RESET_ATC260X
tristate "Actions Semi ATC260x PMIC power-off driver"
depends on MFD_ATC260X
help
This driver provides power-off and restart support for a system
through Actions Semi ATC260x series PMICs.
config POWER_RESET_AXXIA
bool "LSI Axxia reset driver"
depends on ARCH_AXXIA
@ -251,13 +258,6 @@ config POWER_RESET_RMOBILE
help
Reboot support for Renesas R-Mobile and SH-Mobile SoCs.
config POWER_RESET_ZX
tristate "ZTE SoCs reset driver"
depends on ARCH_ZX || COMPILE_TEST
depends on HAS_IOMEM
help
Reboot support for ZTE SoCs.
config REBOOT_MODE
tristate
@ -292,4 +292,3 @@ config NVMEM_REBOOT_MODE
action according to the mode.
endif

View File

@ -3,6 +3,7 @@ obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o
obj-$(CONFIG_POWER_RESET_AT91_POWEROFF) += at91-poweroff.o
obj-$(CONFIG_POWER_RESET_AT91_RESET) += at91-reset.o
obj-$(CONFIG_POWER_RESET_AT91_SAMA5D2_SHDWC) += at91-sama5d2_shdwc.o
obj-$(CONFIG_POWER_RESET_ATC260X) += atc260x-poweroff.o
obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o
obj-$(CONFIG_POWER_RESET_BRCMKONA) += brcm-kona-reset.o
obj-$(CONFIG_POWER_RESET_BRCMSTB) += brcmstb-reboot.o
@ -29,7 +30,6 @@ obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o
obj-$(CONFIG_POWER_RESET_SYSCON) += syscon-reboot.o
obj-$(CONFIG_POWER_RESET_SYSCON_POWEROFF) += syscon-poweroff.o
obj-$(CONFIG_POWER_RESET_RMOBILE) += rmobile-reset.o
obj-$(CONFIG_POWER_RESET_ZX) += zx-reboot.o
obj-$(CONFIG_REBOOT_MODE) += reboot-mode.o
obj-$(CONFIG_SYSCON_REBOOT_MODE) += syscon-reboot-mode.o
obj-$(CONFIG_POWER_RESET_SC27XX) += sc27xx-poweroff.o

View File

@ -37,7 +37,7 @@
#define AT91_SHDW_MR 0x04 /* Shut Down Mode Register */
#define AT91_SHDW_WKUPDBC_SHIFT 24
#define AT91_SHDW_WKUPDBC_MASK GENMASK(31, 16)
#define AT91_SHDW_WKUPDBC_MASK GENMASK(26, 24)
#define AT91_SHDW_WKUPDBC(x) (((x) << AT91_SHDW_WKUPDBC_SHIFT) \
& AT91_SHDW_WKUPDBC_MASK)
@ -78,9 +78,15 @@ struct pmc_reg_config {
u8 mckr;
};
struct ddrc_reg_config {
u32 type_offset;
u32 type_mask;
};
struct reg_config {
struct shdwc_reg_config shdwc;
struct pmc_reg_config pmc;
struct ddrc_reg_config ddrc;
};
struct shdwc {
@ -262,6 +268,10 @@ static const struct reg_config sama5d2_reg_config = {
.pmc = {
.mckr = 0x30,
},
.ddrc = {
.type_offset = AT91_DDRSDRC_MDR,
.type_mask = AT91_DDRSDRC_MD
},
};
static const struct reg_config sam9x60_reg_config = {
@ -275,6 +285,23 @@ static const struct reg_config sam9x60_reg_config = {
.pmc = {
.mckr = 0x28,
},
.ddrc = {
.type_offset = AT91_DDRSDRC_MDR,
.type_mask = AT91_DDRSDRC_MD
},
};
static const struct reg_config sama7g5_reg_config = {
.shdwc = {
.wkup_pin_input = 0,
.mr_rtcwk_shift = 17,
.mr_rttwk_shift = 16,
.sr_rtcwk_shift = 5,
.sr_rttwk_shift = 4,
},
.pmc = {
.mckr = 0x28,
},
};
static const struct of_device_id at91_shdwc_of_match[] = {
@ -285,6 +312,10 @@ static const struct of_device_id at91_shdwc_of_match[] = {
{
.compatible = "microchip,sam9x60-shdwc",
.data = &sam9x60_reg_config,
},
{
.compatible = "microchip,sama7g5-shdwc",
.data = &sama7g5_reg_config,
}, {
/*sentinel*/
}
@ -294,6 +325,7 @@ MODULE_DEVICE_TABLE(of, at91_shdwc_of_match);
static const struct of_device_id at91_pmc_ids[] = {
{ .compatible = "atmel,sama5d2-pmc" },
{ .compatible = "microchip,sam9x60-pmc" },
{ .compatible = "microchip,sama7g5-pmc" },
{ /* Sentinel. */ }
};
@ -355,30 +387,34 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
goto clk_disable;
}
np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
if (!np) {
ret = -ENODEV;
goto unmap;
}
if (at91_shdwc->rcfg->ddrc.type_mask) {
np = of_find_compatible_node(NULL, NULL,
"atmel,sama5d3-ddramc");
if (!np) {
ret = -ENODEV;
goto unmap;
}
at91_shdwc->mpddrc_base = of_iomap(np, 0);
of_node_put(np);
at91_shdwc->mpddrc_base = of_iomap(np, 0);
of_node_put(np);
if (!at91_shdwc->mpddrc_base) {
ret = -ENOMEM;
goto unmap;
if (!at91_shdwc->mpddrc_base) {
ret = -ENOMEM;
goto unmap;
}
ddr_type = readl(at91_shdwc->mpddrc_base +
at91_shdwc->rcfg->ddrc.type_offset) &
at91_shdwc->rcfg->ddrc.type_mask;
if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&
ddr_type != AT91_DDRSDRC_MD_LPDDR3) {
iounmap(at91_shdwc->mpddrc_base);
at91_shdwc->mpddrc_base = NULL;
}
}
pm_power_off = at91_poweroff;
ddr_type = readl(at91_shdwc->mpddrc_base + AT91_DDRSDRC_MDR) &
AT91_DDRSDRC_MD;
if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&
ddr_type != AT91_DDRSDRC_MD_LPDDR3) {
iounmap(at91_shdwc->mpddrc_base);
at91_shdwc->mpddrc_base = NULL;
}
return 0;
unmap:

View File

@ -0,0 +1,262 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Poweroff & reset driver for Actions Semi ATC260x PMICs
*
* Copyright (c) 2020 Cristian Ciocaltea <cristian.ciocaltea@gmail.com>
*/
#include <linux/delay.h>
#include <linux/mfd/atc260x/core.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
struct atc260x_pwrc {
struct device *dev;
struct regmap *regmap;
struct notifier_block restart_nb;
int (*do_poweroff)(const struct atc260x_pwrc *pwrc, bool restart);
};
/* Global variable needed only for pm_power_off */
static struct atc260x_pwrc *atc260x_pwrc_data;
static int atc2603c_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart)
{
int ret, deep_sleep = 0;
uint reg_mask, reg_val;
/* S4-Deep Sleep Mode is NOT available for WALL/USB power */
if (!restart && !power_supply_is_system_supplied()) {
deep_sleep = 1;
dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode");
}
/* Update wakeup sources */
reg_val = ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN |
(restart ? ATC2603C_PMU_SYS_CTL0_RESET_WK_EN
: ATC2603C_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN);
ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0,
ATC2603C_PMU_SYS_CTL0_WK_ALL, reg_val);
if (ret)
dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
/* Update power mode */
reg_mask = ATC2603C_PMU_SYS_CTL3_EN_S2 | ATC2603C_PMU_SYS_CTL3_EN_S3;
ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3, reg_mask,
deep_sleep ? 0 : ATC2603C_PMU_SYS_CTL3_EN_S3);
if (ret) {
dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
return ret;
}
/* Trigger poweroff / restart sequence */
reg_mask = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN
: ATC2603C_PMU_SYS_CTL1_EN_S1;
reg_val = restart ? ATC2603C_PMU_SYS_CTL0_RESTART_EN : 0;
ret = regmap_update_bits(pwrc->regmap,
restart ? ATC2603C_PMU_SYS_CTL0 : ATC2603C_PMU_SYS_CTL1,
reg_mask, reg_val);
if (ret) {
dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n",
restart ? 0 : 1, ret);
return ret;
}
/* Wait for trigger completion */
mdelay(200);
return 0;
}
static int atc2609a_do_poweroff(const struct atc260x_pwrc *pwrc, bool restart)
{
int ret, deep_sleep = 0;
uint reg_mask, reg_val;
/* S4-Deep Sleep Mode is NOT available for WALL/USB power */
if (!restart && !power_supply_is_system_supplied()) {
deep_sleep = 1;
dev_info(pwrc->dev, "Enabling S4-Deep Sleep Mode");
}
/* Update wakeup sources */
reg_val = ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN |
(restart ? ATC2609A_PMU_SYS_CTL0_RESET_WK_EN
: ATC2609A_PMU_SYS_CTL0_ONOFF_SHORT_WK_EN);
ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0,
ATC2609A_PMU_SYS_CTL0_WK_ALL, reg_val);
if (ret)
dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
/* Update power mode */
reg_mask = ATC2609A_PMU_SYS_CTL3_EN_S2 | ATC2609A_PMU_SYS_CTL3_EN_S3;
ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL3, reg_mask,
deep_sleep ? 0 : ATC2609A_PMU_SYS_CTL3_EN_S3);
if (ret) {
dev_err(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
return ret;
}
/* Trigger poweroff / restart sequence */
reg_mask = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN
: ATC2609A_PMU_SYS_CTL1_EN_S1;
reg_val = restart ? ATC2609A_PMU_SYS_CTL0_RESTART_EN : 0;
ret = regmap_update_bits(pwrc->regmap,
restart ? ATC2609A_PMU_SYS_CTL0 : ATC2609A_PMU_SYS_CTL1,
reg_mask, reg_val);
if (ret) {
dev_err(pwrc->dev, "failed to write SYS_CTL%d: %d\n",
restart ? 0 : 1, ret);
return ret;
}
/* Wait for trigger completion */
mdelay(200);
return 0;
}
static int atc2603c_init(const struct atc260x_pwrc *pwrc)
{
int ret;
/*
* Delay transition from S2/S3 to S1 in order to avoid
* DDR init failure in Bootloader.
*/
ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL3,
ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN,
ATC2603C_PMU_SYS_CTL3_S2S3TOS1_TIMER_EN);
if (ret)
dev_warn(pwrc->dev, "failed to write SYS_CTL3: %d\n", ret);
/* Set wakeup sources */
ret = regmap_update_bits(pwrc->regmap, ATC2603C_PMU_SYS_CTL0,
ATC2603C_PMU_SYS_CTL0_WK_ALL,
ATC2603C_PMU_SYS_CTL0_HDSW_WK_EN |
ATC2603C_PMU_SYS_CTL0_ONOFF_LONG_WK_EN);
if (ret)
dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
return ret;
}
static int atc2609a_init(const struct atc260x_pwrc *pwrc)
{
int ret;
/* Set wakeup sources */
ret = regmap_update_bits(pwrc->regmap, ATC2609A_PMU_SYS_CTL0,
ATC2609A_PMU_SYS_CTL0_WK_ALL,
ATC2609A_PMU_SYS_CTL0_HDSW_WK_EN |
ATC2609A_PMU_SYS_CTL0_ONOFF_LONG_WK_EN);
if (ret)
dev_warn(pwrc->dev, "failed to write SYS_CTL0: %d\n", ret);
return ret;
}
static void atc260x_pwrc_pm_handler(void)
{
atc260x_pwrc_data->do_poweroff(atc260x_pwrc_data, false);
WARN_ONCE(1, "Unable to power off system\n");
}
static int atc260x_pwrc_restart_handler(struct notifier_block *nb,
unsigned long mode, void *cmd)
{
struct atc260x_pwrc *pwrc = container_of(nb, struct atc260x_pwrc,
restart_nb);
pwrc->do_poweroff(pwrc, true);
return NOTIFY_DONE;
}
static int atc260x_pwrc_probe(struct platform_device *pdev)
{
struct atc260x *atc260x = dev_get_drvdata(pdev->dev.parent);
struct atc260x_pwrc *priv;
int ret;
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = &pdev->dev;
priv->regmap = atc260x->regmap;
priv->restart_nb.notifier_call = atc260x_pwrc_restart_handler;
priv->restart_nb.priority = 192;
switch (atc260x->ic_type) {
case ATC2603C:
priv->do_poweroff = atc2603c_do_poweroff;
ret = atc2603c_init(priv);
break;
case ATC2609A:
priv->do_poweroff = atc2609a_do_poweroff;
ret = atc2609a_init(priv);
break;
default:
dev_err(priv->dev,
"Poweroff not supported for ATC260x PMIC type: %u\n",
atc260x->ic_type);
return -EINVAL;
}
if (ret)
return ret;
platform_set_drvdata(pdev, priv);
if (!pm_power_off) {
atc260x_pwrc_data = priv;
pm_power_off = atc260x_pwrc_pm_handler;
} else {
dev_warn(priv->dev, "Poweroff callback already assigned\n");
}
ret = register_restart_handler(&priv->restart_nb);
if (ret)
dev_err(priv->dev, "failed to register restart handler: %d\n",
ret);
return ret;
}
static int atc260x_pwrc_remove(struct platform_device *pdev)
{
struct atc260x_pwrc *priv = platform_get_drvdata(pdev);
if (atc260x_pwrc_data == priv) {
pm_power_off = NULL;
atc260x_pwrc_data = NULL;
}
unregister_restart_handler(&priv->restart_nb);
return 0;
}
static struct platform_driver atc260x_pwrc_driver = {
.probe = atc260x_pwrc_probe,
.remove = atc260x_pwrc_remove,
.driver = {
.name = "atc260x-pwrc",
},
};
module_platform_driver(atc260x_pwrc_driver);
MODULE_DESCRIPTION("Poweroff & reset driver for ATC260x PMICs");
MODULE_AUTHOR("Cristian Ciocaltea <cristian.ciocaltea@gmail.com>");
MODULE_LICENSE("GPL");

View File

@ -113,6 +113,7 @@ static int __init linkstation_poweroff_init(void)
return -EPROBE_DEFER;
phydev = phy_find_first(bus);
put_device(&bus->dev);
if (!phydev)
return -EPROBE_DEFER;

View File

@ -1,86 +0,0 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* ZTE zx296702 SoC reset code
*
* Copyright (c) 2015 Linaro Ltd.
*
* Author: Jun Nie <jun.nie@linaro.org>
*/
#include <linux/delay.h>
#include <linux/io.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/of_address.h>
#include <linux/platform_device.h>
#include <linux/reboot.h>
static void __iomem *base;
static void __iomem *pcu_base;
static int zx_restart_handler(struct notifier_block *this,
unsigned long mode, void *cmd)
{
writel_relaxed(1, base + 0xb0);
writel_relaxed(1, pcu_base + 0x34);
mdelay(50);
pr_emerg("Unable to restart system\n");
return NOTIFY_DONE;
}
static struct notifier_block zx_restart_nb = {
.notifier_call = zx_restart_handler,
.priority = 128,
};
static int zx_reboot_probe(struct platform_device *pdev)
{
struct device_node *np = pdev->dev.of_node;
int err;
base = of_iomap(np, 0);
if (!base) {
WARN(1, "failed to map base address");
return -ENODEV;
}
np = of_find_compatible_node(NULL, NULL, "zte,zx296702-pcu");
pcu_base = of_iomap(np, 0);
of_node_put(np);
if (!pcu_base) {
iounmap(base);
WARN(1, "failed to map pcu_base address");
return -ENODEV;
}
err = register_restart_handler(&zx_restart_nb);
if (err) {
iounmap(base);
iounmap(pcu_base);
dev_err(&pdev->dev, "Register restart handler failed(err=%d)\n",
err);
}
return err;
}
static const struct of_device_id zx_reboot_of_match[] = {
{ .compatible = "zte,sysctrl" },
{}
};
MODULE_DEVICE_TABLE(of, zx_reboot_of_match);
static struct platform_driver zx_reboot_driver = {
.probe = zx_reboot_probe,
.driver = {
.name = "zx-reboot",
.of_match_table = zx_reboot_of_match,
},
};
module_platform_driver(zx_reboot_driver);
MODULE_DESCRIPTION("ZTE SoCs reset driver");
MODULE_AUTHOR("Jun Nie <jun.nie@linaro.org>");
MODULE_LICENSE("GPL v2");

View File

@ -229,6 +229,7 @@ config BATTERY_SBS
config CHARGER_SBS
tristate "SBS Compliant charger"
depends on I2C
select REGMAP_I2C
help
Say Y to include support for SBS compliant battery chargers.
@ -513,6 +514,14 @@ config CHARGER_LT3651
Say Y to include support for the Analog Devices (Linear Technology)
LT3651 battery charger which reports its status via GPIO lines.
config CHARGER_LTC4162L
tristate "LTC4162-L charger"
depends on I2C
select REGMAP_I2C
help
Say Y to include support for the Analog Devices (Linear Technology)
LTC4162-L battery charger connected to I2C.
config CHARGER_MAX14577
tristate "Maxim MAX14577/77836 battery charger driver"
depends on MFD_MAX14577
@ -546,6 +555,7 @@ config CHARGER_MAX77693
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997
depends on EXTCON || !EXTCON
help
Say Y to enable support for the battery charger control sysfs and
platform data of MAX8997/LP3974 PMICs.
@ -645,6 +655,17 @@ config CHARGER_BQ25980
Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960
series of fast battery chargers.
config CHARGER_BQ256XX
tristate "TI BQ256XX battery charger driver"
depends on I2C
depends on GPIOLIB || COMPILE_TEST
select REGMAP_I2C
help
Say Y to enable support for the TI BQ256XX battery chargers. The
BQ256XX family of devices are highly-integrated, switch-mode battery
charge management and system power path management devices for single
cell Li-ion and Li-polymer batteries.
config CHARGER_SMB347
tristate "Summit Microelectronics SMB3XX Battery Charger"
depends on I2C
@ -774,4 +795,10 @@ config RN5T618_POWER
This driver can also be built as a module. If so, the module will be
called rn5t618_power.
config BATTERY_ACER_A500
tristate "Acer Iconia Tab A500 battery driver"
depends on MFD_ACER_A500_EC
help
Say Y to include support for Acer Iconia Tab A500 battery fuel gauge.
endif # POWER_SUPPLY

View File

@ -70,6 +70,7 @@ obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
obj-$(CONFIG_CHARGER_LT3651) += lt3651-charger.o
obj-$(CONFIG_CHARGER_LTC4162L) += ltc4162-l-charger.o
obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
obj-$(CONFIG_CHARGER_DETECTOR_MAX14656) += max14656_charger_detector.o
obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
@ -85,6 +86,7 @@ obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o
obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o
obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o
obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
@ -98,3 +100,4 @@ obj-$(CONFIG_CHARGER_BD70528) += bd70528-charger.o
obj-$(CONFIG_CHARGER_BD99954) += bd99954-charger.o
obj-$(CONFIG_CHARGER_WILCO) += wilco-charger.o
obj-$(CONFIG_RN5T618_POWER) += rn5t618_power.o
obj-$(CONFIG_BATTERY_ACER_A500) += acer_a500_battery.o

View File

@ -857,7 +857,7 @@ static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
const struct abx500_v_to_cap *tbl;
int cap = 0;
tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl,
tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl;
tbl_size = di->bm->bat_type[di->bm->batt_id].n_v_cap_tbl_elements;
for (i = 0; i < tbl_size; ++i) {

View File

@ -0,0 +1,297 @@
// SPDX-License-Identifier: GPL-2.0+
/*
* Battery driver for Acer Iconia Tab A500.
*
* Copyright 2020 GRATE-driver project.
*
* Based on downstream driver from Acer Inc.
* Based on NVIDIA Gas Gauge driver for SBS Compliant Batteries.
*
* Copyright (c) 2010, NVIDIA Corporation.
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
enum {
REG_CAPACITY,
REG_VOLTAGE,
REG_CURRENT,
REG_DESIGN_CAPACITY,
REG_TEMPERATURE,
};
#define EC_DATA(_reg, _psp) { \
.psp = POWER_SUPPLY_PROP_ ## _psp, \
.reg = _reg, \
}
static const struct battery_register {
enum power_supply_property psp;
unsigned int reg;
} ec_data[] = {
[REG_CAPACITY] = EC_DATA(0x00, CAPACITY),
[REG_VOLTAGE] = EC_DATA(0x01, VOLTAGE_NOW),
[REG_CURRENT] = EC_DATA(0x03, CURRENT_NOW),
[REG_DESIGN_CAPACITY] = EC_DATA(0x08, CHARGE_FULL_DESIGN),
[REG_TEMPERATURE] = EC_DATA(0x0a, TEMP),
};
static const enum power_supply_property a500_battery_properties[] = {
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
struct a500_battery {
struct delayed_work poll_work;
struct power_supply *psy;
struct regmap *regmap;
unsigned int capacity;
};
static bool a500_battery_update_capacity(struct a500_battery *bat)
{
unsigned int capacity;
int err;
err = regmap_read(bat->regmap, ec_data[REG_CAPACITY].reg, &capacity);
if (err)
return false;
/* capacity can be >100% even if max value is 100% */
capacity = min(capacity, 100u);
if (bat->capacity != capacity) {
bat->capacity = capacity;
return true;
}
return false;
}
static int a500_battery_get_status(struct a500_battery *bat)
{
if (bat->capacity < 100) {
if (power_supply_am_i_supplied(bat->psy))
return POWER_SUPPLY_STATUS_CHARGING;
else
return POWER_SUPPLY_STATUS_DISCHARGING;
}
return POWER_SUPPLY_STATUS_FULL;
}
static void a500_battery_unit_adjustment(struct device *dev,
enum power_supply_property psp,
union power_supply_propval *val)
{
const unsigned int base_unit_conversion = 1000;
const unsigned int temp_kelvin_to_celsius = 2731;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval *= base_unit_conversion;
break;
case POWER_SUPPLY_PROP_TEMP:
val->intval -= temp_kelvin_to_celsius;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = !!val->intval;
break;
default:
dev_dbg(dev,
"%s: no need for unit conversion %d\n", __func__, psp);
}
}
static int a500_battery_get_ec_data_index(struct device *dev,
enum power_supply_property psp)
{
unsigned int i;
/*
* DESIGN_CAPACITY register always returns a non-zero value if
* battery is connected and zero if disconnected, hence we'll use
* it for judging the battery presence.
*/
if (psp == POWER_SUPPLY_PROP_PRESENT)
psp = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
for (i = 0; i < ARRAY_SIZE(ec_data); i++)
if (psp == ec_data[i].psp)
return i;
dev_dbg(dev, "%s: invalid property %u\n", __func__, psp);
return -EINVAL;
}
static int a500_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct a500_battery *bat = power_supply_get_drvdata(psy);
struct device *dev = psy->dev.parent;
int ret = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = a500_battery_get_status(bat);
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CAPACITY:
a500_battery_update_capacity(bat);
val->intval = bat->capacity;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_PRESENT:
case POWER_SUPPLY_PROP_TEMP:
ret = a500_battery_get_ec_data_index(dev, psp);
if (ret < 0)
break;
ret = regmap_read(bat->regmap, ec_data[ret].reg, &val->intval);
break;
default:
dev_err(dev, "%s: invalid property %u\n", __func__, psp);
return -EINVAL;
}
if (!ret) {
/* convert units to match requirements of power supply class */
a500_battery_unit_adjustment(dev, psp, val);
}
dev_dbg(dev, "%s: property = %d, value = %x\n",
__func__, psp, val->intval);
/* return NODATA for properties if battery not presents */
if (ret)
return -ENODATA;
return 0;
}
static void a500_battery_poll_work(struct work_struct *work)
{
struct a500_battery *bat;
bool capacity_changed;
bat = container_of(work, struct a500_battery, poll_work.work);
capacity_changed = a500_battery_update_capacity(bat);
if (capacity_changed)
power_supply_changed(bat->psy);
/* continuously send uevent notification */
schedule_delayed_work(&bat->poll_work, 30 * HZ);
}
static const struct power_supply_desc a500_battery_desc = {
.name = "ec-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = a500_battery_properties,
.get_property = a500_battery_get_property,
.num_properties = ARRAY_SIZE(a500_battery_properties),
.external_power_changed = power_supply_changed,
};
static int a500_battery_probe(struct platform_device *pdev)
{
struct power_supply_config psy_cfg = {};
struct a500_battery *bat;
bat = devm_kzalloc(&pdev->dev, sizeof(*bat), GFP_KERNEL);
if (!bat)
return -ENOMEM;
platform_set_drvdata(pdev, bat);
psy_cfg.of_node = pdev->dev.parent->of_node;
psy_cfg.drv_data = bat;
bat->regmap = dev_get_regmap(pdev->dev.parent, "KB930");
if (!bat->regmap)
return -EINVAL;
bat->psy = devm_power_supply_register_no_ws(&pdev->dev,
&a500_battery_desc,
&psy_cfg);
if (IS_ERR(bat->psy))
return dev_err_probe(&pdev->dev, PTR_ERR(bat->psy),
"failed to register battery\n");
INIT_DELAYED_WORK(&bat->poll_work, a500_battery_poll_work);
schedule_delayed_work(&bat->poll_work, HZ);
return 0;
}
static int a500_battery_remove(struct platform_device *pdev)
{
struct a500_battery *bat = dev_get_drvdata(&pdev->dev);
cancel_delayed_work_sync(&bat->poll_work);
return 0;
}
static int __maybe_unused a500_battery_suspend(struct device *dev)
{
struct a500_battery *bat = dev_get_drvdata(dev);
cancel_delayed_work_sync(&bat->poll_work);
return 0;
}
static int __maybe_unused a500_battery_resume(struct device *dev)
{
struct a500_battery *bat = dev_get_drvdata(dev);
schedule_delayed_work(&bat->poll_work, HZ);
return 0;
}
static SIMPLE_DEV_PM_OPS(a500_battery_pm_ops,
a500_battery_suspend, a500_battery_resume);
static struct platform_driver a500_battery_driver = {
.driver = {
.name = "acer-a500-iconia-battery",
.pm = &a500_battery_pm_ops,
},
.probe = a500_battery_probe,
.remove = a500_battery_remove,
};
module_platform_driver(a500_battery_driver);
MODULE_DESCRIPTION("Battery gauge driver for Acer Iconia Tab A500");
MODULE_AUTHOR("Dmitry Osipenko <digetx@gmail.com>");
MODULE_ALIAS("platform:acer-a500-iconia-battery");
MODULE_LICENSE("GPL");

View File

@ -593,6 +593,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
power->axp20x_id = axp_data->axp20x_id;
power->regmap = axp20x->regmap;
power->num_irqs = axp_data->num_irq_names;
INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
if (power->axp20x_id == AXP202_ID) {
/* Enable vbus valid checking */
@ -645,7 +646,6 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
}
}
INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
if (axp20x_usb_vbus_needs_polling(power))
queue_delayed_work(system_power_efficient_wq, &power->vbus_detect, 0);

View File

@ -732,6 +732,12 @@ static const struct dmi_system_id axp288_fuel_gauge_blacklist[] = {
DMI_MATCH(DMI_BOARD_VERSION, "V1.1"),
},
},
{ /* Mele PCG03 Mini PC */
.matches = {
DMI_EXACT_MATCH(DMI_BOARD_VENDOR, "Mini PC"),
DMI_EXACT_MATCH(DMI_BOARD_NAME, "Mini PC"),
},
},
{
/* Minix Neo Z83-4 mini PC */
.matches = {

View File

@ -1766,7 +1766,7 @@ static int bq24190_probe(struct i2c_client *client,
charger_cfg.drv_data = bdi;
charger_cfg.of_node = dev->of_node;
charger_cfg.supplied_to = bq24190_charger_supplied_to;
charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to),
charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to);
bdi->charger = power_supply_register(dev, &bq24190_charger_desc,
&charger_cfg);
if (IS_ERR(bdi->charger)) {

File diff suppressed because it is too large Load Diff

View File

@ -1285,7 +1285,7 @@ static int bq25980_probe(struct i2c_client *client,
static const struct i2c_device_id bq25980_i2c_ids[] = {
{ "bq25980", BQ25980 },
{ "bq25975", BQ25975 },
{ "bq25975", BQ25975 },
{ "bq25960", BQ25960 },
{},
};
MODULE_DEVICE_TABLE(i2c, bq25980_i2c_ids);

View File

@ -110,6 +110,7 @@ enum bq27xxx_reg_index {
BQ27XXX_REG_TTES, /* Time-to-Empty Standby */
BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */
BQ27XXX_REG_NAC, /* Nominal Available Capacity */
BQ27XXX_REG_RC, /* Remaining Capacity */
BQ27XXX_REG_FCC, /* Full Charge Capacity */
BQ27XXX_REG_CYCT, /* Cycle Count */
BQ27XXX_REG_AE, /* Available Energy */
@ -145,6 +146,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = INVALID_REG_ADDR,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -169,6 +171,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = INVALID_REG_ADDR,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -193,6 +196,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1a,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -215,6 +219,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -237,6 +242,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1a,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x1e,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -257,6 +263,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
[BQ27XXX_REG_AE] = 0x22,
@ -277,6 +284,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -297,6 +305,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = 0x26,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -317,6 +326,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1c,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x1e,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -337,6 +347,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = INVALID_REG_ADDR,
[BQ27XXX_REG_RC] = INVALID_REG_ADDR,
[BQ27XXX_REG_FCC] = INVALID_REG_ADDR,
[BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -361,6 +372,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -382,6 +394,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -405,6 +418,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x0c,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -425,6 +439,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = 0x08,
[BQ27XXX_REG_RC] = 0x0c,
[BQ27XXX_REG_FCC] = 0x0e,
[BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
[BQ27XXX_REG_AE] = INVALID_REG_ADDR,
@ -450,6 +465,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = INVALID_REG_ADDR,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -470,6 +486,7 @@ static u8
[BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = INVALID_REG_ADDR,
[BQ27XXX_REG_RC] = 0x10,
[BQ27XXX_REG_FCC] = 0x12,
[BQ27XXX_REG_CYCT] = 0x2a,
[BQ27XXX_REG_AE] = 0x22,
@ -490,6 +507,7 @@ static u8
[BQ27XXX_REG_TTES] = 0x1e,
[BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
[BQ27XXX_REG_NAC] = INVALID_REG_ADDR,
[BQ27XXX_REG_RC] = 0x04,
[BQ27XXX_REG_FCC] = 0x06,
[BQ27XXX_REG_CYCT] = 0x2c,
[BQ27XXX_REG_AE] = 0x24,
@ -745,6 +763,7 @@ static enum power_supply_property bq27z561_props[] = {
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_POWER_AVG,
@ -764,6 +783,7 @@ static enum power_supply_property bq28z610_props[] = {
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_POWER_AVG,
@ -784,6 +804,7 @@ static enum power_supply_property bq34z100_props[] = {
POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_ENERGY_NOW,
@ -1518,6 +1539,15 @@ static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di)
return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC);
}
/*
* Return the battery Remaining Capacity in µAh
* Or < 0 if something fails.
*/
static inline int bq27xxx_battery_read_rc(struct bq27xxx_device_info *di)
{
return bq27xxx_battery_read_charge(di, BQ27XXX_REG_RC);
}
/*
* Return the battery Full Charge Capacity in µAh
* Or < 0 if something fails.
@ -1789,7 +1819,7 @@ static int bq27xxx_battery_current(struct bq27xxx_device_info *di,
if (di->opts & BQ27XXX_O_ZERO) {
flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true);
if (flags & BQ27000_FLAG_CHGS) {
if (!(flags & BQ27000_FLAG_CHGS)) {
dev_dbg(di->dev, "negative current!\n");
curr = -curr;
}
@ -1797,7 +1827,7 @@ static int bq27xxx_battery_current(struct bq27xxx_device_info *di,
val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS;
} else {
/* Other gauges return signed value */
val->intval = (int)((s16)curr) * 1000;
val->intval = -(int)((s16)curr) * 1000;
}
return 0;
@ -1965,7 +1995,10 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val);
if (di->regs[BQ27XXX_REG_NAC] != INVALID_REG_ADDR)
ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val);
else
ret = bq27xxx_simple_value(bq27xxx_battery_read_rc(di), val);
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
ret = bq27xxx_simple_value(di->cache.charge_full, val);

View File

@ -570,7 +570,7 @@ static int cm_get_target_status(struct charger_manager *cm)
return POWER_SUPPLY_STATUS_DISCHARGING;
if (cm_check_thermal_status(cm)) {
/* Check if discharging duration exeeds limit. */
/* Check if discharging duration exceeds limit. */
if (check_charging_duration(cm))
goto charging_ok;
return POWER_SUPPLY_STATUS_NOT_CHARGING;
@ -578,7 +578,7 @@ static int cm_get_target_status(struct charger_manager *cm)
switch (cm->battery_status) {
case POWER_SUPPLY_STATUS_CHARGING:
/* Check if charging duration exeeds limit. */
/* Check if charging duration exceeds limit. */
if (check_charging_duration(cm))
return POWER_SUPPLY_STATUS_FULL;
fallthrough;
@ -723,9 +723,9 @@ static int charger_get_property(struct power_supply *psy,
val->intval = cm->battery_status;
break;
case POWER_SUPPLY_PROP_HEALTH:
if (cm->emergency_stop > 0)
if (cm->emergency_stop == CM_BATT_OVERHEAT)
val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
else if (cm->emergency_stop < 0)
else if (cm->emergency_stop == CM_BATT_COLD)
val->intval = POWER_SUPPLY_HEALTH_COLD;
else
val->intval = POWER_SUPPLY_HEALTH_GOOD;

View File

@ -3,7 +3,7 @@
*
* Copyright (C) 2017 Tony Lindgren <tony@atomide.com>
*
* Some parts of the code based on earlie Motorola mapphone Linux kernel
* Some parts of the code based on earlier Motorola mapphone Linux kernel
* drivers:
*
* Copyright (C) 2009-2010 Motorola, Inc.
@ -28,6 +28,7 @@
#include <linux/power_supply.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/moduleparam.h>
#include <linux/iio/consumer.h>
#include <linux/iio/types.h>
@ -110,6 +111,8 @@ struct cpcap_coulomb_counter_data {
enum cpcap_battery_state {
CPCAP_BATTERY_STATE_PREVIOUS,
CPCAP_BATTERY_STATE_LATEST,
CPCAP_BATTERY_STATE_EMPTY,
CPCAP_BATTERY_STATE_FULL,
CPCAP_BATTERY_STATE_NR,
};
@ -132,12 +135,17 @@ struct cpcap_battery_ddata {
struct cpcap_battery_state_data state[CPCAP_BATTERY_STATE_NR];
u32 cc_lsb; /* μAms per LSB */
atomic_t active;
int charge_full;
int status;
u16 vendor;
unsigned int is_full:1;
};
#define CPCAP_NO_BATTERY -400
static bool ignore_temperature_probe;
module_param(ignore_temperature_probe, bool, 0660);
static struct cpcap_battery_state_data *
cpcap_battery_get_state(struct cpcap_battery_ddata *ddata,
enum cpcap_battery_state state)
@ -160,6 +168,18 @@ cpcap_battery_previous(struct cpcap_battery_ddata *ddata)
return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_PREVIOUS);
}
static struct cpcap_battery_state_data *
cpcap_battery_get_empty(struct cpcap_battery_ddata *ddata)
{
return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_EMPTY);
}
static struct cpcap_battery_state_data *
cpcap_battery_get_full(struct cpcap_battery_ddata *ddata)
{
return cpcap_battery_get_state(ddata, CPCAP_BATTERY_STATE_FULL);
}
static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata,
int *value)
{
@ -169,7 +189,8 @@ static int cpcap_charger_battery_temperature(struct cpcap_battery_ddata *ddata,
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);
if (!ignore_temperature_probe)
dev_warn(ddata->dev, "%s failed: %i\n", __func__, error);
*value = CPCAP_NO_BATTERY;
return error;
@ -366,20 +387,81 @@ static int cpcap_battery_cc_get_avg_current(struct cpcap_battery_ddata *ddata)
return cpcap_battery_cc_to_ua(ddata, sample, acc, offset);
}
static int cpcap_battery_get_charger_status(struct cpcap_battery_ddata *ddata,
int *val)
{
union power_supply_propval prop;
struct power_supply *charger;
int error;
charger = power_supply_get_by_name("usb");
if (!charger)
return -ENODEV;
error = power_supply_get_property(charger, POWER_SUPPLY_PROP_STATUS,
&prop);
if (error)
*val = POWER_SUPPLY_STATUS_UNKNOWN;
else
*val = prop.intval;
power_supply_put(charger);
return error;
}
static bool cpcap_battery_full(struct cpcap_battery_ddata *ddata)
{
struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata);
unsigned int vfull;
int error, val;
if (state->voltage >=
(ddata->config.bat.constant_charge_voltage_max_uv - 18000))
return true;
error = cpcap_battery_get_charger_status(ddata, &val);
if (!error) {
switch (val) {
case POWER_SUPPLY_STATUS_DISCHARGING:
dev_dbg(ddata->dev, "charger disconnected\n");
ddata->is_full = 0;
break;
case POWER_SUPPLY_STATUS_FULL:
dev_dbg(ddata->dev, "charger full status\n");
ddata->is_full = 1;
break;
default:
break;
}
}
return false;
/*
* The full battery voltage here can be inaccurate, it's used just to
* filter out any trickle charging events. We clear the is_full status
* on charger disconnect above anyways.
*/
vfull = ddata->config.bat.constant_charge_voltage_max_uv - 120000;
if (ddata->is_full && state->voltage < vfull)
ddata->is_full = 0;
return ddata->is_full;
}
static bool cpcap_battery_low(struct cpcap_battery_ddata *ddata)
{
struct cpcap_battery_state_data *state = cpcap_battery_latest(ddata);
static bool is_low;
if (state->current_ua > 0 && (state->voltage <= 3350000 || is_low))
is_low = true;
else
is_low = false;
return is_low;
}
static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata)
{
struct cpcap_battery_state_data state, *latest, *previous;
struct cpcap_battery_state_data state, *latest, *previous,
*empty, *full;
ktime_t now;
int error;
@ -408,9 +490,47 @@ static int cpcap_battery_update_status(struct cpcap_battery_ddata *ddata)
memcpy(previous, latest, sizeof(*previous));
memcpy(latest, &state, sizeof(*latest));
if (cpcap_battery_full(ddata)) {
full = cpcap_battery_get_full(ddata);
memcpy(full, latest, sizeof(*full));
empty = cpcap_battery_get_empty(ddata);
if (empty->voltage && empty->voltage != -1) {
empty->voltage = -1;
ddata->charge_full =
empty->counter_uah - full->counter_uah;
} else if (ddata->charge_full) {
empty->voltage = -1;
empty->counter_uah =
full->counter_uah + ddata->charge_full;
}
} else if (cpcap_battery_low(ddata)) {
empty = cpcap_battery_get_empty(ddata);
memcpy(empty, latest, sizeof(*empty));
full = cpcap_battery_get_full(ddata);
if (full->voltage) {
full->voltage = 0;
ddata->charge_full =
empty->counter_uah - full->counter_uah;
}
}
return 0;
}
/*
* Update battery status when cpcap-charger calls power_supply_changed().
* This allows us to detect battery full condition before the charger
* disconnects.
*/
static void cpcap_battery_external_power_changed(struct power_supply *psy)
{
union power_supply_propval prop;
power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &prop);
}
static enum power_supply_property cpcap_battery_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
@ -421,10 +541,13 @@ static enum power_supply_property cpcap_battery_props[] = {
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_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,
POWER_SUPPLY_PROP_CAPACITY_LEVEL,
POWER_SUPPLY_PROP_SCOPE,
POWER_SUPPLY_PROP_TEMP,
@ -435,7 +558,7 @@ static int cpcap_battery_get_property(struct power_supply *psy,
union power_supply_propval *val)
{
struct cpcap_battery_ddata *ddata = power_supply_get_drvdata(psy);
struct cpcap_battery_state_data *latest, *previous;
struct cpcap_battery_state_data *latest, *previous, *empty;
u32 sample;
s32 accumulator;
int cached;
@ -450,7 +573,7 @@ static int cpcap_battery_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
if (latest->temperature > CPCAP_NO_BATTERY)
if (latest->temperature > CPCAP_NO_BATTERY || ignore_temperature_probe)
val->intval = 1;
else
val->intval = 0;
@ -515,6 +638,16 @@ static int cpcap_battery_get_property(struct power_supply *psy,
tmp *= ((latest->voltage + previous->voltage) / 20000);
val->intval = div64_s64(tmp, 100);
break;
case POWER_SUPPLY_PROP_CAPACITY:
empty = cpcap_battery_get_empty(ddata);
if (!empty->voltage || !ddata->charge_full)
return -ENODATA;
/* (ddata->charge_full / 200) is needed for rounding */
val->intval = empty->counter_uah - latest->counter_uah +
ddata->charge_full / 200;
val->intval = clamp(val->intval, 0, ddata->charge_full);
val->intval = val->intval * 100 / ddata->charge_full;
break;
case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
if (cpcap_battery_full(ddata))
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
@ -529,6 +662,21 @@ static int cpcap_battery_get_property(struct power_supply *psy,
else
val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
empty = cpcap_battery_get_empty(ddata);
if (!empty->voltage)
return -ENODATA;
val->intval = empty->counter_uah - latest->counter_uah;
if (val->intval < 0)
val->intval = 0;
else if (ddata->charge_full && ddata->charge_full < val->intval)
val->intval = ddata->charge_full;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (!ddata->charge_full)
return -ENODATA;
val->intval = ddata->charge_full;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = ddata->config.info.charge_full_design;
break;
@ -536,6 +684,8 @@ static int cpcap_battery_get_property(struct power_supply *psy,
val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
break;
case POWER_SUPPLY_PROP_TEMP:
if (ignore_temperature_probe)
return -ENODATA;
val->intval = latest->temperature;
break;
default:
@ -561,17 +711,21 @@ static int cpcap_battery_update_charger(struct cpcap_battery_ddata *ddata,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
&prop);
if (error)
return error;
goto out_put;
/* Allow charger const voltage lower than battery const voltage */
if (const_charge_voltage > prop.intval)
return 0;
goto out_put;
val.intval = const_charge_voltage;
return power_supply_set_property(charger,
error = power_supply_set_property(charger,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
&val);
out_put:
power_supply_put(charger);
return error;
}
static int cpcap_battery_set_property(struct power_supply *psy,
@ -590,6 +744,15 @@ static int cpcap_battery_set_property(struct power_supply *psy,
ddata->config.bat.constant_charge_voltage_max_uv = val->intval;
return cpcap_battery_update_charger(ddata, val->intval);
case POWER_SUPPLY_PROP_CHARGE_FULL:
if (val->intval < 0)
return -EINVAL;
if (val->intval > ddata->config.info.charge_full_design)
return -EINVAL;
ddata->charge_full = val->intval;
return 0;
default:
return -EINVAL;
}
@ -602,6 +765,7 @@ static int cpcap_battery_property_is_writeable(struct power_supply *psy,
{
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
case POWER_SUPPLY_PROP_CHARGE_FULL:
return 1;
default:
return 0;
@ -666,7 +830,7 @@ static int cpcap_battery_init_irq(struct platform_device *pdev,
error = devm_request_threaded_irq(ddata->dev, irq, NULL,
cpcap_battery_irq_thread,
IRQF_SHARED,
IRQF_SHARED | IRQF_ONESHOT,
name, ddata);
if (error) {
dev_err(ddata->dev, "could not get irq %s: %i\n",
@ -835,9 +999,19 @@ static const struct of_device_id cpcap_battery_id_table[] = {
MODULE_DEVICE_TABLE(of, cpcap_battery_id_table);
#endif
static const struct power_supply_desc cpcap_charger_battery_desc = {
.name = "battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = cpcap_battery_props,
.num_properties = ARRAY_SIZE(cpcap_battery_props),
.get_property = cpcap_battery_get_property,
.set_property = cpcap_battery_set_property,
.property_is_writeable = cpcap_battery_property_is_writeable,
.external_power_changed = cpcap_battery_external_power_changed,
};
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 = {};
@ -892,22 +1066,11 @@ static int cpcap_battery_probe(struct platform_device *pdev)
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_desc->set_property = cpcap_battery_set_property;
psy_desc->property_is_writeable = cpcap_battery_property_is_writeable;
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = ddata;
ddata->psy = devm_power_supply_register(ddata->dev, psy_desc,
ddata->psy = devm_power_supply_register(ddata->dev,
&cpcap_charger_battery_desc,
&psy_cfg);
error = PTR_ERR_OR_ZERO(ddata->psy);
if (error) {

View File

@ -89,6 +89,8 @@
* 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.
*
* Except 70mA and 1.596A and unlimited, these are simply 88.7mA / step.
*/
#define CPCAP_REG_CRM_ICHRG(val) (((val) & 0xf) << 0)
#define CPCAP_REG_CRM_ICHRG_0A000 CPCAP_REG_CRM_ICHRG(0x0)
@ -120,13 +122,6 @@ enum {
CPCAP_CHARGER_IIO_NR,
};
enum {
CPCAP_CHARGER_DISCONNECTED,
CPCAP_CHARGER_DETECTING,
CPCAP_CHARGER_CHARGING,
CPCAP_CHARGER_DONE,
};
struct cpcap_charger_ddata {
struct device *dev;
struct regmap *reg;
@ -145,8 +140,8 @@ struct cpcap_charger_ddata {
atomic_t active;
int status;
int state;
int voltage;
int limit_current;
};
struct cpcap_interrupt_desc {
@ -173,6 +168,7 @@ static enum power_supply_property cpcap_charger_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
};
@ -236,6 +232,9 @@ static int cpcap_charger_get_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_STATUS:
val->intval = ddata->status;
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
val->intval = ddata->limit_current;
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
val->intval = ddata->voltage;
break;
@ -301,11 +300,33 @@ cpcap_charger_get_bat_const_charge_voltage(struct cpcap_charger_ddata *ddata)
&prop);
if (!error)
voltage = prop.intval;
power_supply_put(battery);
}
return voltage;
}
static int cpcap_charger_current_to_regval(int microamp)
{
int miliamp = microamp / 1000;
int res;
if (miliamp < 0)
return -EINVAL;
if (miliamp < 70)
return CPCAP_REG_CRM_ICHRG(0x0);
if (miliamp < 177)
return CPCAP_REG_CRM_ICHRG(0x1);
if (miliamp > 1596)
return CPCAP_REG_CRM_ICHRG(0xe);
res = microamp / 88666;
if (res > 0xd)
res = 0xd;
return CPCAP_REG_CRM_ICHRG(res);
}
static int cpcap_charger_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
@ -314,6 +335,12 @@ static int cpcap_charger_set_property(struct power_supply *psy,
int voltage, batvolt;
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
if (cpcap_charger_current_to_regval(val->intval) < 0)
return -EINVAL;
ddata->limit_current = val->intval;
schedule_delayed_work(&ddata->detect_work, 0);
break;
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
voltage = cpcap_charger_match_voltage(val->intval);
batvolt = cpcap_charger_get_bat_const_charge_voltage(ddata);
@ -333,6 +360,7 @@ static int cpcap_charger_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
return 1;
default:
@ -358,30 +386,63 @@ static void cpcap_charger_set_inductive_path(struct cpcap_charger_ddata *ddata,
gpiod_set_value(ddata->gpio[1], enabled);
}
static int cpcap_charger_set_state(struct cpcap_charger_ddata *ddata,
int max_voltage, int charge_current,
int trickle_current)
static void cpcap_charger_update_state(struct cpcap_charger_ddata *ddata,
int state)
{
const char *status;
if (state > POWER_SUPPLY_STATUS_FULL) {
dev_warn(ddata->dev, "unknown state: %i\n", state);
return;
}
ddata->status = state;
switch (state) {
case POWER_SUPPLY_STATUS_DISCHARGING:
status = "DISCONNECTED";
break;
case POWER_SUPPLY_STATUS_NOT_CHARGING:
status = "DETECTING";
break;
case POWER_SUPPLY_STATUS_CHARGING:
status = "CHARGING";
break;
case POWER_SUPPLY_STATUS_FULL:
status = "DONE";
break;
default:
return;
}
dev_dbg(ddata->dev, "state: %s\n", status);
}
static int cpcap_charger_disable(struct cpcap_charger_ddata *ddata)
{
bool enable;
int error;
enable = (charge_current || trickle_current);
dev_dbg(ddata->dev, "%s enable: %i\n", __func__, enable);
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, 0x3fff,
CPCAP_REG_CRM_FET_OVRD |
CPCAP_REG_CRM_FET_CTRL);
if (error)
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
if (!enable) {
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM,
0x3fff,
CPCAP_REG_CRM_FET_OVRD |
CPCAP_REG_CRM_FET_CTRL);
if (error) {
ddata->status = POWER_SUPPLY_STATUS_UNKNOWN;
goto out_err;
}
return error;
}
ddata->status = POWER_SUPPLY_STATUS_DISCHARGING;
static int cpcap_charger_enable(struct cpcap_charger_ddata *ddata,
int max_voltage, int charge_current,
int trickle_current)
{
int error;
return 0;
}
if (!max_voltage || !charge_current)
return -EINVAL;
dev_dbg(ddata->dev, "enable: %i %i %i\n",
max_voltage, charge_current, trickle_current);
error = regmap_update_bits(ddata->reg, CPCAP_REG_CRM, 0x3fff,
CPCAP_REG_CRM_CHRG_LED_EN |
@ -390,17 +451,8 @@ static int cpcap_charger_set_state(struct cpcap_charger_ddata *ddata,
CPCAP_REG_CRM_FET_CTRL |
max_voltage |
charge_current);
if (error) {
ddata->status = POWER_SUPPLY_STATUS_UNKNOWN;
goto out_err;
}
ddata->status = POWER_SUPPLY_STATUS_CHARGING;
return 0;
out_err:
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
if (error)
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
return error;
}
@ -433,7 +485,7 @@ static void cpcap_charger_vbus_work(struct work_struct *work)
if (ddata->vbus_enabled) {
vbus = cpcap_charger_vbus_valid(ddata);
if (vbus) {
dev_info(ddata->dev, "VBUS already provided\n");
dev_dbg(ddata->dev, "VBUS already provided\n");
return;
}
@ -442,10 +494,13 @@ static void cpcap_charger_vbus_work(struct work_struct *work)
cpcap_charger_set_cable_path(ddata, false);
cpcap_charger_set_inductive_path(ddata, false);
error = cpcap_charger_set_state(ddata, 0, 0, 0);
error = cpcap_charger_disable(ddata);
if (error)
goto out_err;
cpcap_charger_update_state(ddata,
POWER_SUPPLY_STATUS_DISCHARGING);
error = regmap_update_bits(ddata->reg, CPCAP_REG_VUSBC,
CPCAP_BIT_VBUS_SWITCH,
CPCAP_BIT_VBUS_SWITCH);
@ -476,6 +531,7 @@ static void cpcap_charger_vbus_work(struct work_struct *work)
return;
out_err:
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN);
dev_err(ddata->dev, "%s could not %s vbus: %i\n", __func__,
ddata->vbus_enabled ? "enable" : "disable", error);
}
@ -527,39 +583,6 @@ static int cpcap_charger_get_ints_state(struct cpcap_charger_ddata *ddata,
return 0;
}
static void cpcap_charger_update_state(struct cpcap_charger_ddata *ddata,
int state)
{
const char *status;
if (state > CPCAP_CHARGER_DONE) {
dev_warn(ddata->dev, "unknown state: %i\n", state);
return;
}
ddata->state = state;
switch (state) {
case CPCAP_CHARGER_DISCONNECTED:
status = "DISCONNECTED";
break;
case CPCAP_CHARGER_DETECTING:
status = "DETECTING";
break;
case CPCAP_CHARGER_CHARGING:
status = "CHARGING";
break;
case CPCAP_CHARGER_DONE:
status = "DONE";
break;
default:
return;
}
dev_dbg(ddata->dev, "state: %s\n", status);
}
static int cpcap_charger_voltage_to_regval(int voltage)
{
int offset;
@ -591,9 +614,21 @@ static void cpcap_charger_disconnect(struct cpcap_charger_ddata *ddata,
{
int error;
error = cpcap_charger_set_state(ddata, 0, 0, 0);
if (error)
/* Update battery state before disconnecting the charger */
switch (state) {
case POWER_SUPPLY_STATUS_DISCHARGING:
case POWER_SUPPLY_STATUS_FULL:
power_supply_changed(ddata->usb);
break;
default:
break;
}
error = cpcap_charger_disable(ddata);
if (error) {
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN);
return;
}
cpcap_charger_update_state(ddata, state);
power_supply_changed(ddata->usb);
@ -604,7 +639,7 @@ static void cpcap_usb_detect(struct work_struct *work)
{
struct cpcap_charger_ddata *ddata;
struct cpcap_charger_ints_state s;
int error;
int error, new_state;
ddata = container_of(work, struct cpcap_charger_ddata,
detect_work.work);
@ -615,7 +650,8 @@ static void cpcap_usb_detect(struct work_struct *work)
/* Just init the state if a charger is connected with no chrg_det set */
if (!s.chrg_det && s.chrgcurr1 && s.vbusvld) {
cpcap_charger_update_state(ddata, CPCAP_CHARGER_DETECTING);
cpcap_charger_update_state(ddata,
POWER_SUPPLY_STATUS_NOT_CHARGING);
return;
}
@ -625,28 +661,35 @@ static void cpcap_usb_detect(struct work_struct *work)
* charged to 4.35V by Android. Try again in 10 minutes.
*/
if (cpcap_charger_get_charge_voltage(ddata) > ddata->voltage) {
cpcap_charger_disconnect(ddata, CPCAP_CHARGER_DETECTING,
cpcap_charger_disconnect(ddata,
POWER_SUPPLY_STATUS_NOT_CHARGING,
HZ * 60 * 10);
return;
}
/* Throttle chrgcurr2 interrupt for charger done and retry */
switch (ddata->state) {
case CPCAP_CHARGER_CHARGING:
switch (ddata->status) {
case POWER_SUPPLY_STATUS_CHARGING:
if (s.chrgcurr2)
break;
new_state = POWER_SUPPLY_STATUS_FULL;
if (s.chrgcurr1 && s.vbusvld) {
cpcap_charger_disconnect(ddata, CPCAP_CHARGER_DONE,
HZ * 5);
cpcap_charger_disconnect(ddata, new_state, HZ * 5);
return;
}
break;
case CPCAP_CHARGER_DONE:
case POWER_SUPPLY_STATUS_FULL:
if (!s.chrgcurr2)
break;
cpcap_charger_disconnect(ddata, CPCAP_CHARGER_DETECTING,
HZ * 5);
if (s.vbusvld)
new_state = POWER_SUPPLY_STATUS_NOT_CHARGING;
else
new_state = POWER_SUPPLY_STATUS_DISCHARGING;
cpcap_charger_disconnect(ddata, new_state, HZ * 5);
return;
default:
break;
@ -654,32 +697,37 @@ static void cpcap_usb_detect(struct work_struct *work)
if (!ddata->feeding_vbus && cpcap_charger_vbus_valid(ddata) &&
s.chrgcurr1) {
int max_current;
int vchrg;
int max_current = 532000;
int vchrg, ichrg;
if (cpcap_charger_battery_found(ddata))
max_current = CPCAP_REG_CRM_ICHRG_1A596;
else
max_current = CPCAP_REG_CRM_ICHRG_0A532;
max_current = 1596000;
if (max_current > ddata->limit_current)
max_current = ddata->limit_current;
ichrg = cpcap_charger_current_to_regval(max_current);
vchrg = cpcap_charger_voltage_to_regval(ddata->voltage);
error = cpcap_charger_set_state(ddata,
CPCAP_REG_CRM_VCHRG(vchrg),
max_current, 0);
error = cpcap_charger_enable(ddata,
CPCAP_REG_CRM_VCHRG(vchrg),
ichrg, 0);
if (error)
goto out_err;
cpcap_charger_update_state(ddata, CPCAP_CHARGER_CHARGING);
cpcap_charger_update_state(ddata,
POWER_SUPPLY_STATUS_CHARGING);
} else {
error = cpcap_charger_set_state(ddata, 0, 0, 0);
error = cpcap_charger_disable(ddata);
if (error)
goto out_err;
cpcap_charger_update_state(ddata, CPCAP_CHARGER_DISCONNECTED);
cpcap_charger_update_state(ddata,
POWER_SUPPLY_STATUS_DISCHARGING);
}
power_supply_changed(ddata->usb);
return;
out_err:
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN);
dev_err(ddata->dev, "%s failed with %i\n", __func__, error);
}
@ -708,7 +756,7 @@ static int cpcap_usb_init_irq(struct platform_device *pdev,
error = devm_request_threaded_irq(ddata->dev, irq, NULL,
cpcap_charger_irq_thread,
IRQF_SHARED,
IRQF_SHARED | IRQF_ONESHOT,
name, ddata);
if (error) {
dev_err(ddata->dev, "could not get irq %s: %i\n",
@ -799,6 +847,10 @@ static int cpcap_charger_init_iio(struct cpcap_charger_ddata *ddata)
return error;
}
static char *cpcap_charger_supplied_to[] = {
"battery",
};
static const struct power_supply_desc cpcap_charger_usb_desc = {
.name = "usb",
.type = POWER_SUPPLY_TYPE_USB,
@ -837,6 +889,7 @@ static int cpcap_charger_probe(struct platform_device *pdev)
ddata->dev = &pdev->dev;
ddata->voltage = 4200000;
ddata->limit_current = 532000;
ddata->reg = dev_get_regmap(ddata->dev->parent, NULL);
if (!ddata->reg)
@ -855,6 +908,8 @@ static int cpcap_charger_probe(struct platform_device *pdev)
psy_cfg.of_node = pdev->dev.of_node;
psy_cfg.drv_data = ddata;
psy_cfg.supplied_to = cpcap_charger_supplied_to;
psy_cfg.num_supplicants = ARRAY_SIZE(cpcap_charger_supplied_to),
ddata->usb = devm_power_supply_register(ddata->dev,
&cpcap_charger_usb_desc,
@ -885,7 +940,7 @@ static int cpcap_charger_probe(struct platform_device *pdev)
return 0;
}
static int cpcap_charger_remove(struct platform_device *pdev)
static void cpcap_charger_shutdown(struct platform_device *pdev)
{
struct cpcap_charger_ddata *ddata = platform_get_drvdata(pdev);
int error;
@ -896,12 +951,20 @@ static int cpcap_charger_remove(struct platform_device *pdev)
dev_warn(ddata->dev, "could not clear USB comparator: %i\n",
error);
error = cpcap_charger_set_state(ddata, 0, 0, 0);
if (error)
error = cpcap_charger_disable(ddata);
if (error) {
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_UNKNOWN);
dev_warn(ddata->dev, "could not clear charger: %i\n",
error);
}
cpcap_charger_update_state(ddata, POWER_SUPPLY_STATUS_DISCHARGING);
cancel_delayed_work_sync(&ddata->vbus_work);
cancel_delayed_work_sync(&ddata->detect_work);
}
static int cpcap_charger_remove(struct platform_device *pdev)
{
cpcap_charger_shutdown(pdev);
return 0;
}
@ -912,6 +975,7 @@ static struct platform_driver cpcap_charger_driver = {
.name = "cpcap-charger",
.of_match_table = of_match_ptr(cpcap_charger_id_table),
},
.shutdown = cpcap_charger_shutdown,
.remove = cpcap_charger_remove,
};
module_platform_driver(cpcap_charger_driver);

View File

@ -198,7 +198,7 @@ static ssize_t w1_slave_read(struct file *filp, struct kobject *kobj,
struct bin_attribute *bin_attr, char *buf,
loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
return w1_ds2760_read(dev, buf, off, count);
}

View File

@ -624,7 +624,7 @@ static ssize_t ds2780_read_param_eeprom_bin(struct file *filp,
struct bin_attribute *bin_attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct power_supply *psy = to_power_supply(dev);
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
@ -637,7 +637,7 @@ static ssize_t ds2780_write_param_eeprom_bin(struct file *filp,
struct bin_attribute *bin_attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct power_supply *psy = to_power_supply(dev);
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
int ret;
@ -669,7 +669,7 @@ static ssize_t ds2780_read_user_eeprom_bin(struct file *filp,
struct bin_attribute *bin_attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct power_supply *psy = to_power_supply(dev);
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
@ -682,7 +682,7 @@ static ssize_t ds2780_write_user_eeprom_bin(struct file *filp,
struct bin_attribute *bin_attr,
char *buf, loff_t off, size_t count)
{
struct device *dev = container_of(kobj, struct device, kobj);
struct device *dev = kobj_to_dev(kobj);
struct power_supply *psy = to_power_supply(dev);
struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
int ret;

View File

@ -52,7 +52,7 @@ static int ingenic_battery_get_property(struct power_supply *psy,
return 0;
default:
return -EINVAL;
};
}
}
/* Set the most appropriate IIO channel voltage reference scale

View File

@ -0,0 +1,931 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* Driver for Analog Devices (Linear Technology) LTC4162-L charger IC.
* Copyright (C) 2020, Topic Embedded Products
*/
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/of_device.h>
#include <linux/pm_runtime.h>
#include <linux/power_supply.h>
#include <linux/i2c.h>
#include <linux/regmap.h>
/* Registers (names based on what datasheet uses) */
#define LTC4162L_EN_LIMIT_ALERTS_REG 0x0D
#define LTC4162L_EN_CHARGER_STATE_ALERTS_REG 0x0E
#define LTC4162L_EN_CHARGE_STATUS_ALERTS_REG 0x0F
#define LTC4162L_CONFIG_BITS_REG 0x14
#define LTC4162L_IIN_LIMIT_TARGET 0x15
#define LTC4162L_ARM_SHIP_MODE 0x19
#define LTC4162L_CHARGE_CURRENT_SETTING 0X1A
#define LTC4162L_VCHARGE_SETTING 0X1B
#define LTC4162L_C_OVER_X_THRESHOLD 0x1C
#define LTC4162L_MAX_CV_TIME 0X1D
#define LTC4162L_MAX_CHARGE_TIME 0X1E
#define LTC4162L_CHARGER_CONFIG_BITS 0x29
#define LTC4162L_CHARGER_STATE 0x34
#define LTC4162L_CHARGE_STATUS 0x35
#define LTC4162L_LIMIT_ALERTS_REG 0x36
#define LTC4162L_CHARGER_STATE_ALERTS_REG 0x37
#define LTC4162L_CHARGE_STATUS_ALERTS_REG 0x38
#define LTC4162L_SYSTEM_STATUS_REG 0x39
#define LTC4162L_VBAT 0x3A
#define LTC4162L_VIN 0x3B
#define LTC4162L_VOUT 0x3C
#define LTC4162L_IBAT 0x3D
#define LTC4162L_IIN 0x3E
#define LTC4162L_DIE_TEMPERATURE 0x3F
#define LTC4162L_THERMISTOR_VOLTAGE 0x40
#define LTC4162L_BSR 0x41
#define LTC4162L_JEITA_REGION 0x42
#define LTC4162L_CHEM_CELLS_REG 0x43
#define LTC4162L_ICHARGE_DAC 0x44
#define LTC4162L_VCHARGE_DAC 0x45
#define LTC4162L_IIN_LIMIT_DAC 0x46
#define LTC4162L_VBAT_FILT 0x47
#define LTC4162L_INPUT_UNDERVOLTAGE_DAC 0x4B
/* Enumeration as in datasheet. Individual bits are mutually exclusive. */
enum ltc4162l_state {
battery_detection = 2048,
charger_suspended = 256,
precharge = 128, /* trickle on low bat voltage */
cc_cv_charge = 64, /* normal charge */
ntc_pause = 32,
timer_term = 16,
c_over_x_term = 8, /* battery is full */
max_charge_time_fault = 4,
bat_missing_fault = 2,
bat_short_fault = 1
};
/* Individual bits are mutually exclusive. Only active in charging states.*/
enum ltc4162l_charge_status {
ilim_reg_active = 32,
thermal_reg_active = 16,
vin_uvcl_active = 8,
iin_limit_active = 4,
constant_current = 2,
constant_voltage = 1,
charger_off = 0
};
/* Magic number to write to ARM_SHIP_MODE register */
#define LTC4162L_ARM_SHIP_MODE_MAGIC 21325
struct ltc4162l_info {
struct i2c_client *client;
struct regmap *regmap;
struct power_supply *charger;
u32 rsnsb; /* Series resistor that sets charge current, microOhm */
u32 rsnsi; /* Series resistor to measure input current, microOhm */
u8 cell_count; /* Number of connected cells, 0 while unknown */
};
static u8 ltc4162l_get_cell_count(struct ltc4162l_info *info)
{
int ret;
unsigned int val;
/* Once read successfully */
if (info->cell_count)
return info->cell_count;
ret = regmap_read(info->regmap, LTC4162L_CHEM_CELLS_REG, &val);
if (ret)
return 0;
/* Lower 4 bits is the cell count, or 0 if the chip doesn't know yet */
val &= 0x0f;
if (!val)
return 0;
/* Once determined, keep the value */
info->cell_count = val;
return val;
};
/* Convert enum value to POWER_SUPPLY_STATUS value */
static int ltc4162l_state_decode(enum ltc4162l_state value)
{
switch (value) {
case precharge:
case cc_cv_charge:
return POWER_SUPPLY_STATUS_CHARGING;
case c_over_x_term:
return POWER_SUPPLY_STATUS_FULL;
case bat_missing_fault:
case bat_short_fault:
return POWER_SUPPLY_STATUS_UNKNOWN;
default:
return POWER_SUPPLY_STATUS_NOT_CHARGING;
}
};
static int ltc4162l_get_status(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, &regval);
if (ret) {
dev_err(&info->client->dev, "Failed to read CHARGER_STATE\n");
return ret;
}
val->intval = ltc4162l_state_decode(regval);
return 0;
}
static int ltc4162l_charge_status_decode(enum ltc4162l_charge_status value)
{
if (!value)
return POWER_SUPPLY_CHARGE_TYPE_NONE;
/* constant voltage/current and input_current limit are "fast" modes */
if (value <= iin_limit_active)
return POWER_SUPPLY_CHARGE_TYPE_FAST;
/* Anything that's not fast we'll return as trickle */
return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
}
static int ltc4162l_get_charge_type(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, &regval);
if (ret)
return ret;
val->intval = ltc4162l_charge_status_decode(regval);
return 0;
}
static int ltc4162l_state_to_health(enum ltc4162l_state value)
{
switch (value) {
case ntc_pause:
return POWER_SUPPLY_HEALTH_OVERHEAT;
case timer_term:
return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
case max_charge_time_fault:
return POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE;
case bat_missing_fault:
return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
case bat_short_fault:
return POWER_SUPPLY_HEALTH_DEAD;
default:
return POWER_SUPPLY_HEALTH_GOOD;
}
}
static int ltc4162l_get_health(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CHARGER_STATE, &regval);
if (ret)
return ret;
val->intval = ltc4162l_state_to_health(regval);
return 0;
}
static int ltc4162l_get_online(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_SYSTEM_STATUS_REG, &regval);
if (ret)
return ret;
/* BIT(2) indicates if input voltage is sufficient to charge */
val->intval = !!(regval & BIT(2));
return 0;
}
static int ltc4162l_get_vbat(struct ltc4162l_info *info,
unsigned int reg,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, reg, &regval);
if (ret)
return ret;
/* cell_count × 192.4μV/LSB */
regval *= 1924;
regval *= ltc4162l_get_cell_count(info);
regval /= 10;
val->intval = regval;
return 0;
}
static int ltc4162l_get_ibat(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_IBAT, &regval);
if (ret)
return ret;
/* Signed 16-bit number, 1.466μV / RSNSB amperes/LSB. */
ret = (s16)(regval & 0xFFFF);
val->intval = 100 * mult_frac(ret, 14660, (int)info->rsnsb);
return 0;
}
static int ltc4162l_get_input_voltage(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_VIN, &regval);
if (ret)
return ret;
/* 1.649mV/LSB */
val->intval = regval * 1694;
return 0;
}
static int ltc4162l_get_input_current(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_IIN, &regval);
if (ret)
return ret;
/* Signed 16-bit number, 1.466μV / RSNSI amperes/LSB. */
ret = (s16)(regval & 0xFFFF);
ret *= 14660;
ret /= info->rsnsi;
ret *= 100;
val->intval = ret;
return 0;
}
static int ltc4162l_get_icharge(struct ltc4162l_info *info,
unsigned int reg,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, reg, &regval);
if (ret)
return ret;
regval &= BIT(6) - 1; /* Only the lower 5 bits */
/* The charge current servo level: (icharge_dac + 1) × 1mV/RSNSB */
++regval;
val->intval = 10000u * mult_frac(regval, 100000u, info->rsnsb);
return 0;
}
static int ltc4162l_set_icharge(struct ltc4162l_info *info,
unsigned int reg,
unsigned int value)
{
value = mult_frac(value, info->rsnsb, 100000u);
value /= 10000u;
/* Round to lowest possible */
if (value)
--value;
if (value > 31)
return -EINVAL;
return regmap_write(info->regmap, reg, value);
}
static int ltc4162l_get_vcharge(struct ltc4162l_info *info,
unsigned int reg,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
u32 voltage;
ret = regmap_read(info->regmap, reg, &regval);
if (ret)
return ret;
regval &= BIT(6) - 1; /* Only the lower 5 bits */
/*
* charge voltage setting can be computed from
* cell_count × (vcharge_setting × 12.5mV + 3.8125V)
* where vcharge_setting ranges from 0 to 31 (4.2V max).
*/
voltage = 3812500 + (regval * 12500);
voltage *= ltc4162l_get_cell_count(info);
val->intval = voltage;
return 0;
}
static int ltc4162l_set_vcharge(struct ltc4162l_info *info,
unsigned int reg,
unsigned int value)
{
u8 cell_count = ltc4162l_get_cell_count(info);
if (!cell_count)
return -EBUSY; /* Not available yet, try again later */
value /= cell_count;
if (value < 3812500)
return -EINVAL;
value -= 3812500;
value /= 12500;
if (value > 31)
return -EINVAL;
return regmap_write(info->regmap, reg, value);
}
static int ltc4162l_get_iin_limit_dac(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_IIN_LIMIT_DAC, &regval);
if (ret)
return ret;
regval &= BIT(6) - 1; /* Only 6 bits */
/* (iin_limit_dac + 1) × 500μV / RSNSI */
++regval;
regval *= 5000000u;
regval /= info->rsnsi;
val->intval = 100u * regval;
return 0;
}
static int ltc4162l_set_iin_limit(struct ltc4162l_info *info,
unsigned int value)
{
unsigned int regval;
regval = mult_frac(value, info->rsnsi, 50000u);
regval /= 10000u;
if (regval)
--regval;
if (regval > 63)
regval = 63;
return regmap_write(info->regmap, LTC4162L_IIN_LIMIT_TARGET, regval);
}
static int ltc4162l_get_die_temp(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_DIE_TEMPERATURE, &regval);
if (ret)
return ret;
/* die_temp × 0.0215°C/LSB - 264.4°C */
ret = (s16)(regval & 0xFFFF);
ret *= 215;
ret /= 100; /* Centidegrees scale */
ret -= 26440;
val->intval = ret;
return 0;
}
static int ltc4162l_get_term_current(struct ltc4162l_info *info,
union power_supply_propval *val)
{
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CHARGER_CONFIG_BITS, &regval);
if (ret)
return ret;
/* Check if C_OVER_X_THRESHOLD is enabled */
if (!(regval & BIT(2))) {
val->intval = 0;
return 0;
}
ret = regmap_read(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, &regval);
if (ret)
return ret;
/* 1.466μV / RSNSB amperes/LSB */
regval *= 14660u;
regval /= info->rsnsb;
val->intval = 100 * regval;
return 0;
}
static int ltc4162l_set_term_current(struct ltc4162l_info *info,
unsigned int value)
{
int ret;
unsigned int regval;
if (!value) {
/* Disable en_c_over_x_term when set to zero */
return regmap_update_bits(info->regmap,
LTC4162L_CHARGER_CONFIG_BITS,
BIT(2), 0);
}
regval = mult_frac(value, info->rsnsb, 14660u);
regval /= 100u;
ret = regmap_write(info->regmap, LTC4162L_C_OVER_X_THRESHOLD, regval);
if (ret)
return ret;
/* Set en_c_over_x_term after changing the threshold value */
return regmap_update_bits(info->regmap, LTC4162L_CHARGER_CONFIG_BITS,
BIT(2), BIT(2));
}
/* Custom properties */
static const char * const ltc4162l_charge_status_name[] = {
"ilim_reg_active", /* 32 */
"thermal_reg_active",
"vin_uvcl_active",
"iin_limit_active",
"constant_current",
"constant_voltage",
"charger_off" /* 0 */
};
static ssize_t charge_status_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
const char *result = ltc4162l_charge_status_name[
ARRAY_SIZE(ltc4162l_charge_status_name) - 1];
unsigned int regval;
unsigned int mask;
unsigned int index;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CHARGE_STATUS, &regval);
if (ret)
return ret;
/* Only one bit is set according to datasheet, let's be safe here */
for (mask = 32, index = 0; mask != 0; mask >>= 1, ++index) {
if (regval & mask) {
result = ltc4162l_charge_status_name[index];
break;
}
}
return sprintf(buf, "%s\n", result);
}
static DEVICE_ATTR_RO(charge_status);
static ssize_t vbat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
union power_supply_propval val;
int ret;
ret = ltc4162l_get_vbat(info, LTC4162L_VBAT, &val);
if (ret)
return ret;
return sprintf(buf, "%d\n", val.intval);
}
static DEVICE_ATTR_RO(vbat);
static ssize_t vbat_avg_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
union power_supply_propval val;
int ret;
ret = ltc4162l_get_vbat(info, LTC4162L_VBAT_FILT, &val);
if (ret)
return ret;
return sprintf(buf, "%d\n", val.intval);
}
static DEVICE_ATTR_RO(vbat_avg);
static ssize_t ibat_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
union power_supply_propval val;
int ret;
ret = ltc4162l_get_ibat(info, &val);
if (ret)
return ret;
return sprintf(buf, "%d\n", val.intval);
}
static DEVICE_ATTR_RO(ibat);
static ssize_t force_telemetry_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_CONFIG_BITS_REG, &regval);
if (ret)
return ret;
return sprintf(buf, "%u\n", regval & BIT(2) ? 1 : 0);
}
static ssize_t force_telemetry_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
int ret;
unsigned int value;
ret = kstrtouint(buf, 0, &value);
if (ret < 0)
return ret;
ret = regmap_update_bits(info->regmap, LTC4162L_CONFIG_BITS_REG,
BIT(2), value ? BIT(2) : 0);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(force_telemetry);
static ssize_t arm_ship_mode_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
unsigned int regval;
int ret;
ret = regmap_read(info->regmap, LTC4162L_ARM_SHIP_MODE, &regval);
if (ret)
return ret;
return sprintf(buf, "%u\n",
regval == LTC4162L_ARM_SHIP_MODE_MAGIC ? 1 : 0);
}
static ssize_t arm_ship_mode_store(struct device *dev,
struct device_attribute *attr,
const char *buf,
size_t count)
{
struct power_supply *psy = to_power_supply(dev);
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
int ret;
unsigned int value;
ret = kstrtouint(buf, 0, &value);
if (ret < 0)
return ret;
ret = regmap_write(info->regmap, LTC4162L_ARM_SHIP_MODE,
value ? LTC4162L_ARM_SHIP_MODE_MAGIC : 0);
if (ret < 0)
return ret;
return count;
}
static DEVICE_ATTR_RW(arm_ship_mode);
static struct attribute *ltc4162l_sysfs_entries[] = {
&dev_attr_charge_status.attr,
&dev_attr_ibat.attr,
&dev_attr_vbat.attr,
&dev_attr_vbat_avg.attr,
&dev_attr_force_telemetry.attr,
&dev_attr_arm_ship_mode.attr,
NULL,
};
static const struct attribute_group ltc4162l_attr_group = {
.name = NULL, /* put in device directory */
.attrs = ltc4162l_sysfs_entries,
};
static const struct attribute_group *ltc4162l_attr_groups[] = {
&ltc4162l_attr_group,
NULL,
};
static int ltc4162l_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
return ltc4162l_get_status(info, val);
case POWER_SUPPLY_PROP_CHARGE_TYPE:
return ltc4162l_get_charge_type(info, val);
case POWER_SUPPLY_PROP_HEALTH:
return ltc4162l_get_health(info, val);
case POWER_SUPPLY_PROP_ONLINE:
return ltc4162l_get_online(info, val);
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
return ltc4162l_get_input_voltage(info, val);
case POWER_SUPPLY_PROP_CURRENT_NOW:
return ltc4162l_get_input_current(info, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
return ltc4162l_get_icharge(info,
LTC4162L_ICHARGE_DAC, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return ltc4162l_get_icharge(info,
LTC4162L_CHARGE_CURRENT_SETTING, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
return ltc4162l_get_vcharge(info,
LTC4162L_VCHARGE_DAC, val);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
return ltc4162l_get_vcharge(info,
LTC4162L_VCHARGE_SETTING, val);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return ltc4162l_get_iin_limit_dac(info, val);
case POWER_SUPPLY_PROP_TEMP:
return ltc4162l_get_die_temp(info, val);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return ltc4162l_get_term_current(info, val);
default:
return -EINVAL;
}
}
static int ltc4162l_set_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct ltc4162l_info *info = power_supply_get_drvdata(psy);
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
return ltc4162l_set_icharge(info,
LTC4162L_CHARGE_CURRENT_SETTING, val->intval);
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
return ltc4162l_set_vcharge(info,
LTC4162L_VCHARGE_SETTING, val->intval);
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return ltc4162l_set_iin_limit(info, val->intval);
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return ltc4162l_set_term_current(info, val->intval);
default:
return -EINVAL;
}
}
static int ltc4162l_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
return 1;
default:
return 0;
}
}
/* Charger power supply property routines */
static enum power_supply_property ltc4162l_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
};
static const struct power_supply_desc ltc4162l_desc = {
.name = "ltc4162-l",
.type = POWER_SUPPLY_TYPE_MAINS,
.properties = ltc4162l_properties,
.num_properties = ARRAY_SIZE(ltc4162l_properties),
.get_property = ltc4162l_get_property,
.set_property = ltc4162l_set_property,
.property_is_writeable = ltc4162l_property_is_writeable,
};
static bool ltc4162l_is_writeable_reg(struct device *dev, unsigned int reg)
{
/* all registers up to this one are writeable */
if (reg <= LTC4162L_CHARGER_CONFIG_BITS)
return true;
/* The ALERTS registers can be written to clear alerts */
if (reg >= LTC4162L_LIMIT_ALERTS_REG &&
reg <= LTC4162L_CHARGE_STATUS_ALERTS_REG)
return true;
return false;
}
static bool ltc4162l_is_volatile_reg(struct device *dev, unsigned int reg)
{
/* all registers after this one are read-only status registers */
return reg > LTC4162L_CHARGER_CONFIG_BITS;
}
static const struct regmap_config ltc4162l_regmap_config = {
.reg_bits = 8,
.val_bits = 16,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
.writeable_reg = ltc4162l_is_writeable_reg,
.volatile_reg = ltc4162l_is_volatile_reg,
.max_register = LTC4162L_INPUT_UNDERVOLTAGE_DAC,
.cache_type = REGCACHE_RBTREE,
};
static void ltc4162l_clear_interrupts(struct ltc4162l_info *info)
{
/* Acknowledge interrupt to chip by clearing all events */
regmap_write(info->regmap, LTC4162L_LIMIT_ALERTS_REG, 0);
regmap_write(info->regmap, LTC4162L_CHARGER_STATE_ALERTS_REG, 0);
regmap_write(info->regmap, LTC4162L_CHARGE_STATUS_ALERTS_REG, 0);
}
static int ltc4162l_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
struct i2c_adapter *adapter = client->adapter;
struct device *dev = &client->dev;
struct ltc4162l_info *info;
struct power_supply_config ltc4162l_config = {};
u32 value;
int ret;
if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) {
dev_err(dev, "No support for SMBUS_WORD_DATA\n");
return -ENODEV;
}
info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
if (!info)
return -ENOMEM;
info->client = client;
i2c_set_clientdata(client, info);
info->regmap = devm_regmap_init_i2c(client, &ltc4162l_regmap_config);
if (IS_ERR(info->regmap)) {
dev_err(dev, "Failed to initialize register map\n");
return PTR_ERR(info->regmap);
}
ret = device_property_read_u32(dev, "lltc,rsnsb-micro-ohms",
&info->rsnsb);
if (ret) {
dev_err(dev, "Missing lltc,rsnsb-micro-ohms property\n");
return ret;
}
if (!info->rsnsb)
return -EINVAL;
ret = device_property_read_u32(dev, "lltc,rsnsi-micro-ohms",
&info->rsnsi);
if (ret) {
dev_err(dev, "Missing lltc,rsnsi-micro-ohms property\n");
return ret;
}
if (!info->rsnsi)
return -EINVAL;
if (!device_property_read_u32(dev, "lltc,cell-count", &value))
info->cell_count = value;
ltc4162l_config.of_node = dev->of_node;
ltc4162l_config.drv_data = info;
ltc4162l_config.attr_grp = ltc4162l_attr_groups;
info->charger = devm_power_supply_register(dev, &ltc4162l_desc,
&ltc4162l_config);
if (IS_ERR(info->charger)) {
dev_err(dev, "Failed to register charger\n");
return PTR_ERR(info->charger);
}
/* Disable the threshold alerts, we're not using them */
regmap_write(info->regmap, LTC4162L_EN_LIMIT_ALERTS_REG, 0);
/* Enable interrupts on all status changes */
regmap_write(info->regmap, LTC4162L_EN_CHARGER_STATE_ALERTS_REG,
0x1fff);
regmap_write(info->regmap, LTC4162L_EN_CHARGE_STATUS_ALERTS_REG, 0x1f);
ltc4162l_clear_interrupts(info);
return 0;
}
static void ltc4162l_alert(struct i2c_client *client,
enum i2c_alert_protocol type, unsigned int flag)
{
struct ltc4162l_info *info = i2c_get_clientdata(client);
if (type != I2C_PROTOCOL_SMBUS_ALERT)
return;
ltc4162l_clear_interrupts(info);
power_supply_changed(info->charger);
}
static const struct i2c_device_id ltc4162l_i2c_id_table[] = {
{ "ltc4162-l", 0 },
{ },
};
MODULE_DEVICE_TABLE(i2c, ltc4162l_i2c_id_table);
static const struct of_device_id ltc4162l_of_match[] = {
{ .compatible = "lltc,ltc4162-l", },
{ },
};
MODULE_DEVICE_TABLE(of, ltc4162l_of_match);
static struct i2c_driver ltc4162l_driver = {
.probe = ltc4162l_probe,
.alert = ltc4162l_alert,
.id_table = ltc4162l_i2c_id_table,
.driver = {
.name = "ltc4162-l-charger",
.of_match_table = of_match_ptr(ltc4162l_of_match),
},
};
module_i2c_driver(ltc4162l_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Mike Looijmans <mike.looijmans@topic.nl>");
MODULE_DESCRIPTION("LTC4162-L charger driver");

View File

@ -15,8 +15,6 @@
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
#include <linux/workqueue.h>
#include <linux/power_supply.h>

View File

@ -6,22 +6,32 @@
* MyungJoo Ham <myungjoo.ham@samsung.com>
*/
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/of_gpio.h>
#include <linux/slab.h>
#include <linux/power_supply.h>
#include <linux/platform_device.h>
#include <linux/power/max8903_charger.h>
struct max8903_data {
struct max8903_pdata *pdata;
struct device *dev;
struct power_supply *psy;
struct power_supply_desc psy_desc;
/*
* GPIOs
* chg, flt, dcm and usus are optional.
* dok or uok must be present.
* If dok is present, cen must be present.
*/
struct gpio_desc *cen; /* Charger Enable input */
struct gpio_desc *dok; /* DC (Adapter) Power OK output */
struct gpio_desc *uok; /* USB Power OK output */
struct gpio_desc *chg; /* Charger status output */
struct gpio_desc *flt; /* Fault output */
struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */
struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */
bool fault;
bool usb_in;
bool ta_in;
@ -42,8 +52,9 @@ static int max8903_get_property(struct power_supply *psy,
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
if (gpio_is_valid(data->pdata->chg)) {
if (gpio_get_value(data->pdata->chg) == 0)
if (data->chg) {
if (gpiod_get_value(data->chg))
/* CHG asserted */
val->intval = POWER_SUPPLY_STATUS_CHARGING;
else if (data->usb_in || data->ta_in)
val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
@ -71,11 +82,17 @@ static int max8903_get_property(struct power_supply *psy,
static irqreturn_t max8903_dcin(int irq, void *_data)
{
struct max8903_data *data = _data;
struct max8903_pdata *pdata = data->pdata;
bool ta_in;
enum power_supply_type old_type;
ta_in = gpio_get_value(pdata->dok) ? false : true;
/*
* This means the line is asserted.
*
* The signal is active low, but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree.
*/
ta_in = gpiod_get_value(data->dok);
if (ta_in == data->ta_in)
return IRQ_HANDLED;
@ -83,13 +100,25 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
data->ta_in = ta_in;
/* Set Current-Limit-Mode 1:DC 0:USB */
if (gpio_is_valid(pdata->dcm))
gpio_set_value(pdata->dcm, ta_in ? 1 : 0);
if (data->dcm)
gpiod_set_value(data->dcm, ta_in);
/* Charger Enable / Disable (cen is negated) */
if (gpio_is_valid(pdata->cen))
gpio_set_value(pdata->cen, ta_in ? 0 :
(data->usb_in ? 0 : 1));
/* Charger Enable / Disable */
if (data->cen) {
int val;
if (ta_in)
/* Certainly enable if DOK is asserted */
val = 1;
else if (data->usb_in)
/* Enable if the USB charger is enabled */
val = 1;
else
/* Else default-disable */
val = 0;
gpiod_set_value(data->cen, val);
}
dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
"Connected" : "Disconnected");
@ -112,11 +141,17 @@ static irqreturn_t max8903_dcin(int irq, void *_data)
static irqreturn_t max8903_usbin(int irq, void *_data)
{
struct max8903_data *data = _data;
struct max8903_pdata *pdata = data->pdata;
bool usb_in;
enum power_supply_type old_type;
usb_in = gpio_get_value(pdata->uok) ? false : true;
/*
* This means the line is asserted.
*
* The signal is active low, but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree.
*/
usb_in = gpiod_get_value(data->uok);
if (usb_in == data->usb_in)
return IRQ_HANDLED;
@ -125,10 +160,22 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
/* Do not touch Current-Limit-Mode */
/* Charger Enable / Disable (cen is negated) */
if (gpio_is_valid(pdata->cen))
gpio_set_value(pdata->cen, usb_in ? 0 :
(data->ta_in ? 0 : 1));
/* Charger Enable / Disable */
if (data->cen) {
int val;
if (usb_in)
/* Certainly enable if UOK is asserted */
val = 1;
else if (data->ta_in)
/* Enable if the DC charger is enabled */
val = 1;
else
/* Else default-disable */
val = 0;
gpiod_set_value(data->cen, val);
}
dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?
"Connected" : "Disconnected");
@ -151,10 +198,16 @@ static irqreturn_t max8903_usbin(int irq, void *_data)
static irqreturn_t max8903_fault(int irq, void *_data)
{
struct max8903_data *data = _data;
struct max8903_pdata *pdata = data->pdata;
bool fault;
fault = gpio_get_value(pdata->flt) ? false : true;
/*
* This means the line is asserted.
*
* The signal is active low, but the inversion is handled in the GPIO
* library as the line should be flagged GPIO_ACTIVE_LOW in the device
* tree.
*/
fault = gpiod_get_value(data->flt);
if (fault == data->fault)
return IRQ_HANDLED;
@ -169,159 +222,100 @@ static irqreturn_t max8903_fault(int irq, void *_data)
return IRQ_HANDLED;
}
static struct max8903_pdata *max8903_parse_dt_data(struct device *dev)
{
struct device_node *np = dev->of_node;
struct max8903_pdata *pdata = NULL;
if (!np)
return NULL;
pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return NULL;
pdata->dc_valid = false;
pdata->usb_valid = false;
pdata->cen = of_get_named_gpio(np, "cen-gpios", 0);
if (!gpio_is_valid(pdata->cen))
pdata->cen = -EINVAL;
pdata->chg = of_get_named_gpio(np, "chg-gpios", 0);
if (!gpio_is_valid(pdata->chg))
pdata->chg = -EINVAL;
pdata->flt = of_get_named_gpio(np, "flt-gpios", 0);
if (!gpio_is_valid(pdata->flt))
pdata->flt = -EINVAL;
pdata->usus = of_get_named_gpio(np, "usus-gpios", 0);
if (!gpio_is_valid(pdata->usus))
pdata->usus = -EINVAL;
pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0);
if (!gpio_is_valid(pdata->dcm))
pdata->dcm = -EINVAL;
pdata->dok = of_get_named_gpio(np, "dok-gpios", 0);
if (!gpio_is_valid(pdata->dok))
pdata->dok = -EINVAL;
else
pdata->dc_valid = true;
pdata->uok = of_get_named_gpio(np, "uok-gpios", 0);
if (!gpio_is_valid(pdata->uok))
pdata->uok = -EINVAL;
else
pdata->usb_valid = true;
return pdata;
}
static int max8903_setup_gpios(struct platform_device *pdev)
{
struct max8903_data *data = platform_get_drvdata(pdev);
struct device *dev = &pdev->dev;
struct max8903_pdata *pdata = pdev->dev.platform_data;
int ret = 0;
int gpio;
int ta_in = 0;
int usb_in = 0;
bool ta_in = false;
bool usb_in = false;
enum gpiod_flags flags;
if (pdata->dc_valid) {
if (gpio_is_valid(pdata->dok)) {
ret = devm_gpio_request(dev, pdata->dok,
data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for dok: %d err %d\n",
pdata->dok, ret);
return ret;
}
gpio = pdata->dok; /* PULL_UPed Interrupt */
ta_in = gpio_get_value(gpio) ? 0 : 1;
} else {
dev_err(dev, "When DC is wired, DOK should be wired as well.\n");
return -EINVAL;
}
data->dok = devm_gpiod_get_optional(dev, "dok", GPIOD_IN);
if (IS_ERR(data->dok))
return dev_err_probe(dev, PTR_ERR(data->dok),
"failed to get DOK GPIO");
if (data->dok) {
gpiod_set_consumer_name(data->dok, data->psy_desc.name);
/*
* The DC OK is pulled up to 1 and goes low when a charger
* is plugged in (active low) but in the device tree the
* line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted)
* here if the DC charger is plugged in.
*/
ta_in = gpiod_get_value(data->dok);
}
if (gpio_is_valid(pdata->dcm)) {
ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for dcm: %d err %d\n",
pdata->dcm, ret);
return ret;
}
gpio = pdata->dcm; /* Output */
gpio_set_value(gpio, ta_in);
data->uok = devm_gpiod_get_optional(dev, "uok", GPIOD_IN);
if (IS_ERR(data->uok))
return dev_err_probe(dev, PTR_ERR(data->uok),
"failed to get UOK GPIO");
if (data->uok) {
gpiod_set_consumer_name(data->uok, data->psy_desc.name);
/*
* The USB OK is pulled up to 1 and goes low when a USB charger
* is plugged in (active low) but in the device tree the
* line is marked as GPIO_ACTIVE_LOW so we get a 1 (asserted)
* here if the USB charger is plugged in.
*/
usb_in = gpiod_get_value(data->uok);
}
if (pdata->usb_valid) {
if (gpio_is_valid(pdata->uok)) {
ret = devm_gpio_request(dev, pdata->uok,
data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for uok: %d err %d\n",
pdata->uok, ret);
return ret;
}
gpio = pdata->uok;
usb_in = gpio_get_value(gpio) ? 0 : 1;
} else {
dev_err(dev, "When USB is wired, UOK should be wired."
"as well.\n");
return -EINVAL;
}
/* Either DC OK or USB OK must be provided */
if (!data->dok && !data->uok) {
dev_err(dev, "no valid power source\n");
return -EINVAL;
}
if (gpio_is_valid(pdata->cen)) {
ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for cen: %d err %d\n",
pdata->cen, ret);
return ret;
}
/*
* If either charger is already connected at this point,
* assert the CEN line and enable charging from the start.
*
* The line is active low but also marked with GPIO_ACTIVE_LOW
* in the device tree, so when we assert the line with
* GPIOD_OUT_HIGH the line will be driven low.
*/
flags = (ta_in || usb_in) ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
/*
* If DC OK is provided, Charger Enable CEN is compulsory
* so this is not optional here.
*/
data->cen = devm_gpiod_get(dev, "cen", flags);
if (IS_ERR(data->cen))
return dev_err_probe(dev, PTR_ERR(data->cen),
"failed to get CEN GPIO");
gpiod_set_consumer_name(data->cen, data->psy_desc.name);
gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
}
/*
* If the DC charger is connected, then select it.
*
* The DCM line should be marked GPIO_ACTIVE_HIGH in the
* device tree. Driving it high will enable the DC charger
* input over the USB charger input.
*/
flags = ta_in ? GPIOD_OUT_HIGH : GPIOD_OUT_LOW;
data->dcm = devm_gpiod_get_optional(dev, "dcm", flags);
if (IS_ERR(data->dcm))
return dev_err_probe(dev, PTR_ERR(data->dcm),
"failed to get DCM GPIO");
gpiod_set_consumer_name(data->dcm, data->psy_desc.name);
if (gpio_is_valid(pdata->chg)) {
ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for chg: %d err %d\n",
pdata->chg, ret);
return ret;
}
}
data->chg = devm_gpiod_get_optional(dev, "chg", GPIOD_IN);
if (IS_ERR(data->chg))
return dev_err_probe(dev, PTR_ERR(data->chg),
"failed to get CHG GPIO");
gpiod_set_consumer_name(data->chg, data->psy_desc.name);
if (gpio_is_valid(pdata->flt)) {
ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for flt: %d err %d\n",
pdata->flt, ret);
return ret;
}
}
data->flt = devm_gpiod_get_optional(dev, "flt", GPIOD_IN);
if (IS_ERR(data->flt))
return dev_err_probe(dev, PTR_ERR(data->flt),
"failed to get FLT GPIO");
gpiod_set_consumer_name(data->flt, data->psy_desc.name);
if (gpio_is_valid(pdata->usus)) {
ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name);
if (ret) {
dev_err(dev,
"Failed GPIO request for usus: %d err %d\n",
pdata->usus, ret);
return ret;
}
}
data->usus = devm_gpiod_get_optional(dev, "usus", GPIOD_IN);
if (IS_ERR(data->usus))
return dev_err_probe(dev, PTR_ERR(data->usus),
"failed to get USUS GPIO");
gpiod_set_consumer_name(data->usus, data->psy_desc.name);
data->fault = false;
data->ta_in = ta_in;
@ -334,7 +328,6 @@ static int max8903_probe(struct platform_device *pdev)
{
struct max8903_data *data;
struct device *dev = &pdev->dev;
struct max8903_pdata *pdata = pdev->dev.platform_data;
struct power_supply_config psy_cfg = {};
int ret = 0;
@ -342,24 +335,9 @@ static int max8903_probe(struct platform_device *pdev)
if (!data)
return -ENOMEM;
if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node)
pdata = max8903_parse_dt_data(dev);
if (!pdata) {
dev_err(dev, "No platform data.\n");
return -EINVAL;
}
pdev->dev.platform_data = pdata;
data->pdata = pdata;
data->dev = dev;
platform_set_drvdata(pdev, data);
if (pdata->dc_valid == false && pdata->usb_valid == false) {
dev_err(dev, "No valid power sources.\n");
return -EINVAL;
}
ret = max8903_setup_gpios(pdev);
if (ret)
return ret;
@ -381,41 +359,41 @@ static int max8903_probe(struct platform_device *pdev)
return PTR_ERR(data->psy);
}
if (pdata->dc_valid) {
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok),
if (data->dok) {
ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->dok),
NULL, max8903_dcin,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 DC IN", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for DC (%d)\n",
gpio_to_irq(pdata->dok), ret);
gpiod_to_irq(data->dok), ret);
return ret;
}
}
if (pdata->usb_valid) {
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok),
if (data->uok) {
ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->uok),
NULL, max8903_usbin,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 USB IN", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for USB (%d)\n",
gpio_to_irq(pdata->uok), ret);
gpiod_to_irq(data->uok), ret);
return ret;
}
}
if (gpio_is_valid(pdata->flt)) {
ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt),
if (data->flt) {
ret = devm_request_threaded_irq(dev, gpiod_to_irq(data->flt),
NULL, max8903_fault,
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING | IRQF_ONESHOT,
"MAX8903 Fault", data);
if (ret) {
dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
gpio_to_irq(pdata->flt), ret);
gpiod_to_irq(data->flt), ret);
return ret;
}
}

View File

@ -6,12 +6,14 @@
// MyungJoo Ham <myungjoo.ham@samsung.com>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/platform_device.h>
#include <linux/power_supply.h>
#include <linux/mfd/max8997.h>
#include <linux/mfd/max8997-private.h>
#include <linux/regulator/consumer.h>
/* MAX8997_REG_STATUS4 */
#define DCINOK_SHIFT 1
@ -31,6 +33,10 @@ struct charger_data {
struct device *dev;
struct max8997_dev *iodev;
struct power_supply *battery;
struct regulator *reg;
struct extcon_dev *edev;
struct notifier_block extcon_nb;
struct work_struct extcon_work;
};
static enum power_supply_property max8997_battery_props[] = {
@ -88,6 +94,67 @@ static int max8997_battery_get_property(struct power_supply *psy,
return 0;
}
static void max8997_battery_extcon_evt_stop_work(void *data)
{
struct charger_data *charger = data;
cancel_work_sync(&charger->extcon_work);
}
static void max8997_battery_extcon_evt_worker(struct work_struct *work)
{
struct charger_data *charger =
container_of(work, struct charger_data, extcon_work);
struct extcon_dev *edev = charger->edev;
int current_limit;
if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) {
dev_dbg(charger->dev, "USB SDP charger is connected\n");
current_limit = 450000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) {
dev_dbg(charger->dev, "USB DCP charger is connected\n");
current_limit = 650000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) {
dev_dbg(charger->dev, "USB FAST charger is connected\n");
current_limit = 650000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) {
dev_dbg(charger->dev, "USB SLOW charger is connected\n");
current_limit = 650000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) {
dev_dbg(charger->dev, "USB CDP charger is connected\n");
current_limit = 650000;
} else {
dev_dbg(charger->dev, "USB charger is disconnected\n");
current_limit = -1;
}
if (current_limit > 0) {
int ret = regulator_set_current_limit(charger->reg, current_limit, current_limit);
if (ret) {
dev_err(charger->dev, "failed to set current limit: %d\n", ret);
return;
}
ret = regulator_enable(charger->reg);
if (ret)
dev_err(charger->dev, "failed to enable regulator: %d\n", ret);
} else {
int ret = regulator_disable(charger->reg);
if (ret)
dev_err(charger->dev, "failed to disable regulator: %d\n", ret);
}
}
static int max8997_battery_extcon_evt(struct notifier_block *nb,
unsigned long event, void *param)
{
struct charger_data *charger =
container_of(nb, struct charger_data, extcon_nb);
schedule_work(&charger->extcon_work);
return NOTIFY_OK;
}
static const struct power_supply_desc max8997_battery_desc = {
.name = "max8997_pmic",
.type = POWER_SUPPLY_TYPE_BATTERY,
@ -170,6 +237,35 @@ static int max8997_battery_probe(struct platform_device *pdev)
return PTR_ERR(charger->battery);
}
charger->reg = devm_regulator_get_optional(&pdev->dev, "charger");
if (IS_ERR(charger->reg)) {
if (PTR_ERR(charger->reg) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_info(&pdev->dev, "couldn't get charger regulator\n");
}
charger->edev = extcon_get_edev_by_phandle(&pdev->dev, 0);
if (IS_ERR(charger->edev)) {
if (PTR_ERR(charger->edev) == -EPROBE_DEFER)
return -EPROBE_DEFER;
dev_info(charger->dev, "couldn't get extcon device\n");
}
if (!IS_ERR(charger->reg) && !IS_ERR(charger->edev)) {
INIT_WORK(&charger->extcon_work, max8997_battery_extcon_evt_worker);
ret = devm_add_action(&pdev->dev, max8997_battery_extcon_evt_stop_work, charger);
if (ret) {
dev_err(&pdev->dev, "failed to add extcon evt stop action: %d\n", ret);
return ret;
}
charger->extcon_nb.notifier_call = max8997_battery_extcon_evt;
ret = devm_extcon_register_notifier_all(&pdev->dev, charger->edev,
&charger->extcon_nb);
if (ret) {
dev_err(&pdev->dev, "failed to register extcon notifier\n");
return ret;
};
}
return 0;
}

View File

@ -299,13 +299,11 @@ static const struct hwmon_channel_info *power_supply_hwmon_info[] = {
HWMON_T_INPUT |
HWMON_T_MAX |
HWMON_T_MIN |
HWMON_T_MIN_ALARM |
HWMON_T_MIN_ALARM,
HWMON_T_LABEL |
HWMON_T_INPUT |
HWMON_T_MIN_ALARM |
HWMON_T_LABEL |
HWMON_T_MAX_ALARM),
HWMON_CHANNEL_INFO(curr,

View File

@ -374,7 +374,7 @@ static umode_t power_supply_attr_is_visible(struct kobject *kobj,
return 0;
}
static struct attribute_group power_supply_attr_group = {
static const struct attribute_group power_supply_attr_group = {
.attrs = __power_supply_attrs,
.is_visible = power_supply_attr_is_visible,
};

View File

@ -137,6 +137,7 @@
* @mains_online: is AC/DC input connected
* @usb_online: is USB input connected
* @charging_enabled: is charging enabled
* @irq_unsupported: is interrupt unsupported by SMB hardware
* @max_charge_current: maximum current (in uA) the battery can be charged
* @max_charge_voltage: maximum voltage (in uV) the battery can be charged
* @pre_charge_current: current (in uA) to use in pre-charging phase
@ -193,6 +194,7 @@ struct smb347_charger {
bool mains_online;
bool usb_online;
bool charging_enabled;
bool irq_unsupported;
unsigned int max_charge_current;
unsigned int max_charge_voltage;
@ -862,6 +864,9 @@ static int smb347_irq_set(struct smb347_charger *smb, bool enable)
{
int ret;
if (smb->irq_unsupported)
return 0;
ret = smb347_set_writable(smb, true);
if (ret < 0)
return ret;
@ -923,8 +928,6 @@ static int smb347_irq_init(struct smb347_charger *smb,
ret = regmap_update_bits(smb->regmap, CFG_STAT,
CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED,
CFG_STAT_DISABLED);
if (ret < 0)
client->irq = 0;
smb347_set_writable(smb, false);
@ -1345,6 +1348,7 @@ static int smb347_probe(struct i2c_client *client,
if (ret < 0) {
dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
dev_warn(dev, "disabling IRQ support\n");
smb->irq_unsupported = true;
} else {
smb347_irq_enable(smb);
}
@ -1357,8 +1361,8 @@ static int smb347_remove(struct i2c_client *client)
{
struct smb347_charger *smb = i2c_get_clientdata(client);
if (client->irq)
smb347_irq_disable(smb);
smb347_irq_disable(smb);
return 0;
}

View File

@ -15,11 +15,12 @@
#include <linux/wm97xx.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/irq.h>
#include <linux/slab.h>
static struct work_struct bat_work;
static struct gpio_desc *charge_gpiod;
static DEFINE_MUTEX(work_lock);
static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
static enum power_supply_property *prop;
@ -96,12 +97,11 @@ static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps)
static void wm97xx_bat_update(struct power_supply *bat_ps)
{
int old_status = bat_status;
struct wm97xx_batt_pdata *pdata = power_supply_get_drvdata(bat_ps);
mutex_lock(&work_lock);
bat_status = (pdata->charge_gpio >= 0) ?
(gpio_get_value(pdata->charge_gpio) ?
bat_status = (charge_gpiod) ?
(gpiod_get_value(charge_gpiod) ?
POWER_SUPPLY_STATUS_DISCHARGING :
POWER_SUPPLY_STATUS_CHARGING) :
POWER_SUPPLY_STATUS_UNKNOWN;
@ -171,18 +171,19 @@ static int wm97xx_bat_probe(struct platform_device *dev)
if (dev->id != -1)
return -EINVAL;
if (gpio_is_valid(pdata->charge_gpio)) {
ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
if (ret)
goto err;
ret = gpio_direction_input(pdata->charge_gpio);
if (ret)
goto err2;
ret = request_irq(gpio_to_irq(pdata->charge_gpio),
charge_gpiod = devm_gpiod_get_optional(&dev->dev, NULL, GPIOD_IN);
if (IS_ERR(charge_gpiod))
return dev_err_probe(&dev->dev,
PTR_ERR(charge_gpiod),
"failed to get charge GPIO\n");
if (charge_gpiod) {
gpiod_set_consumer_name(charge_gpiod, "BATT CHRG");
ret = request_irq(gpiod_to_irq(charge_gpiod),
wm97xx_chrg_irq, 0,
"AC Detect", dev);
if (ret)
goto err2;
return dev_err_probe(&dev->dev, ret,
"failed to request GPIO irq\n");
props++; /* POWER_SUPPLY_PROP_STATUS */
}
@ -204,7 +205,7 @@ static int wm97xx_bat_probe(struct platform_device *dev)
}
prop[i++] = POWER_SUPPLY_PROP_PRESENT;
if (pdata->charge_gpio >= 0)
if (charge_gpiod)
prop[i++] = POWER_SUPPLY_PROP_STATUS;
if (pdata->batt_tech >= 0)
prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
@ -242,23 +243,15 @@ static int wm97xx_bat_probe(struct platform_device *dev)
err4:
kfree(prop);
err3:
if (gpio_is_valid(pdata->charge_gpio))
free_irq(gpio_to_irq(pdata->charge_gpio), dev);
err2:
if (gpio_is_valid(pdata->charge_gpio))
gpio_free(pdata->charge_gpio);
err:
if (charge_gpiod)
free_irq(gpiod_to_irq(charge_gpiod), dev);
return ret;
}
static int wm97xx_bat_remove(struct platform_device *dev)
{
struct wm97xx_batt_pdata *pdata = dev->dev.platform_data;
if (pdata && gpio_is_valid(pdata->charge_gpio)) {
free_irq(gpio_to_irq(pdata->charge_gpio), dev);
gpio_free(pdata->charge_gpio);
}
if (charge_gpiod)
free_irq(gpiod_to_irq(charge_gpiod), dev);
cancel_work_sync(&bat_work);
power_supply_unregister(bat_psy);
kfree(prop);

View File

@ -6,7 +6,7 @@
*/
#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/gpio/consumer.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
@ -18,6 +18,7 @@
struct z2_charger {
struct z2_battery_info *info;
struct gpio_desc *charge_gpiod;
int bat_status;
struct i2c_client *client;
struct power_supply *batt_ps;
@ -95,8 +96,8 @@ static void z2_batt_update(struct z2_charger *charger)
mutex_lock(&charger->work_lock);
charger->bat_status = (info->charge_gpio >= 0) ?
(gpio_get_value(info->charge_gpio) ?
charger->bat_status = charger->charge_gpiod ?
(gpiod_get_value(charger->charge_gpiod) ?
POWER_SUPPLY_STATUS_CHARGING :
POWER_SUPPLY_STATUS_DISCHARGING) :
POWER_SUPPLY_STATUS_UNKNOWN;
@ -131,7 +132,7 @@ static int z2_batt_ps_init(struct z2_charger *charger, int props)
enum power_supply_property *prop;
struct z2_battery_info *info = charger->info;
if (info->charge_gpio >= 0)
if (charger->charge_gpiod)
props++; /* POWER_SUPPLY_PROP_STATUS */
if (info->batt_tech >= 0)
props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */
@ -147,7 +148,7 @@ static int z2_batt_ps_init(struct z2_charger *charger, int props)
return -ENOMEM;
prop[i++] = POWER_SUPPLY_PROP_PRESENT;
if (info->charge_gpio >= 0)
if (charger->charge_gpiod)
prop[i++] = POWER_SUPPLY_PROP_STATUS;
if (info->batt_tech >= 0)
prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
@ -206,22 +207,23 @@ static int z2_batt_probe(struct i2c_client *client,
mutex_init(&charger->work_lock);
if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
ret = gpio_request(info->charge_gpio, "BATT CHRG");
if (ret)
goto err;
charger->charge_gpiod = devm_gpiod_get_optional(&client->dev,
NULL, GPIOD_IN);
if (IS_ERR(charger->charge_gpiod))
return dev_err_probe(&client->dev,
PTR_ERR(charger->charge_gpiod),
"failed to get charge GPIO\n");
ret = gpio_direction_input(info->charge_gpio);
if (ret)
goto err2;
if (charger->charge_gpiod) {
gpiod_set_consumer_name(charger->charge_gpiod, "BATT CHRG");
irq_set_irq_type(gpio_to_irq(info->charge_gpio),
irq_set_irq_type(gpiod_to_irq(charger->charge_gpiod),
IRQ_TYPE_EDGE_BOTH);
ret = request_irq(gpio_to_irq(info->charge_gpio),
ret = request_irq(gpiod_to_irq(charger->charge_gpiod),
z2_charge_switch_irq, 0,
"AC Detect", charger);
if (ret)
goto err3;
goto err;
}
ret = z2_batt_ps_init(charger, props);
@ -245,11 +247,8 @@ static int z2_batt_probe(struct i2c_client *client,
err4:
kfree(charger->batt_ps_desc.properties);
err3:
if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
free_irq(gpio_to_irq(info->charge_gpio), charger);
err2:
if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
gpio_free(info->charge_gpio);
if (charger->charge_gpiod)
free_irq(gpiod_to_irq(charger->charge_gpiod), charger);
err:
kfree(charger);
return ret;
@ -258,16 +257,13 @@ static int z2_batt_probe(struct i2c_client *client,
static int z2_batt_remove(struct i2c_client *client)
{
struct z2_charger *charger = i2c_get_clientdata(client);
struct z2_battery_info *info = charger->info;
cancel_work_sync(&charger->bat_work);
power_supply_unregister(charger->batt_ps);
kfree(charger->batt_ps_desc.properties);
if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
free_irq(gpio_to_irq(info->charge_gpio), charger);
gpio_free(info->charge_gpio);
}
if (charger->charge_gpiod)
free_irq(gpiod_to_irq(charger->charge_gpiod), charger);
kfree(charger);

View File

@ -1,43 +0,0 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* max8903_charger.h - Maxim 8903 USB/Adapter Charger Driver
*
* Copyright (C) 2011 Samsung Electronics
* MyungJoo Ham <myungjoo.ham@samsung.com>
*/
#ifndef __MAX8903_CHARGER_H__
#define __MAX8903_CHARGER_H__
struct max8903_pdata {
/*
* GPIOs
* cen, chg, flt, dcm and usus are optional.
* dok and uok are not optional depending on the status of
* dc_valid and usb_valid.
*/
int cen; /* Charger Enable input */
int dok; /* DC(Adapter) Power OK output */
int uok; /* USB Power OK output */
int chg; /* Charger status output */
int flt; /* Fault output */
int dcm; /* Current-Limit Mode input (1: DC, 2: USB) */
int usus; /* USB Suspend Input (1: suspended) */
/*
* DC(Adapter/TA) is wired
* When dc_valid is true,
* dok should be valid.
*
* At least one of dc_valid or usb_valid should be true.
*/
bool dc_valid;
/*
* USB is wired
* When usb_valid is true,
* uok should be valid.
*/
bool usb_valid;
};
#endif /* __MAX8903_CHARGER_H__ */

View File

@ -294,7 +294,6 @@ struct wm97xx {
struct wm97xx_batt_pdata {
int batt_aux;
int temp_aux;
int charge_gpio;
int min_voltage;
int max_voltage;
int batt_div;

View File

@ -6,7 +6,6 @@ struct z2_battery_info {
int batt_I2C_bus;
int batt_I2C_addr;
int batt_I2C_reg;
int charge_gpio;
int min_voltage;
int max_voltage;
int batt_div;