* [PATCH v5 0/5] Introduce MAX77759 charger driver
@ 2026-02-03 22:50 Amit Sunil Dhamne via B4 Relay
2026-02-03 22:50 ` [PATCH v5 1/5] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property Amit Sunil Dhamne via B4 Relay
` (4 more replies)
0 siblings, 5 replies; 11+ messages in thread
From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw)
To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman,
Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin,
Tudor Ambarus, Alim Akhtar
Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel,
linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne,
Krzysztof Kozlowski
MAX77759 PMIC is used in Pixel 6 and 6 Pro (Oriole/Raven) boards.
One of the functions of the MAX77759 PMIC is a battery charger. This
patchset introduces a driver for this function. One of the unique
features of this charger driver is that it works with a USB input where
the Type-C controller is TCPCI based.
Changes to the board files will follow soon once this patchset is reviewed.
For reference to the MAX77759 MFD based patchset (present in upstream):
https://lore.kernel.org/all/20250509-max77759-mfd-v10-0-962ac15ee3ef@linaro.org/
Dependency list:
Patch 5 depends on Patch 4 which depends on Patch 3.
Signed-off-by: Amit Sunil Dhamne <amitsd@google.com>
---
Changes in v5:
- Use linear_range library instead of reinventing it. (André Draszik)
- This requires a selector_max value so modified mfd/max77759.h to
include it for CHGCC and CHG_ILIM.
- Removed "reviewed-by" for Patch 3 (mfd) due to above
- Use asnyc probe type. (André Draszik)
- Retry mechanism for psy_work. (André Draszik)
- Minor nits (André Draszik):
- Use named initializers for instantiating structs.
- Use static qualifier for `psy_name` variable.
- Refactor if-else ladder to remove else handling if return in prior
"if" loop.
- Remove redundant `unlikely`.
- Link to v4: https://lore.kernel.org/r/20260121-max77759-charger-v4-0-694234c8ded1@google.com
Changes in v4:
- Removed a stray tabspace in mfd/max77759.h. (André Draszik)
- Fixed the following issues in Patch 4/5 (André Draszik):
- Re-order Kconfig entry
- Refactored to not use global variable
- Use of clamp() to clamp values instead of duplicating logic
- Return IRQ_NONE for unhandled irqs or error conditions
- Remove debug messages in irq handler
- Refactor code to use dev_err_probe in *_init_irqhandler()
- Remove unneeded irq_flags
- Check return values of regmap ops
- Other nits like newlines, not using greedy init, using print stmnts
- Link to v3: https://lore.kernel.org/r/20251227-max77759-charger-v3-0-54e664f5ca92@google.com
Changes in v3:
- Had incorrectly folded the charger sub-device with the pmic parent.
Corrected it. (Krzysztof Kozlowski)
- Link to v2: https://lore.kernel.org/r/20251218-max77759-charger-v2-0-2b259980a686@google.com
Changes in v2:
- Fold charger binding in maxim,max77759-charger.yaml to its parent
node. (Krzysztof Kozlowski)
- Renamed regulator supplier & consumer. (Krzysztof Kozlowski & Heikki
Krogerus)
- Removed explicit setting of irq trigger types in max77759 driver.
(André Draszik & Krzysztof Kozlowski)
- Complete bit definitions for IRQ registers. (André Draszik)
- Consolidate all bit definitions for charger IP in mfd/max77759.h.
(André Draszik)
- Modify the handling of charger IRQs such that regmap IRQ chip handles
masking, de-mux and acking of interrupts. (André Draszik)
- Remove unused macro definitions relating to Charger modes in tcpci
maxim driver (André Draszik)
- Add dependency on Regulator class in Kconfig definition for max77759
chg. (Kernel Test Robot)
- Link to v1: https://lore.kernel.org/r/20251123-max77759-charger-v1-0-6b2e4b8f7f54@google.com
---
Amit Sunil Dhamne (5):
dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property
dt-bindings: usb: maxim,max33359: Add supply property for vbus
mfd: max77759: add register bitmasks and modify irq configs for charger
power: supply: max77759: add charger driver
usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode
.../devicetree/bindings/mfd/maxim,max77759.yaml | 16 +-
.../devicetree/bindings/usb/maxim,max33359.yaml | 4 +
MAINTAINERS | 6 +
drivers/mfd/max77759.c | 91 ++-
drivers/power/supply/Kconfig | 11 +
drivers/power/supply/Makefile | 1 +
drivers/power/supply/max77759_charger.c | 777 +++++++++++++++++++++
drivers/usb/typec/tcpm/tcpci_maxim.h | 1 +
drivers/usb/typec/tcpm/tcpci_maxim_core.c | 54 +-
include/linux/mfd/max77759.h | 204 +++++-
10 files changed, 1106 insertions(+), 59 deletions(-)
---
base-commit: 8dfce8991b95d8625d0a1d2896e42f93b9d7f68d
change-id: 20251105-max77759-charger-852b626d661a
Best regards,
--
Amit Sunil Dhamne <amitsd@google.com>
^ permalink raw reply [flat|nested] 11+ messages in thread* [PATCH v5 1/5] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 ` Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 2/5] dt-bindings: usb: maxim,max33359: Add supply property for vbus Amit Sunil Dhamne via B4 Relay ` (3 subsequent siblings) 4 siblings, 0 replies; 11+ messages in thread From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw) To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne, Krzysztof Kozlowski From: Amit Sunil Dhamne <amitsd@google.com> Extend the max77759 binding to reference power-supply schema, so that PMIC node can reference its supplier. Also, add regulator property to control CHGIN (OTG) voltage. Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> Reviewed-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> Reviewed-by: André Draszik <andre.draszik@linaro.org> --- .../devicetree/bindings/mfd/maxim,max77759.yaml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml index 525de9ab3c2b7b431e48497330640857540625b1..42e4a84d52043c09bd073dec391a3bd76dbd359b 100644 --- a/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml +++ b/Documentation/devicetree/bindings/mfd/maxim,max77759.yaml @@ -16,6 +16,9 @@ description: | The MAX77759 includes Battery Charger, Fuel Gauge, temperature sensors, USB Type-C Port Controller (TCPC), NVMEM, and a GPIO expander. +allOf: + - $ref: /schemas/power/supply/power-supply.yaml# + properties: compatible: const: maxim,max77759 @@ -37,12 +40,18 @@ properties: nvmem-0: $ref: /schemas/nvmem/maxim,max77759-nvmem.yaml + chgin-otg-regulator: + type: object + description: Provides Boost for sourcing VBUS. + $ref: /schemas/regulator/regulator.yaml# + unevaluatedProperties: false + required: - compatible - interrupts - reg -additionalProperties: false +unevaluatedProperties: false examples: - | @@ -59,6 +68,11 @@ examples: interrupt-controller; #interrupt-cells = <2>; + power-supplies = <&maxtcpci>; + + chgin-otg-regulator { + regulator-name = "chgin-otg"; + }; gpio { compatible = "maxim,max77759-gpio"; -- 2.53.0.rc2.204.g2597b5adb4-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 2/5] dt-bindings: usb: maxim,max33359: Add supply property for vbus 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 1/5] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 ` Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger Amit Sunil Dhamne via B4 Relay ` (2 subsequent siblings) 4 siblings, 0 replies; 11+ messages in thread From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw) To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne, Krzysztof Kozlowski From: Amit Sunil Dhamne <amitsd@google.com> Add a regulator supply property for vbus. This notifies the regulator provider to source vbus when Type-C operates in Source power mode, while turn off sourcing vbus when operating in Sink mode or disconnected. Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> Acked-by: Krzysztof Kozlowski <krzysztof.kozlowski@oss.qualcomm.com> --- Documentation/devicetree/bindings/usb/maxim,max33359.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml index 3de4dc40b79192b60443421b557bd2fb18683bf7..e652a24902eac93396fc61281cc9e2cb1d37a390 100644 --- a/Documentation/devicetree/bindings/usb/maxim,max33359.yaml +++ b/Documentation/devicetree/bindings/usb/maxim,max33359.yaml @@ -32,6 +32,9 @@ properties: description: Properties for usb c connector. + vbus-supply: + description: Regulator to control sourcing Vbus. + required: - compatible - reg @@ -53,6 +56,7 @@ examples: reg = <0x25>; interrupt-parent = <&gpa8>; interrupts = <2 IRQ_TYPE_LEVEL_LOW>; + vbus-supply = <&chgin_otg_reg>; connector { compatible = "usb-c-connector"; -- 2.53.0.rc2.204.g2597b5adb4-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 1/5] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 2/5] dt-bindings: usb: maxim,max33359: Add supply property for vbus Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 ` Amit Sunil Dhamne via B4 Relay 2026-02-04 6:46 ` André Draszik 2026-02-03 22:50 ` [PATCH v5 4/5] power: supply: max77759: add charger driver Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 5/5] usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode Amit Sunil Dhamne via B4 Relay 4 siblings, 1 reply; 11+ messages in thread From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw) To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne From: Amit Sunil Dhamne <amitsd@google.com> Add register bitmasks for charger function. In addition split the charger IRQs further such that each bit represents an IRQ downstream of charger regmap irq chip. In addition populate the ack_base to offload irq ack to the regmap irq chip framework. Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> --- drivers/mfd/max77759.c | 91 +++++++++++++++++-- include/linux/mfd/max77759.h | 204 +++++++++++++++++++++++++++++++++++++------ 2 files changed, 258 insertions(+), 37 deletions(-) diff --git a/drivers/mfd/max77759.c b/drivers/mfd/max77759.c index 6cf6306c4a3b25655ff08077b6f94316ab8cae04..a5f7da003edd43f1917da428025dd10fee9211ce 100644 --- a/drivers/mfd/max77759.c +++ b/drivers/mfd/max77759.c @@ -201,8 +201,24 @@ static const struct regmap_config max77759_regmap_config_charger = { * - SYSUVLO_INT * - FSHIP_NOT_RD * - CHGR_INT: charger - * - CHG_INT - * - CHG_INT2 + * - INT1 + * - AICL + * - CHGIN + * - WCIN + * - CHG + * - BAT + * - INLIM + * - THM2 + * - BYP + * - INT2 + * - INSEL + * - SYS_UVLO1 + * - SYS_UVLO2 + * - BAT_OILO + * - CHG_STA_CC + * - CHG_STA_CV + * - CHG_STA_TO + * - CHG_STA_DONE */ enum { MAX77759_INT_MAXQ, @@ -228,8 +244,22 @@ enum { }; enum { - MAX77759_CHARGER_INT_1, - MAX77759_CHARGER_INT_2, + MAX77759_CHGR_INT1_AICL, + MAX77759_CHGR_INT1_CHGIN, + MAX77759_CHGR_INT1_WCIN, + MAX77759_CHGR_INT1_CHG, + MAX77759_CHGR_INT1_BAT, + MAX77759_CHGR_INT1_INLIM, + MAX77759_CHGR_INT1_THM2, + MAX77759_CHGR_INT1_BYP, + MAX77759_CHGR_INT2_INSEL, + MAX77759_CHGR_INT2_SYS_UVLO1, + MAX77759_CHGR_INT2_SYS_UVLO2, + MAX77759_CHGR_INT2_BAT_OILO, + MAX77759_CHGR_INT2_CHG_STA_CC, + MAX77759_CHGR_INT2_CHG_STA_CV, + MAX77759_CHGR_INT2_CHG_STA_TO, + MAX77759_CHGR_INT2_CHG_STA_DONE, }; static const struct regmap_irq max77759_pmic_irqs[] = { @@ -256,8 +286,38 @@ static const struct regmap_irq max77759_topsys_irqs[] = { }; static const struct regmap_irq max77759_chgr_irqs[] = { - REGMAP_IRQ_REG(MAX77759_CHARGER_INT_1, 0, GENMASK(7, 0)), - REGMAP_IRQ_REG(MAX77759_CHARGER_INT_2, 1, GENMASK(7, 0)), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_AICL, 0, + MAX77759_CHGR_REG_CHG_INT_AICL), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHGIN, 0, + MAX77759_CHGR_REG_CHG_INT_CHGIN), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_WCIN, 0, + MAX77759_CHGR_REG_CHG_INT_WCIN), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_CHG, 0, + MAX77759_CHGR_REG_CHG_INT_CHG), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BAT, 0, + MAX77759_CHGR_REG_CHG_INT_BAT), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_INLIM, 0, + MAX77759_CHGR_REG_CHG_INT_INLIM), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_THM2, 0, + MAX77759_CHGR_REG_CHG_INT_THM2), + REGMAP_IRQ_REG(MAX77759_CHGR_INT1_BYP, 0, + MAX77759_CHGR_REG_CHG_INT_BYP), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_INSEL, 1, + MAX77759_CHGR_REG_CHG_INT2_INSEL), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO1, 1, + MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO1), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_SYS_UVLO2, 1, + MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO2), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_BAT_OILO, 1, + MAX77759_CHGR_REG_CHG_INT2_BAT_OILO), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CC, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CC), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_CV, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CV), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_TO, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_TO), + REGMAP_IRQ_REG(MAX77759_CHGR_INT2_CHG_STA_DONE, 1, + MAX77759_CHGR_REG_CHG_INT2_CHG_STA_DONE), }; static const struct regmap_irq_chip max77759_pmic_irq_chip = { @@ -302,6 +362,7 @@ static const struct regmap_irq_chip max77759_chrg_irq_chip = { .domain_suffix = "CHGR", .status_base = MAX77759_CHGR_REG_CHG_INT, .mask_base = MAX77759_CHGR_REG_CHG_INT_MASK, + .ack_base = MAX77759_CHGR_REG_CHG_INT, .num_regs = 2, .irqs = max77759_chgr_irqs, .num_irqs = ARRAY_SIZE(max77759_chgr_irqs), @@ -325,8 +386,22 @@ static const struct resource max77759_gpio_resources[] = { }; static const struct resource max77759_charger_resources[] = { - DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_1, "INT1"), - DEFINE_RES_IRQ_NAMED(MAX77759_CHARGER_INT_2, "INT2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_AICL, "AICL"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHGIN, "CHGIN"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_WCIN, "WCIN"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_CHG, "CHG"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BAT, "BAT"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_INLIM, "INLIM"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_THM2, "THM2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT1_BYP, "BYP"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_INSEL, "INSEL"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO1, "SYS_UVLO1"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_SYS_UVLO2, "SYS_UVLO2"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_BAT_OILO, "BAT_OILO"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CC, "CHG_STA_CC"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_CV, "CHG_STA_CV"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_TO, "CHG_STA_TO"), + DEFINE_RES_IRQ_NAMED(MAX77759_CHGR_INT2_CHG_STA_DONE, "CHG_STA_DONE"), }; static const struct mfd_cell max77759_cells[] = { diff --git a/include/linux/mfd/max77759.h b/include/linux/mfd/max77759.h index c6face34e38555cbc09db4131925b6ed781af511..2e2f7631356257303c6067b3cdb0dd0c0e7f5850 100644 --- a/include/linux/mfd/max77759.h +++ b/include/linux/mfd/max77759.h @@ -59,35 +59,65 @@ #define MAX77759_MAXQ_REG_AP_DATAIN0 0xb1 #define MAX77759_MAXQ_REG_UIC_SWRST 0xe0 -#define MAX77759_CHGR_REG_CHG_INT 0xb0 -#define MAX77759_CHGR_REG_CHG_INT2 0xb1 -#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 -#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 -#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 -#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 -#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 -#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 -#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 -#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 -#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba -#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb -#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc -#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd -#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe -#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf -#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 -#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 -#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 -#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 -#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 -#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 -#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 -#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 -#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 -#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 -#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca -#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb -#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc +#define MAX77759_CHGR_REG_CHG_INT 0xb0 +#define MAX77759_CHGR_REG_CHG_INT_AICL BIT(7) +#define MAX77759_CHGR_REG_CHG_INT_CHGIN BIT(6) +#define MAX77759_CHGR_REG_CHG_INT_WCIN BIT(5) +#define MAX77759_CHGR_REG_CHG_INT_CHG BIT(4) +#define MAX77759_CHGR_REG_CHG_INT_BAT BIT(3) +#define MAX77759_CHGR_REG_CHG_INT_INLIM BIT(2) +#define MAX77759_CHGR_REG_CHG_INT_THM2 BIT(1) +#define MAX77759_CHGR_REG_CHG_INT_BYP BIT(0) +#define MAX77759_CHGR_REG_CHG_INT2 0xb1 +#define MAX77759_CHGR_REG_CHG_INT2_INSEL BIT(7) +#define MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO1 BIT(6) +#define MAX77759_CHGR_REG_CHG_INT2_SYS_UVLO2 BIT(5) +#define MAX77759_CHGR_REG_CHG_INT2_BAT_OILO BIT(4) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CC BIT(3) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_CV BIT(2) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_TO BIT(1) +#define MAX77759_CHGR_REG_CHG_INT2_CHG_STA_DONE BIT(0) +#define MAX77759_CHGR_REG_CHG_INT_MASK 0xb2 +#define MAX77759_CHGR_REG_CHG_INT2_MASK 0xb3 +#define MAX77759_CHGR_REG_CHG_INT_OK 0xb4 +#define MAX77759_CHGR_REG_CHG_DETAILS_00 0xb5 +#define MAX77759_CHGR_REG_CHG_DETAILS_OO_CHGIN_DTLS GENMASK(6, 5) +#define MAX77759_CHGR_REG_CHG_DETAILS_01 0xb6 +#define MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS GENMASK(6, 4) +#define MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS GENMASK(3, 0) +#define MAX77759_CHGR_REG_CHG_DETAILS_02 0xb7 +#define MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS BIT(5) +#define MAX77759_CHGR_REG_CHG_DETAILS_03 0xb8 +#define MAX77759_CHGR_REG_CHG_CNFG_00 0xb9 +#define MAX77759_CHGR_REG_CHG_CNFG_00_MODE GENMASK(3, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_01 0xba +#define MAX77759_CHGR_REG_CHG_CNFG_02 0xbb +#define MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC GENMASK(5, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_03 0xbc +#define MAX77759_CHGR_REG_CHG_CNFG_04 0xbd +#define MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM GENMASK(5, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_05 0xbe +#define MAX77759_CHGR_REG_CHG_CNFG_06 0xbf +#define MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT GENMASK(3, 2) +#define MAX77759_CHGR_REG_CHG_CNFG_07 0xc0 +#define MAX77759_CHGR_REG_CHG_CNFG_08 0xc1 +#define MAX77759_CHGR_REG_CHG_CNFG_09 0xc2 +#define MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM GENMASK(6, 0) +#define MAX77759_CHGR_REG_CHG_CNFG_10 0xc3 +#define MAX77759_CHGR_REG_CHG_CNFG_11 0xc4 +#define MAX77759_CHGR_REG_CHG_CNFG_12 0xc5 +/* Wireless Charging input channel select */ +#define MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL BIT(6) +/* CHGIN/USB input channel select */ +#define MAX77759_CHGR_REG_CHG_CNFG_12_CHGINSEL BIT(5) +#define MAX77759_CHGR_REG_CHG_CNFG_13 0xc6 +#define MAX77759_CHGR_REG_CHG_CNFG_14 0xc7 +#define MAX77759_CHGR_REG_CHG_CNFG_15 0xc8 +#define MAX77759_CHGR_REG_CHG_CNFG_16 0xc9 +#define MAX77759_CHGR_REG_CHG_CNFG_17 0xca +#define MAX77759_CHGR_REG_CHG_CNFG_18 0xcb +#define MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN BIT(0) +#define MAX77759_CHGR_REG_CHG_CNFG_19 0xcc /* MaxQ opcodes for max77759_maxq_command() */ #define MAX77759_MAXQ_OPCODE_MAXLENGTH (MAX77759_MAXQ_REG_AP_DATAOUT32 - \ @@ -101,6 +131,122 @@ #define MAX77759_MAXQ_OPCODE_USER_SPACE_READ 0x81 #define MAX77759_MAXQ_OPCODE_USER_SPACE_WRITE 0x82 +/* + * Charger Input Status + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: + * Charger input voltage (Vchgin) < Under Voltage Threshold (Vuvlo) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: Vchgin > Vuvlo and + * Vchgin < (Battery Voltage (Vbatt) + system voltage (Vsys)) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: + * Vchgin > Over Voltage threshold (Vovlo) + * @MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: + * Vchgin > Vuvlo, Vchgin < Vovlo and Vchgin > (Vsys + Vbatt) + */ +enum max77759_chgr_chgin_dtls_status { + MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE, + MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID, +}; + +/* + * Battery Details + * @MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: + * No battery and the charger suspended + * @MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: Vbatt < Vtrickle + * @MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: + * Charging suspended due to timer fault + * @MAX77759_CHGR_BAT_DTLS_BAT_OKAY: + * Battery okay and Vbatt > Min Sys Voltage (Vsysmin) + * @MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: + * Battery is okay. Vtrickle < Vbatt < Vsysmin + * @MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: + * Battery voltage > Overvoltage threshold + * @MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: + * Battery current exceeds overcurrent threshold + * @MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: + * Battery only mode and battery level not available + */ +enum max77759_chgr_bat_dtls_states { + MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP, + MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY, + MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT, + MAX77759_CHGR_BAT_DTLS_BAT_OKAY, + MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE, + MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE, + MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT, + MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE, +}; + +/* + * Charger Details + * @MAX77759_CHGR_CHG_DTLS_PREQUAL: Charger in prequalification mode + * @MAX77759_CHGR_CHG_DTLS_CC: Charger in fast charge const curr mode + * @MAX77759_CHGR_CHG_DTLS_CV: Charger in fast charge const voltage mode + * @MAX77759_CHGR_CHG_DTLS_TO: Charger is in top off mode + * @MAX77759_CHGR_CHG_DTLS_DONE: Charger is done + * @MAX77759_CHGR_CHG_DTLS_RSVD_1: Reserved + * @MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: Charger is in timer fault mode + * @MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + * Charger is suspended as bettery removal detected + * @MAX77759_CHGR_CHG_DTLS_OFF: + * Charger is off. Input invalid or charger disabled + * @MAX77759_CHGR_CHG_DTLS_RSVD_2: Reserved + * @MAX77759_CHGR_CHG_DTLS_RSVD_3: Reserved + * @MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + * Charger is off as watchdog timer expired + * @MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: Charger is in JEITA control mode + */ +enum max77759_chgr_chg_dtls_states { + MAX77759_CHGR_CHG_DTLS_PREQUAL, + MAX77759_CHGR_CHG_DTLS_CC, + MAX77759_CHGR_CHG_DTLS_CV, + MAX77759_CHGR_CHG_DTLS_TO, + MAX77759_CHGR_CHG_DTLS_DONE, + MAX77759_CHGR_CHG_DTLS_RSVD_1, + MAX77759_CHGR_CHG_DTLS_TIMER_FAULT, + MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM, + MAX77759_CHGR_CHG_DTLS_OFF, + MAX77759_CHGR_CHG_DTLS_RSVD_2, + MAX77759_CHGR_CHG_DTLS_RSVD_3, + MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER, + MAX77759_CHGR_CHG_DTLS_SUSP_JEITA, +}; + +enum max77759_chgr_mode { + MAX77759_CHGR_MODE_OFF, + MAX77759_CHGR_MODE_CHG_BUCK_ON = 0x5, + MAX77759_CHGR_MODE_OTG_BOOST_ON = 0xA, +}; + +/* Fast charge current limits */ +#define MAX77759_CHGR_CHGCC_MIN_UA 133330 +#define MAX77759_CHGR_CHGCC_MAX_UA 4000000 +#define MAX77759_CHGR_CHGCC_STEP_UA 66670 +#define MAX77759_CHGR_CHGCC_MIN_REG 0x2 +#define MAX77759_CHGR_CHGCC_MAX_REG 0x3C + +/* Charge Termination Voltage Limits (in 2 ranges) */ +/* [3.8, 3.9] V range */ +#define MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV 3800 +#define MAX77759_CHGR_CHG_CV_PRM_LO_MAX_MV 3900 +#define MAX77759_CHGR_CHG_CV_PRM_LO_STEP_MV 100 +#define MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG 0x38 +#define MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG 0x39 +/* [4, 4.5] V range */ +#define MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV 4000 +#define MAX77759_CHGR_CHG_CV_PRM_HI_MAX_MV 4500 +#define MAX77759_CHGR_CHG_CV_PRM_HI_STEP_MV 10 +#define MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG 0x0 +#define MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG 0x32 + +/* USB input current limits */ +#define MAX77759_CHGR_CHGIN_ILIM_MIN_UA 100000 +#define MAX77759_CHGR_CHGIN_ILIM_MAX_UA 3200000 +#define MAX77759_CHGR_CHGIN_ILIM_STEP_UA 25000 +#define MAX77759_CHGR_CHGIN_ILIM_MIN_REG 0x3 +#define MAX77759_CHGR_CHGIN_ILIM_MAX_REG 0x7F + /** * struct max77759 - core max77759 internal data structure * -- 2.53.0.rc2.204.g2597b5adb4-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger 2026-02-03 22:50 ` [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger Amit Sunil Dhamne via B4 Relay @ 2026-02-04 6:46 ` André Draszik 0 siblings, 0 replies; 11+ messages in thread From: André Draszik @ 2026-02-04 6:46 UTC (permalink / raw) To: amitsd, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso On Tue, 2026-02-03 at 22:50 +0000, Amit Sunil Dhamne via B4 Relay wrote: > From: Amit Sunil Dhamne <amitsd@google.com> > > Add register bitmasks for charger function. > In addition split the charger IRQs further such that each bit represents > an IRQ downstream of charger regmap irq chip. In addition populate the > ack_base to offload irq ack to the regmap irq chip framework. > > Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> > --- > drivers/mfd/max77759.c | 91 +++++++++++++++++-- > include/linux/mfd/max77759.h | 204 +++++++++++++++++++++++++++++++++++++------ > 2 files changed, 258 insertions(+), 37 deletions(-) Reviewed-by: André Draszik <andre.draszik@linaro.org> ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 4/5] power: supply: max77759: add charger driver 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay ` (2 preceding siblings ...) 2026-02-03 22:50 ` [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 ` Amit Sunil Dhamne via B4 Relay 2026-02-04 5:34 ` kernel test robot 2026-02-04 12:49 ` André Draszik 2026-02-03 22:50 ` [PATCH v5 5/5] usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode Amit Sunil Dhamne via B4 Relay 4 siblings, 2 replies; 11+ messages in thread From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw) To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne From: Amit Sunil Dhamne <amitsd@google.com> Add support for MAX77759 battery charger driver. This is a 4A 1-Cell Li+/LiPoly dual input switch mode charger. While the device can support USB & wireless charger inputs, this implementation only supports USB input. This implementation supports both buck and boost modes. Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> --- MAINTAINERS | 6 + drivers/power/supply/Kconfig | 11 + drivers/power/supply/Makefile | 1 + drivers/power/supply/max77759_charger.c | 777 ++++++++++++++++++++++++++++++++ 4 files changed, 795 insertions(+) diff --git a/MAINTAINERS b/MAINTAINERS index 67db88b04537b431c927b73624993233eef43e3f..7f6d1c5c2569a1d1536642b075b3e6939553382a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -15553,6 +15553,12 @@ F: drivers/mfd/max77759.c F: drivers/nvmem/max77759-nvmem.c F: include/linux/mfd/max77759.h +MAXIM MAX77759 BATTERY CHARGER DRIVER +M: Amit Sunil Dhamne <amitsd@google.com> +L: linux-kernel@vger.kernel.org +S: Maintained +F: drivers/power/supply/max77759_charger.c + MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER M: Javier Martinez Canillas <javier@dowhile0.org> L: linux-kernel@vger.kernel.org diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index 92f9f7aae92f249aa165e68dbcd4cebb569286ea..3a2cdb95c98e44324151ac2b86d740ae2923ee77 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -631,6 +631,17 @@ config CHARGER_MAX77705 help Say Y to enable support for the Maxim MAX77705 battery charger. +config CHARGER_MAX77759 + tristate "Maxim MAX77759 battery charger driver" + depends on MFD_MAX77759 && REGULATOR + default MFD_MAX77759 + help + Say M or Y here to enable the MAX77759 battery charger. MAX77759 + charger is a function of the MAX77759 PMIC. This is a dual input + switch-mode charger. This driver supports buck and OTG boost modes. + + If built as a module, it will be called max77759_charger. + config CHARGER_MAX77976 tristate "Maxim MAX77976 battery charger driver" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 4b79d5abc49a7fd1e37a26d0c89f94d9fe3a916f..6af905875ad5e3b393a7030405355b9a975870f6 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -128,3 +128,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o +obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c new file mode 100644 index 0000000000000000000000000000000000000000..8c7465c1e7cdd0ff7e8d2a134752ebf7812f28ac --- /dev/null +++ b/drivers/power/supply/max77759_charger.c @@ -0,0 +1,777 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * max77759_charger.c - Battery charger driver for MAX77759 charger device. + * + * Copyright 2025 Google LLC. + */ + +#include <linux/bitfield.h> +#include <linux/cleanup.h> +#include <linux/device.h> +#include <linux/devm-helpers.h> +#include <linux/interrupt.h> +#include <linux/irq.h> +#include <linux/linear_range.h> +#include <linux/math64.h> +#include <linux/mfd/max77759.h> +#include <linux/module.h> +#include <linux/mod_devicetable.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regmap.h> +#include <linux/regulator/driver.h> +#include <linux/string_choices.h> +#include <linux/workqueue.h> + +/* Default values for Fast Charge Current & Float Voltage */ +#define CHG_CC_DEFAULT_UA 2266770 +#define CHG_FV_DEFAULT_MV 4300 + +#define MAX_NUM_RETRIES 3 +#define PSY_WORK_RETRY_DELAY_MS 10 + +#define FOREACH_IRQ(S) \ + S(AICL), \ + S(CHGIN), \ + S(CHG), \ + S(INLIM), \ + S(BAT_OILO), \ + S(CHG_STA_CC), \ + S(CHG_STA_CV), \ + S(CHG_STA_TO), \ + S(CHG_STA_DONE) + +#define GENERATE_ENUM(e) e +#define GENERATE_STRING(s) #s + +enum { + FOREACH_IRQ(GENERATE_ENUM) +}; + +static const char *const chgr_irqs_str[] = { + FOREACH_IRQ(GENERATE_STRING) +}; + +#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) + +enum { + MAX77759_CHGR_RANGE_CHGCC, + MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, + MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, + MAX77759_CHGR_RANGE_CHGIN_ILIM, +}; + +static const struct linear_range chg_ranges[] = { + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGCC, MAX77759_CHGR_CHGCC_MIN_UA, + MAX77759_CHGR_CHGCC_MIN_REG, + MAX77759_CHGR_CHGCC_MAX_REG, + MAX77759_CHGR_CHGCC_STEP_UA), + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV, + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG, + MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG, + MAX77759_CHGR_CHG_CV_PRM_LO_STEP_MV), + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV, + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG, + MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG, + MAX77759_CHGR_CHG_CV_PRM_HI_STEP_MV), + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGIN_ILIM, + MAX77759_CHGR_CHGIN_ILIM_MIN_UA, + MAX77759_CHGR_CHGIN_ILIM_MIN_REG, + MAX77759_CHGR_CHGIN_ILIM_MAX_REG, + MAX77759_CHGR_CHGIN_ILIM_STEP_UA), +}; + +struct max77759_charger { + struct device *dev; + struct regmap *regmap; + struct power_supply *psy; + struct regulator_dev *chgin_otg_rdev; + struct notifier_block nb; + struct power_supply *tcpm_psy; + struct delayed_work psy_work; + u32 psy_work_retry_cnt; + int irqs[NUM_IRQS]; + struct mutex lock; /* protects the state below */ + enum max77759_chgr_mode mode; +}; + +static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) +{ + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, + MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock + ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); +} + +static int charger_input_valid(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); + if (ret) + return ret; + + return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && + (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); +} + +static int get_online(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = charger_input_valid(chg); + if (ret <= 0) + return ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); + if (ret) + return ret; + + guard(mutex)(&chg->lock); + return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && + (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); +} + +static int get_status(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_STATUS_CHARGING; + case MAX77759_CHGR_CHG_DTLS_DONE: + return POWER_SUPPLY_STATUS_FULL; + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + return POWER_SUPPLY_STATUS_NOT_CHARGING; + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_STATUS_DISCHARGING; + default: + break; + } + + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int get_charge_type(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { + case MAX77759_CHGR_CHG_DTLS_PREQUAL: + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + case MAX77759_CHGR_CHG_DTLS_CC: + case MAX77759_CHGR_CHG_DTLS_CV: + return POWER_SUPPLY_CHARGE_TYPE_FAST; + case MAX77759_CHGR_CHG_DTLS_TO: + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; + case MAX77759_CHGR_CHG_DTLS_DONE: + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: + case MAX77759_CHGR_CHG_DTLS_OFF: + return POWER_SUPPLY_CHARGE_TYPE_NONE; + default: + break; + } + + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; +} + +static int get_chg_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_OO_CHGIN_DTLS, val)) { + case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: + case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: + return POWER_SUPPLY_HEALTH_GOOD; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_batt_health(struct max77759_charger *chg) +{ + u32 val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); + if (ret) + return ret; + + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { + case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: + return POWER_SUPPLY_HEALTH_NO_BATTERY; + case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: + return POWER_SUPPLY_HEALTH_DEAD; + case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: + return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: + case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: + return POWER_SUPPLY_HEALTH_GOOD; + case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; + case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: + return POWER_SUPPLY_HEALTH_OVERCURRENT; + default: + break; + } + + return POWER_SUPPLY_HEALTH_UNKNOWN; +} + +static int get_health(struct max77759_charger *chg) +{ + int ret; + + ret = get_online(chg); + if (ret < 0) + return ret; + + if (ret) { + ret = get_chg_health(chg); + if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) + return ret; + } + + return get_batt_health(chg); +} + +static int get_fast_charge_current(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); + if (regval <= MAX77759_CHGR_CHGCC_MIN_REG) + return MAX77759_CHGR_CHGCC_MIN_UA; + + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], + regval, &val); + if (ret) + return ret; + + return ret ? ret : val; +} + +static int set_fast_charge_current_limit(struct max77759_charger *chg, + u32 cc_max_ua) +{ + bool found; + u32 regval; + int ret; + + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], + cc_max_ua, ®val, &found); + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, + MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); +} + +static int get_float_voltage(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); + switch (regval) { + case MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG: + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], + regval, &val); + break; + case MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG: + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], + regval, &val); + break; + default: + ret = -EINVAL; + break; + } + + return ret ? ret : val; +} + +static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) +{ + u32 regval; + bool found; + int ret; + + if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV && + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_LO_MAX_MV) { + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], + fv_mv, ®val, &found); + } else if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV && + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_HI_MAX_MV) { + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], + fv_mv, ®val, &found); + } else { + return -EINVAL; + } + + if (!found) + return -EINVAL; + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, + MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, + regval); +} + +static int get_input_current_limit(struct max77759_charger *chg) +{ + u32 regval, val; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); + if (ret) + return ret; + + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); + if (regval <= MAX77759_CHGR_CHGIN_ILIM_MIN_REG) + return MAX77759_CHGR_CHGIN_ILIM_MIN_UA; + + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], + regval, &val); + + return ret ? ret : val; +} + +static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) +{ + u32 regval; + + if (ilim_ua < 0) + return -EINVAL; + + linear_range_get_selector_within(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], + ilim_ua, ®val); + + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, + MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, + regval); +} + +static const enum power_supply_property max77759_charger_props[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, +}; + +static int max77759_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *pval) +{ + struct max77759_charger *chg = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = get_online(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + ret = charger_input_valid(chg); + break; + case POWER_SUPPLY_PROP_STATUS: + ret = get_status(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = get_health(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = get_fast_charge_current(chg); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = get_float_voltage(chg); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = get_input_current_limit(chg); + break; + default: + ret = -EINVAL; + } + + pval->intval = ret; + return ret < 0 ? ret : 0; +} + +static const struct power_supply_desc max77759_charger_desc = { + .name = "max77759-charger", + .type = POWER_SUPPLY_TYPE_USB, + .properties = max77759_charger_props, + .num_properties = ARRAY_SIZE(max77759_charger_props), + .get_property = max77759_charger_get_property, +}; + +static int charger_set_mode(struct max77759_charger *chg, + enum max77759_chgr_mode mode) +{ + int ret; + + guard(mutex)(&chg->lock); + + if (chg->mode == mode) + return 0; + + if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || + mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && + chg->mode != MAX77759_CHGR_MODE_OFF) { + dev_err(chg->dev, "Invalid mode transition from %d to %d", + chg->mode, mode); + return -EINVAL; + } + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, + MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); + if (ret) + return ret; + + chg->mode = mode; + return 0; +} + +static int enable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); +} + +static int disable_chgin_otg(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); +} + +static int chgin_otg_status(struct regulator_dev *rdev) +{ + struct max77759_charger *chg = rdev_get_drvdata(rdev); + + guard(mutex)(&chg->lock); + return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; +} + +static const struct regulator_ops chgin_otg_reg_ops = { + .enable = enable_chgin_otg, + .disable = disable_chgin_otg, + .is_enabled = chgin_otg_status, +}; + +static const struct regulator_desc chgin_otg_reg_desc = { + .name = "chgin-otg", + .of_match = of_match_ptr("chgin-otg-regulator"), + .owner = THIS_MODULE, + .ops = &chgin_otg_reg_ops, + .fixed_uV = 5000000, + .n_voltages = 1, +}; + +static irqreturn_t irq_handler(int irq, void *data) +{ + struct max77759_charger *chg = data; + struct device *dev = chg->dev; + int i; + + for (i = 0; i < NUM_IRQS && chg->irqs[i] != irq; i++) + ; + + if (i == NUM_IRQS) { + dev_err(dev, "Unable to handle irq=%d", irq); + return IRQ_NONE; + } + + if (i == BAT_OILO) + dev_warn(dev, "Battery over-current threshold crossed"); + + power_supply_changed(chg->psy); + return IRQ_HANDLED; +} + +static int max77759_init_irqhandler(struct max77759_charger *chg) +{ + struct device *dev = chg->dev; + int i, ret; + + for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { + ret = platform_get_irq_byname(to_platform_device(dev), + chgr_irqs_str[i]); + if (ret < 0) + return dev_err_probe(dev, ret, + "Failed to get irq resource for %s", + chgr_irqs_str[i]); + + chg->irqs[i] = ret; + ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, + irq_handler, 0, dev_name(dev), + chg); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register irq handler for %s", + chgr_irqs_str[i]); + } + + return 0; +} + +static int max77759_charger_init(struct max77759_charger *chg) +{ + struct power_supply_battery_info *info; + u32 regval, fast_chg_curr, fv; + int ret; + + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); + if (ret) + return ret; + + chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); + ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + if (ret) + return ret; + + if (power_supply_get_battery_info(chg->psy, &info)) { + fv = CHG_FV_DEFAULT_MV; + fast_chg_curr = CHG_CC_DEFAULT_UA; + } else { + fv = info->constant_charge_voltage_max_uv / 1000; + fast_chg_curr = info->constant_charge_current_max_ua; + } + + ret = set_fast_charge_current_limit(chg, fast_chg_curr); + if (ret) + return ret; + + ret = set_float_voltage_limit(chg, fv); + if (ret) + return ret; + + ret = unlock_prot_regs(chg, true); + if (ret) + return ret; + + /* Disable wireless charging input */ + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, + MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); + if (ret) + return ret; + + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, + MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); + if (ret) + return ret; + + return unlock_prot_regs(chg, false); +} + +static void psy_work_item(struct work_struct *work) +{ + struct max77759_charger *chg = + container_of(work, struct max77759_charger, psy_work.work); + union power_supply_propval current_limit, online; + int ret; + + ret = power_supply_get_property(chg->tcpm_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, + ¤t_limit); + if (ret) { + dev_err(chg->dev, + "Failed to get CURRENT_MAX psy property, ret=%d", + ret); + goto err; + } + + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, + &online); + if (ret) { + dev_err(chg->dev, + "Failed to get ONLINE psy property, ret=%d", + ret); + goto err; + } + + if (online.intval && current_limit.intval) { + ret = set_input_current_limit(chg, current_limit.intval); + if (ret) { + dev_err(chg->dev, + "Unable to set current limit, ret=%d", ret); + goto err; + } + + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); + } else { + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + } + + chg->psy_work_retry_cnt = 0; + return; + +err: + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) + return; + + ++chg->psy_work_retry_cnt; + dev_err(chg->dev, "Retrying %u/%u chg psy_work", + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); + schedule_delayed_work(&chg->psy_work, + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); +} + +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) +{ + struct max77759_charger *chg = container_of(nb, struct max77759_charger, + nb); + static const char *psy_name = "tcpm-source"; + struct power_supply *psy = data; + + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || + evt != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + chg->tcpm_psy = psy; + schedule_delayed_work(&chg->psy_work, 0); + + return NOTIFY_OK; +} + +static void max_tcpci_unregister_psy_notifier(void *nb) +{ + power_supply_unreg_notifier(nb); +} + +static int max77759_charger_probe(struct platform_device *pdev) +{ + struct regulator_config chgin_otg_reg_cfg; + struct power_supply_config psy_cfg; + struct device *dev = &pdev->dev; + struct max77759_charger *chg; + int ret; + + device_set_of_node_from_dev(dev, dev->parent); + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); + if (!chg) + return -ENOMEM; + + platform_set_drvdata(pdev, chg); + chg->dev = dev; + chg->regmap = dev_get_regmap(dev->parent, "charger"); + if (!chg->regmap) + return dev_err_probe(dev, -ENODEV, "Missing regmap"); + + ret = devm_mutex_init(dev, &chg->lock); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize lock"); + + psy_cfg.fwnode = dev_fwnode(dev); + psy_cfg.drv_data = chg; + chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, + &psy_cfg); + if (IS_ERR(chg->psy)) + return dev_err_probe(dev, -EPROBE_DEFER, + "Failed to register psy, ret=%ld", + PTR_ERR(chg->psy)); + + ret = max77759_charger_init(chg); + if (ret) + return dev_err_probe(dev, ret, + "Failed to initialize max77759 charger"); + + chgin_otg_reg_cfg.dev = dev; + chgin_otg_reg_cfg.driver_data = chg; + chgin_otg_reg_cfg.of_node = dev_of_node(dev); + chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, + &chgin_otg_reg_cfg); + if (IS_ERR(chg->chgin_otg_rdev)) + return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), + "Failed to register chgin otg regulator"); + + ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); + if (ret) + return dev_err_probe(dev, ret, "Failed to initialize psy work"); + + chg->nb.notifier_call = psy_changed; + ret = power_supply_reg_notifier(&chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Unable to register psy notifier"); + + ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, + &chg->nb); + if (ret) + return dev_err_probe(dev, ret, + "Failed to add devm action to unregister psy notifier"); + + return max77759_init_irqhandler(chg); +} + +static const struct platform_device_id max77759_charger_id[] = { + { .name = "max77759-charger", }, + { } +}; +MODULE_DEVICE_TABLE(platform, max77759_charger_id); + +static struct platform_driver max77759_charger_driver = { + .driver = { + .name = "max77759-charger", + .probe_type = PROBE_PREFER_ASYNCHRONOUS, + }, + .probe = max77759_charger_probe, + .id_table = max77759_charger_id, +}; +module_platform_driver(max77759_charger_driver); + +MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>"); +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); +MODULE_LICENSE("GPL"); -- 2.53.0.rc2.204.g2597b5adb4-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 4/5] power: supply: max77759: add charger driver 2026-02-03 22:50 ` [PATCH v5 4/5] power: supply: max77759: add charger driver Amit Sunil Dhamne via B4 Relay @ 2026-02-04 5:34 ` kernel test robot 2026-02-04 12:49 ` André Draszik 1 sibling, 0 replies; 11+ messages in thread From: kernel test robot @ 2026-02-04 5:34 UTC (permalink / raw) To: Amit Sunil Dhamne via B4 Relay, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: oe-kbuild-all, linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne Hi Amit, kernel test robot noticed the following build warnings: [auto build test WARNING on 8dfce8991b95d8625d0a1d2896e42f93b9d7f68d] url: https://github.com/intel-lab-lkp/linux/commits/Amit-Sunil-Dhamne-via-B4-Relay/dt-bindings-mfd-maxim-max77759-reference-power-supply-schema-and-add-regulator-property/20260204-065326 base: 8dfce8991b95d8625d0a1d2896e42f93b9d7f68d patch link: https://lore.kernel.org/r/20260203-max77759-charger-v5-4-b50395376a5f%40google.com patch subject: [PATCH v5 4/5] power: supply: max77759: add charger driver config: sparc-randconfig-001-20260204 (https://download.01.org/0day-ci/archive/20260204/202602041321.6ubHpkpR-lkp@intel.com/config) compiler: sparc-linux-gcc (GCC) 8.5.0 reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260204/202602041321.6ubHpkpR-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202602041321.6ubHpkpR-lkp@intel.com/ All warnings (new ones prefixed by >>): drivers/power/supply/max77759_charger.c: In function 'set_fast_charge_current_limit': >> drivers/power/supply/max77759_charger.c:301:6: warning: variable 'ret' set but not used [-Wunused-but-set-variable] int ret; ^~~ drivers/power/supply/max77759_charger.c: In function 'set_float_voltage_limit': drivers/power/supply/max77759_charger.c:343:6: warning: variable 'ret' set but not used [-Wunused-but-set-variable] int ret; ^~~ vim +/ret +301 drivers/power/supply/max77759_charger.c 295 296 static int set_fast_charge_current_limit(struct max77759_charger *chg, 297 u32 cc_max_ua) 298 { 299 bool found; 300 u32 regval; > 301 int ret; 302 303 ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], 304 cc_max_ua, ®val, &found); 305 if (!found) 306 return -EINVAL; 307 308 return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, 309 MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); 310 } 311 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 4/5] power: supply: max77759: add charger driver 2026-02-03 22:50 ` [PATCH v5 4/5] power: supply: max77759: add charger driver Amit Sunil Dhamne via B4 Relay 2026-02-04 5:34 ` kernel test robot @ 2026-02-04 12:49 ` André Draszik 2026-02-10 0:42 ` Amit Sunil Dhamne 1 sibling, 1 reply; 11+ messages in thread From: André Draszik @ 2026-02-04 12:49 UTC (permalink / raw) To: amitsd, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso On Tue, 2026-02-03 at 22:50 +0000, Amit Sunil Dhamne via B4 Relay wrote: > From: Amit Sunil Dhamne <amitsd@google.com> > > Add support for MAX77759 battery charger driver. This is a 4A 1-Cell > Li+/LiPoly dual input switch mode charger. While the device can support > USB & wireless charger inputs, this implementation only supports USB > input. This implementation supports both buck and boost modes. > > Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> > --- > MAINTAINERS | 6 + > drivers/power/supply/Kconfig | 11 + > drivers/power/supply/Makefile | 1 + > drivers/power/supply/max77759_charger.c | 777 ++++++++++++++++++++++++++++++++ > 4 files changed, 795 insertions(+) > > diff --git a/MAINTAINERS b/MAINTAINERS > index 67db88b04537b431c927b73624993233eef43e3f..7f6d1c5c2569a1d1536642b075b3e6939553382a 100644 > --- a/MAINTAINERS > +++ b/MAINTAINERS > @@ -15553,6 +15553,12 @@ F: drivers/mfd/max77759.c > F: drivers/nvmem/max77759-nvmem.c > F: include/linux/mfd/max77759.h > > +MAXIM MAX77759 BATTERY CHARGER DRIVER > +M: Amit Sunil Dhamne <amitsd@google.com> > +L: linux-kernel@vger.kernel.org > +S: Maintained > +F: drivers/power/supply/max77759_charger.c > + > MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER > M: Javier Martinez Canillas <javier@dowhile0.org> > L: linux-kernel@vger.kernel.org > diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig > index 92f9f7aae92f249aa165e68dbcd4cebb569286ea..3a2cdb95c98e44324151ac2b86d740ae2923ee77 100644 > --- a/drivers/power/supply/Kconfig > +++ b/drivers/power/supply/Kconfig > @@ -631,6 +631,17 @@ config CHARGER_MAX77705 > help > Say Y to enable support for the Maxim MAX77705 battery charger. > > +config CHARGER_MAX77759 > + tristate "Maxim MAX77759 battery charger driver" > + depends on MFD_MAX77759 && REGULATOR > + default MFD_MAX77759 > + help > + Say M or Y here to enable the MAX77759 battery charger. MAX77759 > + charger is a function of the MAX77759 PMIC. This is a dual input > + switch-mode charger. This driver supports buck and OTG boost modes. > + > + If built as a module, it will be called max77759_charger. > + > config CHARGER_MAX77976 > tristate "Maxim MAX77976 battery charger driver" > depends on I2C > diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile > index 4b79d5abc49a7fd1e37a26d0c89f94d9fe3a916f..6af905875ad5e3b393a7030405355b9a975870f6 100644 > --- a/drivers/power/supply/Makefile > +++ b/drivers/power/supply/Makefile > @@ -128,3 +128,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o > obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o > obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o > obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o > +obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o > diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c > new file mode 100644 > index 0000000000000000000000000000000000000000..8c7465c1e7cdd0ff7e8d2a134752ebf7812f28ac > --- /dev/null > +++ b/drivers/power/supply/max77759_charger.c > @@ -0,0 +1,777 @@ > +// SPDX-License-Identifier: GPL-2.0-only > +/* > + * max77759_charger.c - Battery charger driver for MAX77759 charger device. > + * > + * Copyright 2025 Google LLC. > + */ > + > +#include <linux/bitfield.h> > +#include <linux/cleanup.h> > +#include <linux/device.h> > +#include <linux/devm-helpers.h> > +#include <linux/interrupt.h> > +#include <linux/irq.h> > +#include <linux/linear_range.h> > +#include <linux/math64.h> > +#include <linux/mfd/max77759.h> > +#include <linux/module.h> > +#include <linux/mod_devicetable.h> > +#include <linux/mutex.h> > +#include <linux/of.h> > +#include <linux/platform_device.h> > +#include <linux/power_supply.h> > +#include <linux/regmap.h> > +#include <linux/regulator/driver.h> > +#include <linux/string_choices.h> > +#include <linux/workqueue.h> > + > +/* Default values for Fast Charge Current & Float Voltage */ > +#define CHG_CC_DEFAULT_UA 2266770 > +#define CHG_FV_DEFAULT_MV 4300 > + > +#define MAX_NUM_RETRIES 3 > +#define PSY_WORK_RETRY_DELAY_MS 10 > + > +#define FOREACH_IRQ(S) \ > + S(AICL), \ > + S(CHGIN), \ > + S(CHG), \ > + S(INLIM), \ > + S(BAT_OILO), \ > + S(CHG_STA_CC), \ > + S(CHG_STA_CV), \ > + S(CHG_STA_TO), \ > + S(CHG_STA_DONE) > + > +#define GENERATE_ENUM(e) e > +#define GENERATE_STRING(s) #s > + > +enum { > + FOREACH_IRQ(GENERATE_ENUM) > +}; > + > +static const char *const chgr_irqs_str[] = { > + FOREACH_IRQ(GENERATE_STRING) > +}; > + > +#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) > + > +enum { > + MAX77759_CHGR_RANGE_CHGCC, > + MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, > + MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, > + MAX77759_CHGR_RANGE_CHGIN_ILIM, > +}; > + > +static const struct linear_range chg_ranges[] = { > + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGCC, MAX77759_CHGR_CHGCC_MIN_UA, > + MAX77759_CHGR_CHGCC_MIN_REG, > + MAX77759_CHGR_CHGCC_MAX_REG, > + MAX77759_CHGR_CHGCC_STEP_UA), > + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, > + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV, > + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG, > + MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG, > + MAX77759_CHGR_CHG_CV_PRM_LO_STEP_MV), > + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, > + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV, > + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG, > + MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG, > + MAX77759_CHGR_CHG_CV_PRM_HI_STEP_MV), > + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGIN_ILIM, > + MAX77759_CHGR_CHGIN_ILIM_MIN_UA, > + MAX77759_CHGR_CHGIN_ILIM_MIN_REG, > + MAX77759_CHGR_CHGIN_ILIM_MAX_REG, > + MAX77759_CHGR_CHGIN_ILIM_STEP_UA), > +}; I wouldn't use macros for the selectors and values, as that makes it very hard to reason about. Please see existing regulator and power supply drivers, which almost all use the values directly for legibility. Then you can also drop them from the header file. Also, the linear range APIs support arrays of ranges, so MAX77759_CHGR_RANGE_CHG_CV_PRM_LO and MAX77759_CHGR_RANGE_CHG_CV_PRM_HI could be moved into their own range[], allowing to simplify some code below. > + > +struct max77759_charger { > + struct device *dev; > + struct regmap *regmap; > + struct power_supply *psy; > + struct regulator_dev *chgin_otg_rdev; > + struct notifier_block nb; > + struct power_supply *tcpm_psy; > + struct delayed_work psy_work; > + u32 psy_work_retry_cnt; > + int irqs[NUM_IRQS]; > + struct mutex lock; /* protects the state below */ > + enum max77759_chgr_mode mode; > +}; > + > +static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) > +{ > + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, > + MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock > + ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); > +} > + > +static int charger_input_valid(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); > + if (ret) > + return ret; > + > + return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && > + (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); > +} > + > +static int get_online(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = charger_input_valid(chg); > + if (ret <= 0) > + return ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); > + if (ret) > + return ret; > + > + guard(mutex)(&chg->lock); > + return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && > + (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); > +} > + > +static int get_status(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); > + if (ret) > + return ret; > + > + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { > + case MAX77759_CHGR_CHG_DTLS_PREQUAL: > + case MAX77759_CHGR_CHG_DTLS_CC: > + case MAX77759_CHGR_CHG_DTLS_CV: > + case MAX77759_CHGR_CHG_DTLS_TO: > + return POWER_SUPPLY_STATUS_CHARGING; > + case MAX77759_CHGR_CHG_DTLS_DONE: > + return POWER_SUPPLY_STATUS_FULL; > + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: > + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: > + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: > + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: > + return POWER_SUPPLY_STATUS_NOT_CHARGING; > + case MAX77759_CHGR_CHG_DTLS_OFF: > + return POWER_SUPPLY_STATUS_DISCHARGING; > + default: > + break; > + } > + > + return POWER_SUPPLY_STATUS_UNKNOWN; > +} > + > +static int get_charge_type(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); > + if (ret) > + return ret; > + > + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { > + case MAX77759_CHGR_CHG_DTLS_PREQUAL: > + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; > + case MAX77759_CHGR_CHG_DTLS_CC: > + case MAX77759_CHGR_CHG_DTLS_CV: > + return POWER_SUPPLY_CHARGE_TYPE_FAST; > + case MAX77759_CHGR_CHG_DTLS_TO: > + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; > + case MAX77759_CHGR_CHG_DTLS_DONE: > + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: > + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: > + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: > + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: > + case MAX77759_CHGR_CHG_DTLS_OFF: > + return POWER_SUPPLY_CHARGE_TYPE_NONE; > + default: > + break; > + } > + > + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; > +} > + > +static int get_chg_health(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); > + if (ret) > + return ret; > + > + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_OO_CHGIN_DTLS, val)) { > + case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: > + case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: > + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; > + case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: > + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; > + case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: > + return POWER_SUPPLY_HEALTH_GOOD; > + default: > + break; > + } > + > + return POWER_SUPPLY_HEALTH_UNKNOWN; > +} > + > +static int get_batt_health(struct max77759_charger *chg) > +{ > + u32 val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); > + if (ret) > + return ret; > + > + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { > + case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: > + return POWER_SUPPLY_HEALTH_NO_BATTERY; > + case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: > + return POWER_SUPPLY_HEALTH_DEAD; > + case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: > + return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; > + case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: > + case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: > + return POWER_SUPPLY_HEALTH_GOOD; > + case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: > + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; > + case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: > + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; > + case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: > + return POWER_SUPPLY_HEALTH_OVERCURRENT; > + default: > + break; > + } > + > + return POWER_SUPPLY_HEALTH_UNKNOWN; > +} > + > +static int get_health(struct max77759_charger *chg) > +{ > + int ret; > + > + ret = get_online(chg); > + if (ret < 0) > + return ret; > + > + if (ret) { > + ret = get_chg_health(chg); > + if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) > + return ret; > + } > + > + return get_batt_health(chg); > +} > + > +static int get_fast_charge_current(struct max77759_charger *chg) > +{ > + u32 regval, val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); > + if (ret) > + return ret; > + > + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); > + if (regval <= MAX77759_CHGR_CHGCC_MIN_REG) > + return MAX77759_CHGR_CHGCC_MIN_UA; I'd reference your linear range here, instead of open-coding/duplicating, making it less likely to reference a wrong value, or helping to avoid mistakes if chg_ranges[] ever changed in the future: + if (regval < chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min_sel) + return chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min; > + > + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], > + regval, &val); > + if (ret) > + return ret; > + > + return ret ? ret : val; The above is duplicating the test of ret. How about: + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); + /* use max() here because first few selectors are valid and all same */ + regval = max(regval, chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min_sel); + + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], + regval, &val); + + return ret ? ret : val; > +} > + > +static int set_fast_charge_current_limit(struct max77759_charger *chg, > + u32 cc_max_ua) > +{ > + bool found; > + u32 regval; > + int ret; > + > + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], > + cc_max_ua, ®val, &found); > + if (!found) > + return -EINVAL; > + > + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, > + MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); > +} > + > +static int get_float_voltage(struct max77759_charger *chg) > +{ > + u32 regval, val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); > + if (ret) > + return ret; > + > + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); > + switch (regval) { > + case MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG: > + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], > + regval, &val); > + break; > + case MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG: > + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], > + regval, &val); > + break; > + default: > + ret = -EINVAL; > + break; > + } > + > + return ret ? ret : val; > +} For the case statements, I'd again reference each linear range's .min_sel and .max_sel here, instead of open-coding. That said, if you switched to using linear_range_get_value_array() instead of manually determining which range to use, you wouldn't need the switch statement in the first place. > + > +static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) > +{ > + u32 regval; > + bool found; > + int ret; > + > + if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV && > + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_LO_MAX_MV) { > + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], > + fv_mv, ®val, &found); > + } else if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV && > + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_HI_MAX_MV) { > + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], > + fv_mv, ®val, &found); > + } else { > + return -EINVAL; > + } > + > + if (!found) > + return -EINVAL; If you're only going by found, you don't need ret. Is there an underlying reason to use linear_range_get_selector_high()? Could it use linear_range_get_selector_low_array() instead? Or maybe it'd make sense to introduce a linear_range_get_selector_high_array()? > + > + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, > + MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, > + regval); > +} > + > +static int get_input_current_limit(struct max77759_charger *chg) > +{ > + u32 regval, val; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); > + if (ret) > + return ret; > + > + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); > + if (regval <= MAX77759_CHGR_CHGIN_ILIM_MIN_REG) > + return MAX77759_CHGR_CHGIN_ILIM_MIN_UA; > + > + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], > + regval, &val); > + > + return ret ? ret : val; Similar to get_fast_charge_current() above. > +} > + > +static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) > +{ > + u32 regval; > + > + if (ilim_ua < 0) > + return -EINVAL; > + > + linear_range_get_selector_within(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], > + ilim_ua, ®val); > + > + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, > + MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, > + regval); > +} > + > +static const enum power_supply_property max77759_charger_props[] = { > + POWER_SUPPLY_PROP_ONLINE, > + POWER_SUPPLY_PROP_PRESENT, > + POWER_SUPPLY_PROP_STATUS, > + POWER_SUPPLY_PROP_CHARGE_TYPE, > + POWER_SUPPLY_PROP_HEALTH, > + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, > + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, > + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, > +}; > + > +static int max77759_charger_get_property(struct power_supply *psy, > + enum power_supply_property psp, > + union power_supply_propval *pval) > +{ > + struct max77759_charger *chg = power_supply_get_drvdata(psy); > + int ret; > + > + switch (psp) { > + case POWER_SUPPLY_PROP_ONLINE: > + ret = get_online(chg); > + break; > + case POWER_SUPPLY_PROP_PRESENT: > + ret = charger_input_valid(chg); > + break; > + case POWER_SUPPLY_PROP_STATUS: > + ret = get_status(chg); > + break; > + case POWER_SUPPLY_PROP_CHARGE_TYPE: > + ret = get_charge_type(chg); > + break; > + case POWER_SUPPLY_PROP_HEALTH: > + ret = get_health(chg); > + break; > + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: > + ret = get_fast_charge_current(chg); > + break; > + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: > + ret = get_float_voltage(chg); > + break; > + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: > + ret = get_input_current_limit(chg); > + break; > + default: > + ret = -EINVAL; > + } > + > + pval->intval = ret; > + return ret < 0 ? ret : 0; > +} > + > +static const struct power_supply_desc max77759_charger_desc = { > + .name = "max77759-charger", > + .type = POWER_SUPPLY_TYPE_USB, > + .properties = max77759_charger_props, > + .num_properties = ARRAY_SIZE(max77759_charger_props), > + .get_property = max77759_charger_get_property, > +}; > + > +static int charger_set_mode(struct max77759_charger *chg, > + enum max77759_chgr_mode mode) > +{ > + int ret; > + > + guard(mutex)(&chg->lock); > + > + if (chg->mode == mode) > + return 0; > + > + if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || > + mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && > + chg->mode != MAX77759_CHGR_MODE_OFF) { > + dev_err(chg->dev, "Invalid mode transition from %d to %d", > + chg->mode, mode); > + return -EINVAL; > + } > + > + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, > + MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); > + if (ret) > + return ret; > + > + chg->mode = mode; > + return 0; > +} > + > +static int enable_chgin_otg(struct regulator_dev *rdev) > +{ > + struct max77759_charger *chg = rdev_get_drvdata(rdev); > + > + return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); > +} > + > +static int disable_chgin_otg(struct regulator_dev *rdev) > +{ > + struct max77759_charger *chg = rdev_get_drvdata(rdev); > + > + return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > +} > + > +static int chgin_otg_status(struct regulator_dev *rdev) > +{ > + struct max77759_charger *chg = rdev_get_drvdata(rdev); > + > + guard(mutex)(&chg->lock); > + return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; > +} > + > +static const struct regulator_ops chgin_otg_reg_ops = { > + .enable = enable_chgin_otg, > + .disable = disable_chgin_otg, > + .is_enabled = chgin_otg_status, > +}; > + > +static const struct regulator_desc chgin_otg_reg_desc = { > + .name = "chgin-otg", > + .of_match = of_match_ptr("chgin-otg-regulator"), > + .owner = THIS_MODULE, > + .ops = &chgin_otg_reg_ops, > + .fixed_uV = 5000000, > + .n_voltages = 1, > +}; > + > +static irqreturn_t irq_handler(int irq, void *data) > +{ > + struct max77759_charger *chg = data; > + struct device *dev = chg->dev; > + int i; > + > + for (i = 0; i < NUM_IRQS && chg->irqs[i] != irq; i++) > + ; > + > + if (i == NUM_IRQS) { > + dev_err(dev, "Unable to handle irq=%d", irq); This needs to be dev_err_ratelimited() as mentioned before. You don't want to render the system unusable due to excessive potential logging. > + return IRQ_NONE; > + } > + > + if (i == BAT_OILO) > + dev_warn(dev, "Battery over-current threshold crossed"); dev_warn_ratelimited() > + > + power_supply_changed(chg->psy); > + return IRQ_HANDLED; > +} It seems the i==NUM_IRQS test is only required because it's trying to figure out if it is the BAT_OILO irq. How about instead: static irqreturn_t irq_handler(int irq, void *data) { struct max77759_charger *chg = data; power_supply_changed(chg->psy); return IRQ_HANDLED; } static irqreturn_t bat_oilo_irq_handler(int irq, void *data) { struct max77759_charger *chg = data; dev_warn_ratelimited(chg->dev, "Battery over-current threshold crossed"); return irq_handler(irq, data); } and then in max77759_init_irqhandler(), you pick bat_oilo_irq_handler() for i==BAT_OILO and irq_handler() otherwise? This way, lots of unnecessary tests are avoided for the usual cases, and even for unexpected ones. Would that work? > + > +static int max77759_init_irqhandler(struct max77759_charger *chg) > +{ > + struct device *dev = chg->dev; > + int i, ret; > + > + for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { > + ret = platform_get_irq_byname(to_platform_device(dev), > + chgr_irqs_str[i]); > + if (ret < 0) > + return dev_err_probe(dev, ret, > + "Failed to get irq resource for %s", > + chgr_irqs_str[i]); > + > + chg->irqs[i] = ret; > + ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, > + irq_handler, 0, dev_name(dev), > + chg); Could you supplement dev_name() here with the irq description please, to e.g. give more meaningful output in /proc/interrupts etc: name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev), chgr_irqs_str[i]); (+ error handling), and then use name in the request irq call instead of dev_name(dev). > + if (ret) > + return dev_err_probe(dev, ret, > + "Unable to register irq handler for %s", > + chgr_irqs_str[i]); > + } > + > + return 0; > +} > + > +static int max77759_charger_init(struct max77759_charger *chg) > +{ > + struct power_supply_battery_info *info; > + u32 regval, fast_chg_curr, fv; > + int ret; > + > + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); > + if (ret) > + return ret; > + > + chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); > + ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > + if (ret) > + return ret; > + > + if (power_supply_get_battery_info(chg->psy, &info)) { > + fv = CHG_FV_DEFAULT_MV; > + fast_chg_curr = CHG_CC_DEFAULT_UA; > + } else { > + fv = info->constant_charge_voltage_max_uv / 1000; > + fast_chg_curr = info->constant_charge_current_max_ua; > + } > + > + ret = set_fast_charge_current_limit(chg, fast_chg_curr); > + if (ret) > + return ret; > + > + ret = set_float_voltage_limit(chg, fv); > + if (ret) > + return ret; > + > + ret = unlock_prot_regs(chg, true); > + if (ret) > + return ret; > + > + /* Disable wireless charging input */ > + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, > + MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); > + if (ret) > + return ret; > + > + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, > + MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); > + if (ret) > + return ret; > + > + return unlock_prot_regs(chg, false); > +} > + > +static void psy_work_item(struct work_struct *work) > +{ > + struct max77759_charger *chg = > + container_of(work, struct max77759_charger, psy_work.work); > + union power_supply_propval current_limit, online; > + int ret; > + > + ret = power_supply_get_property(chg->tcpm_psy, > + POWER_SUPPLY_PROP_CURRENT_MAX, > + ¤t_limit); > + if (ret) { > + dev_err(chg->dev, > + "Failed to get CURRENT_MAX psy property, ret=%d", > + ret); > + goto err; > + } > + > + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, > + &online); > + if (ret) { > + dev_err(chg->dev, > + "Failed to get ONLINE psy property, ret=%d", > + ret); > + goto err; > + } > + > + if (online.intval && current_limit.intval) { > + ret = set_input_current_limit(chg, current_limit.intval); > + if (ret) { > + dev_err(chg->dev, > + "Unable to set current limit, ret=%d", ret); > + goto err; > + } > + > + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); > + } else { > + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > + } > + > + chg->psy_work_retry_cnt = 0; > + return; > + > +err: > + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) > + return; I'd say this final giving up could benefit from a dev_err(), while ... > + > + ++chg->psy_work_retry_cnt; > + dev_err(chg->dev, "Retrying %u/%u chg psy_work", > + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); ... this one could be demoted (but doesn't have to). That'd make it easier to determine if it's still in the process of trying, or if it has given up fully. > + schedule_delayed_work(&chg->psy_work, > + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); > +} > + > +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) > +{ > + struct max77759_charger *chg = container_of(nb, struct max77759_charger, > + nb); > + static const char *psy_name = "tcpm-source"; > + struct power_supply *psy = data; > + > + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || > + evt != PSY_EVENT_PROP_CHANGED) > + return NOTIFY_OK; > + > + chg->tcpm_psy = psy; Do you need locking here? What if this is changed while a previous psy_work_item() is still executing? Cheers, Andre' > + schedule_delayed_work(&chg->psy_work, 0); > + > + return NOTIFY_OK; > +} > + > +static void max_tcpci_unregister_psy_notifier(void *nb) > +{ > + power_supply_unreg_notifier(nb); > +} > + > +static int max77759_charger_probe(struct platform_device *pdev) > +{ > + struct regulator_config chgin_otg_reg_cfg; > + struct power_supply_config psy_cfg; > + struct device *dev = &pdev->dev; > + struct max77759_charger *chg; > + int ret; > + > + device_set_of_node_from_dev(dev, dev->parent); > + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); > + if (!chg) > + return -ENOMEM; > + > + platform_set_drvdata(pdev, chg); > + chg->dev = dev; > + chg->regmap = dev_get_regmap(dev->parent, "charger"); > + if (!chg->regmap) > + return dev_err_probe(dev, -ENODEV, "Missing regmap"); > + > + ret = devm_mutex_init(dev, &chg->lock); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to initialize lock"); > + > + psy_cfg.fwnode = dev_fwnode(dev); > + psy_cfg.drv_data = chg; > + chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, > + &psy_cfg); > + if (IS_ERR(chg->psy)) > + return dev_err_probe(dev, -EPROBE_DEFER, > + "Failed to register psy, ret=%ld", > + PTR_ERR(chg->psy)); > + > + ret = max77759_charger_init(chg); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to initialize max77759 charger"); > + > + chgin_otg_reg_cfg.dev = dev; > + chgin_otg_reg_cfg.driver_data = chg; > + chgin_otg_reg_cfg.of_node = dev_of_node(dev); > + chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, > + &chgin_otg_reg_cfg); > + if (IS_ERR(chg->chgin_otg_rdev)) > + return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), > + "Failed to register chgin otg regulator"); > + > + ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); > + if (ret) > + return dev_err_probe(dev, ret, "Failed to initialize psy work"); > + > + chg->nb.notifier_call = psy_changed; > + ret = power_supply_reg_notifier(&chg->nb); > + if (ret) > + return dev_err_probe(dev, ret, > + "Unable to register psy notifier"); > + > + ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, > + &chg->nb); > + if (ret) > + return dev_err_probe(dev, ret, > + "Failed to add devm action to unregister psy notifier"); > + > + return max77759_init_irqhandler(chg); > +} > + > +static const struct platform_device_id max77759_charger_id[] = { > + { .name = "max77759-charger", }, > + { } > +}; > +MODULE_DEVICE_TABLE(platform, max77759_charger_id); > + > +static struct platform_driver max77759_charger_driver = { > + .driver = { > + .name = "max77759-charger", > + .probe_type = PROBE_PREFER_ASYNCHRONOUS, > + }, > + .probe = max77759_charger_probe, > + .id_table = max77759_charger_id, > +}; > +module_platform_driver(max77759_charger_driver); > + > +MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>"); > +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); > +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 4/5] power: supply: max77759: add charger driver 2026-02-04 12:49 ` André Draszik @ 2026-02-10 0:42 ` Amit Sunil Dhamne 2026-02-10 6:39 ` André Draszik 0 siblings, 1 reply; 11+ messages in thread From: Amit Sunil Dhamne @ 2026-02-10 0:42 UTC (permalink / raw) To: André Draszik, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso Hi Andre', On 2/4/26 4:49 AM, André Draszik wrote: > On Tue, 2026-02-03 at 22:50 +0000, Amit Sunil Dhamne via B4 Relay wrote: >> From: Amit Sunil Dhamne <amitsd@google.com> >> >> Add support for MAX77759 battery charger driver. This is a 4A 1-Cell >> Li+/LiPoly dual input switch mode charger. While the device can support >> USB & wireless charger inputs, this implementation only supports USB >> input. This implementation supports both buck and boost modes. >> >> Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> >> --- >> MAINTAINERS | 6 + >> drivers/power/supply/Kconfig | 11 + >> drivers/power/supply/Makefile | 1 + >> drivers/power/supply/max77759_charger.c | 777 ++++++++++++++++++++++++++++++++ >> 4 files changed, 795 insertions(+) >> >> diff --git a/MAINTAINERS b/MAINTAINERS >> index 67db88b04537b431c927b73624993233eef43e3f..7f6d1c5c2569a1d1536642b075b3e6939553382a 100644 >> --- a/MAINTAINERS >> +++ b/MAINTAINERS >> @@ -15553,6 +15553,12 @@ F: drivers/mfd/max77759.c >> F: drivers/nvmem/max77759-nvmem.c >> F: include/linux/mfd/max77759.h >> >> +MAXIM MAX77759 BATTERY CHARGER DRIVER >> +M: Amit Sunil Dhamne <amitsd@google.com> >> +L: linux-kernel@vger.kernel.org >> +S: Maintained >> +F: drivers/power/supply/max77759_charger.c >> + >> MAXIM MAX77802 PMIC REGULATOR DEVICE DRIVER >> M: Javier Martinez Canillas <javier@dowhile0.org> >> L: linux-kernel@vger.kernel.org >> diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig >> index 92f9f7aae92f249aa165e68dbcd4cebb569286ea..3a2cdb95c98e44324151ac2b86d740ae2923ee77 100644 >> --- a/drivers/power/supply/Kconfig >> +++ b/drivers/power/supply/Kconfig >> @@ -631,6 +631,17 @@ config CHARGER_MAX77705 >> help >> Say Y to enable support for the Maxim MAX77705 battery charger. >> >> +config CHARGER_MAX77759 >> + tristate "Maxim MAX77759 battery charger driver" >> + depends on MFD_MAX77759 && REGULATOR >> + default MFD_MAX77759 >> + help >> + Say M or Y here to enable the MAX77759 battery charger. MAX77759 >> + charger is a function of the MAX77759 PMIC. This is a dual input >> + switch-mode charger. This driver supports buck and OTG boost modes. >> + >> + If built as a module, it will be called max77759_charger. >> + >> config CHARGER_MAX77976 >> tristate "Maxim MAX77976 battery charger driver" >> depends on I2C >> diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile >> index 4b79d5abc49a7fd1e37a26d0c89f94d9fe3a916f..6af905875ad5e3b393a7030405355b9a975870f6 100644 >> --- a/drivers/power/supply/Makefile >> +++ b/drivers/power/supply/Makefile >> @@ -128,3 +128,4 @@ obj-$(CONFIG_CHARGER_SURFACE) += surface_charger.o >> obj-$(CONFIG_BATTERY_UG3105) += ug3105_battery.o >> obj-$(CONFIG_CHARGER_QCOM_SMB2) += qcom_smbx.o >> obj-$(CONFIG_FUEL_GAUGE_MM8013) += mm8013.o >> +obj-$(CONFIG_CHARGER_MAX77759) += max77759_charger.o >> diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c >> new file mode 100644 >> index 0000000000000000000000000000000000000000..8c7465c1e7cdd0ff7e8d2a134752ebf7812f28ac >> --- /dev/null >> +++ b/drivers/power/supply/max77759_charger.c >> @@ -0,0 +1,777 @@ >> +// SPDX-License-Identifier: GPL-2.0-only >> +/* >> + * max77759_charger.c - Battery charger driver for MAX77759 charger device. >> + * >> + * Copyright 2025 Google LLC. >> + */ >> + >> +#include <linux/bitfield.h> >> +#include <linux/cleanup.h> >> +#include <linux/device.h> >> +#include <linux/devm-helpers.h> >> +#include <linux/interrupt.h> >> +#include <linux/irq.h> >> +#include <linux/linear_range.h> >> +#include <linux/math64.h> >> +#include <linux/mfd/max77759.h> >> +#include <linux/module.h> >> +#include <linux/mod_devicetable.h> >> +#include <linux/mutex.h> >> +#include <linux/of.h> >> +#include <linux/platform_device.h> >> +#include <linux/power_supply.h> >> +#include <linux/regmap.h> >> +#include <linux/regulator/driver.h> >> +#include <linux/string_choices.h> >> +#include <linux/workqueue.h> >> + >> +/* Default values for Fast Charge Current & Float Voltage */ >> +#define CHG_CC_DEFAULT_UA 2266770 >> +#define CHG_FV_DEFAULT_MV 4300 >> + >> +#define MAX_NUM_RETRIES 3 >> +#define PSY_WORK_RETRY_DELAY_MS 10 >> + >> +#define FOREACH_IRQ(S) \ >> + S(AICL), \ >> + S(CHGIN), \ >> + S(CHG), \ >> + S(INLIM), \ >> + S(BAT_OILO), \ >> + S(CHG_STA_CC), \ >> + S(CHG_STA_CV), \ >> + S(CHG_STA_TO), \ >> + S(CHG_STA_DONE) >> + >> +#define GENERATE_ENUM(e) e >> +#define GENERATE_STRING(s) #s >> + >> +enum { >> + FOREACH_IRQ(GENERATE_ENUM) >> +}; >> + >> +static const char *const chgr_irqs_str[] = { >> + FOREACH_IRQ(GENERATE_STRING) >> +}; >> + >> +#define NUM_IRQS ARRAY_SIZE(chgr_irqs_str) >> + >> +enum { >> + MAX77759_CHGR_RANGE_CHGCC, >> + MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, >> + MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, >> + MAX77759_CHGR_RANGE_CHGIN_ILIM, >> +}; >> + >> +static const struct linear_range chg_ranges[] = { >> + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGCC, MAX77759_CHGR_CHGCC_MIN_UA, >> + MAX77759_CHGR_CHGCC_MIN_REG, >> + MAX77759_CHGR_CHGCC_MAX_REG, >> + MAX77759_CHGR_CHGCC_STEP_UA), >> + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_LO, >> + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV, >> + MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG, >> + MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG, >> + MAX77759_CHGR_CHG_CV_PRM_LO_STEP_MV), >> + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHG_CV_PRM_HI, >> + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV, >> + MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG, >> + MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG, >> + MAX77759_CHGR_CHG_CV_PRM_HI_STEP_MV), >> + LINEAR_RANGE_IDX(MAX77759_CHGR_RANGE_CHGIN_ILIM, >> + MAX77759_CHGR_CHGIN_ILIM_MIN_UA, >> + MAX77759_CHGR_CHGIN_ILIM_MIN_REG, >> + MAX77759_CHGR_CHGIN_ILIM_MAX_REG, >> + MAX77759_CHGR_CHGIN_ILIM_STEP_UA), >> +}; > I wouldn't use macros for the selectors and values, as that makes it very > hard to reason about. Please see existing regulator and power supply > drivers, which almost all use the values directly for legibility. Then you > can also drop them from the header file. Sounds good. > Also, the linear range APIs support arrays of ranges, so > MAX77759_CHGR_RANGE_CHG_CV_PRM_LO and MAX77759_CHGR_RANGE_CHG_CV_PRM_HI > could be moved into their own range[], allowing to simplify some code > below. I think I will create separate linear_range_array[] instance for each of {fast_charge_current, chg_cv_prm and chgin_ilim}. It seems to be cleaner and can help with limit checks. >> + >> +struct max77759_charger { >> + struct device *dev; >> + struct regmap *regmap; >> + struct power_supply *psy; >> + struct regulator_dev *chgin_otg_rdev; >> + struct notifier_block nb; >> + struct power_supply *tcpm_psy; >> + struct delayed_work psy_work; >> + u32 psy_work_retry_cnt; >> + int irqs[NUM_IRQS]; >> + struct mutex lock; /* protects the state below */ >> + enum max77759_chgr_mode mode; >> +}; >> + >> +static inline int unlock_prot_regs(struct max77759_charger *chg, bool unlock) >> +{ >> + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_06, >> + MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT, unlock >> + ? MAX77759_CHGR_REG_CHG_CNFG_06_CHGPROT : 0); >> +} >> + >> +static int charger_input_valid(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_INT_OK, &val); >> + if (ret) >> + return ret; >> + >> + return (val & MAX77759_CHGR_REG_CHG_INT_CHG) && >> + (val & MAX77759_CHGR_REG_CHG_INT_CHGIN); >> +} >> + >> +static int get_online(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = charger_input_valid(chg); >> + if (ret <= 0) >> + return ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_02, &val); >> + if (ret) >> + return ret; >> + >> + guard(mutex)(&chg->lock); >> + return (val & MAX77759_CHGR_REG_CHG_DETAILS_02_CHGIN_STS) && >> + (chg->mode == MAX77759_CHGR_MODE_CHG_BUCK_ON); >> +} >> + >> +static int get_status(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); >> + if (ret) >> + return ret; >> + >> + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { >> + case MAX77759_CHGR_CHG_DTLS_PREQUAL: >> + case MAX77759_CHGR_CHG_DTLS_CC: >> + case MAX77759_CHGR_CHG_DTLS_CV: >> + case MAX77759_CHGR_CHG_DTLS_TO: >> + return POWER_SUPPLY_STATUS_CHARGING; >> + case MAX77759_CHGR_CHG_DTLS_DONE: >> + return POWER_SUPPLY_STATUS_FULL; >> + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: >> + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: >> + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: >> + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: >> + return POWER_SUPPLY_STATUS_NOT_CHARGING; >> + case MAX77759_CHGR_CHG_DTLS_OFF: >> + return POWER_SUPPLY_STATUS_DISCHARGING; >> + default: >> + break; >> + } >> + >> + return POWER_SUPPLY_STATUS_UNKNOWN; >> +} >> + >> +static int get_charge_type(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); >> + if (ret) >> + return ret; >> + >> + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_CHG_DTLS, val)) { >> + case MAX77759_CHGR_CHG_DTLS_PREQUAL: >> + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; >> + case MAX77759_CHGR_CHG_DTLS_CC: >> + case MAX77759_CHGR_CHG_DTLS_CV: >> + return POWER_SUPPLY_CHARGE_TYPE_FAST; >> + case MAX77759_CHGR_CHG_DTLS_TO: >> + return POWER_SUPPLY_CHARGE_TYPE_STANDARD; >> + case MAX77759_CHGR_CHG_DTLS_DONE: >> + case MAX77759_CHGR_CHG_DTLS_TIMER_FAULT: >> + case MAX77759_CHGR_CHG_DTLS_SUSP_BATT_THM: >> + case MAX77759_CHGR_CHG_DTLS_OFF_WDOG_TIMER: >> + case MAX77759_CHGR_CHG_DTLS_SUSP_JEITA: >> + case MAX77759_CHGR_CHG_DTLS_OFF: >> + return POWER_SUPPLY_CHARGE_TYPE_NONE; >> + default: >> + break; >> + } >> + >> + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; >> +} >> + >> +static int get_chg_health(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_00, &val); >> + if (ret) >> + return ret; >> + >> + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_OO_CHGIN_DTLS, val)) { >> + case MAX77759_CHGR_CHGIN_DTLS_VBUS_UNDERVOLTAGE: >> + case MAX77759_CHGR_CHGIN_DTLS_VBUS_MARGINAL_VOLTAGE: >> + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; >> + case MAX77759_CHGR_CHGIN_DTLS_VBUS_OVERVOLTAGE: >> + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; >> + case MAX77759_CHGR_CHGIN_DTLS_VBUS_VALID: >> + return POWER_SUPPLY_HEALTH_GOOD; >> + default: >> + break; >> + } >> + >> + return POWER_SUPPLY_HEALTH_UNKNOWN; >> +} >> + >> +static int get_batt_health(struct max77759_charger *chg) >> +{ >> + u32 val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_DETAILS_01, &val); >> + if (ret) >> + return ret; >> + >> + switch (FIELD_GET(MAX77759_CHGR_REG_CHG_DETAILS_01_BAT_DTLS, val)) { >> + case MAX77759_CHGR_BAT_DTLS_NO_BATT_CHG_SUSP: >> + return POWER_SUPPLY_HEALTH_NO_BATTERY; >> + case MAX77759_CHGR_BAT_DTLS_DEAD_BATTERY: >> + return POWER_SUPPLY_HEALTH_DEAD; >> + case MAX77759_CHGR_BAT_DTLS_BAT_CHG_TIMER_FAULT: >> + return POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; >> + case MAX77759_CHGR_BAT_DTLS_BAT_OKAY: >> + case MAX77759_CHGR_BAT_DTLS_BAT_ONLY_MODE: >> + return POWER_SUPPLY_HEALTH_GOOD; >> + case MAX77759_CHGR_BAT_DTLS_BAT_UNDERVOLTAGE: >> + return POWER_SUPPLY_HEALTH_UNDERVOLTAGE; >> + case MAX77759_CHGR_BAT_DTLS_BAT_OVERVOLTAGE: >> + return POWER_SUPPLY_HEALTH_OVERVOLTAGE; >> + case MAX77759_CHGR_BAT_DTLS_BAT_OVERCURRENT: >> + return POWER_SUPPLY_HEALTH_OVERCURRENT; >> + default: >> + break; >> + } >> + >> + return POWER_SUPPLY_HEALTH_UNKNOWN; >> +} >> + >> +static int get_health(struct max77759_charger *chg) >> +{ >> + int ret; >> + >> + ret = get_online(chg); >> + if (ret < 0) >> + return ret; >> + >> + if (ret) { >> + ret = get_chg_health(chg); >> + if (ret < 0 || ret != POWER_SUPPLY_HEALTH_GOOD) >> + return ret; >> + } >> + >> + return get_batt_health(chg); >> +} >> + >> +static int get_fast_charge_current(struct max77759_charger *chg) >> +{ >> + u32 regval, val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, ®val); >> + if (ret) >> + return ret; >> + >> + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); >> + if (regval <= MAX77759_CHGR_CHGCC_MIN_REG) >> + return MAX77759_CHGR_CHGCC_MIN_UA; > I'd reference your linear range here, instead of open-coding/duplicating, > making it less likely to reference a wrong value, or helping to avoid mistakes > if chg_ranges[] ever changed in the future: > > + if (regval < chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min_sel) > + return chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min; I think with the new changes I am coming up with this limit check may not be needed. > >> + >> + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], >> + regval, &val); >> + if (ret) >> + return ret; >> + >> + return ret ? ret : val; > The above is duplicating the test of ret. How about: > > + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); > + /* use max() here because first few selectors are valid and all same */ > + regval = max(regval, chg_ranges[MAX77759_CHGR_RANGE_CHGCC].min_sel); > + > + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], > + regval, &val); > + > + return ret ? ret : val; Thanks for the suggestion. Will fix. >> +} >> + >> +static int set_fast_charge_current_limit(struct max77759_charger *chg, >> + u32 cc_max_ua) >> +{ >> + bool found; >> + u32 regval; >> + int ret; >> + >> + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHGCC], >> + cc_max_ua, ®val, &found); >> + if (!found) >> + return -EINVAL; >> + >> + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_02, >> + MAX77759_CHGR_REG_CHG_CNFG_02_CHGCC, regval); >> +} >> + >> +static int get_float_voltage(struct max77759_charger *chg) >> +{ >> + u32 regval, val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, ®val); >> + if (ret) >> + return ret; >> + >> + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, regval); >> + switch (regval) { >> + case MAX77759_CHGR_CHG_CV_PRM_HI_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_HI_MAX_REG: >> + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], >> + regval, &val); >> + break; >> + case MAX77759_CHGR_CHG_CV_PRM_LO_MIN_REG ... MAX77759_CHGR_CHG_CV_PRM_LO_MAX_REG: >> + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], >> + regval, &val); >> + break; >> + default: >> + ret = -EINVAL; >> + break; >> + } >> + >> + return ret ? ret : val; >> +} > For the case statements, I'd again reference each linear range's .min_sel > and .max_sel here, instead of open-coding. > > That said, if you switched to using linear_range_get_value_array() instead > of manually determining which range to use, you wouldn't need the switch > statement in the first place. Will fix. >> + >> +static int set_float_voltage_limit(struct max77759_charger *chg, u32 fv_mv) >> +{ >> + u32 regval; >> + bool found; >> + int ret; >> + >> + if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_LO_MIN_MV && >> + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_LO_MAX_MV) { >> + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_LO], >> + fv_mv, ®val, &found); >> + } else if (fv_mv >= MAX77759_CHGR_CHG_CV_PRM_HI_MIN_MV && >> + fv_mv <= MAX77759_CHGR_CHG_CV_PRM_HI_MAX_MV) { >> + ret = linear_range_get_selector_high(&chg_ranges[MAX77759_CHGR_RANGE_CHG_CV_PRM_HI], >> + fv_mv, ®val, &found); >> + } else { >> + return -EINVAL; >> + } >> + >> + if (!found) >> + return -EINVAL; > If you're only going by found, you don't need ret. > > Is there an underlying reason to use linear_range_get_selector_high()? > Could it use linear_range_get_selector_low_array() instead? Or maybe > it'd make sense to introduce a linear_range_get_selector_high_array()? I want to use the rounded_up value. I will implement the linear_range_get_selector_high_array(). > >> + >> + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_04, >> + MAX77759_CHGR_REG_CHG_CNFG_04_CHG_CV_PRM, >> + regval); >> +} >> + >> +static int get_input_current_limit(struct max77759_charger *chg) >> +{ >> + u32 regval, val; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, ®val); >> + if (ret) >> + return ret; >> + >> + regval = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, regval); >> + if (regval <= MAX77759_CHGR_CHGIN_ILIM_MIN_REG) >> + return MAX77759_CHGR_CHGIN_ILIM_MIN_UA; >> + >> + ret = linear_range_get_value(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], >> + regval, &val); >> + >> + return ret ? ret : val; > Similar to get_fast_charge_current() above. Will fix. >> +} >> + >> +static int set_input_current_limit(struct max77759_charger *chg, int ilim_ua) >> +{ >> + u32 regval; >> + >> + if (ilim_ua < 0) >> + return -EINVAL; >> + >> + linear_range_get_selector_within(&chg_ranges[MAX77759_CHGR_RANGE_CHGIN_ILIM], >> + ilim_ua, ®val); >> + >> + return regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_09, >> + MAX77759_CHGR_REG_CHG_CNFG_09_CHGIN_ILIM, >> + regval); >> +} >> + >> +static const enum power_supply_property max77759_charger_props[] = { >> + POWER_SUPPLY_PROP_ONLINE, >> + POWER_SUPPLY_PROP_PRESENT, >> + POWER_SUPPLY_PROP_STATUS, >> + POWER_SUPPLY_PROP_CHARGE_TYPE, >> + POWER_SUPPLY_PROP_HEALTH, >> + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, >> + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, >> + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, >> +}; >> + >> +static int max77759_charger_get_property(struct power_supply *psy, >> + enum power_supply_property psp, >> + union power_supply_propval *pval) >> +{ >> + struct max77759_charger *chg = power_supply_get_drvdata(psy); >> + int ret; >> + >> + switch (psp) { >> + case POWER_SUPPLY_PROP_ONLINE: >> + ret = get_online(chg); >> + break; >> + case POWER_SUPPLY_PROP_PRESENT: >> + ret = charger_input_valid(chg); >> + break; >> + case POWER_SUPPLY_PROP_STATUS: >> + ret = get_status(chg); >> + break; >> + case POWER_SUPPLY_PROP_CHARGE_TYPE: >> + ret = get_charge_type(chg); >> + break; >> + case POWER_SUPPLY_PROP_HEALTH: >> + ret = get_health(chg); >> + break; >> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: >> + ret = get_fast_charge_current(chg); >> + break; >> + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: >> + ret = get_float_voltage(chg); >> + break; >> + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: >> + ret = get_input_current_limit(chg); >> + break; >> + default: >> + ret = -EINVAL; >> + } >> + >> + pval->intval = ret; >> + return ret < 0 ? ret : 0; >> +} >> + >> +static const struct power_supply_desc max77759_charger_desc = { >> + .name = "max77759-charger", >> + .type = POWER_SUPPLY_TYPE_USB, >> + .properties = max77759_charger_props, >> + .num_properties = ARRAY_SIZE(max77759_charger_props), >> + .get_property = max77759_charger_get_property, >> +}; >> + >> +static int charger_set_mode(struct max77759_charger *chg, >> + enum max77759_chgr_mode mode) >> +{ >> + int ret; >> + >> + guard(mutex)(&chg->lock); >> + >> + if (chg->mode == mode) >> + return 0; >> + >> + if ((mode == MAX77759_CHGR_MODE_CHG_BUCK_ON || >> + mode == MAX77759_CHGR_MODE_OTG_BOOST_ON) && >> + chg->mode != MAX77759_CHGR_MODE_OFF) { >> + dev_err(chg->dev, "Invalid mode transition from %d to %d", >> + chg->mode, mode); >> + return -EINVAL; >> + } >> + >> + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, >> + MAX77759_CHGR_REG_CHG_CNFG_00_MODE, mode); >> + if (ret) >> + return ret; >> + >> + chg->mode = mode; >> + return 0; >> +} >> + >> +static int enable_chgin_otg(struct regulator_dev *rdev) >> +{ >> + struct max77759_charger *chg = rdev_get_drvdata(rdev); >> + >> + return charger_set_mode(chg, MAX77759_CHGR_MODE_OTG_BOOST_ON); >> +} >> + >> +static int disable_chgin_otg(struct regulator_dev *rdev) >> +{ >> + struct max77759_charger *chg = rdev_get_drvdata(rdev); >> + >> + return charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); >> +} >> + >> +static int chgin_otg_status(struct regulator_dev *rdev) >> +{ >> + struct max77759_charger *chg = rdev_get_drvdata(rdev); >> + >> + guard(mutex)(&chg->lock); >> + return chg->mode == MAX77759_CHGR_MODE_OTG_BOOST_ON; >> +} >> + >> +static const struct regulator_ops chgin_otg_reg_ops = { >> + .enable = enable_chgin_otg, >> + .disable = disable_chgin_otg, >> + .is_enabled = chgin_otg_status, >> +}; >> + >> +static const struct regulator_desc chgin_otg_reg_desc = { >> + .name = "chgin-otg", >> + .of_match = of_match_ptr("chgin-otg-regulator"), >> + .owner = THIS_MODULE, >> + .ops = &chgin_otg_reg_ops, >> + .fixed_uV = 5000000, >> + .n_voltages = 1, >> +}; >> + >> +static irqreturn_t irq_handler(int irq, void *data) >> +{ >> + struct max77759_charger *chg = data; >> + struct device *dev = chg->dev; >> + int i; >> + >> + for (i = 0; i < NUM_IRQS && chg->irqs[i] != irq; i++) >> + ; >> + >> + if (i == NUM_IRQS) { >> + dev_err(dev, "Unable to handle irq=%d", irq); > This needs to be dev_err_ratelimited() as mentioned before. You don't want > to render the system unusable due to excessive potential logging. Will fix. > >> + return IRQ_NONE; >> + } >> + >> + if (i == BAT_OILO) >> + dev_warn(dev, "Battery over-current threshold crossed"); > dev_warn_ratelimited() Will fix. > >> + >> + power_supply_changed(chg->psy); >> + return IRQ_HANDLED; >> +} > It seems the i==NUM_IRQS test is only required because it's trying to > figure out if it is the BAT_OILO irq. How about instead: It's also checking if this irq handler callback is called with a valid irq number. But I think that may not be required as it's redundant. > > > static irqreturn_t irq_handler(int irq, void *data) > { > struct max77759_charger *chg = data; > > power_supply_changed(chg->psy); > > return IRQ_HANDLED; > } > > static irqreturn_t bat_oilo_irq_handler(int irq, void *data) > { > struct max77759_charger *chg = data; > > dev_warn_ratelimited(chg->dev, "Battery over-current threshold crossed"); > > return irq_handler(irq, data); > } > > and then in max77759_init_irqhandler(), you pick bat_oilo_irq_handler() > for i==BAT_OILO and irq_handler() otherwise? This way, lots of unnecessary > tests are avoided for the usual cases, and even for unexpected ones. > > Would that work? LGTM. Will fix. > >> + >> +static int max77759_init_irqhandler(struct max77759_charger *chg) >> +{ >> + struct device *dev = chg->dev; >> + int i, ret; >> + >> + for (i = 0; i < ARRAY_SIZE(chgr_irqs_str); i++) { >> + ret = platform_get_irq_byname(to_platform_device(dev), >> + chgr_irqs_str[i]); >> + if (ret < 0) >> + return dev_err_probe(dev, ret, >> + "Failed to get irq resource for %s", >> + chgr_irqs_str[i]); >> + >> + chg->irqs[i] = ret; >> + ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, >> + irq_handler, 0, dev_name(dev), >> + chg); > Could you supplement dev_name() here with the irq description please, > to e.g. give more meaningful output in /proc/interrupts etc: > > name = devm_kasprintf(dev, GFP_KERNEL, "%s:%s", dev_name(dev), > chgr_irqs_str[i]); > > (+ error handling), and then use name in the request irq call instead of > dev_name(dev). Will fix. > >> + if (ret) >> + return dev_err_probe(dev, ret, >> + "Unable to register irq handler for %s", >> + chgr_irqs_str[i]); >> + } >> + >> + return 0; >> +} >> + >> +static int max77759_charger_init(struct max77759_charger *chg) >> +{ >> + struct power_supply_battery_info *info; >> + u32 regval, fast_chg_curr, fv; >> + int ret; >> + >> + ret = regmap_read(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_00, ®val); >> + if (ret) >> + return ret; >> + >> + chg->mode = FIELD_GET(MAX77759_CHGR_REG_CHG_CNFG_00_MODE, regval); >> + ret = charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); >> + if (ret) >> + return ret; >> + >> + if (power_supply_get_battery_info(chg->psy, &info)) { >> + fv = CHG_FV_DEFAULT_MV; >> + fast_chg_curr = CHG_CC_DEFAULT_UA; >> + } else { >> + fv = info->constant_charge_voltage_max_uv / 1000; >> + fast_chg_curr = info->constant_charge_current_max_ua; >> + } >> + >> + ret = set_fast_charge_current_limit(chg, fast_chg_curr); >> + if (ret) >> + return ret; >> + >> + ret = set_float_voltage_limit(chg, fv); >> + if (ret) >> + return ret; >> + >> + ret = unlock_prot_regs(chg, true); >> + if (ret) >> + return ret; >> + >> + /* Disable wireless charging input */ >> + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_12, >> + MAX77759_CHGR_REG_CHG_CNFG_12_WCINSEL, 0); >> + if (ret) >> + return ret; >> + >> + ret = regmap_update_bits(chg->regmap, MAX77759_CHGR_REG_CHG_CNFG_18, >> + MAX77759_CHGR_REG_CHG_CNFG_18_WDTEN, 0); >> + if (ret) >> + return ret; >> + >> + return unlock_prot_regs(chg, false); >> +} >> + >> +static void psy_work_item(struct work_struct *work) >> +{ >> + struct max77759_charger *chg = >> + container_of(work, struct max77759_charger, psy_work.work); >> + union power_supply_propval current_limit, online; >> + int ret; >> + >> + ret = power_supply_get_property(chg->tcpm_psy, >> + POWER_SUPPLY_PROP_CURRENT_MAX, >> + ¤t_limit); >> + if (ret) { >> + dev_err(chg->dev, >> + "Failed to get CURRENT_MAX psy property, ret=%d", >> + ret); >> + goto err; >> + } >> + >> + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, >> + &online); >> + if (ret) { >> + dev_err(chg->dev, >> + "Failed to get ONLINE psy property, ret=%d", >> + ret); >> + goto err; >> + } >> + >> + if (online.intval && current_limit.intval) { >> + ret = set_input_current_limit(chg, current_limit.intval); >> + if (ret) { >> + dev_err(chg->dev, >> + "Unable to set current limit, ret=%d", ret); >> + goto err; >> + } >> + >> + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); >> + } else { >> + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); >> + } >> + >> + chg->psy_work_retry_cnt = 0; >> + return; >> + >> +err: >> + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); >> + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) >> + return; > I'd say this final giving up could benefit from a dev_err(), while ... I want to clarify if you want me to add this final giving up print just once or every time I am returning early? > >> + >> + ++chg->psy_work_retry_cnt; >> + dev_err(chg->dev, "Retrying %u/%u chg psy_work", >> + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); > ... this one could be demoted (but doesn't have to). > > That'd make it easier to determine if it's still in the process of > trying, or if it has given up fully. I was assuming the printing of "3/3" would indicate the final giving up and sufficient. > >> + schedule_delayed_work(&chg->psy_work, >> + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); >> +} >> + >> +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) >> +{ >> + struct max77759_charger *chg = container_of(nb, struct max77759_charger, >> + nb); >> + static const char *psy_name = "tcpm-source"; >> + struct power_supply *psy = data; >> + >> + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || >> + evt != PSY_EVENT_PROP_CHANGED) >> + return NOTIFY_OK; >> + >> + chg->tcpm_psy = psy; > Do you need locking here? What if this is changed while a previous > psy_work_item() is still executing? I don't think that's ever possible in this case though. The power supply that this driver registers is downstream of the tcpm's. I could possibly optimize this by means of conditional initialization. BR, Amit > > Cheers, > Andre' > > >> + schedule_delayed_work(&chg->psy_work, 0); >> + >> + return NOTIFY_OK; >> +} >> + >> +static void max_tcpci_unregister_psy_notifier(void *nb) >> +{ >> + power_supply_unreg_notifier(nb); >> +} >> + >> +static int max77759_charger_probe(struct platform_device *pdev) >> +{ >> + struct regulator_config chgin_otg_reg_cfg; >> + struct power_supply_config psy_cfg; >> + struct device *dev = &pdev->dev; >> + struct max77759_charger *chg; >> + int ret; >> + >> + device_set_of_node_from_dev(dev, dev->parent); >> + chg = devm_kzalloc(dev, sizeof(*chg), GFP_KERNEL); >> + if (!chg) >> + return -ENOMEM; >> + >> + platform_set_drvdata(pdev, chg); >> + chg->dev = dev; >> + chg->regmap = dev_get_regmap(dev->parent, "charger"); >> + if (!chg->regmap) >> + return dev_err_probe(dev, -ENODEV, "Missing regmap"); >> + >> + ret = devm_mutex_init(dev, &chg->lock); >> + if (ret) >> + return dev_err_probe(dev, ret, "Failed to initialize lock"); >> + >> + psy_cfg.fwnode = dev_fwnode(dev); >> + psy_cfg.drv_data = chg; >> + chg->psy = devm_power_supply_register(dev, &max77759_charger_desc, >> + &psy_cfg); >> + if (IS_ERR(chg->psy)) >> + return dev_err_probe(dev, -EPROBE_DEFER, >> + "Failed to register psy, ret=%ld", >> + PTR_ERR(chg->psy)); >> + >> + ret = max77759_charger_init(chg); >> + if (ret) >> + return dev_err_probe(dev, ret, >> + "Failed to initialize max77759 charger"); >> + >> + chgin_otg_reg_cfg.dev = dev; >> + chgin_otg_reg_cfg.driver_data = chg; >> + chgin_otg_reg_cfg.of_node = dev_of_node(dev); >> + chg->chgin_otg_rdev = devm_regulator_register(dev, &chgin_otg_reg_desc, >> + &chgin_otg_reg_cfg); >> + if (IS_ERR(chg->chgin_otg_rdev)) >> + return dev_err_probe(dev, PTR_ERR(chg->chgin_otg_rdev), >> + "Failed to register chgin otg regulator"); >> + >> + ret = devm_delayed_work_autocancel(dev, &chg->psy_work, psy_work_item); >> + if (ret) >> + return dev_err_probe(dev, ret, "Failed to initialize psy work"); >> + >> + chg->nb.notifier_call = psy_changed; >> + ret = power_supply_reg_notifier(&chg->nb); >> + if (ret) >> + return dev_err_probe(dev, ret, >> + "Unable to register psy notifier"); >> + >> + ret = devm_add_action_or_reset(dev, max_tcpci_unregister_psy_notifier, >> + &chg->nb); >> + if (ret) >> + return dev_err_probe(dev, ret, >> + "Failed to add devm action to unregister psy notifier"); >> + >> + return max77759_init_irqhandler(chg); >> +} >> + >> +static const struct platform_device_id max77759_charger_id[] = { >> + { .name = "max77759-charger", }, >> + { } >> +}; >> +MODULE_DEVICE_TABLE(platform, max77759_charger_id); >> + >> +static struct platform_driver max77759_charger_driver = { >> + .driver = { >> + .name = "max77759-charger", >> + .probe_type = PROBE_PREFER_ASYNCHRONOUS, >> + }, >> + .probe = max77759_charger_probe, >> + .id_table = max77759_charger_id, >> +}; >> +module_platform_driver(max77759_charger_driver); >> + >> +MODULE_AUTHOR("Amit Sunil Dhamne <amitsd@google.com>"); >> +MODULE_DESCRIPTION("Maxim MAX77759 charger driver"); >> +MODULE_LICENSE("GPL"); ^ permalink raw reply [flat|nested] 11+ messages in thread
* Re: [PATCH v5 4/5] power: supply: max77759: add charger driver 2026-02-10 0:42 ` Amit Sunil Dhamne @ 2026-02-10 6:39 ` André Draszik 0 siblings, 0 replies; 11+ messages in thread From: André Draszik @ 2026-02-10 6:39 UTC (permalink / raw) To: Amit Sunil Dhamne, Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso Hi Amit, On Mon, 2026-02-09 at 16:42 -0800, Amit Sunil Dhamne wrote: > Hi Andre', > > On 2/4/26 4:49 AM, André Draszik wrote: > > On Tue, 2026-02-03 at 22:50 +0000, Amit Sunil Dhamne via B4 Relay wrote: > > > > > + > > > +static void psy_work_item(struct work_struct *work) > > > +{ > > > + struct max77759_charger *chg = > > > + container_of(work, struct max77759_charger, psy_work.work); > > > + union power_supply_propval current_limit, online; > > > + int ret; > > > + > > > + ret = power_supply_get_property(chg->tcpm_psy, > > > + POWER_SUPPLY_PROP_CURRENT_MAX, > > > + ¤t_limit); > > > + if (ret) { > > > + dev_err(chg->dev, > > > + "Failed to get CURRENT_MAX psy property, ret=%d", > > > + ret); > > > + goto err; > > > + } > > > + > > > + ret = power_supply_get_property(chg->tcpm_psy, POWER_SUPPLY_PROP_ONLINE, > > > + &online); > > > + if (ret) { > > > + dev_err(chg->dev, > > > + "Failed to get ONLINE psy property, ret=%d", > > > + ret); > > > + goto err; > > > + } > > > + > > > + if (online.intval && current_limit.intval) { > > > + ret = set_input_current_limit(chg, current_limit.intval); > > > + if (ret) { > > > + dev_err(chg->dev, > > > + "Unable to set current limit, ret=%d", ret); > > > + goto err; > > > + } > > > + > > > + charger_set_mode(chg, MAX77759_CHGR_MODE_CHG_BUCK_ON); > > > + } else { > > > + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > > > + } > > > + > > > + chg->psy_work_retry_cnt = 0; > > > + return; > > > + > > > +err: > > > + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); > > > + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) > > > + return; > > I'd say this final giving up could benefit from a dev_err(), while ... > > I want to clarify if you want me to add this final giving up print just > once or every time I am returning early? I meant something along the lines of this: + if (chg->psy_work_retry_cnt) + dev_dbg(chg->dev, "chg psy_work succeeded after %d\n", + chg->psy_work_retry_cnt) + chg->psy_work_retry_cnt = 0; + return; + +err: + charger_set_mode(chg, MAX77759_CHGR_MODE_OFF); + if (chg->psy_work_retry_cnt >= MAX_NUM_RETRIES) { + dev_warn(chg->dev, "chg psy_work failed, giving up", + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); + return; + } > > > > > + > > > + ++chg->psy_work_retry_cnt; > > > + dev_err(chg->dev, "Retrying %u/%u chg psy_work", > > > + chg->psy_work_retry_cnt, MAX_NUM_RETRIES); > > ... this one could be demoted (but doesn't have to). > > > > That'd make it easier to determine if it's still in the process of > > trying, or if it has given up fully. > > I was assuming the printing of "3/3" would indicate the final giving up > and sufficient. If you see 3/3 in the log, you'll know that it has scheduled the work (for the last attempt), but you won't easily know if 3/3 has ran yet or if it has completed successfully this time or if it was still unsuccessful. > > > > > + schedule_delayed_work(&chg->psy_work, > > > + msecs_to_jiffies(PSY_WORK_RETRY_DELAY_MS)); > > > +} > > > + > > > +static int psy_changed(struct notifier_block *nb, unsigned long evt, void *data) > > > +{ > > > + struct max77759_charger *chg = container_of(nb, struct max77759_charger, > > > + nb); > > > + static const char *psy_name = "tcpm-source"; > > > + struct power_supply *psy = data; > > > + > > > + if (!strnstr(psy->desc->name, psy_name, strlen(psy_name)) || > > > + evt != PSY_EVENT_PROP_CHANGED) > > > + return NOTIFY_OK; > > > + > > > + chg->tcpm_psy = psy; > > Do you need locking here? What if this is changed while a previous > > psy_work_item() is still executing? > > I don't think that's ever possible in this case though. The power > supply that this driver registers is downstream of the tcpm's. A previous work could still be executing (e.g. due to retrying), no? It should also probably set chg->psy_work_retry_cnt = 0; here, to allow it to retry up to MAX_NUM_RETRIES when a new work is queued and a previous one was unsuccessful? Cheers, Andre ^ permalink raw reply [flat|nested] 11+ messages in thread
* [PATCH v5 5/5] usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay ` (3 preceding siblings ...) 2026-02-03 22:50 ` [PATCH v5 4/5] power: supply: max77759: add charger driver Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 ` Amit Sunil Dhamne via B4 Relay 4 siblings, 0 replies; 11+ messages in thread From: Amit Sunil Dhamne via B4 Relay @ 2026-02-03 22:50 UTC (permalink / raw) To: Sebastian Reichel, Rob Herring, Krzysztof Kozlowski, Conor Dooley, André Draszik, Lee Jones, Greg Kroah-Hartman, Badhri Jagan Sridharan, Heikki Krogerus, Peter Griffin, Tudor Ambarus, Alim Akhtar Cc: linux-kernel, linux-pm, devicetree, linux-usb, linux-arm-kernel, linux-samsung-soc, RD Babiera, Kyle Tso, Amit Sunil Dhamne From: Amit Sunil Dhamne <amitsd@google.com> TCPCI maxim driver directly writes to the charger's register space to set charger mode depending on the power role. As MAX77759 chg driver exists, this WAR is not required. Instead, use a regulator interface to source vbus when typec is in source power mode. In other power modes, this regulator will be turned off if active. Signed-off-by: Amit Sunil Dhamne <amitsd@google.com> Reviewed-by: Heikki Krogerus <heikki.krogerus@linux.intel.com> Reviewed-by: André Draszik <andre.draszik@linaro.org> --- drivers/usb/typec/tcpm/tcpci_maxim.h | 1 + drivers/usb/typec/tcpm/tcpci_maxim_core.c | 54 +++++++++++++++++++------------ 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim.h b/drivers/usb/typec/tcpm/tcpci_maxim.h index b33540a42a953dc6d8197790ee4af3b6f52791ce..b314606eb0f67ddbc80d8760244aa6dee61bebc1 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim.h +++ b/drivers/usb/typec/tcpm/tcpci_maxim.h @@ -60,6 +60,7 @@ struct max_tcpci_chip { struct tcpm_port *port; enum contamiant_state contaminant_state; bool veto_vconn_swap; + struct regulator *vbus_reg; }; static inline int max_tcpci_read16(struct max_tcpci_chip *chip, unsigned int reg, u16 *val) diff --git a/drivers/usb/typec/tcpm/tcpci_maxim_core.c b/drivers/usb/typec/tcpm/tcpci_maxim_core.c index 19f63865079658fb2a446dc390262d141b940e9a..e9e2405c5ca036e28c307088d4b785726834e71f 100644 --- a/drivers/usb/typec/tcpm/tcpci_maxim_core.c +++ b/drivers/usb/typec/tcpm/tcpci_maxim_core.c @@ -10,6 +10,7 @@ #include <linux/kernel.h> #include <linux/module.h> #include <linux/regmap.h> +#include <linux/regulator/consumer.h> #include <linux/usb/pd.h> #include <linux/usb/tcpci.h> #include <linux/usb/tcpm.h> @@ -35,12 +36,6 @@ */ #define TCPC_RECEIVE_BUFFER_LEN 32 -#define MAX_BUCK_BOOST_SID 0x69 -#define MAX_BUCK_BOOST_OP 0xb9 -#define MAX_BUCK_BOOST_OFF 0 -#define MAX_BUCK_BOOST_SOURCE 0xa -#define MAX_BUCK_BOOST_SINK 0x5 - static const struct regmap_range max_tcpci_tcpci_range[] = { regmap_reg_range(0x00, 0x95) }; @@ -202,32 +197,49 @@ static void process_rx(struct max_tcpci_chip *chip, u16 status) tcpm_pd_receive(chip->port, &msg, rx_type); } +static int get_vbus_regulator_handle(struct max_tcpci_chip *chip) +{ + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + chip->vbus_reg = devm_regulator_get_exclusive(chip->dev, + "vbus"); + if (IS_ERR_OR_NULL(chip->vbus_reg)) { + dev_err(chip->dev, + "Failed to get vbus regulator handle"); + return -ENODEV; + } + } + + return 0; +} + static int max_tcpci_set_vbus(struct tcpci *tcpci, struct tcpci_data *tdata, bool source, bool sink) { struct max_tcpci_chip *chip = tdata_to_max_tcpci(tdata); - u8 buffer_source[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SOURCE}; - u8 buffer_sink[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_SINK}; - u8 buffer_none[2] = {MAX_BUCK_BOOST_OP, MAX_BUCK_BOOST_OFF}; - struct i2c_client *i2c = chip->client; int ret; - struct i2c_msg msgs[] = { - { - .addr = MAX_BUCK_BOOST_SID, - .flags = i2c->flags & I2C_M_TEN, - .len = 2, - .buf = source ? buffer_source : sink ? buffer_sink : buffer_none, - }, - }; - if (source && sink) { dev_err(chip->dev, "Both source and sink set\n"); return -EINVAL; } - ret = i2c_transfer(i2c->adapter, msgs, 1); + ret = get_vbus_regulator_handle(chip); + if (ret) { + /* + * Regulator is not necessary for sink only applications. Return + * success in cases where sink mode is being modified. + */ + return source ? ret : 1; + } + + if (source) { + if (!regulator_is_enabled(chip->vbus_reg)) + ret = regulator_enable(chip->vbus_reg); + } else { + if (regulator_is_enabled(chip->vbus_reg)) + ret = regulator_disable(chip->vbus_reg); + } - return ret < 0 ? ret : 1; + return ret < 0 ? ret : 1; } static void process_power_status(struct max_tcpci_chip *chip) -- 2.53.0.rc2.204.g2597b5adb4-goog ^ permalink raw reply [flat|nested] 11+ messages in thread
end of thread, other threads:[~2026-02-10 6:38 UTC | newest] Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2026-02-03 22:50 [PATCH v5 0/5] Introduce MAX77759 charger driver Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 1/5] dt-bindings: mfd: maxim,max77759: reference power-supply schema and add regulator property Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 2/5] dt-bindings: usb: maxim,max33359: Add supply property for vbus Amit Sunil Dhamne via B4 Relay 2026-02-03 22:50 ` [PATCH v5 3/5] mfd: max77759: add register bitmasks and modify irq configs for charger Amit Sunil Dhamne via B4 Relay 2026-02-04 6:46 ` André Draszik 2026-02-03 22:50 ` [PATCH v5 4/5] power: supply: max77759: add charger driver Amit Sunil Dhamne via B4 Relay 2026-02-04 5:34 ` kernel test robot 2026-02-04 12:49 ` André Draszik 2026-02-10 0:42 ` Amit Sunil Dhamne 2026-02-10 6:39 ` André Draszik 2026-02-03 22:50 ` [PATCH v5 5/5] usb: typec: tcpm/tcpci_maxim: deprecate WAR for setting charger mode Amit Sunil Dhamne via B4 Relay
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®