power supply and reset changes for the 6.16 series

* power-supply core
   - power: supply: support charge_types in extensions
  * power-supply drivers
   - new driver for Pegatron Chagall battery
   - new driver for Maxim MAX8971 charger
   - new driver for Huawei Matebook E Go
   - bq27xxx: retry failed I2C transmissions
   - bq24190: add BQ24193 support
   - misc. small cleanups and fixes
  * reset drivers
   - new driver for Toradex SMARC Embedded Controller
   - reboot-mode: add support for modes containing / in DT
   - atmel,at91sam9260-reset: support sama7d65
   - syscon-reboot: add Google GS101 support
   - misc. small cleanups and fixes
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEE72YNB0Y/i3JqeVQT2O7X88g7+poFAmg0RIsACgkQ2O7X88g7
 +ppO3RAAhvaAnwBevIldvayaL/8upc4weEm13JKfZ2rrbmKCzFvaIIbi9uJxcp7U
 utmO2T7JKXvh6HzzW2MMTx/CEDfHZJxcfjEzdt3ajOXKq6KcFQ/f0psk2ZzY/Tej
 /L7dV3ps4RZRDDG+q9v3EsE5/A3KiFyLekzGhQovgwBnSLg8JVoPYc/gXYVq1R8L
 bx8If8SkpJn99gLBhuHEkWVBSFvpXFCOu75wAiSeULBdc/KefP/x5TPEi5CdIHti
 zLAukmr8egVI3CxjHb5lIgfh0fbfpwgCjyZLAoW8IUZGykoorwbKFy+jTdUPJqc4
 Q/hRBegSKF15TcxXDROYto+FQpTX20BGIf+lasqXKePAk57f6+QjMCW6MWr7JmId
 0HVN82Q4DNw0Au0dEWAe3WLY4BgABgx0tbsdhUqchgW5APaQTJ452JiPvSAFwygh
 m2MXKWwUHADfu6j+Mqk7PyNw4pUq9Xk3vQxDyXzrrd7w5AFk14Ttr/L1jNTJwEA3
 cDNa10Wdu6zToIzoGDy5uE4atwQfjVDx1JQ53clIueSM2f92lPjCTCZSBl0XuCAv
 Ic/LvuCeOQp4VbYWuB7ygTzSBA6M0hcUGgUmGUxy/sg6EPQiv93gKk9ET7dI6sF+
 WDX7hS7kGqAathU1TFY3GWnu5RQhSFzhhJ6G3gPuKTEgsEYDg1w=
 =+qUp
 -----END PGP SIGNATURE-----

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

Pull power supply and reset updates from Sebastian Reichel:
 "Power-supply core:
   - support charge_types in extensions

  Power-supply drivers:
   - new driver for Pegatron Chagall battery
   - new driver for Maxim MAX8971 charger
   - new driver for Huawei Matebook E Go
   - bq27xxx: retry failed I2C transmissions
   - bq24190: add BQ24193 support
   - misc small cleanups and fixes

  Reset drivers:
   - new driver for Toradex SMARC Embedded Controller
   - reboot-mode: add support for modes containing / in DT
   - atmel,at91sam9260-reset: support sama7d65
   - syscon-reboot: add Google GS101 support
   - misc small cleanups and fixes"

* tag 'for-v6.16' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (28 commits)
  power: supply: rt9471: Simplify definition of some struct linear_range
  power: supply: max77976: add EXTCON dependency
  power: supply: Add support for Maxim MAX8971 charger
  dt-bindings: power: supply: Document Maxim MAX8971 charger
  power: supply: max17040: adjust thermal channel scaling
  power: reset: syscon-reboot: add gs101-specific reset
  dt-bindings: reset: syscon-reboot: add google,gs101-reboot
  power: supply: add Huawei Matebook E Go psy driver
  power: supply: Add driver for Pegatron Chagall battery
  dt-bindings: power: supply: Document Pegatron Chagall fuel gauge
  dt-bindings: vendor-prefixes: add prefix for Pegatron Corporation
  power: supply: cros_charge-control: Avoid -Wflex-array-member-not-at-end warning
  power: reset: add Toradex Embedded Controller
  dt-bindings: power: reset: add toradex,smarc-ec
  power: supply: support charge_types in extensions
  power: supply: max77705: Fix workqueue error handling in probe
  power: supply: wm831x: Constify struct chg_map and some arrays
  power: bq24190: Add BQ24193 support
  dt-bindings: power: supply: bq24190: Add BQ24193 compatible
  power: supply: sysfs: Remove duplicate NUL termination
  ...
This commit is contained in:
Linus Torvalds 2025-05-27 16:14:56 -07:00
commit c7c1863536
39 changed files with 2418 additions and 125 deletions

View File

@ -822,3 +822,46 @@ Description:
Each entry is a link to the device which registered the extension.
Access: Read
What: /sys/class/power_supply/max8971-charger/fast_charge_timer
Date: May 2025
KernelVersion: 6.15.0
Contact: Svyatoslav Ryhel <clamor95@gmail.com>
Description:
This entry shows and sets the maximum time the max8971
charger operates in fast-charge mode. When the timer expires
the device will terminate fast-charge mode (charging current
will drop to 0 A) and will trigger interrupt.
Valid values:
- 4 - 10 (hours), step by 1
- 0: disabled.
What: /sys/class/power_supply/max8971-charger/top_off_threshold_current
Date: May 2025
KernelVersion: 6.15.0
Contact: Svyatoslav Ryhel <clamor95@gmail.com>
Description:
This entry shows and sets the charging current threshold for
entering top-off charging mode. When charging current in fast
charge mode drops below this value, the charger will trigger
interrupt and start top-off charging mode.
Valid values:
- 50000 - 200000 (microamps), step by 50000 (rounded down)
What: /sys/class/power_supply/max8971-charger/top_off_timer
Date: May 2025
KernelVersion: 6.15.0
Contact: Svyatoslav Ryhel <clamor95@gmail.com>
Description:
This entry shows and sets the maximum time the max8971
charger operates in top-off charge mode. When the timer expires
the device will terminate top-off charge mode (charging current
will drop to 0 A) and will trigger interrupt.
Valid values:
- 0 - 70 (minutes), step by 10 (rounded down)

View File

@ -0,0 +1,27 @@
What: /sys/class/power_supply/gaokun-ec-battery/smart_charge_delay
Date: March 2025
KernelVersion: 6.15
Contact: Pengyu Luo <mitltlatltl@gmail.com>
Description:
This entry allows configuration of smart charging delay.
Smart charging behavior: when the power adapter is connected
for delay hours, battery charging will follow the rules of
charge_control_start_threshold and charge_control_end_threshold.
For more information about charge control, please refer to
sysfs-class-power.
Access: Read, Write
Valid values: In hours (non-negative)
What: /sys/class/power_supply/gaokun-ec-battery/battery_adaptive_charge
Date: March 2025
KernelVersion: 6.15
Contact: Pengyu Luo <mitltlatltl@gmail.com>
Description:
This entry allows enabling battery adaptive charging.
Access: Read, Write
Valid values: 0 (disabled) or 1 (enabled)

View File

@ -21,7 +21,9 @@ description: |+
properties:
compatible:
const: syscon-reboot
enum:
- syscon-reboot
- google,gs101-reboot
mask:
$ref: /schemas/types.yaml#/definitions/uint32
@ -49,12 +51,6 @@ properties:
priority:
default: 192
oneOf:
- required:
- offset
- required:
- reg
required:
- compatible
@ -63,12 +59,29 @@ additionalProperties: false
allOf:
- $ref: restart-handler.yaml#
- if:
not:
required:
- mask
properties:
compatible:
contains:
const: google,gs101-reboot
then:
required:
- value
properties:
mask: false
offset: false
reg: false
value: false
else:
if:
not:
required:
- mask
then:
required:
- value
oneOf:
- required: [offset]
- required: [reg]
examples:
- |
@ -78,3 +91,8 @@ examples:
offset = <0x0>;
mask = <0x1>;
};
- |
reboot {
compatible = "google,gs101-reboot";
};

View File

@ -0,0 +1,52 @@
# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/reset/toradex,smarc-ec.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Toradex Embedded Controller
maintainers:
- Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
- Francesco Dolcini <francesco.dolcini@toradex.com>
description: |
The Toradex Embedded Controller (EC) is used on Toradex SMARC modules,
primarily to manage power and reset functionalities.
The EC provides the following functions:
- Reads the SMARC POWER_BTN# and RESET_IN# signals and controls the PMIC accordingly.
- Controls the SoC boot mode signals based on the SMARC BOOT_SEL# and FORCE_RECOV# inputs.
- Manages the CARRIER_STDBY# signal in response to relevant SoC signals.
The EC runs a small firmware, factory programmed into its internal flash, and communicates over I2C.
It allows software to control power-off and reset functionalities of the module.
properties:
compatible:
items:
- enum:
- toradex,smarc-imx95-ec
- toradex,smarc-imx8mp-ec
- const: toradex,smarc-ec
reg:
maxItems: 1
required:
- compatible
- reg
additionalProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
reset-controller@28 {
compatible = "toradex,smarc-imx95-ec", "toradex,smarc-ec";
reg = <0x28>;
};
};

View File

@ -19,6 +19,7 @@ properties:
- ti,bq24190
- ti,bq24192
- ti,bq24192i
- ti,bq24193
- ti,bq24196
- ti,bq24296
- ti,bq24297

View File

@ -87,28 +87,28 @@ unevaluatedProperties: false
examples:
- |
bat: battery {
compatible = "simple-battery";
constant-charge-current-max-microamp = <4000000>;
constant-charge-voltage-max-microvolt = <8400000>;
precharge-current-microamp = <160000>;
charge-term-current-microamp = <160000>;
compatible = "simple-battery";
constant-charge-current-max-microamp = <4000000>;
constant-charge-voltage-max-microvolt = <8400000>;
precharge-current-microamp = <160000>;
charge-term-current-microamp = <160000>;
};
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
bq25980: charger@65 {
compatible = "ti,bq25980";
reg = <0x65>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
ti,watchdog-timeout-ms = <0>;
ti,sc-ocp-limit-microamp = <2000000>;
ti,sc-ovp-limit-microvolt = <17800000>;
monitored-battery = <&bat>;
};
bq25980: charger@65 {
compatible = "ti,bq25980";
reg = <0x65>;
interrupt-parent = <&gpio1>;
interrupts = <16 IRQ_TYPE_EDGE_FALLING>;
ti,watchdog-timeout-ms = <0>;
ti,sc-ocp-limit-microamp = <2000000>;
ti,sc-ovp-limit-microvolt = <17800000>;
monitored-battery = <&bat>;
};
};
...

View File

@ -48,14 +48,14 @@ examples:
#include <dt-bindings/iio/adc/ingenic,adc.h>
simple_battery: battery {
compatible = "simple-battery";
voltage-min-design-microvolt = <3600000>;
voltage-max-design-microvolt = <4200000>;
compatible = "simple-battery";
voltage-min-design-microvolt = <3600000>;
voltage-max-design-microvolt = <4200000>;
};
ingenic-battery {
compatible = "ingenic,jz4740-battery";
io-channels = <&adc INGENIC_ADC_BATTERY>;
io-channel-names = "battery";
monitored-battery = <&simple_battery>;
compatible = "ingenic,jz4740-battery";
io-channels = <&adc INGENIC_ADC_BATTERY>;
io-channel-names = "battery";
monitored-battery = <&simple_battery>;
};

View File

@ -61,13 +61,13 @@ additionalProperties: false
examples:
- |
i2c {
#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>;
};
#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

