Merge tag 'for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power...
authorLinus Torvalds <torvalds@linux-foundation.org>
Thu, 30 Jan 2020 15:51:24 +0000 (07:51 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Thu, 30 Jan 2020 15:51:24 +0000 (07:51 -0800)
Pull power supply and reset updates from Sebastian Reichel:
 "Core:
   - Add battery internal resistance temperature table support

  Drivers:
   - sc27xx: Optimize the battery resistance with measuring temperature
   - max17042-battery: Add MAX17055 support
   - bq25890-charger: Add support of BQ25892 and BQ25896 chips
   - misc fixes"

* tag 'for-v5.6' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (44 commits)
  power: supply: ipaq_micro_battery: remove unneeded semicolon
  power: supply: bq25890_charger: fix incorrect error return when bq25890_field_read fails
  power: supply: axp20x_usb_power: Only poll while offline
  power: supply: axp20x_usb_power: Add wakeup control
  power: supply: axp20x_usb_power: Allow offlining
  power: supply: axp20x_usb_power: Use a match structure
  power: suppy: ucs1002: Make the symbol 'ucs1002_regulator_enable' static
  power: reset: at91-poweroff: use proper master clock register offset
  power: reset: at91-poweroff: introduce struct shdwc_reg_config
  power: supply: bq25890_charger: Add DT and I2C ids for all supported chips
  dt-bindings: Add new chips to bq25890 binding documentation
  power: supply: bq25890_charger: Add support of BQ25892 and BQ25896 chips
  power: supply: core: Update sysfs-class-power ABI document
  power: supply: sbs-battery: Fix a signedness bug in sbs_get_battery_capacity()
  power: supply: ltc2941-battery-gauge: fix use-after-free
  power: supply: max17040: Correct IRQ wake handling
  power: supply: axp20x_usb_power: Remove unused device_node
  power: supply: axp20x_ac_power: Add wakeup control
  power: supply: axp20x_ac_power: Allow offlining
  power: supply: axp20x_ac_power: Fix reporting online status
  ...

30 files changed:
Documentation/ABI/testing/sysfs-class-power
Documentation/devicetree/bindings/power/supply/battery.txt
Documentation/devicetree/bindings/power/supply/bq25890.txt
Documentation/devicetree/bindings/power/supply/max17040_battery.txt [new file with mode: 0644]
Documentation/devicetree/bindings/power/supply/max17042_battery.txt
Documentation/devicetree/bindings/power/supply/sc27xx-fg.txt
drivers/power/reset/Kconfig
drivers/power/reset/at91-sama5d2_shdwc.c
drivers/power/reset/gpio-restart.c
drivers/power/supply/Kconfig
drivers/power/supply/ab8500_charger.c
drivers/power/supply/ab8500_fg.c
drivers/power/supply/abx500_chargalg.c
drivers/power/supply/axp20x_ac_power.c
drivers/power/supply/axp20x_usb_power.c
drivers/power/supply/bq25890_charger.c
drivers/power/supply/cros_usbpd-charger.c
drivers/power/supply/ingenic-battery.c
drivers/power/supply/ipaq_micro_battery.c
drivers/power/supply/ltc2941-battery-gauge.c
drivers/power/supply/max17040_battery.c
drivers/power/supply/max17042_battery.c
drivers/power/supply/max77650-charger.c
drivers/power/supply/pda_power.c
drivers/power/supply/power_supply_core.c
drivers/power/supply/sbs-battery.c
drivers/power/supply/sc27xx_fuel_gauge.c
drivers/power/supply/ucs1002_power.c
include/linux/power/max17042_battery.h
include/linux/power_supply.h

index 27edc06..bf3b48f 100644 (file)
@@ -189,7 +189,8 @@ Description:
                Access: Read
                Valid values: "Unknown", "Good", "Overheat", "Dead",
                              "Over voltage", "Unspecified failure", "Cold",
-                             "Watchdog timer expire", "Safety timer expire"
+                             "Watchdog timer expire", "Safety timer expire",
+                             "Over current"
 
 What:          /sys/class/power_supply/<supply_name>/precharge_current
 Date:          June 2017
index 5c913d4..3049cf8 100644 (file)
@@ -35,6 +35,10 @@ Optional Properties:
    for each of the battery capacity lookup table. The first temperature value
    specifies the OCV table 0, and the second temperature value specifies the
    OCV table 1, and so on.
+ - resistance-temp-table: An array providing the temperature in degree Celsius
+   and corresponding battery internal resistance percent, which is used to look
+   up the resistance percent according to current temperature to get a accurate
+   batterty internal resistance in different temperatures.
 
 Battery properties are named, where possible, for the corresponding
 elements in enum power_supply_property, defined in
@@ -61,6 +65,7 @@ Example:
                ocv-capacity-table-0 = <4185000 100>, <4113000 95>, <4066000 90>, ...;
                ocv-capacity-table-1 = <4200000 100>, <4185000 95>, <4113000 90>, ...;
                ocv-capacity-table-2 = <4250000 100>, <4200000 95>, <4185000 90>, ...;
+               resistance-temp-table = <20 100>, <10 90>, <0 80>, <(-10) 60>;
        };
 
        charger: charger@11 {
index dc05689..dc9c8f7 100644 (file)
@@ -1,11 +1,14 @@
 Binding for TI bq25890 Li-Ion Charger
 
-This driver will support the bq25896 and the bq25890. There are other ICs
-in the same family but those have not been tested.
+This driver will support the bq25892, the bq25896 and the bq25890. There are
+other ICs in the same family but those have not been tested.
 
 Required properties:
 - compatible: Should contain one of the following:
     * "ti,bq25890"
+    * "ti,bq25892"
+    * "ti,bq25895"
+    * "ti,bq25896"
 - reg: integer, i2c address of the device.
 - ti,battery-regulation-voltage: integer, maximum charging voltage (in uV);
 - ti,charge-current: integer, maximum charging current (in uA);
diff --git a/Documentation/devicetree/bindings/power/supply/max17040_battery.txt b/Documentation/devicetree/bindings/power/supply/max17040_battery.txt
new file mode 100644 (file)
index 0000000..4e0186b
--- /dev/null
@@ -0,0 +1,33 @@
+max17040_battery
+~~~~~~~~~~~~~~~~
+
+Required properties :
+ - compatible : "maxim,max17040" or "maxim,max77836-battery"
+ - reg: i2c slave address
+
+Optional properties :
+- maxim,alert-low-soc-level :  The alert threshold that sets the state of
+                               charge level (%) where an interrupt is
+                               generated. Can be configured from 1 up to 32
+                               (%). If skipped the power up default value of
+                               4 (%) will be used.
+- interrupts :                         Interrupt line see Documentation/devicetree/
+                               bindings/interrupt-controller/interrupts.txt
+- wakeup-source :              This device has wakeup capabilities. Use this
+                               property to use alert low SOC level interrupt
+                               as wake up source.
+
+Optional properties support interrupt functionality for alert low state of
+charge level, present in some ICs in the same family, and should be used with
+compatible "maxim,max77836-battery".
+
+Example:
+
+       battery-fuel-gauge@36 {
+               compatible = "maxim,max77836-battery";
+               reg = <0x36>;
+               maxim,alert-low-soc-level = <10>;
+               interrupt-parent = <&gpio7>;
+               interrupts = <2 IRQ_TYPE_EDGE_FALLING>;
+               wakeup-source;
+       };
index 3f3894a..f34c5da 100644 (file)
@@ -2,7 +2,11 @@ max17042_battery
 ~~~~~~~~~~~~~~~~
 
 Required properties :
- - compatible : "maxim,max17042"
+ - compatible : one of the following
+ * "maxim,max17042"
+ * "maxim,max17047"
+ * "maxim,max17050"
+ * "maxim,max17055"
 
 Optional properties :
  - maxim,rsns-microohm : Resistance of rsns resistor in micro Ohms
index 0a5705b..b6359b5 100644 (file)
@@ -13,6 +13,8 @@ Required properties:
 - io-channel-names: Should be "bat-temp" or "charge-vol".
 - nvmem-cells: A phandle to the calibration cells provided by eFuse device.
 - nvmem-cell-names: Should be "fgu_calib".
+- sprd,calib-resistance-micro-ohms: Specify the real resistance of coulomb counter
+  chip in micro Ohms.
 - monitored-battery: Phandle of battery characteristics devicetree node.
   See Documentation/devicetree/bindings/power/supply/battery.txt
 
@@ -52,5 +54,6 @@ Example:
                        nvmem-cells = <&fgu_calib>;
                        nvmem-cell-names = "fgu_calib";
                        monitored-battery = <&bat>;
+                       sprd,calib-resistance-micro-ohms = <21500>;
                };
        };
index c721939..0498363 100644 (file)
@@ -141,14 +141,14 @@ config POWER_RESET_LTC2952
          down via the LTC2952. Bindings are made in the device tree.
 
 config POWER_RESET_MT6323
-       bool "MediaTek MT6323 power-off driver"
-       depends on MFD_MT6397
-       help
-         The power-off driver is responsible for externally shutdown down
-         the power of a remote MediaTek SoC MT6323 is connected to through
-         controlling a tiny circuit BBPU inside MT6323 RTC.
-
-         Say Y if you have a board where MT6323 could be found.
+       bool "MediaTek MT6323 power-off driver"
+       depends on MFD_MT6397
+       help
+         The power-off driver is responsible for externally shutdown down
+         the power of a remote MediaTek SoC MT6323 is connected to through
+         controlling a tiny circuit BBPU inside MT6323 RTC.
+
+         Say Y if you have a board where MT6323 could be found.
 
 config POWER_RESET_QNAP
        bool "QNAP power-off driver"
index 1c18f46..2fe3a62 100644 (file)
@@ -66,7 +66,7 @@
 
 #define SHDW_CFG_NOT_USED      (32)
 
