OSDN Git Service

Merge tag 'for-v4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux...
authorLinus Torvalds <torvalds@linux-foundation.org>
Sat, 29 Dec 2018 04:22:45 +0000 (20:22 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sat, 29 Dec 2018 04:22:45 +0000 (20:22 -0800)
Pull power supply and reset updates from Sebastian Reichel:

 - New core support:
    - battery internal resistance
    - battery OCV capacity lookup table
    - support for custom sysfs attributes

 - Convert all drivers to use power-supply core support for custom sysfs
   attributes

 - bq24190-charger: bq24196 support

 - axp20x-charger: AXP813 support

 - sc27xx-battery: new fuel gauge driver

 - gpio-poweroff: support for specific active and inactive delays

 - Misc fixes

* tag 'for-v4.21' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (53 commits)
  power: supply: bq25890: fix BAT_COMP field definition
  power: supply: gpio-charger: Do not use deprecated POWER_SUPPLY_TYPE_USB_*
  power: supply: ds2781: switch to devm_power_supply_register
  power: supply: ds2780: switch to devm_power_supply_register
  power: supply: ds2781: fix race-condition in bin attribute registration
  power: supply: ds2780: fix race-condition in bin attribute registration
  power: supply: pcf50633: fix race-condition in sysfs registration
  power: supply: charger-manager: fix race-condition in sysfs registration
  power: supply: charger-manager: simplify generation of sysfs attribute group name
  power: supply: bq24257: fix race-condition in sysfs registration
  power: supply: bq24190_charger: fix race-condition in sysfs registration
  power: supply: lp8788: fix race-condition in sysfs registration
  power: supply: ds2781: fix race-condition in sysfs registration
  power: supply: ds2780: fix race-condition in sysfs registration
  power: supply: bq2415x: fix race-condition in sysfs registration
  power: supply: core: add support for custom sysfs attributes
  power: supply: sc27xx: Save last battery capacity
  power: reset: at91-poweroff: move shdwc related data to one structure
  power: supply: sc27xx: Add suspend/resume interfaces
  power: supply: sc27xx: Add fuel gauge low voltage alarm
  ...

32 files changed:
Documentation/devicetree/bindings/power/reset/gpio-poweroff.txt
Documentation/devicetree/bindings/power/supply/axp20x_ac_power.txt
Documentation/devicetree/bindings/power/supply/battery.txt
Documentation/devicetree/bindings/power/supply/bq24190.txt
Documentation/devicetree/bindings/power/supply/sc27xx-fg.txt [new file with mode: 0644]
drivers/power/reset/at91-poweroff.c
drivers/power/reset/gpio-poweroff.c
drivers/power/reset/ocelot-reset.c
drivers/power/supply/Kconfig
drivers/power/supply/Makefile
drivers/power/supply/axp20x_ac_power.c
drivers/power/supply/axp20x_usb_power.c
drivers/power/supply/axp288_charger.c
drivers/power/supply/bq2415x_charger.c
drivers/power/supply/bq24190_charger.c
drivers/power/supply/bq24257_charger.c
drivers/power/supply/bq25890_charger.c
drivers/power/supply/charger-manager.c
drivers/power/supply/cpcap-battery.c
drivers/power/supply/cpcap-charger.c
drivers/power/supply/ds2780_battery.c
drivers/power/supply/ds2781_battery.c
drivers/power/supply/gpio-charger.c
drivers/power/supply/lp8788-charger.c
drivers/power/supply/olpc_battery.c
drivers/power/supply/pcf50633-charger.c
drivers/power/supply/power_supply_core.c
drivers/power/supply/sc2731_charger.c
drivers/power/supply/sc27xx_fuel_gauge.c [new file with mode: 0644]
include/linux/mfd/axp20x.h
include/linux/power/charger-manager.h
include/linux/power_supply.h

index 6d8980c..3e56c1b 100644 (file)
@@ -27,6 +27,8 @@ Optional properties:
   it to an output when the power-off handler is called. If this optional
   property is not specified, the GPIO is initialized as an output in its
   inactive state.
+- active-delay-ms: Delay (default 100) to wait after driving gpio active
+- inactive-delay-ms: Delay (default 100) to wait after driving gpio inactive
 - timeout-ms: Time to wait before asserting a WARN_ON(1). If nothing is
               specified, 3000 ms is used.
 
index 826e8a8..7a1fb53 100644 (file)
@@ -4,6 +4,7 @@ Required Properties:
  - compatible: One of:
                        "x-powers,axp202-ac-power-supply"
                        "x-powers,axp221-ac-power-supply"
+                       "x-powers,axp813-ac-power-supply"
 
 This node is a subnode of the axp20x PMIC.
 
@@ -13,6 +14,8 @@ reading ADC channels from the AXP20X ADC.
 The AXP22X is only able to tell if an AC power supply is present and
 usable.
 
+AXP813/AXP803 are able to limit current and supply voltage
+
 Example:
 
 &axp209 {
index f4d3b4a..89871ab 100644 (file)
@@ -22,6 +22,18 @@ Optional Properties:
  - charge-term-current-microamp: current for charge termination phase
  - constant-charge-current-max-microamp: maximum constant input current
  - constant-charge-voltage-max-microvolt: maximum constant input voltage
+ - factory-internal-resistance-micro-ohms: battery factory internal resistance
+ - ocv-capacity-table-0: An array providing the open circuit voltage (OCV)
+   of the battery and corresponding battery capacity percent, which is used
+   to look up battery capacity according to current OCV value. And the open
+   circuit voltage unit is microvolt.
+ - ocv-capacity-table-1: Same as ocv-capacity-table-0
+ ......
+ - ocv-capacity-table-n: Same as ocv-capacity-table-0
+ - ocv-capacity-celsius: An array containing the temperature in degree Celsius,
+   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.
 
 Battery properties are named, where possible, for the corresponding
 elements in enum power_supply_property, defined in
@@ -42,6 +54,11 @@ Example:
                charge-term-current-microamp = <128000>;
                constant-charge-current-max-microamp = <900000>;
                constant-charge-voltage-max-microvolt = <4200000>;
+               factory-internal-resistance-micro-ohms = <250000>;
+               ocv-capacity-celsius = <(-10) 0 10>;
+               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>, ...;
        };
 
        charger: charger@11 {
index 9e517d3..ffe2be4 100644 (file)
@@ -3,7 +3,9 @@ TI BQ24190 Li-Ion Battery Charger
 Required properties:
 - compatible: contains one of the following:
     * "ti,bq24190"
+    * "ti,bq24192"
     * "ti,bq24192i"
+    * "ti,bq24196"
 - reg: integer, I2C address of the charger.
 - interrupts[-extended]: configuration for charger INT pin.
 
@@ -19,6 +21,12 @@ Optional properties:
 - ti,system-minimum-microvolt: when power is connected and the battery is below
   minimum system voltage, the system will be regulated above this setting.
 
+child nodes:
+- usb-otg-vbus:
+  Usage: optional
+  Description: Regulator that is used to control the VBUS voltage direction for
+               either USB host mode or for charging on the OTG port.
+
 Notes:
 - Some circuit boards wire the chip's "OTG" pin high (enabling 500mA default
   charge current on USB SDP ports, among other features). To simulate this on
@@ -39,6 +47,8 @@ Example:
                interrupts-extended = <&gpiochip 10 IRQ_TYPE_EDGE_FALLING>;
                monitored-battery = <&bat>;
                ti,system-minimum-microvolt = <3200000>;
+
+               usb_otg_vbus: usb-otg-vbus { };
        };
 
        &twl_gpio {
diff --git a/Documentation/devicetree/bindings/power/supply/sc27xx-fg.txt b/Documentation/devicetree/bindings/power/supply/sc27xx-fg.txt
new file mode 100644 (file)
index 0000000..fc35ac5
--- /dev/null
@@ -0,0 +1,56 @@
+Spreadtrum SC27XX PMICs Fuel Gauge Unit Power Supply Bindings
+
+Required properties:
+- compatible: Should be one of the following:
+  "sprd,sc2720-fgu",
+  "sprd,sc2721-fgu",
+  "sprd,sc2723-fgu",
+  "sprd,sc2730-fgu",
+  "sprd,sc2731-fgu".
+- reg: The address offset of fuel gauge unit.
+- battery-detect-gpios: GPIO for battery detection.
+- io-channels: Specify the IIO ADC channel to get temperature.
+- io-channel-names: Should be "bat-temp".
+- nvmem-cells: A phandle to the calibration cells provided by eFuse device.
+- nvmem-cell-names: Should be "fgu_calib".
+- monitored-battery: Phandle of battery characteristics devicetree node.
+  See Documentation/devicetree/bindings/power/supply/battery.txt
+
+Example:
+
+       bat: battery {
+               compatible = "simple-battery";
+               charge-full-design-microamp-hours = <1900000>;
+               constant-charge-voltage-max-microvolt = <4350000>;
+               ocv-capacity-celsius = <20>;
+               ocv-capacity-table-0 = <4185000 100>, <4113000 95>, <4066000 90>,
+                                       <4022000 85>, <3983000 80>, <3949000 75>,
+                                       <3917000 70>, <3889000 65>, <3864000 60>,
+                                       <3835000 55>, <3805000 50>, <3787000 45>,
+                                       <3777000 40>, <3773000 35>, <3770000 30>,
+                                       <3765000 25>, <3752000 20>, <3724000 15>,
+                                       <3680000 10>, <3605000 5>, <3400000 0>;
+               ......
+       };
+
+       sc2731_pmic: pmic@0 {
+               compatible = "sprd,sc2731";
+               reg = <0>;
+               spi-max-frequency = <26000000>;
+               interrupts = <GIC_SPI 31 IRQ_TYPE_LEVEL_HIGH>;
+               interrupt-controller;
+               #interrupt-cells = <2>;
+               #address-cells = <1>;
+               #size-cells = <0>;
+
+               fgu@a00 {
+                       compatible = "sprd,sc2731-fgu";
+                       reg = <0xa00>;
+                       battery-detect-gpios = <&pmic_eic 9 GPIO_ACTIVE_HIGH>;
+                       io-channels = <&pmic_adc 5>;
+                       io-channel-names = "bat-temp";
+                       nvmem-cells = <&fgu_calib>;
+                       nvmem-cell-names = "fgu_calib";
+                       monitored-battery = <&bat>;
+               };
+       };
index fb2fc8f..9e74e13 100644 (file)
@@ -51,14 +51,16 @@ static const char *shdwc_wakeup_modes[] = {
        [AT91_SHDW_WKMODE0_ANYLEVEL]    = "any",
 };
 
-static void __iomem *at91_shdwc_base;
-static struct clk *sclk;
-static void __iomem *mpddrc_base;
+static struct shdwc {
+       struct clk *sclk;
+       void __iomem *shdwc_base;
+       void __iomem *mpddrc_base;
+} at91_shdwc;
 
 static void __init at91_wakeup_status(struct platform_device *pdev)
 {
        const char *reason;
-       u32 reg = readl(at91_shdwc_base + AT91_SHDW_SR);
+       u32 reg = readl(at91_shdwc.shdwc_base + AT91_SHDW_SR);
 
        /* Simple power-on, just bail out */
        if (!reg)
@@ -76,11 +78,6 @@ static void __init at91_wakeup_status(struct platform_device *pdev)
 
 static void at91_poweroff(void)
 {
-       writel(AT91_SHDW_KEY | AT91_SHDW_SHDW, at91_shdwc_base + AT91_SHDW_CR);
-}
-
-static void at91_lpddr_poweroff(void)
-{
        asm volatile(
                /* Align to cache lines */
                ".balign 32\n\t"
@@ -89,15 +86,17 @@ static void at91_lpddr_poweroff(void)
                "       ldr     r6, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
 
                /* Power down SDRAM0 */
+               "       tst     %0, #0\n\t"
+               "       beq     1f\n\t"
                "       str     %1, [%0, #" __stringify(AT91_DDRSDRC_LPR) "]\n\t"
                /* Shutdown CPU */
-               "       str     %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
+               "1:     str     %3, [%2, #" __stringify(AT91_SHDW_CR) "]\n\t"
 
                "       b       .\n\t"
                :
-               : "r" (mpddrc_base),
+               : "r" (at91_shdwc.mpddrc_base),
                  "r" cpu_to_le32(AT91_DDRSDRC_LPDDR2_PWOFF),
-                 "r" (at91_shdwc_base),
+                 "r" (at91_shdwc.shdwc_base),
                  "r" cpu_to_le32(AT91_SHDW_KEY | AT91_SHDW_SHDW)
                : "r6");
 }
@@ -147,7 +146,7 @@ static void at91_poweroff_dt_set_wakeup_mode(struct platform_device *pdev)
        if (of_property_read_bool(np, "atmel,wakeup-rtt-timer"))
                        mode |= AT91_SHDW_RTTWKEN;
 
-       writel(wakeup_mode | mode, at91_shdwc_base + AT91_SHDW_MR);
+       writel(wakeup_mode | mode, at91_shdwc.shdwc_base + AT91_SHDW_MR);
 }
 
 static int __init at91_poweroff_probe(struct platform_device *pdev)
@@ -158,15 +157,15 @@ static int __init at91_poweroff_probe(struct platform_device *pdev)
        int ret;
 
        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
-       at91_shdwc_base = devm_ioremap_resource(&pdev->dev, res);
-       if (IS_ERR(at91_shdwc_base))
-               return PTR_ERR(at91_shdwc_base);
+       at91_shdwc.shdwc_base = devm_ioremap_resource(&pdev->dev, res);
+       if (IS_ERR(at91_shdwc.shdwc_base))
+               return PTR_ERR(at91_shdwc.shdwc_base);
 
-       sclk = devm_clk_get(&pdev->dev, NULL);
-       if (IS_ERR(sclk))
-               return PTR_ERR(sclk);
+       at91_shdwc.sclk = devm_clk_get(&pdev->dev, NULL);
+       if (IS_ERR(at91_shdwc.sclk))
+               return PTR_ERR(at91_shdwc.sclk);
 
-       ret = clk_prepare_enable(sclk);
+       ret = clk_prepare_enable(at91_shdwc.sclk);
        if (ret) {
                dev_err(&pdev->dev, "Could not enable slow clock\n");
                return ret;
@@ -177,44 +176,47 @@ static int __init at91_poweroff_probe(struct platform_device *pdev)
        if (pdev->dev.of_node)
                at91_poweroff_dt_set_wakeup_mode(pdev);
 
-       pm_power_off = at91_poweroff;
-
        np = of_find_compatible_node(NULL, NULL, "atmel,sama5d3-ddramc");
-       if (!np)
-               return 0;
+       if (np) {
+               at91_shdwc.mpddrc_base = of_iomap(np, 0);
+               of_node_put(np);
 
-       mpddrc_base = of_iomap(np, 0);
-       of_node_put(np);
+               if (!at91_shdwc.mpddrc_base) {
+                       ret = -ENOMEM;
+                       goto clk_disable;
+               }
 
-       if (!mpddrc_base)
-               return 0;
+               ddr_type = readl(at91_shdwc.mpddrc_base + AT91_DDRSDRC_MDR) &
+                                AT91_DDRSDRC_MD;
+               if (ddr_type != AT91_DDRSDRC_MD_LPDDR2 &&
+                   ddr_type != AT91_DDRSDRC_MD_LPDDR3) {
+                       iounmap(at91_shdwc.mpddrc_base);
+                       at91_shdwc.mpddrc_base = NULL;
+               }
+       }
 
-       ddr_type = readl(mpddrc_base + AT91_DDRSDRC_MDR) & AT91_DDRSDRC_MD;
-       if ((ddr_type == AT91_DDRSDRC_MD_LPDDR2) ||
-           (ddr_type == AT91_DDRSDRC_MD_LPDDR3))
-               pm_power_off = at91_lpddr_poweroff;
-       else
-               iounmap(mpddrc_base);
+       pm_power_off = at91_poweroff;
 
        return 0;
+
+clk_disable:
+       clk_disable_unprepare(at91_shdwc.sclk);
+       return ret;
 }
 
 static int __exit at91_poweroff_remove(struct platform_device *pdev)
 {
-       if (pm_power_off == at91_poweroff ||
-           pm_power_off == at91_lpddr_poweroff)
+       if (pm_power_off == at91_poweroff)
                pm_power_off = NULL;
 
-       clk_disable_unprepare(sclk);
+       if (at91_shdwc.mpddrc_base)
+               iounmap(at91_shdwc.mpddrc_base);
+
+       clk_disable_unprepare(at91_shdwc.sclk);
 
        return 0;
 }
 
-static const struct of_device_id at91_ramc_of_match[] = {
-       { .compatible = "atmel,sama5d3-ddramc", },
-       { /* sentinel */ }
-};
-
 static const struct of_device_id at91_poweroff_of_match[] = {
        { .compatible = "atmel,at91sam9260-shdwc", },
        { .compatible = "atmel,at91sam9rl-shdwc", },
index 38206c3..52525b6 100644 (file)
@@ -26,6 +26,8 @@
  */
 static struct gpio_desc *reset_gpio;
 static u32 timeout = DEFAULT_TIMEOUT_MS;
+static u32 active_delay = 100;
+static u32 inactive_delay = 100;
 
 static void gpio_poweroff_do_poweroff(void)
 {
@@ -33,10 +35,11 @@ static void gpio_poweroff_do_poweroff(void)
 
        /* drive it active, also inactive->active edge */
        gpiod_direction_output(reset_gpio, 1);
-       mdelay(100);
+       mdelay(active_delay);
+
        /* drive inactive, also active->inactive edge */
        gpiod_set_value_cansleep(reset_gpio, 0);
-       mdelay(100);
+       mdelay(inactive_delay);
 
        /* drive it active, also inactive->active edge */
        gpiod_set_value_cansleep(reset_gpio, 1);
@@ -66,6 +69,9 @@ static int gpio_poweroff_probe(struct platform_device *pdev)
        else
                flags = GPIOD_OUT_LOW;
 
+       device_property_read_u32(&pdev->dev, "active-delay-ms", &active_delay);
+       device_property_read_u32(&pdev->dev, "inactive-delay-ms",
+                                &inactive_delay);
        device_property_read_u32(&pdev->dev, "timeout-ms", &timeout);
 
        reset_gpio = devm_gpiod_get(&pdev->dev, NULL, flags);
index 5a13a5c..419952c 100644 (file)
@@ -26,6 +26,13 @@ struct ocelot_reset_context {
 
 #define SOFT_CHIP_RST BIT(0)
 
+#define ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL  0x24
+#define IF_SI_OWNER_MASK                       GENMASK(1, 0)
+#define IF_SI_OWNER_SISL                       0
+#define IF_SI_OWNER_SIBM                       1
+#define IF_SI_OWNER_SIMC                       2
+#define IF_SI_OWNER_OFFSET                     4
+
 static int ocelot_restart_handle(struct notifier_block *this,
                                 unsigned long mode, void *cmd)
 {
@@ -37,6 +44,11 @@ static int ocelot_restart_handle(struct notifier_block *this,
        regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_RESET,
                           CORE_RST_PROTECT, 0);
 
+       /* Make the SI back to boot mode */
+       regmap_update_bits(ctx->cpu_ctrl, ICPU_CFG_CPU_SYSTEM_CTRL_GENERAL_CTRL,
+                          IF_SI_OWNER_MASK << IF_SI_OWNER_OFFSET,
+                          IF_SI_OWNER_SIBM << IF_SI_OWNER_OFFSET);
+
        writel(SOFT_CHIP_RST, ctx->base);
 
        pr_emerg("Unable to restart system\n");
index f27cf07..e901b98 100644 (file)
@@ -652,4 +652,12 @@ config CHARGER_SC2731
         Say Y here to enable support for battery charging with SC2731
         PMIC chips.
 
+config FUEL_GAUGE_SC27XX
+       tristate "Spreadtrum SC27XX fuel gauge driver"
+       depends on MFD_SC27XX_PMIC || COMPILE_TEST
+       depends on IIO
+       help
+        Say Y here to enable support for fuel gauge with SC27XX
+        PMIC chips.
+
 endif # POWER_SUPPLY
index 767105b..b731c2a 100644 (file)
@@ -86,3 +86,4 @@ obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
 obj-$(CONFIG_AXP288_CHARGER)   += axp288_charger.o
 obj-$(CONFIG_CHARGER_CROS_USBPD)       += cros_usbpd-charger.o
 obj-$(CONFIG_CHARGER_SC2731)   += sc2731_charger.o
+obj-$(CONFIG_FUEL_GAUGE_SC27XX)        += sc27xx_fuel_gauge.o
index 0771f95..59b4c8d 100644 (file)
 #define AXP20X_PWR_STATUS_ACIN_PRESENT BIT(7)
 #define AXP20X_PWR_STATUS_ACIN_AVAIL   BIT(6)
 
+#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)      \
+       (((((x) & AXP813_VHOLD_MASK) >> 3) + 40) * 100000)
+
+#define AXP813_CURR_LIMIT_MASK         GENMASK(2, 0)
+#define AXP813_CURR_LIMIT_UA_TO_BIT(x) (((x) / 500000) - 3)
+#define AXP813_CURR_LIMIT_REG_TO_UA(x) \
+       ((((x) & AXP813_CURR_LIMIT_MASK) + 3) * 500000)
+
 #define DRVNAME "axp20x-ac-power-supply"
 
 struct axp20x_ac_power {
@@ -102,6 +112,57 @@ static int axp20x_ac_power_get_property(struct power_supply *psy,
 
                return 0;
 
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+               ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, &reg);
+               if (ret)
+                       return ret;
+
+               val->intval = AXP813_VHOLD_REG_TO_UV(reg);
+
+               return 0;
+
+       case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+               ret = regmap_read(power->regmap, AXP813_ACIN_PATH_CTRL, &reg);
+               if (ret)
+                       return ret;
+
+               val->intval = AXP813_CURR_LIMIT_REG_TO_UA(reg);
+               /* AXP813 datasheet defines values 11x as 4000mA */
+               if (val->intval > 4000000)
+                       val->intval = 4000000;
+
+               return 0;
+
+       default:
+               return -EINVAL;
+       }
+
+       return -EINVAL;
+}
+
+static int axp813_ac_power_set_property(struct power_supply *psy,
+                                       enum power_supply_property psp,
+                                       const union power_supply_propval *val)
+{
+       struct axp20x_ac_power *power = power_supply_get_drvdata(psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+               if (val->intval < 4000000 || val->intval > 4700000)
+                       return -EINVAL;
+
+               return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL,
+                                         AXP813_VHOLD_MASK,
+                                         AXP813_VHOLD_UV_TO_BIT(val->intval));
+
+       case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+               if (val->intval < 1500000 || val->intval > 4000000)
+                       return -EINVAL;
+
+               return regmap_update_bits(power->regmap, AXP813_ACIN_PATH_CTRL,
+                                         AXP813_CURR_LIMIT_MASK,
+                                         AXP813_CURR_LIMIT_UA_TO_BIT(val->intval));
+
        default:
                return -EINVAL;
        }
@@ -109,6 +170,13 @@ static int axp20x_ac_power_get_property(struct power_supply *psy,
        return -EINVAL;
 }
 
+static int axp813_ac_power_prop_writeable(struct power_supply *psy,
+                                         enum power_supply_property psp)
+{
+       return psp == POWER_SUPPLY_PROP_VOLTAGE_MIN ||
+              psp == POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT;
+}
+
 static enum power_supply_property axp20x_ac_power_properties[] = {
        POWER_SUPPLY_PROP_HEALTH,
        POWER_SUPPLY_PROP_PRESENT,
@@ -123,6 +191,14 @@ static enum power_supply_property axp22x_ac_power_properties[] = {
        POWER_SUPPLY_PROP_ONLINE,
 };
 
+static enum power_supply_property axp813_ac_power_properties[] = {
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_VOLTAGE_MIN,
+       POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+};
+
 static const struct power_supply_desc axp20x_ac_power_desc = {
        .name = "axp20x-ac",
        .type = POWER_SUPPLY_TYPE_MAINS,
@@ -139,6 +215,16 @@ static const struct power_supply_desc axp22x_ac_power_desc = {
        .get_property = axp20x_ac_power_get_property,
 };
 
+static const struct power_supply_desc axp813_ac_power_desc = {
+       .name = "axp813-ac",
+       .type = POWER_SUPPLY_TYPE_MAINS,
+       .properties = axp813_ac_power_properties,
+       .num_properties = ARRAY_SIZE(axp813_ac_power_properties),
+       .property_is_writeable = axp813_ac_power_prop_writeable,
+       .get_property = axp20x_ac_power_get_property,
+       .set_property = axp813_ac_power_set_property,
+};
+
 struct axp_data {
        const struct power_supply_desc  *power_desc;
        bool                            acin_adc;
@@ -154,6 +240,11 @@ static const struct axp_data axp22x_data = {
        .acin_adc = false,
 };
 
+static const struct axp_data axp813_data = {
+       .power_desc = &axp813_ac_power_desc,
+       .acin_adc = false,
+};
+
 static int axp20x_ac_power_probe(struct platform_device *pdev)
 {
        struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
@@ -234,6 +325,9 @@ static const struct of_device_id axp20x_ac_power_match[] = {
        }, {
                .compatible = "x-powers,axp221-ac-power-supply",
                .data = &axp22x_data,
+       }, {
+               .compatible = "x-powers,axp813-ac-power-supply",
+               .data = &axp813_data,
        }, { /* sentinel */ }
 };
 MODULE_DEVICE_TABLE(of, axp20x_ac_power_match);
index 42001df..f52fe77 100644 (file)
@@ -10,6 +10,7 @@
  * option) any later version.
  */
 
+#include <linux/bitops.h>
 #include <linux/device.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
index 735658e..f8c6da9 100644 (file)
@@ -16,6 +16,7 @@
  */
 
 #include <linux/acpi.h>
+#include <linux/bitops.h>
 #include <linux/module.h>
 #include <linux/device.h>
 #include <linux/regmap.h>
 #include <linux/mfd/axp20x.h>
 #include <linux/extcon.h>
 
-#define PS_STAT_VBUS_TRIGGER           (1 << 0)
-#define PS_STAT_BAT_CHRG_DIR           (1 << 2)
-#define PS_STAT_VBAT_ABOVE_VHOLD       (1 << 3)
-#define PS_STAT_VBUS_VALID             (1 << 4)
-#define PS_STAT_VBUS_PRESENT           (1 << 5)
+#define PS_STAT_VBUS_TRIGGER           BIT(0)
+#define PS_STAT_BAT_CHRG_DIR           BIT(2)
+#define PS_STAT_VBAT_ABOVE_VHOLD       BIT(3)
+#define PS_STAT_VBUS_VALID             BIT(4)
+#define PS_STAT_VBUS_PRESENT           BIT(5)
 
-#define CHRG_STAT_BAT_SAFE_MODE                (1 << 3)
-#define CHRG_STAT_BAT_VALID            (1 << 4)
-#define CHRG_STAT_BAT_PRESENT          (1 << 5)
-#define CHRG_STAT_CHARGING             (1 << 6)
-#define CHRG_STAT_PMIC_OTP             (1 << 7)
+#define CHRG_STAT_BAT_SAFE_MODE                BIT(3)
+#define CHRG_STAT_BAT_VALID            BIT(4)
+#define CHRG_STAT_BAT_PRESENT          BIT(5)
+#define CHRG_STAT_CHARGING             BIT(6)
+#define CHRG_STAT_PMIC_OTP             BIT(7)
 
 #define VBUS_ISPOUT_CUR_LIM_MASK       0x03
 #define VBUS_ISPOUT_CUR_LIM_BIT_POS    0
 #define VBUS_ISPOUT_VHOLD_SET_OFFSET   4000    /* 4000mV */
 #define VBUS_ISPOUT_VHOLD_SET_LSB_RES  100     /* 100mV */
 #define VBUS_ISPOUT_VHOLD_SET_4300MV   0x3     /* 4300mV */
-#define VBUS_ISPOUT_VBUS_PATH_DIS      (1 << 7)
+#define VBUS_ISPOUT_VBUS_PATH_DIS      BIT(7)
 
 #define CHRG_CCCV_CC_MASK              0xf             /* 4 bits */
 #define CHRG_CCCV_CC_BIT_POS           0
 #define CHRG_CCCV_CC_OFFSET            200             /* 200mA */
 #define CHRG_CCCV_CC_LSB_RES           200             /* 200mA */
-#define CHRG_CCCV_ITERM_20P            (1 << 4)        /* 20% of CC */
+#define CHRG_CCCV_ITERM_20P            BIT(4)          /* 20% of CC */
 #define CHRG_CCCV_CV_MASK              0x60            /* 2 bits */
 #define CHRG_CCCV_CV_BIT_POS           5
 #define CHRG_CCCV_CV_4100MV            0x0             /* 4.10V */
 #define CHRG_CCCV_CV_4150MV            0x1             /* 4.15V */
 #define CHRG_CCCV_CV_4200MV            0x2             /* 4.20V */
 #define CHRG_CCCV_CV_4350MV            0x3             /* 4.35V */
-#define CHRG_CCCV_CHG_EN               (1 << 7)
+#define CHRG_CCCV_CHG_EN               BIT(7)
 
 #define CNTL2_CC_TIMEOUT_MASK          0x3     /* 2 bits */
 #define CNTL2_CC_TIMEOUT_OFFSET                6       /* 6 Hrs */
 #define CNTL2_CC_TIMEOUT_LSB_RES       2       /* 2 Hrs */
 #define CNTL2_CC_TIMEOUT_12HRS         0x3     /* 12 Hrs */
-#define CNTL2_CHGLED_TYPEB             (1 << 4)
-#define CNTL2_CHG_OUT_TURNON           (1 << 5)
+#define CNTL2_CHGLED_TYPEB             BIT(4)
+#define CNTL2_CHG_OUT_TURNON           BIT(5)
 #define CNTL2_PC_TIMEOUT_MASK          0xC0
 #define CNTL2_PC_TIMEOUT_OFFSET                40      /* 40 mins */
 #define CNTL2_PC_TIMEOUT_LSB_RES       10      /* 10 mins */
 #define CNTL2_PC_TIMEOUT_70MINS                0x3
 
-#define CHRG_ILIM_TEMP_LOOP_EN         (1 << 3)
+#define CHRG_ILIM_TEMP_LOOP_EN         BIT(3)
 #define CHRG_VBUS_ILIM_MASK            0xf0
 #define CHRG_VBUS_ILIM_BIT_POS         4
 #define CHRG_VBUS_ILIM_100MA           0x0     /* 100mA */
@@ -94,7 +95,7 @@
 #define CHRG_VLTFC_0C                  0xA5    /* 0 DegC */
 #define CHRG_VHTFC_45C                 0x1F    /* 45 DegC */
 
-#define FG_CNTL_OCV_ADJ_EN             (1 << 3)
+#define FG_CNTL_OCV_ADJ_EN             BIT(3)
 
 #define CV_4100MV                      4100    /* 4100mV */
 #define CV_4150MV                      4150    /* 4150mV */
index cbec70f..6693e7a 100644 (file)
@@ -1032,54 +1032,6 @@ static int bq2415x_power_supply_get_property(struct power_supply *psy,
        return 0;
 }
 
-static int bq2415x_power_supply_init(struct bq2415x_device *bq)
-{
-       int ret;
-       int chip;
-       char revstr[8];
-       struct power_supply_config psy_cfg = {
-               .drv_data = bq,
-               .of_node = bq->dev->of_node,
-       };
-
-       bq->charger_desc.name = bq->name;
-       bq->charger_desc.type = POWER_SUPPLY_TYPE_USB;
-       bq->charger_desc.properties = bq2415x_power_supply_props;
-       bq->charger_desc.num_properties =
-                       ARRAY_SIZE(bq2415x_power_supply_props);
-       bq->charger_desc.get_property = bq2415x_power_supply_get_property;
-
-       ret = bq2415x_detect_chip(bq);
-       if (ret < 0)
-               chip = BQUNKNOWN;
-       else
-               chip = ret;
-
-       ret = bq2415x_detect_revision(bq);
-       if (ret < 0)
-               strcpy(revstr, "unknown");
-       else
-               sprintf(revstr, "1.%d", ret);
-
-       bq->model = kasprintf(GFP_KERNEL,
-                               "chip %s, revision %s, vender code %.3d",
-                               bq2415x_chip_name[chip], revstr,
-                               bq2415x_get_vender_code(bq));
-       if (!bq->model) {
-               dev_err(bq->dev, "failed to allocate model name\n");
-               return -ENOMEM;
-       }
-
-       bq->charger = power_supply_register(bq->dev, &bq->charger_desc,
-                                           &psy_cfg);
-       if (IS_ERR(bq->charger)) {
-               kfree(bq->model);
-               return PTR_ERR(bq->charger);
-       }
-
-       return 0;
-}
-
 static void bq2415x_power_supply_exit(struct bq2415x_device *bq)
 {
        bq->autotimer = 0;
@@ -1496,7 +1448,7 @@ static DEVICE_ATTR(charge_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
 static DEVICE_ATTR(boost_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
 static DEVICE_ATTR(fault_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
 
-static struct attribute *bq2415x_sysfs_attributes[] = {
+static struct attribute *bq2415x_sysfs_attrs[] = {
        /*
         * TODO: some (appropriate) of these attrs should be switched to
         * use power supply class props.
@@ -1525,19 +1477,55 @@ static struct attribute *bq2415x_sysfs_attributes[] = {
        NULL,
 };
 
-static const struct attribute_group bq2415x_sysfs_attr_group = {
-       .attrs = bq2415x_sysfs_attributes,
-};
+ATTRIBUTE_GROUPS(bq2415x_sysfs);
 
-static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+static int bq2415x_power_supply_init(struct bq2415x_device *bq)
 {
-       return sysfs_create_group(&bq->charger->dev.kobj,
-                       &bq2415x_sysfs_attr_group);
-}
+       int ret;
+       int chip;
+       char revstr[8];
+       struct power_supply_config psy_cfg = {
+               .drv_data = bq,
+               .of_node = bq->dev->of_node,
+               .attr_grp = bq2415x_sysfs_groups,
+       };
 
-static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
-{
-       sysfs_remove_group(&bq->charger->dev.kobj, &bq2415x_sysfs_attr_group);
+       bq->charger_desc.name = bq->name;
+       bq->charger_desc.type = POWER_SUPPLY_TYPE_USB;
+       bq->charger_desc.properties = bq2415x_power_supply_props;
+       bq->charger_desc.num_properties =
+                       ARRAY_SIZE(bq2415x_power_supply_props);
+       bq->charger_desc.get_property = bq2415x_power_supply_get_property;
+
+       ret = bq2415x_detect_chip(bq);
+       if (ret < 0)
+               chip = BQUNKNOWN;
+       else
+               chip = ret;
+
+       ret = bq2415x_detect_revision(bq);
+       if (ret < 0)
+               strcpy(revstr, "unknown");
+       else
+               sprintf(revstr, "1.%d", ret);
+
+       bq->model = kasprintf(GFP_KERNEL,
+                               "chip %s, revision %s, vender code %.3d",
+                               bq2415x_chip_name[chip], revstr,
+                               bq2415x_get_vender_code(bq));
+       if (!bq->model) {
+               dev_err(bq->dev, "failed to allocate model name\n");
+               return -ENOMEM;
+       }
+
+       bq->charger = power_supply_register(bq->dev, &bq->charger_desc,
+                                           &psy_cfg);
+       if (IS_ERR(bq->charger)) {
+               kfree(bq->model);
+               return PTR_ERR(bq->charger);
+       }
+
+       return 0;
 }
 
 /* main bq2415x probe function */
@@ -1651,16 +1639,10 @@ static int bq2415x_probe(struct i2c_client *client,
                goto error_2;
        }
 
-       ret = bq2415x_sysfs_init(bq);
-       if (ret) {
-               dev_err(bq->dev, "failed to create sysfs entries: %d\n", ret);
-               goto error_3;
-       }
-
        ret = bq2415x_set_defaults(bq);
        if (ret) {
                dev_err(bq->dev, "failed to set default values: %d\n", ret);
-               goto error_4;
+               goto error_3;
        }
 
        if (bq->notify_node || bq->init_data.notify_device) {
@@ -1668,7 +1650,7 @@ static int bq2415x_probe(struct i2c_client *client,
                ret = power_supply_reg_notifier(&bq->nb);
                if (ret) {
                        dev_err(bq->dev, "failed to reg notifier: %d\n", ret);
-                       goto error_4;
+                       goto error_3;
                }
 
                bq->automode = 1;
@@ -1707,8 +1689,6 @@ static int bq2415x_probe(struct i2c_client *client,
        dev_info(bq->dev, "driver registered\n");
        return 0;
 
-error_4:
-       bq2415x_sysfs_exit(bq);
 error_3:
        bq2415x_power_supply_exit(bq);
 error_2:
@@ -1733,7 +1713,6 @@ static int bq2415x_remove(struct i2c_client *client)
                power_supply_unreg_notifier(&bq->nb);
 
        of_node_put(bq->notify_node);
-       bq2415x_sysfs_exit(bq);
        bq2415x_power_supply_exit(bq);
 
        bq2415x_reset_chip(bq);
index b58df04..cc0dfdc 100644 (file)
@@ -21,6 +21,7 @@
 #include <linux/workqueue.h>
 #include <linux/gpio.h>
 #include <linux/i2c.h>
+#include <linux/extcon-provider.h>
 
 #define        BQ24190_MANUFACTURER    "Texas Instruments"
 
 #define BQ24190_REG_VPRS_PN_MASK               (BIT(5) | BIT(4) | BIT(3))
 #define BQ24190_REG_VPRS_PN_SHIFT              3
 #define BQ24190_REG_VPRS_PN_24190                      0x4
-#define BQ24190_REG_VPRS_PN_24192                      0x5 /* Also 24193 */
+#define BQ24190_REG_VPRS_PN_24192                      0x5 /* Also 24193, 24196 */
 #define BQ24190_REG_VPRS_PN_24192I                     0x3
 #define BQ24190_REG_VPRS_TS_PROFILE_MASK       BIT(2)
 #define BQ24190_REG_VPRS_TS_PROFILE_SHIFT      2
 struct bq24190_dev_info {
        struct i2c_client               *client;
        struct device                   *dev;
+       struct extcon_dev               *edev;
        struct power_supply             *charger;
        struct power_supply             *battery;
        struct delayed_work             input_current_limit_work;
@@ -174,6 +176,11 @@ struct bq24190_dev_info {
        u8                              watchdog;
 };
 
+static const unsigned int bq24190_usb_extcon_cable[] = {
+       EXTCON_USB,
+       EXTCON_NONE,
+};
+
 /*
  * The tables below provide a 2-way mapping for the value that goes in
  * the register field and the real-world value that it represents.
@@ -402,9 +409,7 @@ static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = {
 static struct attribute *
        bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1];
 
-static const struct attribute_group bq24190_sysfs_attr_group = {
-       .attrs = bq24190_sysfs_attrs,
-};
+ATTRIBUTE_GROUPS(bq24190_sysfs);
 
 static void bq24190_sysfs_init_attrs(void)
 {
@@ -491,26 +496,6 @@ static ssize_t bq24190_sysfs_store(struct device *dev,
 
        return count;
 }
-
-static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
-{
-       bq24190_sysfs_init_attrs();
-
-       return sysfs_create_group(&bdi->charger->dev.kobj,
-                       &bq24190_sysfs_attr_group);
-}
-
-static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi)
-{
-       sysfs_remove_group(&bdi->charger->dev.kobj, &bq24190_sysfs_attr_group);
-}
-#else
-static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi)
-{
-       return 0;
-}
-
-static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {}
 #endif
 
 #ifdef CONFIG_REGULATOR
@@ -577,6 +562,7 @@ static const struct regulator_ops bq24190_vbus_ops = {
 
 static const struct regulator_desc bq24190_vbus_desc = {
        .name = "usb_otg_vbus",
+       .of_match = "usb-otg-vbus",
        .type = REGULATOR_VOLTAGE,
        .owner = THIS_MODULE,
        .ops = &bq24190_vbus_ops,
@@ -1527,6 +1513,20 @@ static const struct power_supply_desc bq24190_battery_desc = {
        .property_is_writeable  = bq24190_battery_property_is_writeable,
 };
 
+static int bq24190_configure_usb_otg(struct bq24190_dev_info *bdi, u8 ss_reg)
+{
+       bool otg_enabled;
+       int ret;
+
+       otg_enabled = !!(ss_reg & BQ24190_REG_SS_VBUS_STAT_MASK);
+       ret = extcon_set_state_sync(bdi->edev, EXTCON_USB, otg_enabled);
+       if (ret < 0)
+               dev_err(bdi->dev, "Can't set extcon state to %d: %d\n",
+                       otg_enabled, ret);
+
+       return ret;
+}
+
 static void bq24190_check_status(struct bq24190_dev_info *bdi)
 {
        const u8 battery_mask_ss = BQ24190_REG_SS_CHRG_STAT_MASK;
@@ -1596,8 +1596,10 @@ static void bq24190_check_status(struct bq24190_dev_info *bdi)
                bdi->ss_reg = ss_reg;
        }
 
-       if (alert_charger || alert_battery)
+       if (alert_charger || alert_battery) {
                power_supply_changed(bdi->charger);
+               bq24190_configure_usb_otg(bdi, ss_reg);
+       }
        if (alert_battery && bdi->battery)
                power_supply_changed(bdi->battery);
 
@@ -1637,8 +1639,12 @@ static int bq24190_hw_init(struct bq24190_dev_info *bdi)
        if (ret < 0)
                return ret;
 
-       if (v != BQ24190_REG_VPRS_PN_24190 &&
-           v != BQ24190_REG_VPRS_PN_24192I) {
+       switch (v) {
+       case BQ24190_REG_VPRS_PN_24190:
+       case BQ24190_REG_VPRS_PN_24192:
+       case BQ24190_REG_VPRS_PN_24192I:
+               break;
+       default:
                dev_err(bdi->dev, "Error unknown model: 0x%02x\n", v);
                return -ENODEV;
        }
@@ -1727,6 +1733,14 @@ static int bq24190_probe(struct i2c_client *client,
                return -EINVAL;
        }
 
+       bdi->edev = devm_extcon_dev_allocate(dev, bq24190_usb_extcon_cable);
+       if (IS_ERR(bdi->edev))
+               return PTR_ERR(bdi->edev);
+
+       ret = devm_extcon_dev_register(dev, bdi->edev);
+       if (ret < 0)
+               return ret;
+
        pm_runtime_enable(dev);
        pm_runtime_use_autosuspend(dev);
        pm_runtime_set_autosuspend_delay(dev, 600);
@@ -1736,6 +1750,11 @@ static int bq24190_probe(struct i2c_client *client,
                goto out_pmrt;
        }
 
+#ifdef CONFIG_SYSFS
+       bq24190_sysfs_init_attrs();
+       charger_cfg.attr_grp = bq24190_sysfs_groups;
+#endif
+
        charger_cfg.drv_data = bdi;
        charger_cfg.of_node = dev->of_node;
        charger_cfg.supplied_to = bq24190_charger_supplied_to;
@@ -1773,11 +1792,9 @@ static int bq24190_probe(struct i2c_client *client,
                goto out_charger;
        }
 
-       ret = bq24190_sysfs_create_group(bdi);
-       if (ret < 0) {
-               dev_err(dev, "Can't create sysfs entries\n");
+       ret = bq24190_configure_usb_otg(bdi, bdi->ss_reg);
+       if (ret < 0)
                goto out_charger;
-       }
 
        bdi->initialized = true;
 
@@ -1787,12 +1804,12 @@ static int bq24190_probe(struct i2c_client *client,
                        "bq24190-charger", bdi);
        if (ret < 0) {
                dev_err(dev, "Can't set up irq handler\n");
-               goto out_sysfs;
+               goto out_charger;
        }
 
        ret = bq24190_register_vbus_regulator(bdi);
        if (ret < 0)
-               goto out_sysfs;
+               goto out_charger;
 
        enable_irq_wake(client->irq);
 
@@ -1801,9 +1818,6 @@ static int bq24190_probe(struct i2c_client *client,
 
        return 0;
 
-out_sysfs:
-       bq24190_sysfs_remove_group(bdi);
-
 out_charger:
        if (!IS_ERR_OR_NULL(bdi->battery))
                power_supply_unregister(bdi->battery);
@@ -1828,7 +1842,6 @@ static int bq24190_remove(struct i2c_client *client)
        }
 
        bq24190_register_reset(bdi);
-       bq24190_sysfs_remove_group(bdi);
        if (bdi->battery)
                power_supply_unregister(bdi->battery);
        power_supply_unregister(bdi->charger);
@@ -1931,7 +1944,9 @@ static const struct dev_pm_ops bq24190_pm_ops = {
 
 static const struct i2c_device_id bq24190_i2c_ids[] = {
        { "bq24190" },
+       { "bq24192" },
        { "bq24192i" },
+       { "bq24196" },
        { },
 };
 MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
@@ -1939,7 +1954,9 @@ MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
 #ifdef CONFIG_OF
 static const struct of_device_id bq24190_of_match[] = {
        { .compatible = "ti,bq24190", },
+       { .compatible = "ti,bq24192", },
        { .compatible = "ti,bq24192i", },
+       { .compatible = "ti,bq24196", },
        { },
 };
 MODULE_DEVICE_TABLE(of, bq24190_of_match);
index 6fc31bd..673c7d6 100644 (file)
@@ -845,7 +845,7 @@ static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
 static DEVICE_ATTR(sysoff_enable, S_IWUSR | S_IRUGO,
                   bq24257_sysfs_show_enable, bq24257_sysfs_set_enable);
 
-static struct attribute *bq24257_charger_attr[] = {
+static struct attribute *bq24257_charger_sysfs_attrs[] = {
        &dev_attr_ovp_voltage.attr,
        &dev_attr_in_dpm_voltage.attr,
        &dev_attr_high_impedance_enable.attr,
@@ -853,14 +853,13 @@ static struct attribute *bq24257_charger_attr[] = {
        NULL,
 };
 
-static const struct attribute_group bq24257_attr_group = {
-       .attrs = bq24257_charger_attr,
-};
+ATTRIBUTE_GROUPS(bq24257_charger_sysfs);
 
 static int bq24257_power_supply_init(struct bq24257_device *bq)
 {
        struct power_supply_config psy_cfg = { .drv_data = bq, };
 
+       psy_cfg.attr_grp = bq24257_charger_sysfs_groups;
        psy_cfg.supplied_to = bq24257_charger_supplied_to;
        psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to);
 
@@ -1084,12 +1083,6 @@ static int bq24257_probe(struct i2c_client *client,
                return ret;
        }
 
-       ret = sysfs_create_group(&bq->charger->dev.kobj, &bq24257_attr_group);
-       if (ret < 0) {
-               dev_err(dev, "Can't create sysfs entries\n");
-               return ret;
-       }
-
        return 0;
 }
 
@@ -1100,8 +1093,6 @@ static int bq24257_remove(struct i2c_client *client)
        if (bq->iilimit_autoset_enable)
                cancel_delayed_work_sync(&bq->iilimit_setup_work);
 
-       sysfs_remove_group(&bq->charger->dev.kobj, &bq24257_attr_group);
-
        bq24257_field_write(bq, F_RESET, 1); /* reset to defaults */
 
        return 0;
index 70b90db..3f6fb49 100644 (file)
@@ -183,7 +183,7 @@ static const struct reg_field bq25890_reg_fields[] = {
        [F_CHG_TMR]             = REG_FIELD(0x07, 1, 2),
        [F_JEITA_ISET]          = REG_FIELD(0x07, 0, 0),
        /* REG08 */
-       [F_BATCMP]              = REG_FIELD(0x08, 6, 7), // 5-7 on BQ25896
+       [F_BATCMP]              = REG_FIELD(0x08, 5, 7),
        [F_VCLAMP]              = REG_FIELD(0x08, 2, 4),
        [F_TREG]                = REG_FIELD(0x08, 0, 1),
        /* REG09 */
index faa1a67..38be91f 100644 (file)
@@ -363,7 +363,7 @@ static int try_charger_enable(struct charger_manager *cm, bool enable)
        int err = 0, i;
        struct charger_desc *desc = cm->desc;
 
-       /* Ignore if it's redundent command */
+       /* Ignore if it's redundant command */
        if (enable == cm->charger_enabled)
                return 0;
 
@@ -1212,7 +1212,6 @@ static int charger_extcon_init(struct charger_manager *cm,
        if (ret < 0) {
                pr_info("Cannot register extcon_dev for %s(cable: %s)\n",
                        cable->extcon_name, cable->name);
-               ret = -EINVAL;
        }
 
        return ret;
@@ -1352,7 +1351,7 @@ static ssize_t charger_externally_control_store(struct device *dev,
 }
 
 /**
- * charger_manager_register_sysfs - Register sysfs entry for each charger
+ * charger_manager_prepare_sysfs - Prepare sysfs entry for each charger
  * @cm: the Charger Manager representing the battery.
  *
  * This function add sysfs entry for charger(regulator) to control charger from
@@ -1364,34 +1363,30 @@ static ssize_t charger_externally_control_store(struct device *dev,
  * externally_control, this charger isn't controlled from charger-manager and
  * always stay off state of regulator.
  */
-static int charger_manager_register_sysfs(struct charger_manager *cm)
+static int charger_manager_prepare_sysfs(struct charger_manager *cm)
 {
        struct charger_desc *desc = cm->desc;
        struct charger_regulator *charger;
        int chargers_externally_control = 1;
-       char buf[11];
-       char *str;
-       int ret;
+       char *name;
        int i;
 
        /* Create sysfs entry to control charger(regulator) */
        for (i = 0; i < desc->num_charger_regulators; i++) {
                charger = &desc->charger_regulators[i];
 
-               snprintf(buf, 10, "charger.%d", i);
-               str = devm_kzalloc(cm->dev,
-                               strlen(buf) + 1, GFP_KERNEL);
-               if (!str)
+               name = devm_kasprintf(cm->dev, GFP_KERNEL, "charger.%d", i);
+               if (!name)
                        return -ENOMEM;
 
-               strcpy(str, buf);
-
                charger->attrs[0] = &charger->attr_name.attr;
                charger->attrs[1] = &charger->attr_state.attr;
                charger->attrs[2] = &charger->attr_externally_control.attr;
                charger->attrs[3] = NULL;
-               charger->attr_g.name = str;
-               charger->attr_g.attrs = charger->attrs;
+
+               charger->attr_grp.name = name;
+               charger->attr_grp.attrs = charger->attrs;
+               desc->sysfs_groups[i] = &charger->attr_grp;
 
                sysfs_attr_init(&charger->attr_name.attr);
                charger->attr_name.attr.name = "name";
@@ -1418,14 +1413,6 @@ static int charger_manager_register_sysfs(struct charger_manager *cm)
 
                dev_info(cm->dev, "'%s' regulator's externally_control is %d\n",
                         charger->regulator_name, charger->externally_control);
-
-               ret = sysfs_create_group(&cm->charger_psy->dev.kobj,
-                                       &charger->attr_g);
-               if (ret < 0) {
-                       dev_err(cm->dev, "Cannot create sysfs entry of %s regulator\n",
-                               charger->regulator_name);
-                       return ret;
-               }
        }
 
        if (chargers_externally_control) {
@@ -1521,19 +1508,19 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev)
        /* chargers */
        of_property_read_u32(np, "cm-num-chargers", &num_chgs);
        if (num_chgs) {
+               int i;
+
                /* Allocate empty bin at the tail of array */
                desc->psy_charger_stat = devm_kcalloc(dev,
                                                      num_chgs + 1,
                                                      sizeof(char *),
                                                      GFP_KERNEL);
-               if (desc->psy_charger_stat) {
-                       int i;
-                       for (i = 0; i < num_chgs; i++)
-                               of_property_read_string_index(np, "cm-chargers",
-                                               i, &desc->psy_charger_stat[i]);
-               } else {
+               if (!desc->psy_charger_stat)
                        return ERR_PTR(-ENOMEM);
-               }
+
+               for (i = 0; i < num_chgs; i++)
+                       of_property_read_string_index(np, "cm-chargers",
+                                                     i, &desc->psy_charger_stat[i]);
        }
 
        of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge);
@@ -1566,6 +1553,13 @@ static struct charger_desc *of_cm_parse_desc(struct device *dev)
 
                desc->charger_regulators = chg_regs;
 
+               desc->sysfs_groups = devm_kcalloc(dev,
+                                       desc->num_charger_regulators + 1,
+                                       sizeof(*desc->sysfs_groups),
+                                       GFP_KERNEL);
+               if (!desc->sysfs_groups)
+                       return ERR_PTR(-ENOMEM);
+
                for_each_child_of_node(np, child) {
                        struct charger_cable *cables;
                        struct device_node *_child;
@@ -1633,7 +1627,7 @@ static int charger_manager_probe(struct platform_device *pdev)
 
        if (IS_ERR(desc)) {
                dev_err(&pdev->dev, "No platform data (desc) found\n");
-               return -ENODEV;
+               return PTR_ERR(desc);
        }
 
        cm = devm_kzalloc(&pdev->dev, sizeof(*cm), GFP_KERNEL);
@@ -1687,10 +1681,6 @@ static int charger_manager_probe(struct platform_device *pdev)
                return -EINVAL;
        }
 
-       /* Counting index only */
-       while (desc->psy_charger_stat[i])
-               i++;
-
        /* Check if charger's supplies are present at probe */
        for (i = 0; desc->psy_charger_stat[i]; i++) {
                struct power_supply *psy;
@@ -1772,6 +1762,15 @@ static int charger_manager_probe(struct platform_device *pdev)
 
        INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
 
+       /* Register sysfs entry for charger(regulator) */
+       ret = charger_manager_prepare_sysfs(cm);
+       if (ret < 0) {
+               dev_err(&pdev->dev,
+                       "Cannot prepare sysfs entry of regulators\n");
+               return ret;
+       }
+       psy_cfg.attr_grp = desc->sysfs_groups;
+
        cm->charger_psy = power_supply_register(&pdev->dev,
                                                &cm->charger_psy_desc,
                                                &psy_cfg);
@@ -1788,14 +1787,6 @@ static int charger_manager_probe(struct platform_device *pdev)
                goto err_reg_extcon;
        }
 
-       /* Register sysfs entry for charger(regulator) */
-       ret = charger_manager_register_sysfs(cm);
-       if (ret < 0) {
-               dev_err(&pdev->dev,
-                       "Cannot initialize sysfs entry of regulator\n");
-               goto err_reg_sysfs;
-       }
-
        /* Add to the list */
        mutex_lock(&cm_list_mtx);
        list_add(&cm->entry, &cm_list);
@@ -1803,7 +1794,7 @@ static int charger_manager_probe(struct platform_device *pdev)
 
        /*
         * Charger-manager is capable of waking up the systme from sleep
-        * when event is happend through cm_notify_event()
+        * when event is happened through cm_notify_event()
         */
        device_init_wakeup(&pdev->dev, true);
        device_set_wakeup_capable(&pdev->dev, false);
@@ -1819,14 +1810,6 @@ static int charger_manager_probe(struct platform_device *pdev)
 
        return 0;
 
-err_reg_sysfs:
-       for (i = 0; i < desc->num_charger_regulators; i++) {
-               struct charger_regulator *charger;
-
-               charger = &desc->charger_regulators[i];
-               sysfs_remove_group(&cm->charger_psy->dev.kobj,
-                               &charger->attr_g);
-       }
 err_reg_extcon:
        for (i = 0; i < desc->num_charger_regulators; i++) {
                struct charger_regulator *charger;
@@ -2023,7 +2006,7 @@ module_exit(charger_manager_cleanup);
  * cm_notify_event - charger driver notify Charger Manager of charger event
  * @psy: pointer to instance of charger's power_supply
  * @type: type of charger event
- * @msg: optional message passed to uevent_notify fuction
+ * @msg: optional message passed to uevent_notify function
  */
 void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
                     char *msg)
index 98ba078..08d5037 100644 (file)
@@ -620,7 +620,7 @@ static int cpcap_battery_init_irq(struct platform_device *pdev,
 static int cpcap_battery_init_interrupts(struct platform_device *pdev,
                                         struct cpcap_battery_ddata *ddata)
 {
-       const char * const cpcap_battery_irqs[] = {
+       static const char * const cpcap_battery_irqs[] = {
                "eol", "lowbph", "lowbpl",
                "chrgcurr1", "battdetb"
        };
index e4905be..c843eaf 100644 (file)
@@ -544,7 +544,7 @@ static void cpcap_charger_init_optional_gpios(struct cpcap_charger_ddata *ddata)
                if (IS_ERR(ddata->gpio[i])) {
                        dev_info(ddata->dev, "no mode change GPIO%i: %li\n",
                                 i, PTR_ERR(ddata->gpio[i]));
-                                ddata->gpio[i] = NULL;
+                       ddata->gpio[i] = NULL;
                }
        }
 }
index cad14ba..5bf7c71 100644 (file)
@@ -658,7 +658,7 @@ static ssize_t ds2780_write_param_eeprom_bin(struct file *filp,
        return count;
 }
 
-static const struct bin_attribute ds2780_param_eeprom_bin_attr = {
+static struct bin_attribute ds2780_param_eeprom_bin_attr = {
        .attr = {
                .name = "param_eeprom",
                .mode = S_IRUGO | S_IWUSR,
@@ -703,7 +703,7 @@ static ssize_t ds2780_write_user_eeprom_bin(struct file *filp,
        return count;
 }
 
-static const struct bin_attribute ds2780_user_eeprom_bin_attr = {
+static struct bin_attribute ds2780_user_eeprom_bin_attr = {
        .attr = {
                .name = "user_eeprom",
                .mode = S_IRUGO | S_IWUSR,
@@ -722,8 +722,7 @@ static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting,
 static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin,
        ds2780_set_pio_pin);
 
-
-static struct attribute *ds2780_attributes[] = {
+static struct attribute *ds2780_sysfs_attrs[] = {
        &dev_attr_pmod_enabled.attr,
        &dev_attr_sense_resistor_value.attr,
        &dev_attr_rsgain_setting.attr,
@@ -731,21 +730,30 @@ static struct attribute *ds2780_attributes[] = {
        NULL
 };
 
-static const struct attribute_group ds2780_attr_group = {
-       .attrs = ds2780_attributes,
+static struct bin_attribute *ds2780_sysfs_bin_attrs[] = {
+       &ds2780_param_eeprom_bin_attr,
+       &ds2780_user_eeprom_bin_attr,
+       NULL
+};
+
+static const struct attribute_group ds2780_sysfs_group = {
+       .attrs = ds2780_sysfs_attrs,
+       .bin_attrs = ds2780_sysfs_bin_attrs,
+};
+
+static const struct attribute_group *ds2780_sysfs_groups[] = {
+       &ds2780_sysfs_group,
+       NULL,
 };
 
 static int ds2780_battery_probe(struct platform_device *pdev)
 {
        struct power_supply_config psy_cfg = {};
-       int ret = 0;
        struct ds2780_device_info *dev_info;
 
        dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL);
-       if (!dev_info) {
-               ret = -ENOMEM;
-               goto fail;
-       }
+       if (!dev_info)
+               return -ENOMEM;
 
        platform_set_drvdata(pdev, dev_info);
 
@@ -758,62 +766,16 @@ static int ds2780_battery_probe(struct platform_device *pdev)
        dev_info->bat_desc.get_property = ds2780_battery_get_property;
 
        psy_cfg.drv_data                = dev_info;
+       psy_cfg.attr_grp                = ds2780_sysfs_groups;
 
-       dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc,
-                                             &psy_cfg);
+       dev_info->bat = devm_power_supply_register(&pdev->dev,
+                                                  &dev_info->bat_desc,
+                                                  &psy_cfg);
        if (IS_ERR(dev_info->bat)) {
                dev_err(dev_info->dev, "failed to register battery\n");
-               ret = PTR_ERR(dev_info->bat);
-               goto fail;
-       }
-
-       ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2780_attr_group);
-       if (ret) {
-               dev_err(dev_info->dev, "failed to create sysfs group\n");
-               goto fail_unregister;
+               return PTR_ERR(dev_info->bat);
        }
 
-       ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj,
-                                       &ds2780_param_eeprom_bin_attr);
-       if (ret) {
-               dev_err(dev_info->dev,
-                               "failed to create param eeprom bin file");
-               goto fail_remove_group;
-       }
-
-       ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj,
-                                       &ds2780_user_eeprom_bin_attr);
-       if (ret) {
-               dev_err(dev_info->dev,
-                               "failed to create user eeprom bin file");
-               goto fail_remove_bin_file;
-       }
-
-       return 0;
-
-fail_remove_bin_file:
-       sysfs_remove_bin_file(&dev_info->bat->dev.kobj,
-                               &ds2780_param_eeprom_bin_attr);
-fail_remove_group:
-       sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group);
-fail_unregister:
-       power_supply_unregister(dev_info->bat);
-fail:
-       return ret;
-}
-
-static int ds2780_battery_remove(struct platform_device *pdev)
-{
-       struct ds2780_device_info *dev_info = platform_get_drvdata(pdev);
-
-       /*
-        * Remove attributes before unregistering power supply
-        * because 'bat' will be freed on power_supply_unregister() call.
-        */
-       sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2780_attr_group);
-
-       power_supply_unregister(dev_info->bat);
-
        return 0;
 }
 
@@ -822,7 +784,6 @@ static struct platform_driver ds2780_battery_driver = {
                .name = "ds2780-battery",
        },
        .probe    = ds2780_battery_probe,
-       .remove   = ds2780_battery_remove,
 };
 
 module_platform_driver(ds2780_battery_driver);
index 5e79460..166a8bd 100644 (file)
@@ -660,7 +660,7 @@ static ssize_t ds2781_write_param_eeprom_bin(struct file *filp,
        return count;
 }
 
-static const struct bin_attribute ds2781_param_eeprom_bin_attr = {
+static struct bin_attribute ds2781_param_eeprom_bin_attr = {
        .attr = {
                .name = "param_eeprom",
                .mode = S_IRUGO | S_IWUSR,
@@ -706,7 +706,7 @@ static ssize_t ds2781_write_user_eeprom_bin(struct file *filp,
        return count;
 }
 
-static const struct bin_attribute ds2781_user_eeprom_bin_attr = {
+static struct bin_attribute ds2781_user_eeprom_bin_attr = {
        .attr = {
                .name = "user_eeprom",
                .mode = S_IRUGO | S_IWUSR,
@@ -725,8 +725,7 @@ static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting,
 static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin,
        ds2781_set_pio_pin);
 
-
-static struct attribute *ds2781_attributes[] = {
+static struct attribute *ds2781_sysfs_attrs[] = {
        &dev_attr_pmod_enabled.attr,
        &dev_attr_sense_resistor_value.attr,
        &dev_attr_rsgain_setting.attr,
@@ -734,14 +733,26 @@ static struct attribute *ds2781_attributes[] = {
        NULL
 };
 
-static const struct attribute_group ds2781_attr_group = {
-       .attrs = ds2781_attributes,
+static struct bin_attribute *ds2781_sysfs_bin_attrs[] = {
+       &ds2781_param_eeprom_bin_attr,
+       &ds2781_user_eeprom_bin_attr,
+       NULL,
+};
+
+static const struct attribute_group ds2781_sysfs_group = {
+       .attrs = ds2781_sysfs_attrs,
+       .bin_attrs = ds2781_sysfs_bin_attrs,
+
+};
+
+static const struct attribute_group *ds2781_sysfs_groups[] = {
+       &ds2781_sysfs_group,
+       NULL,
 };
 
 static int ds2781_battery_probe(struct platform_device *pdev)
 {
        struct power_supply_config psy_cfg = {};
-       int ret = 0;
        struct ds2781_device_info *dev_info;
 
        dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL);
@@ -759,63 +770,17 @@ static int ds2781_battery_probe(struct platform_device *pdev)
        dev_info->bat_desc.get_property = ds2781_battery_get_property;
 
        psy_cfg.drv_data                = dev_info;
+       psy_cfg.attr_grp                = ds2781_sysfs_groups;
 
-       dev_info->bat = power_supply_register(&pdev->dev, &dev_info->bat_desc,
-                                               &psy_cfg);
+       dev_info->bat = devm_power_supply_register(&pdev->dev,
+                                                  &dev_info->bat_desc,
+                                                  &psy_cfg);
        if (IS_ERR(dev_info->bat)) {
                dev_err(dev_info->dev, "failed to register battery\n");
-               ret = PTR_ERR(dev_info->bat);
-               goto fail;
-       }
-
-       ret = sysfs_create_group(&dev_info->bat->dev.kobj, &ds2781_attr_group);
-       if (ret) {
-               dev_err(dev_info->dev, "failed to create sysfs group\n");
-               goto fail_unregister;
-       }
-
-       ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj,
-                                       &ds2781_param_eeprom_bin_attr);
-       if (ret) {
-               dev_err(dev_info->dev,
-                               "failed to create param eeprom bin file");
-               goto fail_remove_group;
-       }
-
-       ret = sysfs_create_bin_file(&dev_info->bat->dev.kobj,
-                                       &ds2781_user_eeprom_bin_attr);
-       if (ret) {
-               dev_err(dev_info->dev,
-                               "failed to create user eeprom bin file");
-               goto fail_remove_bin_file;
+               return PTR_ERR(dev_info->bat);
        }
 
        return 0;
-
-fail_remove_bin_file:
-       sysfs_remove_bin_file(&dev_info->bat->dev.kobj,
-                               &ds2781_param_eeprom_bin_attr);
-fail_remove_group:
-       sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group);
-fail_unregister:
-       power_supply_unregister(dev_info->bat);
-fail:
-       return ret;
-}
-
-static int ds2781_battery_remove(struct platform_device *pdev)
-{
-       struct ds2781_device_info *dev_info = platform_get_drvdata(pdev);
-
-       /*
-        * Remove attributes before unregistering power supply
-        * because 'bat' will be freed on power_supply_unregister() call.
-        */
-       sysfs_remove_group(&dev_info->bat->dev.kobj, &ds2781_attr_group);
-
-       power_supply_unregister(dev_info->bat);
-
-       return 0;
 }
 
 static struct platform_driver ds2781_battery_driver = {
@@ -823,7 +788,6 @@ static struct platform_driver ds2781_battery_driver = {
                .name = "ds2781-battery",
        },
        .probe    = ds2781_battery_probe,
-       .remove   = ds2781_battery_remove,
 };
 module_platform_driver(ds2781_battery_driver);
 
index c3f2a94..7e4f11d 100644 (file)
@@ -82,11 +82,11 @@ static enum power_supply_type gpio_charger_get_type(struct device *dev)
                if (!strcmp("usb-sdp", chargetype))
                        return POWER_SUPPLY_TYPE_USB;
                if (!strcmp("usb-dcp", chargetype))
-                       return POWER_SUPPLY_TYPE_USB_DCP;
+                       return POWER_SUPPLY_TYPE_USB;
                if (!strcmp("usb-cdp", chargetype))
-                       return POWER_SUPPLY_TYPE_USB_CDP;
+                       return POWER_SUPPLY_TYPE_USB;
                if (!strcmp("usb-aca", chargetype))
-                       return POWER_SUPPLY_TYPE_USB_ACA;
+                       return POWER_SUPPLY_TYPE_USB;
        }
        dev_warn(dev, "unknown charger type %s\n", chargetype);
 
index 0f34327..309e6ef 100644 (file)
@@ -410,30 +410,6 @@ static const struct power_supply_desc lp8788_psy_battery_desc = {
        .get_property   = lp8788_battery_get_property,
 };
 
-static int lp8788_psy_register(struct platform_device *pdev,
-                               struct lp8788_charger *pchg)
-{
-       struct power_supply_config charger_cfg = {};
-
-       charger_cfg.supplied_to = battery_supplied_to;
-       charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
-
-       pchg->charger = power_supply_register(&pdev->dev,
-                                             &lp8788_psy_charger_desc,
-                                             &charger_cfg);
-       if (IS_ERR(pchg->charger))
-               return -EPERM;
-
-       pchg->battery = power_supply_register(&pdev->dev,
-                                             &lp8788_psy_battery_desc, NULL);
-       if (IS_ERR(pchg->battery)) {
-               power_supply_unregister(pchg->charger);
-               return -EPERM;
-       }
-
-       return 0;
-}
-
 static void lp8788_psy_unregister(struct lp8788_charger *pchg)
 {
        power_supply_unregister(pchg->battery);
@@ -690,16 +666,39 @@ static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL);
 static DEVICE_ATTR(eoc_time, S_IRUSR, lp8788_show_eoc_time, NULL);
 static DEVICE_ATTR(eoc_level, S_IRUSR, lp8788_show_eoc_level, NULL);
 
-static struct attribute *lp8788_charger_attr[] = {
+static struct attribute *lp8788_charger_sysfs_attrs[] = {
        &dev_attr_charger_status.attr,
        &dev_attr_eoc_time.attr,
        &dev_attr_eoc_level.attr,
        NULL,
 };
 
-static const struct attribute_group lp8788_attr_group = {
-       .attrs = lp8788_charger_attr,
-};
+ATTRIBUTE_GROUPS(lp8788_charger_sysfs);
+
+static int lp8788_psy_register(struct platform_device *pdev,
+                               struct lp8788_charger *pchg)
+{
+       struct power_supply_config charger_cfg = {};
+
+       charger_cfg.attr_grp = lp8788_charger_sysfs_groups;
+       charger_cfg.supplied_to = battery_supplied_to;
+       charger_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+       pchg->charger = power_supply_register(&pdev->dev,
+                                             &lp8788_psy_charger_desc,
+                                             &charger_cfg);
+       if (IS_ERR(pchg->charger))
+               return -EPERM;
+
+       pchg->battery = power_supply_register(&pdev->dev,
+                                             &lp8788_psy_battery_desc, NULL);
+       if (IS_ERR(pchg->battery)) {
+               power_supply_unregister(pchg->charger);
+               return -EPERM;
+       }
+
+       return 0;
+}
 
 static int lp8788_charger_probe(struct platform_device *pdev)
 {
@@ -726,12 +725,6 @@ static int lp8788_charger_probe(struct platform_device *pdev)
        if (ret)
                return ret;
 
-       ret = sysfs_create_group(&pdev->dev.kobj, &lp8788_attr_group);
-       if (ret) {
-               lp8788_psy_unregister(pchg);
-               return ret;
-       }
-
        ret = lp8788_irq_register(pdev, pchg);
        if (ret)
                dev_warn(dev, "failed to register charger irq: %d\n", ret);
@@ -745,7 +738,6 @@ static int lp8788_charger_remove(struct platform_device *pdev)
 
        flush_work(&pchg->charger_work);
        lp8788_irq_unregister(pdev, pchg);
-       sysfs_remove_group(&pdev->dev.kobj, &lp8788_attr_group);
        lp8788_psy_unregister(pchg);
        lp8788_release_adc_channel(pchg);
 
index 6da79ae..5a97e42 100644 (file)
@@ -428,14 +428,14 @@ static int olpc_bat_get_property(struct power_supply *psy,
                if (ret)
                        return ret;
 
-               val->intval = (s16)be16_to_cpu(ec_word) * 100 / 256;
+               val->intval = (s16)be16_to_cpu(ec_word) * 10 / 256;
                break;
        case POWER_SUPPLY_PROP_TEMP_AMBIENT:
                ret = olpc_ec_cmd(EC_AMB_TEMP, NULL, 0, (void *)&ec_word, 2);
                if (ret)
                        return ret;
 
-               val->intval = (int)be16_to_cpu(ec_word) * 100 / 256;
+               val->intval = (int)be16_to_cpu(ec_word) * 10 / 256;
                break;
        case POWER_SUPPLY_PROP_CHARGE_COUNTER:
                ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
index 1aba140..28ed463 100644 (file)
@@ -245,17 +245,14 @@ static ssize_t set_chglim(struct device *dev,
  */
 static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim);
 
-static struct attribute *pcf50633_mbc_sysfs_entries[] = {
+static struct attribute *pcf50633_mbc_sysfs_attrs[] = {
        &dev_attr_chgmode.attr,
        &dev_attr_usb_curlim.attr,
        &dev_attr_chg_curlim.attr,
        NULL,
 };
 
-static const struct attribute_group mbc_attr_group = {
-       .name   = NULL,                 /* put in device directory */
-       .attrs  = pcf50633_mbc_sysfs_entries,
-};
+ATTRIBUTE_GROUPS(pcf50633_mbc_sysfs);
 
 static void
 pcf50633_mbc_irq_handler(int irq, void *data)
@@ -390,6 +387,7 @@ static const struct power_supply_desc pcf50633_mbc_ac_desc = {
 static int pcf50633_mbc_probe(struct platform_device *pdev)
 {
        struct power_supply_config psy_cfg = {};
+       struct power_supply_config usb_psy_cfg;
        struct pcf50633_mbc *mbc;
        int i;
        u8 mbcs1;
@@ -419,8 +417,11 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
                return PTR_ERR(mbc->adapter);
        }
 
+       usb_psy_cfg = psy_cfg;
+       usb_psy_cfg.attr_grp = pcf50633_mbc_sysfs_groups;
+
        mbc->usb = power_supply_register(&pdev->dev, &pcf50633_mbc_usb_desc,
-                                        &psy_cfg);
+                                        &usb_psy_cfg);
        if (IS_ERR(mbc->usb)) {
                dev_err(mbc->pcf->dev, "failed to register usb\n");
                power_supply_unregister(mbc->adapter);
@@ -436,9 +437,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev)
                return PTR_ERR(mbc->ac);
        }
 
-       if (sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group))
-               dev_err(mbc->pcf->dev, "failed to create sysfs entries\n");
-
        mbcs1 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS1);
        if (mbcs1 & PCF50633_MBCS1_USBPRES)
                pcf50633_mbc_irq_handler(PCF50633_IRQ_USBINS, mbc);
@@ -457,7 +455,6 @@ static int pcf50633_mbc_remove(struct platform_device *pdev)
        for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
                pcf50633_free_irq(mbc->pcf, mbc_irq_handlers[i]);
 
-       sysfs_remove_group(&pdev->dev.kobj, &mbc_attr_group);
        power_supply_unregister(mbc->usb);
        power_supply_unregister(mbc->adapter);
        power_supply_unregister(mbc->ac);
index e853618..569790e 100644 (file)
@@ -570,7 +570,7 @@ int power_supply_get_battery_info(struct power_supply *psy,
 {
        struct device_node *battery_np;
        const char *value;
-       int err;
+       int err, len, index;
 
        info->energy_full_design_uwh         = -EINVAL;
        info->charge_full_design_uah         = -EINVAL;
@@ -579,6 +579,13 @@ int power_supply_get_battery_info(struct power_supply *psy,
        info->charge_term_current_ua         = -EINVAL;
        info->constant_charge_current_max_ua = -EINVAL;
        info->constant_charge_voltage_max_uv = -EINVAL;
+       info->factory_internal_resistance_uohm  = -EINVAL;
+
+       for (index = 0; index < POWER_SUPPLY_OCV_TEMP_MAX; index++) {
+               info->ocv_table[index]       = NULL;
+               info->ocv_temp[index]        = -EINVAL;
+               info->ocv_table_size[index]  = -EINVAL;
+       }
 
        if (!psy->of_node) {
                dev_warn(&psy->dev, "%s currently only supports devicetree\n",
@@ -616,11 +623,142 @@ int power_supply_get_battery_info(struct power_supply *psy,
                             &info->constant_charge_current_max_ua);
        of_property_read_u32(battery_np, "constant_charge_voltage_max_microvolt",
                             &info->constant_charge_voltage_max_uv);
+       of_property_read_u32(battery_np, "factory-internal-resistance-micro-ohms",
+                            &info->factory_internal_resistance_uohm);
+
+       len = of_property_count_u32_elems(battery_np, "ocv-capacity-celsius");
+       if (len < 0 && len != -EINVAL) {
+               return len;
+       } else if (len > POWER_SUPPLY_OCV_TEMP_MAX) {
+               dev_err(&psy->dev, "Too many temperature values\n");
+               return -EINVAL;
+       } else if (len > 0) {
+               of_property_read_u32_array(battery_np, "ocv-capacity-celsius",
+                                          info->ocv_temp, len);
+       }
+
+       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);
+               list = of_get_property(battery_np, propname, &size);
+               if (!list || !size) {
+                       dev_err(&psy->dev, "failed to get %s\n", propname);
+                       kfree(propname);
+                       power_supply_put_battery_info(psy, info);
+                       return -EINVAL;
+               }
+
+               kfree(propname);
+               tab_len = size / (2 * sizeof(__be32));
+               info->ocv_table_size[index] = tab_len;
+
+               table = info->ocv_table[index] =
+                       devm_kcalloc(&psy->dev, tab_len, sizeof(*table), GFP_KERNEL);
+               if (!info->ocv_table[index]) {
+                       power_supply_put_battery_info(psy, info);
+                       return -ENOMEM;
+               }
+
+               for (i = 0; i < tab_len; i++) {
+                       table[i].ocv = be32_to_cpu(*list++);
+                       table[i].capacity = be32_to_cpu(*list++);
+               }
+       }
 
        return 0;
 }
 EXPORT_SYMBOL_GPL(power_supply_get_battery_info);
 
+void power_supply_put_battery_info(struct power_supply *psy,
+                                  struct power_supply_battery_info *info)
+{
+       int i;
+
+       for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
+               if (info->ocv_table[i])
+                       devm_kfree(&psy->dev, info->ocv_table[i]);
+       }
+}
+EXPORT_SYMBOL_GPL(power_supply_put_battery_info);
+
+/**
+ * power_supply_ocv2cap_simple() - find the battery capacity
+ * @table: Pointer to battery OCV lookup table
+ * @table_len: OCV table length
+ * @ocv: Current OCV value
+ *
+ * This helper function is used to look up battery capacity according to
+ * current OCV value from one OCV table, and the OCV table must be ordered
+ * descending.
+ *
+ * Return: the battery capacity.
+ */
+int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table,
+                               int table_len, int ocv)
+{
+       int i, cap, tmp;
+
+       for (i = 0; i < table_len; i++)
+               if (ocv > table[i].ocv)
+                       break;
+
+       if (i > 0 && i < table_len) {
+               tmp = (table[i - 1].capacity - table[i].capacity) *
+                       (ocv - table[i].ocv);
+               tmp /= table[i - 1].ocv - table[i].ocv;
+               cap = tmp + table[i].capacity;
+       } else if (i == 0) {
+               cap = table[0].capacity;
+       } else {
+               cap = table[table_len - 1].capacity;
+       }
+
+       return cap;
+}
+EXPORT_SYMBOL_GPL(power_supply_ocv2cap_simple);
+
+struct power_supply_battery_ocv_table *
+power_supply_find_ocv2cap_table(struct power_supply_battery_info *info,
+                               int temp, int *table_len)
+{
+       int best_temp_diff = INT_MAX, temp_diff;
+       u8 i, best_index = 0;
+
+       if (!info->ocv_table[0])
+               return NULL;
+
+       for (i = 0; i < POWER_SUPPLY_OCV_TEMP_MAX; i++) {
+               temp_diff = abs(info->ocv_temp[i] - temp);
+
+               if (temp_diff < best_temp_diff) {
+                       best_temp_diff = temp_diff;
+                       best_index = i;
+               }
+       }
+
+       *table_len = info->ocv_table_size[best_index];
+       return info->ocv_table[best_index];
+}
+EXPORT_SYMBOL_GPL(power_supply_find_ocv2cap_table);
+
+int power_supply_batinfo_ocv2cap(struct power_supply_battery_info *info,
+                                int ocv, int temp)
+{
+       struct power_supply_battery_ocv_table *table;
+       int table_len;
+
+       table = power_supply_find_ocv2cap_table(info, temp, &table_len);
+       if (!table)
+               return -EINVAL;
+
+       return power_supply_ocv2cap_simple(table, table_len, ocv);
+}
+EXPORT_SYMBOL_GPL(power_supply_batinfo_ocv2cap);
+
 int power_supply_get_property(struct power_supply *psy,
                            enum power_supply_property psp,
                            union power_supply_propval *val)
@@ -880,6 +1018,7 @@ __power_supply_register(struct device *parent,
        dev_set_drvdata(dev, psy);
        psy->desc = desc;
        if (cfg) {
+               dev->groups = cfg->attr_grp;
                psy->drv_data = cfg->drv_data;
                psy->of_node =
                        cfg->fwnode ? to_of_node(cfg->fwnode) : cfg->of_node;
index 525a820..335cb85 100644 (file)
@@ -57,9 +57,11 @@ struct sc2731_charger_info {
        struct usb_phy *usb_phy;
        struct notifier_block usb_notify;
        struct power_supply *psy_usb;
+       struct work_struct work;
        struct mutex lock;
        bool charging;
        u32 base;
+       u32 limit;
 };
 
 static void sc2731_charger_stop_charge(struct sc2731_charger_info *info)
@@ -318,22 +320,21 @@ static const struct power_supply_desc sc2731_charger_desc = {
        .property_is_writeable  = sc2731_charger_property_is_writeable,
 };
 
-static int sc2731_charger_usb_change(struct notifier_block *nb,
-                                    unsigned long limit, void *data)
+static void sc2731_charger_work(struct work_struct *data)
 {
        struct sc2731_charger_info *info =
-               container_of(nb, struct sc2731_charger_info, usb_notify);
-       int ret = 0;
+               container_of(data, struct sc2731_charger_info, work);
+       int ret;
 
        mutex_lock(&info->lock);
 
-       if (limit > 0) {
+       if (info->limit > 0 && !info->charging) {
                /* set current limitation and start to charge */
-               ret = sc2731_charger_set_current_limit(info, limit);
+               ret = sc2731_charger_set_current_limit(info, info->limit);
                if (ret)
                        goto out;
 
-               ret = sc2731_charger_set_current(info, limit);
+               ret = sc2731_charger_set_current(info, info->limit);
                if (ret)
                        goto out;
 
@@ -342,7 +343,7 @@ static int sc2731_charger_usb_change(struct notifier_block *nb,
                        goto out;
 
                info->charging = true;
-       } else {
+       } else if (!info->limit && info->charging) {
                /* Stop charging */
                info->charging = false;
                sc2731_charger_stop_charge(info);
@@ -350,7 +351,19 @@ static int sc2731_charger_usb_change(struct notifier_block *nb,
 
 out:
        mutex_unlock(&info->lock);
-       return ret;
+}
+
+static int sc2731_charger_usb_change(struct notifier_block *nb,
+                                    unsigned long limit, void *data)
+{
+       struct sc2731_charger_info *info =
+               container_of(nb, struct sc2731_charger_info, usb_notify);
+
+       info->limit = limit;
+
+       schedule_work(&info->work);
+
+       return NOTIFY_OK;
 }
 
 static int sc2731_charger_hw_init(struct sc2731_charger_info *info)
@@ -395,6 +408,8 @@ static int sc2731_charger_hw_init(struct sc2731_charger_info *info)
                        vol_val = (term_voltage - 4200) / 100;
                else
                        vol_val = 0;
+
+               power_supply_put_battery_info(info->psy_usb, &bat_info);
        }
 
        /* Set charge termination current */
@@ -419,6 +434,24 @@ error:
        return ret;
 }
 
+static void sc2731_charger_detect_status(struct sc2731_charger_info *info)
+{
+       unsigned int min, max;
+
+       /*
+        * If the USB charger status has been USB_CHARGER_PRESENT before
+        * registering the notifier, we should start to charge with getting
+        * the charge current.
+        */
+       if (info->usb_phy->chg_state != USB_CHARGER_PRESENT)
+               return;
+
+       usb_phy_get_charger_current(info->usb_phy, &min, &max);
+       info->limit = min;
+
+       schedule_work(&info->work);
+}
+
 static int sc2731_charger_probe(struct platform_device *pdev)
 {
        struct device_node *np = pdev->dev.of_node;
@@ -432,6 +465,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);
 
        info->regmap = dev_get_regmap(pdev->dev.parent, NULL);
        if (!info->regmap) {
@@ -472,6 +506,8 @@ static int sc2731_charger_probe(struct platform_device *pdev)
                return ret;
        }
 
+       sc2731_charger_detect_status(info);
+
        return 0;
 }
 
diff --git a/drivers/power/supply/sc27xx_fuel_gauge.c b/drivers/power/supply/sc27xx_fuel_gauge.c
new file mode 100644 (file)
index 0000000..76da189
--- /dev/null
@@ -0,0 +1,1075 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2018 Spreadtrum Communications Inc.
+
+#include <linux/gpio/consumer.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/nvmem-consumer.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+/* PMIC global control registers definition */
+#define SC27XX_MODULE_EN0              0xc08
+#define SC27XX_CLK_EN0                 0xc18
+#define SC27XX_FGU_EN                  BIT(7)
+#define SC27XX_FGU_RTC_EN              BIT(6)
+
+/* FGU registers definition */
+#define SC27XX_FGU_START               0x0
+#define SC27XX_FGU_CONFIG              0x4
+#define SC27XX_FGU_ADC_CONFIG          0x8
+#define SC27XX_FGU_STATUS              0xc
+#define SC27XX_FGU_INT_EN              0x10
+#define SC27XX_FGU_INT_CLR             0x14
+#define SC27XX_FGU_INT_STS             0x1c
+#define SC27XX_FGU_VOLTAGE             0x20
+#define SC27XX_FGU_OCV                 0x24
+#define SC27XX_FGU_POCV                        0x28
+#define SC27XX_FGU_CURRENT             0x2c
+#define SC27XX_FGU_LOW_OVERLOAD                0x34
+#define SC27XX_FGU_CLBCNT_SETH         0x50
+#define SC27XX_FGU_CLBCNT_SETL         0x54
+#define SC27XX_FGU_CLBCNT_DELTH                0x58
+#define SC27XX_FGU_CLBCNT_DELTL                0x5c
+#define SC27XX_FGU_CLBCNT_VALH         0x68
+#define SC27XX_FGU_CLBCNT_VALL         0x6c
+#define SC27XX_FGU_CLBCNT_QMAXL                0x74
+#define SC27XX_FGU_USER_AREA_SET       0xa0
+#define SC27XX_FGU_USER_AREA_CLEAR     0xa4
+#define SC27XX_FGU_USER_AREA_STATUS    0xa8
+
+#define SC27XX_WRITE_SELCLB_EN         BIT(0)
+#define SC27XX_FGU_CLBCNT_MASK         GENMASK(15, 0)
+#define SC27XX_FGU_CLBCNT_SHIFT                16
+#define SC27XX_FGU_LOW_OVERLOAD_MASK   GENMASK(12, 0)
+
+#define SC27XX_FGU_INT_MASK            GENMASK(9, 0)
+#define SC27XX_FGU_LOW_OVERLOAD_INT    BIT(0)
+#define SC27XX_FGU_CLBCNT_DELTA_INT    BIT(2)
+
+#define SC27XX_FGU_MODE_AREA_MASK      GENMASK(15, 12)
+#define SC27XX_FGU_CAP_AREA_MASK       GENMASK(11, 0)
+#define SC27XX_FGU_MODE_AREA_SHIFT     12
+
+#define SC27XX_FGU_FIRST_POWERTON      GENMASK(3, 0)
+#define SC27XX_FGU_DEFAULT_CAP         GENMASK(11, 0)
+#define SC27XX_FGU_NORMAIL_POWERTON    0x5
+
+#define SC27XX_FGU_CUR_BASIC_ADC       8192
+#define SC27XX_FGU_SAMPLE_HZ           2
+
+/*
+ * struct sc27xx_fgu_data: describe the FGU device
+ * @regmap: regmap for register access
+ * @dev: platform device
+ * @battery: battery power supply
+ * @base: the base offset for the controller
+ * @lock: protect the structure
+ * @gpiod: GPIO for battery detection
+ * @channel: IIO channel to get battery temperature
+ * @internal_resist: the battery internal resistance in mOhm
+ * @total_cap: the total capacity of the battery in mAh
+ * @init_cap: the initial capacity of the battery in mAh
+ * @alarm_cap: the alarm capacity
+ * @init_clbcnt: the initial coulomb counter
+ * @max_volt: the maximum constant input voltage in millivolt
+ * @min_volt: the minimum drained battery voltage in microvolt
+ * @table_len: the capacity table length
+ * @cur_1000ma_adc: ADC value corresponding to 1000 mA
+ * @vol_1000mv_adc: ADC value corresponding to 1000 mV
+ * @cap_table: capacity table with corresponding ocv
+ */
+struct sc27xx_fgu_data {
+       struct regmap *regmap;
+       struct device *dev;
+       struct power_supply *battery;
+       u32 base;
+       struct mutex lock;
+       struct gpio_desc *gpiod;
+       struct iio_channel *channel;
+       bool bat_present;
+       int internal_resist;
+       int total_cap;
+       int init_cap;
+       int alarm_cap;
+       int init_clbcnt;
+       int max_volt;
+       int min_volt;
+       int table_len;
+       int cur_1000ma_adc;
+       int vol_1000mv_adc;
+       struct power_supply_battery_ocv_table *cap_table;
+};
+
+static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity);
+
+static const char * const sc27xx_charger_supply_name[] = {
+       "sc2731_charger",
+       "sc2720_charger",
+       "sc2721_charger",
+       "sc2723_charger",
+};
+
+static int sc27xx_fgu_adc_to_current(struct sc27xx_fgu_data *data, int adc)
+{
+       return DIV_ROUND_CLOSEST(adc * 1000, data->cur_1000ma_adc);
+}
+
+static int sc27xx_fgu_adc_to_voltage(struct sc27xx_fgu_data *data, int adc)
+{
+       return DIV_ROUND_CLOSEST(adc * 1000, data->vol_1000mv_adc);
+}
+
+static int sc27xx_fgu_voltage_to_adc(struct sc27xx_fgu_data *data, int vol)
+{
+       return DIV_ROUND_CLOSEST(vol * data->vol_1000mv_adc, 1000);
+}
+
+static bool sc27xx_fgu_is_first_poweron(struct sc27xx_fgu_data *data)
+{
+       int ret, status, cap, mode;
+
+       ret = regmap_read(data->regmap,
+                         data->base + SC27XX_FGU_USER_AREA_STATUS, &status);
+       if (ret)
+               return false;
+
+       /*
+        * We use low 4 bits to save the last battery capacity and high 12 bits
+        * to save the system boot mode.
+        */
+       mode = (status & SC27XX_FGU_MODE_AREA_MASK) >> SC27XX_FGU_MODE_AREA_SHIFT;
+       cap = status & SC27XX_FGU_CAP_AREA_MASK;
+
+       /*
+        * When FGU has been powered down, the user area registers became
+        * default value (0xffff), which can be used to valid if the system is
+        * first power on or not.
+        */
+       if (mode == SC27XX_FGU_FIRST_POWERTON || cap == SC27XX_FGU_DEFAULT_CAP)
+               return true;
+
+       return false;
+}
+
+static int sc27xx_fgu_save_boot_mode(struct sc27xx_fgu_data *data,
+                                    int boot_mode)
+{
+       int ret;
+
+       ret = regmap_update_bits(data->regmap,
+                                data->base + SC27XX_FGU_USER_AREA_CLEAR,
+                                SC27XX_FGU_MODE_AREA_MASK,
+                                SC27XX_FGU_MODE_AREA_MASK);
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(data->regmap,
+                                 data->base + SC27XX_FGU_USER_AREA_SET,
+                                 SC27XX_FGU_MODE_AREA_MASK,
+                                 boot_mode << SC27XX_FGU_MODE_AREA_SHIFT);
+}
+
+static int sc27xx_fgu_save_last_cap(struct sc27xx_fgu_data *data, int cap)
+{
+       int ret;
+
+       ret = regmap_update_bits(data->regmap,
+                                data->base + SC27XX_FGU_USER_AREA_CLEAR,
+                                SC27XX_FGU_CAP_AREA_MASK,
+                                SC27XX_FGU_CAP_AREA_MASK);
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(data->regmap,
+                                 data->base + SC27XX_FGU_USER_AREA_SET,
+                                 SC27XX_FGU_CAP_AREA_MASK, cap);
+}
+
+static int sc27xx_fgu_read_last_cap(struct sc27xx_fgu_data *data, int *cap)
+{
+       int ret, value;
+
+       ret = regmap_read(data->regmap,
+                         data->base + SC27XX_FGU_USER_AREA_STATUS, &value);
+       if (ret)
+               return ret;
+
+       *cap = value & SC27XX_FGU_CAP_AREA_MASK;
+       return 0;
+}
+
+/*
+ * When system boots on, we can not read battery capacity from coulomb
+ * registers, since now the coulomb registers are invalid. So we should
+ * calculate the battery open circuit voltage, and get current battery
+ * capacity according to the capacity table.
+ */
+static int sc27xx_fgu_get_boot_capacity(struct sc27xx_fgu_data *data, int *cap)
+{
+       int volt, cur, oci, ocv, ret;
+       bool is_first_poweron = sc27xx_fgu_is_first_poweron(data);
+
+       /*
+        * If system is not the first power on, we should use the last saved
+        * battery capacity as the initial battery capacity. Otherwise we should
+        * re-calculate the initial battery capacity.
+        */
+       if (!is_first_poweron) {
+               ret = sc27xx_fgu_read_last_cap(data, cap);
+               if (ret)
+                       return ret;
+
+               return sc27xx_fgu_save_boot_mode(data, SC27XX_FGU_NORMAIL_POWERTON);
+       }
+
+       /*
+        * After system booting on, the SC27XX_FGU_CLBCNT_QMAXL register saved
+        * the first sampled open circuit current.
+        */
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CLBCNT_QMAXL,
+                         &cur);
+       if (ret)
+               return ret;
+
+       cur <<= 1;
+       oci = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
+
+       /*
+        * Should get the OCV from SC27XX_FGU_POCV register at the system
+        * beginning. It is ADC values reading from registers which need to
+        * convert the corresponding voltage.
+        */
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_POCV, &volt);
+       if (ret)
+               return ret;
+
+       volt = sc27xx_fgu_adc_to_voltage(data, volt);
+       ocv = volt * 1000 - oci * data->internal_resist;
+
+       /*
+        * Parse the capacity table to look up the correct capacity percent
+        * according to current battery's corresponding OCV values.
+        */
+       *cap = power_supply_ocv2cap_simple(data->cap_table, data->table_len,
+                                          ocv);
+
+       ret = sc27xx_fgu_save_last_cap(data, *cap);
+       if (ret)
+               return ret;
+
+       return sc27xx_fgu_save_boot_mode(data, SC27XX_FGU_NORMAIL_POWERTON);
+}
+
+static int sc27xx_fgu_set_clbcnt(struct sc27xx_fgu_data *data, int clbcnt)
+{
+       int ret;
+
+       clbcnt *= SC27XX_FGU_SAMPLE_HZ;
+
+       ret = regmap_update_bits(data->regmap,
+                                data->base + SC27XX_FGU_CLBCNT_SETL,
+                                SC27XX_FGU_CLBCNT_MASK, clbcnt);
+       if (ret)
+               return ret;
+
+       ret = regmap_update_bits(data->regmap,
+                                data->base + SC27XX_FGU_CLBCNT_SETH,
+                                SC27XX_FGU_CLBCNT_MASK,
+                                clbcnt >> SC27XX_FGU_CLBCNT_SHIFT);
+       if (ret)
+               return ret;
+
+       return regmap_update_bits(data->regmap, data->base + SC27XX_FGU_START,
+                                SC27XX_WRITE_SELCLB_EN,
+                                SC27XX_WRITE_SELCLB_EN);
+}
+
+static int sc27xx_fgu_get_clbcnt(struct sc27xx_fgu_data *data, int *clb_cnt)
+{
+       int ccl, cch, ret;
+
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CLBCNT_VALL,
+                         &ccl);
+       if (ret)
+               return ret;
+
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CLBCNT_VALH,
+                         &cch);
+       if (ret)
+               return ret;
+
+       *clb_cnt = ccl & SC27XX_FGU_CLBCNT_MASK;
+       *clb_cnt |= (cch & SC27XX_FGU_CLBCNT_MASK) << SC27XX_FGU_CLBCNT_SHIFT;
+       *clb_cnt /= SC27XX_FGU_SAMPLE_HZ;
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_capacity(struct sc27xx_fgu_data *data, int *cap)
+{
+       int ret, cur_clbcnt, delta_clbcnt, delta_cap, temp;
+
+       /* Get current coulomb counters firstly */
+       ret = sc27xx_fgu_get_clbcnt(data, &cur_clbcnt);
+       if (ret)
+               return ret;
+
+       delta_clbcnt = cur_clbcnt - data->init_clbcnt;
+
+       /*
+        * Convert coulomb counter to delta capacity (mAh), and set multiplier
+        * as 100 to improve the precision.
+        */
+       temp = DIV_ROUND_CLOSEST(delta_clbcnt, 360);
+       temp = sc27xx_fgu_adc_to_current(data, temp);
+
+       /*
+        * Convert to capacity percent of the battery total capacity,
+        * and multiplier is 100 too.
+        */
+       delta_cap = DIV_ROUND_CLOSEST(temp * 100, data->total_cap);
+       *cap = delta_cap + data->init_cap;
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_vbat_vol(struct sc27xx_fgu_data *data, int *val)
+{
+       int ret, vol;
+
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_VOLTAGE, &vol);
+       if (ret)
+               return ret;
+
+       /*
+        * It is ADC values reading from registers which need to convert to
+        * corresponding voltage values.
+        */
+       *val = sc27xx_fgu_adc_to_voltage(data, vol);
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_current(struct sc27xx_fgu_data *data, int *val)
+{
+       int ret, cur;
+
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_CURRENT, &cur);
+       if (ret)
+               return ret;
+
+       /*
+        * It is ADC values reading from registers which need to convert to
+        * corresponding current values.
+        */
+       *val = sc27xx_fgu_adc_to_current(data, cur - SC27XX_FGU_CUR_BASIC_ADC);
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_vbat_ocv(struct sc27xx_fgu_data *data, int *val)
+{
+       int vol, cur, ret;
+
+       ret = sc27xx_fgu_get_vbat_vol(data, &vol);
+       if (ret)
+               return ret;
+
+       ret = sc27xx_fgu_get_current(data, &cur);
+       if (ret)
+               return ret;
+
+       /* Return the battery OCV in micro volts. */
+       *val = vol * 1000 - cur * data->internal_resist;
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_temp(struct sc27xx_fgu_data *data, int *temp)
+{
+       return iio_read_channel_processed(data->channel, temp);
+}
+
+static int sc27xx_fgu_get_health(struct sc27xx_fgu_data *data, int *health)
+{
+       int ret, vol;
+
+       ret = sc27xx_fgu_get_vbat_vol(data, &vol);
+       if (ret)
+               return ret;
+
+       if (vol > data->max_volt)
+               *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+       else
+               *health = POWER_SUPPLY_HEALTH_GOOD;
+
+       return 0;
+}
+
+static int sc27xx_fgu_get_status(struct sc27xx_fgu_data *data, int *status)
+{
+       union power_supply_propval val;
+       struct power_supply *psy;
+       int i, ret = -EINVAL;
+
+       for (i = 0; i < ARRAY_SIZE(sc27xx_charger_supply_name); i++) {
+               psy = power_supply_get_by_name(sc27xx_charger_supply_name[i]);
+               if (!psy)
+                       continue;
+
+               ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS,
+                                               &val);
+               power_supply_put(psy);
+               if (ret)
+                       return ret;
+
+               *status = val.intval;
+       }
+
+       return ret;
+}
+
+static int sc27xx_fgu_get_property(struct power_supply *psy,
+                                  enum power_supply_property psp,
+                                  union power_supply_propval *val)
+{
+       struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy);
+       int ret = 0;
+       int value;
+
+       mutex_lock(&data->lock);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               ret = sc27xx_fgu_get_status(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value;
+               break;
+
+       case POWER_SUPPLY_PROP_HEALTH:
+               ret = sc27xx_fgu_get_health(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value;
+               break;
+
+       case POWER_SUPPLY_PROP_PRESENT:
+               val->intval = data->bat_present;
+               break;
+
+       case POWER_SUPPLY_PROP_TEMP:
+               ret = sc27xx_fgu_get_temp(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value;
+               break;
+
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+               break;
+
+       case POWER_SUPPLY_PROP_CAPACITY:
+               ret = sc27xx_fgu_get_capacity(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               ret = sc27xx_fgu_get_vbat_vol(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value * 1000;
+               break;
+
+       case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+               ret = sc27xx_fgu_get_vbat_ocv(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value;
+               break;
+
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+       case POWER_SUPPLY_PROP_CURRENT_AVG:
+               ret = sc27xx_fgu_get_current(data, &value);
+               if (ret)
+                       goto error;
+
+               val->intval = value * 1000;
+               break;
+
+       default:
+               ret = -EINVAL;
+               break;
+       }
+
+error:
+       mutex_unlock(&data->lock);
+       return ret;
+}
+
+static int sc27xx_fgu_set_property(struct power_supply *psy,
+                                  enum power_supply_property psp,
+                                  const union power_supply_propval *val)
+{
+       struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy);
+       int ret;
+
+       if (psp != POWER_SUPPLY_PROP_CAPACITY)
+               return -EINVAL;
+
+       mutex_lock(&data->lock);
+
+       ret = sc27xx_fgu_save_last_cap(data, val->intval);
+
+       mutex_unlock(&data->lock);
+
+       if (ret < 0)
+               dev_err(data->dev, "failed to save battery capacity\n");
+
+       return ret;
+}
+
+static void sc27xx_fgu_external_power_changed(struct power_supply *psy)
+{
+       struct sc27xx_fgu_data *data = power_supply_get_drvdata(psy);
+
+       power_supply_changed(data->battery);
+}
+
+static int sc27xx_fgu_property_is_writeable(struct power_supply *psy,
+                                           enum power_supply_property psp)
+{
+       return psp == POWER_SUPPLY_PROP_CAPACITY;
+}
+
+static enum power_supply_property sc27xx_fgu_props[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_VOLTAGE_OCV,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static const struct power_supply_desc sc27xx_fgu_desc = {
+       .name                   = "sc27xx-fgu",
+       .type                   = POWER_SUPPLY_TYPE_BATTERY,
+       .properties             = sc27xx_fgu_props,
+       .num_properties         = ARRAY_SIZE(sc27xx_fgu_props),
+       .get_property           = sc27xx_fgu_get_property,
+       .set_property           = sc27xx_fgu_set_property,
+       .external_power_changed = sc27xx_fgu_external_power_changed,
+       .property_is_writeable  = sc27xx_fgu_property_is_writeable,
+};
+
+static void sc27xx_fgu_adjust_cap(struct sc27xx_fgu_data *data, int cap)
+{
+       data->init_cap = cap;
+       data->init_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, data->init_cap);
+}
+
+static irqreturn_t sc27xx_fgu_interrupt(int irq, void *dev_id)
+{
+       struct sc27xx_fgu_data *data = dev_id;
+       int ret, cap, ocv, adc;
+       u32 status;
+
+       mutex_lock(&data->lock);
+
+       ret = regmap_read(data->regmap, data->base + SC27XX_FGU_INT_STS,
+                         &status);
+       if (ret)
+               goto out;
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR,
+                                status, status);
+       if (ret)
+               goto out;
+
+       /*
+        * When low overload voltage interrupt happens, we should calibrate the
+        * battery capacity in lower voltage stage.
+        */
+       if (!(status & SC27XX_FGU_LOW_OVERLOAD_INT))
+               goto out;
+
+       ret = sc27xx_fgu_get_capacity(data, &cap);
+       if (ret)
+               goto out;
+
+       ret = sc27xx_fgu_get_vbat_ocv(data, &ocv);
+       if (ret)
+               goto out;
+
+       /*
+        * If current OCV value is less than the minimum OCV value in OCV table,
+        * which means now battery capacity is 0%, and we should adjust the
+        * inititial capacity to 0.
+        */
+       if (ocv <= data->cap_table[data->table_len - 1].ocv) {
+               sc27xx_fgu_adjust_cap(data, 0);
+       } else if (ocv <= data->min_volt) {
+               /*
+                * If current OCV value is less than the low alarm voltage, but
+                * current capacity is larger than the alarm capacity, we should
+                * adjust the inititial capacity to alarm capacity.
+                */
+               if (cap > data->alarm_cap) {
+                       sc27xx_fgu_adjust_cap(data, data->alarm_cap);
+               } else if (cap <= 0) {
+                       int cur_cap;
+
+                       /*
+                        * If current capacity is equal with 0 or less than 0
+                        * (some error occurs), we should adjust inititial
+                        * capacity to the capacity corresponding to current OCV
+                        * value.
+                        */
+                       cur_cap = power_supply_ocv2cap_simple(data->cap_table,
+                                                             data->table_len,
+                                                             ocv);
+                       sc27xx_fgu_adjust_cap(data, cur_cap);
+               }
+
+               /*
+                * After adjusting the battery capacity, we should set the
+                * lowest alarm voltage instead.
+                */
+               data->min_volt = data->cap_table[data->table_len - 1].ocv;
+               adc = sc27xx_fgu_voltage_to_adc(data, data->min_volt / 1000);
+               regmap_update_bits(data->regmap, data->base + SC27XX_FGU_LOW_OVERLOAD,
+                                  SC27XX_FGU_LOW_OVERLOAD_MASK, adc);
+       }
+
+out:
+       mutex_unlock(&data->lock);
+
+       power_supply_changed(data->battery);
+       return IRQ_HANDLED;
+}
+
+static irqreturn_t sc27xx_fgu_bat_detection(int irq, void *dev_id)
+{
+       struct sc27xx_fgu_data *data = dev_id;
+       int state;
+
+       mutex_lock(&data->lock);
+
+       state = gpiod_get_value_cansleep(data->gpiod);
+       if (state < 0) {
+               dev_err(data->dev, "failed to get gpio state\n");
+               mutex_unlock(&data->lock);
+               return IRQ_RETVAL(state);
+       }
+
+       data->bat_present = !!state;
+
+       mutex_unlock(&data->lock);
+
+       power_supply_changed(data->battery);
+       return IRQ_HANDLED;
+}
+
+static void sc27xx_fgu_disable(void *_data)
+{
+       struct sc27xx_fgu_data *data = _data;
+
+       regmap_update_bits(data->regmap, SC27XX_CLK_EN0, SC27XX_FGU_RTC_EN, 0);
+       regmap_update_bits(data->regmap, SC27XX_MODULE_EN0, SC27XX_FGU_EN, 0);
+}
+
+static int sc27xx_fgu_cap_to_clbcnt(struct sc27xx_fgu_data *data, int capacity)
+{
+       /*
+        * Get current capacity (mAh) = battery total capacity (mAh) *
+        * current capacity percent (capacity / 100).
+        */
+       int cur_cap = DIV_ROUND_CLOSEST(data->total_cap * capacity, 100);
+
+       /*
+        * Convert current capacity (mAh) to coulomb counter according to the
+        * formula: 1 mAh =3.6 coulomb.
+        */
+       return DIV_ROUND_CLOSEST(cur_cap * 36, 10);
+}
+
+static int sc27xx_fgu_calibration(struct sc27xx_fgu_data *data)
+{
+       struct nvmem_cell *cell;
+       int calib_data, cal_4200mv;
+       void *buf;
+       size_t len;
+
+       cell = nvmem_cell_get(data->dev, "fgu_calib");
+       if (IS_ERR(cell))
+               return PTR_ERR(cell);
+
+       buf = nvmem_cell_read(cell, &len);
+       nvmem_cell_put(cell);
+
+       if (IS_ERR(buf))
+               return PTR_ERR(buf);
+
+       memcpy(&calib_data, buf, min(len, sizeof(u32)));
+
+       /*
+        * Get the ADC value corresponding to 4200 mV from eFuse controller
+        * according to below formula. Then convert to ADC values corresponding
+        * to 1000 mV and 1000 mA.
+        */
+       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;
+
+       kfree(buf);
+       return 0;
+}
+
+static int sc27xx_fgu_hw_init(struct sc27xx_fgu_data *data)
+{
+       struct power_supply_battery_info info = { };
+       struct power_supply_battery_ocv_table *table;
+       int ret, delta_clbcnt, alarm_adc;
+
+       ret = power_supply_get_battery_info(data->battery, &info);
+       if (ret) {
+               dev_err(data->dev, "failed to get battery information\n");
+               return ret;
+       }
+
+       data->total_cap = info.charge_full_design_uah / 1000;
+       data->max_volt = info.constant_charge_voltage_max_uv / 1000;
+       data->internal_resist = info.factory_internal_resistance_uohm / 1000;
+       data->min_volt = info.voltage_min_design_uv;
+
+       /*
+        * For SC27XX fuel gauge device, we only use one ocv-capacity
+        * table in normal temperature 20 Celsius.
+        */
+       table = power_supply_find_ocv2cap_table(&info, 20, &data->table_len);
+       if (!table)
+               return -EINVAL;
+
+       data->cap_table = devm_kmemdup(data->dev, table,
+                                      data->table_len * sizeof(*table),
+                                      GFP_KERNEL);
+       if (!data->cap_table) {
+               power_supply_put_battery_info(data->battery, &info);
+               return -ENOMEM;
+       }
+
+       data->alarm_cap = power_supply_ocv2cap_simple(data->cap_table,
+                                                     data->table_len,
+                                                     data->min_volt);
+
+       power_supply_put_battery_info(data->battery, &info);
+
+       ret = sc27xx_fgu_calibration(data);
+       if (ret)
+               return ret;
+
+       /* Enable the FGU module */
+       ret = regmap_update_bits(data->regmap, SC27XX_MODULE_EN0,
+                                SC27XX_FGU_EN, SC27XX_FGU_EN);
+       if (ret) {
+               dev_err(data->dev, "failed to enable fgu\n");
+               return ret;
+       }
+
+       /* Enable the FGU RTC clock to make it work */
+       ret = regmap_update_bits(data->regmap, SC27XX_CLK_EN0,
+                                SC27XX_FGU_RTC_EN, SC27XX_FGU_RTC_EN);
+       if (ret) {
+               dev_err(data->dev, "failed to enable fgu RTC clock\n");
+               goto disable_fgu;
+       }
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_CLR,
+                                SC27XX_FGU_INT_MASK, SC27XX_FGU_INT_MASK);
+       if (ret) {
+               dev_err(data->dev, "failed to clear interrupt status\n");
+               goto disable_clk;
+       }
+
+       /*
+        * Set the voltage low overload threshold, which means when the battery
+        * voltage is lower than this threshold, the controller will generate
+        * one interrupt to notify.
+        */
+       alarm_adc = sc27xx_fgu_voltage_to_adc(data, data->min_volt / 1000);
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_LOW_OVERLOAD,
+                                SC27XX_FGU_LOW_OVERLOAD_MASK, alarm_adc);
+       if (ret) {
+               dev_err(data->dev, "failed to set fgu low overload\n");
+               goto disable_clk;
+       }
+
+       /*
+        * Set the coulomb counter delta threshold, that means when the coulomb
+        * counter change is multiples of the delta threshold, the controller
+        * will generate one interrupt to notify the users to update the battery
+        * capacity. Now we set the delta threshold as a counter value of 1%
+        * capacity.
+        */
+       delta_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, 1);
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_CLBCNT_DELTL,
+                                SC27XX_FGU_CLBCNT_MASK, delta_clbcnt);
+       if (ret) {
+               dev_err(data->dev, "failed to set low delta coulomb counter\n");
+               goto disable_clk;
+       }
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_CLBCNT_DELTH,
+                                SC27XX_FGU_CLBCNT_MASK,
+                                delta_clbcnt >> SC27XX_FGU_CLBCNT_SHIFT);
+       if (ret) {
+               dev_err(data->dev, "failed to set high delta coulomb counter\n");
+               goto disable_clk;
+       }
+
+       /*
+        * Get the boot battery capacity when system powers on, which is used to
+        * initialize the coulomb counter. After that, we can read the coulomb
+        * counter to measure the battery capacity.
+        */
+       ret = sc27xx_fgu_get_boot_capacity(data, &data->init_cap);
+       if (ret) {
+               dev_err(data->dev, "failed to get boot capacity\n");
+               goto disable_clk;
+       }
+
+       /*
+        * Convert battery capacity to the corresponding initial coulomb counter
+        * and set into coulomb counter registers.
+        */
+       data->init_clbcnt = sc27xx_fgu_cap_to_clbcnt(data, data->init_cap);
+       ret = sc27xx_fgu_set_clbcnt(data, data->init_clbcnt);
+       if (ret) {
+               dev_err(data->dev, "failed to initialize coulomb counter\n");
+               goto disable_clk;
+       }
+
+       return 0;
+
+disable_clk:
+       regmap_update_bits(data->regmap, SC27XX_CLK_EN0, SC27XX_FGU_RTC_EN, 0);
+disable_fgu:
+       regmap_update_bits(data->regmap, SC27XX_MODULE_EN0, SC27XX_FGU_EN, 0);
+
+       return ret;
+}
+
+static int sc27xx_fgu_probe(struct platform_device *pdev)
+{
+       struct device_node *np = pdev->dev.of_node;
+       struct power_supply_config fgu_cfg = { };
+       struct sc27xx_fgu_data *data;
+       int ret, irq;
+
+       data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       data->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+       if (!data->regmap) {
+               dev_err(&pdev->dev, "failed to get regmap\n");
+               return -ENODEV;
+       }
+
+       ret = device_property_read_u32(&pdev->dev, "reg", &data->base);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to get fgu address\n");
+               return ret;
+       }
+
+       data->channel = devm_iio_channel_get(&pdev->dev, "bat-temp");
+       if (IS_ERR(data->channel)) {
+               dev_err(&pdev->dev, "failed to get IIO channel\n");
+               return PTR_ERR(data->channel);
+       }
+
+       data->gpiod = devm_gpiod_get(&pdev->dev, "bat-detect", GPIOD_IN);
+       if (IS_ERR(data->gpiod)) {
+               dev_err(&pdev->dev, "failed to get battery detection GPIO\n");
+               return PTR_ERR(data->gpiod);
+       }
+
+       ret = gpiod_get_value_cansleep(data->gpiod);
+       if (ret < 0) {
+               dev_err(&pdev->dev, "failed to get gpio state\n");
+               return ret;
+       }
+
+       data->bat_present = !!ret;
+       mutex_init(&data->lock);
+       data->dev = &pdev->dev;
+       platform_set_drvdata(pdev, data);
+
+       fgu_cfg.drv_data = data;
+       fgu_cfg.of_node = np;
+       data->battery = devm_power_supply_register(&pdev->dev, &sc27xx_fgu_desc,
+                                                  &fgu_cfg);
+       if (IS_ERR(data->battery)) {
+               dev_err(&pdev->dev, "failed to register power supply\n");
+               return PTR_ERR(data->battery);
+       }
+
+       ret = sc27xx_fgu_hw_init(data);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to initialize fgu hardware\n");
+               return ret;
+       }
+
+       ret = devm_add_action(&pdev->dev, sc27xx_fgu_disable, data);
+       if (ret) {
+               sc27xx_fgu_disable(data);
+               dev_err(&pdev->dev, "failed to add fgu disable action\n");
+               return ret;
+       }
+
+       irq = platform_get_irq(pdev, 0);
+       if (irq < 0) {
+               dev_err(&pdev->dev, "no irq resource specified\n");
+               return irq;
+       }
+
+       ret = devm_request_threaded_irq(data->dev, irq, NULL,
+                                       sc27xx_fgu_interrupt,
+                                       IRQF_NO_SUSPEND | IRQF_ONESHOT,
+                                       pdev->name, data);
+       if (ret) {
+               dev_err(data->dev, "failed to request fgu IRQ\n");
+               return ret;
+       }
+
+       irq = gpiod_to_irq(data->gpiod);
+       if (irq < 0) {
+               dev_err(&pdev->dev, "failed to translate GPIO to IRQ\n");
+               return irq;
+       }
+
+       ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+                                       sc27xx_fgu_bat_detection,
+                                       IRQF_ONESHOT | IRQF_TRIGGER_RISING |
+                                       IRQF_TRIGGER_FALLING,
+                                       pdev->name, data);
+       if (ret) {
+               dev_err(&pdev->dev, "failed to request IRQ\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int sc27xx_fgu_resume(struct device *dev)
+{
+       struct sc27xx_fgu_data *data = dev_get_drvdata(dev);
+       int ret;
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_EN,
+                                SC27XX_FGU_LOW_OVERLOAD_INT |
+                                SC27XX_FGU_CLBCNT_DELTA_INT, 0);
+       if (ret) {
+               dev_err(data->dev, "failed to disable fgu interrupts\n");
+               return ret;
+       }
+
+       return 0;
+}
+
+static int sc27xx_fgu_suspend(struct device *dev)
+{
+       struct sc27xx_fgu_data *data = dev_get_drvdata(dev);
+       int ret, status, ocv;
+
+       ret = sc27xx_fgu_get_status(data, &status);
+       if (ret)
+               return ret;
+
+       /*
+        * If we are charging, then no need to enable the FGU interrupts to
+        * adjust the battery capacity.
+        */
+       if (status != POWER_SUPPLY_STATUS_NOT_CHARGING)
+               return 0;
+
+       ret = regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_EN,
+                                SC27XX_FGU_LOW_OVERLOAD_INT,
+                                SC27XX_FGU_LOW_OVERLOAD_INT);
+       if (ret) {
+               dev_err(data->dev, "failed to enable low voltage interrupt\n");
+               return ret;
+       }
+
+       ret = sc27xx_fgu_get_vbat_ocv(data, &ocv);
+       if (ret)
+               goto disable_int;
+
+       /*
+        * If current OCV is less than the minimum voltage, we should enable the
+        * coulomb counter threshold interrupt to notify events to adjust the
+        * battery capacity.
+        */
+       if (ocv < data->min_volt) {
+               ret = regmap_update_bits(data->regmap,
+                                        data->base + SC27XX_FGU_INT_EN,
+                                        SC27XX_FGU_CLBCNT_DELTA_INT,
+                                        SC27XX_FGU_CLBCNT_DELTA_INT);
+               if (ret) {
+                       dev_err(data->dev,
+                               "failed to enable coulomb threshold int\n");
+                       goto disable_int;
+               }
+       }
+
+       return 0;
+
+disable_int:
+       regmap_update_bits(data->regmap, data->base + SC27XX_FGU_INT_EN,
+                          SC27XX_FGU_LOW_OVERLOAD_INT, 0);
+       return ret;
+}
+#endif
+
+static const struct dev_pm_ops sc27xx_fgu_pm_ops = {
+       SET_SYSTEM_SLEEP_PM_OPS(sc27xx_fgu_suspend, sc27xx_fgu_resume)
+};
+
+static const struct of_device_id sc27xx_fgu_of_match[] = {
+       { .compatible = "sprd,sc2731-fgu", },
+       { }
+};
+
+static struct platform_driver sc27xx_fgu_driver = {
+       .probe = sc27xx_fgu_probe,
+       .driver = {
+               .name = "sc27xx-fgu",
+               .of_match_table = sc27xx_fgu_of_match,
+               .pm = &sc27xx_fgu_pm_ops,
+       }
+};
+
+module_platform_driver(sc27xx_fgu_driver);
+
+MODULE_DESCRIPTION("Spreadtrum SC27XX PMICs Fual Gauge Unit Driver");
+MODULE_LICENSE("GPL v2");
index 1293695..a353cd2 100644 (file)
@@ -266,6 +266,7 @@ enum axp20x_variants {
 #define AXP288_RT_BATT_V_H             0xa0
 #define AXP288_RT_BATT_V_L             0xa1
 
+#define AXP813_ACIN_PATH_CTRL          0x3a
 #define AXP813_ADC_RATE                        0x85
 
 /* Fuel Gauge */
index c4fa907..2ce8d00 100644 (file)
@@ -119,7 +119,7 @@ struct charger_regulator {
        struct charger_cable *cables;
        int num_cables;
 
-       struct attribute_group attr_g;
+       struct attribute_group attr_grp;
        struct device_attribute attr_name;
        struct device_attribute attr_state;
        struct device_attribute attr_externally_control;
@@ -186,6 +186,7 @@ struct charger_desc {
 
        int num_charger_regulators;
        struct charger_regulator *charger_regulators;
+       const struct attribute_group **sysfs_groups;
 
        const char *psy_fuel_gauge;
 
index f807691..57b2ab8 100644 (file)
@@ -204,6 +204,9 @@ struct power_supply_config {
        /* Driver private data */
        void *drv_data;
 
+       /* Device specific sysfs attributes */
+       const struct attribute_group **attr_grp;
+
        char **supplied_to;
        size_t num_supplicants;
 };
@@ -309,6 +312,13 @@ struct power_supply_info {
        int use_for_apm;
 };
 
+struct power_supply_battery_ocv_table {
+       int ocv;        /* microVolts */
+       int capacity;   /* percent */
+};
+
+#define POWER_SUPPLY_OCV_TEMP_MAX 20
+
 /*
  * This is the recommended struct to manage static battery parameters,
  * populated by power_supply_get_battery_info(). Most platform drivers should
@@ -326,6 +336,10 @@ struct power_supply_battery_info {
        int charge_term_current_ua;         /* microAmps */
        int constant_charge_current_max_ua; /* microAmps */
        int constant_charge_voltage_max_uv; /* microVolts */
+       int factory_internal_resistance_uohm;   /* microOhms */
+       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];
 };
 
 extern struct atomic_notifier_head power_supply_notifier;
@@ -349,6 +363,15 @@ devm_power_supply_get_by_phandle(struct device *dev, const char *property)
 
 extern int power_supply_get_battery_info(struct power_supply *psy,
                                         struct power_supply_battery_info *info);
+extern void power_supply_put_battery_info(struct power_supply *psy,
+                                         struct power_supply_battery_info *info);
+extern int power_supply_ocv2cap_simple(struct power_supply_battery_ocv_table *table,
+                                      int table_len, int ocv);
+extern struct power_supply_battery_ocv_table *
+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 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(