@ -37,8 +37,8 @@ examples:
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
#address-cells = <1>;
#size-cells = <0>;
charger@69 {
compatible = "maxim,max77705-charger";

View File

@ -0,0 +1,68 @@
# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/maxim,max8971.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Maxim MAX8971 IC charger
maintainers:
- Svyatoslav Ryhel <clamor95@gmail.com>
description:
The MAX8971 is a compact, high-frequency, high-efficiency switch-mode charger
for a one-cell lithium-ion (Li+) battery.
allOf:
- $ref: power-supply.yaml#
properties:
compatible:
const: maxim,max8971
reg:
maxItems: 1
interrupts:
maxItems: 1
monitored-battery: true
port:
description:
An optional port node to link the extcon device to detect type of plug.
$ref: /schemas/graph.yaml#/properties/port
required:
- compatible
- reg
- interrupts
unevaluatedProperties: false
examples:
- |
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
i2c {
#address-cells = <1>;
#size-cells = <0>;
charger@35 {
compatible = "maxim,max8971";
reg = <0x35>;
interrupt-parent = <&gpio>;
interrupts = <74 IRQ_TYPE_LEVEL_LOW>;
monitored-battery = <&battery>;
port {
charger_input: endpoint {
remote-endpoint = <&extcon_output>;
};
};
};
};
...

View File

@ -0,0 +1,49 @@
# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
%YAML 1.2
---
$id: http://devicetree.org/schemas/power/supply/pegatron,chagall-ec.yaml#
$schema: http://devicetree.org/meta-schemas/core.yaml#
title: Pegatron Chagall EC
maintainers:
- Svyatoslav Ryhel <clamor95@gmail.com>
description:
Pegatron Chagall EC is based on an 8-bit programmable microcontroller from
Infineon/Cypress Semiconductor, it communicates over I2C and is used in the
Pegatron Chagall tablet for fuel gauge and battery control functions.
$ref: /schemas/power/supply/power-supply.yaml
properties:
compatible:
const: pegatron,chagall-ec
reg:
maxItems: 1
monitored-battery: true
power-supplies: true
required:
- compatible
- reg
unevaluatedProperties: false
examples:
- |
i2c {
#address-cells = <1>;
#size-cells = <0>;
embedded-controller@10 {
compatible = "pegatron,chagall-ec";
reg = <0x10>;
monitored-battery = <&battery>;
power-supplies = <&mains>;
};
};
...

View File

@ -23,6 +23,9 @@ properties:
- atmel,sama5d3-rstc
- microchip,sam9x60-rstc
- microchip,sama7g5-rstc
- items:
- const: microchip,sama7d65-rstc
- const: microchip,sama7g5-rstc
- items:
- const: atmel,sama5d3-rstc
- const: atmel,at91sam9g45-rstc

View File

@ -1158,6 +1158,8 @@ patternProperties:
description: Parallax Inc.
"^pda,.*":
description: Precision Design Associates, Inc.
"^pegatron,.*":
description: Pegatron Corporation
"^pericom,.*":
description: Pericom Technology Inc.
"^pervasive,.*":

View File

@ -11002,6 +11002,7 @@ M: Pengyu Luo <mitltlatltl@gmail.com>
S: Maintained
F: Documentation/devicetree/bindings/platform/huawei,gaokun-ec.yaml
F: drivers/platform/arm64/huawei-gaokun-ec.c
F: drivers/power/supply/huawei-gaokun-battery.c
F: include/linux/platform_data/huawei-gaokun-ec.h
HUGETLB SUBSYSTEM
@ -24683,6 +24684,13 @@ L: platform-driver-x86@vger.kernel.org
S: Maintained
F: drivers/platform/x86/topstar-laptop.c
TORADEX EMBEDDED CONTROLLER DRIVER
M: Emanuele Ghidoli <ghidoliemanuele@gmail.com>
M: Francesco Dolcini <francesco@dolcini.it>
S: Maintained
F: Documentation/devicetree/bindings/power/reset/toradex,smarc-ec.yaml
F: drivers/power/reset/tdx-ec-poweroff.c
TORTURE-TEST MODULES
M: Davidlohr Bueso <dave@stgolabs.net>
M: "Paul E. McKenney" <paulmck@kernel.org>

View File

@ -216,6 +216,19 @@ config POWER_RESET_ST
help
Reset support for STMicroelectronics boards.
config POWER_RESET_TORADEX_EC
tristate "Toradex Embedded Controller power-off and reset driver"
depends on I2C
select REGMAP_I2C
help
This driver supports power-off and reset for SMARC Toradex SoMs,
for example the SMARC iMX8MP and SMARC iMX95, using Toradex
Embedded Controller (EC).
Say Y here if you have a Toradex SMARC SoM.
If unsure, say N.
config POWER_RESET_TPS65086
bool "TPS65086 restart driver"
depends on MFD_TPS65086

View File

@ -24,6 +24,7 @@ obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
obj-$(CONFIG_POWER_RESET_REGULATOR) += regulator-poweroff.o
obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
obj-$(CONFIG_POWER_RESET_ST) += st-poweroff.o
obj-$(CONFIG_POWER_RESET_TORADEX_EC) += tdx-ec-poweroff.o
obj-$(CONFIG_POWER_RESET_TPS65086) += tps65086-restart.o
obj-$(CONFIG_POWER_RESET_VERSATILE) += arm-versatile-reboot.o
obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o

View File

@ -129,12 +129,11 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
" str %4, [%0, %6]\n\t"
/* Disable SDRAM1 accesses */
"1: tst %1, #0\n\t"
" beq 2f\n\t"
" strne %3, [%1, #" __stringify(AT91_DDRSDRC_RTR) "]\n\t"
/* Power down SDRAM1 */
" strne %4, [%1, %6]\n\t"
/* Reset CPU */
"2: str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
" str %5, [%2, #" __stringify(AT91_RSTC_CR) "]\n\t"
" b .\n\t"
:
@ -145,7 +144,7 @@ static int at91_reset(struct notifier_block *this, unsigned long mode,
"r" cpu_to_le32(AT91_DDRSDRC_LPCB_POWER_DOWN),
"r" (reset->data->reset_args),
"r" (reset->ramc_lpr)
: "r4");
);
return NOTIFY_DONE;
}

View File

@ -23,20 +23,29 @@ static unsigned int get_reboot_mode_magic(struct reboot_mode_driver *reboot,
const char *cmd)
{
const char *normal = "normal";
int magic = 0;
struct mode_info *info;
char cmd_[110];
if (!cmd)
cmd = normal;
list_for_each_entry(info, &reboot->head, list) {
if (!strcmp(info->mode, cmd)) {
magic = info->magic;
break;
}
}
list_for_each_entry(info, &reboot->head, list)
if (!strcmp(info->mode, cmd))
return info->magic;
return magic;
/* try to match again, replacing characters impossible in DT */
if (strscpy(cmd_, cmd, sizeof(cmd_)) == -E2BIG)
return 0;
strreplace(cmd_, ' ', '-');
strreplace(cmd_, ',', '-');
strreplace(cmd_, '/', '-');
list_for_each_entry(info, &reboot->head, list)
if (!strcmp(info->mode, cmd_))
return info->magic;
return 0;
}
static int reboot_mode_notify(struct notifier_block *this,

View File

@ -14,11 +14,24 @@
#include <linux/reboot.h>
#include <linux/regmap.h>
struct reboot_mode_bits {
u32 offset;
u32 mask;
u32 value;
bool valid;
};
struct reboot_data {
struct reboot_mode_bits mode_bits[REBOOT_SOFT + 1];
struct reboot_mode_bits catchall;
};
struct syscon_reboot_context {
struct regmap *map;
u32 offset;
u32 value;
u32 mask;
const struct reboot_data *rd; /* from of match data, if any */
struct reboot_mode_bits catchall; /* from DT */
struct notifier_block restart_handler;
};
@ -28,9 +41,21 @@ static int syscon_restart_handle(struct notifier_block *this,
struct syscon_reboot_context *ctx =
container_of(this, struct syscon_reboot_context,
restart_handler);
const struct reboot_mode_bits *mode_bits;
if (ctx->rd) {
if (mode < ARRAY_SIZE(ctx->rd->mode_bits) &&
ctx->rd->mode_bits[mode].valid)
mode_bits = &ctx->rd->mode_bits[mode];
else
mode_bits = &ctx->rd->catchall;
} else {
mode_bits = &ctx->catchall;
}
/* Issue the reboot */
regmap_update_bits(ctx->map, ctx->offset, ctx->mask, ctx->value);
regmap_update_bits(ctx->map, mode_bits->offset, mode_bits->mask,
mode_bits->value);
mdelay(1000);
@ -42,7 +67,6 @@ static int syscon_reboot_probe(struct platform_device *pdev)
{
struct syscon_reboot_context *ctx;
struct device *dev = &pdev->dev;
int mask_err, value_err;
int priority;
int err;
@ -60,24 +84,33 @@ static int syscon_reboot_probe(struct platform_device *pdev)
if (of_property_read_s32(pdev->dev.of_node, "priority", &priority))
priority = 192;
if (of_property_read_u32(pdev->dev.of_node, "offset", &ctx->offset))
if (of_property_read_u32(pdev->dev.of_node, "reg", &ctx->offset))
ctx->rd = of_device_get_match_data(dev);
if (!ctx->rd) {
int mask_err, value_err;
if (of_property_read_u32(pdev->dev.of_node, "offset",
&ctx->catchall.offset) &&
of_property_read_u32(pdev->dev.of_node, "reg",
&ctx->catchall.offset))
return -EINVAL;
value_err = of_property_read_u32(pdev->dev.of_node, "value", &ctx->value);
mask_err = of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask);
if (value_err && mask_err) {
dev_err(dev, "unable to read 'value' and 'mask'");
return -EINVAL;
}
value_err = of_property_read_u32(pdev->dev.of_node, "value",
&ctx->catchall.value);
mask_err = of_property_read_u32(pdev->dev.of_node, "mask",
&ctx->catchall.mask);
if (value_err && mask_err) {
dev_err(dev, "unable to read 'value' and 'mask'");
return -EINVAL;
}
if (value_err) {
/* support old binding */
ctx->value = ctx->mask;
ctx->mask = 0xFFFFFFFF;
} else if (mask_err) {
/* support value without mask*/
ctx->mask = 0xFFFFFFFF;
if (value_err) {
/* support old binding */
ctx->catchall.value = ctx->catchall.mask;
ctx->catchall.mask = 0xFFFFFFFF;
} else if (mask_err) {
/* support value without mask */
ctx->catchall.mask = 0xFFFFFFFF;
}
}
ctx->restart_handler.notifier_call = syscon_restart_handle;
@ -89,7 +122,30 @@ static int syscon_reboot_probe(struct platform_device *pdev)
return err;
}
static const struct reboot_data gs101_reboot_data = {
.mode_bits = {
[REBOOT_WARM] = {
.offset = 0x3a00, /* SYSTEM_CONFIGURATION */
.mask = 0x00000002, /* SWRESET_SYSTEM */
.value = 0x00000002,
.valid = true,
},
[REBOOT_SOFT] = {
.offset = 0x3a00, /* SYSTEM_CONFIGURATION */
.mask = 0x00000002, /* SWRESET_SYSTEM */
.value = 0x00000002,
.valid = true,
},
},
.catchall = {
.offset = 0x3e9c, /* PAD_CTRL_PWR_HOLD */
.mask = 0x00000100,
.value = 0x00000000,
},
};
static const struct of_device_id syscon_reboot_of_match[] = {
{ .compatible = "google,gs101-reboot", .data = &gs101_reboot_data },
{ .compatible = "syscon-reboot" },
{}
};

View File

@ -0,0 +1,150 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Toradex Embedded Controller driver
*
* Copyright (C) 2025 Toradex
*
* Author: Emanuele Ghidoli <emanuele.ghidoli@toradex.com>
*/
#include <linux/array_size.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/reboot.h>
#include <linux/regmap.h>
#include <linux/types.h>
#define EC_CHIP_ID_REG 0x00
#define EC_CHIP_ID_SMARC_IMX95 0x11
#define EC_CHIP_ID_SMARC_IMX8MP 0x12
#define EC_VERSION_REG_MAJOR 0x01
#define EC_VERSION_REG_MINOR 0x02
#define EC_ID_VERSION_LEN 3
#define EC_CMD_REG 0xD0
#define EC_CMD_POWEROFF 0x01
#define EC_CMD_RESET 0x02
#define EC_REG_MAX 0xD0
static const struct regmap_range volatile_ranges[] = {
regmap_reg_range(EC_CMD_REG, EC_CMD_REG),
};
static const struct regmap_access_table volatile_table = {
.yes_ranges = volatile_ranges,
.n_yes_ranges = ARRAY_SIZE(volatile_ranges),
};
static const struct regmap_range read_ranges[] = {
regmap_reg_range(EC_CHIP_ID_REG, EC_VERSION_REG_MINOR),
};
static const struct regmap_access_table read_table = {
.yes_ranges = read_ranges,
.n_yes_ranges = ARRAY_SIZE(read_ranges),
};
static const struct regmap_config regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = EC_REG_MAX,
.cache_type = REGCACHE_RBTREE,
.rd_table = &read_table,
.volatile_table = &volatile_table,
};
static int tdx_ec_cmd(struct regmap *regmap, u8 cmd)
{
int err = regmap_write(regmap, EC_CMD_REG, cmd);
if (err)
dev_err(regmap_get_device(regmap), "Failed to send command 0x%02X: %d\n", cmd, err);
return err;
}
static int tdx_ec_power_off(struct sys_off_data *data)
{
struct regmap *regmap = data->cb_data;
int err;
err = tdx_ec_cmd(regmap, EC_CMD_POWEROFF);
return err ? NOTIFY_BAD : NOTIFY_DONE;
}
static int tdx_ec_restart(struct sys_off_data *data)
{
struct regmap *regmap = data->cb_data;
int err;
err = tdx_ec_cmd(regmap, EC_CMD_RESET);
return err ? NOTIFY_BAD : NOTIFY_DONE;
}
static int tdx_ec_register_power_off_restart(struct device *dev, struct regmap *regmap)
{
int err;
err = devm_register_sys_off_handler(dev, SYS_OFF_MODE_RESTART,
SYS_OFF_PRIO_FIRMWARE,
tdx_ec_restart, regmap);
if (err)
return err;
return devm_register_sys_off_handler(dev, SYS_OFF_MODE_POWER_OFF,
SYS_OFF_PRIO_FIRMWARE,
tdx_ec_power_off, regmap);
}
static int tdx_ec_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
u8 reg_val[EC_ID_VERSION_LEN];
struct regmap *regmap;
int err;
regmap = devm_regmap_init_i2c(client, &regmap_config);
if (IS_ERR(regmap))
return PTR_ERR(regmap);
err = regmap_bulk_read(regmap, EC_CHIP_ID_REG, &reg_val, EC_ID_VERSION_LEN);
if (err)
return dev_err_probe(dev, err,
"Cannot read id and version registers\n");
dev_info(dev, "Toradex Embedded Controller id %x - Firmware %u.%u\n",
reg_val[0], reg_val[1], reg_val[2]);
err = tdx_ec_register_power_off_restart(dev, regmap);
if (err)
return dev_err_probe(dev, err,
"Cannot register system restart handler\n");
return 0;
}
static const struct of_device_id __maybe_unused of_tdx_ec_match[] = {
{ .compatible = "toradex,smarc-ec" },
{}
};
MODULE_DEVICE_TABLE(of, of_tdx_ec_match);
static struct i2c_driver tdx_ec_driver = {
.probe = tdx_ec_probe,
.driver = {
.name = "toradex-smarc-ec",
.of_match_table = of_tdx_ec_match,
},
};
module_i2c_driver(tdx_ec_driver);
MODULE_AUTHOR("Emanuele Ghidoli <emanuele.ghidoli@toradex.com>");
MODULE_DESCRIPTION("Toradex SMARC Embedded Controller driver");
MODULE_LICENSE("GPL");