-struct shdwc_config {
+struct shdwc_reg_config {
        u8 wkup_pin_input;
        u8 mr_rtcwk_shift;
        u8 mr_rttwk_shift;
@@ -74,8 +74,17 @@ struct shdwc_config {
        u8 sr_rttwk_shift;
 };
 
+struct pmc_reg_config {
+       u8 mckr;
+};
+
+struct reg_config {
+       struct shdwc_reg_config shdwc;
+       struct pmc_reg_config pmc;
+};
+
 struct shdwc {
-       const struct shdwc_config *cfg;
+       const struct reg_config *rcfg;
        struct clk *sclk;
        void __iomem *shdwc_base;
        void __iomem *mpddrc_base;
@@ -95,6 +104,7 @@ static const unsigned long long sdwc_dbc_period[] = {
 static void __init at91_wakeup_status(struct platform_device *pdev)
 {
        struct shdwc *shdw = platform_get_drvdata(pdev);
+       const struct reg_config *rcfg = shdw->rcfg;
        u32 reg;
        char *reason = "unknown";
 
@@ -106,11 +116,11 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
        if (!reg)
                return;
 
-       if (SHDW_WK_PIN(reg, shdw->cfg))
+       if (SHDW_WK_PIN(reg, &rcfg->shdwc))
                reason = "WKUP pin";
-       else if (SHDW_RTCWK(reg, shdw->cfg))
+       else if (SHDW_RTCWK(reg, &rcfg->shdwc))
                reason = "RTC";
-       else if (SHDW_RTTWK(reg, shdw->cfg))
+       else if (SHDW_RTTWK(reg, &rcfg->shdwc))
                reason = "RTT";
 
        pr_info("AT91: Wake-Up source: %s\n", reason);
@@ -131,9 +141,9 @@ static void at91_poweroff(void)
                "       str     %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
 
                /* Switch the master clock source to slow clock. */
-               "1:     ldr     r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+               "1:     ldr     r6, [%4, %5]\n\t"
                "       bic     r6, r6,  #" __stringify(AT91_PMC_CSS) "\n\t"
-               "       str     r6, [%4, #" __stringify(AT91_PMC_MCKR) "]\n\t"
+               "       str     r6, [%4, %5]\n\t"
                /* Wait for clock switch. */
                "2:     ldr     r6, [%4, #" __stringify(AT91_PMC_SR) "]\n\t"
                "       tst     r6, #"      __stringify(AT91_PMC_MCKRDY) "\n\t"
@@ -148,7 +158,8 @@ static void at91_poweroff(void)
                  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
                  "r" (at91_shdwc->shdwc_base),
                  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW),
-                 "r" (at91_shdwc->pmc_base)
+                 "r" (at91_shdwc->pmc_base),
+                 "r" (at91_shdwc->rcfg->pmc.mckr)
                : "r6");
 }
 
@@ -215,6 +226,7 @@ static u32 at91_shdwc_get_wakeup_input(struct platform_device *pdev,
 static void at91_shdwc_dt_configure(struct platform_device *pdev)
 {
        struct shdwc *shdw = platform_get_drvdata(pdev);
+       const struct reg_config *rcfg = shdw->rcfg;
        struct device_node *np = pdev->dev.of_node;
        u32 mode = 0, tmp, input;
 
@@ -227,10 +239,10 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
                mode |= AT91_SHDW_WKUPDBC(at91_shdwc_debouncer_value(pdev, tmp));
 
        if (of_property_read_bool(np, "atmel,wakeup-rtc-timer"))
-               mode |= SHDW_RTCWKEN(shdw->cfg);
+               mode |= SHDW_RTCWKEN(&rcfg->shdwc);
 
        if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
-               mode |= SHDW_RTTWKEN(shdw->cfg);
+               mode |= SHDW_RTTWKEN(&rcfg->shdwc);
 
        dev_dbg(&pdev->dev, "%s: mode = %#x\n", __func__, mode);
        writel(mode, shdw->shdwc_base + AT91_SHDW_MR);
@@ -239,30 +251,40 @@ static void at91_shdwc_dt_configure(struct platform_device *pdev)
        writel(input, shdw->shdwc_base + AT91_SHDW_WUIR);
 }
 
-static const struct shdwc_config sama5d2_shdwc_config = {
-       .wkup_pin_input = 0,
-       .mr_rtcwk_shift = 17,
-       .mr_rttwk_shift = SHDW_CFG_NOT_USED,
-       .sr_rtcwk_shift = 5,
-       .sr_rttwk_shift = SHDW_CFG_NOT_USED,
+static const struct reg_config sama5d2_reg_config = {
+       .shdwc = {
+               .wkup_pin_input = 0,
+               .mr_rtcwk_shift = 17,
+               .mr_rttwk_shift = SHDW_CFG_NOT_USED,
+               .sr_rtcwk_shift = 5,
+               .sr_rttwk_shift = SHDW_CFG_NOT_USED,
+       },
+       .pmc = {
+               .mckr           = 0x30,
+       },
 };
 
-static const struct shdwc_config sam9x60_shdwc_config = {
-       .wkup_pin_input = 0,
-       .mr_rtcwk_shift = 17,
-       .mr_rttwk_shift = 16,
-       .sr_rtcwk_shift = 5,
-       .sr_rttwk_shift = 4,
+static const struct reg_config sam9x60_reg_config = {
+       .shdwc = {
+               .wkup_pin_input = 0,
+               .mr_rtcwk_shift = 17,
+               .mr_rttwk_shift = 16,
+               .sr_rtcwk_shift = 5,
+               .sr_rttwk_shift = 4,
+       },
+       .pmc = {
+               .mckr           = 0x28,
+       },
 };
 
 static const struct of_device_id at91_shdwc_of_match[] = {
        {
                .compatible = "atmel,sama5d2-shdwc",
-               .data = &sama5d2_shdwc_config,
+               .data = &sama5d2_reg_config,
        },
        {
                .compatible = "microchip,sam9x60-shdwc",
-               .data = &sam9x60_shdwc_config,
+               .data = &sam9x60_reg_config,
        }, {
                /*sentinel*/
        }
@@ -303,7 +325,7 @@ static int __init at91_shdwc_probe(struct platform_device *pdev)
        }
 
        match = of_match_node(at91_shdwc_of_match, pdev->dev.of_node);
-       at91_shdwc->cfg = match->data;
+       at91_shdwc->rcfg = match->data;
 
        at91_shdwc->sclk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(at91_shdwc->sclk))
index 308ca9d..5466eee 100644 (file)
@@ -64,9 +64,11 @@ static int gpio_restart_probe(struct platform_device *pdev)
 
        gpio_restart->reset_gpio = devm_gpiod_get(&pdev->dev, NULL,
                        open_source ? GPIOD_IN : GPIOD_OUT_LOW);
-       if (IS_ERR(gpio_restart->reset_gpio)) {
-               dev_err(&pdev->dev, "Could not get reset GPIO\n");
-               return PTR_ERR(gpio_restart->reset_gpio);
+       ret = PTR_ERR_OR_ZERO(gpio_restart->reset_gpio);
+       if (ret) {
+               if (ret != -EPROBE_DEFER)
+                       dev_err(&pdev->dev, "Could not get reset GPIO\n");
+               return ret;
        }
 
        gpio_restart->restart_handler.notifier_call = gpio_restart_notify;
index 27164a1..9a5591a 100644 (file)
@@ -73,10 +73,10 @@ config WM831X_POWER
          provided by Wolfson Microelectronics WM831x PMICs.
 
 config WM8350_POWER
-        tristate "WM8350 PMU support"
-        depends on MFD_WM8350
-        help
-          Say Y here to enable support for the power management unit
+       tristate "WM8350 PMU support"
+       depends on MFD_WM8350
+       help
+         Say Y here to enable support for the power management unit
          provided by the Wolfson Microelectronics WM8350 PMIC.
 
 config TEST_POWER
@@ -209,16 +209,16 @@ config BATTERY_WM97XX
          Say Y to enable support for battery measured by WM97xx aux port.
 
 config BATTERY_SBS
-        tristate "SBS Compliant gas gauge"
-        depends on I2C
-        help
+       tristate "SBS Compliant gas gauge"
+       depends on I2C
+       help
          Say Y to include support for SBS battery driver for SBS-compliant
          gas gauges.
 
 config CHARGER_SBS
-        tristate "SBS Compliant charger"
-        depends on I2C
-        help
+       tristate "SBS Compliant charger"
+       depends on I2C
+       help
          Say Y to include support for SBS compliant battery chargers.
 
 config MANAGER_SBS
@@ -484,11 +484,11 @@ config CHARGER_MANAGER
        depends on REGULATOR
        select EXTCON
        help
-          Say Y to enable charger-manager support, which allows multiple
-          chargers attached to a battery and multiple batteries attached to a
-          system. The charger-manager also can monitor charging status in
-          runtime and in suspend-to-RAM by waking up the system periodically
-          with help of suspend_again support.
+         Say Y to enable charger-manager support, which allows multiple
+         chargers attached to a battery and multiple batteries attached to a
+         system. The charger-manager also can monitor charging status in
+         runtime and in suspend-to-RAM by waking up the system periodically
+         with help of suspend_again support.
 
 config CHARGER_LT3651
        tristate "Analog Devices LT3651 charger"
index 8a0f9d7..f69550d 100644 (file)
@@ -789,7 +789,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
                di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
                ret = -ENXIO;
                break;
-       };
+       }
 
        di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
        dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
@@ -1079,7 +1079,7 @@ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
                di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
                ret = -EPERM;
                break;
-       };
+       }
        di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
        return ret;
 }
@@ -2427,7 +2427,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
 
        default:
                break;
