Merge tag 'for-v3.12' of git://git.infradead.org/battery-2.6
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 11 Sep 2013 05:58:14 +0000 (22:58 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 11 Sep 2013 05:58:14 +0000 (22:58 -0700)
Pull battery/power supply driver updates from Anton Vorontsov:
 "New drivers:

   - APM X-Gene system reboot driver by Feng Kan and Loc Ho (APM).

   - Qualcomm MSM reboot/poweroff driver by Abhimanyu Kapur (Codeaurora).

   - Texas Instruments BQ24190 charger driver by Mark A.  Greer (Animal
     Creek Technologies).

   - Texas Instruments TWL4030 MADC battery driver by Lukas Märdian and
     Marek Belisko (Golden Delicious Computers).  The driver is used on
     Freerunner GTA04 phones.

  Highlighted fixes and improvements:

   - Suspend/wakeup logic improvements: power supply objects will block
     system suspend until all power supply events are processed.  Thanks
     to Zoran Markovic (Linaro), Arve Hjonnevag and Todd Poynor (Google)"

* tag 'for-v3.12' of git://git.infradead.org/battery-2.6:
  rx51_battery: Fix channel number when reading adc value
  power: Add twl4030_madc battery driver.
  bq24190_charger: Workaround SS definition problem on i386 builds
  power_supply: Prevent suspend until power supply events are processed
  vexpress-poweroff: Should depend on the required infrastructure
  twl4030-charger: Fix compiler warning with regulator_enable()
  rx51_battery: Replace hardcoded channels values.
  bq24190_charger: Add support for TI BQ24190 Battery Charger
  ab8500-charger: We print an unintended error message
  max8925_power: Fix missing of_node_put
  power_supply: Replace strict_strtol() with kstrtol()
  power: Add APM X-Gene system reboot driver
  power_supply: tosa_battery: Get rid of irq_to_gpio usage
  power supply: collie_battery: Convert to use dev_pm_ops
  power_supply: Make goldfish_battery depend on GOLDFISH || COMPILE_TEST
  power: reset: Add msm restart support
  MAINTAINERS: drivers/power: add entry for SmartReflex AVS drivers

21 files changed:
Documentation/devicetree/bindings/power_supply/msm-poweroff.txt [new file with mode: 0644]
MAINTAINERS
drivers/power/Kconfig
drivers/power/Makefile
drivers/power/ab8500_charger.c
drivers/power/bq24190_charger.c [new file with mode: 0644]
drivers/power/collie_battery.c
drivers/power/max8925_power.c
drivers/power/power_supply_core.c
drivers/power/power_supply_sysfs.c
drivers/power/reset/Kconfig
drivers/power/reset/Makefile
drivers/power/reset/msm-poweroff.c [new file with mode: 0644]
drivers/power/reset/xgene-reboot.c [new file with mode: 0644]
drivers/power/rx51_battery.c
drivers/power/tosa_battery.c
drivers/power/twl4030_charger.c
drivers/power/twl4030_madc_battery.c [new file with mode: 0644]
include/linux/power/bq24190_charger.h [new file with mode: 0644]
include/linux/power/twl4030_madc_battery.h [new file with mode: 0644]
include/linux/power_supply.h

diff --git a/Documentation/devicetree/bindings/power_supply/msm-poweroff.txt b/Documentation/devicetree/bindings/power_supply/msm-poweroff.txt
new file mode 100644 (file)
index 0000000..ce44ad3
--- /dev/null
@@ -0,0 +1,17 @@
+MSM Restart Driver
+
+A power supply hold (ps-hold) bit is set to power the msm chipsets.
+Clearing that bit allows us to restart/poweroff. The difference
+between poweroff and restart is determined by unique power manager IC
+settings.
+
+Required Properties:
+-compatible: "qcom,pshold"
+-reg: Specifies the physical address of the ps-hold register
+
+Example:
+
+       restart@fc4ab000 {
+               compatible = "qcom,pshold";
+               reg = <0xfc4ab000 0x4>;
+       };
index 38a37f7..d721af1 100644 (file)
@@ -7624,6 +7624,14 @@ S:       Maintained
 F:     Documentation/security/Smack.txt
 F:     security/smack/
 
+SMARTREFLEX DRIVERS FOR ADAPTIVE VOLTAGE SCALING (AVS)
+M:     Kevin Hilman <khilman@kernel.org>
+M:     Nishanth Menon <nm@ti.com>
+S:     Maintained
+F:     drivers/power/avs/smartreflex.c
+F:     include/linux/power/smartreflex.h
+L:     linux-pm@vger.kernel.org
+
 SMC91x ETHERNET DRIVER
 M:     Nicolas Pitre <nico@fluxnic.net>
 S:     Odd Fixes
index 7b8979c..bb49ab6 100644 (file)
@@ -216,6 +216,13 @@ config BATTERY_S3C_ADC
        help
          Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery
 
+config BATTERY_TWL4030_MADC
+       tristate "TWL4030 MADC battery driver"
+       depends on TWL4030_MADC
+       help
+         Say Y here to enable this dumb driver for batteries managed
+         through the TWL4030 MADC.
+
 config CHARGER_88PM860X
        tristate "Marvell 88PM860x Charger driver"
        depends on MFD_88PM860X && BATTERY_88PM860X
@@ -334,6 +341,12 @@ config CHARGER_BQ2415X
          You'll need this driver to charge batteries on e.g. Nokia
          RX-51/N900.
 
+config CHARGER_BQ24190
+       tristate "TI BQ24190 battery charger driver"
+       depends on I2C && GPIOLIB
+       help
+         Say Y to enable support for the TI BQ24190 battery charger.
+
 config CHARGER_SMB347
        tristate "Summit Microelectronics SMB347 Battery Charger"
        depends on I2C
@@ -357,7 +370,7 @@ config AB8500_BM
 
 config BATTERY_GOLDFISH
        tristate "Goldfish battery driver"
-       depends on GENERIC_HARDIRQS
+       depends on GENERIC_HARDIRQS && (GOLDFISH || COMPILE_TEST)
        help
          Say Y to enable support for the battery and AC power in the
          Goldfish emulator.
index 653bf6c..a4b7417 100644 (file)
@@ -34,6 +34,7 @@ obj-$(CONFIG_BATTERY_MAX17040)        += max17040_battery.o
 obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
 obj-$(CONFIG_BATTERY_Z2)       += z2_battery.o
 obj-$(CONFIG_BATTERY_S3C_ADC)  += s3c_adc_battery.o
+obj-$(CONFIG_BATTERY_TWL4030_MADC)     += twl4030_madc_battery.o
 obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o
 obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o
 obj-$(CONFIG_BATTERY_JZ4740)   += jz4740-battery.o
@@ -50,6 +51,7 @@ obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
 obj-$(CONFIG_CHARGER_MAX8997)  += max8997_charger.o
 obj-$(CONFIG_CHARGER_MAX8998)  += max8998_charger.o
 obj-$(CONFIG_CHARGER_BQ2415X)  += bq2415x_charger.o
+obj-$(CONFIG_CHARGER_BQ24190)  += bq24190_charger.o
 obj-$(CONFIG_POWER_AVS)                += avs/
 obj-$(CONFIG_CHARGER_SMB347)   += smb347-charger.o
 obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
index f098fda..a4c4a10 100644 (file)
@@ -774,6 +774,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
                di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
                dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
                                di->max_usb_in_curr.usb_type_max);
+               break;
        case USB_STAT_NOT_VALID_LINK:
                dev_err(di->dev, "USB Type invalid - try charging anyway\n");
                di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c