View File

@ -107,6 +107,18 @@ config BATTERY_ACT8945A
Say Y here to enable support for power supply provided by
Active-semi ActivePath ACT8945A charger.
config BATTERY_CHAGALL
tristate "Pegatron Chagall battery driver"
depends on I2C
depends on LEDS_CLASS
help
Say Y to include support for Cypress CG7153AM IC based battery
fuel gauge with custom firmware found in Pegatron Chagall based
tablet line.
This driver can also be built as a module. If so, the module will be
called chagall-battery.
config BATTERY_CPCAP
tristate "Motorola CPCAP PMIC battery driver"
depends on MFD_CPCAP && IIO
@ -161,6 +173,16 @@ config BATTERY_DS2782
Say Y here to enable support for the DS2782/DS2786 standalone battery
gas-gauge.
config BATTERY_HUAWEI_GAOKUN
tristate "Huawei Matebook E Go power supply"
depends on EC_HUAWEI_GAOKUN
help
This driver enables battery and adapter support on the Huawei Matebook
E Go, which is a sc8280xp-based 2-in-1 tablet.
To compile the driver as a module, choose M here: the module will be
called huawei-gaokun-battery.
config BATTERY_LEGO_EV3
tristate "LEGO MINDSTORMS EV3 battery"
depends on OF && IIO && GPIOLIB && (ARCH_DAVINCI_DA850 || COMPILE_TEST)
@ -595,6 +617,21 @@ config CHARGER_MAX77976
This driver can also be built as a module. If so, the module will be
called max77976_charger.
config CHARGER_MAX8971
tristate "Maxim MAX8971 battery charger driver"
depends on I2C
depends on EXTCON || !EXTCON
select REGMAP_I2C
help
The MAX8971 is a compact, high-frequency, high-efficiency switch-mode
charger for a one-cell lithium-ion (Li+) battery. It delivers up to
1.55A of current to the battery from inputs up to 7.5V and withstands
transient inputs up to 22V.
Say Y to enable support for the Maxim MAX8971 battery charger.
This driver can also be built as a module. If so, the module will be
called max8971_charger.
config CHARGER_MAX8997
tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
depends on MFD_MAX8997 && REGULATOR_MAX8997

View File

@ -23,6 +23,7 @@ obj-$(CONFIG_CHARGER_ADP5061) += adp5061.o
obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
obj-$(CONFIG_BATTERY_AXP20X) += axp20x_battery.o
obj-$(CONFIG_CHARGER_AXP20X) += axp20x_ac_power.o
obj-$(CONFIG_BATTERY_CHAGALL) += chagall-battery.o
obj-$(CONFIG_BATTERY_CPCAP) += cpcap-battery.o
obj-$(CONFIG_BATTERY_CW2015) += cw2015_battery.o
obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
@ -31,6 +32,7 @@ obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o
obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
obj-$(CONFIG_BATTERY_HUAWEI_GAOKUN) += huawei-gaokun-battery.o
obj-$(CONFIG_BATTERY_LEGO_EV3) += lego_ev3_battery.o
obj-$(CONFIG_BATTERY_LENOVO_YOGA_C630) += lenovo_yoga_c630_battery.o
obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
@ -81,6 +83,7 @@ obj-$(CONFIG_CHARGER_MAX77650) += max77650-charger.o
obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
obj-$(CONFIG_CHARGER_MAX77705) += max77705_charger.o
obj-$(CONFIG_CHARGER_MAX77976) += max77976_charger.o
obj-$(CONFIG_CHARGER_MAX8971) += max8971_charger.o
obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
obj-$(CONFIG_CHARGER_MP2629) += mp2629_charger.o

View File