-       };
+       }
 }
 
 /**
index c3912ee..b96f90a 100644 (file)
@@ -2221,10 +2221,10 @@ static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
                                                ab8500_fg_update_cap_scalers(di);
                                        queue_work(di->fg_wq, &di->fg_work);
                                        break;
-                               };
+                               }
                        default:
                                break;
-                       };
+                       }
                        break;
                case POWER_SUPPLY_PROP_TECHNOLOGY:
                        switch (ext->desc->type) {
@@ -2331,7 +2331,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
                if (ret) {
                        dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__);
                        goto out;
-               };
+               }
 
                ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
                        AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
@@ -2339,7 +2339,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
                if (ret) {
                        dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
                        goto out;
-               };
+               }
 
                ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
                        AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
@@ -2347,7 +2347,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
                if (ret) {
                        dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__);
                        goto out;
-               };
+               }
 
                ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
                        AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time);
@@ -2355,7 +2355,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
                if (ret) {
                        dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__);
                        goto out;
-               };
+               }
 
                ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
                        AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable);
@@ -2363,7 +2363,7 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
                if (ret) {
                        dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__);
                        goto out;
-               };
+               }
        }
 out:
        return ret;
index e6e37d4..2fb33a0 100644 (file)
@@ -1823,7 +1823,7 @@ static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di,
                        "Enter 0. Disable AC/USB Charging\n"
                        "1. Enable AC charging\n"
                        "2. Enable USB Charging\n");
-       };
+       }
        return strlen(buf);
 }
 
index 0d34a93..ac36001 100644 (file)
@@ -15,6 +15,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm.h>
 #include <linux/power_supply.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
@@ -23,6 +24,9 @@
 #define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
 #define AXP20X_PWR_STATUS_ACIN_AVAIL   BIT(6)
 
+#define AXP813_ACIN_PATH_SEL           BIT(7)
+#define AXP813_ACIN_PATH_SEL_TO_BIT(x) (!!(x) << 7)
+
 #define AXP813_VHOLD_MASK              GENMASK(5, 3)
 #define AXP813_VHOLD_UV_TO_BIT(x)      ((((x) / 100000) - 40) << 3)
 #define AXP813_VHOLD_REG_TO_UV(x)      \
@@ -40,6 +44,9 @@ struct axp20x_ac_power {
        struct power_supply *supply;
        struct iio_channel *acin_v;
        struct iio_channel *acin_i;
+       bool has_acin_path_sel;
+       unsigned int num_irqs;
+       unsigned int irqs[];
 };
 
 static irqreturn_t axp20x_ac_power_irq(int irq, void *devid)
@@ -86,6 +93,17 @@ static int axp20x_ac_power_get_property(struct power_supply *psy,
                        return ret;
 
                val->intval = !!(reg & AXP20X_PWR_STATUS_ACIN_AVAIL);
+
+               /* ACIN_PATH_SEL disables ACIN even if ACIN_AVAIL is set. */
+               if (val->intval && power->has_acin_path_sel) {
+                       ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL,
+                                         &reg);
+                       if (ret)
+                               return ret;
+
+                       val->intval = !!(reg & AXP813_ACIN_PATH_SEL);
+               }
+
                return 0;
 
        case POWER_SUPPLY_PROP_VOLTAGE_NOW:
@@ -143,6 +161,11 @@ static int axp813_ac_power_set_property(struct power_supply *psy,
        struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
 
        switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL,
+                                         AXP813_ACIN_PATH_SEL,
+                                         AXP813_ACIN_PATH_SEL_TO_BIT(val->intval));
+
        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
                if (val->intval < 4000000 || val->intval > 4700000)
                        return -EINVAL;
@@ -169,7 +192,8 @@ static int axp813_ac_power_set_property(struct power_supply *psy,
 static int axp813_ac_power_prop_writeable(struct power_supply *psy,
                                          enum power_supply_property psp)
 {
-       return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
+       return psp == POWER_SUPPLY_PROP_ONLINE ||
+              psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
               psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
 }
 
@@ -221,34 +245,86 @@ static const struct power_supply_desc axp813_ac_power_desc = {
        .set_property = axp813_ac_power_set_property,
 };
 
+static const char * const axp20x_irq_names[] = {
+       "ACIN_PLUGIN",
+       "ACIN_REMOVAL",
+};
+
 struct axp_data {
        const struct power_supply_desc  *power_desc;
+       const char * const              *irq_names;
+       unsigned int                    num_irq_names;
        bool                            acin_adc;
+       bool                            acin_path_sel;
 };
 
 static const struct axp_data axp20x_data = {
-       .power_desc = &axp20x_ac_power_desc,
-       .acin_adc = true,
+       .power_desc     = &axp20x_ac_power_desc,
+       .irq_names      = axp20x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp20x_irq_names),
+       .acin_adc       = true,
+       .acin_path_sel  = false,
 };
 
 static const struct axp_data axp22x_data = {
-       .power_desc = &axp22x_ac_power_desc,
-       .acin_adc = false,
+       .power_desc     = &axp22x_ac_power_desc,
+       .irq_names      = axp20x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp20x_irq_names),
+       .acin_adc       = false,
+       .acin_path_sel  = false,
 };
 
 static const struct axp_data axp813_data = {
-       .power_desc = &axp813_ac_power_desc,
-       .acin_adc = false,
+       .power_desc     = &axp813_ac_power_desc,
+       .irq_names      = axp20x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp20x_irq_names),
+       .acin_adc       = false,
+       .acin_path_sel  = true,
 };
 