new file mode 100644 (file)
index 0000000..ad3ff8f
--- /dev/null
@@ -0,0 +1,1549 @@
+/*
+ * Driver for the TI bq24190 battery charger.
+ *
+ * Author: Mark A. Greer <mgreer@animalcreek.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/of_irq.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+
+#include <linux/power/bq24190_charger.h>
+
+
+#define        BQ24190_MANUFACTURER    "Texas Instruments"
+
+#define BQ24190_REG_ISC                0x00 /* Input Source Control */
+#define BQ24190_REG_ISC_EN_HIZ_MASK            BIT(7)
+#define BQ24190_REG_ISC_EN_HIZ_SHIFT           7
+#define BQ24190_REG_ISC_VINDPM_MASK            (BIT(6) | BIT(5) | BIT(4) | \
+                                                BIT(3))
+#define BQ24190_REG_ISC_VINDPM_SHIFT           3
+#define BQ24190_REG_ISC_IINLIM_MASK            (BIT(2) | BIT(1) | BIT(0))
+#define BQ24190_REG_ISC_IINLIM_SHIFT           0
+
+#define BQ24190_REG_POC                0x01 /* Power-On Configuration */
+#define BQ24190_REG_POC_RESET_MASK             BIT(7)
+#define BQ24190_REG_POC_RESET_SHIFT            7
+#define BQ24190_REG_POC_WDT_RESET_MASK         BIT(6)
+#define BQ24190_REG_POC_WDT_RESET_SHIFT                6
+#define BQ24190_REG_POC_CHG_CONFIG_MASK                (BIT(5) | BIT(4))
+#define BQ24190_REG_POC_CHG_CONFIG_SHIFT       4
+#define BQ24190_REG_POC_SYS_MIN_MASK           (BIT(3) | BIT(2) | BIT(1))
+#define BQ24190_REG_POC_SYS_MIN_SHIFT          1
+#define BQ24190_REG_POC_BOOST_LIM_MASK         BIT(0)
+#define BQ24190_REG_POC_BOOST_LIM_SHIFT                0
+
+#define BQ24190_REG_CCC                0x02 /* Charge Current Control */
+#define BQ24190_REG_CCC_ICHG_MASK              (BIT(7) | BIT(6) | BIT(5) | \
+                                                BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_CCC_ICHG_SHIFT             2
+#define BQ24190_REG_CCC_FORCE_20PCT_MASK       BIT(0)
+#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT      0
+
+#define BQ24190_REG_PCTCC      0x03 /* Pre-charge/Termination Current Cntl */
+#define BQ24190_REG_PCTCC_IPRECHG_MASK         (BIT(7) | BIT(6) | BIT(5) | \
+                                                BIT(4))
+#define BQ24190_REG_PCTCC_IPRECHG_SHIFT                4
+#define BQ24190_REG_PCTCC_ITERM_MASK           (BIT(3) | BIT(2) | BIT(1) | \
+                                                BIT(0))
+#define BQ24190_REG_PCTCC_ITERM_SHIFT          0
+
+#define BQ24190_REG_CVC                0x04 /* Charge Voltage Control */
+#define BQ24190_REG_CVC_VREG_MASK              (BIT(7) | BIT(6) | BIT(5) | \
+                                                BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_CVC_VREG_SHIFT             2
+#define BQ24190_REG_CVC_BATLOWV_MASK           BIT(1)
+#define BQ24190_REG_CVC_BATLOWV_SHIFT          1
+#define BQ24190_REG_CVC_VRECHG_MASK            BIT(0)
+#define BQ24190_REG_CVC_VRECHG_SHIFT           0
+
+#define BQ24190_REG_CTTC       0x05 /* Charge Term/Timer Control */
+#define BQ24190_REG_CTTC_EN_TERM_MASK          BIT(7)
+#define BQ24190_REG_CTTC_EN_TERM_SHIFT         7
+#define BQ24190_REG_CTTC_TERM_STAT_MASK                BIT(6)
+#define BQ24190_REG_CTTC_TERM_STAT_SHIFT       6
+#define BQ24190_REG_CTTC_WATCHDOG_MASK         (BIT(5) | BIT(4))
+#define BQ24190_REG_CTTC_WATCHDOG_SHIFT                4
+#define BQ24190_REG_CTTC_EN_TIMER_MASK         BIT(3)
+#define BQ24190_REG_CTTC_EN_TIMER_SHIFT                3
+#define BQ24190_REG_CTTC_CHG_TIMER_MASK                (BIT(2) | BIT(1))
+#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT       1
+#define BQ24190_REG_CTTC_JEITA_ISET_MASK       BIT(0)
+#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT      0
+
+#define BQ24190_REG_ICTRC      0x06 /* IR Comp/Thermal Regulation Control */
+#define BQ24190_REG_ICTRC_BAT_COMP_MASK                (BIT(7) | BIT(6) | BIT(5))
+#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT       5
+#define BQ24190_REG_ICTRC_VCLAMP_MASK          (BIT(4) | BIT(3) | BIT(2))
+#define BQ24190_REG_ICTRC_VCLAMP_SHIFT         2
+#define BQ24190_REG_ICTRC_TREG_MASK            (BIT(1) | BIT(0))
+#define BQ24190_REG_ICTRC_TREG_SHIFT           0
+
+#define BQ24190_REG_MOC                0x07 /* Misc. Operation Control */
+#define BQ24190_REG_MOC_DPDM_EN_MASK           BIT(7)
+#define BQ24190_REG_MOC_DPDM_EN_SHIFT          7
+#define BQ24190_REG_MOC_TMR2X_EN_MASK          BIT(6)
+#define BQ24190_REG_MOC_TMR2X_EN_SHIFT         6
+#define BQ24190_REG_MOC_BATFET_DISABLE_MASK    BIT(5)
+#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT   5
+#define BQ24190_REG_MOC_JEITA_VSET_MASK                BIT(4)
+#define BQ24190_REG_MOC_JEITA_VSET_SHIFT       4
+#define BQ24190_REG_MOC_INT_MASK_MASK          (BIT(1) | BIT(0))
+#define BQ24190_REG_MOC_INT_MASK_SHIFT         0
+
+#define BQ24190_REG_SS         0x08 /* System Status */
+#define BQ24190_REG_SS_VBUS_STAT_MASK          (BIT(7) | BIT(6))
+#define BQ24190_REG_SS_VBUS_STAT_SHIFT         6
+#define BQ24190_REG_SS_CHRG_STAT_MASK          (BIT(5) | BIT(4))
+#define BQ24190_REG_SS_CHRG_STAT_SHIFT         4
+#define BQ24190_REG_SS_DPM_STAT_MASK           BIT(3)
+#define BQ24190_REG_SS_DPM_STAT_SHIFT          3
+#define BQ24190_REG_SS_PG_STAT_MASK            BIT(2)
+#define BQ24190_REG_SS_PG_STAT_SHIFT           2
+#define BQ24190_REG_SS_THERM_STAT_MASK         BIT(1)
+#define BQ24190_REG_SS_THERM_STAT_SHIFT                1
+#define BQ24190_REG_SS_VSYS_STAT_MASK          BIT(0)
+#define BQ24190_REG_SS_VSYS_STAT_SHIFT         0
+
+#define BQ24190_REG_F          0x09 /* Fault */
+#define BQ24190_REG_F_WATCHDOG_FAULT_MASK      BIT(7)
+#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT     7
+#define BQ24190_REG_F_BOOST_FAULT_MASK         BIT(6)
+#define BQ24190_REG_F_BOOST_FAULT_SHIFT                6
+#define BQ24190_REG_F_CHRG_FAULT_MASK          (BIT(5) | BIT(4))
+#define BQ24190_REG_F_CHRG_FAULT_SHIFT         4
+#define BQ24190_REG_F_BAT_FAULT_MASK           BIT(3)
+#define BQ24190_REG_F_BAT_FAULT_SHIFT          3
+#define BQ24190_REG_F_NTC_FAULT_MASK           (BIT(2) | BIT(1) | BIT(0))
+#define BQ24190_REG_F_NTC_FAULT_SHIFT          0
+
+#define BQ24190_REG_VPRS       0x0A /* Vendor/Part/Revision Status */
+#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_24192I                     0x3
+#define BQ24190_REG_VPRS_TS_PROFILE_MASK       BIT(2)
+#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT      2
+#define BQ24190_REG_VPRS_DEV_REG_MASK          (BIT(1) | BIT(0))
+#define BQ24190_REG_VPRS_DEV_REG_SHIFT         0
+
+/*
+ * The FAULT register is latched by the bq24190 (except for NTC_FAULT)
+ * so the first read after a fault returns the latched value and subsequent
+ * reads return the current value.  In order to return the fault status
+ * to the user, have the interrupt handler save the reg's value and retrieve
+ * it in the appropriate health/status routine.  Each routine has its own
+ * flag indicating whether it should use the value stored by the last run
+ * of the interrupt handler or do an actual reg read.  That way each routine
+ * can report back whatever fault may have occured.
+ */
+struct bq24190_dev_info {
+       struct i2c_client               *client;
+       struct device                   *dev;
+       struct power_supply             charger;
+       struct power_supply             battery;
+       char                            model_name[I2C_NAME_SIZE];
+       kernel_ulong_t                  model;
+       unsigned int                    gpio_int;
+       unsigned int                    irq;
+       struct mutex                    f_reg_lock;
+       bool                            first_time;
+       bool                            charger_health_valid;
+       bool                            battery_health_valid;
+       bool                            battery_status_valid;
+       u8                              f_reg;
+       u8                              ss_reg;
+       u8                              watchdog;
+};
+
+/*
+ * 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.
+ * The index of the array is the value that goes in the register; the
+ * number at that index in the array is the real-world value that it
+ * represents.
+ */
+/* REG02[7:2] (ICHG) in uAh */
+static const int bq24190_ccc_ichg_values[] = {
+        512000,  576000,  640000,  704000,  768000,  832000,  896000,  960000,
+       1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000,
+       1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000,
+       2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000,
+       2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000,
+       3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000,
+       3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000,
+       4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000
+};
+
+/* REG04[7:2] (VREG) in uV */
+static const int bq24190_cvc_vreg_values[] = {
+       3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000,
+       3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000,
+       3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000,
+       3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000,
+       4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000,
+       4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000,
+       4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000,
+       4400000
+};
+
+/* REG06[1:0] (TREG) in tenths of degrees Celcius */
+static const int bq24190_ictrc_treg_values[] = {
+       600, 800, 1000, 1200
+};
+
+/*
+ * Return the index in 'tbl' of greatest value that is less than or equal to
+ * 'val'.  The index range returned is 0 to 'tbl_size' - 1.  Assumes that
+ * the values in 'tbl' are sorted from smallest to largest and 'tbl_size'
+ * is less than 2^8.
+ */
+static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v)
+{
+       int i;
+
+       for (i = 1; i < tbl_size; i++)
+               if (v < tbl[i])
+                       break;
+
+       return i - 1;
+}
+
+/* Basic driver I/O routines */
+
+static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data)
+{
+       int ret;
+
+       ret = i2c_smbus_read_byte_data(bdi->client, reg);
+       if (ret < 0)
+               return ret;
+
+       *data = ret;
+       return 0;
+}
+
+static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data)
+{
+       return i2c_smbus_write_byte_data(bdi->client, reg, data);
+}
+
+static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg,
+               u8 mask, u8 shift, u8 *data)
+{
+       u8 v;
+       int ret;
+
+       ret = bq24190_read(bdi, reg, &v);
+       if (ret < 0)
+               return ret;
+
+       v &= mask;
+       v >>= shift;
+       *data = v;
+
+       return 0;
+}
+
+static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg,
+               u8 mask, u8 shift, u8 data)
+{
+       u8 v;
+       int ret;
+
+       ret = bq24190_read(bdi, reg, &v);
+       if (ret < 0)
+               return ret;
+
+       v &= ~mask;
+       v |= ((data << shift) & mask);
+
+       return bq24190_write(bdi, reg, v);
+}
+
+static int bq24190_get_field_val(struct bq24190_dev_info *bdi,
+               u8 reg, u8 mask, u8 shift,
+               const int tbl[], int tbl_size,
+               int *val)
+{
+       u8 v;
+       int ret;
+
+       ret = bq24190_read_mask(bdi, reg, mask, shift, &v);
+       if (ret < 0)
+               return ret;
+
+       v = (v >= tbl_size) ? (tbl_size - 1) : v;
+       *val = tbl[v];
+
+       return 0;
+}
+
+static int bq24190_set_field_val(struct bq24190_dev_info *bdi,
+               u8 reg, u8 mask, u8 shift,
+               const int tbl[], int tbl_size,
+               int val)
+{
+       u8 idx;
+
+       idx = bq24190_find_idx(tbl, tbl_size, val);
+
+       return bq24190_write_mask(bdi, reg, mask, shift, idx);
+}
+
+#ifdef CONFIG_SYSFS
+/*
+ * There are a numerous options that are configurable on the bq24190
+ * that go well beyond what the power_supply properties provide access to.
+ * Provide sysfs access to them so they can be examined and possibly modified
+ * on the fly.  They will be provided for the charger power_supply object only
+ * and will be prefixed by 'f_' to make them easier to recognize.
+ */
+
+#define BQ24190_SYSFS_FIELD(_name, r, f, m, store)                     \
+{                                                                      \
+       .attr   = __ATTR(f_##_name, m, bq24190_sysfs_show, store),      \
+       .reg    = BQ24190_REG_##r,                                      \
+       .mask   = BQ24190_REG_##r##_##f##_MASK,                         \
+       .shift  = BQ24190_REG_##r##_##f##_SHIFT,                        \
+}
+
+#define BQ24190_SYSFS_FIELD_RW(_name, r, f)                            \
+               BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO,     \
+                               bq24190_sysfs_store)
+
+#define BQ24190_SYSFS_FIELD_RO(_name, r, f)                            \
+               BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL)
+
+static ssize_t bq24190_sysfs_show(struct device *dev,
+               struct device_attribute *attr, char *buf);
+static ssize_t bq24190_sysfs_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count);
+
+struct bq24190_sysfs_field_info {
+       struct device_attribute attr;
+       u8      reg;
+       u8      mask;
+       u8      shift;
+};
+
+/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */
+#undef SS
+
+static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = {
+                       /*      sysfs name      reg     field in reg */
+       BQ24190_SYSFS_FIELD_RW(en_hiz,          ISC,    EN_HIZ),
+       BQ24190_SYSFS_FIELD_RW(vindpm,          ISC,    VINDPM),
+       BQ24190_SYSFS_FIELD_RW(iinlim,          ISC,    IINLIM),
+       BQ24190_SYSFS_FIELD_RW(chg_config,      POC,    CHG_CONFIG),
+       BQ24190_SYSFS_FIELD_RW(sys_min,         POC,    SYS_MIN),
+       BQ24190_SYSFS_FIELD_RW(boost_lim,       POC,    BOOST_LIM),
+       BQ24190_SYSFS_FIELD_RW(ichg,            CCC,    ICHG),
+       BQ24190_SYSFS_FIELD_RW(force_20_pct,    CCC,    FORCE_20PCT),
+       BQ24190_SYSFS_FIELD_RW(iprechg,         PCTCC,  IPRECHG),
+       BQ24190_SYSFS_FIELD_RW(iterm,           PCTCC,  ITERM),
+       BQ24190_SYSFS_FIELD_RW(vreg,            CVC,    VREG),
+       BQ24190_SYSFS_FIELD_RW(batlowv,         CVC,    BATLOWV),
+       BQ24190_SYSFS_FIELD_RW(vrechg,          CVC,    VRECHG),
+       BQ24190_SYSFS_FIELD_RW(en_term,         CTTC,   EN_TERM),
+       BQ24190_SYSFS_FIELD_RW(term_stat,       CTTC,   TERM_STAT),
+       BQ24190_SYSFS_FIELD_RO(watchdog,        CTTC,   WATCHDOG),
+       BQ24190_SYSFS_FIELD_RW(en_timer,        CTTC,   EN_TIMER),
+       BQ24190_SYSFS_FIELD_RW(chg_timer,       CTTC,   CHG_TIMER),
+       BQ24190_SYSFS_FIELD_RW(jeta_iset,       CTTC,   JEITA_ISET),
+       BQ24190_SYSFS_FIELD_RW(bat_comp,        ICTRC,  BAT_COMP),
+       BQ24190_SYSFS_FIELD_RW(vclamp,          ICTRC,  VCLAMP),
+       BQ24190_SYSFS_FIELD_RW(treg,            ICTRC,  TREG),
+       BQ24190_SYSFS_FIELD_RW(dpdm_en,         MOC,    DPDM_EN),
+       BQ24190_SYSFS_FIELD_RW(tmr2x_en,        MOC,    TMR2X_EN),
+       BQ24190_SYSFS_FIELD_RW(batfet_disable,  MOC,    BATFET_DISABLE),
+       BQ24190_SYSFS_FIELD_RW(jeita_vset,      MOC,    JEITA_VSET),
+       BQ24190_SYSFS_FIELD_RO(int_mask,        MOC,    INT_MASK),
+       BQ24190_SYSFS_FIELD_RO(vbus_stat,       SS,     VBUS_STAT),
+       BQ24190_SYSFS_FIELD_RO(chrg_stat,       SS,     CHRG_STAT),
+       BQ24190_SYSFS_FIELD_RO(dpm_stat,        SS,     DPM_STAT),
+       BQ24190_SYSFS_FIELD_RO(pg_stat,         SS,     PG_STAT),
+       BQ24190_SYSFS_FIELD_RO(therm_stat,      SS,     THERM_STAT),
+       BQ24190_SYSFS_FIELD_RO(vsys_stat,       SS,     VSYS_STAT),
+       BQ24190_SYSFS_FIELD_RO(watchdog_fault,  F,      WATCHDOG_FAULT),
+       BQ24190_SYSFS_FIELD_RO(boost_fault,     F,      BOOST_FAULT),
+       BQ24190_SYSFS_FIELD_RO(chrg_fault,      F,      CHRG_FAULT),
+       BQ24190_SYSFS_FIELD_RO(bat_fault,       F,      BAT_FAULT),
+       BQ24190_SYSFS_FIELD_RO(ntc_fault,       F,      NTC_FAULT),
+       BQ24190_SYSFS_FIELD_RO(pn,              VPRS,   PN),
+       BQ24190_SYSFS_FIELD_RO(ts_profile,      VPRS,   TS_PROFILE),
+       BQ24190_SYSFS_FIELD_RO(dev_reg,         VPRS,   DEV_REG),
+};
+
+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,
+};
+
+static void bq24190_sysfs_init_attrs(void)
+{
+       int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
+
+       for (i = 0; i < limit; i++)
+               bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr;
+
+       bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */
+}
+
+static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup(
+               const char *name)
+{
+       int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl);
+
+       for (i = 0; i < limit; i++)
+               if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name))
+                       break;
+
+       if (i >= limit)
+               return NULL;
+
+       return &bq24190_sysfs_field_tbl[i];
+}
+
+static ssize_t bq24190_sysfs_show(struct device *dev,
+               struct device_attribute *attr, char *buf)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, charger);
+       struct bq24190_sysfs_field_info *info;
+       int ret;
+       u8 v;
+
+       info = bq24190_sysfs_field_lookup(attr->attr.name);
+       if (!info)
+               return -EINVAL;
+
+       ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v);
+       if (ret)
+               return ret;
+
+       return scnprintf(buf, PAGE_SIZE, "%hhx\n", v);
+}
+
+static ssize_t bq24190_sysfs_store(struct device *dev,
+               struct device_attribute *attr, const char *buf, size_t count)
+{
+       struct power_supply *psy = dev_get_drvdata(dev);
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, charger);
+       struct bq24190_sysfs_field_info *info;
+       int ret;
+       u8 v;
+
+       info = bq24190_sysfs_field_lookup(attr->attr.name);
+       if (!info)
+               return -EINVAL;
+
+       ret = kstrtou8(buf, 0, &v);
+       if (ret < 0)
+               return ret;
+
+       ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v);
+       if (ret)
+               return ret;
+
+       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
+
+/*
+ * According to the "Host Mode and default Mode" section of the
+ * manual, a write to any register causes the bq24190 to switch
+ * from default mode to host mode.  It will switch back to default
+ * mode after a WDT timeout unless the WDT is turned off as well.
+ * So, by simply turning off the WDT, we accomplish both with the
+ * same write.
+ */
+static int bq24190_set_mode_host(struct bq24190_dev_info *bdi)
+{
+       int ret;
+       u8 v;
+
+       ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v);
+       if (ret < 0)
+               return ret;
+
+       bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >>
+                                       BQ24190_REG_CTTC_WATCHDOG_SHIFT);
+       v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK;
+
+       return bq24190_write(bdi, BQ24190_REG_CTTC, v);
+}
+
+static int bq24190_register_reset(struct bq24190_dev_info *bdi)
+{
+       int ret, limit = 100;
+       u8 v;
+
+       /* Reset the registers */
+       ret = bq24190_write_mask(bdi, BQ24190_REG_POC,
+                       BQ24190_REG_POC_RESET_MASK,
+                       BQ24190_REG_POC_RESET_SHIFT,
+                       0x1);
+       if (ret < 0)
+               return ret;
+
+       /* Reset bit will be cleared by hardware so poll until it is */
+       do {
+               ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
+                               BQ24190_REG_POC_RESET_MASK,
+                               BQ24190_REG_POC_RESET_SHIFT,
+                               &v);
+               if (ret < 0)
+                       return ret;
+
+               if (!v)
+                       break;
+
+               udelay(10);
+       } while (--limit);
+
+       if (!limit)
+               return -EIO;
+
+       return 0;
+}
+
+/* Charger power supply property routines */
+
+static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 v;
+       int type, ret;
+
+       ret = bq24190_read_mask(bdi, BQ24190_REG_POC,
+                       BQ24190_REG_POC_CHG_CONFIG_MASK,
+                       BQ24190_REG_POC_CHG_CONFIG_SHIFT,
+                       &v);
+       if (ret < 0)
+               return ret;
+
+       /* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */
+       if (!v) {
+               type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+       } else {
+               ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+                               BQ24190_REG_CCC_FORCE_20PCT_MASK,
+                               BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
+                               &v);
+               if (ret < 0)
+                       return ret;
+
+               type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE :
+                            POWER_SUPPLY_CHARGE_TYPE_FAST;
+       }
+
+       val->intval = type;
+
+       return 0;
+}
+
+static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi,
+               const union power_supply_propval *val)
+{
+       u8 chg_config, force_20pct, en_term;
+       int ret;
+
+       /*
+        * According to the "Termination when REG02[0] = 1" section of
+        * the bq24190 manual, the trickle charge could be less than the
+        * termination current so it recommends turning off the termination
+        * function.
+        *
+        * Note: AFAICT from the datasheet, the user will have to manually
+        * turn off the charging when in 20% mode.  If its not turned off,
+        * there could be battery damage.  So, use this mode at your own risk.
+        */
+       switch (val->intval) {
+       case POWER_SUPPLY_CHARGE_TYPE_NONE:
+               chg_config = 0x0;
+               break;
+       case POWER_SUPPLY_CHARGE_TYPE_TRICKLE:
+               chg_config = 0x1;
+               force_20pct = 0x1;
+               en_term = 0x0;
+               break;
+       case POWER_SUPPLY_CHARGE_TYPE_FAST:
+               chg_config = 0x1;
+               force_20pct = 0x0;
+               en_term = 0x1;
+               break;
+       default:
+               return -EINVAL;
+       }
+
+       if (chg_config) { /* Enabling the charger */
+               ret = bq24190_write_mask(bdi, BQ24190_REG_CCC,
+                               BQ24190_REG_CCC_FORCE_20PCT_MASK,
+                               BQ24190_REG_CCC_FORCE_20PCT_SHIFT,
+                               force_20pct);
+               if (ret < 0)
+                       return ret;
+
+               ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC,
+                               BQ24190_REG_CTTC_EN_TERM_MASK,
+                               BQ24190_REG_CTTC_EN_TERM_SHIFT,
+                               en_term);
+               if (ret < 0)
+                       return ret;
+       }
+
+       return bq24190_write_mask(bdi, BQ24190_REG_POC,
+                       BQ24190_REG_POC_CHG_CONFIG_MASK,
+                       BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config);
+}
+
+static int bq24190_charger_get_health(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 v;
+       int health, ret;
+
+       mutex_lock(&bdi->f_reg_lock);
+
+       if (bdi->charger_health_valid) {
+               v = bdi->f_reg;
+               bdi->charger_health_valid = false;
+               mutex_unlock(&bdi->f_reg_lock);
+       } else {
+               mutex_unlock(&bdi->f_reg_lock);
+
+               ret = bq24190_read(bdi, BQ24190_REG_F, &v);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (v & BQ24190_REG_F_BOOST_FAULT_MASK) {
+               /*
+                * This could be over-current or over-voltage but there's
+                * no way to tell which.  Return 'OVERVOLTAGE' since there
+                * isn't an 'OVERCURRENT' value defined that we can return
+                * even if it was over-current.
+                */
+               health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+       } else {
+               v &= BQ24190_REG_F_CHRG_FAULT_MASK;
+               v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
+
+               switch (v) {
+               case 0x0: /* Normal */
+                       health = POWER_SUPPLY_HEALTH_GOOD;
+                       break;
+               case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */
+                       /*
+                        * This could be over-voltage or under-voltage
+                        * and there's no way to tell which.  Instead
+                        * of looking foolish and returning 'OVERVOLTAGE'
+                        * when its really under-voltage, just return
+                        * 'UNSPEC_FAILURE'.
+                        */
+                       health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+                       break;
+               case 0x2: /* Thermal Shutdown */
+                       health = POWER_SUPPLY_HEALTH_OVERHEAT;
+                       break;
+               case 0x3: /* Charge Safety Timer Expiration */
+                       health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+                       break;
+               default:
+                       health = POWER_SUPPLY_HEALTH_UNKNOWN;
+               }
+       }
+
+       val->intval = health;
+
+       return 0;
+}
+
+static int bq24190_charger_get_online(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 v;
+       int ret;
+
+       ret = bq24190_read_mask(bdi, BQ24190_REG_SS,
+                       BQ24190_REG_SS_PG_STAT_MASK,
+                       BQ24190_REG_SS_PG_STAT_SHIFT, &v);
+       if (ret < 0)
+               return ret;
+
+       val->intval = v;
+       return 0;
+}
+
+static int bq24190_charger_get_current(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 v;
+       int curr, ret;
+
+       ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC,
+                       BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
+                       bq24190_ccc_ichg_values,
+                       ARRAY_SIZE(bq24190_ccc_ichg_values), &curr);
+       if (ret < 0)
+               return ret;
+
+       ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+                       BQ24190_REG_CCC_FORCE_20PCT_MASK,
+                       BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
+       if (ret < 0)
+               return ret;
+
+       /* If FORCE_20PCT is enabled, then current is 20% of ICHG value */
+       if (v)
+               curr /= 5;
+
+       val->intval = curr;
+       return 0;
+}
+
+static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1;
+
+       val->intval = bq24190_ccc_ichg_values[idx];
+       return 0;
+}
+
+static int bq24190_charger_set_current(struct bq24190_dev_info *bdi,
+               const union power_supply_propval *val)
+{
+       u8 v;
+       int ret, curr = val->intval;
+
+       ret = bq24190_read_mask(bdi, BQ24190_REG_CCC,
+                       BQ24190_REG_CCC_FORCE_20PCT_MASK,
+                       BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v);
+       if (ret < 0)
+               return ret;
+
+       /* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */
+       if (v)
+               curr *= 5;
+
+       return bq24190_set_field_val(bdi, BQ24190_REG_CCC,
+                       BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT,
+                       bq24190_ccc_ichg_values,
+                       ARRAY_SIZE(bq24190_ccc_ichg_values), curr);
+}
+
+static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       int voltage, ret;
+
+       ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC,
+                       BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
+                       bq24190_cvc_vreg_values,
+                       ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage);
+       if (ret < 0)
+               return ret;
+
+       val->intval = voltage;
+       return 0;
+}
+
+static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1;
+
+       val->intval = bq24190_cvc_vreg_values[idx];
+       return 0;
+}
+
+static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi,
+               const union power_supply_propval *val)
+{
+       return bq24190_set_field_val(bdi, BQ24190_REG_CVC,
+                       BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT,
+                       bq24190_cvc_vreg_values,
+                       ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval);
+}
+
+static int bq24190_charger_get_property(struct power_supply *psy,
+               enum power_supply_property psp, union power_supply_propval *val)
+{
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, charger);
+       int ret;
+
+       dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+       pm_runtime_get_sync(bdi->dev);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               ret = bq24190_charger_get_charge_type(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               ret = bq24190_charger_get_health(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               ret = bq24190_charger_get_online(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               ret = bq24190_charger_get_current(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+               ret = bq24190_charger_get_current_max(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+               ret = bq24190_charger_get_voltage(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+               ret = bq24190_charger_get_voltage_max(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+               ret = 0;
+               break;
+       case POWER_SUPPLY_PROP_MODEL_NAME:
+               val->strval = bdi->model_name;
+               ret = 0;
+               break;
+       case POWER_SUPPLY_PROP_MANUFACTURER:
+               val->strval = BQ24190_MANUFACTURER;
+               ret = 0;
+               break;
+       default:
+               ret = -ENODATA;
+       }
+
+       pm_runtime_put_sync(bdi->dev);
+       return ret;
+}
+
+static int bq24190_charger_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, charger);
+       int ret;
+
+       dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+       pm_runtime_get_sync(bdi->dev);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+               ret = bq24190_charger_set_charge_type(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+               ret = bq24190_charger_set_current(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+               ret = bq24190_charger_set_voltage(bdi, val);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       pm_runtime_put_sync(bdi->dev);
+       return ret;
+}
+
+static int bq24190_charger_property_is_writeable(struct power_supply *psy,
+               enum power_supply_property psp)
+{
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_CHARGE_TYPE:
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+       case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+               ret = 1;
+               break;
+       default:
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static enum power_supply_property bq24190_charger_properties[] = {
+       POWER_SUPPLY_PROP_TYPE,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+       POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+       POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+       POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+       POWER_SUPPLY_PROP_SCOPE,
+       POWER_SUPPLY_PROP_MODEL_NAME,
+       POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static char *bq24190_charger_supplied_to[] = {
+       "main-battery",
+};
+
+static void bq24190_charger_init(struct power_supply *charger)
+{
+       charger->name = "bq24190-charger";
+       charger->type = POWER_SUPPLY_TYPE_USB;
+       charger->properties = bq24190_charger_properties;
+       charger->num_properties = ARRAY_SIZE(bq24190_charger_properties);
+       charger->supplied_to = bq24190_charger_supplied_to;
+       charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to);
+       charger->get_property = bq24190_charger_get_property;
+       charger->set_property = bq24190_charger_set_property;
+       charger->property_is_writeable = bq24190_charger_property_is_writeable;
+}
+
+/* Battery power supply property routines */
+
+static int bq24190_battery_get_status(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 ss_reg, chrg_fault;
+       int status, ret;
+
+       mutex_lock(&bdi->f_reg_lock);
+
+       if (bdi->battery_status_valid) {
+               chrg_fault = bdi->f_reg;
+               bdi->battery_status_valid = false;
+               mutex_unlock(&bdi->f_reg_lock);
+       } else {
+               mutex_unlock(&bdi->f_reg_lock);
+
+               ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault);
+               if (ret < 0)
+                       return ret;
+       }
+
+       chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK;
+       chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT;
+
+       ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
+       if (ret < 0)
+               return ret;
+
+       /*
+        * The battery must be discharging when any of these are true:
+        * - there is no good power source;
+        * - there is a charge fault.
+        * Could also be discharging when in "supplement mode" but
+        * there is no way to tell when its in that mode.
+        */
+       if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) {
+               status = POWER_SUPPLY_STATUS_DISCHARGING;
+       } else {
+               ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK;
+               ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT;
+
+               switch (ss_reg) {
+               case 0x0: /* Not Charging */
+                       status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+                       break;
+               case 0x1: /* Pre-charge */
+               case 0x2: /* Fast Charging */
+                       status = POWER_SUPPLY_STATUS_CHARGING;
+                       break;
+               case 0x3: /* Charge Termination Done */
+                       status = POWER_SUPPLY_STATUS_FULL;
+                       break;
+               default:
+                       ret = -EIO;
+               }
+       }
+
+       if (!ret)
+               val->intval = status;
+
+       return ret;
+}
+
+static int bq24190_battery_get_health(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 v;
+       int health, ret;
+
+       mutex_lock(&bdi->f_reg_lock);
+
+       if (bdi->battery_health_valid) {
+               v = bdi->f_reg;
+               bdi->battery_health_valid = false;
+               mutex_unlock(&bdi->f_reg_lock);
+       } else {
+               mutex_unlock(&bdi->f_reg_lock);
+
+               ret = bq24190_read(bdi, BQ24190_REG_F, &v);
+               if (ret < 0)
+                       return ret;
+       }
+
+       if (v & BQ24190_REG_F_BAT_FAULT_MASK) {
+               health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+       } else {
+               v &= BQ24190_REG_F_NTC_FAULT_MASK;
+               v >>= BQ24190_REG_F_NTC_FAULT_SHIFT;
+
+               switch (v) {
+               case 0x0: /* Normal */
+                       health = POWER_SUPPLY_HEALTH_GOOD;
+                       break;
+               case 0x1: /* TS1 Cold */
+               case 0x3: /* TS2 Cold */
+               case 0x5: /* Both Cold */
+                       health = POWER_SUPPLY_HEALTH_COLD;
+                       break;
+               case 0x2: /* TS1 Hot */
+               case 0x4: /* TS2 Hot */
+               case 0x6: /* Both Hot */
+                       health = POWER_SUPPLY_HEALTH_OVERHEAT;
+                       break;
+               default:
+                       health = POWER_SUPPLY_HEALTH_UNKNOWN;
+               }
+       }
+
+       val->intval = health;
+       return 0;
+}
+
+static int bq24190_battery_get_online(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       u8 batfet_disable;
+       int ret;
+
+       ret = bq24190_read_mask(bdi, BQ24190_REG_MOC,
+                       BQ24190_REG_MOC_BATFET_DISABLE_MASK,
+                       BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable);
+       if (ret < 0)
+               return ret;
+
+       val->intval = !batfet_disable;
+       return 0;
+}
+
+static int bq24190_battery_set_online(struct bq24190_dev_info *bdi,
+               const union power_supply_propval *val)
+{
+       return bq24190_write_mask(bdi, BQ24190_REG_MOC,
+                       BQ24190_REG_MOC_BATFET_DISABLE_MASK,
+                       BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval);
+}
+
+static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi,
+               union power_supply_propval *val)
+{
+       int temp, ret;
+
+       ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC,
+                       BQ24190_REG_ICTRC_TREG_MASK,
+                       BQ24190_REG_ICTRC_TREG_SHIFT,
+                       bq24190_ictrc_treg_values,
+                       ARRAY_SIZE(bq24190_ictrc_treg_values), &temp);
+       if (ret < 0)
+               return ret;
+
+       val->intval = temp;
+       return 0;
+}
+
+static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi,
+               const union power_supply_propval *val)
+{
+       return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC,
+                       BQ24190_REG_ICTRC_TREG_MASK,
+                       BQ24190_REG_ICTRC_TREG_SHIFT,
+                       bq24190_ictrc_treg_values,
+                       ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval);
+}
+
+static int bq24190_battery_get_property(struct power_supply *psy,
+               enum power_supply_property psp, union power_supply_propval *val)
+{
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, battery);
+       int ret;
+
+       dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+       pm_runtime_get_sync(bdi->dev);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               ret = bq24190_battery_get_status(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_HEALTH:
+               ret = bq24190_battery_get_health(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_ONLINE:
+               ret = bq24190_battery_get_online(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               /* Could be Li-on or Li-polymer but no way to tell which */
+               val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+               ret = 0;
+               break;
+       case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+               ret = bq24190_battery_get_temp_alert_max(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_SCOPE:
+               val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+               ret = 0;
+               break;
+       default:
+               ret = -ENODATA;
+       }
+
+       pm_runtime_put_sync(bdi->dev);
+       return ret;
+}
+
+static int bq24190_battery_set_property(struct power_supply *psy,
+               enum power_supply_property psp,
+               const union power_supply_propval *val)
+{
+       struct bq24190_dev_info *bdi =
+                       container_of(psy, struct bq24190_dev_info, battery);
+       int ret;
+
+       dev_dbg(bdi->dev, "prop: %d\n", psp);
+
+       pm_runtime_put_sync(bdi->dev);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+               ret = bq24190_battery_set_online(bdi, val);
+               break;
+       case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+               ret = bq24190_battery_set_temp_alert_max(bdi, val);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+
+       pm_runtime_put_sync(bdi->dev);
+       return ret;
+}
+
+static int bq24190_battery_property_is_writeable(struct power_supply *psy,
+               enum power_supply_property psp)
+{
+       int ret;
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_ONLINE:
+       case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+               ret = 1;
+               break;
+       default:
+               ret = 0;
+       }
+
+       return ret;
+}
+
+static enum power_supply_property bq24190_battery_properties[] = {
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_HEALTH,
+       POWER_SUPPLY_PROP_ONLINE,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+       POWER_SUPPLY_PROP_SCOPE,
+};
+
+static void bq24190_battery_init(struct power_supply *battery)
+{
+       battery->name = "bq24190-battery";
+       battery->type = POWER_SUPPLY_TYPE_BATTERY;
+       battery->properties = bq24190_battery_properties;
+       battery->num_properties = ARRAY_SIZE(bq24190_battery_properties);
+       battery->get_property = bq24190_battery_get_property;
+       battery->set_property = bq24190_battery_set_property;
+       battery->property_is_writeable = bq24190_battery_property_is_writeable;
+}
+
+static irqreturn_t bq24190_irq_handler_thread(int irq, void *data)
+{
+       struct bq24190_dev_info *bdi = data;
+       bool alert_userspace = false;
+       u8 ss_reg, f_reg;
+       int ret;
+
+       pm_runtime_get_sync(bdi->dev);
+
+       ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg);
+       if (ret < 0) {
+               dev_err(bdi->dev, "Can't read SS reg: %d\n", ret);
+               goto out;
+       }
+
+       if (ss_reg != bdi->ss_reg) {
+               /*
+                * The device is in host mode so when PG_STAT goes from 1->0
+                * (i.e., power removed) HIZ needs to be disabled.
+                */
+               if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) &&
+                               !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) {
+                       ret = bq24190_write_mask(bdi, BQ24190_REG_ISC,
+                                       BQ24190_REG_ISC_EN_HIZ_MASK,
+                                       BQ24190_REG_ISC_EN_HIZ_SHIFT,
+                                       0);
+                       if (ret < 0)
+                               dev_err(bdi->dev, "Can't access ISC reg: %d\n",
+                                       ret);
+               }
+
+               bdi->ss_reg = ss_reg;
+               alert_userspace = true;
+       }
+
+       mutex_lock(&bdi->f_reg_lock);
+
+       ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg);
+       if (ret < 0) {
+               mutex_unlock(&bdi->f_reg_lock);
+               dev_err(bdi->dev, "Can't read F reg: %d\n", ret);
+               goto out;
+       }
+
+       if (f_reg != bdi->f_reg) {
+               bdi->f_reg = f_reg;
+               bdi->charger_health_valid = true;
+               bdi->battery_health_valid = true;
+               bdi->battery_status_valid = true;
+
+               alert_userspace = true;
+       }
+
+       mutex_unlock(&bdi->f_reg_lock);
+
+       /*
+        * Sometimes bq24190 gives a steady trickle of interrupts even
+        * though the watchdog timer is turned off and neither the STATUS
+        * nor FAULT registers have changed.  Weed out these sprurious
+        * interrupts so userspace isn't alerted for no reason.
+        * In addition, the chip always generates an interrupt after
+        * register reset so we should ignore that one (the very first
+        * interrupt received).
+        */
+       if (alert_userspace && !bdi->first_time) {
+               power_supply_changed(&bdi->charger);
+               power_supply_changed(&bdi->battery);
+               bdi->first_time = false;
+       }
+
+out:
+       pm_runtime_put_sync(bdi->dev);
+
+       dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg);
+
+       return IRQ_HANDLED;
+}
+
+static int bq24190_hw_init(struct bq24190_dev_info *bdi)
+{
+       u8 v;
+       int ret;
+
+       pm_runtime_get_sync(bdi->dev);
+
+       /* First check that the device really is what its supposed to be */
+       ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS,
+                       BQ24190_REG_VPRS_PN_MASK,
+                       BQ24190_REG_VPRS_PN_SHIFT,
+                       &v);
+       if (ret < 0)
+               goto out;
+
+       if (v != bdi->model) {
+               ret = -ENODEV;
+               goto out;
+       }
+
+       ret = bq24190_register_reset(bdi);
+       if (ret < 0)
+               goto out;
+
+       ret = bq24190_set_mode_host(bdi);
+out:
+       pm_runtime_put_sync(bdi->dev);
+       return ret;
+}
+
+#ifdef CONFIG_OF
+static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
+{
+       bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0);
+       if (bdi->irq <= 0)
+               return -1;
+
+       return 0;
+}
+#else
+static int bq24190_setup_dt(struct bq24190_dev_info *bdi)
+{
+       return -1;
+}
+#endif
+
+static int bq24190_setup_pdata(struct bq24190_dev_info *bdi,
+               struct bq24190_platform_data *pdata)
+{
+       int ret;
+
+       if (!gpio_is_valid(pdata->gpio_int))
+               return -1;
+
+       ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev));
+       if (ret < 0)
+               return -1;
+
+       ret = gpio_direction_input(pdata->gpio_int);
+       if (ret < 0)
+               goto out;
+
+       bdi->irq = gpio_to_irq(pdata->gpio_int);
+       if (!bdi->irq)
+               goto out;
+
+       bdi->gpio_int = pdata->gpio_int;
+       return 0;
+
+out:
+       gpio_free(pdata->gpio_int);
+       return -1;
+}
+
+static int bq24190_probe(struct i2c_client *client,
+               const struct i2c_device_id *id)
+{
+       struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+       struct device *dev = &client->dev;
+       struct bq24190_platform_data *pdata = client->dev.platform_data;
+       struct bq24190_dev_info *bdi;
+       int ret;
+
+       if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+               dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+               return -ENODEV;
+       }
+
+       bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL);
+       if (!bdi) {
+               dev_err(dev, "Can't alloc bdi struct\n");
+               return -ENOMEM;
+       }
+
+       bdi->client = client;
+       bdi->dev = dev;
+       bdi->model = id->driver_data;
+       strncpy(bdi->model_name, id->name, I2C_NAME_SIZE);
+       mutex_init(&bdi->f_reg_lock);
+       bdi->first_time = true;
+       bdi->charger_health_valid = false;
+       bdi->battery_health_valid = false;
+       bdi->battery_status_valid = false;
+
+       i2c_set_clientdata(client, bdi);
+
+       if (dev->of_node)
+               ret = bq24190_setup_dt(bdi);
+       else
+               ret = bq24190_setup_pdata(bdi, pdata);
+
+       if (ret) {
+               dev_err(dev, "Can't get irq info\n");
+               return -EINVAL;
+       }
+
+       ret = devm_request_threaded_irq(dev, bdi->irq, NULL,
+                       bq24190_irq_handler_thread,
+                       IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+                       "bq24190-charger", bdi);
+       if (ret < 0) {
+               dev_err(dev, "Can't set up irq handler\n");
+               goto out1;
+       }
+
+       pm_runtime_enable(dev);
+       pm_runtime_resume(dev);
+
+       ret = bq24190_hw_init(bdi);
+       if (ret < 0) {
+               dev_err(dev, "Hardware init failed\n");
+               goto out2;
+       }
+
+       bq24190_charger_init(&bdi->charger);
+
+       ret = power_supply_register(dev, &bdi->charger);
+       if (ret) {
+               dev_err(dev, "Can't register charger\n");
+               goto out2;
+       }
+
+       bq24190_battery_init(&bdi->battery);
+
+       ret = power_supply_register(dev, &bdi->battery);
+       if (ret) {
+               dev_err(dev, "Can't register battery\n");
+               goto out3;
+       }
+
+       ret = bq24190_sysfs_create_group(bdi);
+       if (ret) {
+               dev_err(dev, "Can't create sysfs entries\n");
+               goto out4;
+       }
+
+       return 0;
+
+out4:
+       power_supply_unregister(&bdi->battery);
+out3:
+       power_supply_unregister(&bdi->charger);
+out2:
+       pm_runtime_disable(dev);
+out1:
+       if (bdi->gpio_int)
+               gpio_free(bdi->gpio_int);
+
+       return ret;
+}
+
+static int bq24190_remove(struct i2c_client *client)
+{
+       struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+       pm_runtime_get_sync(bdi->dev);
+       bq24190_register_reset(bdi);
+       pm_runtime_put_sync(bdi->dev);
+
+       bq24190_sysfs_remove_group(bdi);
+       power_supply_unregister(&bdi->battery);
+       power_supply_unregister(&bdi->charger);
+       pm_runtime_disable(bdi->dev);
+
+       if (bdi->gpio_int)
+               gpio_free(bdi->gpio_int);
+
+       return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq24190_pm_suspend(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+       pm_runtime_get_sync(bdi->dev);
+       bq24190_register_reset(bdi);
+       pm_runtime_put_sync(bdi->dev);
+
+       return 0;
+}
+
+static int bq24190_pm_resume(struct device *dev)
+{
+       struct i2c_client *client = to_i2c_client(dev);
+       struct bq24190_dev_info *bdi = i2c_get_clientdata(client);
+
+       bdi->charger_health_valid = false;
+       bdi->battery_health_valid = false;
+       bdi->battery_status_valid = false;
+
+       pm_runtime_get_sync(bdi->dev);
+       bq24190_register_reset(bdi);
+       pm_runtime_put_sync(bdi->dev);
+
+       /* Things may have changed while suspended so alert upper layer */
+       power_supply_changed(&bdi->charger);
+       power_supply_changed(&bdi->battery);
+
+       return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume);
+
+/*
+ * Only support the bq24190 right now.  The bq24192, bq24192i, and bq24193
+ * are similar but not identical so the driver needs to be extended to
+ * support them.
+ */
+static const struct i2c_device_id bq24190_i2c_ids[] = {
+       { "bq24190", BQ24190_REG_VPRS_PN_24190 },
+       { },
+};
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq24190_of_match[] = {
+       { .compatible = "ti,bq24190", },
+       { },
+};
+MODULE_DEVICE_TABLE(of, bq24190_of_match);
+#else
+static const struct of_device_id bq24190_of_match[] = {
+       { },
+};
+#endif
+
+static struct i2c_driver bq24190_driver = {
+       .probe          = bq24190_probe,
+       .remove         = bq24190_remove,
+       .id_table       = bq24190_i2c_ids,
+       .driver = {
+               .name           = "bq24190-charger",
+               .owner          = THIS_MODULE,
+               .pm             = &bq24190_pm_ops,
+               .of_match_table = of_match_ptr(bq24190_of_match),
+       },
+};
+module_i2c_driver(bq24190_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>");
+MODULE_ALIAS("i2c:bq24190-charger");
+MODULE_DESCRIPTION("TI BQ24190 Charger Driver");
index c58d0e3..d02ae02 100644 (file)
@@ -287,7 +287,7 @@ static struct gpio collie_batt_gpios[] = {
 };
 
 #ifdef CONFIG_PM
-static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state)
+static int collie_bat_suspend(struct ucb1x00_dev *dev)
 {
        /* flush all pending status updates */
        flush_work(&bat_work);
index 0ee1e14..b4513f2 100644 (file)
@@ -458,6 +458,7 @@ max8925_power_dt_init(struct platform_device *pdev)
        of_property_read_u32(np, "fast-charge", &fast_charge);
        of_property_read_u32(np, "no-insert-detect", &no_insert_detect);
        of_property_read_u32(np, "no-temp-support", &no_temp_support);
+       of_node_put(np);
 
        pdata->batt_detect = batt_detect;
        pdata->fast_charge = fast_charge;
index 3b2d5df..00e6672 100644 (file)
@@ -67,23 +67,42 @@ static int __power_supply_changed_work(struct device *dev, void *data)
 
 static void power_supply_changed_work(struct work_struct *work)
 {
+       unsigned long flags;
        struct power_supply *psy = container_of(work, struct power_supply,
                                                changed_work);
 
        dev_dbg(psy->dev, "%s\n", __func__);
 
-       class_for_each_device(power_supply_class, NULL, psy,
-                             __power_supply_changed_work);
-
-       power_supply_update_leds(psy);
-
-       kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+       spin_lock_irqsave(&psy->changed_lock, flags);
+       if (psy->changed) {
+               psy->changed = false;
+               spin_unlock_irqrestore(&psy->changed_lock, flags);
+               class_for_each_device(power_supply_class, NULL, psy,
+                                     __power_supply_changed_work);
+               power_supply_update_leds(psy);
+               kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE);
+               spin_lock_irqsave(&psy->changed_lock, flags);
+       }
+       /*
+        * Dependent power supplies (e.g. battery) may have changed state
+        * as a result of this event, so poll again and hold the
+        * wakeup_source until all events are processed.
+        */
+       if (!psy->changed)
+               pm_relax(psy->dev);
+       spin_unlock_irqrestore(&psy->changed_lock, flags);
 }
 
 void power_supply_changed(struct power_supply *psy)
 {
+       unsigned long flags;
+
        dev_dbg(psy->dev, "%s\n", __func__);
 
+       spin_lock_irqsave(&psy->changed_lock, flags);
+       psy->changed = true;
+       pm_stay_awake(psy->dev);
+       spin_unlock_irqrestore(&psy->changed_lock, flags);
        schedule_work(&psy->changed_work);
 }
 EXPORT_SYMBOL_GPL(power_supply_changed);
@@ -500,6 +519,11 @@ int power_supply_register(struct device *parent, struct power_supply *psy)
                goto check_supplies_failed;
        }
 