@ -207,6 +207,7 @@ enum bq24190_chip {
BQ24190,
BQ24192,
BQ24192i,
BQ24193,
BQ24196,
BQ24296,
BQ24297,
@ -2014,6 +2015,17 @@ static const struct bq24190_chip_info bq24190_chip_info_tbl[] = {
.ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values),
#ifdef CONFIG_REGULATOR
.vbus_desc = &bq24190_vbus_desc,
#endif
.check_chip = bq24190_check_chip,
.set_chg_config = bq24190_battery_set_chg_config,
.ntc_fault_mask = BQ24190_REG_F_NTC_FAULT_MASK,
.get_ntc_status = bq24190_charger_get_ntc_status,
.set_otg_vbus = bq24190_set_otg_vbus,
},
[BQ24193] = {
.ichg_array_size = ARRAY_SIZE(bq24190_ccc_ichg_values),
#ifdef CONFIG_REGULATOR
.vbus_desc = &bq24190_vbus_desc,
#endif
.check_chip = bq24190_check_chip,
.set_chg_config = bq24190_battery_set_chg_config,
@ -2308,6 +2320,7 @@ static const struct i2c_device_id bq24190_i2c_ids[] = {
{ "bq24190", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24190] },
{ "bq24192", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192] },
{ "bq24192i", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24192i] },
{ "bq24193", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24193] },
{ "bq24196", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24196] },
{ "bq24296", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24296] },
{ "bq24297", (kernel_ulong_t)&bq24190_chip_info_tbl[BQ24297] },
@ -2319,6 +2332,7 @@ static const struct of_device_id bq24190_of_match[] = {
{ .compatible = "ti,bq24190", .data = &bq24190_chip_info_tbl[BQ24190] },
{ .compatible = "ti,bq24192", .data = &bq24190_chip_info_tbl[BQ24192] },
{ .compatible = "ti,bq24192i", .data = &bq24190_chip_info_tbl[BQ24192i] },
{ .compatible = "ti,bq24193", .data = &bq24190_chip_info_tbl[BQ24193] },
{ .compatible = "ti,bq24196", .data = &bq24190_chip_info_tbl[BQ24196] },
{ .compatible = "ti,bq24296", .data = &bq24190_chip_info_tbl[BQ24296] },
{ .compatible = "ti,bq24297", .data = &bq24190_chip_info_tbl[BQ24297] },

View File

@ -2131,7 +2131,7 @@ static int bq27xxx_battery_get_property(struct power_supply *psy,
mutex_unlock(&di->lock);
if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0)
return -ENODEV;
return di->cache.flags;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:

View File

@ -6,6 +6,7 @@
* Andrew F. Davis <afd@ti.com>
*/
#include <linux/delay.h>
#include <linux/i2c.h>
#include <linux/interrupt.h>
#include <linux/module.h>
@ -31,6 +32,7 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
struct i2c_msg msg[2];
u8 data[2];
int ret;
int retry = 0;
if (!client->adapter)
return -ENODEV;
@ -47,7 +49,16 @@ static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
else
msg[1].len = 2;
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
do {
ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
if (ret == -EBUSY && ++retry < 3) {
/* sleep 10 milliseconds when busy */
usleep_range(10000, 11000);
continue;
}
break;
} while (1);
if (ret < 0)
return ret;

View File

@ -0,0 +1,291 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/array_size.h>
#include <linux/delay.h>
#include <linux/devm-helpers.h>
#include <linux/err.h>
#include <linux/i2c.h>
#include <linux/leds.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#define CHAGALL_REG_LED_AMBER 0x60
#define CHAGALL_REG_LED_WHITE 0x70
#define CHAGALL_REG_BATTERY_TEMPERATURE 0xa2
#define CHAGALL_REG_BATTERY_VOLTAGE 0xa4
#define CHAGALL_REG_BATTERY_CURRENT 0xa6
#define CHAGALL_REG_BATTERY_CAPACITY 0xa8
#define CHAGALL_REG_BATTERY_CHARGING_CURRENT 0xaa
#define CHAGALL_REG_BATTERY_CHARGING_VOLTAGE 0xac
#define CHAGALL_REG_BATTERY_STATUS 0xae
#define BATTERY_DISCHARGING BIT(6)
#define BATTERY_FULL_CHARGED BIT(5)
#define BATTERY_FULL_DISCHARGED BIT(4)
#define CHAGALL_REG_BATTERY_REMAIN_CAPACITY 0xb0
#define CHAGALL_REG_BATTERY_FULL_CAPACITY 0xb2
#define CHAGALL_REG_MAX_COUNT 0xb4
#define CHAGALL_BATTERY_DATA_REFRESH 5000
#define TEMP_CELSIUS_OFFSET 2731
static const struct regmap_config chagall_battery_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = CHAGALL_REG_MAX_COUNT,
.reg_format_endian = REGMAP_ENDIAN_LITTLE,
.val_format_endian = REGMAP_ENDIAN_LITTLE,
};
struct chagall_battery_data {
struct regmap *regmap;
struct led_classdev amber_led;
struct led_classdev white_led;
struct power_supply *battery;
struct delayed_work poll_work;
u16 last_state;
};
static void chagall_led_set_brightness_amber(struct led_classdev *led,
enum led_brightness brightness)
{
struct chagall_battery_data *cg =
container_of(led, struct chagall_battery_data, amber_led);
regmap_write(cg->regmap, CHAGALL_REG_LED_AMBER, brightness);
}
static void chagall_led_set_brightness_white(struct led_classdev *led,
enum led_brightness brightness)
{
struct chagall_battery_data *cg =
container_of(led, struct chagall_battery_data, white_led);
regmap_write(cg->regmap, CHAGALL_REG_LED_WHITE, brightness);
}
static const enum power_supply_property chagall_battery_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_VOLTAGE_MAX,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CURRENT_MAX,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_TEMP,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
};
static const unsigned int chagall_battery_prop_offs[] = {
[POWER_SUPPLY_PROP_STATUS] = CHAGALL_REG_BATTERY_STATUS,
[POWER_SUPPLY_PROP_VOLTAGE_NOW] = CHAGALL_REG_BATTERY_VOLTAGE,
[POWER_SUPPLY_PROP_VOLTAGE_MAX] = CHAGALL_REG_BATTERY_CHARGING_VOLTAGE,
[POWER_SUPPLY_PROP_CURRENT_NOW] = CHAGALL_REG_BATTERY_CURRENT,
[POWER_SUPPLY_PROP_CURRENT_MAX] = CHAGALL_REG_BATTERY_CHARGING_CURRENT,
[POWER_SUPPLY_PROP_CAPACITY] = CHAGALL_REG_BATTERY_CAPACITY,
[POWER_SUPPLY_PROP_TEMP] = CHAGALL_REG_BATTERY_TEMPERATURE,
[POWER_SUPPLY_PROP_CHARGE_FULL] = CHAGALL_REG_BATTERY_FULL_CAPACITY,
[POWER_SUPPLY_PROP_CHARGE_NOW] = CHAGALL_REG_BATTERY_REMAIN_CAPACITY,
};
static int chagall_battery_get_value(struct chagall_battery_data *cg,
enum power_supply_property psp, u32 *val)
{
if (psp >= ARRAY_SIZE(chagall_battery_prop_offs))
return -EINVAL;
if (!chagall_battery_prop_offs[psp])
return -EINVAL;
/* Battery data is stored in 2 consecutive registers with little-endian */
return regmap_bulk_read(cg->regmap, chagall_battery_prop_offs[psp], val, 2);
}
static int chagall_battery_get_status(u32 status_reg)
{
if (status_reg & BATTERY_FULL_CHARGED)
return POWER_SUPPLY_STATUS_FULL;
else if (status_reg & BATTERY_DISCHARGING)
return POWER_SUPPLY_STATUS_DISCHARGING;
else
return POWER_SUPPLY_STATUS_CHARGING;
}
static int chagall_battery_get_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct chagall_battery_data *cg = power_supply_get_drvdata(psy);
int ret;
switch (psp) {
case POWER_SUPPLY_PROP_PRESENT:
val->intval = 1;
break;
default:
ret = chagall_battery_get_value(cg, psp, &val->intval);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_TEMP:
val->intval -= TEMP_CELSIUS_OFFSET;
break;
case POWER_SUPPLY_PROP_VOLTAGE_MAX:
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
case POWER_SUPPLY_PROP_CURRENT_MAX:
case POWER_SUPPLY_PROP_CURRENT_NOW:
case POWER_SUPPLY_PROP_CHARGE_FULL:
case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval *= 1000;
break;
case POWER_SUPPLY_PROP_STATUS:
val->intval = chagall_battery_get_status(val->intval);
break;
default:
break;
}
break;
}
return 0;
}
static void chagall_battery_poll_work(struct work_struct *work)
{
struct chagall_battery_data *cg =
container_of(work, struct chagall_battery_data, poll_work.work);
u32 state;
int ret;
ret = chagall_battery_get_value(cg, POWER_SUPPLY_PROP_STATUS, &state);
if (ret)
return;
state = chagall_battery_get_status(state);
if (cg->last_state != state) {
cg->last_state = state;
power_supply_changed(cg->battery);
}
/* continuously send uevent notification */
schedule_delayed_work(&cg->poll_work,
msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
}
static const struct power_supply_desc chagall_battery_desc = {
.name = "chagall-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.properties = chagall_battery_properties,
.num_properties = ARRAY_SIZE(chagall_battery_properties),
.get_property = chagall_battery_get_property,
.external_power_changed = power_supply_changed,
};
static int chagall_battery_probe(struct i2c_client *client)
{
struct chagall_battery_data *cg;
struct device *dev = &client->dev;
struct power_supply_config cfg = { };
int ret;
cg = devm_kzalloc(dev, sizeof(*cg), GFP_KERNEL);
if (!cg)
return -ENOMEM;
cfg.drv_data = cg;
cfg.fwnode = dev_fwnode(dev);
i2c_set_clientdata(client, cg);
cg->regmap = devm_regmap_init_i2c(client, &chagall_battery_regmap_config);
if (IS_ERR(cg->regmap))
return dev_err_probe(dev, PTR_ERR(cg->regmap), "cannot allocate regmap\n");
cg->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
cg->battery = devm_power_supply_register(dev, &chagall_battery_desc, &cfg);
if (IS_ERR(cg->battery))
return dev_err_probe(dev, PTR_ERR(cg->battery),
"failed to register power supply\n");
cg->amber_led.name = "power::amber";
cg->amber_led.max_brightness = 1;
cg->amber_led.flags = LED_CORE_SUSPENDRESUME;
cg->amber_led.brightness_set = chagall_led_set_brightness_amber;
cg->amber_led.default_trigger = "chagall-battery-charging";
ret = devm_led_classdev_register(dev, &cg->amber_led);
if (ret)
return dev_err_probe(dev, ret, "failed to register amber LED\n");
cg->white_led.name = "power::white";
cg->white_led.max_brightness = 1;
cg->white_led.flags = LED_CORE_SUSPENDRESUME;
cg->white_led.brightness_set = chagall_led_set_brightness_white;
cg->white_led.default_trigger = "chagall-battery-full";
ret = devm_led_classdev_register(dev, &cg->white_led);
if (ret)
return dev_err_probe(dev, ret, "failed to register white LED\n");
led_set_brightness(&cg->amber_led, LED_OFF);
led_set_brightness(&cg->white_led, LED_OFF);
ret = devm_delayed_work_autocancel(dev, &cg->poll_work, chagall_battery_poll_work);
if (ret)
return ret;
schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
return 0;
}
static int __maybe_unused chagall_battery_suspend(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct chagall_battery_data *cg = i2c_get_clientdata(client);
cancel_delayed_work_sync(&cg->poll_work);
return 0;
}
static int __maybe_unused chagall_battery_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct chagall_battery_data *cg = i2c_get_clientdata(client);
schedule_delayed_work(&cg->poll_work, msecs_to_jiffies(CHAGALL_BATTERY_DATA_REFRESH));
return 0;
}
static SIMPLE_DEV_PM_OPS(chagall_battery_pm_ops,
chagall_battery_suspend, chagall_battery_resume);
static const struct of_device_id chagall_of_match[] = {
{ .compatible = "pegatron,chagall-ec" },
{ }
};
MODULE_DEVICE_TABLE(of, chagall_of_match);
static struct i2c_driver chagall_battery_driver = {
.driver = {
.name = "chagall-battery",
.pm = &chagall_battery_pm_ops,
.of_match_table = chagall_of_match,
},
.probe = chagall_battery_probe,
};
module_i2c_driver(chagall_battery_driver);
MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
MODULE_DESCRIPTION("Pegatron Chagall fuel gauge driver");
MODULE_LICENSE("GPL");

View File