+#ifdef CONFIG_PM_SLEEP
+static int axp20x_ac_power_suspend(struct device *dev)
+{
+       struct axp20x_ac_power *power = dev_get_drvdata(dev);
+       int i = 0;
+
+       /*
+        * Allow wake via ACIN_PLUGIN only.
+        *
+        * As nested threaded IRQs are not automatically disabled during
+        * suspend, we must explicitly disable the remainder of the IRQs.
+        */
+       if (device_may_wakeup(&power->supply->dev))
+               enable_irq_wake(power->irqs[i++]);
+       while (i < power->num_irqs)
+               disable_irq(power->irqs[i++]);
+
+       return 0;
+}
+
+static int axp20x_ac_power_resume(struct device *dev)
+{
+       struct axp20x_ac_power *power = dev_get_drvdata(dev);
+       int i = 0;
+
+       if (device_may_wakeup(&power->supply->dev))
+               disable_irq_wake(power->irqs[i++]);
+       while (i < power->num_irqs)
+               enable_irq(power->irqs[i++]);
+
+       return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(axp20x_ac_power_pm_ops, axp20x_ac_power_suspend,
+                                                axp20x_ac_power_resume);
+
 static int axp20x_ac_power_probe(struct platform_device *pdev)
 {
        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
        struct power_supply_config psy_cfg = {};
        struct axp20x_ac_power *power;
        const struct axp_data *axp_data;
-       static const char * const irq_names[] = { "ACIN_PLUGIN", "ACIN_REMOVAL",
-               NULL };
        int i, irq, ret;
 
        if (!of_device_is_available(pdev->dev.of_node))
@@ -259,12 +335,14 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+       axp_data = of_device_get_match_data(&pdev->dev);
+
+       power = devm_kzalloc(&pdev->dev,
+                            struct_size(power, irqs, axp_data->num_irq_names),
+                            GFP_KERNEL);
        if (!power)
                return -ENOMEM;
 
-       axp_data = of_device_get_match_data(&pdev->dev);
-
        if (axp_data->acin_adc) {
                power->acin_v = devm_iio_channel_get(&pdev->dev, "acin_v");
                if (IS_ERR(power->acin_v)) {
@@ -282,6 +360,8 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
        }
 
        power->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+       power->has_acin_path_sel = axp_data->acin_path_sel;
+       power->num_irqs = axp_data->num_irq_names;
 
        platform_set_drvdata(pdev, power);
 
@@ -295,20 +375,22 @@ static int axp20x_ac_power_probe(struct platform_device *pdev)
                return PTR_ERR(power->supply);
 
        /* Request irqs after registering, as irqs may trigger immediately */
-       for (i = 0; irq_names[i]; i++) {
-               irq = platform_get_irq_byname(pdev, irq_names[i]);
+       for (i = 0; i < axp_data->num_irq_names; i++) {
+               irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
                if (irq < 0) {
-                       dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
-                                irq_names[i], irq);
-                       continue;
+                       dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+                               axp_data->irq_names[i], irq);
+                       return irq;
                }
-               irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
-               ret = devm_request_any_context_irq(&pdev->dev, irq,
+               power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+               ret = devm_request_any_context_irq(&pdev->dev, power->irqs[i],
                                                   axp20x_ac_power_irq, 0,
                                                   DRVNAME, power);
-               if (ret < 0)
-                       dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
-                                irq_names[i], ret);
+               if (ret < 0) {
+                       dev_err(&pdev->dev, "Error requesting %s IRQ: %d\n",
+                               axp_data->irq_names[i], ret);
+                       return ret;
+               }
        }
 
        return 0;
@@ -331,8 +413,9 @@ MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
 static struct platform_driver axp20x_ac_power_driver = {
        .probe = axp20x_ac_power_probe,
        .driver = {
-               .name = DRVNAME,
-               .of_match_table = axp20x_ac_power_match,
+               .name           = DRVNAME,
+               .of_match_table = axp20x_ac_power_match,
+               .pm             = &axp20x_ac_power_pm_ops,
        },
 };
 
index 5f0a572..4fde24b 100644 (file)
@@ -16,6 +16,7 @@
 #include <linux/of.h>
 #include <linux/of_device.h>
 #include <linux/platform_device.h>
+#include <linux/pm.h>
 #include <linux/power_supply.h>
 #include <linux/regmap.h>
 #include <linux/slab.h>
@@ -29,6 +30,9 @@
 
 #define AXP20X_USB_STATUS_VBUS_VALID   BIT(2)
 
+#define AXP20X_VBUS_PATH_SEL           BIT(7)
+#define AXP20X_VBUS_PATH_SEL_OFFSET    7
+
 #define AXP20X_VBUS_VHOLD_uV(b)                (4000000 + (((b) >> 3) & 7) * 100000)
 #define AXP20X_VBUS_VHOLD_MASK         GENMASK(5, 3)
 #define AXP20X_VBUS_VHOLD_OFFSET       3
@@ -57,7 +61,6 @@
 #define DEBOUNCE_TIME                  msecs_to_jiffies(50)
 
 struct axp20x_usb_power {
-       struct device_node *np;
        struct regmap *regmap;
        struct power_supply *supply;
        enum axp20x_variants axp20x_id;
@@ -65,14 +68,32 @@ struct axp20x_usb_power {
        struct iio_channel *vbus_i;
        struct delayed_work vbus_detect;
        unsigned int old_status;
+       unsigned int online;
+       unsigned int num_irqs;
+       unsigned int irqs[];
 };
 
+static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
+{
+       /*
+        * Polling is only necessary while VBUS is offline. While online, a
+        * present->absent transition implies an online->offline transition
+        * and will triger the VBUS_REMOVAL IRQ.
+        */
+       if (power->axp20x_id >= AXP221_ID && !power->online)
+               return true;
+
+       return false;
+}
+
 static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
 {
        struct axp20x_usb_power *power = devid;
 
        power_supply_changed(power->supply);
 
+       mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
+
        return IRQ_HANDLED;
 }
 
@@ -92,17 +113,11 @@ static void axp20x_usb_power_poll_vbus(struct work_struct *work)
                power_supply_changed(power->supply);
 
        power->old_status = val;
+       power->online = val & AXP20X_PWR_STATUS_VBUS_USED;
 
 out:
-       mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
-}
-
-static bool axp20x_usb_vbus_needs_polling(struct axp20x_usb_power *power)
-{
-       if (power->axp20x_id >= AXP221_ID)
-               return true;
-
-       return false;
+       if (axp20x_usb_vbus_needs_polling(power))
+               mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
 }
 
 static int axp20x_get_current_max(struct axp20x_usb_power *power, int *val)
@@ -264,6 +279,16 @@ static int axp20x_usb_power_get_property(struct power_supply *psy,
        return 0;
 }
 
+static int axp813_usb_power_set_online(struct axp20x_usb_power *power,
+                                      int intval)
+{
+       int val = !intval << AXP20X_VBUS_PATH_SEL_OFFSET;
+
+       return regmap_update_bits(power->regmap,
+                                 AXP20X_VBUS_IPSOUT_MGMT,
+                                 AXP20X_VBUS_PATH_SEL, val);
+}
+
 static int axp20x_usb_power_set_voltage_min(struct axp20x_usb_power *power,
                                            int intval)
 {
@@ -345,6 +370,11 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
        struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
 
        switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               if (power->axp20x_id != AXP813_ID)
+                       return -EINVAL;
+               return axp813_usb_power_set_online(power, val->intval);
+
        case POWER_SUPPLY_PROP_VOLTAGE_MIN:
                return axp20x_usb_power_set_voltage_min(power, val->intval);
 
@@ -364,6 +394,18 @@ static int axp20x_usb_power_set_property(struct power_supply *psy,
 static int axp20x_usb_power_prop_writeable(struct power_supply *psy,
                                           enum power_supply_property psp)
 {
+       struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
+
+       /*
+        * The VBUS path select flag works differently on on AXP288 and newer:
+        *  - On AXP20x and AXP22x, the flag enables VBUS (ignoring N_VBUSEN).
+        *  - On AXP288 and AXP8xx, the flag disables VBUS (ignoring N_VBUSEN).
+        * We only expose the control on variants where it can be used to force
+        * the VBUS input offline.
+        */
+       if (psp == POWER_SUPPLY_PROP_ONLINE)
+               return power->axp20x_id == AXP813_ID;
+
        return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
               psp == POWER_SUPPLY_PROP_CURRENT_MAX;
 }
@@ -406,6 +448,92 @@ static const struct power_supply_desc axp22x_usb_power_desc = {
        .set_property = axp20x_usb_power_set_property,
 };
 
+static const char * const axp20x_irq_names[] = {
+       "VBUS_PLUGIN",
+       "VBUS_REMOVAL",
+       "VBUS_VALID",
+       "VBUS_NOT_VALID",
+};
+
+static const char * const axp22x_irq_names[] = {
+       "VBUS_PLUGIN",
+       "VBUS_REMOVAL",
+};
+
+struct axp_data {
+       const struct power_supply_desc  *power_desc;
+       const char * const              *irq_names;
+       unsigned int                    num_irq_names;
+       enum axp20x_variants            axp20x_id;
+};
+
+static const struct axp_data axp202_data = {
+       .power_desc     = &axp20x_usb_power_desc,
+       .irq_names      = axp20x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp20x_irq_names),
+       .axp20x_id      = AXP202_ID,
+};
+
+static const struct axp_data axp221_data = {
+       .power_desc     = &axp22x_usb_power_desc,
+       .irq_names      = axp22x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp22x_irq_names),
+       .axp20x_id      = AXP221_ID,
+};
+
+static const struct axp_data axp223_data = {
+       .power_desc     = &axp22x_usb_power_desc,
+       .irq_names      = axp22x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp22x_irq_names),
+       .axp20x_id      = AXP223_ID,
+};
+
+static const struct axp_data axp813_data = {
+       .power_desc     = &axp22x_usb_power_desc,
+       .irq_names      = axp22x_irq_names,
+       .num_irq_names  = ARRAY_SIZE(axp22x_irq_names),
+       .axp20x_id      = AXP813_ID,
+};
+
+#ifdef CONFIG_PM_SLEEP
+static int axp20x_usb_power_suspend(struct device *dev)
+{
+       struct axp20x_usb_power *power = dev_get_drvdata(dev);
+       int i = 0;
+
+       /*
+        * Allow wake via VBUS_PLUGIN only.
+        *
+        * As nested threaded IRQs are not automatically disabled during
+        * suspend, we must explicitly disable the remainder of the IRQs.
+        */
+       if (device_may_wakeup(&power->supply->dev))
+               enable_irq_wake(power->irqs[i++]);
+       while (i < power->num_irqs)
+               disable_irq(power->irqs[i++]);
+
+       return 0;
+}
+
+static int axp20x_usb_power_resume(struct device *dev)
+{
+       struct axp20x_usb_power *power = dev_get_drvdata(dev);
+       int i = 0;
+
+       if (device_may_wakeup(&power->supply->dev))
+               disable_irq_wake(power->irqs[i++]);
+       while (i < power->num_irqs)
+               enable_irq(power->irqs[i++]);
+
+       mod_delayed_work(system_wq, &power->vbus_detect, DEBOUNCE_TIME);
+
+       return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(axp20x_usb_power_pm_ops, axp20x_usb_power_suspend,
+                                                 axp20x_usb_power_resume);
+
 static int configure_iio_channels(struct platform_device *pdev,
                                  struct axp20x_usb_power *power)
 {
@@ -441,12 +569,7 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
        struct power_supply_config psy_cfg = {};
        struct axp20x_usb_power *power;
-       static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN",
-               "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
-       static const char * const axp22x_irq_names[] = {
-               "VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
-       const char * const *irq_names;
-       const struct power_supply_desc *usb_power_desc;
+       const struct axp_data *axp_data;
        int i, irq, ret;
 
        if (!of_device_is_available(pdev->dev.of_node))
@@ -457,16 +580,19 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+       axp_data = of_device_get_match_data(&pdev->dev);
+
+       power = devm_kzalloc(&pdev->dev,
+                            struct_size(power, irqs, axp_data->num_irq_names),
+                            GFP_KERNEL);
        if (!power)
                return -ENOMEM;
 
        platform_set_drvdata(pdev, power);
-       power->axp20x_id = (enum axp20x_variants)of_device_get_match_data(
-                                                               &pdev->dev);
 
-       power->np = pdev->dev.of_node;
+       power->axp20x_id = axp_data->axp20x_id;
        power->regmap = axp20x->regmap;
+       power->num_irqs = axp_data->num_irq_names;
 
        if (power->axp20x_id == AXP202_ID) {
                /* Enable vbus valid checking */
@@ -483,18 +609,6 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
 
                if (ret)
                        return ret;
-
-               usb_power_desc = &axp20x_usb_power_desc;
-               irq_names = axp20x_irq_names;
-       } else if (power->axp20x_id == AXP221_ID ||
-                  power->axp20x_id == AXP223_ID ||
-                  power->axp20x_id == AXP813_ID) {
-               usb_power_desc = &axp22x_usb_power_desc;
-               irq_names = axp22x_irq_names;
-       } else {
-               dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n",
-                       axp20x->variant);
-               return -EINVAL;
        }
 
        if (power->axp20x_id == AXP813_ID) {
@@ -506,25 +620,29 @@ static int axp20x_usb_power_probe(struct platform_device *pdev)
        psy_cfg.of_node = pdev->dev.of_node;
        psy_cfg.drv_data = power;
 
-       power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc,
+       power->supply = devm_power_supply_register(&pdev->dev,
+                                                  axp_data->power_desc,
                                                   &psy_cfg);
        if (IS_ERR(power->supply))
                return PTR_ERR(power->supply);
 
        /* Request irqs after registering, as irqs may trigger immediately */
-       for (i = 0; irq_names[i]; i++) {
-               irq = platform_get_irq_byname(pdev, irq_names[i]);
+       for (i = 0; i < axp_data->num_irq_names; i++) {
+               irq = platform_get_irq_byname(pdev, axp_data->irq_names[i]);
                if (irq < 0) {
-                       dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
-                                irq_names[i], irq);
-                       continue;
+                       dev_err(&pdev->dev, "No IRQ for %s: %d\n",
+                               axp_data->irq_names[i], irq);
+                       return irq;
+               }
+               power->irqs[i] = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+               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);
+                       return ret;
                }
-               irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
-               ret = devm_request_any_context_irq(&pdev->dev, irq,
-                               axp20x_usb_power_irq, 0, DRVNAME, power);
-               if (ret < 0)
-                       dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
-                                irq_names[i], ret);
        }
 
        INIT_DELAYED_WORK(&power->vbus_detect, axp20x_usb_power_poll_vbus);
@@ -546,16 +664,16 @@ static int axp20x_usb_power_remove(struct platform_device *pdev)
 static const struct of_device_id axp20x_usb_power_match[] = {
        {
                .compatible = "x-powers,axp202-usb-power-supply",
-               .data = (void *)AXP202_ID,
+               .data = &axp202_data,
        }, {
                .compatible = "x-powers,axp221-usb-power-supply",
-               .data = (void *)AXP221_ID,
+               .data = &axp221_data,
        }, {
                .compatible = "x-powers,axp223-usb-power-supply",
-               .data = (void *)AXP223_ID,
+               .data = &axp223_data,
        }, {
                .compatible = "x-powers,axp813-usb-power-supply",
-               .data = (void *)AXP813_ID,
+               .data = &axp813_data,
        }, { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
@@ -564,8 +682,9 @@ static struct platform_driver axp20x_usb_power_driver = {
        .probe = axp20x_usb_power_probe,
        .remove = axp20x_usb_power_remove,
        .driver = {
-               .name = DRVNAME,
-               .of_match_table = axp20x_usb_power_match,
+               .name           = DRVNAME,
+               .of_match_table = axp20x_usb_power_match,
+               .pm             = &axp20x_usb_power_pm_ops,
        },
 };
 
index 9d1ec8d..aebd125 100644 (file)
 #define BQ25895_ID                     7
 #define BQ25896_ID                     0
 
+enum bq25890_chip_version {
+       BQ25890,
+       BQ25892,
+       BQ25895,
+       BQ25896,
+};
+
 enum bq25890_fields {
        F_EN_HIZ, F_EN_ILIM, F_IILIM,                                /* Reg00 */
        F_BHOT, F_BCOLD, F_VINDPM_OFS,                               /* Reg01 */
        F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
        F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN,          /* Reg02 */
-       F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN,    /* Reg03 */
+       F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN,
+       F_MIN_VBAT_SEL,                                              /* Reg03 */
        F_PUMPX_EN, F_ICHG,                                          /* Reg04 */
        F_IPRECHG, F_ITERM,                                          /* Reg05 */
        F_VREG, F_BATLOWV, F_VRECHG,                                 /* Reg06 */
@@ -39,8 +47,9 @@ enum bq25890_fields {
        F_BATCMP, F_VCLAMP, F_TREG,                                  /* Reg08 */
        F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET,
        F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN,       /* Reg09 */
-       F_BOOSTV, F_BOOSTI,                                          /* Reg0A */
-       F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */
+       F_BOOSTV, F_PFM_OTG_DIS, F_BOOSTI,                           /* Reg0A */
+       F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_0B_RSVD,
+       F_VSYS_STAT,                                                 /* Reg0B */
        F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT,
        F_NTC_FAULT,                                                 /* Reg0C */
        F_FORCE_VINDPM, F_VINDPM,                                    /* Reg0D */
@@ -91,7 +100,7 @@ struct bq25890_device {
        struct regmap *rmap;
        struct regmap_field *rmap_fields[F_MAX_FIELDS];
 
-       int chip_id;
+       enum bq25890_chip_version chip_version;
        struct bq25890_init_data init_data;
        struct bq25890_state state;
 
@@ -111,8 +120,7 @@ static const struct regmap_access_table bq25890_writeable_regs = {
 static const struct regmap_range bq25890_volatile_reg_ranges[] = {
        regmap_reg_range(0x00, 0x00),
        regmap_reg_range(0x09, 0x09),
-       regmap_reg_range(0x0b, 0x0c),
-       regmap_reg_range(0x0e, 0x14),
+       regmap_reg_range(0x0b, 0x14),
 };
 
 static const struct regmap_access_table bq25890_volatile_regs = {
@@ -155,7 +163,7 @@ static const struct reg_field bq25890_reg_fields[] = {
        [F_OTG_CFG]             = REG_FIELD(0x03, 5, 5),
        [F_CHG_CFG]             = REG_FIELD(0x03, 4, 4),
        [F_SYSVMIN]             = REG_FIELD(0x03, 1, 3),
-       /* MIN_VBAT_SEL on BQ25896 */
+       [F_MIN_VBAT_SEL]        = REG_FIELD(0x03, 0, 0), // BQ25896 only
        /* REG04 */
        [F_PUMPX_EN]            = REG_FIELD(0x04, 7, 7),
        [F_ICHG]                = REG_FIELD(0x04, 0, 6),
@@ -188,8 +196,8 @@ static const struct reg_field bq25890_reg_fields[] = {
        [F_PUMPX_DN]            = REG_FIELD(0x09, 0, 0),
        /* REG0A */
        [F_BOOSTV]              = REG_FIELD(0x0A, 4, 7),
-       /* PFM_OTG_DIS 3 on BQ25896 */
        [F_BOOSTI]              = REG_FIELD(0x0A, 0, 2), // reserved on BQ25895
+       [F_PFM_OTG_DIS]         = REG_FIELD(0x0A, 3, 3), // BQ25896 only
        /* REG0B */
        [F_VBUS_STAT]           = REG_FIELD(0x0B, 5, 7),
        [F_CHG_STAT]            = REG_FIELD(0x0B, 3, 4),
@@ -275,6 +283,7 @@ static const union {
        struct bq25890_lookup lt;
 } bq25890_tables[] = {
        /* range tables */
+       /* TODO: BQ25896 has max ICHG 3008 mA */
        [TBL_ICHG] =    { .rt = {0,       5056000, 64000} },     /* uA */
        [TBL_ITERM] =   { .rt = {64000,   1024000, 64000} },     /* uA */
        [TBL_VREG] =    { .rt = {3840000, 4608000, 16000} },     /* uV */
@@ -391,11 +400,13 @@ static int bq25890_power_supply_get_property(struct power_supply *psy,
                break;
 
        case POWER_SUPPLY_PROP_MODEL_NAME:
-               if (bq->chip_id == BQ25890_ID)
+               if (bq->chip_version == BQ25890)
                        val->strval = "BQ25890";
-               else if (bq->chip_id == BQ25895_ID)
+               else if (bq->chip_version == BQ25892)
+                       val->strval = "BQ25892";
+               else if (bq->chip_version == BQ25895)
                        val->strval = "BQ25895";
-               else if (bq->chip_id == BQ25896_ID)
+               else if (bq->chip_version == BQ25896)
                        val->strval = "BQ25896";
                else
                        val->strval = "UNKNOWN";
@@ -741,6 +752,56 @@ static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
        return NOTIFY_OK;
 }
 
+static int bq25890_get_chip_version(struct bq25890_device *bq)
+{
+       int id, rev;
+
+       id = bq25890_field_read(bq, F_PN);
+       if (id < 0) {
+               dev_err(bq->dev, "Cannot read chip ID.\n");
+               return id;
+       }
+
+       rev = bq25890_field_read(bq, F_DEV_REV);
+       if (rev < 0) {
+               dev_err(bq->dev, "Cannot read chip revision.\n");
+               return rev;
+       }
+
+       switch (id) {
+       case BQ25890_ID:
+               bq->chip_version = BQ25890;
+               break;
+
+       /* BQ25892 and BQ25896 share same ID 0 */
+       case BQ25896_ID:
+               switch (rev) {
+               case 2:
+                       bq->chip_version = BQ25896;
+                       break;
+               case 1:
+                       bq->chip_version = BQ25892;
+                       break;
+               default:
+                       dev_err(bq->dev,
+                               "Unknown device revision %d, assume BQ25892\n",
+                               rev);
+                       bq->chip_version = BQ25892;
+               }
+               break;
+
+       case BQ25895_ID:
+               bq->chip_version = BQ25895;
+               break;
+
+       default:
+               dev_err(bq->dev, "Unknown chip ID %d\n", id);
+               return -ENODEV;
+       }
+
+       return 0;
+}
+
 static int bq25890_irq_probe(struct bq25890_device *bq)
 {
        struct gpio_desc *irq;
@@ -859,16 +920,10 @@ static int bq25890_probe(struct i2c_client *client,
 
        i2c_set_clientdata(client, bq);
 
-       bq->chip_id = bq25890_field_read(bq, F_PN);
-       if (bq->chip_id < 0) {
-               dev_err(dev, "Cannot read chip ID.\n");
-               return bq->chip_id;
-       }
-
-       if ((bq->chip_id != BQ25890_ID) && (bq->chip_id != BQ25895_ID)
-                       && (bq->chip_id != BQ25896_ID)) {
-               dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
-               return -ENODEV;
+       ret = bq25890_get_chip_version(bq);
+       if (ret) {
+               dev_err(dev, "Cannot read chip ID or unknown chip.\n");
+               return ret;
        }
 
        if (!dev->platform_data) {
@@ -986,12 +1041,18 @@ static const struct dev_pm_ops bq25890_pm = {
 
 static const struct i2c_device_id bq25890_i2c_ids[] = {
        { "bq25890", 0 },
+       { "bq25892", 0 },
+       { "bq25895", 0 },
+       { "bq25896", 0 },
        {},
 };
 MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
 
 static const struct of_device_id bq25890_of_match[] = {
        { .compatible = "ti,bq25890", },
+       { .compatible = "ti,bq25892", },
+       { .compatible = "ti,bq25895", },
+       { .compatible = "ti,bq25896", },
        { },
 };
 MODULE_DEVICE_TABLE(of, bq25890_of_match);
index 6cc7c39..ffad9ee 100644 (file)
@@ -132,11 +132,8 @@ static int cros_usbpd_charger_get_num_ports(struct charger_data *charger)
        ret = cros_usbpd_charger_ec_command(charger, 0,
                                            EC_CMD_CHARGE_PORT_COUNT,
                                            NULL, 0, &resp, sizeof(resp));
-       if (ret < 0) {
-               dev_err(charger->dev,
-                       "Unable to get the number of ports (err:0x%x)\n", ret);
+       if (ret < 0)
                return ret;
-       }
 
        return resp.port_count;
 }
@@ -148,11 +145,8 @@ static int cros_usbpd_charger_get_usbpd_num_ports(struct charger_data *charger)
 
        ret = cros_usbpd_charger_ec_command(charger, 0, EC_CMD_USB_PD_PORTS,
                                            NULL, 0, &resp, sizeof(resp));
-       if (ret < 0) {
-               dev_err(charger->dev,
-                       "Unable to get the number or ports (err:0x%x)\n", ret);
+       if (ret < 0)
                return ret;
-       }
 
        return resp.num_ports;
 }
index 35816d4..2748715 100644 (file)
@@ -100,10 +100,17 @@ static int ingenic_battery_set_scale(struct ingenic_battery *bat)
                return -EINVAL;
        }
 
-       return iio_write_channel_attribute(bat->channel,
-                                          scale_raw[best_idx],
-                                          scale_raw[best_idx + 1],
-                                          IIO_CHAN_INFO_SCALE);
+       /* Only set scale if there is more than one (fractional) entry */
+       if (scale_len > 2) {
+               ret = iio_write_channel_attribute(bat->channel,
+                                                 scale_raw[best_idx],
+                                                 scale_raw[best_idx + 1],
+                                                 IIO_CHAN_INFO_SCALE);
+               if (ret)
+                       return ret;
+       }
+
+       return 0;
 }
 
 static enum power_supply_property ingenic_battery_properties[] = {
index 03592ce..192d9db 100644 (file)
@@ -149,7 +149,7 @@ static int micro_batt_get_property(struct power_supply *b,
                default:
                        val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
                        break;
-               };
+               }
                break;
        case POWER_SUPPLY_PROP_STATUS:
                val->intval = get_status(b);
@@ -168,7 +168,7 @@ static int micro_batt_get_property(struct power_supply *b,
                break;
        default:
                return -EINVAL;
-       };
+       }
 
        return 0;
 }
@@ -185,7 +185,7 @@ static int micro_ac_get_property(struct power_supply *b,
                break;
        default:
                return -EINVAL;
-       };
+       }
 
        return 0;
 }
index da49436..30a9014 100644 (file)
@@ -449,7 +449,7 @@ static int ltc294x_i2c_remove(struct i2c_client *client)
 {
        struct ltc294x_info *info = i2c_get_clientdata(client);
 
-       cancel_delayed_work(&info->work);
+       cancel_delayed_work_sync(&info->work);
        power_supply_unregister(info->supply);
        return 0;
 }
index 6249901..8a1f0ee 100644 (file)
@@ -13,6 +13,7 @@
 #include <linux/err.h>
 #include <linux/i2c.h>
 #include <linux/delay.h>
+#include <linux/interrupt.h>
 #include <linux/power_supply.h>
 #include <linux/max17040_battery.h>
 #include <linux/slab.h>
@@ -28,6 +29,9 @@
 #define MAX17040_DELAY         1000
 #define MAX17040_BATTERY_FULL  95
 
+#define MAX17040_ATHD_MASK             0xFFC0
+#define MAX17040_ATHD_DEFAULT_POWER_UP 4
+
 struct max17040_chip {
        struct i2c_client               *client;
        struct delayed_work             work;
@@ -42,6 +46,8 @@ struct max17040_chip {
        int soc;
        /* State Of Charge */
        int status;
+       /* Low alert threshold from 32% to 1% of the State of Charge */
+       u32 low_soc_alert;
 };
 
 static int max17040_get_property(struct power_supply *psy,
@@ -98,6 +104,21 @@ static void max17040_reset(struct i2c_client *client)
        max17040_write_reg(client, MAX17040_CMD, 0x0054);
 }
 
+static int max17040_set_low_soc_alert(struct i2c_client *client, u32 level)
+{
+       int ret;
+       u16 data;
+
+       level = 32 - level;
+       data = max17040_read_reg(client, MAX17040_RCOMP);
+       /* clear the alrt bit and set LSb 5 bits */
+       data &= MAX17040_ATHD_MASK;
+       data |= level;
+       ret = max17040_write_reg(client, MAX17040_RCOMP, data);
+
+       return ret;
+}
+
 static void max17040_get_vcell(struct i2c_client *client)
 {
        struct max17040_chip *chip = i2c_get_clientdata(client);
@@ -160,21 +181,81 @@ static void max17040_get_status(struct i2c_client *client)
                chip->status = POWER_SUPPLY_STATUS_FULL;
 }
 
+static int max17040_get_of_data(struct max17040_chip *chip)
+{
+       struct device *dev = &chip->client->dev;
+
+       chip->low_soc_alert = MAX17040_ATHD_DEFAULT_POWER_UP;
+       device_property_read_u32(dev,
+                                "maxim,alert-low-soc-level",
+                                &chip->low_soc_alert);
+
+       if (chip->low_soc_alert <= 0 || chip->low_soc_alert >= 33)
+               return -EINVAL;
+
+       return 0;
+}
+
+static void max17040_check_changes(struct i2c_client *client)
+{
+       max17040_get_vcell(client);
+       max17040_get_soc(client);
+       max17040_get_online(client);
+       max17040_get_status(client);
+}
+
 static void max17040_work(struct work_struct *work)
 {
        struct max17040_chip *chip;
+       int last_soc, last_status;
 
        chip = container_of(work, struct max17040_chip, work.work);
 
-       max17040_get_vcell(chip->client);
-       max17040_get_soc(chip->client);
-       max17040_get_online(chip->client);
-       max17040_get_status(chip->client);
+       /* store SOC and status to check changes */
+       last_soc = chip->soc;
+       last_status = chip->status;
+       max17040_check_changes(chip->client);
+
+       /* check changes and send uevent */
+       if (last_soc != chip->soc || last_status != chip->status)
+               power_supply_changed(chip->battery);
 
        queue_delayed_work(system_power_efficient_wq, &chip->work,
                           MAX17040_DELAY);
 }
 
+static irqreturn_t max17040_thread_handler(int id, void *dev)
+{
+       struct max17040_chip *chip = dev;
+       struct i2c_client *client = chip->client;
+
+       dev_warn(&client->dev, "IRQ: Alert battery low level");
+       /* read registers */
+       max17040_check_changes(chip->client);
+
+       /* send uevent */
+       power_supply_changed(chip->battery);
+
+       /* reset alert bit */
+       max17040_set_low_soc_alert(client, chip->low_soc_alert);
+
+       return IRQ_HANDLED;
+}
+
+static int max17040_enable_alert_irq(struct max17040_chip *chip)
+{
+       struct i2c_client *client = chip->client;
+       unsigned int flags;
+       int ret;
+
+       flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
+       ret = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+                                       max17040_thread_handler, flags,
+                                       chip->battery->desc->name, chip);
+
+       return ret;
+}
+
 static enum power_supply_property max17040_battery_props[] = {
        POWER_SUPPLY_PROP_STATUS,
        POWER_SUPPLY_PROP_ONLINE,
@@ -196,6 +277,7 @@ static int max17040_probe(struct i2c_client *client,
        struct i2c_adapter *adapter = client->adapter;
        struct power_supply_config psy_cfg = {};
        struct max17040_chip *chip;
+       int ret;
 
        if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
                return -EIO;
@@ -206,6 +288,12 @@ static int max17040_probe(struct i2c_client *client,
 
        chip->client = client;
        chip->pdata = client->dev.platform_data;
+       ret = max17040_get_of_data(chip);
+       if (ret) {
+               dev_err(&client->dev,
+                       "failed: low SOC alert OF data out of bounds\n");
+               return ret;
+       }
 
        i2c_set_clientdata(client, chip);
        psy_cfg.drv_data = chip;
@@ -220,6 +308,24 @@ static int max17040_probe(struct i2c_client *client,
        max17040_reset(client);
        max17040_get_version(client);
 
+       /* check interrupt */
+       if (client->irq && of_device_is_compatible(client->dev.of_node,
+                                                  "maxim,max77836-battery")) {
+               ret = max17040_set_low_soc_alert(client, chip->low_soc_alert);
+               if (ret) {
+                       dev_err(&client->dev,
+                               "Failed to set low SOC alert: err %d\n", ret);
+                       return ret;
+               }
+
+               ret = max17040_enable_alert_irq(chip);
+               if (ret) {
+                       client->irq = 0;
+                       dev_warn(&client->dev,
+                                "Failed to get IRQ err %d\n", ret);
+               }
+       }
+
        INIT_DEFERRABLE_WORK(&chip->work, max17040_work);
        queue_delayed_work(system_power_efficient_wq, &chip->work,
                           MAX17040_DELAY);
@@ -244,6 +350,10 @@ static int max17040_suspend(struct device *dev)
        struct max17040_chip *chip = i2c_get_clientdata(client);
 
        cancel_delayed_work(&chip->work);
+
+       if (client->irq && device_may_wakeup(dev))
+               enable_irq_wake(client->irq);
+
        return 0;
 }
 
@@ -254,6 +364,10 @@ static int max17040_resume(struct device *dev)
 
        queue_delayed_work(system_power_efficient_wq, &chip->work,
                           MAX17040_DELAY);
+
+       if (client->irq && device_may_wakeup(dev))
+               disable_irq_wake(client->irq);
+
        return 0;
 }
 
index 0dfad2c..69ec429 100644 (file)
@@ -282,6 +282,8 @@ static int max17042_get_property(struct power_supply *psy,
        case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
                if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
                        ret = regmap_read(map, MAX17042_V_empty, &data);
+               else if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+                       ret = regmap_read(map, MAX17055_V_empty, &data);
                else
                        ret = regmap_read(map, MAX17047_V_empty, &data);
                if (ret < 0)
@@ -627,7 +629,8 @@ static void max17042_write_config_regs(struct max17042_chip *chip)
                        config->filter_cfg);
        regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
        if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
-                       chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)
+                       chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050 ||
+                       chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
                regmap_write(map, MAX17047_FullSOCThr,
                                                config->full_soc_thresh);
 }
@@ -758,6 +761,8 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
 
        if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
                max17042_override_por(map, MAX17042_V_empty, config->vempty);
+       if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17055)
+               max17042_override_por(map, MAX17055_V_empty, config->vempty);
        else
                max17042_override_por(map, MAX17047_V_empty, config->vempty);
        max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
@@ -765,7 +770,10 @@ static inline void max17042_override_por_values(struct max17042_chip *chip)
        max17042_override_por(map, MAX17042_FCTC, config->fctc);
        max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
        max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
-       if (chip->chip_type) {
+       if (chip->chip_type &&
+           ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) ||
+           (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+           (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050))) {
                max17042_override_por(map, MAX17042_EmptyTempCo,
                                                config->empty_tempco);
                max17042_override_por(map, MAX17042_K_empty0,
@@ -929,7 +937,8 @@ max17042_get_default_pdata(struct max17042_chip *chip)
        if (!pdata)
                return pdata;
 
-       if (chip->chip_type != MAXIM_DEVICE_TYPE_MAX17042) {
+       if ((chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047) ||
+           (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)) {
                pdata->init_data = max17047_default_pdata_init_regs;
                pdata->num_init_data =
                        ARRAY_SIZE(max17047_default_pdata_init_regs);
@@ -1167,6 +1176,7 @@ static const struct of_device_id max17042_dt_match[] = {
        { .compatible = "maxim,max17042" },
        { .compatible = "maxim,max17047" },
        { .compatible = "maxim,max17050" },
+       { .compatible = "maxim,max17055" },
        { },
 };
 MODULE_DEVICE_TABLE(of, max17042_dt_match);
@@ -1176,6 +1186,7 @@ static const struct i2c_device_id max17042_id[] = {
        { "max17042", MAXIM_DEVICE_TYPE_MAX17042 },
        { "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
        { "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
+       { "max17055", MAXIM_DEVICE_TYPE_MAX17055 },
        { }
 };
 MODULE_DEVICE_TABLE(i2c, max17042_id);
index 5f9477c..d913428 100644 (file)
@@ -354,9 +354,16 @@ static int max77650_charger_remove(struct platform_device *pdev)
        return max77650_charger_disable(chg);
 }
 
+static const struct of_device_id max77650_charger_of_match[] = {
+       { .compatible = "maxim,max77650-charger" },
+       { }
+};
+MODULE_DEVICE_TABLE(of, max77650_charger_of_match);
+
 static struct platform_driver max77650_charger_driver = {
        .driver = {
                .name = "max77650-charger",
+               .of_match_table = max77650_charger_of_match,
        },
        .probe = max77650_charger_probe,
        .remove = max77650_charger_remove,
index 3ae5707..03a37fd 100644 (file)
@@ -429,6 +429,10 @@ wrongid:
 
 static int pda_power_remove(struct platform_device *pdev)
 {
+#if IS_ENABLED(CONFIG_USB_PHY)
+       if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier)
+               usb_unregister_notifier(transceiver, &otg_nb);
+#endif
        if (pdata->is_usb_online && usb_irq)
                free_irq(usb_irq->start, pda_psy_usb);
        if (pdata->is_ac_online && ac_irq)
index 5c36c43..1a9a9fa 100644 (file)
@@ -565,9 +565,11 @@ EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
 int power_supply_get_battery_info(struct power_supply *psy,
                                  struct power_supply_battery_info *info)
 {
+       struct power_supply_resistance_temp_table *resist_table;
        struct device_node *battery_np;
        const char *value;
        int err, len, index;
+       const __be32 *list;
 
        info->energy_full_design_uwh         = -EINVAL;
        info->charge_full_design_uah         = -EINVAL;
@@ -578,6 +580,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
        info->constant_charge_current_max_ua = -EINVAL;
        info->constant_charge_voltage_max_uv = -EINVAL;
        info->factory_internal_resistance_uohm  = -EINVAL;
+       info->resist_table = NULL;
 
        for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
                info->ocv_table[index]       = NULL;
@@ -644,7 +647,6 @@ int power_supply_get_battery_info(struct power_supply *psy,
        for (index = 0; index < len; index++) {
                struct power_supply_battery_ocv_table *table;
                char *propname;
-               const __be32 *list;
                int i, tab_len, size;
 
                propname = kasprintf(GFP_KERNEL, "ocv-capacity-table-%d", index);
@@ -677,6 +679,26 @@ int power_supply_get_battery_info(struct power_supply *psy,
                }
        }
 
+       list = of_get_property(battery_np, "resistance-temp-table", &len);
+       if (!list || !len)
+               goto out_put_node;
+
+       info->resist_table_size = len / (2 * sizeof(__be32));
+       resist_table = info->resist_table = devm_kcalloc(&psy->dev,
+                                                        info->resist_table_size,
+                                                        sizeof(*resist_table),
+                                                        GFP_KERNEL);
+       if (!info->resist_table) {
+               power_supply_put_battery_info(psy, info);
+               err = -ENOMEM;
+               goto out_put_node;
+       }
+
+       for (index = 0; index < info->resist_table_size; index++) {
+               resist_table[index].temp = be32_to_cpu(*list++);
+               resist_table[index].resistance = be32_to_cpu(*list++);
+       }
+
 out_put_node:
        of_node_put(battery_np);
        return err;
@@ -692,9 +714,52 @@ void power_supply_put_battery_info(struct power_supply *psy,
                if (info->ocv_table[i])
                        devm_kfree(&psy->dev, info->ocv_table[i]);
        }
+
+       if (info->resist_table)
+               devm_kfree(&psy->dev, info->resist_table);
 }
 EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
 
+/**
+ * power_supply_temp2resist_simple() - find the battery internal resistance
+ * percent
+ * @table: Pointer to battery resistance temperature table
+ * @table_len: The table length
+ * @ocv: Current temperature
+ *
+ * This helper function is used to look up battery internal resistance percent
+ * according to current temperature value from the resistance temperature table,
+ * and the table must be ordered descending. Then the actual battery internal
+ * resistance = the ideal battery internal resistance * percent / 100.
+ *
+ * Return: the battery internal resistance percent
+ */
+int power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
+                                   int table_len, int temp)
+{
+       int i, resist;
+
+       for (i = 0; i < table_len; i++)
+               if (temp > table[i].temp)
+                       break;
+
+       if (i > 0 && i < table_len) {
+               int tmp;
+
+               tmp = (table[i - 1].resistance - table[i].resistance) *
+                       (temp - table[i].temp);
+               tmp /= table[i - 1].temp - table[i].temp;
+               resist = tmp + table[i].resistance;
+       } else if (i == 0) {
+               resist = table[0].resistance;
+       } else {
+               resist = table[table_len - 1].resistance;
+       }
+
+       return resist;
+}
+EXPORT_SYMBOL_GPL(power_supply_temp2resist_simple);
+
 /**
  * power_supply_ocv2cap_simple() - find the battery capacity
  * @table: Pointer to battery OCV lookup table
index f8d74e9..6acd242 100644 (file)
@@ -5,6 +5,7 @@
  * Copyright (c) 2010, NVIDIA Corporation.
  */
 
+#include <linux/bits.h>
 #include <linux/delay.h>
 #include <linux/err.h>
 #include <linux/gpio/consumer.h>
@@ -46,10 +47,10 @@ enum {
 
 /* Battery Mode defines */
 #define BATTERY_MODE_OFFSET            0x03
-#define BATTERY_MODE_MASK              0x8000
-enum sbs_battery_mode {
-       BATTERY_MODE_AMPS = 0,
-       BATTERY_MODE_WATTS = 0x8000
+#define BATTERY_MODE_CAPACITY_MASK     BIT(15)
+enum sbs_capacity_mode {
+       CAPACITY_MODE_AMPS = 0,
+       CAPACITY_MODE_WATTS = BATTERY_MODE_CAPACITY_MASK
 };
 
 /* manufacturer access defines */
@@ -518,8 +519,8 @@ static void  sbs_unit_adjustment(struct i2c_client *client,
        }
 }
 
-static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
-       enum sbs_battery_mode mode)
+static enum sbs_capacity_mode sbs_set_capacity_mode(struct i2c_client *client,
+       enum sbs_capacity_mode mode)
 {
        int ret, original_val;
 
@@ -527,13 +528,13 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
        if (original_val < 0)
                return original_val;
 
-       if ((original_val & BATTERY_MODE_MASK) == mode)
+       if ((original_val & BATTERY_MODE_CAPACITY_MASK) == mode)
                return mode;
 
-       if (mode == BATTERY_MODE_AMPS)
-               ret = original_val & ~BATTERY_MODE_MASK;
+       if (mode == CAPACITY_MODE_AMPS)
+               ret = original_val & ~BATTERY_MODE_CAPACITY_MASK;
        else
-               ret = original_val | BATTERY_MODE_MASK;
+               ret = original_val | BATTERY_MODE_CAPACITY_MASK;
 
        ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret);
        if (ret < 0)
@@ -541,7 +542,7 @@ static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
 
        usleep_range(1000, 2000);
 
-       return original_val & BATTERY_MODE_MASK;
+       return original_val & BATTERY_MODE_CAPACITY_MASK;
 }
 
 static int sbs_get_battery_capacity(struct i2c_client *client,
@@ -549,13 +550,13 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
        union power_supply_propval *val)
 {
        s32 ret;
-       enum sbs_battery_mode mode = BATTERY_MODE_WATTS;
+       enum sbs_capacity_mode mode = CAPACITY_MODE_WATTS;
 
        if (power_supply_is_amp_property(psp))
-               mode = BATTERY_MODE_AMPS;
+               mode = CAPACITY_MODE_AMPS;
 
-       mode = sbs_set_battery_mode(client, mode);
-       if (mode < 0)
+       mode = sbs_set_capacity_mode(client, mode);
+       if ((int)mode < 0)
                return mode;
 
        ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
@@ -564,7 +565,7 @@ static int sbs_get_battery_capacity(struct i2c_client *client,
 
        val->intval = ret;
 
-       ret = sbs_set_battery_mode(client, mode);
+       ret = sbs_set_capacity_mode(client, mode);
        if (ret < 0)
                return ret;
 
@@ -1001,6 +1002,6 @@ module_i2c_driver(sbs_battery_driver);
 MODULE_DESCRIPTION("SBS battery monitor driver");
 MODULE_LICENSE("GPL");
 
-module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH);
+module_param(force_load, bool, 0444);
 MODULE_PARM_DESC(force_load,
                 "Attempt to load the driver even if no battery is connected");
index bc8f5bd..469c83f 100644 (file)
@@ -62,6 +62,8 @@
 
 #define SC27XX_FGU_CUR_BASIC_ADC       8192
 #define SC27XX_FGU_SAMPLE_HZ           2
+/* micro Ohms */
+#define SC27XX_FGU_IDEAL_RESISTANCE    20000
 
 /*
  * struct sc27xx_fgu_data: describe the FGU device
  * @max_volt: the maximum constant input voltage in millivolt
  * @min_volt: the minimum drained battery voltage in microvolt
  * @table_len: the capacity table length
+ * @resist_table_len: the resistance table length
  * @cur_1000ma_adc: ADC value corresponding to 1000 mA
  * @vol_1000mv_adc: ADC value corresponding to 1000 mV
+ * @calib_resist: the real resistance of coulomb counter chip in uOhm
  * @cap_table: capacity table with corresponding ocv
+ * @resist_table: resistance percent table with corresponding temperature
  */
 struct sc27xx_fgu_data {
        struct regmap *regmap;
@@ -103,15 +108,19 @@ struct sc27xx_fgu_data {
        int max_volt;
        int min_volt;
        int table_len;
+       int resist_table_len;
        int cur_1000ma_adc;
        int vol_1000mv_adc;
+       int calib_resist;
        struct power_supply_battery_ocv_table *cap_table;
+       struct power_supply_resistance_temp_table *resist_table;
 };
 
 static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity);
 static void sc27xx_fgu_capacity_calibration(struct sc27xx_fgu_data *data,
                                            int cap, bool int_mode);
 static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap);
+static int sc27xx_fgu_get_temp(struct sc27xx_fgu_data *data, int *temp);
 
 static const char * const sc27xx_charger_supply_name[] = {
        "sc2731_charger",
@@ -434,7 +443,7 @@ static int sc27xx_fgu_get_current(struct sc27xx_fgu_data *data, int *val)
 
 static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
 {
-       int vol, cur, ret;
+       int vol, cur, ret, temp, resistance;
 
        ret = sc27xx_fgu_get_vbat_vol(data, &vol);
        if (ret)
@@ -444,8 +453,19 @@ static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
        if (ret)
                return ret;
 
+       resistance = data->internal_resist;
+       if (data->resist_table_len > 0) {
+               ret = sc27xx_fgu_get_temp(data, &temp);
+               if (ret)
+                       return ret;
+
+               resistance = power_supply_temp2resist_simple(data->resist_table,
+                                               data->resist_table_len, temp);
+               resistance = data->internal_resist * resistance / 100;
+       }
+
        /* Return the battery OCV in micro volts. */
-       *val = vol * 1000 - cur * data->internal_resist;
+       *val = vol * 1000 - cur * resistance;
 
        return 0;
 }
@@ -884,7 +904,9 @@ static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data)
         */
        cal_4200mv = (calib_data & 0x1ff) + 6963 - 4096 - 256;
        data->vol_1000mv_adc = DIV_ROUND_CLOSEST(cal_4200mv * 10, 42);
-       data->cur_1000ma_adc = data->vol_1000mv_adc * 4;
+       data->cur_1000ma_adc =
+               DIV_ROUND_CLOSEST(data->vol_1000mv_adc * 4 * data->calib_resist,
+                                 SC27XX_FGU_IDEAL_RESISTANCE);
 
        kfree(buf);
        return 0;
@@ -929,6 +951,18 @@ static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data)
        if (!data->alarm_cap)
                data->alarm_cap += 1;
 
+       data->resist_table_len = info.resist_table_size;
+       if (data->resist_table_len > 0) {
+               data->resist_table = devm_kmemdup(data->dev, info.resist_table,
+                                                 data->resist_table_len *
+                                                 sizeof(struct power_supply_resistance_temp_table),
+                                                 GFP_KERNEL);
+               if (!data->resist_table) {
+                       power_supply_put_battery_info(data->battery, &info);
+                       return -ENOMEM;
+               }
+       }
+
        power_supply_put_battery_info(data->battery, &info);
 
        ret = sc27xx_fgu_calibration(data);
@@ -1051,6 +1085,15 @@ static int sc27xx_fgu_probe(struct platform_device *pdev)
                return ret;
        }
 
+       ret = device_property_read_u32(&pdev->dev,
+                                      "sprd,calib-resistance-micro-ohms",
+                                      &data->calib_resist);
+       if (ret) {
+               dev_err(&pdev->dev,
+                       "failed to get fgu calibration resistance\n");
+               return ret;
+       }
+
        data->channel = devm_iio_channel_get(dev, "bat-temp");
        if (IS_ERR(data->channel)) {
                dev_err(dev, "failed to get IIO channel\n");
index 1b80ae4..cdb9a23 100644 (file)
@@ -100,7 +100,9 @@ struct ucs1002_info {
        struct i2c_client *client;
        struct regmap *regmap;
        struct regulator_desc *regulator_descriptor;
+       struct regulator_dev *rdev;
        bool present;
+       bool output_disable;
 };
 
 static enum power_supply_property ucs1002_props[] = {
@@ -233,6 +235,11 @@ static int ucs1002_get_max_current(struct ucs1002_info *info,
        unsigned int reg;
        int ret;
 
+       if (info->output_disable) {
+               val->intval = 0;
+               return 0;
+       }
+
        ret = regmap_read(info->regmap, UCS1002_REG_ILIMIT, &reg);
        if (ret)
                return ret;
@@ -247,6 +254,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val)
        unsigned int reg;
        int ret, idx;
 
+       if (val == 0) {
+               info->output_disable = true;
+               regulator_disable_regmap(info->rdev);
+               return 0;
+       }
+
        for (idx = 0; idx < ARRAY_SIZE(ucs1002_current_limit_uA); idx++) {
                if (val == ucs1002_current_limit_uA[idx])
                        break;
@@ -270,6 +283,12 @@ static int ucs1002_set_max_current(struct ucs1002_info *info, u32 val)
        if (reg != idx)
                return -EINVAL;
 
+       info->output_disable = false;
+
+       if (info->rdev && info->rdev->use_count &&
+           !regulator_is_enabled_regmap(info->rdev))
+               regulator_enable_regmap(info->rdev);
+
        return 0;
 }
 
@@ -470,9 +489,24 @@ static irqreturn_t ucs1002_alert_irq(int irq, void *data)
        return IRQ_HANDLED;
 }
 
+static int ucs1002_regulator_enable(struct regulator_dev *rdev)
+{
+       struct ucs1002_info *info = rdev_get_drvdata(rdev);
+
+       /*
+        * If the output is disabled due to 0 maximum current, just pretend the
+        * enable did work. The regulator will be enabled as soon as we get a
+        * a non-zero maximum current budget.
+        */
+       if (info->output_disable)
+               return 0;
+
+       return regulator_enable_regmap(rdev);
+}
+
 static const struct regulator_ops ucs1002_regulator_ops = {
        .is_enabled     = regulator_is_enabled_regmap,
-       .enable         = regulator_enable_regmap,
+       .enable         = ucs1002_regulator_enable,
        .disable        = regulator_disable_regmap,
 };
 
@@ -499,7 +533,6 @@ static int ucs1002_probe(struct i2c_client *client,
        };
        struct regulator_config regulator_config = {};
        int irq_a_det, irq_alert, ret;
-       struct regulator_dev *rdev;
        struct ucs1002_info *info;
        unsigned int regval;
 
@@ -589,10 +622,11 @@ static int ucs1002_probe(struct i2c_client *client,
        regulator_config.dev = dev;
        regulator_config.of_node = dev->of_node;
        regulator_config.regmap = info->regmap;
+       regulator_config.driver_data = info;
 
-       rdev = devm_regulator_register(dev, info->regulator_descriptor,
+       info->rdev = devm_regulator_register(dev, info->regulator_descriptor,
                                       &regulator_config);
-       ret = PTR_ERR_OR_ZERO(rdev);
+       ret = PTR_ERR_OR_ZERO(info->rdev);
        if (ret) {
                dev_err(dev, "Failed to register VBUS regulator: %d\n", ret);
                return ret;
index 4badd53..d55c746 100644 (file)
@@ -105,11 +105,56 @@ enum max17042_register {
 
        MAX17042_OCV            = 0xEE,
 
-       MAX17042_OCVInternal    = 0xFB,
+       MAX17042_OCVInternal    = 0xFB,  /* MAX17055 VFOCV */
 
        MAX17042_VFSOC          = 0xFF,
 };
 
+enum max17055_register {
+       MAX17055_QRes           = 0x0C,
+       MAX17055_TTF            = 0x20,
+       MAX17055_V_empty        = 0x3A,
+       MAX17055_TIMER          = 0x3E,
+       MAX17055_USER_MEM       = 0x40,
+       MAX17055_RGAIN          = 0x42,
+
+       MAX17055_ConvgCfg       = 0x49,
+       MAX17055_VFRemCap       = 0x4A,
+
+       MAX17055_STATUS2        = 0xB0,
+       MAX17055_POWER          = 0xB1,
+       MAX17055_ID             = 0xB2,
+       MAX17055_AvgPower       = 0xB3,
+       MAX17055_IAlrtTh        = 0xB4,
+       MAX17055_TTFCfg         = 0xB5,
+       MAX17055_CVMixCap       = 0xB6,
+       MAX17055_CVHalfTime     = 0xB7,
+       MAX17055_CGTempCo       = 0xB8,
+       MAX17055_Curve          = 0xB9,
+       MAX17055_HibCfg         = 0xBA,
+       MAX17055_Config2        = 0xBB,
+       MAX17055_VRipple        = 0xBC,
+       MAX17055_RippleCfg      = 0xBD,
+       MAX17055_TimerH         = 0xBE,
+
+       MAX17055_RSense         = 0xD0,
+       MAX17055_ScOcvLim       = 0xD1,
+
+       MAX17055_SOCHold        = 0xD3,
+       MAX17055_MaxPeakPwr     = 0xD4,
+       MAX17055_SusPeakPwr     = 0xD5,
+       MAX17055_PackResistance = 0xD6,
+       MAX17055_SysResistance  = 0xD7,
+       MAX17055_MinSysV        = 0xD8,
+       MAX17055_MPPCurrent     = 0xD9,
+       MAX17055_SPPCurrent     = 0xDA,
+       MAX17055_ModelCfg       = 0xDB,
+       MAX17055_AtQResidual    = 0xDC,
+       MAX17055_AtTTE          = 0xDD,
+       MAX17055_AtAvSOC        = 0xDE,
+       MAX17055_AtAvCap        = 0xDF,
+};
+
 /* Registers specific to max17047/50 */
 enum max17047_register {
        MAX17047_QRTbl00        = 0x12,
@@ -125,6 +170,7 @@ enum max170xx_chip_type {
        MAXIM_DEVICE_TYPE_MAX17042,
        MAXIM_DEVICE_TYPE_MAX17047,
        MAXIM_DEVICE_TYPE_MAX17050,
+       MAXIM_DEVICE_TYPE_MAX17055,
 
        MAXIM_DEVICE_TYPE_NUM
 };
index 28413f7..dcd5a71 100644 (file)
@@ -325,6 +325,11 @@ struct power_supply_battery_ocv_table {
        int capacity;   /* percent */
 };
 
+struct power_supply_resistance_temp_table {
+       int temp;       /* celsius */
+       int resistance; /* internal resistance percent */
+};
+
 #define POWER_SUPPLY_OCV_TEMP_MAX 20
 
 /*
@@ -349,6 +354,8 @@ struct power_supply_battery_info {
        int ocv_temp[POWER_SUPPLY_OCV_TEMP_MAX];/* celsius */
        struct power_supply_battery_ocv_table *ocv_table[POWER_SUPPLY_OCV_TEMP_MAX];
        int ocv_table_size[POWER_SUPPLY_OCV_TEMP_MAX];
+       struct power_supply_resistance_temp_table *resist_table;
+       int resist_table_size;
 };
 
 extern struct atomic_notifier_head power_supply_notifier;
@@ -381,6 +388,9 @@ power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
                                int temp, int *table_len);
 extern int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
                                        int ocv, int temp);
+extern int
+power_supply_temp2resist_simple(struct power_supply_resistance_temp_table *table,
+                               int table_len, int temp);
 extern void power_supply_changed(struct power_supply *psy);
 extern int power_supply_am_i_supplied(struct power_supply *psy);
 extern int power_supply_set_input_current_limit_from_supplier(