+       spin_lock_init(&psy->changed_lock);
+       rc = device_init_wakeup(dev, true);
+       if (rc)
+               goto wakeup_init_failed;
+
        rc = kobject_set_name(&dev->kobj, "%s", psy->name);
        if (rc)
                goto kobject_set_name_failed;
@@ -529,6 +553,7 @@ create_triggers_failed:
 register_cooler_failed:
        psy_unregister_thermal(psy);
 register_thermal_failed:
+wakeup_init_failed:
        device_del(dev);
 kobject_set_name_failed:
 device_add_failed:
@@ -546,6 +571,7 @@ void power_supply_unregister(struct power_supply *psy)
        power_supply_remove_triggers(psy);
        psy_unregister_cooler(psy);
        psy_unregister_thermal(psy);
+       device_init_wakeup(psy->dev, false);
        device_unregister(psy->dev);
 }
 EXPORT_SYMBOL_GPL(power_supply_unregister);
index 29178f7..44420d1 100644 (file)
@@ -118,7 +118,7 @@ static ssize_t power_supply_store_property(struct device *dev,
        long long_val;
 
        /* TODO: support other types than int */
-       ret = strict_strtol(buf, 10, &long_val);
+       ret = kstrtol(buf, 10, &long_val);
        if (ret < 0)
                return ret;
 
index ee039dc..9b3ea53 100644 (file)
@@ -14,6 +14,12 @@ config POWER_RESET_GPIO
          If your board needs a GPIO high/low to power down, say Y and
          create a binding in your devicetree.
 
+config POWER_RESET_MSM
+       bool "Qualcomm MSM power-off driver"
+       depends on POWER_RESET && ARCH_MSM
+       help
+         Power off and restart support for Qualcomm boards.
+
 config POWER_RESET_QNAP
        bool "QNAP power-off driver"
        depends on OF_GPIO && POWER_RESET && PLAT_ORION
@@ -34,7 +40,14 @@ config POWER_RESET_RESTART
 config POWER_RESET_VEXPRESS
        bool "ARM Versatile Express power-off and reset driver"
        depends on ARM || ARM64
-       depends on POWER_RESET
+       depends on POWER_RESET && VEXPRESS_CONFIG
        help
          Power off and reset support for the ARM Ltd. Versatile
          Express boards.
+
+config POWER_RESET_XGENE
+       bool "APM SoC X-Gene reset driver"
+       depends on ARM64
+       depends on POWER_RESET
+       help
+         Reboot support for the APM SoC X-Gene Eval boards.
index 372807f..3e6ed88 100644 (file)
@@ -1,4 +1,6 @@
 obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o
+obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o
 obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o
 obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
 obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o
+obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o
diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c
new file mode 100644 (file)
index 0000000..774f9a3
--- /dev/null
@@ -0,0 +1,73 @@
+/* Copyright (c) 2013, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/io.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/reboot.h>
+
+#include <asm/system_misc.h>
+
+static void __iomem *msm_ps_hold;
+
+static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd)
+{
+       writel(0, msm_ps_hold);
+       mdelay(10000);
+}
+
+static void do_msm_poweroff(void)
+{
+       /* TODO: Add poweroff capability */
+       do_msm_restart(REBOOT_HARD, NULL);
+}
+
+static int msm_restart_probe(struct platform_device *pdev)
+{
+       struct device *dev = &pdev->dev;
+       struct resource *mem;
+
+       mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+       msm_ps_hold = devm_ioremap_resource(dev, mem);
+       if (IS_ERR(msm_ps_hold))
+               return PTR_ERR(msm_ps_hold);
+
+       pm_power_off = do_msm_poweroff;
+       arm_pm_restart = do_msm_restart;
+       return 0;
+}
+
+static const struct of_device_id of_msm_restart_match[] = {
+       { .compatible = "qcom,pshold", },
+       {},
+};
+MODULE_DEVICE_TABLE(of, of_msm_restart_match);
+
+static struct platform_driver msm_restart_driver = {
+       .probe = msm_restart_probe,
+       .driver = {
+               .name = "msm-restart",
+               .of_match_table = of_match_ptr(of_msm_restart_match),
+       },
+};
+
+static int __init msm_restart_init(void)
+{
+       return platform_driver_register(&msm_restart_driver);
+}
+device_initcall(msm_restart_init);
diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c
new file mode 100644 (file)
index 0000000..ecd55f8
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+ * AppliedMicro X-Gene SoC Reboot Driver
+ *
+ * Copyright (c) 2013, Applied Micro Circuits Corporation
+ * Author: Feng Kan <fkan@apm.com>
+ * Author: Loc Ho <lho@apm.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
+ * MA 02111-1307 USA
+ *
+ * This driver provides system reboot functionality for APM X-Gene SoC.
+ * For system shutdown, this is board specify. If a board designer
+ * implements GPIO shutdown, use the gpio-poweroff.c driver.
+ */
+#include <linux/io.h>
+#include <linux/of_device.h>
+#include <linux/of_address.h>
+#include <linux/platform_device.h>
+#include <linux/stat.h>
+#include <linux/slab.h>
+#include <asm/system_misc.h>
+
+struct xgene_reboot_context {
+       struct platform_device *pdev;
+       void *csr;
+       u32 mask;
+};
+
+static struct xgene_reboot_context *xgene_restart_ctx;
+
+static void xgene_restart(char str, const char *cmd)
+{
+       struct xgene_reboot_context *ctx = xgene_restart_ctx;
+       unsigned long timeout;
+
+       /* Issue the reboot */
+       if (ctx)
+               writel(ctx->mask, ctx->csr);
+
+       timeout = jiffies + HZ;
+       while (time_before(jiffies, timeout))
+               cpu_relax();
+
+       dev_emerg(&ctx->pdev->dev, "Unable to restart system\n");
+}
+
+static int xgene_reboot_probe(struct platform_device *pdev)
+{
+       struct xgene_reboot_context *ctx;
+
+       ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL);
+       if (!ctx) {
+               dev_err(&pdev->dev, "out of memory for context\n");
+               return -ENODEV;
+       }
+
+       ctx->csr = of_iomap(pdev->dev.of_node, 0);
+       if (!ctx->csr) {
+               devm_kfree(&pdev->dev, ctx);
+               dev_err(&pdev->dev, "can not map resource\n");
+               return -ENODEV;
+       }
+
+       if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask))
+               ctx->mask = 0xFFFFFFFF;
+
+       ctx->pdev = pdev;
+       arm_pm_restart = xgene_restart;
+       xgene_restart_ctx = ctx;
+
+       return 0;
+}
+
+static struct of_device_id xgene_reboot_of_match[] = {
+       { .compatible = "apm,xgene-reboot" },
+       {}
+};
+
+static struct platform_driver xgene_reboot_driver = {
+       .probe = xgene_reboot_probe,
+       .driver = {
+               .name = "xgene-reboot",
+               .of_match_table = xgene_reboot_of_match,
+       },
+};
+
+static int __init xgene_reboot_init(void)
+{
+       return platform_driver_register(&xgene_reboot_driver);
+}
+device_initcall(xgene_reboot_init);
index 8a6288d..1bc5857 100644 (file)
 #include <linux/slab.h>
 #include <linux/i2c/twl4030-madc.h>
 