@ -440,6 +440,7 @@ static int collie_bat_probe(struct ucb1x00_dev *dev)
static void collie_bat_remove(struct ucb1x00_dev *dev)
{
device_init_wakeup(&ucb->dev, 0);
free_irq(gpiod_to_irq(collie_bat_main.gpio_full), &collie_bat_main);
power_supply_unregister(collie_bat_bu.psy);
power_supply_unregister(collie_bat_main.psy);

View File

@ -47,29 +47,20 @@ struct cros_chctl_priv {
static int cros_chctl_send_charge_control_cmd(struct cros_ec_device *cros_ec,
u8 cmd_version, struct ec_params_charge_control *req)
{
int ret;
static const u8 outsizes[] = {
[1] = offsetof(struct ec_params_charge_control, cmd),
[2] = sizeof(struct ec_params_charge_control),
[3] = sizeof(struct ec_params_charge_control),
};
struct {
struct cros_ec_command msg;
union {
struct ec_params_charge_control req;
struct ec_response_charge_control resp;
} __packed data;
} __packed buf = {
.msg = {
.command = EC_CMD_CHARGE_CONTROL,
.version = cmd_version,
.insize = 0,
.outsize = outsizes[cmd_version],
},
.data.req = *req,
};
ret = cros_ec_cmd(cros_ec, cmd_version, EC_CMD_CHARGE_CONTROL, req,
outsizes[cmd_version], NULL, 0);
return cros_ec_cmd_xfer_status(cros_ec, &buf.msg);
if (ret < 0)
return ret;
return 0;
}
static int cros_chctl_configure_ec(struct cros_chctl_priv *priv)

View File

@ -366,7 +366,9 @@ static int gpio_charger_probe(struct platform_device *pdev)
platform_set_drvdata(pdev, gpio_charger);
device_init_wakeup(dev, 1);
ret = devm_device_init_wakeup(dev);
if (ret)
return dev_err_probe(dev, ret, "Failed to init wakeup\n");
return 0;
}

View File

@ -0,0 +1,645 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* huawei-gaokun-battery - A power supply driver for HUAWEI Matebook E Go
*
* Copyright (C) 2024 Pengyu Luo <mitltlatltl@gmail.com>
*/
#include <linux/auxiliary_bus.h>
#include <linux/bits.h>
#include <linux/delay.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/notifier.h>
#include <linux/platform_data/huawei-gaokun-ec.h>
#include <linux/power_supply.h>
#include <linux/sprintf.h>
/* -------------------------------------------------------------------------- */
/* String Data Reg */
#define EC_BAT_VENDOR 0x01 /* from 0x01 to 0x0F, SUNWODA */
#define EC_BAT_MODEL 0x11 /* from 0x11 to 0x1F, HB30A8P9ECW-22T */
#define EC_ADP_STATUS 0x81
#define EC_AC_STATUS BIT(0)
#define EC_BAT_PRESENT BIT(1) /* BATC._STA */
#define EC_BAT_STATUS 0x82 /* _BST */
#define EC_BAT_DISCHARGING BIT(0)
#define EC_BAT_CHARGING BIT(1)
#define EC_BAT_CRITICAL BIT(2) /* Low Battery Level */
#define EC_BAT_FULL BIT(3)
/* -------------------------------------------------------------------------- */
/* Word Data Reg */
/* 0x5A: ?
* 0x5C: ?
* 0x5E: ?
* 0X60: ?
* 0x84: ?
*/
#define EC_BAT_STATUS_START 0x90
#define EC_BAT_PERCENTAGE 0x90
#define EC_BAT_VOLTAGE 0x92
#define EC_BAT_CAPACITY 0x94
#define EC_BAT_FULL_CAPACITY 0x96
/* 0x98: ? */
#define EC_BAT_CURRENT 0x9A
/* 0x9C: ? */
#define EC_BAT_INFO_START 0xA0
/* 0xA0: POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT? */
#define EC_BAT_DESIGN_CAPACITY 0xA2
#define EC_BAT_DESIGN_VOLTAGE 0xA4
#define EC_BAT_SERIAL_NUMBER 0xA6
#define EC_BAT_CYCLE_COUNT 0xAA
/* -------------------------------------------------------------------------- */
/* Battery Event ID */
#define EC_EVENT_BAT_A0 0xA0
#define EC_EVENT_BAT_A1 0xA1
#define EC_EVENT_BAT_A2 0xA2
#define EC_EVENT_BAT_A3 0xA3
#define EC_EVENT_BAT_B1 0xB1
/* EVENT B1 A0 A1 repeat about every 1s 2s 3s respectively */
/* ACPI _BIX field, Min sampling time, the duration between two _BST */
#define CACHE_TIME 2000 /* cache time in milliseconds */
#define MILLI_TO_MICRO 1000
#define SMART_CHARGE_MODE 0
#define SMART_CHARGE_DELAY 1
#define SMART_CHARGE_START 2
#define SMART_CHARGE_END 3
#define NO_DELAY_MODE 1
#define DELAY_MODE 4
struct gaokun_psy_bat_status {
__le16 percentage_now; /* 0x90 */
__le16 voltage_now;
__le16 capacity_now;
__le16 full_capacity;
__le16 unknown1;
__le16 rate_now;
__le16 unknown2; /* 0x9C */
} __packed;
struct gaokun_psy_bat_info {
__le16 unknown3; /* 0xA0 */
__le16 design_capacity;
__le16 design_voltage;
__le16 serial_number;
__le16 padding2;
__le16 cycle_count; /* 0xAA */
} __packed;
struct gaokun_psy {
struct gaokun_ec *ec;
struct device *dev;
struct notifier_block nb;
struct power_supply *bat_psy;
struct power_supply *adp_psy;
unsigned long update_time;
struct gaokun_psy_bat_status status;
struct gaokun_psy_bat_info info;
char battery_model[0x10]; /* HB30A8P9ECW-22T, the real one is XXX-22A */
char battery_serial[0x10];
char battery_vendor[0x10];
int charge_now;
int online;
int bat_present;
};
/* -------------------------------------------------------------------------- */
/* Adapter */
static int gaokun_psy_get_adp_status(struct gaokun_psy *ecbat)
{
/* _PSR */
int ret;
u8 online;
ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &online);
if (ret)
return ret;
ecbat->online = !!(online & EC_AC_STATUS);
return 0;
}
static int gaokun_psy_get_adp_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
int ret;
ret = gaokun_psy_get_adp_status(ecbat);
if (ret)
return ret;
switch (psp) {
case POWER_SUPPLY_PROP_ONLINE:
val->intval = ecbat->online;
break;
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = POWER_SUPPLY_USB_TYPE_C;
break;
default:
return -EINVAL;
}
return 0;
}
static enum power_supply_property gaokun_psy_adp_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_USB_TYPE,
};
static const struct power_supply_desc gaokun_psy_adp_desc = {
.name = "gaokun-ec-adapter",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_C),
.get_property = gaokun_psy_get_adp_property,
.properties = gaokun_psy_adp_props,
.num_properties = ARRAY_SIZE(gaokun_psy_adp_props),
};
/* -------------------------------------------------------------------------- */
/* Battery */
static inline void gaokun_psy_get_bat_present(struct gaokun_psy *ecbat)
{
int ret;
u8 present;
/* Some kind of initialization */
gaokun_ec_write(ecbat->ec, (u8 []){0x02, 0xB2, 1, 0x90});
ret = gaokun_ec_psy_read_byte(ecbat->ec, EC_ADP_STATUS, &present);
ecbat->bat_present = ret ? false : !!(present & EC_BAT_PRESENT);
}
static inline int gaokun_psy_bat_present(struct gaokun_psy *ecbat)
{
return ecbat->bat_present;
}
static int gaokun_psy_get_bat_info(struct gaokun_psy *ecbat)
{
/* _BIX */
if (!gaokun_psy_bat_present(ecbat))
return 0;
return gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_INFO_START,
sizeof(ecbat->info), (u8 *)&ecbat->info);
}
static void gaokun_psy_update_bat_charge(struct gaokun_psy *ecbat)
{
u8 charge;
gaokun_ec_psy_read_byte(ecbat->ec, EC_BAT_STATUS, &charge);
switch (charge) {
case EC_BAT_CHARGING:
ecbat->charge_now = POWER_SUPPLY_STATUS_CHARGING;
break;
case EC_BAT_DISCHARGING:
ecbat->charge_now = POWER_SUPPLY_STATUS_DISCHARGING;
break;
case EC_BAT_FULL:
ecbat->charge_now = POWER_SUPPLY_STATUS_FULL;
break;
default:
dev_warn(ecbat->dev, "unknown charge state %d\n", charge);
}
}
static int gaokun_psy_get_bat_status(struct gaokun_psy *ecbat)
{
/* _BST */
int ret;
if (time_before(jiffies, ecbat->update_time +
msecs_to_jiffies(CACHE_TIME)))
return 0;
gaokun_psy_update_bat_charge(ecbat);
ret = gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_STATUS_START,
sizeof(ecbat->status), (u8 *)&ecbat->status);
ecbat->update_time = jiffies;
return ret;
}
static void gaokun_psy_init(struct gaokun_psy *ecbat)
{
gaokun_psy_get_bat_present(ecbat);
if (!gaokun_psy_bat_present(ecbat))
return;
gaokun_psy_get_bat_info(ecbat);
snprintf(ecbat->battery_serial, sizeof(ecbat->battery_serial),
"%d", le16_to_cpu(ecbat->info.serial_number));
gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_VENDOR,
sizeof(ecbat->battery_vendor) - 1,
ecbat->battery_vendor);
gaokun_ec_psy_multi_read(ecbat->ec, EC_BAT_MODEL,
sizeof(ecbat->battery_model) - 1,
ecbat->battery_model);
ecbat->battery_model[14] = 'A'; /* FIX UP */
}
static int gaokun_psy_get_bat_property(struct power_supply *psy,
enum power_supply_property psp,
union power_supply_propval *val)
{
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
int ret;
if (gaokun_psy_bat_present(ecbat))
gaokun_psy_get_bat_status(ecbat);
else if (psp != POWER_SUPPLY_PROP_PRESENT)
return -ENODEV;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
val->intval = ecbat->charge_now;
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = ecbat->bat_present;
break;
case POWER_SUPPLY_PROP_TECHNOLOGY:
val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
break;
case POWER_SUPPLY_PROP_CYCLE_COUNT:
val->intval = le16_to_cpu(ecbat->info.cycle_count);
break;
case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
val->intval = le16_to_cpu(ecbat->info.design_voltage) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_VOLTAGE_NOW:
val->intval = le16_to_cpu(ecbat->status.voltage_now) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_CURRENT_NOW:
val->intval = (s16)le16_to_cpu(ecbat->status.rate_now) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
val->intval = le16_to_cpu(ecbat->info.design_capacity) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_CHARGE_FULL:
val->intval = le16_to_cpu(ecbat->status.full_capacity) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_CHARGE_NOW:
val->intval = le16_to_cpu(ecbat->status.capacity_now) * MILLI_TO_MICRO;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
if (ret)
return ret;
if (psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD)
val->intval = buf[SMART_CHARGE_START];
else
val->intval = buf[SMART_CHARGE_END];
break;
case POWER_SUPPLY_PROP_CAPACITY:
val->intval = le16_to_cpu(ecbat->status.percentage_now);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = ecbat->battery_model;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = ecbat->battery_vendor;
break;
case POWER_SUPPLY_PROP_SERIAL_NUMBER:
val->strval = ecbat->battery_serial;
break;
default:
return -EINVAL;
}
return 0;
}
static int gaokun_psy_set_bat_property(struct power_supply *psy,
enum power_supply_property psp,
const union power_supply_propval *val)
{
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE];
int ret;
if (!gaokun_psy_bat_present(ecbat))
return -ENODEV;
ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, buf);
if (ret)
return ret;
switch (psp) {
/*
* Resetting another thershold makes single thersold setting more likely
* to succeed. But setting start = end makes thing strange(failure).
*/
case POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD:
buf[SMART_CHARGE_START] = val->intval;
if (buf[SMART_CHARGE_START] > buf[SMART_CHARGE_END])
buf[SMART_CHARGE_END] = buf[SMART_CHARGE_START] + 1;
return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);
case POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD:
buf[SMART_CHARGE_END] = val->intval;
if (buf[SMART_CHARGE_END] < buf[SMART_CHARGE_START])
buf[SMART_CHARGE_START] = buf[SMART_CHARGE_END] - 1;
return gaokun_ec_psy_set_smart_charge(ecbat->ec, buf);
default:
return -EINVAL;
}
return 0;
}
static int gaokun_psy_is_bat_property_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD ||
psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD;
}
static enum power_supply_property gaokun_psy_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_TECHNOLOGY,
POWER_SUPPLY_PROP_CYCLE_COUNT,
POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
POWER_SUPPLY_PROP_CHARGE_FULL,
POWER_SUPPLY_PROP_CHARGE_NOW,
POWER_SUPPLY_PROP_CHARGE_CONTROL_START_THRESHOLD,
POWER_SUPPLY_PROP_CHARGE_CONTROL_END_THRESHOLD,
POWER_SUPPLY_PROP_CAPACITY,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
POWER_SUPPLY_PROP_SERIAL_NUMBER,
};
static const struct power_supply_desc gaokun_psy_bat_desc = {
.name = "gaokun-ec-battery",
.type = POWER_SUPPLY_TYPE_BATTERY,
.get_property = gaokun_psy_get_bat_property,
.set_property = gaokun_psy_set_bat_property,
.property_is_writeable = gaokun_psy_is_bat_property_writeable,
.properties = gaokun_psy_bat_props,
.num_properties = ARRAY_SIZE(gaokun_psy_bat_props),
};
/* -------------------------------------------------------------------------- */
/* Sysfs */
/*
* Note that, HUAWEI calls them SBAC/GBAC and SBCM/GBCM in DSDT, they are likely
* Set/Get Battery Adaptive Charging and Set/Get Battery Charging Mode.
*/
/* battery adaptive charge */
static ssize_t battery_adaptive_charge_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
int ret;
bool on;
ret = gaokun_ec_psy_get_smart_charge_enable(ecbat->ec, &on);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", on);
}
static ssize_t battery_adaptive_charge_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct power_supply *psy = to_power_supply(dev);
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
int ret;
bool on;
if (kstrtobool(buf, &on))
return -EINVAL;
ret = gaokun_ec_psy_set_smart_charge_enable(ecbat->ec, on);
if (ret)
return ret;
return size;
}
static DEVICE_ATTR_RW(battery_adaptive_charge);
static inline int get_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE])
{
return buf[SMART_CHARGE_MODE] == NO_DELAY_MODE ? 0 : buf[SMART_CHARGE_DELAY];
}
static inline void
set_charge_delay(u8 buf[GAOKUN_SMART_CHARGE_DATA_SIZE], u8 delay)
{
if (delay) {
buf[SMART_CHARGE_DELAY] = delay;
buf[SMART_CHARGE_MODE] = DELAY_MODE;
} else {
/* No writing zero, there is a specific mode for it. */
buf[SMART_CHARGE_MODE] = NO_DELAY_MODE;
}
}
/* Smart charge */
static ssize_t smart_charge_delay_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
int ret;
ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
if (ret)
return ret;
return sysfs_emit(buf, "%d\n", get_charge_delay(bf));
}
static ssize_t smart_charge_delay_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
struct power_supply *psy = to_power_supply(dev);
struct gaokun_psy *ecbat = power_supply_get_drvdata(psy);
u8 bf[GAOKUN_SMART_CHARGE_DATA_SIZE];
u8 delay;
int ret;
if (kstrtou8(buf, 10, &delay))
return -EINVAL;
ret = gaokun_ec_psy_get_smart_charge(ecbat->ec, bf);
if (ret)
return ret;
set_charge_delay(bf, delay);
ret = gaokun_ec_psy_set_smart_charge(ecbat->ec, bf);
if (ret)
return ret;
return size;
}
static DEVICE_ATTR_RW(smart_charge_delay);
static struct attribute *gaokun_psy_features_attrs[] = {
&dev_attr_battery_adaptive_charge.attr,
&dev_attr_smart_charge_delay.attr,
NULL,
};
ATTRIBUTE_GROUPS(gaokun_psy_features);
static int gaokun_psy_notify(struct notifier_block *nb,
unsigned long action, void *data)
{
struct gaokun_psy *ecbat = container_of(nb, struct gaokun_psy, nb);
switch (action) {
case EC_EVENT_BAT_A2:
case EC_EVENT_BAT_B1:
gaokun_psy_get_bat_info(ecbat);
return NOTIFY_OK;
case EC_EVENT_BAT_A0:
gaokun_psy_get_adp_status(ecbat);
power_supply_changed(ecbat->adp_psy);
msleep(10);
fallthrough;
case EC_EVENT_BAT_A1:
case EC_EVENT_BAT_A3:
if (action == EC_EVENT_BAT_A3) {
gaokun_psy_get_bat_info(ecbat);
msleep(100);
}
gaokun_psy_get_bat_status(ecbat);
power_supply_changed(ecbat->bat_psy);
return NOTIFY_OK;
default:
return NOTIFY_DONE;
}
}
static int gaokun_psy_probe(struct auxiliary_device *adev,
const struct auxiliary_device_id *id)
{
struct gaokun_ec *ec = adev->dev.platform_data;
struct power_supply_config psy_cfg = {};
struct device *dev = &adev->dev;
struct gaokun_psy *ecbat;
ecbat = devm_kzalloc(&adev->dev, sizeof(*ecbat), GFP_KERNEL);
if (!ecbat)
return -ENOMEM;
ecbat->ec = ec;
ecbat->dev = dev;
ecbat->nb.notifier_call = gaokun_psy_notify;
auxiliary_set_drvdata(adev, ecbat);
psy_cfg.drv_data = ecbat;
ecbat->adp_psy = devm_power_supply_register(dev, &gaokun_psy_adp_desc,
&psy_cfg);
if (IS_ERR(ecbat->adp_psy))
return dev_err_probe(dev, PTR_ERR(ecbat->adp_psy),
"Failed to register AC power supply\n");
psy_cfg.supplied_to = (char **)&gaokun_psy_bat_desc.name;
psy_cfg.num_supplicants = 1;
psy_cfg.no_wakeup_source = true;
psy_cfg.attr_grp = gaokun_psy_features_groups;
ecbat->bat_psy = devm_power_supply_register(dev, &gaokun_psy_bat_desc,
&psy_cfg);
if (IS_ERR(ecbat->bat_psy))
return dev_err_probe(dev, PTR_ERR(ecbat->bat_psy),
"Failed to register battery power supply\n");
gaokun_psy_init(ecbat);
return gaokun_ec_register_notify(ec, &ecbat->nb);
}
static void gaokun_psy_remove(struct auxiliary_device *adev)
{
struct gaokun_psy *ecbat = auxiliary_get_drvdata(adev);
gaokun_ec_unregister_notify(ecbat->ec, &ecbat->nb);
}
static const struct auxiliary_device_id gaokun_psy_id_table[] = {
{ .name = GAOKUN_MOD_NAME "." GAOKUN_DEV_PSY, },
{}
};
MODULE_DEVICE_TABLE(auxiliary, gaokun_psy_id_table);
static struct auxiliary_driver gaokun_psy_driver = {
.name = GAOKUN_DEV_PSY,
.id_table = gaokun_psy_id_table,
.probe = gaokun_psy_probe,
.remove = gaokun_psy_remove,
};
module_auxiliary_driver(gaokun_psy_driver);
MODULE_DESCRIPTION("HUAWEI Matebook E Go psy driver");
MODULE_LICENSE("GPL");

