diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index 32697b926cc8..5641f1fd5fd6 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -525,7 +525,7 @@ Description: Valid values: "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", - "NiCd", "LiMn" + "NiCd", "LiMn", "PbAc", "NiZn", "RAM", "ZnAr" What: /sys/class/power_supply//voltage_avg, diff --git a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml index 979a377cb4ff..3d07aa004eb6 100644 --- a/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml +++ b/Documentation/devicetree/bindings/power/reset/qcom,pon.yaml @@ -17,12 +17,16 @@ description: | properties: compatible: - enum: - - qcom,pm8916-pon - - qcom,pm8941-pon - - qcom,pms405-pon - - qcom,pm8998-pon - - qcom,pmk8350-pon + oneOf: + - enum: + - qcom,pm8916-pon + - qcom,pm8941-pon + - qcom,pms405-pon + - qcom,pm8998-pon + - qcom,pmk8350-pon + - items: + - const: qcom,pmm8654au-pon + - const: qcom,pmk8350-pon reg: description: | @@ -79,6 +83,10 @@ allOf: reg-names: items: - const: pon + patternProperties: + # Negative look-ahead to disallow unsupported modes. The '$' has to be + # part of lookahead group to work, instead of trailing outside of (). + "^mode-(?!(bootloader$|recovery$))": false else: patternProperties: "^mode-.*$": false diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml index 242b33f2bcba..b2e5ddc5c580 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max17042.yaml @@ -67,13 +67,11 @@ properties: Voltage threshold to report battery as over voltage (in mV). Default is not to report over-voltage events. - power-supplies: true - required: - compatible - reg -additionalProperties: false +unevaluatedProperties: false examples: - | diff --git a/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml index 86af38378999..b3d8c0a5f214 100644 --- a/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml +++ b/Documentation/devicetree/bindings/power/supply/maxim,max8903.yaml @@ -44,9 +44,40 @@ properties: maxItems: 1 description: USB suspend pin (active high, output) + dc-current-limit-gpios: + minItems: 1 + maxItems: 4 + description: + GPIOs controlling DC input current limit via resistor mux. + Used with dc-current-limit-mapping to select charging current. + + dc-current-limit-mapping: + $ref: /schemas/types.yaml#/definitions/uint32-matrix + minItems: 2 + maxItems: 16 + description: | + Array of (current_microamps, gpio_bit_pattern) pairs defining available + DC current limits. The gpio_bit_pattern is applied to dc-current-limit-gpios + to select that current level. + items: + items: + - description: Current limit in microamps + - description: GPIO bit pattern value + + usb-current-limit-gpios: + maxItems: 1 + description: + GPIO driving the MAX8903 IUSB pin. IUSB is a silicon-fixed + two-state input - low selects 100 mA, high selects 500 mA. + Only the GPIO mapping is board-specific. + required: - compatible +dependentRequired: + dc-current-limit-gpios: [dc-current-limit-mapping] + dc-current-limit-mapping: [dc-current-limit-gpios] + anyOf: - required: - dok-gpios @@ -65,3 +96,25 @@ examples: chg-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>; cen-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; }; + - | + /* Example with DC and USB current limit control */ + #include + charger { + compatible = "maxim,max8903"; + dok-gpios = <&gpio2 3 GPIO_ACTIVE_LOW>; + flt-gpios = <&gpio2 2 GPIO_ACTIVE_LOW>; + chg-gpios = <&gpio3 15 GPIO_ACTIVE_LOW>; + cen-gpios = <&gpio2 5 GPIO_ACTIVE_LOW>; + dcm-gpios = <&gpio2 6 GPIO_ACTIVE_HIGH>; + + /* DC input current limit via IDC resistor mux */ + dc-current-limit-gpios = <&gpio1 0 GPIO_ACTIVE_HIGH>, + <&gpio1 1 GPIO_ACTIVE_HIGH>; + dc-current-limit-mapping = <750000 0>, /* GPIO[1:0]=0b00 */ + <900000 1>, /* GPIO[1:0]=0b01 */ + <1400000 3>, /* GPIO[1:0]=0b11 */ + <2000000 2>; /* GPIO[1:0]=0b10 */ + + /* USB current limit: IUSB pin, 100 mA (low) / 500 mA (high) */ + usb-current-limit-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml new file mode 100644 index 000000000000..af80fe528505 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/sgmicro,sgm41542.yaml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/sgmicro,sgm41542.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: SGM41542 Battery Charger + +description: + The SGMicro SGM41542 is a single cell battery charger/boost controller. + +maintainers: + - Chris Morgan + - Xu Shengfei + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: sgmicro,sgm41542 + + input-current-limit-microamp: + default: 2400000 + minimum: 100000 + maximum: 3800000 + + input-voltage-limit-microvolt: + default: 4500000 + minimum: 3900000 + maximum: 12000000 + + interrupts: + maxItems: 1 + + monitored-battery: + description: | + The charger uses the following battery properties + constant-charge-current-max-microamp (default 2040000) + constant-charge-voltage-max-microvolt (default 4208000) + charge-term-current-microamp (default 180000) + precharge-current-microamp (default 180000) + + reg: + maxItems: 1 + + regulators: + type: object + properties: + otg-vbus: + type: object + description: OTG boost regulator + $ref: /schemas/regulator/regulator.yaml + unevaluatedProperties: false + additionalProperties: false + +required: + - compatible + - reg + +unevaluatedProperties: false + +examples: + - | + #include + #include + + battery: battery { + compatible = "simple-battery"; + constant-charge-current-max-microamp = <10000000>; + constant-charge-voltage-max-microvolt = <4350000>; + precharge-current-microamp = <180000>; + charge-term-current-microamp = <300000>; + }; + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + charger@3b { + compatible = "sgmicro,sgm41542"; + reg = <0x3b>; + input-current-limit-microamp = <3000000>; + input-voltage-limit-microvolt = <4500000>; + interrupt-parent = <&gpio0>; + interrupts = <26 IRQ_TYPE_EDGE_FALLING>; + monitored-battery = <&battery>; + + regulators { + otg-vbus { + regulator-max-microvolt = <5000000>; + regulator-min-microvolt = <5000000>; + }; + }; + }; + }; diff --git a/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml b/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml new file mode 100644 index 000000000000..3f2904731c5a --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ti,bq25630.yaml @@ -0,0 +1,55 @@ +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause) +%YAML 1.2 +--- +$id: http://devicetree.org/schemas/power/supply/ti,bq25630.yaml# +$schema: http://devicetree.org/meta-schemas/core.yaml# + +title: TI BQ25630 battery charger + +maintainers: + - Waqar Hameed + +description: | + I2C controlled single cell Li-ion and Li-polymer 5A buck charger. + Datasheet: https://www.ti.com/lit/gpn/bq25630 + +allOf: + - $ref: power-supply.yaml# + +properties: + compatible: + const: ti,bq25630 + + reg: + const: 0x6b + + interrupts: + maxItems: 1 + description: + Device sends active low 256 µs pulse. Type should therefore be + IRQ_TYPE_EDGE_FALLING. + +required: + - compatible + - reg + - interrupts + - monitored-battery + +unevaluatedProperties: false + +examples: + - | + #include + + i2c { + #address-cells = <1>; + #size-cells = <0>; + + charger@6b { + compatible = "ti,bq25630"; + reg = <0x6b>; + interrupts = <13 IRQ_TYPE_EDGE_FALLING>; + monitored-battery = <&bat>; + }; + }; +... diff --git a/MAINTAINERS b/MAINTAINERS index 078ae622a400..4739fd5d60ac 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -10330,6 +10330,12 @@ S: Maintained F: drivers/mmc/host/sdhci-esdhc-mcf.c F: include/linux/platform_data/mmc-esdhc-mcf.h +FREESCALE COLDFIRE M5441X RCM POWER-ON REASON DRIVER +M: Jean-Michel Hautbois +L: linux-m68k@lists.linux-m68k.org +S: Maintained +F: drivers/power/reset/mcf-rcm-reset.c + FREESCALE DIU FRAMEBUFFER DRIVER M: Timur Tabi L: linux-fbdev@vger.kernel.org diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 124afb99febe..bce996bbef28 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -128,6 +128,18 @@ config POWER_RESET_LINKSTATION Say Y here if you have a Buffalo LinkStation LS421D/E. +config POWER_RESET_MCF_RCM + tristate "Freescale ColdFire RCM power-on reason driver" + depends on M5441x || COMPILE_TEST + depends on HAS_IOMEM + help + This driver exposes the cause of the last reset on Freescale + ColdFire 5441x SoCs through the standard power_on_reason sysfs + ABI. It reads the Reset Status Register (RSR) of the Reset + Controller Module once at probe time and reports a normalised + string (regular power-up, reset button action, software reset + or unknown reason). + config POWER_RESET_MACSMC tristate "Apple SMC reset/power-off driver" depends on MFD_MACSMC diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index d7ae97241a83..e31cab4ba78e 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_GPIO_RESTART) += gpio-restart.o obj-$(CONFIG_POWER_RESET_HISI) += hisi-reboot.o obj-$(CONFIG_POWER_RESET_LINKSTATION) += linkstation-poweroff.o +obj-$(CONFIG_POWER_RESET_MCF_RCM) += mcf-rcm-reset.o obj-$(CONFIG_POWER_RESET_MACSMC) += macsmc-reboot.o obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o obj-$(CONFIG_POWER_RESET_MT6323) += mt6323-poweroff.o diff --git a/drivers/power/reset/mcf-rcm-reset.c b/drivers/power/reset/mcf-rcm-reset.c new file mode 100644 index 000000000000..18f11c05a868 --- /dev/null +++ b/drivers/power/reset/mcf-rcm-reset.c @@ -0,0 +1,126 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Freescale ColdFire MCF5441x RCM power-on reason driver + * + * Copyright (C) 2026 Jean-Michel Hautbois + */ + +#include +#include +#include +#include +#include + +/* + * Reset Status Register (RSR) layout, MCF54418 Reference Manual chapter + * 12.3.2. The register is 8-bit read-only and latches the cause (or + * causes) of the most recent reset until the next one occurs. + */ +#define MCF_RSR_SOFT 0x20 /* Last reset caused by software */ +#define MCF_RSR_LOC 0x10 /* Last reset caused by PLL loss of clock */ +#define MCF_RSR_POR 0x08 /* Last reset caused by power-on */ +#define MCF_RSR_EXT 0x04 /* Last reset caused by external pin */ +#define MCF_RSR_WDRCORE 0x02 /* Last reset caused by core watchdog */ +#define MCF_RSR_LOL 0x01 /* Last reset caused by PLL loss of lock */ + +#define MCF_RSR_KNOWN_CAUSES (MCF_RSR_POR | MCF_RSR_EXT | MCF_RSR_WDRCORE | \ + MCF_RSR_LOC | MCF_RSR_LOL | MCF_RSR_SOFT) + +struct mcf_rcm { + const char *reason; +}; + +/* + * Decode RSR into a power_on_reason string. + * + * The MCF5441x Reset Status Register can latch several cause bits at the + * same time (Reference Manual chapter 12.3.2: "one or more status bits + * may be set at the same time"). A power-on, for example, also resets + * the PLL and may co-flag LOC and LOL during the boot sequence. The + * power_on_reason ABI carries a single string, so this routine picks + * one cause; the chosen priority surfaces the most explanatory one for + * diagnostics: + * + * POR cold boot dominates any spurious co-flagged cause + * EXT operator action via the RESET pin + * WDRCORE core watchdog timeout, a fault to investigate + * LOC, LOL PLL clock or lock failure, hardware fault + * SOFT explicit software-requested reset + */ +static const char *mcf_rcm_decode(u8 rsr) +{ + if (rsr & MCF_RSR_POR) + return POWER_ON_REASON_REGULAR; + if (rsr & MCF_RSR_EXT) + return POWER_ON_REASON_RST_BTN; + if (rsr & MCF_RSR_WDRCORE) + return POWER_ON_REASON_WATCHDOG; + if (rsr & (MCF_RSR_LOC | MCF_RSR_LOL)) + return POWER_ON_REASON_CPU_CLK_FAIL; + if (rsr & MCF_RSR_SOFT) + return POWER_ON_REASON_SOFTWARE; + return POWER_ON_REASON_UNKNOWN; +} + +static ssize_t power_on_reason_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct mcf_rcm *rcm = platform_get_drvdata(to_platform_device(dev)); + + return sysfs_emit(buf, "%s\n", rcm->reason); +} +static DEVICE_ATTR_RO(power_on_reason); + +static struct attribute *mcf_rcm_attrs[] = { + &dev_attr_power_on_reason.attr, + NULL, +}; +ATTRIBUTE_GROUPS(mcf_rcm); + +static int mcf_rcm_probe(struct platform_device *pdev) +{ + struct mcf_rcm *rcm; + void __iomem *base; + u8 rsr; + + rcm = devm_kzalloc(&pdev->dev, sizeof(*rcm), GFP_KERNEL); + if (!rcm) + return -ENOMEM; + + base = devm_platform_ioremap_resource(pdev, 0); + if (IS_ERR(base)) + return PTR_ERR(base); + + rsr = readb_relaxed(base); + rcm->reason = mcf_rcm_decode(rsr); + + platform_set_drvdata(pdev, rcm); + + if (!(rsr & MCF_RSR_KNOWN_CAUSES)) + dev_warn(&pdev->dev, "Unknown reset cause (RSR=0x%02x)\n", rsr); + else + dev_info(&pdev->dev, "Starting after %s (RSR=0x%02x)\n", + rcm->reason, rsr); + + return 0; +} + +static const struct platform_device_id mcf_rcm_id[] = { + { "mcf-rcm-reset" }, + { } +}; +MODULE_DEVICE_TABLE(platform, mcf_rcm_id); + +static struct platform_driver mcf_rcm_driver = { + .probe = mcf_rcm_probe, + .id_table = mcf_rcm_id, + .driver = { + .name = "mcf-rcm-reset", + .dev_groups = mcf_rcm_groups, + }, +}; +module_platform_driver(mcf_rcm_driver); + +MODULE_AUTHOR("Jean-Michel Hautbois "); +MODULE_DESCRIPTION("Freescale ColdFire RCM power-on reason driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/reset/piix4-poweroff.c b/drivers/power/reset/piix4-poweroff.c index e6822c021000..1a6f3d22d297 100644 --- a/drivers/power/reset/piix4-poweroff.c +++ b/drivers/power/reset/piix4-poweroff.c @@ -96,6 +96,7 @@ static const struct pci_device_id piix4_poweroff_ids[] = { { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82371AB_3) }, { 0 }, }; +MODULE_DEVICE_TABLE(pci, piix4_poweroff_ids); static struct pci_driver piix4_poweroff_driver = { .name = "piix4-poweroff", diff --git a/drivers/power/reset/pwr-mlxbf.c b/drivers/power/reset/pwr-mlxbf.c index 15d92c9f64dd..459c2b7f3a21 100644 --- a/drivers/power/reset/pwr-mlxbf.c +++ b/drivers/power/reset/pwr-mlxbf.c @@ -68,8 +68,6 @@ static int pwr_mlxbf_probe(struct platform_device *pdev) return err; err = devm_request_irq(dev, irq, pwr_mlxbf_irq, 0, hid, priv); - if (err) - dev_err(dev, "Failed request of %s irq\n", priv->hid); return err; } diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index d20e44db0532..af00c00eceee 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -168,10 +169,11 @@ error: */ int reboot_mode_register(struct reboot_mode_driver *reboot) { - struct mode_info *info; + struct mode_info *info = NULL; struct property *prop; struct device_node *np = reboot->dev->of_node; size_t len = strlen(PREFIX); + u32 magic; int ret; INIT_LIST_HEAD(&reboot->head); @@ -180,22 +182,22 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) if (strncmp(prop->name, PREFIX, len)) continue; - info = devm_kzalloc(reboot->dev, sizeof(*info), GFP_KERNEL); + if (device_property_read_u32(reboot->dev, prop->name, &magic)) { + dev_dbg(reboot->dev, "reboot mode %s without magic number\n", + prop->name); + continue; + } + + info = kzalloc_obj(*info, GFP_KERNEL); if (!info) { ret = -ENOMEM; goto error; } - if (of_property_read_u32(np, prop->name, &info->magic)) { - dev_err(reboot->dev, "reboot mode %s without magic number\n", - info->mode); - devm_kfree(reboot->dev, info); - continue; - } - + info->magic = magic; info->mode = kstrdup_const(prop->name + len, GFP_KERNEL); if (!info->mode) { - ret = -ENOMEM; + ret = -ENOMEM; goto error; } else if (info->mode[0] == '\0') { kfree_const(info->mode); @@ -206,6 +208,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) } list_add_tail(&info->list, &reboot->head); + info = NULL; } reboot->reboot_notifier.notifier_call = reboot_mode_notify; @@ -218,6 +221,7 @@ int reboot_mode_register(struct reboot_mode_driver *reboot) return 0; error: + kfree(info); reboot_mode_unregister(reboot); return ret; } @@ -261,12 +265,16 @@ static inline void reboot_mode_unregister_device(struct reboot_mode_driver *rebo int reboot_mode_unregister(struct reboot_mode_driver *reboot) { struct mode_info *info; + struct mode_info *next; unregister_reboot_notifier(&reboot->reboot_notifier); reboot_mode_unregister_device(reboot); - list_for_each_entry(info, &reboot->head, list) + list_for_each_entry_safe(info, next, &reboot->head, list) { + list_del(&info->list); kfree_const(info->mode); + kfree(info); + } return 0; } diff --git a/drivers/power/reset/syscon-reboot.c b/drivers/power/reset/syscon-reboot.c index 2e2cf5f62d73..60e855cae68e 100644 --- a/drivers/power/reset/syscon-reboot.c +++ b/drivers/power/reset/syscon-reboot.c @@ -67,7 +67,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) { struct syscon_reboot_context *ctx; struct device *dev = &pdev->dev; - int priority; + u32 priority; int err; ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); @@ -81,7 +81,7 @@ static int syscon_reboot_probe(struct platform_device *pdev) return PTR_ERR(ctx->map); } - if (of_property_read_s32(pdev->dev.of_node, "priority", &priority)) + if (of_property_read_u32(pdev->dev.of_node, "priority", &priority)) priority = 192; ctx->rd = of_device_get_match_data(dev); diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c index edae1e843c51..8d93c7a6a5f0 100644 --- a/drivers/power/supply/88pm860x_battery.c +++ b/drivers/power/supply/88pm860x_battery.c @@ -959,20 +959,14 @@ static int pm860x_battery_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL, pm860x_coulomb_handler, IRQF_ONESHOT, "coulomb", info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq_cc, ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL, pm860x_batt_handler, IRQF_ONESHOT, "battery", info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq_batt, ret); + if (ret < 0) return ret; - } return 0; diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c index 8d99c6ff72ed..47f95f31e472 100644 --- a/drivers/power/supply/88pm860x_charger.c +++ b/drivers/power/supply/88pm860x_charger.c @@ -716,11 +716,8 @@ static int pm860x_charger_probe(struct platform_device *pdev) pm860x_irq_descs[i].handler, IRQF_ONESHOT, pm860x_irq_descs[i].name, info); - if (ret < 0) { - dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n", - info->irq[i], ret); + if (ret < 0) return ret; - } } return 0; } diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig index f0ede1cecb6a..b89ef40df7f8 100644 --- a/drivers/power/supply/Kconfig +++ b/drivers/power/supply/Kconfig @@ -839,6 +839,13 @@ config CHARGER_BQ25980 Say Y to enable support for the TI BQ25980, BQ25975 and BQ25960 series of fast battery chargers. +config CHARGER_BQ25630 + tristate "TI BQ25630 battery charger driver" + depends on I2C + select REGMAP_I2C + help + Say Y to enable support for the TI BQ25630 battery charger. + config CHARGER_BQ256XX tristate "TI BQ256XX battery charger driver" depends on I2C @@ -866,6 +873,15 @@ config CHARGER_S2M devices provide USB power supply information and also required for USB OTG role switching. +config CHARGER_SGM41542 + tristate "SGM41542 charger driver" + depends on I2C + depends on GPIOLIB || COMPILE_TEST + depends on REGULATOR + select REGMAP_I2C + help + Say Y to enable support for the SGM41542 battery charger. + config CHARGER_SMB347 tristate "Summit Microelectronics SMB3XX Battery Charger" depends on I2C diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile index 31fe4a145929..f3c0d1006c1a 100644 --- a/drivers/power/supply/Makefile +++ b/drivers/power/supply/Makefile @@ -105,9 +105,11 @@ obj-$(CONFIG_CHARGER_BQ2515X) += bq2515x_charger.o obj-$(CONFIG_CHARGER_BQ257XX) += bq257xx_charger.o obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o obj-$(CONFIG_CHARGER_BQ25980) += bq25980_charger.o +obj-$(CONFIG_CHARGER_BQ25630) += bq25630_charger.o obj-$(CONFIG_CHARGER_BQ256XX) += bq256xx_charger.o obj-$(CONFIG_CHARGER_RK817) += rk817_charger.o obj-$(CONFIG_CHARGER_S2M) += s2m-charger.o +obj-$(CONFIG_CHARGER_SGM41542) += sgm41542_charger.o obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index e5202a7b6209..032140bd6682 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -787,11 +787,8 @@ static int ab8500_btemp_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_btemp_irq[i].name, di); - if (ret) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_btemp_irq[i].name, irq, ret); + if (ret) return ret; - } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_btemp_irq[i].name, irq, ret); } diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 1813fbdfa1c1..bc6c070a564d 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3605,11 +3605,8 @@ static int ab8500_charger_probe(struct platform_device *pdev) IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT, ab8500_charger_irq[i].name, di); - if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n" - , ab8500_charger_irq[i].name, irq, ret); + if (ret) return ret; - } dev_dbg(dev, "Requested %s IRQ %d: %d\n", ab8500_charger_irq[i].name, irq, ret); } diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index eb5c1ae68e44..f8f392592193 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3178,8 +3178,6 @@ static int ab8500_fg_probe(struct platform_device *pdev) ab8500_fg_irq[i].name, di); if (ret != 0) { - dev_err(dev, "failed to request %s IRQ %d: %d\n", - ab8500_fg_irq[i].name, irq, ret); destroy_workqueue(di->fg_wq); return ret; } diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 9dec4486b143..7e4b087f7679 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -620,10 +620,8 @@ static int act8945a_charger_probe(struct platform_device *pdev) ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed, IRQF_TRIGGER_FALLING, "act8945a_interrupt", charger); - if (ret) { - dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n"); + if (ret) return ret; - } platform_set_drvdata(pdev, charger); diff --git a/drivers/power/supply/axp20x_ac_power.c b/drivers/power/supply/axp20x_ac_power.c index 5f6ea416fa30..474661ba7cf2 100644 --- a/drivers/power/supply/axp20x_ac_power.c +++ b/drivers/power/supply/axp20x_ac_power.c @@ -383,11 +383,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev) ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_ac_power_irq, 0, DRVNAME, power); - if (ret < 0) { - dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", - axp_data->irq_names[i], ret); + if (ret < 0) return ret; - } } return 0; diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c index e75d1e377ac1..29f105f9212d 100644 --- a/drivers/power/supply/axp20x_usb_power.c +++ b/drivers/power/supply/axp20x_usb_power.c @@ -1030,11 +1030,8 @@ static int axp20x_usb_power_probe(struct platform_device *pdev) ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i], axp20x_usb_power_irq, 0, DRVNAME, power); - if (ret < 0) { - dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n", - axp_data->irq_names[i], ret); + if (ret < 0) return ret; - } } if (axp20x_usb_vbus_needs_polling(power)) diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index 24a8b1ee2fec..8f2090356017 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -946,8 +946,7 @@ static int axp288_charger_probe(struct platform_device *pdev) NULL, axp288_charger_irq_thread_handler, IRQF_ONESHOT, info->pdev->name, info); if (ret) - return dev_err_probe(dev, ret, "failed to request interrupt=%d\n", - info->irq[i]); + return ret; } return 0; diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 5af334c0a980..aa04c54ff97e 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -791,7 +791,7 @@ static int axp288_fuel_gauge_probe(struct platform_device *pdev) fuel_gauge_thread_handler, IRQF_ONESHOT, DEV_NAME, info); if (ret) - return dev_err_probe(dev, ret, "requesting IRQ %d\n", info->irq[i]); + return ret; } return 0; diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c index 8e28f86ae09f..4bea6fd83c36 100644 --- a/drivers/power/supply/bq24190_charger.c +++ b/drivers/power/supply/bq24190_charger.c @@ -242,7 +242,6 @@ struct bq24190_dev_info { struct mutex f_reg_lock; u8 f_reg; u8 ss_reg; - u8 watchdog; const struct bq24190_chip_info *info; }; @@ -751,13 +750,6 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) int ret; u8 v; - ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v); - if (ret < 0) - return ret; - - bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >> - BQ24190_REG_CTTC_WATCHDOG_SHIFT); - /* * According to the "Host Mode and default Mode" section of the * manual, a write to any register causes the bq24190 to switch @@ -766,9 +758,9 @@ static int bq24190_set_config(struct bq24190_dev_info *bdi) * So, by simply turning off the WDT, we accomplish both with the * same write. */ - v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK; - - ret = bq24190_write(bdi, BQ24190_REG_CTTC, v); + ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC, + BQ24190_REG_CTTC_WATCHDOG_MASK, + BQ24190_REG_CTTC_WATCHDOG_SHIFT, 0); if (ret < 0) return ret; @@ -2169,10 +2161,8 @@ static int bq24190_probe(struct i2c_client *client) bq24190_irq_handler_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "bq24190-charger", bdi); - if (ret < 0) { - dev_err(dev, "Can't set up irq handler\n"); + if (ret < 0) goto out_charger; - } ret = bq24190_register_vbus_regulator(bdi); if (ret < 0) diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 72f1bfea8d54..d6286be4af74 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -1052,10 +1052,8 @@ static int bq24257_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, bq->info->name, bq); - if (ret) { - dev_err(dev, "Failed to request IRQ #%d\n", client->irq); + if (ret) return ret; - } return 0; } diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index 99abbaf0470f..22c2b86c3b30 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -462,12 +462,8 @@ static int bq24735_charger_probe(struct i2c_client *client) IRQF_ONESHOT, supply_desc->name, charger->charger); - if (ret) { - dev_err(&client->dev, - "Unable to register IRQ %d err %d\n", - client->irq, ret); + if (ret) return ret; - } } else { ret = device_property_read_u32(&client->dev, "poll-interval", &charger->poll_interval); diff --git a/drivers/power/supply/bq25630_charger.c b/drivers/power/supply/bq25630_charger.c new file mode 100644 index 000000000000..200f74f8eab6 --- /dev/null +++ b/drivers/power/supply/bq25630_charger.c @@ -0,0 +1,1075 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Driver for TI BQ25630 charger. + * + * Copyright (C) 2026 Axis Communications AB + */ + +#include +#include +#include +#include +#include + +#include + +#define BQ25630_DRV_NAME "bq25630-charger" + +/* Registers. */ +#define BQ25630_REG_CHARGE_CURRENT_LIMIT 0x02 +#define BQ25630_REG_CHARGE_VOLTAGE_LIMIT 0x04 +#define BQ25630_REG_INPUT_CURRENT_LIMIT 0x06 +#define BQ25630_REG_INPUT_VOLTAGE_LIMIT 0x08 +#define BQ25630_REG_IOTG_REGULATION 0x0a +#define BQ25630_REG_VOTG_REGULATION 0x0c +#define BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE 0x0e +#define BQ25630_REG_PRECHARGE_CONTROL 0x10 +#define BQ25630_REG_TERMINATION_CONTROL 0x12 +#define BQ25630_REG_CHARGE_TIMER_CONTROL 0x14 +#define BQ25630_REG_CHARGER_CONTROL_0 0x15 +#define BQ25630_REG_CHARGER_CONTROL_1 0x16 +#define BQ25630_REG_CHARGER_CONTROL_2 0x17 +#define BQ25630_REG_CHARGER_CONTROL_3 0x18 +#define BQ25630_REG_CHARGER_CONTROL_4 0x19 +#define BQ25630_REG_CHARGER_CONTROL_5 0x1a +#define BQ25630_REG_NTC_CONTROL_0 0x1b +#define BQ25630_REG_NTC_CONTROL_1 0x1c +#define BQ25630_REG_NTC_CONTROL_2 0x1d +#define BQ25630_REG_NTC_CONTROL_3 0x1e +#define BQ25630_REG_CHARGER_STATUS_0 0x1f +#define BQ25630_REG_CHARGER_STATUS_1 0x20 +#define BQ25630_REG_CHARGER_STATUS_2 0x21 +#define BQ25630_REG_FAULT_STATUS 0x22 +#define BQ25630_REG_CHARGER_FLAG_0 0x23 +#define BQ25630_REG_CHARGER_FLAG_1 0x24 +#define BQ25630_REG_FAULT_FLAG 0x25 +#define BQ25630_REG_CHARGER_MASK_0 0x26 +#define BQ25630_REG_CHARGER_MASK_1 0x27 +#define BQ25630_REG_FAULT_MASK 0x28 +#define BQ25630_REG_ICO_CURRENT_LIMIT 0x29 +#define BQ25630_REG_ADC_CONTROL 0x2b +#define BQ25630_REG_ADC_CHANNEL_DISABLE_1 0x2c +#define BQ25630_REG_IBUS_ADC 0x32 +#define BQ25630_REG_IBAT_ADC 0x34 +#define BQ25630_REG_VBUS_ADC 0x36 +#define BQ25630_REG_VPMID_ADC 0x38 +#define BQ25630_REG_VBAT_ADC 0x3a +#define BQ25630_REG_VSYS_ADC 0x3c +#define BQ25630_REG_TS_ADC 0x3e +#define BQ25630_REG_TDIE_ADC 0x40 +#define BQ25630_REG_USB_C_CONTROL_0 0x44 +#define BQ25630_REG_USB_C_CONTROL_1 0x45 +#define BQ25630_REG_LIQUID_CONTROL_0 0x46 +#define BQ25630_REG_LIQUID_CONTROL_1 0x47 +#define BQ25630_REG_USB_C_INFORMATION_0 0x48 +#define BQ25630_REG_USB_C_INFORMATION_1 0x49 +#define BQ25630_REG_USB_DAC_CONTROL_0 0x4a +#define BQ25630_REG_USB_DAC_CONTROL_1 0x4b +#define BQ25630_REG_PART_INFORMATION 0x4d + +#define BQ25630_NR_STAT_REGS \ + (BQ25630_REG_FAULT_FLAG - BQ25630_REG_CHARGER_STATUS_0 + 1) + +/* Charge current limits. */ +#define BQ25630_ICHG_MIN_REGVAL 0x04 +#define BQ25630_ICHG_MIN 80000 +#define BQ25630_ICHG_MAX 5040000 +#define BQ25630_ICHG_STEP 20000 + +/* Charge voltage limits. */ +#define BQ25630_VREG_MIN_REGVAL 0x15e +#define BQ25630_VREG_MIN 3500000 +#define BQ25630_VREG_MAX 4800000 +#define BQ25630_VREG_STEP 10000 + +/* Input current limits. */ +#define BQ25630_IINDPM_MIN_REGVAL 0x0a +#define BQ25630_IINDPM_MIN 100000 +#define BQ25630_IINDPM_MAX 3200000 +#define BQ25630_IINDPM_STEP 10000 + +/* Input voltage limits. */ +#define BQ25630_VINDPM_MIN_REGVAL 0x5f +#define BQ25630_VINDPM_MIN 3800000 +#define BQ25630_VINDPM_MAX 16800000 +#define BQ25630_VINDPM_STEP 40000 + +/* Minimal system voltage limits. */ +#define BQ25630_VSYSMIN_MIN_REGVAL 0x20 +#define BQ25630_VSYSMIN_MIN 2560000 +#define BQ25630_VSYSMIN_MAX 4000000 +#define BQ25630_VSYSMIN_STEP 80000 + +/* Pre-charge current limits. */ +#define BQ25630_IPRECHG_MIN_REGVAL 0x02 +#define BQ25630_IPRECHG_MIN 40000 +#define BQ25630_IPRECHG_MAX 1000000 +#define BQ25630_IPRECHG_STEP 20000 + +/* Termination current limits. */ +#define BQ25630_ITERM_MIN_REGVAL 0x03 +#define BQ25630_ITERM_MIN 30000 +#define BQ25630_ITERM_MAX 1000000 +#define BQ25630_ITERM_STEP 10000 + +/* Charge types. */ +#define BQ25630_CHG_STAT_NOT_CHARGING 0x00 +#define BQ25630_CHG_STAT_TRICKLE_CHARGE 0x01 +#define BQ25630_CHG_STAT_PRE_CHARGE 0x02 +#define BQ25630_CHG_STAT_FAST_CHARGE 0x03 +#define BQ25630_CHG_STAT_TAPER_CHARGE 0x04 +#define BQ25630_CHG_STAT_TERMINATION 0x07 + +/* USB types (VBUS). */ +#define BQ25630_VBUS_STAT_NONE 0x00 +#define BQ25630_VBUS_STAT_SDP 0x01 +#define BQ25630_VBUS_STAT_CDP 0x02 +#define BQ25630_VBUS_STAT_DCP 0x03 +#define BQ25630_VBUS_STAT_HVDCP 0x06 +#define BQ25630_VBUS_STAT_BOOST_OTG 0x07 +#define BQ25630_VBUS_STAT_USB_C_DEFAULT 0x08 +#define BQ25630_VBUS_STAT_USB_C_MEDIUM 0x09 +#define BQ25630_VBUS_STAT_USB_C_HIGH 0x0a + +/* Temperature status. */ +#define BQ25630_TS_STAT_NORMAL 0x00 +#define BQ25630_TS_STAT_COLD 0x01 +#define BQ25630_TS_STAT_HOT 0x02 +#define BQ25630_TS_STAT_COOL 0x03 +#define BQ25630_TS_STAT_WARM 0x04 +#define BQ25630_TS_STAT_PRECOOL 0x05 +#define BQ25630_TS_STAT_PREWARM 0x06 + +/* Register fields. */ +enum bq25630_regfield { + /* Charge current limit. */ + BQ25630_REGF_ICHG, + /* Charge voltage limit. */ + BQ25630_REGF_VREG, + /* Input current limit. */ + BQ25630_REGF_IINDPM, + /* Input voltage limit. */ + BQ25630_REGF_VINDPM, + /* Minimal system voltage. */ + BQ25630_REGF_VSYSMIN, + /* Pre-charge current limit. */ + BQ25630_REGF_IPRECHG, + /* Termination current threshold. */ + BQ25630_REGF_ITERM, + + /* IBUS ADC reading. */ + BQ25630_REGF_IBUS_ADC, + /* VBUS ADC reading. */ + BQ25630_REGF_VBUS_ADC, + + /* Watchdog timer. */ + BQ25630_REGF_WATCHDOG, + /* Enable charger. */ + BQ25630_REGF_EN_CHG, + /* Register reset. */ + BQ25630_REGF_REG_RST, + /* BATFET control. */ + BQ25630_REGF_BATFET_CTRL, + /* Power good indicator. */ + BQ25630_REGF_PG_STAT, + /* Charge status. */ + BQ25630_REGF_CHG_STAT, + /* VBUS status. */ + BQ25630_REGF_VBUS_STAT, + + /* Temperature zone. */ + BQ25630_REGF_TS_STAT, + /* Temperature shutdwon. */ + BQ25630_REGF_TSHUT_STAT, + /* OTG fault. */ + BQ25630_REGF_OTG_FAULT_STAT, + /* System voltage fault. */ + BQ25630_REGF_VSYS_FAULT_STAT, + /* Battery fault. */ + BQ25630_REGF_BAT_FAULT_STAT, + /* VBUS fault. */ + BQ25630_REGF_VBUS_FAULT_STAT, + + /* Sentinel value. */ + BQ25630_REGF_MAX +}; + +static const struct reg_field bq25630_regfields[] = { + [BQ25630_REGF_ICHG] = + REG_FIELD(BQ25630_REG_CHARGE_CURRENT_LIMIT, 4, 11), + [BQ25630_REGF_VREG] = + REG_FIELD(BQ25630_REG_CHARGE_VOLTAGE_LIMIT, 3, 11), + [BQ25630_REGF_IINDPM] = + REG_FIELD(BQ25630_REG_INPUT_CURRENT_LIMIT, 3, 11), + [BQ25630_REGF_VINDPM] = + REG_FIELD(BQ25630_REG_INPUT_VOLTAGE_LIMIT, 5, 13), + + [BQ25630_REGF_VSYSMIN] = + REG_FIELD(BQ25630_REG_MINIMAL_SYSTEM_VOLTAGE, 6, 11), + + [BQ25630_REGF_IPRECHG] = + REG_FIELD(BQ25630_REG_PRECHARGE_CONTROL, 4, 9), + [BQ25630_REGF_ITERM] = + REG_FIELD(BQ25630_REG_TERMINATION_CONTROL, 3, 9), + + [BQ25630_REGF_IBUS_ADC] = REG_FIELD(BQ25630_REG_IBUS_ADC, 1, 15), + [BQ25630_REGF_VBUS_ADC] = REG_FIELD(BQ25630_REG_VBUS_ADC, 2, 14), + + [BQ25630_REGF_WATCHDOG] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 0, 1), + [BQ25630_REGF_EN_CHG] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_1, 5, 5), + [BQ25630_REGF_REG_RST] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_2, 7, 7), + [BQ25630_REGF_BATFET_CTRL] = + REG_FIELD(BQ25630_REG_CHARGER_CONTROL_3, 0, 1), + [BQ25630_REGF_PG_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_0, 7, 7), + [BQ25630_REGF_CHG_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_1, 3, 5), + [BQ25630_REGF_VBUS_STAT] = + REG_FIELD(BQ25630_REG_CHARGER_STATUS_2, 4, 7), + + [BQ25630_REGF_TS_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 0, 2), + [BQ25630_REGF_TSHUT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 3, 3), + [BQ25630_REGF_OTG_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 4, 4), + [BQ25630_REGF_VSYS_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 5, 5), + [BQ25630_REGF_BAT_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 6, 6), + [BQ25630_REGF_VBUS_FAULT_STAT] = + REG_FIELD(BQ25630_REG_FAULT_STATUS, 7, 7), +}; + +/* 8-bit value regmap. */ +static const struct regmap_range bq25630_read_reg_range8[] = { + regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL, + BQ25630_REG_FAULT_MASK), + regmap_reg_range(BQ25630_REG_ADC_CONTROL, + BQ25630_REG_ADC_CHANNEL_DISABLE_1), + regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0, + BQ25630_REG_PART_INFORMATION), +}; + +static const struct regmap_range bq25630_write_reg_range8[] = { + regmap_reg_range(BQ25630_REG_CHARGE_TIMER_CONTROL, + BQ25630_REG_NTC_CONTROL_3), + regmap_reg_range(BQ25630_REG_CHARGER_MASK_0, + BQ25630_REG_FAULT_MASK), + regmap_reg_range(BQ25630_REG_ADC_CONTROL, + BQ25630_REG_ADC_CHANNEL_DISABLE_1), + regmap_reg_range(BQ25630_REG_USB_C_CONTROL_0, + BQ25630_REG_LIQUID_CONTROL_1), + regmap_reg_range(BQ25630_REG_USB_DAC_CONTROL_0, + BQ25630_REG_USB_DAC_CONTROL_1), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table8 = { + .yes_ranges = bq25630_read_reg_range8, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range8), +}; + +static const struct regmap_access_table bq25630_write_reg_access_table8 = { + .yes_ranges = bq25630_write_reg_range8, + .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range8), +}; + +static const struct regmap_config bq25630_regmap_config8 = { + .name = BQ25630_DRV_NAME "-8bit", + .reg_bits = 8, + .val_bits = 8, + .max_register = BQ25630_REG_PART_INFORMATION, + .rd_table = &bq25630_read_reg_access_table8, + .wr_table = &bq25630_write_reg_access_table8, +}; + +/* 16-bit little-endian value regmap. */ +static const struct regmap_range bq25630_read_reg_range16le[] = { + regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT, + BQ25630_REG_TERMINATION_CONTROL), + regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT, + BQ25630_REG_ICO_CURRENT_LIMIT), +}; + +static const struct regmap_range bq25630_write_reg_range16le[] = { + regmap_reg_range(BQ25630_REG_CHARGE_CURRENT_LIMIT, + BQ25630_REG_TERMINATION_CONTROL), + regmap_reg_range(BQ25630_REG_ICO_CURRENT_LIMIT, + BQ25630_REG_ICO_CURRENT_LIMIT), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table16le = { + .yes_ranges = bq25630_read_reg_range16le, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16le), +}; + +static const struct regmap_access_table bq25630_write_reg_access_table16le = { + .yes_ranges = bq25630_write_reg_range16le, + .n_yes_ranges = ARRAY_SIZE(bq25630_write_reg_range16le), +}; + +static const struct regmap_config bq25630_regmap_config16le = { + .name = BQ25630_DRV_NAME "-16bit-le", + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 2, + .val_format_endian = REGMAP_ENDIAN_LITTLE, + /* + * Datasheet doesn't mention that this register is little-endian, but it + * looks like it? + */ + .max_register = BQ25630_REG_TERMINATION_CONTROL, + .rd_table = &bq25630_read_reg_access_table16le, + .wr_table = &bq25630_write_reg_access_table16le, +}; + +/* 16-bit big-endian value regmap. */ +static const struct regmap_range bq25630_read_reg_range16be[] = { + regmap_reg_range(BQ25630_REG_IBUS_ADC, + BQ25630_REG_TDIE_ADC), +}; + +static const struct regmap_access_table bq25630_read_reg_access_table16be = { + .yes_ranges = bq25630_read_reg_range16be, + .n_yes_ranges = ARRAY_SIZE(bq25630_read_reg_range16be), +}; + +static const struct regmap_config bq25630_regmap_config16be = { + .name = BQ25630_DRV_NAME "-16bit-be", + .reg_bits = 8, + .val_bits = 16, + .reg_stride = 2, + .val_format_endian = REGMAP_ENDIAN_BIG, + .max_register = BQ25630_REG_TDIE_ADC, + .rd_table = &bq25630_read_reg_access_table16be, + .wr_table = NULL, +}; + +struct bq25630_data { + struct device *dev; + struct regmap *regmap8; + struct regmap *regmap16le; + struct regmap *regmap16be; + struct regmap_field *regfields[BQ25630_REGF_MAX]; + + struct power_supply *psy; + + /* State status from IRQs. */ + u8 statregs[BQ25630_NR_STAT_REGS]; +}; + +static int bq25630_alloc_regfield_range(struct bq25630_data *data, + const enum bq25630_regfield from, + const enum bq25630_regfield to, + struct regmap *regmap) +{ + int i; + + for (i = from; i <= to; ++i) { + data->regfields[i] = devm_regmap_field_alloc( + data->dev, regmap, bq25630_regfields[i]); + if (IS_ERR(data->regfields[i])) + return dev_err_probe( + data->dev, PTR_ERR(data->regfields[i]), + "Could not allocate register field %d\n", i); + } + + return 0; +} + +static irqreturn_t bq25630_irq_thread(int irq, void *dev_id) +{ + struct bq25630_data *data = dev_id; + u8 regbuf[BQ25630_NR_STAT_REGS]; + int ret; + + BUILD_BUG_ON(ARRAY_SIZE(regbuf) != ARRAY_SIZE(data->statregs)); + + ret = regmap_bulk_read(data->regmap8, BQ25630_REG_CHARGER_STATUS_0, + regbuf, ARRAY_SIZE(regbuf)); + if (ret) { + dev_err(data->dev, "Could not bulk read IRQ registers (%d)\n", + ret); + goto out; + } + + if (memcmp(data->statregs, regbuf, ARRAY_SIZE(data->statregs))) { + power_supply_changed(data->psy); + memcpy(data->statregs, regbuf, ARRAY_SIZE(data->statregs)); + } + +out: + return IRQ_HANDLED; +} + +static int bq25630_read_limit(struct bq25630_data *data, + const enum bq25630_regfield regfield, + const int minval, const int step, + const int minregval, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[regfield], ®val); + if (ret) { + dev_err(data->dev, "Could not read limit (%d)\n", ret); + return ret; + } + + *val = minval + step * (regval - minregval); + + return 0; +} + +static int bq25630_write_limit(struct bq25630_data *data, + const enum bq25630_regfield regfield, + const int minval, const int maxval, + const int step, const int minregval, int val) +{ + unsigned int regval; + int ret; + + val = clamp(val, minval, maxval); + regval = minregval + ((val - minval) / step); + ret = regmap_field_write(data->regfields[regfield], regval); + if (ret) { + dev_err(data->dev, "Could not write limit (%d)\n", ret); + return ret; + } + + return 0; +} + +static int bq25630_read_charge_type(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_CHG_STAT], + ®val); + if (ret) { + dev_err(data->dev, "Could not read charge type (%d)\n", ret); + return ret; + } + + switch (regval) { + case BQ25630_CHG_STAT_NOT_CHARGING: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + case BQ25630_CHG_STAT_TRICKLE_CHARGE: + case BQ25630_CHG_STAT_PRE_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case BQ25630_CHG_STAT_FAST_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case BQ25630_CHG_STAT_TAPER_CHARGE: + *val = POWER_SUPPLY_CHARGE_TYPE_LONGLIFE; + break; + case BQ25630_CHG_STAT_TERMINATION: + *val = POWER_SUPPLY_CHARGE_TYPE_BYPASS; + break; + default: + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + return 0; +} + +static int bq25630_read_health(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + u8 temp; + + ret = regmap_read(data->regmap8, BQ25630_REG_FAULT_STATUS, ®val); + if (ret) { + dev_err(data->dev, "Could not read fault status (%d)\n", ret); + return ret; + } + + temp = regval & GENMASK(bq25630_regfields[BQ25630_REGF_TS_STAT].msb, + bq25630_regfields[BQ25630_REGF_TS_STAT].lsb); + if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_VBUS_FAULT_STAT].lsb)) { + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_BAT_FAULT_STAT].lsb)) { + /* + * We can't differentiate between dead, under voltage or over + * voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_VSYS_FAULT_STAT].lsb)) { + /* + * We can't differentiate between under voltage or over voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].msb, + bq25630_regfields[BQ25630_REGF_OTG_FAULT_STAT].lsb)) { + /* + * We can't differentiate between under voltage or over voltage. + */ + *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + } else if (regval & + GENMASK(bq25630_regfields[BQ25630_REGF_TSHUT_STAT].msb, + bq25630_regfields[BQ25630_REGF_TSHUT_STAT].lsb)) { + /* Temperature shutdown is always due to hot temperatures. */ + *val = POWER_SUPPLY_HEALTH_HOT; + } else if (temp) { + switch (temp) { + case BQ25630_TS_STAT_COLD: + *val = POWER_SUPPLY_HEALTH_COLD; + break; + case BQ25630_TS_STAT_COOL: + *val = POWER_SUPPLY_HEALTH_COOL; + break; + case BQ25630_TS_STAT_WARM: + *val = POWER_SUPPLY_HEALTH_WARM; + break; + case BQ25630_TS_STAT_HOT: + *val = POWER_SUPPLY_HEALTH_HOT; + break; + default: + /* Interpret PRECOOL and PREWARM as NORMAL. */ + *val = POWER_SUPPLY_HEALTH_GOOD; + } + } else { + *val = POWER_SUPPLY_HEALTH_GOOD; + } + + return 0; +} + +static int bq25630_read_vbus(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_VBUS_STAT], + ®val); + if (ret) { + dev_err(data->dev, "Could not read VBUS (%d)\n", ret); + return ret; + } + + switch (regval) { + case BQ25630_VBUS_STAT_NONE: + *val = -1; + break; + case BQ25630_VBUS_STAT_SDP: + *val = POWER_SUPPLY_USB_TYPE_SDP; + break; + case BQ25630_VBUS_STAT_CDP: + *val = POWER_SUPPLY_USB_TYPE_CDP; + break; + case BQ25630_VBUS_STAT_DCP: + case BQ25630_VBUS_STAT_HVDCP: + *val = POWER_SUPPLY_USB_TYPE_DCP; + break; + case BQ25630_VBUS_STAT_USB_C_DEFAULT: + case BQ25630_VBUS_STAT_USB_C_MEDIUM: + case BQ25630_VBUS_STAT_USB_C_HIGH: + *val = POWER_SUPPLY_USB_TYPE_C; + break; + default: + *val = POWER_SUPPLY_USB_TYPE_UNKNOWN; + } + + return 0; +} + +static int bq25630_get_status(struct bq25630_data *data, int *val) +{ + unsigned int regval; + int ret; + + ret = regmap_field_read(data->regfields[BQ25630_REGF_PG_STAT], ®val); + if (ret) { + dev_err(data->dev, "Could not read PG status (%d)", ret); + return ret; + } + + if (!regval) { + /* There is not enough power, battery must be discharging. */ + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG], ®val); + if (ret) { + dev_err(data->dev, "Could not read charge status (%d)", ret); + return ret; + } + + if (!regval) { + /* Charging is not enabled, battery must be discharging. */ + *val = POWER_SUPPLY_STATUS_DISCHARGING; + return 0; + } + + ret = bq25630_read_charge_type(data, val); + if (ret) + return ret; + + switch (*val) { + case POWER_SUPPLY_CHARGE_TYPE_NONE: + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case POWER_SUPPLY_CHARGE_TYPE_BYPASS: + /* Corresponds to BQ25630_CHG_STAT_TERMINATION. */ + *val = POWER_SUPPLY_STATUS_FULL; + break; + default: + *val = POWER_SUPPLY_STATUS_CHARGING; + } + + return 0; +} + +static int bq25630_reset(struct bq25630_data *data) +{ + unsigned int regval = 1; + int ret; + + ret = regmap_field_force_write(data->regfields[BQ25630_REGF_REG_RST], + regval); + if (ret) { + dev_err(data->dev, + "Could not force write reset register field (%d)\n", + ret); + return ret; + } + + /* + * After a successful register reset, the device signals by resetting + * this register field to 0. Try reading it for some interrupt cycles. + */ + ret = regmap_field_read_poll_timeout( + data->regfields[BQ25630_REGF_REG_RST], regval, regval == 0, 256, + 100000); + if (ret) { + dev_err(data->dev, "Could not read reset register field (%d)\n", + ret); + return ret; + } + + return 0; +} + +static int bq25630_setup(struct power_supply *psy) +{ + struct bq25630_data *data = power_supply_get_drvdata(psy); + struct power_supply_battery_info *batinfo; + int ret; + + ret = bq25630_reset(data); + if (ret) { + dev_err(data->dev, "Could not reset device (%d)\n", ret); + return ret; + } + + /* Disable the watchdog. */ + ret = regmap_field_write(data->regfields[BQ25630_REGF_WATCHDOG], 0); + if (ret) { + dev_err(data->dev, "Could not write watchdog timer (%d)\n", + ret); + return ret; + } + + ret = power_supply_get_battery_info(psy, &batinfo); + if (ret) { + dev_err(data->dev, "Could not get battery info (%d)\n", ret); + return ret; + } + + /* + * Set values according to battery info. Warn on missing "dangerous" + * properties. + */ + if (batinfo->voltage_min_design_uv >= 0) { + ret = bq25630_write_limit(data, BQ25630_REGF_VSYSMIN, + BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_MAX, + BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, + batinfo->voltage_min_design_uv); + if (ret) + goto out_put_batinfo; + } else + dev_warn(data->dev, + "Using default value for minimum voltage\n"); + + if (batinfo->constant_charge_voltage_max_uv >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_VREG, BQ25630_VREG_MIN, + BQ25630_VREG_MAX, BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, + batinfo->constant_charge_voltage_max_uv); + if (ret) + goto out_put_batinfo; + } else + dev_warn(data->dev, + "Using default value for maximum constant charge voltage\n"); + + if (batinfo->constant_charge_current_max_ua >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_ICHG, BQ25630_ICHG_MIN, + BQ25630_ICHG_MAX, BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, + batinfo->constant_charge_current_max_ua); + if (ret) + goto out_put_batinfo; + } else + dev_warn(data->dev, + "Using default value for maximum constant charge current\n"); + + if (batinfo->charge_term_current_ua >= 0) { + ret = bq25630_write_limit( + data, BQ25630_REGF_ITERM, BQ25630_ITERM_MIN, + BQ25630_ITERM_MAX, BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + batinfo->charge_term_current_ua); + if (ret) + goto out_put_batinfo; + } + + if (batinfo->precharge_current_ua >= 0) { + ret = bq25630_write_limit(data, BQ25630_REGF_IPRECHG, + BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_MAX, + BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, + batinfo->precharge_current_ua); + if (ret) + goto out_put_batinfo; + } + +out_put_batinfo: + power_supply_put_battery_info(psy, batinfo); + + return ret; +} + +static int bq25630_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct bq25630_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq25630_get_status(data, &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CHARGE_TYPES: + ret = bq25630_read_charge_type(data, &val->intval); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq25630_read_health(data, &val->intval); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_field_read(data->regfields[BQ25630_REGF_EN_CHG], + &val->intval); + if (ret || !val->intval) { + /* Charging is not even enabled. */ + break; + } + + ret = bq25630_read_vbus(data, &val->intval); + val->intval = val->intval >= 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = bq25630_read_limit(data, BQ25630_REGF_VSYSMIN, + BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_ICHG, + BQ25630_ICHG_MIN, BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = BQ25630_ICHG_MAX; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25630_read_limit(data, BQ25630_REGF_VREG, + BQ25630_VREG_MIN, BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, &val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + val->intval = BQ25630_VREG_MAX; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25630_read_limit(data, BQ25630_REGF_IINDPM, + BQ25630_IINDPM_MIN, + BQ25630_IINDPM_STEP, + BQ25630_IINDPM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25630_read_limit(data, BQ25630_REGF_VINDPM, + BQ25630_VINDPM_MIN, + BQ25630_VINDPM_STEP, + BQ25630_VINDPM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_USB_TYPE: + ret = bq25630_read_vbus(data, &val->intval); + if (!ret && val->intval < 0) { + /* Nothing connected. */ + val->intval = POWER_SUPPLY_USB_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_IPRECHG, + BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = bq25630_read_limit(data, BQ25630_REGF_ITERM, + BQ25630_ITERM_MIN, BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + &val->intval); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "BQ25630"; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = "Texas Instruments"; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bq25630_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq25630_data *data = power_supply_get_drvdata(psy); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = regmap_field_write(data->regfields[BQ25630_REGF_EN_CHG], + !!val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + ret = bq25630_write_limit( + data, BQ25630_REGF_VSYSMIN, BQ25630_VSYSMIN_MIN, + BQ25630_VSYSMIN_MAX, BQ25630_VSYSMIN_STEP, + BQ25630_VSYSMIN_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq25630_write_limit(data, BQ25630_REGF_ICHG, + BQ25630_ICHG_MIN, BQ25630_ICHG_MAX, + BQ25630_ICHG_STEP, + BQ25630_ICHG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq25630_write_limit(data, BQ25630_REGF_VREG, + BQ25630_VREG_MIN, BQ25630_VREG_MAX, + BQ25630_VREG_STEP, + BQ25630_VREG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = bq25630_write_limit( + data, BQ25630_REGF_IINDPM, BQ25630_IINDPM_MIN, + BQ25630_IINDPM_MAX, BQ25630_IINDPM_STEP, + BQ25630_IINDPM_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + ret = bq25630_write_limit( + data, BQ25630_REGF_VINDPM, BQ25630_VINDPM_MIN, + BQ25630_VINDPM_MAX, BQ25630_VINDPM_STEP, + BQ25630_VINDPM_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + ret = bq25630_write_limit( + data, BQ25630_REGF_IPRECHG, BQ25630_IPRECHG_MIN, + BQ25630_IPRECHG_MAX, BQ25630_IPRECHG_STEP, + BQ25630_IPRECHG_MIN_REGVAL, val->intval); + break; + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + ret = bq25630_write_limit(data, BQ25630_REGF_ITERM, + BQ25630_ITERM_MIN, BQ25630_ITERM_MAX, + BQ25630_ITERM_STEP, + BQ25630_ITERM_MIN_REGVAL, + val->intval); + break; + default: + return -EINVAL; + } + + return ret; +} + +static int bq25630_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: + case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: + return true; + default: + return false; + } +} + +static const enum power_supply_property bq25630_charger_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CHARGE_TYPES, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_USB_TYPE, + POWER_SUPPLY_PROP_PRECHARGE_CURRENT, + POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static const struct power_supply_desc bq25630_charger_psy_desc = { + .name = BQ25630_DRV_NAME, + .type = POWER_SUPPLY_TYPE_USB, + .charge_types = BIT(POWER_SUPPLY_CHARGE_TYPE_NONE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_TRICKLE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_FAST) | + BIT(POWER_SUPPLY_CHARGE_TYPE_LONGLIFE) | + BIT(POWER_SUPPLY_CHARGE_TYPE_BYPASS) | + BIT(POWER_SUPPLY_CHARGE_TYPE_UNKNOWN), + .usb_types = BIT(POWER_SUPPLY_USB_TYPE_UNKNOWN) | + BIT(POWER_SUPPLY_USB_TYPE_SDP) | + BIT(POWER_SUPPLY_USB_TYPE_DCP) | + BIT(POWER_SUPPLY_USB_TYPE_CDP) | + BIT(POWER_SUPPLY_USB_TYPE_C), + .properties = bq25630_charger_properties, + .num_properties = ARRAY_SIZE(bq25630_charger_properties), + .get_property = bq25630_charger_get_property, + .set_property = bq25630_charger_set_property, + .property_is_writeable = bq25630_charger_property_is_writeable, + .init = bq25630_setup, +}; + +static int bq25630_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct bq25630_data *data; + int ret; + + data = devm_kzalloc(&client->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->dev = &client->dev; + + data->regmap8 = devm_regmap_init_i2c(client, &bq25630_regmap_config8); + if (IS_ERR(data->regmap8)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap8), + "Could not initialize regmap8\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_WATCHDOG, + BQ25630_REGF_TS_STAT, + data->regmap8); + if (ret) + return ret; + + data->regmap16le = + devm_regmap_init_i2c(client, &bq25630_regmap_config16le); + if (IS_ERR(data->regmap16le)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap16le), + "Could not initialize regmap16le\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_ICHG, + BQ25630_REGF_ITERM, + data->regmap16le); + if (ret) + return ret; + + data->regmap16be = + devm_regmap_init_i2c(client, &bq25630_regmap_config16be); + if (IS_ERR(data->regmap16be)) + return dev_err_probe(data->dev, PTR_ERR(data->regmap16be), + "Could not initialize regmap16be\n"); + + ret = bq25630_alloc_regfield_range(data, BQ25630_REGF_IBUS_ADC, + BQ25630_REGF_VBUS_ADC, + data->regmap16be); + if (ret) + return ret; + + psy_cfg.drv_data = data; + psy_cfg.fwnode = dev_fwnode(data->dev); + data->psy = devm_power_supply_register( + data->dev, &bq25630_charger_psy_desc, &psy_cfg); + if (IS_ERR(data->psy)) + return dev_err_probe(data->dev, PTR_ERR(data->psy), + "Could not register power supply\n"); + + /* + * Device sends active low 256 µs pulse to report status and fault. + * + * Note that we need to request this *after* registering the power + * supply so devm destructs it correctly in the reverse order. Otherwise + * spurious interrupts could call power_supply_changed() wrongly with a + * uninitialized/deallocated power supply. + */ + ret = devm_request_threaded_irq(data->dev, client->irq, NULL, + bq25630_irq_thread, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + NULL, data); + if (ret) + return dev_err_probe(data->dev, ret, "Could not request IRQ\n"); + + return 0; +} + +static const struct of_device_id bq25630_of_match[] = { + { + .compatible = "ti,bq25630", + }, + {} +}; +MODULE_DEVICE_TABLE(of, bq25630_of_match); + +static struct i2c_driver bq25630_driver = { + .driver = { + .name = BQ25630_DRV_NAME, + .of_match_table = bq25630_of_match, + }, + .probe = bq25630_probe, +}; +module_i2c_driver(bq25630_driver); + +MODULE_AUTHOR("Waqar Hameed "); +MODULE_DESCRIPTION("TI BQ25630 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/bq256xx_charger.c b/drivers/power/supply/bq256xx_charger.c index 5a6634fde837..ba88cf90e730 100644 --- a/drivers/power/supply/bq256xx_charger.c +++ b/drivers/power/supply/bq256xx_charger.c @@ -347,12 +347,8 @@ static int bq256xx_array_parse(int array_size, int val, const int array[]) if (val == array[i]) return i; - if (val > array[i - 1] && val < array[i]) { - if (val < array[i]) - return i - 1; - else - return i; - } + if (val > array[i - 1] && val < array[i]) + return i - 1; } return -EINVAL; } @@ -1752,10 +1748,8 @@ static int bq256xx_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&client->dev), bq); - if (ret < 0) { - dev_err(dev, "get irq fail: %d\n", ret); + if (ret < 0) return ret; - } } ret = bq256xx_hw_init(bq); diff --git a/drivers/power/supply/bq257xx_charger.c b/drivers/power/supply/bq257xx_charger.c index 9c082865e745..b735bb02a8e0 100644 --- a/drivers/power/supply/bq257xx_charger.c +++ b/drivers/power/supply/bq257xx_charger.c @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -88,6 +89,53 @@ struct bq257xx_chg { u32 vsys_min; }; +/** + * bq25792_read16() - Read a 16-bit value from device register + * @pdata: driver platform data + * @reg: register address to read from + * @val: pointer to store the register value + * + * Read a 16-bit big-endian value from the BQ25792 device via regmap + * and convert to CPU byte order. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_read16(struct bq257xx_chg *pdata, unsigned int reg, u16 *val) +{ + __be16 regval; + int ret; + + ret = regmap_raw_read(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + *val = be16_to_cpu(regval); + return 0; +} + +/** + * bq25792_write16() - Write a 16-bit value to device register + * @pdata: driver platform data + * @reg: register address to write to + * @val: 16-bit value to write in CPU byte order + * + * Convert the value to big-endian and write a 16-bit value to the + * BQ25792 device via regmap. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_write16(struct bq257xx_chg *pdata, unsigned int reg, u16 val) +{ + __be16 regval = cpu_to_be16(val); + int ret; + + ret = regmap_raw_write(pdata->bq->regmap, reg, ®val, sizeof(regval)); + if (ret) + return ret; + + return 0; +} + /** * bq25703_get_state() - Get the current state of the device * @pdata: driver platform data @@ -119,6 +167,43 @@ static int bq25703_get_state(struct bq257xx_chg *pdata) return 0; } +/** + * bq25792_get_state() - Get the current state of the device + * @pdata: driver platform data + * + * Get the current state of the BQ25792 charger by reading status + * registers. Updates the online, charging, overvoltage, and fault + * status fields in the driver data structure. + * + * Return: Returns 0 on success or error on failure to read device. + */ +static int bq25792_get_state(struct bq257xx_chg *pdata) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1B_CHARGER_STATUS_0, ®); + if (ret) + return ret; + + pdata->online = reg & BQ25792_REG1B_PG_STAT; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG1C_CHARGER_STATUS_1, ®); + if (ret) + return ret; + + pdata->charging = reg & BQ25792_REG1C_CHG_STAT_MASK; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG20_FAULT_STATUS_0, ®); + if (ret) + return ret; + + pdata->overvoltage = reg & BQ25792_REG20_OVERVOLTAGE_MASK; + pdata->oc_fault = reg & BQ25792_REG20_OVERCURRENT_MASK; + + return 0; +} + /** * bq25703_get_min_vsys() - Get the minimum system voltage * @pdata: driver platform data @@ -142,6 +227,31 @@ static int bq25703_get_min_vsys(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_min_vsys() - Get the minimum system voltage + * @pdata: driver platform data + * @intval: pointer to store the minimum voltage value + * + * Read the current minimum system voltage setting from the device + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_min_vsys(struct bq257xx_chg *pdata, int *intval) +{ + unsigned int reg; + int ret; + + ret = regmap_read(pdata->bq->regmap, BQ25792_REG00_MIN_SYS_VOLTAGE, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG00_VSYSMIN_MASK, reg); + *intval = (reg * BQ25792_MINVSYS_STEP_UV) + BQ25792_MINVSYS_MIN_UV; + + return ret; +} + /** * bq25703_set_min_vsys() - Set the minimum system voltage * @pdata: driver platform data @@ -166,6 +276,29 @@ static int bq25703_set_min_vsys(struct bq257xx_chg *pdata, int vsys) reg); } +/** + * bq25792_set_min_vsys() - Set the minimum system voltage + * @pdata: driver platform data + * @vsys: voltage value to set in uV + * + * Set the minimum system voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_min_vsys(struct bq257xx_chg *pdata, int vsys) +{ + unsigned int reg; + int vsys_min = pdata->vsys_min; + + vsys = clamp(vsys, vsys_min, BQ25792_MINVSYS_MAX_UV); + reg = ((vsys - BQ25792_MINVSYS_MIN_UV) / BQ25792_MINVSYS_STEP_UV); + reg = FIELD_PREP(BQ25792_REG00_VSYSMIN_MASK, reg); + + return regmap_write(pdata->bq->regmap, + BQ25792_REG00_MIN_SYS_VOLTAGE, reg); +} + /** * bq25703_get_cur() - Get the reported current from the battery * @pdata: driver platform data @@ -195,6 +328,30 @@ static int bq25703_get_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_cur() - Get the reported current from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery current value + * + * Read the current ADC value from the device representing the battery + * charge or discharge current and return it in microamps. + * + * Return: Returns 0 on success or error on failure to read. + */ +static int bq25792_get_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG33_IBAT_ADC, ®); + if (ret < 0) + return ret; + + *intval = (s16)reg * BQ25792_ADCIBAT_STEP_UA; + + return ret; +} + /** * bq25703_get_ichg_cur() - Get the maximum reported charge current * @pdata: driver platform data @@ -218,6 +375,30 @@ static int bq25703_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_ichg_cur() - Get the maximum reported charge current + * @pdata: driver platform data + * @intval: pointer to store the maximum charge current value + * + * Read the programmed maximum charge current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_ichg_cur(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG03_ICHG_MASK, reg) * + BQ25792_ICHG_STEP_UA; + + return ret; +} + /** * bq25703_set_ichg_cur() - Set the maximum charge current * @pdata: driver platform data @@ -242,6 +423,28 @@ static int bq25703_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) reg); } +/** + * bq25792_set_ichg_cur() - Set the maximum charge current + * @pdata: driver platform data + * @ichg: current value to set in uA + * + * Set the maximum charge current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_ichg_cur(struct bq257xx_chg *pdata, int ichg) +{ + int ichg_max = pdata->ichg_max; + u16 reg; + + ichg = clamp(ichg, BQ25792_ICHG_MIN_UA, ichg_max); + reg = FIELD_PREP(BQ25792_REG03_ICHG_MASK, + (ichg / BQ25792_ICHG_STEP_UA)); + + return bq25792_write16(pdata, BQ25792_REG03_CHARGE_CURRENT_LIMIT, reg); +} + /** * bq25703_get_chrg_volt() - Get the maximum set charge voltage * @pdata: driver platform data @@ -265,6 +468,30 @@ static int bq25703_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_chrg_volt() - Get the maximum set charge voltage + * @pdata: driver platform data + * @intval: pointer to store the maximum charge voltage value + * + * Read the current charge voltage limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_chrg_volt(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, ®); + if (ret) + return ret; + + *intval = FIELD_GET(BQ25792_REG01_VREG_MASK, reg) * + BQ25792_VBATREG_STEP_UV; + + return ret; +} + /** * bq25703_set_chrg_volt() - Set the maximum charge voltage * @pdata: driver platform data @@ -291,6 +518,29 @@ static int bq25703_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) reg); } +/** + * bq25792_set_chrg_volt() - Set the maximum charge voltage + * @pdata: driver platform data + * @vbat: voltage value to set in uV + * + * Set the maximum charge voltage by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_chrg_volt(struct bq257xx_chg *pdata, int vbat) +{ + int vbat_max = pdata->vbat_max; + u16 reg; + + vbat = clamp(vbat, BQ25792_VBATREG_MIN_UV, vbat_max); + + reg = FIELD_PREP(BQ25792_REG01_VREG_MASK, + (vbat / BQ25792_VBATREG_STEP_UV)); + + return bq25792_write16(pdata, BQ25792_REG01_CHARGE_VOLTAGE_LIMIT, reg); +} + /** * bq25703_get_iindpm() - Get the maximum set input current * @pdata: driver platform data @@ -319,6 +569,30 @@ static int bq25703_get_iindpm(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_iindpm() - Get the maximum set input current + * @pdata: driver platform data + * @intval: pointer to store the maximum input current value + * + * Read the current input current limit from the device. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_iindpm(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, ®); + if (ret) + return ret; + + reg = FIELD_GET(BQ25792_REG06_IINDPM_MASK, reg); + *intval = reg * BQ25792_IINDPM_STEP_UA; + + return ret; +} + /** * bq25703_set_iindpm() - Set the maximum input current * @pdata: driver platform data @@ -344,6 +618,29 @@ static int bq25703_set_iindpm(struct bq257xx_chg *pdata, int iindpm) FIELD_PREP(BQ25703_IINDPM_MASK, reg)); } +/** + * bq25792_set_iindpm() - Set the maximum input current + * @pdata: driver platform data + * @iindpm: current value in uA + * + * Set the maximum input current by clamping the requested value + * between device limits and writing to the appropriate register. + * + * Return: Returns 0 on success or error on failure to write. + */ +static int bq25792_set_iindpm(struct bq257xx_chg *pdata, int iindpm) +{ + u16 reg; + int iindpm_max = pdata->iindpm_max; + + iindpm = clamp(iindpm, BQ25792_IINDPM_MIN_UA, iindpm_max); + + reg = iindpm / BQ25792_IINDPM_STEP_UA; + + return bq25792_write16(pdata, BQ25792_REG06_INPUT_CURRENT_LIMIT, + FIELD_PREP(BQ25792_REG06_IINDPM_MASK, reg)); +} + /** * bq25703_get_vbat() - Get the reported voltage from the battery * @pdata: driver platform data @@ -368,6 +665,30 @@ static int bq25703_get_vbat(struct bq257xx_chg *pdata, int *intval) return ret; } +/** + * bq25792_get_vbat() - Get the reported voltage from the battery + * @pdata: driver platform data + * @intval: pointer to store the battery voltage value + * + * Read the current ADC value representing the battery voltage + * and return it in microvolts. + * + * Return: Returns 0 on success or error on failure to read value. + */ +static int bq25792_get_vbat(struct bq257xx_chg *pdata, int *intval) +{ + u16 reg; + int ret; + + ret = bq25792_read16(pdata, BQ25792_REG3B_VBAT_ADC, ®); + if (ret) + return ret; + + *intval = reg * BQ25792_ADCVSYSVBAT_STEP_UV; + + return ret; +} + /** * bq25703_hw_init() - Set all the required registers to init the charger * @pdata: driver platform data @@ -434,6 +755,108 @@ static int bq25703_hw_init(struct bq257xx_chg *pdata) return ret; } +/** + * bq25792_hw_init() - Initialize BQ25792 hardware + * @pdata: driver platform data + * + * Initialize the BQ25792 by disabling the watchdog, enabling discharge + * current sensing with 5A limit, and configuring input current regulation. + * Set the charge current, charge voltage, minimum system voltage, and + * input current limit from platform data. Enable and configure the ADC + * to measure all available channels. + * + * Return: Returns 0 on success or error code on error. + */ +static int bq25792_hw_init(struct bq257xx_chg *pdata) +{ + struct regmap *regmap = pdata->bq->regmap; + int ret = 0; + u8 reg; + + /* Disable watchdog (TODO: make it work instead) */ + ret = regmap_write(regmap, BQ25792_REG10_CHARGER_CONTROL_1, 0); + if (ret) + return ret; + + /* + * Enable battery discharge current sensing, 5A discharge current + * limit, input current regulation and ship FET functions + */ + ret = regmap_write(regmap, BQ25792_REG14_CHARGER_CONTROL_5, + BQ25792_REG14_SFET_PRESENT | + BQ25792_REG14_EN_IBAT | + BQ25792_IBAT_5A | + BQ25792_REG14_EN_IINDPM); + if (ret) + return ret; + + if (pdata->vbat_max < 5000000) { + /* 1S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_1S); + } else if (pdata->vbat_max < 10000000) { + /* 2S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_2S); + } else if (pdata->vbat_max < 14000000) { + /* 3S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_3S); + } else { + /* 4S batteries */ + reg = FIELD_PREP(BQ25792_REG0A_CELL_MASK, BQ25792_CELL_4S); + } + + /* Recharge voltage detection deglitch time (default 1024ms) */ + reg |= FIELD_PREP(BQ25792_REG0A_TRECHG_MASK, BQ25792_TRECHG_1024MS); + + /* Recharge voltage offset: 5% of the set charge voltage */ + reg |= FIELD_PREP(BQ25792_REG0A_VRECHG_MASK, + (pdata->vbat_max / 20 - BQ25792_VRECHG_MIN_UV) / BQ25792_VRECHG_STEP_UV); + + ret = regmap_write(regmap, BQ25792_REG0A_RECHARGE_CONTROL, reg); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_ichg(pdata, pdata->ichg_max); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_vbatreg(pdata, pdata->vbat_max); + if (ret) + return ret; + + ret = bq25792_set_min_vsys(pdata, pdata->vsys_min); + if (ret) + return ret; + + ret = pdata->chip->bq257xx_set_iindpm(pdata, pdata->iindpm_max); + if (ret) + return ret; + + /* Enable the Input Current Optimizer (the rest is at POR value) */ + ret = regmap_write(regmap, BQ25792_REG0F_CHARGER_CONTROL_0, + BQ25792_REG0F_EN_AUTO_IBATDIS | + BQ25792_REG0F_EN_CHG | + BQ25792_REG0F_EN_ICO | + BQ25792_REG0F_EN_TERM); + if (ret) + return ret; + + /* Enable the ADC. */ + ret = regmap_write(regmap, BQ25792_REG2E_ADC_CONTROL, BQ25792_REG2E_ADC_EN); + if (ret) + return ret; + + /* Clear per-channel ADC disable bits - enable all channels */ + ret = regmap_write(regmap, BQ25792_REG2F_ADC_FUNCTION_DISABLE_0, 0); + if (ret) + return ret; + + ret = regmap_write(regmap, BQ25792_REG30_ADC_FUNCTION_DISABLE_1, 0); + if (ret) + return ret; + + return ret; +} + /** * bq25703_hw_shutdown() - Set registers for shutdown * @pdata: driver platform data @@ -446,6 +869,30 @@ static void bq25703_hw_shutdown(struct bq257xx_chg *pdata) BQ25703_EN_LWPWR, BQ25703_EN_LWPWR); } +/** + * bq25792_hw_shutdown() - Shutdown BQ25792 hardware + * @pdata: driver platform data + * + * Perform hardware shutdown for the BQ25792. Currently a no-op + * as the device does not require special shutdown configuration. + */ +static void bq25792_hw_shutdown(struct bq257xx_chg *pdata) +{ + /* Nothing to do here */ +} + +/** + * bq257xx_set_charger_property() - Set a power supply property + * @psy: power supply device + * @prop: power supply property to set + * @val: value to set for the property + * + * Handle requests to set power supply properties such as input current + * limit, constant charge voltage, and constant charge current. Routes + * the request to the chip-specific implementation. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_set_charger_property(struct power_supply *psy, enum power_supply_property prop, const union power_supply_propval *val) @@ -469,6 +916,19 @@ static int bq257xx_set_charger_property(struct power_supply *psy, return -EINVAL; } +/** + * bq257xx_get_charger_property() - Get a power supply property + * @psy: power supply device + * @psp: power supply property to get + * @val: pointer to store the property value + * + * Handle requests to get power supply properties, including status, + * health, manufacturer, online state, and various voltage/current + * measurements. Reads current device state and routes chip-specific + * property requests to appropriate handlers. + * + * Return: Returns 0 on success or -EINVAL if property is not supported. + */ static int bq257xx_get_charger_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -550,6 +1010,17 @@ static enum power_supply_property bq257xx_power_supply_props[] = { POWER_SUPPLY_PROP_USB_TYPE, }; +/** + * bq257xx_property_is_writeable() - Check if a property is writeable + * @psy: power supply device + * @prop: power supply property to check + * + * Determines which power supply properties can be written to. Only + * charge current limit, charge voltage limit, and input current + * limit are writeable. + * + * Return: Returns 1 if property is writeable, 0 otherwise. + */ static int bq257xx_property_is_writeable(struct power_supply *psy, enum power_supply_property prop) { @@ -622,6 +1093,17 @@ out: power_supply_changed(psy); } +/** + * bq257xx_irq_handler_thread() - Handle charger interrupt + * @irq: interrupt number + * @private: pointer to driver private data + * + * Thread handler for charger interrupts. Triggers re-evaluation of + * external power status and updates power supply state in response + * to charger events. + * + * Return: Returns IRQ_HANDLED if interrupt was processed. + */ static irqreturn_t bq257xx_irq_handler_thread(int irq, void *private) { struct bq257xx_chg *pdata = private; @@ -662,6 +1144,22 @@ static const struct bq257xx_chip_info bq25703_chip_info = { .bq257xx_get_min_vsys = &bq25703_get_min_vsys, }; +static const struct bq257xx_chip_info bq25792_chip_info = { + .default_iindpm_uA = BQ25792_IINDPM_DEFAULT_UA, + .bq257xx_hw_init = &bq25792_hw_init, + .bq257xx_hw_shutdown = &bq25792_hw_shutdown, + .bq257xx_get_state = &bq25792_get_state, + .bq257xx_get_ichg = &bq25792_get_ichg_cur, + .bq257xx_set_ichg = &bq25792_set_ichg_cur, + .bq257xx_get_vbatreg = &bq25792_get_chrg_volt, + .bq257xx_set_vbatreg = &bq25792_set_chrg_volt, + .bq257xx_get_iindpm = &bq25792_get_iindpm, + .bq257xx_set_iindpm = &bq25792_set_iindpm, + .bq257xx_get_cur = &bq25792_get_cur, + .bq257xx_get_vbat = &bq25792_get_vbat, + .bq257xx_get_min_vsys = &bq25792_get_min_vsys, +}; + /** * bq257xx_parse_dt() - Parse the device tree for required properties * @pdata: driver platform data @@ -707,6 +1205,17 @@ static int bq257xx_parse_dt(struct bq257xx_chg *pdata, return 0; } +/** + * bq257xx_charger_probe() - Probe routine for charger platform device + * @pdev: platform device + * + * Probe the charger device, allocate driver data structure, select the + * appropriate chip-specific function pointers, register the power supply, + * parse device tree properties for battery limits, initialize hardware, + * and set up the interrupt handler if available. + * + * Return: Returns 0 on success or error code on failure. + */ static int bq257xx_charger_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -722,7 +1231,17 @@ static int bq257xx_charger_probe(struct platform_device *pdev) return -ENOMEM; pdata->bq = bq; - pdata->chip = &bq25703_chip_info; + + switch (bq->type) { + case BQ25703A: + pdata->chip = &bq25703_chip_info; + break; + case BQ25792: + pdata->chip = &bq25792_chip_info; + break; + default: + return dev_err_probe(dev, -EINVAL, "Unknown chip type\n"); + } platform_set_drvdata(pdev, pdata); @@ -753,13 +1272,18 @@ static int bq257xx_charger_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&bq->client->dev), pdata); - if (ret < 0) - dev_err_probe(dev, ret, "Charger get irq failed\n"); } return ret; } +/** + * bq257xx_charger_shutdown() - Shutdown routine for charger platform device + * @pdev: platform device + * + * Called during system shutdown to perform charger cleanup, including + * disabling watchdog timers or other chip-specific shutdown procedures. + */ static void bq257xx_charger_shutdown(struct platform_device *pdev) { struct bq257xx_chg *pdata = platform_get_drvdata(pdev); diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c index 180bc137a863..741a45cc5f35 100644 --- a/drivers/power/supply/bq25890_charger.c +++ b/drivers/power/supply/bq25890_charger.c @@ -1389,6 +1389,14 @@ static int bq25890_fw_read_u32_props(struct bq25890_device *bq) return 0; } +static void bq25890_release_secondary_chrg(void *data) +{ + struct bq25890_device *bq = data; + + power_supply_put(bq->secondary_chrg); + bq->secondary_chrg = NULL; +} + static int bq25890_fw_probe(struct bq25890_device *bq) { int ret; @@ -1401,6 +1409,10 @@ static int bq25890_fw_probe(struct bq25890_device *bq) bq->secondary_chrg = power_supply_get_by_name(str); if (!bq->secondary_chrg) return -EPROBE_DEFER; + + ret = devm_add_action_or_reset(bq->dev, bq25890_release_secondary_chrg, bq); + if (ret) + return ret; } /* Optional, left at 0 if property is not present */ diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c index 71b6e49a835f..fd4cd9cd2ce2 100644 --- a/drivers/power/supply/charger-manager.c +++ b/drivers/power/supply/charger-manager.c @@ -1014,6 +1014,29 @@ static int charger_extcon_init(struct charger_manager *cm, return 0; } +static int charger_manager_get_regulators(struct charger_manager *cm) +{ + struct charger_desc *desc = cm->desc; + struct charger_regulator *charger; + int i, ret; + + for (i = 0; i < desc->num_charger_regulators; i++) { + charger = &desc->charger_regulators[i]; + charger->consumer = regulator_get(cm->dev, + charger->regulator_name); + if (IS_ERR(charger->consumer)) { + dev_err(cm->dev, "Cannot find charger(%s)\n", + charger->regulator_name); + ret = PTR_ERR(charger->consumer); + while (i-- > 0) + regulator_put(desc->charger_regulators[i].consumer); + return ret; + } + charger->cm = cm; + } + return 0; +} + /** * charger_manager_register_extcon - Register extcon device to receive state * of charger cable. @@ -1036,15 +1059,6 @@ static int charger_manager_register_extcon(struct charger_manager *cm) for (i = 0; i < desc->num_charger_regulators; i++) { charger = &desc->charger_regulators[i]; - charger->consumer = regulator_get(cm->dev, - charger->regulator_name); - if (IS_ERR(charger->consumer)) { - dev_err(cm->dev, "Cannot find charger(%s)\n", - charger->regulator_name); - return PTR_ERR(charger->consumer); - } - charger->cm = cm; - for (j = 0; j < charger->num_cables; j++) { struct charger_cable *cable = &charger->cables[j]; @@ -1580,13 +1594,23 @@ static int charger_manager_probe(struct platform_device *pdev) } psy_cfg.attr_grp = desc->sysfs_groups; + /* + * Acquire charger regulators before exposing the sysfs entries, so + * userspace cannot reach externally_control before the regulators + * (and charger->cm) are available. Mirrors the order in remove(). + */ + ret = charger_manager_get_regulators(cm); + if (ret < 0) + return ret; + cm->charger_psy = power_supply_register(&pdev->dev, &cm->charger_psy_desc, &psy_cfg); if (IS_ERR(cm->charger_psy)) { dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", cm->charger_psy_desc.name); - return PTR_ERR(cm->charger_psy); + ret = PTR_ERR(cm->charger_psy); + goto err_regulator; } /* Register extcon device for charger cable */ @@ -1620,11 +1644,11 @@ static int charger_manager_probe(struct platform_device *pdev) return 0; err_reg_extcon: + power_supply_unregister(cm->charger_psy); +err_regulator: for (i = 0; i < desc->num_charger_regulators; i++) regulator_put(desc->charger_regulators[i].consumer); - power_supply_unregister(cm->charger_psy); - return ret; } @@ -1642,12 +1666,12 @@ static void charger_manager_remove(struct platform_device *pdev) cancel_work_sync(&setup_polling); cancel_delayed_work_sync(&cm_monitor_work); - for (i = 0 ; i < desc->num_charger_regulators ; i++) - regulator_put(desc->charger_regulators[i].consumer); + try_charger_enable(cm, false); power_supply_unregister(cm->charger_psy); - try_charger_enable(cm, false); + for (i = 0 ; i < desc->num_charger_regulators ; i++) + regulator_put(desc->charger_regulators[i].consumer); } static const struct platform_device_id charger_manager_id[] = { diff --git a/drivers/power/supply/cpcap-battery.c b/drivers/power/supply/cpcap-battery.c index 59c741993ef8..1d2b8bb08564 100644 --- a/drivers/power/supply/cpcap-battery.c +++ b/drivers/power/supply/cpcap-battery.c @@ -957,12 +957,8 @@ static int cpcap_battery_init_irq(struct platform_device *pdev, cpcap_battery_irq_thread, IRQF_SHARED | IRQF_ONESHOT, name, ddata); - if (error) { - dev_err(ddata->dev, "could not get irq %s: %i\n", - name, error); - + if (error) return error; - } d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); if (!d) diff --git a/drivers/power/supply/cpcap-charger.c b/drivers/power/supply/cpcap-charger.c index ec8d2a9245d9..76e2d2e3f5f5 100644 --- a/drivers/power/supply/cpcap-charger.c +++ b/drivers/power/supply/cpcap-charger.c @@ -761,12 +761,8 @@ static int cpcap_usb_init_irq(struct platform_device *pdev, cpcap_charger_irq_thread, IRQF_SHARED | IRQF_ONESHOT, name, ddata); - if (error) { - dev_err(ddata->dev, "could not get irq %s: %i\n", - name, error); - + if (error) return error; - } d = devm_kzalloc(ddata->dev, sizeof(*d), GFP_KERNEL); if (!d) diff --git a/drivers/power/supply/cros_peripheral_charger.c b/drivers/power/supply/cros_peripheral_charger.c index 612bc7badac0..b640930366d4 100644 --- a/drivers/power/supply/cros_peripheral_charger.c +++ b/drivers/power/supply/cros_peripheral_charger.c @@ -258,6 +258,14 @@ static int cros_ec_notify(struct notifier_block *nb, return cros_pchg_event(charger); } +static void cros_pchg_unregister_notifier(void *data) +{ + struct charger_data *charger = data; + + blocking_notifier_chain_unregister(&charger->ec_device->event_notifier, + &charger->notifier); +} + static int cros_pchg_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; @@ -345,9 +353,10 @@ static int cros_pchg_probe(struct platform_device *pdev) ret = blocking_notifier_chain_register(&ec_dev->ec_dev->event_notifier, nb); if (ret < 0) - dev_err(dev, "Failed to register notifier (err:%d)\n", ret); + return dev_err_probe(dev, ret, "Failed to register notifier\n"); - return 0; + return devm_add_action_or_reset(dev, cros_pchg_unregister_notifier, + charger); } #ifdef CONFIG_PM_SLEEP diff --git a/drivers/power/supply/cros_usbpd-charger.c b/drivers/power/supply/cros_usbpd-charger.c index c1cbe6e5476e..69d227ceb5c9 100644 --- a/drivers/power/supply/cros_usbpd-charger.c +++ b/drivers/power/supply/cros_usbpd-charger.c @@ -124,6 +124,11 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger) if (ret < 0) return ret; + if (resp.port_count > EC_USB_PD_MAX_PORTS) { + dev_warn(charger->dev, "Charge port count out of bounds\n"); + return EC_USB_PD_MAX_PORTS; + } + return resp.port_count; } @@ -137,6 +142,11 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger) if (ret < 0) return ret; + if (resp.num_ports > EC_USB_PD_MAX_PORTS) { + dev_warn(charger->dev, "USB PD port count out of bounds\n"); + return EC_USB_PD_MAX_PORTS; + } + return resp.num_ports; } @@ -588,10 +598,13 @@ static int cros_usbpd_charger_probe(struct platform_device *pd) /* * Sanity checks on the number of ports: - * there should be at most 1 dedicated port + * there should be at most 1 dedicated port, and the count must + * not exceed the maximum number of supported ports + * (EC_USB_PD_MAX_PORTS). */ if (charger->num_charger_ports < charger->num_usbpd_ports || - charger->num_charger_ports > (charger->num_usbpd_ports + 1)) { + charger->num_charger_ports > (charger->num_usbpd_ports + 1) || + charger->num_charger_ports > EC_USB_PD_MAX_PORTS) { dev_err(dev, "Unexpected number of charge port count\n"); ret = -EPROTO; goto fail_nowarn; diff --git a/drivers/power/supply/cw2015_battery.c b/drivers/power/supply/cw2015_battery.c index 7496295d2948..1bcb60cf763d 100644 --- a/drivers/power/supply/cw2015_battery.c +++ b/drivers/power/supply/cw2015_battery.c @@ -736,6 +736,7 @@ static const struct i2c_device_id cw_bat_id_table[] = { { .name = "cw2015" }, { } }; +MODULE_DEVICE_TABLE(i2c, cw_bat_id_table); static const struct of_device_id cw2015_of_match[] = { { .compatible = "cellwise,cw2015" }, diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c index 4f28ef1bba1a..7b2fa723acd9 100644 --- a/drivers/power/supply/da9150-fg.c +++ b/drivers/power/supply/da9150-fg.c @@ -524,10 +524,8 @@ static int da9150_fg_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq, IRQF_ONESHOT, "FG", fg); - if (ret) { - dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret); + if (ret) return ret; - } return 0; } diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c index f5f2566b3a32..6913179ebb84 100644 --- a/drivers/power/supply/generic-adc-battery.c +++ b/drivers/power/supply/generic-adc-battery.c @@ -246,7 +246,7 @@ static int gab_probe(struct platform_device *pdev) IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "battery charged", adc_bat); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "Failed to register irq\n"); + return ret; } platform_set_drvdata(pdev, adc_bat); diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c index 237912a92272..e329321d06db 100644 --- a/drivers/power/supply/isp1704_charger.c +++ b/drivers/power/supply/isp1704_charger.c @@ -482,6 +482,7 @@ static void isp1704_charger_remove(struct platform_device *pdev) struct isp1704_charger *isp = platform_get_drvdata(pdev); usb_unregister_notifier(isp->phy, &isp->nb); + cancel_work_sync(&isp->work); power_supply_unregister(isp->psy); isp1704_charger_set_power(isp, 0); } diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c index 5b6760722f38..d1be314f36a0 100644 --- a/drivers/power/supply/ltc2941-battery-gauge.c +++ b/drivers/power/supply/ltc2941-battery-gauge.c @@ -463,7 +463,7 @@ static int ltc294x_i2c_probe(struct i2c_client *client) /* r_sense can be negative, when sense+ is connected to the battery * instead of the sense-. This results in reversed measurements. */ - ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense); + ret = of_property_read_s32(np, "lltc,resistor-sense", &r_sense); if (ret < 0) return dev_err_probe(&client->dev, ret, "Could not find lltc,resistor-sense in devicetree\n"); diff --git a/drivers/power/supply/max14656_charger_detector.c b/drivers/power/supply/max14656_charger_detector.c index 81798f22a037..f78d0341d0ef 100644 --- a/drivers/power/supply/max14656_charger_detector.c +++ b/drivers/power/supply/max14656_charger_detector.c @@ -287,10 +287,8 @@ static int max14656_probe(struct i2c_client *client) ret = devm_request_irq(dev, chip->irq, max14656_irq, IRQF_TRIGGER_FALLING, MAX14656_NAME, chip); - if (ret) { - dev_err(dev, "request_irq %d failed\n", chip->irq); + if (ret) return -EINVAL; - } enable_irq_wake(chip->irq); schedule_delayed_work(&chip->irq_work, msecs_to_jiffies(2000)); diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c index 03ac569e185c..7836cc672af2 100644 --- a/drivers/power/supply/max17040_battery.c +++ b/drivers/power/supply/max17040_battery.c @@ -192,8 +192,11 @@ static int max17040_raw_vcell_to_uvolts(struct max17040_chip *chip, u16 vcell) static int max17040_get_vcell(struct max17040_chip *chip) { u32 vcell; + int ret; - regmap_read(chip->regmap, MAX17040_VCELL, &vcell); + ret = regmap_read(chip->regmap, MAX17040_VCELL, &vcell); + if (ret) + return ret; return max17040_raw_vcell_to_uvolts(chip, vcell); } @@ -201,8 +204,11 @@ static int max17040_get_vcell(struct max17040_chip *chip) static int max17040_get_soc(struct max17040_chip *chip) { u32 soc; + int ret; - regmap_read(chip->regmap, MAX17040_SOC, &soc); + ret = regmap_read(chip->regmap, MAX17040_SOC, &soc); + if (ret) + return ret; return soc >> (chip->quirk_double_soc ? 9 : 8); } @@ -261,7 +267,11 @@ static int max17040_get_of_data(struct max17040_chip *chip) static void max17040_check_changes(struct max17040_chip *chip) { - chip->soc = max17040_get_soc(chip); + int soc; + + soc = max17040_get_soc(chip); + if (soc >= 0) + chip->soc = soc; } static void max17040_queue_work(struct max17040_chip *chip) @@ -396,10 +406,16 @@ static int max17040_get_property(struct power_supply *psy, val->intval = max17040_get_online(chip); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - val->intval = max17040_get_vcell(chip); + ret = max17040_get_vcell(chip); + if (ret < 0) + return ret; + val->intval = ret; break; case POWER_SUPPLY_PROP_CAPACITY: - val->intval = max17040_get_soc(chip); + ret = max17040_get_soc(chip); + if (ret < 0) + return ret; + val->intval = ret; break; case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN: val->intval = chip->low_soc_alert; diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c index d409d2f0d383..cbf3ac9c3e14 100644 --- a/drivers/power/supply/max17042_battery.c +++ b/drivers/power/supply/max17042_battery.c @@ -62,18 +62,32 @@ #define MAX17042_RESISTANCE_LSB 1 / 4096 /* Ω */ #define MAX17042_TEMPERATURE_LSB 1 / 256 /* °C */ +#define MAX17055_DQACC_DIV 32 +#define MAX17055_DPACC_FACTOR 44138 +#define MAX17055_DPACC_VCHG_FACTOR 51200 +#define MAX17055_FSTAT_DNR_BIT BIT(0) +#define MAX17055_VCHG_THRESHOLD_UV 4275000 +#define MAX17055_DNR_POLL_US 10000 +#define MAX17055_DNR_TIMEOUT_US 2000000 +#define MAX17055_INIT_RETRY_DELAY_MS 10000 +#define MAX17055_REFRESH_POLL_US 10000 +#define MAX17055_REFRESH_TIMEOUT_US 1000000 + struct max17042_chip { struct device *dev; struct regmap *regmap; struct power_supply *battery; enum max170xx_chip_type chip_type; struct max17042_config_data *config_data; - struct work_struct work; - int init_complete; + struct delayed_work work; int irq; int task_period; bool enable_current_sense; bool enable_por_init; + bool enable_vchg_override; + bool init_complete; + bool hib_restore_pending; + u16 hib_cfg; unsigned int r_sns; int vmin; /* in millivolts */ int vmax; /* in millivolts */ @@ -256,7 +270,7 @@ static int max17042_get_property(struct power_supply *psy, u32 data; u64 data64; - if (!chip->init_complete) + if (!READ_ONCE(chip->init_complete)) return -EAGAIN; switch (psp) { @@ -566,6 +580,24 @@ static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value) return ret; } +static int max17055_write_verify_reg(struct regmap *map, u8 reg, u32 value) +{ + u32 read_value; + int ret; + + ret = regmap_write(map, reg, value); + if (ret) + return ret; + + usleep_range(1000, 2000); + + ret = regmap_read(map, reg, &read_value); + if (ret) + return ret; + + return read_value == value ? 0 : -EIO; +} + static inline void max17042_override_por(struct regmap *map, u8 reg, u16 value) { @@ -801,8 +833,12 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17042_CONFIG, config->config); max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer); - max17042_override_por(map, MAX17042_DesignCap, config->design_cap); - max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term); + if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { + max17042_override_por(map, MAX17042_DesignCap, + config->design_cap); + max17042_override_por(map, MAX17042_ICHGTerm, + config->ichgt_term); + } max17042_override_por(map, MAX17042_AtRate, config->at_rate); max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg); @@ -812,8 +848,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17042_FullCAP, config->fullcap); max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom); - max17042_override_por(map, MAX17042_dQacc, config->dqacc); - max17042_override_por(map, MAX17042_dPacc, config->dpacc); + if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { + max17042_override_por(map, MAX17042_dQacc, config->dqacc); + max17042_override_por(map, MAX17042_dPacc, config->dpacc); + } max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0); max17042_override_por(map, MAX17042_TempCo, config->tcompc0); @@ -843,30 +881,183 @@ static inline void max17042_override_por_values(struct max17042_chip *chip) max17042_override_por(map, MAX17047_V_empty, config->vempty); } - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055 && + !chip->enable_vchg_override) max17042_override_por(map, MAX17055_ModelCfg, config->model_cfg); } +static int max17055_override_battery_values(struct max17042_chip *chip) +{ + struct max17042_config_data *config = chip->config_data; + struct regmap *map = chip->regmap; + unsigned int design_cap; + unsigned int model_cfg; + unsigned int dqacc; + u64 dpacc; + int ret; + + if (config->design_cap) { + ret = max17055_write_verify_reg(map, MAX17042_DesignCap, + config->design_cap); + if (ret) + return ret; + } + + if (config->dqacc) { + ret = max17055_write_verify_reg(map, MAX17042_dQacc, + config->dqacc); + if (ret) + return ret; + } + + if (config->ichgt_term) { + ret = max17055_write_verify_reg(map, MAX17042_ICHGTerm, + config->ichgt_term); + if (ret) + return ret; + } + + if (chip->enable_vchg_override) { + ret = regmap_update_bits(map, MAX17055_ModelCfg, + MAX17055_MODELCFG_VCHG_BIT, + config->model_cfg & + MAX17055_MODELCFG_VCHG_BIT); + if (ret) + return ret; + + usleep_range(1000, 2000); + } + + if (!config->design_cap && !config->dqacc && + !chip->enable_vchg_override) + return 0; + + ret = regmap_read(map, MAX17042_DesignCap, &design_cap); + if (ret) + return ret; + + ret = regmap_read(map, MAX17042_dQacc, &dqacc); + if (ret) + return ret; + + ret = regmap_read(map, MAX17055_ModelCfg, &model_cfg); + if (ret) + return ret; + if (chip->enable_vchg_override && + (model_cfg & MAX17055_MODELCFG_VCHG_BIT) != + (config->model_cfg & MAX17055_MODELCFG_VCHG_BIT)) + return -EIO; + + if (!design_cap || !dqacc) + return -ERANGE; + + dpacc = (u64)dqacc * + (model_cfg & MAX17055_MODELCFG_VCHG_BIT ? + MAX17055_DPACC_VCHG_FACTOR : MAX17055_DPACC_FACTOR); + do_div(dpacc, design_cap); + if (dpacc > U16_MAX) + return -ERANGE; + + return max17055_write_verify_reg(map, MAX17042_dPacc, (u16)dpacc); +} + +static int max17055_restore_hibernate(struct max17042_chip *chip) +{ + int restore_hib_ret; + int soft_wakeup_ret; + + soft_wakeup_ret = regmap_write(chip->regmap, MAX17055_SoftWakeup, 0); + restore_hib_ret = max17055_write_verify_reg(chip->regmap, + MAX17055_HibCfg, + chip->hib_cfg); + if (!soft_wakeup_ret && !restore_hib_ret) + chip->hib_restore_pending = false; + + return soft_wakeup_ret ?: restore_hib_ret; +} + +static int max17055_init_chip(struct max17042_chip *chip) +{ + struct regmap *map = chip->regmap; + unsigned int hib_cfg; + unsigned int model_cfg; + unsigned int fstat; + int restore_ret; + int ret; + + if (chip->hib_restore_pending) { + ret = max17055_restore_hibernate(chip); + if (ret) + return ret; + } + + ret = regmap_read_poll_timeout(map, MAX17042_FSTAT, fstat, + !(fstat & MAX17055_FSTAT_DNR_BIT), + MAX17055_DNR_POLL_US, + MAX17055_DNR_TIMEOUT_US); + if (ret) + return ret; + + ret = regmap_read(map, MAX17055_HibCfg, &hib_cfg); + if (ret) + return ret; + + chip->hib_cfg = hib_cfg; + chip->hib_restore_pending = true; + + ret = regmap_write(map, MAX17055_SoftWakeup, 0x0090); + if (ret) + goto restore_hibernate; + + ret = max17055_write_verify_reg(map, MAX17055_HibCfg, 0); + if (ret) + goto restore_hibernate; + + ret = regmap_write(map, MAX17055_SoftWakeup, 0); + if (ret) + goto restore_hibernate; + + max17042_override_por_values(chip); + + ret = max17055_override_battery_values(chip); + if (ret) + goto restore_hibernate; + + ret = regmap_write_bits(map, MAX17055_ModelCfg, + MAX17055_MODELCFG_REFRESH_BIT, + MAX17055_MODELCFG_REFRESH_BIT); + if (ret) + goto restore_hibernate; + + ret = regmap_read_poll_timeout(map, MAX17055_ModelCfg, model_cfg, + !(model_cfg & + MAX17055_MODELCFG_REFRESH_BIT), + MAX17055_REFRESH_POLL_US, + MAX17055_REFRESH_TIMEOUT_US); + +restore_hibernate: + restore_ret = max17055_restore_hibernate(chip); + if (restore_ret) + return restore_ret; + + return ret; +} + static int max17042_init_chip(struct max17042_chip *chip) { struct regmap *map = chip->regmap; int ret; - max17042_override_por_values(chip); - if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) { - regmap_write_bits(map, MAX17055_ModelCfg, - MAX17055_MODELCFG_REFRESH_BIT, - MAX17055_MODELCFG_REFRESH_BIT); - } + ret = max17055_init_chip(chip); + if (ret) + return ret; + } else { + max17042_override_por_values(chip); - /* After Power up, the MAX17042 requires 500mS in order - * to perform signal debouncing and initial SOC reporting - */ - msleep(500); + /* Allow signal debouncing and initial SOC reporting. */ + msleep(500); - if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) { - /* Initialize configuration */ max17042_write_config_regs(chip); /* write cell characterization data */ @@ -902,8 +1093,7 @@ static int max17042_init_chip(struct max17042_chip *chip) } /* Init complete, Clear the POR bit */ - regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0); - return 0; + return regmap_clear_bits(map, MAX17042_STATUS, STATUS_POR_BIT); } static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off) @@ -989,18 +1179,28 @@ static irqreturn_t max17042_thread_handler(int id, void *dev) static void max17042_init_worker(struct work_struct *work) { - struct max17042_chip *chip = container_of(work, + struct max17042_chip *chip = container_of(to_delayed_work(work), struct max17042_chip, work); - int ret; + int ret = 0; /* Initialize registers according to values from config_data */ - if (chip->enable_por_init && chip->config_data) { + if (chip->enable_por_init && chip->config_data) ret = max17042_init_chip(chip); - if (ret) - return; + + if (ret) { + if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055) { + dev_warn_ratelimited(chip->dev, + "initialization failed: %d, retrying\n", ret); + schedule_delayed_work(&chip->work, + msecs_to_jiffies(MAX17055_INIT_RETRY_DELAY_MS)); + } else { + dev_err(chip->dev, "initialization failed: %d\n", ret); + } + return; } - chip->init_complete = 1; + WRITE_ONCE(chip->init_complete, true); + power_supply_changed(chip->battery); } #ifdef CONFIG_OF @@ -1026,10 +1226,14 @@ static int max17042_parse_dt(struct max17042_chip *chip) chip->temp_min = INT_MIN; if (of_property_read_s32(np, "maxim,over-heat-temp", &chip->temp_max)) chip->temp_max = INT_MAX; - if (of_property_read_s32(np, "maxim,dead-volt", &chip->vmin)) + if (of_property_read_u32(np, "maxim,dead-volt", &prop)) chip->vmin = INT_MIN; - if (of_property_read_s32(np, "maxim,over-volt", &chip->vmax)) + else + chip->vmin = prop; + if (of_property_read_u32(np, "maxim,over-volt", &prop)) chip->vmax = INT_MAX; + else + chip->vmin = prop; return 0; } @@ -1060,6 +1264,102 @@ static int max17042_init_defaults(struct max17042_chip *chip) return 0; } +static int max17042_apply_battery_properties(struct max17042_chip *chip, + struct power_supply_battery_info *info) +{ + struct max17042_config_data *config; + struct device *dev = chip->dev; + bool have_design_cap; + bool have_ichgt_term; + bool have_vchg; + u16 design_cap = 0; + u16 ichgt_term = 0; + u16 dqacc = 0; + u16 model_cfg = 0; + u64 data64; + + if (!info || chip->chip_type != MAXIM_DEVICE_TYPE_MAX17055) + return 0; + + have_design_cap = chip->enable_current_sense && + info->charge_full_design_uah > 0; + have_ichgt_term = chip->enable_current_sense && + info->charge_term_current_ua > 0; + have_vchg = info->voltage_max_design_uv >= 0; + if (!have_design_cap && !have_ichgt_term && !have_vchg) + return 0; + + if (have_design_cap) { + data64 = (u64)info->charge_full_design_uah * chip->r_sns; + do_div(data64, MAX17042_CAPACITY_LSB); + if (!data64) + return dev_err_probe(dev, -ERANGE, + "battery design capacity is too small for sense resistor\n"); + if (data64 > U16_MAX) + return dev_err_probe(dev, -ERANGE, + "battery design capacity exceeds register range\n"); + + design_cap = (u16)data64; + dqacc = design_cap / MAX17055_DQACC_DIV; + if (!dqacc) + return dev_err_probe(dev, -ERANGE, + "battery design capacity is too small for EZ config\n"); + } + + if (have_ichgt_term) { + data64 = (u64)info->charge_term_current_ua * chip->r_sns; + do_div(data64, MAX17042_CURRENT_LSB); + if (!data64) + return dev_err_probe(dev, -ERANGE, + "charge termination current is too small for sense resistor\n"); + if (data64 > S16_MAX) + return dev_err_probe(dev, -ERANGE, + "charge termination current exceeds positive register range\n"); + + ichgt_term = (u16)data64; + } + + if (have_vchg) { + if (!info->voltage_max_design_uv) + return dev_err_probe(dev, -EINVAL, + "battery design voltage must be positive\n"); + + if (info->voltage_max_design_uv > MAX17055_VCHG_THRESHOLD_UV) + model_cfg = MAX17055_MODELCFG_VCHG_BIT; + } + + config = chip->config_data; + if (!config) { + config = devm_kzalloc(dev, sizeof(*config), GFP_KERNEL); + if (!config) + return -ENOMEM; + } + + if (have_design_cap) { + config->design_cap = design_cap; + config->dqacc = dqacc; + } + if (have_ichgt_term) + config->ichgt_term = ichgt_term; + if (have_vchg) { + config->model_cfg &= ~MAX17055_MODELCFG_VCHG_BIT; + config->model_cfg |= model_cfg; + chip->enable_vchg_override = true; + } + + chip->config_data = config; + chip->enable_por_init = true; + + return 0; +} + +static int max17042_init_battery(struct power_supply *psy) +{ + struct max17042_chip *chip = power_supply_get_drvdata(psy); + + return max17042_apply_battery_properties(chip, psy->battery_info); +} + static const struct regmap_config max17042_regmap_config = { .name = "max17042", .reg_bits = 8, @@ -1113,6 +1413,7 @@ static const struct power_supply_desc max17042_psy_desc = { .set_property = max17042_set_property, .property_is_writeable = max17042_property_is_writeable, .external_power_changed = power_supply_changed, + .init = max17042_init_battery, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props), }; @@ -1123,6 +1424,7 @@ static const struct power_supply_desc max17042_no_current_sense_psy_desc = { .get_property = max17042_get_property, .set_property = max17042_set_property, .property_is_writeable = max17042_property_is_writeable, + .init = max17042_init_battery, .properties = max17042_battery_props, .num_properties = ARRAY_SIZE(max17042_battery_props) - 2, }; @@ -1242,15 +1544,18 @@ static int max17042_probe(struct i2c_client *client, struct device *dev, int irq chip->irq = irq; - regmap_read(chip->regmap, MAX17042_STATUS, &val); + ret = regmap_read(chip->regmap, MAX17042_STATUS, &val); + if (ret) + return dev_err_probe(dev, ret, "failed to read status\n"); + if (val & STATUS_POR_BIT) { - ret = devm_work_autocancel(dev, &chip->work, - max17042_init_worker); + ret = devm_delayed_work_autocancel(dev, &chip->work, + max17042_init_worker); if (ret) return ret; - schedule_work(&chip->work); + schedule_delayed_work(&chip->work, 0); } else { - chip->init_complete = 1; + WRITE_ONCE(chip->init_complete, true); } return 0; diff --git a/drivers/power/supply/max77759_charger.c b/drivers/power/supply/max77759_charger.c index 41d810bb6744..689fc733fa3f 100644 --- a/drivers/power/supply/max77759_charger.c +++ b/drivers/power/supply/max77759_charger.c @@ -532,9 +532,7 @@ static int max77759_init_irqhandler(struct max77759_charger *chg) ret = devm_request_threaded_irq(dev, chg->irqs[i], NULL, thread_fn, 0, name, chg); if (ret) - return dev_err_probe(dev, ret, - "Unable to register irq handler for %s\n", - chgr_irqs_str[i]); + return ret; } return 0; diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c index 45fbaad6c647..d5267ba19969 100644 --- a/drivers/power/supply/max8903_charger.c +++ b/drivers/power/supply/max8903_charger.c @@ -9,11 +9,29 @@ #include #include #include +#include #include +#include #include #include #include +/* + * IUSB pin: hardcoded by silicon to 100 mA (low) / 500 mA (high). + * MAX8903A/B/C/D/E/F/G/H/I datasheet, "Pin Description" table: + * "USB Current-Limit Set Input. Drive IUSB logic-low to set the + * USB current limit to 100mA. Drive IUSB logic-high to set the + * USB current limit to 500mA." + * Not a board parameter - never DT-configurable. + */ +#define MAX8903_USB_CURRENT_LIMIT_LOW_UA 100000 +#define MAX8903_USB_CURRENT_LIMIT_HIGH_UA 500000 + +struct max8903_current_limit_mapping { + u32 limit_ua; /* Current limit in microamps */ + u32 gpio_value; /* GPIO bit pattern */ +}; + struct max8903_data { struct device *dev; struct power_supply *psy; @@ -31,6 +49,25 @@ struct max8903_data { struct gpio_desc *flt; /* Fault output */ struct gpio_desc *dcm; /* Current-Limit Mode input (1: DC, 2: USB) */ struct gpio_desc *usus; /* USB Suspend Input (1: suspended) */ + + /* DC current limit control (ISET pins) */ + struct gpio_descs *dc_current_limit_gpios; + struct max8903_current_limit_mapping *dc_current_limit_map; + u32 dc_current_limit_map_size; + u32 dc_current_limit_ua; /* Current setting in uA */ + + /* USB current limit control (IUSB pin) */ + struct gpio_desc *usb_current_limit_gpio; + u32 usb_current_limit_ua; /* Current setting in uA */ + + /* + * Serialises ta_in / usb_in updates against + * max8903_set_property() which steers the current-limit write to + * the DC or USB path based on which source is currently online. + * The IRQ handlers are requested with IRQF_ONESHOT (threaded), so + * a sleepable mutex is the right primitive in both contexts. + */ + struct mutex source_lock; bool fault; bool usb_in; bool ta_in; @@ -40,6 +77,7 @@ static enum power_supply_property max8903_charger_props[] = { POWER_SUPPLY_PROP_STATUS, /* Charger status output */ POWER_SUPPLY_PROP_ONLINE, /* External power source */ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */ + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, /* Input current limit */ }; static int max8903_get_property(struct power_supply *psy, @@ -47,6 +85,24 @@ static int max8903_get_property(struct power_supply *psy, union power_supply_propval *val) { struct max8903_data *data = power_supply_get_drvdata(psy); + bool ta_in, usb_in; + u32 dc_limit, usb_limit; + + /* + * Snapshot the source flags and current-limit settings under the + * source_lock that the IRQs (max8903_dcin / max8903_usbin) and + * max8903_set_property() take when updating them, so we never + * observe a torn pair of (source-online flag, current-limit ua). + * The gpiod_get_value() reads further down deliberately stay + * outside the lock — they hit the GPIO controller, not driver + * state, and the IRQs do not touch them under the lock either. + */ + mutex_lock(&data->source_lock); + ta_in = data->ta_in; + usb_in = data->usb_in; + dc_limit = data->dc_current_limit_ua; + usb_limit = data->usb_current_limit_ua; + mutex_unlock(&data->source_lock); switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -55,21 +111,42 @@ static int max8903_get_property(struct power_supply *psy, if (gpiod_get_value(data->chg)) /* CHG asserted */ val->intval = POWER_SUPPLY_STATUS_CHARGING; - else if (data->usb_in || data->ta_in) + else if (usb_in || ta_in) val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; else val->intval = POWER_SUPPLY_STATUS_DISCHARGING; } break; case POWER_SUPPLY_PROP_ONLINE: - val->intval = 0; - if (data->usb_in || data->ta_in) - val->intval = 1; + val->intval = (ta_in || usb_in) ? 1 : 0; break; case POWER_SUPPLY_PROP_HEALTH: - val->intval = POWER_SUPPLY_HEALTH_GOOD; - if (data->fault) - val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + /* + * data->fault is a single bool toggled from one IRQ + * handler, so a torn read is not possible; no need to + * extend source_lock coverage here. + */ + val->intval = data->fault ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + : POWER_SUPPLY_HEALTH_GOOD; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* + * Hardware prioritises DC over USB - when ta_in is asserted + * the part draws from the DC input regardless of USB state. + * So always report the DC-side limit when DC is online, and + * refuse rather than silently fall back to the USB cap if + * the DC GPIOs are not configured - that would mis-describe + * the active source. Same policy applies in the set path. + */ + if (ta_in) { + if (!data->dc_current_limit_gpios) + return -ENODATA; + val->intval = dc_limit; + } else if (usb_in && data->usb_current_limit_gpio) { + val->intval = usb_limit; + } else { + return -ENODATA; + } break; default: return -EINVAL; @@ -78,6 +155,144 @@ static int max8903_get_property(struct power_supply *psy, return 0; } +static int max8903_set_dc_current_limit(struct max8903_data *data, u32 limit_ua) +{ + int i, best_idx = -1; + /* + * The mapping's gpio_value fits in the lowest ndescs bits of one + * unsigned long (parse_dc_current_limit enforces ndescs < 32 and + * gpio_value < BIT(ndescs)); a single-word bitmap is sufficient + * on both 32- and 64-bit builds. Don't use bitmap_from_arr32() - + * that macro reinterprets its source pointer as unsigned long on + * 64-bit and would read past the on-stack u32. + */ + DECLARE_BITMAP(values, BITS_PER_TYPE(u32)); + + if (!data->dc_current_limit_gpios) + return -EOPNOTSUPP; + + /* + * Find the highest supported current <= requested. Use a -1 + * "not found" sentinel rather than tracking best_limit > 0 so + * that a 0 uA entry (used to disable charging) can be selected + * by a 0 uA request. + */ + for (i = 0; i < data->dc_current_limit_map_size; i++) { + if (data->dc_current_limit_map[i].limit_ua > limit_ua) + continue; + if (best_idx < 0 || + data->dc_current_limit_map[i].limit_ua > + data->dc_current_limit_map[best_idx].limit_ua) + best_idx = i; + } + + if (best_idx < 0) + return -EINVAL; + + bitmap_zero(values, BITS_PER_TYPE(u32)); + values[0] = data->dc_current_limit_map[best_idx].gpio_value; + gpiod_set_array_value_cansleep(data->dc_current_limit_gpios->ndescs, + data->dc_current_limit_gpios->desc, + data->dc_current_limit_gpios->info, + values); + + data->dc_current_limit_ua = data->dc_current_limit_map[best_idx].limit_ua; + dev_dbg(data->dev, "DC current limit set to %u uA\n", + data->dc_current_limit_ua); + + return 0; +} + +static int max8903_set_usb_current_limit(struct max8903_data *data, u32 limit_ua) +{ + u32 selected; + int gpio_val; + + if (!data->usb_current_limit_gpio) + return -EOPNOTSUPP; + + /* + * IUSB is a single-bit input with two silicon-fixed settings; + * pick HIGH (500 mA) iff the caller's cap can absorb it, else + * LOW (100 mA), else refuse rather than program a higher current + * than the request allows. + */ + if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_HIGH_UA) { + selected = MAX8903_USB_CURRENT_LIMIT_HIGH_UA; + gpio_val = 1; + } else if (limit_ua >= MAX8903_USB_CURRENT_LIMIT_LOW_UA) { + selected = MAX8903_USB_CURRENT_LIMIT_LOW_UA; + gpio_val = 0; + } else { + return -EINVAL; + } + + gpiod_set_value_cansleep(data->usb_current_limit_gpio, gpio_val); + data->usb_current_limit_ua = selected; + + dev_dbg(data->dev, "USB current limit set to %u uA\n", + data->usb_current_limit_ua); + + return 0; +} + +static int max8903_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct max8903_data *data = power_supply_get_drvdata(psy); + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + /* + * val->intval is signed; the set_*_current_limit() helpers + * take a u32. Reject negatives explicitly so a negative + * request cannot widen into a huge unsigned value, bypass + * the "limit <= cap" bounds check inside the helper, and + * silently program the maximum permitted current. + */ + if (val->intval < 0) + return -EINVAL; + /* + * Hold source_lock across the source check and the + * resulting hardware write so the IRQ handler cannot + * flip ta_in/usb_in between them and have us program the + * limit for a source that has just gone offline. Mirror + * the DC-priority policy of the get path: if DC is online + * route to the DC helper (refuse if DC GPIOs aren't + * configured) rather than fall through to USB. + */ + mutex_lock(&data->source_lock); + if (data->ta_in) + ret = data->dc_current_limit_gpios ? + max8903_set_dc_current_limit(data, val->intval) : + -ENODEV; + else if (data->usb_in && data->usb_current_limit_gpio) + ret = max8903_set_usb_current_limit(data, val->intval); + else + ret = -EINVAL; + mutex_unlock(&data->source_lock); + return ret; + default: + return -EINVAL; + } +} + +static int max8903_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + struct max8903_data *data = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + return data->dc_current_limit_gpios || + data->usb_current_limit_gpio; + default: + return 0; + } +} + static irqreturn_t max8903_dcin(int irq, void *_data) { struct max8903_data *data = _data; @@ -91,10 +306,21 @@ static irqreturn_t max8903_dcin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* + * Hold source_lock across the full read-modify-evaluate block: + * - so a concurrent max8903_set_property() sees a consistent + * state (lock release would otherwise expose a window where + * data->ta_in is updated but the cen/dcm writes still pend); + * - so the cen enable calculation reads a stable data->usb_in + * rather than racing with max8903_usbin() and writing the + * wrong enable state. + */ + mutex_lock(&data->source_lock); ta_in = gpiod_get_value(data->dok); - - if (ta_in == data->ta_in) + if (ta_in == data->ta_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } data->ta_in = ta_in; @@ -119,9 +345,6 @@ static irqreturn_t max8903_dcin(int irq, void *_data) gpiod_set_value(data->cen, val); } - dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? - "Connected" : "Disconnected"); - old_type = data->psy_desc.type; if (data->ta_in) @@ -130,6 +353,10 @@ static irqreturn_t max8903_dcin(int irq, void *_data) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + mutex_unlock(&data->source_lock); + + dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ? + "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); @@ -150,10 +377,13 @@ static irqreturn_t max8903_usbin(int irq, void *_data) * library as the line should be flagged GPIO_ACTIVE_LOW in the device * tree. */ + /* See max8903_dcin(): hold the lock across the full update. */ + mutex_lock(&data->source_lock); usb_in = gpiod_get_value(data->uok); - - if (usb_in == data->usb_in) + if (usb_in == data->usb_in) { + mutex_unlock(&data->source_lock); return IRQ_HANDLED; + } data->usb_in = usb_in; @@ -176,9 +406,6 @@ static irqreturn_t max8903_usbin(int irq, void *_data) gpiod_set_value(data->cen, val); } - dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? - "Connected" : "Disconnected"); - old_type = data->psy_desc.type; if (data->ta_in) @@ -187,6 +414,10 @@ static irqreturn_t max8903_usbin(int irq, void *_data) data->psy_desc.type = POWER_SUPPLY_TYPE_USB; else data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY; + mutex_unlock(&data->source_lock); + + dev_dbg(data->dev, "USB Charger %s.\n", usb_in ? + "Connected" : "Disconnected"); if (old_type != data->psy_desc.type) power_supply_changed(data->psy); @@ -221,6 +452,145 @@ static irqreturn_t max8903_fault(int irq, void *_data) return IRQ_HANDLED; } +static int max8903_parse_dc_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev = &pdev->dev; + int ret, i, map_size; + u32 *map; + + data->dc_current_limit_gpios = devm_gpiod_get_array_optional(dev, + "dc-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->dc_current_limit_gpios)) + return dev_err_probe(dev, PTR_ERR(data->dc_current_limit_gpios), + "failed to get DC current limit GPIOs"); + + if (!data->dc_current_limit_gpios) + return 0; /* Optional feature not present */ + + /* + * gpio_value entries below are bit patterns indexed into the + * dc-current-limit GPIO array. The driver represents them in + * a single unsigned long for gpiod_set_array_value_cansleep(), + * and BIT(ndescs) further down assumes ndescs fits in a u32 + * shift; reject pathological DTs at parse time instead of + * relying on undefined-behaviour-free dtschema. The binding + * already caps maxItems at 4 so this is purely defensive. + */ + if (data->dc_current_limit_gpios->ndescs >= BITS_PER_TYPE(u32)) { + dev_err(dev, "dc-current-limit-gpios: %u GPIOs exceeds %u-bit cap\n", + data->dc_current_limit_gpios->ndescs, + (unsigned int)BITS_PER_TYPE(u32)); + return -EINVAL; + } + + /* Parse mapping: pairs of (current_ua, gpio_value) */ + map_size = device_property_count_u32(dev, "dc-current-limit-mapping"); + if (map_size <= 0 || map_size % 2) { + dev_err(dev, "invalid dc-current-limit-mapping\n"); + return -EINVAL; + } + + /* + * map[] is a scratch buffer used only inside this function to + * read the property and unpack it into data->dc_current_limit_map. + * Use a plain kmalloc + kfree rather than devm_*: there is no + * reason to keep the raw mirror around for the lifetime of the + * device. + */ + map = kmalloc_array(map_size, sizeof(*map), GFP_KERNEL); + if (!map) + return -ENOMEM; + + ret = device_property_read_u32_array(dev, "dc-current-limit-mapping", + map, map_size); + if (ret) { + dev_err(dev, "failed to read dc-current-limit-mapping\n"); + kfree(map); + return ret; + } + + data->dc_current_limit_map_size = map_size / 2; + data->dc_current_limit_map = devm_kcalloc(dev, + data->dc_current_limit_map_size, + sizeof(*data->dc_current_limit_map), + GFP_KERNEL); + if (!data->dc_current_limit_map) { + kfree(map); + return -ENOMEM; + } + + for (i = 0; i < data->dc_current_limit_map_size; i++) { + u32 gpio_value = map[i * 2 + 1]; + + /* + * gpio_value is the bitmap programmed across the + * dc-current-limit GPIOs, so it cannot represent more + * bits than the GPIO array width. A larger value would + * be silently truncated by gpiod_set_array_value() and + * select the wrong limit; reject it at parse time so + * the bogus DT is visible to the integrator. + */ + if (gpio_value >= BIT(data->dc_current_limit_gpios->ndescs)) { + dev_err(dev, + "dc-current-limit-mapping entry %d: gpio_value 0x%x exceeds %u-GPIO range\n", + i, gpio_value, + data->dc_current_limit_gpios->ndescs); + kfree(map); + return -EINVAL; + } + data->dc_current_limit_map[i].limit_ua = map[i * 2]; + data->dc_current_limit_map[i].gpio_value = gpio_value; + } + + kfree(map); + + /* + * devm_gpiod_get_array_optional() above asked for GPIOD_OUT_LOW, + * so the hardware mux starts at gpio_value 0. Require the DT + * mapping to include a gpio_value=0 entry so the software + * current-limit state has a definite initial value matching the + * hardware. Without this entry we would have to guess and the + * reported INPUT_CURRENT_LIMIT could disagree with what the + * mux is actually wired to until a set_property write picks a + * real value. + */ + for (i = 0; i < data->dc_current_limit_map_size; i++) + if (data->dc_current_limit_map[i].gpio_value == 0) + break; + if (i == data->dc_current_limit_map_size) { + dev_err(dev, + "dc-current-limit-mapping must include a gpio_value=0 entry to describe the boot-time mux state\n"); + return -EINVAL; + } + data->dc_current_limit_ua = data->dc_current_limit_map[i].limit_ua; + + dev_dbg(dev, "DC current limit control: %d levels available, initial %u uA\n", + data->dc_current_limit_map_size, data->dc_current_limit_ua); + + return 0; +} + +static int max8903_parse_usb_current_limit(struct platform_device *pdev, + struct max8903_data *data) +{ + struct device *dev = &pdev->dev; + + data->usb_current_limit_gpio = devm_gpiod_get_optional(dev, + "usb-current-limit", GPIOD_OUT_LOW); + if (IS_ERR(data->usb_current_limit_gpio)) + return dev_err_probe(dev, PTR_ERR(data->usb_current_limit_gpio), + "failed to get USB current limit GPIO"); + + if (!data->usb_current_limit_gpio) + return 0; /* Optional feature not present */ + + /* Start at low current (IUSB low = 100 mA) for safety */ + data->usb_current_limit_ua = MAX8903_USB_CURRENT_LIMIT_LOW_UA; + + return 0; +} + static int max8903_setup_gpios(struct platform_device *pdev) { struct max8903_data *data = platform_get_drvdata(pdev); @@ -335,17 +705,28 @@ static int max8903_probe(struct platform_device *pdev) return -ENOMEM; data->dev = dev; + mutex_init(&data->source_lock); platform_set_drvdata(pdev, data); ret = max8903_setup_gpios(pdev); if (ret) return ret; + ret = max8903_parse_dc_current_limit(pdev, data); + if (ret) + return ret; + + ret = max8903_parse_usb_current_limit(pdev, data); + if (ret) + return ret; + data->psy_desc.name = "max8903_charger"; data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS : ((data->usb_in) ? POWER_SUPPLY_TYPE_USB : POWER_SUPPLY_TYPE_BATTERY); data->psy_desc.get_property = max8903_get_property; + data->psy_desc.set_property = max8903_set_property; + data->psy_desc.property_is_writeable = max8903_property_is_writeable; data->psy_desc.properties = max8903_charger_props; data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props); @@ -364,11 +745,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 DC IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for DC (%d)\n", - gpiod_to_irq(data->dok), ret); + if (ret) return ret; - } } if (data->uok) { @@ -377,11 +755,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 USB IN", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for USB (%d)\n", - gpiod_to_irq(data->uok), ret); + if (ret) return ret; - } } if (data->flt) { @@ -390,11 +765,8 @@ static int max8903_probe(struct platform_device *pdev) IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_ONESHOT, "MAX8903 Fault", data); - if (ret) { - dev_err(dev, "Cannot request irq %d for Fault (%d)\n", - gpiod_to_irq(data->flt), ret); + if (ret) return ret; - } } return 0; diff --git a/drivers/power/supply/max8971_charger.c b/drivers/power/supply/max8971_charger.c index 82f20b89b6b4..0d69e9f44c05 100644 --- a/drivers/power/supply/max8971_charger.c +++ b/drivers/power/supply/max8971_charger.c @@ -683,7 +683,7 @@ static int max8971_probe(struct i2c_client *client) err = devm_request_threaded_irq(dev, client->irq, NULL, &max8971_interrupt, IRQF_ONESHOT | IRQF_SHARED, client->name, priv); if (err) - return dev_err_probe(dev, err, "failed to register IRQ %d\n", client->irq); + return err; extcon = of_graph_get_remote_node(dev->of_node, -1, -1); if (!extcon) diff --git a/drivers/power/supply/mp2629_charger.c b/drivers/power/supply/mp2629_charger.c index f758d6a7bc8c..04294359ed7d 100644 --- a/drivers/power/supply/mp2629_charger.c +++ b/drivers/power/supply/mp2629_charger.c @@ -630,10 +630,8 @@ static int mp2629_charger_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(dev, irq, NULL, mp2629_irq_handler, IRQF_ONESHOT | IRQF_TRIGGER_RISING, "mp2629-charger", charger); - if (ret) { - dev_err(dev, "failed to request gpio IRQ\n"); + if (ret) return ret; - } regmap_update_bits(charger->regmap, MP2629_REG_INTERRUPT, GENMASK(6, 5), BIT(6) | BIT(5)); diff --git a/drivers/power/supply/mt6360_charger.c b/drivers/power/supply/mt6360_charger.c index d3b0731f6562..b4d462343151 100644 --- a/drivers/power/supply/mt6360_charger.c +++ b/drivers/power/supply/mt6360_charger.c @@ -721,8 +721,7 @@ static int mt6360_chg_irq_register(struct platform_device *pdev) irq_descs[i].name, platform_get_drvdata(pdev)); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, "Failed to request %s irq\n", - irq_descs[i].name); + return ret; } return 0; diff --git a/drivers/power/supply/mt6370-charger.c b/drivers/power/supply/mt6370-charger.c index 916556baa854..9d388bc53098 100644 --- a/drivers/power/supply/mt6370-charger.c +++ b/drivers/power/supply/mt6370-charger.c @@ -837,9 +837,7 @@ static int mt6370_chg_init_irq(struct mt6370_priv *priv) IRQF_TRIGGER_FALLING, dev_name(priv->dev), priv); if (ret) - return dev_err_probe(priv->dev, ret, - "Failed to request irq %s\n", - mt6370_chg_irqs[i].name); + return ret; } return 0; diff --git a/drivers/power/supply/pf1550-charger.c b/drivers/power/supply/pf1550-charger.c index 41036f4cb64a..2ead1df60e65 100644 --- a/drivers/power/supply/pf1550-charger.c +++ b/drivers/power/supply/pf1550-charger.c @@ -514,7 +514,7 @@ static int pf1550_reg_init(struct pf1550_charger *chg) * a battery. The other supported mode is mode 2, the charger is turned * on to charge a battery when present. */ - if (power_supply_get_battery_info(chg->charger, &info)) { + if (!power_supply_get_battery_info(chg->charger, &info)) { ret = regmap_write(chg->pf1550->regmap, PF1550_CHARG_REG_CHG_OPER, PF1550_CHG_BAT_ON); @@ -612,8 +612,7 @@ static int pf1550_charger_probe(struct platform_device *pdev) IRQF_NO_SUSPEND, "pf1550-charger", chg); if (ret) - return dev_err_probe(&pdev->dev, ret, - "failed irq request\n"); + return ret; } pf1550_dt_parse_dev_info(chg); diff --git a/drivers/power/supply/pm8916_lbc.c b/drivers/power/supply/pm8916_lbc.c index cdc4d78c4219..6910b8c24343 100644 --- a/drivers/power/supply/pm8916_lbc.c +++ b/drivers/power/supply/pm8916_lbc.c @@ -231,11 +231,7 @@ static int pm8916_lbc_charger_probe_dt(struct pm8916_lbc_charger *chg) return ret; /* Disable charger timeout. */ - ret = regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); - if (ret) - return ret; - - return ret; + return regmap_write(chg->regmap, chg->reg[LBC_CHGR] + PM8916_LBC_CHGR_TCHG_MAX_EN, 0x00); } static const struct power_supply_desc pm8916_lbc_charger_psy_desc = { diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c index 2532e221b2e1..00d8bc98d588 100644 --- a/drivers/power/supply/power_supply_core.c +++ b/drivers/power/supply/power_supply_core.c @@ -477,6 +477,159 @@ struct power_supply *power_supply_get_by_name(const char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +static bool power_supply_is_system_battery(struct power_supply *psy) +{ + union power_supply_propval val; + + if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY) + return false; + + if (!power_supply_get_property_direct(psy, POWER_SUPPLY_PROP_SCOPE, + &val)) + if (val.intval == POWER_SUPPLY_SCOPE_DEVICE) + return false; + + return true; +} + +static int __power_supply_get_num_system_batteries(struct power_supply *epsy, + void *data) +{ + int *count = data; + + if (power_supply_is_system_battery(epsy)) + (*count)++; + + return 0; +} + +static int power_supply_get_num_system_batteries(struct device *dev) +{ + int ret, count = 0; + + ret = power_supply_for_each_psy(&count, + __power_supply_get_num_system_batteries); + + dev_dbg(dev, "%s: count: %d ret %d\n", __func__, count, ret); + + if (ret) + return ret; + + return count; +} + +struct psy_get_supplies_data { + int cnt; + int size; + struct power_supply **psys; +}; + +static int +__power_supply_populate_system_batteries_array(struct power_supply *epsy, + void *_data) +{ + struct psy_get_supplies_data *data = _data; + + if (power_supply_is_system_battery(epsy)) { + if (data->size <= data->cnt) + return -EOVERFLOW; + + get_device(&epsy->dev); + data->psys[data->cnt] = epsy; + atomic_inc(&epsy->use_cnt); + data->cnt++; + } + + return 0; +} + +static int +power_supply_populate_system_batteries_array(struct device *dev, int size, + struct power_supply **batteries) +{ + int ret; + + struct psy_get_supplies_data data = { + .cnt = 0, + .size = size, + .psys = batteries, + }; + + ret = power_supply_for_each_psy(&data, + __power_supply_populate_system_batteries_array); + + dev_dbg(dev, "%s Found %d batteries with array size %d ret %d\n", + __func__, data.cnt, data.size, ret); + + if (ret < 0 || !data.cnt) { + power_supply_put_system_batteries(batteries, data.cnt); + return ret; + } + + return data.cnt; +} + +/** + * power_supply_get_system_batteries() - Fetches references to battery type + * power supplies in the system. + * @dev: Pointer to device requesting the power supply refs. + * @psys: Pointer to an array of power supply refs. + * + * Helper function to get handles to battery type power supplies in the system. + * If acquiring a ref to a power supply fails, then the search for battery type + * power supplies will abort and the acquired power supply references will be + * released. + * + * Return: Indicates the number of battery type power supplies returned on + * success or a negative error code on failure. + * + * Call power_supply_put_system_batteries() after use to cleanup resources. + */ +int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + int ret; + + if (!psys) + return -EINVAL; + + ret = power_supply_get_num_system_batteries(dev); + if (ret <= 0) { + *psys = NULL; + return ret; + } + + *psys = kzalloc_objs(**psys, ret); + if (!*psys) + return -ENOMEM; + + ret = power_supply_populate_system_batteries_array(dev, ret, *psys); + if (ret <= 0) + *psys = NULL; + + return ret; +} +EXPORT_SYMBOL_GPL(power_supply_get_system_batteries); + +/** + * power_supply_put_system_batteries() - Cleanup resources allocated by + * power_supply_get_system_batteries() + * @psys: Array of power supply references to release and free. + * @count: Number of elements in the array. + */ +void power_supply_put_system_batteries(struct power_supply **psys, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (psys[i]) + power_supply_put(psys[i]); + } + + kfree(psys); +} +EXPORT_SYMBOL_GPL(power_supply_put_system_batteries); + /** * power_supply_put() - Drop reference obtained with power_supply_get_by_name * @psy: Reference to put @@ -1624,6 +1777,14 @@ __power_supply_register(struct device *parent, init_rwsem(&psy->extensions_sem); INIT_LIST_HEAD(&psy->extensions); + if (desc->init) { + rc = desc->init(psy); + if (WARN_ON_ONCE(rc > 0)) + rc = -EINVAL; + if (rc) + goto check_supplies_failed; + } + rc = device_add(dev); if (rc) goto device_add_failed; @@ -1636,11 +1797,11 @@ __power_supply_register(struct device *parent, if (rc) goto register_thermal_failed; - rc = power_supply_create_triggers(psy); - if (rc) - goto create_triggers_failed; - scoped_guard(rwsem_read, &psy->extensions_sem) { + rc = power_supply_create_triggers(psy); + if (rc) + goto create_triggers_failed; + rc = power_supply_add_hwmon_sysfs(psy); if (rc) goto add_hwmon_sysfs_failed; diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c index 1548aaba3362..538cc0440e53 100644 --- a/drivers/power/supply/power_supply_leds.c +++ b/drivers/power/supply/power_supply_leds.c @@ -17,8 +17,6 @@ #include "power_supply.h" -/* Battery specific LEDs triggers. */ - struct power_supply_led_trigger { struct led_trigger trig; struct power_supply *psy; @@ -89,7 +87,7 @@ static void power_supply_unregister_led_trigger(struct led_trigger *trig) kfree(psy_trig); } -static void power_supply_update_bat_leds(struct power_supply *psy) +static void power_supply_update_status_leds(struct power_supply *psy) { union power_supply_propval status; unsigned int intensity_green[3] = { 0, 255, 0 }; @@ -102,7 +100,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) switch (status.intval) { case POWER_SUPPLY_STATUS_FULL: - led_trigger_event(psy->trig, LED_FULL); + led_trigger_event(psy->charging_or_full_trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_FULL); /* Going from blink to LED on requires a LED_OFF event to stop blink */ @@ -114,7 +112,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; case POWER_SUPPLY_STATUS_CHARGING: - led_trigger_event(psy->trig, LED_FULL); + led_trigger_event(psy->charging_or_full_trig, LED_FULL); led_trigger_event(psy->charging_trig, LED_FULL); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_blink(psy->charging_blink_full_solid_trig, 0, 0); @@ -124,7 +122,7 @@ static void power_supply_update_bat_leds(struct power_supply *psy) LED_FULL); break; default: - led_trigger_event(psy->trig, LED_OFF); + led_trigger_event(psy->charging_or_full_trig, LED_OFF); led_trigger_event(psy->charging_trig, LED_OFF); led_trigger_event(psy->full_trig, LED_OFF); led_trigger_event(psy->charging_blink_full_solid_trig, @@ -135,21 +133,15 @@ static void power_supply_update_bat_leds(struct power_supply *psy) } } -static void power_supply_remove_bat_triggers(struct power_supply *psy) -{ - power_supply_unregister_led_trigger(psy->trig); - power_supply_unregister_led_trigger(psy->charging_trig); - power_supply_unregister_led_trigger(psy->full_trig); - power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); - power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); -} - -static int power_supply_create_bat_triggers(struct power_supply *psy) +static int power_supply_create_status_triggers(struct power_supply *psy) { int err = 0; + if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_STATUS)) + return 0; + power_supply_register_led_trigger(psy, "%s-charging-or-full", - &psy->trig, &err); + &psy->charging_or_full_trig, &err); power_supply_register_led_trigger(psy, "%s-charging", &psy->charging_trig, &err); power_supply_register_led_trigger(psy, "%s-full", @@ -158,15 +150,11 @@ static int power_supply_create_bat_triggers(struct power_supply *psy) &psy->charging_blink_full_solid_trig, &err); power_supply_register_led_trigger(psy, "%s-charging-orange-full-green", &psy->charging_orange_full_green_trig, &err); - if (err) - power_supply_remove_bat_triggers(psy); return err; } -/* Generated power specific LEDs triggers. */ - -static void power_supply_update_gen_leds(struct power_supply *psy) +static void power_supply_update_online_leds(struct power_supply *psy) { union power_supply_propval online; @@ -175,43 +163,53 @@ static void power_supply_update_gen_leds(struct power_supply *psy) dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval); - if (online.intval) - led_trigger_event(psy->trig, LED_FULL); - else - led_trigger_event(psy->trig, LED_OFF); + led_trigger_event(psy->online_trig, online.intval ? LED_FULL : LED_OFF); } -static int power_supply_create_gen_triggers(struct power_supply *psy) +static int power_supply_create_online_trigger(struct power_supply *psy) { - return power_supply_register_led_trigger(psy, "%s-online", &psy->trig, NULL); -} + int err = 0; -static void power_supply_remove_gen_triggers(struct power_supply *psy) -{ - power_supply_unregister_led_trigger(psy->trig); -} + if (!power_supply_has_property(psy, POWER_SUPPLY_PROP_ONLINE)) + return 0; -/* Choice what triggers to create&update. */ + power_supply_register_led_trigger(psy, "%s-online", &psy->online_trig, + &err); + + return err; +} void power_supply_update_leds(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_update_bat_leds(psy); - else - power_supply_update_gen_leds(psy); + power_supply_update_online_leds(psy); + power_supply_update_status_leds(psy); } int power_supply_create_triggers(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - return power_supply_create_bat_triggers(psy); - return power_supply_create_gen_triggers(psy); + int err; + + err = power_supply_create_online_trigger(psy); + if (err) + goto err_remove; + + err = power_supply_create_status_triggers(psy); + if (err) + goto err_remove; + + return 0; + +err_remove: + power_supply_remove_triggers(psy); + return err; } void power_supply_remove_triggers(struct power_supply *psy) { - if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY) - power_supply_remove_bat_triggers(psy); - else - power_supply_remove_gen_triggers(psy); + power_supply_unregister_led_trigger(psy->online_trig); + power_supply_unregister_led_trigger(psy->charging_or_full_trig); + power_supply_unregister_led_trigger(psy->charging_trig); + power_supply_unregister_led_trigger(psy->full_trig); + power_supply_unregister_led_trigger(psy->charging_blink_full_solid_trig); + power_supply_unregister_led_trigger(psy->charging_orange_full_green_trig); } diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c index f30a7b9ccd5e..9d6b24856c8b 100644 --- a/drivers/power/supply/power_supply_sysfs.c +++ b/drivers/power/supply/power_supply_sysfs.c @@ -124,6 +124,10 @@ static const char * const POWER_SUPPLY_TECHNOLOGY_TEXT[] = { [POWER_SUPPLY_TECHNOLOGY_LiFe] = "LiFe", [POWER_SUPPLY_TECHNOLOGY_NiCd] = "NiCd", [POWER_SUPPLY_TECHNOLOGY_LiMn] = "LiMn", + [POWER_SUPPLY_TECHNOLOGY_PbAc] = "PbAc", + [POWER_SUPPLY_TECHNOLOGY_NiZn] = "NiZn", + [POWER_SUPPLY_TECHNOLOGY_RAM] = "RAM", + [POWER_SUPPLY_TECHNOLOGY_ZnAr] = "ZnAr", }; static const char * const POWER_SUPPLY_CAPACITY_LEVEL_TEXT[] = { diff --git a/drivers/power/supply/qcom_battmgr.c b/drivers/power/supply/qcom_battmgr.c index 490137a23d00..c76389b2f7e8 100644 --- a/drivers/power/supply/qcom_battmgr.c +++ b/drivers/power/supply/qcom_battmgr.c @@ -1231,7 +1231,7 @@ static void qcom_battmgr_sc8280xp_strcpy(char *dest, const char *src) memcpy(dest, src + 1, len); dest[len] = '\0'; } else { - memcpy(dest, src, BATTMGR_STRING_LEN); + strscpy(dest, src, BATTMGR_STRING_LEN); } } diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c index 28afe758a2da..f33aab9619c4 100644 --- a/drivers/power/supply/qcom_smbb.c +++ b/drivers/power/supply/qcom_smbb.c @@ -937,11 +937,8 @@ static int smbb_charger_probe(struct platform_device *pdev) rc = devm_request_threaded_irq(&pdev->dev, irq, NULL, smbb_charger_irqs[i].handler, IRQF_ONESHOT, smbb_charger_irqs[i].name, chg); - if (rc) { - dev_err(&pdev->dev, "failed to request irq '%s'\n", - smbb_charger_irqs[i].name); + if (rc) return rc; - } } /* diff --git a/drivers/power/supply/qcom_smbx.c b/drivers/power/supply/qcom_smbx.c index bf2e2ccc454a..7bda5cd77a75 100644 --- a/drivers/power/supply/qcom_smbx.c +++ b/drivers/power/supply/qcom_smbx.c @@ -922,8 +922,7 @@ static int smb_init_irq(struct smb_chip *chip, int *irq, const char *name, rc = devm_request_threaded_irq(chip->dev, irqnum, NULL, handler, IRQF_ONESHOT, name, chip); if (rc < 0) - return dev_err_probe(chip->dev, rc, "Couldn't request irq %s\n", - name); + return rc; if (irq) *irq = irqnum; diff --git a/drivers/power/supply/rk817_charger.c b/drivers/power/supply/rk817_charger.c index 9436c6bbf51f..5558b182a1a6 100644 --- a/drivers/power/supply/rk817_charger.c +++ b/drivers/power/supply/rk817_charger.c @@ -1186,19 +1186,15 @@ static int rk817_charger_probe(struct platform_device *pdev) rk817_plug_in_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_in", charger); - if (ret) { - return dev_err_probe(&pdev->dev, ret, - "plug_in_irq request failed!\n"); - } + if (ret) + return ret; ret = devm_request_threaded_irq(charger->dev, plugout_irq, NULL, rk817_plug_out_isr, IRQF_TRIGGER_RISING | IRQF_ONESHOT, "rk817_plug_out", charger); - if (ret) { - return dev_err_probe(&pdev->dev, ret, - "plug_out_irq request failed!\n"); - } + if (ret) + return ret; ret = devm_delayed_work_autocancel(&pdev->dev, &charger->work, rk817_charging_monitor); diff --git a/drivers/power/supply/rn5t618_power.c b/drivers/power/supply/rn5t618_power.c index 40dec55a9f73..5963a55341ee 100644 --- a/drivers/power/supply/rn5t618_power.c +++ b/drivers/power/supply/rn5t618_power.c @@ -800,11 +800,8 @@ static int rn5t618_power_probe(struct platform_device *pdev) "rn5t618_power", &pdev->dev); - if (ret < 0) { - dev_err(&pdev->dev, "request IRQ:%d fail\n", - info->irq); + if (ret < 0) info->irq = -1; - } } return 0; diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c index 7045d2908148..313a7371211e 100644 --- a/drivers/power/supply/rt9455_charger.c +++ b/drivers/power/supply/rt9455_charger.c @@ -1582,6 +1582,19 @@ static const struct regmap_config rt9455_regmap_config = { .cache_type = REGCACHE_MAPLE, }; +static void rt9455_cancel_all_delayed_works(void *data) +{ + struct rt9455_info *info = data; + + /* + * Both pwr_rdy_work and batt_presence_work can queue + * max_charging_time_work, so cancel them first. + */ + cancel_delayed_work_sync(&info->pwr_rdy_work); + cancel_delayed_work_sync(&info->batt_presence_work); + cancel_delayed_work_sync(&info->max_charging_time_work); +} + static int rt9455_probe(struct i2c_client *client) { struct i2c_adapter *adapter = client->adapter; @@ -1672,14 +1685,16 @@ static int rt9455_probe(struct i2c_client *client) goto put_usb_notifier; } + ret = devm_add_action_or_reset(dev, rt9455_cancel_all_delayed_works, info); + if (ret) + goto put_usb_notifier; + ret = devm_request_threaded_irq(dev, client->irq, NULL, rt9455_irq_handler_thread, IRQF_TRIGGER_LOW | IRQF_ONESHOT, RT9455_DRIVER_NAME, info); - if (ret) { - dev_err(dev, "Failed to register IRQ handler\n"); + if (ret) goto put_usb_notifier; - } ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr); if (ret) { @@ -1712,10 +1727,6 @@ static void rt9455_remove(struct i2c_client *client) if (info->nb.notifier_call) usb_unregister_notifier(info->usb_phy, &info->nb); #endif - - cancel_delayed_work_sync(&info->pwr_rdy_work); - cancel_delayed_work_sync(&info->max_charging_time_work); - cancel_delayed_work_sync(&info->batt_presence_work); } static const struct i2c_device_id rt9455_i2c_id_table[] = { diff --git a/drivers/power/supply/rt9467-charger.c b/drivers/power/supply/rt9467-charger.c index de0471e54978..a9a83b263e18 100644 --- a/drivers/power/supply/rt9467-charger.c +++ b/drivers/power/supply/rt9467-charger.c @@ -1030,8 +1030,7 @@ static int rt9467_request_interrupt(struct rt9467_chg_data *data) ret = devm_request_threaded_irq(dev, virq, NULL, chg_irqs[i].handler, IRQF_ONESHOT, chg_irqs[i].name, data); if (ret) - return dev_err_probe(dev, ret, "Failed to request (%s) irq\n", - chg_irqs[i].name); + return ret; } return 0; diff --git a/drivers/power/supply/rt9471.c b/drivers/power/supply/rt9471.c index ca7d426849a7..b67f5abaa993 100644 --- a/drivers/power/supply/rt9471.c +++ b/drivers/power/supply/rt9471.c @@ -369,23 +369,28 @@ static int rt9471_charger_set_property(struct power_supply *psy, const union power_supply_propval *val) { struct rt9471_chip *chip = power_supply_get_drvdata(psy); - int value = val->intval; switch (psp) { case POWER_SUPPLY_PROP_STATUS: - return regmap_field_write(chip->rm_fields[F_CHG_EN], !!value); + return regmap_field_write(chip->rm_fields[F_CHG_EN], + !!val->intval); case POWER_SUPPLY_PROP_ONLINE: - return regmap_field_write(chip->rm_fields[F_HZ], !value); + return regmap_field_write(chip->rm_fields[F_HZ], !val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return rt9471_set_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, value); + return rt9471_set_value_by_field_range( + chip, F_ICHG_REG, RT9471_RANGE_ICHG, val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return rt9471_set_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, value); + return rt9471_set_value_by_field_range( + chip, F_VBAT_REG, RT9471_RANGE_VCHG, val->intval); case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return rt9471_set_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, value); + return rt9471_set_value_by_field_range( + chip, F_AICR, RT9471_RANGE_AICR, val->intval); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: - return rt9471_set_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, value); + return rt9471_set_value_by_field_range( + chip, F_MIVR, RT9471_RANGE_MIVR, val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: - return rt9471_set_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, value); + return rt9471_set_value_by_field_range( + chip, F_IPRE_CHG, RT9471_RANGE_IPRE, val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: return rt9471_set_ieoc(chip, val->intval); default: @@ -401,35 +406,39 @@ static int rt9471_charger_get_property(struct power_supply *psy, union power_supply_propval *val) { struct rt9471_chip *chip = power_supply_get_drvdata(psy); - int *pvalue = &val->intval; switch (psp) { case POWER_SUPPLY_PROP_STATUS: - return rt9471_get_status(chip, pvalue); + return rt9471_get_status(chip, &val->intval); case POWER_SUPPLY_PROP_ONLINE: - return rt9471_get_vbus_good(chip, pvalue); + return rt9471_get_vbus_good(chip, &val->intval); case POWER_SUPPLY_PROP_CURRENT_MAX: - return rt9471_get_usb_type_current(chip, pvalue); + return rt9471_get_usb_type_current(chip, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: - return rt9471_get_value_by_field_range(chip, F_ICHG_REG, RT9471_RANGE_ICHG, pvalue); + return rt9471_get_value_by_field_range( + chip, F_ICHG_REG, RT9471_RANGE_ICHG, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: - *pvalue = RT9471_ICHG_MAXUA; + val->intval = RT9471_ICHG_MAXUA; return 0; case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: - return rt9471_get_value_by_field_range(chip, F_VBAT_REG, RT9471_RANGE_VCHG, pvalue); + return rt9471_get_value_by_field_range( + chip, F_VBAT_REG, RT9471_RANGE_VCHG, &val->intval); case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: val->intval = RT9471_VCHG_MAXUV; return 0; case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: - return rt9471_get_value_by_field_range(chip, F_AICR, RT9471_RANGE_AICR, pvalue); + return rt9471_get_value_by_field_range( + chip, F_AICR, RT9471_RANGE_AICR, &val->intval); case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: - return rt9471_get_value_by_field_range(chip, F_MIVR, RT9471_RANGE_MIVR, pvalue); + return rt9471_get_value_by_field_range( + chip, F_MIVR, RT9471_RANGE_MIVR, &val->intval); case POWER_SUPPLY_PROP_USB_TYPE: - return rt9471_get_usb_type(chip, pvalue); + return rt9471_get_usb_type(chip, &val->intval); case POWER_SUPPLY_PROP_PRECHARGE_CURRENT: - return rt9471_get_value_by_field_range(chip, F_IPRE_CHG, RT9471_RANGE_IPRE, pvalue); + return rt9471_get_value_by_field_range( + chip, F_IPRE_CHG, RT9471_RANGE_IPRE, &val->intval); case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT: - return rt9471_get_ieoc(chip, pvalue); + return rt9471_get_ieoc(chip, &val->intval); case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = rt9471_model; return 0; @@ -583,8 +592,7 @@ static int rt9471_register_interrupts(struct rt9471_chip *chip) ret = devm_request_threaded_irq(dev, virq, NULL, curr->handler, IRQF_ONESHOT, curr->name, chip); if (ret) - return dev_err_probe(dev, ret, "Failed to register IRQ (%s)\n", - curr->name); + return ret; } return 0; diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 017ec06be766..501c8e069755 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -217,6 +217,7 @@ struct sbs_info { u32 flags; int technology; char strings[NR_STRING_BUFFERS][I2C_SMBUS_BLOCK_MAX + 1]; + char serial[5]; }; static char *sbs_get_string_buf(struct sbs_info *chip, @@ -821,18 +822,18 @@ static int sbs_get_battery_capacity(struct i2c_client *client, return 0; } -static char sbs_serial[5]; static int sbs_get_battery_serial_number(struct i2c_client *client, union power_supply_propval *val) { + struct sbs_info *chip = i2c_get_clientdata(client); int ret; ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr); if (ret < 0) return ret; - sprintf(sbs_serial, "%04x", ret); - val->strval = sbs_serial; + sprintf(chip->serial, "%04x", (u16)ret); + val->strval = chip->serial; return 0; } @@ -860,6 +861,14 @@ static int sbs_get_chemistry(struct sbs_info *chip, chip->technology = POWER_SUPPLY_TECHNOLOGY_NiCd; else if (!strncasecmp(chemistry, "NiMH", 4)) chip->technology = POWER_SUPPLY_TECHNOLOGY_NiMH; + else if (!strncasecmp(chemistry, "PbAc", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_PbAc; + else if (!strncasecmp(chemistry, "NiZn", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_NiZn; + else if (!strncasecmp(chemistry, "RAM", 3)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_RAM; + else if (!strncasecmp(chemistry, "ZnAr", 4)) + chip->technology = POWER_SUPPLY_TECHNOLOGY_ZnAr; else chip->technology = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; diff --git a/drivers/power/supply/sbs-charger.c b/drivers/power/supply/sbs-charger.c index a00c710601e8..e7b4e10dcfd5 100644 --- a/drivers/power/supply/sbs-charger.c +++ b/drivers/power/supply/sbs-charger.c @@ -217,7 +217,7 @@ static int sbs_probe(struct i2c_client *client) IRQF_TRIGGER_FALLING | IRQF_ONESHOT, dev_name(&client->dev), chip); if (ret) - return dev_err_probe(&client->dev, ret, "Failed to request irq\n"); + return ret; } else { ret = devm_delayed_work_autocancel(&client->dev, &chip->work, sbs_delayed_work); diff --git a/drivers/power/supply/sc2731_charger.c b/drivers/power/supply/sc2731_charger.c index 58b86fd78771..2b25e44da797 100644 --- a/drivers/power/supply/sc2731_charger.c +++ b/drivers/power/supply/sc2731_charger.c @@ -466,6 +466,7 @@ static int sc2731_charger_probe(struct platform_device *pdev) mutex_init(&info->lock); info->dev = &pdev->dev; INIT_WORK(&info->work, sc2731_charger_work); + platform_set_drvdata(pdev, info); info->regmap = dev_get_regmap(pdev->dev.parent, NULL); if (!info->regmap) { @@ -516,6 +517,7 @@ static void sc2731_charger_remove(struct platform_device *pdev) struct sc2731_charger_info *info = platform_get_drvdata(pdev); usb_unregister_notifier(info->usb_phy, &info->usb_notify); + cancel_work_sync(&info->work); } static const struct of_device_id sc2731_charger_of_match[] = { diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c index a7ed9de8a289..9af0017fa88e 100644 --- a/drivers/power/supply/sc27xx_fuel_gauge.c +++ b/drivers/power/supply/sc27xx_fuel_gauge.c @@ -1231,10 +1231,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) sc27xx_fgu_interrupt, IRQF_NO_SUSPEND | IRQF_ONESHOT, pdev->name, data); - if (ret) { - dev_err(data->dev, "failed to request fgu IRQ\n"); + if (ret) return ret; - } irq = gpiod_to_irq(data->gpiod); if (irq < 0) { @@ -1247,10 +1245,8 @@ static int sc27xx_fgu_probe(struct platform_device *pdev) IRQF_ONESHOT | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, pdev->name, data); - if (ret) { - dev_err(dev, "failed to request IRQ\n"); + if (ret) return ret; - } return 0; } diff --git a/drivers/power/supply/sgm41542_charger.c b/drivers/power/supply/sgm41542_charger.c new file mode 100644 index 000000000000..8880962528d6 --- /dev/null +++ b/drivers/power/supply/sgm41542_charger.c @@ -0,0 +1,1127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Charger driver for SGM4154x + * + * Copyright (c) 2026 Rockchip Electronics Co., Ltd. + * + * Author: Xu Shengfei + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SGM4154X_MANUFACTURER "SGMICRO" +#define SGM4154X_NAME "sgm41542" + +#define SGM4154X_CHRG_CTRL_0 0x00 +#define SGM4154X_HIZ_EN BIT(7) +#define SGM4154X_IINDPM_I_MASK GENMASK(4, 0) +#define SGM4154X_IINDPM_I_MIN_UA 100000 +#define SGM4154X_IINDPM_I_MAX_UA 3800000 +#define SGM4154X_IINDPM_STEP_UA 100000 +#define SGM4154X_IINDPM_DEF_UA 2400000 + +#define SGM4154X_CHRG_CTRL_1 0x01 +#define SGM4154X_WDT_RST BIT(6) +#define SGM4154X_OTG_EN BIT(5) +#define SGM4154X_CHRG_EN BIT(4) + +#define SGM4154X_CHRG_CTRL_2 0x02 +#define SGM4154X_BOOST_LIM BIT(7) +#define SGM4154X_ICHRG_CUR_MASK GENMASK(5, 0) +#define SGM4154X_ICHRG_I_STEP_UA 60000 +#define SGM4154X_ICHRG_I_MIN_UA 0 +#define SGM4154X_ICHRG_I_MAX_UA 3780000 +#define SGM4154X_ICHRG_I_DEF_UA 2040000 + +#define SGM4154X_CHRG_CTRL_3 0x03 +#define SGM4154X_PRECHRG_CUR_MASK GENMASK(7, 4) +#define SGM4154X_PRECHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_PRECHRG_I_MIN_UA 60000 +#define SGM4154X_PRECHRG_I_MAX_UA 780000 +#define SGM4154X_PRECHRG_I_DEF_UA 180000 +#define SGM4154X_TERMCHRG_CUR_MASK GENMASK(3, 0) +#define SGM4154X_TERMCHRG_CURRENT_STEP_UA 60000 +#define SGM4154X_TERMCHRG_I_MIN_UA 60000 +#define SGM4154X_TERMCHRG_I_MAX_UA 960000 +#define SGM4154X_TERMCHRG_I_DEF_UA 180000 + +#define SGM4154X_CHRG_CTRL_4 0x04 +#define SGM4154X_VREG_V_MASK GENMASK(7, 3) +#define SGM4154X_VREG_V_MAX_UV 4624000 +#define SGM4154X_VREG_V_MIN_UV 3856000 +#define SGM4154X_VREG_V_DEF_UV 4208000 +#define SGM4154X_VREG_V_STEP_UV 32000 +#define SGM4154X_VRECHARGE BIT(0) +#define SGM4154X_VRECHRG_STEP_UV 100000 +#define SGM4154X_VRECHRG_OFFSET_UV 100000 + +#define SGM4154X_CHRG_CTRL_5 0x05 +#define SGM4154X_TERM_EN BIT(7) +#define SGM4154X_WDT_TIMER_MASK GENMASK(5, 4) + +#define SGM4154X_CHRG_CTRL_6 0x06 +#define SGM4154X_VAC_OVP_MASK GENMASK(7, 6) +#define SGM4154X_OVP_14V (BIT(7) | BIT(6)) +#define SGM4154X_OVP_10_5V BIT(7) +#define SGM4154X_OVP_6_5V BIT(6) +#define SGM4154X_OVP_5_5V 0 +#define SGM4154X_OVP_DEFAULT SGM4154X_OVP_14V +#define SGM4154X_BOOSTV GENMASK(5, 4) +#define SGM4154X_VINDPM_V_MASK GENMASK(3, 0) +#define SGM4154X_VINDPM_V_MIN_UV 3900000 +#define SGM4154X_VINDPM_V_MAX_UV 12000000 +#define SGM4154X_VINDPM_STEP_UV 100000 +#define SGM4154X_VINDPM_DEF_UV 4500000 + +#define SGM4154X_CHRG_CTRL_7 0x07 + +#define SGM4154X_CHRG_STAT 0x08 +#define SGM4154X_VBUS_STAT_MASK GENMASK(7, 5) +#define SGM4154X_OTG_MODE (BIT(7) | BIT(6) | BIT(5)) +#define SGM4154X_NON_STANDARD (BIT(7) | BIT(6)) +#define SGM4154X_UNKNOWN (BIT(7) | BIT(5)) +#define SGM4154X_USB_DCP (BIT(6) | BIT(5)) +#define SGM4154X_USB_CDP BIT(6) +#define SGM4154X_USB_SDP BIT(5) +#define SGM4154X_NOT_CHRGING 0 +#define SGM4154X_CHG_STAT_MASK GENMASK(4, 3) +#define SGM4154X_TERM_CHRG (BIT(4) | BIT(3)) +#define SGM4154X_FAST_CHRG BIT(4) +#define SGM4154X_PRECHRG BIT(3) +#define SGM4154X_PG_STAT BIT(2) +#define SGM4154X_THERM_STAT BIT(1) +#define SGM4154X_VSYS_STAT BIT(0) + +#define SGM4154X_CHRG_FAULT 0x09 +#define SGM4154X_TEMP_MASK GENMASK(2, 0) +#define SGM4154X_TEMP_HOT (BIT(2) | BIT(1)) +#define SGM4154X_TEMP_COLD (BIT(2) | BIT(0)) +#define SGM4154X_TEMP_COOL (BIT(1) | BIT(0)) +#define SGM4154X_TEMP_WARM BIT(1) +#define SGM4154X_TEMP_NORMAL BIT(0) + +#define SGM4154X_CHRG_CTRL_A 0x0a +#define SGM4154X_VBUS_GOOD BIT(7) +#define SGM4154X_VINDPM_INT_MASK BIT(1) +#define SGM4154X_IINDPM_INT_MASK BIT(0) + +#define SGM4154X_CHRG_CTRL_B 0x0b +#define SGM4154X_PN_ID (BIT(6) | BIT(5) | BIT(3)) +#define SGM4154X_PN_MASK GENMASK(6, 3) + +#define SGM4154X_CHRG_CTRL_C 0x0c + +#define SGM4154X_CHRG_CTRL_D 0x0d +#define SGM4154X_JEITA_EN BIT(0) + +#define SGM4154X_INPUT_DET 0x0e +#define SGM4154X_DPDM_ONGOING BIT(7) + +#define SGM4154X_CHRG_CTRL_F 0x0f +#define SGM4154X_VINDPM_OS_MASK GENMASK(1, 0) + +#define SGM4154X_DEFAULT_INPUT_CUR (500 * 1000) + +struct sgm4154x_init_data { + int ilim; /* input current limit */ + int vlim; /* minimum system voltage limit */ + int iterm; /* termination current */ + int iprechg; /* precharge current */ + int max_ichg; /* maximum charge current */ + int max_vreg; /* maximum charge voltage */ +}; + +struct sgm4154x_state { + bool vsys_stat; + bool therm_stat; + bool online; + u8 chrg_stat; + bool chrg_en; + bool vbus_gd; + u8 chrg_type; + u8 health; + u8 chrg_fault; + u8 ntc_fault; +}; + +struct sgm4154x_device { + struct device *dev; + struct power_supply *charger; + struct regmap *regmap; + struct sgm4154x_init_data init_data; + struct sgm4154x_state state; + struct regulator_dev *otg_rdev; + struct mutex lock; + bool watchdog_enable; + struct workqueue_struct *sgm_monitor_wq; + struct delayed_work sgm_delay_work; +}; + +enum SGM4154X_VINDPM_OS { + VINDPM_OS_3900MV, + VINDPM_OS_5900MV, + VINDPM_OS_7500MV, + VINDPM_OS_10500MV, +}; + +enum sgm4154x_wdt_values { + SGM4154X_WDT_TIMER_DISABLE = 0, + SGM4154X_WDT_TIMER_40S = 1, + SGM4154X_WDT_TIMER_80S = 2, + SGM4154X_WDT_TIMER_160S = 3, +}; + +static int sgm4154x_set_term_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_TERMCHRG_I_MIN_UA, SGM4154X_TERMCHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_TERMCHRG_I_MIN_UA) / SGM4154X_TERMCHRG_CURRENT_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_TERMCHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set term current error!\n"); + + return ret; +} + +static int sgm4154x_set_prechrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_PRECHRG_I_MIN_UA, SGM4154X_PRECHRG_I_MAX_UA); + reg_val = (cur_ua - SGM4154X_PRECHRG_I_MIN_UA) / SGM4154X_PRECHRG_CURRENT_STEP_UA; + + reg_val = FIELD_PREP(SGM4154X_PRECHRG_CUR_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_3, + SGM4154X_PRECHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set precharge current error!\n"); + + return ret; +} + +static int sgm4154x_set_ichrg_curr(struct sgm4154x_device *sgm, int cur_ua) +{ + int reg_val; + int ret; + + cur_ua = clamp(cur_ua, SGM4154X_ICHRG_I_MIN_UA, sgm->init_data.max_ichg); + reg_val = cur_ua / SGM4154X_ICHRG_I_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_2, + SGM4154X_ICHRG_CUR_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set icharge current error!\n"); + + return ret; +} + +static int sgm4154x_get_ichrg_curr(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_2, ®); + if (ret) { + dev_err(sgm->dev, "get charge current error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_ICHRG_CUR_MASK, reg); + + return val * SGM4154X_ICHRG_I_STEP_UA; +} + +static int sgm4154x_set_chrg_volt(struct sgm4154x_device *sgm, int chrg_volt) +{ + int reg_val; + int ret; + + /* + * Note that the value of 0x01111 represents a "special value" + * corresponding to 4352000uV instead of the expected 4336000uV, + * per the datasheet. All other values are as expected. So not + * only do we need to clamp between max and min values, but + * also clamp anything below 4352000uv to 4304000uv to prevent + * overcharging. + */ + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, sgm->init_data.max_vreg); + if (chrg_volt < 4352000) + chrg_volt = clamp(chrg_volt, SGM4154X_VREG_V_MIN_UV, 4304000); + reg_val = (chrg_volt - SGM4154X_VREG_V_MIN_UV) / SGM4154X_VREG_V_STEP_UV; + reg_val = FIELD_PREP(SGM4154X_VREG_V_MASK, reg_val); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VREG_V_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set charge voltage error!\n"); + + return ret; +} + +static int sgm4154x_get_chrg_volt(struct sgm4154x_device *sgm) +{ + u32 reg; + int ret, val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_4, ®); + if (ret) { + dev_err(sgm->dev, "get charge voltage error!\n"); + return ret; + } + + val = FIELD_GET(SGM4154X_VREG_V_MASK, reg); + + /* + * 0x01111 is a special value meaning 4352000uV, all other + * values are as expected based on the offset and step values. + */ + if (val == 0x0f) + return 4352000; + + return val * SGM4154X_VREG_V_STEP_UV + SGM4154X_VREG_V_MIN_UV; +} + +static int sgm4154x_set_input_volt_lim(struct sgm4154x_device *sgm, + unsigned int vindpm) +{ + enum SGM4154X_VINDPM_OS os_val; + unsigned int offset; + u8 reg_val; + int ret; + + + if (vindpm < SGM4154X_VINDPM_V_MIN_UV || + vindpm > SGM4154X_VINDPM_V_MAX_UV) { + dev_err(sgm->dev, "input voltage limit %u outside range\n", vindpm); + return -EINVAL; + } + + /* + * Supported ranges per the datasheet are as follows: + * 3.9v - 5.4v with a 3.9v offset + * 5.9v - 7.4v with a 5.9v offset + * 7.5v - 9.0v with a 7.5v offset + * 10.5v - 12.0v with a 10.5v offset + * Step size is a constant 100mv + */ + if (vindpm < 5900000) { + offset = 3900000; + vindpm = clamp(vindpm, offset, 5400000); + os_val = VINDPM_OS_3900MV; + } else if (vindpm < 7500000) { + offset = 5900000; + vindpm = clamp(vindpm, offset, 7400000); + os_val = VINDPM_OS_5900MV; + } else if (vindpm < 10500000) { + offset = 7500000; + vindpm = clamp(vindpm, offset, 9000000); + os_val = VINDPM_OS_7500MV; + } else { + offset = 10500000; + vindpm = clamp(vindpm, offset, 12000000); + os_val = VINDPM_OS_10500MV; + } + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_F, + SGM4154X_VINDPM_OS_MASK, + os_val); + if (ret) { + dev_err(sgm->dev, "set vin dpm error!\n"); + return ret; + } + + reg_val = (vindpm - offset) / SGM4154X_VINDPM_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, SGM4154X_CHRG_CTRL_6, + SGM4154X_VINDPM_V_MASK, reg_val); + if (ret) + dev_err(sgm->dev, "input voltage error!\n"); + + return ret; +} + +static int sgm4154x_set_input_curr_lim(struct sgm4154x_device *sgm, int iindpm) +{ + int reg_val; + int ret; + + if (iindpm < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Protect against a timing-issue edge case if a sysfs write occurs in the middle + * of a probe (very unlikely). + */ + if (sgm->init_data.ilim < SGM4154X_IINDPM_I_MIN_UA) + return -EINVAL; + + /* + * Per the datasheet, values between 100000uA and 3100000uA work + * as expected with the register defined as having a step of + * 100000 and a min/max of 100000 (0x00) through 3100000 (0x1e). + * The register value of 0x1f however corresponds to 3800000uA not + * 3200000uA as one would expect. + */ + if ((iindpm > SGM4154X_IINDPM_I_MAX_UA) || (iindpm > sgm->init_data.ilim)) + iindpm = min(SGM4154X_IINDPM_I_MAX_UA, sgm->init_data.ilim); + + if (iindpm > 3100000 && iindpm < SGM4154X_IINDPM_I_MAX_UA) + iindpm = 3100000; + + if (iindpm == SGM4154X_IINDPM_I_MAX_UA) + reg_val = 0x1f; + else + reg_val = (iindpm - SGM4154X_IINDPM_I_MIN_UA) / SGM4154X_IINDPM_STEP_UA; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_0, + SGM4154X_IINDPM_I_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set input current limit error!\n"); + + return ret; +} + +static int sgm4154x_get_input_curr_lim(struct sgm4154x_device *sgm) +{ + int ret; + int ilim; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_0, &ilim); + if (ret) { + dev_err(sgm->dev, "get input current limit error!\n"); + return ret; + } + + ilim &= SGM4154X_IINDPM_I_MASK; + + /* Max value is not 3200000uA as expected but is 3800000uA */ + if (ilim == SGM4154X_IINDPM_I_MASK) + return SGM4154X_IINDPM_I_MAX_UA; + + ilim = ilim * SGM4154X_IINDPM_STEP_UA + SGM4154X_IINDPM_I_MIN_UA; + + return ilim; +} + +static int sgm4154x_watchdog_timer_reset(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_WDT_RST, + SGM4154X_WDT_RST); + + if (ret) + dev_err(sgm->dev, "set watchdog timer error!\n"); + + return ret; +} + +static int sgm4154x_set_watchdog_timer(struct sgm4154x_device *sgm, u8 time) +{ + int ret; + + if (time > SGM4154X_WDT_TIMER_160S) + return -EINVAL; + + time = FIELD_PREP(SGM4154X_WDT_TIMER_MASK, time); + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_5, + SGM4154X_WDT_TIMER_MASK, + time); + + if (ret) { + dev_err(sgm->dev, "set watchdog timer error!\n"); + return ret; + } + + if (time) { + if (!sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); + sgm->watchdog_enable = true; + } else { + sgm->watchdog_enable = false; + sgm4154x_watchdog_timer_reset(sgm); + } + + return ret; +} + +static int sgm4154x_enable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + SGM4154X_CHRG_EN); + if (ret) + dev_err(sgm->dev, "enable charger error!\n"); + + return ret; +} + +static int sgm4154x_disable_charger(struct sgm4154x_device *sgm) +{ + int ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_1, + SGM4154X_CHRG_EN, + 0); + if (ret) + dev_err(sgm->dev, "disable charger error!\n"); + + return ret; +} + +static int sgm4154x_set_vac_ovp(struct sgm4154x_device *sgm) +{ + int reg_val; + int ret; + + reg_val = SGM4154X_OVP_DEFAULT & SGM4154X_VAC_OVP_MASK; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_6, + SGM4154X_VAC_OVP_MASK, + reg_val); + if (ret) + dev_err(sgm->dev, "set vac ovp error!\n"); + + return ret; +} + +static int sgm4154x_set_recharge_volt_ua(struct sgm4154x_device *sgm, int recharge_volt) +{ + int reg_val; + int ret; + + reg_val = (recharge_volt - SGM4154X_VRECHRG_OFFSET_UV) / SGM4154X_VRECHRG_STEP_UV; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_4, + SGM4154X_VRECHARGE, + reg_val); + if (ret) + dev_err(sgm->dev, "set recharger error!\n"); + + return ret; +} + +static int sgm4154x_get_state(struct sgm4154x_device *sgm, + struct sgm4154x_state *state) +{ + unsigned int reg; + int ret; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_STAT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_STAT fail\n"); + return ret; + } + state->chrg_type = reg & SGM4154X_VBUS_STAT_MASK; + state->chrg_stat = reg & SGM4154X_CHG_STAT_MASK; + state->online = !!(reg & SGM4154X_PG_STAT); + state->therm_stat = !!(reg & SGM4154X_THERM_STAT); + state->vsys_stat = !!(reg & SGM4154X_VSYS_STAT); + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_FAULT, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_FAULT fail\n"); + return ret; + } + state->chrg_fault = reg; + state->ntc_fault = reg & SGM4154X_TEMP_MASK; + state->health = state->ntc_fault; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_A, ®); + if (ret) { + dev_err(sgm->dev, "read SGM4154X_CHRG_CTRL_A fail\n"); + return ret; + } + state->vbus_gd = !!(reg & SGM4154X_VBUS_GOOD); + + return ret; +} + +static int sgm4154x_property_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_ONLINE: + return true; + default: + return false; + } +} + +static int sgm4154x_charger_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + int ret = -EINVAL; + + guard(mutex)(&sgm->lock); + + switch (prop) { + case POWER_SUPPLY_PROP_ONLINE: + if (val->intval) { + ret = sgm4154x_enable_charger(sgm); + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + } else { + sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + ret = sgm4154x_disable_charger(sgm); + } + break; + + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + ret = sgm4154x_set_input_curr_lim(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = sgm4154x_set_ichrg_curr(sgm, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = sgm4154x_set_chrg_volt(sgm, val->intval); + break; + + default: + return -EINVAL; + } + + return ret; +} + +static int sgm4154x_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct sgm4154x_state state; + int ret; + + scoped_guard(mutex, &sgm->lock) { + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return ret; + } + sgm->state = state; + } + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (!state.chrg_type || (state.chrg_type == SGM4154X_OTG_MODE)) + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + else if (!state.chrg_stat) + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + else if (state.chrg_stat == SGM4154X_TERM_CHRG) + val->intval = POWER_SUPPLY_STATUS_FULL; + else + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + switch (state.chrg_stat) { + case SGM4154X_PRECHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_FAST_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST; + break; + case SGM4154X_TERM_CHRG: + val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + break; + case SGM4154X_NOT_CHRGING: + val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE; + break; + default: + val->intval = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = SGM4154X_MANUFACTURER; + break; + + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = SGM4154X_NAME; + break; + + case POWER_SUPPLY_PROP_ONLINE: + val->intval = state.online; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = state.vbus_gd; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + val->intval = sgm4154x_get_chrg_volt(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + val->intval = sgm4154x_get_ichrg_curr(sgm); + if (val->intval < 0) + return -EINVAL; + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT: + val->intval = sgm->init_data.vlim; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT: + val->intval = sgm4154x_get_input_curr_lim(sgm); + if (val->intval < 0) + return -EINVAL; + break; + default: + return -EINVAL; + } + + return ret; +} + +static irqreturn_t sgm4154x_irq_handler_thread(int irq, void *private) +{ + struct sgm4154x_device *sgm = private; + struct sgm4154x_state oldstate, state; + int ret; + + guard(mutex)(&sgm->lock); + + oldstate = sgm->state; + + ret = sgm4154x_get_state(sgm, &state); + if (ret) { + dev_err(sgm->dev, "get state error!\n"); + return IRQ_NONE; + } + + sgm->state = state; + + if (state.vbus_gd && !oldstate.vbus_gd) { + if (sgm->init_data.ilim >= SGM4154X_DEFAULT_INPUT_CUR) { + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) { + dev_err(sgm->dev, "set input current error!\n"); + /* + * Reading IRQ clears interrupt, so return handled + * even if error. + */ + } + } + } + + power_supply_changed(sgm->charger); + + return IRQ_HANDLED; +} + +static int sgm4154x_hw_init(struct power_supply *psy) +{ + struct sgm4154x_device *sgm = power_supply_get_drvdata(psy); + struct power_supply_battery_info *bat_info; + int ret; + u32 val; + + /* + * If unable to read devicetree info, use default/reset + * values from hardware. If the devicetree info has data out + * of range for any of the values, use the default value. + */ + sgm->init_data.iprechg = SGM4154X_PRECHRG_I_DEF_UA; + sgm->init_data.iterm = SGM4154X_TERMCHRG_I_DEF_UA; + sgm->init_data.max_ichg = SGM4154X_ICHRG_I_DEF_UA; + sgm->init_data.max_vreg = SGM4154X_VREG_V_DEF_UV; + sgm->init_data.vlim = SGM4154X_VINDPM_DEF_UV; + sgm->init_data.ilim = SGM4154X_IINDPM_DEF_UA; + + ret = power_supply_get_battery_info(psy, &bat_info); + if (ret) + dev_warn(sgm->dev, "sgm4154x: cannot read battery info\n"); + else { + if ((bat_info->constant_charge_current_max_ua >= SGM4154X_ICHRG_I_MIN_UA) && + (bat_info->constant_charge_current_max_ua <= SGM4154X_ICHRG_I_MAX_UA)) + sgm->init_data.max_ichg = bat_info->constant_charge_current_max_ua; + if ((bat_info->constant_charge_voltage_max_uv >= SGM4154X_VREG_V_MIN_UV) && + (bat_info->constant_charge_voltage_max_uv <= SGM4154X_VREG_V_MAX_UV)) + sgm->init_data.max_vreg = bat_info->constant_charge_voltage_max_uv; + if ((bat_info->charge_term_current_ua >= SGM4154X_TERMCHRG_I_MIN_UA) && + (bat_info->charge_term_current_ua <= SGM4154X_TERMCHRG_I_MAX_UA)) + sgm->init_data.iterm = bat_info->charge_term_current_ua; + if ((bat_info->precharge_current_ua >= SGM4154X_PRECHRG_I_MIN_UA) && + (bat_info->precharge_current_ua <= SGM4154X_PRECHRG_I_MAX_UA)) + sgm->init_data.iprechg = bat_info->precharge_current_ua; + + power_supply_put_battery_info(psy, bat_info); + } + + ret = device_property_read_u32(sgm->dev, + "input-voltage-limit-microvolt", + &val); + if (!ret) + sgm->init_data.vlim = clamp(val, SGM4154X_VINDPM_V_MIN_UV, + SGM4154X_VINDPM_V_MAX_UV); + + ret = device_property_read_u32(sgm->dev, + "input-current-limit-microamp", + &val); + if (!ret) + sgm->init_data.ilim = clamp(val, SGM4154X_IINDPM_I_MIN_UA, + SGM4154X_IINDPM_I_MAX_UA); + + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + + ret = sgm4154x_set_prechrg_curr(sgm, sgm->init_data.iprechg); + if (ret) + return ret; + + ret = sgm4154x_set_chrg_volt(sgm, sgm->init_data.max_vreg); + if (ret) + return ret; + + ret = sgm4154x_set_term_curr(sgm, sgm->init_data.iterm); + if (ret) + return ret; + + ret = sgm4154x_set_ichrg_curr(sgm, sgm->init_data.max_ichg); + if (ret) + return ret; + + ret = sgm4154x_set_input_volt_lim(sgm, sgm->init_data.vlim); + if (ret) + return ret; + + ret = sgm4154x_set_input_curr_lim(sgm, sgm->init_data.ilim); + if (ret) + return ret; + + ret = sgm4154x_set_vac_ovp(sgm); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_D, + SGM4154X_JEITA_EN, + 0); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_IINDPM_INT_MASK, + SGM4154X_IINDPM_INT_MASK); + if (ret) + return ret; + + ret = regmap_update_bits(sgm->regmap, + SGM4154X_CHRG_CTRL_A, + SGM4154X_VINDPM_INT_MASK, + SGM4154X_VINDPM_INT_MASK); + if (ret) + return ret; + + /* + * Recharge microvolt set to 200000 by BSP driver instead of hardware + * default value of 100000. + */ + ret = sgm4154x_set_recharge_volt_ua(sgm, 200000); + + return ret; +} + +static enum power_supply_property sgm4154x_power_supply_props[] = { + POWER_SUPPLY_PROP_MANUFACTURER, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_LIMIT, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_PRESENT +}; + +static struct power_supply_desc sgm4154x_power_supply_desc = { + .name = "sgm4154x-charger", + .type = POWER_SUPPLY_TYPE_USB, + .init = sgm4154x_hw_init, + .properties = sgm4154x_power_supply_props, + .num_properties = ARRAY_SIZE(sgm4154x_power_supply_props), + .get_property = sgm4154x_charger_get_property, + .set_property = sgm4154x_charger_set_property, + .property_is_writeable = sgm4154x_property_is_writeable, +}; + +static const struct regmap_config sgm4154x_regmap_config = { + .reg_bits = 8, + .val_bits = 8, + .max_register = SGM4154X_CHRG_CTRL_F, + .cache_type = REGCACHE_NONE, +}; + +static const u32 sgm4154x_chg_otg_cur_ua[] = { + 1200000, 2000000, +}; + +static const struct regulator_ops sgm4154x_vbus_ops = { + .list_voltage = regulator_list_voltage_linear, + .enable = regulator_enable_regmap, + .disable = regulator_disable_regmap, + .is_enabled = regulator_is_enabled_regmap, + .set_voltage_sel = regulator_set_voltage_sel_regmap, + .get_voltage_sel = regulator_get_voltage_sel_regmap, + .set_current_limit = regulator_set_current_limit_regmap, + .get_current_limit = regulator_get_current_limit_regmap, +}; + +static const struct regulator_desc sgm4154x_otg_rdesc = { + .of_match = "otg-vbus", + .name = "otg-vbus", + .regulators_node = of_match_ptr("regulators"), + .ops = &sgm4154x_vbus_ops, + .owner = THIS_MODULE, + .type = REGULATOR_VOLTAGE, + .min_uV = 4850000, + .uV_step = 150000, + .n_voltages = 4, + .vsel_reg = SGM4154X_CHRG_CTRL_6, + .vsel_mask = SGM4154X_BOOSTV, + .enable_reg = SGM4154X_CHRG_CTRL_1, + .enable_mask = SGM4154X_OTG_EN, + .curr_table = sgm4154x_chg_otg_cur_ua, + .n_current_limits = ARRAY_SIZE(sgm4154x_chg_otg_cur_ua), + .csel_reg = SGM4154X_CHRG_CTRL_2, + .csel_mask = SGM4154X_BOOST_LIM, +}; + +static int sgm4154x_vbus_regulator_register(struct sgm4154x_device *sgm) +{ + struct regulator_config config = { + .dev = sgm->dev, + .regmap = sgm->regmap, + .driver_data = sgm, + }; + + sgm->otg_rdev = devm_regulator_register(sgm->dev, + &sgm4154x_otg_rdesc, + &config); + + return PTR_ERR_OR_ZERO(sgm->otg_rdev); +} + +static int sgm4154x_hw_chipid_detect(struct sgm4154x_device *sgm) +{ + int ret; + int val; + + ret = regmap_read(sgm->regmap, SGM4154X_CHRG_CTRL_B, &val); + if (ret) + return ret; + + if ((val & SGM4154X_PN_MASK) != SGM4154X_PN_ID) + dev_warn(sgm->dev, "sgm4154x device ID mismatch\n"); + + return 0; +} + +static void sgm_charger_work(struct work_struct *work) +{ + struct sgm4154x_device *sgm = + container_of(work, + struct sgm4154x_device, + sgm_delay_work.work); + + sgm4154x_watchdog_timer_reset(sgm); + if (sgm->watchdog_enable) + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, + msecs_to_jiffies(1000 * 5)); +} + +static void sgm4154x_disable_irq_wake(void *data) +{ + struct sgm4154x_device *sgm = data; + struct i2c_client *client = to_i2c_client(sgm->dev); + + disable_irq_wake(client->irq); +} + +static int sgm4154x_probe(struct i2c_client *client) +{ + struct power_supply_config psy_cfg = {}; + struct device *dev = &client->dev; + struct sgm4154x_device *sgm; + int ret; + + sgm = devm_kzalloc(dev, sizeof(*sgm), GFP_KERNEL); + if (!sgm) + return -ENOMEM; + + sgm->dev = dev; + + sgm->regmap = devm_regmap_init_i2c(client, &sgm4154x_regmap_config); + if (IS_ERR(sgm->regmap)) + return dev_err_probe(dev, PTR_ERR(sgm->regmap), + "Failed to allocate register map\n"); + + i2c_set_clientdata(client, sgm); + + ret = devm_mutex_init(dev, &sgm->lock); + if (ret) + return ret; + + ret = sgm4154x_hw_chipid_detect(sgm); + if (ret) + return dev_err_probe(dev, ret, "Unable to read HW ID\n"); + + devm_device_init_wakeup(dev); + + sgm->sgm_monitor_wq = devm_alloc_ordered_workqueue(dev, "sgm-monitor-wq", + WQ_MEM_RECLAIM | WQ_FREEZABLE); + if (!sgm->sgm_monitor_wq) + return -EINVAL; + + ret = devm_delayed_work_autocancel(dev, &sgm->sgm_delay_work, + sgm_charger_work); + if (ret) + return dev_err_probe(dev, ret, "Unable to register delayed work\n"); + + psy_cfg.drv_data = sgm; + psy_cfg.fwnode = dev_fwnode(dev); + + sgm->charger = devm_power_supply_register(sgm->dev, + &sgm4154x_power_supply_desc, + &psy_cfg); + if (IS_ERR(sgm->charger)) + return dev_err_probe(dev, PTR_ERR(sgm->charger), + "Failed to register power supply\n"); + + if (client->irq) { + ret = devm_request_threaded_irq(dev, client->irq, NULL, + sgm4154x_irq_handler_thread, + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "sgm41542-irq", sgm); + if (ret) + return ret; + + ret = enable_irq_wake(client->irq); + if (!ret) { + ret = devm_add_action_or_reset(dev, sgm4154x_disable_irq_wake, sgm); + if (ret) + return ret; + } + } + + ret = sgm4154x_vbus_regulator_register(sgm); + if (ret) { + return dev_err_probe(dev, ret, + "Unable to register VBUS regulator\n"); + } + + return 0; +} + +static int __maybe_unused sgm4154x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + bool watchdog = sgm->watchdog_enable; + int ret; + + /* + * Disable watchdog during suspend and stop delayed work. When + * delayed work is restarted after resume watchdog will be + * re-enabled if it was previously enabled. Retain the current + * state of the watchdog timer though, so it can be re-enabled + * if necessary by the work queue once resume is triggered. + */ + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_DISABLE); + if (ret) + return ret; + cancel_delayed_work_sync(&sgm->sgm_delay_work); + sgm->watchdog_enable = watchdog; + return 0; +} + +static int __maybe_unused sgm4154x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct sgm4154x_device *sgm = i2c_get_clientdata(client); + int ret; + + if (sgm->watchdog_enable) { + ret = sgm4154x_set_watchdog_timer(sgm, SGM4154X_WDT_TIMER_40S); + if (ret) + return ret; + } + + queue_delayed_work(sgm->sgm_monitor_wq, + &sgm->sgm_delay_work, 0); + return 0; +} + +static SIMPLE_DEV_PM_OPS(sgm4154x_pm_ops, sgm4154x_suspend, sgm4154x_resume); + +static const struct i2c_device_id sgm4154x_i2c_ids[] = { + { "sgm41542" }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, sgm4154x_i2c_ids); + +static const struct of_device_id sgm4154x_of_match[] = { + { .compatible = "sgmicro,sgm41542", }, + { }, +}; +MODULE_DEVICE_TABLE(of, sgm4154x_of_match); + +static struct i2c_driver sgm4154x_driver = { + .driver = { + .name = "sgm4154x-charger", + .of_match_table = sgm4154x_of_match, + .pm = &sgm4154x_pm_ops, + }, + .probe = sgm4154x_probe, + .id_table = sgm4154x_i2c_ids, +}; + +module_i2c_driver(sgm4154x_driver); + +MODULE_AUTHOR("Xu Shengfei "); +MODULE_DESCRIPTION("sgm4154x charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/surface-rt-ec.c b/drivers/power/supply/surface-rt-ec.c index a728ae0db858..da57b0a1a495 100644 --- a/drivers/power/supply/surface-rt-ec.c +++ b/drivers/power/supply/surface-rt-ec.c @@ -337,7 +337,7 @@ static int srt_ec_probe(struct i2c_client *client) ret = devm_request_threaded_irq(dev, client->irq, NULL, srt_psy_detect_irq, IRQF_ONESHOT, client->name, srt); if (ret < 0) - return dev_err_probe(dev, ret, "failed to request interrupt\n"); + return ret; ret = devm_delayed_work_autocancel(dev, &srt->poll_work, srt_bat_poll_work); if (ret < 0) diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c index d010f013af8c..36340c56d272 100644 --- a/drivers/power/supply/tps65090-charger.c +++ b/drivers/power/supply/tps65090-charger.c @@ -302,12 +302,8 @@ static int tps65090_charger_probe(struct platform_device *pdev) if (irq != -ENXIO) { ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, tps65090_charger_isr, IRQF_ONESHOT, "tps65090-charger", cdata); - if (ret) { - dev_err(cdata->dev, - "Unable to register irq %d err %d\n", irq, - ret); + if (ret) return ret; - } } else { cdata->poll_task = kthread_run(tps65090_charger_poll_task, cdata, "ktps65090charger"); diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 6af17ce0b204..cd00a3f372b3 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -239,12 +239,8 @@ static int tps65217_charger_probe(struct platform_device *pdev) tps65217_charger_irq, IRQF_SHARED, "tps65217-charger", charger); - if (ret) { - dev_err(charger->dev, - "Unable to register irq %d err %d\n", irq[i], - ret); + if (ret) return ret; - } /* Check current state */ tps65217_charger_irq(-1, charger); diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c index 04216b2bfb6c..c46b22ab8d99 100644 --- a/drivers/power/supply/twl4030_charger.c +++ b/drivers/power/supply/twl4030_charger.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -1002,8 +1003,15 @@ static int twl4030_bci_probe(struct platform_device *pdev) platform_set_drvdata(pdev, bci); - INIT_WORK(&bci->work, twl4030_bci_usb_work); - INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker); + ret = devm_delayed_work_autocancel(&pdev->dev, &bci->current_worker, + twl4030_current_worker); + if (ret) + return ret; + + ret = devm_work_autocancel(&pdev->dev, &bci->work, + twl4030_bci_usb_work); + if (ret) + return ret; bci->channel_vac = devm_iio_channel_get(&pdev->dev, "vac"); if (IS_ERR(bci->channel_vac)) { @@ -1055,19 +1063,13 @@ static int twl4030_bci_probe(struct platform_device *pdev) ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL, twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name, bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_chg, ret); + if (ret < 0) return ret; - } ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL, twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci); - if (ret < 0) { - dev_err(&pdev->dev, "could not request irq %d, status %d\n", - bci->irq_bci, ret); + if (ret < 0) return ret; - } /* Enable interrupts now. */ reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 | diff --git a/drivers/power/supply/twl6030_charger.c b/drivers/power/supply/twl6030_charger.c index b4ec26ff257c..020512d0e306 100644 --- a/drivers/power/supply/twl6030_charger.c +++ b/drivers/power/supply/twl6030_charger.c @@ -542,9 +542,7 @@ static int twl6030_charger_probe(struct platform_device *pdev) IRQF_ONESHOT, pdev->name, charger); if (ret < 0) - return dev_err_probe(&pdev->dev, ret, - "could not request irq %d\n", - charger->irq_chg); + return ret; /* turing to charging to configure things */ twl6030_charger_write(CONTROLLER_CTRL1, 0); diff --git a/drivers/power/supply/ucs1002_power.c b/drivers/power/supply/ucs1002_power.c index 3f44cc902f84..735880ed1cc8 100644 --- a/drivers/power/supply/ucs1002_power.c +++ b/drivers/power/supply/ucs1002_power.c @@ -647,21 +647,15 @@ static int ucs1002_probe(struct i2c_client *client) ucs1002_charger_irq, IRQF_ONESHOT, "ucs1002-a_det", info); - if (ret) { - dev_err(dev, "Failed to request A_DET threaded irq: %d\n", - ret); + if (ret) return ret; - } } if (irq_alert > 0) { ret = devm_request_irq(dev, irq_alert, ucs1002_alert_irq, 0,"ucs1002-alert", info); - if (ret) { - dev_err(dev, "Failed to request ALERT threaded irq: %d\n", - ret); + if (ret) return ret; - } } return 0; diff --git a/include/linux/mfd/bq257xx.h b/include/linux/mfd/bq257xx.h index 4ec72eb920f2..379ef4ee8291 100644 --- a/include/linux/mfd/bq257xx.h +++ b/include/linux/mfd/bq257xx.h @@ -200,6 +200,20 @@ #define BQ25792_REG0A_TRECHG_MASK GENMASK(5, 4) #define BQ25792_REG0A_VRECHG_MASK GENMASK(3, 0) +#define BQ25792_CELL_1S 0 +#define BQ25792_CELL_2S 1 +#define BQ25792_CELL_3S 2 +#define BQ25792_CELL_4S 3 + +#define BQ25792_TRECHG_64MS 0 +#define BQ25792_TRECHG_256MS 1 +#define BQ25792_TRECHG_1024MS 2 +#define BQ25792_TRECHG_2048MS 3 + +#define BQ25792_VRECHG_MIN_UV 50000 +#define BQ25792_VRECHG_STEP_UV 50000 +#define BQ25792_VRECHG_MAX_UV 800000 + /* VOTG regulation */ #define BQ25792_REG0B_VOTG_MASK GENMASK(10, 0) diff --git a/include/linux/power/max17042_battery.h b/include/linux/power/max17042_battery.h index 13aeab1597c6..810e068eafd2 100644 --- a/include/linux/power/max17042_battery.h +++ b/include/linux/power/max17042_battery.h @@ -25,6 +25,7 @@ #define MAX17042_CHARACTERIZATION_DATA_SIZE 48 #define MAX17055_MODELCFG_REFRESH_BIT BIT(15) +#define MAX17055_MODELCFG_VCHG_BIT BIT(10) enum max17042_register { MAX17042_STATUS = 0x00, @@ -124,6 +125,7 @@ enum max17055_register { MAX17055_ConvgCfg = 0x49, MAX17055_VFRemCap = 0x4A, + MAX17055_SoftWakeup = 0x60, MAX17055_STATUS2 = 0xB0, MAX17055_POWER = 0xB1, diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h index 7a5e4c3242a0..a9c056f13077 100644 --- a/include/linux/power_supply.h +++ b/include/linux/power_supply.h @@ -83,6 +83,10 @@ enum { POWER_SUPPLY_TECHNOLOGY_LiFe, POWER_SUPPLY_TECHNOLOGY_NiCd, POWER_SUPPLY_TECHNOLOGY_LiMn, + POWER_SUPPLY_TECHNOLOGY_PbAc, + POWER_SUPPLY_TECHNOLOGY_NiZn, + POWER_SUPPLY_TECHNOLOGY_RAM, + POWER_SUPPLY_TECHNOLOGY_ZnAr, }; enum { @@ -281,6 +285,15 @@ struct power_supply_desc { int (*property_is_writeable)(struct power_supply *psy, enum power_supply_property psp); void (*external_power_changed)(struct power_supply *psy); + /* + * Optional registration-time initialization. This runs in sleepable + * process context after driver data and any battery info are available, + * but before the device is added. The callback must not publish changes or + * start asynchronous activity that can access the power supply before + * registration completes. Return 0 on success or a negative errno on + * failure. + */ + int (*init)(struct power_supply *psy); /* * Set if thermal zone should not be created for this power supply. @@ -346,7 +359,8 @@ struct power_supply { #endif #ifdef CONFIG_LEDS_TRIGGERS - struct led_trigger *trig; + struct led_trigger *charging_or_full_trig; + struct led_trigger *online_trig; struct led_trigger *charging_trig; struct led_trigger *full_trig; struct led_trigger *charging_blink_full_solid_trig; @@ -806,11 +820,26 @@ extern int power_supply_reg_notifier(struct notifier_block *nb); extern void power_supply_unreg_notifier(struct notifier_block *nb); #if IS_ENABLED(CONFIG_POWER_SUPPLY) extern struct power_supply *power_supply_get_by_name(const char *name); +extern int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys); +extern void power_supply_put_system_batteries(struct power_supply **psys, int count); extern void power_supply_put(struct power_supply *psy); #else static inline void power_supply_put(struct power_supply *psy) {} static inline struct power_supply *power_supply_get_by_name(const char *name) { return NULL; } +static inline int __must_check power_supply_get_system_batteries(struct device *dev, + struct power_supply ***psys) +{ + if (psys) + *psys = NULL; + return 0; +} + +static inline void power_supply_put_system_batteries(struct power_supply **psys, + int count) +{ +} #endif extern struct power_supply *power_supply_get_by_reference(struct fwnode_handle *fwnode, const char *property); diff --git a/tools/testing/selftests/power_supply/helpers.sh b/tools/testing/selftests/power_supply/helpers.sh index 1ec90d7c9108..ff1175aeed72 100644 --- a/tools/testing/selftests/power_supply/helpers.sh +++ b/tools/testing/selftests/power_supply/helpers.sh @@ -108,7 +108,7 @@ test_sysfs_prop_optional_range() { test_sysfs_prop_optional_list() { PROP=$1 - LIST=$2 + shift TEST_NAME="$DEVNAME".sysfs."$PROP" @@ -118,9 +118,9 @@ test_sysfs_prop_optional_list() { valid=0 OLDIFS=$IFS - IFS="," - for item in $LIST; do - if [ "$DATA" = "$item" ]; then + IFS=",$IFS" + for item in $*; do + if [ "$item" ] && [ "$DATA" = "$item" ]; then valid=1 break fi diff --git a/tools/testing/selftests/power_supply/test_power_supply_properties.sh b/tools/testing/selftests/power_supply/test_power_supply_properties.sh index a66b1313ed88..1ebac6fe5d23 100755 --- a/tools/testing/selftests/power_supply/test_power_supply_properties.sh +++ b/tools/testing/selftests/power_supply/test_power_supply_properties.sh @@ -74,7 +74,8 @@ for DEVNAME in $supplies; do test_sysfs_prop_optional model_name test_sysfs_prop_optional manufacturer test_sysfs_prop_optional serial_number - test_sysfs_prop_optional_list technology "Unknown","NiMH","Li-ion","Li-poly","LiFe","NiCd","LiMn" + test_sysfs_prop_optional_list technology "Unknown","NiMH","Li-ion","Li-poly","LiFe","NiCd"\ + ,"LiMn","PbAc","NiZn","RAM","ZnAr" test_sysfs_prop_optional cycle_count