+/* RX51 specific channels */
+#define TWL4030_MADC_BTEMP_RX51        TWL4030_MADC_ADCIN0
+#define TWL4030_MADC_BCI_RX51  TWL4030_MADC_ADCIN4
+
 struct rx51_device_info {
        struct device *dev;
        struct power_supply bat;
@@ -37,7 +41,7 @@ static int rx51_battery_read_adc(int channel)
 {
        struct twl4030_madc_request req;
 
-       req.channels = 1 << channel;
+       req.channels = channel;
        req.do_avg = 1;
        req.method = TWL4030_MADC_SW1;
        req.func_cb = NULL;
@@ -47,7 +51,7 @@ static int rx51_battery_read_adc(int channel)
        if (twl4030_madc_conversion(&req) <= 0)
                return -ENODATA;
 
-       return req.rbuf[channel];
+       return req.rbuf[ffs(channel) - 1];
 }
 
 /*
@@ -56,7 +60,7 @@ static int rx51_battery_read_adc(int channel)
  */
 static int rx51_battery_read_voltage(struct rx51_device_info *di)
 {
-       int voltage = rx51_battery_read_adc(12);
+       int voltage = rx51_battery_read_adc(TWL4030_MADC_VBAT);
 
        if (voltage < 0)
                return voltage;
@@ -108,7 +112,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
 {
        int min = 0;
        int max = ARRAY_SIZE(rx51_temp_table2) - 1;
-       int raw = rx51_battery_read_adc(0);
+       int raw = rx51_battery_read_adc(TWL4030_MADC_BTEMP_RX51);
 
        /* Zero and negative values are undefined */
        if (raw <= 0)
@@ -142,7 +146,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di)
  */
 static int rx51_battery_read_capacity(struct rx51_device_info *di)
 {
-       int capacity = rx51_battery_read_adc(4);
+       int capacity = rx51_battery_read_adc(TWL4030_MADC_BCI_RX51);
 
        if (capacity < 0)
                return capacity;
index 0224de5..f4d80df 100644 (file)
@@ -150,7 +150,7 @@ static void tosa_bat_external_power_changed(struct power_supply *psy)
 
 static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
 {
-       pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq)));
+       pr_info("tosa_bat_gpio irq\n");
        schedule_work(&bat_work);
        return IRQ_HANDLED;
 }