View File

@ -410,8 +410,9 @@ static int max17040_get_property(struct power_supply *psy,
if (!chip->channel_temp)
return -ENODATA;
iio_read_channel_processed_scale(chip->channel_temp,
&val->intval, 10);
iio_read_channel_processed(chip->channel_temp, &val->intval);
val->intval /= 100; /* Convert from milli- to deci-degree */
break;
default:
return -EINVAL;

View File

@ -545,20 +545,28 @@ static int max77705_charger_probe(struct i2c_client *i2c)
return dev_err_probe(dev, ret, "failed to add irq chip\n");
chg->wqueue = create_singlethread_workqueue(dev_name(dev));
if (IS_ERR(chg->wqueue))
return dev_err_probe(dev, PTR_ERR(chg->wqueue), "failed to create workqueue\n");
if (!chg->wqueue)
return dev_err_probe(dev, -ENOMEM, "failed to create workqueue\n");
ret = devm_work_autocancel(dev, &chg->chgin_work, max77705_chgin_isr_work);
if (ret)
return dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
if (ret) {
dev_err_probe(dev, ret, "failed to initialize interrupt work\n");
goto destroy_wq;
}
max77705_charger_initialize(chg);
ret = max77705_charger_enable(chg);
if (ret)
return dev_err_probe(dev, ret, "failed to enable charge\n");
if (ret) {
dev_err_probe(dev, ret, "failed to enable charge\n");
goto destroy_wq;
}
return devm_add_action_or_reset(dev, max77705_charger_disable, chg);
destroy_wq:
destroy_workqueue(chg->wqueue);
return ret;
}
static const struct of_device_id max77705_charger_of_match[] = {

View File

@ -0,0 +1,752 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/devm-helpers.h>
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/extcon.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/of_graph.h>
#include <linux/property.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <linux/pm.h>
#include <linux/power_supply.h>
#include <linux/regmap.h>
#include <linux/sysfs.h>
#include <linux/types.h>
#define MAX8971_REG_CHGINT 0x0f
#define MAX8971_REG_CHG_RST BIT(0)
#define MAX8971_REG_CHGINT_MASK 0x01
#define MAX8971_AICL_MASK BIT(7)
#define MAX8971_REG_CHG_STAT 0x02
#define MAX8971_CHG_MASK BIT(3)
#define MAX8971_REG_DETAILS1 0x03
#define MAX8971_REG_DETAILS2 0x04
#define MAX8971_REG_CHGCNTL1 0x05
#define MAX8971_REG_FCHGCRNT 0x06
#define MAX8971_REG_DCCRNT 0x07
#define MAX8971_CHGRSTRT_MASK BIT(6)
#define MAX8971_REG_TOPOFF 0x08
#define MAX8971_REG_TEMPREG 0x09
#define MAX8971_REG_PROTCMD 0x0a
#define MAX8971_CHGPROT_LOCKED 0x00
#define MAX8971_CHGPROT_UNLOCKED 0x03
#define MAX8971_FCHGT_DEFAULT 2
#define MAX8971_TOPOFFT_DEFAULT 3
static const char *max8971_manufacturer = "Maxim Integrated";
static const char *max8971_model = "MAX8971";
enum max8971_charging_state {
MAX8971_CHARGING_DEAD_BATTERY,
MAX8971_CHARGING_PREQUALIFICATION,
MAX8971_CHARGING_FAST_CONST_CURRENT,
MAX8971_CHARGING_FAST_CONST_VOLTAGE,
MAX8971_CHARGING_TOP_OFF,
MAX8971_CHARGING_DONE,
MAX8971_CHARGING_TIMER_FAULT,
MAX8971_CHARGING_SUSPENDED_THERMAL,
MAX8971_CHARGING_OFF,
MAX8971_CHARGING_THERMAL_LOOP,
};
enum max8971_health_state {
MAX8971_HEALTH_UNKNOWN,
MAX8971_HEALTH_COLD,
MAX8971_HEALTH_COOL,
MAX8971_HEALTH_WARM,
MAX8971_HEALTH_HOT,
MAX8971_HEALTH_OVERHEAT,
};
/* Fast-Charge current limit, 250..1550 mA, 50 mA steps */
#define MAX8971_CHG_CC_STEP 50000U
#define MAX8971_CHG_CC_MIN 250000U
#define MAX8971_CHG_CC_MAX 1550000U
/* Input current limit, 250..1500 mA, 25 mA steps */
#define MAX8971_DCILMT_STEP 25000U
#define MAX8971_DCILMT_MIN 250000U
#define MAX8971_DCILMT_MAX 1500000U
enum max8971_field_idx {
THM_DTLS, /* DETAILS1 */
BAT_DTLS, CHG_DTLS, /* DETAILS2 */
CHG_CC, FCHG_T, /* FCHGCRNT */
DCI_LMT, /* DCCRNT */
TOPOFF_T, TOPOFF_S, /* TOPOFF */
CPROT, /* PROTCMD */
MAX8971_N_REGMAP_FIELDS
};
static const struct reg_field max8971_reg_field[MAX8971_N_REGMAP_FIELDS] = {
[THM_DTLS] = REG_FIELD(MAX8971_REG_DETAILS1, 0, 2),
[BAT_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 4, 5),
[CHG_DTLS] = REG_FIELD(MAX8971_REG_DETAILS2, 0, 3),
[CHG_CC] = REG_FIELD(MAX8971_REG_FCHGCRNT, 0, 4),
[FCHG_T] = REG_FIELD(MAX8971_REG_FCHGCRNT, 5, 7),
[DCI_LMT] = REG_FIELD(MAX8971_REG_DCCRNT, 0, 5),
[TOPOFF_T] = REG_FIELD(MAX8971_REG_TOPOFF, 5, 7),
[TOPOFF_S] = REG_FIELD(MAX8971_REG_TOPOFF, 2, 3),
[CPROT] = REG_FIELD(MAX8971_REG_PROTCMD, 2, 3),
};
static const struct regmap_config max8971_regmap_config = {
.reg_bits = 8,
.val_bits = 8,
.max_register = MAX8971_REG_CHGINT,
};
struct max8971_data {
struct device *dev;
struct power_supply *psy_mains;
struct extcon_dev *edev;
struct notifier_block extcon_nb;
struct delayed_work extcon_work;
struct regmap *regmap;
struct regmap_field *rfield[MAX8971_N_REGMAP_FIELDS];
enum power_supply_usb_type usb_type;
u32 fchgt;
u32 tofft;
u32 toffs;
bool present;
};
static int max8971_get_status(struct max8971_data *priv, int *val)
{
u32 regval;
int err;
err = regmap_field_read(priv->rfield[CHG_DTLS], &regval);
if (err)
return err;
switch (regval) {
case MAX8971_CHARGING_DEAD_BATTERY:
case MAX8971_CHARGING_PREQUALIFICATION:
case MAX8971_CHARGING_FAST_CONST_CURRENT:
case MAX8971_CHARGING_FAST_CONST_VOLTAGE:
case MAX8971_CHARGING_TOP_OFF:
case MAX8971_CHARGING_THERMAL_LOOP:
*val = POWER_SUPPLY_STATUS_CHARGING;
break;
case MAX8971_CHARGING_DONE:
*val = POWER_SUPPLY_STATUS_FULL;
break;
case MAX8971_CHARGING_TIMER_FAULT:
*val = POWER_SUPPLY_STATUS_NOT_CHARGING;
break;
case MAX8971_CHARGING_OFF:
case MAX8971_CHARGING_SUSPENDED_THERMAL:
*val = POWER_SUPPLY_STATUS_DISCHARGING;
break;
default:
*val = POWER_SUPPLY_STATUS_UNKNOWN;
}
return 0;
}
static int max8971_get_charge_type(struct max8971_data *priv, int *val)
{
u32 regval;
int err;
err = regmap_field_read(priv->rfield[CHG_DTLS], &regval);
if (err)
return err;
switch (regval) {
case MAX8971_CHARGING_DEAD_BATTERY:
case MAX8971_CHARGING_PREQUALIFICATION:
*val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
break;
case MAX8971_CHARGING_FAST_CONST_CURRENT:
case MAX8971_CHARGING_FAST_CONST_VOLTAGE:
*val = POWER_SUPPLY_CHARGE_TYPE_FAST;
break;
case MAX8971_CHARGING_TOP_OFF:
case MAX8971_CHARGING_THERMAL_LOOP:
*val = POWER_SUPPLY_CHARGE_TYPE_STANDARD;
break;
case MAX8971_CHARGING_DONE:
case MAX8971_CHARGING_TIMER_FAULT:
case MAX8971_CHARGING_SUSPENDED_THERMAL:
case MAX8971_CHARGING_OFF:
*val = POWER_SUPPLY_CHARGE_TYPE_NONE;
break;
default:
*val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
}
return 0;
}
static int max8971_get_health(struct max8971_data *priv, int *val)
{
u32 regval;
int err;
err = regmap_field_read(priv->rfield[THM_DTLS], &regval);
if (err)
return err;
switch (regval) {
case MAX8971_HEALTH_COLD:
*val = POWER_SUPPLY_HEALTH_COLD;
break;
case MAX8971_HEALTH_COOL:
*val = POWER_SUPPLY_HEALTH_COOL;
break;
case MAX8971_HEALTH_WARM:
*val = POWER_SUPPLY_HEALTH_GOOD;
break;
case MAX8971_HEALTH_HOT:
*val = POWER_SUPPLY_HEALTH_HOT;
break;
case MAX8971_HEALTH_OVERHEAT:
*val = POWER_SUPPLY_HEALTH_OVERHEAT;
break;
case MAX8971_HEALTH_UNKNOWN:
default:
*val = POWER_SUPPLY_HEALTH_UNKNOWN;
}
return 0;
}
static int max8971_get_online(struct max8971_data *priv, int *val)
{
u32 regval;
int err;
err = regmap_read(priv->regmap, MAX8971_REG_CHG_STAT, &regval);
if (err)
return err;
if (priv->present)
/* CHG_OK bit is 0 when charger is online */
*val = !(regval & MAX8971_CHG_MASK);
else
*val = priv->present;
return 0;
}
static int max8971_get_integer(struct max8971_data *priv, enum max8971_field_idx fidx,
u32 clamp_min, u32 clamp_max, u32 mult, int *val)
{
u32 regval;
int err;
err = regmap_field_read(priv->rfield[fidx], &regval);
if (err)
return err;
*val = clamp_val(regval * mult, clamp_min, clamp_max);
return 0;
}
static int max8971_set_integer(struct max8971_data *priv, enum max8971_field_idx fidx,
u32 clamp_min, u32 clamp_max, u32 div, int val)
{
u32 regval;
regval = clamp_val(val, clamp_min, clamp_max) / div;
return regmap_field_write(priv->rfield[fidx], regval);
}
static int max8971_get_property(struct power_supply *psy, enum power_supply_property psp,
union power_supply_propval *val)
{
struct max8971_data *priv = power_supply_get_drvdata(psy);
int err = 0;
switch (psp) {
case POWER_SUPPLY_PROP_STATUS:
err = max8971_get_status(priv, &val->intval);
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
err = max8971_get_charge_type(priv, &val->intval);
break;
case POWER_SUPPLY_PROP_USB_TYPE:
val->intval = priv->usb_type;
break;
case POWER_SUPPLY_PROP_HEALTH:
err = max8971_get_health(priv, &val->intval);
break;
case POWER_SUPPLY_PROP_ONLINE:
err = max8971_get_online(priv, &val->intval);
break;
case POWER_SUPPLY_PROP_PRESENT:
val->intval = priv->present;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
val->intval = MAX8971_CHG_CC_MAX;
break;
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
err = max8971_get_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX,
MAX8971_CHG_CC_STEP, &val->intval);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
err = max8971_get_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX,
MAX8971_DCILMT_STEP, &val->intval);
break;
case POWER_SUPPLY_PROP_MODEL_NAME:
val->strval = max8971_model;
break;
case POWER_SUPPLY_PROP_MANUFACTURER:
val->strval = max8971_manufacturer;
break;
default:
err = -EINVAL;
}
return err;
}
static int max8971_set_property(struct power_supply *psy, enum power_supply_property psp,
const union power_supply_propval *val)
{
struct max8971_data *priv = power_supply_get_drvdata(psy);
int err = 0;
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
err = max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX,
MAX8971_CHG_CC_STEP, val->intval);
break;
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
err = max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX,
MAX8971_DCILMT_STEP, val->intval);
break;
default:
err = -EINVAL;
}
return err;
};
static int max8971_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
switch (psp) {
case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
return true;
default:
return false;
}
}
static enum power_supply_property max8971_properties[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_CHARGE_TYPE,
POWER_SUPPLY_PROP_USB_TYPE,
POWER_SUPPLY_PROP_HEALTH,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_PRESENT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
POWER_SUPPLY_PROP_MODEL_NAME,
POWER_SUPPLY_PROP_MANUFACTURER,
};
static const struct power_supply_desc max8971_charger_desc = {
.name = "max8971-charger",
.type = POWER_SUPPLY_TYPE_USB,
.usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) |
BIT(POWER_SUPPLY_USB_TYPE_SDP) |
BIT(POWER_SUPPLY_USB_TYPE_DCP) |
BIT(POWER_SUPPLY_USB_TYPE_CDP) |
BIT(POWER_SUPPLY_USB_TYPE_ACA),
.properties = max8971_properties,
.num_properties = ARRAY_SIZE(max8971_properties),
.get_property = max8971_get_property,
.set_property = max8971_set_property,
.property_is_writeable = max8971_property_is_writeable,
};
static void max8971_update_config(struct max8971_data *priv)
{
regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED);
if (priv->fchgt != MAX8971_FCHGT_DEFAULT)
regmap_field_write(priv->rfield[FCHG_T], priv->fchgt);
regmap_write_bits(priv->regmap, MAX8971_REG_DCCRNT, MAX8971_CHGRSTRT_MASK,
MAX8971_CHGRSTRT_MASK);
if (priv->tofft != MAX8971_TOPOFFT_DEFAULT)
regmap_field_write(priv->rfield[TOPOFF_T], priv->tofft);
if (priv->toffs)
regmap_field_write(priv->rfield[TOPOFF_S], priv->toffs);
regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED);
}
static ssize_t fast_charge_timer_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
u32 regval;
int err;
err = regmap_field_read(priv->rfield[FCHG_T], &regval);
if (err)
return err;
switch (regval) {
case 0x1 ... 0x7:
/* Time is off by 3 hours comparing to value */
regval += 3;
break;
case 0x0:
default:
regval = 0;
break;
}
return sysfs_emit(buf, "%u\n", regval);
}
static ssize_t fast_charge_timer_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
unsigned long hours;
int val, err;
err = kstrtoul(buf, 10, &hours);
if (err)
return err;
val = hours - 3;
if (val <= 0 || val > 7)
priv->fchgt = 0;
else
priv->fchgt = val;
max8971_update_config(priv);
return count;
}
static ssize_t top_off_threshold_current_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
u32 regval, val;
int err;
err = regmap_field_read(priv->rfield[TOPOFF_S], &regval);
if (err)
return err;
/* 50uA start with 50uA step */
val = regval * 50 + 50;
val *= 1000;
return sysfs_emit(buf, "%u\n", val);
}
static ssize_t top_off_threshold_current_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
unsigned long uamp;
int err;
err = kstrtoul(buf, 10, &uamp);
if (err)
return err;
if (uamp < 50000 || uamp > 200000)
return -EINVAL;
priv->toffs = uamp / 50000 - 1;
max8971_update_config(priv);
return count;
}
static ssize_t top_off_timer_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
u32 regval;
int err;
err = regmap_field_read(priv->rfield[TOPOFF_T], &regval);
if (err)
return err;
/* 10 min intervals */
regval *= 10;
return sysfs_emit(buf, "%u\n", regval);
}
static ssize_t top_off_timer_store(struct device *dev, struct device_attribute *attr,
const char *buf, size_t count)
{
struct power_supply *psy = to_power_supply(dev);
struct max8971_data *priv = power_supply_get_drvdata(psy);
unsigned long minutes;
int err;
err = kstrtoul(buf, 10, &minutes);
if (err)
return err;
if (minutes > 70)
return -EINVAL;
priv->tofft = minutes / 10;
max8971_update_config(priv);
return count;
}
static DEVICE_ATTR_RW(fast_charge_timer);
static DEVICE_ATTR_RW(top_off_threshold_current);
static DEVICE_ATTR_RW(top_off_timer);
static struct attribute *max8971_attrs[] = {
&dev_attr_fast_charge_timer.attr,
&dev_attr_top_off_threshold_current.attr,
&dev_attr_top_off_timer.attr,
NULL
};
ATTRIBUTE_GROUPS(max8971);
static void max8971_extcon_evt_worker(struct work_struct *work)
{
struct max8971_data *priv =
container_of(work, struct max8971_data, extcon_work.work);
struct device *dev = priv->dev;
struct extcon_dev *edev = priv->edev;
u32 chgcc, dcilmt;
if (extcon_get_state(edev, EXTCON_CHG_USB_SDP) > 0) {
dev_dbg(dev, "USB SDP charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
chgcc = 500000;
dcilmt = 500000;
} else if (extcon_get_state(edev, EXTCON_USB) > 0) {
dev_dbg(dev, "USB charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
chgcc = 500000;
dcilmt = 500000;
} else if (extcon_get_state(edev, EXTCON_DISP_MHL) > 0) {
dev_dbg(dev, "MHL plug is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_SDP;
chgcc = 500000;
dcilmt = 500000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_DCP) > 0) {
dev_dbg(dev, "USB DCP charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_DCP;
chgcc = 900000;
dcilmt = 1200000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_FAST) > 0) {
dev_dbg(dev, "USB FAST charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
chgcc = 900000;
dcilmt = 1200000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_SLOW) > 0) {
dev_dbg(dev, "USB SLOW charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_ACA;
chgcc = 900000;
dcilmt = 1200000;
} else if (extcon_get_state(edev, EXTCON_CHG_USB_CDP) > 0) {
dev_dbg(dev, "USB CDP charger is connected\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_CDP;
chgcc = 900000;
dcilmt = 1200000;
} else {
dev_dbg(dev, "USB state is unknown\n");
priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
return;
}
regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_UNLOCKED);
max8971_set_integer(priv, CHG_CC, MAX8971_CHG_CC_MIN, MAX8971_CHG_CC_MAX,
MAX8971_CHG_CC_STEP, chgcc);
max8971_set_integer(priv, DCI_LMT, MAX8971_DCILMT_MIN, MAX8971_DCILMT_MAX,
MAX8971_DCILMT_STEP, dcilmt);
regmap_field_write(priv->rfield[CPROT], MAX8971_CHGPROT_LOCKED);
}
static int extcon_get_charger_type(struct notifier_block *nb,
unsigned long state, void *data)
{
struct max8971_data *priv =
container_of(nb, struct max8971_data, extcon_nb);
schedule_delayed_work(&priv->extcon_work, 0);
return NOTIFY_OK;
}
static irqreturn_t max8971_interrupt(int irq, void *dev_id)
{
struct max8971_data *priv = dev_id;
struct device *dev = priv->dev;
int err, state;
err = regmap_read(priv->regmap, MAX8971_REG_CHGINT, &state);
if (err)
dev_err(dev, "interrupt reg read failed %d\n", err);
err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK,
MAX8971_AICL_MASK, MAX8971_AICL_MASK);
if (err)
dev_err(dev, "failed to mask IRQ\n");
/* set presence prop */
priv->present = state & MAX8971_REG_CHG_RST;
/* on every plug chip resets to default */
if (priv->present)
max8971_update_config(priv);
/* update supply status */
power_supply_changed(priv->psy_mains);
return IRQ_HANDLED;
}
static int max8971_probe(struct i2c_client *client)
{
struct device *dev = &client->dev;
struct max8971_data *priv;
struct device_node *extcon;
struct power_supply_config cfg = { };
int err, i;
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->dev = dev;
priv->usb_type = POWER_SUPPLY_USB_TYPE_UNKNOWN;
i2c_set_clientdata(client, priv);
priv->regmap = devm_regmap_init_i2c(client, &max8971_regmap_config);
if (IS_ERR(priv->regmap))
return dev_err_probe(dev, PTR_ERR(priv->regmap), "cannot allocate regmap\n");
for (i = 0; i < MAX8971_N_REGMAP_FIELDS; i++) {
priv->rfield[i] = devm_regmap_field_alloc(dev, priv->regmap, max8971_reg_field[i]);
if (IS_ERR(priv->rfield[i]))
return dev_err_probe(dev, PTR_ERR(priv->rfield[i]),
"cannot allocate regmap field\n");
}
cfg.attr_grp = max8971_groups;
cfg.drv_data = priv;
cfg.fwnode = dev_fwnode(dev);
priv->psy_mains = devm_power_supply_register(dev, &max8971_charger_desc, &cfg);
if (IS_ERR(priv->psy_mains))
return dev_err_probe(dev, PTR_ERR(priv->psy_mains),
"failed to register mains supply\n");
err = regmap_write_bits(priv->regmap, MAX8971_REG_CHGINT_MASK, MAX8971_AICL_MASK,
MAX8971_AICL_MASK);
if (err)
return dev_err_probe(dev, err, "failed to mask IRQ\n");
err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt,
IRQF_ONESHOT | IRQF_SHARED, client->name, priv);
if (err)
return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq);
extcon = of_graph_get_remote_node(dev->of_node, -1, -1);
if (!extcon)
return 0;
priv->edev = extcon_find_edev_by_node(extcon);
of_node_put(extcon);
if (IS_ERR(priv->edev))
return dev_err_probe(dev, PTR_ERR(priv->edev), "failed to find extcon\n");
err = devm_delayed_work_autocancel(dev, &priv->extcon_work,
max8971_extcon_evt_worker);
if (err)
return dev_err_probe(dev, err, "failed to add extcon evt stop action\n");
priv->extcon_nb.notifier_call = extcon_get_charger_type;
err = devm_extcon_register_notifier_all(dev, priv->edev, &priv->extcon_nb);
if (err)
return dev_err_probe(dev, err, "failed to register notifier\n");
/* Initial configuration work with 1 sec delay */
schedule_delayed_work(&priv->extcon_work, msecs_to_jiffies(1000));
return 0;
}
static int __maybe_unused max8971_resume(struct device *dev)
{
struct i2c_client *client = to_i2c_client(dev);
struct max8971_data *priv = i2c_get_clientdata(client);
irq_wake_thread(client->irq, priv);
return 0;
}
static SIMPLE_DEV_PM_OPS(max8971_pm_ops, NULL, max8971_resume);
static const struct of_device_id max8971_match_ids[] = {
{ .compatible = "maxim,max8971" },
{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, max8971_match_ids);
static const struct i2c_device_id max8971_i2c_id[] = {
{ "max8971" },
{ }
};
MODULE_DEVICE_TABLE(i2c, max8971_i2c_id);
static struct i2c_driver max8971_driver = {
.driver = {
.name = "max8971-charger",
.of_match_table = max8971_match_ids,
.pm = &max8971_pm_ops,
},
.probe = max8971_probe,
.id_table = max8971_i2c_id,
};
module_i2c_driver(max8971_driver);
MODULE_AUTHOR("Svyatoslav Ryhel <clamor95@gmail.com>");
MODULE_DESCRIPTION("MAX8971 Charger Driver");
MODULE_LICENSE("GPL");

View File

@ -321,6 +321,27 @@ static ssize_t power_supply_show_charge_behaviour(struct device *dev,
value->intval, buf);
}
static ssize_t power_supply_show_charge_types(struct device *dev,
struct power_supply *psy,
enum power_supply_charge_type current_type,
char *buf)
{
struct power_supply_ext_registration *reg;
scoped_guard(rwsem_read, &psy->extensions_sem) {
power_supply_for_each_extension(reg, psy) {
if (power_supply_ext_has_property(reg->ext,
POWER_SUPPLY_PROP_CHARGE_TYPES))
return power_supply_charge_types_show(dev,
reg->ext->charge_types,
current_type, buf);
}
}
return power_supply_charge_types_show(dev, psy->desc->charge_types,
current_type, buf);
}
static ssize_t power_supply_format_property(struct device *dev,
bool uevent,
struct device_attribute *attr,
@ -365,7 +386,7 @@ static ssize_t power_supply_format_property(struct device *dev,
case POWER_SUPPLY_PROP_CHARGE_TYPES:
if (uevent) /* no possible values in uevents */
goto default_format;
ret = power_supply_charge_types_show(dev, psy->desc->charge_types,
ret = power_supply_show_charge_types(dev, psy,
value.intval, buf);
break;
case POWER_SUPPLY_PROP_MODEL_NAME ... POWER_SUPPLY_PROP_SERIAL_NUMBER:

View File

@ -1088,7 +1088,7 @@ static int rk817_charger_probe(struct platform_device *pdev)
rk817_bat_calib_vol(charger);
pscfg.drv_data = charger;
pscfg.fwnode = node ? &node->fwnode : NULL;
pscfg.fwnode = &node->fwnode;
/*
* Get sample resistor value. Note only values of 10000 or 20000

View File

@ -192,12 +192,12 @@ static const struct reg_field rt9471_reg_fields[F_MAX_FIELDS] = {
};
static const struct linear_range rt9471_chg_ranges[RT9471_MAX_RANGES] = {
[RT9471_RANGE_AICR] = { .min = 50000, .min_sel = 1, .max_sel = 63, .step = 50000 },
[RT9471_RANGE_MIVR] = { .min = 3900000, .min_sel = 0, .max_sel = 15, .step = 100000 },
[RT9471_RANGE_IPRE] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 },
[RT9471_RANGE_VCHG] = { .min = 3900000, .min_sel = 0, .max_sel = 80, .step = 10000 },
[RT9471_RANGE_ICHG] = { .min = 0, .min_sel = 0, .max_sel = 63, .step = 50000 },
[RT9471_RANGE_IEOC] = { .min = 50000, .min_sel = 0, .max_sel = 15, .step = 50000 },
[RT9471_RANGE_AICR] = LINEAR_RANGE(50000, 1, 63, 50000),
[RT9471_RANGE_MIVR] = LINEAR_RANGE(3900000, 0, 15, 100000),
[RT9471_RANGE_IPRE] = LINEAR_RANGE(50000, 0, 15, 50000),
[RT9471_RANGE_VCHG] = LINEAR_RANGE(3900000, 0, 80, 10000),
[RT9471_RANGE_ICHG] = LINEAR_RANGE(0, 0, 63, 50000),
[RT9471_RANGE_IEOC] = LINEAR_RANGE(50000, 0, 15, 50000),
};
static int rt9471_set_value_by_field_range(struct rt9471_chip *chip,

View File

@ -37,6 +37,8 @@ static int battery_charge_counter = -1000;
static int battery_current = -1600;
static enum power_supply_charge_behaviour battery_charge_behaviour =
POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO;
static enum power_supply_charge_type battery_charge_types =
POWER_SUPPLY_CHARGE_TYPE_STANDARD;
static bool battery_extension;
static bool module_initialized;
@ -87,7 +89,7 @@ static int test_power_get_battery_property(struct power_supply *psy,
val->intval = battery_status;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPE:
val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
val->intval = battery_charge_types;
break;
case POWER_SUPPLY_PROP_HEALTH:
val->intval = battery_health;
@ -129,6 +131,9 @@ static int test_power_get_battery_property(struct power_supply *psy,
case POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR:
val->intval = battery_charge_behaviour;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPES:
val->intval = battery_charge_types;
break;
default:
pr_info("%s: some properties deliberately report errors.\n",
__func__);
@ -140,7 +145,7 @@ static int test_power_get_battery_property(struct power_supply *psy,
static int test_power_battery_property_is_writeable(struct power_supply *psy,
enum power_supply_property psp)
{
return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR;
return psp == POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR || psp == POWER_SUPPLY_PROP_CHARGE_TYPES;
}
static int test_power_set_battery_property(struct power_supply *psy,
@ -156,6 +161,14 @@ static int test_power_set_battery_property(struct power_supply *psy,
}
battery_charge_behaviour = val->intval;
break;
case POWER_SUPPLY_PROP_CHARGE_TYPES:
if (val->intval < 0 ||
val->intval >= BITS_PER_TYPE(typeof(psy->desc->charge_types)) ||
!(BIT(val->intval) & psy->desc->charge_types)) {
return -EINVAL;
}
battery_charge_types = val->intval;
break;
default:
return -EINVAL;
}
@ -188,6 +201,7 @@ static enum power_supply_property test_power_battery_props[] = {
POWER_SUPPLY_PROP_CURRENT_AVG,
POWER_SUPPLY_PROP_CURRENT_NOW,
POWER_SUPPLY_PROP_CHARGE_BEHAVIOUR,
POWER_SUPPLY_PROP_CHARGE_TYPES,
};
static char *test_power_ac_supplied_to[] = {
@ -215,6 +229,8 @@ static const struct power_supply_desc test_power_desc[] = {
.charge_behaviours = BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_AUTO)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_INHIBIT_CHARGE)
| BIT(POWER_SUPPLY_CHARGE_BEHAVIOUR_FORCE_DISCHARGE),
.charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_STANDARD)
| BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE)
},
[TEST_USB] = {
.name = "test_usb",

View File

@ -89,7 +89,7 @@ static int wm831x_wall_get_prop(struct power_supply *psy,
return ret;
}
static enum power_supply_property wm831x_wall_props[] = {
static const enum power_supply_property wm831x_wall_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
@ -120,7 +120,7 @@ static int wm831x_usb_get_prop(struct power_supply *psy,
return ret;
}
static enum power_supply_property wm831x_usb_props[] = {
static const enum power_supply_property wm831x_usb_props[] = {
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
};
@ -171,21 +171,21 @@ struct chg_map {
int reg_val;
};
static struct chg_map trickle_ilims[] = {
static const struct chg_map trickle_ilims[] = {
{ 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT },
{ 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT },
};
static struct chg_map vsels[] = {
static const struct chg_map vsels[] = {
{ 4050, 0 << WM831X_CHG_VSEL_SHIFT },
{ 4100, 1 << WM831X_CHG_VSEL_SHIFT },
{ 4150, 2 << WM831X_CHG_VSEL_SHIFT },
{ 4200, 3 << WM831X_CHG_VSEL_SHIFT },
};
static struct chg_map fast_ilims[] = {
static const struct chg_map fast_ilims[] = {
{ 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT },
{ 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT },
{ 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT },
@ -204,7 +204,7 @@ static struct chg_map fast_ilims[] = {
{ 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT },
};
static struct chg_map eoc_iterms[] = {
static const struct chg_map eoc_iterms[] = {
{ 20, 0 << WM831X_CHG_ITERM_SHIFT },
{ 30, 1 << WM831X_CHG_ITERM_SHIFT },
{ 40, 2 << WM831X_CHG_ITERM_SHIFT },
@ -215,7 +215,7 @@ static struct chg_map eoc_iterms[] = {
{ 90, 7 << WM831X_CHG_ITERM_SHIFT },
};
static struct chg_map chg_times[] = {
static const struct chg_map chg_times[] = {
{ 60, 0 << WM831X_CHG_TIME_SHIFT },
{ 90, 1 << WM831X_CHG_TIME_SHIFT },
{ 120, 2 << WM831X_CHG_TIME_SHIFT },
@ -235,7 +235,7 @@ static struct chg_map chg_times[] = {
};
static void wm831x_battery_apply_config(struct wm831x *wm831x,
struct chg_map *map, int count, int val,
const struct chg_map *map, int count, int val,
int *reg, const char *name,
const char *units)
{
@ -462,7 +462,7 @@ static int wm831x_bat_get_prop(struct power_supply *psy,
return ret;
}
static enum power_supply_property wm831x_bat_props[] = {
static const enum power_supply_property wm831x_bat_props[] = {
POWER_SUPPLY_PROP_STATUS,
POWER_SUPPLY_PROP_ONLINE,
POWER_SUPPLY_PROP_VOLTAGE_NOW,
@ -470,7 +470,7 @@ static enum power_supply_property wm831x_bat_props[] = {
POWER_SUPPLY_PROP_CHARGE_TYPE,
};
static const char *wm831x_bat_irqs[] = {
static const char * const wm831x_bat_irqs[] = {
"BATT HOT",
"BATT COLD",
"BATT FAIL",

View File

@ -288,6 +288,7 @@ struct power_supply_desc {
struct power_supply_ext {
const char *const name;
u8 charge_behaviours;
u32 charge_types;
const enum power_supply_property *properties;
size_t num_properties;