index be98e70..d98abe9 100644 (file)
@@ -189,7 +189,12 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
 
                /* Need to keep regulator on */
                if (!bci->usb_enabled) {
-                       regulator_enable(bci->usb_reg);
+                       ret = regulator_enable(bci->usb_reg);
+                       if (ret) {
+                               dev_err(bci->dev,
+                                       "Failed to enable regulator\n");
+                               return ret;
+                       }
                        bci->usb_enabled = 1;
                }
 
diff --git a/drivers/power/twl4030_madc_battery.c b/drivers/power/twl4030_madc_battery.c
new file mode 100644 (file)
index 0000000..7ef445a
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * Dumb driver for LiIon batteries using TWL4030 madc.
+ *
+ * Copyright 2013 Golden Delicious Computers
+ * Lukas Märdian <lukas@goldelico.com>
+ *
+ * Based on dumb driver for gta01 battery
+ * Copyright 2009 Openmoko, Inc
+ * Balaji Rao <balajirrao@openmoko.org>
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/delay.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/sort.h>
+#include <linux/i2c/twl4030-madc.h>
+#include <linux/power/twl4030_madc_battery.h>
+
+struct twl4030_madc_battery {
+       struct power_supply psy;
+       struct twl4030_madc_bat_platform_data *pdata;
+};
+
+static enum power_supply_property twl4030_madc_bat_props[] = {
+       POWER_SUPPLY_PROP_PRESENT,
+       POWER_SUPPLY_PROP_STATUS,
+       POWER_SUPPLY_PROP_TECHNOLOGY,
+       POWER_SUPPLY_PROP_VOLTAGE_NOW,
+       POWER_SUPPLY_PROP_CURRENT_NOW,
+       POWER_SUPPLY_PROP_CAPACITY,
+       POWER_SUPPLY_PROP_CHARGE_FULL,
+       POWER_SUPPLY_PROP_CHARGE_NOW,
+       POWER_SUPPLY_PROP_TEMP,
+       POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+};
+
+static int madc_read(int index)
+{
+       struct twl4030_madc_request req;
+       int val;
+
+       req.channels = index;
+       req.method = TWL4030_MADC_SW2;
+       req.type = TWL4030_MADC_WAIT;
+       req.do_avg = 0;
+       req.raw = false;
+       req.func_cb = NULL;
+
+       val = twl4030_madc_conversion(&req);
+       if (val < 0)
+               return val;
+
+       return req.rbuf[ffs(index) - 1];
+}
+
+static int twl4030_madc_bat_get_charging_status(void)
+{
+       return (madc_read(TWL4030_MADC_ICHG) > 0) ? 1 : 0;
+}
+
+static int twl4030_madc_bat_get_voltage(void)
+{
+       return madc_read(TWL4030_MADC_VBAT);
+}
+
+static int twl4030_madc_bat_get_current(void)
+{
+       return madc_read(TWL4030_MADC_ICHG) * 1000;
+}
+
+static int twl4030_madc_bat_get_temp(void)
+{
+       return madc_read(TWL4030_MADC_BTEMP) * 10;
+}
+
+static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat,
+                                       int volt)
+{
+       struct twl4030_madc_bat_calibration *calibration;
+       int i, res = 0;
+
+       /* choose charging curve */
+       if (twl4030_madc_bat_get_charging_status())
+               calibration = bat->pdata->charging;
+       else
+               calibration = bat->pdata->discharging;
+
+       if (volt > calibration[0].voltage) {
+               res = calibration[0].level;
+       } else {
+               for (i = 0; calibration[i+1].voltage >= 0; i++) {
+                       if (volt <= calibration[i].voltage &&
+                                       volt >= calibration[i+1].voltage) {
+                               /* interval found - interpolate within range */
+                               res = calibration[i].level -
+                                       ((calibration[i].voltage - volt) *
+                                       (calibration[i].level -
+                                       calibration[i+1].level)) /
+                                       (calibration[i].voltage -
+                                       calibration[i+1].voltage);
+                               break;
+                       }
+               }
+       }
+       return res;
+}
+
+static int twl4030_madc_bat_get_property(struct power_supply *psy,
+                                       enum power_supply_property psp,
+                                       union power_supply_propval *val)
+{
+       struct twl4030_madc_battery *bat = container_of(psy,
+                                       struct twl4030_madc_battery, psy);
+
+       switch (psp) {
+       case POWER_SUPPLY_PROP_STATUS:
+               if (twl4030_madc_bat_voltscale(bat,
+                               twl4030_madc_bat_get_voltage()) > 95)
+                       val->intval = POWER_SUPPLY_STATUS_FULL;
+               else {
+                       if (twl4030_madc_bat_get_charging_status())
+                               val->intval = POWER_SUPPLY_STATUS_CHARGING;
+                       else
+                               val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+               }
+               break;
+       case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+               val->intval = twl4030_madc_bat_get_voltage() * 1000;
+               break;
+       case POWER_SUPPLY_PROP_TECHNOLOGY:
+               val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+               break;
+       case POWER_SUPPLY_PROP_CURRENT_NOW:
+               val->intval = twl4030_madc_bat_get_current();
+               break;
+       case POWER_SUPPLY_PROP_PRESENT:
+               /* assume battery is always present */
+               val->intval = 1;
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_NOW: {
+                       int percent = twl4030_madc_bat_voltscale(bat,
+                                       twl4030_madc_bat_get_voltage());
+                       val->intval = (percent * bat->pdata->capacity) / 100;
+                       break;
+               }
+       case POWER_SUPPLY_PROP_CAPACITY:
+               val->intval = twl4030_madc_bat_voltscale(bat,
+                                       twl4030_madc_bat_get_voltage());
+               break;
+       case POWER_SUPPLY_PROP_CHARGE_FULL:
+               val->intval = bat->pdata->capacity;
+               break;
+       case POWER_SUPPLY_PROP_TEMP:
+               val->intval = twl4030_madc_bat_get_temp();
+               break;
+       case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
+                       int percent = twl4030_madc_bat_voltscale(bat,
+                                       twl4030_madc_bat_get_voltage());
+                       /* in mAh */
+                       int chg = (percent * (bat->pdata->capacity/1000))/100;
+
+                       /* assume discharge with 400 mA (ca. 1.5W) */
+                       val->intval = (3600l * chg) / 400;
+                       break;
+               }
+       default:
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
+static void twl4030_madc_bat_ext_changed(struct power_supply *psy)
+{
+       struct twl4030_madc_battery *bat = container_of(psy,
+                                       struct twl4030_madc_battery, psy);
+
+       power_supply_changed(&bat->psy);
+}
+
+static int twl4030_cmp(const void *a, const void *b)
+{
+       return ((struct twl4030_madc_bat_calibration *)b)->voltage -
+               ((struct twl4030_madc_bat_calibration *)a)->voltage;
+}
+
+static int twl4030_madc_battery_probe(struct platform_device *pdev)
+{
+       struct twl4030_madc_battery *twl4030_madc_bat;
+       struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data;
+
+       twl4030_madc_bat = kzalloc(sizeof(*twl4030_madc_bat), GFP_KERNEL);
+       if (!twl4030_madc_bat)
+               return -ENOMEM;
+
+       twl4030_madc_bat->psy.name = "twl4030_battery";
+       twl4030_madc_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY;
+       twl4030_madc_bat->psy.properties = twl4030_madc_bat_props;
+       twl4030_madc_bat->psy.num_properties =
+                                       ARRAY_SIZE(twl4030_madc_bat_props);
+       twl4030_madc_bat->psy.get_property = twl4030_madc_bat_get_property;
+       twl4030_madc_bat->psy.external_power_changed =
+                                       twl4030_madc_bat_ext_changed;
+
+       /* sort charging and discharging calibration data */
+       sort(pdata->charging, pdata->charging_size,
+               sizeof(struct twl4030_madc_bat_calibration),
+               twl4030_cmp, NULL);
+       sort(pdata->discharging, pdata->discharging_size,
+               sizeof(struct twl4030_madc_bat_calibration),
+               twl4030_cmp, NULL);
+
+       twl4030_madc_bat->pdata = pdata;
+       platform_set_drvdata(pdev, twl4030_madc_bat);
+       power_supply_register(&pdev->dev, &twl4030_madc_bat->psy);
+
+       return 0;
+}
+
+static int twl4030_madc_battery_remove(struct platform_device *pdev)
+{
+       struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
+
+       power_supply_unregister(&bat->psy);
+       kfree(bat);
+
+       return 0;
+}
+
+static struct platform_driver twl4030_madc_battery_driver = {
+       .driver = {
+               .name = "twl4030_madc_battery",
+       },
+       .probe  = twl4030_madc_battery_probe,
+       .remove = twl4030_madc_battery_remove,
+};
+module_platform_driver(twl4030_madc_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>");
+MODULE_DESCRIPTION("twl4030_madc battery driver");
diff --git a/include/linux/power/bq24190_charger.h b/include/linux/power/bq24190_charger.h
new file mode 100644 (file)
index 0000000..9f02837
--- /dev/null
@@ -0,0 +1,16 @@
+/*
+ * Platform data for the TI bq24190 battery charger driver.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#ifndef _BQ24190_CHARGER_H_
+#define _BQ24190_CHARGER_H_
+
+struct bq24190_platform_data {
+       unsigned int    gpio_int;       /* GPIO pin that's connected to INT# */
+};
+
+#endif
diff --git a/include/linux/power/twl4030_madc_battery.h b/include/linux/power/twl4030_madc_battery.h
new file mode 100644 (file)
index 0000000..23110dc
--- /dev/null
@@ -0,0 +1,39 @@
+/*
+ * Dumb driver for LiIon batteries using TWL4030 madc.
+ *
+ * Copyright 2013 Golden Delicious Computers
+ * Nikolaus Schaller <hns@goldelico.com>
+ *
+ *  This program is free software; you can redistribute it and/or modify it
+ *  under the terms of the GNU General Public License as published by the
+ *  Free Software Foundation;  either version 2 of the License, or (at your
+ *  option) any later version.
+ *
+ *  You should have received a copy of the GNU General Public License along
+ *  with this program; if not, write to the Free Software Foundation, Inc.,
+ *  675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ */
+
+#ifndef __TWL4030_MADC_BATTERY_H
+#define __TWL4030_MADC_BATTERY_H
+
+/*
+ * Usually we can assume 100% @ 4.15V and 0% @ 3.3V but curves differ for
+ * charging and discharging!
+ */
+
+struct twl4030_madc_bat_calibration {
+       short voltage;  /* in mV - specify -1 for end of list */
+       short level;    /* in percent (0 .. 100%) */
+};
+
+struct twl4030_madc_bat_platform_data {
+       unsigned int capacity;  /* total capacity in uAh */
+       struct twl4030_madc_bat_calibration *charging;
+       int charging_size;
+       struct twl4030_madc_bat_calibration *discharging;
+       int discharging_size;
+};
+
+#endif
index 804b906..5c26006 100644 (file)
@@ -15,6 +15,7 @@
 
 #include <linux/workqueue.h>
 #include <linux/leds.h>
+#include <linux/spinlock.h>
 
 struct device;
 
@@ -194,6 +195,8 @@ struct power_supply {
        /* private */
        struct device *dev;
        struct work_struct changed_work;
+       spinlock_t changed_lock;
+       bool changed;
 #ifdef CONFIG_THERMAL
        struct thermal_zone_device *tzd;
        struct thermal_cooling_device *tcd;