summaryrefslogtreecommitdiffstats
path: root/drivers/power/supply
diff options
context:
space:
mode:
authorLinus Torvalds <torvalds@linux-foundation.org>2016-10-06 18:21:15 -0700
committerLinus Torvalds <torvalds@linux-foundation.org>2016-10-06 18:21:15 -0700
commit021723e6c5a5e7b50eb68f9812418406de9860b2 (patch)
treefe22465563a9056925f0e4edd236d9a6c456e6cc /drivers/power/supply
parentc6594fc6af1924b7fdce600088b10e3b3b159fb7 (diff)
parent1d72706f0485b58e151b5a7584c4c65d66670587 (diff)
downloadop-kernel-dev-021723e6c5a5e7b50eb68f9812418406de9860b2.zip
op-kernel-dev-021723e6c5a5e7b50eb68f9812418406de9860b2.tar.gz
Merge tag 'for-v4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply
Pull power supply and reset updates from Sebastian Reichel: - move power supply drivers to drivers/power/supply - unify location of power supply DT documentation - tps65217-charger: IRQ support - act8945a-charger: misc. cleanups & improvements - sbs-battery cleanup - fix users of deprecated create_singlethread_workqueue() - misc fixes. * tag 'for-v4.9' of git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply: (46 commits) power: supply: bq27xxx_battery: allow kernel poll_interval parameter runtime update power: supply: sbs-battery: Cleanup removal of chip->pdata power: reset: st: Remove obsolete platforms from dt doc power: reset: st-poweroff: Remove obsolete platforms. power: reset: zx-reboot: Unmap region obtained by of_iomap power: reset: xgene-reboot: Unmap region obtained by of_iomap power: supply: ab8500: cleanup with list_first_entry_or_null() power: reset: add in missing white space in error message text sbs-battery: make writes to ManufacturerAccess optional power: bq24257: Fix use of uninitialized pointer bq->charger power: supply: sbs-battery: simplify DT parsing power: supply: bq24735-charger: Request status GPIO with initial input setup power: supply: sbs-battery: Use gpio_desc and sleeping calls for battery detect power: supply: act8945a_charger: Add max current property power: supply: act8945a_charger: Add capacity level property doc: bindings: power: act8945a-charger: Update properties. power: supply: act8945a_charger: Fix the power supply type power: supply: act8945a_charger: Add status change update support power: supply: act8945a_charger: Improve state handling power: supply: act8945a_charger: Remove "battery_temperature" ...
Diffstat (limited to 'drivers/power/supply')
-rw-r--r--drivers/power/supply/88pm860x_battery.c1021
-rw-r--r--drivers/power/supply/88pm860x_charger.c760
-rw-r--r--drivers/power/supply/Kconfig514
-rw-r--r--drivers/power/supply/Makefile74
-rw-r--r--drivers/power/supply/ab8500_bmdata.c605
-rw-r--r--drivers/power/supply/ab8500_btemp.c1212
-rw-r--r--drivers/power/supply/ab8500_charger.c3765
-rw-r--r--drivers/power/supply/ab8500_fg.c3267
-rw-r--r--drivers/power/supply/abx500_chargalg.c2166
-rw-r--r--drivers/power/supply/act8945a_charger.c666
-rw-r--r--drivers/power/supply/apm_power.c376
-rw-r--r--drivers/power/supply/axp20x_usb_power.c294
-rw-r--r--drivers/power/supply/axp288_charger.c969
-rw-r--r--drivers/power/supply/axp288_fuel_gauge.c1154
-rw-r--r--drivers/power/supply/bq2415x_charger.c1815
-rw-r--r--drivers/power/supply/bq24190_charger.c1546
-rw-r--r--drivers/power/supply/bq24257_charger.c1196
-rw-r--r--drivers/power/supply/bq24735-charger.c489
-rw-r--r--drivers/power/supply/bq25890_charger.c994
-rw-r--r--drivers/power/supply/bq27xxx_battery.c1139
-rw-r--r--drivers/power/supply/bq27xxx_battery_i2c.c205
-rw-r--r--drivers/power/supply/charger-manager.c2074
-rw-r--r--drivers/power/supply/collie_battery.c422
-rw-r--r--drivers/power/supply/da9030_battery.c596
-rw-r--r--drivers/power/supply/da9052-battery.c669
-rw-r--r--drivers/power/supply/da9150-charger.c694
-rw-r--r--drivers/power/supply/da9150-fg.c579
-rw-r--r--drivers/power/supply/ds2760_battery.c648
-rw-r--r--drivers/power/supply/ds2780_battery.c838
-rw-r--r--drivers/power/supply/ds2781_battery.c839
-rw-r--r--drivers/power/supply/ds2782_battery.c475
-rw-r--r--drivers/power/supply/generic-adc-battery.c432
-rw-r--r--drivers/power/supply/goldfish_battery.c256
-rw-r--r--drivers/power/supply/gpio-charger.c280
-rw-r--r--drivers/power/supply/intel_mid_battery.c795
-rw-r--r--drivers/power/supply/ipaq_micro_battery.c316
-rw-r--r--drivers/power/supply/isp1704_charger.c559
-rw-r--r--drivers/power/supply/jz4740-battery.c425
-rw-r--r--drivers/power/supply/lp8727_charger.c631
-rw-r--r--drivers/power/supply/lp8788-charger.c764
-rw-r--r--drivers/power/supply/ltc2941-battery-gauge.c514
-rw-r--r--drivers/power/supply/max14577_charger.c648
-rw-r--r--drivers/power/supply/max17040_battery.c305
-rw-r--r--drivers/power/supply/max17042_battery.c1019
-rw-r--r--drivers/power/supply/max77693_charger.c771
-rw-r--r--drivers/power/supply/max8903_charger.c459
-rw-r--r--drivers/power/supply/max8925_power.c596
-rw-r--r--drivers/power/supply/max8997_charger.c211
-rw-r--r--drivers/power/supply/max8998_charger.c202
-rw-r--r--drivers/power/supply/olpc_battery.c692
-rw-r--r--drivers/power/supply/pcf50633-charger.c488
-rw-r--r--drivers/power/supply/pda_power.c514
-rw-r--r--drivers/power/supply/pm2301_charger.c1258
-rw-r--r--drivers/power/supply/pm2301_charger.h493
-rw-r--r--drivers/power/supply/pmu_battery.c226
-rw-r--r--drivers/power/supply/power_supply.h42
-rw-r--r--drivers/power/supply/power_supply_core.c989
-rw-r--r--drivers/power/supply/power_supply_leds.c170
-rw-r--r--drivers/power/supply/power_supply_sysfs.c337
-rw-r--r--drivers/power/supply/qcom_smbb.c972
-rw-r--r--drivers/power/supply/rt5033_battery.c182
-rw-r--r--drivers/power/supply/rt9455_charger.c1763
-rw-r--r--drivers/power/supply/rx51_battery.c297
-rw-r--r--drivers/power/supply/s3c_adc_battery.c459
-rw-r--r--drivers/power/supply/sbs-battery.c934
-rw-r--r--drivers/power/supply/smb347-charger.c1334
-rw-r--r--drivers/power/supply/test_power.c533
-rw-r--r--drivers/power/supply/tosa_battery.c470
-rw-r--r--drivers/power/supply/tps65090-charger.c370
-rw-r--r--drivers/power/supply/tps65217_charger.c295
-rw-r--r--drivers/power/supply/twl4030_charger.c1162
-rw-r--r--drivers/power/supply/twl4030_madc_battery.c278
-rw-r--r--drivers/power/supply/wm831x_backup.c225
-rw-r--r--drivers/power/supply/wm831x_power.c673
-rw-r--r--drivers/power/supply/wm8350_power.c540
-rw-r--r--drivers/power/supply/wm97xx_battery.c297
-rw-r--r--drivers/power/supply/z2_battery.c330
77 files changed, 57567 insertions, 0 deletions
diff --git a/drivers/power/supply/88pm860x_battery.c b/drivers/power/supply/88pm860x_battery.c
new file mode 100644
index 0000000..63c57dc
--- /dev/null
+++ b/drivers/power/supply/88pm860x_battery.c
@@ -0,0 +1,1021 @@
+/*
+ * Battery driver for Marvell 88PM860x PMIC
+ *
+ * Copyright (c) 2012 Marvell International Ltd.
+ * Author: Jett Zhou <jtzhou@marvell.com>
+ * Haojian Zhuang <haojian.zhuang@marvell.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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/mutex.h>
+#include <linux/string.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/delay.h>
+
+/* bit definitions of Status Query Interface 2 */
+#define STATUS2_CHG (1 << 2)
+#define STATUS2_BAT (1 << 3)
+#define STATUS2_VBUS (1 << 4)
+
+/* bit definitions of Measurement Enable 1 Register */
+#define MEAS1_TINT (1 << 3)
+#define MEAS1_GP1 (1 << 5)
+
+/* bit definitions of Measurement Enable 3 Register */
+#define MEAS3_IBAT (1 << 0)
+#define MEAS3_BAT_DET (1 << 1)
+#define MEAS3_CC (1 << 2)
+
+/* bit definitions of Measurement Off Time Register */
+#define MEAS_OFF_SLEEP_EN (1 << 1)
+
+/* bit definitions of GPADC Bias Current 2 Register */
+#define GPBIAS2_GPADC1_SET (2 << 4)
+/* GPADC1 Bias Current value in uA unit */
+#define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1)
+
+/* bit definitions of GPADC Misc 1 Register */
+#define GPMISC1_GPADC_EN (1 << 0)
+
+/* bit definitions of Charger Control 6 Register */
+#define CC6_BAT_DET_GPADC1 1
+
+/* bit definitions of Coulomb Counter Reading Register */
+#define CCNT_AVG_SEL (4 << 3)
+
+/* bit definitions of RTC miscellaneous Register1 */
+#define RTC_SOC_5LSB (0x1F << 3)
+
+/* bit definitions of RTC Register1 */
+#define RTC_SOC_3MSB (0x7)
+
+/* bit definitions of Power up Log register */
+#define BAT_WU_LOG (1<<6)
+
+/* coulomb counter index */
+#define CCNT_POS1 0
+#define CCNT_POS2 1
+#define CCNT_NEG1 2
+#define CCNT_NEG2 3
+#define CCNT_SPOS 4
+#define CCNT_SNEG 5
+
+/* OCV -- Open Circuit Voltage */
+#define OCV_MODE_ACTIVE 0
+#define OCV_MODE_SLEEP 1
+
+/* Vbat range of CC for measuring Rbat */
+#define LOW_BAT_THRESHOLD 3600
+#define VBATT_RESISTOR_MIN 3800
+#define VBATT_RESISTOR_MAX 4100
+
+/* TBAT for batt, TINT for chip itself */
+#define PM860X_TEMP_TINT (0)
+#define PM860X_TEMP_TBAT (1)
+
+/*
+ * Battery temperature based on NTC resistor, defined
+ * corresponding resistor value -- Ohm / C degeree.
+ */
+#define TBAT_NEG_25D 127773 /* -25 */
+#define TBAT_NEG_10D 54564 /* -10 */
+#define TBAT_0D 32330 /* 0 */
+#define TBAT_10D 19785 /* 10 */
+#define TBAT_20D 12468 /* 20 */
+#define TBAT_30D 8072 /* 30 */
+#define TBAT_40D 5356 /* 40 */
+
+struct pm860x_battery_info {
+ struct pm860x_chip *chip;
+ struct i2c_client *i2c;
+ struct device *dev;
+
+ struct power_supply *battery;
+ struct mutex lock;
+ int status;
+ int irq_cc;
+ int irq_batt;
+ int max_capacity;
+ int resistor; /* Battery Internal Resistor */
+ int last_capacity;
+ int start_soc;
+ unsigned present:1;
+ unsigned temp_type:1; /* TINT or TBAT */
+};
+
+struct ccnt {
+ unsigned long long int pos;
+ unsigned long long int neg;
+ unsigned int spos;
+ unsigned int sneg;
+
+ int total_chg; /* mAh(3.6C) */
+ int total_dischg; /* mAh(3.6C) */
+};
+
+/*
+ * State of Charge.
+ * The first number is mAh(=3.6C), and the second number is percent point.
+ */
+static int array_soc[][2] = {
+ {4170, 100}, {4154, 99}, {4136, 98}, {4122, 97}, {4107, 96},
+ {4102, 95}, {4088, 94}, {4081, 93}, {4070, 92}, {4060, 91},
+ {4053, 90}, {4044, 89}, {4035, 88}, {4028, 87}, {4019, 86},
+ {4013, 85}, {4006, 84}, {3995, 83}, {3987, 82}, {3982, 81},
+ {3976, 80}, {3968, 79}, {3962, 78}, {3954, 77}, {3946, 76},
+ {3941, 75}, {3934, 74}, {3929, 73}, {3922, 72}, {3916, 71},
+ {3910, 70}, {3904, 69}, {3898, 68}, {3892, 67}, {3887, 66},
+ {3880, 65}, {3874, 64}, {3868, 63}, {3862, 62}, {3854, 61},
+ {3849, 60}, {3843, 59}, {3840, 58}, {3833, 57}, {3829, 56},
+ {3824, 55}, {3818, 54}, {3815, 53}, {3810, 52}, {3808, 51},
+ {3804, 50}, {3801, 49}, {3798, 48}, {3796, 47}, {3792, 46},
+ {3789, 45}, {3785, 44}, {3784, 43}, {3782, 42}, {3780, 41},
+ {3777, 40}, {3776, 39}, {3774, 38}, {3772, 37}, {3771, 36},
+ {3769, 35}, {3768, 34}, {3764, 33}, {3763, 32}, {3760, 31},
+ {3760, 30}, {3754, 29}, {3750, 28}, {3749, 27}, {3744, 26},
+ {3740, 25}, {3734, 24}, {3732, 23}, {3728, 22}, {3726, 21},
+ {3720, 20}, {3716, 19}, {3709, 18}, {3703, 17}, {3698, 16},
+ {3692, 15}, {3683, 14}, {3675, 13}, {3670, 12}, {3665, 11},
+ {3661, 10}, {3649, 9}, {3637, 8}, {3622, 7}, {3609, 6},
+ {3580, 5}, {3558, 4}, {3540, 3}, {3510, 2}, {3429, 1},
+};
+
+static struct ccnt ccnt_data;
+
+/*
+ * register 1 bit[7:0] -- bit[11:4] of measured value of voltage
+ * register 0 bit[3:0] -- bit[3:0] of measured value of voltage
+ */
+static int measure_12bit_voltage(struct pm860x_battery_info *info,
+ int offset, int *data)
+{
+ unsigned char buf[2];
+ int ret;
+
+ ret = pm860x_bulk_read(info->i2c, offset, 2, buf);
+ if (ret < 0)
+ return ret;
+
+ *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f);
+ /* V_MEAS(mV) = data * 1.8 * 1000 / (2^12) */
+ *data = ((*data & 0xfff) * 9 * 25) >> 9;
+ return 0;
+}
+
+static int measure_vbatt(struct pm860x_battery_info *info, int state,
+ int *data)
+{
+ unsigned char buf[5];
+ int ret;
+
+ switch (state) {
+ case OCV_MODE_ACTIVE:
+ ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data);
+ if (ret)
+ return ret;
+ /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */
+ *data *= 3;
+ break;
+ case OCV_MODE_SLEEP:
+ /*
+ * voltage value of VBATT in sleep mode is saved in different
+ * registers.
+ * bit[11:10] -- bit[7:6] of LDO9(0x18)
+ * bit[9:8] -- bit[7:6] of LDO8(0x17)
+ * bit[7:6] -- bit[7:6] of LDO7(0x16)
+ * bit[5:4] -- bit[7:6] of LDO6(0x15)
+ * bit[3:0] -- bit[7:4] of LDO5(0x14)
+ */
+ ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf);
+ if (ret < 0)
+ return ret;
+ ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8)
+ | ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4)
+ | (buf[0] >> 4);
+ /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */
+ *data = ((*data & 0xff) * 27 * 25) >> 9;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Return value is signed data.
+ * Negative value means discharging, and positive value means charging.
+ */
+static int measure_current(struct pm860x_battery_info *info, int *data)
+{
+ unsigned char buf[2];
+ short s;
+ int ret;
+
+ ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf);
+ if (ret < 0)
+ return ret;
+
+ s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
+ /* current(mA) = value * 0.125 */
+ *data = s >> 3;
+ return 0;
+}
+
+static int set_charger_current(struct pm860x_battery_info *info, int data,
+ int *old)
+{
+ int ret;
+
+ if (data < 50 || data > 1600 || !old)
+ return -EINVAL;
+
+ data = ((data - 50) / 50) & 0x1f;
+ *old = pm860x_reg_read(info->i2c, PM8607_CHG_CTRL2);
+ *old = (*old & 0x1f) * 50 + 50;
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, data);
+ if (ret < 0)
+ return ret;
+ return 0;
+}
+
+static int read_ccnt(struct pm860x_battery_info *info, int offset,
+ int *ccnt)
+{
+ unsigned char buf[2];
+ int ret;
+
+ ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7, offset & 7);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_bulk_read(info->i2c, PM8607_CCNT_MEAS1, 2, buf);
+ if (ret < 0)
+ goto out;
+ *ccnt = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff);
+ return 0;
+out:
+ return ret;
+}
+
+static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt)
+{
+ unsigned int sum;
+ int ret;
+ int data;
+
+ ret = read_ccnt(info, CCNT_POS1, &data);
+ if (ret)
+ goto out;
+ sum = data & 0xffff;
+ ret = read_ccnt(info, CCNT_POS2, &data);
+ if (ret)
+ goto out;
+ sum |= (data & 0xffff) << 16;
+ ccnt->pos += sum;
+
+ ret = read_ccnt(info, CCNT_NEG1, &data);
+ if (ret)
+ goto out;
+ sum = data & 0xffff;
+ ret = read_ccnt(info, CCNT_NEG2, &data);
+ if (ret)
+ goto out;
+ sum |= (data & 0xffff) << 16;
+ sum = ~sum + 1; /* since it's negative */
+ ccnt->neg += sum;
+
+ ret = read_ccnt(info, CCNT_SPOS, &data);
+ if (ret)
+ goto out;
+ ccnt->spos += data;
+ ret = read_ccnt(info, CCNT_SNEG, &data);
+ if (ret)
+ goto out;
+
+ /*
+ * charge(mAh) = count * 1.6984 * 1e(-8)
+ * = count * 16984 * 1.024 * 1.024 * 1.024 / (2 ^ 40)
+ * = count * 18236 / (2 ^ 40)
+ */
+ ccnt->total_chg = (int) ((ccnt->pos * 18236) >> 40);
+ ccnt->total_dischg = (int) ((ccnt->neg * 18236) >> 40);
+ return 0;
+out:
+ return ret;
+}
+
+static int clear_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt)
+{
+ int data;
+
+ memset(ccnt, 0, sizeof(*ccnt));
+ /* read to clear ccnt */
+ read_ccnt(info, CCNT_POS1, &data);
+ read_ccnt(info, CCNT_POS2, &data);
+ read_ccnt(info, CCNT_NEG1, &data);
+ read_ccnt(info, CCNT_NEG2, &data);
+ read_ccnt(info, CCNT_SPOS, &data);
+ read_ccnt(info, CCNT_SNEG, &data);
+ return 0;
+}
+
+/* Calculate Open Circuit Voltage */
+static int calc_ocv(struct pm860x_battery_info *info, int *ocv)
+{
+ int ret;
+ int i;
+ int data;
+ int vbatt_avg;
+ int vbatt_sum;
+ int ibatt_avg;
+ int ibatt_sum;
+
+ if (!ocv)
+ return -EINVAL;
+
+ for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) {
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ goto out;
+ vbatt_sum += data;
+ ret = measure_current(info, &data);
+ if (ret)
+ goto out;
+ ibatt_sum += data;
+ }
+ vbatt_avg = vbatt_sum / 10;
+ ibatt_avg = ibatt_sum / 10;
+
+ mutex_lock(&info->lock);
+ if (info->present)
+ *ocv = vbatt_avg - ibatt_avg * info->resistor / 1000;
+ else
+ *ocv = vbatt_avg;
+ mutex_unlock(&info->lock);
+ dev_dbg(info->dev, "VBAT average:%d, OCV:%d\n", vbatt_avg, *ocv);
+ return 0;
+out:
+ return ret;
+}
+
+/* Calculate State of Charge (percent points) */
+static int calc_soc(struct pm860x_battery_info *info, int state, int *soc)
+{
+ int i;
+ int ocv;
+ int count;
+ int ret = -EINVAL;
+
+ if (!soc)
+ return -EINVAL;
+
+ switch (state) {
+ case OCV_MODE_ACTIVE:
+ ret = calc_ocv(info, &ocv);
+ break;
+ case OCV_MODE_SLEEP:
+ ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv);
+ break;
+ }
+ if (ret)
+ return ret;
+
+ count = ARRAY_SIZE(array_soc);
+ if (ocv < array_soc[count - 1][0]) {
+ *soc = 0;
+ return 0;
+ }
+
+ for (i = 0; i < count; i++) {
+ if (ocv >= array_soc[i][0]) {
+ *soc = array_soc[i][1];
+ break;
+ }
+ }
+ return 0;
+}
+
+static irqreturn_t pm860x_coulomb_handler(int irq, void *data)
+{
+ struct pm860x_battery_info *info = data;
+
+ calc_ccnt(info, &ccnt_data);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_batt_handler(int irq, void *data)
+{
+ struct pm860x_battery_info *info = data;
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ if (ret & STATUS2_BAT) {
+ info->present = 1;
+ info->temp_type = PM860X_TEMP_TBAT;
+ } else {
+ info->present = 0;
+ info->temp_type = PM860X_TEMP_TINT;
+ }
+ mutex_unlock(&info->lock);
+ /* clear ccnt since battery is attached or dettached */
+ clear_ccnt(info, &ccnt_data);
+ return IRQ_HANDLED;
+}
+
+static void pm860x_init_battery(struct pm860x_battery_info *info)
+{
+ unsigned char buf[2];
+ int ret;
+ int data;
+ int bat_remove;
+ int soc;
+
+ /* measure enable on GPADC1 */
+ data = MEAS1_GP1;
+ if (info->temp_type == PM860X_TEMP_TINT)
+ data |= MEAS1_TINT;
+ ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data);
+ if (ret)
+ goto out;
+
+ /* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */
+ data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC;
+ ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data);
+ if (ret)
+ goto out;
+
+ /* measure disable CC in sleep time */
+ ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82);
+ if (ret)
+ goto out;
+ ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c);
+ if (ret)
+ goto out;
+
+ /* enable GPADC */
+ ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1,
+ GPMISC1_GPADC_EN, GPMISC1_GPADC_EN);
+ if (ret < 0)
+ goto out;
+
+ /* detect battery via GPADC1 */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6,
+ CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1);
+ if (ret < 0)
+ goto out;
+
+ ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3,
+ CCNT_AVG_SEL);
+ if (ret < 0)
+ goto out;
+
+ /* set GPADC1 bias */
+ ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4,
+ GPBIAS2_GPADC1_SET);
+ if (ret < 0)
+ goto out;
+
+ /* check whether battery present) */
+ mutex_lock(&info->lock);
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ if (ret < 0) {
+ mutex_unlock(&info->lock);
+ goto out;
+ }
+ if (ret & STATUS2_BAT) {
+ info->present = 1;
+ info->temp_type = PM860X_TEMP_TBAT;
+ } else {
+ info->present = 0;
+ info->temp_type = PM860X_TEMP_TINT;
+ }
+ mutex_unlock(&info->lock);
+
+ calc_soc(info, OCV_MODE_ACTIVE, &soc);
+
+ data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG);
+ bat_remove = data & BAT_WU_LOG;
+
+ dev_dbg(info->dev, "battery wake up? %s\n",
+ bat_remove != 0 ? "yes" : "no");
+
+ /* restore SOC from RTC domain register */
+ if (bat_remove == 0) {
+ buf[0] = pm860x_reg_read(info->i2c, PM8607_RTC_MISC2);
+ buf[1] = pm860x_reg_read(info->i2c, PM8607_RTC1);
+ data = ((buf[1] & 0x3) << 5) | ((buf[0] >> 3) & 0x1F);
+ if (data > soc + 15)
+ info->start_soc = soc;
+ else if (data < soc - 15)
+ info->start_soc = soc;
+ else
+ info->start_soc = data;
+ dev_dbg(info->dev, "soc_rtc %d, soc_ocv :%d\n", data, soc);
+ } else {
+ pm860x_set_bits(info->i2c, PM8607_POWER_UP_LOG,
+ BAT_WU_LOG, BAT_WU_LOG);
+ info->start_soc = soc;
+ }
+ info->last_capacity = info->start_soc;
+ dev_dbg(info->dev, "init soc : %d\n", info->last_capacity);
+out:
+ return;
+}
+
+static void set_temp_threshold(struct pm860x_battery_info *info,
+ int min, int max)
+{
+ int data;
+
+ /* (tmp << 8) / 1800 */
+ if (min <= 0)
+ data = 0;
+ else
+ data = (min << 8) / 1800;
+ pm860x_reg_write(info->i2c, PM8607_GPADC1_HIGHTH, data);
+ dev_dbg(info->dev, "TEMP_HIGHTH : min: %d, 0x%x\n", min, data);
+
+ if (max <= 0)
+ data = 0xff;
+ else
+ data = (max << 8) / 1800;
+ pm860x_reg_write(info->i2c, PM8607_GPADC1_LOWTH, data);
+ dev_dbg(info->dev, "TEMP_LOWTH:max : %d, 0x%x\n", max, data);
+}
+
+static int measure_temp(struct pm860x_battery_info *info, int *data)
+{
+ int ret;
+ int temp;
+ int min;
+ int max;
+
+ if (info->temp_type == PM860X_TEMP_TINT) {
+ ret = measure_12bit_voltage(info, PM8607_TINT_MEAS1, data);
+ if (ret)
+ return ret;
+ *data = (*data - 884) * 1000 / 3611;
+ } else {
+ ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data);
+ if (ret)
+ return ret;
+ /* meausered Vtbat(mV) / Ibias_current(11uA)*/
+ *data = (*data * 1000) / GPBIAS2_GPADC1_UA;
+
+ if (*data > TBAT_NEG_25D) {
+ temp = -30; /* over cold , suppose -30 roughly */
+ max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, 0, max);
+ } else if (*data > TBAT_NEG_10D) {
+ temp = -15; /* -15 degree, code */
+ max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, 0, max);
+ } else if (*data > TBAT_0D) {
+ temp = -5; /* -5 degree */
+ min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, max);
+ } else if (*data > TBAT_10D) {
+ temp = 5; /* in range of (0, 10) */
+ min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, max);
+ } else if (*data > TBAT_20D) {
+ temp = 15; /* in range of (10, 20) */
+ min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, max);
+ } else if (*data > TBAT_30D) {
+ temp = 25; /* in range of (20, 30) */
+ min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, max);
+ } else if (*data > TBAT_40D) {
+ temp = 35; /* in range of (30, 40) */
+ min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000;
+ max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, max);
+ } else {
+ min = TBAT_40D * GPBIAS2_GPADC1_UA / 1000;
+ set_temp_threshold(info, min, 0);
+ temp = 45; /* over heat ,suppose 45 roughly */
+ }
+
+ dev_dbg(info->dev, "temp_C:%d C,temp_mv:%d mv\n", temp, *data);
+ *data = temp;
+ }
+ return 0;
+}
+
+static int calc_resistor(struct pm860x_battery_info *info)
+{
+ int vbatt_sum1;
+ int vbatt_sum2;
+ int chg_current;
+ int ibatt_sum1;
+ int ibatt_sum2;
+ int data;
+ int ret;
+ int i;
+
+ ret = measure_current(info, &data);
+ /* make sure that charging is launched by data > 0 */
+ if (ret || data < 0)
+ goto out;
+
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ goto out;
+ /* calculate resistor only in CC charge mode */
+ if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX)
+ goto out;
+
+ /* current is saved */
+ if (set_charger_current(info, 500, &chg_current))
+ goto out;
+
+ /*
+ * set charge current as 500mA, wait about 500ms till charging
+ * process is launched and stable with the newer charging current.
+ */
+ msleep(500);
+
+ for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) {
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ goto out_meas;
+ vbatt_sum1 += data;
+ ret = measure_current(info, &data);
+ if (ret)
+ goto out_meas;
+
+ if (data < 0)
+ ibatt_sum1 = ibatt_sum1 - data; /* discharging */
+ else
+ ibatt_sum1 = ibatt_sum1 + data; /* charging */
+ }
+
+ if (set_charger_current(info, 100, &ret))
+ goto out_meas;
+ /*
+ * set charge current as 100mA, wait about 500ms till charging
+ * process is launched and stable with the newer charging current.
+ */
+ msleep(500);
+
+ for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) {
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ goto out_meas;
+ vbatt_sum2 += data;
+ ret = measure_current(info, &data);
+ if (ret)
+ goto out_meas;
+
+ if (data < 0)
+ ibatt_sum2 = ibatt_sum2 - data; /* discharging */
+ else
+ ibatt_sum2 = ibatt_sum2 + data; /* charging */
+ }
+
+ /* restore current setting */
+ if (set_charger_current(info, chg_current, &ret))
+ goto out_meas;
+
+ if ((vbatt_sum1 > vbatt_sum2) && (ibatt_sum1 > ibatt_sum2) &&
+ (ibatt_sum2 > 0)) {
+ /* calculate resistor in discharging case */
+ data = 1000 * (vbatt_sum1 - vbatt_sum2)
+ / (ibatt_sum1 - ibatt_sum2);
+ if ((data - info->resistor > 0) &&
+ (data - info->resistor < info->resistor))
+ info->resistor = data;
+ if ((info->resistor - data > 0) &&
+ (info->resistor - data < data))
+ info->resistor = data;
+ }
+ return 0;
+
+out_meas:
+ set_charger_current(info, chg_current, &ret);
+out:
+ return -EINVAL;
+}
+
+static int calc_capacity(struct pm860x_battery_info *info, int *cap)
+{
+ int ret;
+ int data;
+ int ibat;
+ int cap_ocv = 0;
+ int cap_cc = 0;
+
+ ret = calc_ccnt(info, &ccnt_data);
+ if (ret)
+ goto out;
+soc:
+ data = info->max_capacity * info->start_soc / 100;
+ if (ccnt_data.total_dischg - ccnt_data.total_chg <= data) {
+ cap_cc =
+ data + ccnt_data.total_chg - ccnt_data.total_dischg;
+ } else {
+ clear_ccnt(info, &ccnt_data);
+ calc_soc(info, OCV_MODE_ACTIVE, &info->start_soc);
+ dev_dbg(info->dev, "restart soc = %d !\n",
+ info->start_soc);
+ goto soc;
+ }
+
+ cap_cc = cap_cc * 100 / info->max_capacity;
+ if (cap_cc < 0)
+ cap_cc = 0;
+ else if (cap_cc > 100)
+ cap_cc = 100;
+
+ dev_dbg(info->dev, "%s, last cap : %d", __func__,
+ info->last_capacity);
+
+ ret = measure_current(info, &ibat);
+ if (ret)
+ goto out;
+ /* Calculate the capacity when discharging(ibat < 0) */
+ if (ibat < 0) {
+ ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv);
+ if (ret)
+ cap_ocv = info->last_capacity;
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ goto out;
+ if (data <= LOW_BAT_THRESHOLD) {
+ /* choose the lower capacity value to report
+ * between vbat and CC when vbat < 3.6v;
+ * than 3.6v;
+ */
+ *cap = min(cap_ocv, cap_cc);
+ } else {
+ /* when detect vbat > 3.6v, but cap_cc < 15,and
+ * cap_ocv is 10% larger than cap_cc, we can think
+ * CC have some accumulation error, switch to OCV
+ * to estimate capacity;
+ * */
+ if (cap_cc < 15 && cap_ocv - cap_cc > 10)
+ *cap = cap_ocv;
+ else
+ *cap = cap_cc;
+ }
+ /* when discharging, make sure current capacity
+ * is lower than last*/
+ if (*cap > info->last_capacity)
+ *cap = info->last_capacity;
+ } else {
+ *cap = cap_cc;
+ }
+ info->last_capacity = *cap;
+
+ dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n",
+ (ibat < 0) ? "discharging" : "charging",
+ cap_ocv, cap_cc, *cap);
+ /*
+ * store the current capacity to RTC domain register,
+ * after next power up , it will be restored.
+ */
+ pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB,
+ (*cap & 0x1F) << 3);
+ pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB,
+ ((*cap >> 5) & 0x3));
+ return 0;
+out:
+ return ret;
+}
+
+static void pm860x_external_power_changed(struct power_supply *psy)
+{
+ struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent);
+
+ calc_resistor(info);
+}
+
+static int pm860x_batt_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent);
+ int data;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = info->present;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = calc_capacity(info, &data);
+ if (ret)
+ return ret;
+ if (data < 0)
+ data = 0;
+ else if (data > 100)
+ data = 100;
+ /* return 100 if battery is not attached */
+ if (!info->present)
+ data = 100;
+ val->intval = data;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* return real vbatt Voltage */
+ ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data);
+ if (ret)
+ return ret;
+ val->intval = data * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /* return Open Circuit Voltage (not measured voltage) */
+ ret = calc_ocv(info, &data);
+ if (ret)
+ return ret;
+ val->intval = data * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = measure_current(info, &data);
+ if (ret)
+ return ret;
+ val->intval = data;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (info->present) {
+ ret = measure_temp(info, &data);
+ if (ret)
+ return ret;
+ data *= 10;
+ } else {
+ /* Fake Temp 25C Without Battery */
+ data = 250;
+ }
+ val->intval = data;
+ break;
+ default:
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static int pm860x_batt_set_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct pm860x_battery_info *info = dev_get_drvdata(psy->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ clear_ccnt(info, &ccnt_data);
+ info->start_soc = 100;
+ dev_dbg(info->dev, "chg done, update soc = %d\n",
+ info->start_soc);
+ break;
+ default:
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+
+static enum power_supply_property pm860x_batt_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static const struct power_supply_desc pm860x_battery_desc = {
+ .name = "battery-monitor",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = pm860x_batt_props,
+ .num_properties = ARRAY_SIZE(pm860x_batt_props),
+ .get_property = pm860x_batt_get_prop,
+ .set_property = pm860x_batt_set_prop,
+ .external_power_changed = pm860x_external_power_changed,
+};
+
+static int pm860x_battery_probe(struct platform_device *pdev)
+{
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct pm860x_battery_info *info;
+ struct pm860x_power_pdata *pdata;
+ int ret;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->irq_cc = platform_get_irq(pdev, 0);
+ if (info->irq_cc <= 0) {
+ dev_err(&pdev->dev, "No IRQ resource!\n");
+ return -EINVAL;
+ }
+
+ info->irq_batt = platform_get_irq(pdev, 1);
+ if (info->irq_batt <= 0) {
+ dev_err(&pdev->dev, "No IRQ resource!\n");
+ return -EINVAL;
+ }
+
+ info->chip = chip;
+ info->i2c =
+ (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
+ info->dev = &pdev->dev;
+ info->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ pdata = pdev->dev.platform_data;
+
+ mutex_init(&info->lock);
+ platform_set_drvdata(pdev, info);
+
+ pm860x_init_battery(info);
+
+ if (pdata && pdata->max_capacity)
+ info->max_capacity = pdata->max_capacity;
+ else
+ info->max_capacity = 1500; /* set default capacity */
+ if (pdata && pdata->resistor)
+ info->resistor = pdata->resistor;
+ else
+ info->resistor = 300; /* set default internal resistor */
+
+ info->battery = devm_power_supply_register(&pdev->dev,
+ &pm860x_battery_desc,
+ NULL);
+ if (IS_ERR(info->battery))
+ return PTR_ERR(info->battery);
+ info->battery->dev.parent = &pdev->dev;
+
+ ret = devm_request_threaded_irq(chip->dev, info->irq_cc, NULL,
+ pm860x_coulomb_handler, IRQF_ONESHOT,
+ "coulomb", info);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ info->irq_cc, ret);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(chip->dev, info->irq_batt, NULL,
+ pm860x_batt_handler,
+ IRQF_ONESHOT, "battery", info);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ info->irq_batt, ret);
+ return ret;
+ }
+
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int pm860x_battery_suspend(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev))
+ chip->wakeup_flag |= 1 << PM8607_IRQ_CC;
+ return 0;
+}
+
+static int pm860x_battery_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+
+ if (device_may_wakeup(dev))
+ chip->wakeup_flag &= ~(1 << PM8607_IRQ_CC);
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(pm860x_battery_pm_ops,
+ pm860x_battery_suspend, pm860x_battery_resume);
+
+static struct platform_driver pm860x_battery_driver = {
+ .driver = {
+ .name = "88pm860x-battery",
+ .pm = &pm860x_battery_pm_ops,
+ },
+ .probe = pm860x_battery_probe,
+};
+module_platform_driver(pm860x_battery_driver);
+
+MODULE_DESCRIPTION("Marvell 88PM860x Battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/88pm860x_charger.c b/drivers/power/supply/88pm860x_charger.c
new file mode 100644
index 0000000..2b82e44
--- /dev/null
+++ b/drivers/power/supply/88pm860x_charger.c
@@ -0,0 +1,760 @@
+/*
+ * Battery driver for Marvell 88PM860x PMIC
+ *
+ * Copyright (c) 2012 Marvell International Ltd.
+ * Author: Jett Zhou <jtzhou@marvell.com>
+ * Haojian Zhuang <haojian.zhuang@marvell.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/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/88pm860x.h>
+#include <linux/delay.h>
+#include <linux/uaccess.h>
+#include <asm/div64.h>
+
+/* bit definitions of Status Query Interface 2 */
+#define STATUS2_CHG (1 << 2)
+
+/* bit definitions of Reset Out Register */
+#define RESET_SW_PD (1 << 7)
+
+/* bit definitions of PreReg 1 */
+#define PREREG1_90MA (0x0)
+#define PREREG1_180MA (0x1)
+#define PREREG1_450MA (0x4)
+#define PREREG1_540MA (0x5)
+#define PREREG1_1350MA (0xE)
+#define PREREG1_VSYS_4_5V (3 << 4)
+
+/* bit definitions of Charger Control 1 Register */
+#define CC1_MODE_OFF (0)
+#define CC1_MODE_PRECHARGE (1)
+#define CC1_MODE_FASTCHARGE (2)
+#define CC1_MODE_PULSECHARGE (3)
+#define CC1_ITERM_20MA (0 << 2)
+#define CC1_ITERM_60MA (2 << 2)
+#define CC1_VFCHG_4_2V (9 << 4)
+
+/* bit definitions of Charger Control 2 Register */
+#define CC2_ICHG_100MA (0x1)
+#define CC2_ICHG_500MA (0x9)
+#define CC2_ICHG_1000MA (0x13)
+
+/* bit definitions of Charger Control 3 Register */
+#define CC3_180MIN_TIMEOUT (0x6 << 4)
+#define CC3_270MIN_TIMEOUT (0x7 << 4)
+#define CC3_360MIN_TIMEOUT (0xA << 4)
+#define CC3_DISABLE_TIMEOUT (0xF << 4)
+
+/* bit definitions of Charger Control 4 Register */
+#define CC4_IPRE_40MA (7)
+#define CC4_VPCHG_3_2V (3 << 4)
+#define CC4_IFCHG_MON_EN (1 << 6)
+#define CC4_BTEMP_MON_EN (1 << 7)
+
+/* bit definitions of Charger Control 6 Register */
+#define CC6_BAT_OV_EN (1 << 2)
+#define CC6_BAT_UV_EN (1 << 3)
+#define CC6_UV_VBAT_SET (0x3 << 6) /* 2.8v */
+
+/* bit definitions of Charger Control 7 Register */
+#define CC7_BAT_REM_EN (1 << 3)
+#define CC7_IFSM_EN (1 << 7)
+
+/* bit definitions of Measurement Enable 1 Register */
+#define MEAS1_VBAT (1 << 0)
+
+/* bit definitions of Measurement Enable 3 Register */
+#define MEAS3_IBAT_EN (1 << 0)
+#define MEAS3_CC_EN (1 << 2)
+
+#define FSM_INIT 0
+#define FSM_DISCHARGE 1
+#define FSM_PRECHARGE 2
+#define FSM_FASTCHARGE 3
+
+#define PRECHARGE_THRESHOLD 3100
+#define POWEROFF_THRESHOLD 3400
+#define CHARGE_THRESHOLD 4000
+#define DISCHARGE_THRESHOLD 4180
+
+/* over-temperature on PM8606 setting */
+#define OVER_TEMP_FLAG (1 << 6)
+#define OVTEMP_AUTORECOVER (1 << 3)
+
+/* over-voltage protect on vchg setting mv */
+#define VCHG_NORMAL_LOW 4200
+#define VCHG_NORMAL_CHECK 5800
+#define VCHG_NORMAL_HIGH 6000
+#define VCHG_OVP_LOW 5500
+
+struct pm860x_charger_info {
+ struct pm860x_chip *chip;
+ struct i2c_client *i2c;
+ struct i2c_client *i2c_8606;
+ struct device *dev;
+
+ struct power_supply *usb;
+ struct mutex lock;
+ int irq_nums;
+ int irq[7];
+ unsigned state:3; /* fsm state */
+ unsigned online:1; /* usb charger */
+ unsigned present:1; /* battery present */
+ unsigned allowed:1;
+};
+
+static char *pm860x_supplied_to[] = {
+ "battery-monitor",
+};
+
+static int measure_vchg(struct pm860x_charger_info *info, int *data)
+{
+ unsigned char buf[2];
+ int ret = 0;
+
+ ret = pm860x_bulk_read(info->i2c, PM8607_VCHG_MEAS1, 2, buf);
+ if (ret < 0)
+ return ret;
+
+ *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f);
+ /* V_BATT_MEAS(mV) = value * 5 * 1.8 * 1000 / (2^12) */
+ *data = ((*data & 0xfff) * 9 * 125) >> 9;
+
+ dev_dbg(info->dev, "%s, vchg: %d mv\n", __func__, *data);
+
+ return ret;
+}
+
+static void set_vchg_threshold(struct pm860x_charger_info *info,
+ int min, int max)
+{
+ int data;
+
+ /* (tmp << 8) * / 5 / 1800 */
+ if (min <= 0)
+ data = 0;
+ else
+ data = (min << 5) / 1125;
+ pm860x_reg_write(info->i2c, PM8607_VCHG_LOWTH, data);
+ dev_dbg(info->dev, "VCHG_LOWTH:%dmv, 0x%x\n", min, data);
+
+ if (max <= 0)
+ data = 0xff;
+ else
+ data = (max << 5) / 1125;
+ pm860x_reg_write(info->i2c, PM8607_VCHG_HIGHTH, data);
+ dev_dbg(info->dev, "VCHG_HIGHTH:%dmv, 0x%x\n", max, data);
+
+}
+
+static void set_vbatt_threshold(struct pm860x_charger_info *info,
+ int min, int max)
+{
+ int data;
+
+ /* (tmp << 8) * 3 / 1800 */
+ if (min <= 0)
+ data = 0;
+ else
+ data = (min << 5) / 675;
+ pm860x_reg_write(info->i2c, PM8607_VBAT_LOWTH, data);
+ dev_dbg(info->dev, "VBAT Min:%dmv, LOWTH:0x%x\n", min, data);
+
+ if (max <= 0)
+ data = 0xff;
+ else
+ data = (max << 5) / 675;
+ pm860x_reg_write(info->i2c, PM8607_VBAT_HIGHTH, data);
+ dev_dbg(info->dev, "VBAT Max:%dmv, HIGHTH:0x%x\n", max, data);
+
+ return;
+}
+
+static int start_precharge(struct pm860x_charger_info *info)
+{
+ int ret;
+
+ dev_dbg(info->dev, "Start Pre-charging!\n");
+ set_vbatt_threshold(info, 0, 0);
+
+ ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA,
+ PREREG1_1350MA | PREREG1_VSYS_4_5V);
+ if (ret < 0)
+ goto out;
+ /* stop charging */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3,
+ CC1_MODE_OFF);
+ if (ret < 0)
+ goto out;
+ /* set 270 minutes timeout */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4),
+ CC3_270MIN_TIMEOUT);
+ if (ret < 0)
+ goto out;
+ /* set precharge current, termination voltage, IBAT & TBAT monitor */
+ ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL4,
+ CC4_IPRE_40MA | CC4_VPCHG_3_2V |
+ CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7,
+ CC7_BAT_REM_EN | CC7_IFSM_EN,
+ CC7_BAT_REM_EN | CC7_IFSM_EN);
+ if (ret < 0)
+ goto out;
+ /* trigger precharge */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3,
+ CC1_MODE_PRECHARGE);
+out:
+ return ret;
+}
+
+static int start_fastcharge(struct pm860x_charger_info *info)
+{
+ int ret;
+
+ dev_dbg(info->dev, "Start Fast-charging!\n");
+
+ /* set fastcharge termination current & voltage, disable charging */
+ ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL1,
+ CC1_MODE_OFF | CC1_ITERM_60MA |
+ CC1_VFCHG_4_2V);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA,
+ PREREG1_540MA | PREREG1_VSYS_4_5V);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f,
+ CC2_ICHG_500MA);
+ if (ret < 0)
+ goto out;
+ /* set 270 minutes timeout */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4),
+ CC3_270MIN_TIMEOUT);
+ if (ret < 0)
+ goto out;
+ /* set IBAT & TBAT monitor */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL4,
+ CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN,
+ CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6,
+ CC6_BAT_OV_EN | CC6_BAT_UV_EN |
+ CC6_UV_VBAT_SET,
+ CC6_BAT_OV_EN | CC6_BAT_UV_EN |
+ CC6_UV_VBAT_SET);
+ if (ret < 0)
+ goto out;
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7,
+ CC7_BAT_REM_EN | CC7_IFSM_EN,
+ CC7_BAT_REM_EN | CC7_IFSM_EN);
+ if (ret < 0)
+ goto out;
+ /* launch fast-charge */
+ ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3,
+ CC1_MODE_FASTCHARGE);
+ /* vchg threshold setting */
+ set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_NORMAL_HIGH);
+out:
+ return ret;
+}
+
+static void stop_charge(struct pm860x_charger_info *info, int vbatt)
+{
+ dev_dbg(info->dev, "Stop charging!\n");
+ pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, CC1_MODE_OFF);
+ if (vbatt > CHARGE_THRESHOLD && info->online)
+ set_vbatt_threshold(info, CHARGE_THRESHOLD, 0);
+}
+
+static void power_off_notification(struct pm860x_charger_info *info)
+{
+ dev_dbg(info->dev, "Power-off notification!\n");
+}
+
+static int set_charging_fsm(struct pm860x_charger_info *info)
+{
+ struct power_supply *psy;
+ union power_supply_propval data;
+ unsigned char fsm_state[][16] = { "init", "discharge", "precharge",
+ "fastcharge",
+ };
+ int ret;
+ int vbatt;
+
+ psy = power_supply_get_by_name(pm860x_supplied_to[0]);
+ if (!psy)
+ return -EINVAL;
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &data);
+ if (ret) {
+ power_supply_put(psy);
+ return ret;
+ }
+ vbatt = data.intval / 1000;
+
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT, &data);
+ if (ret) {
+ power_supply_put(psy);
+ return ret;
+ }
+ power_supply_put(psy);
+
+ mutex_lock(&info->lock);
+ info->present = data.intval;
+
+ dev_dbg(info->dev, "Entering FSM:%s, Charger:%s, Battery:%s, "
+ "Allowed:%d\n",
+ &fsm_state[info->state][0],
+ (info->online) ? "online" : "N/A",
+ (info->present) ? "present" : "N/A", info->allowed);
+ dev_dbg(info->dev, "set_charging_fsm:vbatt:%d(mV)\n", vbatt);
+
+ switch (info->state) {
+ case FSM_INIT:
+ if (info->online && info->present && info->allowed) {
+ if (vbatt < PRECHARGE_THRESHOLD) {
+ info->state = FSM_PRECHARGE;
+ start_precharge(info);
+ } else if (vbatt > DISCHARGE_THRESHOLD) {
+ info->state = FSM_DISCHARGE;
+ stop_charge(info, vbatt);
+ } else if (vbatt < DISCHARGE_THRESHOLD) {
+ info->state = FSM_FASTCHARGE;
+ start_fastcharge(info);
+ }
+ } else {
+ if (vbatt < POWEROFF_THRESHOLD) {
+ power_off_notification(info);
+ } else {
+ info->state = FSM_DISCHARGE;
+ stop_charge(info, vbatt);
+ }
+ }
+ break;
+ case FSM_PRECHARGE:
+ if (info->online && info->present && info->allowed) {
+ if (vbatt > PRECHARGE_THRESHOLD) {
+ info->state = FSM_FASTCHARGE;
+ start_fastcharge(info);
+ }
+ } else {
+ info->state = FSM_DISCHARGE;
+ stop_charge(info, vbatt);
+ }
+ break;
+ case FSM_FASTCHARGE:
+ if (info->online && info->present && info->allowed) {
+ if (vbatt < PRECHARGE_THRESHOLD) {
+ info->state = FSM_PRECHARGE;
+ start_precharge(info);
+ }
+ } else {
+ info->state = FSM_DISCHARGE;
+ stop_charge(info, vbatt);
+ }
+ break;
+ case FSM_DISCHARGE:
+ if (info->online && info->present && info->allowed) {
+ if (vbatt < PRECHARGE_THRESHOLD) {
+ info->state = FSM_PRECHARGE;
+ start_precharge(info);
+ } else if (vbatt < DISCHARGE_THRESHOLD) {
+ info->state = FSM_FASTCHARGE;
+ start_fastcharge(info);
+ }
+ } else {
+ if (vbatt < POWEROFF_THRESHOLD)
+ power_off_notification(info);
+ else if (vbatt > CHARGE_THRESHOLD && info->online)
+ set_vbatt_threshold(info, CHARGE_THRESHOLD, 0);
+ }
+ break;
+ default:
+ dev_warn(info->dev, "FSM meets wrong state:%d\n",
+ info->state);
+ break;
+ }
+ dev_dbg(info->dev,
+ "Out FSM:%s, Charger:%s, Battery:%s, Allowed:%d\n",
+ &fsm_state[info->state][0],
+ (info->online) ? "online" : "N/A",
+ (info->present) ? "present" : "N/A", info->allowed);
+ mutex_unlock(&info->lock);
+
+ return 0;
+}
+
+static irqreturn_t pm860x_charger_handler(int irq, void *data)
+{
+ struct pm860x_charger_info *info = data;
+ int ret;
+
+ mutex_lock(&info->lock);
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ if (ret < 0) {
+ mutex_unlock(&info->lock);
+ goto out;
+ }
+ if (ret & STATUS2_CHG) {
+ info->online = 1;
+ info->allowed = 1;
+ } else {
+ info->online = 0;
+ info->allowed = 0;
+ }
+ mutex_unlock(&info->lock);
+ dev_dbg(info->dev, "%s, Charger:%s, Allowed:%d\n", __func__,
+ (info->online) ? "online" : "N/A", info->allowed);
+
+ set_charging_fsm(info);
+
+ power_supply_changed(info->usb);
+out:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_temp_handler(int irq, void *data)
+{
+ struct power_supply *psy;
+ struct pm860x_charger_info *info = data;
+ union power_supply_propval temp;
+ int value;
+ int ret;
+
+ psy = power_supply_get_by_name(pm860x_supplied_to[0]);
+ if (!psy)
+ return IRQ_HANDLED;
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &temp);
+ if (ret)
+ goto out;
+ value = temp.intval / 10;
+
+ mutex_lock(&info->lock);
+ /* Temperature < -10 C or >40 C, Will not allow charge */
+ if (value < -10 || value > 40)
+ info->allowed = 0;
+ else
+ info->allowed = 1;
+ dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed);
+ mutex_unlock(&info->lock);
+
+ set_charging_fsm(info);
+out:
+ power_supply_put(psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_exception_handler(int irq, void *data)
+{
+ struct pm860x_charger_info *info = data;
+
+ mutex_lock(&info->lock);
+ info->allowed = 0;
+ mutex_unlock(&info->lock);
+ dev_dbg(info->dev, "%s, irq: %d\n", __func__, irq);
+
+ set_charging_fsm(info);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_done_handler(int irq, void *data)
+{
+ struct pm860x_charger_info *info = data;
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+ int vbatt;
+
+ mutex_lock(&info->lock);
+ /* pre-charge done, will transimit to fast-charge stage */
+ if (info->state == FSM_PRECHARGE) {
+ info->allowed = 1;
+ goto out;
+ }
+ /*
+ * Fast charge done, delay to read
+ * the correct status of CHG_DET.
+ */
+ mdelay(5);
+ info->allowed = 0;
+ psy = power_supply_get_by_name(pm860x_supplied_to[0]);
+ if (!psy)
+ goto out;
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ &val);
+ if (ret)
+ goto out_psy_put;
+ vbatt = val.intval / 1000;
+ /*
+ * CHG_DONE interrupt is faster than CHG_DET interrupt when
+ * plug in/out usb, So we can not rely on info->online, we
+ * need check pm8607 status register to check usb is online
+ * or not, then we can decide it is real charge done
+ * automatically or it is triggered by usb plug out;
+ */
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ if (ret < 0)
+ goto out_psy_put;
+ if (vbatt > CHARGE_THRESHOLD && ret & STATUS2_CHG)
+ power_supply_set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL,
+ &val);
+
+out_psy_put:
+ power_supply_put(psy);
+out:
+ mutex_unlock(&info->lock);
+ dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed);
+ set_charging_fsm(info);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_vbattery_handler(int irq, void *data)
+{
+ struct pm860x_charger_info *info = data;
+
+ mutex_lock(&info->lock);
+
+ set_vbatt_threshold(info, 0, 0);
+
+ if (info->present && info->online)
+ info->allowed = 1;
+ else
+ info->allowed = 0;
+ mutex_unlock(&info->lock);
+ dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed);
+
+ set_charging_fsm(info);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t pm860x_vchg_handler(int irq, void *data)
+{
+ struct pm860x_charger_info *info = data;
+ int vchg = 0;
+
+ if (info->present)
+ goto out;
+
+ measure_vchg(info, &vchg);
+
+ mutex_lock(&info->lock);
+ if (!info->online) {
+ int status;
+ /* check if over-temp on pm8606 or not */
+ status = pm860x_reg_read(info->i2c_8606, PM8606_FLAGS);
+ if (status & OVER_TEMP_FLAG) {
+ /* clear over temp flag and set auto recover */
+ pm860x_set_bits(info->i2c_8606, PM8606_FLAGS,
+ OVER_TEMP_FLAG, OVER_TEMP_FLAG);
+ pm860x_set_bits(info->i2c_8606,
+ PM8606_VSYS,
+ OVTEMP_AUTORECOVER,
+ OVTEMP_AUTORECOVER);
+ dev_dbg(info->dev,
+ "%s, pm8606 over-temp occurred\n", __func__);
+ }
+ }
+
+ if (vchg > VCHG_NORMAL_CHECK) {
+ set_vchg_threshold(info, VCHG_OVP_LOW, 0);
+ info->allowed = 0;
+ dev_dbg(info->dev,
+ "%s,pm8607 over-vchg occurred,vchg = %dmv\n",
+ __func__, vchg);
+ } else if (vchg < VCHG_OVP_LOW) {
+ set_vchg_threshold(info, VCHG_NORMAL_LOW,
+ VCHG_NORMAL_HIGH);
+ info->allowed = 1;
+ dev_dbg(info->dev,
+ "%s,pm8607 over-vchg recover,vchg = %dmv\n",
+ __func__, vchg);
+ }
+ mutex_unlock(&info->lock);
+
+ dev_dbg(info->dev, "%s, Allowed: %d\n", __func__, info->allowed);
+ set_charging_fsm(info);
+out:
+ return IRQ_HANDLED;
+}
+
+static int pm860x_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm860x_charger_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (info->state == FSM_FASTCHARGE ||
+ info->state == FSM_PRECHARGE)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->online;
+ break;
+ default:
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static enum power_supply_property pm860x_usb_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int pm860x_init_charger(struct pm860x_charger_info *info)
+{
+ int ret;
+
+ ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&info->lock);
+ info->state = FSM_INIT;
+ if (ret & STATUS2_CHG) {
+ info->online = 1;
+ info->allowed = 1;
+ } else {
+ info->online = 0;
+ info->allowed = 0;
+ }
+ mutex_unlock(&info->lock);
+
+ set_charging_fsm(info);
+ return 0;
+}
+
+static struct pm860x_irq_desc {
+ const char *name;
+ irqreturn_t (*handler)(int irq, void *data);
+} pm860x_irq_descs[] = {
+ { "usb supply detect", pm860x_charger_handler },
+ { "charge done", pm860x_done_handler },
+ { "charge timeout", pm860x_exception_handler },
+ { "charge fault", pm860x_exception_handler },
+ { "temperature", pm860x_temp_handler },
+ { "vbatt", pm860x_vbattery_handler },
+ { "vchg", pm860x_vchg_handler },
+};
+
+static const struct power_supply_desc pm860x_charger_desc = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = pm860x_usb_props,
+ .num_properties = ARRAY_SIZE(pm860x_usb_props),
+ .get_property = pm860x_usb_get_prop,
+};
+
+static int pm860x_charger_probe(struct platform_device *pdev)
+{
+ struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct pm860x_charger_info *info;
+ int ret;
+ int count;
+ int i;
+ int j;
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ count = pdev->num_resources;
+ for (i = 0, j = 0; i < count; i++) {
+ info->irq[j] = platform_get_irq(pdev, i);
+ if (info->irq[j] < 0)
+ continue;
+ j++;
+ }
+ info->irq_nums = j;
+
+ info->chip = chip;
+ info->i2c =
+ (chip->id == CHIP_PM8607) ? chip->client : chip->companion;
+ info->i2c_8606 =
+ (chip->id == CHIP_PM8607) ? chip->companion : chip->client;
+ if (!info->i2c_8606) {
+ dev_err(&pdev->dev, "Missed I2C address of 88PM8606!\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ info->dev = &pdev->dev;
+
+ /* set init value for the case we are not using battery */
+ set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_OVP_LOW);
+
+ mutex_init(&info->lock);
+ platform_set_drvdata(pdev, info);
+
+ psy_cfg.drv_data = info;
+ psy_cfg.supplied_to = pm860x_supplied_to;
+ psy_cfg.num_supplicants = ARRAY_SIZE(pm860x_supplied_to);
+ info->usb = power_supply_register(&pdev->dev, &pm860x_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(info->usb)) {
+ ret = PTR_ERR(info->usb);
+ goto out;
+ }
+
+ pm860x_init_charger(info);
+
+ for (i = 0; i < ARRAY_SIZE(info->irq); i++) {
+ ret = request_threaded_irq(info->irq[i], NULL,
+ pm860x_irq_descs[i].handler,
+ IRQF_ONESHOT, pm860x_irq_descs[i].name, info);
+ if (ret < 0) {
+ dev_err(chip->dev, "Failed to request IRQ: #%d: %d\n",
+ info->irq[i], ret);
+ goto out_irq;
+ }
+ }
+ return 0;
+
+out_irq:
+ power_supply_unregister(info->usb);
+ while (--i >= 0)
+ free_irq(info->irq[i], info);
+out:
+ return ret;
+}
+
+static int pm860x_charger_remove(struct platform_device *pdev)
+{
+ struct pm860x_charger_info *info = platform_get_drvdata(pdev);
+ int i;
+
+ power_supply_unregister(info->usb);
+ for (i = 0; i < info->irq_nums; i++)
+ free_irq(info->irq[i], info);
+ return 0;
+}
+
+static struct platform_driver pm860x_charger_driver = {
+ .driver = {
+ .name = "88pm860x-charger",
+ },
+ .probe = pm860x_charger_probe,
+ .remove = pm860x_charger_remove,
+};
+module_platform_driver(pm860x_charger_driver);
+
+MODULE_DESCRIPTION("Marvell 88PM860x Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
new file mode 100644
index 0000000..76806a0
--- /dev/null
+++ b/drivers/power/supply/Kconfig
@@ -0,0 +1,514 @@
+menuconfig POWER_SUPPLY
+ bool "Power supply class support"
+ help
+ Say Y here to enable power supply class support. This allows
+ power supply (batteries, AC, USB) monitoring by userspace
+ via sysfs and uevent (if available) and/or APM kernel interface
+ (if selected below).
+
+if POWER_SUPPLY
+
+config POWER_SUPPLY_DEBUG
+ bool "Power supply debug"
+ help
+ Say Y here to enable debugging messages for power supply class
+ and drivers.
+
+config PDA_POWER
+ tristate "Generic PDA/phone power driver"
+ depends on !S390
+ help
+ Say Y here to enable generic power driver for PDAs and phones with
+ one or two external power supplies (AC/USB) connected to main and
+ backup batteries, and optional builtin charger.
+
+config APM_POWER
+ tristate "APM emulation for class batteries"
+ depends on APM_EMULATION
+ help
+ Say Y here to enable support APM status emulation using
+ battery class devices.
+
+config GENERIC_ADC_BATTERY
+ tristate "Generic battery support using IIO"
+ depends on IIO
+ help
+ Say Y here to enable support for the generic battery driver
+ which uses IIO framework to read adc.
+
+config MAX8925_POWER
+ tristate "MAX8925 battery charger support"
+ depends on MFD_MAX8925
+ help
+ Say Y here to enable support for the battery charger in the Maxim
+ MAX8925 PMIC.
+
+config WM831X_BACKUP
+ tristate "WM831X backup battery charger support"
+ depends on MFD_WM831X
+ help
+ Say Y here to enable support for the backup battery charger
+ in the Wolfson Microelectronics WM831x PMICs.
+
+config WM831X_POWER
+ tristate "WM831X PMU support"
+ depends on MFD_WM831X
+ help
+ Say Y here to enable support for the power management unit
+ provided by Wolfson Microelectronics WM831x PMICs.
+
+config WM8350_POWER
+ tristate "WM8350 PMU support"
+ depends on MFD_WM8350
+ help
+ Say Y here to enable support for the power management unit
+ provided by the Wolfson Microelectronics WM8350 PMIC.
+
+config TEST_POWER
+ tristate "Test power driver"
+ help
+ This driver is used for testing. It's safe to say M here.
+
+config BATTERY_88PM860X
+ tristate "Marvell 88PM860x battery driver"
+ depends on MFD_88PM860X
+ help
+ Say Y here to enable battery monitor for Marvell 88PM860x chip.
+
+config BATTERY_ACT8945A
+ tristate "Active-semi ACT8945A charger driver"
+ depends on MFD_ACT8945A || COMPILE_TEST
+ help
+ Say Y here to enable support for power supply provided by
+ Active-semi ActivePath ACT8945A charger.
+
+config BATTERY_DS2760
+ tristate "DS2760 battery driver (HP iPAQ & others)"
+ depends on W1 && W1_SLAVE_DS2760
+ help
+ Say Y here to enable support for batteries with ds2760 chip.
+
+config BATTERY_DS2780
+ tristate "DS2780 battery driver"
+ depends on HAS_IOMEM
+ select W1
+ select W1_SLAVE_DS2780
+ help
+ Say Y here to enable support for batteries with ds2780 chip.
+
+config BATTERY_DS2781
+ tristate "DS2781 battery driver"
+ depends on HAS_IOMEM
+ select W1
+ select W1_SLAVE_DS2781
+ help
+ If you enable this you will have the DS2781 battery driver support.
+
+ The battery monitor chip is used in many batteries/devices
+ as the one who is responsible for charging/discharging/monitoring
+ Li+ batteries.
+
+ If you are unsure, say N.
+
+config BATTERY_DS2782
+ tristate "DS2782/DS2786 standalone gas-gauge"
+ depends on I2C
+ help
+ Say Y here to enable support for the DS2782/DS2786 standalone battery
+ gas-gauge.
+
+config BATTERY_PMU
+ tristate "Apple PMU battery"
+ depends on PPC32 && ADB_PMU
+ help
+ Say Y here to expose battery information on Apple machines
+ through the generic battery class.
+
+config BATTERY_OLPC
+ tristate "One Laptop Per Child battery"
+ depends on X86_32 && OLPC
+ help
+ Say Y to enable support for the battery on the OLPC laptop.
+
+config BATTERY_TOSA
+ tristate "Sharp SL-6000 (tosa) battery"
+ depends on MACH_TOSA && MFD_TC6393XB && TOUCHSCREEN_WM97XX
+ help
+ Say Y to enable support for the battery on the Sharp Zaurus
+ SL-6000 (tosa) models.
+
+config BATTERY_COLLIE
+ tristate "Sharp SL-5500 (collie) battery"
+ depends on SA1100_COLLIE && MCP_UCB1200
+ help
+ Say Y to enable support for the battery on the Sharp Zaurus
+ SL-5500 (collie) models.
+
+config BATTERY_IPAQ_MICRO
+ tristate "iPAQ Atmel Micro ASIC battery driver"
+ depends on MFD_IPAQ_MICRO
+ help
+ Choose this option if you want to monitor battery status on
+ Compaq/HP iPAQ h3100 and h3600.
+
+config BATTERY_WM97XX
+ bool "WM97xx generic battery driver"
+ depends on TOUCHSCREEN_WM97XX=y
+ help
+ Say Y to enable support for battery measured by WM97xx aux port.
+
+config BATTERY_SBS
+ tristate "SBS Compliant gas gauge"
+ depends on I2C
+ help
+ Say Y to include support for SBS battery driver for SBS-compliant
+ gas gauges.
+
+config BATTERY_BQ27XXX
+ tristate "BQ27xxx battery driver"
+ help
+ Say Y here to enable support for batteries with BQ27xxx chips.
+
+config BATTERY_BQ27XXX_I2C
+ tristate "BQ27xxx I2C support"
+ depends on BATTERY_BQ27XXX
+ depends on I2C
+ default y
+ help
+ Say Y here to enable support for batteries with BQ27xxx chips
+ connected over an I2C bus.
+
+config BATTERY_DA9030
+ tristate "DA9030 battery driver"
+ depends on PMIC_DA903X
+ help
+ Say Y here to enable support for batteries charger integrated into
+ DA9030 PMIC.
+
+config BATTERY_DA9052
+ tristate "Dialog DA9052 Battery"
+ depends on PMIC_DA9052
+ help
+ Say Y here to enable support for batteries charger integrated into
+ DA9052 PMIC.
+
+config CHARGER_DA9150
+ tristate "Dialog Semiconductor DA9150 Charger support"
+ depends on MFD_DA9150
+ depends on DA9150_GPADC
+ depends on IIO
+ help
+ Say Y here to enable support for charger unit of the DA9150
+ Integrated Charger & Fuel-Gauge IC.
+
+ This driver can also be built as a module. If so, the module will be
+ called da9150-charger.
+
+config BATTERY_DA9150
+ tristate "Dialog Semiconductor DA9150 Fuel Gauge support"
+ depends on MFD_DA9150
+ help
+ Say Y here to enable support for the Fuel-Gauge unit of the DA9150
+ Integrated Charger & Fuel-Gauge IC
+
+ This driver can also be built as a module. If so, the module will be
+ called da9150-fg.
+
+config AXP288_CHARGER
+ tristate "X-Powers AXP288 Charger"
+ depends on MFD_AXP20X && EXTCON_AXP288
+ help
+ Say yes here to have support X-Power AXP288 power management IC (PMIC)
+ integrated charger.
+
+config AXP288_FUEL_GAUGE
+ tristate "X-Powers AXP288 Fuel Gauge"
+ depends on MFD_AXP20X && IIO
+ help
+ Say yes here to have support for X-Power power management IC (PMIC)
+ Fuel Gauge. The device provides battery statistics and status
+ monitoring as well as alerts for battery over/under voltage and
+ over/under temperature.
+
+config BATTERY_MAX17040
+ tristate "Maxim MAX17040 Fuel Gauge"
+ depends on I2C
+ help
+ MAX17040 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The MAX17040 is configured
+ to operate with a single lithium cell
+
+config BATTERY_MAX17042
+ tristate "Maxim MAX17042/17047/17050/8997/8966 Fuel Gauge"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ MAX17042 is fuel-gauge systems for lithium-ion (Li+) batteries
+ in handheld and portable equipment. The MAX17042 is configured
+ to operate with a single lithium cell. MAX8997 and MAX8966 are
+ multi-function devices that include fuel gauages that are compatible
+ with MAX17042. This driver also supports max17047/50 chips which are
+ improved version of max17042.
+
+config BATTERY_Z2
+ tristate "Z2 battery driver"
+ depends on I2C && MACH_ZIPIT2
+ help
+ Say Y to include support for the battery on the Zipit Z2.
+
+config BATTERY_S3C_ADC
+ tristate "Battery driver for Samsung ADC based monitoring"
+ depends on 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
+ help
+ Say Y here to enable charger for Marvell 88PM860x chip.
+
+config CHARGER_PCF50633
+ tristate "NXP PCF50633 MBC"
+ depends on MFD_PCF50633
+ help
+ Say Y to include support for NXP PCF50633 Main Battery Charger.
+
+config BATTERY_JZ4740
+ tristate "Ingenic JZ4740 battery"
+ depends on MACH_JZ4740
+ depends on MFD_JZ4740_ADC
+ help
+ Say Y to enable support for the battery on Ingenic JZ4740 based
+ boards.
+
+ This driver can be build as a module. If so, the module will be
+ called jz4740-battery.
+
+config BATTERY_INTEL_MID
+ tristate "Battery driver for Intel MID platforms"
+ depends on INTEL_SCU_IPC && SPI
+ help
+ Say Y here to enable the battery driver on Intel MID
+ platforms.
+
+config BATTERY_RX51
+ tristate "Nokia RX-51 (N900) battery driver"
+ depends on TWL4030_MADC
+ help
+ Say Y here to enable support for battery information on Nokia
+ RX-51, also known as N900 tablet.
+
+config CHARGER_ISP1704
+ tristate "ISP1704 USB Charger Detection"
+ depends on USB_PHY
+ depends on USB_GADGET || !USB_GADGET # if USB_GADGET=m, this can't be 'y'
+ help
+ Say Y to enable support for USB Charger Detection with
+ ISP1707/ISP1704 USB transceivers.
+
+config CHARGER_MAX8903
+ tristate "MAX8903 Battery DC-DC Charger for USB and Adapter Power"
+ help
+ Say Y to enable support for the MAX8903 DC-DC charger and sysfs.
+ The driver supports controlling charger-enable and current-limit
+ pins based on the status of charger connections with interrupt
+ handlers.
+
+config CHARGER_TWL4030
+ tristate "OMAP TWL4030 BCI charger driver"
+ depends on IIO && TWL4030_CORE
+ help
+ Say Y here to enable support for TWL4030 Battery Charge Interface.
+
+config CHARGER_LP8727
+ tristate "TI/National Semiconductor LP8727 charger driver"
+ depends on I2C
+ help
+ Say Y here to enable support for LP8727 Charger Driver.
+
+config CHARGER_LP8788
+ tristate "TI LP8788 charger driver"
+ depends on MFD_LP8788
+ depends on LP8788_ADC
+ depends on IIO
+ help
+ Say Y to enable support for the LP8788 linear charger.
+
+config CHARGER_GPIO
+ tristate "GPIO charger"
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y to include support for chargers which report their online status
+ through a GPIO pin.
+
+ This driver can be build as a module. If so, the module will be
+ called gpio-charger.
+
+config CHARGER_MANAGER
+ bool "Battery charger manager for multiple chargers"
+ depends on REGULATOR
+ select EXTCON
+ help
+ Say Y to enable charger-manager support, which allows multiple
+ chargers attached to a battery and multiple batteries attached to a
+ system. The charger-manager also can monitor charging status in
+ runtime and in suspend-to-RAM by waking up the system periodically
+ with help of suspend_again support.
+
+config CHARGER_MAX14577
+ tristate "Maxim MAX14577/77836 battery charger driver"
+ depends on MFD_MAX14577
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX14577/77836 MUICs.
+
+config CHARGER_MAX77693
+ tristate "Maxim MAX77693 battery charger driver"
+ depends on MFD_MAX77693
+ help
+ Say Y to enable support for the Maxim MAX77693 battery charger.
+
+config CHARGER_MAX8997
+ tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver"
+ depends on MFD_MAX8997 && REGULATOR_MAX8997
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8997/LP3974 PMICs.
+
+config CHARGER_MAX8998
+ tristate "Maxim MAX8998/LP3974 PMIC battery charger driver"
+ depends on MFD_MAX8998 && REGULATOR_MAX8998
+ help
+ Say Y to enable support for the battery charger control sysfs and
+ platform data of MAX8998/LP3974 PMICs.
+
+config CHARGER_QCOM_SMBB
+ tristate "Qualcomm Switch-Mode Battery Charger and Boost"
+ depends on MFD_SPMI_PMIC || COMPILE_TEST
+ depends on OF
+ depends on EXTCON
+ help
+ Say Y to include support for the Switch-Mode Battery Charger and
+ Boost (SMBB) hardware found in Qualcomm PM8941 PMICs. The charger
+ is an integrated, single-cell lithium-ion battery charger. DT
+ configuration is required for loading, see the devicetree
+ documentation for more detail. The base name for this driver is
+ 'pm8941_charger'.
+
+config CHARGER_BQ2415X
+ tristate "TI BQ2415x battery charger driver"
+ depends on I2C
+ help
+ Say Y to enable support for the TI BQ2415x battery charger
+ PMICs.
+
+ 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
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y to enable support for the TI BQ24190 battery charger.
+
+config CHARGER_BQ24257
+ tristate "TI BQ24250/24251/24257 battery charger driver"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ depends on REGMAP_I2C
+ help
+ Say Y to enable support for the TI BQ24250, BQ24251, and BQ24257 battery
+ chargers.
+
+config CHARGER_BQ24735
+ tristate "TI BQ24735 battery charger support"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ help
+ Say Y to enable support for the TI BQ24735 battery charger.
+
+config CHARGER_BQ25890
+ tristate "TI BQ25890 battery charger driver"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select REGMAP_I2C
+ help
+ Say Y to enable support for the TI BQ25890 battery charger.
+
+config CHARGER_SMB347
+ tristate "Summit Microelectronics SMB347 Battery Charger"
+ depends on I2C
+ select REGMAP_I2C
+ help
+ Say Y to include support for Summit Microelectronics SMB347
+ Battery Charger.
+
+config CHARGER_TPS65090
+ tristate "TPS65090 battery charger driver"
+ depends on MFD_TPS65090
+ help
+ Say Y here to enable support for battery charging with TPS65090
+ PMIC chips.
+
+config CHARGER_TPS65217
+ tristate "TPS65217 battery charger driver"
+ depends on MFD_TPS65217
+ help
+ Say Y here to enable support for battery charging with TPS65217
+ PMIC chips.
+
+config BATTERY_GAUGE_LTC2941
+ tristate "LTC2941/LTC2943 Battery Gauge Driver"
+ depends on I2C
+ help
+ Say Y here to include support for LTC2941 and LTC2943 Battery
+ Gauge IC. The driver reports the charge count continuously, and
+ measures the voltage and temperature every 10 seconds.
+
+config AB8500_BM
+ bool "AB8500 Battery Management Driver"
+ depends on AB8500_CORE && AB8500_GPADC
+ help
+ Say Y to include support for AB8500 battery management.
+
+config BATTERY_GOLDFISH
+ tristate "Goldfish battery driver"
+ depends on GOLDFISH || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Say Y to enable support for the battery and AC power in the
+ Goldfish emulator.
+
+config BATTERY_RT5033
+ tristate "RT5033 fuel gauge support"
+ depends on MFD_RT5033
+ help
+ This adds support for battery fuel gauge in Richtek RT5033 PMIC.
+ The fuelgauge calculates and determines the battery state of charge
+ according to battery open circuit voltage.
+
+config CHARGER_RT9455
+ tristate "Richtek RT9455 battery charger driver"
+ depends on I2C
+ depends on GPIOLIB || COMPILE_TEST
+ select REGMAP_I2C
+ help
+ Say Y to enable support for Richtek RT9455 battery charger.
+
+config AXP20X_POWER
+ tristate "AXP20x power supply driver"
+ depends on MFD_AXP20X
+ help
+ This driver provides support for the power supply features of
+ AXP20x PMIC.
+
+endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
new file mode 100644
index 0000000..36c599d
--- /dev/null
+++ b/drivers/power/supply/Makefile
@@ -0,0 +1,74 @@
+subdir-ccflags-$(CONFIG_POWER_SUPPLY_DEBUG) := -DDEBUG
+
+power_supply-y := power_supply_core.o
+power_supply-$(CONFIG_SYSFS) += power_supply_sysfs.o
+power_supply-$(CONFIG_LEDS_TRIGGERS) += power_supply_leds.o
+
+obj-$(CONFIG_POWER_SUPPLY) += power_supply.o
+obj-$(CONFIG_GENERIC_ADC_BATTERY) += generic-adc-battery.o
+
+obj-$(CONFIG_PDA_POWER) += pda_power.o
+obj-$(CONFIG_APM_POWER) += apm_power.o
+obj-$(CONFIG_AXP20X_POWER) += axp20x_usb_power.o
+obj-$(CONFIG_MAX8925_POWER) += max8925_power.o
+obj-$(CONFIG_WM831X_BACKUP) += wm831x_backup.o
+obj-$(CONFIG_WM831X_POWER) += wm831x_power.o
+obj-$(CONFIG_WM8350_POWER) += wm8350_power.o
+obj-$(CONFIG_TEST_POWER) += test_power.o
+
+obj-$(CONFIG_BATTERY_88PM860X) += 88pm860x_battery.o
+obj-$(CONFIG_BATTERY_ACT8945A) += act8945a_charger.o
+obj-$(CONFIG_BATTERY_DS2760) += ds2760_battery.o
+obj-$(CONFIG_BATTERY_DS2780) += ds2780_battery.o
+obj-$(CONFIG_BATTERY_DS2781) += ds2781_battery.o
+obj-$(CONFIG_BATTERY_DS2782) += ds2782_battery.o
+obj-$(CONFIG_BATTERY_GAUGE_LTC2941) += ltc2941-battery-gauge.o
+obj-$(CONFIG_BATTERY_GOLDFISH) += goldfish_battery.o
+obj-$(CONFIG_BATTERY_PMU) += pmu_battery.o
+obj-$(CONFIG_BATTERY_OLPC) += olpc_battery.o
+obj-$(CONFIG_BATTERY_TOSA) += tosa_battery.o
+obj-$(CONFIG_BATTERY_COLLIE) += collie_battery.o
+obj-$(CONFIG_BATTERY_IPAQ_MICRO) += ipaq_micro_battery.o
+obj-$(CONFIG_BATTERY_WM97XX) += wm97xx_battery.o
+obj-$(CONFIG_BATTERY_SBS) += sbs-battery.o
+obj-$(CONFIG_BATTERY_BQ27XXX) += bq27xxx_battery.o
+obj-$(CONFIG_BATTERY_BQ27XXX_I2C) += bq27xxx_battery_i2c.o
+obj-$(CONFIG_BATTERY_DA9030) += da9030_battery.o
+obj-$(CONFIG_BATTERY_DA9052) += da9052-battery.o
+obj-$(CONFIG_CHARGER_DA9150) += da9150-charger.o
+obj-$(CONFIG_BATTERY_DA9150) += da9150-fg.o
+obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o
+obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o
+obj-$(CONFIG_BATTERY_Z2) += z2_battery.o
+obj-$(CONFIG_BATTERY_RT5033) += rt5033_battery.o
+obj-$(CONFIG_CHARGER_RT9455) += rt9455_charger.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
+obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o
+obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o
+obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o
+obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o
+obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o
+obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o
+obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o
+obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o
+obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o
+obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o
+obj-$(CONFIG_CHARGER_MAX14577) += max14577_charger.o
+obj-$(CONFIG_CHARGER_MAX77693) += max77693_charger.o
+obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o
+obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o
+obj-$(CONFIG_CHARGER_QCOM_SMBB) += qcom_smbb.o
+obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o
+obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o
+obj-$(CONFIG_CHARGER_BQ24257) += bq24257_charger.o
+obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o
+obj-$(CONFIG_CHARGER_BQ25890) += bq25890_charger.o
+obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o
+obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o
+obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o
+obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
+obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o
diff --git a/drivers/power/supply/ab8500_bmdata.c b/drivers/power/supply/ab8500_bmdata.c
new file mode 100644
index 0000000..d298645
--- /dev/null
+++ b/drivers/power/supply/ab8500_bmdata.c
@@ -0,0 +1,605 @@
+#include <linux/export.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+
+/*
+ * These are the defined batteries that uses a NTC and ID resistor placed
+ * inside of the battery pack.
+ * Note that the res_to_temp table must be strictly sorted by falling resistance
+ * values to work.
+ */
+const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = {
+ {-5, 53407},
+ { 0, 48594},
+ { 5, 43804},
+ {10, 39188},
+ {15, 34870},
+ {20, 30933},
+ {25, 27422},
+ {30, 24347},
+ {35, 21694},
+ {40, 19431},
+ {45, 17517},
+ {50, 15908},
+ {55, 14561},
+ {60, 13437},
+ {65, 12500},
+};
+EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor);
+
+const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor);
+EXPORT_SYMBOL(ab8500_temp_tbl_a_size);
+
+const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = {
+ {-5, 200000},
+ { 0, 159024},
+ { 5, 151921},
+ {10, 144300},
+ {15, 136424},
+ {20, 128565},
+ {25, 120978},
+ {30, 113875},
+ {35, 107397},
+ {40, 101629},
+ {45, 96592},
+ {50, 92253},
+ {55, 88569},
+ {60, 85461},
+ {65, 82869},
+};
+EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor);
+
+const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor);
+EXPORT_SYMBOL(ab8500_temp_tbl_b_size);
+
+static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = {
+ {4171, 100},
+ {4114, 95},
+ {4009, 83},
+ {3947, 74},
+ {3907, 67},
+ {3863, 59},
+ {3830, 56},
+ {3813, 53},
+ {3791, 46},
+ {3771, 33},
+ {3754, 25},
+ {3735, 20},
+ {3717, 17},
+ {3681, 13},
+ {3664, 8},
+ {3651, 6},
+ {3635, 5},
+ {3560, 3},
+ {3408, 1},
+ {3247, 0},
+};
+
+static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = {
+ {4161, 100},
+ {4124, 98},
+ {4044, 90},
+ {4003, 85},
+ {3966, 80},
+ {3933, 75},
+ {3888, 67},
+ {3849, 60},
+ {3813, 55},
+ {3787, 47},
+ {3772, 30},
+ {3751, 25},
+ {3718, 20},
+ {3681, 16},
+ {3660, 14},
+ {3589, 10},
+ {3546, 7},
+ {3495, 4},
+ {3404, 2},
+ {3250, 0},
+};
+
+static const struct abx500_v_to_cap cap_tbl[] = {
+ {4186, 100},
+ {4163, 99},
+ {4114, 95},
+ {4068, 90},
+ {3990, 80},
+ {3926, 70},
+ {3898, 65},
+ {3866, 60},
+ {3833, 55},
+ {3812, 50},
+ {3787, 40},
+ {3768, 30},
+ {3747, 25},
+ {3730, 20},
+ {3705, 15},
+ {3699, 14},
+ {3684, 12},
+ {3672, 9},
+ {3657, 7},
+ {3638, 6},
+ {3556, 4},
+ {3424, 2},
+ {3317, 1},
+ {3094, 0},
+};
+
+/*
+ * Note that the res_to_temp table must be strictly sorted by falling
+ * resistance values to work.
+ */
+static const struct abx500_res_to_temp temp_tbl[] = {
+ {-5, 214834},
+ { 0, 162943},
+ { 5, 124820},
+ {10, 96520},
+ {15, 75306},
+ {20, 59254},
+ {25, 47000},
+ {30, 37566},
+ {35, 30245},
+ {40, 24520},
+ {45, 20010},
+ {50, 16432},
+ {55, 13576},
+ {60, 11280},
+ {65, 9425},
+};
+
+/*
+ * Note that the batres_vs_temp table must be strictly sorted by falling
+ * temperature values to work.
+ */
+static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = {
+ { 40, 120},
+ { 30, 135},
+ { 20, 165},
+ { 10, 230},
+ { 00, 325},
+ {-10, 445},
+ {-20, 595},
+};
+
+/*
+ * Note that the batres_vs_temp table must be strictly sorted by falling
+ * temperature values to work.
+ */
+static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = {
+ { 60, 300},
+ { 30, 300},
+ { 20, 300},
+ { 10, 300},
+ { 00, 300},
+ {-10, 300},
+ {-20, 300},
+};
+
+/* battery resistance table for LI ION 9100 battery */
+static const struct batres_vs_temp temp_to_batres_tbl_9100[] = {
+ { 60, 180},
+ { 30, 180},
+ { 20, 180},
+ { 10, 180},
+ { 00, 180},
+ {-10, 180},
+ {-20, 180},
+};
+
+static struct abx500_battery_type bat_type_thermistor[] = {
+ [BATTERY_UNKNOWN] = {
+ /* First element always represent the UNKNOWN battery */
+ .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
+ .resis_high = 0,
+ .resis_low = 0,
+ .battery_resistance = 300,
+ .charge_full_design = 612,
+ .nominal_voltage = 3700,
+ .termination_vol = 4050,
+ .termination_curr = 200,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 400,
+ .normal_vol_lvl = 4100,
+ .maint_a_cur_lvl = 400,
+ .maint_a_vol_lvl = 4050,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 400,
+ .maint_b_vol_lvl = 4000,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+ .r_to_t_tbl = temp_tbl,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+ .v_to_cap_tbl = cap_tbl,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+ {
+ .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+ .resis_high = 53407,
+ .resis_low = 12500,
+ .battery_resistance = 300,
+ .charge_full_design = 900,
+ .nominal_voltage = 3600,
+ .termination_vol = 4150,
+ .termination_curr = 80,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 700,
+ .normal_vol_lvl = 4200,
+ .maint_a_cur_lvl = 600,
+ .maint_a_vol_lvl = 4150,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 600,
+ .maint_b_vol_lvl = 4100,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor),
+ .r_to_t_tbl = ab8500_temp_tbl_a_thermistor,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_a_thermistor),
+ .v_to_cap_tbl = cap_tbl_a_thermistor,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+
+ },
+ {
+ .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+ .resis_high = 200000,
+ .resis_low = 82869,
+ .battery_resistance = 300,
+ .charge_full_design = 900,
+ .nominal_voltage = 3600,
+ .termination_vol = 4150,
+ .termination_curr = 80,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 700,
+ .normal_vol_lvl = 4200,
+ .maint_a_cur_lvl = 600,
+ .maint_a_vol_lvl = 4150,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 600,
+ .maint_b_vol_lvl = 4100,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor),
+ .r_to_t_tbl = ab8500_temp_tbl_b_thermistor,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_b_thermistor),
+ .v_to_cap_tbl = cap_tbl_b_thermistor,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+};
+
+static struct abx500_battery_type bat_type_ext_thermistor[] = {
+ [BATTERY_UNKNOWN] = {
+ /* First element always represent the UNKNOWN battery */
+ .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN,
+ .resis_high = 0,
+ .resis_low = 0,
+ .battery_resistance = 300,
+ .charge_full_design = 612,
+ .nominal_voltage = 3700,
+ .termination_vol = 4050,
+ .termination_curr = 200,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 400,
+ .normal_vol_lvl = 4100,
+ .maint_a_cur_lvl = 400,
+ .maint_a_vol_lvl = 4050,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 400,
+ .maint_b_vol_lvl = 4000,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+ .r_to_t_tbl = temp_tbl,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+ .v_to_cap_tbl = cap_tbl,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+/*
+ * These are the batteries that doesn't have an internal NTC resistor to measure
+ * its temperature. The temperature in this case is measure with a NTC placed
+ * near the battery but on the PCB.
+ */
+ {
+ .name = POWER_SUPPLY_TECHNOLOGY_LIPO,
+ .resis_high = 76000,
+ .resis_low = 53000,
+ .battery_resistance = 300,
+ .charge_full_design = 900,
+ .nominal_voltage = 3700,
+ .termination_vol = 4150,
+ .termination_curr = 100,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 700,
+ .normal_vol_lvl = 4200,
+ .maint_a_cur_lvl = 600,
+ .maint_a_vol_lvl = 4150,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 600,
+ .maint_b_vol_lvl = 4100,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+ .r_to_t_tbl = temp_tbl,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+ .v_to_cap_tbl = cap_tbl,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+ {
+ .name = POWER_SUPPLY_TECHNOLOGY_LION,
+ .resis_high = 30000,
+ .resis_low = 10000,
+ .battery_resistance = 300,
+ .charge_full_design = 950,
+ .nominal_voltage = 3700,
+ .termination_vol = 4150,
+ .termination_curr = 100,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 700,
+ .normal_vol_lvl = 4200,
+ .maint_a_cur_lvl = 600,
+ .maint_a_vol_lvl = 4150,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 600,
+ .maint_b_vol_lvl = 4100,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+ .r_to_t_tbl = temp_tbl,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+ .v_to_cap_tbl = cap_tbl,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+ {
+ .name = POWER_SUPPLY_TECHNOLOGY_LION,
+ .resis_high = 95000,
+ .resis_low = 76001,
+ .battery_resistance = 300,
+ .charge_full_design = 950,
+ .nominal_voltage = 3700,
+ .termination_vol = 4150,
+ .termination_curr = 100,
+ .recharge_cap = 95,
+ .normal_cur_lvl = 700,
+ .normal_vol_lvl = 4200,
+ .maint_a_cur_lvl = 600,
+ .maint_a_vol_lvl = 4150,
+ .maint_a_chg_timer_h = 60,
+ .maint_b_cur_lvl = 600,
+ .maint_b_vol_lvl = 4100,
+ .maint_b_chg_timer_h = 200,
+ .low_high_cur_lvl = 300,
+ .low_high_vol_lvl = 4000,
+ .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl),
+ .r_to_t_tbl = temp_tbl,
+ .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl),
+ .v_to_cap_tbl = cap_tbl,
+ .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor),
+ .batres_tbl = temp_to_batres_tbl_thermistor,
+ },
+};
+
+static const struct abx500_bm_capacity_levels cap_levels = {
+ .critical = 2,
+ .low = 10,
+ .normal = 70,
+ .high = 95,
+ .full = 100,
+};
+
+static const struct abx500_fg_parameters fg = {
+ .recovery_sleep_timer = 10,
+ .recovery_total_time = 100,
+ .init_timer = 1,
+ .init_discard_time = 5,
+ .init_total_time = 40,
+ .high_curr_time = 60,
+ .accu_charging = 30,
+ .accu_high_curr = 30,
+ .high_curr_threshold = 50,
+ .lowbat_threshold = 3100,
+ .battok_falling_th_sel0 = 2860,
+ .battok_raising_th_sel1 = 2860,
+ .maint_thres = 95,
+ .user_cap_limit = 15,
+ .pcut_enable = 1,
+ .pcut_max_time = 127,
+ .pcut_flag_time = 112,
+ .pcut_max_restart = 15,
+ .pcut_debounce_time = 2,
+};
+
+static const struct abx500_maxim_parameters ab8500_maxi_params = {
+ .ena_maxi = true,
+ .chg_curr = 910,
+ .wait_cycles = 10,
+ .charger_curr_step = 100,
+};
+
+static const struct abx500_maxim_parameters abx540_maxi_params = {
+ .ena_maxi = true,
+ .chg_curr = 3000,
+ .wait_cycles = 10,
+ .charger_curr_step = 200,
+};
+
+static const struct abx500_bm_charger_parameters chg = {
+ .usb_volt_max = 5500,
+ .usb_curr_max = 1500,
+ .ac_volt_max = 7500,
+ .ac_curr_max = 1500,
+};
+
+/*
+ * This array maps the raw hex value to charger output current used by the
+ * AB8500 values
+ */
+static int ab8500_charge_output_curr_map[] = {
+ 100, 200, 300, 400, 500, 600, 700, 800,
+ 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500,
+};
+
+static int ab8540_charge_output_curr_map[] = {
+ 0, 0, 0, 75, 100, 125, 150, 175,
+ 200, 225, 250, 275, 300, 325, 350, 375,
+ 400, 425, 450, 475, 500, 525, 550, 575,
+ 600, 625, 650, 675, 700, 725, 750, 775,
+ 800, 825, 850, 875, 900, 925, 950, 975,
+ 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175,
+ 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375,
+ 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000,
+};
+
+/*
+ * This array maps the raw hex value to charger input current used by the
+ * AB8500 values
+ */
+static int ab8500_charge_input_curr_map[] = {
+ 50, 98, 193, 290, 380, 450, 500, 600,
+ 700, 800, 900, 1000, 1100, 1300, 1400, 1500,
+};
+
+static int ab8540_charge_input_curr_map[] = {
+ 25, 50, 75, 100, 125, 150, 175, 200,
+ 225, 250, 275, 300, 325, 350, 375, 400,
+ 425, 450, 475, 500, 525, 550, 575, 600,
+ 625, 650, 675, 700, 725, 750, 775, 800,
+ 825, 850, 875, 900, 925, 950, 975, 1000,
+ 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200,
+ 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400,
+ 1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500,
+};
+
+struct abx500_bm_data ab8500_bm_data = {
+ .temp_under = 3,
+ .temp_low = 8,
+ .temp_high = 43,
+ .temp_over = 48,
+ .main_safety_tmr_h = 4,
+ .temp_interval_chg = 20,
+ .temp_interval_nochg = 120,
+ .usb_safety_tmr_h = 4,
+ .bkup_bat_v = BUP_VCH_SEL_2P6V,
+ .bkup_bat_i = BUP_ICH_SEL_150UA,
+ .no_maintenance = false,
+ .capacity_scaling = false,
+ .adc_therm = ABx500_ADC_THERM_BATCTRL,
+ .chg_unknown_bat = false,
+ .enable_overshoot = false,
+ .fg_res = 100,
+ .cap_levels = &cap_levels,
+ .bat_type = bat_type_thermistor,
+ .n_btypes = ARRAY_SIZE(bat_type_thermistor),
+ .batt_id = 0,
+ .interval_charging = 5,
+ .interval_not_charging = 120,
+ .temp_hysteresis = 3,
+ .gnd_lift_resistance = 34,
+ .chg_output_curr = ab8500_charge_output_curr_map,
+ .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map),
+ .maxi = &ab8500_maxi_params,
+ .chg_params = &chg,
+ .fg_params = &fg,
+ .chg_input_curr = ab8500_charge_input_curr_map,
+ .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map),
+};
+
+struct abx500_bm_data ab8540_bm_data = {
+ .temp_under = 3,
+ .temp_low = 8,
+ .temp_high = 43,
+ .temp_over = 48,
+ .main_safety_tmr_h = 4,
+ .temp_interval_chg = 20,
+ .temp_interval_nochg = 120,
+ .usb_safety_tmr_h = 4,
+ .bkup_bat_v = BUP_VCH_SEL_2P6V,
+ .bkup_bat_i = BUP_ICH_SEL_150UA,
+ .no_maintenance = false,
+ .capacity_scaling = false,
+ .adc_therm = ABx500_ADC_THERM_BATCTRL,
+ .chg_unknown_bat = false,
+ .enable_overshoot = false,
+ .fg_res = 100,
+ .cap_levels = &cap_levels,
+ .bat_type = bat_type_thermistor,
+ .n_btypes = ARRAY_SIZE(bat_type_thermistor),
+ .batt_id = 0,
+ .interval_charging = 5,
+ .interval_not_charging = 120,
+ .temp_hysteresis = 3,
+ .gnd_lift_resistance = 0,
+ .maxi = &abx540_maxi_params,
+ .chg_params = &chg,
+ .fg_params = &fg,
+ .chg_output_curr = ab8540_charge_output_curr_map,
+ .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map),
+ .chg_input_curr = ab8540_charge_input_curr_map,
+ .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map),
+};
+
+int ab8500_bm_of_probe(struct device *dev,
+ struct device_node *np,
+ struct abx500_bm_data *bm)
+{
+ const struct batres_vs_temp *tmp_batres_tbl;
+ struct device_node *battery_node;
+ const char *btech;
+ int i;
+
+ /* get phandle to 'battery-info' node */
+ battery_node = of_parse_phandle(np, "battery", 0);
+ if (!battery_node) {
+ dev_err(dev, "battery node or reference missing\n");
+ return -EINVAL;
+ }
+
+ btech = of_get_property(battery_node, "stericsson,battery-type", NULL);
+ if (!btech) {
+ dev_warn(dev, "missing property battery-name/type\n");
+ return -EINVAL;
+ }
+
+ if (strncmp(btech, "LION", 4) == 0) {
+ bm->no_maintenance = true;
+ bm->chg_unknown_bat = true;
+ bm->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600;
+ bm->bat_type[BATTERY_UNKNOWN].termination_vol = 4150;
+ bm->bat_type[BATTERY_UNKNOWN].recharge_cap = 95;
+ bm->bat_type[BATTERY_UNKNOWN].normal_cur_lvl = 520;
+ bm->bat_type[BATTERY_UNKNOWN].normal_vol_lvl = 4200;
+ }
+
+ if (of_property_read_bool(battery_node, "thermistor-on-batctrl")) {
+ if (strncmp(btech, "LION", 4) == 0)
+ tmp_batres_tbl = temp_to_batres_tbl_9100;
+ else
+ tmp_batres_tbl = temp_to_batres_tbl_thermistor;
+ } else {
+ bm->n_btypes = 4;
+ bm->bat_type = bat_type_ext_thermistor;
+ bm->adc_therm = ABx500_ADC_THERM_BATTEMP;
+ tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor;
+ }
+
+ /* select the battery resolution table */
+ for (i = 0; i < bm->n_btypes; ++i)
+ bm->bat_type[i].batres_tbl = tmp_batres_tbl;
+
+ of_node_put(battery_node);
+
+ return 0;
+}
diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c
new file mode 100644
index 0000000..6ffdc18
--- /dev/null
+++ b/drivers/power/supply/ab8500_btemp.c
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Battery temperature driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+
+#define VTVOUT_V 1800
+
+#define BTEMP_THERMAL_LOW_LIMIT -10
+#define BTEMP_THERMAL_MED_LIMIT 0
+#define BTEMP_THERMAL_HIGH_LIMIT_52 52
+#define BTEMP_THERMAL_HIGH_LIMIT_57 57
+#define BTEMP_THERMAL_HIGH_LIMIT_62 62
+
+#define BTEMP_BATCTRL_CURR_SRC_7UA 7
+#define BTEMP_BATCTRL_CURR_SRC_20UA 20
+
+#define BTEMP_BATCTRL_CURR_SRC_16UA 16
+#define BTEMP_BATCTRL_CURR_SRC_18UA 18
+
+#define BTEMP_BATCTRL_CURR_SRC_60UA 60
+#define BTEMP_BATCTRL_CURR_SRC_120UA 120
+
+/**
+ * struct ab8500_btemp_interrupts - ab8500 interrupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_btemp_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_btemp_events {
+ bool batt_rem;
+ bool btemp_high;
+ bool btemp_medhigh;
+ bool btemp_lowmed;
+ bool btemp_low;
+ bool ac_conn;
+ bool usb_conn;
+};
+
+struct ab8500_btemp_ranges {
+ int btemp_high_limit;
+ int btemp_med_limit;
+ int btemp_low_limit;
+};
+
+/**
+ * struct ab8500_btemp - ab8500 BTEMP device information
+ * @dev: Pointer to the structure device
+ * @node: List of AB8500 BTEMPs, hence prepared for reentrance
+ * @curr_source: What current source we use, in uA
+ * @bat_temp: Dispatched battery temperature in degree Celcius
+ * @prev_bat_temp Last measured battery temperature in degree Celcius
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @fg: Pointer to the struct fg
+ * @bm: Platform specific battery management information
+ * @btemp_psy: Structure for BTEMP specific battery properties
+ * @events: Structure for information about events triggered
+ * @btemp_ranges: Battery temperature range structure
+ * @btemp_wq: Work queue for measuring the temperature periodically
+ * @btemp_periodic_work: Work for measuring the temperature periodically
+ * @initialized: True if battery id read.
+ */
+struct ab8500_btemp {
+ struct device *dev;
+ struct list_head node;
+ int curr_source;
+ int bat_temp;
+ int prev_bat_temp;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct ab8500_fg *fg;
+ struct abx500_bm_data *bm;
+ struct power_supply *btemp_psy;
+ struct ab8500_btemp_events events;
+ struct ab8500_btemp_ranges btemp_ranges;
+ struct workqueue_struct *btemp_wq;
+ struct delayed_work btemp_periodic_work;
+ bool initialized;
+};
+
+/* BTEMP power supply properties */
+static enum power_supply_property ab8500_btemp_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static LIST_HEAD(ab8500_btemp_list);
+
+/**
+ * ab8500_btemp_get() - returns a reference to the primary AB8500 BTEMP
+ * (i.e. the first BTEMP in the instance list)
+ */
+struct ab8500_btemp *ab8500_btemp_get(void)
+{
+ struct ab8500_btemp *btemp;
+ btemp = list_first_entry(&ab8500_btemp_list, struct ab8500_btemp, node);
+
+ return btemp;
+}
+EXPORT_SYMBOL(ab8500_btemp_get);
+
+/**
+ * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance
+ * @di: pointer to the ab8500_btemp structure
+ * @v_batctrl: measured batctrl voltage
+ * @inst_curr: measured instant current
+ *
+ * This function returns the battery resistance that is
+ * derived from the BATCTRL voltage.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di,
+ int v_batctrl, int inst_curr)
+{
+ int rbs;
+
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ /*
+ * For ABB cut1.0 and 1.1 BAT_CTRL is internally
+ * connected to 1.8V through a 450k resistor
+ */
+ return (450000 * (v_batctrl)) / (1800 - v_batctrl);
+ }
+
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) {
+ /*
+ * If the battery has internal NTC, we use the current
+ * source to calculate the resistance.
+ */
+ rbs = (v_batctrl * 1000
+ - di->bm->gnd_lift_resistance * inst_curr)
+ / di->curr_source;
+ } else {
+ /*
+ * BAT_CTRL is internally
+ * connected to 1.8V through a 80k resistor
+ */
+ rbs = (80000 * (v_batctrl)) / (1800 - v_batctrl);
+ }
+
+ return rbs;
+}
+
+/**
+ * ab8500_btemp_read_batctrl_voltage() - measure batctrl voltage
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function returns the voltage on BATCTRL. Returns value in mV.
+ */
+static int ab8500_btemp_read_batctrl_voltage(struct ab8500_btemp *di)
+{
+ int vbtemp;
+ static int prev;
+
+ vbtemp = ab8500_gpadc_convert(di->gpadc, BAT_CTRL);
+ if (vbtemp < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed, using previous value",
+ __func__);
+ return prev;
+ }
+ prev = vbtemp;
+ return vbtemp;
+}
+
+/**
+ * ab8500_btemp_curr_source_enable() - enable/disable batctrl current source
+ * @di: pointer to the ab8500_btemp structure
+ * @enable: enable or disable the current source
+ *
+ * Enable or disable the current sources for the BatCtrl AD channel
+ */
+static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di,
+ bool enable)
+{
+ int curr;
+ int ret = 0;
+
+ /*
+ * BATCTRL current sources are included on AB8500 cut2.0
+ * and future versions
+ */
+ if (is_ab8500_1p1_or_earlier(di->parent))
+ return 0;
+
+ /* Only do this for batteries with internal NTC */
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) {
+
+ if (is_ab8540(di->parent)) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA)
+ curr = BAT_CTRL_60U_ENA;
+ else
+ curr = BAT_CTRL_120U_ENA;
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA)
+ curr = BAT_CTRL_16U_ENA;
+ else
+ curr = BAT_CTRL_18U_ENA;
+ } else {
+ if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_7UA)
+ curr = BAT_CTRL_7U_ENA;
+ else
+ curr = BAT_CTRL_20U_ENA;
+ }
+
+ dev_dbg(di->dev, "Set BATCTRL %duA\n", di->curr_source);
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed setting cmp_force\n",
+ __func__);
+ return ret;
+ }
+
+ /*
+ * We have to wait one 32kHz cycle before enabling
+ * the current source, since ForceBatCtrlCmpHigh needs
+ * to be written in a separate cycle
+ */
+ udelay(32);
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH | curr);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling current source\n",
+ __func__);
+ goto disable_curr_source;
+ }
+ } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) {
+ dev_dbg(di->dev, "Disable BATCTRL curr source\n");
+
+ if (is_ab8540(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
+ ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(
+ di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ goto disable_curr_source;
+ }
+
+ /* Enable Pull-Up and comparator */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling PU and comp\n",
+ __func__);
+ goto enable_pu_comp;
+ }
+
+ /*
+ * We have to wait one 32kHz cycle before disabling
+ * ForceBatCtrlCmpHigh since this needs to be written
+ * in a separate cycle
+ */
+ udelay(32);
+
+ /* Disable 'force comparator' */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling force comp\n",
+ __func__);
+ goto disable_force_comp;
+ }
+ }
+ return ret;
+
+ /*
+ * We have to try unsetting FORCE_BAT_CTRL_CMP_HIGH one more time
+ * if we got an error above
+ */
+disable_curr_source:
+ if (is_ab8540(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA,
+ ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA));
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA,
+ ~(BAT_CTRL_16U_ENA | BAT_CTRL_18U_ENA));
+ } else {
+ /* Write 0 to the curr bits */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA,
+ ~(BAT_CTRL_7U_ENA | BAT_CTRL_20U_ENA));
+ }
+
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling current source\n",
+ __func__);
+ return ret;
+ }
+enable_pu_comp:
+ /* Enable Pull-Up and comparator */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA,
+ BAT_CTRL_PULL_UP_ENA | BAT_CTRL_CMP_ENA);
+ if (ret) {
+ dev_err(di->dev, "%s failed enabling PU and comp\n",
+ __func__);
+ return ret;
+ }
+
+disable_force_comp:
+ /*
+ * We have to wait one 32kHz cycle before disabling
+ * ForceBatCtrlCmpHigh since this needs to be written
+ * in a separate cycle
+ */
+ udelay(32);
+
+ /* Disable 'force comparator' */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE,
+ FORCE_BAT_CTRL_CMP_HIGH, ~FORCE_BAT_CTRL_CMP_HIGH);
+ if (ret) {
+ dev_err(di->dev, "%s failed disabling force comp\n",
+ __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_btemp_get_batctrl_res() - get battery resistance
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function returns the battery pack identification resistance.
+ * Returns value in Ohms.
+ */
+static int ab8500_btemp_get_batctrl_res(struct ab8500_btemp *di)
+{
+ int ret;
+ int batctrl = 0;
+ int res;
+ int inst_curr;
+ int i;
+
+ /*
+ * BATCTRL current sources are included on AB8500 cut2.0
+ * and future versions
+ */
+ ret = ab8500_btemp_curr_source_enable(di, true);
+ if (ret) {
+ dev_err(di->dev, "%s curr source enabled failed\n", __func__);
+ return ret;
+ }
+
+ if (!di->fg)
+ di->fg = ab8500_fg_get();
+ if (!di->fg) {
+ dev_err(di->dev, "No fg found\n");
+ return -EINVAL;
+ }
+
+ ret = ab8500_fg_inst_curr_start(di->fg);
+
+ if (ret) {
+ dev_err(di->dev, "Failed to start current measurement\n");
+ return ret;
+ }
+
+ do {
+ msleep(20);
+ } while (!ab8500_fg_inst_curr_started(di->fg));
+
+ i = 0;
+
+ do {
+ batctrl += ab8500_btemp_read_batctrl_voltage(di);
+ i++;
+ msleep(20);
+ } while (!ab8500_fg_inst_curr_done(di->fg));
+ batctrl /= i;
+
+ ret = ab8500_fg_inst_curr_finalize(di->fg, &inst_curr);
+ if (ret) {
+ dev_err(di->dev, "Failed to finalize current measurement\n");
+ return ret;
+ }
+
+ res = ab8500_btemp_batctrl_volt_to_res(di, batctrl, inst_curr);
+
+ ret = ab8500_btemp_curr_source_enable(di, false);
+ if (ret) {
+ dev_err(di->dev, "%s curr source disable failed\n", __func__);
+ return ret;
+ }
+
+ dev_dbg(di->dev, "%s batctrl: %d res: %d inst_curr: %d samples: %d\n",
+ __func__, batctrl, res, inst_curr, i);
+
+ return res;
+}
+
+/**
+ * ab8500_btemp_res_to_temp() - resistance to temperature
+ * @di: pointer to the ab8500_btemp structure
+ * @tbl: pointer to the resiatance to temperature table
+ * @tbl_size: size of the resistance to temperature table
+ * @res: resistance to calculate the temperature from
+ *
+ * This function returns the battery temperature in degrees Celcius
+ * based on the NTC resistance.
+ */
+static int ab8500_btemp_res_to_temp(struct ab8500_btemp *di,
+ const struct abx500_res_to_temp *tbl, int tbl_size, int res)
+{
+ int i, temp;
+ /*
+ * Calculate the formula for the straight line
+ * Simple interpolation if we are within
+ * the resistance table limits, extrapolate
+ * if resistance is outside the limits.
+ */
+ if (res > tbl[0].resist)
+ i = 0;
+ else if (res <= tbl[tbl_size - 1].resist)
+ i = tbl_size - 2;
+ else {
+ i = 0;
+ while (!(res <= tbl[i].resist &&
+ res > tbl[i + 1].resist))
+ i++;
+ }
+
+ temp = tbl[i].temp + ((tbl[i + 1].temp - tbl[i].temp) *
+ (res - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist);
+ return temp;
+}
+
+/**
+ * ab8500_btemp_measure_temp() - measure battery temperature
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature (on success) else the previous temperature
+ */
+static int ab8500_btemp_measure_temp(struct ab8500_btemp *di)
+{
+ int temp;
+ static int prev;
+ int rbat, rntc, vntc;
+ u8 id;
+
+ id = di->bm->batt_id;
+
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ id != BATTERY_UNKNOWN) {
+
+ rbat = ab8500_btemp_get_batctrl_res(di);
+ if (rbat < 0) {
+ dev_err(di->dev, "%s get batctrl res failed\n",
+ __func__);
+ /*
+ * Return out-of-range temperature so that
+ * charging is stopped
+ */
+ return BTEMP_THERMAL_LOW_LIMIT;
+ }
+
+ temp = ab8500_btemp_res_to_temp(di,
+ di->bm->bat_type[id].r_to_t_tbl,
+ di->bm->bat_type[id].n_temp_tbl_elements, rbat);
+ } else {
+ vntc = ab8500_gpadc_convert(di->gpadc, BTEMP_BALL);
+ if (vntc < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed,"
+ " using previous value\n", __func__);
+ return prev;
+ }
+ /*
+ * The PCB NTC is sourced from VTVOUT via a 230kOhm
+ * resistor.
+ */
+ rntc = 230000 * vntc / (VTVOUT_V - vntc);
+
+ temp = ab8500_btemp_res_to_temp(di,
+ di->bm->bat_type[id].r_to_t_tbl,
+ di->bm->bat_type[id].n_temp_tbl_elements, rntc);
+ prev = temp;
+ }
+ dev_dbg(di->dev, "Battery temperature is %d\n", temp);
+ return temp;
+}
+
+/**
+ * ab8500_btemp_id() - Identify the connected battery
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * This function will try to identify the battery by reading the ID
+ * resistor. Some brands use a combined ID resistor with a NTC resistor to
+ * both be able to identify and to read the temperature of it.
+ */
+static int ab8500_btemp_id(struct ab8500_btemp *di)
+{
+ int res;
+ u8 i;
+ if (is_ab8540(di->parent))
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
+ else if (is_ab9540(di->parent) || is_ab8505(di->parent))
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ else
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA;
+
+ di->bm->batt_id = BATTERY_UNKNOWN;
+
+ res = ab8500_btemp_get_batctrl_res(di);
+ if (res < 0) {
+ dev_err(di->dev, "%s get batctrl res failed\n", __func__);
+ return -ENXIO;
+ }
+
+ /* BATTERY_UNKNOWN is defined on position 0, skip it! */
+ for (i = BATTERY_UNKNOWN + 1; i < di->bm->n_btypes; i++) {
+ if ((res <= di->bm->bat_type[i].resis_high) &&
+ (res >= di->bm->bat_type[i].resis_low)) {
+ dev_dbg(di->dev, "Battery detected on %s"
+ " low %d < res %d < high: %d"
+ " index: %d\n",
+ di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL ?
+ "BATCTRL" : "BATTEMP",
+ di->bm->bat_type[i].resis_low, res,
+ di->bm->bat_type[i].resis_high, i);
+
+ di->bm->batt_id = i;
+ break;
+ }
+ }
+
+ if (di->bm->batt_id == BATTERY_UNKNOWN) {
+ dev_warn(di->dev, "Battery identified as unknown"
+ ", resistance %d Ohm\n", res);
+ return -ENXIO;
+ }
+
+ /*
+ * We only have to change current source if the
+ * detected type is Type 1.
+ */
+ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL &&
+ di->bm->batt_id == 1) {
+ if (is_ab8540(di->parent)) {
+ dev_dbg(di->dev,
+ "Set BATCTRL current source to 60uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA;
+ } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) {
+ dev_dbg(di->dev,
+ "Set BATCTRL current source to 16uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA;
+ } else {
+ dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n");
+ di->curr_source = BTEMP_BATCTRL_CURR_SRC_20UA;
+ }
+ }
+
+ return di->bm->batt_id;
+}
+
+/**
+ * ab8500_btemp_periodic_work() - Measuring the temperature periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work function for measuring the temperature periodically
+ */
+static void ab8500_btemp_periodic_work(struct work_struct *work)
+{
+ int interval;
+ int bat_temp;
+ struct ab8500_btemp *di = container_of(work,
+ struct ab8500_btemp, btemp_periodic_work.work);
+
+ if (!di->initialized) {
+ /* Identify the battery */
+ if (ab8500_btemp_id(di) < 0)
+ dev_warn(di->dev, "failed to identify the battery\n");
+ }
+
+ bat_temp = ab8500_btemp_measure_temp(di);
+ /*
+ * Filter battery temperature.
+ * Allow direct updates on temperature only if two samples result in
+ * same temperature. Else only allow 1 degree change from previous
+ * reported value in the direction of the new measurement.
+ */
+ if ((bat_temp == di->prev_bat_temp) || !di->initialized) {
+ if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) {
+ di->initialized = true;
+ di->bat_temp = bat_temp;
+ power_supply_changed(di->btemp_psy);
+ }
+ } else if (bat_temp < di->prev_bat_temp) {
+ di->bat_temp--;
+ power_supply_changed(di->btemp_psy);
+ } else if (bat_temp > di->prev_bat_temp) {
+ di->bat_temp++;
+ power_supply_changed(di->btemp_psy);
+ }
+ di->prev_bat_temp = bat_temp;
+
+ if (di->events.ac_conn || di->events.usb_conn)
+ interval = di->bm->temp_interval_chg;
+ else
+ interval = di->bm->temp_interval_nochg;
+
+ /* Schedule a new measurement */
+ queue_delayed_work(di->btemp_wq,
+ &di->btemp_periodic_work,
+ round_jiffies(interval * HZ));
+}
+
+/**
+ * ab8500_btemp_batctrlindb_handler() - battery removal detected
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_batctrlindb_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+ dev_err(di->dev, "Battery removal detected!\n");
+
+ di->events.batt_rem = true;
+ power_supply_changed(di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_templow_handler() - battery temp lower than 10 degrees
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_templow_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ if (is_ab8500_3p3_or_earlier(di->parent)) {
+ dev_dbg(di->dev, "Ignore false btemp low irq"
+ " for ABB cut 1.0, 1.1, 2.0 and 3.3\n");
+ } else {
+ dev_crit(di->dev, "Battery temperature lower than -10deg c\n");
+
+ di->events.btemp_low = true;
+ di->events.btemp_high = false;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_lowmed = false;
+ power_supply_changed(di->btemp_psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_temphigh_handler() - battery temp higher than max temp
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_temphigh_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_crit(di->dev, "Battery temperature is higher than MAX temp\n");
+
+ di->events.btemp_high = true;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_lowmed = false;
+ di->events.btemp_low = false;
+ power_supply_changed(di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_lowmed_handler() - battery temp between low and medium
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_lowmed_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_dbg(di->dev, "Battery temperature is between low and medium\n");
+
+ di->events.btemp_lowmed = true;
+ di->events.btemp_medhigh = false;
+ di->events.btemp_high = false;
+ di->events.btemp_low = false;
+ power_supply_changed(di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_medhigh_handler() - battery temp between medium and high
+ * @irq: interrupt number
+ * @_di: void pointer that has to address of ab8500_btemp
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_btemp_medhigh_handler(int irq, void *_di)
+{
+ struct ab8500_btemp *di = _di;
+
+ dev_dbg(di->dev, "Battery temperature is between medium and high\n");
+
+ di->events.btemp_medhigh = true;
+ di->events.btemp_lowmed = false;
+ di->events.btemp_high = false;
+ di->events.btemp_low = false;
+ power_supply_changed(di->btemp_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_btemp_periodic() - Periodic temperature measurements
+ * @di: pointer to the ab8500_btemp structure
+ * @enable: enable or disable periodic temperature measurements
+ *
+ * Starts of stops periodic temperature measurements. Periodic measurements
+ * should only be done when a charger is connected.
+ */
+static void ab8500_btemp_periodic(struct ab8500_btemp *di,
+ bool enable)
+{
+ dev_dbg(di->dev, "Enable periodic temperature measurements: %d\n",
+ enable);
+ /*
+ * Make sure a new measurement is done directly by cancelling
+ * any pending work
+ */
+ cancel_delayed_work_sync(&di->btemp_periodic_work);
+
+ if (enable)
+ queue_delayed_work(di->btemp_wq, &di->btemp_periodic_work, 0);
+}
+
+/**
+ * ab8500_btemp_get_temp() - get battery temperature
+ * @di: pointer to the ab8500_btemp structure
+ *
+ * Returns battery temperature
+ */
+int ab8500_btemp_get_temp(struct ab8500_btemp *di)
+{
+ int temp = 0;
+
+ /*
+ * The BTEMP events are not reliabe on AB8500 cut3.3
+ * and prior versions
+ */
+ if (is_ab8500_3p3_or_earlier(di->parent)) {
+ temp = di->bat_temp * 10;
+ } else {
+ if (di->events.btemp_low) {
+ if (temp > di->btemp_ranges.btemp_low_limit)
+ temp = di->btemp_ranges.btemp_low_limit * 10;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_high) {
+ if (temp < di->btemp_ranges.btemp_high_limit)
+ temp = di->btemp_ranges.btemp_high_limit * 10;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_lowmed) {
+ if (temp > di->btemp_ranges.btemp_med_limit)
+ temp = di->btemp_ranges.btemp_med_limit * 10;
+ else
+ temp = di->bat_temp * 10;
+ } else if (di->events.btemp_medhigh) {
+ if (temp < di->btemp_ranges.btemp_med_limit)
+ temp = di->btemp_ranges.btemp_med_limit * 10;
+ else
+ temp = di->bat_temp * 10;
+ } else
+ temp = di->bat_temp * 10;
+ }
+ return temp;
+}
+EXPORT_SYMBOL(ab8500_btemp_get_temp);
+
+/**
+ * ab8500_btemp_get_batctrl_temp() - get the temperature
+ * @btemp: pointer to the btemp structure
+ *
+ * Returns the batctrl temperature in millidegrees
+ */
+int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp)
+{
+ return btemp->bat_temp * 1000;
+}
+EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp);
+
+/**
+ * ab8500_btemp_get_property() - get the btemp properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the btemp
+ * properties by reading the sysfs files.
+ * online: presence of the battery
+ * present: presence of the battery
+ * technology: battery technology
+ * temp: battery temperature
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_btemp_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_btemp *di = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (di->events.batt_rem)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = di->bm->bat_type[di->bm->batt_id].name;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = ab8500_btemp_get_temp(di);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab8500_btemp_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext = dev_get_drvdata(dev);
+ const char **supplicants = (const char **)ext->supplied_to;
+ struct ab8500_btemp *di;
+ union power_supply_propval ret;
+ int j;
+
+ psy = (struct power_supply *)data;
+ di = power_supply_get_drvdata(psy);
+
+ /*
+ * For all psy where the name of your driver
+ * appears in any supplied_to
+ */
+ j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ if (j < 0)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->desc->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->desc->properties[j];
+
+ if (power_supply_get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC disconnected */
+ if (!ret.intval && di->events.ac_conn) {
+ di->events.ac_conn = false;
+ }
+ /* AC connected */
+ else if (ret.intval && !di->events.ac_conn) {
+ di->events.ac_conn = true;
+ if (!di->events.usb_conn)
+ ab8500_btemp_periodic(di, true);
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB disconnected */
+ if (!ret.intval && di->events.usb_conn) {
+ di->events.usb_conn = false;
+ }
+ /* USB connected */
+ else if (ret.intval && !di->events.usb_conn) {
+ di->events.usb_conn = true;
+ if (!di->events.ac_conn)
+ ab8500_btemp_periodic(di, true);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_btemp_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is pointing to the function pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in the external power
+ * supply to the btemp.
+ */
+static void ab8500_btemp_external_power_changed(struct power_supply *psy)
+{
+ struct ab8500_btemp *di = power_supply_get_drvdata(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ di->btemp_psy, ab8500_btemp_get_ext_psy_data);
+}
+
+/* ab8500 btemp driver interrupts and their respective isr */
+static struct ab8500_btemp_interrupts ab8500_btemp_irq[] = {
+ {"BAT_CTRL_INDB", ab8500_btemp_batctrlindb_handler},
+ {"BTEMP_LOW", ab8500_btemp_templow_handler},
+ {"BTEMP_HIGH", ab8500_btemp_temphigh_handler},
+ {"BTEMP_LOW_MEDIUM", ab8500_btemp_lowmed_handler},
+ {"BTEMP_MEDIUM_HIGH", ab8500_btemp_medhigh_handler},
+};
+
+#if defined(CONFIG_PM)
+static int ab8500_btemp_resume(struct platform_device *pdev)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+ ab8500_btemp_periodic(di, true);
+
+ return 0;
+}
+
+static int ab8500_btemp_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+
+ ab8500_btemp_periodic(di, false);
+
+ return 0;
+}
+#else
+#define ab8500_btemp_suspend NULL
+#define ab8500_btemp_resume NULL
+#endif
+
+static int ab8500_btemp_remove(struct platform_device *pdev)
+{
+ struct ab8500_btemp *di = platform_get_drvdata(pdev);
+ int i, irq;
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ free_irq(irq, di);
+ }
+
+ /* Delete the work queue */
+ destroy_workqueue(di->btemp_wq);
+
+ flush_scheduled_work();
+ power_supply_unregister(di->btemp_psy);
+
+ return 0;
+}
+
+static char *supply_interface[] = {
+ "ab8500_chargalg",
+ "ab8500_fg",
+};
+
+static const struct power_supply_desc ab8500_btemp_desc = {
+ .name = "ab8500_btemp",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = ab8500_btemp_props,
+ .num_properties = ARRAY_SIZE(ab8500_btemp_props),
+ .get_property = ab8500_btemp_get_property,
+ .external_power_changed = ab8500_btemp_external_power_changed,
+};
+
+static int ab8500_btemp_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct ab8500_btemp *di;
+ int irq, i, ret = 0;
+ u8 val;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "%s no mem for ab8500_btemp\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
+ }
+ }
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ di->initialized = false;
+
+ psy_cfg.supplied_to = supply_interface;
+ psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
+ psy_cfg.drv_data = di;
+
+ /* Create a work queue for the btemp */
+ di->btemp_wq =
+ alloc_workqueue("ab8500_btemp_wq", WQ_MEM_RECLAIM, 0);
+ if (di->btemp_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ return -ENOMEM;
+ }
+
+ /* Init work for measuring temperature periodically */
+ INIT_DEFERRABLE_WORK(&di->btemp_periodic_work,
+ ab8500_btemp_periodic_work);
+
+ /* Set BTEMP thermal limits. Low and Med are fixed */
+ di->btemp_ranges.btemp_low_limit = BTEMP_THERMAL_LOW_LIMIT;
+ di->btemp_ranges.btemp_med_limit = BTEMP_THERMAL_MED_LIMIT;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_BTEMP_HIGH_TH, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ goto free_btemp_wq;
+ }
+ switch (val) {
+ case BTEMP_HIGH_TH_57_0:
+ case BTEMP_HIGH_TH_57_1:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_57;
+ break;
+ case BTEMP_HIGH_TH_52:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_52;
+ break;
+ case BTEMP_HIGH_TH_62:
+ di->btemp_ranges.btemp_high_limit =
+ BTEMP_THERMAL_HIGH_LIMIT_62;
+ break;
+ }
+
+ /* Register BTEMP power supply class */
+ di->btemp_psy = power_supply_register(di->dev, &ab8500_btemp_desc,
+ &psy_cfg);
+ if (IS_ERR(di->btemp_psy)) {
+ dev_err(di->dev, "failed to register BTEMP psy\n");
+ ret = PTR_ERR(di->btemp_psy);
+ goto free_btemp_wq;
+ }
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_btemp_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_btemp_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_btemp_irq[i].name, di);
+
+ if (ret) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_btemp_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_btemp_irq[i].name, irq, ret);
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ /* Kick off periodic temperature measurements */
+ ab8500_btemp_periodic(di, true);
+ list_add_tail(&di->node, &ab8500_btemp_list);
+
+ return ret;
+
+free_irq:
+ power_supply_unregister(di->btemp_psy);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab8500_btemp_irq[i].name);
+ free_irq(irq, di);
+ }
+free_btemp_wq:
+ destroy_workqueue(di->btemp_wq);
+ return ret;
+}
+
+static const struct of_device_id ab8500_btemp_match[] = {
+ { .compatible = "stericsson,ab8500-btemp", },
+ { },
+};
+
+static struct platform_driver ab8500_btemp_driver = {
+ .probe = ab8500_btemp_probe,
+ .remove = ab8500_btemp_remove,
+ .suspend = ab8500_btemp_suspend,
+ .resume = ab8500_btemp_resume,
+ .driver = {
+ .name = "ab8500-btemp",
+ .of_match_table = ab8500_btemp_match,
+ },
+};
+
+static int __init ab8500_btemp_init(void)
+{
+ return platform_driver_register(&ab8500_btemp_driver);
+}
+
+static void __exit ab8500_btemp_exit(void)
+{
+ platform_driver_unregister(&ab8500_btemp_driver);
+}
+
+device_initcall(ab8500_btemp_init);
+module_exit(ab8500_btemp_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-btemp");
+MODULE_DESCRIPTION("AB8500 battery temperature driver");
diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c
new file mode 100644
index 0000000..5cee9aa
--- /dev/null
+++ b/drivers/power/supply/ab8500_charger.c
@@ -0,0 +1,3765 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * Charger driver for AB8500
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/notifier.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/usb/otg.h>
+#include <linux/mutex.h>
+
+/* Charger constants */
+#define NO_PW_CONN 0
+#define AC_PW_CONN 1
+#define USB_PW_CONN 2
+
+#define MAIN_WDOG_ENA 0x01
+#define MAIN_WDOG_KICK 0x02
+#define MAIN_WDOG_DIS 0x00
+#define CHARG_WD_KICK 0x01
+#define MAIN_CH_ENA 0x01
+#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02
+#define USB_CH_ENA 0x01
+#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02
+#define MAIN_CH_DET 0x01
+#define MAIN_CH_CV_ON 0x04
+#define USB_CH_CV_ON 0x08
+#define VBUS_DET_DBNC100 0x02
+#define VBUS_DET_DBNC1 0x01
+#define OTP_ENABLE_WD 0x01
+#define DROP_COUNT_RESET 0x01
+#define USB_CH_DET 0x01
+
+#define MAIN_CH_INPUT_CURR_SHIFT 4
+#define VBUS_IN_CURR_LIM_SHIFT 4
+#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2
+#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4
+#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F
+#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */
+
+#define LED_INDICATOR_PWM_ENA 0x01
+#define LED_INDICATOR_PWM_DIS 0x00
+#define LED_IND_CUR_5MA 0x04
+#define LED_INDICATOR_PWM_DUTY_252_256 0xBF
+
+/* HW failure constants */
+#define MAIN_CH_TH_PROT 0x02
+#define VBUS_CH_NOK 0x08
+#define USB_CH_TH_PROT 0x02
+#define VBUS_OVV_TH 0x01
+#define MAIN_CH_NOK 0x01
+#define VBUS_DET 0x80
+
+#define MAIN_CH_STATUS2_MAINCHGDROP 0x80
+#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC 0x40
+#define USB_CH_VBUSDROP 0x40
+#define USB_CH_VBUSDETDBNC 0x01
+
+/* UsbLineStatus register bit masks */
+#define AB8500_USB_LINK_STATUS 0x78
+#define AB8505_USB_LINK_STATUS 0xF8
+#define AB8500_STD_HOST_SUSP 0x18
+#define USB_LINK_STATUS_SHIFT 3
+
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+#define WD_KICK_INTERVAL (60 * HZ)
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG 0x4E
+
+/* Step up/down delay in us */
+#define STEP_UDELAY 1000
+
+#define CHARGER_STATUS_POLL 10 /* in ms */
+
+#define CHG_WD_INTERVAL (60 * HZ)
+
+#define AB8500_SW_CONTROL_FALLBACK 0x03
+/* Wait for enumeration before charing in us */
+#define WAIT_ACA_RID_ENUMERATION (5 * 1000)
+/*External charger control*/
+#define AB8500_SYS_CHARGER_CONTROL_REG 0x52
+#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03
+#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07
+
+/* UsbLineStatus register - usb types */
+enum ab8500_charger_link_status {
+ USB_STAT_NOT_CONFIGURED,
+ USB_STAT_STD_HOST_NC,
+ USB_STAT_STD_HOST_C_NS,
+ USB_STAT_STD_HOST_C_S,
+ USB_STAT_HOST_CHG_NM,
+ USB_STAT_HOST_CHG_HS,
+ USB_STAT_HOST_CHG_HS_CHIRP,
+ USB_STAT_DEDICATED_CHG,
+ USB_STAT_ACA_RID_A,
+ USB_STAT_ACA_RID_B,
+ USB_STAT_ACA_RID_C_NM,
+ USB_STAT_ACA_RID_C_HS,
+ USB_STAT_ACA_RID_C_HS_CHIRP,
+ USB_STAT_HM_IDGND,
+ USB_STAT_RESERVED,
+ USB_STAT_NOT_VALID_LINK,
+ USB_STAT_PHY_EN,
+ USB_STAT_SUP_NO_IDGND_VBUS,
+ USB_STAT_SUP_IDGND_VBUS,
+ USB_STAT_CHARGER_LINE_1,
+ USB_STAT_CARKIT_1,
+ USB_STAT_CARKIT_2,
+ USB_STAT_ACA_DOCK_CHARGER,
+};
+
+enum ab8500_usb_state {
+ AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */
+ AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */
+ AB8500_BM_USB_STATE_CONFIGURED,
+ AB8500_BM_USB_STATE_SUSPEND,
+ AB8500_BM_USB_STATE_RESUME,
+ AB8500_BM_USB_STATE_MAX,
+};
+
+/* VBUS input current limits supported in AB8500 in mA */
+#define USB_CH_IP_CUR_LVL_0P05 50
+#define USB_CH_IP_CUR_LVL_0P09 98
+#define USB_CH_IP_CUR_LVL_0P19 193
+#define USB_CH_IP_CUR_LVL_0P29 290
+#define USB_CH_IP_CUR_LVL_0P38 380
+#define USB_CH_IP_CUR_LVL_0P45 450
+#define USB_CH_IP_CUR_LVL_0P5 500
+#define USB_CH_IP_CUR_LVL_0P6 600
+#define USB_CH_IP_CUR_LVL_0P7 700
+#define USB_CH_IP_CUR_LVL_0P8 800
+#define USB_CH_IP_CUR_LVL_0P9 900
+#define USB_CH_IP_CUR_LVL_1P0 1000
+#define USB_CH_IP_CUR_LVL_1P1 1100
+#define USB_CH_IP_CUR_LVL_1P3 1300
+#define USB_CH_IP_CUR_LVL_1P4 1400
+#define USB_CH_IP_CUR_LVL_1P5 1500
+
+#define VBAT_TRESH_IP_CUR_RED 3800
+
+#define to_ab8500_charger_usb_device_info(x) container_of((x), \
+ struct ab8500_charger, usb_chg)
+#define to_ab8500_charger_ac_device_info(x) container_of((x), \
+ struct ab8500_charger, ac_chg)
+
+/**
+ * struct ab8500_charger_interrupts - ab8500 interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_charger_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct ab8500_charger_info {
+ int charger_connected;
+ int charger_online;
+ int charger_voltage;
+ int cv_active;
+ bool wd_expired;
+ int charger_current;
+};
+
+struct ab8500_charger_event_flags {
+ bool mainextchnotok;
+ bool main_thermal_prot;
+ bool usb_thermal_prot;
+ bool vbus_ovv;
+ bool usbchargernotok;
+ bool chgwdexp;
+ bool vbus_collapse;
+ bool vbus_drop_end;
+};
+
+struct ab8500_charger_usb_state {
+ int usb_current;
+ int usb_current_tmp;
+ enum ab8500_usb_state state;
+ enum ab8500_usb_state state_tmp;
+ spinlock_t usb_lock;
+};
+
+struct ab8500_charger_max_usb_in_curr {
+ int usb_type_max;
+ int set_max;
+ int calculated_max;
+};
+
+/**
+ * struct ab8500_charger - ab8500 Charger device information
+ * @dev: Pointer to the structure device
+ * @vbus_detected: VBUS detected
+ * @vbus_detected_start:
+ * VBUS detected during startup
+ * @ac_conn: This will be true when the AC charger has been plugged
+ * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC
+ * charger is enabled
+ * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB
+ * charger is enabled
+ * @vbat Battery voltage
+ * @old_vbat Previously measured battery voltage
+ * @usb_device_is_unrecognised USB device is unrecognised by the hardware
+ * @autopower Indicate if we should have automatic pwron after pwrloss
+ * @autopower_cfg platform specific power config support for "pwron after pwrloss"
+ * @invalid_charger_detect_state State when forcing AB to use invalid charger
+ * @is_aca_rid: Incicate if accessory is ACA type
+ * @current_stepping_sessions:
+ * Counter for current stepping sessions
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @bm: Platform specific battery management information
+ * @flags: Structure for information about events triggered
+ * @usb_state: Structure for usb stack information
+ * @max_usb_in_curr: Max USB charger input current
+ * @ac_chg: AC charger power supply
+ * @usb_chg: USB charger power supply
+ * @ac: Structure that holds the AC charger properties
+ * @usb: Structure that holds the USB charger properties
+ * @regu: Pointer to the struct regulator
+ * @charger_wq: Work queue for the IRQs and checking HW state
+ * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals
+ * @pm_lock: Lock to prevent system to suspend
+ * @check_vbat_work Work for checking vbat threshold to adjust vbus current
+ * @check_hw_failure_work: Work for checking HW state
+ * @check_usbchgnotok_work: Work for checking USB charger not ok status
+ * @kick_wd_work: Work for kicking the charger watchdog in case
+ * of ABB rev 1.* due to the watchog logic bug
+ * @ac_charger_attached_work: Work for checking if AC charger is still
+ * connected
+ * @usb_charger_attached_work: Work for checking if USB charger is still
+ * connected
+ * @ac_work: Work for checking AC charger connection
+ * @detect_usb_type_work: Work for detecting the USB type connected
+ * @usb_link_status_work: Work for checking the new USB link status
+ * @usb_state_changed_work: Work for checking USB state
+ * @attach_work: Work for detecting USB type
+ * @vbus_drop_end_work: Work for detecting VBUS drop end
+ * @check_main_thermal_prot_work:
+ * Work for checking Main thermal status
+ * @check_usb_thermal_prot_work:
+ * Work for checking USB thermal status
+ * @charger_attached_mutex: For controlling the wakelock
+ */
+struct ab8500_charger {
+ struct device *dev;
+ bool vbus_detected;
+ bool vbus_detected_start;
+ bool ac_conn;
+ bool vddadc_en_ac;
+ bool vddadc_en_usb;
+ int vbat;
+ int old_vbat;
+ bool usb_device_is_unrecognised;
+ bool autopower;
+ bool autopower_cfg;
+ int invalid_charger_detect_state;
+ int is_aca_rid;
+ atomic_t current_stepping_sessions;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct abx500_bm_data *bm;
+ struct ab8500_charger_event_flags flags;
+ struct ab8500_charger_usb_state usb_state;
+ struct ab8500_charger_max_usb_in_curr max_usb_in_curr;
+ struct ux500_charger ac_chg;
+ struct ux500_charger usb_chg;
+ struct ab8500_charger_info ac;
+ struct ab8500_charger_info usb;
+ struct regulator *regu;
+ struct workqueue_struct *charger_wq;
+ struct mutex usb_ipt_crnt_lock;
+ struct delayed_work check_vbat_work;
+ struct delayed_work check_hw_failure_work;
+ struct delayed_work check_usbchgnotok_work;
+ struct delayed_work kick_wd_work;
+ struct delayed_work usb_state_changed_work;
+ struct delayed_work attach_work;
+ struct delayed_work ac_charger_attached_work;
+ struct delayed_work usb_charger_attached_work;
+ struct delayed_work vbus_drop_end_work;
+ struct work_struct ac_work;
+ struct work_struct detect_usb_type_work;
+ struct work_struct usb_link_status_work;
+ struct work_struct check_main_thermal_prot_work;
+ struct work_struct check_usb_thermal_prot_work;
+ struct usb_phy *usb_phy;
+ struct notifier_block nb;
+ struct mutex charger_attached_mutex;
+};
+
+/* AC properties */
+static enum power_supply_property ab8500_charger_ac_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/* USB properties */
+static enum power_supply_property ab8500_charger_usb_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+/*
+ * Function for enabling and disabling sw fallback mode
+ * should always be disabled when no charger is connected.
+ */
+static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di,
+ bool fallback)
+{
+ u8 val;
+ u8 reg;
+ u8 bank;
+ u8 bit;
+ int ret;
+
+ dev_dbg(di->dev, "SW Fallback: %d\n", fallback);
+
+ if (is_ab8500(di->parent)) {
+ bank = 0x15;
+ reg = 0x0;
+ bit = 3;
+ } else {
+ bank = AB8500_SYS_CTRL1_BLOCK;
+ reg = AB8500_SW_CONTROL_FALLBACK;
+ bit = 0;
+ }
+
+ /* read the register containing fallback bit */
+ ret = abx500_get_register_interruptible(di->dev, bank, reg, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%d read failed\n", __LINE__);
+ return;
+ }
+
+ if (is_ab8500(di->parent)) {
+ /* enable the OPT emulation registers */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ goto disable_otp;
+ }
+ }
+
+ if (fallback)
+ val |= (1 << bit);
+ else
+ val &= ~(1 << bit);
+
+ /* write back the changed fallback bit value to register */
+ ret = abx500_set_register_interruptible(di->dev, bank, reg, val);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ }
+
+disable_otp:
+ if (is_ab8500(di->parent)) {
+ /* disable the set OTP registers again */
+ ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0);
+ if (ret) {
+ dev_err(di->dev, "%d write failed\n", __LINE__);
+ }
+ }
+}
+
+/**
+ * ab8500_power_supply_changed - a wrapper with local extentions for
+ * power_supply_changed
+ * @di: pointer to the ab8500_charger structure
+ * @psy: pointer to power_supply_that have changed.
+ *
+ */
+static void ab8500_power_supply_changed(struct ab8500_charger *di,
+ struct power_supply *psy)
+{
+ if (di->autopower_cfg) {
+ if (!di->usb.charger_connected &&
+ !di->ac.charger_connected &&
+ di->autopower) {
+ di->autopower = false;
+ ab8500_enable_disable_sw_fallback(di, false);
+ } else if (!di->autopower &&
+ (di->ac.charger_connected ||
+ di->usb.charger_connected)) {
+ di->autopower = true;
+ ab8500_enable_disable_sw_fallback(di, true);
+ }
+ }
+ power_supply_changed(psy);
+}
+
+static void ab8500_charger_set_usb_connected(struct ab8500_charger *di,
+ bool connected)
+{
+ if (connected != di->usb.charger_connected) {
+ dev_dbg(di->dev, "USB connected:%i\n", connected);
+ di->usb.charger_connected = connected;
+
+ if (!connected)
+ di->flags.vbus_drop_end = false;
+
+ sysfs_notify(&di->usb_chg.psy->dev.kobj, NULL, "present");
+
+ if (connected) {
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+ } else {
+ cancel_delayed_work_sync(&di->usb_charger_attached_work);
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+ }
+ }
+}
+
+/**
+ * ab8500_charger_get_ac_voltage() - get ac charger voltage
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger voltage (on success)
+ */
+static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di)
+{
+ int vch;
+
+ /* Only measure voltage if the charger is connected */
+ if (di->ac.charger_connected) {
+ vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V);
+ if (vch < 0)
+ dev_err(di->dev, "%s gpadc conv failed,\n", __func__);
+ } else {
+ vch = 0;
+ }
+ return vch;
+}
+
+/**
+ * ab8500_charger_ac_cv() - check if the main charger is in CV mode
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_ac_cv(struct ab8500_charger *di)
+{
+ u8 val;
+ int ret = 0;
+
+ /* Only check CV mode if the charger is online */
+ if (di->ac.charger_online) {
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_STATUS1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return 0;
+ }
+
+ if (val & MAIN_CH_CV_ON)
+ ret = 1;
+ else
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_get_vbus_voltage() - get vbus voltage
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the vbus voltage.
+ * Returns vbus voltage (on success)
+ */
+static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di)
+{
+ int vch;
+
+ /* Only measure voltage if the charger is connected */
+ if (di->usb.charger_connected) {
+ vch = ab8500_gpadc_convert(di->gpadc, VBUS_V);
+ if (vch < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ vch = 0;
+ }
+ return vch;
+}
+
+/**
+ * ab8500_charger_get_usb_current() - get usb charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the usb charger current.
+ * Returns usb current (on success) and error code on failure
+ */
+static int ab8500_charger_get_usb_current(struct ab8500_charger *di)
+{
+ int ich;
+
+ /* Only measure current if the charger is online */
+ if (di->usb.charger_online) {
+ ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C);
+ if (ich < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ ich = 0;
+ }
+ return ich;
+}
+
+/**
+ * ab8500_charger_get_ac_current() - get ac charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * This function returns the ac charger current.
+ * Returns ac current (on success) and error code on failure.
+ */
+static int ab8500_charger_get_ac_current(struct ab8500_charger *di)
+{
+ int ich;
+
+ /* Only measure current if the charger is online */
+ if (di->ac.charger_online) {
+ ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C);
+ if (ich < 0)
+ dev_err(di->dev, "%s gpadc conv failed\n", __func__);
+ } else {
+ ich = 0;
+ }
+ return ich;
+}
+
+/**
+ * ab8500_charger_usb_cv() - check if the usb charger is in CV mode
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Returns ac charger CV mode (on success) else error code
+ */
+static int ab8500_charger_usb_cv(struct ab8500_charger *di)
+{
+ int ret;
+ u8 val;
+
+ /* Only check CV mode if the charger is online */
+ if (di->usb.charger_online) {
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return 0;
+ }
+
+ if (val & USB_CH_CV_ON)
+ ret = 1;
+ else
+ ret = 0;
+ } else {
+ ret = 0;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_detect_chargers() - Detect the connected chargers
+ * @di: pointer to the ab8500_charger structure
+ * @probe: if probe, don't delay and wait for HW
+ *
+ * Returns the type of charger connected.
+ * For USB it will not mean we can actually charge from it
+ * but that there is a USB cable connected that we have to
+ * identify. This is used during startup when we don't get
+ * interrupts of the charger detection
+ *
+ * Returns an integer value, that means,
+ * NO_PW_CONN no power supply is connected
+ * AC_PW_CONN if the AC power supply is connected
+ * USB_PW_CONN if the USB power supply is connected
+ * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected
+ */
+static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe)
+{
+ int result = NO_PW_CONN;
+ int ret;
+ u8 val;
+
+ /* Check for AC charger */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_STATUS1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ if (val & MAIN_CH_DET)
+ result = AC_PW_CONN;
+
+ /* Check for USB charger */
+
+ if (!probe) {
+ /*
+ * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100
+ * when disconnecting ACA even though no
+ * charger was connected. Try waiting a little
+ * longer than the 100 ms of VBUS_DET_DBNC100...
+ */
+ msleep(110);
+ }
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ dev_dbg(di->dev,
+ "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__,
+ val);
+ if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100))
+ result |= USB_PW_CONN;
+
+ return result;
+}
+
+/**
+ * ab8500_charger_max_usb_curr() - get the max curr for the USB type
+ * @di: pointer to the ab8500_charger structure
+ * @link_status: the identified USB type
+ *
+ * Get the maximum current that is allowed to be drawn from the host
+ * based on the USB type.
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_max_usb_curr(struct ab8500_charger *di,
+ enum ab8500_charger_link_status link_status)
+{
+ int ret = 0;
+
+ di->usb_device_is_unrecognised = false;
+
+ /*
+ * Platform only supports USB 2.0.
+ * This means that charging current from USB source
+ * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_*
+ * should set USB_CH_IP_CUR_LVL_0P5.
+ */
+
+ switch (link_status) {
+ case USB_STAT_STD_HOST_NC:
+ case USB_STAT_STD_HOST_C_NS:
+ case USB_STAT_STD_HOST_C_S:
+ dev_dbg(di->dev, "USB Type - Standard host is "
+ "detected through USB driver\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_HOST_CHG_HS_CHIRP:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_HOST_CHG_HS:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_ACA_RID_C_HS:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_ACA_RID_A:
+ /*
+ * Dedicated charger level minus maximum current accessory
+ * can consume (900mA). Closest level is 500mA
+ */
+ dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 1;
+ break;
+ case USB_STAT_ACA_RID_B:
+ /*
+ * Dedicated charger level minus 120mA (20mA for ACA and
+ * 100mA for potential accessory). Closest level is 1300mA
+ */
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status,
+ di->max_usb_in_curr.usb_type_max);
+ di->is_aca_rid = 1;
+ break;
+ case USB_STAT_HOST_CHG_NM:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_DEDICATED_CHG:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5;
+ di->is_aca_rid = 0;
+ break;
+ case USB_STAT_ACA_RID_C_HS_CHIRP:
+ case USB_STAT_ACA_RID_C_NM:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5;
+ di->is_aca_rid = 1;
+ break;
+ case USB_STAT_NOT_CONFIGURED:
+ if (di->vbus_detected) {
+ di->usb_device_is_unrecognised = true;
+ dev_dbg(di->dev, "USB Type - Legacy charger.\n");
+ di->max_usb_in_curr.usb_type_max =
+ USB_CH_IP_CUR_LVL_1P5;
+ break;
+ }
+ case USB_STAT_HM_IDGND:
+ dev_err(di->dev, "USB Type - Charging not allowed\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
+ ret = -ENXIO;
+ break;
+ case USB_STAT_RESERVED:
+ if (is_ab8500(di->parent)) {
+ di->flags.vbus_collapse = true;
+ dev_err(di->dev, "USB Type - USB_STAT_RESERVED "
+ "VBUS has collapsed\n");
+ ret = -ENXIO;
+ break;
+ } else {
+ dev_dbg(di->dev, "USB Type - Charging not allowed\n");
+ di->max_usb_in_curr.usb_type_max =
+ USB_CH_IP_CUR_LVL_0P05;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+ link_status,
+ di->max_usb_in_curr.usb_type_max);
+ ret = -ENXIO;
+ break;
+ }
+ case USB_STAT_CARKIT_1:
+ case USB_STAT_CARKIT_2:
+ case USB_STAT_ACA_DOCK_CHARGER:
+ case USB_STAT_CHARGER_LINE_1:
+ 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;
+ break;
+
+ default:
+ dev_err(di->dev, "USB Type - Unknown\n");
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
+ ret = -ENXIO;
+ break;
+ };
+
+ di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
+ dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d",
+ link_status, di->max_usb_in_curr.set_max);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_read_usb_type() - read the type of usb connected
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_read_usb_type(struct ab8500_charger *di)
+{
+ int ret;
+ u8 val;
+
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ /* get the USB type */
+ if (is_ab8500(di->parent))
+ val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
+ else
+ val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT;
+ ret = ab8500_charger_max_usb_curr(di,
+ (enum ab8500_charger_link_status) val);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_detect_usb_type() - get the type of usb connected
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Detect the type of the plugged USB
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_charger_detect_usb_type(struct ab8500_charger *di)
+{
+ int i, ret;
+ u8 val;
+
+ /*
+ * On getting the VBUS rising edge detect interrupt there
+ * is a 250ms delay after which the register UsbLineStatus
+ * is filled with valid data.
+ */
+ for (i = 0; i < 10; i++) {
+ msleep(250);
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG,
+ &val);
+ dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n",
+ __func__, val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return ret;
+ }
+ dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__,
+ val);
+ /*
+ * Until the IT source register is read the UsbLineStatus
+ * register is not updated, hence doing the same
+ * Revisit this:
+ */
+
+ /* get the USB type */
+ if (is_ab8500(di->parent))
+ val = (val & AB8500_USB_LINK_STATUS) >>
+ USB_LINK_STATUS_SHIFT;
+ else
+ val = (val & AB8505_USB_LINK_STATUS) >>
+ USB_LINK_STATUS_SHIFT;
+ if (val)
+ break;
+ }
+ ret = ab8500_charger_max_usb_curr(di,
+ (enum ab8500_charger_link_status) val);
+
+ return ret;
+}
+
+/*
+ * This array maps the raw hex value to charger voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_charger_voltage_map[] = {
+ 3500 ,
+ 3525 ,
+ 3550 ,
+ 3575 ,
+ 3600 ,
+ 3625 ,
+ 3650 ,
+ 3675 ,
+ 3700 ,
+ 3725 ,
+ 3750 ,
+ 3775 ,
+ 3800 ,
+ 3825 ,
+ 3850 ,
+ 3875 ,
+ 3900 ,
+ 3925 ,
+ 3950 ,
+ 3975 ,
+ 4000 ,
+ 4025 ,
+ 4050 ,
+ 4060 ,
+ 4070 ,
+ 4080 ,
+ 4090 ,
+ 4100 ,
+ 4110 ,
+ 4120 ,
+ 4130 ,
+ 4140 ,
+ 4150 ,
+ 4160 ,
+ 4170 ,
+ 4180 ,
+ 4190 ,
+ 4200 ,
+ 4210 ,
+ 4220 ,
+ 4230 ,
+ 4240 ,
+ 4250 ,
+ 4260 ,
+ 4270 ,
+ 4280 ,
+ 4290 ,
+ 4300 ,
+ 4310 ,
+ 4320 ,
+ 4330 ,
+ 4340 ,
+ 4350 ,
+ 4360 ,
+ 4370 ,
+ 4380 ,
+ 4390 ,
+ 4400 ,
+ 4410 ,
+ 4420 ,
+ 4430 ,
+ 4440 ,
+ 4450 ,
+ 4460 ,
+ 4470 ,
+ 4480 ,
+ 4490 ,
+ 4500 ,
+ 4510 ,
+ 4520 ,
+ 4530 ,
+ 4540 ,
+ 4550 ,
+ 4560 ,
+ 4570 ,
+ 4580 ,
+ 4590 ,
+ 4600 ,
+};
+
+static int ab8500_voltage_to_regval(int voltage)
+{
+ int i;
+
+ /* Special case for voltage below 3.5V */
+ if (voltage < ab8500_charger_voltage_map[0])
+ return LOW_VOLT_REG;
+
+ for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) {
+ if (voltage < ab8500_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1;
+ if (voltage == ab8500_charger_voltage_map[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab8500_current_to_regval(struct ab8500_charger *di, int curr)
+{
+ int i;
+
+ if (curr < di->bm->chg_output_curr[0])
+ return 0;
+
+ for (i = 0; i < di->bm->n_chg_out_curr; i++) {
+ if (curr < di->bm->chg_output_curr[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = di->bm->n_chg_out_curr - 1;
+ if (curr == di->bm->chg_output_curr[i])
+ return i;
+ else
+ return -1;
+}
+
+static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr)
+{
+ int i;
+
+ if (curr < di->bm->chg_input_curr[0])
+ return 0;
+
+ for (i = 0; i < di->bm->n_chg_in_curr; i++) {
+ if (curr < di->bm->chg_input_curr[i])
+ return i - 1;
+ }
+
+ /* If not last element, return error */
+ i = di->bm->n_chg_in_curr - 1;
+ if (curr == di->bm->chg_input_curr[i])
+ return i;
+ else
+ return -1;
+}
+
+/**
+ * ab8500_charger_get_usb_cur() - get usb current
+ * @di: pointer to the ab8500_charger structre
+ *
+ * The usb stack provides the maximum current that can be drawn from
+ * the standard usb host. This will be in mA.
+ * This function converts current in mA to a value that can be written
+ * to the register. Returns -1 if charging is not allowed
+ */
+static int ab8500_charger_get_usb_cur(struct ab8500_charger *di)
+{
+ int ret = 0;
+ switch (di->usb_state.usb_current) {
+ case 100:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09;
+ break;
+ case 200:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19;
+ break;
+ case 300:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29;
+ break;
+ case 400:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38;
+ break;
+ case 500:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5;
+ break;
+ default:
+ di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05;
+ ret = -EPERM;
+ break;
+ };
+ di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max;
+ return ret;
+}
+
+/**
+ * ab8500_charger_check_continue_stepping() - Check to allow stepping
+ * @di: pointer to the ab8500_charger structure
+ * @reg: select what charger register to check
+ *
+ * Check if current stepping should be allowed to continue.
+ * Checks if charger source has not collapsed. If it has, further stepping
+ * is not allowed.
+ */
+static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di,
+ int reg)
+{
+ if (reg == AB8500_USBCH_IPT_CRNTLVL_REG)
+ return !di->flags.vbus_drop_end;
+ else
+ return true;
+}
+
+/**
+ * ab8500_charger_set_current() - set charger current
+ * @di: pointer to the ab8500_charger structure
+ * @ich: charger current, in mA
+ * @reg: select what charger register to set
+ *
+ * Set charger current.
+ * There is no state machine in the AB to step up/down the charger
+ * current to avoid dips and spikes on MAIN, VBUS and VBAT when
+ * charging is started. Instead we need to implement
+ * this charger current step-up/down here.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_current(struct ab8500_charger *di,
+ int ich, int reg)
+{
+ int ret = 0;
+ int curr_index, prev_curr_index, shift_value, i;
+ u8 reg_value;
+ u32 step_udelay;
+ bool no_stepping = false;
+
+ atomic_inc(&di->current_stepping_sessions);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ goto exit_set_current;
+ }
+
+ switch (reg) {
+ case AB8500_MCH_IPT_CURLVL_REG:
+ shift_value = MAIN_CH_INPUT_CURR_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_current_to_regval(di, ich);
+ step_udelay = STEP_UDELAY;
+ if (!di->ac.charger_connected)
+ no_stepping = true;
+ break;
+ case AB8500_USBCH_IPT_CRNTLVL_REG:
+ if (is_ab8540(di->parent))
+ shift_value = AB8540_VBUS_IN_CURR_LIM_SHIFT;
+ else
+ shift_value = VBUS_IN_CURR_LIM_SHIFT;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_vbus_in_curr_to_regval(di, ich);
+ step_udelay = STEP_UDELAY * 100;
+
+ if (!di->usb.charger_connected)
+ no_stepping = true;
+ break;
+ case AB8500_CH_OPT_CRNTLVL_REG:
+ shift_value = 0;
+ prev_curr_index = (reg_value >> shift_value);
+ curr_index = ab8500_current_to_regval(di, ich);
+ step_udelay = STEP_UDELAY;
+ if (curr_index && (curr_index - prev_curr_index) > 1)
+ step_udelay *= 100;
+
+ if (!di->usb.charger_connected && !di->ac.charger_connected)
+ no_stepping = true;
+
+ break;
+ default:
+ dev_err(di->dev, "%s current register not valid\n", __func__);
+ ret = -ENXIO;
+ goto exit_set_current;
+ }
+
+ if (curr_index < 0) {
+ dev_err(di->dev, "requested current limit out-of-range\n");
+ ret = -ENXIO;
+ goto exit_set_current;
+ }
+
+ /* only update current if it's been changed */
+ if (prev_curr_index == curr_index) {
+ dev_dbg(di->dev, "%s current not changed for reg: 0x%02x\n",
+ __func__, reg);
+ ret = 0;
+ goto exit_set_current;
+ }
+
+ dev_dbg(di->dev, "%s set charger current: %d mA for reg: 0x%02x\n",
+ __func__, ich, reg);
+
+ if (no_stepping) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ reg, (u8)curr_index << shift_value);
+ if (ret)
+ dev_err(di->dev, "%s write failed\n", __func__);
+ } else if (prev_curr_index > curr_index) {
+ for (i = prev_curr_index - 1; i >= curr_index; i--) {
+ dev_dbg(di->dev, "curr change_1 to: %x for 0x%02x\n",
+ (u8) i << shift_value, reg);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8)i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto exit_set_current;
+ }
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
+ }
+ } else {
+ bool allow = true;
+ for (i = prev_curr_index + 1; i <= curr_index && allow; i++) {
+ dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n",
+ (u8)i << shift_value, reg);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, reg, (u8)i << shift_value);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto exit_set_current;
+ }
+ if (i != curr_index)
+ usleep_range(step_udelay, step_udelay * 2);
+
+ allow = ab8500_charger_check_continue_stepping(di, reg);
+ }
+ }
+
+exit_set_current:
+ atomic_dec(&di->current_stepping_sessions);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_set_vbus_in_curr() - set VBUS input current limit
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: charger input current limit
+ *
+ * Sets the current that can be drawn from the USB host
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ int min_value;
+ int ret;
+
+ /* We should always use to lowest current limit */
+ min_value = min(di->bm->chg_params->usb_curr_max, ich_in);
+ if (di->max_usb_in_curr.set_max > 0)
+ min_value = min(di->max_usb_in_curr.set_max, min_value);
+
+ if (di->usb_state.usb_current >= 0)
+ min_value = min(di->usb_state.usb_current, min_value);
+
+ switch (min_value) {
+ case 100:
+ if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+ min_value = USB_CH_IP_CUR_LVL_0P05;
+ break;
+ case 500:
+ if (di->vbat < VBAT_TRESH_IP_CUR_RED)
+ min_value = USB_CH_IP_CUR_LVL_0P45;
+ break;
+ default:
+ break;
+ }
+
+ dev_info(di->dev, "VBUS input current limit set to %d mA\n", min_value);
+
+ mutex_lock(&di->usb_ipt_crnt_lock);
+ ret = ab8500_charger_set_current(di, min_value,
+ AB8500_USBCH_IPT_CRNTLVL_REG);
+ mutex_unlock(&di->usb_ipt_crnt_lock);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_set_main_in_curr() - set main charger input current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_in: input charger current, in mA
+ *
+ * Set main charger input current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_main_in_curr(struct ab8500_charger *di,
+ int ich_in)
+{
+ return ab8500_charger_set_current(di, ich_in,
+ AB8500_MCH_IPT_CURLVL_REG);
+}
+
+/**
+ * ab8500_charger_set_output_curr() - set charger output current
+ * @di: pointer to the ab8500_charger structure
+ * @ich_out: output charger current, in mA
+ *
+ * Set charger output current.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_set_output_curr(struct ab8500_charger *di,
+ int ich_out)
+{
+ return ab8500_charger_set_current(di, ich_out,
+ AB8500_CH_OPT_CRNTLVL_REG);
+}
+
+/**
+ * ab8500_charger_led_en() - turn on/off chargign led
+ * @di: pointer to the ab8500_charger structure
+ * @on: flag to turn on/off the chargign led
+ *
+ * Power ON/OFF charging LED indication
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_led_en(struct ab8500_charger *di, int on)
+{
+ int ret;
+
+ if (on) {
+ /* Power ON charging LED indicator, set LED current to 5mA */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_CTRL,
+ (LED_IND_CUR_5MA | LED_INDICATOR_PWM_ENA));
+ if (ret) {
+ dev_err(di->dev, "Power ON LED failed\n");
+ return ret;
+ }
+ /* LED indicator PWM duty cycle 252/256 */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_DUTY,
+ LED_INDICATOR_PWM_DUTY_252_256);
+ if (ret) {
+ dev_err(di->dev, "Set LED PWM duty cycle failed\n");
+ return ret;
+ }
+ } else {
+ /* Power off charging LED indicator */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_LED_INDICATOR_PWM_CTRL,
+ LED_INDICATOR_PWM_DIS);
+ if (ret) {
+ dev_err(di->dev, "Power-off LED failed\n");
+ return ret;
+ }
+ }
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_ac_en() - enable or disable ac charging
+ * @di: pointer to the ab8500_charger structure
+ * @enable: enable/disable flag
+ * @vset: charging voltage
+ * @iset: charging current
+ *
+ * Enable/Disable AC/Mains charging and turns on/off the charging led
+ * respectively.
+ **/
+static int ab8500_charger_ac_en(struct ux500_charger *charger,
+ int enable, int vset, int iset)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ int input_curr_index;
+ u8 overshoot = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger);
+
+ if (enable) {
+ /* Check if AC is connected */
+ if (!di->ac.charger_connected) {
+ dev_err(di->dev, "AC charger not connected\n");
+ return -ENXIO;
+ }
+
+ /* Enable AC charging */
+ dev_dbg(di->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+
+ /*
+ * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+ * will be triggered everytime we enable the VDD ADC supply.
+ * This will turn off charging for a short while.
+ * It can be avoided by having the supply on when
+ * there is a charger enabled. Normally the VDD ADC supply
+ * is enabled everytime a GPADC conversion is triggered. We will
+ * force it to be enabled from this driver to have
+ * the GPADC module independant of the AB8500 chargers
+ */
+ if (!di->vddadc_en_ac) {
+ ret = regulator_enable(di->regu);
+ if (ret)
+ dev_warn(di->dev,
+ "Failed to enable regulator\n");
+ else
+ di->vddadc_en_ac = true;
+ }
+
+ /* Check if the requested voltage or current is valid */
+ volt_index = ab8500_voltage_to_regval(vset);
+ curr_index = ab8500_current_to_regval(di, iset);
+ input_curr_index = ab8500_current_to_regval(di,
+ di->bm->chg_params->ac_curr_max);
+ if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) {
+ dev_err(di->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ /* ChVoltLevel: maximum battery charging voltage */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* MainChInputCurr: current that can be drawn from the charger*/
+ ret = ab8500_charger_set_main_in_curr(di,
+ di->bm->chg_params->ac_curr_max);
+ if (ret) {
+ dev_err(di->dev, "%s Failed to set MainChInputCurr\n",
+ __func__);
+ return ret;
+ }
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, iset);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+
+ /* Check if VBAT overshoot control should be enabled */
+ if (!di->bm->enable_overshoot)
+ overshoot = MAIN_CH_NO_OVERSHOOT_ENA_N;
+
+ /* Enable Main Charger */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_MCH_CTRL1, MAIN_CH_ENA | overshoot);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* Power on charging LED indication */
+ ret = ab8500_charger_led_en(di, true);
+ if (ret < 0)
+ dev_err(di->dev, "failed to enable LED\n");
+
+ di->ac.charger_online = 1;
+ } else {
+ /* Disable AC charging */
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the
+ * watchdog logic. That means we have to continously
+ * kick the charger watchdog even when no charger is
+ * connected. This is only valid once the AC charger
+ * has been enabled. This is a bug that is not handled
+ * by the algorithm and the watchdog have to be kicked
+ * by the charger driver when the AC charger
+ * is disabled
+ */
+ if (di->ac_conn) {
+ queue_delayed_work(di->charger_wq,
+ &di->kick_wd_work,
+ round_jiffies(WD_KICK_INTERVAL));
+ }
+
+ /*
+ * We can't turn off charging completely
+ * due to a bug in AB8500 cut1.
+ * If we do, charging will not start again.
+ * That is why we set the lowest voltage
+ * and current possible
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, CH_VOL_LVL_3P5);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+
+ ret = ab8500_charger_set_output_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+ } else {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_MCH_CTRL1, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0)
+ dev_err(di->dev, "failed to disable LED\n");
+
+ di->ac.charger_online = 0;
+ di->ac.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (di->vddadc_en_ac) {
+ regulator_disable(di->regu);
+ di->vddadc_en_ac = false;
+ }
+
+ dev_dbg(di->dev, "%s Disabled AC charging\n", __func__);
+ }
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_usb_en() - enable usb charging
+ * @di: pointer to the ab8500_charger structure
+ * @enable: enable/disable flag
+ * @vset: charging voltage
+ * @ich_out: charger output current
+ *
+ * Enable/Disable USB charging and turns on/off the charging led respectively.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_en(struct ux500_charger *charger,
+ int enable, int vset, int ich_out)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ u8 overshoot = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
+
+ if (enable) {
+ /* Check if USB is connected */
+ if (!di->usb.charger_connected) {
+ dev_err(di->dev, "USB charger not connected\n");
+ return -ENXIO;
+ }
+
+ /*
+ * Due to a bug in AB8500, BTEMP_HIGH/LOW interrupts
+ * will be triggered everytime we enable the VDD ADC supply.
+ * This will turn off charging for a short while.
+ * It can be avoided by having the supply on when
+ * there is a charger enabled. Normally the VDD ADC supply
+ * is enabled everytime a GPADC conversion is triggered. We will
+ * force it to be enabled from this driver to have
+ * the GPADC module independant of the AB8500 chargers
+ */
+ if (!di->vddadc_en_usb) {
+ ret = regulator_enable(di->regu);
+ if (ret)
+ dev_warn(di->dev,
+ "Failed to enable regulator\n");
+ else
+ di->vddadc_en_usb = true;
+ }
+
+ /* Enable USB charging */
+ dev_dbg(di->dev, "Enable USB: %dmV %dmA\n", vset, ich_out);
+
+ /* Check if the requested voltage or current is valid */
+ volt_index = ab8500_voltage_to_regval(vset);
+ curr_index = ab8500_current_to_regval(di, ich_out);
+ if (volt_index < 0 || curr_index < 0) {
+ dev_err(di->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ /* ChVoltLevel: max voltage upto which battery can be charged */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_REG, (u8) volt_index);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+ /* Check if VBAT overshoot control should be enabled */
+ if (!di->bm->enable_overshoot)
+ overshoot = USB_CHG_NO_OVERSHOOT_ENA_N;
+
+ /* Enable USB Charger */
+ dev_dbg(di->dev,
+ "Enabling USB with write to AB8500_USBCH_CTRL1_REG\n");
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, USB_CH_ENA | overshoot);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ /* If success power on charging LED indication */
+ ret = ab8500_charger_led_en(di, true);
+ if (ret < 0)
+ dev_err(di->dev, "failed to enable LED\n");
+
+ di->usb.charger_online = 1;
+
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+
+ queue_delayed_work(di->charger_wq, &di->check_vbat_work, HZ);
+
+ } else {
+ /* Disable USB charging */
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "%s write failed\n", __func__);
+ return ret;
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0)
+ dev_err(di->dev, "failed to disable LED\n");
+ /* USBChInputCurr: current that can be drawn from the usb */
+ ret = ab8500_charger_set_vbus_in_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "setting USBChInputCurr failed\n");
+ return ret;
+ }
+
+ /* ChOutputCurentLevel: protected output current */
+ ret = ab8500_charger_set_output_curr(di, 0);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to reset ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+ di->usb.charger_online = 0;
+ di->usb.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (di->vddadc_en_usb) {
+ regulator_disable(di->regu);
+ di->vddadc_en_usb = false;
+ }
+
+ dev_dbg(di->dev, "%s Disabled USB charging\n", __func__);
+
+ /* Cancel any pending Vbat check work */
+ cancel_delayed_work(&di->check_vbat_work);
+
+ }
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+
+ return ret;
+}
+
+static int ab8500_external_charger_prepare(struct notifier_block *charger_nb,
+ unsigned long event, void *data)
+{
+ int ret;
+ struct device *dev = data;
+ /*Toggle External charger control pin*/
+ ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK,
+ AB8500_SYS_CHARGER_CONTROL_REG,
+ EXTERNAL_CHARGER_DISABLE_REG_VAL);
+ if (ret < 0) {
+ dev_err(dev, "write reg failed %d\n", ret);
+ goto out;
+ }
+ ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK,
+ AB8500_SYS_CHARGER_CONTROL_REG,
+ EXTERNAL_CHARGER_ENABLE_REG_VAL);
+ if (ret < 0)
+ dev_err(dev, "Write reg failed %d\n", ret);
+
+out:
+ return ret;
+}
+
+/**
+ * ab8500_charger_usb_check_enable() - enable usb charging
+ * @charger: pointer to the ux500_charger structure
+ * @vset: charging voltage
+ * @iset: charger output current
+ *
+ * Check if the VBUS charger has been disconnected and reconnected without
+ * AB8500 rising an interrupt. Returns 0 on success.
+ */
+static int ab8500_charger_usb_check_enable(struct ux500_charger *charger,
+ int vset, int iset)
+{
+ u8 usbch_ctrl1 = 0;
+ int ret = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger);
+
+ if (!di->usb.charger_connected)
+ return ret;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_USBCH_CTRL1_REG, &usbch_ctrl1);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ return ret;
+ }
+ dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1);
+
+ if (!(usbch_ctrl1 & USB_CH_ENA)) {
+ dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL,
+ DROP_COUNT_RESET, DROP_COUNT_RESET);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 write failed %d\n", __LINE__);
+ return ret;
+ }
+
+ ret = ab8500_charger_usb_en(&di->usb_chg, true, vset, iset);
+ if (ret < 0) {
+ dev_err(di->dev, "Failed to enable VBUS charger %d\n",
+ __LINE__);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+/**
+ * ab8500_charger_ac_check_enable() - enable usb charging
+ * @charger: pointer to the ux500_charger structure
+ * @vset: charging voltage
+ * @iset: charger output current
+ *
+ * Check if the AC charger has been disconnected and reconnected without
+ * AB8500 rising an interrupt. Returns 0 on success.
+ */
+static int ab8500_charger_ac_check_enable(struct ux500_charger *charger,
+ int vset, int iset)
+{
+ u8 mainch_ctrl1 = 0;
+ int ret = 0;
+
+ struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger);
+
+ if (!di->ac.charger_connected)
+ return ret;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_MCH_CTRL1, &mainch_ctrl1);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ return ret;
+ }
+ dev_dbg(di->dev, "AC charger ctrl: 0x%02x\n", mainch_ctrl1);
+
+ if (!(mainch_ctrl1 & MAIN_CH_ENA)) {
+ dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL,
+ DROP_COUNT_RESET, DROP_COUNT_RESET);
+
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 write failed %d\n", __LINE__);
+ return ret;
+ }
+
+ ret = ab8500_charger_ac_en(&di->usb_chg, true, vset, iset);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to enable AC charger %d\n",
+ __LINE__);
+ return ret;
+ }
+ }
+ return ret;
+}
+
+/**
+ * ab8500_charger_watchdog_kick() - kick charger watchdog
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Kick charger watchdog
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ di = to_ab8500_charger_ac_device_info(charger);
+ else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ return ret;
+}
+
+/**
+ * ab8500_charger_update_charger_current() - update charger current
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Update the charger output current for the specified charger
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ di = to_ab8500_charger_ac_device_info(charger);
+ else if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = ab8500_charger_set_output_curr(di, ich_out);
+ if (ret) {
+ dev_err(di->dev, "%s "
+ "Failed to set ChOutputCurentLevel\n",
+ __func__);
+ return ret;
+ }
+
+ /* Reset the main and usb drop input current measurement counter */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARGER_CTRL, DROP_COUNT_RESET);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * ab8540_charger_power_path_enable() - enable usb power path mode
+ * @charger: pointer to the ux500_charger structure
+ * @enable: enable/disable flag
+ *
+ * Enable or disable the power path for usb mode
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8540_charger_power_path_enable(struct ux500_charger *charger,
+ bool enable)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_MODE_REG,
+ BUS_POWER_PATH_MODE_ENA, enable);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+
+/**
+ * ab8540_charger_usb_pre_chg_enable() - enable usb pre change
+ * @charger: pointer to the ux500_charger structure
+ * @enable: enable/disable flag
+ *
+ * Enable or disable the pre-chage for usb mode
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8540_charger_usb_pre_chg_enable(struct ux500_charger *charger,
+ bool enable)
+{
+ int ret;
+ struct ab8500_charger *di;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ di = to_ab8500_charger_usb_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_CHR_REG,
+ BUS_POWER_PATH_PRECHG_ENA, enable);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ return ret;
+ }
+
+ return ret;
+}
+
+static int ab8500_charger_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext = dev_get_drvdata(dev);
+ const char **supplicants = (const char **)ext->supplied_to;
+ struct ab8500_charger *di;
+ union power_supply_propval ret;
+ int j;
+ struct ux500_charger *usb_chg;
+
+ usb_chg = (struct ux500_charger *)data;
+ psy = usb_chg->psy;
+
+ di = to_ab8500_charger_usb_device_info(usb_chg);
+
+ /* For all psy where the driver name appears in any supplied_to */
+ j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ if (j < 0)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->desc->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->desc->properties[j];
+
+ if (power_supply_get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->vbat = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_check_vbat_work() - keep vbus current within spec
+ * @work pointer to the work_struct structure
+ *
+ * Due to a asic bug it is necessary to lower the input current to the vbus
+ * charger when charging with at some specific levels. This issue is only valid
+ * for below a certain battery voltage. This function makes sure that the
+ * the allowed current limit isn't exceeded.
+ */
+static void ab8500_charger_check_vbat_work(struct work_struct *work)
+{
+ int t = 10;
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_vbat_work.work);
+
+ class_for_each_device(power_supply_class, NULL,
+ di->usb_chg.psy, ab8500_charger_get_ext_psy_data);
+
+ /* First run old_vbat is 0. */
+ if (di->old_vbat == 0)
+ di->old_vbat = di->vbat;
+
+ if (!((di->old_vbat <= VBAT_TRESH_IP_CUR_RED &&
+ di->vbat <= VBAT_TRESH_IP_CUR_RED) ||
+ (di->old_vbat > VBAT_TRESH_IP_CUR_RED &&
+ di->vbat > VBAT_TRESH_IP_CUR_RED))) {
+
+ dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d,"
+ " old: %d\n", di->max_usb_in_curr.usb_type_max,
+ di->vbat, di->old_vbat);
+ ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ power_supply_changed(di->usb_chg.psy);
+ }
+
+ di->old_vbat = di->vbat;
+
+ /*
+ * No need to check the battery voltage every second when not close to
+ * the threshold.
+ */
+ if (di->vbat < (VBAT_TRESH_IP_CUR_RED + 100) &&
+ (di->vbat > (VBAT_TRESH_IP_CUR_RED - 100)))
+ t = 1;
+
+ queue_delayed_work(di->charger_wq, &di->check_vbat_work, t * HZ);
+}
+
+/**
+ * ab8500_charger_check_hw_failure_work() - check main charger failure
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_check_hw_failure_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_hw_failure_work.work);
+
+ /* Check if the status bits for HW failure is still active */
+ if (di->flags.mainextchnotok) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (!(reg_value & MAIN_CH_NOK)) {
+ di->flags.mainextchnotok = false;
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+ }
+ }
+ if (di->flags.vbus_ovv) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (!(reg_value & VBUS_OVV_TH)) {
+ di->flags.vbus_ovv = false;
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ }
+ }
+ /* If we still have a failure, schedule a new check */
+ if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, round_jiffies(HZ));
+ }
+}
+
+/**
+ * ab8500_charger_kick_watchdog_work() - kick the watchdog
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog.
+ *
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+static void ab8500_charger_kick_watchdog_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, kick_wd_work.work);
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ /* Schedule a new watchdog kick */
+ queue_delayed_work(di->charger_wq,
+ &di->kick_wd_work, round_jiffies(WD_KICK_INTERVAL));
+}
+
+/**
+ * ab8500_charger_ac_work() - work to get and set main charger status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the main charger status
+ */
+static void ab8500_charger_ac_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, ac_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if the main charger is
+ * connected by reading the status register
+ */
+ ret = ab8500_charger_detect_chargers(di, false);
+ if (ret < 0)
+ return;
+
+ if (ret & AC_PW_CONN) {
+ di->ac.charger_connected = 1;
+ di->ac_conn = true;
+ } else {
+ di->ac.charger_connected = 0;
+ }
+
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+ sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present");
+}
+
+static void ab8500_charger_usb_attached_work(struct work_struct *work)
+{
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger,
+ usb_charger_attached_work.work);
+ int usbch = (USB_CH_VBUSDROP | USB_CH_VBUSDETDBNC);
+ int ret, i;
+ u8 statval;
+
+ for (i = 0; i < 10; i++) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT1_REG,
+ &statval);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ goto reschedule;
+ }
+ if ((statval & usbch) != usbch)
+ goto reschedule;
+
+ msleep(CHARGER_STATUS_POLL);
+ }
+
+ ab8500_charger_usb_en(&di->usb_chg, 0, 0, 0);
+
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return;
+
+reschedule:
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+}
+
+static void ab8500_charger_ac_attached_work(struct work_struct *work)
+{
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger,
+ ac_charger_attached_work.work);
+ int mainch = (MAIN_CH_STATUS2_MAINCHGDROP |
+ MAIN_CH_STATUS2_MAINCHARGERDETDBNC);
+ int ret, i;
+ u8 statval;
+
+ for (i = 0; i < 10; i++) {
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_STATUS2_REG,
+ &statval);
+ if (ret < 0) {
+ dev_err(di->dev, "ab8500 read failed %d\n", __LINE__);
+ goto reschedule;
+ }
+
+ if ((statval & mainch) != mainch)
+ goto reschedule;
+
+ msleep(CHARGER_STATUS_POLL);
+ }
+
+ ab8500_charger_ac_en(&di->ac_chg, 0, 0, 0);
+ queue_work(di->charger_wq, &di->ac_work);
+
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return;
+
+reschedule:
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
+}
+
+/**
+ * ab8500_charger_detect_usb_type_work() - work to detect USB type
+ * @work: Pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_detect_usb_type_work(struct work_struct *work)
+{
+ int ret;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, detect_usb_type_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if is
+ * connected by reading the status register
+ */
+ ret = ab8500_charger_detect_chargers(di, false);
+ if (ret < 0)
+ return;
+
+ if (!(ret & USB_PW_CONN)) {
+ dev_dbg(di->dev, "%s di->vbus_detected = false\n", __func__);
+ di->vbus_detected = false;
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ } else {
+ dev_dbg(di->dev, "%s di->vbus_detected = true\n", __func__);
+ di->vbus_detected = true;
+
+ if (is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = ab8500_charger_detect_usb_type(di);
+ if (!ret) {
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di,
+ di->usb_chg.psy);
+ }
+ } else {
+ /*
+ * For ABB cut2.0 and onwards we have an IRQ,
+ * USB_LINK_STATUS that will be triggered when the USB
+ * link status changes. The exception is USB connected
+ * during startup. Then we don't get a
+ * USB_LINK_STATUS IRQ
+ */
+ if (di->vbus_detected_start) {
+ di->vbus_detected_start = false;
+ ret = ab8500_charger_detect_usb_type(di);
+ if (!ret) {
+ ab8500_charger_set_usb_connected(di,
+ true);
+ ab8500_power_supply_changed(di,
+ di->usb_chg.psy);
+ }
+ }
+ }
+ }
+}
+
+/**
+ * ab8500_charger_usb_link_attach_work() - work to detect USB type
+ * @work: pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_attach_work(struct work_struct *work)
+{
+ struct ab8500_charger *di =
+ container_of(work, struct ab8500_charger, attach_work.work);
+ int ret;
+
+ /* Update maximum input current if USB enumeration is not detected */
+ if (!di->usb.charger_online) {
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ if (ret)
+ return;
+ }
+
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_usb_link_status_work() - work to detect USB type
+ * @work: pointer to the work_struct structure
+ *
+ * Detect the type of USB plugged
+ */
+static void ab8500_charger_usb_link_status_work(struct work_struct *work)
+{
+ int detected_chargers;
+ int ret;
+ u8 val;
+ u8 link_status;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, usb_link_status_work);
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if is
+ * connected by reading the status register
+ */
+ detected_chargers = ab8500_charger_detect_chargers(di, false);
+ if (detected_chargers < 0)
+ return;
+
+ /*
+ * Some chargers that breaks the USB spec is
+ * identified as invalid by AB8500 and it refuse
+ * to start the charging process. but by jumping
+ * thru a few hoops it can be forced to start.
+ */
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINE_STAT_REG, &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev, AB8500_USB,
+ AB8500_USB_LINK1_STAT_REG, &val);
+
+ if (ret >= 0)
+ dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val);
+ else
+ dev_dbg(di->dev, "Error reading USB link status\n");
+
+ if (is_ab8500(di->parent))
+ link_status = AB8500_USB_LINK_STATUS;
+ else
+ link_status = AB8505_USB_LINK_STATUS;
+
+ if (detected_chargers & USB_PW_CONN) {
+ if (((val & link_status) >> USB_LINK_STATUS_SHIFT) ==
+ USB_STAT_NOT_VALID_LINK &&
+ di->invalid_charger_detect_state == 0) {
+ dev_dbg(di->dev,
+ "Invalid charger detected, state= 0\n");
+ /*Enable charger*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_USBCH_CTRL1_REG,
+ USB_CH_ENA, USB_CH_ENA);
+ /*Enable charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
+ USB_CH_DET, USB_CH_DET);
+ di->invalid_charger_detect_state = 1;
+ /*exit and wait for new link status interrupt.*/
+ return;
+
+ }
+ if (di->invalid_charger_detect_state == 1) {
+ dev_dbg(di->dev,
+ "Invalid charger detected, state= 1\n");
+ /*Stop charger detection*/
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_CTRL2_REG,
+ USB_CH_DET, 0x00);
+ /*Check link status*/
+ if (is_ab8500(di->parent))
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINE_STAT_REG,
+ &val);
+ else
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_USB, AB8500_USB_LINK1_STAT_REG,
+ &val);
+
+ dev_dbg(di->dev, "USB link status= 0x%02x\n",
+ (val & link_status) >> USB_LINK_STATUS_SHIFT);
+ di->invalid_charger_detect_state = 2;
+ }
+ } else {
+ di->invalid_charger_detect_state = 0;
+ }
+
+ if (!(detected_chargers & USB_PW_CONN)) {
+ di->vbus_detected = false;
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ return;
+ }
+
+ dev_dbg(di->dev,"%s di->vbus_detected = true\n",__func__);
+ di->vbus_detected = true;
+ ret = ab8500_charger_read_usb_type(di);
+ if (ret) {
+ if (ret == -ENXIO) {
+ /* No valid charger type detected */
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ }
+ return;
+ }
+
+ if (di->usb_device_is_unrecognised) {
+ dev_dbg(di->dev,
+ "Potential Legacy Charger device. "
+ "Delay work for %d msec for USB enum "
+ "to finish",
+ WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else if (di->is_aca_rid == 1) {
+ /* Only wait once */
+ di->is_aca_rid++;
+ dev_dbg(di->dev,
+ "%s Wait %d msec for USB enum to finish",
+ __func__, WAIT_ACA_RID_ENUMERATION);
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ msecs_to_jiffies(WAIT_ACA_RID_ENUMERATION));
+ } else {
+ queue_delayed_work(di->charger_wq,
+ &di->attach_work,
+ 0);
+ }
+}
+
+static void ab8500_charger_usb_state_changed_work(struct work_struct *work)
+{
+ int ret;
+ unsigned long flags;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, usb_state_changed_work.work);
+
+ if (!di->vbus_detected) {
+ dev_dbg(di->dev,
+ "%s !di->vbus_detected\n",
+ __func__);
+ return;
+ }
+
+ spin_lock_irqsave(&di->usb_state.usb_lock, flags);
+ di->usb_state.state = di->usb_state.state_tmp;
+ di->usb_state.usb_current = di->usb_state.usb_current_tmp;
+ spin_unlock_irqrestore(&di->usb_state.usb_lock, flags);
+
+ dev_dbg(di->dev, "%s USB state: 0x%02x mA: %d\n",
+ __func__, di->usb_state.state, di->usb_state.usb_current);
+
+ switch (di->usb_state.state) {
+ case AB8500_BM_USB_STATE_RESET_HS:
+ case AB8500_BM_USB_STATE_RESET_FS:
+ case AB8500_BM_USB_STATE_SUSPEND:
+ case AB8500_BM_USB_STATE_MAX:
+ ab8500_charger_set_usb_connected(di, false);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ break;
+
+ case AB8500_BM_USB_STATE_RESUME:
+ /*
+ * when suspend->resume there should be delay
+ * of 1sec for enabling charging
+ */
+ msleep(1000);
+ /* Intentional fall through */
+ case AB8500_BM_USB_STATE_CONFIGURED:
+ /*
+ * USB is configured, enable charging with the charging
+ * input current obtained from USB driver
+ */
+ if (!ab8500_charger_get_usb_cur(di)) {
+ /* Update maximum input current */
+ ret = ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+ if (ret)
+ return;
+
+ ab8500_charger_set_usb_connected(di, true);
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ }
+ break;
+
+ default:
+ break;
+ };
+}
+
+/**
+ * ab8500_charger_check_usbchargernotok_work() - check USB chg not ok status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB charger Not OK status
+ */
+static void ab8500_charger_check_usbchargernotok_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+ bool prev_status;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_usbchgnotok_work.work);
+
+ /* Check if the status bit for usbchargernotok is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ prev_status = di->flags.usbchargernotok;
+
+ if (reg_value & VBUS_CH_NOK) {
+ di->flags.usbchargernotok = true;
+ /* Check again in 1sec */
+ queue_delayed_work(di->charger_wq,
+ &di->check_usbchgnotok_work, HZ);
+ } else {
+ di->flags.usbchargernotok = false;
+ di->flags.vbus_collapse = false;
+ }
+
+ if (prev_status != di->flags.usbchargernotok)
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_main_thermal_prot_work() - check main thermal status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the Main thermal prot status
+ */
+static void ab8500_charger_check_main_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_main_thermal_prot_work);
+
+ /* Check if the status bit for main_thermal_prot is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STATUS2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (reg_value & MAIN_CH_TH_PROT)
+ di->flags.main_thermal_prot = true;
+ else
+ di->flags.main_thermal_prot = false;
+
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+}
+
+/**
+ * ab8500_charger_check_usb_thermal_prot_work() - check usb thermal status
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the USB thermal prot status
+ */
+static void ab8500_charger_check_usb_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, check_usb_thermal_prot_work);
+
+ /* Check if the status bit for usb_thermal_prot is still active */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if (reg_value & USB_CH_TH_PROT)
+ di->flags.usb_thermal_prot = true;
+ else
+ di->flags.usb_thermal_prot = false;
+
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+}
+
+/**
+ * ab8500_charger_mainchunplugdet_handler() - main charger unplugged
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchunplugdet_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger unplugged\n");
+ queue_work(di->charger_wq, &di->ac_work);
+
+ cancel_delayed_work_sync(&di->ac_charger_attached_work);
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchplugdet_handler() - main charger plugged
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger plugged\n");
+ queue_work(di->charger_wq, &di->ac_work);
+
+ mutex_lock(&di->charger_attached_mutex);
+ mutex_unlock(&di->charger_attached_mutex);
+
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainextchnotok_handler() - main charger not ok
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainextchnotok_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Main charger not ok\n");
+ di->flags.mainextchnotok = true;
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotr_handler() - Die temp is above main charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp above Main charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_mainchthprotf_handler() - Die temp is below main charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_mainchthprotf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp ok for Main charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_main_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+static void ab8500_charger_vbus_drop_end_work(struct work_struct *work)
+{
+ struct ab8500_charger *di = container_of(work,
+ struct ab8500_charger, vbus_drop_end_work.work);
+ int ret, curr;
+ u8 reg_value;
+
+ di->flags.vbus_drop_end = false;
+
+ /* Reset the drop counter */
+ abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01);
+
+ if (is_ab8540(di->parent))
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8540_CH_USBCH_STAT3_REG, &reg_value);
+ else
+ ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_USBCH_STAT2_REG, &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s read failed\n", __func__);
+ return;
+ }
+
+ if (is_ab8540(di->parent))
+ curr = di->bm->chg_input_curr[
+ reg_value & AB8540_AUTO_VBUS_IN_CURR_MASK];
+ else
+ curr = di->bm->chg_input_curr[
+ reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT];
+
+ if (di->max_usb_in_curr.calculated_max != curr) {
+ /* USB source is collapsing */
+ di->max_usb_in_curr.calculated_max = curr;
+ dev_dbg(di->dev,
+ "VBUS input current limiting to %d mA\n",
+ di->max_usb_in_curr.calculated_max);
+ } else {
+ /*
+ * USB source can not give more than this amount.
+ * Taking more will collapse the source.
+ */
+ di->max_usb_in_curr.set_max =
+ di->max_usb_in_curr.calculated_max;
+ dev_dbg(di->dev,
+ "VBUS input current limited to %d mA\n",
+ di->max_usb_in_curr.set_max);
+ }
+
+ if (di->usb.charger_connected)
+ ab8500_charger_set_vbus_in_curr(di,
+ di->max_usb_in_curr.usb_type_max);
+}
+
+/**
+ * ab8500_charger_vbusdetf_handler() - VBUS falling detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ di->vbus_detected = false;
+ dev_dbg(di->dev, "VBUS falling detected\n");
+ queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusdetr_handler() - VBUS rising detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusdetr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ di->vbus_detected = true;
+ dev_dbg(di->dev, "VBUS rising detected\n");
+
+ queue_work(di->charger_wq, &di->detect_usb_type_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usblinkstatus_handler() - USB link status has changed
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usblinkstatus_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "USB link status changed\n");
+
+ queue_work(di->charger_wq, &di->usb_link_status_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotr_handler() - Die temp is above usb charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp above USB charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchthprotf_handler() - Die temp is below usb charger
+ * thermal protection threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchthprotf_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev,
+ "Die temp ok for USB charger thermal protection threshold\n");
+ queue_work(di->charger_wq, &di->check_usb_thermal_prot_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_usbchargernotokr_handler() - USB charger not ok detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_usbchargernotokr_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Not allowed USB charger detected\n");
+ queue_delayed_work(di->charger_wq, &di->check_usbchgnotok_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_chwdexp_handler() - Charger watchdog expired
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_chwdexp_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "Charger watchdog expired\n");
+
+ /*
+ * The charger that was online when the watchdog expired
+ * needs to be restarted for charging to start again
+ */
+ if (di->ac.charger_online) {
+ di->ac.wd_expired = true;
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+ }
+ if (di->usb.charger_online) {
+ di->usb.wd_expired = true;
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbuschdropend_handler() - VBUS drop removed
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS charger drop ended\n");
+ di->flags.vbus_drop_end = true;
+
+ /*
+ * VBUS might have dropped due to bad connection.
+ * Schedule a new input limit set to the value SW requests.
+ */
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work,
+ round_jiffies(VBUS_IN_CURR_LIM_RETRY_SET_TIME * HZ));
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_vbusovv_handler() - VBUS overvoltage detected
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_charger structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_charger_vbusovv_handler(int irq, void *_di)
+{
+ struct ab8500_charger *di = _di;
+
+ dev_dbg(di->dev, "VBUS overvoltage detected\n");
+ di->flags.vbus_ovv = true;
+ ab8500_power_supply_changed(di, di->usb_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->charger_wq, &di->check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_charger_ac_get_property() - get the ac/mains properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the ac/mains
+ * properties by reading the sysfs files.
+ * AC/Mains properties are online, present and voltage.
+ * online: ac/mains charging is in progress or not
+ * present: presence of the ac/mains
+ * voltage: AC/Mains voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_charger *di;
+ int ret;
+
+ di = to_ab8500_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->flags.mainextchnotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (di->ac.wd_expired || di->usb.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (di->flags.main_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = di->ac.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->ac.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ab8500_charger_get_ac_voltage(di);
+ if (ret >= 0)
+ di->ac.charger_voltage = ret;
+ /* On error, use previous value */
+ val->intval = di->ac.charger_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /*
+ * This property is used to indicate when CV mode is entered
+ * for the AC charger
+ */
+ di->ac.cv_active = ab8500_charger_ac_cv(di);
+ val->intval = di->ac.cv_active;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ab8500_charger_get_ac_current(di);
+ if (ret >= 0)
+ di->ac.charger_current = ret;
+ val->intval = di->ac.charger_current * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_usb_get_property() - get the usb properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the usb
+ * properties by reading the sysfs files.
+ * USB properties are online, present and voltage.
+ * online: usb charging is in progress or not
+ * present: presence of the usb
+ * voltage: vbus voltage
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_charger_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_charger *di;
+ int ret;
+
+ di = to_ab8500_charger_usb_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->flags.usbchargernotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (di->ac.wd_expired || di->usb.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (di->flags.usb_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (di->flags.vbus_ovv)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = di->usb.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->usb.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ab8500_charger_get_vbus_voltage(di);
+ if (ret >= 0)
+ di->usb.charger_voltage = ret;
+ val->intval = di->usb.charger_voltage * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ /*
+ * This property is used to indicate when CV mode is entered
+ * for the USB charger
+ */
+ di->usb.cv_active = ab8500_charger_usb_cv(di);
+ val->intval = di->usb.cv_active;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ab8500_charger_get_usb_current(di);
+ if (ret >= 0)
+ di->usb.charger_current = ret;
+ val->intval = di->usb.charger_current * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ /*
+ * This property is used to indicate when VBUS has collapsed
+ * due to too high output current from the USB charger
+ */
+ if (di->flags.vbus_collapse)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/**
+ * ab8500_charger_init_hw_registers() - Set up charger related registers
+ * @di: pointer to the ab8500_charger structure
+ *
+ * Set up charger OVV, watchdog and maximum voltage registers as well as
+ * charging of the backup battery
+ */
+static int ab8500_charger_init_hw_registers(struct ab8500_charger *di)
+{
+ int ret = 0;
+ u8 bup_vch_range = 0, vbup33_vrtcn = 0;
+
+ /* Setup maximum charger current and voltage for ABB cut2.0 */
+ if (!is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_CH_VOLT_LVL_MAX_REG, CH_VOL_LVL_4P6);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to set CH_VOLT_LVL_MAX_REG\n");
+ goto out;
+ }
+
+ if (is_ab8540(di->parent))
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
+ CH_OP_CUR_LVL_2P);
+ else
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG,
+ CH_OP_CUR_LVL_1P6);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to set CH_OPT_CRNTLVL_MAX_REG\n");
+ goto out;
+ }
+ }
+
+ if (is_ab9540_2p0(di->parent) || is_ab9540_3p0(di->parent)
+ || is_ab8505_2p0(di->parent) || is_ab8540(di->parent))
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_AUTO_IN_CURR_LIM_ENA,
+ VBUS_AUTO_IN_CURR_LIM_ENA);
+ else
+ /*
+ * VBUS OVV set to 6.3V and enable automatic current limitation
+ */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_USBCH_CTRL2_REG,
+ VBUS_OVV_SELECT_6P3V | VBUS_AUTO_IN_CURR_LIM_ENA);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to set automatic current limitation\n");
+ goto out;
+ }
+
+ /* Enable main watchdog in OTP */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_OTP_EMUL, AB8500_OTP_CONF_15, OTP_ENABLE_WD);
+ if (ret) {
+ dev_err(di->dev, "failed to enable main WD in OTP\n");
+ goto out;
+ }
+
+ /* Enable main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_ENA);
+ if (ret) {
+ dev_err(di->dev, "faile to enable main watchdog\n");
+ goto out;
+ }
+
+ /*
+ * Due to internal synchronisation, Enable and Kick watchdog bits
+ * cannot be enabled in a single write.
+ * A minimum delay of 2*32 kHz period (62.5µs) must be inserted
+ * between writing Enable then Kick bits.
+ */
+ udelay(63);
+
+ /* Kick main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG,
+ (MAIN_WDOG_ENA | MAIN_WDOG_KICK));
+ if (ret) {
+ dev_err(di->dev, "failed to kick main watchdog\n");
+ goto out;
+ }
+
+ /* Disable main watchdog */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_MAIN_WDOG_CTRL_REG, MAIN_WDOG_DIS);
+ if (ret) {
+ dev_err(di->dev, "failed to disable main watchdog\n");
+ goto out;
+ }
+
+ /* Set watchdog timeout */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CH_WD_TIMER_REG, WD_TIMER);
+ if (ret) {
+ dev_err(di->dev, "failed to set charger watchdog timeout\n");
+ goto out;
+ }
+
+ ret = ab8500_charger_led_en(di, false);
+ if (ret < 0) {
+ dev_err(di->dev, "failed to disable LED\n");
+ goto out;
+ }
+
+ /* Backup battery voltage and current */
+ if (di->bm->bkup_bat_v > BUP_VCH_SEL_3P1V)
+ bup_vch_range = BUP_VCH_RANGE;
+ if (di->bm->bkup_bat_v == BUP_VCH_SEL_3P3V)
+ vbup33_vrtcn = VBUP33_VRTCN;
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_BACKUP_CHG_REG,
+ (di->bm->bkup_bat_v & 0x3) | di->bm->bkup_bat_i);
+ if (ret) {
+ dev_err(di->dev, "failed to setup backup battery charging\n");
+ goto out;
+ }
+ if (is_ab8540(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_RTC,
+ AB8500_RTC_CTRL1_REG,
+ bup_vch_range | vbup33_vrtcn);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup backup battery charging\n");
+ goto out;
+ }
+ }
+
+ /* Enable backup battery charging */
+ abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_RTC, AB8500_RTC_CTRL_REG,
+ RTC_BUP_CH_ENA, RTC_BUP_CH_ENA);
+ if (ret < 0)
+ dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+ if (is_ab8540(di->parent)) {
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_MODE_REG,
+ BUS_VSYS_VOL_SELECT_MASK, BUS_VSYS_VOL_SELECT_3P6V);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup usb power path vsys voltage\n");
+ goto out;
+ }
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8540_USB_PP_CHR_REG,
+ BUS_PP_PRECHG_CURRENT_MASK, 0);
+ if (ret) {
+ dev_err(di->dev,
+ "failed to setup usb power path prechage current\n");
+ goto out;
+ }
+ }
+
+out:
+ return ret;
+}
+
+/*
+ * ab8500 charger driver interrupts and their respective isr
+ */
+static struct ab8500_charger_interrupts ab8500_charger_irq[] = {
+ {"MAIN_CH_UNPLUG_DET", ab8500_charger_mainchunplugdet_handler},
+ {"MAIN_CHARGE_PLUG_DET", ab8500_charger_mainchplugdet_handler},
+ {"MAIN_EXT_CH_NOT_OK", ab8500_charger_mainextchnotok_handler},
+ {"MAIN_CH_TH_PROT_R", ab8500_charger_mainchthprotr_handler},
+ {"MAIN_CH_TH_PROT_F", ab8500_charger_mainchthprotf_handler},
+ {"VBUS_DET_F", ab8500_charger_vbusdetf_handler},
+ {"VBUS_DET_R", ab8500_charger_vbusdetr_handler},
+ {"USB_LINK_STATUS", ab8500_charger_usblinkstatus_handler},
+ {"USB_CH_TH_PROT_R", ab8500_charger_usbchthprotr_handler},
+ {"USB_CH_TH_PROT_F", ab8500_charger_usbchthprotf_handler},
+ {"USB_CHARGER_NOT_OKR", ab8500_charger_usbchargernotokr_handler},
+ {"VBUS_OVV", ab8500_charger_vbusovv_handler},
+ {"CH_WD_EXP", ab8500_charger_chwdexp_handler},
+ {"VBUS_CH_DROP_END", ab8500_charger_vbuschdropend_handler},
+};
+
+static int ab8500_charger_usb_notifier_call(struct notifier_block *nb,
+ unsigned long event, void *power)
+{
+ struct ab8500_charger *di =
+ container_of(nb, struct ab8500_charger, nb);
+ enum ab8500_usb_state bm_usb_state;
+ unsigned mA = *((unsigned *)power);
+
+ if (!di)
+ return NOTIFY_DONE;
+
+ if (event != USB_EVENT_VBUS) {
+ dev_dbg(di->dev, "not a standard host, returning\n");
+ return NOTIFY_DONE;
+ }
+
+ /* TODO: State is fabricate here. See if charger really needs USB
+ * state or if mA is enough
+ */
+ if ((di->usb_state.usb_current == 2) && (mA > 2))
+ bm_usb_state = AB8500_BM_USB_STATE_RESUME;
+ else if (mA == 0)
+ bm_usb_state = AB8500_BM_USB_STATE_RESET_HS;
+ else if (mA == 2)
+ bm_usb_state = AB8500_BM_USB_STATE_SUSPEND;
+ else if (mA >= 8) /* 8, 100, 500 */
+ bm_usb_state = AB8500_BM_USB_STATE_CONFIGURED;
+ else /* Should never occur */
+ bm_usb_state = AB8500_BM_USB_STATE_RESET_FS;
+
+ dev_dbg(di->dev, "%s usb_state: 0x%02x mA: %d\n",
+ __func__, bm_usb_state, mA);
+
+ spin_lock(&di->usb_state.usb_lock);
+ di->usb_state.state_tmp = bm_usb_state;
+ di->usb_state.usb_current_tmp = mA;
+ spin_unlock(&di->usb_state.usb_lock);
+
+ /*
+ * wait for some time until you get updates from the usb stack
+ * and negotiations are completed
+ */
+ queue_delayed_work(di->charger_wq, &di->usb_state_changed_work, HZ/2);
+
+ return NOTIFY_OK;
+}
+
+#if defined(CONFIG_PM)
+static int ab8500_charger_resume(struct platform_device *pdev)
+{
+ int ret;
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+ if (di->ac_conn && is_ab8500_1p1_or_earlier(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER,
+ AB8500_CHARG_WD_CTRL, CHARG_WD_KICK);
+ if (ret)
+ dev_err(di->dev, "Failed to kick WD!\n");
+
+ /* If not already pending start a new timer */
+ queue_delayed_work(di->charger_wq, &di->kick_wd_work,
+ round_jiffies(WD_KICK_INTERVAL));
+ }
+
+ /* If we still have a HW failure, schedule a new check */
+ if (di->flags.mainextchnotok || di->flags.vbus_ovv) {
+ queue_delayed_work(di->charger_wq,
+ &di->check_hw_failure_work, 0);
+ }
+
+ if (di->flags.vbus_drop_end)
+ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, 0);
+
+ return 0;
+}
+
+static int ab8500_charger_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+
+ /* Cancel any pending jobs */
+ cancel_delayed_work(&di->check_hw_failure_work);
+ cancel_delayed_work(&di->vbus_drop_end_work);
+
+ flush_delayed_work(&di->attach_work);
+ flush_delayed_work(&di->usb_charger_attached_work);
+ flush_delayed_work(&di->ac_charger_attached_work);
+ flush_delayed_work(&di->check_usbchgnotok_work);
+ flush_delayed_work(&di->check_vbat_work);
+ flush_delayed_work(&di->kick_wd_work);
+
+ flush_work(&di->usb_link_status_work);
+ flush_work(&di->ac_work);
+ flush_work(&di->detect_usb_type_work);
+
+ if (atomic_read(&di->current_stepping_sessions))
+ return -EAGAIN;
+
+ return 0;
+}
+#else
+#define ab8500_charger_suspend NULL
+#define ab8500_charger_resume NULL
+#endif
+
+static struct notifier_block charger_nb = {
+ .notifier_call = ab8500_external_charger_prepare,
+};
+
+static int ab8500_charger_remove(struct platform_device *pdev)
+{
+ struct ab8500_charger *di = platform_get_drvdata(pdev);
+ int i, irq, ret;
+
+ /* Disable AC charging */
+ ab8500_charger_ac_en(&di->ac_chg, false, 0, 0);
+
+ /* Disable USB charging */
+ ab8500_charger_usb_en(&di->usb_chg, false, 0, 0);
+
+ /* Disable interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+
+ /* Backup battery voltage and current disable */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_RTC, AB8500_RTC_CTRL_REG, RTC_BUP_CH_ENA, 0);
+ if (ret < 0)
+ dev_err(di->dev, "%s mask and set failed\n", __func__);
+
+ usb_unregister_notifier(di->usb_phy, &di->nb);
+ usb_put_phy(di->usb_phy);
+
+ /* Delete the work queue */
+ destroy_workqueue(di->charger_wq);
+
+ /* Unregister external charger enable notifier */
+ if (!di->ac_chg.enabled)
+ blocking_notifier_chain_unregister(
+ &charger_notifier_list, &charger_nb);
+
+ flush_scheduled_work();
+ if (di->usb_chg.enabled)
+ power_supply_unregister(di->usb_chg.psy);
+
+ if (di->ac_chg.enabled && !di->ac_chg.external)
+ power_supply_unregister(di->ac_chg.psy);
+
+ return 0;
+}
+
+static char *supply_interface[] = {
+ "ab8500_chargalg",
+ "ab8500_fg",
+ "ab8500_btemp",
+};
+
+static const struct power_supply_desc ab8500_ac_chg_desc = {
+ .name = "ab8500_ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = ab8500_charger_ac_props,
+ .num_properties = ARRAY_SIZE(ab8500_charger_ac_props),
+ .get_property = ab8500_charger_ac_get_property,
+};
+
+static const struct power_supply_desc ab8500_usb_chg_desc = {
+ .name = "ab8500_usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = ab8500_charger_usb_props,
+ .num_properties = ARRAY_SIZE(ab8500_charger_usb_props),
+ .get_property = ab8500_charger_usb_get_property,
+};
+
+static int ab8500_charger_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
+ struct power_supply_config ac_psy_cfg = {}, usb_psy_cfg = {};
+ struct ab8500_charger *di;
+ int irq, i, charger_status, ret = 0, ch_stat;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "%s no mem for ab8500_charger\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
+ }
+ di->autopower_cfg = of_property_read_bool(np, "autopower_cfg");
+ } else
+ di->autopower_cfg = false;
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ /* initialize lock */
+ spin_lock_init(&di->usb_state.usb_lock);
+ mutex_init(&di->usb_ipt_crnt_lock);
+
+ di->autopower = false;
+ di->invalid_charger_detect_state = 0;
+
+ /* AC and USB supply config */
+ ac_psy_cfg.supplied_to = supply_interface;
+ ac_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
+ ac_psy_cfg.drv_data = &di->ac_chg;
+ usb_psy_cfg.supplied_to = supply_interface;
+ usb_psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
+ usb_psy_cfg.drv_data = &di->usb_chg;
+
+ /* AC supply */
+ /* ux500_charger sub-class */
+ di->ac_chg.ops.enable = &ab8500_charger_ac_en;
+ di->ac_chg.ops.check_enable = &ab8500_charger_ac_check_enable;
+ di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+ di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+ di->ac_chg.max_out_volt = ab8500_charger_voltage_map[
+ ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+ di->ac_chg.max_out_curr =
+ di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
+ di->ac_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->ac_chg.enabled = di->bm->ac_enabled;
+ di->ac_chg.external = false;
+
+ /*notifier for external charger enabling*/
+ if (!di->ac_chg.enabled)
+ blocking_notifier_chain_register(
+ &charger_notifier_list, &charger_nb);
+
+ /* USB supply */
+ /* ux500_charger sub-class */
+ di->usb_chg.ops.enable = &ab8500_charger_usb_en;
+ di->usb_chg.ops.check_enable = &ab8500_charger_usb_check_enable;
+ di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick;
+ di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current;
+ di->usb_chg.ops.pp_enable = &ab8540_charger_power_path_enable;
+ di->usb_chg.ops.pre_chg_enable = &ab8540_charger_usb_pre_chg_enable;
+ di->usb_chg.max_out_volt = ab8500_charger_voltage_map[
+ ARRAY_SIZE(ab8500_charger_voltage_map) - 1];
+ di->usb_chg.max_out_curr =
+ di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1];
+ di->usb_chg.wdt_refresh = CHG_WD_INTERVAL;
+ di->usb_chg.enabled = di->bm->usb_enabled;
+ di->usb_chg.external = false;
+ di->usb_chg.power_path = di->bm->usb_power_path;
+ di->usb_state.usb_current = -1;
+
+ /* Create a work queue for the charger */
+ di->charger_wq = alloc_ordered_workqueue("ab8500_charger_wq",
+ WQ_MEM_RECLAIM);
+ if (di->charger_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&di->charger_attached_mutex);
+
+ /* Init work for HW failure check */
+ INIT_DEFERRABLE_WORK(&di->check_hw_failure_work,
+ ab8500_charger_check_hw_failure_work);
+ INIT_DEFERRABLE_WORK(&di->check_usbchgnotok_work,
+ ab8500_charger_check_usbchargernotok_work);
+
+ INIT_DELAYED_WORK(&di->ac_charger_attached_work,
+ ab8500_charger_ac_attached_work);
+ INIT_DELAYED_WORK(&di->usb_charger_attached_work,
+ ab8500_charger_usb_attached_work);
+
+ /*
+ * For ABB revision 1.0 and 1.1 there is a bug in the watchdog
+ * logic. That means we have to continously kick the charger
+ * watchdog even when no charger is connected. This is only
+ * valid once the AC charger has been enabled. This is
+ * a bug that is not handled by the algorithm and the
+ * watchdog have to be kicked by the charger driver
+ * when the AC charger is disabled
+ */
+ INIT_DEFERRABLE_WORK(&di->kick_wd_work,
+ ab8500_charger_kick_watchdog_work);
+
+ INIT_DEFERRABLE_WORK(&di->check_vbat_work,
+ ab8500_charger_check_vbat_work);
+
+ INIT_DELAYED_WORK(&di->attach_work,
+ ab8500_charger_usb_link_attach_work);
+
+ INIT_DELAYED_WORK(&di->usb_state_changed_work,
+ ab8500_charger_usb_state_changed_work);
+
+ INIT_DELAYED_WORK(&di->vbus_drop_end_work,
+ ab8500_charger_vbus_drop_end_work);
+
+ /* Init work for charger detection */
+ INIT_WORK(&di->usb_link_status_work,
+ ab8500_charger_usb_link_status_work);
+ INIT_WORK(&di->ac_work, ab8500_charger_ac_work);
+ INIT_WORK(&di->detect_usb_type_work,
+ ab8500_charger_detect_usb_type_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&di->check_main_thermal_prot_work,
+ ab8500_charger_check_main_thermal_prot_work);
+ INIT_WORK(&di->check_usb_thermal_prot_work,
+ ab8500_charger_check_usb_thermal_prot_work);
+
+ /*
+ * VDD ADC supply needs to be enabled from this driver when there
+ * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+ * interrupts during charging
+ */
+ di->regu = devm_regulator_get(di->dev, "vddadc");
+ if (IS_ERR(di->regu)) {
+ ret = PTR_ERR(di->regu);
+ dev_err(di->dev, "failed to get vddadc regulator\n");
+ goto free_charger_wq;
+ }
+
+
+ /* Initialize OVV, and other registers */
+ ret = ab8500_charger_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize ABB registers\n");
+ goto free_charger_wq;
+ }
+
+ /* Register AC charger class */
+ if (di->ac_chg.enabled) {
+ di->ac_chg.psy = power_supply_register(di->dev,
+ &ab8500_ac_chg_desc,
+ &ac_psy_cfg);
+ if (IS_ERR(di->ac_chg.psy)) {
+ dev_err(di->dev, "failed to register AC charger\n");
+ ret = PTR_ERR(di->ac_chg.psy);
+ goto free_charger_wq;
+ }
+ }
+
+ /* Register USB charger class */
+ if (di->usb_chg.enabled) {
+ di->usb_chg.psy = power_supply_register(di->dev,
+ &ab8500_usb_chg_desc,
+ &usb_psy_cfg);
+ if (IS_ERR(di->usb_chg.psy)) {
+ dev_err(di->dev, "failed to register USB charger\n");
+ ret = PTR_ERR(di->usb_chg.psy);
+ goto free_ac;
+ }
+ }
+
+ di->usb_phy = usb_get_phy(USB_PHY_TYPE_USB2);
+ if (IS_ERR_OR_NULL(di->usb_phy)) {
+ dev_err(di->dev, "failed to get usb transceiver\n");
+ ret = -EINVAL;
+ goto free_usb;
+ }
+ di->nb.notifier_call = ab8500_charger_usb_notifier_call;
+ ret = usb_register_notifier(di->usb_phy, &di->nb);
+ if (ret) {
+ dev_err(di->dev, "failed to register usb notifier\n");
+ goto put_usb_phy;
+ }
+
+ /* Identify the connected charger types during startup */
+ charger_status = ab8500_charger_detect_chargers(di, true);
+ if (charger_status & AC_PW_CONN) {
+ di->ac.charger_connected = 1;
+ di->ac_conn = true;
+ ab8500_power_supply_changed(di, di->ac_chg.psy);
+ sysfs_notify(&di->ac_chg.psy->dev.kobj, NULL, "present");
+ }
+
+ if (charger_status & USB_PW_CONN) {
+ di->vbus_detected = true;
+ di->vbus_detected_start = true;
+ queue_work(di->charger_wq,
+ &di->detect_usb_type_work);
+ }
+
+ /* Register interrupts */
+ for (i = 0; i < ARRAY_SIZE(ab8500_charger_irq); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_charger_irq[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_charger_irq[i].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n"
+ , ab8500_charger_irq[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_charger_irq[i].name, irq, ret);
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ mutex_lock(&di->charger_attached_mutex);
+
+ ch_stat = ab8500_charger_detect_chargers(di, false);
+
+ if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) {
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->ac_charger_attached_work,
+ HZ);
+ }
+ if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) {
+ if (is_ab8500(di->parent))
+ queue_delayed_work(di->charger_wq,
+ &di->usb_charger_attached_work,
+ HZ);
+ }
+
+ mutex_unlock(&di->charger_attached_mutex);
+
+ return ret;
+
+free_irq:
+ usb_unregister_notifier(di->usb_phy, &di->nb);
+
+ /* We also have to free all successfully registered irqs */
+ for (i = i - 1; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, ab8500_charger_irq[i].name);
+ free_irq(irq, di);
+ }
+put_usb_phy:
+ usb_put_phy(di->usb_phy);
+free_usb:
+ if (di->usb_chg.enabled)
+ power_supply_unregister(di->usb_chg.psy);
+free_ac:
+ if (di->ac_chg.enabled)
+ power_supply_unregister(di->ac_chg.psy);
+free_charger_wq:
+ destroy_workqueue(di->charger_wq);
+ return ret;
+}
+
+static const struct of_device_id ab8500_charger_match[] = {
+ { .compatible = "stericsson,ab8500-charger", },
+ { },
+};
+
+static struct platform_driver ab8500_charger_driver = {
+ .probe = ab8500_charger_probe,
+ .remove = ab8500_charger_remove,
+ .suspend = ab8500_charger_suspend,
+ .resume = ab8500_charger_resume,
+ .driver = {
+ .name = "ab8500-charger",
+ .of_match_table = ab8500_charger_match,
+ },
+};
+
+static int __init ab8500_charger_init(void)
+{
+ return platform_driver_register(&ab8500_charger_driver);
+}
+
+static void __exit ab8500_charger_exit(void)
+{
+ platform_driver_unregister(&ab8500_charger_driver);
+}
+
+subsys_initcall_sync(ab8500_charger_init);
+module_exit(ab8500_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski, Arun R Murthy");
+MODULE_ALIAS("platform:ab8500-charger");
+MODULE_DESCRIPTION("AB8500 charger management driver");
diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c
new file mode 100644
index 0000000..2199f67
--- /dev/null
+++ b/drivers/power/supply/ab8500_fg.c
@@ -0,0 +1,3267 @@
+/*
+ * Copyright (C) ST-Ericsson AB 2012
+ *
+ * Main and Back-up battery management driver.
+ *
+ * Note: Backup battery management is required in case of Li-Ion battery and not
+ * for capacitive battery. HREF boards have capacitive battery and hence backup
+ * battery management is not used and the supported code is available in this
+ * driver.
+ *
+ * License Terms: GNU General Public License v2
+ * Author:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/kobject.h>
+#include <linux/slab.h>
+#include <linux/delay.h>
+#include <linux/time.h>
+#include <linux/time64.h>
+#include <linux/of.h>
+#include <linux/completion.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ab8500-gpadc.h>
+#include <linux/kernel.h>
+
+#define MILLI_TO_MICRO 1000
+#define FG_LSB_IN_MA 1627
+#define QLSB_NANO_AMP_HOURS_X10 1071
+#define INS_CURR_TIMEOUT (3 * HZ)
+
+#define SEC_TO_SAMPLE(S) (S * 4)
+
+#define NBR_AVG_SAMPLES 20
+
+#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */
+
+#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */
+#define BATT_OK_MIN 2360 /* mV */
+#define BATT_OK_INCREMENT 50 /* mV */
+#define BATT_OK_MAX_NR_INCREMENTS 0xE
+
+/* FG constants */
+#define BATT_OVV 0x01
+
+#define interpolate(x, x1, y1, x2, y2) \
+ ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1))));
+
+/**
+ * struct ab8500_fg_interrupts - ab8500 fg interupts
+ * @name: name of the interrupt
+ * @isr function pointer to the isr
+ */
+struct ab8500_fg_interrupts {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+enum ab8500_fg_discharge_state {
+ AB8500_FG_DISCHARGE_INIT,
+ AB8500_FG_DISCHARGE_INITMEASURING,
+ AB8500_FG_DISCHARGE_INIT_RECOVERY,
+ AB8500_FG_DISCHARGE_RECOVERY,
+ AB8500_FG_DISCHARGE_READOUT_INIT,
+ AB8500_FG_DISCHARGE_READOUT,
+ AB8500_FG_DISCHARGE_WAKEUP,
+};
+
+static char *discharge_state[] = {
+ "DISCHARGE_INIT",
+ "DISCHARGE_INITMEASURING",
+ "DISCHARGE_INIT_RECOVERY",
+ "DISCHARGE_RECOVERY",
+ "DISCHARGE_READOUT_INIT",
+ "DISCHARGE_READOUT",
+ "DISCHARGE_WAKEUP",
+};
+
+enum ab8500_fg_charge_state {
+ AB8500_FG_CHARGE_INIT,
+ AB8500_FG_CHARGE_READOUT,
+};
+
+static char *charge_state[] = {
+ "CHARGE_INIT",
+ "CHARGE_READOUT",
+};
+
+enum ab8500_fg_calibration_state {
+ AB8500_FG_CALIB_INIT,
+ AB8500_FG_CALIB_WAIT,
+ AB8500_FG_CALIB_END,
+};
+
+struct ab8500_fg_avg_cap {
+ int avg;
+ int samples[NBR_AVG_SAMPLES];
+ time64_t time_stamps[NBR_AVG_SAMPLES];
+ int pos;
+ int nbr_samples;
+ int sum;
+};
+
+struct ab8500_fg_cap_scaling {
+ bool enable;
+ int cap_to_scale[2];
+ int disable_cap_level;
+ int scaled_cap;
+};
+
+struct ab8500_fg_battery_capacity {
+ int max_mah_design;
+ int max_mah;
+ int mah;
+ int permille;
+ int level;
+ int prev_mah;
+ int prev_percent;
+ int prev_level;
+ int user_mah;
+ struct ab8500_fg_cap_scaling cap_scale;
+};
+
+struct ab8500_fg_flags {
+ bool fg_enabled;
+ bool conv_done;
+ bool charging;
+ bool fully_charged;
+ bool force_full;
+ bool low_bat_delay;
+ bool low_bat;
+ bool bat_ovv;
+ bool batt_unknown;
+ bool calibrate;
+ bool user_cap;
+ bool batt_id_received;
+};
+
+struct inst_curr_result_list {
+ struct list_head list;
+ int *result;
+};
+
+/**
+ * struct ab8500_fg - ab8500 FG device information
+ * @dev: Pointer to the structure device
+ * @node: a list of AB8500 FGs, hence prepared for reentrance
+ * @irq holds the CCEOC interrupt number
+ * @vbat: Battery voltage in mV
+ * @vbat_nom: Nominal battery voltage in mV
+ * @inst_curr: Instantenous battery current in mA
+ * @avg_curr: Average battery current in mA
+ * @bat_temp battery temperature
+ * @fg_samples: Number of samples used in the FG accumulation
+ * @accu_charge: Accumulated charge from the last conversion
+ * @recovery_cnt: Counter for recovery mode
+ * @high_curr_cnt: Counter for high current mode
+ * @init_cnt: Counter for init mode
+ * @low_bat_cnt Counter for number of consecutive low battery measures
+ * @nbr_cceoc_irq_cnt Counter for number of CCEOC irqs received since enabled
+ * @recovery_needed: Indicate if recovery is needed
+ * @high_curr_mode: Indicate if we're in high current mode
+ * @init_capacity: Indicate if initial capacity measuring should be done
+ * @turn_off_fg: True if fg was off before current measurement
+ * @calib_state State during offset calibration
+ * @discharge_state: Current discharge state
+ * @charge_state: Current charge state
+ * @ab8500_fg_started Completion struct used for the instant current start
+ * @ab8500_fg_complete Completion struct used for the instant current reading
+ * @flags: Structure for information about events triggered
+ * @bat_cap: Structure for battery capacity specific parameters
+ * @avg_cap: Average capacity filter
+ * @parent: Pointer to the struct ab8500
+ * @gpadc: Pointer to the struct gpadc
+ * @bm: Platform specific battery management information
+ * @fg_psy: Structure that holds the FG specific battery properties
+ * @fg_wq: Work queue for running the FG algorithm
+ * @fg_periodic_work: Work to run the FG algorithm periodically
+ * @fg_low_bat_work: Work to check low bat condition
+ * @fg_reinit_work Work used to reset and reinitialise the FG algorithm
+ * @fg_work: Work to run the FG algorithm instantly
+ * @fg_acc_cur_work: Work to read the FG accumulator
+ * @fg_check_hw_failure_work: Work for checking HW state
+ * @cc_lock: Mutex for locking the CC
+ * @fg_kobject: Structure of type kobject
+ */
+struct ab8500_fg {
+ struct device *dev;
+ struct list_head node;
+ int irq;
+ int vbat;
+ int vbat_nom;
+ int inst_curr;
+ int avg_curr;
+ int bat_temp;
+ int fg_samples;
+ int accu_charge;
+ int recovery_cnt;
+ int high_curr_cnt;
+ int init_cnt;
+ int low_bat_cnt;
+ int nbr_cceoc_irq_cnt;
+ bool recovery_needed;
+ bool high_curr_mode;
+ bool init_capacity;
+ bool turn_off_fg;
+ enum ab8500_fg_calibration_state calib_state;
+ enum ab8500_fg_discharge_state discharge_state;
+ enum ab8500_fg_charge_state charge_state;
+ struct completion ab8500_fg_started;
+ struct completion ab8500_fg_complete;
+ struct ab8500_fg_flags flags;
+ struct ab8500_fg_battery_capacity bat_cap;
+ struct ab8500_fg_avg_cap avg_cap;
+ struct ab8500 *parent;
+ struct ab8500_gpadc *gpadc;
+ struct abx500_bm_data *bm;
+ struct power_supply *fg_psy;
+ struct workqueue_struct *fg_wq;
+ struct delayed_work fg_periodic_work;
+ struct delayed_work fg_low_bat_work;
+ struct delayed_work fg_reinit_work;
+ struct work_struct fg_work;
+ struct work_struct fg_acc_cur_work;
+ struct delayed_work fg_check_hw_failure_work;
+ struct mutex cc_lock;
+ struct kobject fg_kobject;
+};
+static LIST_HEAD(ab8500_fg_list);
+
+/**
+ * ab8500_fg_get() - returns a reference to the primary AB8500 fuel gauge
+ * (i.e. the first fuel gauge in the instance list)
+ */
+struct ab8500_fg *ab8500_fg_get(void)
+{
+ return list_first_entry_or_null(&ab8500_fg_list, struct ab8500_fg,
+ node);
+}
+
+/* Main battery properties */
+static enum power_supply_property ab8500_fg_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+};
+
+/*
+ * This array maps the raw hex value to lowbat voltage used by the AB8500
+ * Values taken from the UM0836
+ */
+static int ab8500_fg_lowbat_voltage_map[] = {
+ 2300 ,
+ 2325 ,
+ 2350 ,
+ 2375 ,
+ 2400 ,
+ 2425 ,
+ 2450 ,
+ 2475 ,
+ 2500 ,
+ 2525 ,
+ 2550 ,
+ 2575 ,
+ 2600 ,
+ 2625 ,
+ 2650 ,
+ 2675 ,
+ 2700 ,
+ 2725 ,
+ 2750 ,
+ 2775 ,
+ 2800 ,
+ 2825 ,
+ 2850 ,
+ 2875 ,
+ 2900 ,
+ 2925 ,
+ 2950 ,
+ 2975 ,
+ 3000 ,
+ 3025 ,
+ 3050 ,
+ 3075 ,
+ 3100 ,
+ 3125 ,
+ 3150 ,
+ 3175 ,
+ 3200 ,
+ 3225 ,
+ 3250 ,
+ 3275 ,
+ 3300 ,
+ 3325 ,
+ 3350 ,
+ 3375 ,
+ 3400 ,
+ 3425 ,
+ 3450 ,
+ 3475 ,
+ 3500 ,
+ 3525 ,
+ 3550 ,
+ 3575 ,
+ 3600 ,
+ 3625 ,
+ 3650 ,
+ 3675 ,
+ 3700 ,
+ 3725 ,
+ 3750 ,
+ 3775 ,
+ 3800 ,
+ 3825 ,
+ 3850 ,
+ 3850 ,
+};
+
+static u8 ab8500_volt_to_regval(int voltage)
+{
+ int i;
+
+ if (voltage < ab8500_fg_lowbat_voltage_map[0])
+ return 0;
+
+ for (i = 0; i < ARRAY_SIZE(ab8500_fg_lowbat_voltage_map); i++) {
+ if (voltage < ab8500_fg_lowbat_voltage_map[i])
+ return (u8) i - 1;
+ }
+
+ /* If not captured above, return index of last element */
+ return (u8) ARRAY_SIZE(ab8500_fg_lowbat_voltage_map) - 1;
+}
+
+/**
+ * ab8500_fg_is_low_curr() - Low or high current mode
+ * @di: pointer to the ab8500_fg structure
+ * @curr: the current to base or our decision on
+ *
+ * Low current mode if the current consumption is below a certain threshold
+ */
+static int ab8500_fg_is_low_curr(struct ab8500_fg *di, int curr)
+{
+ /*
+ * We want to know if we're in low current mode
+ */
+ if (curr > -di->bm->fg_params->high_curr_threshold)
+ return true;
+ else
+ return false;
+}
+
+/**
+ * ab8500_fg_add_cap_sample() - Add capacity to average filter
+ * @di: pointer to the ab8500_fg structure
+ * @sample: the capacity in mAh to add to the filter
+ *
+ * A capacity is added to the filter and a new mean capacity is calculated and
+ * returned
+ */
+static int ab8500_fg_add_cap_sample(struct ab8500_fg *di, int sample)
+{
+ struct timespec64 ts64;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ getnstimeofday64(&ts64);
+
+ do {
+ avg->sum += sample - avg->samples[avg->pos];
+ avg->samples[avg->pos] = sample;
+ avg->time_stamps[avg->pos] = ts64.tv_sec;
+ avg->pos++;
+
+ if (avg->pos == NBR_AVG_SAMPLES)
+ avg->pos = 0;
+
+ if (avg->nbr_samples < NBR_AVG_SAMPLES)
+ avg->nbr_samples++;
+
+ /*
+ * Check the time stamp for each sample. If too old,
+ * replace with latest sample
+ */
+ } while (ts64.tv_sec - VALID_CAPACITY_SEC > avg->time_stamps[avg->pos]);
+
+ avg->avg = avg->sum / avg->nbr_samples;
+
+ return avg->avg;
+}
+
+/**
+ * ab8500_fg_clear_cap_samples() - Clear average filter
+ * @di: pointer to the ab8500_fg structure
+ *
+ * The capacity filter is is reset to zero.
+ */
+static void ab8500_fg_clear_cap_samples(struct ab8500_fg *di)
+{
+ int i;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ avg->pos = 0;
+ avg->nbr_samples = 0;
+ avg->sum = 0;
+ avg->avg = 0;
+
+ for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+ avg->samples[i] = 0;
+ avg->time_stamps[i] = 0;
+ }
+}
+
+/**
+ * ab8500_fg_fill_cap_sample() - Fill average filter
+ * @di: pointer to the ab8500_fg structure
+ * @sample: the capacity in mAh to fill the filter with
+ *
+ * The capacity filter is filled with a capacity in mAh
+ */
+static void ab8500_fg_fill_cap_sample(struct ab8500_fg *di, int sample)
+{
+ int i;
+ struct timespec64 ts64;
+ struct ab8500_fg_avg_cap *avg = &di->avg_cap;
+
+ getnstimeofday64(&ts64);
+
+ for (i = 0; i < NBR_AVG_SAMPLES; i++) {
+ avg->samples[i] = sample;
+ avg->time_stamps[i] = ts64.tv_sec;
+ }
+
+ avg->pos = 0;
+ avg->nbr_samples = NBR_AVG_SAMPLES;
+ avg->sum = sample * NBR_AVG_SAMPLES;
+ avg->avg = sample;
+}
+
+/**
+ * ab8500_fg_coulomb_counter() - enable coulomb counter
+ * @di: pointer to the ab8500_fg structure
+ * @enable: enable/disable
+ *
+ * Enable/Disable coulomb counter.
+ * On failure returns negative value.
+ */
+static int ab8500_fg_coulomb_counter(struct ab8500_fg *di, bool enable)
+{
+ int ret = 0;
+ mutex_lock(&di->cc_lock);
+ if (enable) {
+ /* To be able to reprogram the number of samples, we have to
+ * first stop the CC and then enable it again */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0x00);
+ if (ret)
+ goto cc_err;
+
+ /* Program the samples */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+ di->fg_samples);
+ if (ret)
+ goto cc_err;
+
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret)
+ goto cc_err;
+
+ di->flags.fg_enabled = true;
+ } else {
+ /* Clear any pending read requests */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ (RESET_ACCU | READ_REQ), 0);
+ if (ret)
+ goto cc_err;
+
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU_CTRL, 0);
+ if (ret)
+ goto cc_err;
+
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0);
+ if (ret)
+ goto cc_err;
+
+ di->flags.fg_enabled = false;
+
+ }
+ dev_dbg(di->dev, " CC enabled: %d Samples: %d\n",
+ enable, di->fg_samples);
+
+ mutex_unlock(&di->cc_lock);
+
+ return ret;
+cc_err:
+ dev_err(di->dev, "%s Enabling coulomb counter failed\n", __func__);
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_start() - start battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 0 or error code
+ * Note: This is part "one" and has to be called before
+ * ab8500_fg_inst_curr_finalize()
+ */
+int ab8500_fg_inst_curr_start(struct ab8500_fg *di)
+{
+ u8 reg_val;
+ int ret;
+
+ mutex_lock(&di->cc_lock);
+
+ di->nbr_cceoc_irq_cnt = 0;
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, &reg_val);
+ if (ret < 0)
+ goto fail;
+
+ if (!(reg_val & CC_PWR_UP_ENA)) {
+ dev_dbg(di->dev, "%s Enable FG\n", __func__);
+ di->turn_off_fg = true;
+
+ /* Program the samples */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_NCOV_ACCU,
+ SEC_TO_SAMPLE(10));
+ if (ret)
+ goto fail;
+
+ /* Start the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG,
+ (CC_DEEP_SLEEP_ENA | CC_PWR_UP_ENA));
+ if (ret)
+ goto fail;
+ } else {
+ di->turn_off_fg = false;
+ }
+
+ /* Return and WFI */
+ reinit_completion(&di->ab8500_fg_started);
+ reinit_completion(&di->ab8500_fg_complete);
+ enable_irq(di->irq);
+
+ /* Note: cc_lock is still locked */
+ return 0;
+fail:
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_started() - check if fg conversion has started
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 1 if conversion started, 0 if still waiting
+ */
+int ab8500_fg_inst_curr_started(struct ab8500_fg *di)
+{
+ return completion_done(&di->ab8500_fg_started);
+}
+
+/**
+ * ab8500_fg_inst_curr_done() - check if fg conversion is done
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns 1 if conversion done, 0 if still waiting
+ */
+int ab8500_fg_inst_curr_done(struct ab8500_fg *di)
+{
+ return completion_done(&di->ab8500_fg_complete);
+}
+
+/**
+ * ab8500_fg_inst_curr_finalize() - battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ * @res: battery instantenous current(on success)
+ *
+ * Returns 0 or an error code
+ * Note: This is part "two" and has to be called at earliest 250 ms
+ * after ab8500_fg_inst_curr_start()
+ */
+int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res)
+{
+ u8 low, high;
+ int val;
+ int ret;
+ unsigned long timeout;
+
+ if (!completion_done(&di->ab8500_fg_complete)) {
+ timeout = wait_for_completion_timeout(
+ &di->ab8500_fg_complete,
+ INS_CURR_TIMEOUT);
+ dev_dbg(di->dev, "Finalize time: %d ms\n",
+ jiffies_to_msecs(INS_CURR_TIMEOUT - timeout));
+ if (!timeout) {
+ ret = -ETIME;
+ disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
+ dev_err(di->dev, "completion timed out [%d]\n",
+ __LINE__);
+ goto fail;
+ }
+ }
+
+ disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ READ_REQ, READ_REQ);
+
+ /* 100uS between read request and read is needed */
+ usleep_range(100, 100);
+
+ /* Read CC Sample conversion value Low and high */
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_SMPL_CNVL_REG, &low);
+ if (ret < 0)
+ goto fail;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_SMPL_CNVH_REG, &high);
+ if (ret < 0)
+ goto fail;
+
+ /*
+ * negative value for Discharging
+ * convert 2's compliment into decimal
+ */
+ if (high & 0x10)
+ val = (low | (high << 8) | 0xFFFFE000);
+ else
+ val = (low | (high << 8));
+
+ /*
+ * Convert to unit value in mA
+ * Full scale input voltage is
+ * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA
+ * Given a 250ms conversion cycle time the LSB corresponds
+ * to 107.1 nAh. Convert to current by dividing by the conversion
+ * time in hours (250ms = 1 / (3600 * 4)h)
+ * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ */
+ val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) /
+ (1000 * di->bm->fg_res);
+
+ if (di->turn_off_fg) {
+ dev_dbg(di->dev, "%s Disable FG\n", __func__);
+
+ /* Clear any pending read requests */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG, 0);
+ if (ret)
+ goto fail;
+
+ /* Stop the CC */
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8500_RTC_CC_CONF_REG, 0);
+ if (ret)
+ goto fail;
+ }
+ mutex_unlock(&di->cc_lock);
+ (*res) = val;
+
+ return 0;
+fail:
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_inst_curr_blocking() - battery instantaneous current
+ * @di: pointer to the ab8500_fg structure
+ * @res: battery instantenous current(on success)
+ *
+ * Returns 0 else error code
+ */
+int ab8500_fg_inst_curr_blocking(struct ab8500_fg *di)
+{
+ int ret;
+ unsigned long timeout;
+ int res = 0;
+
+ ret = ab8500_fg_inst_curr_start(di);
+ if (ret) {
+ dev_err(di->dev, "Failed to initialize fg_inst\n");
+ return 0;
+ }
+
+ /* Wait for CC to actually start */
+ if (!completion_done(&di->ab8500_fg_started)) {
+ timeout = wait_for_completion_timeout(
+ &di->ab8500_fg_started,
+ INS_CURR_TIMEOUT);
+ dev_dbg(di->dev, "Start time: %d ms\n",
+ jiffies_to_msecs(INS_CURR_TIMEOUT - timeout));
+ if (!timeout) {
+ ret = -ETIME;
+ dev_err(di->dev, "completion timed out [%d]\n",
+ __LINE__);
+ goto fail;
+ }
+ }
+
+ ret = ab8500_fg_inst_curr_finalize(di, &res);
+ if (ret) {
+ dev_err(di->dev, "Failed to finalize fg_inst\n");
+ return 0;
+ }
+
+ dev_dbg(di->dev, "%s instant current: %d", __func__, res);
+ return res;
+fail:
+ disable_irq(di->irq);
+ mutex_unlock(&di->cc_lock);
+ return ret;
+}
+
+/**
+ * ab8500_fg_acc_cur_work() - average battery current
+ * @work: pointer to the work_struct structure
+ *
+ * Updated the average battery current obtained from the
+ * coulomb counter.
+ */
+static void ab8500_fg_acc_cur_work(struct work_struct *work)
+{
+ int val;
+ int ret;
+ u8 low, med, high;
+
+ struct ab8500_fg *di = container_of(work,
+ struct ab8500_fg, fg_acc_cur_work);
+
+ mutex_lock(&di->cc_lock);
+ ret = abx500_set_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_CTRL, RD_NCONV_ACCU_REQ);
+ if (ret)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_LOW, &low);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_MED, &med);
+ if (ret < 0)
+ goto exit;
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_GAS_GAUGE,
+ AB8500_GASG_CC_NCOV_ACCU_HIGH, &high);
+ if (ret < 0)
+ goto exit;
+
+ /* Check for sign bit in case of negative value, 2's compliment */
+ if (high & 0x10)
+ val = (low | (med << 8) | (high << 16) | 0xFFE00000);
+ else
+ val = (low | (med << 8) | (high << 16));
+
+ /*
+ * Convert to uAh
+ * Given a 250ms conversion cycle time the LSB corresponds
+ * to 112.9 nAh.
+ * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm
+ */
+ di->accu_charge = (val * QLSB_NANO_AMP_HOURS_X10) /
+ (100 * di->bm->fg_res);
+
+ /*
+ * Convert to unit value in mA
+ * by dividing by the conversion
+ * time in hours (= samples / (3600 * 4)h)
+ * and multiply with 1000
+ */
+ di->avg_curr = (val * QLSB_NANO_AMP_HOURS_X10 * 36) /
+ (1000 * di->bm->fg_res * (di->fg_samples / 4));
+
+ di->flags.conv_done = true;
+
+ mutex_unlock(&di->cc_lock);
+
+ queue_work(di->fg_wq, &di->fg_work);
+
+ dev_dbg(di->dev, "fg_res: %d, fg_samples: %d, gasg: %d, accu_charge: %d \n",
+ di->bm->fg_res, di->fg_samples, val, di->accu_charge);
+ return;
+exit:
+ dev_err(di->dev,
+ "Failed to read or write gas gauge registers\n");
+ mutex_unlock(&di->cc_lock);
+ queue_work(di->fg_wq, &di->fg_work);
+}
+
+/**
+ * ab8500_fg_bat_voltage() - get battery voltage
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery voltage(on success) else error code
+ */
+static int ab8500_fg_bat_voltage(struct ab8500_fg *di)
+{
+ int vbat;
+ static int prev;
+
+ vbat = ab8500_gpadc_convert(di->gpadc, MAIN_BAT_V);
+ if (vbat < 0) {
+ dev_err(di->dev,
+ "%s gpadc conversion failed, using previous value\n",
+ __func__);
+ return prev;
+ }
+
+ prev = vbat;
+ return vbat;
+}
+
+/**
+ * ab8500_fg_volt_to_capacity() - Voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ * @voltage: The voltage to convert to a capacity
+ *
+ * Returns battery capacity in per mille based on voltage
+ */
+static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage)
+{
+ int i, tbl_size;
+ const struct abx500_v_to_cap *tbl;
+ int cap = 0;
+
+ tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl,
+ tbl_size = di->bm->bat_type[di->bm->batt_id].n_v_cap_tbl_elements;
+
+ for (i = 0; i < tbl_size; ++i) {
+ if (voltage > tbl[i].voltage)
+ break;
+ }
+
+ if ((i > 0) && (i < tbl_size)) {
+ cap = interpolate(voltage,
+ tbl[i].voltage,
+ tbl[i].capacity * 10,
+ tbl[i-1].voltage,
+ tbl[i-1].capacity * 10);
+ } else if (i == 0) {
+ cap = 1000;
+ } else {
+ cap = 0;
+ }
+
+ dev_dbg(di->dev, "%s Vbat: %d, Cap: %d per mille",
+ __func__, voltage, cap);
+
+ return cap;
+}
+
+/**
+ * ab8500_fg_uncomp_volt_to_capacity() - Uncompensated voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is not compensated
+ * for the voltage drop due to the load
+ */
+static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di)
+{
+ di->vbat = ab8500_fg_bat_voltage(di);
+ return ab8500_fg_volt_to_capacity(di, di->vbat);
+}
+
+/**
+ * ab8500_fg_battery_resistance() - Returns the battery inner resistance
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery inner resistance added with the fuel gauge resistor value
+ * to get the total resistance in the whole link from gnd to bat+ node.
+ */
+static int ab8500_fg_battery_resistance(struct ab8500_fg *di)
+{
+ int i, tbl_size;
+ const struct batres_vs_temp *tbl;
+ int resist = 0;
+
+ tbl = di->bm->bat_type[di->bm->batt_id].batres_tbl;
+ tbl_size = di->bm->bat_type[di->bm->batt_id].n_batres_tbl_elements;
+
+ for (i = 0; i < tbl_size; ++i) {
+ if (di->bat_temp / 10 > tbl[i].temp)
+ break;
+ }
+
+ if ((i > 0) && (i < tbl_size)) {
+ resist = interpolate(di->bat_temp / 10,
+ tbl[i].temp,
+ tbl[i].resist,
+ tbl[i-1].temp,
+ tbl[i-1].resist);
+ } else if (i == 0) {
+ resist = tbl[0].resist;
+ } else {
+ resist = tbl[tbl_size - 1].resist;
+ }
+
+ dev_dbg(di->dev, "%s Temp: %d battery internal resistance: %d"
+ " fg resistance %d, total: %d (mOhm)\n",
+ __func__, di->bat_temp, resist, di->bm->fg_res / 10,
+ (di->bm->fg_res / 10) + resist);
+
+ /* fg_res variable is in 0.1mOhm */
+ resist += di->bm->fg_res / 10;
+
+ return resist;
+}
+
+/**
+ * ab8500_fg_load_comp_volt_to_capacity() - Load compensated voltage based capacity
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Returns battery capacity based on battery voltage that is load compensated
+ * for the voltage drop
+ */
+static int ab8500_fg_load_comp_volt_to_capacity(struct ab8500_fg *di)
+{
+ int vbat_comp, res;
+ int i = 0;
+ int vbat = 0;
+
+ ab8500_fg_inst_curr_start(di);
+
+ do {
+ vbat += ab8500_fg_bat_voltage(di);
+ i++;
+ usleep_range(5000, 6000);
+ } while (!ab8500_fg_inst_curr_done(di));
+
+ ab8500_fg_inst_curr_finalize(di, &di->inst_curr);
+
+ di->vbat = vbat / i;
+ res = ab8500_fg_battery_resistance(di);
+
+ /* Use Ohms law to get the load compensated voltage */
+ vbat_comp = di->vbat - (di->inst_curr * res) / 1000;
+
+ dev_dbg(di->dev, "%s Measured Vbat: %dmV,Compensated Vbat %dmV, "
+ "R: %dmOhm, Current: %dmA Vbat Samples: %d\n",
+ __func__, di->vbat, vbat_comp, res, di->inst_curr, i);
+
+ return ab8500_fg_volt_to_capacity(di, vbat_comp);
+}
+
+/**
+ * ab8500_fg_convert_mah_to_permille() - Capacity in mAh to permille
+ * @di: pointer to the ab8500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in permille
+ */
+static int ab8500_fg_convert_mah_to_permille(struct ab8500_fg *di, int cap_mah)
+{
+ return (cap_mah * 1000) / di->bat_cap.max_mah_design;
+}
+
+/**
+ * ab8500_fg_convert_permille_to_mah() - Capacity in permille to mAh
+ * @di: pointer to the ab8500_fg structure
+ * @cap_pm: capacity in permille
+ *
+ * Converts capacity in permille to capacity in mAh
+ */
+static int ab8500_fg_convert_permille_to_mah(struct ab8500_fg *di, int cap_pm)
+{
+ return cap_pm * di->bat_cap.max_mah_design / 1000;
+}
+
+/**
+ * ab8500_fg_convert_mah_to_uwh() - Capacity in mAh to uWh
+ * @di: pointer to the ab8500_fg structure
+ * @cap_mah: capacity in mAh
+ *
+ * Converts capacity in mAh to capacity in uWh
+ */
+static int ab8500_fg_convert_mah_to_uwh(struct ab8500_fg *di, int cap_mah)
+{
+ u64 div_res;
+ u32 div_rem;
+
+ div_res = ((u64) cap_mah) * ((u64) di->vbat_nom);
+ div_rem = do_div(div_res, 1000);
+
+ /* Make sure to round upwards if necessary */
+ if (div_rem >= 1000 / 2)
+ div_res++;
+
+ return (int) div_res;
+}
+
+/**
+ * ab8500_fg_calc_cap_charging() - Calculate remaining capacity while charging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. The filter is filled with this capacity
+ */
+static int ab8500_fg_calc_cap_charging(struct ab8500_fg *di)
+{
+ dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+ __func__,
+ di->bat_cap.mah,
+ di->accu_charge);
+
+ /* Capacity should not be less than 0 */
+ if (di->bat_cap.mah + di->accu_charge > 0)
+ di->bat_cap.mah += di->accu_charge;
+ else
+ di->bat_cap.mah = 0;
+ /*
+ * We force capacity to 100% once when the algorithm
+ * reports that it's full.
+ */
+ if (di->bat_cap.mah >= di->bat_cap.max_mah_design ||
+ di->flags.force_full) {
+ di->bat_cap.mah = di->bat_cap.max_mah_design;
+ }
+
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ /* We need to update battery voltage and inst current when charging */
+ di->vbat = ab8500_fg_bat_voltage(di);
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_voltage() - Capacity in discharge with voltage
+ * @di: pointer to the ab8500_fg structure
+ * @comp: if voltage should be load compensated before capacity calc
+ *
+ * Return the capacity in mAh based on the battery voltage. The voltage can
+ * either be load compensated or not. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_voltage(struct ab8500_fg *di, bool comp)
+{
+ int permille, mah;
+
+ if (comp)
+ permille = ab8500_fg_load_comp_volt_to_capacity(di);
+ else
+ permille = ab8500_fg_uncomp_volt_to_capacity(di);
+
+ mah = ab8500_fg_convert_permille_to_mah(di, permille);
+
+ di->bat_cap.mah = ab8500_fg_add_cap_sample(di, mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_calc_cap_discharge_fg() - Capacity in discharge with FG
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Return the capacity in mAh based on previous calculated capcity and the FG
+ * accumulator register value. This value is added to the filter and a
+ * new mean value is calculated and returned.
+ */
+static int ab8500_fg_calc_cap_discharge_fg(struct ab8500_fg *di)
+{
+ int permille_volt, permille;
+
+ dev_dbg(di->dev, "%s cap_mah %d accu_charge %d\n",
+ __func__,
+ di->bat_cap.mah,
+ di->accu_charge);
+
+ /* Capacity should not be less than 0 */
+ if (di->bat_cap.mah + di->accu_charge > 0)
+ di->bat_cap.mah += di->accu_charge;
+ else
+ di->bat_cap.mah = 0;
+
+ if (di->bat_cap.mah >= di->bat_cap.max_mah_design)
+ di->bat_cap.mah = di->bat_cap.max_mah_design;
+
+ /*
+ * Check against voltage based capacity. It can not be lower
+ * than what the uncompensated voltage says
+ */
+ permille = ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ permille_volt = ab8500_fg_uncomp_volt_to_capacity(di);
+
+ if (permille < permille_volt) {
+ di->bat_cap.permille = permille_volt;
+ di->bat_cap.mah = ab8500_fg_convert_permille_to_mah(di,
+ di->bat_cap.permille);
+
+ dev_dbg(di->dev, "%s voltage based: perm %d perm_volt %d\n",
+ __func__,
+ permille,
+ permille_volt);
+
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ } else {
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.mah);
+ di->bat_cap.permille =
+ ab8500_fg_convert_mah_to_permille(di, di->bat_cap.mah);
+ }
+
+ return di->bat_cap.mah;
+}
+
+/**
+ * ab8500_fg_capacity_level() - Get the battery capacity level
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Get the battery capacity level based on the capacity in percent
+ */
+static int ab8500_fg_capacity_level(struct ab8500_fg *di)
+{
+ int ret, percent;
+
+ percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10);
+
+ if (percent <= di->bm->cap_levels->critical ||
+ di->flags.low_bat)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else if (percent <= di->bm->cap_levels->low)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (percent <= di->bm->cap_levels->normal)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ else if (percent <= di->bm->cap_levels->high)
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+ else
+ ret = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+
+ return ret;
+}
+
+/**
+ * ab8500_fg_calculate_scaled_capacity() - Capacity scaling
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Calculates the capacity to be shown to upper layers. Scales the capacity
+ * to have 100% as a reference from the actual capacity upon removal of charger
+ * when charging is in maintenance mode.
+ */
+static int ab8500_fg_calculate_scaled_capacity(struct ab8500_fg *di)
+{
+ struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale;
+ int capacity = di->bat_cap.prev_percent;
+
+ if (!cs->enable)
+ return capacity;
+
+ /*
+ * As long as we are in fully charge mode scale the capacity
+ * to show 100%.
+ */
+ if (di->flags.fully_charged) {
+ cs->cap_to_scale[0] = 100;
+ cs->cap_to_scale[1] =
+ max(capacity, di->bm->fg_params->maint_thres);
+ dev_dbg(di->dev, "Scale cap with %d/%d\n",
+ cs->cap_to_scale[0], cs->cap_to_scale[1]);
+ }
+
+ /* Calculates the scaled capacity. */
+ if ((cs->cap_to_scale[0] != cs->cap_to_scale[1])
+ && (cs->cap_to_scale[1] > 0))
+ capacity = min(100,
+ DIV_ROUND_CLOSEST(di->bat_cap.prev_percent *
+ cs->cap_to_scale[0],
+ cs->cap_to_scale[1]));
+
+ if (di->flags.charging) {
+ if (capacity < cs->disable_cap_level) {
+ cs->disable_cap_level = capacity;
+ dev_dbg(di->dev, "Cap to stop scale lowered %d%%\n",
+ cs->disable_cap_level);
+ } else if (!di->flags.fully_charged) {
+ if (di->bat_cap.prev_percent >=
+ cs->disable_cap_level) {
+ dev_dbg(di->dev, "Disabling scaled capacity\n");
+ cs->enable = false;
+ capacity = di->bat_cap.prev_percent;
+ } else {
+ dev_dbg(di->dev,
+ "Waiting in cap to level %d%%\n",
+ cs->disable_cap_level);
+ capacity = cs->disable_cap_level;
+ }
+ }
+ }
+
+ return capacity;
+}
+
+/**
+ * ab8500_fg_update_cap_scalers() - Capacity scaling
+ * @di: pointer to the ab8500_fg structure
+ *
+ * To be called when state change from charge<->discharge to update
+ * the capacity scalers.
+ */
+static void ab8500_fg_update_cap_scalers(struct ab8500_fg *di)
+{
+ struct ab8500_fg_cap_scaling *cs = &di->bat_cap.cap_scale;
+
+ if (!cs->enable)
+ return;
+ if (di->flags.charging) {
+ di->bat_cap.cap_scale.disable_cap_level =
+ di->bat_cap.cap_scale.scaled_cap;
+ dev_dbg(di->dev, "Cap to stop scale at charge %d%%\n",
+ di->bat_cap.cap_scale.disable_cap_level);
+ } else {
+ if (cs->scaled_cap != 100) {
+ cs->cap_to_scale[0] = cs->scaled_cap;
+ cs->cap_to_scale[1] = di->bat_cap.prev_percent;
+ } else {
+ cs->cap_to_scale[0] = 100;
+ cs->cap_to_scale[1] =
+ max(di->bat_cap.prev_percent,
+ di->bm->fg_params->maint_thres);
+ }
+
+ dev_dbg(di->dev, "Cap to scale at discharge %d/%d\n",
+ cs->cap_to_scale[0], cs->cap_to_scale[1]);
+ }
+}
+
+/**
+ * ab8500_fg_check_capacity_limits() - Check if capacity has changed
+ * @di: pointer to the ab8500_fg structure
+ * @init: capacity is allowed to go up in init mode
+ *
+ * Check if capacity or capacity limit has changed and notify the system
+ * about it using the power_supply framework
+ */
+static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init)
+{
+ bool changed = false;
+ int percent = DIV_ROUND_CLOSEST(di->bat_cap.permille, 10);
+
+ di->bat_cap.level = ab8500_fg_capacity_level(di);
+
+ if (di->bat_cap.level != di->bat_cap.prev_level) {
+ /*
+ * We do not allow reported capacity level to go up
+ * unless we're charging or if we're in init
+ */
+ if (!(!di->flags.charging && di->bat_cap.level >
+ di->bat_cap.prev_level) || init) {
+ dev_dbg(di->dev, "level changed from %d to %d\n",
+ di->bat_cap.prev_level,
+ di->bat_cap.level);
+ di->bat_cap.prev_level = di->bat_cap.level;
+ changed = true;
+ } else {
+ dev_dbg(di->dev, "level not allowed to go up "
+ "since no charger is connected: %d to %d\n",
+ di->bat_cap.prev_level,
+ di->bat_cap.level);
+ }
+ }
+
+ /*
+ * If we have received the LOW_BAT IRQ, set capacity to 0 to initiate
+ * shutdown
+ */
+ if (di->flags.low_bat) {
+ dev_dbg(di->dev, "Battery low, set capacity to 0\n");
+ di->bat_cap.prev_percent = 0;
+ di->bat_cap.permille = 0;
+ percent = 0;
+ di->bat_cap.prev_mah = 0;
+ di->bat_cap.mah = 0;
+ changed = true;
+ } else if (di->flags.fully_charged) {
+ /*
+ * We report 100% if algorithm reported fully charged
+ * and show 100% during maintenance charging (scaling).
+ */
+ if (di->flags.force_full) {
+ di->bat_cap.prev_percent = percent;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+
+ changed = true;
+
+ if (!di->bat_cap.cap_scale.enable &&
+ di->bm->capacity_scaling) {
+ di->bat_cap.cap_scale.enable = true;
+ di->bat_cap.cap_scale.cap_to_scale[0] = 100;
+ di->bat_cap.cap_scale.cap_to_scale[1] =
+ di->bat_cap.prev_percent;
+ di->bat_cap.cap_scale.disable_cap_level = 100;
+ }
+ } else if (di->bat_cap.prev_percent != percent) {
+ dev_dbg(di->dev,
+ "battery reported full "
+ "but capacity dropping: %d\n",
+ percent);
+ di->bat_cap.prev_percent = percent;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+
+ changed = true;
+ }
+ } else if (di->bat_cap.prev_percent != percent) {
+ if (percent == 0) {
+ /*
+ * We will not report 0% unless we've got
+ * the LOW_BAT IRQ, no matter what the FG
+ * algorithm says.
+ */
+ di->bat_cap.prev_percent = 1;
+ percent = 1;
+
+ changed = true;
+ } else if (!(!di->flags.charging &&
+ percent > di->bat_cap.prev_percent) || init) {
+ /*
+ * We do not allow reported capacity to go up
+ * unless we're charging or if we're in init
+ */
+ dev_dbg(di->dev,
+ "capacity changed from %d to %d (%d)\n",
+ di->bat_cap.prev_percent,
+ percent,
+ di->bat_cap.permille);
+ di->bat_cap.prev_percent = percent;
+ di->bat_cap.prev_mah = di->bat_cap.mah;
+
+ changed = true;
+ } else {
+ dev_dbg(di->dev, "capacity not allowed to go up since "
+ "no charger is connected: %d to %d (%d)\n",
+ di->bat_cap.prev_percent,
+ percent,
+ di->bat_cap.permille);
+ }
+ }
+
+ if (changed) {
+ if (di->bm->capacity_scaling) {
+ di->bat_cap.cap_scale.scaled_cap =
+ ab8500_fg_calculate_scaled_capacity(di);
+
+ dev_info(di->dev, "capacity=%d (%d)\n",
+ di->bat_cap.prev_percent,
+ di->bat_cap.cap_scale.scaled_cap);
+ }
+ power_supply_changed(di->fg_psy);
+ if (di->flags.fully_charged && di->flags.force_full) {
+ dev_dbg(di->dev, "Battery full, notifying.\n");
+ di->flags.force_full = false;
+ sysfs_notify(&di->fg_kobject, NULL, "charge_full");
+ }
+ sysfs_notify(&di->fg_kobject, NULL, "charge_now");
+ }
+}
+
+static void ab8500_fg_charge_state_to(struct ab8500_fg *di,
+ enum ab8500_fg_charge_state new_state)
+{
+ dev_dbg(di->dev, "Charge state from %d [%s] to %d [%s]\n",
+ di->charge_state,
+ charge_state[di->charge_state],
+ new_state,
+ charge_state[new_state]);
+
+ di->charge_state = new_state;
+}
+
+static void ab8500_fg_discharge_state_to(struct ab8500_fg *di,
+ enum ab8500_fg_discharge_state new_state)
+{
+ dev_dbg(di->dev, "Disharge state from %d [%s] to %d [%s]\n",
+ di->discharge_state,
+ discharge_state[di->discharge_state],
+ new_state,
+ discharge_state[new_state]);
+
+ di->discharge_state = new_state;
+}
+
+/**
+ * ab8500_fg_algorithm_charging() - FG algorithm for when charging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're charging
+ */
+static void ab8500_fg_algorithm_charging(struct ab8500_fg *di)
+{
+ /*
+ * If we change to discharge mode
+ * we should start with recovery
+ */
+ if (di->discharge_state != AB8500_FG_DISCHARGE_INIT_RECOVERY)
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_INIT_RECOVERY);
+
+ switch (di->charge_state) {
+ case AB8500_FG_CHARGE_INIT:
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bm->fg_params->accu_charging);
+
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_READOUT);
+
+ break;
+
+ case AB8500_FG_CHARGE_READOUT:
+ /*
+ * Read the FG and calculate the new capacity
+ */
+ mutex_lock(&di->cc_lock);
+ if (!di->flags.conv_done && !di->flags.force_full) {
+ /* Wasn't the CC IRQ that got us here */
+ mutex_unlock(&di->cc_lock);
+ dev_dbg(di->dev, "%s CC conv not done\n",
+ __func__);
+
+ break;
+ }
+ di->flags.conv_done = false;
+ mutex_unlock(&di->cc_lock);
+
+ ab8500_fg_calc_cap_charging(di);
+
+ break;
+
+ default:
+ break;
+ }
+
+ /* Check capacity limits */
+ ab8500_fg_check_capacity_limits(di, false);
+}
+
+static void force_capacity(struct ab8500_fg *di)
+{
+ int cap;
+
+ ab8500_fg_clear_cap_samples(di);
+ cap = di->bat_cap.user_mah;
+ if (cap > di->bat_cap.max_mah_design) {
+ dev_dbg(di->dev, "Remaining cap %d can't be bigger than total"
+ " %d\n", cap, di->bat_cap.max_mah_design);
+ cap = di->bat_cap.max_mah_design;
+ }
+ ab8500_fg_fill_cap_sample(di, di->bat_cap.user_mah);
+ di->bat_cap.permille = ab8500_fg_convert_mah_to_permille(di, cap);
+ di->bat_cap.mah = cap;
+ ab8500_fg_check_capacity_limits(di, true);
+}
+
+static bool check_sysfs_capacity(struct ab8500_fg *di)
+{
+ int cap, lower, upper;
+ int cap_permille;
+
+ cap = di->bat_cap.user_mah;
+
+ cap_permille = ab8500_fg_convert_mah_to_permille(di,
+ di->bat_cap.user_mah);
+
+ lower = di->bat_cap.permille - di->bm->fg_params->user_cap_limit * 10;
+ upper = di->bat_cap.permille + di->bm->fg_params->user_cap_limit * 10;
+
+ if (lower < 0)
+ lower = 0;
+ /* 1000 is permille, -> 100 percent */
+ if (upper > 1000)
+ upper = 1000;
+
+ dev_dbg(di->dev, "Capacity limits:"
+ " (Lower: %d User: %d Upper: %d) [user: %d, was: %d]\n",
+ lower, cap_permille, upper, cap, di->bat_cap.mah);
+
+ /* If within limits, use the saved capacity and exit estimation...*/
+ if (cap_permille > lower && cap_permille < upper) {
+ dev_dbg(di->dev, "OK! Using users cap %d uAh now\n", cap);
+ force_capacity(di);
+ return true;
+ }
+ dev_dbg(di->dev, "Capacity from user out of limits, ignoring");
+ return false;
+}
+
+/**
+ * ab8500_fg_algorithm_discharging() - FG algorithm for when discharging
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Battery capacity calculation state machine for when we're discharging
+ */
+static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di)
+{
+ int sleep_time;
+
+ /* If we change to charge mode we should start with init */
+ if (di->charge_state != AB8500_FG_CHARGE_INIT)
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+
+ switch (di->discharge_state) {
+ case AB8500_FG_DISCHARGE_INIT:
+ /* We use the FG IRQ to work on */
+ di->init_cnt = 0;
+ di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_INITMEASURING);
+
+ /* Intentional fallthrough */
+ case AB8500_FG_DISCHARGE_INITMEASURING:
+ /*
+ * Discard a number of samples during startup.
+ * After that, use compensated voltage for a few
+ * samples to get an initial capacity.
+ * Then go to READOUT
+ */
+ sleep_time = di->bm->fg_params->init_timer;
+
+ /* Discard the first [x] seconds */
+ if (di->init_cnt > di->bm->fg_params->init_discard_time) {
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+ ab8500_fg_check_capacity_limits(di, true);
+ }
+
+ di->init_cnt += sleep_time;
+ if (di->init_cnt > di->bm->fg_params->init_total_time)
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT_INIT);
+
+ break;
+
+ case AB8500_FG_DISCHARGE_INIT_RECOVERY:
+ di->recovery_cnt = 0;
+ di->recovery_needed = true;
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_RECOVERY);
+
+ /* Intentional fallthrough */
+
+ case AB8500_FG_DISCHARGE_RECOVERY:
+ sleep_time = di->bm->fg_params->recovery_sleep_timer;
+
+ /*
+ * We should check the power consumption
+ * If low, go to READOUT (after x min) or
+ * RECOVERY_SLEEP if time left.
+ * If high, go to READOUT
+ */
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+ if (di->recovery_cnt >
+ di->bm->fg_params->recovery_total_time) {
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bm->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ di->recovery_needed = false;
+ } else {
+ queue_delayed_work(di->fg_wq,
+ &di->fg_periodic_work,
+ sleep_time * HZ);
+ }
+ di->recovery_cnt += sleep_time;
+ } else {
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bm->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ }
+ break;
+
+ case AB8500_FG_DISCHARGE_READOUT_INIT:
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bm->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+ break;
+
+ case AB8500_FG_DISCHARGE_READOUT:
+ di->inst_curr = ab8500_fg_inst_curr_blocking(di);
+
+ if (ab8500_fg_is_low_curr(di, di->inst_curr)) {
+ /* Detect mode change */
+ if (di->high_curr_mode) {
+ di->high_curr_mode = false;
+ di->high_curr_cnt = 0;
+ }
+
+ if (di->recovery_needed) {
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_INIT_RECOVERY);
+
+ queue_delayed_work(di->fg_wq,
+ &di->fg_periodic_work, 0);
+
+ break;
+ }
+
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ } else {
+ mutex_lock(&di->cc_lock);
+ if (!di->flags.conv_done) {
+ /* Wasn't the CC IRQ that got us here */
+ mutex_unlock(&di->cc_lock);
+ dev_dbg(di->dev, "%s CC conv not done\n",
+ __func__);
+
+ break;
+ }
+ di->flags.conv_done = false;
+ mutex_unlock(&di->cc_lock);
+
+ /* Detect mode change */
+ if (!di->high_curr_mode) {
+ di->high_curr_mode = true;
+ di->high_curr_cnt = 0;
+ }
+
+ di->high_curr_cnt +=
+ di->bm->fg_params->accu_high_curr;
+ if (di->high_curr_cnt >
+ di->bm->fg_params->high_curr_time)
+ di->recovery_needed = true;
+
+ ab8500_fg_calc_cap_discharge_fg(di);
+ }
+
+ ab8500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ case AB8500_FG_DISCHARGE_WAKEUP:
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+
+ di->fg_samples = SEC_TO_SAMPLE(
+ di->bm->fg_params->accu_high_curr);
+ ab8500_fg_coulomb_counter(di, true);
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT);
+
+ ab8500_fg_check_capacity_limits(di, false);
+
+ break;
+
+ default:
+ break;
+ }
+}
+
+/**
+ * ab8500_fg_algorithm_calibrate() - Internal columb counter offset calibration
+ * @di: pointer to the ab8500_fg structure
+ *
+ */
+static void ab8500_fg_algorithm_calibrate(struct ab8500_fg *di)
+{
+ int ret;
+
+ switch (di->calib_state) {
+ case AB8500_FG_CALIB_INIT:
+ dev_dbg(di->dev, "Calibration ongoing...\n");
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_INT_CAL_N_AVG_MASK, CC_INT_CAL_SAMPLES_8);
+ if (ret < 0)
+ goto err;
+
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_INTAVGOFFSET_ENA, CC_INTAVGOFFSET_ENA);
+ if (ret < 0)
+ goto err;
+ di->calib_state = AB8500_FG_CALIB_WAIT;
+ break;
+ case AB8500_FG_CALIB_END:
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_GAS_GAUGE, AB8500_GASG_CC_CTRL_REG,
+ CC_MUXOFFSET, CC_MUXOFFSET);
+ if (ret < 0)
+ goto err;
+ di->flags.calibrate = false;
+ dev_dbg(di->dev, "Calibration done...\n");
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ break;
+ case AB8500_FG_CALIB_WAIT:
+ dev_dbg(di->dev, "Calibration WFI\n");
+ default:
+ break;
+ }
+ return;
+err:
+ /* Something went wrong, don't calibrate then */
+ dev_err(di->dev, "failed to calibrate the CC\n");
+ di->flags.calibrate = false;
+ di->calib_state = AB8500_FG_CALIB_INIT;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+}
+
+/**
+ * ab8500_fg_algorithm() - Entry point for the FG algorithm
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Entry point for the battery capacity calculation state machine
+ */
+static void ab8500_fg_algorithm(struct ab8500_fg *di)
+{
+ if (di->flags.calibrate)
+ ab8500_fg_algorithm_calibrate(di);
+ else {
+ if (di->flags.charging)
+ ab8500_fg_algorithm_charging(di);
+ else
+ ab8500_fg_algorithm_discharging(di);
+ }
+
+ dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d "
+ "%d %d %d %d %d %d %d\n",
+ di->bat_cap.max_mah_design,
+ di->bat_cap.max_mah,
+ di->bat_cap.mah,
+ di->bat_cap.permille,
+ di->bat_cap.level,
+ di->bat_cap.prev_mah,
+ di->bat_cap.prev_percent,
+ di->bat_cap.prev_level,
+ di->vbat,
+ di->inst_curr,
+ di->avg_curr,
+ di->accu_charge,
+ di->flags.charging,
+ di->charge_state,
+ di->discharge_state,
+ di->high_curr_mode,
+ di->recovery_needed);
+}
+
+/**
+ * ab8500_fg_periodic_work() - Run the FG state machine periodically
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for periodic work
+ */
+static void ab8500_fg_periodic_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_periodic_work.work);
+
+ if (di->init_capacity) {
+ /* Get an initial capacity calculation */
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ ab8500_fg_check_capacity_limits(di, true);
+ di->init_capacity = false;
+
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ } else if (di->flags.user_cap) {
+ if (check_sysfs_capacity(di)) {
+ ab8500_fg_check_capacity_limits(di, true);
+ if (di->flags.charging)
+ ab8500_fg_charge_state_to(di,
+ AB8500_FG_CHARGE_INIT);
+ else
+ ab8500_fg_discharge_state_to(di,
+ AB8500_FG_DISCHARGE_READOUT_INIT);
+ }
+ di->flags.user_cap = false;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ } else
+ ab8500_fg_algorithm(di);
+
+}
+
+/**
+ * ab8500_fg_check_hw_failure_work() - Check OVV_BAT condition
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the OVV_BAT condition
+ */
+static void ab8500_fg_check_hw_failure_work(struct work_struct *work)
+{
+ int ret;
+ u8 reg_value;
+
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_check_hw_failure_work.work);
+
+ /*
+ * If we have had a battery over-voltage situation,
+ * check ovv-bit to see if it should be reset.
+ */
+ ret = abx500_get_register_interruptible(di->dev,
+ AB8500_CHARGER, AB8500_CH_STAT_REG,
+ &reg_value);
+ if (ret < 0) {
+ dev_err(di->dev, "%s ab8500 read failed\n", __func__);
+ return;
+ }
+ if ((reg_value & BATT_OVV) == BATT_OVV) {
+ if (!di->flags.bat_ovv) {
+ dev_dbg(di->dev, "Battery OVV\n");
+ di->flags.bat_ovv = true;
+ power_supply_changed(di->fg_psy);
+ }
+ /* Not yet recovered from ovv, reschedule this test */
+ queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work,
+ HZ);
+ } else {
+ dev_dbg(di->dev, "Battery recovered from OVV\n");
+ di->flags.bat_ovv = false;
+ power_supply_changed(di->fg_psy);
+ }
+}
+
+/**
+ * ab8500_fg_low_bat_work() - Check LOW_BAT condition
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for checking the LOW_BAT condition
+ */
+static void ab8500_fg_low_bat_work(struct work_struct *work)
+{
+ int vbat;
+
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_low_bat_work.work);
+
+ vbat = ab8500_fg_bat_voltage(di);
+
+ /* Check if LOW_BAT still fulfilled */
+ if (vbat < di->bm->fg_params->lowbat_threshold) {
+ /* Is it time to shut down? */
+ if (di->low_bat_cnt < 1) {
+ di->flags.low_bat = true;
+ dev_warn(di->dev, "Shut down pending...\n");
+ } else {
+ /*
+ * Else we need to re-schedule this check to be able to detect
+ * if the voltage increases again during charging or
+ * due to decreasing load.
+ */
+ di->low_bat_cnt--;
+ dev_warn(di->dev, "Battery voltage still LOW\n");
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ }
+ } else {
+ di->flags.low_bat_delay = false;
+ di->low_bat_cnt = 10;
+ dev_warn(di->dev, "Battery voltage OK again\n");
+ }
+
+ /* This is needed to dispatch LOW_BAT */
+ ab8500_fg_check_capacity_limits(di, false);
+}
+
+/**
+ * ab8500_fg_battok_calc - calculate the bit pattern corresponding
+ * to the target voltage.
+ * @di: pointer to the ab8500_fg structure
+ * @target target voltage
+ *
+ * Returns bit pattern closest to the target voltage
+ * valid return values are 0-14. (0-BATT_OK_MAX_NR_INCREMENTS)
+ */
+
+static int ab8500_fg_battok_calc(struct ab8500_fg *di, int target)
+{
+ if (target > BATT_OK_MIN +
+ (BATT_OK_INCREMENT * BATT_OK_MAX_NR_INCREMENTS))
+ return BATT_OK_MAX_NR_INCREMENTS;
+ if (target < BATT_OK_MIN)
+ return 0;
+ return (target - BATT_OK_MIN) / BATT_OK_INCREMENT;
+}
+
+/**
+ * ab8500_fg_battok_init_hw_register - init battok levels
+ * @di: pointer to the ab8500_fg structure
+ *
+ */
+
+static int ab8500_fg_battok_init_hw_register(struct ab8500_fg *di)
+{
+ int selected;
+ int sel0;
+ int sel1;
+ int cbp_sel0;
+ int cbp_sel1;
+ int ret;
+ int new_val;
+
+ sel0 = di->bm->fg_params->battok_falling_th_sel0;
+ sel1 = di->bm->fg_params->battok_raising_th_sel1;
+
+ cbp_sel0 = ab8500_fg_battok_calc(di, sel0);
+ cbp_sel1 = ab8500_fg_battok_calc(di, sel1);
+
+ selected = BATT_OK_MIN + cbp_sel0 * BATT_OK_INCREMENT;
+
+ if (selected != sel0)
+ dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+ sel0, selected, cbp_sel0);
+
+ selected = BATT_OK_MIN + cbp_sel1 * BATT_OK_INCREMENT;
+
+ if (selected != sel1)
+ dev_warn(di->dev, "Invalid voltage step:%d, using %d %d\n",
+ sel1, selected, cbp_sel1);
+
+ new_val = cbp_sel0 | (cbp_sel1 << 4);
+
+ dev_dbg(di->dev, "using: %x %d %d\n", new_val, cbp_sel0, cbp_sel1);
+ ret = abx500_set_register_interruptible(di->dev, AB8500_SYS_CTRL2_BLOCK,
+ AB8500_BATT_OK_REG, new_val);
+ return ret;
+}
+
+/**
+ * ab8500_fg_instant_work() - Run the FG state machine instantly
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for instant work
+ */
+static void ab8500_fg_instant_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg, fg_work);
+
+ ab8500_fg_algorithm(di);
+}
+
+/**
+ * ab8500_fg_cc_data_end_handler() - end of data conversion isr.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+ if (!di->nbr_cceoc_irq_cnt) {
+ di->nbr_cceoc_irq_cnt++;
+ complete(&di->ab8500_fg_started);
+ } else {
+ di->nbr_cceoc_irq_cnt = 0;
+ complete(&di->ab8500_fg_complete);
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_int_calib_handler () - end of calibration isr.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_int_calib_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+ di->calib_state = AB8500_FG_CALIB_END;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_cc_convend_handler() - isr to get battery avg current.
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_cc_convend_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ queue_work(di->fg_wq, &di->fg_acc_cur_work);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_batt_ovv_handler() - Battery OVV occured
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_batt_ovv_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ dev_dbg(di->dev, "Battery OVV\n");
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(di->fg_wq, &di->fg_check_hw_failure_work, 0);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_lowbatf_handler() - Battery voltage is below LOW threshold
+ * @irq: interrupt number
+ * @_di: pointer to the ab8500_fg structure
+ *
+ * Returns IRQ status(IRQ_HANDLED)
+ */
+static irqreturn_t ab8500_fg_lowbatf_handler(int irq, void *_di)
+{
+ struct ab8500_fg *di = _di;
+
+ /* Initiate handling in ab8500_fg_low_bat_work() if not already initiated. */
+ if (!di->flags.low_bat_delay) {
+ dev_warn(di->dev, "Battery voltage is below LOW threshold\n");
+ di->flags.low_bat_delay = true;
+ /*
+ * Start a timer to check LOW_BAT again after some time
+ * This is done to avoid shutdown on single voltage dips
+ */
+ queue_delayed_work(di->fg_wq, &di->fg_low_bat_work,
+ round_jiffies(LOW_BAT_CHECK_INTERVAL));
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * ab8500_fg_get_property() - get the fg properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * fg properties by reading the sysfs files.
+ * voltage_now: battery voltage
+ * current_now: battery instant current
+ * current_avg: battery average current
+ * charge_full_design: capacity where battery is considered full
+ * charge_now: battery capacity in nAh
+ * capacity: capacity in percent
+ * capacity_level: capacity level
+ *
+ * Returns error code in case of failure else 0 on success
+ */
+static int ab8500_fg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ /*
+ * If battery is identified as unknown and charging of unknown
+ * batteries is disabled, we always report 100% capacity and
+ * capacity level UNKNOWN, since we can't calculate
+ * remaining capacity
+ */
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (di->flags.bat_ovv)
+ val->intval = BATT_OVV_VALUE * 1000;
+ else
+ val->intval = di->vbat * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = di->inst_curr * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = di->avg_curr * 1000;
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah_design);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.max_mah);
+ else
+ val->intval = ab8500_fg_convert_mah_to_uwh(di,
+ di->bat_cap.prev_mah);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = di->bat_cap.max_mah_design;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = di->bat_cap.max_mah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = di->bat_cap.max_mah;
+ else
+ val->intval = di->bat_cap.prev_mah;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = 100;
+ else
+ val->intval = di->bat_cap.prev_percent;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (di->flags.batt_unknown && !di->bm->chg_unknown_bat &&
+ di->flags.batt_id_received)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ else
+ val->intval = di->bat_cap.prev_level;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int ab8500_fg_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext = dev_get_drvdata(dev);
+ const char **supplicants = (const char **)ext->supplied_to;
+ struct ab8500_fg *di;
+ union power_supply_propval ret;
+ int j;
+
+ psy = (struct power_supply *)data;
+ di = power_supply_get_drvdata(psy);
+
+ /*
+ * For all psy where the name of your driver
+ * appears in any supplied_to
+ */
+ j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ if (j < 0)
+ return 0;
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->desc->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->desc->properties[j];
+
+ if (power_supply_get_property(ext, prop, &ret))
+ continue;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ switch (ret.intval) {
+ case POWER_SUPPLY_STATUS_UNKNOWN:
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ if (!di->flags.charging)
+ break;
+ di->flags.charging = false;
+ di->flags.fully_charged = false;
+ if (di->bm->capacity_scaling)
+ ab8500_fg_update_cap_scalers(di);
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ case POWER_SUPPLY_STATUS_FULL:
+ if (di->flags.fully_charged)
+ break;
+ di->flags.fully_charged = true;
+ di->flags.force_full = true;
+ /* Save current capacity as maximum */
+ di->bat_cap.max_mah = di->bat_cap.mah;
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ if (di->flags.charging &&
+ !di->flags.fully_charged)
+ break;
+ di->flags.charging = true;
+ di->flags.fully_charged = false;
+ if (di->bm->capacity_scaling)
+ ab8500_fg_update_cap_scalers(di);
+ queue_work(di->fg_wq, &di->fg_work);
+ break;
+ };
+ default:
+ break;
+ };
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (!di->flags.batt_id_received &&
+ di->bm->batt_id != BATTERY_UNKNOWN) {
+ const struct abx500_battery_type *b;
+
+ b = &(di->bm->bat_type[di->bm->batt_id]);
+
+ di->flags.batt_id_received = true;
+
+ di->bat_cap.max_mah_design =
+ MILLI_TO_MICRO *
+ b->charge_full_design;
+
+ di->bat_cap.max_mah =
+ di->bat_cap.max_mah_design;
+
+ di->vbat_nom = b->nominal_voltage;
+ }
+
+ if (ret.intval)
+ di->flags.batt_unknown = false;
+ else
+ di->flags.batt_unknown = true;
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (di->flags.batt_id_received)
+ di->bat_temp = ret.intval;
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * ab8500_fg_init_hw_registers() - Set up FG related registers
+ * @di: pointer to the ab8500_fg structure
+ *
+ * Set up battery OVV, low battery voltage registers
+ */
+static int ab8500_fg_init_hw_registers(struct ab8500_fg *di)
+{
+ int ret;
+
+ /* Set VBAT OVV threshold */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_BATT_OVV,
+ BATT_OVV_TH_4P75,
+ BATT_OVV_TH_4P75);
+ if (ret) {
+ dev_err(di->dev, "failed to set BATT_OVV\n");
+ goto out;
+ }
+
+ /* Enable VBAT OVV detection */
+ ret = abx500_mask_and_set_register_interruptible(di->dev,
+ AB8500_CHARGER,
+ AB8500_BATT_OVV,
+ BATT_OVV_ENA,
+ BATT_OVV_ENA);
+ if (ret) {
+ dev_err(di->dev, "failed to enable BATT_OVV\n");
+ goto out;
+ }
+
+ /* Low Battery Voltage */
+ ret = abx500_set_register_interruptible(di->dev,
+ AB8500_SYS_CTRL2_BLOCK,
+ AB8500_LOW_BAT_REG,
+ ab8500_volt_to_regval(
+ di->bm->fg_params->lowbat_threshold) << 1 |
+ LOW_BAT_ENABLE);
+ if (ret) {
+ dev_err(di->dev, "%s write failed\n", __func__);
+ goto out;
+ }
+
+ /* Battery OK threshold */
+ ret = ab8500_fg_battok_init_hw_register(di);
+ if (ret) {
+ dev_err(di->dev, "BattOk init write failed.\n");
+ goto out;
+ }
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(di->dev) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__);
+ goto out;
+ };
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable);
+
+ if (ret) {
+ dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__);
+ goto out;
+ };
+ }
+out:
+ return ret;
+}
+
+/**
+ * ab8500_fg_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void ab8500_fg_external_power_changed(struct power_supply *psy)
+{
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ class_for_each_device(power_supply_class, NULL,
+ di->fg_psy, ab8500_fg_get_ext_psy_data);
+}
+
+/**
+ * abab8500_fg_reinit_work() - work to reset the FG algorithm
+ * @work: pointer to the work_struct structure
+ *
+ * Used to reset the current battery capacity to be able to
+ * retrigger a new voltage base capacity calculation. For
+ * test and verification purpose.
+ */
+static void ab8500_fg_reinit_work(struct work_struct *work)
+{
+ struct ab8500_fg *di = container_of(work, struct ab8500_fg,
+ fg_reinit_work.work);
+
+ if (di->flags.calibrate == false) {
+ dev_dbg(di->dev, "Resetting FG state machine to init.\n");
+ ab8500_fg_clear_cap_samples(di);
+ ab8500_fg_calc_cap_discharge_voltage(di, true);
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+ } else {
+ dev_err(di->dev, "Residual offset calibration ongoing "
+ "retrying..\n");
+ /* Wait one second until next try*/
+ queue_delayed_work(di->fg_wq, &di->fg_reinit_work,
+ round_jiffies(1));
+ }
+}
+
+/* Exposure to the sysfs interface */
+
+struct ab8500_fg_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct ab8500_fg *, char *);
+ ssize_t (*store)(struct ab8500_fg *, const char *, size_t);
+};
+
+static ssize_t charge_full_show(struct ab8500_fg *di, char *buf)
+{
+ return sprintf(buf, "%d\n", di->bat_cap.max_mah);
+}
+
+static ssize_t charge_full_store(struct ab8500_fg *di, const char *buf,
+ size_t count)
+{
+ unsigned long charge_full;
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &charge_full);
+
+ dev_dbg(di->dev, "Ret %zd charge_full %lu", ret, charge_full);
+
+ if (!ret) {
+ di->bat_cap.max_mah = (int) charge_full;
+ ret = count;
+ }
+ return ret;
+}
+
+static ssize_t charge_now_show(struct ab8500_fg *di, char *buf)
+{
+ return sprintf(buf, "%d\n", di->bat_cap.prev_mah);
+}
+
+static ssize_t charge_now_store(struct ab8500_fg *di, const char *buf,
+ size_t count)
+{
+ unsigned long charge_now;
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &charge_now);
+
+ dev_dbg(di->dev, "Ret %zd charge_now %lu was %d",
+ ret, charge_now, di->bat_cap.prev_mah);
+
+ if (!ret) {
+ di->bat_cap.user_mah = (int) charge_now;
+ di->flags.user_cap = true;
+ ret = count;
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+ }
+ return ret;
+}
+
+static struct ab8500_fg_sysfs_entry charge_full_attr =
+ __ATTR(charge_full, 0644, charge_full_show, charge_full_store);
+
+static struct ab8500_fg_sysfs_entry charge_now_attr =
+ __ATTR(charge_now, 0644, charge_now_show, charge_now_store);
+
+static ssize_t
+ab8500_fg_show(struct kobject *kobj, struct attribute *attr, char *buf)
+{
+ struct ab8500_fg_sysfs_entry *entry;
+ struct ab8500_fg *di;
+
+ entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+ di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+ if (!entry->show)
+ return -EIO;
+
+ return entry->show(di, buf);
+}
+static ssize_t
+ab8500_fg_store(struct kobject *kobj, struct attribute *attr, const char *buf,
+ size_t count)
+{
+ struct ab8500_fg_sysfs_entry *entry;
+ struct ab8500_fg *di;
+
+ entry = container_of(attr, struct ab8500_fg_sysfs_entry, attr);
+ di = container_of(kobj, struct ab8500_fg, fg_kobject);
+
+ if (!entry->store)
+ return -EIO;
+
+ return entry->store(di, buf, count);
+}
+
+static const struct sysfs_ops ab8500_fg_sysfs_ops = {
+ .show = ab8500_fg_show,
+ .store = ab8500_fg_store,
+};
+
+static struct attribute *ab8500_fg_attrs[] = {
+ &charge_full_attr.attr,
+ &charge_now_attr.attr,
+ NULL,
+};
+
+static struct kobj_type ab8500_fg_ktype = {
+ .sysfs_ops = &ab8500_fg_sysfs_ops,
+ .default_attrs = ab8500_fg_attrs,
+};
+
+/**
+ * ab8500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di: pointer to the struct ab8500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void ab8500_fg_sysfs_exit(struct ab8500_fg *di)
+{
+ kobject_del(&di->fg_kobject);
+}
+
+/**
+ * ab8500_chargalg_sysfs_init() - init of sysfs entry
+ * @di: pointer to the struct ab8500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int ab8500_fg_sysfs_init(struct ab8500_fg *di)
+{
+ int ret = 0;
+
+ ret = kobject_init_and_add(&di->fg_kobject,
+ &ab8500_fg_ktype,
+ NULL, "battery");
+ if (ret < 0)
+ dev_err(di->dev, "failed to create sysfs entry\n");
+
+ return ret;
+}
+
+static ssize_t ab8505_powercut_flagtime_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_flagtime_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ long unsigned reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+
+ if (reg_value > 0x7F) {
+ dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_maxtime_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+
+}
+
+static ssize_t ab8505_powercut_maxtime_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x7F) {
+ dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_restart_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_restart_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0xF) {
+ dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n");
+
+fail:
+ return count;
+
+}
+
+static ssize_t ab8505_powercut_timer_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_TIME_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_restart_counter_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_RESTART_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4);
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0)
+ goto fail;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x1) {
+ dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_flag_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_debounce_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7));
+
+fail:
+ return ret;
+}
+
+static ssize_t ab8505_powercut_debounce_write(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ int ret;
+ int reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ reg_value = simple_strtoul(buf, NULL, 10);
+ if (reg_value > 0x7) {
+ dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n");
+ goto fail;
+ }
+
+ ret = abx500_set_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value);
+
+ if (ret < 0)
+ dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n");
+
+fail:
+ return count;
+}
+
+static ssize_t ab8505_powercut_enable_status_read(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 reg_value;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct ab8500_fg *di = power_supply_get_drvdata(psy);
+
+ ret = abx500_get_register_interruptible(di->dev, AB8500_RTC,
+ AB8505_RTC_PCUT_CTL_STATUS_REG, &reg_value);
+
+ if (ret < 0) {
+ dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n");
+ goto fail;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5));
+
+fail:
+ return ret;
+}
+
+static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = {
+ __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write),
+ __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write),
+ __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_restart_read, ab8505_powercut_restart_write),
+ __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL),
+ __ATTR(powercut_restart_counter, S_IRUGO,
+ ab8505_powercut_restart_counter_read, NULL),
+ __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_read, ab8505_powercut_write),
+ __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL),
+ __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP),
+ ab8505_powercut_debounce_read, ab8505_powercut_debounce_write),
+ __ATTR(powercut_enable_status, S_IRUGO,
+ ab8505_powercut_enable_status_read, NULL),
+};
+
+static int ab8500_fg_sysfs_psy_create_attrs(struct ab8500_fg *di)
+{
+ unsigned int i;
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(di->dev) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++)
+ if (device_create_file(&di->fg_psy->dev,
+ &ab8505_fg_sysfs_psy_attrs[i]))
+ goto sysfs_psy_create_attrs_failed_ab8505;
+ }
+ return 0;
+sysfs_psy_create_attrs_failed_ab8505:
+ dev_err(&di->fg_psy->dev, "Failed creating sysfs psy attrs for ab8505.\n");
+ while (i--)
+ device_remove_file(&di->fg_psy->dev,
+ &ab8505_fg_sysfs_psy_attrs[i]);
+
+ return -EIO;
+}
+
+static void ab8500_fg_sysfs_psy_remove_attrs(struct ab8500_fg *di)
+{
+ unsigned int i;
+
+ if (((is_ab8505(di->parent) || is_ab9540(di->parent)) &&
+ abx500_get_chip_id(di->dev) >= AB8500_CUT2P0)
+ || is_ab8540(di->parent)) {
+ for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++)
+ (void)device_remove_file(&di->fg_psy->dev,
+ &ab8505_fg_sysfs_psy_attrs[i]);
+ }
+}
+
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int ab8500_fg_resume(struct platform_device *pdev)
+{
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ /*
+ * Change state if we're not charging. If we're charging we will wake
+ * up on the FG IRQ
+ */
+ if (!di->flags.charging) {
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP);
+ queue_work(di->fg_wq, &di->fg_work);
+ }
+
+ return 0;
+}
+
+static int ab8500_fg_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ flush_delayed_work(&di->fg_periodic_work);
+ flush_work(&di->fg_work);
+ flush_work(&di->fg_acc_cur_work);
+ flush_delayed_work(&di->fg_reinit_work);
+ flush_delayed_work(&di->fg_low_bat_work);
+ flush_delayed_work(&di->fg_check_hw_failure_work);
+
+ /*
+ * If the FG is enabled we will disable it before going to suspend
+ * only if we're not charging
+ */
+ if (di->flags.fg_enabled && !di->flags.charging)
+ ab8500_fg_coulomb_counter(di, false);
+
+ return 0;
+}
+#else
+#define ab8500_fg_suspend NULL
+#define ab8500_fg_resume NULL
+#endif
+
+static int ab8500_fg_remove(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct ab8500_fg *di = platform_get_drvdata(pdev);
+
+ list_del(&di->node);
+
+ /* Disable coulomb counter */
+ ret = ab8500_fg_coulomb_counter(di, false);
+ if (ret)
+ dev_err(di->dev, "failed to disable coulomb counter\n");
+
+ destroy_workqueue(di->fg_wq);
+ ab8500_fg_sysfs_exit(di);
+
+ flush_scheduled_work();
+ ab8500_fg_sysfs_psy_remove_attrs(di);
+ power_supply_unregister(di->fg_psy);
+ return ret;
+}
+
+/* ab8500 fg driver interrupts and their respective isr */
+static struct ab8500_fg_interrupts ab8500_fg_irq_th[] = {
+ {"NCONV_ACCU", ab8500_fg_cc_convend_handler},
+ {"BATT_OVV", ab8500_fg_batt_ovv_handler},
+ {"LOW_BAT_F", ab8500_fg_lowbatf_handler},
+ {"CC_INT_CALIB", ab8500_fg_cc_int_calib_handler},
+};
+
+static struct ab8500_fg_interrupts ab8500_fg_irq_bh[] = {
+ {"CCEOC", ab8500_fg_cc_data_end_handler},
+};
+
+static char *supply_interface[] = {
+ "ab8500_chargalg",
+ "ab8500_usb",
+};
+
+static const struct power_supply_desc ab8500_fg_desc = {
+ .name = "ab8500_fg",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = ab8500_fg_props,
+ .num_properties = ARRAY_SIZE(ab8500_fg_props),
+ .get_property = ab8500_fg_get_property,
+ .external_power_changed = ab8500_fg_external_power_changed,
+};
+
+static int ab8500_fg_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct ab8500_fg *di;
+ int i, irq;
+ int ret = 0;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "%s no mem for ab8500_fg\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
+ }
+ }
+
+ mutex_init(&di->cc_lock);
+
+ /* get parent data */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+ di->gpadc = ab8500_gpadc_get("ab8500-gpadc.0");
+
+ psy_cfg.supplied_to = supply_interface;
+ psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
+ psy_cfg.drv_data = di;
+
+ di->bat_cap.max_mah_design = MILLI_TO_MICRO *
+ di->bm->bat_type[di->bm->batt_id].charge_full_design;
+
+ di->bat_cap.max_mah = di->bat_cap.max_mah_design;
+
+ di->vbat_nom = di->bm->bat_type[di->bm->batt_id].nominal_voltage;
+
+ di->init_capacity = true;
+
+ ab8500_fg_charge_state_to(di, AB8500_FG_CHARGE_INIT);
+ ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT);
+
+ /* Create a work queue for running the FG algorithm */
+ di->fg_wq = alloc_ordered_workqueue("ab8500_fg_wq", WQ_MEM_RECLAIM);
+ if (di->fg_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ return -ENOMEM;
+ }
+
+ /* Init work for running the fg algorithm instantly */
+ INIT_WORK(&di->fg_work, ab8500_fg_instant_work);
+
+ /* Init work for getting the battery accumulated current */
+ INIT_WORK(&di->fg_acc_cur_work, ab8500_fg_acc_cur_work);
+
+ /* Init work for reinitialising the fg algorithm */
+ INIT_DEFERRABLE_WORK(&di->fg_reinit_work,
+ ab8500_fg_reinit_work);
+
+ /* Work delayed Queue to run the state machine */
+ INIT_DEFERRABLE_WORK(&di->fg_periodic_work,
+ ab8500_fg_periodic_work);
+
+ /* Work to check low battery condition */
+ INIT_DEFERRABLE_WORK(&di->fg_low_bat_work,
+ ab8500_fg_low_bat_work);
+
+ /* Init work for HW failure check */
+ INIT_DEFERRABLE_WORK(&di->fg_check_hw_failure_work,
+ ab8500_fg_check_hw_failure_work);
+
+ /* Reset battery low voltage flag */
+ di->flags.low_bat = false;
+
+ /* Initialize low battery counter */
+ di->low_bat_cnt = 10;
+
+ /* Initialize OVV, and other registers */
+ ret = ab8500_fg_init_hw_registers(di);
+ if (ret) {
+ dev_err(di->dev, "failed to initialize registers\n");
+ goto free_inst_curr_wq;
+ }
+
+ /* Consider battery unknown until we're informed otherwise */
+ di->flags.batt_unknown = true;
+ di->flags.batt_id_received = false;
+
+ /* Register FG power supply class */
+ di->fg_psy = power_supply_register(di->dev, &ab8500_fg_desc, &psy_cfg);
+ if (IS_ERR(di->fg_psy)) {
+ dev_err(di->dev, "failed to register FG psy\n");
+ ret = PTR_ERR(di->fg_psy);
+ goto free_inst_curr_wq;
+ }
+
+ di->fg_samples = SEC_TO_SAMPLE(di->bm->fg_params->init_timer);
+ ab8500_fg_coulomb_counter(di, true);
+
+ /*
+ * Initialize completion used to notify completion and start
+ * of inst current
+ */
+ init_completion(&di->ab8500_fg_started);
+ init_completion(&di->ab8500_fg_complete);
+
+ /* Register primary interrupt handlers */
+ for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name);
+ ret = request_irq(irq, ab8500_fg_irq_th[i].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND,
+ ab8500_fg_irq_th[i].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n",
+ ab8500_fg_irq_th[i].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_fg_irq_th[i].name, irq, ret);
+ }
+
+ /* Register threaded interrupt handler */
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name);
+ ret = request_threaded_irq(irq, NULL, ab8500_fg_irq_bh[0].isr,
+ IRQF_SHARED | IRQF_NO_SUSPEND | IRQF_ONESHOT,
+ ab8500_fg_irq_bh[0].name, di);
+
+ if (ret != 0) {
+ dev_err(di->dev, "failed to request %s IRQ %d: %d\n",
+ ab8500_fg_irq_bh[0].name, irq, ret);
+ goto free_irq;
+ }
+ dev_dbg(di->dev, "Requested %s IRQ %d: %d\n",
+ ab8500_fg_irq_bh[0].name, irq, ret);
+
+ di->irq = platform_get_irq_byname(pdev, "CCEOC");
+ disable_irq(di->irq);
+ di->nbr_cceoc_irq_cnt = 0;
+
+ platform_set_drvdata(pdev, di);
+
+ ret = ab8500_fg_sysfs_init(di);
+ if (ret) {
+ dev_err(di->dev, "failed to create sysfs entry\n");
+ goto free_irq;
+ }
+
+ ret = ab8500_fg_sysfs_psy_create_attrs(di);
+ if (ret) {
+ dev_err(di->dev, "failed to create FG psy\n");
+ ab8500_fg_sysfs_exit(di);
+ goto free_irq;
+ }
+
+ /* Calibrate the fg first time */
+ di->flags.calibrate = true;
+ di->calib_state = AB8500_FG_CALIB_INIT;
+
+ /* Use room temp as default value until we get an update from driver. */
+ di->bat_temp = 210;
+
+ /* Run the FG algorithm */
+ queue_delayed_work(di->fg_wq, &di->fg_periodic_work, 0);
+
+ list_add_tail(&di->node, &ab8500_fg_list);
+
+ return ret;
+
+free_irq:
+ power_supply_unregister(di->fg_psy);
+
+ /* We also have to free all registered irqs */
+ for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq_th); i++) {
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq_th[i].name);
+ free_irq(irq, di);
+ }
+ irq = platform_get_irq_byname(pdev, ab8500_fg_irq_bh[0].name);
+ free_irq(irq, di);
+free_inst_curr_wq:
+ destroy_workqueue(di->fg_wq);
+ return ret;
+}
+
+static const struct of_device_id ab8500_fg_match[] = {
+ { .compatible = "stericsson,ab8500-fg", },
+ { },
+};
+
+static struct platform_driver ab8500_fg_driver = {
+ .probe = ab8500_fg_probe,
+ .remove = ab8500_fg_remove,
+ .suspend = ab8500_fg_suspend,
+ .resume = ab8500_fg_resume,
+ .driver = {
+ .name = "ab8500-fg",
+ .of_match_table = ab8500_fg_match,
+ },
+};
+
+static int __init ab8500_fg_init(void)
+{
+ return platform_driver_register(&ab8500_fg_driver);
+}
+
+static void __exit ab8500_fg_exit(void)
+{
+ platform_driver_unregister(&ab8500_fg_driver);
+}
+
+subsys_initcall_sync(ab8500_fg_init);
+module_exit(ab8500_fg_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:ab8500-fg");
+MODULE_DESCRIPTION("AB8500 Fuel Gauge driver");
diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c
new file mode 100644
index 0000000..a4411d6
--- /dev/null
+++ b/drivers/power/supply/abx500_chargalg.c
@@ -0,0 +1,2166 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ * Copyright (c) 2012 Sony Mobile Communications AB
+ *
+ * Charging algorithm driver for abx500 variants
+ *
+ * License Terms: GNU General Public License v2
+ * Authors:
+ * Johan Palsson <johan.palsson@stericsson.com>
+ * Karl Komierowski <karl.komierowski@stericsson.com>
+ * Arun R Murthy <arun.murthy@stericsson.com>
+ * Author: Imre Sunyi <imre.sunyi@sonymobile.com>
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/hrtimer.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/completion.h>
+#include <linux/workqueue.h>
+#include <linux/kobject.h>
+#include <linux/of.h>
+#include <linux/mfd/core.h>
+#include <linux/mfd/abx500.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/notifier.h>
+
+/* Watchdog kick interval */
+#define CHG_WD_INTERVAL (6 * HZ)
+
+/* End-of-charge criteria counter */
+#define EOC_COND_CNT 10
+
+/* One hour expressed in seconds */
+#define ONE_HOUR_IN_SECONDS 3600
+
+/* Five minutes expressed in seconds */
+#define FIVE_MINUTES_IN_SECONDS 300
+
+/* Plus margin for the low battery threshold */
+#define BAT_PLUS_MARGIN (100)
+
+#define CHARGALG_CURR_STEP_LOW 0
+#define CHARGALG_CURR_STEP_HIGH 100
+
+enum abx500_chargers {
+ NO_CHG,
+ AC_CHG,
+ USB_CHG,
+};
+
+struct abx500_chargalg_charger_info {
+ enum abx500_chargers conn_chg;
+ enum abx500_chargers prev_conn_chg;
+ enum abx500_chargers online_chg;
+ enum abx500_chargers prev_online_chg;
+ enum abx500_chargers charger_type;
+ bool usb_chg_ok;
+ bool ac_chg_ok;
+ int usb_volt;
+ int usb_curr;
+ int ac_volt;
+ int ac_curr;
+ int usb_vset;
+ int usb_iset;
+ int ac_vset;
+ int ac_iset;
+};
+
+struct abx500_chargalg_suspension_status {
+ bool suspended_change;
+ bool ac_suspended;
+ bool usb_suspended;
+};
+
+struct abx500_chargalg_current_step_status {
+ bool curr_step_change;
+ int curr_step;
+};
+
+struct abx500_chargalg_battery_data {
+ int temp;
+ int volt;
+ int avg_curr;
+ int inst_curr;
+ int percent;
+};
+
+enum abx500_chargalg_states {
+ STATE_HANDHELD_INIT,
+ STATE_HANDHELD,
+ STATE_CHG_NOT_OK_INIT,
+ STATE_CHG_NOT_OK,
+ STATE_HW_TEMP_PROTECT_INIT,
+ STATE_HW_TEMP_PROTECT,
+ STATE_NORMAL_INIT,
+ STATE_USB_PP_PRE_CHARGE,
+ STATE_NORMAL,
+ STATE_WAIT_FOR_RECHARGE_INIT,
+ STATE_WAIT_FOR_RECHARGE,
+ STATE_MAINTENANCE_A_INIT,
+ STATE_MAINTENANCE_A,
+ STATE_MAINTENANCE_B_INIT,
+ STATE_MAINTENANCE_B,
+ STATE_TEMP_UNDEROVER_INIT,
+ STATE_TEMP_UNDEROVER,
+ STATE_TEMP_LOWHIGH_INIT,
+ STATE_TEMP_LOWHIGH,
+ STATE_SUSPENDED_INIT,
+ STATE_SUSPENDED,
+ STATE_OVV_PROTECT_INIT,
+ STATE_OVV_PROTECT,
+ STATE_SAFETY_TIMER_EXPIRED_INIT,
+ STATE_SAFETY_TIMER_EXPIRED,
+ STATE_BATT_REMOVED_INIT,
+ STATE_BATT_REMOVED,
+ STATE_WD_EXPIRED_INIT,
+ STATE_WD_EXPIRED,
+};
+
+static const char *states[] = {
+ "HANDHELD_INIT",
+ "HANDHELD",
+ "CHG_NOT_OK_INIT",
+ "CHG_NOT_OK",
+ "HW_TEMP_PROTECT_INIT",
+ "HW_TEMP_PROTECT",
+ "NORMAL_INIT",
+ "USB_PP_PRE_CHARGE",
+ "NORMAL",
+ "WAIT_FOR_RECHARGE_INIT",
+ "WAIT_FOR_RECHARGE",
+ "MAINTENANCE_A_INIT",
+ "MAINTENANCE_A",
+ "MAINTENANCE_B_INIT",
+ "MAINTENANCE_B",
+ "TEMP_UNDEROVER_INIT",
+ "TEMP_UNDEROVER",
+ "TEMP_LOWHIGH_INIT",
+ "TEMP_LOWHIGH",
+ "SUSPENDED_INIT",
+ "SUSPENDED",
+ "OVV_PROTECT_INIT",
+ "OVV_PROTECT",
+ "SAFETY_TIMER_EXPIRED_INIT",
+ "SAFETY_TIMER_EXPIRED",
+ "BATT_REMOVED_INIT",
+ "BATT_REMOVED",
+ "WD_EXPIRED_INIT",
+ "WD_EXPIRED",
+};
+
+struct abx500_chargalg_events {
+ bool batt_unknown;
+ bool mainextchnotok;
+ bool batt_ovv;
+ bool batt_rem;
+ bool btemp_underover;
+ bool btemp_lowhigh;
+ bool main_thermal_prot;
+ bool usb_thermal_prot;
+ bool main_ovv;
+ bool vbus_ovv;
+ bool usbchargernotok;
+ bool safety_timer_expired;
+ bool maintenance_timer_expired;
+ bool ac_wd_expired;
+ bool usb_wd_expired;
+ bool ac_cv_active;
+ bool usb_cv_active;
+ bool vbus_collapsed;
+};
+
+/**
+ * struct abx500_charge_curr_maximization - Charger maximization parameters
+ * @original_iset: the non optimized/maximised charger current
+ * @current_iset: the charging current used at this moment
+ * @test_delta_i: the delta between the current we want to charge and the
+ current that is really going into the battery
+ * @condition_cnt: number of iterations needed before a new charger current
+ is set
+ * @max_current: maximum charger current
+ * @wait_cnt: to avoid too fast current step down in case of charger
+ * voltage collapse, we insert this delay between step
+ * down
+ * @level: tells in how many steps the charging current has been
+ increased
+ */
+struct abx500_charge_curr_maximization {
+ int original_iset;
+ int current_iset;
+ int test_delta_i;
+ int condition_cnt;
+ int max_current;
+ int wait_cnt;
+ u8 level;
+};
+
+enum maxim_ret {
+ MAXIM_RET_NOACTION,
+ MAXIM_RET_CHANGE,
+ MAXIM_RET_IBAT_TOO_HIGH,
+};
+
+/**
+ * struct abx500_chargalg - abx500 Charging algorithm device information
+ * @dev: pointer to the structure device
+ * @charge_status: battery operating status
+ * @eoc_cnt: counter used to determine end-of_charge
+ * @maintenance_chg: indicate if maintenance charge is active
+ * @t_hyst_norm temperature hysteresis when the temperature has been
+ * over or under normal limits
+ * @t_hyst_lowhigh temperature hysteresis when the temperature has been
+ * over or under the high or low limits
+ * @charge_state: current state of the charging algorithm
+ * @ccm charging current maximization parameters
+ * @chg_info: information about connected charger types
+ * @batt_data: data of the battery
+ * @susp_status: current charger suspension status
+ * @bm: Platform specific battery management information
+ * @curr_status: Current step status for over-current protection
+ * @parent: pointer to the struct abx500
+ * @chargalg_psy: structure that holds the battery properties exposed by
+ * the charging algorithm
+ * @events: structure for information about events triggered
+ * @chargalg_wq: work queue for running the charging algorithm
+ * @chargalg_periodic_work: work to run the charging algorithm periodically
+ * @chargalg_wd_work: work to kick the charger watchdog periodically
+ * @chargalg_work: work to run the charging algorithm instantly
+ * @safety_timer: charging safety timer
+ * @maintenance_timer: maintenance charging timer
+ * @chargalg_kobject: structure of type kobject
+ */
+struct abx500_chargalg {
+ struct device *dev;
+ int charge_status;
+ int eoc_cnt;
+ bool maintenance_chg;
+ int t_hyst_norm;
+ int t_hyst_lowhigh;
+ enum abx500_chargalg_states charge_state;
+ struct abx500_charge_curr_maximization ccm;
+ struct abx500_chargalg_charger_info chg_info;
+ struct abx500_chargalg_battery_data batt_data;
+ struct abx500_chargalg_suspension_status susp_status;
+ struct ab8500 *parent;
+ struct abx500_chargalg_current_step_status curr_status;
+ struct abx500_bm_data *bm;
+ struct power_supply *chargalg_psy;
+ struct ux500_charger *ac_chg;
+ struct ux500_charger *usb_chg;
+ struct abx500_chargalg_events events;
+ struct workqueue_struct *chargalg_wq;
+ struct delayed_work chargalg_periodic_work;
+ struct delayed_work chargalg_wd_work;
+ struct work_struct chargalg_work;
+ struct hrtimer safety_timer;
+ struct hrtimer maintenance_timer;
+ struct kobject chargalg_kobject;
+};
+
+/*External charger prepare notifier*/
+BLOCKING_NOTIFIER_HEAD(charger_notifier_list);
+
+/* Main battery properties */
+static enum power_supply_property abx500_chargalg_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+struct abx500_chargalg_sysfs_entry {
+ struct attribute attr;
+ ssize_t (*show)(struct abx500_chargalg *, char *);
+ ssize_t (*store)(struct abx500_chargalg *, const char *, size_t);
+};
+
+/**
+ * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer
+ * @timer: pointer to the hrtimer structure
+ *
+ * This function gets called when the safety timer for the charger
+ * expires
+ */
+static enum hrtimer_restart
+abx500_chargalg_safety_timer_expired(struct hrtimer *timer)
+{
+ struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
+ safety_timer);
+ dev_err(di->dev, "Safety timer expired\n");
+ di->events.safety_timer_expired = true;
+
+ /* Trigger execution of the algorithm instantly */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * abx500_chargalg_maintenance_timer_expired() - Expiration of
+ * the maintenance timer
+ * @timer: pointer to the timer structure
+ *
+ * This function gets called when the maintenence timer
+ * expires
+ */
+static enum hrtimer_restart
+abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer)
+{
+
+ struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg,
+ maintenance_timer);
+
+ dev_dbg(di->dev, "Maintenance timer expired\n");
+ di->events.maintenance_timer_expired = true;
+
+ /* Trigger execution of the algorithm instantly */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+
+ return HRTIMER_NORESTART;
+}
+
+/**
+ * abx500_chargalg_state_to() - Change charge state
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function gets called when a charge state change should occur
+ */
+static void abx500_chargalg_state_to(struct abx500_chargalg *di,
+ enum abx500_chargalg_states state)
+{
+ dev_dbg(di->dev,
+ "State changed: %s (From state: [%d] %s =to=> [%d] %s )\n",
+ di->charge_state == state ? "NO" : "YES",
+ di->charge_state,
+ states[di->charge_state],
+ state,
+ states[state]);
+
+ di->charge_state = state;
+}
+
+static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di)
+{
+ switch (di->charge_state) {
+ case STATE_NORMAL:
+ case STATE_MAINTENANCE_A:
+ case STATE_MAINTENANCE_B:
+ break;
+ default:
+ return 0;
+ }
+
+ if (di->chg_info.charger_type & USB_CHG) {
+ return di->usb_chg->ops.check_enable(di->usb_chg,
+ di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
+ } else if ((di->chg_info.charger_type & AC_CHG) &&
+ !(di->ac_chg->external)) {
+ return di->ac_chg->ops.check_enable(di->ac_chg,
+ di->bm->bat_type[di->bm->batt_id].normal_vol_lvl,
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
+ }
+ return 0;
+}
+
+/**
+ * abx500_chargalg_check_charger_connection() - Check charger connection change
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function will check if there is a change in the charger connection
+ * and change charge state accordingly. AC has precedence over USB.
+ */
+static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di)
+{
+ if (di->chg_info.conn_chg != di->chg_info.prev_conn_chg ||
+ di->susp_status.suspended_change) {
+ /*
+ * Charger state changed or suspension
+ * has changed since last update
+ */
+ if ((di->chg_info.conn_chg & AC_CHG) &&
+ !di->susp_status.ac_suspended) {
+ dev_dbg(di->dev, "Charging source is AC\n");
+ if (di->chg_info.charger_type != AC_CHG) {
+ di->chg_info.charger_type = AC_CHG;
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ }
+ } else if ((di->chg_info.conn_chg & USB_CHG) &&
+ !di->susp_status.usb_suspended) {
+ dev_dbg(di->dev, "Charging source is USB\n");
+ di->chg_info.charger_type = USB_CHG;
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ } else if (di->chg_info.conn_chg &&
+ (di->susp_status.ac_suspended ||
+ di->susp_status.usb_suspended)) {
+ dev_dbg(di->dev, "Charging is suspended\n");
+ di->chg_info.charger_type = NO_CHG;
+ abx500_chargalg_state_to(di, STATE_SUSPENDED_INIT);
+ } else {
+ dev_dbg(di->dev, "Charging source is OFF\n");
+ di->chg_info.charger_type = NO_CHG;
+ abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+ }
+ di->chg_info.prev_conn_chg = di->chg_info.conn_chg;
+ di->susp_status.suspended_change = false;
+ }
+ return di->chg_info.conn_chg;
+}
+
+/**
+ * abx500_chargalg_check_current_step_status() - Check charging current
+ * step status.
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function will check if there is a change in the charging current step
+ * and change charge state accordingly.
+ */
+static void abx500_chargalg_check_current_step_status
+ (struct abx500_chargalg *di)
+{
+ if (di->curr_status.curr_step_change)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ di->curr_status.curr_step_change = false;
+}
+
+/**
+ * abx500_chargalg_start_safety_timer() - Start charging safety timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The safety timer is used to avoid overcharging of old or bad batteries.
+ * There are different timers for AC and USB
+ */
+static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di)
+{
+ /* Charger-dependent expiration time in hours*/
+ int timer_expiration = 0;
+
+ switch (di->chg_info.charger_type) {
+ case AC_CHG:
+ timer_expiration = di->bm->main_safety_tmr_h;
+ break;
+
+ case USB_CHG:
+ timer_expiration = di->bm->usb_safety_tmr_h;
+ break;
+
+ default:
+ dev_err(di->dev, "Unknown charger to charge from\n");
+ break;
+ }
+
+ di->events.safety_timer_expired = false;
+ hrtimer_set_expires_range(&di->safety_timer,
+ ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0),
+ ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
+ hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL);
+}
+
+/**
+ * abx500_chargalg_stop_safety_timer() - Stop charging safety timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The safety timer is stopped whenever the NORMAL state is exited
+ */
+static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di)
+{
+ if (hrtimer_try_to_cancel(&di->safety_timer) >= 0)
+ di->events.safety_timer_expired = false;
+}
+
+/**
+ * abx500_chargalg_start_maintenance_timer() - Start charging maintenance timer
+ * @di: pointer to the abx500_chargalg structure
+ * @duration: duration of ther maintenance timer in hours
+ *
+ * The maintenance timer is used to maintain the charge in the battery once
+ * the battery is considered full. These timers are chosen to match the
+ * discharge curve of the battery
+ */
+static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di,
+ int duration)
+{
+ hrtimer_set_expires_range(&di->maintenance_timer,
+ ktime_set(duration * ONE_HOUR_IN_SECONDS, 0),
+ ktime_set(FIVE_MINUTES_IN_SECONDS, 0));
+ di->events.maintenance_timer_expired = false;
+ hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL);
+}
+
+/**
+ * abx500_chargalg_stop_maintenance_timer() - Stop maintenance timer
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The maintenance timer is stopped whenever maintenance ends or when another
+ * state is entered
+ */
+static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di)
+{
+ if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0)
+ di->events.maintenance_timer_expired = false;
+}
+
+/**
+ * abx500_chargalg_kick_watchdog() - Kick charger watchdog
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The charger watchdog have to be kicked periodically whenever the charger is
+ * on, else the ABB will reset the system
+ */
+static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di)
+{
+ /* Check if charger exists and kick watchdog if charging */
+ if (di->ac_chg && di->ac_chg->ops.kick_wd &&
+ di->chg_info.online_chg & AC_CHG) {
+ /*
+ * If AB charger watchdog expired, pm2xxx charging
+ * gets disabled. To be safe, kick both AB charger watchdog
+ * and pm2xxx watchdog.
+ */
+ if (di->ac_chg->external &&
+ di->usb_chg && di->usb_chg->ops.kick_wd)
+ di->usb_chg->ops.kick_wd(di->usb_chg);
+
+ return di->ac_chg->ops.kick_wd(di->ac_chg);
+ }
+ else if (di->usb_chg && di->usb_chg->ops.kick_wd &&
+ di->chg_info.online_chg & USB_CHG)
+ return di->usb_chg->ops.kick_wd(di->usb_chg);
+
+ return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_ac_en() - Turn on/off the AC charger
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: charger on/off
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * The AC charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable,
+ int vset, int iset)
+{
+ static int abx500_chargalg_ex_ac_enable_toggle;
+
+ if (!di->ac_chg || !di->ac_chg->ops.enable)
+ return -ENXIO;
+
+ /* Select maximum of what both the charger and the battery supports */
+ if (di->ac_chg->max_out_volt)
+ vset = min(vset, di->ac_chg->max_out_volt);
+ if (di->ac_chg->max_out_curr)
+ iset = min(iset, di->ac_chg->max_out_curr);
+
+ di->chg_info.ac_iset = iset;
+ di->chg_info.ac_vset = vset;
+
+ /* Enable external charger */
+ if (enable && di->ac_chg->external &&
+ !abx500_chargalg_ex_ac_enable_toggle) {
+ blocking_notifier_call_chain(&charger_notifier_list,
+ 0, di->dev);
+ abx500_chargalg_ex_ac_enable_toggle++;
+ }
+
+ return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset);
+}
+
+/**
+ * abx500_chargalg_usb_en() - Turn on/off the USB charger
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: charger on/off
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * The USB charger will be turned on/off with the requested charge voltage and
+ * current
+ */
+static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable,
+ int vset, int iset)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.enable)
+ return -ENXIO;
+
+ /* Select maximum of what both the charger and the battery supports */
+ if (di->usb_chg->max_out_volt)
+ vset = min(vset, di->usb_chg->max_out_volt);
+ if (di->usb_chg->max_out_curr)
+ iset = min(iset, di->usb_chg->max_out_curr);
+
+ di->chg_info.usb_iset = iset;
+ di->chg_info.usb_vset = vset;
+
+ return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset);
+}
+
+ /**
+ * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: power path enable/disable
+ *
+ * The USB power path will be enable/ disable
+ */
+static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.pp_enable)
+ return -ENXIO;
+
+ return di->usb_chg->ops.pp_enable(di->usb_chg, enable);
+}
+
+/**
+ * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge
+ * @di: pointer to the abx500_chargalg structure
+ * @enable: USB pre-charge enable/disable
+ *
+ * The USB USB pre-charge will be enable/ disable
+ */
+static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di,
+ bool enable)
+{
+ if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable)
+ return -ENXIO;
+
+ return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable);
+}
+
+/**
+ * abx500_chargalg_update_chg_curr() - Update charger current
+ * @di: pointer to the abx500_chargalg structure
+ * @iset: requested charger output current
+ *
+ * The charger output current will be updated for the charger
+ * that is currently in use
+ */
+static int abx500_chargalg_update_chg_curr(struct abx500_chargalg *di,
+ int iset)
+{
+ /* Check if charger exists and update current if charging */
+ if (di->ac_chg && di->ac_chg->ops.update_curr &&
+ di->chg_info.charger_type & AC_CHG) {
+ /*
+ * Select maximum of what both the charger
+ * and the battery supports
+ */
+ if (di->ac_chg->max_out_curr)
+ iset = min(iset, di->ac_chg->max_out_curr);
+
+ di->chg_info.ac_iset = iset;
+
+ return di->ac_chg->ops.update_curr(di->ac_chg, iset);
+ } else if (di->usb_chg && di->usb_chg->ops.update_curr &&
+ di->chg_info.charger_type & USB_CHG) {
+ /*
+ * Select maximum of what both the charger
+ * and the battery supports
+ */
+ if (di->usb_chg->max_out_curr)
+ iset = min(iset, di->usb_chg->max_out_curr);
+
+ di->chg_info.usb_iset = iset;
+
+ return di->usb_chg->ops.update_curr(di->usb_chg, iset);
+ }
+
+ return -ENXIO;
+}
+
+/**
+ * abx500_chargalg_stop_charging() - Stop charging
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function is called from any state where charging should be stopped.
+ * All charging is disabled and all status parameters and timers are changed
+ * accordingly
+ */
+static void abx500_chargalg_stop_charging(struct abx500_chargalg *di)
+{
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->maintenance_chg = false;
+ cancel_delayed_work(&di->chargalg_wd_work);
+ power_supply_changed(di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_hold_charging() - Pauses charging
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This function is called in the case where maintenance charging has been
+ * disabled and instead a battery voltage mode is entered to check when the
+ * battery voltage has reached a certain recharge voltage
+ */
+static void abx500_chargalg_hold_charging(struct abx500_chargalg *di)
+{
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->maintenance_chg = false;
+ cancel_delayed_work(&di->chargalg_wd_work);
+ power_supply_changed(di->chargalg_psy);
+}
+
+/**
+ * abx500_chargalg_start_charging() - Start the charger
+ * @di: pointer to the abx500_chargalg structure
+ * @vset: requested charger output voltage
+ * @iset: requested charger output current
+ *
+ * A charger will be enabled depending on the requested charger type that was
+ * detected previously.
+ */
+static void abx500_chargalg_start_charging(struct abx500_chargalg *di,
+ int vset, int iset)
+{
+ switch (di->chg_info.charger_type) {
+ case AC_CHG:
+ dev_dbg(di->dev,
+ "AC parameters: Vset %d, Ich %d\n", vset, iset);
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_ac_en(di, true, vset, iset);
+ break;
+
+ case USB_CHG:
+ dev_dbg(di->dev,
+ "USB parameters: Vset %d, Ich %d\n", vset, iset);
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ abx500_chargalg_usb_en(di, true, vset, iset);
+ break;
+
+ default:
+ dev_err(di->dev, "Unknown charger to charge from\n");
+ break;
+ }
+}
+
+/**
+ * abx500_chargalg_check_temp() - Check battery temperature ranges
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * The battery temperature is checked against the predefined limits and the
+ * charge state is changed accordingly
+ */
+static void abx500_chargalg_check_temp(struct abx500_chargalg *di)
+{
+ if (di->batt_data.temp > (di->bm->temp_low + di->t_hyst_norm) &&
+ di->batt_data.temp < (di->bm->temp_high - di->t_hyst_norm)) {
+ /* Temp OK! */
+ di->events.btemp_underover = false;
+ di->events.btemp_lowhigh = false;
+ di->t_hyst_norm = 0;
+ di->t_hyst_lowhigh = 0;
+ } else {
+ if (((di->batt_data.temp >= di->bm->temp_high) &&
+ (di->batt_data.temp <
+ (di->bm->temp_over - di->t_hyst_lowhigh))) ||
+ ((di->batt_data.temp >
+ (di->bm->temp_under + di->t_hyst_lowhigh)) &&
+ (di->batt_data.temp <= di->bm->temp_low))) {
+ /* TEMP minor!!!!! */
+ di->events.btemp_underover = false;
+ di->events.btemp_lowhigh = true;
+ di->t_hyst_norm = di->bm->temp_hysteresis;
+ di->t_hyst_lowhigh = 0;
+ } else if (di->batt_data.temp <= di->bm->temp_under ||
+ di->batt_data.temp >= di->bm->temp_over) {
+ /* TEMP major!!!!! */
+ di->events.btemp_underover = true;
+ di->events.btemp_lowhigh = false;
+ di->t_hyst_norm = 0;
+ di->t_hyst_lowhigh = di->bm->temp_hysteresis;
+ } else {
+ /* Within hysteresis */
+ dev_dbg(di->dev, "Within hysteresis limit temp: %d "
+ "hyst_lowhigh %d, hyst normal %d\n",
+ di->batt_data.temp, di->t_hyst_lowhigh,
+ di->t_hyst_norm);
+ }
+ }
+}
+
+/**
+ * abx500_chargalg_check_charger_voltage() - Check charger voltage
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * Charger voltage is checked against maximum limit
+ */
+static void abx500_chargalg_check_charger_voltage(struct abx500_chargalg *di)
+{
+ if (di->chg_info.usb_volt > di->bm->chg_params->usb_volt_max)
+ di->chg_info.usb_chg_ok = false;
+ else
+ di->chg_info.usb_chg_ok = true;
+
+ if (di->chg_info.ac_volt > di->bm->chg_params->ac_volt_max)
+ di->chg_info.ac_chg_ok = false;
+ else
+ di->chg_info.ac_chg_ok = true;
+
+}
+
+/**
+ * abx500_chargalg_end_of_charge() - Check if end-of-charge criteria is fulfilled
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * End-of-charge criteria is fulfilled when the battery voltage is above a
+ * certain limit and the battery current is below a certain limit for a
+ * predefined number of consecutive seconds. If true, the battery is full
+ */
+static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di)
+{
+ if (di->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
+ di->charge_state == STATE_NORMAL &&
+ !di->maintenance_chg && (di->batt_data.volt >=
+ di->bm->bat_type[di->bm->batt_id].termination_vol ||
+ di->events.usb_cv_active || di->events.ac_cv_active) &&
+ di->batt_data.avg_curr <
+ di->bm->bat_type[di->bm->batt_id].termination_curr &&
+ di->batt_data.avg_curr > 0) {
+ if (++di->eoc_cnt >= EOC_COND_CNT) {
+ di->eoc_cnt = 0;
+ if ((di->chg_info.charger_type & USB_CHG) &&
+ (di->usb_chg->power_path))
+ ab8540_chargalg_usb_pp_en(di, true);
+ di->charge_status = POWER_SUPPLY_STATUS_FULL;
+ di->maintenance_chg = true;
+ dev_dbg(di->dev, "EOC reached!\n");
+ power_supply_changed(di->chargalg_psy);
+ } else {
+ dev_dbg(di->dev,
+ " EOC limit reached for the %d"
+ " time, out of %d before EOC\n",
+ di->eoc_cnt,
+ EOC_COND_CNT);
+ }
+ } else {
+ di->eoc_cnt = 0;
+ }
+}
+
+static void init_maxim_chg_curr(struct abx500_chargalg *di)
+{
+ di->ccm.original_iset =
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl;
+ di->ccm.current_iset =
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl;
+ di->ccm.test_delta_i = di->bm->maxi->charger_curr_step;
+ di->ccm.max_current = di->bm->maxi->chg_curr;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
+ di->ccm.level = 0;
+}
+
+/**
+ * abx500_chargalg_chg_curr_maxim - increases the charger current to
+ * compensate for the system load
+ * @di pointer to the abx500_chargalg structure
+ *
+ * This maximization function is used to raise the charger current to get the
+ * battery current as close to the optimal value as possible. The battery
+ * current during charging is affected by the system load
+ */
+static enum maxim_ret abx500_chargalg_chg_curr_maxim(struct abx500_chargalg *di)
+{
+ int delta_i;
+
+ if (!di->bm->maxi->ena_maxi)
+ return MAXIM_RET_NOACTION;
+
+ delta_i = di->ccm.original_iset - di->batt_data.inst_curr;
+
+ if (di->events.vbus_collapsed) {
+ dev_dbg(di->dev, "Charger voltage has collapsed %d\n",
+ di->ccm.wait_cnt);
+ if (di->ccm.wait_cnt == 0) {
+ dev_dbg(di->dev, "lowering current\n");
+ di->ccm.wait_cnt++;
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
+ di->ccm.max_current =
+ di->ccm.current_iset - di->ccm.test_delta_i;
+ di->ccm.current_iset = di->ccm.max_current;
+ di->ccm.level--;
+ return MAXIM_RET_CHANGE;
+ } else {
+ dev_dbg(di->dev, "waiting\n");
+ /* Let's go in here twice before lowering curr again */
+ di->ccm.wait_cnt = (di->ccm.wait_cnt + 1) % 3;
+ return MAXIM_RET_NOACTION;
+ }
+ }
+
+ di->ccm.wait_cnt = 0;
+
+ if ((di->batt_data.inst_curr > di->ccm.original_iset)) {
+ dev_dbg(di->dev, " Maximization Ibat (%dmA) too high"
+ " (limit %dmA) (current iset: %dmA)!\n",
+ di->batt_data.inst_curr, di->ccm.original_iset,
+ di->ccm.current_iset);
+
+ if (di->ccm.current_iset == di->ccm.original_iset)
+ return MAXIM_RET_NOACTION;
+
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
+ di->ccm.current_iset = di->ccm.original_iset;
+ di->ccm.level = 0;
+
+ return MAXIM_RET_IBAT_TOO_HIGH;
+ }
+
+ if (delta_i > di->ccm.test_delta_i &&
+ (di->ccm.current_iset + di->ccm.test_delta_i) <
+ di->ccm.max_current) {
+ if (di->ccm.condition_cnt-- == 0) {
+ /* Increse the iset with cco.test_delta_i */
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
+ di->ccm.current_iset += di->ccm.test_delta_i;
+ di->ccm.level++;
+ dev_dbg(di->dev, " Maximization needed, increase"
+ " with %d mA to %dmA (Optimal ibat: %d)"
+ " Level %d\n",
+ di->ccm.test_delta_i,
+ di->ccm.current_iset,
+ di->ccm.original_iset,
+ di->ccm.level);
+ return MAXIM_RET_CHANGE;
+ } else {
+ return MAXIM_RET_NOACTION;
+ }
+ } else {
+ di->ccm.condition_cnt = di->bm->maxi->wait_cycles;
+ return MAXIM_RET_NOACTION;
+ }
+}
+
+static void handle_maxim_chg_curr(struct abx500_chargalg *di)
+{
+ enum maxim_ret ret;
+ int result;
+
+ ret = abx500_chargalg_chg_curr_maxim(di);
+ switch (ret) {
+ case MAXIM_RET_CHANGE:
+ result = abx500_chargalg_update_chg_curr(di,
+ di->ccm.current_iset);
+ if (result)
+ dev_err(di->dev, "failed to set chg curr\n");
+ break;
+ case MAXIM_RET_IBAT_TOO_HIGH:
+ result = abx500_chargalg_update_chg_curr(di,
+ di->bm->bat_type[di->bm->batt_id].normal_cur_lvl);
+ if (result)
+ dev_err(di->dev, "failed to set chg curr\n");
+ break;
+
+ case MAXIM_RET_NOACTION:
+ default:
+ /* Do nothing..*/
+ break;
+ }
+}
+
+static int abx500_chargalg_get_ext_psy_data(struct device *dev, void *data)
+{
+ struct power_supply *psy;
+ struct power_supply *ext = dev_get_drvdata(dev);
+ const char **supplicants = (const char **)ext->supplied_to;
+ struct abx500_chargalg *di;
+ union power_supply_propval ret;
+ int j;
+ bool capacity_updated = false;
+
+ psy = (struct power_supply *)data;
+ di = power_supply_get_drvdata(psy);
+ /* For all psy where the driver name appears in any supplied_to */
+ j = match_string(supplicants, ext->num_supplicants, psy->desc->name);
+ if (j < 0)
+ return 0;
+
+ /*
+ * If external is not registering 'POWER_SUPPLY_PROP_CAPACITY' to its
+ * property because of handling that sysfs entry on its own, this is
+ * the place to get the battery capacity.
+ */
+ if (!power_supply_get_property(ext, POWER_SUPPLY_PROP_CAPACITY, &ret)) {
+ di->batt_data.percent = ret.intval;
+ capacity_updated = true;
+ }
+
+ /* Go through all properties for the psy */
+ for (j = 0; j < ext->desc->num_properties; j++) {
+ enum power_supply_property prop;
+ prop = ext->desc->properties[j];
+
+ /*
+ * Initialize chargers if not already done.
+ * The ab8500_charger*/
+ if (!di->ac_chg &&
+ ext->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ di->ac_chg = psy_to_ux500_charger(ext);
+ else if (!di->usb_chg &&
+ ext->desc->type == POWER_SUPPLY_TYPE_USB)
+ di->usb_chg = psy_to_ux500_charger(ext);
+
+ if (power_supply_get_property(ext, prop, &ret))
+ continue;
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ /* Battery present */
+ if (ret.intval)
+ di->events.batt_rem = false;
+ /* Battery removed */
+ else
+ di->events.batt_rem = true;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC disconnected */
+ if (!ret.intval &&
+ (di->chg_info.conn_chg & AC_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg &= ~AC_CHG;
+ }
+ /* AC connected */
+ else if (ret.intval &&
+ !(di->chg_info.conn_chg & AC_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg |= AC_CHG;
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB disconnected */
+ if (!ret.intval &&
+ (di->chg_info.conn_chg & USB_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg &= ~USB_CHG;
+ }
+ /* USB connected */
+ else if (ret.intval &&
+ !(di->chg_info.conn_chg & USB_CHG)) {
+ di->chg_info.prev_conn_chg =
+ di->chg_info.conn_chg;
+ di->chg_info.conn_chg |= USB_CHG;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AC offline */
+ if (!ret.intval &&
+ (di->chg_info.online_chg & AC_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg &= ~AC_CHG;
+ }
+ /* AC online */
+ else if (ret.intval &&
+ !(di->chg_info.online_chg & AC_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg |= AC_CHG;
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, 0);
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* USB offline */
+ if (!ret.intval &&
+ (di->chg_info.online_chg & USB_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg &= ~USB_CHG;
+ }
+ /* USB online */
+ else if (ret.intval &&
+ !(di->chg_info.online_chg & USB_CHG)) {
+ di->chg_info.prev_online_chg =
+ di->chg_info.online_chg;
+ di->chg_info.online_chg |= USB_CHG;
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, 0);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ switch (ret.intval) {
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ di->events.mainextchnotok = true;
+ di->events.main_thermal_prot = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_DEAD:
+ di->events.ac_wd_expired = true;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.main_thermal_prot = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ di->events.main_thermal_prot = true;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ di->events.main_ovv = true;
+ di->events.mainextchnotok = false;
+ di->events.main_thermal_prot = false;
+ di->events.ac_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_GOOD:
+ di->events.main_thermal_prot = false;
+ di->events.mainextchnotok = false;
+ di->events.main_ovv = false;
+ di->events.ac_wd_expired = false;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_TYPE_USB:
+ switch (ret.intval) {
+ case POWER_SUPPLY_HEALTH_UNSPEC_FAILURE:
+ di->events.usbchargernotok = true;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_DEAD:
+ di->events.usb_wd_expired = true;
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ break;
+ case POWER_SUPPLY_HEALTH_COLD:
+ case POWER_SUPPLY_HEALTH_OVERHEAT:
+ di->events.usb_thermal_prot = true;
+ di->events.usbchargernotok = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_OVERVOLTAGE:
+ di->events.vbus_ovv = true;
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.usb_wd_expired = false;
+ break;
+ case POWER_SUPPLY_HEALTH_GOOD:
+ di->events.usbchargernotok = false;
+ di->events.usb_thermal_prot = false;
+ di->events.vbus_ovv = false;
+ di->events.usb_wd_expired = false;
+ break;
+ default:
+ break;
+ }
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.volt = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_MAINS:
+ di->chg_info.ac_volt = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ di->chg_info.usb_volt = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ /* AVG is used to indicate when we are
+ * in CV mode */
+ if (ret.intval)
+ di->events.ac_cv_active = true;
+ else
+ di->events.ac_cv_active = false;
+
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ /* AVG is used to indicate when we are
+ * in CV mode */
+ if (ret.intval)
+ di->events.usb_cv_active = true;
+ else
+ di->events.usb_cv_active = false;
+
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ if (ret.intval)
+ di->events.batt_unknown = false;
+ else
+ di->events.batt_unknown = true;
+
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ di->batt_data.temp = ret.intval / 10;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_MAINS:
+ di->chg_info.ac_curr =
+ ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ di->chg_info.usb_curr =
+ ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.inst_curr = ret.intval / 1000;
+ break;
+ default:
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ switch (ext->desc->type) {
+ case POWER_SUPPLY_TYPE_BATTERY:
+ di->batt_data.avg_curr = ret.intval / 1000;
+ break;
+ case POWER_SUPPLY_TYPE_USB:
+ if (ret.intval)
+ di->events.vbus_collapsed = true;
+ else
+ di->events.vbus_collapsed = false;
+ break;
+ default:
+ break;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (!capacity_updated)
+ di->batt_data.percent = ret.intval;
+ break;
+ default:
+ break;
+ }
+ }
+ return 0;
+}
+
+/**
+ * abx500_chargalg_external_power_changed() - callback for power supply changes
+ * @psy: pointer to the structure power_supply
+ *
+ * This function is the entry point of the pointer external_power_changed
+ * of the structure power_supply.
+ * This function gets executed when there is a change in any external power
+ * supply that this driver needs to be notified of.
+ */
+static void abx500_chargalg_external_power_changed(struct power_supply *psy)
+{
+ struct abx500_chargalg *di = power_supply_get_drvdata(psy);
+
+ /*
+ * Trigger execution of the algorithm instantly and read
+ * all power_supply properties there instead
+ */
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_algorithm() - Main function for the algorithm
+ * @di: pointer to the abx500_chargalg structure
+ *
+ * This is the main control function for the charging algorithm.
+ * It is called periodically or when something happens that will
+ * trigger a state change
+ */
+static void abx500_chargalg_algorithm(struct abx500_chargalg *di)
+{
+ int charger_status;
+ int ret;
+ int curr_step_lvl;
+
+ /* Collect data from all power_supply class devices */
+ class_for_each_device(power_supply_class, NULL,
+ di->chargalg_psy, abx500_chargalg_get_ext_psy_data);
+
+ abx500_chargalg_end_of_charge(di);
+ abx500_chargalg_check_temp(di);
+ abx500_chargalg_check_charger_voltage(di);
+
+ charger_status = abx500_chargalg_check_charger_connection(di);
+ abx500_chargalg_check_current_step_status(di);
+
+ if (is_ab8500(di->parent)) {
+ ret = abx500_chargalg_check_charger_enable(di);
+ if (ret < 0)
+ dev_err(di->dev, "Checking charger is enabled error"
+ ": Returned Value %d\n", ret);
+ }
+
+ /*
+ * First check if we have a charger connected.
+ * Also we don't allow charging of unknown batteries if configured
+ * this way
+ */
+ if (!charger_status ||
+ (di->events.batt_unknown && !di->bm->chg_unknown_bat)) {
+ if (di->charge_state != STATE_HANDHELD) {
+ di->events.safety_timer_expired = false;
+ abx500_chargalg_state_to(di, STATE_HANDHELD_INIT);
+ }
+ }
+
+ /* If suspended, we should not continue checking the flags */
+ else if (di->charge_state == STATE_SUSPENDED_INIT ||
+ di->charge_state == STATE_SUSPENDED) {
+ /* We don't do anything here, just don,t continue */
+ }
+
+ /* Safety timer expiration */
+ else if (di->events.safety_timer_expired) {
+ if (di->charge_state != STATE_SAFETY_TIMER_EXPIRED)
+ abx500_chargalg_state_to(di,
+ STATE_SAFETY_TIMER_EXPIRED_INIT);
+ }
+ /*
+ * Check if any interrupts has occured
+ * that will prevent us from charging
+ */
+
+ /* Battery removed */
+ else if (di->events.batt_rem) {
+ if (di->charge_state != STATE_BATT_REMOVED)
+ abx500_chargalg_state_to(di, STATE_BATT_REMOVED_INIT);
+ }
+ /* Main or USB charger not ok. */
+ else if (di->events.mainextchnotok || di->events.usbchargernotok) {
+ /*
+ * If vbus_collapsed is set, we have to lower the charger
+ * current, which is done in the normal state below
+ */
+ if (di->charge_state != STATE_CHG_NOT_OK &&
+ !di->events.vbus_collapsed)
+ abx500_chargalg_state_to(di, STATE_CHG_NOT_OK_INIT);
+ }
+ /* VBUS, Main or VBAT OVV. */
+ else if (di->events.vbus_ovv ||
+ di->events.main_ovv ||
+ di->events.batt_ovv ||
+ !di->chg_info.usb_chg_ok ||
+ !di->chg_info.ac_chg_ok) {
+ if (di->charge_state != STATE_OVV_PROTECT)
+ abx500_chargalg_state_to(di, STATE_OVV_PROTECT_INIT);
+ }
+ /* USB Thermal, stop charging */
+ else if (di->events.main_thermal_prot ||
+ di->events.usb_thermal_prot) {
+ if (di->charge_state != STATE_HW_TEMP_PROTECT)
+ abx500_chargalg_state_to(di,
+ STATE_HW_TEMP_PROTECT_INIT);
+ }
+ /* Battery temp over/under */
+ else if (di->events.btemp_underover) {
+ if (di->charge_state != STATE_TEMP_UNDEROVER)
+ abx500_chargalg_state_to(di,
+ STATE_TEMP_UNDEROVER_INIT);
+ }
+ /* Watchdog expired */
+ else if (di->events.ac_wd_expired ||
+ di->events.usb_wd_expired) {
+ if (di->charge_state != STATE_WD_EXPIRED)
+ abx500_chargalg_state_to(di, STATE_WD_EXPIRED_INIT);
+ }
+ /* Battery temp high/low */
+ else if (di->events.btemp_lowhigh) {
+ if (di->charge_state != STATE_TEMP_LOWHIGH)
+ abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH_INIT);
+ }
+
+ dev_dbg(di->dev,
+ "[CHARGALG] Vb %d Ib_avg %d Ib_inst %d Tb %d Cap %d Maint %d "
+ "State %s Active_chg %d Chg_status %d AC %d USB %d "
+ "AC_online %d USB_online %d AC_CV %d USB_CV %d AC_I %d "
+ "USB_I %d AC_Vset %d AC_Iset %d USB_Vset %d USB_Iset %d\n",
+ di->batt_data.volt,
+ di->batt_data.avg_curr,
+ di->batt_data.inst_curr,
+ di->batt_data.temp,
+ di->batt_data.percent,
+ di->maintenance_chg,
+ states[di->charge_state],
+ di->chg_info.charger_type,
+ di->charge_status,
+ di->chg_info.conn_chg & AC_CHG,
+ di->chg_info.conn_chg & USB_CHG,
+ di->chg_info.online_chg & AC_CHG,
+ di->chg_info.online_chg & USB_CHG,
+ di->events.ac_cv_active,
+ di->events.usb_cv_active,
+ di->chg_info.ac_curr,
+ di->chg_info.usb_curr,
+ di->chg_info.ac_vset,
+ di->chg_info.ac_iset,
+ di->chg_info.usb_vset,
+ di->chg_info.usb_iset);
+
+ switch (di->charge_state) {
+ case STATE_HANDHELD_INIT:
+ abx500_chargalg_stop_charging(di);
+ di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ abx500_chargalg_state_to(di, STATE_HANDHELD);
+ /* Intentional fallthrough */
+
+ case STATE_HANDHELD:
+ break;
+
+ case STATE_SUSPENDED_INIT:
+ if (di->susp_status.ac_suspended)
+ abx500_chargalg_ac_en(di, false, 0, 0);
+ if (di->susp_status.usb_suspended)
+ abx500_chargalg_usb_en(di, false, 0, 0);
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->maintenance_chg = false;
+ abx500_chargalg_state_to(di, STATE_SUSPENDED);
+ power_supply_changed(di->chargalg_psy);
+ /* Intentional fallthrough */
+
+ case STATE_SUSPENDED:
+ /* CHARGING is suspended */
+ break;
+
+ case STATE_BATT_REMOVED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_BATT_REMOVED);
+ /* Intentional fallthrough */
+
+ case STATE_BATT_REMOVED:
+ if (!di->events.batt_rem)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_HW_TEMP_PROTECT_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_HW_TEMP_PROTECT);
+ /* Intentional fallthrough */
+
+ case STATE_HW_TEMP_PROTECT:
+ if (!di->events.main_thermal_prot &&
+ !di->events.usb_thermal_prot)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_OVV_PROTECT_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_OVV_PROTECT);
+ /* Intentional fallthrough */
+
+ case STATE_OVV_PROTECT:
+ if (!di->events.vbus_ovv &&
+ !di->events.main_ovv &&
+ !di->events.batt_ovv &&
+ di->chg_info.usb_chg_ok &&
+ di->chg_info.ac_chg_ok)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_CHG_NOT_OK_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_CHG_NOT_OK);
+ /* Intentional fallthrough */
+
+ case STATE_CHG_NOT_OK:
+ if (!di->events.mainextchnotok &&
+ !di->events.usbchargernotok)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_SAFETY_TIMER_EXPIRED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_SAFETY_TIMER_EXPIRED);
+ /* Intentional fallthrough */
+
+ case STATE_SAFETY_TIMER_EXPIRED:
+ /* We exit this state when charger is removed */
+ break;
+
+ case STATE_NORMAL_INIT:
+ if ((di->chg_info.charger_type & USB_CHG) &&
+ di->usb_chg->power_path) {
+ if (di->batt_data.volt >
+ (di->bm->fg_params->lowbat_threshold +
+ BAT_PLUS_MARGIN)) {
+ ab8540_chargalg_usb_pre_chg_en(di, false);
+ ab8540_chargalg_usb_pp_en(di, false);
+ } else {
+ ab8540_chargalg_usb_pp_en(di, true);
+ ab8540_chargalg_usb_pre_chg_en(di, true);
+ abx500_chargalg_state_to(di,
+ STATE_USB_PP_PRE_CHARGE);
+ break;
+ }
+ }
+
+ if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW)
+ abx500_chargalg_stop_charging(di);
+ else {
+ curr_step_lvl = di->bm->bat_type[
+ di->bm->batt_id].normal_cur_lvl
+ * di->curr_status.curr_step
+ / CHARGALG_CURR_STEP_HIGH;
+ abx500_chargalg_start_charging(di,
+ di->bm->bat_type[di->bm->batt_id]
+ .normal_vol_lvl, curr_step_lvl);
+ }
+
+ abx500_chargalg_state_to(di, STATE_NORMAL);
+ abx500_chargalg_start_safety_timer(di);
+ abx500_chargalg_stop_maintenance_timer(di);
+ init_maxim_chg_curr(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->eoc_cnt = 0;
+ di->maintenance_chg = false;
+ power_supply_changed(di->chargalg_psy);
+
+ break;
+
+ case STATE_USB_PP_PRE_CHARGE:
+ if (di->batt_data.volt >
+ (di->bm->fg_params->lowbat_threshold +
+ BAT_PLUS_MARGIN))
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_NORMAL:
+ handle_maxim_chg_curr(di);
+ if (di->charge_status == POWER_SUPPLY_STATUS_FULL &&
+ di->maintenance_chg) {
+ if (di->bm->no_maintenance)
+ abx500_chargalg_state_to(di,
+ STATE_WAIT_FOR_RECHARGE_INIT);
+ else
+ abx500_chargalg_state_to(di,
+ STATE_MAINTENANCE_A_INIT);
+ }
+ break;
+
+ /* This state will be used when the maintenance state is disabled */
+ case STATE_WAIT_FOR_RECHARGE_INIT:
+ abx500_chargalg_hold_charging(di);
+ abx500_chargalg_state_to(di, STATE_WAIT_FOR_RECHARGE);
+ /* Intentional fallthrough */
+
+ case STATE_WAIT_FOR_RECHARGE:
+ if (di->batt_data.percent <=
+ di->bm->bat_type[di->bm->batt_id].
+ recharge_cap)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_MAINTENANCE_A_INIT:
+ abx500_chargalg_stop_safety_timer(di);
+ abx500_chargalg_start_maintenance_timer(di,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_chg_timer_h);
+ abx500_chargalg_start_charging(di,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_a_cur_lvl);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_A);
+ power_supply_changed(di->chargalg_psy);
+ /* Intentional fallthrough*/
+
+ case STATE_MAINTENANCE_A:
+ if (di->events.maintenance_timer_expired) {
+ abx500_chargalg_stop_maintenance_timer(di);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_B_INIT);
+ }
+ break;
+
+ case STATE_MAINTENANCE_B_INIT:
+ abx500_chargalg_start_maintenance_timer(di,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_chg_timer_h);
+ abx500_chargalg_start_charging(di,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].maint_b_cur_lvl);
+ abx500_chargalg_state_to(di, STATE_MAINTENANCE_B);
+ power_supply_changed(di->chargalg_psy);
+ /* Intentional fallthrough*/
+
+ case STATE_MAINTENANCE_B:
+ if (di->events.maintenance_timer_expired) {
+ abx500_chargalg_stop_maintenance_timer(di);
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ }
+ break;
+
+ case STATE_TEMP_LOWHIGH_INIT:
+ abx500_chargalg_start_charging(di,
+ di->bm->bat_type[
+ di->bm->batt_id].low_high_vol_lvl,
+ di->bm->bat_type[
+ di->bm->batt_id].low_high_cur_lvl);
+ abx500_chargalg_stop_maintenance_timer(di);
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ abx500_chargalg_state_to(di, STATE_TEMP_LOWHIGH);
+ power_supply_changed(di->chargalg_psy);
+ /* Intentional fallthrough */
+
+ case STATE_TEMP_LOWHIGH:
+ if (!di->events.btemp_lowhigh)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_WD_EXPIRED_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_WD_EXPIRED);
+ /* Intentional fallthrough */
+
+ case STATE_WD_EXPIRED:
+ if (!di->events.ac_wd_expired &&
+ !di->events.usb_wd_expired)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+
+ case STATE_TEMP_UNDEROVER_INIT:
+ abx500_chargalg_stop_charging(di);
+ abx500_chargalg_state_to(di, STATE_TEMP_UNDEROVER);
+ /* Intentional fallthrough */
+
+ case STATE_TEMP_UNDEROVER:
+ if (!di->events.btemp_underover)
+ abx500_chargalg_state_to(di, STATE_NORMAL_INIT);
+ break;
+ }
+
+ /* Start charging directly if the new state is a charge state */
+ if (di->charge_state == STATE_NORMAL_INIT ||
+ di->charge_state == STATE_MAINTENANCE_A_INIT ||
+ di->charge_state == STATE_MAINTENANCE_B_INIT)
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+}
+
+/**
+ * abx500_chargalg_periodic_work() - Periodic work for the algorithm
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for the charging algorithm
+ */
+static void abx500_chargalg_periodic_work(struct work_struct *work)
+{
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_periodic_work.work);
+
+ abx500_chargalg_algorithm(di);
+
+ /*
+ * If a charger is connected then the battery has to be monitored
+ * frequently, else the work can be delayed.
+ */
+ if (di->chg_info.conn_chg)
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_periodic_work,
+ di->bm->interval_charging * HZ);
+ else
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_periodic_work,
+ di->bm->interval_not_charging * HZ);
+}
+
+/**
+ * abx500_chargalg_wd_work() - periodic work to kick the charger watchdog
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for kicking the charger watchdog
+ */
+static void abx500_chargalg_wd_work(struct work_struct *work)
+{
+ int ret;
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_wd_work.work);
+
+ dev_dbg(di->dev, "abx500_chargalg_wd_work\n");
+
+ ret = abx500_chargalg_kick_watchdog(di);
+ if (ret < 0)
+ dev_err(di->dev, "failed to kick watchdog\n");
+
+ queue_delayed_work(di->chargalg_wq,
+ &di->chargalg_wd_work, CHG_WD_INTERVAL);
+}
+
+/**
+ * abx500_chargalg_work() - Work to run the charging algorithm instantly
+ * @work: pointer to the work_struct structure
+ *
+ * Work queue function for calling the charging algorithm
+ */
+static void abx500_chargalg_work(struct work_struct *work)
+{
+ struct abx500_chargalg *di = container_of(work,
+ struct abx500_chargalg, chargalg_work);
+
+ abx500_chargalg_algorithm(di);
+}
+
+/**
+ * abx500_chargalg_get_property() - get the chargalg properties
+ * @psy: pointer to the power_supply structure
+ * @psp: pointer to the power_supply_property structure
+ * @val: pointer to the power_supply_propval union
+ *
+ * This function gets called when an application tries to get the
+ * chargalg properties by reading the sysfs files.
+ * status: charging/discharging/full/unknown
+ * health: health of the battery
+ * Returns error code in case of failure else 0 on success
+ */
+static int abx500_chargalg_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct abx500_chargalg *di = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = di->charge_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (di->events.batt_ovv) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else if (di->events.btemp_underover) {
+ if (di->batt_data.temp <= di->bm->temp_under)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ } else if (di->charge_state == STATE_SAFETY_TIMER_EXPIRED ||
+ di->charge_state == STATE_SAFETY_TIMER_EXPIRED_INIT) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ } else {
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/* Exposure to the sysfs interface */
+
+static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di,
+ char *buf)
+{
+ return sprintf(buf, "%d\n", di->curr_status.curr_step);
+}
+
+static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di,
+ const char *buf, size_t length)
+{
+ long int param;
+ int ret;
+
+ ret = kstrtol(buf, 10, &param);
+ if (ret < 0)
+ return ret;
+
+ di->curr_status.curr_step = param;
+ if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW &&
+ di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) {
+ di->curr_status.curr_step_change = true;
+ queue_work(di->chargalg_wq, &di->chargalg_work);
+ } else
+ dev_info(di->dev, "Wrong current step\n"
+ "Enter 0. Disable AC/USB Charging\n"
+ "1--100. Set AC/USB charging current step\n"
+ "100. Enable AC/USB Charging\n");
+
+ return strlen(buf);
+}
+
+
+static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di,
+ char *buf)
+{
+ return sprintf(buf, "%d\n",
+ di->susp_status.ac_suspended &&
+ di->susp_status.usb_suspended);
+}
+
+static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di,
+ const char *buf, size_t length)
+{
+ long int param;
+ int ac_usb;
+ int ret;
+
+ ret = kstrtol(buf, 10, &param);
+ if (ret < 0)
+ return ret;
+
+ ac_usb = param;
+ switch (ac_usb) {
+ case 0:
+ /* Disable charging */
+ di->susp_status.ac_suspended = true;
+ di->susp_status.usb_suspended = true;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ case 1:
+ /* Enable AC Charging */
+ di->susp_status.ac_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ case 2:
+ /* Enable USB charging */
+ di->susp_status.usb_suspended = false;
+ di->susp_status.suspended_change = true;
+ /* Trigger a state change */
+ queue_work(di->chargalg_wq,
+ &di->chargalg_work);
+ break;
+ default:
+ dev_info(di->dev, "Wrong input\n"
+ "Enter 0. Disable AC/USB Charging\n"
+ "1. Enable AC charging\n"
+ "2. Enable USB Charging\n");
+ };
+ return strlen(buf);
+}
+
+static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger =
+ __ATTR(chargalg, 0644, abx500_chargalg_en_show,
+ abx500_chargalg_en_store);
+
+static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step =
+ __ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show,
+ abx500_chargalg_curr_step_store);
+
+static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj,
+ struct attribute *attr, char *buf)
+{
+ struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
+ struct abx500_chargalg_sysfs_entry, attr);
+
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+
+ if (!entry->show)
+ return -EIO;
+
+ return entry->show(di, buf);
+}
+
+static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj,
+ struct attribute *attr, const char *buf, size_t length)
+{
+ struct abx500_chargalg_sysfs_entry *entry = container_of(attr,
+ struct abx500_chargalg_sysfs_entry, attr);
+
+ struct abx500_chargalg *di = container_of(kobj,
+ struct abx500_chargalg, chargalg_kobject);
+
+ if (!entry->store)
+ return -EIO;
+
+ return entry->store(di, buf, length);
+}
+
+static struct attribute *abx500_chargalg_chg[] = {
+ &abx500_chargalg_en_charger.attr,
+ &abx500_chargalg_curr_step.attr,
+ NULL,
+};
+
+static const struct sysfs_ops abx500_chargalg_sysfs_ops = {
+ .show = abx500_chargalg_sysfs_show,
+ .store = abx500_chargalg_sysfs_charger,
+};
+
+static struct kobj_type abx500_chargalg_ktype = {
+ .sysfs_ops = &abx500_chargalg_sysfs_ops,
+ .default_attrs = abx500_chargalg_chg,
+};
+
+/**
+ * abx500_chargalg_sysfs_exit() - de-init of sysfs entry
+ * @di: pointer to the struct abx500_chargalg
+ *
+ * This function removes the entry in sysfs.
+ */
+static void abx500_chargalg_sysfs_exit(struct abx500_chargalg *di)
+{
+ kobject_del(&di->chargalg_kobject);
+}
+
+/**
+ * abx500_chargalg_sysfs_init() - init of sysfs entry
+ * @di: pointer to the struct abx500_chargalg
+ *
+ * This function adds an entry in sysfs.
+ * Returns error code in case of failure else 0(on success)
+ */
+static int abx500_chargalg_sysfs_init(struct abx500_chargalg *di)
+{
+ int ret = 0;
+
+ ret = kobject_init_and_add(&di->chargalg_kobject,
+ &abx500_chargalg_ktype,
+ NULL, "abx500_chargalg");
+ if (ret < 0)
+ dev_err(di->dev, "failed to create sysfs entry\n");
+
+ return ret;
+}
+/* Exposure to the sysfs interface <<END>> */
+
+#if defined(CONFIG_PM)
+static int abx500_chargalg_resume(struct platform_device *pdev)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ /* Kick charger watchdog if charging (any charger online) */
+ if (di->chg_info.online_chg)
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0);
+
+ /*
+ * Run the charging algorithm directly to be sure we don't
+ * do it too seldom
+ */
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+ return 0;
+}
+
+static int abx500_chargalg_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ if (di->chg_info.online_chg)
+ cancel_delayed_work_sync(&di->chargalg_wd_work);
+
+ cancel_delayed_work_sync(&di->chargalg_periodic_work);
+
+ return 0;
+}
+#else
+#define abx500_chargalg_suspend NULL
+#define abx500_chargalg_resume NULL
+#endif
+
+static int abx500_chargalg_remove(struct platform_device *pdev)
+{
+ struct abx500_chargalg *di = platform_get_drvdata(pdev);
+
+ /* sysfs interface to enable/disbale charging from user space */
+ abx500_chargalg_sysfs_exit(di);
+
+ hrtimer_cancel(&di->safety_timer);
+ hrtimer_cancel(&di->maintenance_timer);
+
+ cancel_delayed_work_sync(&di->chargalg_periodic_work);
+ cancel_delayed_work_sync(&di->chargalg_wd_work);
+ cancel_work_sync(&di->chargalg_work);
+
+ /* Delete the work queue */
+ destroy_workqueue(di->chargalg_wq);
+
+ power_supply_unregister(di->chargalg_psy);
+
+ return 0;
+}
+
+static char *supply_interface[] = {
+ "ab8500_fg",
+};
+
+static const struct power_supply_desc abx500_chargalg_desc = {
+ .name = "abx500_chargalg",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = abx500_chargalg_props,
+ .num_properties = ARRAY_SIZE(abx500_chargalg_props),
+ .get_property = abx500_chargalg_get_property,
+ .external_power_changed = abx500_chargalg_external_power_changed,
+};
+
+static int abx500_chargalg_probe(struct platform_device *pdev)
+{
+ struct device_node *np = pdev->dev.of_node;
+ struct abx500_bm_data *plat = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct abx500_chargalg *di;
+ int ret = 0;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ dev_err(&pdev->dev, "%s no mem for ab8500_chargalg\n", __func__);
+ return -ENOMEM;
+ }
+
+ if (!plat) {
+ dev_err(&pdev->dev, "no battery management data supplied\n");
+ return -EINVAL;
+ }
+ di->bm = plat;
+
+ if (np) {
+ ret = ab8500_bm_of_probe(&pdev->dev, np, di->bm);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to get battery information\n");
+ return ret;
+ }
+ }
+
+ /* get device struct and parent */
+ di->dev = &pdev->dev;
+ di->parent = dev_get_drvdata(pdev->dev.parent);
+
+ psy_cfg.supplied_to = supply_interface;
+ psy_cfg.num_supplicants = ARRAY_SIZE(supply_interface);
+ psy_cfg.drv_data = di;
+
+ /* Initilialize safety timer */
+ hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
+ di->safety_timer.function = abx500_chargalg_safety_timer_expired;
+
+ /* Initilialize maintenance timer */
+ hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS);
+ di->maintenance_timer.function =
+ abx500_chargalg_maintenance_timer_expired;
+
+ /* Create a work queue for the chargalg */
+ di->chargalg_wq = alloc_ordered_workqueue("abx500_chargalg_wq",
+ WQ_MEM_RECLAIM);
+ if (di->chargalg_wq == NULL) {
+ dev_err(di->dev, "failed to create work queue\n");
+ return -ENOMEM;
+ }
+
+ /* Init work for chargalg */
+ INIT_DEFERRABLE_WORK(&di->chargalg_periodic_work,
+ abx500_chargalg_periodic_work);
+ INIT_DEFERRABLE_WORK(&di->chargalg_wd_work,
+ abx500_chargalg_wd_work);
+
+ /* Init work for chargalg */
+ INIT_WORK(&di->chargalg_work, abx500_chargalg_work);
+
+ /* To detect charger at startup */
+ di->chg_info.prev_conn_chg = -1;
+
+ /* Register chargalg power supply class */
+ di->chargalg_psy = power_supply_register(di->dev, &abx500_chargalg_desc,
+ &psy_cfg);
+ if (IS_ERR(di->chargalg_psy)) {
+ dev_err(di->dev, "failed to register chargalg psy\n");
+ ret = PTR_ERR(di->chargalg_psy);
+ goto free_chargalg_wq;
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ /* sysfs interface to enable/disable charging from user space */
+ ret = abx500_chargalg_sysfs_init(di);
+ if (ret) {
+ dev_err(di->dev, "failed to create sysfs entry\n");
+ goto free_psy;
+ }
+ di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH;
+
+ /* Run the charging algorithm */
+ queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0);
+
+ dev_info(di->dev, "probe success\n");
+ return ret;
+
+free_psy:
+ power_supply_unregister(di->chargalg_psy);
+free_chargalg_wq:
+ destroy_workqueue(di->chargalg_wq);
+ return ret;
+}
+
+static const struct of_device_id ab8500_chargalg_match[] = {
+ { .compatible = "stericsson,ab8500-chargalg", },
+ { },
+};
+
+static struct platform_driver abx500_chargalg_driver = {
+ .probe = abx500_chargalg_probe,
+ .remove = abx500_chargalg_remove,
+ .suspend = abx500_chargalg_suspend,
+ .resume = abx500_chargalg_resume,
+ .driver = {
+ .name = "ab8500-chargalg",
+ .of_match_table = ab8500_chargalg_match,
+ },
+};
+
+module_platform_driver(abx500_chargalg_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Johan Palsson, Karl Komierowski");
+MODULE_ALIAS("platform:abx500-chargalg");
+MODULE_DESCRIPTION("abx500 battery charging algorithm");
diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c
new file mode 100644
index 0000000..d1eb2e3
--- /dev/null
+++ b/drivers/power/supply/act8945a_charger.c
@@ -0,0 +1,666 @@
+/*
+ * Power supply driver for the Active-semi ACT8945A PMIC
+ *
+ * Copyright (C) 2015 Atmel Corporation
+ *
+ * Author: Wenyou Yang <wenyou.yang@atmel.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/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/gpio/consumer.h>
+
+static const char *act8945a_charger_model = "ACT8945A";
+static const char *act8945a_charger_manufacturer = "Active-semi";
+
+/**
+ * ACT8945A Charger Register Map
+ */
+
+/* 0x70: Reserved */
+#define ACT8945A_APCH_CFG 0x71
+#define ACT8945A_APCH_STATUS 0x78
+#define ACT8945A_APCH_CTRL 0x79
+#define ACT8945A_APCH_STATE 0x7A
+
+/* ACT8945A_APCH_CFG */
+#define APCH_CFG_OVPSET (0x3 << 0)
+#define APCH_CFG_OVPSET_6V6 (0x0 << 0)
+#define APCH_CFG_OVPSET_7V (0x1 << 0)
+#define APCH_CFG_OVPSET_7V5 (0x2 << 0)
+#define APCH_CFG_OVPSET_8V (0x3 << 0)
+#define APCH_CFG_PRETIMO (0x3 << 2)
+#define APCH_CFG_PRETIMO_40_MIN (0x0 << 2)
+#define APCH_CFG_PRETIMO_60_MIN (0x1 << 2)
+#define APCH_CFG_PRETIMO_80_MIN (0x2 << 2)
+#define APCH_CFG_PRETIMO_DISABLED (0x3 << 2)
+#define APCH_CFG_TOTTIMO (0x3 << 4)
+#define APCH_CFG_TOTTIMO_3_HOUR (0x0 << 4)
+#define APCH_CFG_TOTTIMO_4_HOUR (0x1 << 4)
+#define APCH_CFG_TOTTIMO_5_HOUR (0x2 << 4)
+#define APCH_CFG_TOTTIMO_DISABLED (0x3 << 4)
+#define APCH_CFG_SUSCHG (0x1 << 7)
+
+#define APCH_STATUS_CHGDAT BIT(0)
+#define APCH_STATUS_INDAT BIT(1)
+#define APCH_STATUS_TEMPDAT BIT(2)
+#define APCH_STATUS_TIMRDAT BIT(3)
+#define APCH_STATUS_CHGSTAT BIT(4)
+#define APCH_STATUS_INSTAT BIT(5)
+#define APCH_STATUS_TEMPSTAT BIT(6)
+#define APCH_STATUS_TIMRSTAT BIT(7)
+
+#define APCH_CTRL_CHGEOCOUT BIT(0)
+#define APCH_CTRL_INDIS BIT(1)
+#define APCH_CTRL_TEMPOUT BIT(2)
+#define APCH_CTRL_TIMRPRE BIT(3)
+#define APCH_CTRL_CHGEOCIN BIT(4)
+#define APCH_CTRL_INCON BIT(5)
+#define APCH_CTRL_TEMPIN BIT(6)
+#define APCH_CTRL_TIMRTOT BIT(7)
+
+#define APCH_STATE_ACINSTAT (0x1 << 1)
+#define APCH_STATE_CSTATE (0x3 << 4)
+#define APCH_STATE_CSTATE_SHIFT 4
+#define APCH_STATE_CSTATE_DISABLED 0x00
+#define APCH_STATE_CSTATE_EOC 0x01
+#define APCH_STATE_CSTATE_FAST 0x02
+#define APCH_STATE_CSTATE_PRE 0x03
+
+struct act8945a_charger {
+ struct power_supply *psy;
+ struct power_supply_desc desc;
+ struct regmap *regmap;
+ struct work_struct work;
+
+ bool init_done;
+ struct gpio_desc *lbo_gpio;
+ struct gpio_desc *chglev_gpio;
+};
+
+static int act8945a_get_charger_state(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int status, state;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ state &= APCH_STATE_CSTATE;
+ state >>= APCH_STATE_CSTATE_SHIFT;
+
+ switch (state) {
+ case APCH_STATE_CSTATE_PRE:
+ case APCH_STATE_CSTATE_FAST:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case APCH_STATE_CSTATE_EOC:
+ if (status & APCH_STATUS_CHGDAT)
+ *val = POWER_SUPPLY_STATUS_FULL;
+ else
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case APCH_STATE_CSTATE_DISABLED:
+ default:
+ if (!(status & APCH_STATUS_INDAT))
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ }
+
+ return 0;
+}
+
+static int act8945a_get_charge_type(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int status, state;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ state &= APCH_STATE_CSTATE;
+ state >>= APCH_STATE_CSTATE_SHIFT;
+
+ switch (state) {
+ case APCH_STATE_CSTATE_PRE:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case APCH_STATE_CSTATE_FAST:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case APCH_STATE_CSTATE_EOC:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case APCH_STATE_CSTATE_DISABLED:
+ default:
+ if (!(status & APCH_STATUS_INDAT))
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ else
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int act8945a_get_battery_health(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int status, state, config;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_CFG, &config);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ state &= APCH_STATE_CSTATE;
+ state >>= APCH_STATE_CSTATE_SHIFT;
+
+ switch (state) {
+ case APCH_STATE_CSTATE_DISABLED:
+ if (config & APCH_CFG_SUSCHG) {
+ *val = POWER_SUPPLY_HEALTH_UNKNOWN;
+ } else if (status & APCH_STATUS_INDAT) {
+ if (!(status & APCH_STATUS_TEMPDAT))
+ *val = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (status & APCH_STATUS_TIMRDAT)
+ *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ else
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ } else {
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ break;
+ case APCH_STATE_CSTATE_PRE:
+ case APCH_STATE_CSTATE_FAST:
+ case APCH_STATE_CSTATE_EOC:
+ default:
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ }
+
+ return 0;
+}
+
+static int act8945a_get_capacity_level(struct act8945a_charger *charger,
+ struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int status, state, config;
+ int lbo_level = gpiod_get_value(charger->lbo_gpio);
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_CFG, &config);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ state &= APCH_STATE_CSTATE;
+ state >>= APCH_STATE_CSTATE_SHIFT;
+
+ switch (state) {
+ case APCH_STATE_CSTATE_PRE:
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ break;
+ case APCH_STATE_CSTATE_FAST:
+ if (lbo_level)
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_HIGH;
+ else
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ break;
+ case APCH_STATE_CSTATE_EOC:
+ if (status & APCH_STATUS_CHGDAT)
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case APCH_STATE_CSTATE_DISABLED:
+ default:
+ if (config & APCH_CFG_SUSCHG) {
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ } else {
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ if (!(status & APCH_STATUS_INDAT)) {
+ if (!lbo_level)
+ *val = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ }
+ }
+ break;
+ }
+
+ return 0;
+}
+
+#define MAX_CURRENT_USB_HIGH 450000
+#define MAX_CURRENT_USB_LOW 90000
+#define MAX_CURRENT_USB_PRE 45000
+/*
+ * Riset(K) = 2336 * (1V/Ichg(mA)) - 0.205
+ * Riset = 2.43K
+ */
+#define MAX_CURRENT_AC_HIGH 886527
+#define MAX_CURRENT_AC_LOW 117305
+#define MAX_CURRENT_AC_HIGH_PRE 88653
+#define MAX_CURRENT_AC_LOW_PRE 11731
+
+static int act8945a_get_current_max(struct act8945a_charger *charger,
+ struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int status, state;
+ unsigned int acin_state;
+ int chgin_level = gpiod_get_value(charger->chglev_gpio);
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ acin_state = (state & APCH_STATE_ACINSTAT) >> 1;
+
+ state &= APCH_STATE_CSTATE;
+ state >>= APCH_STATE_CSTATE_SHIFT;
+
+ switch (state) {
+ case APCH_STATE_CSTATE_PRE:
+ if (acin_state) {
+ if (chgin_level)
+ *val = MAX_CURRENT_AC_HIGH_PRE;
+ else
+ *val = MAX_CURRENT_AC_LOW_PRE;
+ } else {
+ *val = MAX_CURRENT_USB_PRE;
+ }
+ break;
+ case APCH_STATE_CSTATE_FAST:
+ if (acin_state) {
+ if (chgin_level)
+ *val = MAX_CURRENT_AC_HIGH;
+ else
+ *val = MAX_CURRENT_AC_LOW;
+ } else {
+ if (chgin_level)
+ *val = MAX_CURRENT_USB_HIGH;
+ else
+ *val = MAX_CURRENT_USB_LOW;
+ }
+ break;
+ case APCH_STATE_CSTATE_EOC:
+ case APCH_STATE_CSTATE_DISABLED:
+ default:
+ *val = 0;
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property act8945a_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER
+};
+
+static int act8945a_charger_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct act8945a_charger *charger = power_supply_get_drvdata(psy);
+ struct regmap *regmap = charger->regmap;
+ int ret = 0;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = act8945a_get_charger_state(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = act8945a_get_charge_type(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = act8945a_get_battery_health(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ ret = act8945a_get_capacity_level(charger,
+ regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = act8945a_get_current_max(charger,
+ regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = act8945a_charger_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = act8945a_charger_manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static int act8945a_enable_interrupt(struct act8945a_charger *charger)
+{
+ struct regmap *regmap = charger->regmap;
+ unsigned char ctrl;
+ int ret;
+
+ ctrl = APCH_CTRL_CHGEOCOUT | APCH_CTRL_CHGEOCIN |
+ APCH_CTRL_INDIS | APCH_CTRL_INCON |
+ APCH_CTRL_TEMPOUT | APCH_CTRL_TEMPIN |
+ APCH_CTRL_TIMRPRE | APCH_CTRL_TIMRTOT;
+ ret = regmap_write(regmap, ACT8945A_APCH_CTRL, ctrl);
+ if (ret)
+ return ret;
+
+ ctrl = APCH_STATUS_CHGSTAT | APCH_STATUS_INSTAT |
+ APCH_STATUS_TEMPSTAT | APCH_STATUS_TIMRSTAT;
+ ret = regmap_write(regmap, ACT8945A_APCH_STATUS, ctrl);
+ if (ret)
+ return ret;
+
+ return 0;
+}
+
+static unsigned int act8945a_set_supply_type(struct act8945a_charger *charger,
+ unsigned int *type)
+{
+ unsigned int status, state;
+ int ret;
+
+ ret = regmap_read(charger->regmap, ACT8945A_APCH_STATUS, &status);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_read(charger->regmap, ACT8945A_APCH_STATE, &state);
+ if (ret < 0)
+ return ret;
+
+ if (status & APCH_STATUS_INDAT) {
+ if (state & APCH_STATE_ACINSTAT)
+ *type = POWER_SUPPLY_TYPE_MAINS;
+ else
+ *type = POWER_SUPPLY_TYPE_USB;
+ } else {
+ *type = POWER_SUPPLY_TYPE_BATTERY;
+ }
+
+ return 0;
+}
+
+static void act8945a_work(struct work_struct *work)
+{
+ struct act8945a_charger *charger =
+ container_of(work, struct act8945a_charger, work);
+
+ act8945a_set_supply_type(charger, &charger->desc.type);
+
+ power_supply_changed(charger->psy);
+}
+
+static irqreturn_t act8945a_status_changed(int irq, void *dev_id)
+{
+ struct act8945a_charger *charger = dev_id;
+
+ if (charger->init_done)
+ schedule_work(&charger->work);
+
+ return IRQ_HANDLED;
+}
+
+#define DEFAULT_TOTAL_TIME_OUT 3
+#define DEFAULT_PRE_TIME_OUT 40
+#define DEFAULT_INPUT_OVP_THRESHOLD 6600
+
+static int act8945a_charger_config(struct device *dev,
+ struct act8945a_charger *charger)
+{
+ struct device_node *np = dev->of_node;
+ struct regmap *regmap = charger->regmap;
+
+ u32 total_time_out;
+ u32 pre_time_out;
+ u32 input_voltage_threshold;
+ int err, ret;
+
+ unsigned int tmp;
+ unsigned int value = 0;
+
+ if (!np) {
+ dev_err(dev, "no charger of node\n");
+ return -EINVAL;
+ }
+
+ ret = regmap_read(regmap, ACT8945A_APCH_CFG, &tmp);
+ if (ret)
+ return ret;
+
+ if (tmp & APCH_CFG_SUSCHG) {
+ value |= APCH_CFG_SUSCHG;
+ dev_info(dev, "have been suspended\n");
+ }
+
+ charger->lbo_gpio = devm_gpiod_get_optional(dev, "active-semi,lbo",
+ GPIOD_IN);
+ if (IS_ERR(charger->lbo_gpio)) {
+ err = PTR_ERR(charger->lbo_gpio);
+ dev_err(dev, "unable to claim gpio \"lbo\": %d\n", err);
+ return err;
+ }
+
+ ret = devm_request_irq(dev, gpiod_to_irq(charger->lbo_gpio),
+ act8945a_status_changed,
+ (IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING),
+ "act8945a_lbo_detect", charger);
+ if (ret)
+ dev_info(dev, "failed to request gpio \"lbo\" IRQ\n");
+
+ charger->chglev_gpio = devm_gpiod_get_optional(dev,
+ "active-semi,chglev",
+ GPIOD_IN);
+ if (IS_ERR(charger->chglev_gpio)) {
+ err = PTR_ERR(charger->chglev_gpio);
+ dev_err(dev, "unable to claim gpio \"chglev\": %d\n", err);
+ return err;
+ }
+
+ if (of_property_read_u32(np,
+ "active-semi,input-voltage-threshold-microvolt",
+ &input_voltage_threshold))
+ input_voltage_threshold = DEFAULT_INPUT_OVP_THRESHOLD;
+
+ if (of_property_read_u32(np,
+ "active-semi,precondition-timeout",
+ &pre_time_out))
+ pre_time_out = DEFAULT_PRE_TIME_OUT;
+
+ if (of_property_read_u32(np, "active-semi,total-timeout",
+ &total_time_out))
+ total_time_out = DEFAULT_TOTAL_TIME_OUT;
+
+ switch (input_voltage_threshold) {
+ case 8000:
+ value |= APCH_CFG_OVPSET_8V;
+ break;
+ case 7500:
+ value |= APCH_CFG_OVPSET_7V5;
+ break;
+ case 7000:
+ value |= APCH_CFG_OVPSET_7V;
+ break;
+ case 6600:
+ default:
+ value |= APCH_CFG_OVPSET_6V6;
+ break;
+ }
+
+ switch (pre_time_out) {
+ case 60:
+ value |= APCH_CFG_PRETIMO_60_MIN;
+ break;
+ case 80:
+ value |= APCH_CFG_PRETIMO_80_MIN;
+ break;
+ case 0:
+ value |= APCH_CFG_PRETIMO_DISABLED;
+ break;
+ case 40:
+ default:
+ value |= APCH_CFG_PRETIMO_40_MIN;
+ break;
+ }
+
+ switch (total_time_out) {
+ case 4:
+ value |= APCH_CFG_TOTTIMO_4_HOUR;
+ break;
+ case 5:
+ value |= APCH_CFG_TOTTIMO_5_HOUR;
+ break;
+ case 0:
+ value |= APCH_CFG_TOTTIMO_DISABLED;
+ break;
+ case 3:
+ default:
+ value |= APCH_CFG_TOTTIMO_3_HOUR;
+ break;
+ }
+
+ return regmap_write(regmap, ACT8945A_APCH_CFG, value);
+}
+
+static int act8945a_charger_probe(struct platform_device *pdev)
+{
+ struct act8945a_charger *charger;
+ struct power_supply_config psy_cfg = {};
+ int irq, ret;
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ charger->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!charger->regmap) {
+ dev_err(&pdev->dev, "Parent did not provide regmap\n");
+ return -EINVAL;
+ }
+
+ ret = act8945a_charger_config(&pdev->dev, charger);
+ if (ret)
+ return ret;
+
+ irq = of_irq_get(pdev->dev.of_node, 0);
+ if (irq == -EPROBE_DEFER) {
+ dev_err(&pdev->dev, "failed to find IRQ number\n");
+ return -EPROBE_DEFER;
+ }
+
+ ret = devm_request_irq(&pdev->dev, irq, act8945a_status_changed,
+ IRQF_TRIGGER_FALLING, "act8945a_interrupt",
+ charger);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request nIRQ pin IRQ\n");
+ return ret;
+ }
+
+ charger->desc.name = "act8945a-charger";
+ charger->desc.get_property = act8945a_charger_get_property;
+ charger->desc.properties = act8945a_charger_props;
+ charger->desc.num_properties = ARRAY_SIZE(act8945a_charger_props);
+
+ ret = act8945a_set_supply_type(charger, &charger->desc.type);
+ if (ret)
+ return -EINVAL;
+
+ psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.drv_data = charger;
+
+ charger->psy = devm_power_supply_register(&pdev->dev,
+ &charger->desc,
+ &psy_cfg);
+ if (IS_ERR(charger->psy)) {
+ dev_err(&pdev->dev, "failed to register power supply\n");
+ return PTR_ERR(charger->psy);
+ }
+
+ platform_set_drvdata(pdev, charger);
+
+ INIT_WORK(&charger->work, act8945a_work);
+
+ ret = act8945a_enable_interrupt(charger);
+ if (ret)
+ return -EIO;
+
+ charger->init_done = true;
+
+ return 0;
+}
+
+static int act8945a_charger_remove(struct platform_device *pdev)
+{
+ struct act8945a_charger *charger = platform_get_drvdata(pdev);
+
+ charger->init_done = false;
+ cancel_work_sync(&charger->work);
+
+ return 0;
+}
+
+static struct platform_driver act8945a_charger_driver = {
+ .driver = {
+ .name = "act8945a-charger",
+ },
+ .probe = act8945a_charger_probe,
+ .remove = act8945a_charger_remove,
+};
+module_platform_driver(act8945a_charger_driver);
+
+MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver");
+MODULE_AUTHOR("Wenyou Yang <wenyou.yang@atmel.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/apm_power.c b/drivers/power/supply/apm_power.c
new file mode 100644
index 0000000..9d1a7fb
--- /dev/null
+++ b/drivers/power/supply/apm_power.c
@@ -0,0 +1,376 @@
+/*
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2007 Eugeny Boger <eugenyboger@dgap.mipt.ru>
+ *
+ * Author: Eugeny Boger <eugenyboger@dgap.mipt.ru>
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ */
+
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/power_supply.h>
+#include <linux/apm-emulation.h>
+
+
+#define PSY_PROP(psy, prop, val) (power_supply_get_property(psy, \
+ POWER_SUPPLY_PROP_##prop, val))
+
+#define _MPSY_PROP(prop, val) (power_supply_get_property(main_battery, \
+ prop, val))
+
+#define MPSY_PROP(prop, val) _MPSY_PROP(POWER_SUPPLY_PROP_##prop, val)
+
+static DEFINE_MUTEX(apm_mutex);
+static struct power_supply *main_battery;
+
+enum apm_source {
+ SOURCE_ENERGY,
+ SOURCE_CHARGE,
+ SOURCE_VOLTAGE,
+};
+
+struct find_bat_param {
+ struct power_supply *main;
+ struct power_supply *bat;
+ struct power_supply *max_charge_bat;
+ struct power_supply *max_energy_bat;
+ union power_supply_propval full;
+ int max_charge;
+ int max_energy;
+};
+
+static int __find_main_battery(struct device *dev, void *data)
+{
+ struct find_bat_param *bp = (struct find_bat_param *)data;
+
+ bp->bat = dev_get_drvdata(dev);
+
+ if (bp->bat->desc->use_for_apm) {
+ /* nice, we explicitly asked to report this battery. */
+ bp->main = bp->bat;
+ return 1;
+ }
+
+ if (!PSY_PROP(bp->bat, CHARGE_FULL_DESIGN, &bp->full) ||
+ !PSY_PROP(bp->bat, CHARGE_FULL, &bp->full)) {
+ if (bp->full.intval > bp->max_charge) {
+ bp->max_charge_bat = bp->bat;
+ bp->max_charge = bp->full.intval;
+ }
+ } else if (!PSY_PROP(bp->bat, ENERGY_FULL_DESIGN, &bp->full) ||
+ !PSY_PROP(bp->bat, ENERGY_FULL, &bp->full)) {
+ if (bp->full.intval > bp->max_energy) {
+ bp->max_energy_bat = bp->bat;
+ bp->max_energy = bp->full.intval;
+ }
+ }
+ return 0;
+}
+
+static void find_main_battery(void)
+{
+ struct find_bat_param bp;
+ int error;
+
+ memset(&bp, 0, sizeof(struct find_bat_param));
+ main_battery = NULL;
+ bp.main = main_battery;
+
+ error = class_for_each_device(power_supply_class, NULL, &bp,
+ __find_main_battery);
+ if (error) {
+ main_battery = bp.main;
+ return;
+ }
+
+ if ((bp.max_energy_bat && bp.max_charge_bat) &&
+ (bp.max_energy_bat != bp.max_charge_bat)) {
+ /* try guess battery with more capacity */
+ if (!PSY_PROP(bp.max_charge_bat, VOLTAGE_MAX_DESIGN,
+ &bp.full)) {
+ if (bp.max_energy > bp.max_charge * bp.full.intval)
+ main_battery = bp.max_energy_bat;
+ else
+ main_battery = bp.max_charge_bat;
+ } else if (!PSY_PROP(bp.max_energy_bat, VOLTAGE_MAX_DESIGN,
+ &bp.full)) {
+ if (bp.max_charge > bp.max_energy / bp.full.intval)
+ main_battery = bp.max_charge_bat;
+ else
+ main_battery = bp.max_energy_bat;
+ } else {
+ /* give up, choice any */
+ main_battery = bp.max_energy_bat;
+ }
+ } else if (bp.max_charge_bat) {
+ main_battery = bp.max_charge_bat;
+ } else if (bp.max_energy_bat) {
+ main_battery = bp.max_energy_bat;
+ } else {
+ /* give up, try the last if any */
+ main_battery = bp.bat;
+ }
+}
+
+static int do_calculate_time(int status, enum apm_source source)
+{
+ union power_supply_propval full;
+ union power_supply_propval empty;
+ union power_supply_propval cur;
+ union power_supply_propval I;
+ enum power_supply_property full_prop;
+ enum power_supply_property full_design_prop;
+ enum power_supply_property empty_prop;
+ enum power_supply_property empty_design_prop;
+ enum power_supply_property cur_avg_prop;
+ enum power_supply_property cur_now_prop;
+
+ if (MPSY_PROP(CURRENT_AVG, &I)) {
+ /* if battery can't report average value, use momentary */
+ if (MPSY_PROP(CURRENT_NOW, &I))
+ return -1;
+ }
+
+ if (!I.intval)
+ return 0;
+
+ switch (source) {
+ case SOURCE_CHARGE:
+ full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
+ full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ cur_avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
+ break;
+ case SOURCE_ENERGY:
+ full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
+ full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ cur_avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
+ break;
+ case SOURCE_VOLTAGE:
+ full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ cur_avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ cur_now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ break;
+ default:
+ printk(KERN_ERR "Unsupported source: %d\n", source);
+ return -1;
+ }
+
+ if (_MPSY_PROP(full_prop, &full)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(full_design_prop, &full))
+ return -1;
+ }
+
+ if (_MPSY_PROP(empty_prop, &empty)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(empty_design_prop, &empty))
+ empty.intval = 0;
+ }
+
+ if (_MPSY_PROP(cur_avg_prop, &cur)) {
+ /* if battery can't report average value, use momentary */
+ if (_MPSY_PROP(cur_now_prop, &cur))
+ return -1;
+ }
+
+ if (status == POWER_SUPPLY_STATUS_CHARGING)
+ return ((cur.intval - full.intval) * 60L) / I.intval;
+ else
+ return -((cur.intval - empty.intval) * 60L) / I.intval;
+}
+
+static int calculate_time(int status)
+{
+ int time;
+
+ time = do_calculate_time(status, SOURCE_ENERGY);
+ if (time != -1)
+ return time;
+
+ time = do_calculate_time(status, SOURCE_CHARGE);
+ if (time != -1)
+ return time;
+
+ time = do_calculate_time(status, SOURCE_VOLTAGE);
+ if (time != -1)
+ return time;
+
+ return -1;
+}
+
+static int calculate_capacity(enum apm_source source)
+{
+ enum power_supply_property full_prop, empty_prop;
+ enum power_supply_property full_design_prop, empty_design_prop;
+ enum power_supply_property now_prop, avg_prop;
+ union power_supply_propval empty, full, cur;
+ int ret;
+
+ switch (source) {
+ case SOURCE_CHARGE:
+ full_prop = POWER_SUPPLY_PROP_CHARGE_FULL;
+ empty_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY;
+ full_design_prop = POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_CHARGE_NOW;
+ avg_prop = POWER_SUPPLY_PROP_CHARGE_AVG;
+ break;
+ case SOURCE_ENERGY:
+ full_prop = POWER_SUPPLY_PROP_ENERGY_FULL;
+ empty_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY;
+ full_design_prop = POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_ENERGY_EMPTY_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_ENERGY_NOW;
+ avg_prop = POWER_SUPPLY_PROP_ENERGY_AVG;
+ break;
+ case SOURCE_VOLTAGE:
+ full_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ empty_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+ full_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN;
+ empty_design_prop = POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN;
+ now_prop = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ avg_prop = POWER_SUPPLY_PROP_VOLTAGE_AVG;
+ break;
+ default:
+ printk(KERN_ERR "Unsupported source: %d\n", source);
+ return -1;
+ }
+
+ if (_MPSY_PROP(full_prop, &full)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(full_design_prop, &full))
+ return -1;
+ }
+
+ if (_MPSY_PROP(avg_prop, &cur)) {
+ /* if battery can't report average value, use momentary */
+ if (_MPSY_PROP(now_prop, &cur))
+ return -1;
+ }
+
+ if (_MPSY_PROP(empty_prop, &empty)) {
+ /* if battery can't report this property, use design value */
+ if (_MPSY_PROP(empty_design_prop, &empty))
+ empty.intval = 0;
+ }
+
+ if (full.intval - empty.intval)
+ ret = ((cur.intval - empty.intval) * 100L) /
+ (full.intval - empty.intval);
+ else
+ return -1;
+
+ if (ret > 100)
+ return 100;
+ else if (ret < 0)
+ return 0;
+
+ return ret;
+}
+
+static void apm_battery_apm_get_power_status(struct apm_power_info *info)
+{
+ union power_supply_propval status;
+ union power_supply_propval capacity, time_to_full, time_to_empty;
+
+ mutex_lock(&apm_mutex);
+ find_main_battery();
+ if (!main_battery) {
+ mutex_unlock(&apm_mutex);
+ return;
+ }
+
+ /* status */
+
+ if (MPSY_PROP(STATUS, &status))
+ status.intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* ac line status */
+
+ if ((status.intval == POWER_SUPPLY_STATUS_CHARGING) ||
+ (status.intval == POWER_SUPPLY_STATUS_NOT_CHARGING) ||
+ (status.intval == POWER_SUPPLY_STATUS_FULL))
+ info->ac_line_status = APM_AC_ONLINE;
+ else
+ info->ac_line_status = APM_AC_OFFLINE;
+
+ /* battery life (i.e. capacity, in percents) */
+
+ if (MPSY_PROP(CAPACITY, &capacity) == 0) {
+ info->battery_life = capacity.intval;
+ } else {
+ /* try calculate using energy */
+ info->battery_life = calculate_capacity(SOURCE_ENERGY);
+ /* if failed try calculate using charge instead */
+ if (info->battery_life == -1)
+ info->battery_life = calculate_capacity(SOURCE_CHARGE);
+ if (info->battery_life == -1)
+ info->battery_life = calculate_capacity(SOURCE_VOLTAGE);
+ }
+
+ /* charging status */
+
+ if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ info->battery_status = APM_BATTERY_STATUS_CHARGING;
+ } else {
+ if (info->battery_life > 50)
+ info->battery_status = APM_BATTERY_STATUS_HIGH;
+ else if (info->battery_life > 5)
+ info->battery_status = APM_BATTERY_STATUS_LOW;
+ else
+ info->battery_status = APM_BATTERY_STATUS_CRITICAL;
+ }
+ info->battery_flag = info->battery_status;
+
+ /* time */
+
+ info->units = APM_UNITS_MINS;
+
+ if (status.intval == POWER_SUPPLY_STATUS_CHARGING) {
+ if (!MPSY_PROP(TIME_TO_FULL_AVG, &time_to_full) ||
+ !MPSY_PROP(TIME_TO_FULL_NOW, &time_to_full))
+ info->time = time_to_full.intval / 60;
+ else
+ info->time = calculate_time(status.intval);
+ } else {
+ if (!MPSY_PROP(TIME_TO_EMPTY_AVG, &time_to_empty) ||
+ !MPSY_PROP(TIME_TO_EMPTY_NOW, &time_to_empty))
+ info->time = time_to_empty.intval / 60;
+ else
+ info->time = calculate_time(status.intval);
+ }
+
+ mutex_unlock(&apm_mutex);
+}
+
+static int __init apm_battery_init(void)
+{
+ printk(KERN_INFO "APM Battery Driver\n");
+
+ apm_get_power_status = apm_battery_apm_get_power_status;
+ return 0;
+}
+
+static void __exit apm_battery_exit(void)
+{
+ apm_get_power_status = NULL;
+}
+
+module_init(apm_battery_init);
+module_exit(apm_battery_exit);
+
+MODULE_AUTHOR("Eugeny Boger <eugenyboger@dgap.mipt.ru>");
+MODULE_DESCRIPTION("APM emulation driver for battery monitoring class");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/axp20x_usb_power.c b/drivers/power/supply/axp20x_usb_power.c
new file mode 100644
index 0000000..6af6feb
--- /dev/null
+++ b/drivers/power/supply/axp20x_usb_power.c
@@ -0,0 +1,294 @@
+/*
+ * AXP20x PMIC USB power supply status driver
+ *
+ * Copyright (C) 2015 Hans de Goede <hdegoede@redhat.com>
+ * Copyright (C) 2014 Bruno Prémont <bonbons@linux-vserver.org>
+ *
+ * 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.
+ */
+
+#include <linux/device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+
+#define DRVNAME "axp20x-usb-power-supply"
+
+#define AXP20X_PWR_STATUS_VBUS_PRESENT BIT(5)
+#define AXP20X_PWR_STATUS_VBUS_USED BIT(4)
+
+#define AXP20X_USB_STATUS_VBUS_VALID BIT(2)
+
+#define AXP20X_VBUS_VHOLD_uV(b) (4000000 + (((b) >> 3) & 7) * 100000)
+#define AXP20X_VBUS_CLIMIT_MASK 3
+#define AXP20X_VBUC_CLIMIT_900mA 0
+#define AXP20X_VBUC_CLIMIT_500mA 1
+#define AXP20X_VBUC_CLIMIT_100mA 2
+#define AXP20X_VBUC_CLIMIT_NONE 3
+
+#define AXP20X_ADC_EN1_VBUS_CURR BIT(2)
+#define AXP20X_ADC_EN1_VBUS_VOLT BIT(3)
+
+#define AXP20X_VBUS_MON_VBUS_VALID BIT(3)
+
+struct axp20x_usb_power {
+ struct device_node *np;
+ struct regmap *regmap;
+ struct power_supply *supply;
+};
+
+static irqreturn_t axp20x_usb_power_irq(int irq, void *devid)
+{
+ struct axp20x_usb_power *power = devid;
+
+ power_supply_changed(power->supply);
+
+ return IRQ_HANDLED;
+}
+
+static int axp20x_usb_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct axp20x_usb_power *power = power_supply_get_drvdata(psy);
+ unsigned int input, v;
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
+ if (ret)
+ return ret;
+
+ val->intval = AXP20X_VBUS_VHOLD_uV(v);
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = axp20x_read_variable_width(power->regmap,
+ AXP20X_VBUS_V_ADC_H, 12);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret * 1700; /* 1 step = 1.7 mV */
+ return 0;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ ret = regmap_read(power->regmap, AXP20X_VBUS_IPSOUT_MGMT, &v);
+ if (ret)
+ return ret;
+
+ switch (v & AXP20X_VBUS_CLIMIT_MASK) {
+ case AXP20X_VBUC_CLIMIT_100mA:
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ val->intval = 100000;
+ } else {
+ val->intval = -1; /* No 100mA limit */
+ }
+ break;
+ case AXP20X_VBUC_CLIMIT_500mA:
+ val->intval = 500000;
+ break;
+ case AXP20X_VBUC_CLIMIT_900mA:
+ val->intval = 900000;
+ break;
+ case AXP20X_VBUC_CLIMIT_NONE:
+ val->intval = -1;
+ break;
+ }
+ return 0;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = axp20x_read_variable_width(power->regmap,
+ AXP20X_VBUS_I_ADC_H, 12);
+ if (ret < 0)
+ return ret;
+
+ val->intval = ret * 375; /* 1 step = 0.375 mA */
+ return 0;
+ default:
+ break;
+ }
+
+ /* All the properties below need the input-status reg value */
+ ret = regmap_read(power->regmap, AXP20X_PWR_INPUT_STATUS, &input);
+ if (ret)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (!(input & AXP20X_PWR_STATUS_VBUS_PRESENT)) {
+ val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ }
+
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ ret = regmap_read(power->regmap,
+ AXP20X_USB_OTG_STATUS, &v);
+ if (ret)
+ return ret;
+
+ if (!(v & AXP20X_USB_STATUS_VBUS_VALID))
+ val->intval =
+ POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ }
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(input & AXP20X_PWR_STATUS_VBUS_USED);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property axp20x_usb_power_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static enum power_supply_property axp22x_usb_power_properties[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static const struct power_supply_desc axp20x_usb_power_desc = {
+ .name = "axp20x-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = axp20x_usb_power_properties,
+ .num_properties = ARRAY_SIZE(axp20x_usb_power_properties),
+ .get_property = axp20x_usb_power_get_property,
+};
+
+static const struct power_supply_desc axp22x_usb_power_desc = {
+ .name = "axp20x-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = axp22x_usb_power_properties,
+ .num_properties = ARRAY_SIZE(axp22x_usb_power_properties),
+ .get_property = axp20x_usb_power_get_property,
+};
+
+static int axp20x_usb_power_probe(struct platform_device *pdev)
+{
+ struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct axp20x_usb_power *power;
+ static const char * const axp20x_irq_names[] = { "VBUS_PLUGIN",
+ "VBUS_REMOVAL", "VBUS_VALID", "VBUS_NOT_VALID", NULL };
+ static const char * const axp22x_irq_names[] = {
+ "VBUS_PLUGIN", "VBUS_REMOVAL", NULL };
+ static const char * const *irq_names;
+ const struct power_supply_desc *usb_power_desc;
+ int i, irq, ret;
+
+ if (!of_device_is_available(pdev->dev.of_node))
+ return -ENODEV;
+
+ if (!axp20x) {
+ dev_err(&pdev->dev, "Parent drvdata not set\n");
+ return -EINVAL;
+ }
+
+ power = devm_kzalloc(&pdev->dev, sizeof(*power), GFP_KERNEL);
+ if (!power)
+ return -ENOMEM;
+
+ power->np = pdev->dev.of_node;
+ power->regmap = axp20x->regmap;
+
+ if (of_device_is_compatible(power->np,
+ "x-powers,axp202-usb-power-supply")) {
+ /* Enable vbus valid checking */
+ ret = regmap_update_bits(power->regmap, AXP20X_VBUS_MON,
+ AXP20X_VBUS_MON_VBUS_VALID,
+ AXP20X_VBUS_MON_VBUS_VALID);
+ if (ret)
+ return ret;
+
+ /* Enable vbus voltage and current measurement */
+ ret = regmap_update_bits(power->regmap, AXP20X_ADC_EN1,
+ AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT,
+ AXP20X_ADC_EN1_VBUS_CURR | AXP20X_ADC_EN1_VBUS_VOLT);
+ if (ret)
+ return ret;
+
+ usb_power_desc = &axp20x_usb_power_desc;
+ irq_names = axp20x_irq_names;
+ } else if (of_device_is_compatible(power->np,
+ "x-powers,axp221-usb-power-supply")) {
+ usb_power_desc = &axp22x_usb_power_desc;
+ irq_names = axp22x_irq_names;
+ } else {
+ dev_err(&pdev->dev, "Unsupported AXP variant: %ld\n",
+ axp20x->variant);
+ return -EINVAL;
+ }
+
+ psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.drv_data = power;
+
+ power->supply = devm_power_supply_register(&pdev->dev, usb_power_desc,
+ &psy_cfg);
+ if (IS_ERR(power->supply))
+ return PTR_ERR(power->supply);
+
+ /* Request irqs after registering, as irqs may trigger immediately */
+ for (i = 0; irq_names[i]; i++) {
+ irq = platform_get_irq_byname(pdev, irq_names[i]);
+ if (irq < 0) {
+ dev_warn(&pdev->dev, "No IRQ for %s: %d\n",
+ irq_names[i], irq);
+ continue;
+ }
+ irq = regmap_irq_get_virq(axp20x->regmap_irqc, irq);
+ ret = devm_request_any_context_irq(&pdev->dev, irq,
+ axp20x_usb_power_irq, 0, DRVNAME, power);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "Error requesting %s IRQ: %d\n",
+ irq_names[i], ret);
+ }
+
+ return 0;
+}
+
+static const struct of_device_id axp20x_usb_power_match[] = {
+ { .compatible = "x-powers,axp202-usb-power-supply" },
+ { .compatible = "x-powers,axp221-usb-power-supply" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, axp20x_usb_power_match);
+
+static struct platform_driver axp20x_usb_power_driver = {
+ .probe = axp20x_usb_power_probe,
+ .driver = {
+ .name = DRVNAME,
+ .of_match_table = axp20x_usb_power_match,
+ },
+};
+
+module_platform_driver(axp20x_usb_power_driver);
+
+MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>");
+MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c
new file mode 100644
index 0000000..75b8e0c
--- /dev/null
+++ b/drivers/power/supply/axp288_charger.c
@@ -0,0 +1,969 @@
+/*
+ * axp288_charger.c - X-power AXP288 PMIC Charger driver
+ *
+ * Copyright (C) 2014 Intel Corporation
+ * Author: Ramakrishna Pallala <ramakrishna.pallala@intel.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.
+ *
+ * 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/module.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/usb/otg.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/property.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 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 VBUS_ISPOUT_CUR_LIM_MASK 0x03
+#define VBUS_ISPOUT_CUR_LIM_BIT_POS 0
+#define VBUS_ISPOUT_CUR_LIM_900MA 0x0 /* 900mA */
+#define VBUS_ISPOUT_CUR_LIM_1500MA 0x1 /* 1500mA */
+#define VBUS_ISPOUT_CUR_LIM_2000MA 0x2 /* 2000mA */
+#define VBUS_ISPOUT_CUR_NO_LIM 0x3 /* 2500mA */
+#define VBUS_ISPOUT_VHOLD_SET_MASK 0x31
+#define VBUS_ISPOUT_VHOLD_SET_BIT_POS 0x3
+#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 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_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 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_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_VBUS_ILIM_MASK 0xf0
+#define CHRG_VBUS_ILIM_BIT_POS 4
+#define CHRG_VBUS_ILIM_100MA 0x0 /* 100mA */
+#define CHRG_VBUS_ILIM_500MA 0x1 /* 500mA */
+#define CHRG_VBUS_ILIM_900MA 0x2 /* 900mA */
+#define CHRG_VBUS_ILIM_1500MA 0x3 /* 1500mA */
+#define CHRG_VBUS_ILIM_2000MA 0x4 /* 2000mA */
+#define CHRG_VBUS_ILIM_2500MA 0x5 /* 2500mA */
+#define CHRG_VBUS_ILIM_3000MA 0x6 /* 3000mA */
+
+#define CHRG_VLTFC_0C 0xA5 /* 0 DegC */
+#define CHRG_VHTFC_45C 0x1F /* 45 DegC */
+
+#define BAT_IRQ_CFG_CHRG_DONE (1 << 2)
+#define BAT_IRQ_CFG_CHRG_START (1 << 3)
+#define BAT_IRQ_CFG_BAT_SAFE_EXIT (1 << 4)
+#define BAT_IRQ_CFG_BAT_SAFE_ENTER (1 << 5)
+#define BAT_IRQ_CFG_BAT_DISCON (1 << 6)
+#define BAT_IRQ_CFG_BAT_CONN (1 << 7)
+#define BAT_IRQ_CFG_BAT_MASK 0xFC
+
+#define TEMP_IRQ_CFG_QCBTU (1 << 4)
+#define TEMP_IRQ_CFG_CBTU (1 << 5)
+#define TEMP_IRQ_CFG_QCBTO (1 << 6)
+#define TEMP_IRQ_CFG_CBTO (1 << 7)
+#define TEMP_IRQ_CFG_MASK 0xF0
+
+#define FG_CNTL_OCV_ADJ_EN (1 << 3)
+
+#define CV_4100MV 4100 /* 4100mV */
+#define CV_4150MV 4150 /* 4150mV */
+#define CV_4200MV 4200 /* 4200mV */
+#define CV_4350MV 4350 /* 4350mV */
+
+#define CC_200MA 200 /* 200mA */
+#define CC_600MA 600 /* 600mA */
+#define CC_800MA 800 /* 800mA */
+#define CC_1000MA 1000 /* 1000mA */
+#define CC_1600MA 1600 /* 1600mA */
+#define CC_2000MA 2000 /* 2000mA */
+
+#define ILIM_100MA 100 /* 100mA */
+#define ILIM_500MA 500 /* 500mA */
+#define ILIM_900MA 900 /* 900mA */
+#define ILIM_1500MA 1500 /* 1500mA */
+#define ILIM_2000MA 2000 /* 2000mA */
+#define ILIM_2500MA 2500 /* 2500mA */
+#define ILIM_3000MA 3000 /* 3000mA */
+
+#define AXP288_EXTCON_DEV_NAME "axp288_extcon"
+
+enum {
+ VBUS_OV_IRQ = 0,
+ CHARGE_DONE_IRQ,
+ CHARGE_CHARGING_IRQ,
+ BAT_SAFE_QUIT_IRQ,
+ BAT_SAFE_ENTER_IRQ,
+ QCBTU_IRQ,
+ CBTU_IRQ,
+ QCBTO_IRQ,
+ CBTO_IRQ,
+ CHRG_INTR_END,
+};
+
+struct axp288_chrg_info {
+ struct platform_device *pdev;
+ struct axp20x_chrg_pdata *pdata;
+ struct regmap *regmap;
+ struct regmap_irq_chip_data *regmap_irqc;
+ int irq[CHRG_INTR_END];
+ struct power_supply *psy_usb;
+ struct mutex lock;
+
+ /* OTG/Host mode */
+ struct {
+ struct work_struct work;
+ struct extcon_dev *cable;
+ struct notifier_block id_nb;
+ bool id_short;
+ } otg;
+
+ /* SDP/CDP/DCP USB charging cable notifications */
+ struct {
+ struct extcon_dev *edev;
+ bool connected;
+ enum power_supply_type chg_type;
+ struct notifier_block nb;
+ struct work_struct work;
+ } cable;
+
+ int health;
+ int inlmt;
+ int cc;
+ int cv;
+ int max_cc;
+ int max_cv;
+ bool online;
+ bool present;
+ bool enable_charger;
+ bool is_charger_enabled;
+};
+
+static inline int axp288_charger_set_cc(struct axp288_chrg_info *info, int cc)
+{
+ u8 reg_val;
+ int ret;
+
+ if (cc < CHRG_CCCV_CC_OFFSET)
+ cc = CHRG_CCCV_CC_OFFSET;
+ else if (cc > info->max_cc)
+ cc = info->max_cc;
+
+ reg_val = (cc - CHRG_CCCV_CC_OFFSET) / CHRG_CCCV_CC_LSB_RES;
+ cc = (reg_val * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
+ reg_val = reg_val << CHRG_CCCV_CC_BIT_POS;
+
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_CHRG_CTRL1,
+ CHRG_CCCV_CC_MASK, reg_val);
+ if (ret >= 0)
+ info->cc = cc;
+
+ return ret;
+}
+
+static inline int axp288_charger_set_cv(struct axp288_chrg_info *info, int cv)
+{
+ u8 reg_val;
+ int ret;
+
+ if (cv <= CV_4100MV) {
+ reg_val = CHRG_CCCV_CV_4100MV;
+ cv = CV_4100MV;
+ } else if (cv <= CV_4150MV) {
+ reg_val = CHRG_CCCV_CV_4150MV;
+ cv = CV_4150MV;
+ } else if (cv <= CV_4200MV) {
+ reg_val = CHRG_CCCV_CV_4200MV;
+ cv = CV_4200MV;
+ } else {
+ reg_val = CHRG_CCCV_CV_4350MV;
+ cv = CV_4350MV;
+ }
+
+ reg_val = reg_val << CHRG_CCCV_CV_BIT_POS;
+
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_CHRG_CTRL1,
+ CHRG_CCCV_CV_MASK, reg_val);
+
+ if (ret >= 0)
+ info->cv = cv;
+
+ return ret;
+}
+
+static inline int axp288_charger_set_vbus_inlmt(struct axp288_chrg_info *info,
+ int inlmt)
+{
+ int ret;
+ unsigned int val;
+ u8 reg_val;
+
+ /* Read in limit register */
+ ret = regmap_read(info->regmap, AXP20X_CHRG_BAK_CTRL, &val);
+ if (ret < 0)
+ goto set_inlmt_fail;
+
+ if (inlmt <= ILIM_100MA) {
+ reg_val = CHRG_VBUS_ILIM_100MA;
+ inlmt = ILIM_100MA;
+ } else if (inlmt <= ILIM_500MA) {
+ reg_val = CHRG_VBUS_ILIM_500MA;
+ inlmt = ILIM_500MA;
+ } else if (inlmt <= ILIM_900MA) {
+ reg_val = CHRG_VBUS_ILIM_900MA;
+ inlmt = ILIM_900MA;
+ } else if (inlmt <= ILIM_1500MA) {
+ reg_val = CHRG_VBUS_ILIM_1500MA;
+ inlmt = ILIM_1500MA;
+ } else if (inlmt <= ILIM_2000MA) {
+ reg_val = CHRG_VBUS_ILIM_2000MA;
+ inlmt = ILIM_2000MA;
+ } else if (inlmt <= ILIM_2500MA) {
+ reg_val = CHRG_VBUS_ILIM_2500MA;
+ inlmt = ILIM_2500MA;
+ } else {
+ reg_val = CHRG_VBUS_ILIM_3000MA;
+ inlmt = ILIM_3000MA;
+ }
+
+ reg_val = (val & ~CHRG_VBUS_ILIM_MASK)
+ | (reg_val << CHRG_VBUS_ILIM_BIT_POS);
+ ret = regmap_write(info->regmap, AXP20X_CHRG_BAK_CTRL, reg_val);
+ if (ret >= 0)
+ info->inlmt = inlmt;
+ else
+ dev_err(&info->pdev->dev, "charger BAK control %d\n", ret);
+
+
+set_inlmt_fail:
+ return ret;
+}
+
+static int axp288_charger_vbus_path_select(struct axp288_chrg_info *info,
+ bool enable)
+{
+ int ret;
+
+ if (enable)
+ ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
+ VBUS_ISPOUT_VBUS_PATH_DIS, 0);
+ else
+ ret = regmap_update_bits(info->regmap, AXP20X_VBUS_IPSOUT_MGMT,
+ VBUS_ISPOUT_VBUS_PATH_DIS, VBUS_ISPOUT_VBUS_PATH_DIS);
+
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "axp288 vbus path select %d\n", ret);
+
+
+ return ret;
+}
+
+static int axp288_charger_enable_charger(struct axp288_chrg_info *info,
+ bool enable)
+{
+ int ret;
+
+ if (enable)
+ ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
+ CHRG_CCCV_CHG_EN, CHRG_CCCV_CHG_EN);
+ else
+ ret = regmap_update_bits(info->regmap, AXP20X_CHRG_CTRL1,
+ CHRG_CCCV_CHG_EN, 0);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "axp288 enable charger %d\n", ret);
+ else
+ info->is_charger_enabled = enable;
+
+ return ret;
+}
+
+static int axp288_charger_is_present(struct axp288_chrg_info *info)
+{
+ int ret, present = 0;
+ unsigned int val;
+
+ ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & PS_STAT_VBUS_PRESENT)
+ present = 1;
+ return present;
+}
+
+static int axp288_charger_is_online(struct axp288_chrg_info *info)
+{
+ int ret, online = 0;
+ unsigned int val;
+
+ ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+ if (ret < 0)
+ return ret;
+
+ if (val & PS_STAT_VBUS_VALID)
+ online = 1;
+ return online;
+}
+
+static int axp288_get_charger_health(struct axp288_chrg_info *info)
+{
+ int ret, pwr_stat, chrg_stat;
+ int health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ unsigned int val;
+
+ ret = regmap_read(info->regmap, AXP20X_PWR_INPUT_STATUS, &val);
+ if ((ret < 0) || !(val & PS_STAT_VBUS_PRESENT))
+ goto health_read_fail;
+ else
+ pwr_stat = val;
+
+ ret = regmap_read(info->regmap, AXP20X_PWR_OP_MODE, &val);
+ if (ret < 0)
+ goto health_read_fail;
+ else
+ chrg_stat = val;
+
+ if (!(pwr_stat & PS_STAT_VBUS_VALID))
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ else if (chrg_stat & CHRG_STAT_PMIC_OTP)
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (chrg_stat & CHRG_STAT_BAT_SAFE_MODE)
+ health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ else
+ health = POWER_SUPPLY_HEALTH_GOOD;
+
+health_read_fail:
+ return health;
+}
+
+static int axp288_charger_usb_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
+ int ret = 0;
+ int scaled_val;
+
+ mutex_lock(&info->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ scaled_val = min(val->intval, info->max_cc);
+ scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
+ ret = axp288_charger_set_cc(info, scaled_val);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "set charge current failed\n");
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ scaled_val = min(val->intval, info->max_cv);
+ scaled_val = DIV_ROUND_CLOSEST(scaled_val, 1000);
+ ret = axp288_charger_set_cv(info, scaled_val);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "set charge voltage failed\n");
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int axp288_charger_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct axp288_chrg_info *info = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ mutex_lock(&info->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ /* Check for OTG case first */
+ if (info->otg.id_short) {
+ val->intval = 0;
+ break;
+ }
+ ret = axp288_charger_is_present(info);
+ if (ret < 0)
+ goto psy_get_prop_fail;
+ info->present = ret;
+ val->intval = info->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* Check for OTG case first */
+ if (info->otg.id_short) {
+ val->intval = 0;
+ break;
+ }
+ ret = axp288_charger_is_online(info);
+ if (ret < 0)
+ goto psy_get_prop_fail;
+ info->online = ret;
+ val->intval = info->online;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = axp288_get_charger_health(info);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ val->intval = info->cc * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = info->max_cc * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ val->intval = info->cv * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ val->intval = info->max_cv * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ val->intval = info->inlmt * 1000;
+ break;
+ default:
+ ret = -EINVAL;
+ goto psy_get_prop_fail;
+ }
+
+psy_get_prop_fail:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int axp288_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ 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 axp288_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ 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_CHARGE_CONTROL_LIMIT,
+};
+
+static const struct power_supply_desc axp288_charger_desc = {
+ .name = "axp288_charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = axp288_usb_props,
+ .num_properties = ARRAY_SIZE(axp288_usb_props),
+ .get_property = axp288_charger_usb_get_property,
+ .set_property = axp288_charger_usb_set_property,
+ .property_is_writeable = axp288_charger_property_is_writeable,
+};
+
+static irqreturn_t axp288_charger_irq_thread_handler(int irq, void *dev)
+{
+ struct axp288_chrg_info *info = dev;
+ int i;
+
+ for (i = 0; i < CHRG_INTR_END; i++) {
+ if (info->irq[i] == irq)
+ break;
+ }
+
+ if (i >= CHRG_INTR_END) {
+ dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
+ return IRQ_NONE;
+ }
+
+ switch (i) {
+ case VBUS_OV_IRQ:
+ dev_dbg(&info->pdev->dev, "VBUS Over Voltage INTR\n");
+ break;
+ case CHARGE_DONE_IRQ:
+ dev_dbg(&info->pdev->dev, "Charging Done INTR\n");
+ break;
+ case CHARGE_CHARGING_IRQ:
+ dev_dbg(&info->pdev->dev, "Start Charging IRQ\n");
+ break;
+ case BAT_SAFE_QUIT_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Quit Safe Mode(restart timer) Charging IRQ\n");
+ break;
+ case BAT_SAFE_ENTER_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Enter Safe Mode(timer expire) Charging IRQ\n");
+ break;
+ case QCBTU_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Quit Battery Under Temperature(CHRG) INTR\n");
+ break;
+ case CBTU_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Hit Battery Under Temperature(CHRG) INTR\n");
+ break;
+ case QCBTO_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Quit Battery Over Temperature(CHRG) INTR\n");
+ break;
+ case CBTO_IRQ:
+ dev_dbg(&info->pdev->dev,
+ "Hit Battery Over Temperature(CHRG) INTR\n");
+ break;
+ default:
+ dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
+ goto out;
+ }
+
+ power_supply_changed(info->psy_usb);
+out:
+ return IRQ_HANDLED;
+}
+
+static void axp288_charger_extcon_evt_worker(struct work_struct *work)
+{
+ struct axp288_chrg_info *info =
+ container_of(work, struct axp288_chrg_info, cable.work);
+ int ret, current_limit;
+ bool changed = false;
+ struct extcon_dev *edev = info->cable.edev;
+ bool old_connected = info->cable.connected;
+
+ /* Determine cable/charger type */
+ if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_SDP) > 0) {
+ dev_dbg(&info->pdev->dev, "USB SDP charger is connected");
+ info->cable.connected = true;
+ info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
+ } else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_CDP) > 0) {
+ dev_dbg(&info->pdev->dev, "USB CDP charger is connected");
+ info->cable.connected = true;
+ info->cable.chg_type = POWER_SUPPLY_TYPE_USB_CDP;
+ } else if (extcon_get_cable_state_(edev, EXTCON_CHG_USB_DCP) > 0) {
+ dev_dbg(&info->pdev->dev, "USB DCP charger is connected");
+ info->cable.connected = true;
+ info->cable.chg_type = POWER_SUPPLY_TYPE_USB_DCP;
+ } else {
+ if (old_connected)
+ dev_dbg(&info->pdev->dev, "USB charger disconnected");
+ info->cable.connected = false;
+ info->cable.chg_type = POWER_SUPPLY_TYPE_USB;
+ }
+
+ /* Cable status changed */
+ if (old_connected != info->cable.connected)
+ changed = true;
+
+ if (!changed)
+ return;
+
+ mutex_lock(&info->lock);
+
+ if (info->is_charger_enabled && !info->cable.connected) {
+ info->enable_charger = false;
+ ret = axp288_charger_enable_charger(info, info->enable_charger);
+ if (ret < 0)
+ dev_err(&info->pdev->dev,
+ "cannot disable charger (%d)", ret);
+
+ } else if (!info->is_charger_enabled && info->cable.connected) {
+ switch (info->cable.chg_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ current_limit = ILIM_500MA;
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ current_limit = ILIM_1500MA;
+ break;
+ case POWER_SUPPLY_TYPE_USB_DCP:
+ current_limit = ILIM_2000MA;
+ break;
+ default:
+ /* Unknown */
+ current_limit = 0;
+ break;
+ }
+
+ /* Set vbus current limit first, then enable charger */
+ ret = axp288_charger_set_vbus_inlmt(info, current_limit);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev,
+ "error setting current limit (%d)", ret);
+ } else {
+ info->enable_charger = (current_limit > 0);
+ ret = axp288_charger_enable_charger(info,
+ info->enable_charger);
+ if (ret < 0)
+ dev_err(&info->pdev->dev,
+ "cannot enable charger (%d)", ret);
+ }
+ }
+
+ if (changed)
+ info->health = axp288_get_charger_health(info);
+
+ mutex_unlock(&info->lock);
+
+ if (changed)
+ power_supply_changed(info->psy_usb);
+}
+
+static int axp288_charger_handle_cable_evt(struct notifier_block *nb,
+ unsigned long event, void *param)
+{
+ struct axp288_chrg_info *info =
+ container_of(nb, struct axp288_chrg_info, cable.nb);
+
+ schedule_work(&info->cable.work);
+
+ return NOTIFY_OK;
+}
+
+static void axp288_charger_otg_evt_worker(struct work_struct *work)
+{
+ struct axp288_chrg_info *info =
+ container_of(work, struct axp288_chrg_info, otg.work);
+ int ret;
+
+ /* Disable VBUS path before enabling the 5V boost */
+ ret = axp288_charger_vbus_path_select(info, !info->otg.id_short);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "vbus path disable failed\n");
+}
+
+static int axp288_charger_handle_otg_evt(struct notifier_block *nb,
+ unsigned long event, void *param)
+{
+ struct axp288_chrg_info *info =
+ container_of(nb, struct axp288_chrg_info, otg.id_nb);
+ struct extcon_dev *edev = info->otg.cable;
+ int usb_host = extcon_get_cable_state_(edev, EXTCON_USB_HOST);
+
+ dev_dbg(&info->pdev->dev, "external connector USB-Host is %s\n",
+ usb_host ? "attached" : "detached");
+
+ /*
+ * Set usb_id_short flag to avoid running charger detection logic
+ * in case usb host.
+ */
+ info->otg.id_short = usb_host;
+ schedule_work(&info->otg.work);
+
+ return NOTIFY_OK;
+}
+
+static void charger_init_hw_regs(struct axp288_chrg_info *info)
+{
+ int ret, cc, cv;
+ unsigned int val;
+
+ /* Program temperature thresholds */
+ ret = regmap_write(info->regmap, AXP20X_V_LTF_CHRG, CHRG_VLTFC_0C);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_V_LTF_CHRG, ret);
+
+ ret = regmap_write(info->regmap, AXP20X_V_HTF_CHRG, CHRG_VHTFC_45C);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_V_HTF_CHRG, ret);
+
+ /* Do not turn-off charger o/p after charge cycle ends */
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_CHRG_CTRL2,
+ CNTL2_CHG_OUT_TURNON, 1);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_CHRG_CTRL2, ret);
+
+ /* Enable interrupts */
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_IRQ2_EN,
+ BAT_IRQ_CFG_BAT_MASK, 1);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_IRQ2_EN, ret);
+
+ ret = regmap_update_bits(info->regmap, AXP20X_IRQ3_EN,
+ TEMP_IRQ_CFG_MASK, 1);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_IRQ3_EN, ret);
+
+ /* Setup ending condition for charging to be 10% of I(chrg) */
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_CHRG_CTRL1,
+ CHRG_CCCV_ITERM_20P, 0);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_CHRG_CTRL1, ret);
+
+ /* Disable OCV-SOC curve calibration */
+ ret = regmap_update_bits(info->regmap,
+ AXP20X_CC_CTRL,
+ FG_CNTL_OCV_ADJ_EN, 0);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev, "register(%x) write error(%d)\n",
+ AXP20X_CC_CTRL, ret);
+
+ /* Init charging current and voltage */
+ info->max_cc = info->pdata->max_cc;
+ info->max_cv = info->pdata->max_cv;
+
+ /* Read current charge voltage and current limit */
+ ret = regmap_read(info->regmap, AXP20X_CHRG_CTRL1, &val);
+ if (ret < 0) {
+ /* Assume default if cannot read */
+ info->cc = info->pdata->def_cc;
+ info->cv = info->pdata->def_cv;
+ } else {
+ /* Determine charge voltage */
+ cv = (val & CHRG_CCCV_CV_MASK) >> CHRG_CCCV_CV_BIT_POS;
+ switch (cv) {
+ case CHRG_CCCV_CV_4100MV:
+ info->cv = CV_4100MV;
+ break;
+ case CHRG_CCCV_CV_4150MV:
+ info->cv = CV_4150MV;
+ break;
+ case CHRG_CCCV_CV_4200MV:
+ info->cv = CV_4200MV;
+ break;
+ case CHRG_CCCV_CV_4350MV:
+ info->cv = CV_4350MV;
+ break;
+ default:
+ info->cv = INT_MAX;
+ break;
+ }
+
+ /* Determine charge current limit */
+ cc = (ret & CHRG_CCCV_CC_MASK) >> CHRG_CCCV_CC_BIT_POS;
+ cc = (cc * CHRG_CCCV_CC_LSB_RES) + CHRG_CCCV_CC_OFFSET;
+ info->cc = cc;
+
+ /* Program default charging voltage and current */
+ cc = min(info->pdata->def_cc, info->max_cc);
+ cv = min(info->pdata->def_cv, info->max_cv);
+
+ ret = axp288_charger_set_cc(info, cc);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev,
+ "error(%d) in setting CC\n", ret);
+
+ ret = axp288_charger_set_cv(info, cv);
+ if (ret < 0)
+ dev_warn(&info->pdev->dev,
+ "error(%d) in setting CV\n", ret);
+ }
+}
+
+static int axp288_charger_probe(struct platform_device *pdev)
+{
+ int ret, i, pirq;
+ struct axp288_chrg_info *info;
+ struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config charger_cfg = {};
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->pdev = pdev;
+ info->regmap = axp20x->regmap;
+ info->regmap_irqc = axp20x->regmap_irqc;
+ info->pdata = pdev->dev.platform_data;
+
+ if (!info->pdata) {
+ /* Try ACPI provided pdata via device properties */
+ if (!device_property_present(&pdev->dev,
+ "axp288_charger_data\n"))
+ dev_err(&pdev->dev, "failed to get platform data\n");
+ return -ENODEV;
+ }
+
+ info->cable.edev = extcon_get_extcon_dev(AXP288_EXTCON_DEV_NAME);
+ if (info->cable.edev == NULL) {
+ dev_dbg(&pdev->dev, "%s is not ready, probe deferred\n",
+ AXP288_EXTCON_DEV_NAME);
+ return -EPROBE_DEFER;
+ }
+
+ /* Register for extcon notification */
+ INIT_WORK(&info->cable.work, axp288_charger_extcon_evt_worker);
+ info->cable.nb.notifier_call = axp288_charger_handle_cable_evt;
+ ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
+ &info->cable.nb);
+ if (ret) {
+ dev_err(&info->pdev->dev,
+ "failed to register extcon notifier for SDP %d\n", ret);
+ return ret;
+ }
+
+ ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
+ &info->cable.nb);
+ if (ret) {
+ dev_err(&info->pdev->dev,
+ "failed to register extcon notifier for CDP %d\n", ret);
+ extcon_unregister_notifier(info->cable.edev,
+ EXTCON_CHG_USB_SDP, &info->cable.nb);
+ return ret;
+ }
+
+ ret = extcon_register_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
+ &info->cable.nb);
+ if (ret) {
+ dev_err(&info->pdev->dev,
+ "failed to register extcon notifier for DCP %d\n", ret);
+ extcon_unregister_notifier(info->cable.edev,
+ EXTCON_CHG_USB_SDP, &info->cable.nb);
+ extcon_unregister_notifier(info->cable.edev,
+ EXTCON_CHG_USB_CDP, &info->cable.nb);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, info);
+ mutex_init(&info->lock);
+
+ /* Register with power supply class */
+ charger_cfg.drv_data = info;
+ info->psy_usb = power_supply_register(&pdev->dev, &axp288_charger_desc,
+ &charger_cfg);
+ if (IS_ERR(info->psy_usb)) {
+ dev_err(&pdev->dev, "failed to register power supply charger\n");
+ ret = PTR_ERR(info->psy_usb);
+ goto psy_reg_failed;
+ }
+
+ /* Register for OTG notification */
+ INIT_WORK(&info->otg.work, axp288_charger_otg_evt_worker);
+ info->otg.id_nb.notifier_call = axp288_charger_handle_otg_evt;
+ ret = extcon_register_notifier(info->otg.cable, EXTCON_USB_HOST,
+ &info->otg.id_nb);
+ if (ret)
+ dev_warn(&pdev->dev, "failed to register otg notifier\n");
+
+ if (info->otg.cable)
+ info->otg.id_short = extcon_get_cable_state_(
+ info->otg.cable, EXTCON_USB_HOST);
+
+ /* Register charger interrupts */
+ for (i = 0; i < CHRG_INTR_END; i++) {
+ pirq = platform_get_irq(info->pdev, i);
+ info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
+ if (info->irq[i] < 0) {
+ dev_warn(&info->pdev->dev,
+ "failed to get virtual interrupt=%d\n", pirq);
+ ret = info->irq[i];
+ goto intr_reg_failed;
+ }
+ ret = devm_request_threaded_irq(&info->pdev->dev, info->irq[i],
+ NULL, axp288_charger_irq_thread_handler,
+ IRQF_ONESHOT, info->pdev->name, info);
+ if (ret) {
+ dev_err(&pdev->dev, "failed to request interrupt=%d\n",
+ info->irq[i]);
+ goto intr_reg_failed;
+ }
+ }
+
+ charger_init_hw_regs(info);
+
+ return 0;
+
+intr_reg_failed:
+ if (info->otg.cable)
+ extcon_unregister_notifier(info->otg.cable, EXTCON_USB_HOST,
+ &info->otg.id_nb);
+ power_supply_unregister(info->psy_usb);
+psy_reg_failed:
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
+ &info->cable.nb);
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
+ &info->cable.nb);
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
+ &info->cable.nb);
+ return ret;
+}
+
+static int axp288_charger_remove(struct platform_device *pdev)
+{
+ struct axp288_chrg_info *info = dev_get_drvdata(&pdev->dev);
+
+ if (info->otg.cable)
+ extcon_unregister_notifier(info->otg.cable, EXTCON_USB_HOST,
+ &info->otg.id_nb);
+
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_SDP,
+ &info->cable.nb);
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_CDP,
+ &info->cable.nb);
+ extcon_unregister_notifier(info->cable.edev, EXTCON_CHG_USB_DCP,
+ &info->cable.nb);
+ power_supply_unregister(info->psy_usb);
+
+ return 0;
+}
+
+static struct platform_driver axp288_charger_driver = {
+ .probe = axp288_charger_probe,
+ .remove = axp288_charger_remove,
+ .driver = {
+ .name = "axp288_charger",
+ },
+};
+
+module_platform_driver(axp288_charger_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_DESCRIPTION("X-power AXP288 Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c
new file mode 100644
index 0000000..5bdde69
--- /dev/null
+++ b/drivers/power/supply/axp288_fuel_gauge.c
@@ -0,0 +1,1154 @@
+/*
+ * axp288_fuel_gauge.c - Xpower AXP288 PMIC Fuel Gauge Driver
+ *
+ * Copyright (C) 2014 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * 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/module.h>
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/jiffies.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/axp20x.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/iio/consumer.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+
+#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_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_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 CV_4100 4100 /* 4100mV */
+#define CV_4150 4150 /* 4150mV */
+#define CV_4200 4200 /* 4200mV */
+#define CV_4350 4350 /* 4350mV */
+
+#define TEMP_IRQ_CFG_QWBTU (1 << 0)
+#define TEMP_IRQ_CFG_WBTU (1 << 1)
+#define TEMP_IRQ_CFG_QWBTO (1 << 2)
+#define TEMP_IRQ_CFG_WBTO (1 << 3)
+#define TEMP_IRQ_CFG_MASK 0xf
+
+#define FG_IRQ_CFG_LOWBATT_WL2 (1 << 0)
+#define FG_IRQ_CFG_LOWBATT_WL1 (1 << 1)
+#define FG_IRQ_CFG_LOWBATT_MASK 0x3
+#define LOWBAT_IRQ_STAT_LOWBATT_WL2 (1 << 0)
+#define LOWBAT_IRQ_STAT_LOWBATT_WL1 (1 << 1)
+
+#define FG_CNTL_OCV_ADJ_STAT (1 << 2)
+#define FG_CNTL_OCV_ADJ_EN (1 << 3)
+#define FG_CNTL_CAP_ADJ_STAT (1 << 4)
+#define FG_CNTL_CAP_ADJ_EN (1 << 5)
+#define FG_CNTL_CC_EN (1 << 6)
+#define FG_CNTL_GAUGE_EN (1 << 7)
+
+#define FG_REP_CAP_VALID (1 << 7)
+#define FG_REP_CAP_VAL_MASK 0x7F
+
+#define FG_DES_CAP1_VALID (1 << 7)
+#define FG_DES_CAP1_VAL_MASK 0x7F
+#define FG_DES_CAP0_VAL_MASK 0xFF
+#define FG_DES_CAP_RES_LSB 1456 /* 1.456mAhr */
+
+#define FG_CC_MTR1_VALID (1 << 7)
+#define FG_CC_MTR1_VAL_MASK 0x7F
+#define FG_CC_MTR0_VAL_MASK 0xFF
+#define FG_DES_CC_RES_LSB 1456 /* 1.456mAhr */
+
+#define FG_OCV_CAP_VALID (1 << 7)
+#define FG_OCV_CAP_VAL_MASK 0x7F
+#define FG_CC_CAP_VALID (1 << 7)
+#define FG_CC_CAP_VAL_MASK 0x7F
+
+#define FG_LOW_CAP_THR1_MASK 0xf0 /* 5% tp 20% */
+#define FG_LOW_CAP_THR1_VAL 0xa0 /* 15 perc */
+#define FG_LOW_CAP_THR2_MASK 0x0f /* 0% to 15% */
+#define FG_LOW_CAP_WARN_THR 14 /* 14 perc */
+#define FG_LOW_CAP_CRIT_THR 4 /* 4 perc */
+#define FG_LOW_CAP_SHDN_THR 0 /* 0 perc */
+
+#define STATUS_MON_DELAY_JIFFIES (HZ * 60) /*60 sec */
+#define NR_RETRY_CNT 3
+#define DEV_NAME "axp288_fuel_gauge"
+
+/* 1.1mV per LSB expressed in uV */
+#define VOLTAGE_FROM_ADC(a) ((a * 11) / 10)
+/* properties converted to tenths of degrees, uV, uA, uW */
+#define PROP_TEMP(a) ((a) * 10)
+#define UNPROP_TEMP(a) ((a) / 10)
+#define PROP_VOLT(a) ((a) * 1000)
+#define PROP_CURR(a) ((a) * 1000)
+
+#define AXP288_FG_INTR_NUM 6
+enum {
+ QWBTU_IRQ = 0,
+ WBTU_IRQ,
+ QWBTO_IRQ,
+ WBTO_IRQ,
+ WL2_IRQ,
+ WL1_IRQ,
+};
+
+struct axp288_fg_info {
+ struct platform_device *pdev;
+ struct axp20x_fg_pdata *pdata;
+ struct regmap *regmap;
+ struct regmap_irq_chip_data *regmap_irqc;
+ int irq[AXP288_FG_INTR_NUM];
+ struct power_supply *bat;
+ struct mutex lock;
+ int status;
+ struct delayed_work status_monitor;
+ struct dentry *debug_file;
+};
+
+static enum power_supply_property fuel_gauge_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_MAX,
+ POWER_SUPPLY_PROP_TEMP_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int fuel_gauge_reg_readb(struct axp288_fg_info *info, int reg)
+{
+ int ret, i;
+ unsigned int val;
+
+ for (i = 0; i < NR_RETRY_CNT; i++) {
+ ret = regmap_read(info->regmap, reg, &val);
+ if (ret == -EBUSY)
+ continue;
+ else
+ break;
+ }
+
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "axp288 reg read err:%d\n", ret);
+
+ return val;
+}
+
+static int fuel_gauge_reg_writeb(struct axp288_fg_info *info, int reg, u8 val)
+{
+ int ret;
+
+ ret = regmap_write(info->regmap, reg, (unsigned int)val);
+
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "axp288 reg write err:%d\n", ret);
+
+ return ret;
+}
+
+static int pmic_read_adc_val(const char *name, int *raw_val,
+ struct axp288_fg_info *info)
+{
+ int ret, val = 0;
+ struct iio_channel *indio_chan;
+
+ indio_chan = iio_channel_get(NULL, name);
+ if (IS_ERR_OR_NULL(indio_chan)) {
+ ret = PTR_ERR(indio_chan);
+ goto exit;
+ }
+ ret = iio_read_channel_raw(indio_chan, &val);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev,
+ "IIO channel read error: %x, %x\n", ret, val);
+ goto err_exit;
+ }
+
+ dev_dbg(&info->pdev->dev, "adc raw val=%x\n", val);
+ *raw_val = val;
+
+err_exit:
+ iio_channel_release(indio_chan);
+exit:
+ return ret;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int fuel_gauge_debug_show(struct seq_file *s, void *data)
+{
+ struct axp288_fg_info *info = s->private;
+ int raw_val, ret;
+
+ seq_printf(s, " PWR_STATUS[%02x] : %02x\n",
+ AXP20X_PWR_INPUT_STATUS,
+ fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS));
+ seq_printf(s, "PWR_OP_MODE[%02x] : %02x\n",
+ AXP20X_PWR_OP_MODE,
+ fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE));
+ seq_printf(s, " CHRG_CTRL1[%02x] : %02x\n",
+ AXP20X_CHRG_CTRL1,
+ fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1));
+ seq_printf(s, " VLTF[%02x] : %02x\n",
+ AXP20X_V_LTF_DISCHRG,
+ fuel_gauge_reg_readb(info, AXP20X_V_LTF_DISCHRG));
+ seq_printf(s, " VHTF[%02x] : %02x\n",
+ AXP20X_V_HTF_DISCHRG,
+ fuel_gauge_reg_readb(info, AXP20X_V_HTF_DISCHRG));
+ seq_printf(s, " CC_CTRL[%02x] : %02x\n",
+ AXP20X_CC_CTRL,
+ fuel_gauge_reg_readb(info, AXP20X_CC_CTRL));
+ seq_printf(s, "BATTERY CAP[%02x] : %02x\n",
+ AXP20X_FG_RES,
+ fuel_gauge_reg_readb(info, AXP20X_FG_RES));
+ seq_printf(s, " FG_RDC1[%02x] : %02x\n",
+ AXP288_FG_RDC1_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_RDC1_REG));
+ seq_printf(s, " FG_RDC0[%02x] : %02x\n",
+ AXP288_FG_RDC0_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_RDC0_REG));
+ seq_printf(s, " FG_OCVH[%02x] : %02x\n",
+ AXP288_FG_OCVH_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG));
+ seq_printf(s, " FG_OCVL[%02x] : %02x\n",
+ AXP288_FG_OCVL_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG));
+ seq_printf(s, "FG_DES_CAP1[%02x] : %02x\n",
+ AXP288_FG_DES_CAP1_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG));
+ seq_printf(s, "FG_DES_CAP0[%02x] : %02x\n",
+ AXP288_FG_DES_CAP0_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG));
+ seq_printf(s, " FG_CC_MTR1[%02x] : %02x\n",
+ AXP288_FG_CC_MTR1_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG));
+ seq_printf(s, " FG_CC_MTR0[%02x] : %02x\n",
+ AXP288_FG_CC_MTR0_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG));
+ seq_printf(s, " FG_OCV_CAP[%02x] : %02x\n",
+ AXP288_FG_OCV_CAP_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_OCV_CAP_REG));
+ seq_printf(s, " FG_CC_CAP[%02x] : %02x\n",
+ AXP288_FG_CC_CAP_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_CC_CAP_REG));
+ seq_printf(s, " FG_LOW_CAP[%02x] : %02x\n",
+ AXP288_FG_LOW_CAP_REG,
+ fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG));
+ seq_printf(s, "TUNING_CTL0[%02x] : %02x\n",
+ AXP288_FG_TUNE0,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE0));
+ seq_printf(s, "TUNING_CTL1[%02x] : %02x\n",
+ AXP288_FG_TUNE1,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE1));
+ seq_printf(s, "TUNING_CTL2[%02x] : %02x\n",
+ AXP288_FG_TUNE2,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE2));
+ seq_printf(s, "TUNING_CTL3[%02x] : %02x\n",
+ AXP288_FG_TUNE3,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE3));
+ seq_printf(s, "TUNING_CTL4[%02x] : %02x\n",
+ AXP288_FG_TUNE4,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE4));
+ seq_printf(s, "TUNING_CTL5[%02x] : %02x\n",
+ AXP288_FG_TUNE5,
+ fuel_gauge_reg_readb(info, AXP288_FG_TUNE5));
+
+ ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-batttemp : %d\n", raw_val);
+ ret = pmic_read_adc_val("axp288-pmic-temp", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-pmictemp : %d\n", raw_val);
+ ret = pmic_read_adc_val("axp288-system-temp", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-systtemp : %d\n", raw_val);
+ ret = pmic_read_adc_val("axp288-chrg-curr", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-chrgcurr : %d\n", raw_val);
+ ret = pmic_read_adc_val("axp288-chrg-d-curr", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-dchrgcur : %d\n", raw_val);
+ ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
+ if (ret >= 0)
+ seq_printf(s, "axp288-battvolt : %d\n", raw_val);
+
+ return 0;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, fuel_gauge_debug_show, inode->i_private);
+}
+
+static const struct file_operations fg_debug_fops = {
+ .open = debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
+{
+ info->debug_file = debugfs_create_file("fuelgauge", 0666, NULL,
+ info, &fg_debug_fops);
+}
+
+static void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
+{
+ debugfs_remove(info->debug_file);
+}
+#else
+static inline void fuel_gauge_create_debugfs(struct axp288_fg_info *info)
+{
+}
+static inline void fuel_gauge_remove_debugfs(struct axp288_fg_info *info)
+{
+}
+#endif
+
+static void fuel_gauge_get_status(struct axp288_fg_info *info)
+{
+ int pwr_stat, ret;
+ int charge, discharge;
+
+ pwr_stat = fuel_gauge_reg_readb(info, AXP20X_PWR_INPUT_STATUS);
+ if (pwr_stat < 0) {
+ dev_err(&info->pdev->dev,
+ "PWR STAT read failed:%d\n", pwr_stat);
+ return;
+ }
+ ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev,
+ "ADC charge current read failed:%d\n", ret);
+ return;
+ }
+ ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev,
+ "ADC discharge current read failed:%d\n", ret);
+ return;
+ }
+
+ if (charge > 0)
+ info->status = POWER_SUPPLY_STATUS_CHARGING;
+ else if (discharge > 0)
+ info->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else {
+ if (pwr_stat & CHRG_STAT_BAT_PRESENT)
+ info->status = POWER_SUPPLY_STATUS_FULL;
+ else
+ info->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+}
+
+static int fuel_gauge_get_vbatt(struct axp288_fg_info *info, int *vbatt)
+{
+ int ret = 0, raw_val;
+
+ ret = pmic_read_adc_val("axp288-batt-volt", &raw_val, info);
+ if (ret < 0)
+ goto vbatt_read_fail;
+
+ *vbatt = VOLTAGE_FROM_ADC(raw_val);
+vbatt_read_fail:
+ return ret;
+}
+
+static int fuel_gauge_get_current(struct axp288_fg_info *info, int *cur)
+{
+ int ret, value = 0;
+ int charge, discharge;
+
+ ret = pmic_read_adc_val("axp288-chrg-curr", &charge, info);
+ if (ret < 0)
+ goto current_read_fail;
+ ret = pmic_read_adc_val("axp288-chrg-d-curr", &discharge, info);
+ if (ret < 0)
+ goto current_read_fail;
+
+ if (charge > 0)
+ value = charge;
+ else if (discharge > 0)
+ value = -1 * discharge;
+
+ *cur = value;
+current_read_fail:
+ return ret;
+}
+
+static int temp_to_adc(struct axp288_fg_info *info, int tval)
+{
+ int rntc = 0, i, ret, adc_val;
+ int rmin, rmax, tmin, tmax;
+ int tcsz = info->pdata->tcsz;
+
+ /* get the Rntc resitance value for this temp */
+ if (tval > info->pdata->thermistor_curve[0][1]) {
+ rntc = info->pdata->thermistor_curve[0][0];
+ } else if (tval <= info->pdata->thermistor_curve[tcsz-1][1]) {
+ rntc = info->pdata->thermistor_curve[tcsz-1][0];
+ } else {
+ for (i = 1; i < tcsz; i++) {
+ if (tval > info->pdata->thermistor_curve[i][1]) {
+ rmin = info->pdata->thermistor_curve[i-1][0];
+ rmax = info->pdata->thermistor_curve[i][0];
+ tmin = info->pdata->thermistor_curve[i-1][1];
+ tmax = info->pdata->thermistor_curve[i][1];
+ rntc = rmin + ((rmax - rmin) *
+ (tval - tmin) / (tmax - tmin));
+ break;
+ }
+ }
+ }
+
+ /* we need the current to calculate the proper adc voltage */
+ ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
+ ret = 0x30;
+ }
+
+ /*
+ * temperature is proportional to NTS thermistor resistance
+ * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
+ * [12-bit ADC VAL] = R_NTC(Ω) * current / 800
+ */
+ adc_val = rntc * (20 + (20 * ((ret >> 4) & 0x3))) / 800;
+
+ return adc_val;
+}
+
+static int adc_to_temp(struct axp288_fg_info *info, int adc_val)
+{
+ int ret, r, i, tval = 0;
+ int rmin, rmax, tmin, tmax;
+ int tcsz = info->pdata->tcsz;
+
+ ret = fuel_gauge_reg_readb(info, AXP20X_ADC_RATE);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
+ ret = 0x30;
+ }
+
+ /*
+ * temperature is proportional to NTS thermistor resistance
+ * ADC_RATE[5-4] determines current, 00=20uA,01=40uA,10=60uA,11=80uA
+ * R_NTC(Ω) = [12-bit ADC VAL] * 800 / current
+ */
+ r = adc_val * 800 / (20 + (20 * ((ret >> 4) & 0x3)));
+
+ if (r < info->pdata->thermistor_curve[0][0]) {
+ tval = info->pdata->thermistor_curve[0][1];
+ } else if (r >= info->pdata->thermistor_curve[tcsz-1][0]) {
+ tval = info->pdata->thermistor_curve[tcsz-1][1];
+ } else {
+ for (i = 1; i < tcsz; i++) {
+ if (r < info->pdata->thermistor_curve[i][0]) {
+ rmin = info->pdata->thermistor_curve[i-1][0];
+ rmax = info->pdata->thermistor_curve[i][0];
+ tmin = info->pdata->thermistor_curve[i-1][1];
+ tmax = info->pdata->thermistor_curve[i][1];
+ tval = tmin + ((tmax - tmin) *
+ (r - rmin) / (rmax - rmin));
+ break;
+ }
+ }
+ }
+
+ return tval;
+}
+
+static int fuel_gauge_get_btemp(struct axp288_fg_info *info, int *btemp)
+{
+ int ret, raw_val = 0;
+
+ ret = pmic_read_adc_val("axp288-batt-temp", &raw_val, info);
+ if (ret < 0)
+ goto temp_read_fail;
+
+ *btemp = adc_to_temp(info, raw_val);
+
+temp_read_fail:
+ return ret;
+}
+
+static int fuel_gauge_get_vocv(struct axp288_fg_info *info, int *vocv)
+{
+ int ret, value;
+
+ /* 12-bit data value, upper 8 in OCVH, lower 4 in OCVL */
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVH_REG);
+ if (ret < 0)
+ goto vocv_read_fail;
+ value = ret << 4;
+
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_OCVL_REG);
+ if (ret < 0)
+ goto vocv_read_fail;
+ value |= (ret & 0xf);
+
+ *vocv = VOLTAGE_FROM_ADC(value);
+vocv_read_fail:
+ return ret;
+}
+
+static int fuel_gauge_battery_health(struct axp288_fg_info *info)
+{
+ int temp, vocv;
+ int ret, health = POWER_SUPPLY_HEALTH_UNKNOWN;
+
+ ret = fuel_gauge_get_btemp(info, &temp);
+ if (ret < 0)
+ goto health_read_fail;
+
+ ret = fuel_gauge_get_vocv(info, &vocv);
+ if (ret < 0)
+ goto health_read_fail;
+
+ if (vocv > info->pdata->max_volt)
+ health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (temp > info->pdata->max_temp)
+ health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (temp < info->pdata->min_temp)
+ health = POWER_SUPPLY_HEALTH_COLD;
+ else if (vocv < info->pdata->min_volt)
+ health = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ health = POWER_SUPPLY_HEALTH_GOOD;
+
+health_read_fail:
+ return health;
+}
+
+static int fuel_gauge_set_high_btemp_alert(struct axp288_fg_info *info)
+{
+ int ret, adc_val;
+
+ /* program temperature threshold as 1/16 ADC value */
+ adc_val = temp_to_adc(info, info->pdata->max_temp);
+ ret = fuel_gauge_reg_writeb(info, AXP20X_V_HTF_DISCHRG, adc_val >> 4);
+
+ return ret;
+}
+
+static int fuel_gauge_set_low_btemp_alert(struct axp288_fg_info *info)
+{
+ int ret, adc_val;
+
+ /* program temperature threshold as 1/16 ADC value */
+ adc_val = temp_to_adc(info, info->pdata->min_temp);
+ ret = fuel_gauge_reg_writeb(info, AXP20X_V_LTF_DISCHRG, adc_val >> 4);
+
+ return ret;
+}
+
+static int fuel_gauge_get_property(struct power_supply *ps,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct axp288_fg_info *info = power_supply_get_drvdata(ps);
+ int ret = 0, value;
+
+ mutex_lock(&info->lock);
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ fuel_gauge_get_status(info);
+ val->intval = info->status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = fuel_gauge_battery_health(info);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = fuel_gauge_get_vbatt(info, &value);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ val->intval = PROP_VOLT(value);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ ret = fuel_gauge_get_vocv(info, &value);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ val->intval = PROP_VOLT(value);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = fuel_gauge_get_current(info, &value);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ val->intval = PROP_CURR(value);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = fuel_gauge_reg_readb(info, AXP20X_PWR_OP_MODE);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+
+ if (ret & CHRG_STAT_BAT_PRESENT)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+
+ if (!(ret & FG_REP_CAP_VALID))
+ dev_err(&info->pdev->dev,
+ "capacity measurement not valid\n");
+ val->intval = (ret & FG_REP_CAP_VAL_MASK);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ val->intval = (ret & 0x0f);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = fuel_gauge_get_btemp(info, &value);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ val->intval = PROP_TEMP(value);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ val->intval = PROP_TEMP(info->pdata->max_temp);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ val->intval = PROP_TEMP(info->pdata->min_temp);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR1_REG);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+
+ value = (ret & FG_CC_MTR1_VAL_MASK) << 8;
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_CC_MTR0_REG);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ value |= (ret & FG_CC_MTR0_VAL_MASK);
+ val->intval = value * FG_DES_CAP_RES_LSB;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+
+ value = (ret & FG_DES_CAP1_VAL_MASK) << 8;
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP0_REG);
+ if (ret < 0)
+ goto fuel_gauge_read_err;
+ value |= (ret & FG_DES_CAP0_VAL_MASK);
+ val->intval = value * FG_DES_CAP_RES_LSB;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = PROP_CURR(info->pdata->design_cap);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = PROP_VOLT(info->pdata->max_volt);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = PROP_VOLT(info->pdata->min_volt);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = info->pdata->battid;
+ break;
+ default:
+ mutex_unlock(&info->lock);
+ return -EINVAL;
+ }
+
+ mutex_unlock(&info->lock);
+ return 0;
+
+fuel_gauge_read_err:
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int fuel_gauge_set_property(struct power_supply *ps,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct axp288_fg_info *info = power_supply_get_drvdata(ps);
+ int ret = 0;
+
+ mutex_lock(&info->lock);
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ info->status = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ if ((val->intval < PD_DEF_MIN_TEMP) ||
+ (val->intval > PD_DEF_MAX_TEMP)) {
+ ret = -EINVAL;
+ break;
+ }
+ info->pdata->min_temp = UNPROP_TEMP(val->intval);
+ ret = fuel_gauge_set_low_btemp_alert(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev,
+ "temp alert min set fail:%d\n", ret);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ if ((val->intval < PD_DEF_MIN_TEMP) ||
+ (val->intval > PD_DEF_MAX_TEMP)) {
+ ret = -EINVAL;
+ break;
+ }
+ info->pdata->max_temp = UNPROP_TEMP(val->intval);
+ ret = fuel_gauge_set_high_btemp_alert(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev,
+ "temp alert max set fail:%d\n", ret);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
+ if ((val->intval < 0) || (val->intval > 15)) {
+ ret = -EINVAL;
+ break;
+ }
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_LOW_CAP_REG);
+ if (ret < 0)
+ break;
+ ret &= 0xf0;
+ ret |= (val->intval & 0xf);
+ ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, ret);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ mutex_unlock(&info->lock);
+ return ret;
+}
+
+static int fuel_gauge_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ case POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static void fuel_gauge_status_monitor(struct work_struct *work)
+{
+ struct axp288_fg_info *info = container_of(work,
+ struct axp288_fg_info, status_monitor.work);
+
+ fuel_gauge_get_status(info);
+ power_supply_changed(info->bat);
+ schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
+}
+
+static irqreturn_t fuel_gauge_thread_handler(int irq, void *dev)
+{
+ struct axp288_fg_info *info = dev;
+ int i;
+
+ for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
+ if (info->irq[i] == irq)
+ break;
+ }
+
+ if (i >= AXP288_FG_INTR_NUM) {
+ dev_warn(&info->pdev->dev, "spurious interrupt!!\n");
+ return IRQ_NONE;
+ }
+
+ switch (i) {
+ case QWBTU_IRQ:
+ dev_info(&info->pdev->dev,
+ "Quit Battery under temperature in work mode IRQ (QWBTU)\n");
+ break;
+ case WBTU_IRQ:
+ dev_info(&info->pdev->dev,
+ "Battery under temperature in work mode IRQ (WBTU)\n");
+ break;
+ case QWBTO_IRQ:
+ dev_info(&info->pdev->dev,
+ "Quit Battery over temperature in work mode IRQ (QWBTO)\n");
+ break;
+ case WBTO_IRQ:
+ dev_info(&info->pdev->dev,
+ "Battery over temperature in work mode IRQ (WBTO)\n");
+ break;
+ case WL2_IRQ:
+ dev_info(&info->pdev->dev, "Low Batt Warning(2) INTR\n");
+ break;
+ case WL1_IRQ:
+ dev_info(&info->pdev->dev, "Low Batt Warning(1) INTR\n");
+ break;
+ default:
+ dev_warn(&info->pdev->dev, "Spurious Interrupt!!!\n");
+ }
+
+ power_supply_changed(info->bat);
+ return IRQ_HANDLED;
+}
+
+static void fuel_gauge_external_power_changed(struct power_supply *psy)
+{
+ struct axp288_fg_info *info = power_supply_get_drvdata(psy);
+
+ power_supply_changed(info->bat);
+}
+
+static const struct power_supply_desc fuel_gauge_desc = {
+ .name = DEV_NAME,
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = fuel_gauge_props,
+ .num_properties = ARRAY_SIZE(fuel_gauge_props),
+ .get_property = fuel_gauge_get_property,
+ .set_property = fuel_gauge_set_property,
+ .property_is_writeable = fuel_gauge_property_is_writeable,
+ .external_power_changed = fuel_gauge_external_power_changed,
+};
+
+static int fuel_gauge_set_lowbatt_thresholds(struct axp288_fg_info *info)
+{
+ int ret;
+ u8 reg_val;
+
+ ret = fuel_gauge_reg_readb(info, AXP20X_FG_RES);
+ if (ret < 0) {
+ dev_err(&info->pdev->dev, "%s:read err:%d\n", __func__, ret);
+ return ret;
+ }
+ ret = (ret & FG_REP_CAP_VAL_MASK);
+
+ if (ret > FG_LOW_CAP_WARN_THR)
+ reg_val = FG_LOW_CAP_WARN_THR;
+ else if (ret > FG_LOW_CAP_CRIT_THR)
+ reg_val = FG_LOW_CAP_CRIT_THR;
+ else
+ reg_val = FG_LOW_CAP_SHDN_THR;
+
+ reg_val |= FG_LOW_CAP_THR1_VAL;
+ ret = fuel_gauge_reg_writeb(info, AXP288_FG_LOW_CAP_REG, reg_val);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "%s:write err:%d\n", __func__, ret);
+
+ return ret;
+}
+
+static int fuel_gauge_program_vbatt_full(struct axp288_fg_info *info)
+{
+ int ret;
+ u8 val;
+
+ ret = fuel_gauge_reg_readb(info, AXP20X_CHRG_CTRL1);
+ if (ret < 0)
+ goto fg_prog_ocv_fail;
+ else
+ val = (ret & ~CHRG_CCCV_CV_MASK);
+
+ switch (info->pdata->max_volt) {
+ case CV_4100:
+ val |= (CHRG_CCCV_CV_4100MV << CHRG_CCCV_CV_BIT_POS);
+ break;
+ case CV_4150:
+ val |= (CHRG_CCCV_CV_4150MV << CHRG_CCCV_CV_BIT_POS);
+ break;
+ case CV_4200:
+ val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
+ break;
+ case CV_4350:
+ val |= (CHRG_CCCV_CV_4350MV << CHRG_CCCV_CV_BIT_POS);
+ break;
+ default:
+ val |= (CHRG_CCCV_CV_4200MV << CHRG_CCCV_CV_BIT_POS);
+ break;
+ }
+
+ ret = fuel_gauge_reg_writeb(info, AXP20X_CHRG_CTRL1, val);
+fg_prog_ocv_fail:
+ return ret;
+}
+
+static int fuel_gauge_program_design_cap(struct axp288_fg_info *info)
+{
+ int ret;
+
+ ret = fuel_gauge_reg_writeb(info,
+ AXP288_FG_DES_CAP1_REG, info->pdata->cap1);
+ if (ret < 0)
+ goto fg_prog_descap_fail;
+
+ ret = fuel_gauge_reg_writeb(info,
+ AXP288_FG_DES_CAP0_REG, info->pdata->cap0);
+
+fg_prog_descap_fail:
+ return ret;
+}
+
+static int fuel_gauge_program_ocv_curve(struct axp288_fg_info *info)
+{
+ int ret = 0, i;
+
+ for (i = 0; i < OCV_CURVE_SIZE; i++) {
+ ret = fuel_gauge_reg_writeb(info,
+ AXP288_FG_OCV_CURVE_REG + i, info->pdata->ocv_curve[i]);
+ if (ret < 0)
+ goto fg_prog_ocv_fail;
+ }
+
+fg_prog_ocv_fail:
+ return ret;
+}
+
+static int fuel_gauge_program_rdc_vals(struct axp288_fg_info *info)
+{
+ int ret;
+
+ ret = fuel_gauge_reg_writeb(info,
+ AXP288_FG_RDC1_REG, info->pdata->rdc1);
+ if (ret < 0)
+ goto fg_prog_ocv_fail;
+
+ ret = fuel_gauge_reg_writeb(info,
+ AXP288_FG_RDC0_REG, info->pdata->rdc0);
+
+fg_prog_ocv_fail:
+ return ret;
+}
+
+static void fuel_gauge_init_config_regs(struct axp288_fg_info *info)
+{
+ int ret;
+
+ /*
+ * check if the config data is already
+ * programmed and if so just return.
+ */
+
+ ret = fuel_gauge_reg_readb(info, AXP288_FG_DES_CAP1_REG);
+ if (ret < 0) {
+ dev_warn(&info->pdev->dev, "CAP1 reg read err!!\n");
+ } else if (!(ret & FG_DES_CAP1_VALID)) {
+ dev_info(&info->pdev->dev, "FG data needs to be initialized\n");
+ } else {
+ dev_info(&info->pdev->dev, "FG data is already initialized\n");
+ return;
+ }
+
+ ret = fuel_gauge_program_vbatt_full(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "set vbatt full fail:%d\n", ret);
+
+ ret = fuel_gauge_program_design_cap(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "set design cap fail:%d\n", ret);
+
+ ret = fuel_gauge_program_rdc_vals(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "set rdc fail:%d\n", ret);
+
+ ret = fuel_gauge_program_ocv_curve(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "set ocv curve fail:%d\n", ret);
+
+ ret = fuel_gauge_set_lowbatt_thresholds(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "lowbatt thr set fail:%d\n", ret);
+
+ ret = fuel_gauge_reg_writeb(info, AXP20X_CC_CTRL, 0xef);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "gauge cntl set fail:%d\n", ret);
+}
+
+static void fuel_gauge_init_irq(struct axp288_fg_info *info)
+{
+ int ret, i, pirq;
+
+ for (i = 0; i < AXP288_FG_INTR_NUM; i++) {
+ pirq = platform_get_irq(info->pdev, i);
+ info->irq[i] = regmap_irq_get_virq(info->regmap_irqc, pirq);
+ if (info->irq[i] < 0) {
+ dev_warn(&info->pdev->dev,
+ "regmap_irq get virq failed for IRQ %d: %d\n",
+ pirq, info->irq[i]);
+ info->irq[i] = -1;
+ goto intr_failed;
+ }
+ ret = request_threaded_irq(info->irq[i],
+ NULL, fuel_gauge_thread_handler,
+ IRQF_ONESHOT, DEV_NAME, info);
+ if (ret) {
+ dev_warn(&info->pdev->dev,
+ "request irq failed for IRQ %d: %d\n",
+ pirq, info->irq[i]);
+ info->irq[i] = -1;
+ goto intr_failed;
+ } else {
+ dev_info(&info->pdev->dev, "HW IRQ %d -> VIRQ %d\n",
+ pirq, info->irq[i]);
+ }
+ }
+ return;
+
+intr_failed:
+ for (; i > 0; i--) {
+ free_irq(info->irq[i - 1], info);
+ info->irq[i - 1] = -1;
+ }
+}
+
+static void fuel_gauge_init_hw_regs(struct axp288_fg_info *info)
+{
+ int ret;
+ unsigned int val;
+
+ ret = fuel_gauge_set_high_btemp_alert(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "high batt temp set fail:%d\n", ret);
+
+ ret = fuel_gauge_set_low_btemp_alert(info);
+ if (ret < 0)
+ dev_err(&info->pdev->dev, "low batt temp set fail:%d\n", ret);
+
+ /* enable interrupts */
+ val = fuel_gauge_reg_readb(info, AXP20X_IRQ3_EN);
+ val |= TEMP_IRQ_CFG_MASK;
+ fuel_gauge_reg_writeb(info, AXP20X_IRQ3_EN, val);
+
+ val = fuel_gauge_reg_readb(info, AXP20X_IRQ4_EN);
+ val |= FG_IRQ_CFG_LOWBATT_MASK;
+ val = fuel_gauge_reg_writeb(info, AXP20X_IRQ4_EN, val);
+}
+
+static int axp288_fuel_gauge_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct axp288_fg_info *info;
+ struct axp20x_dev *axp20x = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {};
+
+ info = devm_kzalloc(&pdev->dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->pdev = pdev;
+ info->regmap = axp20x->regmap;
+ info->regmap_irqc = axp20x->regmap_irqc;
+ info->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ info->pdata = pdev->dev.platform_data;
+ if (!info->pdata)
+ return -ENODEV;
+
+ platform_set_drvdata(pdev, info);
+
+ mutex_init(&info->lock);
+ INIT_DELAYED_WORK(&info->status_monitor, fuel_gauge_status_monitor);
+
+ psy_cfg.drv_data = info;
+ info->bat = power_supply_register(&pdev->dev, &fuel_gauge_desc, &psy_cfg);
+ if (IS_ERR(info->bat)) {
+ ret = PTR_ERR(info->bat);
+ dev_err(&pdev->dev, "failed to register battery: %d\n", ret);
+ return ret;
+ }
+
+ fuel_gauge_create_debugfs(info);
+ fuel_gauge_init_config_regs(info);
+ fuel_gauge_init_irq(info);
+ fuel_gauge_init_hw_regs(info);
+ schedule_delayed_work(&info->status_monitor, STATUS_MON_DELAY_JIFFIES);
+
+ return ret;
+}
+
+static const struct platform_device_id axp288_fg_id_table[] = {
+ { .name = DEV_NAME },
+ {},
+};
+
+static int axp288_fuel_gauge_remove(struct platform_device *pdev)
+{
+ struct axp288_fg_info *info = platform_get_drvdata(pdev);
+ int i;
+
+ cancel_delayed_work_sync(&info->status_monitor);
+ power_supply_unregister(info->bat);
+ fuel_gauge_remove_debugfs(info);
+
+ for (i = 0; i < AXP288_FG_INTR_NUM; i++)
+ if (info->irq[i] >= 0)
+ free_irq(info->irq[i], info);
+
+ return 0;
+}
+
+static struct platform_driver axp288_fuel_gauge_driver = {
+ .probe = axp288_fuel_gauge_probe,
+ .remove = axp288_fuel_gauge_remove,
+ .id_table = axp288_fg_id_table,
+ .driver = {
+ .name = DEV_NAME,
+ },
+};
+
+module_platform_driver(axp288_fuel_gauge_driver);
+
+MODULE_AUTHOR("Ramakrishna Pallala <ramakrishna.pallala@intel.com>");
+MODULE_AUTHOR("Todd Brandt <todd.e.brandt@linux.intel.com>");
+MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq2415x_charger.c b/drivers/power/supply/bq2415x_charger.c
new file mode 100644
index 0000000..73e2f0b
--- /dev/null
+++ b/drivers/power/supply/bq2415x_charger.c
@@ -0,0 +1,1815 @@
+/*
+ * bq2415x charger driver
+ *
+ * Copyright (C) 2011-2013 Pali Rohár <pali.rohar@gmail.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.
+ *
+ * Datasheets:
+ * http://www.ti.com/product/bq24150
+ * http://www.ti.com/product/bq24150a
+ * http://www.ti.com/product/bq24152
+ * http://www.ti.com/product/bq24153
+ * http://www.ti.com/product/bq24153a
+ * http://www.ti.com/product/bq24155
+ * http://www.ti.com/product/bq24157s
+ * http://www.ti.com/product/bq24158
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/err.h>
+#include <linux/workqueue.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/acpi.h>
+
+#include <linux/power/bq2415x_charger.h>
+
+/* timeout for resetting chip timer */
+#define BQ2415X_TIMER_TIMEOUT 10
+
+#define BQ2415X_REG_STATUS 0x00
+#define BQ2415X_REG_CONTROL 0x01
+#define BQ2415X_REG_VOLTAGE 0x02
+#define BQ2415X_REG_VENDER 0x03
+#define BQ2415X_REG_CURRENT 0x04
+
+/* reset state for all registers */
+#define BQ2415X_RESET_STATUS BIT(6)
+#define BQ2415X_RESET_CONTROL (BIT(4)|BIT(5))
+#define BQ2415X_RESET_VOLTAGE (BIT(1)|BIT(3))
+#define BQ2415X_RESET_CURRENT (BIT(0)|BIT(3)|BIT(7))
+
+/* status register */
+#define BQ2415X_BIT_TMR_RST 7
+#define BQ2415X_BIT_OTG 7
+#define BQ2415X_BIT_EN_STAT 6
+#define BQ2415X_MASK_STAT (BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_STAT 4
+#define BQ2415X_BIT_BOOST 3
+#define BQ2415X_MASK_FAULT (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_FAULT 0
+
+/* control register */
+#define BQ2415X_MASK_LIMIT (BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_LIMIT 6
+#define BQ2415X_MASK_VLOWV (BIT(4)|BIT(5))
+#define BQ2415X_SHIFT_VLOWV 4
+#define BQ2415X_BIT_TE 3
+#define BQ2415X_BIT_CE 2
+#define BQ2415X_BIT_HZ_MODE 1
+#define BQ2415X_BIT_OPA_MODE 0
+
+/* voltage register */
+#define BQ2415X_MASK_VO (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VO 2
+#define BQ2415X_BIT_OTG_PL 1
+#define BQ2415X_BIT_OTG_EN 0
+
+/* vender register */
+#define BQ2415X_MASK_VENDER (BIT(5)|BIT(6)|BIT(7))
+#define BQ2415X_SHIFT_VENDER 5
+#define BQ2415X_MASK_PN (BIT(3)|BIT(4))
+#define BQ2415X_SHIFT_PN 3
+#define BQ2415X_MASK_REVISION (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_REVISION 0
+
+/* current register */
+#define BQ2415X_MASK_RESET BIT(7)
+#define BQ2415X_MASK_VI_CHRG (BIT(4)|BIT(5)|BIT(6))
+#define BQ2415X_SHIFT_VI_CHRG 4
+/* N/A BIT(3) */
+#define BQ2415X_MASK_VI_TERM (BIT(0)|BIT(1)|BIT(2))
+#define BQ2415X_SHIFT_VI_TERM 0
+
+
+enum bq2415x_command {
+ BQ2415X_TIMER_RESET,
+ BQ2415X_OTG_STATUS,
+ BQ2415X_STAT_PIN_STATUS,
+ BQ2415X_STAT_PIN_ENABLE,
+ BQ2415X_STAT_PIN_DISABLE,
+ BQ2415X_CHARGE_STATUS,
+ BQ2415X_BOOST_STATUS,
+ BQ2415X_FAULT_STATUS,
+
+ BQ2415X_CHARGE_TERMINATION_STATUS,
+ BQ2415X_CHARGE_TERMINATION_ENABLE,
+ BQ2415X_CHARGE_TERMINATION_DISABLE,
+ BQ2415X_CHARGER_STATUS,
+ BQ2415X_CHARGER_ENABLE,
+ BQ2415X_CHARGER_DISABLE,
+ BQ2415X_HIGH_IMPEDANCE_STATUS,
+ BQ2415X_HIGH_IMPEDANCE_ENABLE,
+ BQ2415X_HIGH_IMPEDANCE_DISABLE,
+ BQ2415X_BOOST_MODE_STATUS,
+ BQ2415X_BOOST_MODE_ENABLE,
+ BQ2415X_BOOST_MODE_DISABLE,
+
+ BQ2415X_OTG_LEVEL,
+ BQ2415X_OTG_ACTIVATE_HIGH,
+ BQ2415X_OTG_ACTIVATE_LOW,
+ BQ2415X_OTG_PIN_STATUS,
+ BQ2415X_OTG_PIN_ENABLE,
+ BQ2415X_OTG_PIN_DISABLE,
+
+ BQ2415X_VENDER_CODE,
+ BQ2415X_PART_NUMBER,
+ BQ2415X_REVISION,
+};
+
+enum bq2415x_chip {
+ BQUNKNOWN,
+ BQ24150,
+ BQ24150A,
+ BQ24151,
+ BQ24151A,
+ BQ24152,
+ BQ24153,
+ BQ24153A,
+ BQ24155,
+ BQ24156,
+ BQ24156A,
+ BQ24157S,
+ BQ24158,
+};
+
+static char *bq2415x_chip_name[] = {
+ "unknown",
+ "bq24150",
+ "bq24150a",
+ "bq24151",
+ "bq24151a",
+ "bq24152",
+ "bq24153",
+ "bq24153a",
+ "bq24155",
+ "bq24156",
+ "bq24156a",
+ "bq24157s",
+ "bq24158",
+};
+
+struct bq2415x_device {
+ struct device *dev;
+ struct bq2415x_platform_data init_data;
+ struct power_supply *charger;
+ struct power_supply_desc charger_desc;
+ struct delayed_work work;
+ struct device_node *notify_node;
+ struct notifier_block nb;
+ enum bq2415x_mode reported_mode;/* mode reported by hook function */
+ enum bq2415x_mode mode; /* currently configured mode */
+ enum bq2415x_chip chip;
+ const char *timer_error;
+ char *model;
+ char *name;
+ int autotimer; /* 1 - if driver automatically reset timer, 0 - not */
+ int automode; /* 1 - enabled, 0 - disabled; -1 - not supported */
+ int id;
+};
+
+/* each registered chip must have unique id */
+static DEFINE_IDR(bq2415x_id);
+
+static DEFINE_MUTEX(bq2415x_id_mutex);
+static DEFINE_MUTEX(bq2415x_timer_mutex);
+static DEFINE_MUTEX(bq2415x_i2c_mutex);
+
+/**** i2c read functions ****/
+
+/* read value from register */
+static int bq2415x_i2c_read(struct bq2415x_device *bq, u8 reg)
+{
+ struct i2c_client *client = to_i2c_client(bq->dev);
+ struct i2c_msg msg[2];
+ u8 val;
+ int ret;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = &reg;
+ msg[0].len = sizeof(reg);
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = &val;
+ msg[1].len = sizeof(val);
+
+ mutex_lock(&bq2415x_i2c_mutex);
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ mutex_unlock(&bq2415x_i2c_mutex);
+
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
+/* read value from register, apply mask and right shift it */
+static int bq2415x_i2c_read_mask(struct bq2415x_device *bq, u8 reg,
+ u8 mask, u8 shift)
+{
+ int ret;
+
+ if (shift > 8)
+ return -EINVAL;
+
+ ret = bq2415x_i2c_read(bq, reg);
+ if (ret < 0)
+ return ret;
+ return (ret & mask) >> shift;
+}
+
+/* read value from register and return one specified bit */
+static int bq2415x_i2c_read_bit(struct bq2415x_device *bq, u8 reg, u8 bit)
+{
+ if (bit > 8)
+ return -EINVAL;
+ return bq2415x_i2c_read_mask(bq, reg, BIT(bit), bit);
+}
+
+/**** i2c write functions ****/
+
+/* write value to register */
+static int bq2415x_i2c_write(struct bq2415x_device *bq, u8 reg, u8 val)
+{
+ struct i2c_client *client = to_i2c_client(bq->dev);
+ struct i2c_msg msg[1];
+ u8 data[2];
+ int ret;
+
+ data[0] = reg;
+ data[1] = val;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = data;
+ msg[0].len = ARRAY_SIZE(data);
+
+ mutex_lock(&bq2415x_i2c_mutex);
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ mutex_unlock(&bq2415x_i2c_mutex);
+
+ /* i2c_transfer returns number of messages transferred */
+ if (ret < 0)
+ return ret;
+ else if (ret != 1)
+ return -EIO;
+
+ return 0;
+}
+
+/* read value from register, change it with mask left shifted and write back */
+static int bq2415x_i2c_write_mask(struct bq2415x_device *bq, u8 reg, u8 val,
+ u8 mask, u8 shift)
+{
+ int ret;
+
+ if (shift > 8)
+ return -EINVAL;
+
+ ret = bq2415x_i2c_read(bq, reg);
+ if (ret < 0)
+ return ret;
+
+ ret &= ~mask;
+ ret |= val << shift;
+
+ return bq2415x_i2c_write(bq, reg, ret);
+}
+
+/* change only one bit in register */
+static int bq2415x_i2c_write_bit(struct bq2415x_device *bq, u8 reg,
+ bool val, u8 bit)
+{
+ if (bit > 8)
+ return -EINVAL;
+ return bq2415x_i2c_write_mask(bq, reg, val, BIT(bit), bit);
+}
+
+/**** global functions ****/
+
+/* exec command function */
+static int bq2415x_exec_command(struct bq2415x_device *bq,
+ enum bq2415x_command command)
+{
+ int ret;
+
+ switch (command) {
+ case BQ2415X_TIMER_RESET:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS,
+ 1, BQ2415X_BIT_TMR_RST);
+ case BQ2415X_OTG_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+ BQ2415X_BIT_OTG);
+ case BQ2415X_STAT_PIN_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+ BQ2415X_BIT_EN_STAT);
+ case BQ2415X_STAT_PIN_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 1,
+ BQ2415X_BIT_EN_STAT);
+ case BQ2415X_STAT_PIN_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_STATUS, 0,
+ BQ2415X_BIT_EN_STAT);
+ case BQ2415X_CHARGE_STATUS:
+ return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+ BQ2415X_MASK_STAT, BQ2415X_SHIFT_STAT);
+ case BQ2415X_BOOST_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_STATUS,
+ BQ2415X_BIT_BOOST);
+ case BQ2415X_FAULT_STATUS:
+ return bq2415x_i2c_read_mask(bq, BQ2415X_REG_STATUS,
+ BQ2415X_MASK_FAULT, BQ2415X_SHIFT_FAULT);
+
+ case BQ2415X_CHARGE_TERMINATION_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_BIT_TE);
+ case BQ2415X_CHARGE_TERMINATION_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 1, BQ2415X_BIT_TE);
+ case BQ2415X_CHARGE_TERMINATION_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 0, BQ2415X_BIT_TE);
+ case BQ2415X_CHARGER_STATUS:
+ ret = bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_BIT_CE);
+ if (ret < 0)
+ return ret;
+ return ret > 0 ? 0 : 1;
+ case BQ2415X_CHARGER_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 0, BQ2415X_BIT_CE);
+ case BQ2415X_CHARGER_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 1, BQ2415X_BIT_CE);
+ case BQ2415X_HIGH_IMPEDANCE_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_BIT_HZ_MODE);
+ case BQ2415X_HIGH_IMPEDANCE_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 1, BQ2415X_BIT_HZ_MODE);
+ case BQ2415X_HIGH_IMPEDANCE_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 0, BQ2415X_BIT_HZ_MODE);
+ case BQ2415X_BOOST_MODE_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_BIT_OPA_MODE);
+ case BQ2415X_BOOST_MODE_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 1, BQ2415X_BIT_OPA_MODE);
+ case BQ2415X_BOOST_MODE_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_CONTROL,
+ 0, BQ2415X_BIT_OPA_MODE);
+
+ case BQ2415X_OTG_LEVEL:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+ BQ2415X_BIT_OTG_PL);
+ case BQ2415X_OTG_ACTIVATE_HIGH:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+ 1, BQ2415X_BIT_OTG_PL);
+ case BQ2415X_OTG_ACTIVATE_LOW:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+ 0, BQ2415X_BIT_OTG_PL);
+ case BQ2415X_OTG_PIN_STATUS:
+ return bq2415x_i2c_read_bit(bq, BQ2415X_REG_VOLTAGE,
+ BQ2415X_BIT_OTG_EN);
+ case BQ2415X_OTG_PIN_ENABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+ 1, BQ2415X_BIT_OTG_EN);
+ case BQ2415X_OTG_PIN_DISABLE:
+ return bq2415x_i2c_write_bit(bq, BQ2415X_REG_VOLTAGE,
+ 0, BQ2415X_BIT_OTG_EN);
+
+ case BQ2415X_VENDER_CODE:
+ return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+ BQ2415X_MASK_VENDER, BQ2415X_SHIFT_VENDER);
+ case BQ2415X_PART_NUMBER:
+ return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+ BQ2415X_MASK_PN, BQ2415X_SHIFT_PN);
+ case BQ2415X_REVISION:
+ return bq2415x_i2c_read_mask(bq, BQ2415X_REG_VENDER,
+ BQ2415X_MASK_REVISION, BQ2415X_SHIFT_REVISION);
+ }
+ return -EINVAL;
+}
+
+/* detect chip type */
+static enum bq2415x_chip bq2415x_detect_chip(struct bq2415x_device *bq)
+{
+ struct i2c_client *client = to_i2c_client(bq->dev);
+ int ret = bq2415x_exec_command(bq, BQ2415X_PART_NUMBER);
+
+ if (ret < 0)
+ return ret;
+
+ switch (client->addr) {
+ case 0x6b:
+ switch (ret) {
+ case 0:
+ if (bq->chip == BQ24151A)
+ return bq->chip;
+ return BQ24151;
+ case 1:
+ if (bq->chip == BQ24150A ||
+ bq->chip == BQ24152 ||
+ bq->chip == BQ24155)
+ return bq->chip;
+ return BQ24150;
+ case 2:
+ if (bq->chip == BQ24153A)
+ return bq->chip;
+ return BQ24153;
+ default:
+ return BQUNKNOWN;
+ }
+ break;
+
+ case 0x6a:
+ switch (ret) {
+ case 0:
+ if (bq->chip == BQ24156A)
+ return bq->chip;
+ return BQ24156;
+ case 2:
+ if (bq->chip == BQ24157S)
+ return bq->chip;
+ return BQ24158;
+ default:
+ return BQUNKNOWN;
+ }
+ break;
+ }
+
+ return BQUNKNOWN;
+}
+
+/* detect chip revision */
+static int bq2415x_detect_revision(struct bq2415x_device *bq)
+{
+ int ret = bq2415x_exec_command(bq, BQ2415X_REVISION);
+ int chip = bq2415x_detect_chip(bq);
+
+ if (ret < 0 || chip < 0)
+ return -1;
+
+ switch (chip) {
+ case BQ24150:
+ case BQ24150A:
+ case BQ24151:
+ case BQ24151A:
+ case BQ24152:
+ if (ret >= 0 && ret <= 3)
+ return ret;
+ return -1;
+ case BQ24153:
+ case BQ24153A:
+ case BQ24156:
+ case BQ24156A:
+ case BQ24157S:
+ case BQ24158:
+ if (ret == 3)
+ return 0;
+ else if (ret == 1)
+ return 1;
+ return -1;
+ case BQ24155:
+ if (ret == 3)
+ return 3;
+ return -1;
+ case BQUNKNOWN:
+ return -1;
+ }
+
+ return -1;
+}
+
+/* return chip vender code */
+static int bq2415x_get_vender_code(struct bq2415x_device *bq)
+{
+ int ret;
+
+ ret = bq2415x_exec_command(bq, BQ2415X_VENDER_CODE);
+ if (ret < 0)
+ return 0;
+
+ /* convert to binary */
+ return (ret & 0x1) +
+ ((ret >> 1) & 0x1) * 10 +
+ ((ret >> 2) & 0x1) * 100;
+}
+
+/* reset all chip registers to default state */
+static void bq2415x_reset_chip(struct bq2415x_device *bq)
+{
+ bq2415x_i2c_write(bq, BQ2415X_REG_CURRENT, BQ2415X_RESET_CURRENT);
+ bq2415x_i2c_write(bq, BQ2415X_REG_VOLTAGE, BQ2415X_RESET_VOLTAGE);
+ bq2415x_i2c_write(bq, BQ2415X_REG_CONTROL, BQ2415X_RESET_CONTROL);
+ bq2415x_i2c_write(bq, BQ2415X_REG_STATUS, BQ2415X_RESET_STATUS);
+ bq->timer_error = NULL;
+}
+
+/**** properties functions ****/
+
+/* set current limit in mA */
+static int bq2415x_set_current_limit(struct bq2415x_device *bq, int mA)
+{
+ int val;
+
+ if (mA <= 100)
+ val = 0;
+ else if (mA <= 500)
+ val = 1;
+ else if (mA <= 800)
+ val = 2;
+ else
+ val = 3;
+
+ return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+ BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+}
+
+/* get current limit in mA */
+static int bq2415x_get_current_limit(struct bq2415x_device *bq)
+{
+ int ret;
+
+ ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_MASK_LIMIT, BQ2415X_SHIFT_LIMIT);
+ if (ret < 0)
+ return ret;
+ else if (ret == 0)
+ return 100;
+ else if (ret == 1)
+ return 500;
+ else if (ret == 2)
+ return 800;
+ else if (ret == 3)
+ return 1800;
+ return -EINVAL;
+}
+
+/* set weak battery voltage in mV */
+static int bq2415x_set_weak_battery_voltage(struct bq2415x_device *bq, int mV)
+{
+ int val;
+
+ /* round to 100mV */
+ if (mV <= 3400 + 50)
+ val = 0;
+ else if (mV <= 3500 + 50)
+ val = 1;
+ else if (mV <= 3600 + 50)
+ val = 2;
+ else
+ val = 3;
+
+ return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CONTROL, val,
+ BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+}
+
+/* get weak battery voltage in mV */
+static int bq2415x_get_weak_battery_voltage(struct bq2415x_device *bq)
+{
+ int ret;
+
+ ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CONTROL,
+ BQ2415X_MASK_VLOWV, BQ2415X_SHIFT_VLOWV);
+ if (ret < 0)
+ return ret;
+ return 100 * (34 + ret);
+}
+
+/* set battery regulation voltage in mV */
+static int bq2415x_set_battery_regulation_voltage(struct bq2415x_device *bq,
+ int mV)
+{
+ int val = (mV/10 - 350) / 2;
+
+ /*
+ * According to datasheet, maximum battery regulation voltage is
+ * 4440mV which is b101111 = 47.
+ */
+ if (val < 0)
+ val = 0;
+ else if (val > 47)
+ return -EINVAL;
+
+ return bq2415x_i2c_write_mask(bq, BQ2415X_REG_VOLTAGE, val,
+ BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+}
+
+/* get battery regulation voltage in mV */
+static int bq2415x_get_battery_regulation_voltage(struct bq2415x_device *bq)
+{
+ int ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_VOLTAGE,
+ BQ2415X_MASK_VO, BQ2415X_SHIFT_VO);
+
+ if (ret < 0)
+ return ret;
+ return 10 * (350 + 2*ret);
+}
+
+/* set charge current in mA (platform data must provide resistor sense) */
+static int bq2415x_set_charge_current(struct bq2415x_device *bq, int mA)
+{
+ int val;
+
+ if (bq->init_data.resistor_sense <= 0)
+ return -EINVAL;
+
+ val = (mA * bq->init_data.resistor_sense - 37400) / 6800;
+ if (val < 0)
+ val = 0;
+ else if (val > 7)
+ val = 7;
+
+ return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+ BQ2415X_MASK_VI_CHRG | BQ2415X_MASK_RESET,
+ BQ2415X_SHIFT_VI_CHRG);
+}
+
+/* get charge current in mA (platform data must provide resistor sense) */
+static int bq2415x_get_charge_current(struct bq2415x_device *bq)
+{
+ int ret;
+
+ if (bq->init_data.resistor_sense <= 0)
+ return -EINVAL;
+
+ ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+ BQ2415X_MASK_VI_CHRG, BQ2415X_SHIFT_VI_CHRG);
+ if (ret < 0)
+ return ret;
+ return (37400 + 6800*ret) / bq->init_data.resistor_sense;
+}
+
+/* set termination current in mA (platform data must provide resistor sense) */
+static int bq2415x_set_termination_current(struct bq2415x_device *bq, int mA)
+{
+ int val;
+
+ if (bq->init_data.resistor_sense <= 0)
+ return -EINVAL;
+
+ val = (mA * bq->init_data.resistor_sense - 3400) / 3400;
+ if (val < 0)
+ val = 0;
+ else if (val > 7)
+ val = 7;
+
+ return bq2415x_i2c_write_mask(bq, BQ2415X_REG_CURRENT, val,
+ BQ2415X_MASK_VI_TERM | BQ2415X_MASK_RESET,
+ BQ2415X_SHIFT_VI_TERM);
+}
+
+/* get termination current in mA (platform data must provide resistor sense) */
+static int bq2415x_get_termination_current(struct bq2415x_device *bq)
+{
+ int ret;
+
+ if (bq->init_data.resistor_sense <= 0)
+ return -EINVAL;
+
+ ret = bq2415x_i2c_read_mask(bq, BQ2415X_REG_CURRENT,
+ BQ2415X_MASK_VI_TERM, BQ2415X_SHIFT_VI_TERM);
+ if (ret < 0)
+ return ret;
+ return (3400 + 3400*ret) / bq->init_data.resistor_sense;
+}
+
+/* set default value of property */
+#define bq2415x_set_default_value(bq, prop) \
+ do { \
+ int ret = 0; \
+ if (bq->init_data.prop != -1) \
+ ret = bq2415x_set_##prop(bq, bq->init_data.prop); \
+ if (ret < 0) \
+ return ret; \
+ } while (0)
+
+/* set default values of all properties */
+static int bq2415x_set_defaults(struct bq2415x_device *bq)
+{
+ bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+ bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+ bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_DISABLE);
+
+ bq2415x_set_default_value(bq, current_limit);
+ bq2415x_set_default_value(bq, weak_battery_voltage);
+ bq2415x_set_default_value(bq, battery_regulation_voltage);
+
+ if (bq->init_data.resistor_sense > 0) {
+ bq2415x_set_default_value(bq, charge_current);
+ bq2415x_set_default_value(bq, termination_current);
+ bq2415x_exec_command(bq, BQ2415X_CHARGE_TERMINATION_ENABLE);
+ }
+
+ bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+ return 0;
+}
+
+/**** charger mode functions ****/
+
+/* set charger mode */
+static int bq2415x_set_mode(struct bq2415x_device *bq, enum bq2415x_mode mode)
+{
+ int ret = 0;
+ int charger = 0;
+ int boost = 0;
+
+ if (mode == BQ2415X_MODE_BOOST)
+ boost = 1;
+ else if (mode != BQ2415X_MODE_OFF)
+ charger = 1;
+
+ if (!charger)
+ ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_DISABLE);
+
+ if (!boost)
+ ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_DISABLE);
+
+ if (ret < 0)
+ return ret;
+
+ switch (mode) {
+ case BQ2415X_MODE_OFF:
+ dev_dbg(bq->dev, "changing mode to: Offline\n");
+ ret = bq2415x_set_current_limit(bq, 100);
+ break;
+ case BQ2415X_MODE_NONE:
+ dev_dbg(bq->dev, "changing mode to: N/A\n");
+ ret = bq2415x_set_current_limit(bq, 100);
+ break;
+ case BQ2415X_MODE_HOST_CHARGER:
+ dev_dbg(bq->dev, "changing mode to: Host/HUB charger\n");
+ ret = bq2415x_set_current_limit(bq, 500);
+ break;
+ case BQ2415X_MODE_DEDICATED_CHARGER:
+ dev_dbg(bq->dev, "changing mode to: Dedicated charger\n");
+ ret = bq2415x_set_current_limit(bq, 1800);
+ break;
+ case BQ2415X_MODE_BOOST: /* Boost mode */
+ dev_dbg(bq->dev, "changing mode to: Boost\n");
+ ret = bq2415x_set_current_limit(bq, 100);
+ break;
+ }
+
+ if (ret < 0)
+ return ret;
+
+ if (charger)
+ ret = bq2415x_exec_command(bq, BQ2415X_CHARGER_ENABLE);
+ else if (boost)
+ ret = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_ENABLE);
+
+ if (ret < 0)
+ return ret;
+
+ bq2415x_set_default_value(bq, weak_battery_voltage);
+ bq2415x_set_default_value(bq, battery_regulation_voltage);
+
+ bq->mode = mode;
+ sysfs_notify(&bq->charger->dev.kobj, NULL, "mode");
+
+ return 0;
+
+}
+
+static bool bq2415x_update_reported_mode(struct bq2415x_device *bq, int mA)
+{
+ enum bq2415x_mode mode;
+
+ if (mA == 0)
+ mode = BQ2415X_MODE_OFF;
+ else if (mA < 500)
+ mode = BQ2415X_MODE_NONE;
+ else if (mA < 1800)
+ mode = BQ2415X_MODE_HOST_CHARGER;
+ else
+ mode = BQ2415X_MODE_DEDICATED_CHARGER;
+
+ if (bq->reported_mode == mode)
+ return false;
+
+ bq->reported_mode = mode;
+ return true;
+}
+
+static int bq2415x_notifier_call(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct bq2415x_device *bq =
+ container_of(nb, struct bq2415x_device, nb);
+ struct power_supply *psy = v;
+ union power_supply_propval prop;
+ int ret;
+
+ if (val != PSY_EVENT_PROP_CHANGED)
+ return NOTIFY_OK;
+
+ /* Ignore event if it was not send by notify_node/notify_device */
+ if (bq->notify_node) {
+ if (!psy->dev.parent ||
+ psy->dev.parent->of_node != bq->notify_node)
+ return NOTIFY_OK;
+ } else if (bq->init_data.notify_device) {
+ if (strcmp(psy->desc->name, bq->init_data.notify_device) != 0)
+ return NOTIFY_OK;
+ }
+
+ dev_dbg(bq->dev, "notifier call was called\n");
+
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &prop);
+ if (ret != 0)
+ return NOTIFY_OK;
+
+ if (!bq2415x_update_reported_mode(bq, prop.intval))
+ return NOTIFY_OK;
+
+ /* if automode is not enabled do not tell about reported_mode */
+ if (bq->automode < 1)
+ return NOTIFY_OK;
+
+ schedule_delayed_work(&bq->work, 0);
+
+ return NOTIFY_OK;
+}
+
+/**** timer functions ****/
+
+/* enable/disable auto resetting chip timer */
+static void bq2415x_set_autotimer(struct bq2415x_device *bq, int state)
+{
+ mutex_lock(&bq2415x_timer_mutex);
+
+ if (bq->autotimer == state) {
+ mutex_unlock(&bq2415x_timer_mutex);
+ return;
+ }
+
+ bq->autotimer = state;
+
+ if (state) {
+ schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+ bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+ bq->timer_error = NULL;
+ } else {
+ cancel_delayed_work_sync(&bq->work);
+ }
+
+ mutex_unlock(&bq2415x_timer_mutex);
+}
+
+/* called by bq2415x_timer_work on timer error */
+static void bq2415x_timer_error(struct bq2415x_device *bq, const char *msg)
+{
+ bq->timer_error = msg;
+ sysfs_notify(&bq->charger->dev.kobj, NULL, "timer");
+ dev_err(bq->dev, "%s\n", msg);
+ if (bq->automode > 0)
+ bq->automode = 0;
+ bq2415x_set_mode(bq, BQ2415X_MODE_OFF);
+ bq2415x_set_autotimer(bq, 0);
+}
+
+/* delayed work function for auto resetting chip timer */
+static void bq2415x_timer_work(struct work_struct *work)
+{
+ struct bq2415x_device *bq = container_of(work, struct bq2415x_device,
+ work.work);
+ int ret;
+ int error;
+ int boost;
+
+ if (bq->automode > 0 && (bq->reported_mode != bq->mode)) {
+ sysfs_notify(&bq->charger->dev.kobj, NULL, "reported_mode");
+ bq2415x_set_mode(bq, bq->reported_mode);
+ }
+
+ if (!bq->autotimer)
+ return;
+
+ ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+ if (ret < 0) {
+ bq2415x_timer_error(bq, "Resetting timer failed");
+ return;
+ }
+
+ boost = bq2415x_exec_command(bq, BQ2415X_BOOST_MODE_STATUS);
+ if (boost < 0) {
+ bq2415x_timer_error(bq, "Unknown error");
+ return;
+ }
+
+ error = bq2415x_exec_command(bq, BQ2415X_FAULT_STATUS);
+ if (error < 0) {
+ bq2415x_timer_error(bq, "Unknown error");
+ return;
+ }
+
+ if (boost) {
+ switch (error) {
+ /* Non fatal errors, chip is OK */
+ case 0: /* No error */
+ break;
+ case 6: /* Timer expired */
+ dev_err(bq->dev, "Timer expired\n");
+ break;
+ case 3: /* Battery voltage too low */
+ dev_err(bq->dev, "Battery voltage to low\n");
+ break;
+
+ /* Fatal errors, disable and reset chip */
+ case 1: /* Overvoltage protection (chip fried) */
+ bq2415x_timer_error(bq,
+ "Overvoltage protection (chip fried)");
+ return;
+ case 2: /* Overload */
+ bq2415x_timer_error(bq, "Overload");
+ return;
+ case 4: /* Battery overvoltage protection */
+ bq2415x_timer_error(bq,
+ "Battery overvoltage protection");
+ return;
+ case 5: /* Thermal shutdown (too hot) */
+ bq2415x_timer_error(bq,
+ "Thermal shutdown (too hot)");
+ return;
+ case 7: /* N/A */
+ bq2415x_timer_error(bq, "Unknown error");
+ return;
+ }
+ } else {
+ switch (error) {
+ /* Non fatal errors, chip is OK */
+ case 0: /* No error */
+ break;
+ case 2: /* Sleep mode */
+ dev_err(bq->dev, "Sleep mode\n");
+ break;
+ case 3: /* Poor input source */
+ dev_err(bq->dev, "Poor input source\n");
+ break;
+ case 6: /* Timer expired */
+ dev_err(bq->dev, "Timer expired\n");
+ break;
+ case 7: /* No battery */
+ dev_err(bq->dev, "No battery\n");
+ break;
+
+ /* Fatal errors, disable and reset chip */
+ case 1: /* Overvoltage protection (chip fried) */
+ bq2415x_timer_error(bq,
+ "Overvoltage protection (chip fried)");
+ return;
+ case 4: /* Battery overvoltage protection */
+ bq2415x_timer_error(bq,
+ "Battery overvoltage protection");
+ return;
+ case 5: /* Thermal shutdown (too hot) */
+ bq2415x_timer_error(bq,
+ "Thermal shutdown (too hot)");
+ return;
+ }
+ }
+
+ schedule_delayed_work(&bq->work, BQ2415X_TIMER_TIMEOUT * HZ);
+}
+
+/**** power supply interface code ****/
+
+static enum power_supply_property bq2415x_power_supply_props[] = {
+ /* TODO: maybe add more power supply properties */
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int bq2415x_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = bq2415x_exec_command(bq, BQ2415X_CHARGE_STATUS);
+ if (ret < 0)
+ return ret;
+ else if (ret == 0) /* Ready */
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (ret == 1) /* Charge in progress */
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (ret == 2) /* Charge done */
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = bq->model;
+ break;
+ default:
+ return -EINVAL;
+ }
+ 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, };
+
+ 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;
+ if (bq->automode > 0)
+ bq->automode = 0;
+ cancel_delayed_work_sync(&bq->work);
+ power_supply_unregister(bq->charger);
+ kfree(bq->model);
+}
+
+/**** additional sysfs entries for power supply interface ****/
+
+/* show *_status entries */
+static ssize_t bq2415x_sysfs_show_status(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ enum bq2415x_command command;
+ int ret;
+
+ if (strcmp(attr->attr.name, "otg_status") == 0)
+ command = BQ2415X_OTG_STATUS;
+ else if (strcmp(attr->attr.name, "charge_status") == 0)
+ command = BQ2415X_CHARGE_STATUS;
+ else if (strcmp(attr->attr.name, "boost_status") == 0)
+ command = BQ2415X_BOOST_STATUS;
+ else if (strcmp(attr->attr.name, "fault_status") == 0)
+ command = BQ2415X_FAULT_STATUS;
+ else
+ return -EINVAL;
+
+ ret = bq2415x_exec_command(bq, command);
+ if (ret < 0)
+ return ret;
+ return sprintf(buf, "%d\n", ret);
+}
+
+/*
+ * set timer entry:
+ * auto - enable auto mode
+ * off - disable auto mode
+ * (other values) - reset chip timer
+ */
+static ssize_t bq2415x_sysfs_set_timer(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ if (strncmp(buf, "auto", 4) == 0)
+ bq2415x_set_autotimer(bq, 1);
+ else if (strncmp(buf, "off", 3) == 0)
+ bq2415x_set_autotimer(bq, 0);
+ else
+ ret = bq2415x_exec_command(bq, BQ2415X_TIMER_RESET);
+
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+/* show timer entry (auto or off) */
+static ssize_t bq2415x_sysfs_show_timer(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+
+ if (bq->timer_error)
+ return sprintf(buf, "%s\n", bq->timer_error);
+
+ if (bq->autotimer)
+ return sprintf(buf, "auto\n");
+ return sprintf(buf, "off\n");
+}
+
+/*
+ * set mode entry:
+ * auto - if automode is supported, enable it and set mode to reported
+ * none - disable charger and boost mode
+ * host - charging mode for host/hub chargers (current limit 500mA)
+ * dedicated - charging mode for dedicated chargers (unlimited current limit)
+ * boost - disable charger and enable boost mode
+ */
+static ssize_t bq2415x_sysfs_set_mode(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ enum bq2415x_mode mode;
+ int ret = 0;
+
+ if (strncmp(buf, "auto", 4) == 0) {
+ if (bq->automode < 0)
+ return -EINVAL;
+ bq->automode = 1;
+ mode = bq->reported_mode;
+ } else if (strncmp(buf, "off", 3) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_OFF;
+ } else if (strncmp(buf, "none", 4) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_NONE;
+ } else if (strncmp(buf, "host", 4) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_HOST_CHARGER;
+ } else if (strncmp(buf, "dedicated", 9) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_DEDICATED_CHARGER;
+ } else if (strncmp(buf, "boost", 5) == 0) {
+ if (bq->automode > 0)
+ bq->automode = 0;
+ mode = BQ2415X_MODE_BOOST;
+ } else if (strncmp(buf, "reset", 5) == 0) {
+ bq2415x_reset_chip(bq);
+ bq2415x_set_defaults(bq);
+ if (bq->automode <= 0)
+ return count;
+ bq->automode = 1;
+ mode = bq->reported_mode;
+ } else {
+ return -EINVAL;
+ }
+
+ ret = bq2415x_set_mode(bq, mode);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+/* show mode entry (auto, none, host, dedicated or boost) */
+static ssize_t bq2415x_sysfs_show_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ ssize_t ret = 0;
+
+ if (bq->automode > 0)
+ ret += sprintf(buf+ret, "auto (");
+
+ switch (bq->mode) {
+ case BQ2415X_MODE_OFF:
+ ret += sprintf(buf+ret, "off");
+ break;
+ case BQ2415X_MODE_NONE:
+ ret += sprintf(buf+ret, "none");
+ break;
+ case BQ2415X_MODE_HOST_CHARGER:
+ ret += sprintf(buf+ret, "host");
+ break;
+ case BQ2415X_MODE_DEDICATED_CHARGER:
+ ret += sprintf(buf+ret, "dedicated");
+ break;
+ case BQ2415X_MODE_BOOST:
+ ret += sprintf(buf+ret, "boost");
+ break;
+ }
+
+ if (bq->automode > 0)
+ ret += sprintf(buf+ret, ")");
+
+ ret += sprintf(buf+ret, "\n");
+ return ret;
+}
+
+/* show reported_mode entry (none, host, dedicated or boost) */
+static ssize_t bq2415x_sysfs_show_reported_mode(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+
+ if (bq->automode < 0)
+ return -EINVAL;
+
+ switch (bq->reported_mode) {
+ case BQ2415X_MODE_OFF:
+ return sprintf(buf, "off\n");
+ case BQ2415X_MODE_NONE:
+ return sprintf(buf, "none\n");
+ case BQ2415X_MODE_HOST_CHARGER:
+ return sprintf(buf, "host\n");
+ case BQ2415X_MODE_DEDICATED_CHARGER:
+ return sprintf(buf, "dedicated\n");
+ case BQ2415X_MODE_BOOST:
+ return sprintf(buf, "boost\n");
+ }
+
+ return -EINVAL;
+}
+
+/* directly set raw value to chip register, format: 'register value' */
+static ssize_t bq2415x_sysfs_set_registers(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ ssize_t ret = 0;
+ unsigned int reg;
+ unsigned int val;
+
+ if (sscanf(buf, "%x %x", &reg, &val) != 2)
+ return -EINVAL;
+
+ if (reg > 4 || val > 255)
+ return -EINVAL;
+
+ ret = bq2415x_i2c_write(bq, reg, val);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+/* print value of chip register, format: 'register=value' */
+static ssize_t bq2415x_sysfs_print_reg(struct bq2415x_device *bq,
+ u8 reg,
+ char *buf)
+{
+ int ret = bq2415x_i2c_read(bq, reg);
+
+ if (ret < 0)
+ return sprintf(buf, "%#.2x=error %d\n", reg, ret);
+ return sprintf(buf, "%#.2x=%#.2x\n", reg, ret);
+}
+
+/* show all raw values of chip register, format per line: 'register=value' */
+static ssize_t bq2415x_sysfs_show_registers(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ ssize_t ret = 0;
+
+ ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_STATUS, buf+ret);
+ ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CONTROL, buf+ret);
+ ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VOLTAGE, buf+ret);
+ ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_VENDER, buf+ret);
+ ret += bq2415x_sysfs_print_reg(bq, BQ2415X_REG_CURRENT, buf+ret);
+ return ret;
+}
+
+/* set current and voltage limit entries (in mA or mV) */
+static ssize_t bq2415x_sysfs_set_limit(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ long val;
+ int ret;
+
+ if (kstrtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "current_limit") == 0)
+ ret = bq2415x_set_current_limit(bq, val);
+ else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+ ret = bq2415x_set_weak_battery_voltage(bq, val);
+ else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+ ret = bq2415x_set_battery_regulation_voltage(bq, val);
+ else if (strcmp(attr->attr.name, "charge_current") == 0)
+ ret = bq2415x_set_charge_current(bq, val);
+ else if (strcmp(attr->attr.name, "termination_current") == 0)
+ ret = bq2415x_set_termination_current(bq, val);
+ else
+ return -EINVAL;
+
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+/* show current and voltage limit entries (in mA or mV) */
+static ssize_t bq2415x_sysfs_show_limit(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ int ret;
+
+ if (strcmp(attr->attr.name, "current_limit") == 0)
+ ret = bq2415x_get_current_limit(bq);
+ else if (strcmp(attr->attr.name, "weak_battery_voltage") == 0)
+ ret = bq2415x_get_weak_battery_voltage(bq);
+ else if (strcmp(attr->attr.name, "battery_regulation_voltage") == 0)
+ ret = bq2415x_get_battery_regulation_voltage(bq);
+ else if (strcmp(attr->attr.name, "charge_current") == 0)
+ ret = bq2415x_get_charge_current(bq);
+ else if (strcmp(attr->attr.name, "termination_current") == 0)
+ ret = bq2415x_get_termination_current(bq);
+ else
+ return -EINVAL;
+
+ if (ret < 0)
+ return ret;
+ return sprintf(buf, "%d\n", ret);
+}
+
+/* set *_enable entries */
+static ssize_t bq2415x_sysfs_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ enum bq2415x_command command;
+ long val;
+ int ret;
+
+ if (kstrtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+ command = val ? BQ2415X_CHARGE_TERMINATION_ENABLE :
+ BQ2415X_CHARGE_TERMINATION_DISABLE;
+ else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+ command = val ? BQ2415X_HIGH_IMPEDANCE_ENABLE :
+ BQ2415X_HIGH_IMPEDANCE_DISABLE;
+ else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+ command = val ? BQ2415X_OTG_PIN_ENABLE :
+ BQ2415X_OTG_PIN_DISABLE;
+ else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+ command = val ? BQ2415X_STAT_PIN_ENABLE :
+ BQ2415X_STAT_PIN_DISABLE;
+ else
+ return -EINVAL;
+
+ ret = bq2415x_exec_command(bq, command);
+ if (ret < 0)
+ return ret;
+ return count;
+}
+
+/* show *_enable entries */
+static ssize_t bq2415x_sysfs_show_enable(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq2415x_device *bq = power_supply_get_drvdata(psy);
+ enum bq2415x_command command;
+ int ret;
+
+ if (strcmp(attr->attr.name, "charge_termination_enable") == 0)
+ command = BQ2415X_CHARGE_TERMINATION_STATUS;
+ else if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+ command = BQ2415X_HIGH_IMPEDANCE_STATUS;
+ else if (strcmp(attr->attr.name, "otg_pin_enable") == 0)
+ command = BQ2415X_OTG_PIN_STATUS;
+ else if (strcmp(attr->attr.name, "stat_pin_enable") == 0)
+ command = BQ2415X_STAT_PIN_STATUS;
+ else
+ return -EINVAL;
+
+ ret = bq2415x_exec_command(bq, command);
+ if (ret < 0)
+ return ret;
+ return sprintf(buf, "%d\n", ret);
+}
+
+static DEVICE_ATTR(current_limit, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(weak_battery_voltage, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(battery_regulation_voltage, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(charge_current, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+static DEVICE_ATTR(termination_current, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_limit, bq2415x_sysfs_set_limit);
+
+static DEVICE_ATTR(charge_termination_enable, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(otg_pin_enable, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+static DEVICE_ATTR(stat_pin_enable, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_enable, bq2415x_sysfs_set_enable);
+
+static DEVICE_ATTR(reported_mode, S_IRUGO,
+ bq2415x_sysfs_show_reported_mode, NULL);
+static DEVICE_ATTR(mode, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_mode, bq2415x_sysfs_set_mode);
+static DEVICE_ATTR(timer, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_timer, bq2415x_sysfs_set_timer);
+
+static DEVICE_ATTR(registers, S_IWUSR | S_IRUGO,
+ bq2415x_sysfs_show_registers, bq2415x_sysfs_set_registers);
+
+static DEVICE_ATTR(otg_status, S_IRUGO, bq2415x_sysfs_show_status, NULL);
+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[] = {
+ /*
+ * TODO: some (appropriate) of these attrs should be switched to
+ * use power supply class props.
+ */
+ &dev_attr_current_limit.attr,
+ &dev_attr_weak_battery_voltage.attr,
+ &dev_attr_battery_regulation_voltage.attr,
+ &dev_attr_charge_current.attr,
+ &dev_attr_termination_current.attr,
+
+ &dev_attr_charge_termination_enable.attr,
+ &dev_attr_high_impedance_enable.attr,
+ &dev_attr_otg_pin_enable.attr,
+ &dev_attr_stat_pin_enable.attr,
+
+ &dev_attr_reported_mode.attr,
+ &dev_attr_mode.attr,
+ &dev_attr_timer.attr,
+
+ &dev_attr_registers.attr,
+
+ &dev_attr_otg_status.attr,
+ &dev_attr_charge_status.attr,
+ &dev_attr_boost_status.attr,
+ &dev_attr_fault_status.attr,
+ NULL,
+};
+
+static const struct attribute_group bq2415x_sysfs_attr_group = {
+ .attrs = bq2415x_sysfs_attributes,
+};
+
+static int bq2415x_sysfs_init(struct bq2415x_device *bq)
+{
+ return sysfs_create_group(&bq->charger->dev.kobj,
+ &bq2415x_sysfs_attr_group);
+}
+
+static void bq2415x_sysfs_exit(struct bq2415x_device *bq)
+{
+ sysfs_remove_group(&bq->charger->dev.kobj, &bq2415x_sysfs_attr_group);
+}
+
+/* main bq2415x probe function */
+static int bq2415x_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ int num;
+ char *name = NULL;
+ struct bq2415x_device *bq;
+ struct device_node *np = client->dev.of_node;
+ struct bq2415x_platform_data *pdata = client->dev.platform_data;
+ const struct acpi_device_id *acpi_id = NULL;
+ struct power_supply *notify_psy = NULL;
+ union power_supply_propval prop;
+
+ if (!np && !pdata && !ACPI_HANDLE(&client->dev)) {
+ dev_err(&client->dev, "Neither devicetree, nor platform data, nor ACPI support\n");
+ return -ENODEV;
+ }
+
+ /* Get new ID for the new device */
+ mutex_lock(&bq2415x_id_mutex);
+ num = idr_alloc(&bq2415x_id, client, 0, 0, GFP_KERNEL);
+ mutex_unlock(&bq2415x_id_mutex);
+ if (num < 0)
+ return num;
+
+ if (id) {
+ name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num);
+ } else if (ACPI_HANDLE(&client->dev)) {
+ acpi_id =
+ acpi_match_device(client->dev.driver->acpi_match_table,
+ &client->dev);
+ name = kasprintf(GFP_KERNEL, "%s-%d", acpi_id->id, num);
+ }
+ if (!name) {
+ dev_err(&client->dev, "failed to allocate device name\n");
+ ret = -ENOMEM;
+ goto error_1;
+ }
+
+ bq = devm_kzalloc(&client->dev, sizeof(*bq), GFP_KERNEL);
+ if (!bq) {
+ ret = -ENOMEM;
+ goto error_2;
+ }
+
+ i2c_set_clientdata(client, bq);
+
+ bq->id = num;
+ bq->dev = &client->dev;
+ if (id)
+ bq->chip = id->driver_data;
+ else if (ACPI_HANDLE(bq->dev))
+ bq->chip = acpi_id->driver_data;
+ bq->name = name;
+ bq->mode = BQ2415X_MODE_OFF;
+ bq->reported_mode = BQ2415X_MODE_OFF;
+ bq->autotimer = 0;
+ bq->automode = 0;
+
+ if (np || ACPI_HANDLE(bq->dev)) {
+ ret = device_property_read_u32(bq->dev,
+ "ti,current-limit",
+ &bq->init_data.current_limit);
+ if (ret)
+ goto error_2;
+ ret = device_property_read_u32(bq->dev,
+ "ti,weak-battery-voltage",
+ &bq->init_data.weak_battery_voltage);
+ if (ret)
+ goto error_2;
+ ret = device_property_read_u32(bq->dev,
+ "ti,battery-regulation-voltage",
+ &bq->init_data.battery_regulation_voltage);
+ if (ret)
+ goto error_2;
+ ret = device_property_read_u32(bq->dev,
+ "ti,charge-current",
+ &bq->init_data.charge_current);
+ if (ret)
+ goto error_2;
+ ret = device_property_read_u32(bq->dev,
+ "ti,termination-current",
+ &bq->init_data.termination_current);
+ if (ret)
+ goto error_2;
+ ret = device_property_read_u32(bq->dev,
+ "ti,resistor-sense",
+ &bq->init_data.resistor_sense);
+ if (ret)
+ goto error_2;
+ if (np)
+ bq->notify_node = of_parse_phandle(np,
+ "ti,usb-charger-detection", 0);
+ } else {
+ memcpy(&bq->init_data, pdata, sizeof(bq->init_data));
+ }
+
+ bq2415x_reset_chip(bq);
+
+ ret = bq2415x_power_supply_init(bq);
+ if (ret) {
+ dev_err(bq->dev, "failed to register power supply: %d\n", ret);
+ 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;
+ }
+
+ if (bq->notify_node || bq->init_data.notify_device) {
+ bq->nb.notifier_call = bq2415x_notifier_call;
+ ret = power_supply_reg_notifier(&bq->nb);
+ if (ret) {
+ dev_err(bq->dev, "failed to reg notifier: %d\n", ret);
+ goto error_4;
+ }
+
+ bq->automode = 1;
+ dev_info(bq->dev, "automode supported, waiting for events\n");
+ } else {
+ bq->automode = -1;
+ dev_info(bq->dev, "automode not supported\n");
+ }
+
+ /* Query for initial reported_mode and set it */
+ if (bq->nb.notifier_call) {
+ if (np) {
+ notify_psy = power_supply_get_by_phandle(np,
+ "ti,usb-charger-detection");
+ if (IS_ERR(notify_psy))
+ notify_psy = NULL;
+ } else if (bq->init_data.notify_device) {
+ notify_psy = power_supply_get_by_name(
+ bq->init_data.notify_device);
+ }
+ }
+ if (notify_psy) {
+ ret = power_supply_get_property(notify_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+ power_supply_put(notify_psy);
+
+ if (ret == 0) {
+ bq2415x_update_reported_mode(bq, prop.intval);
+ bq2415x_set_mode(bq, bq->reported_mode);
+ }
+ }
+
+ INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work);
+ bq2415x_set_autotimer(bq, 1);
+
+ dev_info(bq->dev, "driver registered\n");
+ return 0;
+
+error_4:
+ bq2415x_sysfs_exit(bq);
+error_3:
+ bq2415x_power_supply_exit(bq);
+error_2:
+ if (bq)
+ of_node_put(bq->notify_node);
+ kfree(name);
+error_1:
+ mutex_lock(&bq2415x_id_mutex);
+ idr_remove(&bq2415x_id, num);
+ mutex_unlock(&bq2415x_id_mutex);
+
+ return ret;
+}
+
+/* main bq2415x remove function */
+
+static int bq2415x_remove(struct i2c_client *client)
+{
+ struct bq2415x_device *bq = i2c_get_clientdata(client);
+
+ if (bq->nb.notifier_call)
+ 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);
+
+ mutex_lock(&bq2415x_id_mutex);
+ idr_remove(&bq2415x_id, bq->id);
+ mutex_unlock(&bq2415x_id_mutex);
+
+ dev_info(bq->dev, "driver unregistered\n");
+
+ kfree(bq->name);
+
+ return 0;
+}
+
+static const struct i2c_device_id bq2415x_i2c_id_table[] = {
+ { "bq2415x", BQUNKNOWN },
+ { "bq24150", BQ24150 },
+ { "bq24150a", BQ24150A },
+ { "bq24151", BQ24151 },
+ { "bq24151a", BQ24151A },
+ { "bq24152", BQ24152 },
+ { "bq24153", BQ24153 },
+ { "bq24153a", BQ24153A },
+ { "bq24155", BQ24155 },
+ { "bq24156", BQ24156 },
+ { "bq24156a", BQ24156A },
+ { "bq24157s", BQ24157S },
+ { "bq24158", BQ24158 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table);
+
+#ifdef CONFIG_ACPI
+static const struct acpi_device_id bq2415x_i2c_acpi_match[] = {
+ { "BQ2415X", BQUNKNOWN },
+ { "BQ241500", BQ24150 },
+ { "BQA24150", BQ24150A },
+ { "BQ241510", BQ24151 },
+ { "BQA24151", BQ24151A },
+ { "BQ241520", BQ24152 },
+ { "BQ241530", BQ24153 },
+ { "BQA24153", BQ24153A },
+ { "BQ241550", BQ24155 },
+ { "BQ241560", BQ24156 },
+ { "BQA24156", BQ24156A },
+ { "BQS24157", BQ24157S },
+ { "BQ241580", BQ24158 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, bq2415x_i2c_acpi_match);
+#endif
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq2415x_of_match_table[] = {
+ { .compatible = "ti,bq24150" },
+ { .compatible = "ti,bq24150a" },
+ { .compatible = "ti,bq24151" },
+ { .compatible = "ti,bq24151a" },
+ { .compatible = "ti,bq24152" },
+ { .compatible = "ti,bq24153" },
+ { .compatible = "ti,bq24153a" },
+ { .compatible = "ti,bq24155" },
+ { .compatible = "ti,bq24156" },
+ { .compatible = "ti,bq24156a" },
+ { .compatible = "ti,bq24157s" },
+ { .compatible = "ti,bq24158" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bq2415x_of_match_table);
+#endif
+
+static struct i2c_driver bq2415x_driver = {
+ .driver = {
+ .name = "bq2415x-charger",
+ .of_match_table = of_match_ptr(bq2415x_of_match_table),
+ .acpi_match_table = ACPI_PTR(bq2415x_i2c_acpi_match),
+ },
+ .probe = bq2415x_probe,
+ .remove = bq2415x_remove,
+ .id_table = bq2415x_i2c_id_table,
+};
+module_i2c_driver(bq2415x_driver);
+
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("bq2415x charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq24190_charger.c b/drivers/power/supply/bq24190_charger.c
new file mode 100644
index 0000000..f5746b9
--- /dev/null
+++ b/drivers/power/supply/bq24190_charger.c
@@ -0,0 +1,1546 @@
+/*
+ * 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 = power_supply_get_drvdata(psy);
+ 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 = power_supply_get_drvdata(psy);
+ 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 = power_supply_get_drvdata(psy);
+ 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 = power_supply_get_drvdata(psy);
+ 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_CHARGE_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 const struct power_supply_desc bq24190_charger_desc = {
+ .name = "bq24190-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = bq24190_charger_properties,
+ .num_properties = ARRAY_SIZE(bq24190_charger_properties),
+ .get_property = bq24190_charger_get_property,
+ .set_property = bq24190_charger_set_property,
+ .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 = power_supply_get_drvdata(psy);
+ 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 = power_supply_get_drvdata(psy);
+ 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 const struct power_supply_desc bq24190_battery_desc = {
+ .name = "bq24190-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = bq24190_battery_properties,
+ .num_properties = ARRAY_SIZE(bq24190_battery_properties),
+ .get_property = bq24190_battery_get_property,
+ .set_property = bq24190_battery_set_property,
+ .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 = 0, f_reg = 0;
+ 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) {
+ if (!bdi->first_time) {
+ power_supply_changed(bdi->charger);
+ power_supply_changed(bdi->battery);
+ } else {
+ 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 power_supply_config charger_cfg = {}, battery_cfg = {};
+ 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;
+ }
+
+ charger_cfg.drv_data = bdi;
+ charger_cfg.supplied_to = bq24190_charger_supplied_to;
+ charger_cfg.num_supplicants = ARRAY_SIZE(bq24190_charger_supplied_to),
+ bdi->charger = power_supply_register(dev, &bq24190_charger_desc,
+ &charger_cfg);
+ if (IS_ERR(bdi->charger)) {
+ dev_err(dev, "Can't register charger\n");
+ ret = PTR_ERR(bdi->charger);
+ goto out2;
+ }
+
+ battery_cfg.drv_data = bdi;
+ bdi->battery = power_supply_register(dev, &bq24190_battery_desc,
+ &battery_cfg);
+ if (IS_ERR(bdi->battery)) {
+ dev_err(dev, "Can't register battery\n");
+ ret = PTR_ERR(bdi->battery);
+ 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 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, bq24190_i2c_ids);
+
+#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",
+ .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_DESCRIPTION("TI BQ24190 Charger Driver");
diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c
new file mode 100644
index 0000000..6fc31bd
--- /dev/null
+++ b/drivers/power/supply/bq24257_charger.c
@@ -0,0 +1,1196 @@
+/*
+ * TI BQ24257 charger driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * 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.
+ *
+ * Datasheets:
+ * http://www.ti.com/product/bq24250
+ * http://www.ti.com/product/bq24251
+ * http://www.ti.com/product/bq24257
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+
+#include <linux/acpi.h>
+#include <linux/of.h>
+
+#define BQ24257_REG_1 0x00
+#define BQ24257_REG_2 0x01
+#define BQ24257_REG_3 0x02
+#define BQ24257_REG_4 0x03
+#define BQ24257_REG_5 0x04
+#define BQ24257_REG_6 0x05
+#define BQ24257_REG_7 0x06
+
+#define BQ24257_MANUFACTURER "Texas Instruments"
+#define BQ24257_PG_GPIO "pg"
+
+#define BQ24257_ILIM_SET_DELAY 1000 /* msec */
+
+/*
+ * When adding support for new devices make sure that enum bq2425x_chip and
+ * bq2425x_chip_name[] always stay in sync!
+ */
+enum bq2425x_chip {
+ BQ24250,
+ BQ24251,
+ BQ24257,
+};
+
+static const char *const bq2425x_chip_name[] = {
+ "bq24250",
+ "bq24251",
+ "bq24257",
+};
+
+enum bq24257_fields {
+ F_WD_FAULT, F_WD_EN, F_STAT, F_FAULT, /* REG 1 */
+ F_RESET, F_IILIMIT, F_EN_STAT, F_EN_TERM, F_CE, F_HZ_MODE, /* REG 2 */
+ F_VBAT, F_USB_DET, /* REG 3 */
+ F_ICHG, F_ITERM, /* REG 4 */
+ F_LOOP_STATUS, F_LOW_CHG, F_DPDM_EN, F_CE_STATUS, F_VINDPM, /* REG 5 */
+ F_X2_TMR_EN, F_TMR, F_SYSOFF, F_TS_EN, F_TS_STAT, /* REG 6 */
+ F_VOVP, F_CLR_VDP, F_FORCE_BATDET, F_FORCE_PTM, /* REG 7 */
+
+ F_MAX_FIELDS
+};
+
+/* initial field values, converted from uV/uA */
+struct bq24257_init_data {
+ u8 ichg; /* charge current */
+ u8 vbat; /* regulation voltage */
+ u8 iterm; /* termination current */
+ u8 iilimit; /* input current limit */
+ u8 vovp; /* over voltage protection voltage */
+ u8 vindpm; /* VDMP input threshold voltage */
+};
+
+struct bq24257_state {
+ u8 status;
+ u8 fault;
+ bool power_good;
+};
+
+struct bq24257_device {
+ struct i2c_client *client;
+ struct device *dev;
+ struct power_supply *charger;
+
+ enum bq2425x_chip chip;
+
+ struct regmap *rmap;
+ struct regmap_field *rmap_fields[F_MAX_FIELDS];
+
+ struct gpio_desc *pg;
+
+ struct delayed_work iilimit_setup_work;
+
+ struct bq24257_init_data init_data;
+ struct bq24257_state state;
+
+ struct mutex lock; /* protect state data */
+
+ bool iilimit_autoset_enable;
+};
+
+static bool bq24257_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case BQ24257_REG_2:
+ case BQ24257_REG_4:
+ return false;
+
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config bq24257_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = BQ24257_REG_7,
+ .cache_type = REGCACHE_RBTREE,
+
+ .volatile_reg = bq24257_is_volatile_reg,
+};
+
+static const struct reg_field bq24257_reg_fields[] = {
+ /* REG 1 */
+ [F_WD_FAULT] = REG_FIELD(BQ24257_REG_1, 7, 7),
+ [F_WD_EN] = REG_FIELD(BQ24257_REG_1, 6, 6),
+ [F_STAT] = REG_FIELD(BQ24257_REG_1, 4, 5),
+ [F_FAULT] = REG_FIELD(BQ24257_REG_1, 0, 3),
+ /* REG 2 */
+ [F_RESET] = REG_FIELD(BQ24257_REG_2, 7, 7),
+ [F_IILIMIT] = REG_FIELD(BQ24257_REG_2, 4, 6),
+ [F_EN_STAT] = REG_FIELD(BQ24257_REG_2, 3, 3),
+ [F_EN_TERM] = REG_FIELD(BQ24257_REG_2, 2, 2),
+ [F_CE] = REG_FIELD(BQ24257_REG_2, 1, 1),
+ [F_HZ_MODE] = REG_FIELD(BQ24257_REG_2, 0, 0),
+ /* REG 3 */
+ [F_VBAT] = REG_FIELD(BQ24257_REG_3, 2, 7),
+ [F_USB_DET] = REG_FIELD(BQ24257_REG_3, 0, 1),
+ /* REG 4 */
+ [F_ICHG] = REG_FIELD(BQ24257_REG_4, 3, 7),
+ [F_ITERM] = REG_FIELD(BQ24257_REG_4, 0, 2),
+ /* REG 5 */
+ [F_LOOP_STATUS] = REG_FIELD(BQ24257_REG_5, 6, 7),
+ [F_LOW_CHG] = REG_FIELD(BQ24257_REG_5, 5, 5),
+ [F_DPDM_EN] = REG_FIELD(BQ24257_REG_5, 4, 4),
+ [F_CE_STATUS] = REG_FIELD(BQ24257_REG_5, 3, 3),
+ [F_VINDPM] = REG_FIELD(BQ24257_REG_5, 0, 2),
+ /* REG 6 */
+ [F_X2_TMR_EN] = REG_FIELD(BQ24257_REG_6, 7, 7),
+ [F_TMR] = REG_FIELD(BQ24257_REG_6, 5, 6),
+ [F_SYSOFF] = REG_FIELD(BQ24257_REG_6, 4, 4),
+ [F_TS_EN] = REG_FIELD(BQ24257_REG_6, 3, 3),
+ [F_TS_STAT] = REG_FIELD(BQ24257_REG_6, 0, 2),
+ /* REG 7 */
+ [F_VOVP] = REG_FIELD(BQ24257_REG_7, 5, 7),
+ [F_CLR_VDP] = REG_FIELD(BQ24257_REG_7, 4, 4),
+ [F_FORCE_BATDET] = REG_FIELD(BQ24257_REG_7, 3, 3),
+ [F_FORCE_PTM] = REG_FIELD(BQ24257_REG_7, 2, 2)
+};
+
+static const u32 bq24257_vbat_map[] = {
+ 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000,
+ 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000,
+ 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000,
+ 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000,
+ 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000,
+ 4300000, 4320000, 4340000, 4360000, 4380000, 4400000, 4420000, 4440000
+};
+
+#define BQ24257_VBAT_MAP_SIZE ARRAY_SIZE(bq24257_vbat_map)
+
+static const u32 bq24257_ichg_map[] = {
+ 500000, 550000, 600000, 650000, 700000, 750000, 800000, 850000, 900000,
+ 950000, 1000000, 1050000, 1100000, 1150000, 1200000, 1250000, 1300000,
+ 1350000, 1400000, 1450000, 1500000, 1550000, 1600000, 1650000, 1700000,
+ 1750000, 1800000, 1850000, 1900000, 1950000, 2000000
+};
+
+#define BQ24257_ICHG_MAP_SIZE ARRAY_SIZE(bq24257_ichg_map)
+
+static const u32 bq24257_iterm_map[] = {
+ 50000, 75000, 100000, 125000, 150000, 175000, 200000, 225000
+};
+
+#define BQ24257_ITERM_MAP_SIZE ARRAY_SIZE(bq24257_iterm_map)
+
+static const u32 bq24257_iilimit_map[] = {
+ 100000, 150000, 500000, 900000, 1500000, 2000000
+};
+
+#define BQ24257_IILIMIT_MAP_SIZE ARRAY_SIZE(bq24257_iilimit_map)
+
+static const u32 bq24257_vovp_map[] = {
+ 6000000, 6500000, 7000000, 8000000, 9000000, 9500000, 10000000,
+ 10500000
+};
+
+#define BQ24257_VOVP_MAP_SIZE ARRAY_SIZE(bq24257_vovp_map)
+
+static const u32 bq24257_vindpm_map[] = {
+ 4200000, 4280000, 4360000, 4440000, 4520000, 4600000, 4680000,
+ 4760000
+};
+
+#define BQ24257_VINDPM_MAP_SIZE ARRAY_SIZE(bq24257_vindpm_map)
+
+static int bq24257_field_read(struct bq24257_device *bq,
+ enum bq24257_fields field_id)
+{
+ int ret;
+ int val;
+
+ ret = regmap_field_read(bq->rmap_fields[field_id], &val);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
+static int bq24257_field_write(struct bq24257_device *bq,
+ enum bq24257_fields field_id, u8 val)
+{
+ return regmap_field_write(bq->rmap_fields[field_id], val);
+}
+
+static u8 bq24257_find_idx(u32 value, const u32 *map, u8 map_size)
+{
+ u8 idx;
+
+ for (idx = 1; idx < map_size; idx++)
+ if (value < map[idx])
+ break;
+
+ return idx - 1;
+}
+
+enum bq24257_status {
+ STATUS_READY,
+ STATUS_CHARGE_IN_PROGRESS,
+ STATUS_CHARGE_DONE,
+ STATUS_FAULT,
+};
+
+enum bq24257_fault {
+ FAULT_NORMAL,
+ FAULT_INPUT_OVP,
+ FAULT_INPUT_UVLO,
+ FAULT_SLEEP,
+ FAULT_BAT_TS,
+ FAULT_BAT_OVP,
+ FAULT_TS,
+ FAULT_TIMER,
+ FAULT_NO_BAT,
+ FAULT_ISET,
+ FAULT_INPUT_LDO_LOW,
+};
+
+static int bq24257_get_input_current_limit(struct bq24257_device *bq,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = bq24257_field_read(bq, F_IILIMIT);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The "External ILIM" and "Production & Test" modes are not exposed
+ * through this driver and not being covered by the lookup table.
+ * Should such a mode have become active let's return an error rather
+ * than exceeding the bounds of the lookup table and returning
+ * garbage.
+ */
+ if (ret >= BQ24257_IILIMIT_MAP_SIZE)
+ return -ENODATA;
+
+ val->intval = bq24257_iilimit_map[ret];
+
+ return 0;
+}
+
+static int bq24257_set_input_current_limit(struct bq24257_device *bq,
+ const union power_supply_propval *val)
+{
+ /*
+ * Address the case where the user manually sets an input current limit
+ * while the charger auto-detection mechanism is is active. In this
+ * case we want to abort and go straight to the user-specified value.
+ */
+ if (bq->iilimit_autoset_enable)
+ cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+ return bq24257_field_write(bq, F_IILIMIT,
+ bq24257_find_idx(val->intval,
+ bq24257_iilimit_map,
+ BQ24257_IILIMIT_MAP_SIZE));
+}
+
+static int bq24257_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+ struct bq24257_state state;
+
+ mutex_lock(&bq->lock);
+ state = bq->state;
+ mutex_unlock(&bq->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!state.power_good)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (state.status == STATUS_READY)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (state.status == STATUS_CHARGE_IN_PROGRESS)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (state.status == STATUS_CHARGE_DONE)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = BQ24257_MANUFACTURER;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = bq2425x_chip_name[bq->chip];
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = state.power_good;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ switch (state.fault) {
+ case FAULT_NORMAL:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+
+ case FAULT_INPUT_OVP:
+ case FAULT_BAT_OVP:
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+
+ case FAULT_TS:
+ case FAULT_BAT_TS:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+
+ case FAULT_TIMER:
+ val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ break;
+
+ default:
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ }
+
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ val->intval = bq24257_ichg_map[bq->init_data.ichg];
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = bq24257_ichg_map[BQ24257_ICHG_MAP_SIZE - 1];
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ val->intval = bq24257_vbat_map[bq->init_data.vbat];
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ val->intval = bq24257_vbat_map[BQ24257_VBAT_MAP_SIZE - 1];
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ val->intval = bq24257_iterm_map[bq->init_data.iterm];
+ break;
+
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return bq24257_get_input_current_limit(bq, val);
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bq24257_power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return bq24257_set_input_current_limit(bq, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int bq24257_power_supply_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int bq24257_get_chip_state(struct bq24257_device *bq,
+ struct bq24257_state *state)
+{
+ int ret;
+
+ ret = bq24257_field_read(bq, F_STAT);
+ if (ret < 0)
+ return ret;
+
+ state->status = ret;
+
+ ret = bq24257_field_read(bq, F_FAULT);
+ if (ret < 0)
+ return ret;
+
+ state->fault = ret;
+
+ if (bq->pg)
+ state->power_good = !gpiod_get_value_cansleep(bq->pg);
+ else
+ /*
+ * If we have a chip without a dedicated power-good GPIO or
+ * some other explicit bit that would provide this information
+ * assume the power is good if there is no supply related
+ * fault - and not good otherwise. There is a possibility for
+ * other errors to mask that power in fact is not good but this
+ * is probably the best we can do here.
+ */
+ switch (state->fault) {
+ case FAULT_INPUT_OVP:
+ case FAULT_INPUT_UVLO:
+ case FAULT_INPUT_LDO_LOW:
+ state->power_good = false;
+ break;
+ default:
+ state->power_good = true;
+ }
+
+ return 0;
+}
+
+static bool bq24257_state_changed(struct bq24257_device *bq,
+ struct bq24257_state *new_state)
+{
+ int ret;
+
+ mutex_lock(&bq->lock);
+ ret = (bq->state.status != new_state->status ||
+ bq->state.fault != new_state->fault ||
+ bq->state.power_good != new_state->power_good);
+ mutex_unlock(&bq->lock);
+
+ return ret;
+}
+
+enum bq24257_loop_status {
+ LOOP_STATUS_NONE,
+ LOOP_STATUS_IN_DPM,
+ LOOP_STATUS_IN_CURRENT_LIMIT,
+ LOOP_STATUS_THERMAL,
+};
+
+enum bq24257_in_ilimit {
+ IILIMIT_100,
+ IILIMIT_150,
+ IILIMIT_500,
+ IILIMIT_900,
+ IILIMIT_1500,
+ IILIMIT_2000,
+ IILIMIT_EXT,
+ IILIMIT_NONE,
+};
+
+enum bq24257_vovp {
+ VOVP_6000,
+ VOVP_6500,
+ VOVP_7000,
+ VOVP_8000,
+ VOVP_9000,
+ VOVP_9500,
+ VOVP_10000,
+ VOVP_10500
+};
+
+enum bq24257_vindpm {
+ VINDPM_4200,
+ VINDPM_4280,
+ VINDPM_4360,
+ VINDPM_4440,
+ VINDPM_4520,
+ VINDPM_4600,
+ VINDPM_4680,
+ VINDPM_4760
+};
+
+enum bq24257_port_type {
+ PORT_TYPE_DCP, /* Dedicated Charging Port */
+ PORT_TYPE_CDP, /* Charging Downstream Port */
+ PORT_TYPE_SDP, /* Standard Downstream Port */
+ PORT_TYPE_NON_STANDARD,
+};
+
+enum bq24257_safety_timer {
+ SAFETY_TIMER_45,
+ SAFETY_TIMER_360,
+ SAFETY_TIMER_540,
+ SAFETY_TIMER_NONE,
+};
+
+static int bq24257_iilimit_autoset(struct bq24257_device *bq)
+{
+ int loop_status;
+ int iilimit;
+ int port_type;
+ int ret;
+ const u8 new_iilimit[] = {
+ [PORT_TYPE_DCP] = IILIMIT_2000,
+ [PORT_TYPE_CDP] = IILIMIT_2000,
+ [PORT_TYPE_SDP] = IILIMIT_500,
+ [PORT_TYPE_NON_STANDARD] = IILIMIT_500
+ };
+
+ ret = bq24257_field_read(bq, F_LOOP_STATUS);
+ if (ret < 0)
+ goto error;
+
+ loop_status = ret;
+
+ ret = bq24257_field_read(bq, F_IILIMIT);
+ if (ret < 0)
+ goto error;
+
+ iilimit = ret;
+
+ /*
+ * All USB ports should be able to handle 500mA. If not, DPM will lower
+ * the charging current to accommodate the power source. No need to set
+ * a lower IILIMIT value.
+ */
+ if (loop_status == LOOP_STATUS_IN_DPM && iilimit == IILIMIT_500)
+ return 0;
+
+ ret = bq24257_field_read(bq, F_USB_DET);
+ if (ret < 0)
+ goto error;
+
+ port_type = ret;
+
+ ret = bq24257_field_write(bq, F_IILIMIT, new_iilimit[port_type]);
+ if (ret < 0)
+ goto error;
+
+ ret = bq24257_field_write(bq, F_TMR, SAFETY_TIMER_360);
+ if (ret < 0)
+ goto error;
+
+ ret = bq24257_field_write(bq, F_CLR_VDP, 1);
+ if (ret < 0)
+ goto error;
+
+ dev_dbg(bq->dev, "port/loop = %d/%d -> iilimit = %d\n",
+ port_type, loop_status, new_iilimit[port_type]);
+
+ return 0;
+
+error:
+ dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
+ return ret;
+}
+
+static void bq24257_iilimit_setup_work(struct work_struct *work)
+{
+ struct bq24257_device *bq = container_of(work, struct bq24257_device,
+ iilimit_setup_work.work);
+
+ bq24257_iilimit_autoset(bq);
+}
+
+static void bq24257_handle_state_change(struct bq24257_device *bq,
+ struct bq24257_state *new_state)
+{
+ int ret;
+ struct bq24257_state old_state;
+
+ mutex_lock(&bq->lock);
+ old_state = bq->state;
+ mutex_unlock(&bq->lock);
+
+ /*
+ * Handle BQ2425x state changes observing whether the D+/D- based input
+ * current limit autoset functionality is enabled.
+ */
+ if (!new_state->power_good) {
+ dev_dbg(bq->dev, "Power removed\n");
+ if (bq->iilimit_autoset_enable) {
+ cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+ /* activate D+/D- port detection algorithm */
+ ret = bq24257_field_write(bq, F_DPDM_EN, 1);
+ if (ret < 0)
+ goto error;
+ }
+ /*
+ * When power is removed always return to the default input
+ * current limit as configured during probe.
+ */
+ ret = bq24257_field_write(bq, F_IILIMIT, bq->init_data.iilimit);
+ if (ret < 0)
+ goto error;
+ } else if (!old_state.power_good) {
+ dev_dbg(bq->dev, "Power inserted\n");
+
+ if (bq->iilimit_autoset_enable)
+ /* configure input current limit */
+ schedule_delayed_work(&bq->iilimit_setup_work,
+ msecs_to_jiffies(BQ24257_ILIM_SET_DELAY));
+ } else if (new_state->fault == FAULT_NO_BAT) {
+ dev_warn(bq->dev, "Battery removed\n");
+ } else if (new_state->fault == FAULT_TIMER) {
+ dev_err(bq->dev, "Safety timer expired! Battery dead?\n");
+ }
+
+ return;
+
+error:
+ dev_err(bq->dev, "%s: Error communicating with the chip.\n", __func__);
+}
+
+static irqreturn_t bq24257_irq_handler_thread(int irq, void *private)
+{
+ int ret;
+ struct bq24257_device *bq = private;
+ struct bq24257_state state;
+
+ ret = bq24257_get_chip_state(bq, &state);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ if (!bq24257_state_changed(bq, &state))
+ return IRQ_HANDLED;
+
+ dev_dbg(bq->dev, "irq(state changed): status/fault/pg = %d/%d/%d\n",
+ state.status, state.fault, state.power_good);
+
+ bq24257_handle_state_change(bq, &state);
+
+ mutex_lock(&bq->lock);
+ bq->state = state;
+ mutex_unlock(&bq->lock);
+
+ power_supply_changed(bq->charger);
+
+ return IRQ_HANDLED;
+}
+
+static int bq24257_hw_init(struct bq24257_device *bq)
+{
+ int ret;
+ int i;
+ struct bq24257_state state;
+
+ const struct {
+ int field;
+ u32 value;
+ } init_data[] = {
+ {F_ICHG, bq->init_data.ichg},
+ {F_VBAT, bq->init_data.vbat},
+ {F_ITERM, bq->init_data.iterm},
+ {F_VOVP, bq->init_data.vovp},
+ {F_VINDPM, bq->init_data.vindpm},
+ };
+
+ /*
+ * Disable the watchdog timer to prevent the IC from going back to
+ * default settings after 50 seconds of I2C inactivity.
+ */
+ ret = bq24257_field_write(bq, F_WD_EN, 0);
+ if (ret < 0)
+ return ret;
+
+ /* configure the charge currents and voltages */
+ for (i = 0; i < ARRAY_SIZE(init_data); i++) {
+ ret = bq24257_field_write(bq, init_data[i].field,
+ init_data[i].value);
+ if (ret < 0)
+ return ret;
+ }
+
+ ret = bq24257_get_chip_state(bq, &state);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&bq->lock);
+ bq->state = state;
+ mutex_unlock(&bq->lock);
+
+ if (!bq->iilimit_autoset_enable) {
+ dev_dbg(bq->dev, "manually setting iilimit = %u\n",
+ bq->init_data.iilimit);
+
+ /* program fixed input current limit */
+ ret = bq24257_field_write(bq, F_IILIMIT,
+ bq->init_data.iilimit);
+ if (ret < 0)
+ return ret;
+ } else if (!state.power_good)
+ /* activate D+/D- detection algorithm */
+ ret = bq24257_field_write(bq, F_DPDM_EN, 1);
+ else if (state.fault != FAULT_NO_BAT)
+ ret = bq24257_iilimit_autoset(bq);
+
+ return ret;
+}
+
+static enum power_supply_property bq24257_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ 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_CHARGE_TERM_CURRENT,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMIT,
+};
+
+static char *bq24257_charger_supplied_to[] = {
+ "main-battery",
+};
+
+static const struct power_supply_desc bq24257_power_supply_desc = {
+ .name = "bq24257-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = bq24257_power_supply_props,
+ .num_properties = ARRAY_SIZE(bq24257_power_supply_props),
+ .get_property = bq24257_power_supply_get_property,
+ .set_property = bq24257_power_supply_set_property,
+ .property_is_writeable = bq24257_power_supply_property_is_writeable,
+};
+
+static ssize_t bq24257_show_ovp_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ bq24257_vovp_map[bq->init_data.vovp]);
+}
+
+static ssize_t bq24257_show_in_dpm_voltage(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ bq24257_vindpm_map[bq->init_data.vindpm]);
+}
+
+static ssize_t bq24257_sysfs_show_enable(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+ int ret;
+
+ if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+ ret = bq24257_field_read(bq, F_HZ_MODE);
+ else if (strcmp(attr->attr.name, "sysoff_enable") == 0)
+ ret = bq24257_field_read(bq, F_SYSOFF);
+ else
+ return -EINVAL;
+
+ if (ret < 0)
+ return ret;
+
+ return scnprintf(buf, PAGE_SIZE, "%d\n", ret);
+}
+
+static ssize_t bq24257_sysfs_set_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ struct bq24257_device *bq = power_supply_get_drvdata(psy);
+ long val;
+ int ret;
+
+ if (kstrtol(buf, 10, &val) < 0)
+ return -EINVAL;
+
+ if (strcmp(attr->attr.name, "high_impedance_enable") == 0)
+ ret = bq24257_field_write(bq, F_HZ_MODE, (bool)val);
+ else if (strcmp(attr->attr.name, "sysoff_enable") == 0)
+ ret = bq24257_field_write(bq, F_SYSOFF, (bool)val);
+ else
+ return -EINVAL;
+
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(ovp_voltage, S_IRUGO, bq24257_show_ovp_voltage, NULL);
+static DEVICE_ATTR(in_dpm_voltage, S_IRUGO, bq24257_show_in_dpm_voltage, NULL);
+static DEVICE_ATTR(high_impedance_enable, S_IWUSR | S_IRUGO,
+ bq24257_sysfs_show_enable, bq24257_sysfs_set_enable);
+static DEVICE_ATTR(sysoff_enable, S_IWUSR | S_IRUGO,
+ bq24257_sysfs_show_enable, bq24257_sysfs_set_enable);
+
+static struct attribute *bq24257_charger_attr[] = {
+ &dev_attr_ovp_voltage.attr,
+ &dev_attr_in_dpm_voltage.attr,
+ &dev_attr_high_impedance_enable.attr,
+ &dev_attr_sysoff_enable.attr,
+ NULL,
+};
+
+static const struct attribute_group bq24257_attr_group = {
+ .attrs = bq24257_charger_attr,
+};
+
+static int bq24257_power_supply_init(struct bq24257_device *bq)
+{
+ struct power_supply_config psy_cfg = { .drv_data = bq, };
+
+ psy_cfg.supplied_to = bq24257_charger_supplied_to;
+ psy_cfg.num_supplicants = ARRAY_SIZE(bq24257_charger_supplied_to);
+
+ bq->charger = devm_power_supply_register(bq->dev,
+ &bq24257_power_supply_desc,
+ &psy_cfg);
+
+ return PTR_ERR_OR_ZERO(bq->charger);
+}
+
+static void bq24257_pg_gpio_probe(struct bq24257_device *bq)
+{
+ bq->pg = devm_gpiod_get_optional(bq->dev, BQ24257_PG_GPIO, GPIOD_IN);
+
+ if (PTR_ERR(bq->pg) == -EPROBE_DEFER) {
+ dev_info(bq->dev, "probe retry requested for PG pin\n");
+ return;
+ } else if (IS_ERR(bq->pg)) {
+ dev_err(bq->dev, "error probing PG pin\n");
+ bq->pg = NULL;
+ return;
+ }
+
+ if (bq->pg)
+ dev_dbg(bq->dev, "probed PG pin = %d\n", desc_to_gpio(bq->pg));
+}
+
+static int bq24257_fw_probe(struct bq24257_device *bq)
+{
+ int ret;
+ u32 property;
+
+ /* Required properties */
+ ret = device_property_read_u32(bq->dev, "ti,charge-current", &property);
+ if (ret < 0)
+ return ret;
+
+ bq->init_data.ichg = bq24257_find_idx(property, bq24257_ichg_map,
+ BQ24257_ICHG_MAP_SIZE);
+
+ ret = device_property_read_u32(bq->dev, "ti,battery-regulation-voltage",
+ &property);
+ if (ret < 0)
+ return ret;
+
+ bq->init_data.vbat = bq24257_find_idx(property, bq24257_vbat_map,
+ BQ24257_VBAT_MAP_SIZE);
+
+ ret = device_property_read_u32(bq->dev, "ti,termination-current",
+ &property);
+ if (ret < 0)
+ return ret;
+
+ bq->init_data.iterm = bq24257_find_idx(property, bq24257_iterm_map,
+ BQ24257_ITERM_MAP_SIZE);
+
+ /* Optional properties. If not provided use reasonable default. */
+ ret = device_property_read_u32(bq->dev, "ti,current-limit",
+ &property);
+ if (ret < 0) {
+ bq->iilimit_autoset_enable = true;
+
+ /*
+ * Explicitly set a default value which will be needed for
+ * devices that don't support the automatic setting of the input
+ * current limit through the charger type detection mechanism.
+ */
+ bq->init_data.iilimit = IILIMIT_500;
+ } else
+ bq->init_data.iilimit =
+ bq24257_find_idx(property,
+ bq24257_iilimit_map,
+ BQ24257_IILIMIT_MAP_SIZE);
+
+ ret = device_property_read_u32(bq->dev, "ti,ovp-voltage",
+ &property);
+ if (ret < 0)
+ bq->init_data.vovp = VOVP_6500;
+ else
+ bq->init_data.vovp = bq24257_find_idx(property,
+ bq24257_vovp_map,
+ BQ24257_VOVP_MAP_SIZE);
+
+ ret = device_property_read_u32(bq->dev, "ti,in-dpm-voltage",
+ &property);
+ if (ret < 0)
+ bq->init_data.vindpm = VINDPM_4360;
+ else
+ bq->init_data.vindpm =
+ bq24257_find_idx(property,
+ bq24257_vindpm_map,
+ BQ24257_VINDPM_MAP_SIZE);
+
+ return 0;
+}
+
+static int bq24257_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;
+ const struct acpi_device_id *acpi_id;
+ struct bq24257_device *bq;
+ int ret;
+ int i;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+ return -ENODEV;
+ }
+
+ bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
+ if (!bq)
+ return -ENOMEM;
+
+ bq->client = client;
+ bq->dev = dev;
+
+ if (ACPI_HANDLE(dev)) {
+ acpi_id = acpi_match_device(dev->driver->acpi_match_table,
+ &client->dev);
+ if (!acpi_id) {
+ dev_err(dev, "Failed to match ACPI device\n");
+ return -ENODEV;
+ }
+ bq->chip = (enum bq2425x_chip)acpi_id->driver_data;
+ } else {
+ bq->chip = (enum bq2425x_chip)id->driver_data;
+ }
+
+ mutex_init(&bq->lock);
+
+ bq->rmap = devm_regmap_init_i2c(client, &bq24257_regmap_config);
+ if (IS_ERR(bq->rmap)) {
+ dev_err(dev, "failed to allocate register map\n");
+ return PTR_ERR(bq->rmap);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bq24257_reg_fields); i++) {
+ const struct reg_field *reg_fields = bq24257_reg_fields;
+
+ bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
+ reg_fields[i]);
+ if (IS_ERR(bq->rmap_fields[i])) {
+ dev_err(dev, "cannot allocate regmap field\n");
+ return PTR_ERR(bq->rmap_fields[i]);
+ }
+ }
+
+ i2c_set_clientdata(client, bq);
+
+ if (!dev->platform_data) {
+ ret = bq24257_fw_probe(bq);
+ if (ret < 0) {
+ dev_err(dev, "Cannot read device properties.\n");
+ return ret;
+ }
+ } else {
+ return -ENODEV;
+ }
+
+ /*
+ * The BQ24250 doesn't support the D+/D- based charger type detection
+ * used for the automatic setting of the input current limit setting so
+ * explicitly disable that feature.
+ */
+ if (bq->chip == BQ24250)
+ bq->iilimit_autoset_enable = false;
+
+ if (bq->iilimit_autoset_enable)
+ INIT_DELAYED_WORK(&bq->iilimit_setup_work,
+ bq24257_iilimit_setup_work);
+
+ /*
+ * The BQ24250 doesn't have a dedicated Power Good (PG) pin so let's
+ * not probe for it and instead use a SW-based approach to determine
+ * the PG state. We also use a SW-based approach for all other devices
+ * if the PG pin is either not defined or can't be probed.
+ */
+ if (bq->chip != BQ24250)
+ bq24257_pg_gpio_probe(bq);
+
+ if (PTR_ERR(bq->pg) == -EPROBE_DEFER)
+ return PTR_ERR(bq->pg);
+ else if (!bq->pg)
+ dev_info(bq->dev, "using SW-based power-good detection\n");
+
+ /* reset all registers to defaults */
+ ret = bq24257_field_write(bq, F_RESET, 1);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Put the RESET bit back to 0, in cache. For some reason the HW always
+ * returns 1 on this bit, so this is the only way to avoid resetting the
+ * chip every time we update another field in this register.
+ */
+ ret = bq24257_field_write(bq, F_RESET, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24257_hw_init(bq);
+ if (ret < 0) {
+ dev_err(dev, "Cannot initialize the chip.\n");
+ return ret;
+ }
+
+ ret = bq24257_power_supply_init(bq);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register power supply\n");
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ bq24257_irq_handler_thread,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ bq2425x_chip_name[bq->chip], bq);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ #%d\n", client->irq);
+ 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;
+}
+
+static int bq24257_remove(struct i2c_client *client)
+{
+ struct bq24257_device *bq = i2c_get_clientdata(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;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq24257_suspend(struct device *dev)
+{
+ struct bq24257_device *bq = dev_get_drvdata(dev);
+ int ret = 0;
+
+ if (bq->iilimit_autoset_enable)
+ cancel_delayed_work_sync(&bq->iilimit_setup_work);
+
+ /* reset all registers to default (and activate standalone mode) */
+ ret = bq24257_field_write(bq, F_RESET, 1);
+ if (ret < 0)
+ dev_err(bq->dev, "Cannot reset chip to standalone mode.\n");
+
+ return ret;
+}
+
+static int bq24257_resume(struct device *dev)
+{
+ int ret;
+ struct bq24257_device *bq = dev_get_drvdata(dev);
+
+ ret = regcache_drop_region(bq->rmap, BQ24257_REG_1, BQ24257_REG_7);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24257_field_write(bq, F_RESET, 0);
+ if (ret < 0)
+ return ret;
+
+ ret = bq24257_hw_init(bq);
+ if (ret < 0) {
+ dev_err(bq->dev, "Cannot init chip after resume.\n");
+ return ret;
+ }
+
+ /* signal userspace, maybe state changed while suspended */
+ power_supply_changed(bq->charger);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops bq24257_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(bq24257_suspend, bq24257_resume)
+};
+
+static const struct i2c_device_id bq24257_i2c_ids[] = {
+ { "bq24250", BQ24250 },
+ { "bq24251", BQ24251 },
+ { "bq24257", BQ24257 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq24257_i2c_ids);
+
+static const struct of_device_id bq24257_of_match[] = {
+ { .compatible = "ti,bq24250", },
+ { .compatible = "ti,bq24251", },
+ { .compatible = "ti,bq24257", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bq24257_of_match);
+
+static const struct acpi_device_id bq24257_acpi_match[] = {
+ { "BQ242500", BQ24250 },
+ { "BQ242510", BQ24251 },
+ { "BQ242570", BQ24257 },
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, bq24257_acpi_match);
+
+static struct i2c_driver bq24257_driver = {
+ .driver = {
+ .name = "bq24257-charger",
+ .of_match_table = of_match_ptr(bq24257_of_match),
+ .acpi_match_table = ACPI_PTR(bq24257_acpi_match),
+ .pm = &bq24257_pm,
+ },
+ .probe = bq24257_probe,
+ .remove = bq24257_remove,
+ .id_table = bq24257_i2c_ids,
+};
+module_i2c_driver(bq24257_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION("bq24257 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c
new file mode 100644
index 0000000..eb7783b
--- /dev/null
+++ b/drivers/power/supply/bq24735-charger.c
@@ -0,0 +1,489 @@
+/*
+ * Battery charger driver for TI BQ24735
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. 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 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.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/i2c.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/gpio/consumer.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/power/bq24735-charger.h>
+
+#define BQ24735_CHG_OPT 0x12
+#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0)
+#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4)
+#define BQ24735_CHARGE_CURRENT 0x14
+#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0
+#define BQ24735_CHARGE_VOLTAGE 0x15
+#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0
+#define BQ24735_INPUT_CURRENT 0x3f
+#define BQ24735_INPUT_CURRENT_MASK 0x1f80
+#define BQ24735_MANUFACTURER_ID 0xfe
+#define BQ24735_DEVICE_ID 0xff
+
+struct bq24735 {
+ struct power_supply *charger;
+ struct power_supply_desc charger_desc;
+ struct i2c_client *client;
+ struct bq24735_platform *pdata;
+ struct mutex lock;
+ struct gpio_desc *status_gpio;
+ bool charging;
+};
+
+static inline struct bq24735 *to_bq24735(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static enum power_supply_property bq24735_charger_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int bq24735_charger_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static inline int bq24735_write_word(struct i2c_client *client, u8 reg,
+ u16 value)
+{
+ return i2c_smbus_write_word_data(client, reg, le16_to_cpu(value));
+}
+
+static inline int bq24735_read_word(struct i2c_client *client, u8 reg)
+{
+ s32 ret = i2c_smbus_read_word_data(client, reg);
+
+ return ret < 0 ? ret : le16_to_cpu(ret);
+}
+
+static int bq24735_update_word(struct i2c_client *client, u8 reg,
+ u16 mask, u16 value)
+{
+ unsigned int tmp;
+ int ret;
+
+ ret = bq24735_read_word(client, reg);
+ if (ret < 0)
+ return ret;
+
+ tmp = ret & ~mask;
+ tmp |= value & mask;
+
+ return bq24735_write_word(client, reg, tmp);
+}
+
+static inline int bq24735_enable_charging(struct bq24735 *charger)
+{
+ if (charger->pdata->ext_control)
+ return 0;
+
+ return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
+ BQ24735_CHG_OPT_CHARGE_DISABLE,
+ ~BQ24735_CHG_OPT_CHARGE_DISABLE);
+}
+
+static inline int bq24735_disable_charging(struct bq24735 *charger)
+{
+ if (charger->pdata->ext_control)
+ return 0;
+
+ return bq24735_update_word(charger->client, BQ24735_CHG_OPT,
+ BQ24735_CHG_OPT_CHARGE_DISABLE,
+ BQ24735_CHG_OPT_CHARGE_DISABLE);
+}
+
+static int bq24735_config_charger(struct bq24735 *charger)
+{
+ struct bq24735_platform *pdata = charger->pdata;
+ int ret;
+ u16 value;
+
+ if (pdata->ext_control)
+ return 0;
+
+ if (pdata->charge_current) {
+ value = pdata->charge_current & BQ24735_CHARGE_CURRENT_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_CHARGE_CURRENT, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write charger current : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (pdata->charge_voltage) {
+ value = pdata->charge_voltage & BQ24735_CHARGE_VOLTAGE_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_CHARGE_VOLTAGE, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write charger voltage : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ if (pdata->input_current) {
+ value = pdata->input_current & BQ24735_INPUT_CURRENT_MASK;
+
+ ret = bq24735_write_word(charger->client,
+ BQ24735_INPUT_CURRENT, value);
+ if (ret < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to write input current : %d\n",
+ ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static bool bq24735_charger_is_present(struct bq24735 *charger)
+{
+ if (charger->status_gpio) {
+ return !gpiod_get_value_cansleep(charger->status_gpio);
+ } else {
+ int ac = 0;
+
+ ac = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
+ if (ac < 0) {
+ dev_err(&charger->client->dev,
+ "Failed to read charger options : %d\n",
+ ac);
+ return false;
+ }
+ return (ac & BQ24735_CHG_OPT_AC_PRESENT) ? true : false;
+ }
+
+ return false;
+}
+
+static int bq24735_charger_is_charging(struct bq24735 *charger)
+{
+ int ret;
+
+ if (!bq24735_charger_is_present(charger))
+ return 0;
+
+ ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT);
+ if (ret < 0)
+ return ret;
+
+ return !(ret & BQ24735_CHG_OPT_CHARGE_DISABLE);
+}
+
+static irqreturn_t bq24735_charger_isr(int irq, void *devid)
+{
+ struct power_supply *psy = devid;
+ struct bq24735 *charger = to_bq24735(psy);
+
+ mutex_lock(&charger->lock);
+
+ if (charger->charging && bq24735_charger_is_present(charger))
+ bq24735_enable_charging(charger);
+ else
+ bq24735_disable_charging(charger);
+
+ mutex_unlock(&charger->lock);
+
+ power_supply_changed(psy);
+
+ return IRQ_HANDLED;
+}
+
+static int bq24735_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct bq24735 *charger = to_bq24735(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = bq24735_charger_is_present(charger) ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (bq24735_charger_is_charging(charger)) {
+ case 1:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case 0:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bq24735_charger_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct bq24735 *charger = to_bq24735(psy);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ switch (val->intval) {
+ case POWER_SUPPLY_STATUS_CHARGING:
+ mutex_lock(&charger->lock);
+ charger->charging = true;
+ ret = bq24735_enable_charging(charger);
+ mutex_unlock(&charger->lock);
+ if (ret)
+ return ret;
+ bq24735_config_charger(charger);
+ break;
+ case POWER_SUPPLY_STATUS_DISCHARGING:
+ case POWER_SUPPLY_STATUS_NOT_CHARGING:
+ mutex_lock(&charger->lock);
+ charger->charging = false;
+ ret = bq24735_disable_charging(charger);
+ mutex_unlock(&charger->lock);
+ if (ret)
+ return ret;
+ break;
+ default:
+ return -EINVAL;
+ }
+ power_supply_changed(psy);
+ break;
+ default:
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client)
+{
+ struct bq24735_platform *pdata;
+ struct device_node *np = client->dev.of_node;
+ u32 val;
+ int ret;
+
+ pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&client->dev,
+ "Memory alloc for bq24735 pdata failed\n");
+ return NULL;
+ }
+
+ ret = of_property_read_u32(np, "ti,charge-current", &val);
+ if (!ret)
+ pdata->charge_current = val;
+
+ ret = of_property_read_u32(np, "ti,charge-voltage", &val);
+ if (!ret)
+ pdata->charge_voltage = val;
+
+ ret = of_property_read_u32(np, "ti,input-current", &val);
+ if (!ret)
+ pdata->input_current = val;
+
+ pdata->ext_control = of_property_read_bool(np, "ti,external-control");
+
+ return pdata;
+}
+
+static int bq24735_charger_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret;
+ struct bq24735 *charger;
+ struct power_supply_desc *supply_desc;
+ struct power_supply_config psy_cfg = {};
+ char *name;
+
+ charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ mutex_init(&charger->lock);
+ charger->charging = true;
+ charger->pdata = client->dev.platform_data;
+
+ if (IS_ENABLED(CONFIG_OF) && !charger->pdata && client->dev.of_node)
+ charger->pdata = bq24735_parse_dt_data(client);
+
+ if (!charger->pdata) {
+ dev_err(&client->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ name = (char *)charger->pdata->name;
+ if (!name) {
+ name = devm_kasprintf(&client->dev, GFP_KERNEL,
+ "bq24735@%s",
+ dev_name(&client->dev));
+ if (!name) {
+ dev_err(&client->dev, "Failed to alloc device name\n");
+ return -ENOMEM;
+ }
+ }
+
+ charger->client = client;
+
+ supply_desc = &charger->charger_desc;
+
+ supply_desc->name = name;
+ supply_desc->type = POWER_SUPPLY_TYPE_MAINS;
+ supply_desc->properties = bq24735_charger_properties;
+ supply_desc->num_properties = ARRAY_SIZE(bq24735_charger_properties);
+ supply_desc->get_property = bq24735_charger_get_property;
+ supply_desc->set_property = bq24735_charger_set_property;
+ supply_desc->property_is_writeable =
+ bq24735_charger_property_is_writeable;
+
+ psy_cfg.supplied_to = charger->pdata->supplied_to;
+ psy_cfg.num_supplicants = charger->pdata->num_supplicants;
+ psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.drv_data = charger;
+
+ i2c_set_clientdata(client, charger);
+
+ charger->status_gpio = devm_gpiod_get_optional(&client->dev,
+ "ti,ac-detect",
+ GPIOD_IN);
+ if (IS_ERR(charger->status_gpio)) {
+ ret = PTR_ERR(charger->status_gpio);
+ dev_err(&client->dev, "Getting gpio failed: %d\n", ret);
+ return ret;
+ }
+
+ if (!charger->status_gpio || bq24735_charger_is_present(charger)) {
+ ret = bq24735_read_word(client, BQ24735_MANUFACTURER_ID);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read manufacturer id : %d\n",
+ ret);
+ return ret;
+ } else if (ret != 0x0040) {
+ dev_err(&client->dev,
+ "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret);
+ return -ENODEV;
+ }
+
+ ret = bq24735_read_word(client, BQ24735_DEVICE_ID);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to read device id : %d\n", ret);
+ return ret;
+ } else if (ret != 0x000B) {
+ dev_err(&client->dev,
+ "device id mismatch. 0x000b != 0x%04x\n", ret);
+ return -ENODEV;
+ }
+ }
+
+ ret = bq24735_config_charger(charger);
+ if (ret < 0) {
+ dev_err(&client->dev, "failed in configuring charger");
+ return ret;
+ }
+
+ /* check for AC adapter presence */
+ if (bq24735_charger_is_present(charger)) {
+ ret = bq24735_enable_charging(charger);
+ if (ret < 0) {
+ dev_err(&client->dev, "Failed to enable charging\n");
+ return ret;
+ }
+ }
+
+ charger->charger = devm_power_supply_register(&client->dev, supply_desc,
+ &psy_cfg);
+ if (IS_ERR(charger->charger)) {
+ ret = PTR_ERR(charger->charger);
+ dev_err(&client->dev, "Failed to register power supply: %d\n",
+ ret);
+ return ret;
+ }
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bq24735_charger_isr,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ supply_desc->name,
+ charger->charger);
+ if (ret) {
+ dev_err(&client->dev,
+ "Unable to register IRQ %d err %d\n",
+ client->irq, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct i2c_device_id bq24735_charger_id[] = {
+ { "bq24735-charger", 0 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, bq24735_charger_id);
+
+static const struct of_device_id bq24735_match_ids[] = {
+ { .compatible = "ti,bq24735", },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, bq24735_match_ids);
+
+static struct i2c_driver bq24735_charger_driver = {
+ .driver = {
+ .name = "bq24735-charger",
+ .of_match_table = bq24735_match_ids,
+ },
+ .probe = bq24735_charger_probe,
+ .id_table = bq24735_charger_id,
+};
+
+module_i2c_driver(bq24735_charger_driver);
+
+MODULE_DESCRIPTION("bq24735 battery charging driver");
+MODULE_AUTHOR("Darbha Sriharsha <dsriharsha@nvidia.com>");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/bq25890_charger.c b/drivers/power/supply/bq25890_charger.c
new file mode 100644
index 0000000..f993a55
--- /dev/null
+++ b/drivers/power/supply/bq25890_charger.c
@@ -0,0 +1,994 @@
+/*
+ * TI BQ25890 charger driver
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * 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.
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/types.h>
+#include <linux/gpio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/usb/phy.h>
+
+#include <linux/acpi.h>
+#include <linux/of.h>
+
+#define BQ25890_MANUFACTURER "Texas Instruments"
+#define BQ25890_IRQ_PIN "bq25890_irq"
+
+#define BQ25890_ID 3
+
+enum bq25890_fields {
+ F_EN_HIZ, F_EN_ILIM, F_IILIM, /* Reg00 */
+ F_BHOT, F_BCOLD, F_VINDPM_OFS, /* Reg01 */
+ F_CONV_START, F_CONV_RATE, F_BOOSTF, F_ICO_EN,
+ F_HVDCP_EN, F_MAXC_EN, F_FORCE_DPM, F_AUTO_DPDM_EN, /* Reg02 */
+ F_BAT_LOAD_EN, F_WD_RST, F_OTG_CFG, F_CHG_CFG, F_SYSVMIN, /* Reg03 */
+ F_PUMPX_EN, F_ICHG, /* Reg04 */
+ F_IPRECHG, F_ITERM, /* Reg05 */
+ F_VREG, F_BATLOWV, F_VRECHG, /* Reg06 */
+ F_TERM_EN, F_STAT_DIS, F_WD, F_TMR_EN, F_CHG_TMR,
+ F_JEITA_ISET, /* Reg07 */
+ F_BATCMP, F_VCLAMP, F_TREG, /* Reg08 */
+ F_FORCE_ICO, F_TMR2X_EN, F_BATFET_DIS, F_JEITA_VSET,
+ F_BATFET_DLY, F_BATFET_RST_EN, F_PUMPX_UP, F_PUMPX_DN, /* Reg09 */
+ F_BOOSTV, F_BOOSTI, /* Reg0A */
+ F_VBUS_STAT, F_CHG_STAT, F_PG_STAT, F_SDP_STAT, F_VSYS_STAT, /* Reg0B */
+ F_WD_FAULT, F_BOOST_FAULT, F_CHG_FAULT, F_BAT_FAULT,
+ F_NTC_FAULT, /* Reg0C */
+ F_FORCE_VINDPM, F_VINDPM, /* Reg0D */
+ F_THERM_STAT, F_BATV, /* Reg0E */
+ F_SYSV, /* Reg0F */
+ F_TSPCT, /* Reg10 */
+ F_VBUS_GD, F_VBUSV, /* Reg11 */
+ F_ICHGR, /* Reg12 */
+ F_VDPM_STAT, F_IDPM_STAT, F_IDPM_LIM, /* Reg13 */
+ F_REG_RST, F_ICO_OPTIMIZED, F_PN, F_TS_PROFILE, F_DEV_REV, /* Reg14 */
+
+ F_MAX_FIELDS
+};
+
+/* initial field values, converted to register values */
+struct bq25890_init_data {
+ u8 ichg; /* charge current */
+ u8 vreg; /* regulation voltage */
+ u8 iterm; /* termination current */
+ u8 iprechg; /* precharge current */
+ u8 sysvmin; /* minimum system voltage limit */
+ u8 boostv; /* boost regulation voltage */
+ u8 boosti; /* boost current limit */
+ u8 boostf; /* boost frequency */
+ u8 ilim_en; /* enable ILIM pin */
+ u8 treg; /* thermal regulation threshold */
+};
+
+struct bq25890_state {
+ u8 online;
+ u8 chrg_status;
+ u8 chrg_fault;
+ u8 vsys_status;
+ u8 boost_fault;
+ u8 bat_fault;
+};
+
+struct bq25890_device {
+ struct i2c_client *client;
+ struct device *dev;
+ struct power_supply *charger;
+
+ struct usb_phy *usb_phy;
+ struct notifier_block usb_nb;
+ struct work_struct usb_work;
+ unsigned long usb_event;
+
+ struct regmap *rmap;
+ struct regmap_field *rmap_fields[F_MAX_FIELDS];
+
+ int chip_id;
+ struct bq25890_init_data init_data;
+ struct bq25890_state state;
+
+ struct mutex lock; /* protect state data */
+};
+
+static const struct regmap_range bq25890_readonly_reg_ranges[] = {
+ regmap_reg_range(0x0b, 0x0c),
+ regmap_reg_range(0x0e, 0x13),
+};
+
+static const struct regmap_access_table bq25890_writeable_regs = {
+ .no_ranges = bq25890_readonly_reg_ranges,
+ .n_no_ranges = ARRAY_SIZE(bq25890_readonly_reg_ranges),
+};
+
+static const struct regmap_range bq25890_volatile_reg_ranges[] = {
+ regmap_reg_range(0x00, 0x00),
+ regmap_reg_range(0x09, 0x09),
+ regmap_reg_range(0x0b, 0x0c),
+ regmap_reg_range(0x0e, 0x14),
+};
+
+static const struct regmap_access_table bq25890_volatile_regs = {
+ .yes_ranges = bq25890_volatile_reg_ranges,
+ .n_yes_ranges = ARRAY_SIZE(bq25890_volatile_reg_ranges),
+};
+
+static const struct regmap_config bq25890_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+
+ .max_register = 0x14,
+ .cache_type = REGCACHE_RBTREE,
+
+ .wr_table = &bq25890_writeable_regs,
+ .volatile_table = &bq25890_volatile_regs,
+};
+
+static const struct reg_field bq25890_reg_fields[] = {
+ /* REG00 */
+ [F_EN_HIZ] = REG_FIELD(0x00, 7, 7),
+ [F_EN_ILIM] = REG_FIELD(0x00, 6, 6),
+ [F_IILIM] = REG_FIELD(0x00, 0, 5),
+ /* REG01 */
+ [F_BHOT] = REG_FIELD(0x01, 6, 7),
+ [F_BCOLD] = REG_FIELD(0x01, 5, 5),
+ [F_VINDPM_OFS] = REG_FIELD(0x01, 0, 4),
+ /* REG02 */
+ [F_CONV_START] = REG_FIELD(0x02, 7, 7),
+ [F_CONV_RATE] = REG_FIELD(0x02, 6, 6),
+ [F_BOOSTF] = REG_FIELD(0x02, 5, 5),
+ [F_ICO_EN] = REG_FIELD(0x02, 4, 4),
+ [F_HVDCP_EN] = REG_FIELD(0x02, 3, 3),
+ [F_MAXC_EN] = REG_FIELD(0x02, 2, 2),
+ [F_FORCE_DPM] = REG_FIELD(0x02, 1, 1),
+ [F_AUTO_DPDM_EN] = REG_FIELD(0x02, 0, 0),
+ /* REG03 */
+ [F_BAT_LOAD_EN] = REG_FIELD(0x03, 7, 7),
+ [F_WD_RST] = REG_FIELD(0x03, 6, 6),
+ [F_OTG_CFG] = REG_FIELD(0x03, 5, 5),
+ [F_CHG_CFG] = REG_FIELD(0x03, 4, 4),
+ [F_SYSVMIN] = REG_FIELD(0x03, 1, 3),
+ /* REG04 */
+ [F_PUMPX_EN] = REG_FIELD(0x04, 7, 7),
+ [F_ICHG] = REG_FIELD(0x04, 0, 6),
+ /* REG05 */
+ [F_IPRECHG] = REG_FIELD(0x05, 4, 7),
+ [F_ITERM] = REG_FIELD(0x05, 0, 3),
+ /* REG06 */
+ [F_VREG] = REG_FIELD(0x06, 2, 7),
+ [F_BATLOWV] = REG_FIELD(0x06, 1, 1),
+ [F_VRECHG] = REG_FIELD(0x06, 0, 0),
+ /* REG07 */
+ [F_TERM_EN] = REG_FIELD(0x07, 7, 7),
+ [F_STAT_DIS] = REG_FIELD(0x07, 6, 6),
+ [F_WD] = REG_FIELD(0x07, 4, 5),
+ [F_TMR_EN] = REG_FIELD(0x07, 3, 3),
+ [F_CHG_TMR] = REG_FIELD(0x07, 1, 2),
+ [F_JEITA_ISET] = REG_FIELD(0x07, 0, 0),
+ /* REG08 */
+ [F_BATCMP] = REG_FIELD(0x08, 6, 7),
+ [F_VCLAMP] = REG_FIELD(0x08, 2, 4),
+ [F_TREG] = REG_FIELD(0x08, 0, 1),
+ /* REG09 */
+ [F_FORCE_ICO] = REG_FIELD(0x09, 7, 7),
+ [F_TMR2X_EN] = REG_FIELD(0x09, 6, 6),
+ [F_BATFET_DIS] = REG_FIELD(0x09, 5, 5),
+ [F_JEITA_VSET] = REG_FIELD(0x09, 4, 4),
+ [F_BATFET_DLY] = REG_FIELD(0x09, 3, 3),
+ [F_BATFET_RST_EN] = REG_FIELD(0x09, 2, 2),
+ [F_PUMPX_UP] = REG_FIELD(0x09, 1, 1),
+ [F_PUMPX_DN] = REG_FIELD(0x09, 0, 0),
+ /* REG0A */
+ [F_BOOSTV] = REG_FIELD(0x0A, 4, 7),
+ [F_BOOSTI] = REG_FIELD(0x0A, 0, 2),
+ /* REG0B */
+ [F_VBUS_STAT] = REG_FIELD(0x0B, 5, 7),
+ [F_CHG_STAT] = REG_FIELD(0x0B, 3, 4),
+ [F_PG_STAT] = REG_FIELD(0x0B, 2, 2),
+ [F_SDP_STAT] = REG_FIELD(0x0B, 1, 1),
+ [F_VSYS_STAT] = REG_FIELD(0x0B, 0, 0),
+ /* REG0C */
+ [F_WD_FAULT] = REG_FIELD(0x0C, 7, 7),
+ [F_BOOST_FAULT] = REG_FIELD(0x0C, 6, 6),
+ [F_CHG_FAULT] = REG_FIELD(0x0C, 4, 5),
+ [F_BAT_FAULT] = REG_FIELD(0x0C, 3, 3),
+ [F_NTC_FAULT] = REG_FIELD(0x0C, 0, 2),
+ /* REG0D */
+ [F_FORCE_VINDPM] = REG_FIELD(0x0D, 7, 7),
+ [F_VINDPM] = REG_FIELD(0x0D, 0, 6),
+ /* REG0E */
+ [F_THERM_STAT] = REG_FIELD(0x0E, 7, 7),
+ [F_BATV] = REG_FIELD(0x0E, 0, 6),
+ /* REG0F */
+ [F_SYSV] = REG_FIELD(0x0F, 0, 6),
+ /* REG10 */
+ [F_TSPCT] = REG_FIELD(0x10, 0, 6),
+ /* REG11 */
+ [F_VBUS_GD] = REG_FIELD(0x11, 7, 7),
+ [F_VBUSV] = REG_FIELD(0x11, 0, 6),
+ /* REG12 */
+ [F_ICHGR] = REG_FIELD(0x12, 0, 6),
+ /* REG13 */
+ [F_VDPM_STAT] = REG_FIELD(0x13, 7, 7),
+ [F_IDPM_STAT] = REG_FIELD(0x13, 6, 6),
+ [F_IDPM_LIM] = REG_FIELD(0x13, 0, 5),
+ /* REG14 */
+ [F_REG_RST] = REG_FIELD(0x14, 7, 7),
+ [F_ICO_OPTIMIZED] = REG_FIELD(0x14, 6, 6),
+ [F_PN] = REG_FIELD(0x14, 3, 5),
+ [F_TS_PROFILE] = REG_FIELD(0x14, 2, 2),
+ [F_DEV_REV] = REG_FIELD(0x14, 0, 1)
+};
+
+/*
+ * Most of the val -> idx conversions can be computed, given the minimum,
+ * maximum and the step between values. For the rest of conversions, we use
+ * lookup tables.
+ */
+enum bq25890_table_ids {
+ /* range tables */
+ TBL_ICHG,
+ TBL_ITERM,
+ TBL_IPRECHG,
+ TBL_VREG,
+ TBL_BATCMP,
+ TBL_VCLAMP,
+ TBL_BOOSTV,
+ TBL_SYSVMIN,
+
+ /* lookup tables */
+ TBL_TREG,
+ TBL_BOOSTI,
+};
+
+/* Thermal Regulation Threshold lookup table, in degrees Celsius */
+static const u32 bq25890_treg_tbl[] = { 60, 80, 100, 120 };
+
+#define BQ25890_TREG_TBL_SIZE ARRAY_SIZE(bq25890_treg_tbl)
+
+/* Boost mode current limit lookup table, in uA */
+static const u32 bq25890_boosti_tbl[] = {
+ 500000, 700000, 1100000, 1300000, 1600000, 1800000, 2100000, 2400000
+};
+
+#define BQ25890_BOOSTI_TBL_SIZE ARRAY_SIZE(bq25890_boosti_tbl)
+
+struct bq25890_range {
+ u32 min;
+ u32 max;
+ u32 step;
+};
+
+struct bq25890_lookup {
+ const u32 *tbl;
+ u32 size;
+};
+
+static const union {
+ struct bq25890_range rt;
+ struct bq25890_lookup lt;
+} bq25890_tables[] = {
+ /* range tables */
+ [TBL_ICHG] = { .rt = {0, 5056000, 64000} }, /* uA */
+ [TBL_ITERM] = { .rt = {64000, 1024000, 64000} }, /* uA */
+ [TBL_VREG] = { .rt = {3840000, 4608000, 16000} }, /* uV */
+ [TBL_BATCMP] = { .rt = {0, 140, 20} }, /* mOhm */
+ [TBL_VCLAMP] = { .rt = {0, 224000, 32000} }, /* uV */
+ [TBL_BOOSTV] = { .rt = {4550000, 5510000, 64000} }, /* uV */
+ [TBL_SYSVMIN] = { .rt = {3000000, 3700000, 100000} }, /* uV */
+
+ /* lookup tables */
+ [TBL_TREG] = { .lt = {bq25890_treg_tbl, BQ25890_TREG_TBL_SIZE} },
+ [TBL_BOOSTI] = { .lt = {bq25890_boosti_tbl, BQ25890_BOOSTI_TBL_SIZE} }
+};
+
+static int bq25890_field_read(struct bq25890_device *bq,
+ enum bq25890_fields field_id)
+{
+ int ret;
+ int val;
+
+ ret = regmap_field_read(bq->rmap_fields[field_id], &val);
+ if (ret < 0)
+ return ret;
+
+ return val;
+}
+
+static int bq25890_field_write(struct bq25890_device *bq,
+ enum bq25890_fields field_id, u8 val)
+{
+ return regmap_field_write(bq->rmap_fields[field_id], val);
+}
+
+static u8 bq25890_find_idx(u32 value, enum bq25890_table_ids id)
+{
+ u8 idx;
+
+ if (id >= TBL_TREG) {
+ const u32 *tbl = bq25890_tables[id].lt.tbl;
+ u32 tbl_size = bq25890_tables[id].lt.size;
+
+ for (idx = 1; idx < tbl_size && tbl[idx] <= value; idx++)
+ ;
+ } else {
+ const struct bq25890_range *rtbl = &bq25890_tables[id].rt;
+ u8 rtbl_size;
+
+ rtbl_size = (rtbl->max - rtbl->min) / rtbl->step + 1;
+
+ for (idx = 1;
+ idx < rtbl_size && (idx * rtbl->step + rtbl->min <= value);
+ idx++)
+ ;
+ }
+
+ return idx - 1;
+}
+
+static u32 bq25890_find_val(u8 idx, enum bq25890_table_ids id)
+{
+ const struct bq25890_range *rtbl;
+
+ /* lookup table? */
+ if (id >= TBL_TREG)
+ return bq25890_tables[id].lt.tbl[idx];
+
+ /* range table */
+ rtbl = &bq25890_tables[id].rt;
+
+ return (rtbl->min + idx * rtbl->step);
+}
+
+enum bq25890_status {
+ STATUS_NOT_CHARGING,
+ STATUS_PRE_CHARGING,
+ STATUS_FAST_CHARGING,
+ STATUS_TERMINATION_DONE,
+};
+
+enum bq25890_chrg_fault {
+ CHRG_FAULT_NORMAL,
+ CHRG_FAULT_INPUT,
+ CHRG_FAULT_THERMAL_SHUTDOWN,
+ CHRG_FAULT_TIMER_EXPIRED,
+};
+
+static int bq25890_power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret;
+ struct bq25890_device *bq = power_supply_get_drvdata(psy);
+ struct bq25890_state state;
+
+ mutex_lock(&bq->lock);
+ state = bq->state;
+ mutex_unlock(&bq->lock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!state.online)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (state.chrg_status == STATUS_NOT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (state.chrg_status == STATUS_PRE_CHARGING ||
+ state.chrg_status == STATUS_FAST_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (state.chrg_status == STATUS_TERMINATION_DONE)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = BQ25890_MANUFACTURER;
+ break;
+
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = state.online;
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (!state.chrg_fault && !state.bat_fault && !state.boost_fault)
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ else if (state.bat_fault)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else if (state.chrg_fault == CHRG_FAULT_TIMER_EXPIRED)
+ val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ else if (state.chrg_fault == CHRG_FAULT_THERMAL_SHUTDOWN)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = bq25890_field_read(bq, F_ICHGR); /* read measured value */
+ if (ret < 0)
+ return ret;
+
+ /* converted_val = ADC_val * 50mA (table 10.3.19) */
+ val->intval = ret * 50000;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = bq25890_tables[TBL_ICHG].rt.max;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ if (!state.online) {
+ val->intval = 0;
+ break;
+ }
+
+ ret = bq25890_field_read(bq, F_BATV); /* read measured value */
+ if (ret < 0)
+ return ret;
+
+ /* converted_val = 2.304V + ADC_val * 20mV (table 10.3.15) */
+ val->intval = 2304000 + ret * 20000;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ val->intval = bq25890_tables[TBL_VREG].rt.max;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ val->intval = bq25890_find_val(bq->init_data.iterm, TBL_ITERM);
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int bq25890_get_chip_state(struct bq25890_device *bq,
+ struct bq25890_state *state)
+{
+ int i, ret;
+
+ struct {
+ enum bq25890_fields id;
+ u8 *data;
+ } state_fields[] = {
+ {F_CHG_STAT, &state->chrg_status},
+ {F_PG_STAT, &state->online},
+ {F_VSYS_STAT, &state->vsys_status},
+ {F_BOOST_FAULT, &state->boost_fault},
+ {F_BAT_FAULT, &state->bat_fault},
+ {F_CHG_FAULT, &state->chrg_fault}
+ };
+
+ for (i = 0; i < ARRAY_SIZE(state_fields); i++) {
+ ret = bq25890_field_read(bq, state_fields[i].id);
+ if (ret < 0)
+ return ret;
+
+ *state_fields[i].data = ret;
+ }
+
+ dev_dbg(bq->dev, "S:CHG/PG/VSYS=%d/%d/%d, F:CHG/BOOST/BAT=%d/%d/%d\n",
+ state->chrg_status, state->online, state->vsys_status,
+ state->chrg_fault, state->boost_fault, state->bat_fault);
+
+ return 0;
+}
+
+static bool bq25890_state_changed(struct bq25890_device *bq,
+ struct bq25890_state *new_state)
+{
+ struct bq25890_state old_state;
+
+ mutex_lock(&bq->lock);
+ old_state = bq->state;
+ mutex_unlock(&bq->lock);
+
+ return (old_state.chrg_status != new_state->chrg_status ||
+ old_state.chrg_fault != new_state->chrg_fault ||
+ old_state.online != new_state->online ||
+ old_state.bat_fault != new_state->bat_fault ||
+ old_state.boost_fault != new_state->boost_fault ||
+ old_state.vsys_status != new_state->vsys_status);
+}
+
+static void bq25890_handle_state_change(struct bq25890_device *bq,
+ struct bq25890_state *new_state)
+{
+ int ret;
+ struct bq25890_state old_state;
+
+ mutex_lock(&bq->lock);
+ old_state = bq->state;
+ mutex_unlock(&bq->lock);
+
+ if (!new_state->online) { /* power removed */
+ /* disable ADC */
+ ret = bq25890_field_write(bq, F_CONV_START, 0);
+ if (ret < 0)
+ goto error;
+ } else if (!old_state.online) { /* power inserted */
+ /* enable ADC, to have control of charge current/voltage */
+ ret = bq25890_field_write(bq, F_CONV_START, 1);
+ if (ret < 0)
+ goto error;
+ }
+
+ return;
+
+error:
+ dev_err(bq->dev, "Error communicating with the chip.\n");
+}
+
+static irqreturn_t bq25890_irq_handler_thread(int irq, void *private)
+{
+ struct bq25890_device *bq = private;
+ int ret;
+ struct bq25890_state state;
+
+ ret = bq25890_get_chip_state(bq, &state);
+ if (ret < 0)
+ goto handled;
+
+ if (!bq25890_state_changed(bq, &state))
+ goto handled;
+
+ bq25890_handle_state_change(bq, &state);
+
+ mutex_lock(&bq->lock);
+ bq->state = state;
+ mutex_unlock(&bq->lock);
+
+ power_supply_changed(bq->charger);
+
+handled:
+ return IRQ_HANDLED;
+}
+
+static int bq25890_chip_reset(struct bq25890_device *bq)
+{
+ int ret;
+ int rst_check_counter = 10;
+
+ ret = bq25890_field_write(bq, F_REG_RST, 1);
+ if (ret < 0)
+ return ret;
+
+ do {
+ ret = bq25890_field_read(bq, F_REG_RST);
+ if (ret < 0)
+ return ret;
+
+ usleep_range(5, 10);
+ } while (ret == 1 && --rst_check_counter);
+
+ if (!rst_check_counter)
+ return -ETIMEDOUT;
+
+ return 0;
+}
+
+static int bq25890_hw_init(struct bq25890_device *bq)
+{
+ int ret;
+ int i;
+ struct bq25890_state state;
+
+ const struct {
+ enum bq25890_fields id;
+ u32 value;
+ } init_data[] = {
+ {F_ICHG, bq->init_data.ichg},
+ {F_VREG, bq->init_data.vreg},
+ {F_ITERM, bq->init_data.iterm},
+ {F_IPRECHG, bq->init_data.iprechg},
+ {F_SYSVMIN, bq->init_data.sysvmin},
+ {F_BOOSTV, bq->init_data.boostv},
+ {F_BOOSTI, bq->init_data.boosti},
+ {F_BOOSTF, bq->init_data.boostf},
+ {F_EN_ILIM, bq->init_data.ilim_en},
+ {F_TREG, bq->init_data.treg}
+ };
+
+ ret = bq25890_chip_reset(bq);
+ if (ret < 0)
+ return ret;
+
+ /* disable watchdog */
+ ret = bq25890_field_write(bq, F_WD, 0);
+ if (ret < 0)
+ return ret;
+
+ /* initialize currents/voltages and other parameters */
+ for (i = 0; i < ARRAY_SIZE(init_data); i++) {
+ ret = bq25890_field_write(bq, init_data[i].id,
+ init_data[i].value);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* Configure ADC for continuous conversions. This does not enable it. */
+ ret = bq25890_field_write(bq, F_CONV_RATE, 1);
+ if (ret < 0)
+ return ret;
+
+ ret = bq25890_get_chip_state(bq, &state);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&bq->lock);
+ bq->state = state;
+ mutex_unlock(&bq->lock);
+
+ return 0;
+}
+
+static enum power_supply_property bq25890_power_supply_props[] = {
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ 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_CHARGE_TERM_CURRENT,
+};
+
+static char *bq25890_charger_supplied_to[] = {
+ "main-battery",
+};
+
+static const struct power_supply_desc bq25890_power_supply_desc = {
+ .name = "bq25890-charger",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = bq25890_power_supply_props,
+ .num_properties = ARRAY_SIZE(bq25890_power_supply_props),
+ .get_property = bq25890_power_supply_get_property,
+};
+
+static int bq25890_power_supply_init(struct bq25890_device *bq)
+{
+ struct power_supply_config psy_cfg = { .drv_data = bq, };
+
+ psy_cfg.supplied_to = bq25890_charger_supplied_to;
+ psy_cfg.num_supplicants = ARRAY_SIZE(bq25890_charger_supplied_to);
+
+ bq->charger = power_supply_register(bq->dev, &bq25890_power_supply_desc,
+ &psy_cfg);
+
+ return PTR_ERR_OR_ZERO(bq->charger);
+}
+
+static void bq25890_usb_work(struct work_struct *data)
+{
+ int ret;
+ struct bq25890_device *bq =
+ container_of(data, struct bq25890_device, usb_work);
+
+ switch (bq->usb_event) {
+ case USB_EVENT_ID:
+ /* Enable boost mode */
+ ret = bq25890_field_write(bq, F_OTG_CFG, 1);
+ if (ret < 0)
+ goto error;
+ break;
+
+ case USB_EVENT_NONE:
+ /* Disable boost mode */
+ ret = bq25890_field_write(bq, F_OTG_CFG, 0);
+ if (ret < 0)
+ goto error;
+
+ power_supply_changed(bq->charger);
+ break;
+ }
+
+ return;
+
+error:
+ dev_err(bq->dev, "Error switching to boost/charger mode.\n");
+}
+
+static int bq25890_usb_notifier(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ struct bq25890_device *bq =
+ container_of(nb, struct bq25890_device, usb_nb);
+
+ bq->usb_event = val;
+ queue_work(system_power_efficient_wq, &bq->usb_work);
+
+ return NOTIFY_OK;
+}
+
+static int bq25890_irq_probe(struct bq25890_device *bq)
+{
+ struct gpio_desc *irq;
+
+ irq = devm_gpiod_get_index(bq->dev, BQ25890_IRQ_PIN, 0, GPIOD_IN);
+ if (IS_ERR(irq)) {
+ dev_err(bq->dev, "Could not probe irq pin.\n");
+ return PTR_ERR(irq);
+ }
+
+ return gpiod_to_irq(irq);
+}
+
+static int bq25890_fw_read_u32_props(struct bq25890_device *bq)
+{
+ int ret;
+ u32 property;
+ int i;
+ struct bq25890_init_data *init = &bq->init_data;
+ struct {
+ char *name;
+ bool optional;
+ enum bq25890_table_ids tbl_id;
+ u8 *conv_data; /* holds converted value from given property */
+ } props[] = {
+ /* required properties */
+ {"ti,charge-current", false, TBL_ICHG, &init->ichg},
+ {"ti,battery-regulation-voltage", false, TBL_VREG, &init->vreg},
+ {"ti,termination-current", false, TBL_ITERM, &init->iterm},
+ {"ti,precharge-current", false, TBL_ITERM, &init->iprechg},
+ {"ti,minimum-sys-voltage", false, TBL_SYSVMIN, &init->sysvmin},
+ {"ti,boost-voltage", false, TBL_BOOSTV, &init->boostv},
+ {"ti,boost-max-current", false, TBL_BOOSTI, &init->boosti},
+
+ /* optional properties */
+ {"ti,thermal-regulation-threshold", true, TBL_TREG, &init->treg}
+ };
+
+ /* initialize data for optional properties */
+ init->treg = 3; /* 120 degrees Celsius */
+
+ for (i = 0; i < ARRAY_SIZE(props); i++) {
+ ret = device_property_read_u32(bq->dev, props[i].name,
+ &property);
+ if (ret < 0) {
+ if (props[i].optional)
+ continue;
+
+ return ret;
+ }
+
+ *props[i].conv_data = bq25890_find_idx(property,
+ props[i].tbl_id);
+ }
+
+ return 0;
+}
+
+static int bq25890_fw_probe(struct bq25890_device *bq)
+{
+ int ret;
+ struct bq25890_init_data *init = &bq->init_data;
+
+ ret = bq25890_fw_read_u32_props(bq);
+ if (ret < 0)
+ return ret;
+
+ init->ilim_en = device_property_read_bool(bq->dev, "ti,use-ilim-pin");
+ init->boostf = device_property_read_bool(bq->dev, "ti,boost-low-freq");
+
+ return 0;
+}
+
+static int bq25890_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 bq25890_device *bq;
+ int ret;
+ int i;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+ return -ENODEV;
+ }
+
+ bq = devm_kzalloc(dev, sizeof(*bq), GFP_KERNEL);
+ if (!bq)
+ return -ENOMEM;
+
+ bq->client = client;
+ bq->dev = dev;
+
+ mutex_init(&bq->lock);
+
+ bq->rmap = devm_regmap_init_i2c(client, &bq25890_regmap_config);
+ if (IS_ERR(bq->rmap)) {
+ dev_err(dev, "failed to allocate register map\n");
+ return PTR_ERR(bq->rmap);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bq25890_reg_fields); i++) {
+ const struct reg_field *reg_fields = bq25890_reg_fields;
+
+ bq->rmap_fields[i] = devm_regmap_field_alloc(dev, bq->rmap,
+ reg_fields[i]);
+ if (IS_ERR(bq->rmap_fields[i])) {
+ dev_err(dev, "cannot allocate regmap field\n");
+ return PTR_ERR(bq->rmap_fields[i]);
+ }
+ }
+
+ i2c_set_clientdata(client, bq);
+
+ bq->chip_id = bq25890_field_read(bq, F_PN);
+ if (bq->chip_id < 0) {
+ dev_err(dev, "Cannot read chip ID.\n");
+ return bq->chip_id;
+ }
+
+ if (bq->chip_id != BQ25890_ID) {
+ dev_err(dev, "Chip with ID=%d, not supported!\n", bq->chip_id);
+ return -ENODEV;
+ }
+
+ if (!dev->platform_data) {
+ ret = bq25890_fw_probe(bq);
+ if (ret < 0) {
+ dev_err(dev, "Cannot read device properties.\n");
+ return ret;
+ }
+ } else {
+ return -ENODEV;
+ }
+
+ ret = bq25890_hw_init(bq);
+ if (ret < 0) {
+ dev_err(dev, "Cannot initialize the chip.\n");
+ return ret;
+ }
+
+ if (client->irq <= 0)
+ client->irq = bq25890_irq_probe(bq);
+
+ if (client->irq < 0) {
+ dev_err(dev, "No irq resource found.\n");
+ return client->irq;
+ }
+
+ /* OTG reporting */
+ bq->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+ if (!IS_ERR_OR_NULL(bq->usb_phy)) {
+ INIT_WORK(&bq->usb_work, bq25890_usb_work);
+ bq->usb_nb.notifier_call = bq25890_usb_notifier;
+ usb_register_notifier(bq->usb_phy, &bq->usb_nb);
+ }
+
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ bq25890_irq_handler_thread,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ BQ25890_IRQ_PIN, bq);
+ if (ret)
+ goto irq_fail;
+
+ ret = bq25890_power_supply_init(bq);
+ if (ret < 0) {
+ dev_err(dev, "Failed to register power supply\n");
+ goto irq_fail;
+ }
+
+ return 0;
+
+irq_fail:
+ if (!IS_ERR_OR_NULL(bq->usb_phy))
+ usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
+
+ return ret;
+}
+
+static int bq25890_remove(struct i2c_client *client)
+{
+ struct bq25890_device *bq = i2c_get_clientdata(client);
+
+ power_supply_unregister(bq->charger);
+
+ if (!IS_ERR_OR_NULL(bq->usb_phy))
+ usb_unregister_notifier(bq->usb_phy, &bq->usb_nb);
+
+ /* reset all registers to default values */
+ bq25890_chip_reset(bq);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int bq25890_suspend(struct device *dev)
+{
+ struct bq25890_device *bq = dev_get_drvdata(dev);
+
+ /*
+ * If charger is removed, while in suspend, make sure ADC is diabled
+ * since it consumes slightly more power.
+ */
+ return bq25890_field_write(bq, F_CONV_START, 0);
+}
+
+static int bq25890_resume(struct device *dev)
+{
+ int ret;
+ struct bq25890_state state;
+ struct bq25890_device *bq = dev_get_drvdata(dev);
+
+ ret = bq25890_get_chip_state(bq, &state);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&bq->lock);
+ bq->state = state;
+ mutex_unlock(&bq->lock);
+
+ /* Re-enable ADC only if charger is plugged in. */
+ if (state.online) {
+ ret = bq25890_field_write(bq, F_CONV_START, 1);
+ if (ret < 0)
+ return ret;
+ }
+
+ /* signal userspace, maybe state changed while suspended */
+ power_supply_changed(bq->charger);
+
+ return 0;
+}
+#endif
+
+static const struct dev_pm_ops bq25890_pm = {
+ SET_SYSTEM_SLEEP_PM_OPS(bq25890_suspend, bq25890_resume)
+};
+
+static const struct i2c_device_id bq25890_i2c_ids[] = {
+ { "bq25890", 0 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq25890_i2c_ids);
+
+static const struct of_device_id bq25890_of_match[] = {
+ { .compatible = "ti,bq25890", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, bq25890_of_match);
+
+static const struct acpi_device_id bq25890_acpi_match[] = {
+ {"BQ258900", 0},
+ {},
+};
+MODULE_DEVICE_TABLE(acpi, bq25890_acpi_match);
+
+static struct i2c_driver bq25890_driver = {
+ .driver = {
+ .name = "bq25890-charger",
+ .of_match_table = of_match_ptr(bq25890_of_match),
+ .acpi_match_table = ACPI_PTR(bq25890_acpi_match),
+ .pm = &bq25890_pm,
+ },
+ .probe = bq25890_probe,
+ .remove = bq25890_remove,
+ .id_table = bq25890_i2c_ids,
+};
+module_i2c_driver(bq25890_driver);
+
+MODULE_AUTHOR("Laurentiu Palcu <laurentiu.palcu@intel.com>");
+MODULE_DESCRIPTION("bq25890 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c
new file mode 100644
index 0000000..3b0dbc6
--- /dev/null
+++ b/drivers/power/supply/bq27xxx_battery.c
@@ -0,0 +1,1139 @@
+/*
+ * BQ27xxx battery driver
+ *
+ * Copyright (C) 2008 Rodolfo Giometti <giometti@linux.it>
+ * Copyright (C) 2008 Eurotech S.p.A. <info@eurotech.it>
+ * Copyright (C) 2010-2011 Lars-Peter Clausen <lars@metafoo.de>
+ * Copyright (C) 2011 Pali Rohár <pali.rohar@gmail.com>
+ *
+ * Based on a previous work by Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * This package 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.
+ *
+ * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ * Datasheets:
+ * http://www.ti.com/product/bq27000
+ * http://www.ti.com/product/bq27200
+ * http://www.ti.com/product/bq27010
+ * http://www.ti.com/product/bq27210
+ * http://www.ti.com/product/bq27500
+ * http://www.ti.com/product/bq27510-g3
+ * http://www.ti.com/product/bq27520-g4
+ * http://www.ti.com/product/bq27530-g1
+ * http://www.ti.com/product/bq27531-g1
+ * http://www.ti.com/product/bq27541-g1
+ * http://www.ti.com/product/bq27542-g1
+ * http://www.ti.com/product/bq27546-g1
+ * http://www.ti.com/product/bq27742-g1
+ * http://www.ti.com/product/bq27545-g1
+ * http://www.ti.com/product/bq27421-g1
+ * http://www.ti.com/product/bq27425-g1
+ * http://www.ti.com/product/bq27411-g1
+ * http://www.ti.com/product/bq27621-g1
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/param.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/delay.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+
+#include <linux/power/bq27xxx_battery.h>
+
+#define DRIVER_VERSION "1.2.0"
+
+#define BQ27XXX_MANUFACTURER "Texas Instruments"
+
+/* BQ27XXX Flags */
+#define BQ27XXX_FLAG_DSC BIT(0)
+#define BQ27XXX_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */
+#define BQ27XXX_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */
+#define BQ27XXX_FLAG_FC BIT(9)
+#define BQ27XXX_FLAG_OTD BIT(14)
+#define BQ27XXX_FLAG_OTC BIT(15)
+#define BQ27XXX_FLAG_UT BIT(14)
+#define BQ27XXX_FLAG_OT BIT(15)
+
+/* BQ27000 has different layout for Flags register */
+#define BQ27000_FLAG_EDVF BIT(0) /* Final End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_EDV1 BIT(1) /* First End-of-Discharge-Voltage flag */
+#define BQ27000_FLAG_CI BIT(4) /* Capacity Inaccurate flag */
+#define BQ27000_FLAG_FC BIT(5)
+#define BQ27000_FLAG_CHGS BIT(7) /* Charge state flag */
+
+#define BQ27XXX_RS (20) /* Resistor sense mOhm */
+#define BQ27XXX_POWER_CONSTANT (29200) /* 29.2 µV^2 * 1000 */
+#define BQ27XXX_CURRENT_CONSTANT (3570) /* 3.57 µV * 1000 */
+
+#define INVALID_REG_ADDR 0xff
+
+/*
+ * bq27xxx_reg_index - Register names
+ *
+ * These are indexes into a device's register mapping array.
+ */
+
+enum bq27xxx_reg_index {
+ BQ27XXX_REG_CTRL = 0, /* Control */
+ BQ27XXX_REG_TEMP, /* Temperature */
+ BQ27XXX_REG_INT_TEMP, /* Internal Temperature */
+ BQ27XXX_REG_VOLT, /* Voltage */
+ BQ27XXX_REG_AI, /* Average Current */
+ BQ27XXX_REG_FLAGS, /* Flags */
+ BQ27XXX_REG_TTE, /* Time-to-Empty */
+ BQ27XXX_REG_TTF, /* Time-to-Full */
+ BQ27XXX_REG_TTES, /* Time-to-Empty Standby */
+ BQ27XXX_REG_TTECP, /* Time-to-Empty at Constant Power */
+ BQ27XXX_REG_NAC, /* Nominal Available Capacity */
+ BQ27XXX_REG_FCC, /* Full Charge Capacity */
+ BQ27XXX_REG_CYCT, /* Cycle Count */
+ BQ27XXX_REG_AE, /* Available Energy */
+ BQ27XXX_REG_SOC, /* State-of-Charge */
+ BQ27XXX_REG_DCAP, /* Design Capacity */
+ BQ27XXX_REG_AP, /* Average Power */
+ BQ27XXX_REG_MAX, /* sentinel */
+};
+
+/* Register mappings */
+static u8 bq27xxx_regs[][BQ27XXX_REG_MAX] = {
+ [BQ27000] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = 0x18,
+ [BQ27XXX_REG_TTES] = 0x1c,
+ [BQ27XXX_REG_TTECP] = 0x26,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = 0x22,
+ [BQ27XXX_REG_SOC] = 0x0b,
+ [BQ27XXX_REG_DCAP] = 0x76,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27010] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = 0x18,
+ [BQ27XXX_REG_TTES] = 0x1c,
+ [BQ27XXX_REG_TTECP] = 0x26,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x0b,
+ [BQ27XXX_REG_DCAP] = 0x76,
+ [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ },
+ [BQ27500] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = 0x1a,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = INVALID_REG_ADDR,
+ },
+ [BQ27530] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x32,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27541] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27545] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x06,
+ [BQ27XXX_REG_INT_TEMP] = 0x28,
+ [BQ27XXX_REG_VOLT] = 0x08,
+ [BQ27XXX_REG_AI] = 0x14,
+ [BQ27XXX_REG_FLAGS] = 0x0a,
+ [BQ27XXX_REG_TTE] = 0x16,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x0c,
+ [BQ27XXX_REG_FCC] = 0x12,
+ [BQ27XXX_REG_CYCT] = 0x2a,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x2c,
+ [BQ27XXX_REG_DCAP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AP] = 0x24,
+ },
+ [BQ27421] = {
+ [BQ27XXX_REG_CTRL] = 0x00,
+ [BQ27XXX_REG_TEMP] = 0x02,
+ [BQ27XXX_REG_INT_TEMP] = 0x1e,
+ [BQ27XXX_REG_VOLT] = 0x04,
+ [BQ27XXX_REG_AI] = 0x10,
+ [BQ27XXX_REG_FLAGS] = 0x06,
+ [BQ27XXX_REG_TTE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTF] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTES] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_TTECP] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_NAC] = 0x08,
+ [BQ27XXX_REG_FCC] = 0x0e,
+ [BQ27XXX_REG_CYCT] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_AE] = INVALID_REG_ADDR,
+ [BQ27XXX_REG_SOC] = 0x1c,
+ [BQ27XXX_REG_DCAP] = 0x3c,
+ [BQ27XXX_REG_AP] = 0x18,
+ },
+};
+
+static enum power_supply_property bq27000_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_POWER_AVG,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27010_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27500_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27530_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_POWER_AVG,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27541_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_POWER_AVG,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27545_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_POWER_AVG,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static enum power_supply_property bq27421_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+#define BQ27XXX_PROP(_id, _prop) \
+ [_id] = { \
+ .props = _prop, \
+ .size = ARRAY_SIZE(_prop), \
+ }
+
+static struct {
+ enum power_supply_property *props;
+ size_t size;
+} bq27xxx_battery_props[] = {
+ BQ27XXX_PROP(BQ27000, bq27000_battery_props),
+ BQ27XXX_PROP(BQ27010, bq27010_battery_props),
+ BQ27XXX_PROP(BQ27500, bq27500_battery_props),
+ BQ27XXX_PROP(BQ27530, bq27530_battery_props),
+ BQ27XXX_PROP(BQ27541, bq27541_battery_props),
+ BQ27XXX_PROP(BQ27545, bq27545_battery_props),
+ BQ27XXX_PROP(BQ27421, bq27421_battery_props),
+};
+
+static DEFINE_MUTEX(bq27xxx_list_lock);
+static LIST_HEAD(bq27xxx_battery_devices);
+
+static int poll_interval_param_set(const char *val, const struct kernel_param *kp)
+{
+ struct bq27xxx_device_info *di;
+ int ret;
+
+ ret = param_set_uint(val, kp);
+ if (ret < 0)
+ return ret;
+
+ mutex_lock(&bq27xxx_list_lock);
+ list_for_each_entry(di, &bq27xxx_battery_devices, list) {
+ cancel_delayed_work_sync(&di->work);
+ schedule_delayed_work(&di->work, 0);
+ }
+ mutex_unlock(&bq27xxx_list_lock);
+
+ return ret;
+}
+
+static const struct kernel_param_ops param_ops_poll_interval = {
+ .get = param_get_uint,
+ .set = poll_interval_param_set,
+};
+
+static unsigned int poll_interval = 360;
+module_param_cb(poll_interval, &param_ops_poll_interval, &poll_interval, 0644);
+MODULE_PARM_DESC(poll_interval,
+ "battery poll interval in seconds - 0 disables polling");
+
+/*
+ * Common code for BQ27xxx devices
+ */
+
+static inline int bq27xxx_read(struct bq27xxx_device_info *di, int reg_index,
+ bool single)
+{
+ /* Reports EINVAL for invalid/missing registers */
+ if (!di || di->regs[reg_index] == INVALID_REG_ADDR)
+ return -EINVAL;
+
+ return di->bus.read(di, di->regs[reg_index], single);
+}
+
+/*
+ * Return the battery State-of-Charge
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_soc(struct bq27xxx_device_info *di)
+{
+ int soc;
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ soc = bq27xxx_read(di, BQ27XXX_REG_SOC, true);
+ else
+ soc = bq27xxx_read(di, BQ27XXX_REG_SOC, false);
+
+ if (soc < 0)
+ dev_dbg(di->dev, "error reading State-of-Charge\n");
+
+ return soc;
+}
+
+/*
+ * Return a battery charge value in µAh
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_charge(struct bq27xxx_device_info *di, u8 reg)
+{
+ int charge;
+
+ charge = bq27xxx_read(di, reg, false);
+ if (charge < 0) {
+ dev_dbg(di->dev, "error reading charge register %02x: %d\n",
+ reg, charge);
+ return charge;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ charge *= BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS;
+ else
+ charge *= 1000;
+
+ return charge;
+}
+
+/*
+ * Return the battery Nominal available capacity in µAh
+ * Or < 0 if something fails.
+ */
+static inline int bq27xxx_battery_read_nac(struct bq27xxx_device_info *di)
+{
+ int flags;
+
+ if (di->chip == BQ27000 || di->chip == BQ27010) {
+ flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true);
+ if (flags >= 0 && (flags & BQ27000_FLAG_CI))
+ return -ENODATA;
+ }
+
+ return bq27xxx_battery_read_charge(di, BQ27XXX_REG_NAC);
+}
+
+/*
+ * Return the battery Full Charge Capacity in µAh
+ * Or < 0 if something fails.
+ */
+static inline int bq27xxx_battery_read_fcc(struct bq27xxx_device_info *di)
+{
+ return bq27xxx_battery_read_charge(di, BQ27XXX_REG_FCC);
+}
+
+/*
+ * Return the Design Capacity in µAh
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_dcap(struct bq27xxx_device_info *di)
+{
+ int dcap;
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, true);
+ else
+ dcap = bq27xxx_read(di, BQ27XXX_REG_DCAP, false);
+
+ if (dcap < 0) {
+ dev_dbg(di->dev, "error reading initial last measured discharge\n");
+ return dcap;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ dcap = (dcap << 8) * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS;
+ else
+ dcap *= 1000;
+
+ return dcap;
+}
+
+/*
+ * Return the battery Available energy in µWh
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_energy(struct bq27xxx_device_info *di)
+{
+ int ae;
+
+ ae = bq27xxx_read(di, BQ27XXX_REG_AE, false);
+ if (ae < 0) {
+ dev_dbg(di->dev, "error reading available energy\n");
+ return ae;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ ae *= BQ27XXX_POWER_CONSTANT / BQ27XXX_RS;
+ else
+ ae *= 1000;
+
+ return ae;
+}
+
+/*
+ * Return the battery temperature in tenths of degree Kelvin
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_temperature(struct bq27xxx_device_info *di)
+{
+ int temp;
+
+ temp = bq27xxx_read(di, BQ27XXX_REG_TEMP, false);
+ if (temp < 0) {
+ dev_err(di->dev, "error reading temperature\n");
+ return temp;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ temp = 5 * temp / 2;
+
+ return temp;
+}
+
+/*
+ * Return the battery Cycle count total
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_read_cyct(struct bq27xxx_device_info *di)
+{
+ int cyct;
+
+ cyct = bq27xxx_read(di, BQ27XXX_REG_CYCT, false);
+ if (cyct < 0)
+ dev_err(di->dev, "error reading cycle count total\n");
+
+ return cyct;
+}
+
+/*
+ * Read a time register.
+ * Return < 0 if something fails.
+ */
+static int bq27xxx_battery_read_time(struct bq27xxx_device_info *di, u8 reg)
+{
+ int tval;
+
+ tval = bq27xxx_read(di, reg, false);
+ if (tval < 0) {
+ dev_dbg(di->dev, "error reading time register %02x: %d\n",
+ reg, tval);
+ return tval;
+ }
+
+ if (tval == 65535)
+ return -ENODATA;
+
+ return tval * 60;
+}
+
+/*
+ * Read an average power register.
+ * Return < 0 if something fails.
+ */
+static int bq27xxx_battery_read_pwr_avg(struct bq27xxx_device_info *di)
+{
+ int tval;
+
+ tval = bq27xxx_read(di, BQ27XXX_REG_AP, false);
+ if (tval < 0) {
+ dev_err(di->dev, "error reading average power register %02x: %d\n",
+ BQ27XXX_REG_AP, tval);
+ return tval;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ return (tval * BQ27XXX_POWER_CONSTANT) / BQ27XXX_RS;
+ else
+ return tval;
+}
+
+/*
+ * Returns true if a battery over temperature condition is detected
+ */
+static bool bq27xxx_battery_overtemp(struct bq27xxx_device_info *di, u16 flags)
+{
+ if (di->chip == BQ27500 || di->chip == BQ27541 || di->chip == BQ27545)
+ return flags & (BQ27XXX_FLAG_OTC | BQ27XXX_FLAG_OTD);
+ if (di->chip == BQ27530 || di->chip == BQ27421)
+ return flags & BQ27XXX_FLAG_OT;
+
+ return false;
+}
+
+/*
+ * Returns true if a battery under temperature condition is detected
+ */
+static bool bq27xxx_battery_undertemp(struct bq27xxx_device_info *di, u16 flags)
+{
+ if (di->chip == BQ27530 || di->chip == BQ27421)
+ return flags & BQ27XXX_FLAG_UT;
+
+ return false;
+}
+
+/*
+ * Returns true if a low state of charge condition is detected
+ */
+static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags)
+{
+ if (di->chip == BQ27000 || di->chip == BQ27010)
+ return flags & (BQ27000_FLAG_EDV1 | BQ27000_FLAG_EDVF);
+ else
+ return flags & (BQ27XXX_FLAG_SOC1 | BQ27XXX_FLAG_SOCF);
+}
+
+/*
+ * Read flag register.
+ * Return < 0 if something fails.
+ */
+static int bq27xxx_battery_read_health(struct bq27xxx_device_info *di)
+{
+ int flags;
+ bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010;
+
+ flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag);
+ if (flags < 0) {
+ dev_err(di->dev, "error reading flag register:%d\n", flags);
+ return flags;
+ }
+
+ /* Unlikely but important to return first */
+ if (unlikely(bq27xxx_battery_overtemp(di, flags)))
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+ if (unlikely(bq27xxx_battery_undertemp(di, flags)))
+ return POWER_SUPPLY_HEALTH_COLD;
+ if (unlikely(bq27xxx_battery_dead(di, flags)))
+ return POWER_SUPPLY_HEALTH_DEAD;
+
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+void bq27xxx_battery_update(struct bq27xxx_device_info *di)
+{
+ struct bq27xxx_reg_cache cache = {0, };
+ bool has_ci_flag = di->chip == BQ27000 || di->chip == BQ27010;
+ bool has_singe_flag = di->chip == BQ27000 || di->chip == BQ27010;
+
+ cache.flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, has_singe_flag);
+ if ((cache.flags & 0xff) == 0xff)
+ cache.flags = -1; /* read error */
+ if (cache.flags >= 0) {
+ cache.temperature = bq27xxx_battery_read_temperature(di);
+ if (has_ci_flag && (cache.flags & BQ27000_FLAG_CI)) {
+ dev_info_once(di->dev, "battery is not calibrated! ignoring capacity values\n");
+ cache.capacity = -ENODATA;
+ cache.energy = -ENODATA;
+ cache.time_to_empty = -ENODATA;
+ cache.time_to_empty_avg = -ENODATA;
+ cache.time_to_full = -ENODATA;
+ cache.charge_full = -ENODATA;
+ cache.health = -ENODATA;
+ } else {
+ if (di->regs[BQ27XXX_REG_TTE] != INVALID_REG_ADDR)
+ cache.time_to_empty = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTE);
+ if (di->regs[BQ27XXX_REG_TTECP] != INVALID_REG_ADDR)
+ cache.time_to_empty_avg = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTECP);
+ if (di->regs[BQ27XXX_REG_TTF] != INVALID_REG_ADDR)
+ cache.time_to_full = bq27xxx_battery_read_time(di, BQ27XXX_REG_TTF);
+ cache.charge_full = bq27xxx_battery_read_fcc(di);
+ cache.capacity = bq27xxx_battery_read_soc(di);
+ if (di->regs[BQ27XXX_REG_AE] != INVALID_REG_ADDR)
+ cache.energy = bq27xxx_battery_read_energy(di);
+ cache.health = bq27xxx_battery_read_health(di);
+ }
+ if (di->regs[BQ27XXX_REG_CYCT] != INVALID_REG_ADDR)
+ cache.cycle_count = bq27xxx_battery_read_cyct(di);
+ if (di->regs[BQ27XXX_REG_AP] != INVALID_REG_ADDR)
+ cache.power_avg = bq27xxx_battery_read_pwr_avg(di);
+
+ /* We only have to read charge design full once */
+ if (di->charge_design_full <= 0)
+ di->charge_design_full = bq27xxx_battery_read_dcap(di);
+ }
+
+ if (di->cache.capacity != cache.capacity)
+ power_supply_changed(di->bat);
+
+ if (memcmp(&di->cache, &cache, sizeof(cache)) != 0)
+ di->cache = cache;
+
+ di->last_update = jiffies;
+}
+EXPORT_SYMBOL_GPL(bq27xxx_battery_update);
+
+static void bq27xxx_battery_poll(struct work_struct *work)
+{
+ struct bq27xxx_device_info *di =
+ container_of(work, struct bq27xxx_device_info,
+ work.work);
+
+ bq27xxx_battery_update(di);
+
+ if (poll_interval > 0)
+ schedule_delayed_work(&di->work, poll_interval * HZ);
+}
+
+/*
+ * Return the battery average current in µA
+ * Note that current can be negative signed as well
+ * Or 0 if something fails.
+ */
+static int bq27xxx_battery_current(struct bq27xxx_device_info *di,
+ union power_supply_propval *val)
+{
+ int curr;
+ int flags;
+
+ curr = bq27xxx_read(di, BQ27XXX_REG_AI, false);
+ if (curr < 0) {
+ dev_err(di->dev, "error reading current\n");
+ return curr;
+ }
+
+ if (di->chip == BQ27000 || di->chip == BQ27010) {
+ flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true);
+ if (flags & BQ27000_FLAG_CHGS) {
+ dev_dbg(di->dev, "negative current!\n");
+ curr = -curr;
+ }
+
+ val->intval = curr * BQ27XXX_CURRENT_CONSTANT / BQ27XXX_RS;
+ } else {
+ /* Other gauges return signed value */
+ val->intval = (int)((s16)curr) * 1000;
+ }
+
+ return 0;
+}
+
+static int bq27xxx_battery_status(struct bq27xxx_device_info *di,
+ union power_supply_propval *val)
+{
+ int status;
+
+ if (di->chip == BQ27000 || di->chip == BQ27010) {
+ if (di->cache.flags & BQ27000_FLAG_FC)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else if (di->cache.flags & BQ27000_FLAG_CHGS)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else if (power_supply_am_i_supplied(di->bat))
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ if (di->cache.flags & BQ27XXX_FLAG_FC)
+ status = POWER_SUPPLY_STATUS_FULL;
+ else if (di->cache.flags & BQ27XXX_FLAG_DSC)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ val->intval = status;
+
+ return 0;
+}
+
+static int bq27xxx_battery_capacity_level(struct bq27xxx_device_info *di,
+ union power_supply_propval *val)
+{
+ int level;
+
+ if (di->chip == BQ27000 || di->chip == BQ27010) {
+ if (di->cache.flags & BQ27000_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27000_FLAG_EDV1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27000_FLAG_EDVF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ } else {
+ if (di->cache.flags & BQ27XXX_FLAG_FC)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (di->cache.flags & BQ27XXX_FLAG_SOC1)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else if (di->cache.flags & BQ27XXX_FLAG_SOCF)
+ level = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ level = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ }
+
+ val->intval = level;
+
+ return 0;
+}
+
+/*
+ * Return the battery Voltage in millivolts
+ * Or < 0 if something fails.
+ */
+static int bq27xxx_battery_voltage(struct bq27xxx_device_info *di,
+ union power_supply_propval *val)
+{
+ int volt;
+
+ volt = bq27xxx_read(di, BQ27XXX_REG_VOLT, false);
+ if (volt < 0) {
+ dev_err(di->dev, "error reading voltage\n");
+ return volt;
+ }
+
+ val->intval = volt * 1000;
+
+ return 0;
+}
+
+static int bq27xxx_simple_value(int value,
+ union power_supply_propval *val)
+{
+ if (value < 0)
+ return value;
+
+ val->intval = value;
+
+ return 0;
+}
+
+static int bq27xxx_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct bq27xxx_device_info *di = power_supply_get_drvdata(psy);
+
+ mutex_lock(&di->lock);
+ if (time_is_before_jiffies(di->last_update + 5 * HZ)) {
+ cancel_delayed_work_sync(&di->work);
+ bq27xxx_battery_poll(&di->work.work);
+ }
+ mutex_unlock(&di->lock);
+
+ if (psp != POWER_SUPPLY_PROP_PRESENT && di->cache.flags < 0)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = bq27xxx_battery_status(di, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = bq27xxx_battery_voltage(di, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = di->cache.flags < 0 ? 0 : 1;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = bq27xxx_battery_current(di, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = bq27xxx_simple_value(di->cache.capacity, val);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ ret = bq27xxx_battery_capacity_level(di, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = bq27xxx_simple_value(di->cache.temperature, val);
+ if (ret == 0)
+ val->intval -= 2731; /* convert decidegree k to c */
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ ret = bq27xxx_simple_value(di->cache.time_to_empty, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ ret = bq27xxx_simple_value(di->cache.time_to_empty_avg, val);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ ret = bq27xxx_simple_value(di->cache.time_to_full, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = bq27xxx_simple_value(bq27xxx_battery_read_nac(di), val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = bq27xxx_simple_value(di->cache.charge_full, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = bq27xxx_simple_value(di->charge_design_full, val);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = bq27xxx_simple_value(di->cache.cycle_count, val);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ ret = bq27xxx_simple_value(di->cache.energy, val);
+ break;
+ case POWER_SUPPLY_PROP_POWER_AVG:
+ ret = bq27xxx_simple_value(di->cache.power_avg, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = bq27xxx_simple_value(di->cache.health, val);
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = BQ27XXX_MANUFACTURER;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static void bq27xxx_external_power_changed(struct power_supply *psy)
+{
+ struct bq27xxx_device_info *di = power_supply_get_drvdata(psy);
+
+ cancel_delayed_work_sync(&di->work);
+ schedule_delayed_work(&di->work, 0);
+}
+
+int bq27xxx_battery_setup(struct bq27xxx_device_info *di)
+{
+ struct power_supply_desc *psy_desc;
+ struct power_supply_config psy_cfg = { .drv_data = di, };
+
+ INIT_DELAYED_WORK(&di->work, bq27xxx_battery_poll);
+ mutex_init(&di->lock);
+ di->regs = bq27xxx_regs[di->chip];
+
+ psy_desc = devm_kzalloc(di->dev, sizeof(*psy_desc), GFP_KERNEL);
+ if (!psy_desc)
+ return -ENOMEM;
+
+ psy_desc->name = di->name;
+ psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ psy_desc->properties = bq27xxx_battery_props[di->chip].props;
+ psy_desc->num_properties = bq27xxx_battery_props[di->chip].size;
+ psy_desc->get_property = bq27xxx_battery_get_property;
+ psy_desc->external_power_changed = bq27xxx_external_power_changed;
+
+ di->bat = power_supply_register_no_ws(di->dev, psy_desc, &psy_cfg);
+ if (IS_ERR(di->bat)) {
+ dev_err(di->dev, "failed to register battery\n");
+ return PTR_ERR(di->bat);
+ }
+
+ dev_info(di->dev, "support ver. %s enabled\n", DRIVER_VERSION);
+
+ bq27xxx_battery_update(di);
+
+ mutex_lock(&bq27xxx_list_lock);
+ list_add(&di->list, &bq27xxx_battery_devices);
+ mutex_unlock(&bq27xxx_list_lock);
+
+ return 0;
+}
+EXPORT_SYMBOL_GPL(bq27xxx_battery_setup);
+
+void bq27xxx_battery_teardown(struct bq27xxx_device_info *di)
+{
+ /*
+ * power_supply_unregister call bq27xxx_battery_get_property which
+ * call bq27xxx_battery_poll.
+ * Make sure that bq27xxx_battery_poll will not call
+ * schedule_delayed_work again after unregister (which cause OOPS).
+ */
+ poll_interval = 0;
+
+ cancel_delayed_work_sync(&di->work);
+
+ power_supply_unregister(di->bat);
+
+ mutex_lock(&bq27xxx_list_lock);
+ list_del(&di->list);
+ mutex_unlock(&bq27xxx_list_lock);
+
+ mutex_destroy(&di->lock);
+}
+EXPORT_SYMBOL_GPL(bq27xxx_battery_teardown);
+
+static int bq27xxx_battery_platform_read(struct bq27xxx_device_info *di, u8 reg,
+ bool single)
+{
+ struct device *dev = di->dev;
+ struct bq27xxx_platform_data *pdata = dev->platform_data;
+ unsigned int timeout = 3;
+ int upper, lower;
+ int temp;
+
+ if (!single) {
+ /* Make sure the value has not changed in between reading the
+ * lower and the upper part */
+ upper = pdata->read(dev, reg + 1);
+ do {
+ temp = upper;
+ if (upper < 0)
+ return upper;
+
+ lower = pdata->read(dev, reg);
+ if (lower < 0)
+ return lower;
+
+ upper = pdata->read(dev, reg + 1);
+ } while (temp != upper && --timeout);
+
+ if (timeout == 0)
+ return -EIO;
+
+ return (upper << 8) | lower;
+ }
+
+ return pdata->read(dev, reg);
+}
+
+static int bq27xxx_battery_platform_probe(struct platform_device *pdev)
+{
+ struct bq27xxx_device_info *di;
+ struct bq27xxx_platform_data *pdata = pdev->dev.platform_data;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "no platform_data supplied\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->read) {
+ dev_err(&pdev->dev, "no hdq read callback supplied\n");
+ return -EINVAL;
+ }
+
+ if (!pdata->chip) {
+ dev_err(&pdev->dev, "no device supplied\n");
+ return -EINVAL;
+ }
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, di);
+
+ di->dev = &pdev->dev;
+ di->chip = pdata->chip;
+ di->name = pdata->name ?: dev_name(&pdev->dev);
+ di->bus.read = bq27xxx_battery_platform_read;
+
+ return bq27xxx_battery_setup(di);
+}
+
+static int bq27xxx_battery_platform_remove(struct platform_device *pdev)
+{
+ struct bq27xxx_device_info *di = platform_get_drvdata(pdev);
+
+ bq27xxx_battery_teardown(di);
+
+ return 0;
+}
+
+static const struct platform_device_id bq27xxx_battery_platform_id_table[] = {
+ { "bq27000-battery", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(platform, bq27xxx_battery_platform_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq27xxx_battery_platform_of_match_table[] = {
+ { .compatible = "ti,bq27000" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bq27xxx_battery_platform_of_match_table);
+#endif
+
+static struct platform_driver bq27xxx_battery_platform_driver = {
+ .probe = bq27xxx_battery_platform_probe,
+ .remove = bq27xxx_battery_platform_remove,
+ .driver = {
+ .name = "bq27000-battery",
+ .of_match_table = of_match_ptr(bq27xxx_battery_platform_of_match_table),
+ },
+ .id_table = bq27xxx_battery_platform_id_table,
+};
+module_platform_driver(bq27xxx_battery_platform_driver);
+
+MODULE_ALIAS("platform:bq27000-battery");
+
+MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>");
+MODULE_DESCRIPTION("BQ27xxx battery monitor driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/bq27xxx_battery_i2c.c b/drivers/power/supply/bq27xxx_battery_i2c.c
new file mode 100644
index 0000000..85d4ea2
--- /dev/null
+++ b/drivers/power/supply/bq27xxx_battery_i2c.c
@@ -0,0 +1,205 @@
+/*
+ * BQ27xxx battery monitor I2C driver
+ *
+ * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/
+ * Andrew F. Davis <afd@ti.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.
+ *
+ * This program is distributed "as is" WITHOUT ANY WARRANTY of any
+ * kind, whether express or implied; without even the implied warranty
+ * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <asm/unaligned.h>
+
+#include <linux/power/bq27xxx_battery.h>
+
+static DEFINE_IDR(battery_id);
+static DEFINE_MUTEX(battery_mutex);
+
+static irqreturn_t bq27xxx_battery_irq_handler_thread(int irq, void *data)
+{
+ struct bq27xxx_device_info *di = data;
+
+ bq27xxx_battery_update(di);
+
+ return IRQ_HANDLED;
+}
+
+static int bq27xxx_battery_i2c_read(struct bq27xxx_device_info *di, u8 reg,
+ bool single)
+{
+ struct i2c_client *client = to_i2c_client(di->dev);
+ struct i2c_msg msg[2];
+ unsigned char data[2];
+ int ret;
+
+ if (!client->adapter)
+ return -ENODEV;
+
+ msg[0].addr = client->addr;
+ msg[0].flags = 0;
+ msg[0].buf = &reg;
+ msg[0].len = sizeof(reg);
+ msg[1].addr = client->addr;
+ msg[1].flags = I2C_M_RD;
+ msg[1].buf = data;
+ if (single)
+ msg[1].len = 1;
+ else
+ msg[1].len = 2;
+
+ ret = i2c_transfer(client->adapter, msg, ARRAY_SIZE(msg));
+ if (ret < 0)
+ return ret;
+
+ if (!single)
+ ret = get_unaligned_le16(data);
+ else
+ ret = data[0];
+
+ return ret;
+}
+
+static int bq27xxx_battery_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct bq27xxx_device_info *di;
+ int ret;
+ char *name;
+ int num;
+
+ /* Get new ID for the new battery device */
+ mutex_lock(&battery_mutex);
+ num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL);
+ mutex_unlock(&battery_mutex);
+ if (num < 0)
+ return num;
+
+ name = devm_kasprintf(&client->dev, GFP_KERNEL, "%s-%d", id->name, num);
+ if (!name)
+ goto err_mem;
+
+ di = devm_kzalloc(&client->dev, sizeof(*di), GFP_KERNEL);
+ if (!di)
+ goto err_mem;
+
+ di->id = num;
+ di->dev = &client->dev;
+ di->chip = id->driver_data;
+ di->name = name;
+ di->bus.read = bq27xxx_battery_i2c_read;
+
+ ret = bq27xxx_battery_setup(di);
+ if (ret)
+ goto err_failed;
+
+ /* Schedule a polling after about 1 min */
+ schedule_delayed_work(&di->work, 60 * HZ);
+
+ i2c_set_clientdata(client, di);
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL, bq27xxx_battery_irq_handler_thread,
+ IRQF_ONESHOT,
+ di->name, di);
+ if (ret) {
+ dev_err(&client->dev,
+ "Unable to register IRQ %d error %d\n",
+ client->irq, ret);
+ return ret;
+ }
+ }
+
+ return 0;
+
+err_mem:
+ ret = -ENOMEM;
+
+err_failed:
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, num);
+ mutex_unlock(&battery_mutex);
+
+ return ret;
+}
+
+static int bq27xxx_battery_i2c_remove(struct i2c_client *client)
+{
+ struct bq27xxx_device_info *di = i2c_get_clientdata(client);
+
+ bq27xxx_battery_teardown(di);
+
+ mutex_lock(&battery_mutex);
+ idr_remove(&battery_id, di->id);
+ mutex_unlock(&battery_mutex);
+
+ return 0;
+}
+
+static const struct i2c_device_id bq27xxx_i2c_id_table[] = {
+ { "bq27200", BQ27000 },
+ { "bq27210", BQ27010 },
+ { "bq27500", BQ27500 },
+ { "bq27510", BQ27500 },
+ { "bq27520", BQ27500 },
+ { "bq27530", BQ27530 },
+ { "bq27531", BQ27530 },
+ { "bq27541", BQ27541 },
+ { "bq27542", BQ27541 },
+ { "bq27546", BQ27541 },
+ { "bq27742", BQ27541 },
+ { "bq27545", BQ27545 },
+ { "bq27421", BQ27421 },
+ { "bq27425", BQ27421 },
+ { "bq27441", BQ27421 },
+ { "bq27621", BQ27421 },
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, bq27xxx_i2c_id_table);
+
+#ifdef CONFIG_OF
+static const struct of_device_id bq27xxx_battery_i2c_of_match_table[] = {
+ { .compatible = "ti,bq27200" },
+ { .compatible = "ti,bq27210" },
+ { .compatible = "ti,bq27500" },
+ { .compatible = "ti,bq27510" },
+ { .compatible = "ti,bq27520" },
+ { .compatible = "ti,bq27530" },
+ { .compatible = "ti,bq27531" },
+ { .compatible = "ti,bq27541" },
+ { .compatible = "ti,bq27542" },
+ { .compatible = "ti,bq27546" },
+ { .compatible = "ti,bq27742" },
+ { .compatible = "ti,bq27545" },
+ { .compatible = "ti,bq27421" },
+ { .compatible = "ti,bq27425" },
+ { .compatible = "ti,bq27441" },
+ { .compatible = "ti,bq27621" },
+ {},
+};
+MODULE_DEVICE_TABLE(of, bq27xxx_battery_i2c_of_match_table);
+#endif
+
+static struct i2c_driver bq27xxx_battery_i2c_driver = {
+ .driver = {
+ .name = "bq27xxx-battery",
+ .of_match_table = of_match_ptr(bq27xxx_battery_i2c_of_match_table),
+ },
+ .probe = bq27xxx_battery_i2c_probe,
+ .remove = bq27xxx_battery_i2c_remove,
+ .id_table = bq27xxx_i2c_id_table,
+};
+module_i2c_driver(bq27xxx_battery_i2c_driver);
+
+MODULE_AUTHOR("Andrew F. Davis <afd@ti.com>");
+MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/charger-manager.c b/drivers/power/supply/charger-manager.c
new file mode 100644
index 0000000..e664ca7
--- /dev/null
+++ b/drivers/power/supply/charger-manager.c
@@ -0,0 +1,2074 @@
+/*
+ * Copyright (C) 2011 Samsung Electronics Co., Ltd.
+ * MyungJoo Ham <myungjoo.ham@samsung.com>
+ *
+ * This driver enables to monitor battery health and control charger
+ * during suspend-to-mem.
+ * Charger manager depends on other devices. register this later than
+ * the depending devices.
+ *
+ * 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.
+**/
+
+#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/irq.h>
+#include <linux/interrupt.h>
+#include <linux/rtc.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+#include <linux/platform_device.h>
+#include <linux/power/charger-manager.h>
+#include <linux/regulator/consumer.h>
+#include <linux/sysfs.h>
+#include <linux/of.h>
+#include <linux/thermal.h>
+
+/*
+ * Default termperature threshold for charging.
+ * Every temperature units are in tenth of centigrade.
+ */
+#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50
+#define CM_DEFAULT_CHARGE_TEMP_MAX 500
+
+static const char * const default_event_names[] = {
+ [CM_EVENT_UNKNOWN] = "Unknown",
+ [CM_EVENT_BATT_FULL] = "Battery Full",
+ [CM_EVENT_BATT_IN] = "Battery Inserted",
+ [CM_EVENT_BATT_OUT] = "Battery Pulled Out",
+ [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat",
+ [CM_EVENT_BATT_COLD] = "Battery Cold",
+ [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach",
+ [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop",
+ [CM_EVENT_OTHERS] = "Other battery events"
+};
+
+/*
+ * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for
+ * delayed works so that we can run delayed works with CM_JIFFIES_SMALL
+ * without any delays.
+ */
+#define CM_JIFFIES_SMALL (2)
+
+/* If y is valid (> 0) and smaller than x, do x = y */
+#define CM_MIN_VALID(x, y) x = (((y > 0) && ((x) > (y))) ? (y) : (x))
+
+/*
+ * Regard CM_RTC_SMALL (sec) is small enough to ignore error in invoking
+ * rtc alarm. It should be 2 or larger
+ */
+#define CM_RTC_SMALL (2)
+
+#define UEVENT_BUF_SIZE 32
+
+static LIST_HEAD(cm_list);
+static DEFINE_MUTEX(cm_list_mtx);
+
+/* About in-suspend (suspend-again) monitoring */
+static struct alarm *cm_timer;
+
+static bool cm_suspended;
+static bool cm_timer_set;
+static unsigned long cm_suspend_duration_ms;
+
+/* About normal (not suspended) monitoring */
+static unsigned long polling_jiffy = ULONG_MAX; /* ULONG_MAX: no polling */
+static unsigned long next_polling; /* Next appointed polling time */
+static struct workqueue_struct *cm_wq; /* init at driver add */
+static struct delayed_work cm_monitor_work; /* init at driver add */
+
+/**
+ * is_batt_present - See if the battery presents in place.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_batt_present(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ struct power_supply *psy;
+ bool present = false;
+ int i, ret;
+
+ switch (cm->desc->battery_present) {
+ case CM_BATTERY_PRESENT:
+ present = true;
+ break;
+ case CM_NO_BATTERY:
+ break;
+ case CM_FUEL_GAUGE:
+ psy = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!psy)
+ break;
+
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_PRESENT,
+ &val);
+ if (ret == 0 && val.intval)
+ present = true;
+ power_supply_put(psy);
+ break;
+ case CM_CHARGER_STAT:
+ for (i = 0; cm->desc->psy_charger_stat[i]; i++) {
+ psy = power_supply_get_by_name(
+ cm->desc->psy_charger_stat[i]);
+ if (!psy) {
+ dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+
+ ret = power_supply_get_property(psy,
+ POWER_SUPPLY_PROP_PRESENT, &val);
+ power_supply_put(psy);
+ if (ret == 0 && val.intval) {
+ present = true;
+ break;
+ }
+ }
+ break;
+ }
+
+ return present;
+}
+
+/**
+ * is_ext_pwr_online - See if an external power source is attached to charge
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if at least one of the chargers of the battery has an external
+ * power source attached to charge the battery regardless of whether it is
+ * actually charging or not.
+ */
+static bool is_ext_pwr_online(struct charger_manager *cm)
+{
+ union power_supply_propval val;
+ struct power_supply *psy;
+ bool online = false;
+ int i, ret;
+
+ /* If at least one of them has one, it's yes. */
+ for (i = 0; cm->desc->psy_charger_stat[i]; i++) {
+ psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]);
+ if (!psy) {
+ dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE,
+ &val);
+ power_supply_put(psy);
+ if (ret == 0 && val.intval) {
+ online = true;
+ break;
+ }
+ }
+
+ return online;
+}
+
+/**
+ * get_batt_uV - Get the voltage level of the battery
+ * @cm: the Charger Manager representing the battery.
+ * @uV: the voltage level returned.
+ *
+ * Returns 0 if there is no error.
+ * Returns a negative value on error.
+ */
+static int get_batt_uV(struct charger_manager *cm, int *uV)
+{
+ union power_supply_propval val;
+ struct power_supply *fuel_gauge;
+ int ret;
+
+ fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge)
+ return -ENODEV;
+
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &val);
+ power_supply_put(fuel_gauge);
+ if (ret)
+ return ret;
+
+ *uV = val.intval;
+ return 0;
+}
+
+/**
+ * is_charging - Returns true if the battery is being charged.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_charging(struct charger_manager *cm)
+{
+ int i, ret;
+ bool charging = false;
+ struct power_supply *psy;
+ union power_supply_propval val;
+
+ /* If there is no battery, it cannot be charged */
+ if (!is_batt_present(cm))
+ return false;
+
+ /* If at least one of the charger is charging, return yes */
+ for (i = 0; cm->desc->psy_charger_stat[i]; i++) {
+ /* 1. The charger sholuld not be DISABLED */
+ if (cm->emergency_stop)
+ continue;
+ if (!cm->charger_enabled)
+ continue;
+
+ psy = power_supply_get_by_name(cm->desc->psy_charger_stat[i]);
+ if (!psy) {
+ dev_err(cm->dev, "Cannot find power supply \"%s\"\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+
+ /* 2. The charger should be online (ext-power) */
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE,
+ &val);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read ONLINE value from %s\n",
+ cm->desc->psy_charger_stat[i]);
+ power_supply_put(psy);
+ continue;
+ }
+ if (val.intval == 0) {
+ power_supply_put(psy);
+ continue;
+ }
+
+ /*
+ * 3. The charger should not be FULL, DISCHARGING,
+ * or NOT_CHARGING.
+ */
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS,
+ &val);
+ power_supply_put(psy);
+ if (ret) {
+ dev_warn(cm->dev, "Cannot read STATUS value from %s\n",
+ cm->desc->psy_charger_stat[i]);
+ continue;
+ }
+ if (val.intval == POWER_SUPPLY_STATUS_FULL ||
+ val.intval == POWER_SUPPLY_STATUS_DISCHARGING ||
+ val.intval == POWER_SUPPLY_STATUS_NOT_CHARGING)
+ continue;
+
+ /* Then, this is charging. */
+ charging = true;
+ break;
+ }
+
+ return charging;
+}
+
+/**
+ * is_full_charged - Returns true if the battery is fully charged.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_full_charged(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ union power_supply_propval val;
+ struct power_supply *fuel_gauge;
+ bool is_full = false;
+ int ret = 0;
+ int uV;
+
+ /* If there is no battery, it cannot be charged */
+ if (!is_batt_present(cm))
+ return false;
+
+ fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge)
+ return false;
+
+ if (desc->fullbatt_full_capacity > 0) {
+ val.intval = 0;
+
+ /* Not full if capacity of fuel gauge isn't full */
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_FULL, &val);
+ if (!ret && val.intval > desc->fullbatt_full_capacity) {
+ is_full = true;
+ goto out;
+ }
+ }
+
+ /* Full, if it's over the fullbatt voltage */
+ if (desc->fullbatt_uV > 0) {
+ ret = get_batt_uV(cm, &uV);
+ if (!ret && uV >= desc->fullbatt_uV) {
+ is_full = true;
+ goto out;
+ }
+ }
+
+ /* Full, if the capacity is more than fullbatt_soc */
+ if (desc->fullbatt_soc > 0) {
+ val.intval = 0;
+
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, &val);
+ if (!ret && val.intval >= desc->fullbatt_soc) {
+ is_full = true;
+ goto out;
+ }
+ }
+
+out:
+ power_supply_put(fuel_gauge);
+ return is_full;
+}
+
+/**
+ * is_polling_required - Return true if need to continue polling for this CM.
+ * @cm: the Charger Manager representing the battery.
+ */
+static bool is_polling_required(struct charger_manager *cm)
+{
+ switch (cm->desc->polling_mode) {
+ case CM_POLL_DISABLE:
+ return false;
+ case CM_POLL_ALWAYS:
+ return true;
+ case CM_POLL_EXTERNAL_POWER_ONLY:
+ return is_ext_pwr_online(cm);
+ case CM_POLL_CHARGING_ONLY:
+ return is_charging(cm);
+ default:
+ dev_warn(cm->dev, "Incorrect polling_mode (%d)\n",
+ cm->desc->polling_mode);
+ }
+
+ return false;
+}
+
+/**
+ * try_charger_enable - Enable/Disable chargers altogether
+ * @cm: the Charger Manager representing the battery.
+ * @enable: true: enable / false: disable
+ *
+ * Note that Charger Manager keeps the charger enabled regardless whether
+ * the charger is charging or not (because battery is full or no external
+ * power source exists) except when CM needs to disable chargers forcibly
+ * bacause of emergency causes; when the battery is overheated or too cold.
+ */
+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 */
+ if (enable == cm->charger_enabled)
+ return 0;
+
+ if (enable) {
+ if (cm->emergency_stop)
+ return -EAGAIN;
+
+ /*
+ * Save start time of charging to limit
+ * maximum possible charging time.
+ */
+ cm->charging_start_time = ktime_to_ms(ktime_get());
+ cm->charging_end_time = 0;
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ if (desc->charger_regulators[i].externally_control)
+ continue;
+
+ err = regulator_enable(desc->charger_regulators[i].consumer);
+ if (err < 0) {
+ dev_warn(cm->dev, "Cannot enable %s regulator\n",
+ desc->charger_regulators[i].regulator_name);
+ }
+ }
+ } else {
+ /*
+ * Save end time of charging to maintain fully charged state
+ * of battery after full-batt.
+ */
+ cm->charging_start_time = 0;
+ cm->charging_end_time = ktime_to_ms(ktime_get());
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ if (desc->charger_regulators[i].externally_control)
+ continue;
+
+ err = regulator_disable(desc->charger_regulators[i].consumer);
+ if (err < 0) {
+ dev_warn(cm->dev, "Cannot disable %s regulator\n",
+ desc->charger_regulators[i].regulator_name);
+ }
+ }
+
+ /*
+ * Abnormal battery state - Stop charging forcibly,
+ * even if charger was enabled at the other places
+ */
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ if (regulator_is_enabled(
+ desc->charger_regulators[i].consumer)) {
+ regulator_force_disable(
+ desc->charger_regulators[i].consumer);
+ dev_warn(cm->dev, "Disable regulator(%s) forcibly\n",
+ desc->charger_regulators[i].regulator_name);
+ }
+ }
+ }
+
+ if (!err)
+ cm->charger_enabled = enable;
+
+ return err;
+}
+
+/**
+ * try_charger_restart - Restart charging.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Restart charging by turning off and on the charger.
+ */
+static int try_charger_restart(struct charger_manager *cm)
+{
+ int err;
+
+ if (cm->emergency_stop)
+ return -EAGAIN;
+
+ err = try_charger_enable(cm, false);
+ if (err)
+ return err;
+
+ return try_charger_enable(cm, true);
+}
+
+/**
+ * uevent_notify - Let users know something has changed.
+ * @cm: the Charger Manager representing the battery.
+ * @event: the event string.
+ *
+ * If @event is null, it implies that uevent_notify is called
+ * by resume function. When called in the resume function, cm_suspended
+ * should be already reset to false in order to let uevent_notify
+ * notify the recent event during the suspend to users. While
+ * suspended, uevent_notify does not notify users, but tracks
+ * events so that uevent_notify can notify users later after resumed.
+ */
+static void uevent_notify(struct charger_manager *cm, const char *event)
+{
+ static char env_str[UEVENT_BUF_SIZE + 1] = "";
+ static char env_str_save[UEVENT_BUF_SIZE + 1] = "";
+
+ if (cm_suspended) {
+ /* Nothing in suspended-event buffer */
+ if (env_str_save[0] == 0) {
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return; /* status not changed */
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+ return;
+ }
+
+ if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE))
+ return; /* Duplicated. */
+ strncpy(env_str_save, event, UEVENT_BUF_SIZE);
+ return;
+ }
+
+ if (event == NULL) {
+ /* No messages pending */
+ if (!env_str_save[0])
+ return;
+
+ strncpy(env_str, env_str_save, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+ env_str_save[0] = 0;
+
+ return;
+ }
+
+ /* status not changed */
+ if (!strncmp(env_str, event, UEVENT_BUF_SIZE))
+ return;
+
+ /* save the status and notify the update */
+ strncpy(env_str, event, UEVENT_BUF_SIZE);
+ kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE);
+
+ dev_info(cm->dev, "%s\n", event);
+}
+
+/**
+ * fullbatt_vchk - Check voltage drop some times after "FULL" event.
+ * @work: the work_struct appointing the function
+ *
+ * If a user has designated "fullbatt_vchkdrop_ms/uV" values with
+ * charger_desc, Charger Manager checks voltage drop after the battery
+ * "FULL" event. It checks whether the voltage has dropped more than
+ * fullbatt_vchkdrop_uV by calling this function after fullbatt_vchkrop_ms.
+ */
+static void fullbatt_vchk(struct work_struct *work)
+{
+ struct delayed_work *dwork = to_delayed_work(work);
+ struct charger_manager *cm = container_of(dwork,
+ struct charger_manager, fullbatt_vchk_work);
+ struct charger_desc *desc = cm->desc;
+ int batt_uV, err, diff;
+
+ /* remove the appointment for fullbatt_vchk */
+ cm->fullbatt_vchk_jiffies_at = 0;
+
+ if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
+ return;
+
+ err = get_batt_uV(cm, &batt_uV);
+ if (err) {
+ dev_err(cm->dev, "%s: get_batt_uV error(%d)\n", __func__, err);
+ return;
+ }
+
+ diff = desc->fullbatt_uV - batt_uV;
+ if (diff < 0)
+ return;
+
+ dev_info(cm->dev, "VBATT dropped %duV after full-batt\n", diff);
+
+ if (diff > desc->fullbatt_vchkdrop_uV) {
+ try_charger_restart(cm);
+ uevent_notify(cm, "Recharging");
+ }
+}
+
+/**
+ * check_charging_duration - Monitor charging/discharging duration
+ * @cm: the Charger Manager representing the battery.
+ *
+ * If whole charging duration exceed 'charging_max_duration_ms',
+ * cm stop charging to prevent overcharge/overheat. If discharging
+ * duration exceed 'discharging _max_duration_ms', charger cable is
+ * attached, after full-batt, cm start charging to maintain fully
+ * charged state for battery.
+ */
+static int check_charging_duration(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ u64 curr = ktime_to_ms(ktime_get());
+ u64 duration;
+ int ret = false;
+
+ if (!desc->charging_max_duration_ms &&
+ !desc->discharging_max_duration_ms)
+ return ret;
+
+ if (cm->charger_enabled) {
+ duration = curr - cm->charging_start_time;
+
+ if (duration > desc->charging_max_duration_ms) {
+ dev_info(cm->dev, "Charging duration exceed %ums\n",
+ desc->charging_max_duration_ms);
+ uevent_notify(cm, "Discharging");
+ try_charger_enable(cm, false);
+ ret = true;
+ }
+ } else if (is_ext_pwr_online(cm) && !cm->charger_enabled) {
+ duration = curr - cm->charging_end_time;
+
+ if (duration > desc->charging_max_duration_ms &&
+ is_ext_pwr_online(cm)) {
+ dev_info(cm->dev, "Discharging duration exceed %ums\n",
+ desc->discharging_max_duration_ms);
+ uevent_notify(cm, "Recharging");
+ try_charger_enable(cm, true);
+ ret = true;
+ }
+ }
+
+ return ret;
+}
+
+static int cm_get_battery_temperature_by_psy(struct charger_manager *cm,
+ int *temp)
+{
+ struct power_supply *fuel_gauge;
+ int ret;
+
+ fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge)
+ return -ENODEV;
+
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_TEMP,
+ (union power_supply_propval *)temp);
+ power_supply_put(fuel_gauge);
+
+ return ret;
+}
+
+static int cm_get_battery_temperature(struct charger_manager *cm,
+ int *temp)
+{
+ int ret;
+
+ if (!cm->desc->measure_battery_temp)
+ return -ENODEV;
+
+#ifdef CONFIG_THERMAL
+ if (cm->tzd_batt) {
+ ret = thermal_zone_get_temp(cm->tzd_batt, temp);
+ if (!ret)
+ /* Calibrate temperature unit */
+ *temp /= 100;
+ } else
+#endif
+ {
+ /* if-else continued from CONFIG_THERMAL */
+ ret = cm_get_battery_temperature_by_psy(cm, temp);
+ }
+
+ return ret;
+}
+
+static int cm_check_thermal_status(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ int temp, upper_limit, lower_limit;
+ int ret = 0;
+
+ ret = cm_get_battery_temperature(cm, &temp);
+ if (ret) {
+ /* FIXME:
+ * No information of battery temperature might
+ * occur hazadous result. We have to handle it
+ * depending on battery type.
+ */
+ dev_err(cm->dev, "Failed to get battery temperature\n");
+ return 0;
+ }
+
+ upper_limit = desc->temp_max;
+ lower_limit = desc->temp_min;
+
+ if (cm->emergency_stop) {
+ upper_limit -= desc->temp_diff;
+ lower_limit += desc->temp_diff;
+ }
+
+ if (temp > upper_limit)
+ ret = CM_EVENT_BATT_OVERHEAT;
+ else if (temp < lower_limit)
+ ret = CM_EVENT_BATT_COLD;
+
+ return ret;
+}
+
+/**
+ * _cm_monitor - Monitor the temperature and return true for exceptions.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * Returns true if there is an event to notify for the battery.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool _cm_monitor(struct charger_manager *cm)
+{
+ int temp_alrt;
+
+ temp_alrt = cm_check_thermal_status(cm);
+
+ /* It has been stopped already */
+ if (temp_alrt && cm->emergency_stop)
+ return false;
+
+ /*
+ * Check temperature whether overheat or cold.
+ * If temperature is out of range normal state, stop charging.
+ */
+ if (temp_alrt) {
+ cm->emergency_stop = temp_alrt;
+ if (!try_charger_enable(cm, false))
+ uevent_notify(cm, default_event_names[temp_alrt]);
+
+ /*
+ * Check whole charging duration and discharing duration
+ * after full-batt.
+ */
+ } else if (!cm->emergency_stop && check_charging_duration(cm)) {
+ dev_dbg(cm->dev,
+ "Charging/Discharging duration is out of range\n");
+ /*
+ * Check dropped voltage of battery. If battery voltage is more
+ * dropped than fullbatt_vchkdrop_uV after fully charged state,
+ * charger-manager have to recharge battery.
+ */
+ } else if (!cm->emergency_stop && is_ext_pwr_online(cm) &&
+ !cm->charger_enabled) {
+ fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+
+ /*
+ * Check whether fully charged state to protect overcharge
+ * if charger-manager is charging for battery.
+ */
+ } else if (!cm->emergency_stop && is_full_charged(cm) &&
+ cm->charger_enabled) {
+ dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
+ uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
+
+ try_charger_enable(cm, false);
+
+ fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+ } else {
+ cm->emergency_stop = 0;
+ if (is_ext_pwr_online(cm)) {
+ if (!try_charger_enable(cm, true))
+ uevent_notify(cm, "CHARGING");
+ }
+ }
+
+ return true;
+}
+
+/**
+ * cm_monitor - Monitor every battery.
+ *
+ * Returns true if there is an event to notify from any of the batteries.
+ * (True if the status of "emergency_stop" changes)
+ */
+static bool cm_monitor(void)
+{
+ bool stop = false;
+ struct charger_manager *cm;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (_cm_monitor(cm))
+ stop = true;
+ }
+
+ mutex_unlock(&cm_list_mtx);
+
+ return stop;
+}
+
+/**
+ * _setup_polling - Setup the next instance of polling.
+ * @work: work_struct of the function _setup_polling.
+ */
+static void _setup_polling(struct work_struct *work)
+{
+ unsigned long min = ULONG_MAX;
+ struct charger_manager *cm;
+ bool keep_polling = false;
+ unsigned long _next_polling;
+
+ mutex_lock(&cm_list_mtx);
+
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (is_polling_required(cm) && cm->desc->polling_interval_ms) {
+ keep_polling = true;
+
+ if (min > cm->desc->polling_interval_ms)
+ min = cm->desc->polling_interval_ms;
+ }
+ }
+
+ polling_jiffy = msecs_to_jiffies(min);
+ if (polling_jiffy <= CM_JIFFIES_SMALL)
+ polling_jiffy = CM_JIFFIES_SMALL + 1;
+
+ if (!keep_polling)
+ polling_jiffy = ULONG_MAX;
+ if (polling_jiffy == ULONG_MAX)
+ goto out;
+
+ WARN(cm_wq == NULL, "charger-manager: workqueue not initialized"
+ ". try it later. %s\n", __func__);
+
+ /*
+ * Use mod_delayed_work() iff the next polling interval should
+ * occur before the currently scheduled one. If @cm_monitor_work
+ * isn't active, the end result is the same, so no need to worry
+ * about stale @next_polling.
+ */
+ _next_polling = jiffies + polling_jiffy;
+
+ if (time_before(_next_polling, next_polling)) {
+ mod_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy);
+ next_polling = _next_polling;
+ } else {
+ if (queue_delayed_work(cm_wq, &cm_monitor_work, polling_jiffy))
+ next_polling = _next_polling;
+ }
+out:
+ mutex_unlock(&cm_list_mtx);
+}
+static DECLARE_WORK(setup_polling, _setup_polling);
+
+/**
+ * cm_monitor_poller - The Monitor / Poller.
+ * @work: work_struct of the function cm_monitor_poller
+ *
+ * During non-suspended state, cm_monitor_poller is used to poll and monitor
+ * the batteries.
+ */
+static void cm_monitor_poller(struct work_struct *work)
+{
+ cm_monitor();
+ schedule_work(&setup_polling);
+}
+
+/**
+ * fullbatt_handler - Event handler for CM_EVENT_BATT_FULL
+ * @cm: the Charger Manager representing the battery.
+ */
+static void fullbatt_handler(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+
+ if (!desc->fullbatt_vchkdrop_uV || !desc->fullbatt_vchkdrop_ms)
+ goto out;
+
+ if (cm_suspended)
+ device_set_wakeup_capable(cm->dev, true);
+
+ mod_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
+ msecs_to_jiffies(desc->fullbatt_vchkdrop_ms));
+ cm->fullbatt_vchk_jiffies_at = jiffies + msecs_to_jiffies(
+ desc->fullbatt_vchkdrop_ms);
+
+ if (cm->fullbatt_vchk_jiffies_at == 0)
+ cm->fullbatt_vchk_jiffies_at = 1;
+
+out:
+ dev_info(cm->dev, "EVENT_HANDLE: Battery Fully Charged\n");
+ uevent_notify(cm, default_event_names[CM_EVENT_BATT_FULL]);
+}
+
+/**
+ * battout_handler - Event handler for CM_EVENT_BATT_OUT
+ * @cm: the Charger Manager representing the battery.
+ */
+static void battout_handler(struct charger_manager *cm)
+{
+ if (cm_suspended)
+ device_set_wakeup_capable(cm->dev, true);
+
+ if (!is_batt_present(cm)) {
+ dev_emerg(cm->dev, "Battery Pulled Out!\n");
+ uevent_notify(cm, default_event_names[CM_EVENT_BATT_OUT]);
+ } else {
+ uevent_notify(cm, "Battery Reinserted?");
+ }
+}
+
+/**
+ * misc_event_handler - Handler for other evnets
+ * @cm: the Charger Manager representing the battery.
+ * @type: the Charger Manager representing the battery.
+ */
+static void misc_event_handler(struct charger_manager *cm,
+ enum cm_event_types type)
+{
+ if (cm_suspended)
+ device_set_wakeup_capable(cm->dev, true);
+
+ if (is_polling_required(cm) && cm->desc->polling_interval_ms)
+ schedule_work(&setup_polling);
+ uevent_notify(cm, default_event_names[type]);
+}
+
+static int charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_manager *cm = power_supply_get_drvdata(psy);
+ struct charger_desc *desc = cm->desc;
+ struct power_supply *fuel_gauge = NULL;
+ int ret = 0;
+ int uV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (is_charging(cm))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (is_ext_pwr_online(cm))
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (cm->emergency_stop > 0)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (cm->emergency_stop < 0)
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (is_batt_present(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = get_batt_uV(cm, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge) {
+ ret = -ENODEV;
+ break;
+ }
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_TEMP_AMBIENT:
+ return cm_get_battery_temperature(cm, &val->intval);
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (!is_batt_present(cm)) {
+ /* There is no battery. Assume 100% */
+ val->intval = 100;
+ break;
+ }
+
+ fuel_gauge = power_supply_get_by_name(cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge) {
+ ret = -ENODEV;
+ break;
+ }
+
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CAPACITY, val);
+ if (ret)
+ break;
+
+ if (val->intval > 100) {
+ val->intval = 100;
+ break;
+ }
+ if (val->intval < 0)
+ val->intval = 0;
+
+ /* Do not adjust SOC when charging: voltage is overrated */
+ if (is_charging(cm))
+ break;
+
+ /*
+ * If the capacity value is inconsistent, calibrate it base on
+ * the battery voltage values and the thresholds given as desc
+ */
+ ret = get_batt_uV(cm, &uV);
+ if (ret) {
+ /* Voltage information not available. No calibration */
+ ret = 0;
+ break;
+ }
+
+ if (desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV &&
+ !is_charging(cm)) {
+ val->intval = 100;
+ break;
+ }
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (is_ext_pwr_online(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ if (is_full_charged(cm))
+ val->intval = 1;
+ else
+ val->intval = 0;
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ if (is_charging(cm)) {
+ fuel_gauge = power_supply_get_by_name(
+ cm->desc->psy_fuel_gauge);
+ if (!fuel_gauge) {
+ ret = -ENODEV;
+ break;
+ }
+
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ val);
+ if (ret) {
+ val->intval = 1;
+ ret = 0;
+ } else {
+ /* If CHARGE_NOW is supplied, use it */
+ val->intval = (val->intval > 0) ?
+ val->intval : 1;
+ }
+ } else {
+ val->intval = 0;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ if (fuel_gauge)
+ power_supply_put(fuel_gauge);
+ return ret;
+}
+
+#define NUM_CHARGER_PSY_OPTIONAL (4)
+static enum power_supply_property default_charger_props[] = {
+ /* Guaranteed to provide */
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ /*
+ * Optional properties are:
+ * POWER_SUPPLY_PROP_CHARGE_NOW,
+ * POWER_SUPPLY_PROP_CURRENT_NOW,
+ * POWER_SUPPLY_PROP_TEMP, and
+ * POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ */
+};
+
+static const struct power_supply_desc psy_default = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = default_charger_props,
+ .num_properties = ARRAY_SIZE(default_charger_props),
+ .get_property = charger_get_property,
+ .no_thermal = true,
+};
+
+/**
+ * cm_setup_timer - For in-suspend monitoring setup wakeup alarm
+ * for suspend_again.
+ *
+ * Returns true if the alarm is set for Charger Manager to use.
+ * Returns false if
+ * cm_setup_timer fails to set an alarm,
+ * cm_setup_timer does not need to set an alarm for Charger Manager,
+ * or an alarm previously configured is to be used.
+ */
+static bool cm_setup_timer(void)
+{
+ struct charger_manager *cm;
+ unsigned int wakeup_ms = UINT_MAX;
+ int timer_req = 0;
+
+ if (time_after(next_polling, jiffies))
+ CM_MIN_VALID(wakeup_ms,
+ jiffies_to_msecs(next_polling - jiffies));
+
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ unsigned int fbchk_ms = 0;
+
+ /* fullbatt_vchk is required. setup timer for that */
+ if (cm->fullbatt_vchk_jiffies_at) {
+ fbchk_ms = jiffies_to_msecs(cm->fullbatt_vchk_jiffies_at
+ - jiffies);
+ if (time_is_before_eq_jiffies(
+ cm->fullbatt_vchk_jiffies_at) ||
+ msecs_to_jiffies(fbchk_ms) < CM_JIFFIES_SMALL) {
+ fullbatt_vchk(&cm->fullbatt_vchk_work.work);
+ fbchk_ms = 0;
+ }
+ }
+ CM_MIN_VALID(wakeup_ms, fbchk_ms);
+
+ /* Skip if polling is not required for this CM */
+ if (!is_polling_required(cm) && !cm->emergency_stop)
+ continue;
+ timer_req++;
+ if (cm->desc->polling_interval_ms == 0)
+ continue;
+ CM_MIN_VALID(wakeup_ms, cm->desc->polling_interval_ms);
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ if (timer_req && cm_timer) {
+ ktime_t now, add;
+
+ /*
+ * Set alarm with the polling interval (wakeup_ms)
+ * The alarm time should be NOW + CM_RTC_SMALL or later.
+ */
+ if (wakeup_ms == UINT_MAX ||
+ wakeup_ms < CM_RTC_SMALL * MSEC_PER_SEC)
+ wakeup_ms = 2 * CM_RTC_SMALL * MSEC_PER_SEC;
+
+ pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms);
+
+ now = ktime_get_boottime();
+ add = ktime_set(wakeup_ms / MSEC_PER_SEC,
+ (wakeup_ms % MSEC_PER_SEC) * NSEC_PER_MSEC);
+ alarm_start(cm_timer, ktime_add(now, add));
+
+ cm_suspend_duration_ms = wakeup_ms;
+
+ return true;
+ }
+ return false;
+}
+
+/**
+ * charger_extcon_work - enable/diable charger according to the state
+ * of charger cable
+ *
+ * @work: work_struct of the function charger_extcon_work.
+ */
+static void charger_extcon_work(struct work_struct *work)
+{
+ struct charger_cable *cable =
+ container_of(work, struct charger_cable, wq);
+ int ret;
+
+ if (cable->attached && cable->min_uA != 0 && cable->max_uA != 0) {
+ ret = regulator_set_current_limit(cable->charger->consumer,
+ cable->min_uA, cable->max_uA);
+ if (ret < 0) {
+ pr_err("Cannot set current limit of %s (%s)\n",
+ cable->charger->regulator_name, cable->name);
+ return;
+ }
+
+ pr_info("Set current limit of %s : %duA ~ %duA\n",
+ cable->charger->regulator_name,
+ cable->min_uA, cable->max_uA);
+ }
+
+ try_charger_enable(cable->cm, cable->attached);
+}
+
+/**
+ * charger_extcon_notifier - receive the state of charger cable
+ * when registered cable is attached or detached.
+ *
+ * @self: the notifier block of the charger_extcon_notifier.
+ * @event: the cable state.
+ * @ptr: the data pointer of notifier block.
+ */
+static int charger_extcon_notifier(struct notifier_block *self,
+ unsigned long event, void *ptr)
+{
+ struct charger_cable *cable =
+ container_of(self, struct charger_cable, nb);
+
+ /*
+ * The newly state of charger cable.
+ * If cable is attached, cable->attached is true.
+ */
+ cable->attached = event;
+
+ /*
+ * Setup monitoring to check battery state
+ * when charger cable is attached.
+ */
+ if (cable->attached && is_polling_required(cable->cm)) {
+ cancel_work_sync(&setup_polling);
+ schedule_work(&setup_polling);
+ }
+
+ /*
+ * Setup work for controlling charger(regulator)
+ * according to charger cable.
+ */
+ schedule_work(&cable->wq);
+
+ return NOTIFY_DONE;
+}
+
+/**
+ * charger_extcon_init - register external connector to use it
+ * as the charger cable
+ *
+ * @cm: the Charger Manager representing the battery.
+ * @cable: the Charger cable representing the external connector.
+ */
+static int charger_extcon_init(struct charger_manager *cm,
+ struct charger_cable *cable)
+{
+ int ret = 0;
+
+ /*
+ * Charger manager use Extcon framework to identify
+ * the charger cable among various external connector
+ * cable (e.g., TA, USB, MHL, Dock).
+ */
+ INIT_WORK(&cable->wq, charger_extcon_work);
+ cable->nb.notifier_call = charger_extcon_notifier;
+ ret = extcon_register_interest(&cable->extcon_dev,
+ cable->extcon_name, cable->name, &cable->nb);
+ if (ret < 0) {
+ pr_info("Cannot register extcon_dev for %s(cable: %s)\n",
+ cable->extcon_name, cable->name);
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+/**
+ * charger_manager_register_extcon - Register extcon device to recevie state
+ * of charger cable.
+ * @cm: the Charger Manager representing the battery.
+ *
+ * This function support EXTCON(External Connector) subsystem to detect the
+ * state of charger cables for enabling or disabling charger(regulator) and
+ * select the charger cable for charging among a number of external cable
+ * according to policy of H/W board.
+ */
+static int charger_manager_register_extcon(struct charger_manager *cm)
+{
+ struct charger_desc *desc = cm->desc;
+ struct charger_regulator *charger;
+ int ret = 0;
+ int i;
+ int j;
+
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ charger = &desc->charger_regulators[i];
+
+ charger->consumer = regulator_get(cm->dev,
+ charger->regulator_name);
+ if (IS_ERR(charger->consumer)) {
+ dev_err(cm->dev, "Cannot find charger(%s)\n",
+ charger->regulator_name);
+ return PTR_ERR(charger->consumer);
+ }
+ charger->cm = cm;
+
+ for (j = 0; j < charger->num_cables; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+
+ ret = charger_extcon_init(cm, cable);
+ if (ret < 0) {
+ dev_err(cm->dev, "Cannot initialize charger(%s)\n",
+ charger->regulator_name);
+ goto err;
+ }
+ cable->charger = charger;
+ cable->cm = cm;
+ }
+ }
+
+err:
+ return ret;
+}
+
+/* help function of sysfs node to control charger(regulator) */
+static ssize_t charger_name_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct charger_regulator *charger
+ = container_of(attr, struct charger_regulator, attr_name);
+
+ return sprintf(buf, "%s\n", charger->regulator_name);
+}
+
+static ssize_t charger_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct charger_regulator *charger
+ = container_of(attr, struct charger_regulator, attr_state);
+ int state = 0;
+
+ if (!charger->externally_control)
+ state = regulator_is_enabled(charger->consumer);
+
+ return sprintf(buf, "%s\n", state ? "enabled" : "disabled");
+}
+
+static ssize_t charger_externally_control_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct charger_regulator *charger = container_of(attr,
+ struct charger_regulator, attr_externally_control);
+
+ return sprintf(buf, "%d\n", charger->externally_control);
+}
+
+static ssize_t charger_externally_control_store(struct device *dev,
+ struct device_attribute *attr, const char *buf,
+ size_t count)
+{
+ struct charger_regulator *charger
+ = container_of(attr, struct charger_regulator,
+ attr_externally_control);
+ struct charger_manager *cm = charger->cm;
+ struct charger_desc *desc = cm->desc;
+ int i;
+ int ret;
+ int externally_control;
+ int chargers_externally_control = 1;
+
+ ret = sscanf(buf, "%d", &externally_control);
+ if (ret == 0) {
+ ret = -EINVAL;
+ return ret;
+ }
+
+ if (!externally_control) {
+ charger->externally_control = 0;
+ return count;
+ }
+
+ for (i = 0; i < desc->num_charger_regulators; i++) {
+ if (&desc->charger_regulators[i] != charger &&
+ !desc->charger_regulators[i].externally_control) {
+ /*
+ * At least, one charger is controlled by
+ * charger-manager
+ */
+ chargers_externally_control = 0;
+ break;
+ }
+ }
+
+ if (!chargers_externally_control) {
+ if (cm->charger_enabled) {
+ try_charger_enable(charger->cm, false);
+ charger->externally_control = externally_control;
+ try_charger_enable(charger->cm, true);
+ } else {
+ charger->externally_control = externally_control;
+ }
+ } else {
+ dev_warn(cm->dev,
+ "'%s' regulator should be controlled in charger-manager because charger-manager must need at least one charger for charging\n",
+ charger->regulator_name);
+ }
+
+ return count;
+}
+
+/**
+ * charger_manager_register_sysfs - Register sysfs entry for each charger
+ * @cm: the Charger Manager representing the battery.
+ *
+ * This function add sysfs entry for charger(regulator) to control charger from
+ * user-space. If some development board use one more chargers for charging
+ * but only need one charger on specific case which is dependent on user
+ * scenario or hardware restrictions, the user enter 1 or 0(zero) to '/sys/
+ * class/power_supply/battery/charger.[index]/externally_control'. For example,
+ * if user enter 1 to 'sys/class/power_supply/battery/charger.[index]/
+ * 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)
+{
+ struct charger_desc *desc = cm->desc;
+ struct charger_regulator *charger;
+ int chargers_externally_control = 1;
+ char buf[11];
+ char *str;
+ int ret = 0;
+ 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,
+ sizeof(char) * (strlen(buf) + 1), GFP_KERNEL);
+ if (!str) {
+ ret = -ENOMEM;
+ goto err;
+ }
+ 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;
+
+ sysfs_attr_init(&charger->attr_name.attr);
+ charger->attr_name.attr.name = "name";
+ charger->attr_name.attr.mode = 0444;
+ charger->attr_name.show = charger_name_show;
+
+ sysfs_attr_init(&charger->attr_state.attr);
+ charger->attr_state.attr.name = "state";
+ charger->attr_state.attr.mode = 0444;
+ charger->attr_state.show = charger_state_show;
+
+ sysfs_attr_init(&charger->attr_externally_control.attr);
+ charger->attr_externally_control.attr.name
+ = "externally_control";
+ charger->attr_externally_control.attr.mode = 0644;
+ charger->attr_externally_control.show
+ = charger_externally_control_show;
+ charger->attr_externally_control.store
+ = charger_externally_control_store;
+
+ if (!desc->charger_regulators[i].externally_control ||
+ !chargers_externally_control)
+ chargers_externally_control = 0;
+
+ 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);
+ ret = -EINVAL;
+ goto err;
+ }
+ }
+
+ if (chargers_externally_control) {
+ dev_err(cm->dev, "Cannot register regulator because charger-manager must need at least one charger for charging battery\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+err:
+ return ret;
+}
+
+static int cm_init_thermal_data(struct charger_manager *cm,
+ struct power_supply *fuel_gauge)
+{
+ struct charger_desc *desc = cm->desc;
+ union power_supply_propval val;
+ int ret;
+
+ /* Verify whether fuel gauge provides battery temperature */
+ ret = power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_TEMP, &val);
+
+ if (!ret) {
+ cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ POWER_SUPPLY_PROP_TEMP;
+ cm->charger_psy_desc.num_properties++;
+ cm->desc->measure_battery_temp = true;
+ }
+#ifdef CONFIG_THERMAL
+ if (ret && desc->thermal_zone) {
+ cm->tzd_batt =
+ thermal_zone_get_zone_by_name(desc->thermal_zone);
+ if (IS_ERR(cm->tzd_batt))
+ return PTR_ERR(cm->tzd_batt);
+
+ /* Use external thermometer */
+ cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ POWER_SUPPLY_PROP_TEMP_AMBIENT;
+ cm->charger_psy_desc.num_properties++;
+ cm->desc->measure_battery_temp = true;
+ ret = 0;
+ }
+#endif
+ if (cm->desc->measure_battery_temp) {
+ /* NOTICE : Default allowable minimum charge temperature is 0 */
+ if (!desc->temp_max)
+ desc->temp_max = CM_DEFAULT_CHARGE_TEMP_MAX;
+ if (!desc->temp_diff)
+ desc->temp_diff = CM_DEFAULT_RECHARGE_TEMP_DIFF;
+ }
+
+ return ret;
+}
+
+static const struct of_device_id charger_manager_match[] = {
+ {
+ .compatible = "charger-manager",
+ },
+ {},
+};
+
+static struct charger_desc *of_cm_parse_desc(struct device *dev)
+{
+ struct charger_desc *desc;
+ struct device_node *np = dev->of_node;
+ u32 poll_mode = CM_POLL_DISABLE;
+ u32 battery_stat = CM_NO_BATTERY;
+ int num_chgs = 0;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return ERR_PTR(-ENOMEM);
+
+ of_property_read_string(np, "cm-name", &desc->psy_name);
+
+ of_property_read_u32(np, "cm-poll-mode", &poll_mode);
+ desc->polling_mode = poll_mode;
+
+ of_property_read_u32(np, "cm-poll-interval",
+ &desc->polling_interval_ms);
+
+ of_property_read_u32(np, "cm-fullbatt-vchkdrop-ms",
+ &desc->fullbatt_vchkdrop_ms);
+ of_property_read_u32(np, "cm-fullbatt-vchkdrop-volt",
+ &desc->fullbatt_vchkdrop_uV);
+ of_property_read_u32(np, "cm-fullbatt-voltage", &desc->fullbatt_uV);
+ of_property_read_u32(np, "cm-fullbatt-soc", &desc->fullbatt_soc);
+ of_property_read_u32(np, "cm-fullbatt-capacity",
+ &desc->fullbatt_full_capacity);
+
+ of_property_read_u32(np, "cm-battery-stat", &battery_stat);
+ desc->battery_present = battery_stat;
+
+ /* chargers */
+ of_property_read_u32(np, "cm-num-chargers", &num_chgs);
+ if (num_chgs) {
+ /* Allocate empty bin at the tail of array */
+ desc->psy_charger_stat = devm_kzalloc(dev, sizeof(char *)
+ * (num_chgs + 1), 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 {
+ return ERR_PTR(-ENOMEM);
+ }
+ }
+
+ of_property_read_string(np, "cm-fuel-gauge", &desc->psy_fuel_gauge);
+
+ of_property_read_string(np, "cm-thermal-zone", &desc->thermal_zone);
+
+ of_property_read_u32(np, "cm-battery-cold", &desc->temp_min);
+ if (of_get_property(np, "cm-battery-cold-in-minus", NULL))
+ desc->temp_min *= -1;
+ of_property_read_u32(np, "cm-battery-hot", &desc->temp_max);
+ of_property_read_u32(np, "cm-battery-temp-diff", &desc->temp_diff);
+
+ of_property_read_u32(np, "cm-charging-max",
+ &desc->charging_max_duration_ms);
+ of_property_read_u32(np, "cm-discharging-max",
+ &desc->discharging_max_duration_ms);
+
+ /* battery charger regualtors */
+ desc->num_charger_regulators = of_get_child_count(np);
+ if (desc->num_charger_regulators) {
+ struct charger_regulator *chg_regs;
+ struct device_node *child;
+
+ chg_regs = devm_kzalloc(dev, sizeof(*chg_regs)
+ * desc->num_charger_regulators,
+ GFP_KERNEL);
+ if (!chg_regs)
+ return ERR_PTR(-ENOMEM);
+
+ desc->charger_regulators = chg_regs;
+
+ for_each_child_of_node(np, child) {
+ struct charger_cable *cables;
+ struct device_node *_child;
+
+ of_property_read_string(child, "cm-regulator-name",
+ &chg_regs->regulator_name);
+
+ /* charger cables */
+ chg_regs->num_cables = of_get_child_count(child);
+ if (chg_regs->num_cables) {
+ cables = devm_kzalloc(dev, sizeof(*cables)
+ * chg_regs->num_cables,
+ GFP_KERNEL);
+ if (!cables) {
+ of_node_put(child);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ chg_regs->cables = cables;
+
+ for_each_child_of_node(child, _child) {
+ of_property_read_string(_child,
+ "cm-cable-name", &cables->name);
+ of_property_read_string(_child,
+ "cm-cable-extcon",
+ &cables->extcon_name);
+ of_property_read_u32(_child,
+ "cm-cable-min",
+ &cables->min_uA);
+ of_property_read_u32(_child,
+ "cm-cable-max",
+ &cables->max_uA);
+ cables++;
+ }
+ }
+ chg_regs++;
+ }
+ }
+ return desc;
+}
+
+static inline struct charger_desc *cm_get_drv_data(struct platform_device *pdev)
+{
+ if (pdev->dev.of_node)
+ return of_cm_parse_desc(&pdev->dev);
+ return dev_get_platdata(&pdev->dev);
+}
+
+static enum alarmtimer_restart cm_timer_func(struct alarm *alarm, ktime_t now)
+{
+ cm_timer_set = false;
+ return ALARMTIMER_NORESTART;
+}
+
+static int charger_manager_probe(struct platform_device *pdev)
+{
+ struct charger_desc *desc = cm_get_drv_data(pdev);
+ struct charger_manager *cm;
+ int ret = 0, i = 0;
+ int j = 0;
+ union power_supply_propval val;
+ struct power_supply *fuel_gauge;
+ struct power_supply_config psy_cfg = {};
+
+ if (IS_ERR(desc)) {
+ dev_err(&pdev->dev, "No platform data (desc) found\n");
+ return -ENODEV;
+ }
+
+ cm = devm_kzalloc(&pdev->dev,
+ sizeof(struct charger_manager), GFP_KERNEL);
+ if (!cm)
+ return -ENOMEM;
+
+ /* Basic Values. Unspecified are Null or 0 */
+ cm->dev = &pdev->dev;
+ cm->desc = desc;
+ psy_cfg.drv_data = cm;
+
+ /* Initialize alarm timer */
+ if (alarmtimer_get_rtcdev()) {
+ cm_timer = devm_kzalloc(cm->dev, sizeof(*cm_timer), GFP_KERNEL);
+ alarm_init(cm_timer, ALARM_BOOTTIME, cm_timer_func);
+ }
+
+ /*
+ * The following two do not need to be errors.
+ * Users may intentionally ignore those two features.
+ */
+ if (desc->fullbatt_uV == 0) {
+ dev_info(&pdev->dev, "Ignoring full-battery voltage threshold as it is not supplied\n");
+ }
+ if (!desc->fullbatt_vchkdrop_ms || !desc->fullbatt_vchkdrop_uV) {
+ dev_info(&pdev->dev, "Disabling full-battery voltage drop checking mechanism as it is not supplied\n");
+ desc->fullbatt_vchkdrop_ms = 0;
+ desc->fullbatt_vchkdrop_uV = 0;
+ }
+ if (desc->fullbatt_soc == 0) {
+ dev_info(&pdev->dev, "Ignoring full-battery soc(state of charge) threshold as it is not supplied\n");
+ }
+ if (desc->fullbatt_full_capacity == 0) {
+ dev_info(&pdev->dev, "Ignoring full-battery full capacity threshold as it is not supplied\n");
+ }
+
+ if (!desc->charger_regulators || desc->num_charger_regulators < 1) {
+ dev_err(&pdev->dev, "charger_regulators undefined\n");
+ return -EINVAL;
+ }
+
+ if (!desc->psy_charger_stat || !desc->psy_charger_stat[0]) {
+ dev_err(&pdev->dev, "No power supply defined\n");
+ return -EINVAL;
+ }
+
+ if (!desc->psy_fuel_gauge) {
+ dev_err(&pdev->dev, "No fuel gauge power supply defined\n");
+ 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;
+
+ psy = power_supply_get_by_name(desc->psy_charger_stat[i]);
+ if (!psy) {
+ dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+ desc->psy_charger_stat[i]);
+ return -ENODEV;
+ }
+ power_supply_put(psy);
+ }
+
+ if (desc->polling_interval_ms == 0 ||
+ msecs_to_jiffies(desc->polling_interval_ms) <= CM_JIFFIES_SMALL) {
+ dev_err(&pdev->dev, "polling_interval_ms is too small\n");
+ return -EINVAL;
+ }
+
+ if (!desc->charging_max_duration_ms ||
+ !desc->discharging_max_duration_ms) {
+ dev_info(&pdev->dev, "Cannot limit charging duration checking mechanism to prevent overcharge/overheat and control discharging duration\n");
+ desc->charging_max_duration_ms = 0;
+ desc->discharging_max_duration_ms = 0;
+ }
+
+ platform_set_drvdata(pdev, cm);
+
+ memcpy(&cm->charger_psy_desc, &psy_default, sizeof(psy_default));
+
+ if (!desc->psy_name)
+ strncpy(cm->psy_name_buf, psy_default.name, PSY_NAME_MAX);
+ else
+ strncpy(cm->psy_name_buf, desc->psy_name, PSY_NAME_MAX);
+ cm->charger_psy_desc.name = cm->psy_name_buf;
+
+ /* Allocate for psy properties because they may vary */
+ cm->charger_psy_desc.properties = devm_kzalloc(&pdev->dev,
+ sizeof(enum power_supply_property)
+ * (ARRAY_SIZE(default_charger_props) +
+ NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL);
+ if (!cm->charger_psy_desc.properties)
+ return -ENOMEM;
+
+ memcpy(cm->charger_psy_desc.properties, default_charger_props,
+ sizeof(enum power_supply_property) *
+ ARRAY_SIZE(default_charger_props));
+ cm->charger_psy_desc.num_properties = psy_default.num_properties;
+
+ /* Find which optional psy-properties are available */
+ fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge);
+ if (!fuel_gauge) {
+ dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n",
+ desc->psy_fuel_gauge);
+ return -ENODEV;
+ }
+ if (!power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CHARGE_NOW, &val)) {
+ cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ POWER_SUPPLY_PROP_CHARGE_NOW;
+ cm->charger_psy_desc.num_properties++;
+ }
+ if (!power_supply_get_property(fuel_gauge,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ &val)) {
+ cm->charger_psy_desc.properties[cm->charger_psy_desc.num_properties] =
+ POWER_SUPPLY_PROP_CURRENT_NOW;
+ cm->charger_psy_desc.num_properties++;
+ }
+
+ ret = cm_init_thermal_data(cm, fuel_gauge);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to initialize thermal data\n");
+ cm->desc->measure_battery_temp = false;
+ }
+ power_supply_put(fuel_gauge);
+
+ INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk);
+
+ cm->charger_psy = power_supply_register(&pdev->dev,
+ &cm->charger_psy_desc,
+ &psy_cfg);
+ if (IS_ERR(cm->charger_psy)) {
+ dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n",
+ cm->charger_psy_desc.name);
+ return PTR_ERR(cm->charger_psy);
+ }
+
+ /* Register extcon device for charger cable */
+ ret = charger_manager_register_extcon(cm);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot initialize extcon device\n");
+ 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);
+ mutex_unlock(&cm_list_mtx);
+
+ /*
+ * Charger-manager is capable of waking up the systme from sleep
+ * when event is happend through cm_notify_event()
+ */
+ device_init_wakeup(&pdev->dev, true);
+ device_set_wakeup_capable(&pdev->dev, false);
+
+ /*
+ * Charger-manager have to check the charging state right after
+ * tialization of charger-manager and then update current charging
+ * state.
+ */
+ cm_monitor();
+
+ schedule_work(&setup_polling);
+
+ 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;
+
+ charger = &desc->charger_regulators[i];
+ for (j = 0; j < charger->num_cables; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+ /* Remove notifier block if only edev exists */
+ if (cable->extcon_dev.edev)
+ extcon_unregister_interest(&cable->extcon_dev);
+ }
+
+ regulator_put(desc->charger_regulators[i].consumer);
+ }
+
+ power_supply_unregister(cm->charger_psy);
+
+ return ret;
+}
+
+static int charger_manager_remove(struct platform_device *pdev)
+{
+ struct charger_manager *cm = platform_get_drvdata(pdev);
+ struct charger_desc *desc = cm->desc;
+ int i = 0;
+ int j = 0;
+
+ /* Remove from the list */
+ mutex_lock(&cm_list_mtx);
+ list_del(&cm->entry);
+ mutex_unlock(&cm_list_mtx);
+
+ cancel_work_sync(&setup_polling);
+ cancel_delayed_work_sync(&cm_monitor_work);
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++) {
+ struct charger_regulator *charger
+ = &desc->charger_regulators[i];
+ for (j = 0 ; j < charger->num_cables ; j++) {
+ struct charger_cable *cable = &charger->cables[j];
+ extcon_unregister_interest(&cable->extcon_dev);
+ }
+ }
+
+ for (i = 0 ; i < desc->num_charger_regulators ; i++)
+ regulator_put(desc->charger_regulators[i].consumer);
+
+ power_supply_unregister(cm->charger_psy);
+
+ try_charger_enable(cm, false);
+
+ return 0;
+}
+
+static const struct platform_device_id charger_manager_id[] = {
+ { "charger-manager", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(platform, charger_manager_id);
+
+static int cm_suspend_noirq(struct device *dev)
+{
+ int ret = 0;
+
+ if (device_may_wakeup(dev)) {
+ device_set_wakeup_capable(dev, false);
+ ret = -EAGAIN;
+ }
+
+ return ret;
+}
+
+static bool cm_need_to_awake(void)
+{
+ struct charger_manager *cm;
+
+ if (cm_timer)
+ return false;
+
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (is_charging(cm)) {
+ mutex_unlock(&cm_list_mtx);
+ return true;
+ }
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ return false;
+}
+
+static int cm_suspend_prepare(struct device *dev)
+{
+ struct charger_manager *cm = dev_get_drvdata(dev);
+
+ if (cm_need_to_awake())
+ return -EBUSY;
+
+ if (!cm_suspended)
+ cm_suspended = true;
+
+ cm_timer_set = cm_setup_timer();
+
+ if (cm_timer_set) {
+ cancel_work_sync(&setup_polling);
+ cancel_delayed_work_sync(&cm_monitor_work);
+ cancel_delayed_work(&cm->fullbatt_vchk_work);
+ }
+
+ return 0;
+}
+
+static void cm_suspend_complete(struct device *dev)
+{
+ struct charger_manager *cm = dev_get_drvdata(dev);
+
+ if (cm_suspended)
+ cm_suspended = false;
+
+ if (cm_timer_set) {
+ ktime_t remain;
+
+ alarm_cancel(cm_timer);
+ cm_timer_set = false;
+ remain = alarm_expires_remaining(cm_timer);
+ cm_suspend_duration_ms -= ktime_to_ms(remain);
+ schedule_work(&setup_polling);
+ }
+
+ _cm_monitor(cm);
+
+ /* Re-enqueue delayed work (fullbatt_vchk_work) */
+ if (cm->fullbatt_vchk_jiffies_at) {
+ unsigned long delay = 0;
+ unsigned long now = jiffies + CM_JIFFIES_SMALL;
+
+ if (time_after_eq(now, cm->fullbatt_vchk_jiffies_at)) {
+ delay = (unsigned long)((long)now
+ - (long)(cm->fullbatt_vchk_jiffies_at));
+ delay = jiffies_to_msecs(delay);
+ } else {
+ delay = 0;
+ }
+
+ /*
+ * Account for cm_suspend_duration_ms with assuming that
+ * timer stops in suspend.
+ */
+ if (delay > cm_suspend_duration_ms)
+ delay -= cm_suspend_duration_ms;
+ else
+ delay = 0;
+
+ queue_delayed_work(cm_wq, &cm->fullbatt_vchk_work,
+ msecs_to_jiffies(delay));
+ }
+ device_set_wakeup_capable(cm->dev, false);
+}
+
+static const struct dev_pm_ops charger_manager_pm = {
+ .prepare = cm_suspend_prepare,
+ .suspend_noirq = cm_suspend_noirq,
+ .complete = cm_suspend_complete,
+};
+
+static struct platform_driver charger_manager_driver = {
+ .driver = {
+ .name = "charger-manager",
+ .pm = &charger_manager_pm,
+ .of_match_table = charger_manager_match,
+ },
+ .probe = charger_manager_probe,
+ .remove = charger_manager_remove,
+ .id_table = charger_manager_id,
+};
+
+static int __init charger_manager_init(void)
+{
+ cm_wq = create_freezable_workqueue("charger_manager");
+ INIT_DELAYED_WORK(&cm_monitor_work, cm_monitor_poller);
+
+ return platform_driver_register(&charger_manager_driver);
+}
+late_initcall(charger_manager_init);
+
+static void __exit charger_manager_cleanup(void)
+{
+ destroy_workqueue(cm_wq);
+ cm_wq = NULL;
+
+ platform_driver_unregister(&charger_manager_driver);
+}
+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
+ */
+void cm_notify_event(struct power_supply *psy, enum cm_event_types type,
+ char *msg)
+{
+ struct charger_manager *cm;
+ bool found_power_supply = false;
+
+ if (psy == NULL)
+ return;
+
+ mutex_lock(&cm_list_mtx);
+ list_for_each_entry(cm, &cm_list, entry) {
+ if (match_string(cm->desc->psy_charger_stat, -1,
+ psy->desc->name) >= 0) {
+ found_power_supply = true;
+ break;
+ }
+ }
+ mutex_unlock(&cm_list_mtx);
+
+ if (!found_power_supply)
+ return;
+
+ switch (type) {
+ case CM_EVENT_BATT_FULL:
+ fullbatt_handler(cm);
+ break;
+ case CM_EVENT_BATT_OUT:
+ battout_handler(cm);
+ break;
+ case CM_EVENT_BATT_IN:
+ case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP:
+ misc_event_handler(cm, type);
+ break;
+ case CM_EVENT_UNKNOWN:
+ case CM_EVENT_OTHERS:
+ uevent_notify(cm, msg ? msg : default_event_names[type]);
+ break;
+ default:
+ dev_err(cm->dev, "%s: type not specified\n", __func__);
+ break;
+ }
+}
+EXPORT_SYMBOL_GPL(cm_notify_event);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("Charger Manager");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/collie_battery.c b/drivers/power/supply/collie_battery.c
new file mode 100644
index 0000000..3a0bc60
--- /dev/null
+++ b/drivers/power/supply/collie_battery.c
@@ -0,0 +1,422 @@
+/*
+ * Battery and Power Management code for the Sharp SL-5x00
+ *
+ * Copyright (C) 2009 Thomas Kunze
+ *
+ * based on tosa_battery.c
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/mfd/ucb1x00.h>
+
+#include <asm/mach/sharpsl_param.h>
+#include <asm/mach-types.h>
+#include <mach/collie.h>
+
+static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
+static struct work_struct bat_work;
+static struct ucb1x00 *ucb;
+
+struct collie_bat {
+ int status;
+ struct power_supply *psy;
+ int full_chrg;
+
+ struct mutex work_lock; /* protects data */
+
+ bool (*is_present)(struct collie_bat *bat);
+ int gpio_full;
+ int gpio_charge_on;
+
+ int technology;
+
+ int gpio_bat;
+ int adc_bat;
+ int adc_bat_divider;
+ int bat_max;
+ int bat_min;
+
+ int gpio_temp;
+ int adc_temp;
+ int adc_temp_divider;
+};
+
+static struct collie_bat collie_bat_main;
+
+static unsigned long collie_read_bat(struct collie_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_bat < 0 || bat->adc_bat < 0)
+ return 0;
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_bat, 1);
+ msleep(5);
+ ucb1x00_adc_enable(ucb);
+ value = ucb1x00_adc_read(ucb, bat->adc_bat, UCB_SYNC);
+ ucb1x00_adc_disable(ucb);
+ gpio_set_value(bat->gpio_bat, 0);
+ mutex_unlock(&bat_lock);
+ value = value * 1000000 / bat->adc_bat_divider;
+
+ return value;
+}
+
+static unsigned long collie_read_temp(struct collie_bat *bat)
+{
+ unsigned long value = 0;
+ if (bat->gpio_temp < 0 || bat->adc_temp < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_temp, 1);
+ msleep(5);
+ ucb1x00_adc_enable(ucb);
+ value = ucb1x00_adc_read(ucb, bat->adc_temp, UCB_SYNC);
+ ucb1x00_adc_disable(ucb);
+ gpio_set_value(bat->gpio_temp, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 10000 / bat->adc_temp_divider;
+
+ return value;
+}
+
+static int collie_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct collie_bat *bat = power_supply_get_drvdata(psy);
+
+ if (bat->is_present && !bat->is_present(bat)
+ && psp != POWER_SUPPLY_PROP_PRESENT) {
+ return -ENODEV;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = collie_read_bat(bat);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (bat->full_chrg == -1)
+ val->intval = bat->bat_max;
+ else
+ val->intval = bat->full_chrg;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->bat_max;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = bat->bat_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = collie_read_temp(bat);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = bat->is_present ? bat->is_present(bat) : 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static void collie_bat_external_power_changed(struct power_supply *psy)
+{
+ schedule_work(&bat_work);
+}
+
+static irqreturn_t collie_bat_gpio_isr(int irq, void *data)
+{
+ pr_info("collie_bat_gpio irq\n");
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+static void collie_bat_update(struct collie_bat *bat)
+{
+ int old;
+ struct power_supply *psy = bat->psy;
+
+ mutex_lock(&bat->work_lock);
+
+ old = bat->status;
+
+ if (bat->is_present && !bat->is_present(bat)) {
+ printk(KERN_NOTICE "%s not present\n", psy->desc->name);
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->full_chrg = -1;
+ } else if (power_supply_am_i_supplied(psy)) {
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ gpio_set_value(bat->gpio_charge_on, 1);
+ mdelay(15);
+ }
+
+ if (gpio_get_value(bat->gpio_full)) {
+ if (old == POWER_SUPPLY_STATUS_CHARGING ||
+ bat->full_chrg == -1)
+ bat->full_chrg = collie_read_bat(bat);
+
+ gpio_set_value(bat->gpio_charge_on, 0);
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ gpio_set_value(bat->gpio_charge_on, 1);
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else {
+ gpio_set_value(bat->gpio_charge_on, 0);
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (old != bat->status)
+ power_supply_changed(psy);
+
+ mutex_unlock(&bat->work_lock);
+}
+
+static void collie_bat_work(struct work_struct *work)
+{
+ collie_bat_update(&collie_bat_main);
+}
+
+
+static enum power_supply_property collie_bat_main_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static enum power_supply_property collie_bat_bu_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static const struct power_supply_desc collie_bat_main_desc = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = collie_bat_main_props,
+ .num_properties = ARRAY_SIZE(collie_bat_main_props),
+ .get_property = collie_bat_get_property,
+ .external_power_changed = collie_bat_external_power_changed,
+ .use_for_apm = 1,
+};
+
+static struct collie_bat collie_bat_main = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = NULL,
+
+ .gpio_full = COLLIE_GPIO_CO,
+ .gpio_charge_on = COLLIE_GPIO_CHARGE_ON,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = COLLIE_GPIO_MBAT_ON,
+ .adc_bat = UCB_ADC_INP_AD1,
+ .adc_bat_divider = 155,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = COLLIE_GPIO_TMP_ON,
+ .adc_temp = UCB_ADC_INP_AD0,
+ .adc_temp_divider = 10000,
+};
+
+static const struct power_supply_desc collie_bat_bu_desc = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = collie_bat_bu_props,
+ .num_properties = ARRAY_SIZE(collie_bat_bu_props),
+ .get_property = collie_bat_get_property,
+ .external_power_changed = collie_bat_external_power_changed,
+};
+
+static struct collie_bat collie_bat_bu = {
+ .status = POWER_SUPPLY_STATUS_UNKNOWN,
+ .full_chrg = -1,
+ .psy = NULL,
+
+ .gpio_full = -1,
+ .gpio_charge_on = -1,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
+
+ .gpio_bat = COLLIE_GPIO_BBAT_ON,
+ .adc_bat = UCB_ADC_INP_AD1,
+ .adc_bat_divider = 155,
+ .bat_max = 3000000,
+ .bat_min = 1900000,
+
+ .gpio_temp = -1,
+ .adc_temp = -1,
+ .adc_temp_divider = -1,
+};
+
+static struct gpio collie_batt_gpios[] = {
+ { COLLIE_GPIO_CO, GPIOF_IN, "main battery full" },
+ { COLLIE_GPIO_MAIN_BAT_LOW, GPIOF_IN, "main battery low" },
+ { COLLIE_GPIO_CHARGE_ON, GPIOF_OUT_INIT_LOW, "main charge on" },
+ { COLLIE_GPIO_MBAT_ON, GPIOF_OUT_INIT_LOW, "main battery" },
+ { COLLIE_GPIO_TMP_ON, GPIOF_OUT_INIT_LOW, "main battery temp" },
+ { COLLIE_GPIO_BBAT_ON, GPIOF_OUT_INIT_LOW, "backup battery" },
+};
+
+#ifdef CONFIG_PM
+static int wakeup_enabled;
+
+static int collie_bat_suspend(struct ucb1x00_dev *dev)
+{
+ /* flush all pending status updates */
+ flush_work(&bat_work);
+
+ if (device_may_wakeup(&dev->ucb->dev) &&
+ collie_bat_main.status == POWER_SUPPLY_STATUS_CHARGING)
+ wakeup_enabled = !enable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO));
+ else
+ wakeup_enabled = 0;
+
+ return 0;
+}
+
+static int collie_bat_resume(struct ucb1x00_dev *dev)
+{
+ if (wakeup_enabled)
+ disable_irq_wake(gpio_to_irq(COLLIE_GPIO_CO));
+
+ /* things may have changed while we were away */
+ schedule_work(&bat_work);
+ return 0;
+}
+#else
+#define collie_bat_suspend NULL
+#define collie_bat_resume NULL
+#endif
+
+static int collie_bat_probe(struct ucb1x00_dev *dev)
+{
+ int ret;
+ struct power_supply_config psy_main_cfg = {}, psy_bu_cfg = {};
+
+ if (!machine_is_collie())
+ return -ENODEV;
+
+ ucb = dev->ucb;
+
+ ret = gpio_request_array(collie_batt_gpios,
+ ARRAY_SIZE(collie_batt_gpios));
+ if (ret)
+ return ret;
+
+ mutex_init(&collie_bat_main.work_lock);
+
+ INIT_WORK(&bat_work, collie_bat_work);
+
+ psy_main_cfg.drv_data = &collie_bat_main;
+ collie_bat_main.psy = power_supply_register(&dev->ucb->dev,
+ &collie_bat_main_desc,
+ &psy_main_cfg);
+ if (IS_ERR(collie_bat_main.psy)) {
+ ret = PTR_ERR(collie_bat_main.psy);
+ goto err_psy_reg_main;
+ }
+
+ psy_bu_cfg.drv_data = &collie_bat_bu;
+ collie_bat_bu.psy = power_supply_register(&dev->ucb->dev,
+ &collie_bat_bu_desc,
+ &psy_bu_cfg);
+ if (IS_ERR(collie_bat_bu.psy)) {
+ ret = PTR_ERR(collie_bat_bu.psy);
+ goto err_psy_reg_bu;
+ }
+
+ ret = request_irq(gpio_to_irq(COLLIE_GPIO_CO),
+ collie_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "main full", &collie_bat_main);
+ if (ret)
+ goto err_irq;
+
+ device_init_wakeup(&ucb->dev, 1);
+ schedule_work(&bat_work);
+
+ return 0;
+
+err_irq:
+ power_supply_unregister(collie_bat_bu.psy);
+err_psy_reg_bu:
+ power_supply_unregister(collie_bat_main.psy);
+err_psy_reg_main:
+
+ /* see comment in collie_bat_remove */
+ cancel_work_sync(&bat_work);
+ gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
+ return ret;
+}
+
+static void collie_bat_remove(struct ucb1x00_dev *dev)
+{
+ free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main);
+
+ power_supply_unregister(collie_bat_bu.psy);
+ power_supply_unregister(collie_bat_main.psy);
+
+ /*
+ * Now cancel the bat_work. We won't get any more schedules,
+ * since all sources (isr and external_power_changed) are
+ * unregistered now.
+ */
+ cancel_work_sync(&bat_work);
+ gpio_free_array(collie_batt_gpios, ARRAY_SIZE(collie_batt_gpios));
+}
+
+static struct ucb1x00_driver collie_bat_driver = {
+ .add = collie_bat_probe,
+ .remove = collie_bat_remove,
+ .suspend = collie_bat_suspend,
+ .resume = collie_bat_resume,
+};
+
+static int __init collie_bat_init(void)
+{
+ return ucb1x00_register_driver(&collie_bat_driver);
+}
+
+static void __exit collie_bat_exit(void)
+{
+ ucb1x00_unregister_driver(&collie_bat_driver);
+}
+
+module_init(collie_bat_init);
+module_exit(collie_bat_exit);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Thomas Kunze");
+MODULE_DESCRIPTION("Collie battery driver");
diff --git a/drivers/power/supply/da9030_battery.c b/drivers/power/supply/da9030_battery.c
new file mode 100644
index 0000000..5ca0f4d
--- /dev/null
+++ b/drivers/power/supply/da9030_battery.c
@@ -0,0 +1,596 @@
+/*
+ * Battery charger driver for Dialog Semiconductor DA9030
+ *
+ * Copyright (C) 2008 Compulab, Ltd.
+ * Mike Rapoport <mike@compulab.co.il>
+ *
+ * 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/kernel.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/workqueue.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/da903x.h>
+
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/notifier.h>
+
+#define DA9030_FAULT_LOG 0x0a
+#define DA9030_FAULT_LOG_OVER_TEMP (1 << 7)
+#define DA9030_FAULT_LOG_VBAT_OVER (1 << 4)
+
+#define DA9030_CHARGE_CONTROL 0x28
+#define DA9030_CHRG_CHARGER_ENABLE (1 << 7)
+
+#define DA9030_ADC_MAN_CONTROL 0x30
+#define DA9030_ADC_TBATREF_ENABLE (1 << 5)
+#define DA9030_ADC_LDO_INT_ENABLE (1 << 4)
+
+#define DA9030_ADC_AUTO_CONTROL 0x31
+#define DA9030_ADC_TBAT_ENABLE (1 << 5)
+#define DA9030_ADC_VBAT_IN_TXON (1 << 4)
+#define DA9030_ADC_VCH_ENABLE (1 << 3)
+#define DA9030_ADC_ICH_ENABLE (1 << 2)
+#define DA9030_ADC_VBAT_ENABLE (1 << 1)
+#define DA9030_ADC_AUTO_SLEEP_ENABLE (1 << 0)
+
+#define DA9030_VBATMON 0x32
+#define DA9030_VBATMONTXON 0x33
+#define DA9030_TBATHIGHP 0x34
+#define DA9030_TBATHIGHN 0x35
+#define DA9030_TBATLOW 0x36
+
+#define DA9030_VBAT_RES 0x41
+#define DA9030_VBATMIN_RES 0x42
+#define DA9030_VBATMINTXON_RES 0x43
+#define DA9030_ICHMAX_RES 0x44
+#define DA9030_ICHMIN_RES 0x45
+#define DA9030_ICHAVERAGE_RES 0x46
+#define DA9030_VCHMAX_RES 0x47
+#define DA9030_VCHMIN_RES 0x48
+#define DA9030_TBAT_RES 0x49
+
+struct da9030_adc_res {
+ uint8_t vbat_res;
+ uint8_t vbatmin_res;
+ uint8_t vbatmintxon;
+ uint8_t ichmax_res;
+ uint8_t ichmin_res;
+ uint8_t ichaverage_res;
+ uint8_t vchmax_res;
+ uint8_t vchmin_res;
+ uint8_t tbat_res;
+ uint8_t adc_in4_res;
+ uint8_t adc_in5_res;
+};
+
+struct da9030_battery_thresholds {
+ int tbat_low;
+ int tbat_high;
+ int tbat_restart;
+
+ int vbat_low;
+ int vbat_crit;
+ int vbat_charge_start;
+ int vbat_charge_stop;
+ int vbat_charge_restart;
+
+ int vcharge_min;
+ int vcharge_max;
+};
+
+struct da9030_charger {
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+
+ struct device *master;
+
+ struct da9030_adc_res adc;
+ struct delayed_work work;
+ unsigned int interval;
+
+ struct power_supply_info *battery_info;
+
+ struct da9030_battery_thresholds thresholds;
+
+ unsigned int charge_milliamp;
+ unsigned int charge_millivolt;
+
+ /* charger status */
+ bool chdet;
+ uint8_t fault;
+ int mA;
+ int mV;
+ bool is_on;
+
+ struct notifier_block nb;
+
+ /* platform callbacks for battery low and critical events */
+ void (*battery_low)(void);
+ void (*battery_critical)(void);
+
+ struct dentry *debug_file;
+};
+
+static inline int da9030_reg_to_mV(int reg)
+{
+ return ((reg * 2650) >> 8) + 2650;
+}
+
+static inline int da9030_millivolt_to_reg(int mV)
+{
+ return ((mV - 2650) << 8) / 2650;
+}
+
+static inline int da9030_reg_to_mA(int reg)
+{
+ return ((reg * 24000) >> 8) / 15;
+}
+
+#ifdef CONFIG_DEBUG_FS
+static int bat_debug_show(struct seq_file *s, void *data)
+{
+ struct da9030_charger *charger = s->private;
+
+ seq_printf(s, "charger is %s\n", charger->is_on ? "on" : "off");
+ if (charger->chdet) {
+ seq_printf(s, "iset = %dmA, vset = %dmV\n",
+ charger->mA, charger->mV);
+ }
+
+ seq_printf(s, "vbat_res = %d (%dmV)\n",
+ charger->adc.vbat_res,
+ da9030_reg_to_mV(charger->adc.vbat_res));
+ seq_printf(s, "vbatmin_res = %d (%dmV)\n",
+ charger->adc.vbatmin_res,
+ da9030_reg_to_mV(charger->adc.vbatmin_res));
+ seq_printf(s, "vbatmintxon = %d (%dmV)\n",
+ charger->adc.vbatmintxon,
+ da9030_reg_to_mV(charger->adc.vbatmintxon));
+ seq_printf(s, "ichmax_res = %d (%dmA)\n",
+ charger->adc.ichmax_res,
+ da9030_reg_to_mV(charger->adc.ichmax_res));
+ seq_printf(s, "ichmin_res = %d (%dmA)\n",
+ charger->adc.ichmin_res,
+ da9030_reg_to_mA(charger->adc.ichmin_res));
+ seq_printf(s, "ichaverage_res = %d (%dmA)\n",
+ charger->adc.ichaverage_res,
+ da9030_reg_to_mA(charger->adc.ichaverage_res));
+ seq_printf(s, "vchmax_res = %d (%dmV)\n",
+ charger->adc.vchmax_res,
+ da9030_reg_to_mA(charger->adc.vchmax_res));
+ seq_printf(s, "vchmin_res = %d (%dmV)\n",
+ charger->adc.vchmin_res,
+ da9030_reg_to_mV(charger->adc.vchmin_res));
+
+ return 0;
+}
+
+static int debug_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, bat_debug_show, inode->i_private);
+}
+
+static const struct file_operations bat_debug_fops = {
+ .open = debug_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
+{
+ charger->debug_file = debugfs_create_file("charger", 0666, NULL,
+ charger, &bat_debug_fops);
+ return charger->debug_file;
+}
+
+static void da9030_bat_remove_debugfs(struct da9030_charger *charger)
+{
+ debugfs_remove(charger->debug_file);
+}
+#else
+static inline struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger)
+{
+ return NULL;
+}
+static inline void da9030_bat_remove_debugfs(struct da9030_charger *charger)
+{
+}
+#endif
+
+static inline void da9030_read_adc(struct da9030_charger *charger,
+ struct da9030_adc_res *adc)
+{
+ da903x_reads(charger->master, DA9030_VBAT_RES,
+ sizeof(*adc), (uint8_t *)adc);
+}
+
+static void da9030_charger_update_state(struct da9030_charger *charger)
+{
+ uint8_t val;
+
+ da903x_read(charger->master, DA9030_CHARGE_CONTROL, &val);
+ charger->is_on = (val & DA9030_CHRG_CHARGER_ENABLE) ? 1 : 0;
+ charger->mA = ((val >> 3) & 0xf) * 100;
+ charger->mV = (val & 0x7) * 50 + 4000;
+
+ da9030_read_adc(charger, &charger->adc);
+ da903x_read(charger->master, DA9030_FAULT_LOG, &charger->fault);
+ charger->chdet = da903x_query_status(charger->master,
+ DA9030_STATUS_CHDET);
+}
+
+static void da9030_set_charge(struct da9030_charger *charger, int on)
+{
+ uint8_t val;
+
+ if (on) {
+ val = DA9030_CHRG_CHARGER_ENABLE;
+ val |= (charger->charge_milliamp / 100) << 3;
+ val |= (charger->charge_millivolt - 4000) / 50;
+ charger->is_on = 1;
+ } else {
+ val = 0;
+ charger->is_on = 0;
+ }
+
+ da903x_write(charger->master, DA9030_CHARGE_CONTROL, val);
+
+ power_supply_changed(charger->psy);
+}
+
+static void da9030_charger_check_state(struct da9030_charger *charger)
+{
+ da9030_charger_update_state(charger);
+
+ /* we wake or boot with external power on */
+ if (!charger->is_on) {
+ if ((charger->chdet) &&
+ (charger->adc.vbat_res <
+ charger->thresholds.vbat_charge_start)) {
+ da9030_set_charge(charger, 1);
+ }
+ } else {
+ /* Charger has been pulled out */
+ if (!charger->chdet) {
+ da9030_set_charge(charger, 0);
+ return;
+ }
+
+ if (charger->adc.vbat_res >=
+ charger->thresholds.vbat_charge_stop) {
+ da9030_set_charge(charger, 0);
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_charge_restart);
+ } else if (charger->adc.vbat_res >
+ charger->thresholds.vbat_low) {
+ /* we are charging and passed LOW_THRESH,
+ so upate DA9030 VBAT threshold
+ */
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_low);
+ }
+ if (charger->adc.vchmax_res > charger->thresholds.vcharge_max ||
+ charger->adc.vchmin_res < charger->thresholds.vcharge_min ||
+ /* Tempreture readings are negative */
+ charger->adc.tbat_res < charger->thresholds.tbat_high ||
+ charger->adc.tbat_res > charger->thresholds.tbat_low) {
+ /* disable charger */
+ da9030_set_charge(charger, 0);
+ }
+ }
+}
+
+static void da9030_charging_monitor(struct work_struct *work)
+{
+ struct da9030_charger *charger;
+
+ charger = container_of(work, struct da9030_charger, work.work);
+
+ da9030_charger_check_state(charger);
+
+ /* reschedule for the next time */
+ schedule_delayed_work(&charger->work, charger->interval);
+}
+
+static enum power_supply_property da9030_battery_props[] = {
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static void da9030_battery_check_status(struct da9030_charger *charger,
+ union power_supply_propval *val)
+{
+ if (charger->chdet) {
+ if (charger->is_on)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+}
+
+static void da9030_battery_check_health(struct da9030_charger *charger,
+ union power_supply_propval *val)
+{
+ if (charger->fault & DA9030_FAULT_LOG_OVER_TEMP)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (charger->fault & DA9030_FAULT_LOG_VBAT_OVER)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int da9030_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9030_charger *charger = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ da9030_battery_check_status(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ da9030_battery_check_health(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = charger->battery_info->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = charger->battery_info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = charger->battery_info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = da9030_reg_to_mV(charger->adc.vbat_res) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval =
+ da9030_reg_to_mA(charger->adc.ichaverage_res) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = charger->battery_info->name;
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void da9030_battery_vbat_event(struct da9030_charger *charger)
+{
+ da9030_read_adc(charger, &charger->adc);
+
+ if (charger->is_on)
+ return;
+
+ if (charger->adc.vbat_res < charger->thresholds.vbat_low) {
+ /* set VBAT threshold for critical */
+ da903x_write(charger->master, DA9030_VBATMON,
+ charger->thresholds.vbat_crit);
+ if (charger->battery_low)
+ charger->battery_low();
+ } else if (charger->adc.vbat_res <
+ charger->thresholds.vbat_crit) {
+ /* notify the system of battery critical */
+ if (charger->battery_critical)
+ charger->battery_critical();
+ }
+}
+
+static int da9030_battery_event(struct notifier_block *nb, unsigned long event,
+ void *data)
+{
+ struct da9030_charger *charger =
+ container_of(nb, struct da9030_charger, nb);
+
+ switch (event) {
+ case DA9030_EVENT_CHDET:
+ cancel_delayed_work_sync(&charger->work);
+ schedule_work(&charger->work.work);
+ break;
+ case DA9030_EVENT_VBATMON:
+ da9030_battery_vbat_event(charger);
+ break;
+ case DA9030_EVENT_CHIOVER:
+ case DA9030_EVENT_TBAT:
+ da9030_set_charge(charger, 0);
+ break;
+ }
+
+ return 0;
+}
+
+static void da9030_battery_convert_thresholds(struct da9030_charger *charger,
+ struct da9030_battery_info *pdata)
+{
+ charger->thresholds.tbat_low = pdata->tbat_low;
+ charger->thresholds.tbat_high = pdata->tbat_high;
+ charger->thresholds.tbat_restart = pdata->tbat_restart;
+
+ charger->thresholds.vbat_low =
+ da9030_millivolt_to_reg(pdata->vbat_low);
+ charger->thresholds.vbat_crit =
+ da9030_millivolt_to_reg(pdata->vbat_crit);
+ charger->thresholds.vbat_charge_start =
+ da9030_millivolt_to_reg(pdata->vbat_charge_start);
+ charger->thresholds.vbat_charge_stop =
+ da9030_millivolt_to_reg(pdata->vbat_charge_stop);
+ charger->thresholds.vbat_charge_restart =
+ da9030_millivolt_to_reg(pdata->vbat_charge_restart);
+
+ charger->thresholds.vcharge_min =
+ da9030_millivolt_to_reg(pdata->vcharge_min);
+ charger->thresholds.vcharge_max =
+ da9030_millivolt_to_reg(pdata->vcharge_max);
+}
+
+static void da9030_battery_setup_psy(struct da9030_charger *charger)
+{
+ struct power_supply_desc *psy_desc = &charger->psy_desc;
+ struct power_supply_info *info = charger->battery_info;
+
+ psy_desc->name = info->name;
+ psy_desc->use_for_apm = info->use_for_apm;
+ psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ psy_desc->get_property = da9030_battery_get_property;
+
+ psy_desc->properties = da9030_battery_props;
+ psy_desc->num_properties = ARRAY_SIZE(da9030_battery_props);
+};
+
+static int da9030_battery_charger_init(struct da9030_charger *charger)
+{
+ char v[5];
+ int ret;
+
+ v[0] = v[1] = charger->thresholds.vbat_low;
+ v[2] = charger->thresholds.tbat_high;
+ v[3] = charger->thresholds.tbat_restart;
+ v[4] = charger->thresholds.tbat_low;
+
+ ret = da903x_writes(charger->master, DA9030_VBATMON, 5, v);
+ if (ret)
+ return ret;
+
+ /*
+ * Enable reference voltage supply for ADC from the LDO_INTERNAL
+ * regulator. Must be set before ADC measurements can be made.
+ */
+ ret = da903x_write(charger->master, DA9030_ADC_MAN_CONTROL,
+ DA9030_ADC_LDO_INT_ENABLE |
+ DA9030_ADC_TBATREF_ENABLE);
+ if (ret)
+ return ret;
+
+ /* enable auto ADC measuremnts */
+ return da903x_write(charger->master, DA9030_ADC_AUTO_CONTROL,
+ DA9030_ADC_TBAT_ENABLE | DA9030_ADC_VBAT_IN_TXON |
+ DA9030_ADC_VCH_ENABLE | DA9030_ADC_ICH_ENABLE |
+ DA9030_ADC_VBAT_ENABLE |
+ DA9030_ADC_AUTO_SLEEP_ENABLE);
+}
+
+static int da9030_battery_probe(struct platform_device *pdev)
+{
+ struct da9030_charger *charger;
+ struct power_supply_config psy_cfg = {};
+ struct da9030_battery_info *pdata = pdev->dev.platform_data;
+ int ret;
+
+ if (pdata == NULL)
+ return -EINVAL;
+
+ if (pdata->charge_milliamp >= 1500 ||
+ pdata->charge_millivolt < 4000 ||
+ pdata->charge_millivolt > 4350)
+ return -EINVAL;
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
+ if (charger == NULL)
+ return -ENOMEM;
+
+ charger->master = pdev->dev.parent;
+
+ /* 10 seconds between monitor runs unless platform defines other
+ interval */
+ charger->interval = msecs_to_jiffies(
+ (pdata->batmon_interval ? : 10) * 1000);
+
+ charger->charge_milliamp = pdata->charge_milliamp;
+ charger->charge_millivolt = pdata->charge_millivolt;
+ charger->battery_info = pdata->battery_info;
+ charger->battery_low = pdata->battery_low;
+ charger->battery_critical = pdata->battery_critical;
+
+ da9030_battery_convert_thresholds(charger, pdata);
+
+ ret = da9030_battery_charger_init(charger);
+ if (ret)
+ goto err_charger_init;
+
+ INIT_DELAYED_WORK(&charger->work, da9030_charging_monitor);
+ schedule_delayed_work(&charger->work, charger->interval);
+
+ charger->nb.notifier_call = da9030_battery_event;
+ ret = da903x_register_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET |
+ DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER |
+ DA9030_EVENT_TBAT);
+ if (ret)
+ goto err_notifier;
+
+ da9030_battery_setup_psy(charger);
+ psy_cfg.drv_data = charger;
+ charger->psy = power_supply_register(&pdev->dev, &charger->psy_desc,
+ &psy_cfg);
+ if (IS_ERR(charger->psy)) {
+ ret = PTR_ERR(charger->psy);
+ goto err_ps_register;
+ }
+
+ charger->debug_file = da9030_bat_create_debugfs(charger);
+ platform_set_drvdata(pdev, charger);
+ return 0;
+
+err_ps_register:
+ da903x_unregister_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
+err_notifier:
+ cancel_delayed_work(&charger->work);
+
+err_charger_init:
+ return ret;
+}
+
+static int da9030_battery_remove(struct platform_device *dev)
+{
+ struct da9030_charger *charger = platform_get_drvdata(dev);
+
+ da9030_bat_remove_debugfs(charger);
+
+ da903x_unregister_notifier(charger->master, &charger->nb,
+ DA9030_EVENT_CHDET | DA9030_EVENT_VBATMON |
+ DA9030_EVENT_CHIOVER | DA9030_EVENT_TBAT);
+ cancel_delayed_work_sync(&charger->work);
+ da9030_set_charge(charger, 0);
+ power_supply_unregister(charger->psy);
+
+ return 0;
+}
+
+static struct platform_driver da903x_battery_driver = {
+ .driver = {
+ .name = "da903x-battery",
+ },
+ .probe = da9030_battery_probe,
+ .remove = da9030_battery_remove,
+};
+
+module_platform_driver(da903x_battery_driver);
+
+MODULE_DESCRIPTION("DA9030 battery charger driver");
+MODULE_AUTHOR("Mike Rapoport, CompuLab");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/da9052-battery.c b/drivers/power/supply/da9052-battery.c
new file mode 100644
index 0000000..830ec46
--- /dev/null
+++ b/drivers/power/supply/da9052-battery.c
@@ -0,0 +1,669 @@
+/*
+ * Batttery Driver for Dialog DA9052 PMICs
+ *
+ * Copyright(c) 2011 Dialog Semiconductor Ltd.
+ *
+ * Author: David Dajun Chen <dchen@diasemi.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.
+ */
+
+#include <linux/delay.h>
+#include <linux/freezer.h>
+#include <linux/fs.h>
+#include <linux/jiffies.h>
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/uaccess.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/da9052/da9052.h>
+#include <linux/mfd/da9052/pdata.h>
+#include <linux/mfd/da9052/reg.h>
+
+/* STATIC CONFIGURATION */
+#define DA9052_BAT_CUTOFF_VOLT 2800
+#define DA9052_BAT_TSH 62000
+#define DA9052_BAT_LOW_CAP 4
+#define DA9052_AVG_SZ 4
+#define DA9052_VC_TBL_SZ 68
+#define DA9052_VC_TBL_REF_SZ 3
+
+#define DA9052_ISET_USB_MASK 0x0F
+#define DA9052_CHG_USB_ILIM_MASK 0x40
+#define DA9052_CHG_LIM_COLS 16
+
+#define DA9052_MEAN(x, y) ((x + y) / 2)
+
+enum charger_type_enum {
+ DA9052_NOCHARGER = 1,
+ DA9052_CHARGER,
+};
+
+static const u16 da9052_chg_current_lim[2][DA9052_CHG_LIM_COLS] = {
+ {70, 80, 90, 100, 110, 120, 400, 450,
+ 500, 550, 600, 650, 700, 900, 1100, 1300},
+ {80, 90, 100, 110, 120, 400, 450, 500,
+ 550, 600, 800, 1000, 1200, 1400, 1600, 1800},
+};
+
+static const u16 vc_tbl_ref[3] = {10, 25, 40};
+/* Lookup table for voltage vs capacity */
+static u32 const vc_tbl[3][68][2] = {
+ /* For temperature 10 degree Celsius */
+ {
+ {4082, 100}, {4036, 98},
+ {4020, 96}, {4008, 95},
+ {3997, 93}, {3983, 91},
+ {3964, 90}, {3943, 88},
+ {3926, 87}, {3912, 85},
+ {3900, 84}, {3890, 82},
+ {3881, 80}, {3873, 79},
+ {3865, 77}, {3857, 76},
+ {3848, 74}, {3839, 73},
+ {3829, 71}, {3820, 70},
+ {3811, 68}, {3802, 67},
+ {3794, 65}, {3785, 64},
+ {3778, 62}, {3770, 61},
+ {3763, 59}, {3756, 58},
+ {3750, 56}, {3744, 55},
+ {3738, 53}, {3732, 52},
+ {3727, 50}, {3722, 49},
+ {3717, 47}, {3712, 46},
+ {3708, 44}, {3703, 43},
+ {3700, 41}, {3696, 40},
+ {3693, 38}, {3691, 37},
+ {3688, 35}, {3686, 34},
+ {3683, 32}, {3681, 31},
+ {3678, 29}, {3675, 28},
+ {3672, 26}, {3669, 25},
+ {3665, 23}, {3661, 22},
+ {3656, 21}, {3651, 19},
+ {3645, 18}, {3639, 16},
+ {3631, 15}, {3622, 13},
+ {3611, 12}, {3600, 10},
+ {3587, 9}, {3572, 7},
+ {3548, 6}, {3503, 5},
+ {3420, 3}, {3268, 2},
+ {2992, 1}, {2746, 0}
+ },
+ /* For temperature 25 degree Celsius */
+ {
+ {4102, 100}, {4065, 98},
+ {4048, 96}, {4034, 95},
+ {4021, 93}, {4011, 92},
+ {4001, 90}, {3986, 88},
+ {3968, 87}, {3952, 85},
+ {3938, 84}, {3926, 82},
+ {3916, 81}, {3908, 79},
+ {3900, 77}, {3892, 76},
+ {3883, 74}, {3874, 73},
+ {3864, 71}, {3855, 70},
+ {3846, 68}, {3836, 67},
+ {3827, 65}, {3819, 64},
+ {3810, 62}, {3801, 61},
+ {3793, 59}, {3786, 58},
+ {3778, 56}, {3772, 55},
+ {3765, 53}, {3759, 52},
+ {3754, 50}, {3748, 49},
+ {3743, 47}, {3738, 46},
+ {3733, 44}, {3728, 43},
+ {3724, 41}, {3720, 40},
+ {3716, 38}, {3712, 37},
+ {3709, 35}, {3706, 34},
+ {3703, 33}, {3701, 31},
+ {3698, 30}, {3696, 28},
+ {3693, 27}, {3690, 25},
+ {3687, 24}, {3683, 22},
+ {3680, 21}, {3675, 19},
+ {3671, 18}, {3666, 17},
+ {3660, 15}, {3654, 14},
+ {3647, 12}, {3639, 11},
+ {3630, 9}, {3621, 8},
+ {3613, 6}, {3606, 5},
+ {3597, 4}, {3582, 2},
+ {3546, 1}, {2747, 0}
+ },
+ /* For temperature 40 degree Celsius */
+ {
+ {4114, 100}, {4081, 98},
+ {4065, 96}, {4050, 95},
+ {4036, 93}, {4024, 92},
+ {4013, 90}, {4002, 88},
+ {3990, 87}, {3976, 85},
+ {3962, 84}, {3950, 82},
+ {3939, 81}, {3930, 79},
+ {3921, 77}, {3912, 76},
+ {3902, 74}, {3893, 73},
+ {3883, 71}, {3874, 70},
+ {3865, 68}, {3856, 67},
+ {3847, 65}, {3838, 64},
+ {3829, 62}, {3820, 61},
+ {3812, 59}, {3803, 58},
+ {3795, 56}, {3787, 55},
+ {3780, 53}, {3773, 52},
+ {3767, 50}, {3761, 49},
+ {3756, 47}, {3751, 46},
+ {3746, 44}, {3741, 43},
+ {3736, 41}, {3732, 40},
+ {3728, 38}, {3724, 37},
+ {3720, 35}, {3716, 34},
+ {3713, 33}, {3710, 31},
+ {3707, 30}, {3704, 28},
+ {3701, 27}, {3698, 25},
+ {3695, 24}, {3691, 22},
+ {3686, 21}, {3681, 19},
+ {3676, 18}, {3671, 17},
+ {3666, 15}, {3661, 14},
+ {3655, 12}, {3648, 11},
+ {3640, 9}, {3632, 8},
+ {3622, 6}, {3616, 5},
+ {3611, 4}, {3604, 2},
+ {3594, 1}, {2747, 0}
+ }
+};
+
+struct da9052_battery {
+ struct da9052 *da9052;
+ struct power_supply *psy;
+ struct notifier_block nb;
+ int charger_type;
+ int status;
+ int health;
+};
+
+static inline int volt_reg_to_mV(int value)
+{
+ return ((value * 1000) / 512) + 2500;
+}
+
+static inline int ichg_reg_to_mA(int value)
+{
+ return (value * 3900) / 1000;
+}
+
+static int da9052_read_chgend_current(struct da9052_battery *bat,
+ int *current_mA)
+{
+ int ret;
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EINVAL;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_ICHG_END_REG);
+ if (ret < 0)
+ return ret;
+
+ *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGEND_ICHGEND);
+
+ return 0;
+}
+
+static int da9052_read_chg_current(struct da9052_battery *bat, int *current_mA)
+{
+ int ret;
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EINVAL;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_ICHG_AV_REG);
+ if (ret < 0)
+ return ret;
+
+ *current_mA = ichg_reg_to_mA(ret & DA9052_ICHGAV_ICHGAV);
+
+ return 0;
+}
+
+static int da9052_bat_check_status(struct da9052_battery *bat, int *status)
+{
+ u8 v[2] = {0, 0};
+ u8 bat_status;
+ u8 chg_end;
+ int ret;
+ int chg_current;
+ int chg_end_current;
+ bool dcinsel;
+ bool dcindet;
+ bool vbussel;
+ bool vbusdet;
+ bool dc;
+ bool vbus;
+
+ ret = da9052_group_read(bat->da9052, DA9052_STATUS_A_REG, 2, v);
+ if (ret < 0)
+ return ret;
+
+ bat_status = v[0];
+ chg_end = v[1];
+
+ dcinsel = bat_status & DA9052_STATUSA_DCINSEL;
+ dcindet = bat_status & DA9052_STATUSA_DCINDET;
+ vbussel = bat_status & DA9052_STATUSA_VBUSSEL;
+ vbusdet = bat_status & DA9052_STATUSA_VBUSDET;
+ dc = dcinsel && dcindet;
+ vbus = vbussel && vbusdet;
+
+ /* Preference to WALL(DCIN) charger unit */
+ if (dc || vbus) {
+ bat->charger_type = DA9052_CHARGER;
+
+ /* If charging end flag is set and Charging current is greater
+ * than charging end limit then battery is charging
+ */
+ if ((chg_end & DA9052_STATUSB_CHGEND) != 0) {
+ ret = da9052_read_chg_current(bat, &chg_current);
+ if (ret < 0)
+ return ret;
+ ret = da9052_read_chgend_current(bat, &chg_end_current);
+ if (ret < 0)
+ return ret;
+
+ if (chg_current >= chg_end_current)
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ /* If Charging end flag is cleared then battery is
+ * charging
+ */
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else if (dcindet || vbusdet) {
+ bat->charger_type = DA9052_CHARGER;
+ bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ bat->charger_type = DA9052_NOCHARGER;
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (status != NULL)
+ *status = bat->status;
+ return 0;
+}
+
+static int da9052_bat_read_volt(struct da9052_battery *bat, int *volt_mV)
+{
+ int volt;
+
+ volt = da9052_adc_manual_read(bat->da9052, DA9052_ADC_MAN_MUXSEL_VBAT);
+ if (volt < 0)
+ return volt;
+
+ *volt_mV = volt_reg_to_mV(volt);
+
+ return 0;
+}
+
+static int da9052_bat_check_presence(struct da9052_battery *bat, int *illegal)
+{
+ int bat_temp;
+
+ bat_temp = da9052_adc_read_temp(bat->da9052);
+ if (bat_temp < 0)
+ return bat_temp;
+
+ if (bat_temp > DA9052_BAT_TSH)
+ *illegal = 1;
+ else
+ *illegal = 0;
+
+ return 0;
+}
+
+static int da9052_bat_interpolate(int vbat_lower, int vbat_upper,
+ int level_lower, int level_upper,
+ int bat_voltage)
+{
+ int tmp;
+
+ tmp = ((level_upper - level_lower) * 1000) / (vbat_upper - vbat_lower);
+ tmp = level_lower + (((bat_voltage - vbat_lower) * tmp) / 1000);
+
+ return tmp;
+}
+
+static unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp)
+{
+ int i;
+
+ if (adc_temp <= vc_tbl_ref[0])
+ return 0;
+
+ if (adc_temp > vc_tbl_ref[DA9052_VC_TBL_REF_SZ - 1])
+ return DA9052_VC_TBL_REF_SZ - 1;
+
+ for (i = 0; i < DA9052_VC_TBL_REF_SZ - 1; i++) {
+ if ((adc_temp > vc_tbl_ref[i]) &&
+ (adc_temp <= DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1])))
+ return i;
+ if ((adc_temp > DA9052_MEAN(vc_tbl_ref[i], vc_tbl_ref[i + 1]))
+ && (adc_temp <= vc_tbl_ref[i]))
+ return i + 1;
+ }
+ /*
+ * For some reason authors of the driver didn't presume that we can
+ * end up here. It might be OK, but might be not, no one knows for
+ * sure. Go check your battery, is it on fire?
+ */
+ WARN_ON(1);
+ return 0;
+}
+
+static int da9052_bat_read_capacity(struct da9052_battery *bat, int *capacity)
+{
+ int adc_temp;
+ int bat_voltage;
+ int vbat_lower;
+ int vbat_upper;
+ int level_upper;
+ int level_lower;
+ int ret;
+ int flag;
+ int i = 0;
+ int j;
+
+ ret = da9052_bat_read_volt(bat, &bat_voltage);
+ if (ret < 0)
+ return ret;
+
+ adc_temp = da9052_adc_read_temp(bat->da9052);
+ if (adc_temp < 0)
+ return adc_temp;
+
+ i = da9052_determine_vc_tbl_index(adc_temp);
+
+ if (bat_voltage >= vc_tbl[i][0][0]) {
+ *capacity = 100;
+ return 0;
+ }
+ if (bat_voltage <= vc_tbl[i][DA9052_VC_TBL_SZ - 1][0]) {
+ *capacity = 0;
+ return 0;
+ }
+ flag = 0;
+
+ for (j = 0; j < (DA9052_VC_TBL_SZ-1); j++) {
+ if ((bat_voltage <= vc_tbl[i][j][0]) &&
+ (bat_voltage >= vc_tbl[i][j + 1][0])) {
+ vbat_upper = vc_tbl[i][j][0];
+ vbat_lower = vc_tbl[i][j + 1][0];
+ level_upper = vc_tbl[i][j][1];
+ level_lower = vc_tbl[i][j + 1][1];
+ flag = 1;
+ break;
+ }
+ }
+ if (!flag)
+ return -EIO;
+
+ *capacity = da9052_bat_interpolate(vbat_lower, vbat_upper, level_lower,
+ level_upper, bat_voltage);
+
+ return 0;
+}
+
+static int da9052_bat_check_health(struct da9052_battery *bat, int *health)
+{
+ int ret;
+ int bat_illegal;
+ int capacity;
+
+ ret = da9052_bat_check_presence(bat, &bat_illegal);
+ if (ret < 0)
+ return ret;
+
+ if (bat_illegal) {
+ bat->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ return 0;
+ }
+
+ if (bat->health != POWER_SUPPLY_HEALTH_OVERHEAT) {
+ ret = da9052_bat_read_capacity(bat, &capacity);
+ if (ret < 0)
+ return ret;
+ if (capacity < DA9052_BAT_LOW_CAP)
+ bat->health = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ bat->health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ *health = bat->health;
+
+ return 0;
+}
+
+static irqreturn_t da9052_bat_irq(int irq, void *data)
+{
+ struct da9052_battery *bat = data;
+ int virq;
+
+ virq = regmap_irq_get_virq(bat->da9052->irq_data, irq);
+ irq -= virq;
+
+ if (irq == DA9052_IRQ_CHGEND)
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ else
+ da9052_bat_check_status(bat, NULL);
+
+ if (irq == DA9052_IRQ_CHGEND || irq == DA9052_IRQ_DCIN ||
+ irq == DA9052_IRQ_VBUS || irq == DA9052_IRQ_TBAT) {
+ power_supply_changed(bat->psy);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int da9052_USB_current_notifier(struct notifier_block *nb,
+ unsigned long events, void *data)
+{
+ u8 row;
+ u8 col;
+ int *current_mA = data;
+ int ret;
+ struct da9052_battery *bat = container_of(nb, struct da9052_battery,
+ nb);
+
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING)
+ return -EPERM;
+
+ ret = da9052_reg_read(bat->da9052, DA9052_CHGBUCK_REG);
+ if (ret & DA9052_CHG_USB_ILIM_MASK)
+ return -EPERM;
+
+ if (bat->da9052->chip_id == DA9052)
+ row = 0;
+ else
+ row = 1;
+
+ if (*current_mA < da9052_chg_current_lim[row][0] ||
+ *current_mA > da9052_chg_current_lim[row][DA9052_CHG_LIM_COLS - 1])
+ return -EINVAL;
+
+ for (col = 0; col <= DA9052_CHG_LIM_COLS - 1 ; col++) {
+ if (*current_mA <= da9052_chg_current_lim[row][col])
+ break;
+ }
+
+ return da9052_reg_update(bat->da9052, DA9052_ISET_REG,
+ DA9052_ISET_USB_MASK, col);
+}
+
+static int da9052_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret;
+ int illegal;
+ struct da9052_battery *bat = power_supply_get_drvdata(psy);
+
+ ret = da9052_bat_check_presence(bat, &illegal);
+ if (ret < 0)
+ return ret;
+
+ if (illegal && psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = da9052_bat_check_status(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval =
+ (bat->charger_type == DA9052_NOCHARGER) ? 0 : 1;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = da9052_bat_check_presence(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = da9052_bat_check_health(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = DA9052_BAT_CUTOFF_VOLT * 1000;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = da9052_bat_read_volt(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = da9052_read_chg_current(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = da9052_bat_read_capacity(bat, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = da9052_adc_read_temp(bat->da9052);
+ ret = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return ret;
+}
+
+static enum power_supply_property da9052_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+};
+
+static struct power_supply_desc psy_desc = {
+ .name = "da9052-bat",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = da9052_bat_props,
+ .num_properties = ARRAY_SIZE(da9052_bat_props),
+ .get_property = da9052_bat_get_property,
+};
+
+static char *da9052_bat_irqs[] = {
+ "BATT TEMP",
+ "DCIN DET",
+ "DCIN REM",
+ "VBUS DET",
+ "VBUS REM",
+ "CHG END",
+};
+
+static int da9052_bat_irq_bits[] = {
+ DA9052_IRQ_TBAT,
+ DA9052_IRQ_DCIN,
+ DA9052_IRQ_DCINREM,
+ DA9052_IRQ_VBUS,
+ DA9052_IRQ_VBUSREM,
+ DA9052_IRQ_CHGEND,
+};
+
+static s32 da9052_bat_probe(struct platform_device *pdev)
+{
+ struct da9052_pdata *pdata;
+ struct da9052_battery *bat;
+ struct power_supply_config psy_cfg = {};
+ int ret;
+ int i;
+
+ bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery),
+ GFP_KERNEL);
+ if (!bat)
+ return -ENOMEM;
+
+ psy_cfg.drv_data = bat;
+
+ bat->da9052 = dev_get_drvdata(pdev->dev.parent);
+ bat->charger_type = DA9052_NOCHARGER;
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ bat->nb.notifier_call = da9052_USB_current_notifier;
+
+ pdata = bat->da9052->dev->platform_data;
+ if (pdata != NULL && pdata->use_for_apm)
+ psy_desc.use_for_apm = pdata->use_for_apm;
+ else
+ psy_desc.use_for_apm = 1;
+
+ for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) {
+ ret = da9052_request_irq(bat->da9052,
+ da9052_bat_irq_bits[i], da9052_bat_irqs[i],
+ da9052_bat_irq, bat);
+
+ if (ret != 0) {
+ dev_err(bat->da9052->dev,
+ "DA9052 failed to request %s IRQ: %d\n",
+ da9052_bat_irqs[i], ret);
+ goto err;
+ }
+ }
+
+ bat->psy = power_supply_register(&pdev->dev, &psy_desc, &psy_cfg);
+ if (IS_ERR(bat->psy)) {
+ ret = PTR_ERR(bat->psy);
+ goto err;
+ }
+
+ platform_set_drvdata(pdev, bat);
+ return 0;
+
+err:
+ while (--i >= 0)
+ da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat);
+
+ return ret;
+}
+static int da9052_bat_remove(struct platform_device *pdev)
+{
+ int i;
+ struct da9052_battery *bat = platform_get_drvdata(pdev);
+
+ for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++)
+ da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat);
+
+ power_supply_unregister(bat->psy);
+
+ return 0;
+}
+
+static struct platform_driver da9052_bat_driver = {
+ .probe = da9052_bat_probe,
+ .remove = da9052_bat_remove,
+ .driver = {
+ .name = "da9052-bat",
+ },
+};
+module_platform_driver(da9052_bat_driver);
+
+MODULE_DESCRIPTION("DA9052 BAT Device Driver");
+MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:da9052-bat");
diff --git a/drivers/power/supply/da9150-charger.c b/drivers/power/supply/da9150-charger.c
new file mode 100644
index 0000000..6009981
--- /dev/null
+++ b/drivers/power/supply/da9150-charger.c
@@ -0,0 +1,694 @@
+/*
+ * DA9150 Charger Driver
+ *
+ * Copyright (c) 2014 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/interrupt.h>
+#include <linux/power_supply.h>
+#include <linux/notifier.h>
+#include <linux/usb/phy.h>
+#include <linux/iio/consumer.h>
+#include <linux/mfd/da9150/core.h>
+#include <linux/mfd/da9150/registers.h>
+
+/* Private data */
+struct da9150_charger {
+ struct da9150 *da9150;
+ struct device *dev;
+
+ struct power_supply *usb;
+ struct power_supply *battery;
+ struct power_supply *supply_online;
+
+ struct usb_phy *usb_phy;
+ struct notifier_block otg_nb;
+ struct work_struct otg_work;
+ unsigned long usb_event;
+
+ struct iio_channel *ibus_chan;
+ struct iio_channel *vbus_chan;
+ struct iio_channel *tjunc_chan;
+ struct iio_channel *vbat_chan;
+};
+
+static inline int da9150_charger_supply_online(struct da9150_charger *charger,
+ struct power_supply *psy,
+ union power_supply_propval *val)
+{
+ val->intval = (psy == charger->supply_online) ? 1 : 0;
+
+ return 0;
+}
+
+/* Charger Properties */
+static int da9150_charger_vbus_voltage_now(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ int v_val, ret;
+
+ /* Read processed value - mV units */
+ ret = iio_read_channel_processed(charger->vbus_chan, &v_val);
+ if (ret < 0)
+ return ret;
+
+ /* Convert voltage to expected uV units */
+ val->intval = v_val * 1000;
+
+ return 0;
+}
+
+static int da9150_charger_ibus_current_avg(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ int i_val, ret;
+
+ /* Read processed value - mA units */
+ ret = iio_read_channel_processed(charger->ibus_chan, &i_val);
+ if (ret < 0)
+ return ret;
+
+ /* Convert current to expected uA units */
+ val->intval = i_val * 1000;
+
+ return 0;
+}
+
+static int da9150_charger_tjunc_temp(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ int t_val, ret;
+
+ /* Read processed value - 0.001 degrees C units */
+ ret = iio_read_channel_processed(charger->tjunc_chan, &t_val);
+ if (ret < 0)
+ return ret;
+
+ /* Convert temp to expect 0.1 degrees C units */
+ val->intval = t_val / 100;
+
+ return 0;
+}
+
+static enum power_supply_property da9150_charger_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static int da9150_charger_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = da9150_charger_supply_online(charger, psy, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = da9150_charger_vbus_voltage_now(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = da9150_charger_ibus_current_avg(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = da9150_charger_tjunc_temp(charger, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/* Battery Properties */
+static int da9150_charger_battery_status(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ /* Check to see if battery is discharging */
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H);
+
+ if (((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_OFF) ||
+ ((reg & DA9150_VBUS_STAT_MASK) == DA9150_VBUS_STAT_WAIT)) {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ return 0;
+ }
+
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J);
+
+ /* Now check for other states */
+ switch (reg & DA9150_CHG_STAT_MASK) {
+ case DA9150_CHG_STAT_ACT:
+ case DA9150_CHG_STAT_PRE:
+ case DA9150_CHG_STAT_CC:
+ case DA9150_CHG_STAT_CV:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case DA9150_CHG_STAT_OFF:
+ case DA9150_CHG_STAT_SUSP:
+ case DA9150_CHG_STAT_TEMP:
+ case DA9150_CHG_STAT_TIME:
+ case DA9150_CHG_STAT_BAT:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case DA9150_CHG_STAT_FULL:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int da9150_charger_battery_health(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J);
+
+ /* Check if temperature limit reached */
+ switch (reg & DA9150_CHG_TEMP_MASK) {
+ case DA9150_CHG_TEMP_UNDER:
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ return 0;
+ case DA9150_CHG_TEMP_OVER:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ return 0;
+ default:
+ break;
+ }
+
+ /* Check for other health states */
+ switch (reg & DA9150_CHG_STAT_MASK) {
+ case DA9150_CHG_STAT_ACT:
+ case DA9150_CHG_STAT_PRE:
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ case DA9150_CHG_STAT_TIME:
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ }
+
+ return 0;
+}
+
+static int da9150_charger_battery_present(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ /* Check if battery present or removed */
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J);
+ if ((reg & DA9150_CHG_STAT_MASK) == DA9150_CHG_STAT_BAT)
+ val->intval = 0;
+ else
+ val->intval = 1;
+
+ return 0;
+}
+
+static int da9150_charger_battery_charge_type(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_J);
+
+ switch (reg & DA9150_CHG_STAT_MASK) {
+ case DA9150_CHG_STAT_CC:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case DA9150_CHG_STAT_ACT:
+ case DA9150_CHG_STAT_PRE:
+ case DA9150_CHG_STAT_CV:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+
+ return 0;
+}
+
+static int da9150_charger_battery_voltage_min(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_C);
+
+ /* Value starts at 2500 mV, 50 mV increments, presented in uV */
+ val->intval = ((reg & DA9150_CHG_VFAULT_MASK) * 50000) + 2500000;
+
+ return 0;
+}
+
+static int da9150_charger_battery_voltage_now(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ int v_val, ret;
+
+ /* Read processed value - mV units */
+ ret = iio_read_channel_processed(charger->vbat_chan, &v_val);
+ if (ret < 0)
+ return ret;
+
+ val->intval = v_val * 1000;
+
+ return 0;
+}
+
+static int da9150_charger_battery_current_max(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ int reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_D);
+
+ /* 25mA increments */
+ val->intval = reg * 25000;
+
+ return 0;
+}
+
+static int da9150_charger_battery_voltage_max(struct da9150_charger *charger,
+ union power_supply_propval *val)
+{
+ u8 reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_PPR_CHGCTRL_B);
+
+ /* Value starts at 3650 mV, 25 mV increments, presented in uV */
+ val->intval = ((reg & DA9150_CHG_VBAT_MASK) * 25000) + 3650000;
+ return 0;
+}
+
+static enum power_supply_property da9150_charger_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+};
+
+static int da9150_charger_battery_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9150_charger *charger = dev_get_drvdata(psy->dev.parent);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = da9150_charger_battery_status(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = da9150_charger_supply_online(charger, psy, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = da9150_charger_battery_health(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = da9150_charger_battery_present(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = da9150_charger_battery_charge_type(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ ret = da9150_charger_battery_voltage_min(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = da9150_charger_battery_voltage_now(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ ret = da9150_charger_battery_current_max(charger, val);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ ret = da9150_charger_battery_voltage_max(charger, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static irqreturn_t da9150_charger_chg_irq(int irq, void *data)
+{
+ struct da9150_charger *charger = data;
+
+ power_supply_changed(charger->battery);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t da9150_charger_tjunc_irq(int irq, void *data)
+{
+ struct da9150_charger *charger = data;
+
+ /* Nothing we can really do except report this. */
+ dev_crit(charger->dev, "TJunc over temperature!!!\n");
+ power_supply_changed(charger->usb);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t da9150_charger_vfault_irq(int irq, void *data)
+{
+ struct da9150_charger *charger = data;
+
+ /* Nothing we can really do except report this. */
+ dev_crit(charger->dev, "VSYS under voltage!!!\n");
+ power_supply_changed(charger->usb);
+ power_supply_changed(charger->battery);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t da9150_charger_vbus_irq(int irq, void *data)
+{
+ struct da9150_charger *charger = data;
+ u8 reg;
+
+ reg = da9150_reg_read(charger->da9150, DA9150_STATUS_H);
+
+ /* Charger plugged in or battery only */
+ switch (reg & DA9150_VBUS_STAT_MASK) {
+ case DA9150_VBUS_STAT_OFF:
+ case DA9150_VBUS_STAT_WAIT:
+ charger->supply_online = charger->battery;
+ break;
+ case DA9150_VBUS_STAT_CHG:
+ charger->supply_online = charger->usb;
+ break;
+ default:
+ dev_warn(charger->dev, "Unknown VBUS state - reg = 0x%x\n",
+ reg);
+ charger->supply_online = NULL;
+ break;
+ }
+
+ power_supply_changed(charger->usb);
+ power_supply_changed(charger->battery);
+
+ return IRQ_HANDLED;
+}
+
+static void da9150_charger_otg_work(struct work_struct *data)
+{
+ struct da9150_charger *charger =
+ container_of(data, struct da9150_charger, otg_work);
+
+ switch (charger->usb_event) {
+ case USB_EVENT_ID:
+ /* Enable OTG Boost */
+ da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A,
+ DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_OTG);
+ break;
+ case USB_EVENT_NONE:
+ /* Revert to charge mode */
+ power_supply_changed(charger->usb);
+ power_supply_changed(charger->battery);
+ da9150_set_bits(charger->da9150, DA9150_PPR_BKCTRL_A,
+ DA9150_VBUS_MODE_MASK, DA9150_VBUS_MODE_CHG);
+ break;
+ }
+}
+
+static int da9150_charger_otg_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ struct da9150_charger *charger =
+ container_of(nb, struct da9150_charger, otg_nb);
+
+ dev_dbg(charger->dev, "DA9150 OTG notify %lu\n", val);
+
+ charger->usb_event = val;
+ schedule_work(&charger->otg_work);
+
+ return NOTIFY_OK;
+}
+
+static int da9150_charger_register_irq(struct platform_device *pdev,
+ irq_handler_t handler,
+ const char *irq_name)
+{
+ struct device *dev = &pdev->dev;
+ struct da9150_charger *charger = platform_get_drvdata(pdev);
+ int irq, ret;
+
+ irq = platform_get_irq_byname(pdev, irq_name);
+ if (irq < 0) {
+ dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq);
+ return irq;
+ }
+
+ ret = request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT, irq_name,
+ charger);
+ if (ret)
+ dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret);
+
+ return ret;
+}
+
+static void da9150_charger_unregister_irq(struct platform_device *pdev,
+ const char *irq_name)
+{
+ struct device *dev = &pdev->dev;
+ struct da9150_charger *charger = platform_get_drvdata(pdev);
+ int irq;
+
+ irq = platform_get_irq_byname(pdev, irq_name);
+ if (irq < 0) {
+ dev_err(dev, "Failed to get IRQ CHG_STATUS: %d\n", irq);
+ return;
+ }
+
+ free_irq(irq, charger);
+}
+
+static const struct power_supply_desc usb_desc = {
+ .name = "da9150-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = da9150_charger_props,
+ .num_properties = ARRAY_SIZE(da9150_charger_props),
+ .get_property = da9150_charger_get_prop,
+};
+
+static const struct power_supply_desc battery_desc = {
+ .name = "da9150-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = da9150_charger_bat_props,
+ .num_properties = ARRAY_SIZE(da9150_charger_bat_props),
+ .get_property = da9150_charger_battery_get_prop,
+};
+
+static int da9150_charger_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da9150 *da9150 = dev_get_drvdata(dev->parent);
+ struct da9150_charger *charger;
+ u8 reg;
+ int ret;
+
+ charger = devm_kzalloc(dev, sizeof(struct da9150_charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, charger);
+ charger->da9150 = da9150;
+ charger->dev = dev;
+
+ /* Acquire ADC channels */
+ charger->ibus_chan = iio_channel_get(dev, "CHAN_IBUS");
+ if (IS_ERR(charger->ibus_chan)) {
+ ret = PTR_ERR(charger->ibus_chan);
+ goto ibus_chan_fail;
+ }
+
+ charger->vbus_chan = iio_channel_get(dev, "CHAN_VBUS");
+ if (IS_ERR(charger->vbus_chan)) {
+ ret = PTR_ERR(charger->vbus_chan);
+ goto vbus_chan_fail;
+ }
+
+ charger->tjunc_chan = iio_channel_get(dev, "CHAN_TJUNC");
+ if (IS_ERR(charger->tjunc_chan)) {
+ ret = PTR_ERR(charger->tjunc_chan);
+ goto tjunc_chan_fail;
+ }
+
+ charger->vbat_chan = iio_channel_get(dev, "CHAN_VBAT");
+ if (IS_ERR(charger->vbat_chan)) {
+ ret = PTR_ERR(charger->vbat_chan);
+ goto vbat_chan_fail;
+ }
+
+ /* Register power supplies */
+ charger->usb = power_supply_register(dev, &usb_desc, NULL);
+ if (IS_ERR(charger->usb)) {
+ ret = PTR_ERR(charger->usb);
+ goto usb_fail;
+ }
+
+ charger->battery = power_supply_register(dev, &battery_desc, NULL);
+ if (IS_ERR(charger->battery)) {
+ ret = PTR_ERR(charger->battery);
+ goto battery_fail;
+ }
+
+ /* Get initial online supply */
+ reg = da9150_reg_read(da9150, DA9150_STATUS_H);
+
+ switch (reg & DA9150_VBUS_STAT_MASK) {
+ case DA9150_VBUS_STAT_OFF:
+ case DA9150_VBUS_STAT_WAIT:
+ charger->supply_online = charger->battery;
+ break;
+ case DA9150_VBUS_STAT_CHG:
+ charger->supply_online = charger->usb;
+ break;
+ default:
+ dev_warn(dev, "Unknown VBUS state - reg = 0x%x\n", reg);
+ charger->supply_online = NULL;
+ break;
+ }
+
+ /* Setup OTG reporting & configuration */
+ charger->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+ if (!IS_ERR_OR_NULL(charger->usb_phy)) {
+ INIT_WORK(&charger->otg_work, da9150_charger_otg_work);
+ charger->otg_nb.notifier_call = da9150_charger_otg_ncb;
+ usb_register_notifier(charger->usb_phy, &charger->otg_nb);
+ }
+
+ /* Register IRQs */
+ ret = da9150_charger_register_irq(pdev, da9150_charger_chg_irq,
+ "CHG_STATUS");
+ if (ret < 0)
+ goto chg_irq_fail;
+
+ ret = da9150_charger_register_irq(pdev, da9150_charger_tjunc_irq,
+ "CHG_TJUNC");
+ if (ret < 0)
+ goto tjunc_irq_fail;
+
+ ret = da9150_charger_register_irq(pdev, da9150_charger_vfault_irq,
+ "CHG_VFAULT");
+ if (ret < 0)
+ goto vfault_irq_fail;
+
+ ret = da9150_charger_register_irq(pdev, da9150_charger_vbus_irq,
+ "CHG_VBUS");
+ if (ret < 0)
+ goto vbus_irq_fail;
+
+ return 0;
+
+
+vbus_irq_fail:
+ da9150_charger_unregister_irq(pdev, "CHG_VFAULT");
+vfault_irq_fail:
+ da9150_charger_unregister_irq(pdev, "CHG_TJUNC");
+tjunc_irq_fail:
+ da9150_charger_unregister_irq(pdev, "CHG_STATUS");
+chg_irq_fail:
+ if (!IS_ERR_OR_NULL(charger->usb_phy))
+ usb_unregister_notifier(charger->usb_phy, &charger->otg_nb);
+battery_fail:
+ power_supply_unregister(charger->usb);
+
+usb_fail:
+ iio_channel_release(charger->vbat_chan);
+
+vbat_chan_fail:
+ iio_channel_release(charger->tjunc_chan);
+
+tjunc_chan_fail:
+ iio_channel_release(charger->vbus_chan);
+
+vbus_chan_fail:
+ iio_channel_release(charger->ibus_chan);
+
+ibus_chan_fail:
+ return ret;
+}
+
+static int da9150_charger_remove(struct platform_device *pdev)
+{
+ struct da9150_charger *charger = platform_get_drvdata(pdev);
+ int irq;
+
+ /* Make sure IRQs are released before unregistering power supplies */
+ irq = platform_get_irq_byname(pdev, "CHG_VBUS");
+ free_irq(irq, charger);
+
+ irq = platform_get_irq_byname(pdev, "CHG_VFAULT");
+ free_irq(irq, charger);
+
+ irq = platform_get_irq_byname(pdev, "CHG_TJUNC");
+ free_irq(irq, charger);
+
+ irq = platform_get_irq_byname(pdev, "CHG_STATUS");
+ free_irq(irq, charger);
+
+ if (!IS_ERR_OR_NULL(charger->usb_phy))
+ usb_unregister_notifier(charger->usb_phy, &charger->otg_nb);
+
+ power_supply_unregister(charger->battery);
+ power_supply_unregister(charger->usb);
+
+ /* Release ADC channels */
+ iio_channel_release(charger->ibus_chan);
+ iio_channel_release(charger->vbus_chan);
+ iio_channel_release(charger->tjunc_chan);
+ iio_channel_release(charger->vbat_chan);
+
+ return 0;
+}
+
+static struct platform_driver da9150_charger_driver = {
+ .driver = {
+ .name = "da9150-charger",
+ },
+ .probe = da9150_charger_probe,
+ .remove = da9150_charger_remove,
+};
+
+module_platform_driver(da9150_charger_driver);
+
+MODULE_DESCRIPTION("Charger Driver for DA9150");
+MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/da9150-fg.c b/drivers/power/supply/da9150-fg.c
new file mode 100644
index 0000000..8b8ce97
--- /dev/null
+++ b/drivers/power/supply/da9150-fg.c
@@ -0,0 +1,579 @@
+/*
+ * DA9150 Fuel-Gauge Driver
+ *
+ * Copyright (c) 2015 Dialog Semiconductor
+ *
+ * Author: Adam Thomson <Adam.Thomson.Opensource@diasemi.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.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/list.h>
+#include <asm/div64.h>
+#include <linux/mfd/da9150/core.h>
+#include <linux/mfd/da9150/registers.h>
+
+/* Core2Wire */
+#define DA9150_QIF_READ (0x0 << 7)
+#define DA9150_QIF_WRITE (0x1 << 7)
+#define DA9150_QIF_CODE_MASK 0x7F
+
+#define DA9150_QIF_BYTE_SIZE 8
+#define DA9150_QIF_BYTE_MASK 0xFF
+#define DA9150_QIF_SHORT_SIZE 2
+#define DA9150_QIF_LONG_SIZE 4
+
+/* QIF Codes */
+#define DA9150_QIF_UAVG 6
+#define DA9150_QIF_UAVG_SIZE DA9150_QIF_LONG_SIZE
+#define DA9150_QIF_IAVG 8
+#define DA9150_QIF_IAVG_SIZE DA9150_QIF_LONG_SIZE
+#define DA9150_QIF_NTCAVG 12
+#define DA9150_QIF_NTCAVG_SIZE DA9150_QIF_LONG_SIZE
+#define DA9150_QIF_SHUNT_VAL 36
+#define DA9150_QIF_SHUNT_VAL_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_SD_GAIN 38
+#define DA9150_QIF_SD_GAIN_SIZE DA9150_QIF_LONG_SIZE
+#define DA9150_QIF_FCC_MAH 40
+#define DA9150_QIF_FCC_MAH_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_SOC_PCT 43
+#define DA9150_QIF_SOC_PCT_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_CHARGE_LIMIT 44
+#define DA9150_QIF_CHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_DISCHARGE_LIMIT 45
+#define DA9150_QIF_DISCHARGE_LIMIT_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_FW_MAIN_VER 118
+#define DA9150_QIF_FW_MAIN_VER_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_E_FG_STATUS 126
+#define DA9150_QIF_E_FG_STATUS_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_SYNC 127
+#define DA9150_QIF_SYNC_SIZE DA9150_QIF_SHORT_SIZE
+#define DA9150_QIF_MAX_CODES 128
+
+/* QIF Sync Timeout */
+#define DA9150_QIF_SYNC_TIMEOUT 1000
+#define DA9150_QIF_SYNC_RETRIES 10
+
+/* QIF E_FG_STATUS */
+#define DA9150_FG_IRQ_LOW_SOC_MASK (1 << 0)
+#define DA9150_FG_IRQ_HIGH_SOC_MASK (1 << 1)
+#define DA9150_FG_IRQ_SOC_MASK \
+ (DA9150_FG_IRQ_LOW_SOC_MASK | DA9150_FG_IRQ_HIGH_SOC_MASK)
+
+/* Private data */
+struct da9150_fg {
+ struct da9150 *da9150;
+ struct device *dev;
+
+ struct mutex io_lock;
+
+ struct power_supply *battery;
+ struct delayed_work work;
+ u32 interval;
+
+ int warn_soc;
+ int crit_soc;
+ int soc;
+};
+
+/* Battery Properties */
+static u32 da9150_fg_read_attr(struct da9150_fg *fg, u8 code, u8 size)
+
+{
+ u8 buf[size];
+ u8 read_addr;
+ u32 res = 0;
+ int i;
+
+ /* Set QIF code (READ mode) */
+ read_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_READ;
+
+ da9150_read_qif(fg->da9150, read_addr, size, buf);
+ for (i = 0; i < size; ++i)
+ res |= (buf[i] << (i * DA9150_QIF_BYTE_SIZE));
+
+ return res;
+}
+
+static void da9150_fg_write_attr(struct da9150_fg *fg, u8 code, u8 size,
+ u32 val)
+
+{
+ u8 buf[size];
+ u8 write_addr;
+ int i;
+
+ /* Set QIF code (WRITE mode) */
+ write_addr = (code & DA9150_QIF_CODE_MASK) | DA9150_QIF_WRITE;
+
+ for (i = 0; i < size; ++i) {
+ buf[i] = (val >> (i * DA9150_QIF_BYTE_SIZE)) &
+ DA9150_QIF_BYTE_MASK;
+ }
+ da9150_write_qif(fg->da9150, write_addr, size, buf);
+}
+
+/* Trigger QIF Sync to update QIF readable data */
+static void da9150_fg_read_sync_start(struct da9150_fg *fg)
+{
+ int i = 0;
+ u32 res = 0;
+
+ mutex_lock(&fg->io_lock);
+
+ /* Check if QIF sync already requested, and write to sync if not */
+ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE);
+ if (res > 0)
+ da9150_fg_write_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE, 0);
+
+ /* Wait for sync to complete */
+ res = 0;
+ while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
+ usleep_range(DA9150_QIF_SYNC_TIMEOUT,
+ DA9150_QIF_SYNC_TIMEOUT * 2);
+ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE);
+ }
+
+ /* Check if sync completed */
+ if (res == 0)
+ dev_err(fg->dev, "Failed to perform QIF read sync!\n");
+}
+
+/*
+ * Should always be called after QIF sync read has been performed, and all
+ * attributes required have been accessed.
+ */
+static inline void da9150_fg_read_sync_end(struct da9150_fg *fg)
+{
+ mutex_unlock(&fg->io_lock);
+}
+
+/* Sync read of single QIF attribute */
+static u32 da9150_fg_read_attr_sync(struct da9150_fg *fg, u8 code, u8 size)
+{
+ u32 val;
+
+ da9150_fg_read_sync_start(fg);
+ val = da9150_fg_read_attr(fg, code, size);
+ da9150_fg_read_sync_end(fg);
+
+ return val;
+}
+
+/* Wait for QIF Sync, write QIF data and wait for ack */
+static void da9150_fg_write_attr_sync(struct da9150_fg *fg, u8 code, u8 size,
+ u32 val)
+{
+ int i = 0;
+ u32 res = 0, sync_val;
+
+ mutex_lock(&fg->io_lock);
+
+ /* Check if QIF sync already requested */
+ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE);
+
+ /* Wait for an existing sync to complete */
+ while ((res == 0) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
+ usleep_range(DA9150_QIF_SYNC_TIMEOUT,
+ DA9150_QIF_SYNC_TIMEOUT * 2);
+ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE);
+ }
+
+ if (res == 0) {
+ dev_err(fg->dev, "Timeout waiting for existing QIF sync!\n");
+ mutex_unlock(&fg->io_lock);
+ return;
+ }
+
+ /* Write value for QIF code */
+ da9150_fg_write_attr(fg, code, size, val);
+
+ /* Wait for write acknowledgment */
+ i = 0;
+ sync_val = res;
+ while ((res == sync_val) && (i++ < DA9150_QIF_SYNC_RETRIES)) {
+ usleep_range(DA9150_QIF_SYNC_TIMEOUT,
+ DA9150_QIF_SYNC_TIMEOUT * 2);
+ res = da9150_fg_read_attr(fg, DA9150_QIF_SYNC,
+ DA9150_QIF_SYNC_SIZE);
+ }
+
+ mutex_unlock(&fg->io_lock);
+
+ /* Check write was actually successful */
+ if (res != (sync_val + 1))
+ dev_err(fg->dev, "Error performing QIF sync write for code %d\n",
+ code);
+}
+
+/* Power Supply attributes */
+static int da9150_fg_capacity(struct da9150_fg *fg,
+ union power_supply_propval *val)
+{
+ val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT,
+ DA9150_QIF_SOC_PCT_SIZE);
+
+ if (val->intval > 100)
+ val->intval = 100;
+
+ return 0;
+}
+
+static int da9150_fg_current_avg(struct da9150_fg *fg,
+ union power_supply_propval *val)
+{
+ u32 iavg, sd_gain, shunt_val;
+ u64 div, res;
+
+ da9150_fg_read_sync_start(fg);
+ iavg = da9150_fg_read_attr(fg, DA9150_QIF_IAVG,
+ DA9150_QIF_IAVG_SIZE);
+ shunt_val = da9150_fg_read_attr(fg, DA9150_QIF_SHUNT_VAL,
+ DA9150_QIF_SHUNT_VAL_SIZE);
+ sd_gain = da9150_fg_read_attr(fg, DA9150_QIF_SD_GAIN,
+ DA9150_QIF_SD_GAIN_SIZE);
+ da9150_fg_read_sync_end(fg);
+
+ div = (u64) (sd_gain * shunt_val * 65536ULL);
+ do_div(div, 1000000);
+ res = (u64) (iavg * 1000000ULL);
+ do_div(res, div);
+
+ val->intval = (int) res;
+
+ return 0;
+}
+
+static int da9150_fg_voltage_avg(struct da9150_fg *fg,
+ union power_supply_propval *val)
+{
+ u64 res;
+
+ val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_UAVG,
+ DA9150_QIF_UAVG_SIZE);
+
+ res = (u64) (val->intval * 186ULL);
+ do_div(res, 10000);
+ val->intval = (int) res;
+
+ return 0;
+}
+
+static int da9150_fg_charge_full(struct da9150_fg *fg,
+ union power_supply_propval *val)
+{
+ val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_FCC_MAH,
+ DA9150_QIF_FCC_MAH_SIZE);
+
+ val->intval = val->intval * 1000;
+
+ return 0;
+}
+
+/*
+ * Temperature reading from device is only valid if battery/system provides
+ * valid NTC to associated pin of DA9150 chip.
+ */
+static int da9150_fg_temp(struct da9150_fg *fg,
+ union power_supply_propval *val)
+{
+ val->intval = da9150_fg_read_attr_sync(fg, DA9150_QIF_NTCAVG,
+ DA9150_QIF_NTCAVG_SIZE);
+
+ val->intval = (val->intval * 10) / 1048576;
+
+ return 0;
+}
+
+static enum power_supply_property da9150_fg_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static int da9150_fg_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct da9150_fg *fg = dev_get_drvdata(psy->dev.parent);
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = da9150_fg_capacity(fg, val);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = da9150_fg_current_avg(fg, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = da9150_fg_voltage_avg(fg, val);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = da9150_fg_charge_full(fg, val);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = da9150_fg_temp(fg, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+/* Repeated SOC check */
+static bool da9150_fg_soc_changed(struct da9150_fg *fg)
+{
+ union power_supply_propval val;
+
+ da9150_fg_capacity(fg, &val);
+ if (val.intval != fg->soc) {
+ fg->soc = val.intval;
+ return true;
+ }
+
+ return false;
+}
+
+static void da9150_fg_work(struct work_struct *work)
+{
+ struct da9150_fg *fg = container_of(work, struct da9150_fg, work.work);
+
+ /* Report if SOC has changed */
+ if (da9150_fg_soc_changed(fg))
+ power_supply_changed(fg->battery);
+
+ schedule_delayed_work(&fg->work, msecs_to_jiffies(fg->interval));
+}
+
+/* SOC level event configuration */
+static void da9150_fg_soc_event_config(struct da9150_fg *fg)
+{
+ int soc;
+
+ soc = da9150_fg_read_attr_sync(fg, DA9150_QIF_SOC_PCT,
+ DA9150_QIF_SOC_PCT_SIZE);
+
+ if (soc > fg->warn_soc) {
+ /* If SOC > warn level, set discharge warn level event */
+ da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT,
+ DA9150_QIF_DISCHARGE_LIMIT_SIZE,
+ fg->warn_soc + 1);
+ } else if ((soc <= fg->warn_soc) && (soc > fg->crit_soc)) {
+ /*
+ * If SOC <= warn level, set discharge crit level event,
+ * and set charge warn level event.
+ */
+ da9150_fg_write_attr_sync(fg, DA9150_QIF_DISCHARGE_LIMIT,
+ DA9150_QIF_DISCHARGE_LIMIT_SIZE,
+ fg->crit_soc + 1);
+
+ da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT,
+ DA9150_QIF_CHARGE_LIMIT_SIZE,
+ fg->warn_soc);
+ } else if (soc <= fg->crit_soc) {
+ /* If SOC <= crit level, set charge crit level event */
+ da9150_fg_write_attr_sync(fg, DA9150_QIF_CHARGE_LIMIT,
+ DA9150_QIF_CHARGE_LIMIT_SIZE,
+ fg->crit_soc);
+ }
+}
+
+static irqreturn_t da9150_fg_irq(int irq, void *data)
+{
+ struct da9150_fg *fg = data;
+ u32 e_fg_status;
+
+ /* Read FG IRQ status info */
+ e_fg_status = da9150_fg_read_attr(fg, DA9150_QIF_E_FG_STATUS,
+ DA9150_QIF_E_FG_STATUS_SIZE);
+
+ /* Handle warning/critical threhold events */
+ if (e_fg_status & DA9150_FG_IRQ_SOC_MASK)
+ da9150_fg_soc_event_config(fg);
+
+ /* Clear any FG IRQs */
+ da9150_fg_write_attr(fg, DA9150_QIF_E_FG_STATUS,
+ DA9150_QIF_E_FG_STATUS_SIZE, e_fg_status);
+
+ return IRQ_HANDLED;
+}
+
+static struct da9150_fg_pdata *da9150_fg_dt_pdata(struct device *dev)
+{
+ struct device_node *fg_node = dev->of_node;
+ struct da9150_fg_pdata *pdata;
+
+ pdata = devm_kzalloc(dev, sizeof(struct da9150_fg_pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ of_property_read_u32(fg_node, "dlg,update-interval",
+ &pdata->update_interval);
+ of_property_read_u8(fg_node, "dlg,warn-soc-level",
+ &pdata->warn_soc_lvl);
+ of_property_read_u8(fg_node, "dlg,crit-soc-level",
+ &pdata->crit_soc_lvl);
+
+ return pdata;
+}
+
+static const struct power_supply_desc fg_desc = {
+ .name = "da9150-fg",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = da9150_fg_props,
+ .num_properties = ARRAY_SIZE(da9150_fg_props),
+ .get_property = da9150_fg_get_prop,
+};
+
+static int da9150_fg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct da9150 *da9150 = dev_get_drvdata(dev->parent);
+ struct da9150_fg_pdata *fg_pdata = dev_get_platdata(dev);
+ struct da9150_fg *fg;
+ int ver, irq, ret = 0;
+
+ fg = devm_kzalloc(dev, sizeof(*fg), GFP_KERNEL);
+ if (fg == NULL)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, fg);
+ fg->da9150 = da9150;
+ fg->dev = dev;
+
+ mutex_init(&fg->io_lock);
+
+ /* Enable QIF */
+ da9150_set_bits(da9150, DA9150_CORE2WIRE_CTRL_A, DA9150_FG_QIF_EN_MASK,
+ DA9150_FG_QIF_EN_MASK);
+
+ fg->battery = devm_power_supply_register(dev, &fg_desc, NULL);
+ if (IS_ERR(fg->battery)) {
+ ret = PTR_ERR(fg->battery);
+ return ret;
+ }
+
+ ver = da9150_fg_read_attr(fg, DA9150_QIF_FW_MAIN_VER,
+ DA9150_QIF_FW_MAIN_VER_SIZE);
+ dev_info(dev, "Version: 0x%x\n", ver);
+
+ /* Handle DT data if provided */
+ if (dev->of_node) {
+ fg_pdata = da9150_fg_dt_pdata(dev);
+ dev->platform_data = fg_pdata;
+ }
+
+ /* Handle any pdata provided */
+ if (fg_pdata) {
+ fg->interval = fg_pdata->update_interval;
+
+ if (fg_pdata->warn_soc_lvl > 100)
+ dev_warn(dev, "Invalid SOC warning level provided, Ignoring");
+ else
+ fg->warn_soc = fg_pdata->warn_soc_lvl;
+
+ if ((fg_pdata->crit_soc_lvl > 100) ||
+ (fg_pdata->crit_soc_lvl >= fg_pdata->warn_soc_lvl))
+ dev_warn(dev, "Invalid SOC critical level provided, Ignoring");
+ else
+ fg->crit_soc = fg_pdata->crit_soc_lvl;
+
+
+ }
+
+ /* Configure initial SOC level events */
+ da9150_fg_soc_event_config(fg);
+
+ /*
+ * If an interval period has been provided then setup repeating
+ * work for reporting data updates.
+ */
+ if (fg->interval) {
+ INIT_DELAYED_WORK(&fg->work, da9150_fg_work);
+ schedule_delayed_work(&fg->work,
+ msecs_to_jiffies(fg->interval));
+ }
+
+ /* Register IRQ */
+ irq = platform_get_irq_byname(pdev, "FG");
+ if (irq < 0) {
+ dev_err(dev, "Failed to get IRQ FG: %d\n", irq);
+ ret = irq;
+ goto irq_fail;
+ }
+
+ ret = devm_request_threaded_irq(dev, irq, NULL, da9150_fg_irq,
+ IRQF_ONESHOT, "FG", fg);
+ if (ret) {
+ dev_err(dev, "Failed to request IRQ %d: %d\n", irq, ret);
+ goto irq_fail;
+ }
+
+ return 0;
+
+irq_fail:
+ if (fg->interval)
+ cancel_delayed_work(&fg->work);
+
+ return ret;
+}
+
+static int da9150_fg_remove(struct platform_device *pdev)
+{
+ struct da9150_fg *fg = platform_get_drvdata(pdev);
+
+ if (fg->interval)
+ cancel_delayed_work(&fg->work);
+
+ return 0;
+}
+
+static int da9150_fg_resume(struct platform_device *pdev)
+{
+ struct da9150_fg *fg = platform_get_drvdata(pdev);
+
+ /*
+ * Trigger SOC check to happen now so as to indicate any value change
+ * since last check before suspend.
+ */
+ if (fg->interval)
+ flush_delayed_work(&fg->work);
+
+ return 0;
+}
+
+static struct platform_driver da9150_fg_driver = {
+ .driver = {
+ .name = "da9150-fuel-gauge",
+ },
+ .probe = da9150_fg_probe,
+ .remove = da9150_fg_remove,
+ .resume = da9150_fg_resume,
+};
+
+module_platform_driver(da9150_fg_driver);
+
+MODULE_DESCRIPTION("Fuel-Gauge Driver for DA9150");
+MODULE_AUTHOR("Adam Thomson <Adam.Thomson.Opensource@diasemi.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c
new file mode 100644
index 0000000..1722568
--- /dev/null
+++ b/drivers/power/supply/ds2760_battery.c
@@ -0,0 +1,648 @@
+/*
+ * Driver for batteries with DS2760 chips inside.
+ *
+ * Copyright © 2007 Anton Vorontsov
+ * 2004-2007 Matt Reimer
+ * 2004 Szabolcs Gyurko
+ *
+ * Use consistent with the GNU GPL is permitted,
+ * provided that this copyright notice is
+ * preserved in its entirety in all copies and derived works.
+ *
+ * Author: Anton Vorontsov <cbou@mail.ru>
+ * February 2007
+ *
+ * Matt Reimer <mreimer@vpop.net>
+ * April 2004, 2005, 2007
+ *
+ * Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>
+ * September 2004
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/jiffies.h>
+#include <linux/workqueue.h>
+#include <linux/pm.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include "../../w1/w1.h"
+#include "../../w1/slaves/w1_ds2760.h"
+
+struct ds2760_device_info {
+ struct device *dev;
+
+ /* DS2760 data, valid after calling ds2760_battery_read_status() */
+ unsigned long update_time; /* jiffies when data read */
+ char raw[DS2760_DATA_SIZE]; /* raw DS2760 data */
+ int voltage_raw; /* units of 4.88 mV */
+ int voltage_uV; /* units of µV */
+ int current_raw; /* units of 0.625 mA */
+ int current_uA; /* units of µA */
+ int accum_current_raw; /* units of 0.25 mAh */
+ int accum_current_uAh; /* units of µAh */
+ int temp_raw; /* units of 0.125 °C */
+ int temp_C; /* units of 0.1 °C */
+ int rated_capacity; /* units of µAh */
+ int rem_capacity; /* percentage */
+ int full_active_uAh; /* units of µAh */
+ int empty_uAh; /* units of µAh */
+ int life_sec; /* units of seconds */
+ int charge_status; /* POWER_SUPPLY_STATUS_* */
+
+ int full_counter;
+ struct power_supply *bat;
+ struct power_supply_desc bat_desc;
+ struct device *w1_dev;
+ struct workqueue_struct *monitor_wqueue;
+ struct delayed_work monitor_work;
+ struct delayed_work set_charged_work;
+};
+
+static unsigned int cache_time = 1000;
+module_param(cache_time, uint, 0644);
+MODULE_PARM_DESC(cache_time, "cache time in milliseconds");
+
+static bool pmod_enabled;
+module_param(pmod_enabled, bool, 0644);
+MODULE_PARM_DESC(pmod_enabled, "PMOD enable bit");
+
+static unsigned int rated_capacity;
+module_param(rated_capacity, uint, 0644);
+MODULE_PARM_DESC(rated_capacity, "rated battery capacity, 10*mAh or index");
+
+static unsigned int current_accum;
+module_param(current_accum, uint, 0644);
+MODULE_PARM_DESC(current_accum, "current accumulator value");
+
+/* Some batteries have their rated capacity stored a N * 10 mAh, while
+ * others use an index into this table. */
+static int rated_capacities[] = {
+ 0,
+ 920, /* Samsung */
+ 920, /* BYD */
+ 920, /* Lishen */
+ 920, /* NEC */
+ 1440, /* Samsung */
+ 1440, /* BYD */
+#ifdef CONFIG_MACH_H4700
+ 1800, /* HP iPAQ hx4700 3.7V 1800mAh (359113-001) */
+#else
+ 1440, /* Lishen */
+#endif
+ 1440, /* NEC */
+ 2880, /* Samsung */
+ 2880, /* BYD */
+ 2880, /* Lishen */
+ 2880, /* NEC */
+#ifdef CONFIG_MACH_H4700
+ 0,
+ 3600, /* HP iPAQ hx4700 3.7V 3600mAh (359114-001) */
+#endif
+};
+
+/* array is level at temps 0°C, 10°C, 20°C, 30°C, 40°C
+ * temp is in Celsius */
+static int battery_interpolate(int array[], int temp)
+{
+ int index, dt;
+
+ if (temp <= 0)
+ return array[0];
+ if (temp >= 40)
+ return array[4];
+
+ index = temp / 10;
+ dt = temp % 10;
+
+ return array[index] + (((array[index + 1] - array[index]) * dt) / 10);
+}
+
+static int ds2760_battery_read_status(struct ds2760_device_info *di)
+{
+ int ret, i, start, count, scale[5];
+
+ if (di->update_time && time_before(jiffies, di->update_time +
+ msecs_to_jiffies(cache_time)))
+ return 0;
+
+ /* The first time we read the entire contents of SRAM/EEPROM,
+ * but after that we just read the interesting bits that change. */
+ if (di->update_time == 0) {
+ start = 0;
+ count = DS2760_DATA_SIZE;
+ } else {
+ start = DS2760_VOLTAGE_MSB;
+ count = DS2760_TEMP_LSB - start + 1;
+ }
+
+ ret = w1_ds2760_read(di->w1_dev, di->raw + start, start, count);
+ if (ret != count) {
+ dev_warn(di->dev, "call to w1_ds2760_read failed (0x%p)\n",
+ di->w1_dev);
+ return 1;
+ }
+
+ di->update_time = jiffies;
+
+ /* DS2760 reports voltage in units of 4.88mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 4880. */
+ di->voltage_raw = (di->raw[DS2760_VOLTAGE_MSB] << 3) |
+ (di->raw[DS2760_VOLTAGE_LSB] >> 5);
+ di->voltage_uV = di->voltage_raw * 4880;
+
+ /* DS2760 reports current in signed units of 0.625mA, but the battery
+ * class reports in units of µA, so convert by multiplying by 625. */
+ di->current_raw =
+ (((signed char)di->raw[DS2760_CURRENT_MSB]) << 5) |
+ (di->raw[DS2760_CURRENT_LSB] >> 3);
+ di->current_uA = di->current_raw * 625;
+
+ /* DS2760 reports accumulated current in signed units of 0.25mAh. */
+ di->accum_current_raw =
+ (((signed char)di->raw[DS2760_CURRENT_ACCUM_MSB]) << 8) |
+ di->raw[DS2760_CURRENT_ACCUM_LSB];
+ di->accum_current_uAh = di->accum_current_raw * 250;
+
+ /* DS2760 reports temperature in signed units of 0.125°C, but the
+ * battery class reports in units of 1/10 °C, so we convert by
+ * multiplying by .125 * 10 = 1.25. */
+ di->temp_raw = (((signed char)di->raw[DS2760_TEMP_MSB]) << 3) |
+ (di->raw[DS2760_TEMP_LSB] >> 5);
+ di->temp_C = di->temp_raw + (di->temp_raw / 4);
+
+ /* At least some battery monitors (e.g. HP iPAQ) store the battery's
+ * maximum rated capacity. */
+ if (di->raw[DS2760_RATED_CAPACITY] < ARRAY_SIZE(rated_capacities))
+ di->rated_capacity = rated_capacities[
+ (unsigned int)di->raw[DS2760_RATED_CAPACITY]];
+ else
+ di->rated_capacity = di->raw[DS2760_RATED_CAPACITY] * 10;
+
+ di->rated_capacity *= 1000; /* convert to µAh */
+
+ /* Calculate the full level at the present temperature. */
+ di->full_active_uAh = di->raw[DS2760_ACTIVE_FULL] << 8 |
+ di->raw[DS2760_ACTIVE_FULL + 1];
+
+ /* If the full_active_uAh value is not given, fall back to the rated
+ * capacity. This is likely to happen when chips are not part of the
+ * battery pack and is therefore not bootstrapped. */
+ if (di->full_active_uAh == 0)
+ di->full_active_uAh = di->rated_capacity / 1000L;
+
+ scale[0] = di->full_active_uAh;
+ for (i = 1; i < 5; i++)
+ scale[i] = scale[i - 1] + di->raw[DS2760_ACTIVE_FULL + 1 + i];
+
+ di->full_active_uAh = battery_interpolate(scale, di->temp_C / 10);
+ di->full_active_uAh *= 1000; /* convert to µAh */
+
+ /* Calculate the empty level at the present temperature. */
+ scale[4] = di->raw[DS2760_ACTIVE_EMPTY + 4];
+ for (i = 3; i >= 0; i--)
+ scale[i] = scale[i + 1] + di->raw[DS2760_ACTIVE_EMPTY + i];
+
+ di->empty_uAh = battery_interpolate(scale, di->temp_C / 10);
+ di->empty_uAh *= 1000; /* convert to µAh */
+
+ if (di->full_active_uAh == di->empty_uAh)
+ di->rem_capacity = 0;
+ else
+ /* From Maxim Application Note 131: remaining capacity =
+ * ((ICA - Empty Value) / (Full Value - Empty Value)) x 100% */
+ di->rem_capacity = ((di->accum_current_uAh - di->empty_uAh) * 100L) /
+ (di->full_active_uAh - di->empty_uAh);
+
+ if (di->rem_capacity < 0)
+ di->rem_capacity = 0;
+ if (di->rem_capacity > 100)
+ di->rem_capacity = 100;
+
+ if (di->current_uA < -100L)
+ di->life_sec = -((di->accum_current_uAh - di->empty_uAh) * 36L)
+ / (di->current_uA / 100L);
+ else
+ di->life_sec = 0;
+
+ return 0;
+}
+
+static void ds2760_battery_set_current_accum(struct ds2760_device_info *di,
+ unsigned int acr_val)
+{
+ unsigned char acr[2];
+
+ /* acr is in units of 0.25 mAh */
+ acr_val *= 4L;
+ acr_val /= 1000;
+
+ acr[0] = acr_val >> 8;
+ acr[1] = acr_val & 0xff;
+
+ if (w1_ds2760_write(di->w1_dev, acr, DS2760_CURRENT_ACCUM_MSB, 2) < 2)
+ dev_warn(di->dev, "ACR write failed\n");
+}
+
+static void ds2760_battery_update_status(struct ds2760_device_info *di)
+{
+ int old_charge_status = di->charge_status;
+
+ ds2760_battery_read_status(di);
+
+ if (di->charge_status == POWER_SUPPLY_STATUS_UNKNOWN)
+ di->full_counter = 0;
+
+ if (power_supply_am_i_supplied(di->bat)) {
+ if (di->current_uA > 10000) {
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ di->full_counter = 0;
+ } else if (di->current_uA < -5000) {
+ if (di->charge_status != POWER_SUPPLY_STATUS_NOT_CHARGING)
+ dev_notice(di->dev, "not enough power to "
+ "charge\n");
+ di->charge_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ di->full_counter = 0;
+ } else if (di->current_uA < 10000 &&
+ di->charge_status != POWER_SUPPLY_STATUS_FULL) {
+
+ /* Don't consider the battery to be full unless
+ * we've seen the current < 10 mA at least two
+ * consecutive times. */
+
+ di->full_counter++;
+
+ if (di->full_counter < 2) {
+ di->charge_status = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ di->charge_status = POWER_SUPPLY_STATUS_FULL;
+ ds2760_battery_set_current_accum(di,
+ di->full_active_uAh);
+ }
+ }
+ } else {
+ di->charge_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ di->full_counter = 0;
+ }
+
+ if (di->charge_status != old_charge_status)
+ power_supply_changed(di->bat);
+}
+
+static void ds2760_battery_write_status(struct ds2760_device_info *di,
+ char status)
+{
+ if (status == di->raw[DS2760_STATUS_REG])
+ return;
+
+ w1_ds2760_write(di->w1_dev, &status, DS2760_STATUS_WRITE_REG, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+}
+
+static void ds2760_battery_write_rated_capacity(struct ds2760_device_info *di,
+ unsigned char rated_capacity)
+{
+ if (rated_capacity == di->raw[DS2760_RATED_CAPACITY])
+ return;
+
+ w1_ds2760_write(di->w1_dev, &rated_capacity, DS2760_RATED_CAPACITY, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+}
+
+static void ds2760_battery_write_active_full(struct ds2760_device_info *di,
+ int active_full)
+{
+ unsigned char tmp[2] = {
+ active_full >> 8,
+ active_full & 0xff
+ };
+
+ if (tmp[0] == di->raw[DS2760_ACTIVE_FULL] &&
+ tmp[1] == di->raw[DS2760_ACTIVE_FULL + 1])
+ return;
+
+ w1_ds2760_write(di->w1_dev, tmp, DS2760_ACTIVE_FULL, sizeof(tmp));
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK0);
+
+ /* Write to the di->raw[] buffer directly - the DS2760_ACTIVE_FULL
+ * values won't be read back by ds2760_battery_read_status() */
+ di->raw[DS2760_ACTIVE_FULL] = tmp[0];
+ di->raw[DS2760_ACTIVE_FULL + 1] = tmp[1];
+}
+
+static void ds2760_battery_work(struct work_struct *work)
+{
+ struct ds2760_device_info *di = container_of(work,
+ struct ds2760_device_info, monitor_work.work);
+ const int interval = HZ * 60;
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ ds2760_battery_update_status(di);
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, interval);
+}
+
+static void ds2760_battery_external_power_changed(struct power_supply *psy)
+{
+ struct ds2760_device_info *di = power_supply_get_drvdata(psy);
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10);
+}
+
+
+static void ds2760_battery_set_charged_work(struct work_struct *work)
+{
+ char bias;
+ struct ds2760_device_info *di = container_of(work,
+ struct ds2760_device_info, set_charged_work.work);
+
+ dev_dbg(di->dev, "%s\n", __func__);
+
+ ds2760_battery_read_status(di);
+
+ /* When we get notified by external circuitry that the battery is
+ * considered fully charged now, we know that there is no current
+ * flow any more. However, the ds2760's internal current meter is
+ * too inaccurate to rely on - spec say something ~15% failure.
+ * Hence, we use the current offset bias register to compensate
+ * that error.
+ */
+
+ if (!power_supply_am_i_supplied(di->bat))
+ return;
+
+ bias = (signed char) di->current_raw +
+ (signed char) di->raw[DS2760_CURRENT_OFFSET_BIAS];
+
+ dev_dbg(di->dev, "%s: bias = %d\n", __func__, bias);
+
+ w1_ds2760_write(di->w1_dev, &bias, DS2760_CURRENT_OFFSET_BIAS, 1);
+ w1_ds2760_store_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+ w1_ds2760_recall_eeprom(di->w1_dev, DS2760_EEPROM_BLOCK1);
+
+ /* Write to the di->raw[] buffer directly - the CURRENT_OFFSET_BIAS
+ * value won't be read back by ds2760_battery_read_status() */
+ di->raw[DS2760_CURRENT_OFFSET_BIAS] = bias;
+}
+
+static void ds2760_battery_set_charged(struct power_supply *psy)
+{
+ struct ds2760_device_info *di = power_supply_get_drvdata(psy);
+
+ /* postpone the actual work by 20 secs. This is for debouncing GPIO
+ * signals and to let the current value settle. See AN4188. */
+ mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20);
+}
+
+static int ds2760_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct ds2760_device_info *di = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = di->charge_status;
+ return 0;
+ default:
+ break;
+ }
+
+ ds2760_battery_read_status(di);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = di->voltage_uV;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = di->current_uA;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = di->rated_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = di->full_active_uAh;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_EMPTY:
+ val->intval = di->empty_uAh;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = di->accum_current_uAh;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = di->temp_C;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW:
+ val->intval = di->life_sec;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = di->rem_capacity;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int ds2760_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ds2760_device_info *di = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ /* the interface counts in uAh, convert the value */
+ ds2760_battery_write_active_full(di, val->intval / 1000L);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ /* ds2760_battery_set_current_accum() does the conversion */
+ ds2760_battery_set_current_accum(di, val->intval);
+ break;
+
+ default:
+ return -EPERM;
+ }
+
+ return 0;
+}
+
+static int ds2760_battery_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ return 1;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property ds2760_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static int ds2760_battery_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = {};
+ char status;
+ int retval = 0;
+ struct ds2760_device_info *di;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di) {
+ retval = -ENOMEM;
+ goto di_alloc_failed;
+ }
+
+ platform_set_drvdata(pdev, di);
+
+ di->dev = &pdev->dev;
+ di->w1_dev = pdev->dev.parent;
+ di->bat_desc.name = dev_name(&pdev->dev);
+ di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->bat_desc.properties = ds2760_battery_props;
+ di->bat_desc.num_properties = ARRAY_SIZE(ds2760_battery_props);
+ di->bat_desc.get_property = ds2760_battery_get_property;
+ di->bat_desc.set_property = ds2760_battery_set_property;
+ di->bat_desc.property_is_writeable =
+ ds2760_battery_property_is_writeable;
+ di->bat_desc.set_charged = ds2760_battery_set_charged;
+ di->bat_desc.external_power_changed =
+ ds2760_battery_external_power_changed;
+
+ psy_cfg.drv_data = di;
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* enable sleep mode feature */
+ ds2760_battery_read_status(di);
+ status = di->raw[DS2760_STATUS_REG];
+ if (pmod_enabled)
+ status |= DS2760_STATUS_PMOD;
+ else
+ status &= ~DS2760_STATUS_PMOD;
+
+ ds2760_battery_write_status(di, status);
+
+ /* set rated capacity from module param */
+ if (rated_capacity)
+ ds2760_battery_write_rated_capacity(di, rated_capacity);
+
+ /* set current accumulator if given as parameter.
+ * this should only be done for bootstrapping the value */
+ if (current_accum)
+ ds2760_battery_set_current_accum(di, current_accum);
+
+ di->bat = power_supply_register(&pdev->dev, &di->bat_desc, &psy_cfg);
+ if (IS_ERR(di->bat)) {
+ dev_err(di->dev, "failed to register battery\n");
+ retval = PTR_ERR(di->bat);
+ goto batt_failed;
+ }
+
+ INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work);
+ INIT_DELAYED_WORK(&di->set_charged_work,
+ ds2760_battery_set_charged_work);
+ di->monitor_wqueue = alloc_ordered_workqueue(dev_name(&pdev->dev),
+ WQ_MEM_RECLAIM);
+ if (!di->monitor_wqueue) {
+ retval = -ESRCH;
+ goto workqueue_failed;
+ }
+ queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ * 1);
+
+ goto success;
+
+workqueue_failed:
+ power_supply_unregister(di->bat);
+batt_failed:
+di_alloc_failed:
+success:
+ return retval;
+}
+
+static int ds2760_battery_remove(struct platform_device *pdev)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&di->monitor_work);
+ cancel_delayed_work_sync(&di->set_charged_work);
+ destroy_workqueue(di->monitor_wqueue);
+ power_supply_unregister(di->bat);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+
+static int ds2760_battery_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ return 0;
+}
+
+static int ds2760_battery_resume(struct platform_device *pdev)
+{
+ struct ds2760_device_info *di = platform_get_drvdata(pdev);
+
+ di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ power_supply_changed(di->bat);
+
+ mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ);
+
+ return 0;
+}
+
+#else
+
+#define ds2760_battery_suspend NULL
+#define ds2760_battery_resume NULL
+
+#endif /* CONFIG_PM */
+
+MODULE_ALIAS("platform:ds2760-battery");
+
+static struct platform_driver ds2760_battery_driver = {
+ .driver = {
+ .name = "ds2760-battery",
+ },
+ .probe = ds2760_battery_probe,
+ .remove = ds2760_battery_remove,
+ .suspend = ds2760_battery_suspend,
+ .resume = ds2760_battery_resume,
+};
+
+module_platform_driver(ds2760_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Szabolcs Gyurko <szabolcs.gyurko@tlt.hu>, "
+ "Matt Reimer <mreimer@vpop.net>, "
+ "Anton Vorontsov <cbou@mail.ru>");
+MODULE_DESCRIPTION("ds2760 battery driver");
diff --git a/drivers/power/supply/ds2780_battery.c b/drivers/power/supply/ds2780_battery.c
new file mode 100644
index 0000000..1b3b6fa
--- /dev/null
+++ b/drivers/power/supply/ds2780_battery.c
@@ -0,0 +1,838 @@
+/*
+ * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC
+ *
+ * Copyright (C) 2010 Indesign, LLC
+ *
+ * Author: Clifton Barnes <cabarnes@indesign-llc.com>
+ *
+ * Based on ds2760_battery and ds2782_battery drivers
+ *
+ * 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/slab.h>
+#include <linux/param.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+
+#include "../../w1/w1.h"
+#include "../../w1/slaves/w1_ds2780.h"
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2780_CURRENT_UNITS 1563
+/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */
+#define DS2780_CHARGE_UNITS 6250
+/* Number of bytes in user EEPROM space */
+#define DS2780_USER_EEPROM_SIZE (DS2780_EEPROM_BLOCK0_END - \
+ DS2780_EEPROM_BLOCK0_START + 1)
+/* Number of bytes in parameter EEPROM space */
+#define DS2780_PARAM_EEPROM_SIZE (DS2780_EEPROM_BLOCK1_END - \
+ DS2780_EEPROM_BLOCK1_START + 1)
+
+struct ds2780_device_info {
+ struct device *dev;
+ struct power_supply *bat;
+ struct power_supply_desc bat_desc;
+ struct device *w1_dev;
+};
+
+enum current_types {
+ CURRENT_NOW,
+ CURRENT_AVG,
+};
+
+static const char model[] = "DS2780";
+static const char manufacturer[] = "Maxim/Dallas";
+
+static inline struct ds2780_device_info *
+to_ds2780_device_info(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static inline struct power_supply *to_power_supply(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline int ds2780_battery_io(struct ds2780_device_info *dev_info,
+ char *buf, int addr, size_t count, int io)
+{
+ return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, io);
+}
+
+static inline int ds2780_read8(struct ds2780_device_info *dev_info, u8 *val,
+ int addr)
+{
+ return ds2780_battery_io(dev_info, val, addr, sizeof(u8), 0);
+}
+
+static int ds2780_read16(struct ds2780_device_info *dev_info, s16 *val,
+ int addr)
+{
+ int ret;
+ u8 raw[2];
+
+ ret = ds2780_battery_io(dev_info, raw, addr, sizeof(raw), 0);
+ if (ret < 0)
+ return ret;
+
+ *val = (raw[0] << 8) | raw[1];
+
+ return 0;
+}
+
+static inline int ds2780_read_block(struct ds2780_device_info *dev_info,
+ u8 *val, int addr, size_t count)
+{
+ return ds2780_battery_io(dev_info, val, addr, count, 0);
+}
+
+static inline int ds2780_write(struct ds2780_device_info *dev_info, u8 *val,
+ int addr, size_t count)
+{
+ return ds2780_battery_io(dev_info, val, addr, count, 1);
+}
+
+static inline int ds2780_store_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_COPY_DATA);
+}
+
+static inline int ds2780_recall_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2780_eeprom_cmd(dev, addr, W1_DS2780_RECALL_DATA);
+}
+
+static int ds2780_save_eeprom(struct ds2780_device_info *dev_info, int reg)
+{
+ int ret;
+
+ ret = ds2780_store_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_recall_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* Set sense resistor value in mhos */
+static int ds2780_set_sense_register(struct ds2780_device_info *dev_info,
+ u8 conductance)
+{
+ int ret;
+
+ ret = ds2780_write(dev_info, &conductance,
+ DS2780_RSNSP_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_RSNSP_REG);
+}
+
+/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2780_get_rsgain_register(struct ds2780_device_info *dev_info,
+ u16 *rsgain)
+{
+ return ds2780_read16(dev_info, rsgain, DS2780_RSGAIN_MSB_REG);
+}
+
+/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2780_set_rsgain_register(struct ds2780_device_info *dev_info,
+ u16 rsgain)
+{
+ int ret;
+ u8 raw[] = {rsgain >> 8, rsgain & 0xFF};
+
+ ret = ds2780_write(dev_info, raw,
+ DS2780_RSGAIN_MSB_REG, sizeof(raw));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_RSGAIN_MSB_REG);
+}
+
+static int ds2780_get_voltage(struct ds2780_device_info *dev_info,
+ int *voltage_uV)
+{
+ int ret;
+ s16 voltage_raw;
+
+ /*
+ * The voltage value is located in 10 bits across the voltage MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the voltage value is in bit 7 of the voltage MSB register
+ * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the
+ * voltage MSB register
+ * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the
+ * voltage LSB register
+ */
+ ret = ds2780_read16(dev_info, &voltage_raw,
+ DS2780_VOLT_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * DS2780 reports voltage in units of 4.88mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 4880.
+ */
+ *voltage_uV = (voltage_raw / 32) * 4880;
+ return 0;
+}
+
+static int ds2780_get_temperature(struct ds2780_device_info *dev_info,
+ int *temperature)
+{
+ int ret;
+ s16 temperature_raw;
+
+ /*
+ * The temperature value is located in 10 bits across the temperature
+ * MSB and LSB registers in two's compliment form
+ * Sign bit of the temperature value is in bit 7 of the temperature
+ * MSB register
+ * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the
+ * temperature MSB register
+ * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the
+ * temperature LSB register
+ */
+ ret = ds2780_read16(dev_info, &temperature_raw,
+ DS2780_TEMP_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Temperature is measured in units of 0.125 degrees celcius, the
+ * power_supply class measures temperature in tenths of degrees
+ * celsius. The temperature value is stored as a 10 bit number, plus
+ * sign in the upper bits of a 16 bit register.
+ */
+ *temperature = ((temperature_raw / 32) * 125) / 100;
+ return 0;
+}
+
+static int ds2780_get_current(struct ds2780_device_info *dev_info,
+ enum current_types type, int *current_uA)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw, reg_msb;
+
+ /*
+ * The units of measurement for current are dependent on the value of
+ * the sense resistor.
+ */
+ ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -EINVAL;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ if (type == CURRENT_NOW)
+ reg_msb = DS2780_CURRENT_MSB_REG;
+ else if (type == CURRENT_AVG)
+ reg_msb = DS2780_IAVG_MSB_REG;
+ else
+ return -EINVAL;
+
+ /*
+ * The current value is located in 16 bits across the current MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the current value is in bit 7 of the current MSB register
+ * Bits 14 - 8 of the current value are in bits 6 - 0 of the current
+ * MSB register
+ * Bits 7 - 0 of the current value are in bits 7 - 0 of the current
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &current_raw, reg_msb);
+ if (ret < 0)
+ return ret;
+
+ *current_uA = current_raw * (DS2780_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2780_get_accumulated_current(struct ds2780_device_info *dev_info,
+ int *accumulated_current)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw;
+
+ /*
+ * The units of measurement for accumulated current are dependent on
+ * the value of the sense resistor.
+ */
+ ret = ds2780_read8(dev_info, &sense_res_raw, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -ENXIO;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ /*
+ * The ACR value is located in 16 bits across the ACR MSB and
+ * LSB registers
+ * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR
+ * MSB register
+ * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &current_raw, DS2780_ACR_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ *accumulated_current = current_raw * (DS2780_CHARGE_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2780_get_capacity(struct ds2780_device_info *dev_info,
+ int *capacity)
+{
+ int ret;
+ u8 raw;
+
+ ret = ds2780_read8(dev_info, &raw, DS2780_RARC_REG);
+ if (ret < 0)
+ return ret;
+
+ *capacity = raw;
+ return raw;
+}
+
+static int ds2780_get_status(struct ds2780_device_info *dev_info, int *status)
+{
+ int ret, current_uA, capacity;
+
+ ret = ds2780_get_current(dev_info, CURRENT_NOW, &current_uA);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_get_capacity(dev_info, &capacity);
+ if (ret < 0)
+ return ret;
+
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA == 0)
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (current_uA < 0)
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+
+ return 0;
+}
+
+static int ds2780_get_charge_now(struct ds2780_device_info *dev_info,
+ int *charge_now)
+{
+ int ret;
+ u16 charge_raw;
+
+ /*
+ * The RAAC value is located in 16 bits across the RAAC MSB and
+ * LSB registers
+ * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC
+ * MSB register
+ * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC
+ * LSB register
+ */
+ ret = ds2780_read16(dev_info, &charge_raw, DS2780_RAAC_MSB_REG);
+ if (ret < 0)
+ return ret;
+
+ *charge_now = charge_raw * 1600;
+ return 0;
+}
+
+static int ds2780_get_control_register(struct ds2780_device_info *dev_info,
+ u8 *control_reg)
+{
+ return ds2780_read8(dev_info, control_reg, DS2780_CONTROL_REG);
+}
+
+static int ds2780_set_control_register(struct ds2780_device_info *dev_info,
+ u8 control_reg)
+{
+ int ret;
+
+ ret = ds2780_write(dev_info, &control_reg,
+ DS2780_CONTROL_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2780_save_eeprom(dev_info, DS2780_CONTROL_REG);
+}
+
+static int ds2780_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ds2780_get_voltage(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds2780_get_temperature(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ds2780_get_current(dev_info, CURRENT_NOW, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = ds2780_get_current(dev_info, CURRENT_AVG, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds2780_get_status(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = ds2780_get_capacity(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = ds2780_get_accumulated_current(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = ds2780_get_charge_now(dev_info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds2780_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+};
+
+static ssize_t ds2780_get_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 control_reg;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ /* Get power mode */
+ ret = ds2780_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n",
+ !!(control_reg & DS2780_CONTROL_REG_PMOD));
+}
+
+static ssize_t ds2780_set_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 control_reg, new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ /* Set power mode */
+ ret = ds2780_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ if (new_setting)
+ control_reg |= DS2780_CONTROL_REG_PMOD;
+ else
+ control_reg &= ~DS2780_CONTROL_REG_PMOD;
+
+ ret = ds2780_set_control_register(dev_info, control_reg);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sense_resistor;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_read8(dev_info, &sense_resistor, DS2780_RSNSP_REG);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sense_resistor);
+ return ret;
+}
+
+static ssize_t ds2780_set_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_set_sense_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u16 rsgain;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_get_rsgain_register(dev_info, &rsgain);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", rsgain);
+}
+
+static ssize_t ds2780_set_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u16 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou16(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ /* Gain can only be from 0 to 1.999 in steps of .001 */
+ if (new_setting > 1999) {
+ dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2780_set_rsgain_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_get_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sfr;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = ds2780_read8(dev_info, &sfr, DS2780_SFR_REG);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sfr & DS2780_SFR_REG_PIOSC);
+ return ret;
+}
+
+static ssize_t ds2780_set_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2780_write(dev_info, &new_setting,
+ DS2780_SFR_REG, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2780_read_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ return ds2780_read_block(dev_info, buf,
+ DS2780_EEPROM_BLOCK1_START + off, count);
+}
+
+static ssize_t ds2780_write_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+ int ret;
+
+ ret = ds2780_write(dev_info, buf,
+ DS2780_EEPROM_BLOCK1_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK1_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2780_param_eeprom_bin_attr = {
+ .attr = {
+ .name = "param_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2780_PARAM_EEPROM_SIZE,
+ .read = ds2780_read_param_eeprom_bin,
+ .write = ds2780_write_param_eeprom_bin,
+};
+
+static ssize_t ds2780_read_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+
+ return ds2780_read_block(dev_info, buf,
+ DS2780_EEPROM_BLOCK0_START + off, count);
+}
+
+static ssize_t ds2780_write_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2780_device_info *dev_info = to_ds2780_device_info(psy);
+ int ret;
+
+ ret = ds2780_write(dev_info, buf,
+ DS2780_EEPROM_BLOCK0_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2780_save_eeprom(dev_info, DS2780_EEPROM_BLOCK0_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2780_user_eeprom_bin_attr = {
+ .attr = {
+ .name = "user_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2780_USER_EEPROM_SIZE,
+ .read = ds2780_read_user_eeprom_bin,
+ .write = ds2780_write_user_eeprom_bin,
+};
+
+static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2780_get_pmod_enabled,
+ ds2780_set_pmod_enabled);
+static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR,
+ ds2780_get_sense_resistor_value, ds2780_set_sense_resistor_value);
+static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2780_get_rsgain_setting,
+ ds2780_set_rsgain_setting);
+static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2780_get_pio_pin,
+ ds2780_set_pio_pin);
+
+
+static struct attribute *ds2780_attributes[] = {
+ &dev_attr_pmod_enabled.attr,
+ &dev_attr_sense_resistor_value.attr,
+ &dev_attr_rsgain_setting.attr,
+ &dev_attr_pio_pin.attr,
+ NULL
+};
+
+static const struct attribute_group ds2780_attr_group = {
+ .attrs = ds2780_attributes,
+};
+
+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;
+ }
+
+ platform_set_drvdata(pdev, dev_info);
+
+ dev_info->dev = &pdev->dev;
+ dev_info->w1_dev = pdev->dev.parent;
+ dev_info->bat_desc.name = dev_name(&pdev->dev);
+ dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ dev_info->bat_desc.properties = ds2780_battery_props;
+ dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2780_battery_props);
+ dev_info->bat_desc.get_property = ds2780_battery_get_property;
+
+ psy_cfg.drv_data = dev_info;
+
+ dev_info->bat = 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;
+ }
+
+ 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;
+}
+
+static struct platform_driver ds2780_battery_driver = {
+ .driver = {
+ .name = "ds2780-battery",
+ },
+ .probe = ds2780_battery_probe,
+ .remove = ds2780_battery_remove,
+};
+
+module_platform_driver(ds2780_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Clifton Barnes <cabarnes@indesign-llc.com>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver");
+MODULE_ALIAS("platform:ds2780-battery");
diff --git a/drivers/power/supply/ds2781_battery.c b/drivers/power/supply/ds2781_battery.c
new file mode 100644
index 0000000..cc01491
--- /dev/null
+++ b/drivers/power/supply/ds2781_battery.c
@@ -0,0 +1,839 @@
+/*
+ * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC
+ *
+ * Author: Renata Sayakhova <renata@oktetlabs.ru>
+ *
+ * Based on ds2780_battery drivers
+ *
+ * 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/slab.h>
+#include <linux/param.h>
+#include <linux/pm.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/idr.h>
+
+#include "../../w1/w1.h"
+#include "../../w1/slaves/w1_ds2781.h"
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2781_CURRENT_UNITS 1563
+/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */
+#define DS2781_CHARGE_UNITS 6250
+/* Number of bytes in user EEPROM space */
+#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \
+ DS2781_EEPROM_BLOCK0_START + 1)
+/* Number of bytes in parameter EEPROM space */
+#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \
+ DS2781_EEPROM_BLOCK1_START + 1)
+
+struct ds2781_device_info {
+ struct device *dev;
+ struct power_supply *bat;
+ struct power_supply_desc bat_desc;
+ struct device *w1_dev;
+};
+
+enum current_types {
+ CURRENT_NOW,
+ CURRENT_AVG,
+};
+
+static const char model[] = "DS2781";
+static const char manufacturer[] = "Maxim/Dallas";
+
+static inline struct ds2781_device_info *
+to_ds2781_device_info(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static inline struct power_supply *to_power_supply(struct device *dev)
+{
+ return dev_get_drvdata(dev);
+}
+
+static inline int ds2781_battery_io(struct ds2781_device_info *dev_info,
+ char *buf, int addr, size_t count, int io)
+{
+ return w1_ds2781_io(dev_info->w1_dev, buf, addr, count, io);
+}
+
+static int w1_ds2781_read(struct ds2781_device_info *dev_info, char *buf,
+ int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, buf, addr, count, 0);
+}
+
+static inline int ds2781_read8(struct ds2781_device_info *dev_info, u8 *val,
+ int addr)
+{
+ return ds2781_battery_io(dev_info, val, addr, sizeof(u8), 0);
+}
+
+static int ds2781_read16(struct ds2781_device_info *dev_info, s16 *val,
+ int addr)
+{
+ int ret;
+ u8 raw[2];
+
+ ret = ds2781_battery_io(dev_info, raw, addr, sizeof(raw), 0);
+ if (ret < 0)
+ return ret;
+
+ *val = (raw[0] << 8) | raw[1];
+
+ return 0;
+}
+
+static inline int ds2781_read_block(struct ds2781_device_info *dev_info,
+ u8 *val, int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, val, addr, count, 0);
+}
+
+static inline int ds2781_write(struct ds2781_device_info *dev_info, u8 *val,
+ int addr, size_t count)
+{
+ return ds2781_battery_io(dev_info, val, addr, count, 1);
+}
+
+static inline int ds2781_store_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_COPY_DATA);
+}
+
+static inline int ds2781_recall_eeprom(struct device *dev, int addr)
+{
+ return w1_ds2781_eeprom_cmd(dev, addr, W1_DS2781_RECALL_DATA);
+}
+
+static int ds2781_save_eeprom(struct ds2781_device_info *dev_info, int reg)
+{
+ int ret;
+
+ ret = ds2781_store_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_recall_eeprom(dev_info->w1_dev, reg);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+/* Set sense resistor value in mhos */
+static int ds2781_set_sense_register(struct ds2781_device_info *dev_info,
+ u8 conductance)
+{
+ int ret;
+
+ ret = ds2781_write(dev_info, &conductance,
+ DS2781_RSNSP, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_RSNSP);
+}
+
+/* Get RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2781_get_rsgain_register(struct ds2781_device_info *dev_info,
+ u16 *rsgain)
+{
+ return ds2781_read16(dev_info, rsgain, DS2781_RSGAIN_MSB);
+}
+
+/* Set RSGAIN value from 0 to 1.999 in steps of 0.001 */
+static int ds2781_set_rsgain_register(struct ds2781_device_info *dev_info,
+ u16 rsgain)
+{
+ int ret;
+ u8 raw[] = {rsgain >> 8, rsgain & 0xFF};
+
+ ret = ds2781_write(dev_info, raw,
+ DS2781_RSGAIN_MSB, sizeof(raw));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_RSGAIN_MSB);
+}
+
+static int ds2781_get_voltage(struct ds2781_device_info *dev_info,
+ int *voltage_uV)
+{
+ int ret;
+ char val[2];
+ int voltage_raw;
+
+ ret = w1_ds2781_read(dev_info, val, DS2781_VOLT_MSB, 2 * sizeof(u8));
+ if (ret < 0)
+ return ret;
+ /*
+ * The voltage value is located in 10 bits across the voltage MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the voltage value is in bit 7 of the voltage MSB register
+ * Bits 9 - 3 of the voltage value are in bits 6 - 0 of the
+ * voltage MSB register
+ * Bits 2 - 0 of the voltage value are in bits 7 - 5 of the
+ * voltage LSB register
+ */
+ voltage_raw = (val[0] << 3) |
+ (val[1] >> 5);
+
+ /* DS2781 reports voltage in units of 9.76mV, but the battery class
+ * reports in units of uV, so convert by multiplying by 9760. */
+ *voltage_uV = voltage_raw * 9760;
+
+ return 0;
+}
+
+static int ds2781_get_temperature(struct ds2781_device_info *dev_info,
+ int *temp)
+{
+ int ret;
+ char val[2];
+ int temp_raw;
+
+ ret = w1_ds2781_read(dev_info, val, DS2781_TEMP_MSB, 2 * sizeof(u8));
+ if (ret < 0)
+ return ret;
+ /*
+ * The temperature value is located in 10 bits across the temperature
+ * MSB and LSB registers in two's compliment form
+ * Sign bit of the temperature value is in bit 7 of the temperature
+ * MSB register
+ * Bits 9 - 3 of the temperature value are in bits 6 - 0 of the
+ * temperature MSB register
+ * Bits 2 - 0 of the temperature value are in bits 7 - 5 of the
+ * temperature LSB register
+ */
+ temp_raw = ((val[0]) << 3) |
+ (val[1] >> 5);
+ *temp = temp_raw + (temp_raw / 4);
+
+ return 0;
+}
+
+static int ds2781_get_current(struct ds2781_device_info *dev_info,
+ enum current_types type, int *current_uA)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw, reg_msb;
+
+ /*
+ * The units of measurement for current are dependent on the value of
+ * the sense resistor.
+ */
+ ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -EINVAL;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ if (type == CURRENT_NOW)
+ reg_msb = DS2781_CURRENT_MSB;
+ else if (type == CURRENT_AVG)
+ reg_msb = DS2781_IAVG_MSB;
+ else
+ return -EINVAL;
+
+ /*
+ * The current value is located in 16 bits across the current MSB
+ * and LSB registers in two's compliment form
+ * Sign bit of the current value is in bit 7 of the current MSB register
+ * Bits 14 - 8 of the current value are in bits 6 - 0 of the current
+ * MSB register
+ * Bits 7 - 0 of the current value are in bits 7 - 0 of the current
+ * LSB register
+ */
+ ret = ds2781_read16(dev_info, &current_raw, reg_msb);
+ if (ret < 0)
+ return ret;
+
+ *current_uA = current_raw * (DS2781_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2781_get_accumulated_current(struct ds2781_device_info *dev_info,
+ int *accumulated_current)
+{
+ int ret, sense_res;
+ s16 current_raw;
+ u8 sense_res_raw;
+
+ /*
+ * The units of measurement for accumulated current are dependent on
+ * the value of the sense resistor.
+ */
+ ret = ds2781_read8(dev_info, &sense_res_raw, DS2781_RSNSP);
+ if (ret < 0)
+ return ret;
+
+ if (sense_res_raw == 0) {
+ dev_err(dev_info->dev, "sense resistor value is 0\n");
+ return -EINVAL;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ /*
+ * The ACR value is located in 16 bits across the ACR MSB and
+ * LSB registers
+ * Bits 15 - 8 of the ACR value are in bits 7 - 0 of the ACR
+ * MSB register
+ * Bits 7 - 0 of the ACR value are in bits 7 - 0 of the ACR
+ * LSB register
+ */
+ ret = ds2781_read16(dev_info, &current_raw, DS2781_ACR_MSB);
+ if (ret < 0)
+ return ret;
+
+ *accumulated_current = current_raw * (DS2781_CHARGE_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2781_get_capacity(struct ds2781_device_info *dev_info,
+ int *capacity)
+{
+ int ret;
+ u8 raw;
+
+ ret = ds2781_read8(dev_info, &raw, DS2781_RARC);
+ if (ret < 0)
+ return ret;
+
+ *capacity = raw;
+ return 0;
+}
+
+static int ds2781_get_status(struct ds2781_device_info *dev_info, int *status)
+{
+ int ret, current_uA, capacity;
+
+ ret = ds2781_get_current(dev_info, CURRENT_NOW, &current_uA);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_get_capacity(dev_info, &capacity);
+ if (ret < 0)
+ return ret;
+
+ if (power_supply_am_i_supplied(dev_info->bat)) {
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA > 50000)
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ return 0;
+}
+
+static int ds2781_get_charge_now(struct ds2781_device_info *dev_info,
+ int *charge_now)
+{
+ int ret;
+ u16 charge_raw;
+
+ /*
+ * The RAAC value is located in 16 bits across the RAAC MSB and
+ * LSB registers
+ * Bits 15 - 8 of the RAAC value are in bits 7 - 0 of the RAAC
+ * MSB register
+ * Bits 7 - 0 of the RAAC value are in bits 7 - 0 of the RAAC
+ * LSB register
+ */
+ ret = ds2781_read16(dev_info, &charge_raw, DS2781_RAAC_MSB);
+ if (ret < 0)
+ return ret;
+
+ *charge_now = charge_raw * 1600;
+ return 0;
+}
+
+static int ds2781_get_control_register(struct ds2781_device_info *dev_info,
+ u8 *control_reg)
+{
+ return ds2781_read8(dev_info, control_reg, DS2781_CONTROL);
+}
+
+static int ds2781_set_control_register(struct ds2781_device_info *dev_info,
+ u8 control_reg)
+{
+ int ret;
+
+ ret = ds2781_write(dev_info, &control_reg,
+ DS2781_CONTROL, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return ds2781_save_eeprom(dev_info, DS2781_CONTROL);
+}
+
+static int ds2781_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = ds2781_get_voltage(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds2781_get_temperature(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = model;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = ds2781_get_current(dev_info, CURRENT_NOW, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ ret = ds2781_get_current(dev_info, CURRENT_AVG, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds2781_get_status(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = ds2781_get_capacity(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = ds2781_get_accumulated_current(dev_info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = ds2781_get_charge_now(dev_info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property ds2781_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+};
+
+static ssize_t ds2781_get_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 control_reg;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ /* Get power mode */
+ ret = ds2781_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n",
+ !!(control_reg & DS2781_CONTROL_PMOD));
+}
+
+static ssize_t ds2781_set_pmod_enabled(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 control_reg, new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ /* Set power mode */
+ ret = ds2781_get_control_register(dev_info, &control_reg);
+ if (ret < 0)
+ return ret;
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pmod setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ if (new_setting)
+ control_reg |= DS2781_CONTROL_PMOD;
+ else
+ control_reg &= ~DS2781_CONTROL_PMOD;
+
+ ret = ds2781_set_control_register(dev_info, control_reg);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_get_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sense_resistor;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_read8(dev_info, &sense_resistor, DS2781_RSNSP);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sense_resistor);
+ return ret;
+}
+
+static ssize_t ds2781_set_sense_resistor_value(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_set_sense_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_get_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u16 rsgain;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_get_rsgain_register(dev_info, &rsgain);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", rsgain);
+}
+
+static ssize_t ds2781_set_rsgain_setting(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u16 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = kstrtou16(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ /* Gain can only be from 0 to 1.999 in steps of .001 */
+ if (new_setting > 1999) {
+ dev_err(dev_info->dev, "Invalid rsgain setting (0 - 1999)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2781_set_rsgain_register(dev_info, new_setting);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_get_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ int ret;
+ u8 sfr;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = ds2781_read8(dev_info, &sfr, DS2781_SFR);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(buf, "%d\n", sfr & DS2781_SFR_PIOSC);
+ return ret;
+}
+
+static ssize_t ds2781_set_pio_pin(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ int ret;
+ u8 new_setting;
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ ret = kstrtou8(buf, 0, &new_setting);
+ if (ret < 0)
+ return ret;
+
+ if ((new_setting != 0) && (new_setting != 1)) {
+ dev_err(dev_info->dev, "Invalid pio_pin setting (0 or 1)\n");
+ return -EINVAL;
+ }
+
+ ret = ds2781_write(dev_info, &new_setting,
+ DS2781_SFR, sizeof(u8));
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static ssize_t ds2781_read_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ return ds2781_read_block(dev_info, buf,
+ DS2781_EEPROM_BLOCK1_START + off, count);
+}
+
+static ssize_t ds2781_write_param_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+ int ret;
+
+ ret = ds2781_write(dev_info, buf,
+ DS2781_EEPROM_BLOCK1_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK1_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2781_param_eeprom_bin_attr = {
+ .attr = {
+ .name = "param_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2781_PARAM_EEPROM_SIZE,
+ .read = ds2781_read_param_eeprom_bin,
+ .write = ds2781_write_param_eeprom_bin,
+};
+
+static ssize_t ds2781_read_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+
+ return ds2781_read_block(dev_info, buf,
+ DS2781_EEPROM_BLOCK0_START + off, count);
+
+}
+
+static ssize_t ds2781_write_user_eeprom_bin(struct file *filp,
+ struct kobject *kobj,
+ struct bin_attribute *bin_attr,
+ char *buf, loff_t off, size_t count)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = to_power_supply(dev);
+ struct ds2781_device_info *dev_info = to_ds2781_device_info(psy);
+ int ret;
+
+ ret = ds2781_write(dev_info, buf,
+ DS2781_EEPROM_BLOCK0_START + off, count);
+ if (ret < 0)
+ return ret;
+
+ ret = ds2781_save_eeprom(dev_info, DS2781_EEPROM_BLOCK0_START);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+static struct bin_attribute ds2781_user_eeprom_bin_attr = {
+ .attr = {
+ .name = "user_eeprom",
+ .mode = S_IRUGO | S_IWUSR,
+ },
+ .size = DS2781_USER_EEPROM_SIZE,
+ .read = ds2781_read_user_eeprom_bin,
+ .write = ds2781_write_user_eeprom_bin,
+};
+
+static DEVICE_ATTR(pmod_enabled, S_IRUGO | S_IWUSR, ds2781_get_pmod_enabled,
+ ds2781_set_pmod_enabled);
+static DEVICE_ATTR(sense_resistor_value, S_IRUGO | S_IWUSR,
+ ds2781_get_sense_resistor_value, ds2781_set_sense_resistor_value);
+static DEVICE_ATTR(rsgain_setting, S_IRUGO | S_IWUSR, ds2781_get_rsgain_setting,
+ ds2781_set_rsgain_setting);
+static DEVICE_ATTR(pio_pin, S_IRUGO | S_IWUSR, ds2781_get_pio_pin,
+ ds2781_set_pio_pin);
+
+
+static struct attribute *ds2781_attributes[] = {
+ &dev_attr_pmod_enabled.attr,
+ &dev_attr_sense_resistor_value.attr,
+ &dev_attr_rsgain_setting.attr,
+ &dev_attr_pio_pin.attr,
+ NULL
+};
+
+static const struct attribute_group ds2781_attr_group = {
+ .attrs = ds2781_attributes,
+};
+
+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);
+ if (!dev_info)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, dev_info);
+
+ dev_info->dev = &pdev->dev;
+ dev_info->w1_dev = pdev->dev.parent;
+ dev_info->bat_desc.name = dev_name(&pdev->dev);
+ dev_info->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ dev_info->bat_desc.properties = ds2781_battery_props;
+ dev_info->bat_desc.num_properties = ARRAY_SIZE(ds2781_battery_props);
+ dev_info->bat_desc.get_property = ds2781_battery_get_property;
+
+ psy_cfg.drv_data = dev_info;
+
+ dev_info->bat = 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 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 = {
+ .driver = {
+ .name = "ds2781-battery",
+ },
+ .probe = ds2781_battery_probe,
+ .remove = ds2781_battery_remove,
+};
+module_platform_driver(ds2781_battery_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>");
+MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver");
+MODULE_ALIAS("platform:ds2781-battery");
+
diff --git a/drivers/power/supply/ds2782_battery.c b/drivers/power/supply/ds2782_battery.c
new file mode 100644
index 0000000..a1b7e05
--- /dev/null
+++ b/drivers/power/supply/ds2782_battery.c
@@ -0,0 +1,475 @@
+/*
+ * I2C client/driver for the Maxim/Dallas DS2782 Stand-Alone Fuel Gauge IC
+ *
+ * Copyright (C) 2009 Bluewater Systems Ltd
+ *
+ * Author: Ryan Mallon
+ *
+ * DS2786 added by Yulia Vilensky <vilensky@compulab.co.il>
+ *
+ * UEvent sending added by Evgeny Romanov <romanov@neurosoft.ru>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/swab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/idr.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/ds2782_battery.h>
+
+#define DS2782_REG_RARC 0x06 /* Remaining active relative capacity */
+
+#define DS278x_REG_VOLT_MSB 0x0c
+#define DS278x_REG_TEMP_MSB 0x0a
+#define DS278x_REG_CURRENT_MSB 0x0e
+
+/* EEPROM Block */
+#define DS2782_REG_RSNSP 0x69 /* Sense resistor value */
+
+/* Current unit measurement in uA for a 1 milli-ohm sense resistor */
+#define DS2782_CURRENT_UNITS 1563
+
+#define DS2786_REG_RARC 0x02 /* Remaining active relative capacity */
+
+#define DS2786_CURRENT_UNITS 25
+
+#define DS278x_DELAY 1000
+
+struct ds278x_info;
+
+struct ds278x_battery_ops {
+ int (*get_battery_current)(struct ds278x_info *info, int *current_uA);
+ int (*get_battery_voltage)(struct ds278x_info *info, int *voltage_uV);
+ int (*get_battery_capacity)(struct ds278x_info *info, int *capacity);
+};
+
+#define to_ds278x_info(x) power_supply_get_drvdata(x)
+
+struct ds278x_info {
+ struct i2c_client *client;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ const struct ds278x_battery_ops *ops;
+ struct delayed_work bat_work;
+ int id;
+ int rsns;
+ int capacity;
+ int status; /* State Of Charge */
+};
+
+static DEFINE_IDR(battery_id);
+static DEFINE_MUTEX(battery_lock);
+
+static inline int ds278x_read_reg(struct ds278x_info *info, int reg, u8 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(info->client, reg);
+ if (ret < 0) {
+ dev_err(&info->client->dev, "register read failed\n");
+ return ret;
+ }
+
+ *val = ret;
+ return 0;
+}
+
+static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb,
+ s16 *val)
+{
+ int ret;
+
+ ret = i2c_smbus_read_word_data(info->client, reg_msb);
+ if (ret < 0) {
+ dev_err(&info->client->dev, "register read failed\n");
+ return ret;
+ }
+
+ *val = swab16(ret);
+ return 0;
+}
+
+static int ds278x_get_temp(struct ds278x_info *info, int *temp)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Temperature is measured in units of 0.125 degrees celcius, the
+ * power_supply class measures temperature in tenths of degrees
+ * celsius. The temperature value is stored as a 10 bit number, plus
+ * sign in the upper bits of a 16 bit register.
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_TEMP_MSB, &raw);
+ if (err)
+ return err;
+ *temp = ((raw / 32) * 125) / 100;
+ return 0;
+}
+
+static int ds2782_get_current(struct ds278x_info *info, int *current_uA)
+{
+ int sense_res;
+ int err;
+ u8 sense_res_raw;
+ s16 raw;
+
+ /*
+ * The units of measurement for current are dependent on the value of
+ * the sense resistor.
+ */
+ err = ds278x_read_reg(info, DS2782_REG_RSNSP, &sense_res_raw);
+ if (err)
+ return err;
+ if (sense_res_raw == 0) {
+ dev_err(&info->client->dev, "sense resistor value is 0\n");
+ return -ENXIO;
+ }
+ sense_res = 1000 / sense_res_raw;
+
+ dev_dbg(&info->client->dev, "sense resistor = %d milli-ohms\n",
+ sense_res);
+ err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw);
+ if (err)
+ return err;
+ *current_uA = raw * (DS2782_CURRENT_UNITS / sense_res);
+ return 0;
+}
+
+static int ds2782_get_voltage(struct ds278x_info *info, int *voltage_uV)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Voltage is measured in units of 4.88mV. The voltage is stored as
+ * a 10-bit number plus sign, in the upper bits of a 16-bit register
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw);
+ if (err)
+ return err;
+ *voltage_uV = (raw / 32) * 4800;
+ return 0;
+}
+
+static int ds2782_get_capacity(struct ds278x_info *info, int *capacity)
+{
+ int err;
+ u8 raw;
+
+ err = ds278x_read_reg(info, DS2782_REG_RARC, &raw);
+ if (err)
+ return err;
+ *capacity = raw;
+ return 0;
+}
+
+static int ds2786_get_current(struct ds278x_info *info, int *current_uA)
+{
+ int err;
+ s16 raw;
+
+ err = ds278x_read_reg16(info, DS278x_REG_CURRENT_MSB, &raw);
+ if (err)
+ return err;
+ *current_uA = (raw / 16) * (DS2786_CURRENT_UNITS / info->rsns);
+ return 0;
+}
+
+static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV)
+{
+ s16 raw;
+ int err;
+
+ /*
+ * Voltage is measured in units of 1.22mV. The voltage is stored as
+ * a 12-bit number plus sign, in the upper bits of a 16-bit register
+ */
+ err = ds278x_read_reg16(info, DS278x_REG_VOLT_MSB, &raw);
+ if (err)
+ return err;
+ *voltage_uV = (raw / 8) * 1220;
+ return 0;
+}
+
+static int ds2786_get_capacity(struct ds278x_info *info, int *capacity)
+{
+ int err;
+ u8 raw;
+
+ err = ds278x_read_reg(info, DS2786_REG_RARC, &raw);
+ if (err)
+ return err;
+ /* Relative capacity is displayed with resolution 0.5 % */
+ *capacity = raw/2 ;
+ return 0;
+}
+
+static int ds278x_get_status(struct ds278x_info *info, int *status)
+{
+ int err;
+ int current_uA;
+ int capacity;
+
+ err = info->ops->get_battery_current(info, &current_uA);
+ if (err)
+ return err;
+
+ err = info->ops->get_battery_capacity(info, &capacity);
+ if (err)
+ return err;
+
+ info->capacity = capacity;
+
+ if (capacity == 100)
+ *status = POWER_SUPPLY_STATUS_FULL;
+ else if (current_uA == 0)
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else if (current_uA < 0)
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+
+ return 0;
+}
+
+static int ds278x_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct ds278x_info *info = to_ds278x_info(psy);
+ int ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = ds278x_get_status(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = info->ops->get_battery_capacity(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = info->ops->get_battery_voltage(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = info->ops->get_battery_current(info, &val->intval);
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = ds278x_get_temp(info, &val->intval);
+ break;
+
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static void ds278x_bat_update(struct ds278x_info *info)
+{
+ int old_status = info->status;
+ int old_capacity = info->capacity;
+
+ ds278x_get_status(info, &info->status);
+
+ if ((old_status != info->status) || (old_capacity != info->capacity))
+ power_supply_changed(info->battery);
+}
+
+static void ds278x_bat_work(struct work_struct *work)
+{
+ struct ds278x_info *info;
+
+ info = container_of(work, struct ds278x_info, bat_work.work);
+ ds278x_bat_update(info);
+
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
+}
+
+static enum power_supply_property ds278x_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static void ds278x_power_supply_init(struct power_supply_desc *battery)
+{
+ battery->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery->properties = ds278x_battery_props;
+ battery->num_properties = ARRAY_SIZE(ds278x_battery_props);
+ battery->get_property = ds278x_battery_get_property;
+ battery->external_power_changed = NULL;
+}
+
+static int ds278x_battery_remove(struct i2c_client *client)
+{
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ power_supply_unregister(info->battery);
+ kfree(info->battery_desc.name);
+
+ mutex_lock(&battery_lock);
+ idr_remove(&battery_id, info->id);
+ mutex_unlock(&battery_lock);
+
+ cancel_delayed_work(&info->bat_work);
+
+ kfree(info);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int ds278x_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&info->bat_work);
+ return 0;
+}
+
+static int ds278x_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ds278x_info *info = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
+ return 0;
+}
+#endif /* CONFIG_PM_SLEEP */
+
+static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume);
+
+enum ds278x_num_id {
+ DS2782 = 0,
+ DS2786,
+};
+
+static const struct ds278x_battery_ops ds278x_ops[] = {
+ [DS2782] = {
+ .get_battery_current = ds2782_get_current,
+ .get_battery_voltage = ds2782_get_voltage,
+ .get_battery_capacity = ds2782_get_capacity,
+ },
+ [DS2786] = {
+ .get_battery_current = ds2786_get_current,
+ .get_battery_voltage = ds2786_get_voltage,
+ .get_battery_capacity = ds2786_get_capacity,
+ }
+};
+
+static int ds278x_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct ds278x_platform_data *pdata = client->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct ds278x_info *info;
+ int ret;
+ int num;
+
+ /*
+ * ds2786 should have the sense resistor value set
+ * in the platform data
+ */
+ if (id->driver_data == DS2786 && !pdata) {
+ dev_err(&client->dev, "missing platform data for ds2786\n");
+ return -EINVAL;
+ }
+
+ /* Get an ID for this battery */
+ mutex_lock(&battery_lock);
+ ret = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL);
+ mutex_unlock(&battery_lock);
+ if (ret < 0)
+ goto fail_id;
+ num = ret;
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info) {
+ ret = -ENOMEM;
+ goto fail_info;
+ }
+
+ info->battery_desc.name = kasprintf(GFP_KERNEL, "%s-%d",
+ client->name, num);
+ if (!info->battery_desc.name) {
+ ret = -ENOMEM;
+ goto fail_name;
+ }
+
+ if (id->driver_data == DS2786)
+ info->rsns = pdata->rsns;
+
+ i2c_set_clientdata(client, info);
+ info->client = client;
+ info->id = num;
+ info->ops = &ds278x_ops[id->driver_data];
+ ds278x_power_supply_init(&info->battery_desc);
+ psy_cfg.drv_data = info;
+
+ info->capacity = 100;
+ info->status = POWER_SUPPLY_STATUS_FULL;
+
+ INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work);
+
+ info->battery = power_supply_register(&client->dev,
+ &info->battery_desc, &psy_cfg);
+ if (IS_ERR(info->battery)) {
+ dev_err(&client->dev, "failed to register battery\n");
+ ret = PTR_ERR(info->battery);
+ goto fail_register;
+ } else {
+ schedule_delayed_work(&info->bat_work, DS278x_DELAY);
+ }
+
+ return 0;
+
+fail_register:
+ kfree(info->battery_desc.name);
+fail_name:
+ kfree(info);
+fail_info:
+ mutex_lock(&battery_lock);
+ idr_remove(&battery_id, num);
+ mutex_unlock(&battery_lock);
+fail_id:
+ return ret;
+}
+
+static const struct i2c_device_id ds278x_id[] = {
+ {"ds2782", DS2782},
+ {"ds2786", DS2786},
+ {},
+};
+MODULE_DEVICE_TABLE(i2c, ds278x_id);
+
+static struct i2c_driver ds278x_battery_driver = {
+ .driver = {
+ .name = "ds2782-battery",
+ .pm = &ds278x_battery_pm_ops,
+ },
+ .probe = ds278x_battery_probe,
+ .remove = ds278x_battery_remove,
+ .id_table = ds278x_id,
+};
+module_i2c_driver(ds278x_battery_driver);
+
+MODULE_AUTHOR("Ryan Mallon");
+MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/generic-adc-battery.c b/drivers/power/supply/generic-adc-battery.c
new file mode 100644
index 0000000..edb36bf
--- /dev/null
+++ b/drivers/power/supply/generic-adc-battery.c
@@ -0,0 +1,432 @@
+/*
+ * Generic battery driver code using IIO
+ * Copyright (C) 2012, Anish Kumar <anish198519851985@gmail.com>
+ * based on jz4740-battery.c
+ * based on s3c_adc_battery.c
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/iio/consumer.h>
+#include <linux/iio/types.h>
+#include <linux/power/generic-adc-battery.h>
+
+#define JITTER_DEFAULT 10 /* hope 10ms is enough */
+
+enum gab_chan_type {
+ GAB_VOLTAGE = 0,
+ GAB_CURRENT,
+ GAB_POWER,
+ GAB_MAX_CHAN_TYPE
+};
+
+/*
+ * gab_chan_name suggests the standard channel names for commonly used
+ * channel types.
+ */
+static const char *const gab_chan_name[] = {
+ [GAB_VOLTAGE] = "voltage",
+ [GAB_CURRENT] = "current",
+ [GAB_POWER] = "power",
+};
+
+struct gab {
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ struct iio_channel *channel[GAB_MAX_CHAN_TYPE];
+ struct gab_platform_data *pdata;
+ struct delayed_work bat_work;
+ int level;
+ int status;
+ bool cable_plugged;
+};
+
+static struct gab *to_generic_bat(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static void gab_ext_power_changed(struct power_supply *psy)
+{
+ struct gab *adc_bat = to_generic_bat(psy);
+
+ schedule_delayed_work(&adc_bat->bat_work, msecs_to_jiffies(0));
+}
+
+static const enum power_supply_property gab_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+/*
+ * This properties are set based on the received platform data and this
+ * should correspond one-to-one with enum chan_type.
+ */
+static const enum power_supply_property gab_dyn_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_POWER_NOW,
+};
+
+static bool gab_charge_finished(struct gab *adc_bat)
+{
+ struct gab_platform_data *pdata = adc_bat->pdata;
+ bool ret = gpio_get_value(pdata->gpio_charge_finished);
+ bool inv = pdata->gpio_inverted;
+
+ if (!gpio_is_valid(pdata->gpio_charge_finished))
+ return false;
+ return ret ^ inv;
+}
+
+static int gab_get_status(struct gab *adc_bat)
+{
+ struct gab_platform_data *pdata = adc_bat->pdata;
+ struct power_supply_info *bat_info;
+
+ bat_info = &pdata->battery_info;
+ if (adc_bat->level == bat_info->charge_full_design)
+ return POWER_SUPPLY_STATUS_FULL;
+ return adc_bat->status;
+}
+
+static enum gab_chan_type gab_prop_to_chan(enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ return GAB_POWER;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return GAB_VOLTAGE;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return GAB_CURRENT;
+ default:
+ WARN_ON(1);
+ break;
+ }
+ return GAB_POWER;
+}
+
+static int read_channel(struct gab *adc_bat, enum power_supply_property psp,
+ int *result)
+{
+ int ret;
+ int chan_index;
+
+ chan_index = gab_prop_to_chan(psp);
+ ret = iio_read_channel_processed(adc_bat->channel[chan_index],
+ result);
+ if (ret < 0)
+ pr_err("read channel error\n");
+ return ret;
+}
+
+static int gab_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct gab *adc_bat;
+ struct gab_platform_data *pdata;
+ struct power_supply_info *bat_info;
+ int result = 0;
+ int ret = 0;
+
+ adc_bat = to_generic_bat(psy);
+ if (!adc_bat) {
+ dev_err(&psy->dev, "no battery infos ?!\n");
+ return -EINVAL;
+ }
+ pdata = adc_bat->pdata;
+ bat_info = &pdata->battery_info;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = gab_get_status(adc_bat);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = pdata->cal_charge(result);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_POWER_NOW:
+ ret = read_channel(adc_bat, psp, &result);
+ if (ret < 0)
+ goto err;
+ val->intval = result;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat_info->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = bat_info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat_info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = bat_info->charge_full_design;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = bat_info->name;
+ break;
+ default:
+ return -EINVAL;
+ }
+err:
+ return ret;
+}
+
+static void gab_work(struct work_struct *work)
+{
+ struct gab *adc_bat;
+ struct gab_platform_data *pdata;
+ struct delayed_work *delayed_work;
+ bool is_plugged;
+ int status;
+
+ delayed_work = to_delayed_work(work);
+ adc_bat = container_of(delayed_work, struct gab, bat_work);
+ pdata = adc_bat->pdata;
+ status = adc_bat->status;
+
+ is_plugged = power_supply_am_i_supplied(adc_bat->psy);
+ adc_bat->cable_plugged = is_plugged;
+
+ if (!is_plugged)
+ adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (gab_charge_finished(adc_bat))
+ adc_bat->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ adc_bat->status = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (status != adc_bat->status)
+ power_supply_changed(adc_bat->psy);
+}
+
+static irqreturn_t gab_charged(int irq, void *dev_id)
+{
+ struct gab *adc_bat = dev_id;
+ struct gab_platform_data *pdata = adc_bat->pdata;
+ int delay;
+
+ delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT;
+ schedule_delayed_work(&adc_bat->bat_work,
+ msecs_to_jiffies(delay));
+ return IRQ_HANDLED;
+}
+
+static int gab_probe(struct platform_device *pdev)
+{
+ struct gab *adc_bat;
+ struct power_supply_desc *psy_desc;
+ struct power_supply_config psy_cfg = {};
+ struct gab_platform_data *pdata = pdev->dev.platform_data;
+ enum power_supply_property *properties;
+ int ret = 0;
+ int chan;
+ int index = 0;
+
+ adc_bat = devm_kzalloc(&pdev->dev, sizeof(*adc_bat), GFP_KERNEL);
+ if (!adc_bat) {
+ dev_err(&pdev->dev, "failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ psy_cfg.drv_data = adc_bat;
+ psy_desc = &adc_bat->psy_desc;
+ psy_desc->name = pdata->battery_info.name;
+
+ /* bootup default values for the battery */
+ adc_bat->cable_plugged = false;
+ adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ psy_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ psy_desc->get_property = gab_get_property;
+ psy_desc->external_power_changed = gab_ext_power_changed;
+ adc_bat->pdata = pdata;
+
+ /*
+ * copying the static properties and allocating extra memory for holding
+ * the extra configurable properties received from platform data.
+ */
+ psy_desc->properties = kcalloc(ARRAY_SIZE(gab_props) +
+ ARRAY_SIZE(gab_chan_name),
+ sizeof(*psy_desc->properties),
+ GFP_KERNEL);
+ if (!psy_desc->properties) {
+ ret = -ENOMEM;
+ goto first_mem_fail;
+ }
+
+ memcpy(psy_desc->properties, gab_props, sizeof(gab_props));
+ properties = (enum power_supply_property *)
+ ((char *)psy_desc->properties + sizeof(gab_props));
+
+ /*
+ * getting channel from iio and copying the battery properties
+ * based on the channel supported by consumer device.
+ */
+ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
+ adc_bat->channel[chan] = iio_channel_get(&pdev->dev,
+ gab_chan_name[chan]);
+ if (IS_ERR(adc_bat->channel[chan])) {
+ ret = PTR_ERR(adc_bat->channel[chan]);
+ adc_bat->channel[chan] = NULL;
+ } else {
+ /* copying properties for supported channels only */
+ memcpy(properties + sizeof(*(psy_desc->properties)) * index,
+ &gab_dyn_props[chan],
+ sizeof(gab_dyn_props[chan]));
+ index++;
+ }
+ }
+
+ /* none of the channels are supported so let's bail out */
+ if (index == 0) {
+ ret = -ENODEV;
+ goto second_mem_fail;
+ }
+
+ /*
+ * Total number of properties is equal to static properties
+ * plus the dynamic properties.Some properties may not be set
+ * as come channels may be not be supported by the device.So
+ * we need to take care of that.
+ */
+ psy_desc->num_properties = ARRAY_SIZE(gab_props) + index;
+
+ adc_bat->psy = power_supply_register(&pdev->dev, psy_desc, &psy_cfg);
+ if (IS_ERR(adc_bat->psy)) {
+ ret = PTR_ERR(adc_bat->psy);
+ goto err_reg_fail;
+ }
+
+ INIT_DELAYED_WORK(&adc_bat->bat_work, gab_work);
+
+ if (gpio_is_valid(pdata->gpio_charge_finished)) {
+ int irq;
+ ret = gpio_request(pdata->gpio_charge_finished, "charged");
+ if (ret)
+ goto gpio_req_fail;
+
+ irq = gpio_to_irq(pdata->gpio_charge_finished);
+ ret = request_any_context_irq(irq, gab_charged,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "battery charged", adc_bat);
+ if (ret < 0)
+ goto err_gpio;
+ }
+
+ platform_set_drvdata(pdev, adc_bat);
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&adc_bat->bat_work,
+ msecs_to_jiffies(0));
+ return 0;
+
+err_gpio:
+ gpio_free(pdata->gpio_charge_finished);
+gpio_req_fail:
+ power_supply_unregister(adc_bat->psy);
+err_reg_fail:
+ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
+ if (adc_bat->channel[chan])
+ iio_channel_release(adc_bat->channel[chan]);
+ }
+second_mem_fail:
+ kfree(psy_desc->properties);
+first_mem_fail:
+ return ret;
+}
+
+static int gab_remove(struct platform_device *pdev)
+{
+ int chan;
+ struct gab *adc_bat = platform_get_drvdata(pdev);
+ struct gab_platform_data *pdata = adc_bat->pdata;
+
+ power_supply_unregister(adc_bat->psy);
+
+ if (gpio_is_valid(pdata->gpio_charge_finished)) {
+ free_irq(gpio_to_irq(pdata->gpio_charge_finished), adc_bat);
+ gpio_free(pdata->gpio_charge_finished);
+ }
+
+ for (chan = 0; chan < ARRAY_SIZE(gab_chan_name); chan++) {
+ if (adc_bat->channel[chan])
+ iio_channel_release(adc_bat->channel[chan]);
+ }
+
+ kfree(adc_bat->psy_desc.properties);
+ cancel_delayed_work(&adc_bat->bat_work);
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int gab_suspend(struct device *dev)
+{
+ struct gab *adc_bat = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&adc_bat->bat_work);
+ adc_bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return 0;
+}
+
+static int gab_resume(struct device *dev)
+{
+ struct gab *adc_bat = dev_get_drvdata(dev);
+ struct gab_platform_data *pdata = adc_bat->pdata;
+ int delay;
+
+ delay = pdata->jitter_delay ? pdata->jitter_delay : JITTER_DEFAULT;
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&adc_bat->bat_work,
+ msecs_to_jiffies(delay));
+ return 0;
+}
+
+static const struct dev_pm_ops gab_pm_ops = {
+ .suspend = gab_suspend,
+ .resume = gab_resume,
+};
+
+#define GAB_PM_OPS (&gab_pm_ops)
+#else
+#define GAB_PM_OPS (NULL)
+#endif
+
+static struct platform_driver gab_driver = {
+ .driver = {
+ .name = "generic-adc-battery",
+ .pm = GAB_PM_OPS
+ },
+ .probe = gab_probe,
+ .remove = gab_remove,
+};
+module_platform_driver(gab_driver);
+
+MODULE_AUTHOR("anish kumar <anish198519851985@gmail.com>");
+MODULE_DESCRIPTION("generic battery driver using IIO");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/goldfish_battery.c b/drivers/power/supply/goldfish_battery.c
new file mode 100644
index 0000000..f5c525e
--- /dev/null
+++ b/drivers/power/supply/goldfish_battery.c
@@ -0,0 +1,256 @@
+/*
+ * Power supply driver for the goldfish emulator
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Copyright (C) 2012 Intel, Inc.
+ * Copyright (C) 2013 Intel, Inc.
+ * Author: Mike Lockwood <lockwood@android.com>
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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/module.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/types.h>
+#include <linux/pci.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/acpi.h>
+
+struct goldfish_battery_data {
+ void __iomem *reg_base;
+ int irq;
+ spinlock_t lock;
+
+ struct power_supply *battery;
+ struct power_supply *ac;
+};
+
+#define GOLDFISH_BATTERY_READ(data, addr) \
+ (readl(data->reg_base + addr))
+#define GOLDFISH_BATTERY_WRITE(data, addr, x) \
+ (writel(x, data->reg_base + addr))
+
+/*
+ * Temporary variable used between goldfish_battery_probe() and
+ * goldfish_battery_open().
+ */
+static struct goldfish_battery_data *battery_data;
+
+enum {
+ /* status register */
+ BATTERY_INT_STATUS = 0x00,
+ /* set this to enable IRQ */
+ BATTERY_INT_ENABLE = 0x04,
+
+ BATTERY_AC_ONLINE = 0x08,
+ BATTERY_STATUS = 0x0C,
+ BATTERY_HEALTH = 0x10,
+ BATTERY_PRESENT = 0x14,
+ BATTERY_CAPACITY = 0x18,
+
+ BATTERY_STATUS_CHANGED = 1U << 0,
+ AC_STATUS_CHANGED = 1U << 1,
+ BATTERY_INT_MASK = BATTERY_STATUS_CHANGED | AC_STATUS_CHANGED,
+};
+
+
+static int goldfish_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct goldfish_battery_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_AC_ONLINE);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int goldfish_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct goldfish_battery_data *data = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_STATUS);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_HEALTH);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = GOLDFISH_BATTERY_READ(data, BATTERY_CAPACITY);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property goldfish_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static enum power_supply_property goldfish_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static irqreturn_t goldfish_battery_interrupt(int irq, void *dev_id)
+{
+ unsigned long irq_flags;
+ struct goldfish_battery_data *data = dev_id;
+ uint32_t status;
+
+ spin_lock_irqsave(&data->lock, irq_flags);
+
+ /* read status flags, which will clear the interrupt */
+ status = GOLDFISH_BATTERY_READ(data, BATTERY_INT_STATUS);
+ status &= BATTERY_INT_MASK;
+
+ if (status & BATTERY_STATUS_CHANGED)
+ power_supply_changed(data->battery);
+ if (status & AC_STATUS_CHANGED)
+ power_supply_changed(data->ac);
+
+ spin_unlock_irqrestore(&data->lock, irq_flags);
+ return status ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static const struct power_supply_desc battery_desc = {
+ .properties = goldfish_battery_props,
+ .num_properties = ARRAY_SIZE(goldfish_battery_props),
+ .get_property = goldfish_battery_get_property,
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+};
+
+static const struct power_supply_desc ac_desc = {
+ .properties = goldfish_ac_props,
+ .num_properties = ARRAY_SIZE(goldfish_ac_props),
+ .get_property = goldfish_ac_get_property,
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+};
+
+static int goldfish_battery_probe(struct platform_device *pdev)
+{
+ int ret;
+ struct resource *r;
+ struct goldfish_battery_data *data;
+ struct power_supply_config psy_cfg = {};
+
+ data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL);
+ if (data == NULL)
+ return -ENOMEM;
+
+ spin_lock_init(&data->lock);
+
+ r = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (r == NULL) {
+ dev_err(&pdev->dev, "platform_get_resource failed\n");
+ return -ENODEV;
+ }
+
+ data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r));
+ if (data->reg_base == NULL) {
+ dev_err(&pdev->dev, "unable to remap MMIO\n");
+ return -ENOMEM;
+ }
+
+ data->irq = platform_get_irq(pdev, 0);
+ if (data->irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ return -ENODEV;
+ }
+
+ ret = devm_request_irq(&pdev->dev, data->irq, goldfish_battery_interrupt,
+ IRQF_SHARED, pdev->name, data);
+ if (ret)
+ return ret;
+
+ psy_cfg.drv_data = data;
+
+ data->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg);
+ if (IS_ERR(data->ac))
+ return PTR_ERR(data->ac);
+
+ data->battery = power_supply_register(&pdev->dev, &battery_desc,
+ &psy_cfg);
+ if (IS_ERR(data->battery)) {
+ power_supply_unregister(data->ac);
+ return PTR_ERR(data->battery);
+ }
+
+ platform_set_drvdata(pdev, data);
+ battery_data = data;
+
+ GOLDFISH_BATTERY_WRITE(data, BATTERY_INT_ENABLE, BATTERY_INT_MASK);
+ return 0;
+}
+
+static int goldfish_battery_remove(struct platform_device *pdev)
+{
+ struct goldfish_battery_data *data = platform_get_drvdata(pdev);
+
+ power_supply_unregister(data->battery);
+ power_supply_unregister(data->ac);
+ battery_data = NULL;
+ return 0;
+}
+
+static const struct of_device_id goldfish_battery_of_match[] = {
+ { .compatible = "google,goldfish-battery", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, goldfish_battery_of_match);
+
+static const struct acpi_device_id goldfish_battery_acpi_match[] = {
+ { "GFSH0001", 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(acpi, goldfish_battery_acpi_match);
+
+static struct platform_driver goldfish_battery_device = {
+ .probe = goldfish_battery_probe,
+ .remove = goldfish_battery_remove,
+ .driver = {
+ .name = "goldfish-battery",
+ .of_match_table = goldfish_battery_of_match,
+ .acpi_match_table = ACPI_PTR(goldfish_battery_acpi_match),
+ }
+};
+module_platform_driver(goldfish_battery_device);
+
+MODULE_AUTHOR("Mike Lockwood lockwood@android.com");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for the Goldfish emulator");
diff --git a/drivers/power/supply/gpio-charger.c b/drivers/power/supply/gpio-charger.c
new file mode 100644
index 0000000..c5869b1
--- /dev/null
+++ b/drivers/power/supply/gpio-charger.c
@@ -0,0 +1,280 @@
+/*
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ * Driver for chargers which report their online status through a GPIO pin
+ *
+ * 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.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/gpio.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+#include <linux/power/gpio-charger.h>
+
+struct gpio_charger {
+ const struct gpio_charger_platform_data *pdata;
+ unsigned int irq;
+ bool wakeup_enabled;
+
+ struct power_supply *charger;
+ struct power_supply_desc charger_desc;
+};
+
+static irqreturn_t gpio_charger_irq(int irq, void *devid)
+{
+ struct power_supply *charger = devid;
+
+ power_supply_changed(charger);
+
+ return IRQ_HANDLED;
+}
+
+static inline struct gpio_charger *psy_to_gpio_charger(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static int gpio_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct gpio_charger *gpio_charger = psy_to_gpio_charger(psy);
+ const struct gpio_charger_platform_data *pdata = gpio_charger->pdata;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!gpio_get_value_cansleep(pdata->gpio);
+ val->intval ^= pdata->gpio_active_low;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property gpio_charger_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static
+struct gpio_charger_platform_data *gpio_charger_parse_dt(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct gpio_charger_platform_data *pdata;
+ const char *chargetype;
+ enum of_gpio_flags flags;
+ int ret;
+
+ if (!np)
+ return ERR_PTR(-ENOENT);
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ pdata->name = np->name;
+
+ pdata->gpio = of_get_gpio_flags(np, 0, &flags);
+ if (pdata->gpio < 0) {
+ if (pdata->gpio != -EPROBE_DEFER)
+ dev_err(dev, "could not get charger gpio\n");
+ return ERR_PTR(pdata->gpio);
+ }
+
+ pdata->gpio_active_low = !!(flags & OF_GPIO_ACTIVE_LOW);
+
+ pdata->type = POWER_SUPPLY_TYPE_UNKNOWN;
+ ret = of_property_read_string(np, "charger-type", &chargetype);
+ if (ret >= 0) {
+ if (!strncmp("unknown", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_UNKNOWN;
+ else if (!strncmp("battery", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_BATTERY;
+ else if (!strncmp("ups", chargetype, 3))
+ pdata->type = POWER_SUPPLY_TYPE_UPS;
+ else if (!strncmp("mains", chargetype, 5))
+ pdata->type = POWER_SUPPLY_TYPE_MAINS;
+ else if (!strncmp("usb-sdp", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_USB;
+ else if (!strncmp("usb-dcp", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_USB_DCP;
+ else if (!strncmp("usb-cdp", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_USB_CDP;
+ else if (!strncmp("usb-aca", chargetype, 7))
+ pdata->type = POWER_SUPPLY_TYPE_USB_ACA;
+ else
+ dev_warn(dev, "unknown charger type %s\n", chargetype);
+ }
+
+ return pdata;
+}
+
+static int gpio_charger_probe(struct platform_device *pdev)
+{
+ const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct gpio_charger *gpio_charger;
+ struct power_supply_desc *charger_desc;
+ int ret;
+ int irq;
+
+ if (!pdata) {
+ pdata = gpio_charger_parse_dt(&pdev->dev);
+ if (IS_ERR(pdata)) {
+ ret = PTR_ERR(pdata);
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "No platform data\n");
+ return ret;
+ }
+ }
+
+ if (!gpio_is_valid(pdata->gpio)) {
+ dev_err(&pdev->dev, "Invalid gpio pin\n");
+ return -EINVAL;
+ }
+
+ gpio_charger = devm_kzalloc(&pdev->dev, sizeof(*gpio_charger),
+ GFP_KERNEL);
+ if (!gpio_charger) {
+ dev_err(&pdev->dev, "Failed to alloc driver structure\n");
+ return -ENOMEM;
+ }
+
+ charger_desc = &gpio_charger->charger_desc;
+
+ charger_desc->name = pdata->name ? pdata->name : "gpio-charger";
+ charger_desc->type = pdata->type;
+ charger_desc->properties = gpio_charger_properties;
+ charger_desc->num_properties = ARRAY_SIZE(gpio_charger_properties);
+ charger_desc->get_property = gpio_charger_get_property;
+
+ psy_cfg.supplied_to = pdata->supplied_to;
+ psy_cfg.num_supplicants = pdata->num_supplicants;
+ psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.drv_data = gpio_charger;
+
+ ret = gpio_request(pdata->gpio, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request gpio pin: %d\n", ret);
+ goto err_free;
+ }
+ ret = gpio_direction_input(pdata->gpio);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to set gpio to input: %d\n", ret);
+ goto err_gpio_free;
+ }
+
+ gpio_charger->pdata = pdata;
+
+ gpio_charger->charger = power_supply_register(&pdev->dev,
+ charger_desc, &psy_cfg);
+ if (IS_ERR(gpio_charger->charger)) {
+ ret = PTR_ERR(gpio_charger->charger);
+ dev_err(&pdev->dev, "Failed to register power supply: %d\n",
+ ret);
+ goto err_gpio_free;
+ }
+
+ irq = gpio_to_irq(pdata->gpio);
+ if (irq > 0) {
+ ret = request_any_context_irq(irq, gpio_charger_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), gpio_charger->charger);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "Failed to request irq: %d\n", ret);
+ else
+ gpio_charger->irq = irq;
+ }
+
+ platform_set_drvdata(pdev, gpio_charger);
+
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+
+err_gpio_free:
+ gpio_free(pdata->gpio);
+err_free:
+ return ret;
+}
+
+static int gpio_charger_remove(struct platform_device *pdev)
+{
+ struct gpio_charger *gpio_charger = platform_get_drvdata(pdev);
+
+ if (gpio_charger->irq)
+ free_irq(gpio_charger->irq, gpio_charger->charger);
+
+ power_supply_unregister(gpio_charger->charger);
+
+ gpio_free(gpio_charger->pdata->gpio);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int gpio_charger_suspend(struct device *dev)
+{
+ struct gpio_charger *gpio_charger = dev_get_drvdata(dev);
+
+ if (device_may_wakeup(dev))
+ gpio_charger->wakeup_enabled =
+ !enable_irq_wake(gpio_charger->irq);
+
+ return 0;
+}
+
+static int gpio_charger_resume(struct device *dev)
+{
+ struct platform_device *pdev = to_platform_device(dev);
+ struct gpio_charger *gpio_charger = platform_get_drvdata(pdev);
+
+ if (device_may_wakeup(dev) && gpio_charger->wakeup_enabled)
+ disable_irq_wake(gpio_charger->irq);
+ power_supply_changed(gpio_charger->charger);
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops,
+ gpio_charger_suspend, gpio_charger_resume);
+
+static const struct of_device_id gpio_charger_match[] = {
+ { .compatible = "gpio-charger" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, gpio_charger_match);
+
+static struct platform_driver gpio_charger_driver = {
+ .probe = gpio_charger_probe,
+ .remove = gpio_charger_remove,
+ .driver = {
+ .name = "gpio-charger",
+ .pm = &gpio_charger_pm_ops,
+ .of_match_table = gpio_charger_match,
+ },
+};
+
+module_platform_driver(gpio_charger_driver);
+
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("Driver for chargers which report their online status through a GPIO");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:gpio-charger");
diff --git a/drivers/power/supply/intel_mid_battery.c b/drivers/power/supply/intel_mid_battery.c
new file mode 100644
index 0000000..dc7feef
--- /dev/null
+++ b/drivers/power/supply/intel_mid_battery.c
@@ -0,0 +1,795 @@
+/*
+ * intel_mid_battery.c - Intel MID PMIC Battery Driver
+ *
+ * Copyright (C) 2009 Intel Corporation
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * 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; version 2 of the License.
+ *
+ * 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.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * Author: Nithish Mahalingam <nithish.mahalingam@intel.com>
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/jiffies.h>
+#include <linux/param.h>
+#include <linux/device.h>
+#include <linux/spi/spi.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <asm/intel_scu_ipc.h>
+
+#define DRIVER_NAME "pmic_battery"
+
+/*********************************************************************
+ * Generic defines
+ *********************************************************************/
+
+static int debug;
+module_param(debug, int, 0444);
+MODULE_PARM_DESC(debug, "Flag to enable PMIC Battery debug messages.");
+
+#define PMIC_BATT_DRV_INFO_UPDATED 1
+#define PMIC_BATT_PRESENT 1
+#define PMIC_BATT_NOT_PRESENT 0
+#define PMIC_USB_PRESENT PMIC_BATT_PRESENT
+#define PMIC_USB_NOT_PRESENT PMIC_BATT_NOT_PRESENT
+
+/* pmic battery register related */
+#define PMIC_BATT_CHR_SCHRGINT_ADDR 0xD2
+#define PMIC_BATT_CHR_SBATOVP_MASK (1 << 1)
+#define PMIC_BATT_CHR_STEMP_MASK (1 << 2)
+#define PMIC_BATT_CHR_SCOMP_MASK (1 << 3)
+#define PMIC_BATT_CHR_SUSBDET_MASK (1 << 4)
+#define PMIC_BATT_CHR_SBATDET_MASK (1 << 5)
+#define PMIC_BATT_CHR_SDCLMT_MASK (1 << 6)
+#define PMIC_BATT_CHR_SUSBOVP_MASK (1 << 7)
+#define PMIC_BATT_CHR_EXCPT_MASK 0x86
+
+#define PMIC_BATT_ADC_ACCCHRG_MASK (1 << 31)
+#define PMIC_BATT_ADC_ACCCHRGVAL_MASK 0x7FFFFFFF
+
+/* pmic ipc related */
+#define PMIC_BATT_CHR_IPC_FCHRG_SUBID 0x4
+#define PMIC_BATT_CHR_IPC_TCHRG_SUBID 0x6
+
+/* types of battery charging */
+enum batt_charge_type {
+ BATT_USBOTG_500MA_CHARGE,
+ BATT_USBOTG_TRICKLE_CHARGE,
+};
+
+/* valid battery events */
+enum batt_event {
+ BATT_EVENT_BATOVP_EXCPT,
+ BATT_EVENT_USBOVP_EXCPT,
+ BATT_EVENT_TEMP_EXCPT,
+ BATT_EVENT_DCLMT_EXCPT,
+ BATT_EVENT_EXCPT
+};
+
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+/*
+ * pmic battery info
+ */
+struct pmic_power_module_info {
+ bool is_dev_info_updated;
+ struct device *dev;
+ /* pmic battery data */
+ unsigned long update_time; /* jiffies when data read */
+ unsigned int usb_is_present;
+ unsigned int batt_is_present;
+ unsigned int batt_health;
+ unsigned int usb_health;
+ unsigned int batt_status;
+ unsigned int batt_charge_now; /* in mAS */
+ unsigned int batt_prev_charge_full; /* in mAS */
+ unsigned int batt_charge_rate; /* in units per second */
+
+ struct power_supply *usb;
+ struct power_supply *batt;
+ int irq; /* GPE_ID or IRQ# */
+ struct workqueue_struct *monitor_wqueue;
+ struct delayed_work monitor_battery;
+ struct work_struct handler;
+};
+
+static unsigned int delay_time = 2000; /* in ms */
+
+/*
+ * pmic ac properties
+ */
+static enum power_supply_property pmic_usb_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+/*
+ * pmic battery properties
+ */
+static enum power_supply_property pmic_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+};
+
+
+/*
+ * Glue functions for talking to the IPC
+ */
+
+struct battery_property {
+ u32 capacity; /* Charger capacity */
+ u8 crnt; /* Quick charge current value*/
+ u8 volt; /* Fine adjustment of constant charge voltage */
+ u8 prot; /* CHRGPROT register value */
+ u8 prot2; /* CHRGPROT1 register value */
+ u8 timer; /* Charging timer */
+};
+
+#define IPCMSG_BATTERY 0xEF
+
+/* Battery coulomb counter accumulator commands */
+#define IPC_CMD_CC_WR 0 /* Update coulomb counter value */
+#define IPC_CMD_CC_RD 1 /* Read coulomb counter value */
+#define IPC_CMD_BATTERY_PROPERTY 2 /* Read Battery property */
+
+/**
+ * pmic_scu_ipc_battery_cc_read - read battery cc
+ * @value: battery coulomb counter read
+ *
+ * Reads the battery couloumb counter value, returns 0 on success, or
+ * an error code
+ *
+ * This function may sleep. Locking for SCU accesses is handled for
+ * the caller.
+ */
+static int pmic_scu_ipc_battery_cc_read(u32 *value)
+{
+ return intel_scu_ipc_command(IPCMSG_BATTERY, IPC_CMD_CC_RD,
+ NULL, 0, value, 1);
+}
+
+/**
+ * pmic_scu_ipc_battery_property_get - fetch properties
+ * @prop: battery properties
+ *
+ * Retrieve the battery properties from the power management
+ *
+ * This function may sleep. Locking for SCU accesses is handled for
+ * the caller.
+ */
+static int pmic_scu_ipc_battery_property_get(struct battery_property *prop)
+{
+ u32 data[3];
+ u8 *p = (u8 *)&data[1];
+ int err = intel_scu_ipc_command(IPCMSG_BATTERY,
+ IPC_CMD_BATTERY_PROPERTY, NULL, 0, data, 3);
+
+ prop->capacity = data[0];
+ prop->crnt = *p++;
+ prop->volt = *p++;
+ prop->prot = *p++;
+ prop->prot2 = *p++;
+ prop->timer = *p++;
+
+ return err;
+}
+
+/**
+ * pmic_scu_ipc_set_charger - set charger
+ * @charger: charger to select
+ *
+ * Switch the charging mode for the SCU
+ */
+
+static int pmic_scu_ipc_set_charger(int charger)
+{
+ return intel_scu_ipc_simple_command(IPCMSG_BATTERY, charger);
+}
+
+/**
+ * pmic_battery_log_event - log battery events
+ * @event: battery event to be logged
+ * Context: can sleep
+ *
+ * There are multiple battery events which may be of interest to users;
+ * this battery function logs the different battery events onto the
+ * kernel log messages.
+ */
+static void pmic_battery_log_event(enum batt_event event)
+{
+ printk(KERN_WARNING "pmic-battery: ");
+ switch (event) {
+ case BATT_EVENT_BATOVP_EXCPT:
+ printk(KERN_CONT "battery overvoltage condition\n");
+ break;
+ case BATT_EVENT_USBOVP_EXCPT:
+ printk(KERN_CONT "usb charger overvoltage condition\n");
+ break;
+ case BATT_EVENT_TEMP_EXCPT:
+ printk(KERN_CONT "high battery temperature condition\n");
+ break;
+ case BATT_EVENT_DCLMT_EXCPT:
+ printk(KERN_CONT "over battery charge current condition\n");
+ break;
+ default:
+ printk(KERN_CONT "charger/battery exception %d\n", event);
+ break;
+ }
+}
+
+/**
+ * pmic_battery_read_status - read battery status information
+ * @pbi: device info structure to update the read information
+ * Context: can sleep
+ *
+ * PMIC power source information need to be updated based on the data read
+ * from the PMIC battery registers.
+ *
+ */
+static void pmic_battery_read_status(struct pmic_power_module_info *pbi)
+{
+ unsigned int update_time_intrvl;
+ unsigned int chrg_val;
+ u32 ccval;
+ u8 r8;
+ struct battery_property batt_prop;
+ int batt_present = 0;
+ int usb_present = 0;
+ int batt_exception = 0;
+
+ /* make sure the last batt_status read happened delay_time before */
+ if (pbi->update_time && time_before(jiffies, pbi->update_time +
+ msecs_to_jiffies(delay_time)))
+ return;
+
+ update_time_intrvl = jiffies_to_msecs(jiffies - pbi->update_time);
+ pbi->update_time = jiffies;
+
+ /* read coulomb counter registers and schrgint register */
+ if (pmic_scu_ipc_battery_cc_read(&ccval)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
+ __func__);
+ return;
+ }
+
+ if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return;
+ }
+
+ /*
+ * set pmic_power_module_info members based on pmic register values
+ * read.
+ */
+
+ /* set batt_is_present */
+ if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
+ pbi->batt_is_present = PMIC_BATT_PRESENT;
+ batt_present = 1;
+ } else {
+ pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ /* set batt_health */
+ if (batt_present) {
+ if (r8 & PMIC_BATT_CHR_SBATOVP_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pmic_battery_log_event(BATT_EVENT_BATOVP_EXCPT);
+ batt_exception = 1;
+ } else if (r8 & PMIC_BATT_CHR_STEMP_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pmic_battery_log_event(BATT_EVENT_TEMP_EXCPT);
+ batt_exception = 1;
+ } else {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ if (r8 & PMIC_BATT_CHR_SDCLMT_MASK) {
+ /* PMIC will change charging current automatically */
+ pmic_battery_log_event(BATT_EVENT_DCLMT_EXCPT);
+ }
+ }
+ }
+
+ /* set usb_is_present */
+ if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
+ pbi->usb_is_present = PMIC_USB_PRESENT;
+ usb_present = 1;
+ } else {
+ pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ }
+
+ if (usb_present) {
+ if (r8 & PMIC_BATT_CHR_SUSBOVP_MASK) {
+ pbi->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ pmic_battery_log_event(BATT_EVENT_USBOVP_EXCPT);
+ } else {
+ pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+ }
+
+ chrg_val = ccval & PMIC_BATT_ADC_ACCCHRGVAL_MASK;
+
+ /* set batt_prev_charge_full to battery capacity the first time */
+ if (!pbi->is_dev_info_updated) {
+ if (pmic_scu_ipc_battery_property_get(&batt_prop)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd failed\n",
+ __func__);
+ return;
+ }
+ pbi->batt_prev_charge_full = batt_prop.capacity;
+ }
+
+ /* set batt_status */
+ if (batt_present && !batt_exception) {
+ if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
+ pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
+ pbi->batt_prev_charge_full = chrg_val;
+ } else if (ccval & PMIC_BATT_ADC_ACCCHRG_MASK) {
+ pbi->batt_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ } else {
+ pbi->batt_status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ }
+
+ /* set batt_charge_rate */
+ if (pbi->is_dev_info_updated && batt_present && !batt_exception) {
+ if (pbi->batt_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ if (pbi->batt_charge_now - chrg_val) {
+ pbi->batt_charge_rate = ((pbi->batt_charge_now -
+ chrg_val) * 1000 * 60) /
+ update_time_intrvl;
+ }
+ } else if (pbi->batt_status == POWER_SUPPLY_STATUS_CHARGING) {
+ if (chrg_val - pbi->batt_charge_now) {
+ pbi->batt_charge_rate = ((chrg_val -
+ pbi->batt_charge_now) * 1000 * 60) /
+ update_time_intrvl;
+ }
+ } else
+ pbi->batt_charge_rate = 0;
+ } else {
+ pbi->batt_charge_rate = -1;
+ }
+
+ /* batt_charge_now */
+ if (batt_present && !batt_exception)
+ pbi->batt_charge_now = chrg_val;
+ else
+ pbi->batt_charge_now = -1;
+
+ pbi->is_dev_info_updated = PMIC_BATT_DRV_INFO_UPDATED;
+}
+
+/**
+ * pmic_usb_get_property - usb power source get property
+ * @psy: usb power supply context
+ * @psp: usb power source property
+ * @val: usb power source property value
+ * Context: can sleep
+ *
+ * PMIC usb power source property needs to be provided to power_supply
+ * subsytem for it to provide the information to users.
+ */
+static int pmic_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pbi->usb_is_present;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = pbi->usb_health;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static inline unsigned long mAStouAh(unsigned long v)
+{
+ /* seconds to hours, mA to µA */
+ return (v * 1000) / 3600;
+}
+
+/**
+ * pmic_battery_get_property - battery power source get property
+ * @psy: battery power supply context
+ * @psp: battery power source property
+ * @val: battery power source property value
+ * Context: can sleep
+ *
+ * PMIC battery power source property needs to be provided to power_supply
+ * subsytem for it to provide the information to users.
+ */
+static int pmic_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmic_power_module_info *pbi = power_supply_get_drvdata(psy);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = pbi->batt_status;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = pbi->batt_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pbi->batt_is_present;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = mAStouAh(pbi->batt_charge_now);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = mAStouAh(pbi->batt_prev_charge_full);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+/**
+ * pmic_battery_monitor - monitor battery status
+ * @work: work structure
+ * Context: can sleep
+ *
+ * PMIC battery status needs to be monitored for any change
+ * and information needs to be frequently updated.
+ */
+static void pmic_battery_monitor(struct work_struct *work)
+{
+ struct pmic_power_module_info *pbi = container_of(work,
+ struct pmic_power_module_info, monitor_battery.work);
+
+ /* update pmic_power_module_info members */
+ pmic_battery_read_status(pbi);
+ queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 10);
+}
+
+/**
+ * pmic_battery_set_charger - set battery charger
+ * @pbi: device info structure
+ * @chrg: charge mode to set battery charger in
+ * Context: can sleep
+ *
+ * PMIC battery charger needs to be enabled based on the usb charge
+ * capabilities connected to the platform.
+ */
+static int pmic_battery_set_charger(struct pmic_power_module_info *pbi,
+ enum batt_charge_type chrg)
+{
+ int retval;
+
+ /* set usblmt bits and chrgcntl register bits appropriately */
+ switch (chrg) {
+ case BATT_USBOTG_500MA_CHARGE:
+ retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_FCHRG_SUBID);
+ break;
+ case BATT_USBOTG_TRICKLE_CHARGE:
+ retval = pmic_scu_ipc_set_charger(PMIC_BATT_CHR_IPC_TCHRG_SUBID);
+ break;
+ default:
+ dev_warn(pbi->dev, "%s(): out of range usb charger "
+ "charge detected\n", __func__);
+ return -EINVAL;
+ }
+
+ if (retval) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return retval;
+ }
+
+ return 0;
+}
+
+/**
+ * pmic_battery_interrupt_handler - pmic battery interrupt handler
+ * Context: interrupt context
+ *
+ * PMIC battery interrupt handler which will be called with either
+ * battery full condition occurs or usb otg & battery connect
+ * condition occurs.
+ */
+static irqreturn_t pmic_battery_interrupt_handler(int id, void *dev)
+{
+ struct pmic_power_module_info *pbi = dev;
+
+ schedule_work(&pbi->handler);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * pmic_battery_handle_intrpt - pmic battery service interrupt
+ * @work: work structure
+ * Context: can sleep
+ *
+ * PMIC battery needs to either update the battery status as full
+ * if it detects battery full condition caused the interrupt or needs
+ * to enable battery charger if it detects usb and battery detect
+ * caused the source of interrupt.
+ */
+static void pmic_battery_handle_intrpt(struct work_struct *work)
+{
+ struct pmic_power_module_info *pbi = container_of(work,
+ struct pmic_power_module_info, handler);
+ enum batt_charge_type chrg;
+ u8 r8;
+
+ if (intel_scu_ipc_ioread8(PMIC_BATT_CHR_SCHRGINT_ADDR, &r8)) {
+ dev_warn(pbi->dev, "%s(): ipc pmic read failed\n",
+ __func__);
+ return;
+ }
+ /* find the cause of the interrupt */
+ if (r8 & PMIC_BATT_CHR_SBATDET_MASK) {
+ pbi->batt_is_present = PMIC_BATT_PRESENT;
+ } else {
+ pbi->batt_is_present = PMIC_BATT_NOT_PRESENT;
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return;
+ }
+
+ if (r8 & PMIC_BATT_CHR_EXCPT_MASK) {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pbi->batt_status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ pmic_battery_log_event(BATT_EVENT_EXCPT);
+ return;
+ } else {
+ pbi->batt_health = POWER_SUPPLY_HEALTH_GOOD;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ if (r8 & PMIC_BATT_CHR_SCOMP_MASK) {
+ u32 ccval;
+ pbi->batt_status = POWER_SUPPLY_STATUS_FULL;
+
+ if (pmic_scu_ipc_battery_cc_read(&ccval)) {
+ dev_warn(pbi->dev, "%s(): ipc config cmd "
+ "failed\n", __func__);
+ return;
+ }
+ pbi->batt_prev_charge_full = ccval &
+ PMIC_BATT_ADC_ACCCHRGVAL_MASK;
+ return;
+ }
+
+ if (r8 & PMIC_BATT_CHR_SUSBDET_MASK) {
+ pbi->usb_is_present = PMIC_USB_PRESENT;
+ } else {
+ pbi->usb_is_present = PMIC_USB_NOT_PRESENT;
+ pbi->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ return;
+ }
+
+ /* setup battery charging */
+
+#if 0
+ /* check usb otg power capability and set charger accordingly */
+ retval = langwell_udc_maxpower(&power);
+ if (retval) {
+ dev_warn(pbi->dev,
+ "%s(): usb otg power query failed with error code %d\n",
+ __func__, retval);
+ return;
+ }
+
+ if (power >= 500)
+ chrg = BATT_USBOTG_500MA_CHARGE;
+ else
+#endif
+ chrg = BATT_USBOTG_TRICKLE_CHARGE;
+
+ /* enable battery charging */
+ if (pmic_battery_set_charger(pbi, chrg)) {
+ dev_warn(pbi->dev,
+ "%s(): failed to set up battery charging\n", __func__);
+ return;
+ }
+
+ dev_dbg(pbi->dev,
+ "pmic-battery: %s() - setting up battery charger successful\n",
+ __func__);
+}
+
+/*
+ * Description of power supplies
+ */
+static const struct power_supply_desc pmic_usb_desc = {
+ .name = "pmic-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = pmic_usb_props,
+ .num_properties = ARRAY_SIZE(pmic_usb_props),
+ .get_property = pmic_usb_get_property,
+};
+
+static const struct power_supply_desc pmic_batt_desc = {
+ .name = "pmic-batt",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = pmic_battery_props,
+ .num_properties = ARRAY_SIZE(pmic_battery_props),
+ .get_property = pmic_battery_get_property,
+};
+
+/**
+ * pmic_battery_probe - pmic battery initialize
+ * @irq: pmic battery device irq
+ * @dev: pmic battery device structure
+ * Context: can sleep
+ *
+ * PMIC battery initializes its internal data structue and other
+ * infrastructure components for it to work as expected.
+ */
+static int probe(int irq, struct device *dev)
+{
+ int retval = 0;
+ struct pmic_power_module_info *pbi;
+ struct power_supply_config psy_cfg = {};
+
+ dev_dbg(dev, "pmic-battery: found pmic battery device\n");
+
+ pbi = kzalloc(sizeof(*pbi), GFP_KERNEL);
+ if (!pbi) {
+ dev_err(dev, "%s(): memory allocation failed\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ pbi->dev = dev;
+ pbi->irq = irq;
+ dev_set_drvdata(dev, pbi);
+ psy_cfg.drv_data = pbi;
+
+ /* initialize all required framework before enabling interrupts */
+ INIT_WORK(&pbi->handler, pmic_battery_handle_intrpt);
+ INIT_DELAYED_WORK(&pbi->monitor_battery, pmic_battery_monitor);
+ pbi->monitor_wqueue = alloc_workqueue(dev_name(dev), WQ_MEM_RECLAIM, 0);
+ if (!pbi->monitor_wqueue) {
+ dev_err(dev, "%s(): wqueue init failed\n", __func__);
+ retval = -ESRCH;
+ goto wqueue_failed;
+ }
+
+ /* register interrupt */
+ retval = request_irq(pbi->irq, pmic_battery_interrupt_handler,
+ 0, DRIVER_NAME, pbi);
+ if (retval) {
+ dev_err(dev, "%s(): cannot get IRQ\n", __func__);
+ goto requestirq_failed;
+ }
+
+ /* register pmic-batt with power supply subsystem */
+ pbi->batt = power_supply_register(dev, &pmic_usb_desc, &psy_cfg);
+ if (IS_ERR(pbi->batt)) {
+ dev_err(dev,
+ "%s(): failed to register pmic battery device with power supply subsystem\n",
+ __func__);
+ retval = PTR_ERR(pbi->batt);
+ goto power_reg_failed;
+ }
+
+ dev_dbg(dev, "pmic-battery: %s() - pmic battery device "
+ "registration with power supply subsystem successful\n",
+ __func__);
+
+ queue_delayed_work(pbi->monitor_wqueue, &pbi->monitor_battery, HZ * 1);
+
+ /* register pmic-usb with power supply subsystem */
+ pbi->usb = power_supply_register(dev, &pmic_batt_desc, &psy_cfg);
+ if (IS_ERR(pbi->usb)) {
+ dev_err(dev,
+ "%s(): failed to register pmic usb device with power supply subsystem\n",
+ __func__);
+ retval = PTR_ERR(pbi->usb);
+ goto power_reg_failed_1;
+ }
+
+ if (debug)
+ printk(KERN_INFO "pmic-battery: %s() - pmic usb device "
+ "registration with power supply subsystem successful\n",
+ __func__);
+
+ return retval;
+
+power_reg_failed_1:
+ power_supply_unregister(pbi->batt);
+power_reg_failed:
+ cancel_delayed_work_sync(&pbi->monitor_battery);
+requestirq_failed:
+ destroy_workqueue(pbi->monitor_wqueue);
+wqueue_failed:
+ kfree(pbi);
+
+ return retval;
+}
+
+static int platform_pmic_battery_probe(struct platform_device *pdev)
+{
+ return probe(pdev->id, &pdev->dev);
+}
+
+/**
+ * pmic_battery_remove - pmic battery finalize
+ * @dev: pmic battery device structure
+ * Context: can sleep
+ *
+ * PMIC battery finalizes its internal data structue and other
+ * infrastructure components that it initialized in
+ * pmic_battery_probe.
+ */
+
+static int platform_pmic_battery_remove(struct platform_device *pdev)
+{
+ struct pmic_power_module_info *pbi = platform_get_drvdata(pdev);
+
+ free_irq(pbi->irq, pbi);
+ cancel_delayed_work_sync(&pbi->monitor_battery);
+ destroy_workqueue(pbi->monitor_wqueue);
+
+ power_supply_unregister(pbi->usb);
+ power_supply_unregister(pbi->batt);
+
+ cancel_work_sync(&pbi->handler);
+ kfree(pbi);
+ return 0;
+}
+
+static struct platform_driver platform_pmic_battery_driver = {
+ .driver = {
+ .name = DRIVER_NAME,
+ },
+ .probe = platform_pmic_battery_probe,
+ .remove = platform_pmic_battery_remove,
+};
+
+module_platform_driver(platform_pmic_battery_driver);
+
+MODULE_AUTHOR("Nithish Mahalingam <nithish.mahalingam@intel.com>");
+MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c
new file mode 100644
index 0000000..4af7b77
--- /dev/null
+++ b/drivers/power/supply/ipaq_micro_battery.c
@@ -0,0 +1,316 @@
+/*
+ * 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.
+ *
+ * h3xxx atmel micro companion support, battery subdevice
+ * based on previous kernel 2.4 version
+ * Author : Alessandro Gardich <gremlin@gremlin.it>
+ * Author : Linus Walleij <linus.walleij@linaro.org>
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/platform_device.h>
+#include <linux/mfd/ipaq-micro.h>
+#include <linux/power_supply.h>
+#include <linux/workqueue.h>
+
+#define BATT_PERIOD 100000 /* 100 seconds in milliseconds */
+
+#define MICRO_BATT_CHEM_ALKALINE 0x01
+#define MICRO_BATT_CHEM_NICD 0x02
+#define MICRO_BATT_CHEM_NIMH 0x03
+#define MICRO_BATT_CHEM_LION 0x04
+#define MICRO_BATT_CHEM_LIPOLY 0x05
+#define MICRO_BATT_CHEM_NOT_INSTALLED 0x06
+#define MICRO_BATT_CHEM_UNKNOWN 0xff
+
+#define MICRO_BATT_STATUS_HIGH 0x01
+#define MICRO_BATT_STATUS_LOW 0x02
+#define MICRO_BATT_STATUS_CRITICAL 0x04
+#define MICRO_BATT_STATUS_CHARGING 0x08
+#define MICRO_BATT_STATUS_CHARGEMAIN 0x10
+#define MICRO_BATT_STATUS_DEAD 0x20 /* Battery will not charge */
+#define MICRO_BATT_STATUS_NOTINSTALLED 0x20 /* For expansion pack batteries */
+#define MICRO_BATT_STATUS_FULL 0x40 /* Battery fully charged */
+#define MICRO_BATT_STATUS_NOBATTERY 0x80
+#define MICRO_BATT_STATUS_UNKNOWN 0xff
+
+struct micro_battery {
+ struct ipaq_micro *micro;
+ struct workqueue_struct *wq;
+ struct delayed_work update;
+ u8 ac;
+ u8 chemistry;
+ unsigned int voltage;
+ u16 temperature;
+ u8 flag;
+};
+
+static void micro_battery_work(struct work_struct *work)
+{
+ struct micro_battery *mb = container_of(work,
+ struct micro_battery, update.work);
+ struct ipaq_micro_msg msg_battery = {
+ .id = MSG_BATTERY,
+ };
+ struct ipaq_micro_msg msg_sensor = {
+ .id = MSG_THERMAL_SENSOR,
+ };
+
+ /* First send battery message */
+ ipaq_micro_tx_msg_sync(mb->micro, &msg_battery);
+ if (msg_battery.rx_len < 4)
+ pr_info("ERROR");
+
+ /*
+ * Returned message format:
+ * byte 0: 0x00 = Not plugged in
+ * 0x01 = AC adapter plugged in
+ * byte 1: chemistry
+ * byte 2: voltage LSB
+ * byte 3: voltage MSB
+ * byte 4: flags
+ * byte 5-9: same for battery 2
+ */
+ mb->ac = msg_battery.rx_data[0];
+ mb->chemistry = msg_battery.rx_data[1];
+ mb->voltage = ((((unsigned short)msg_battery.rx_data[3] << 8) +
+ msg_battery.rx_data[2]) * 5000L) * 1000 / 1024;
+ mb->flag = msg_battery.rx_data[4];
+
+ if (msg_battery.rx_len == 9)
+ pr_debug("second battery ignored\n");
+
+ /* Then read the sensor */
+ ipaq_micro_tx_msg_sync(mb->micro, &msg_sensor);
+ mb->temperature = msg_sensor.rx_data[1] << 8 | msg_sensor.rx_data[0];
+
+ queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD));
+}
+
+static int get_capacity(struct power_supply *b)
+{
+ struct micro_battery *mb = dev_get_drvdata(b->dev.parent);
+
+ switch (mb->flag & 0x07) {
+ case MICRO_BATT_STATUS_HIGH:
+ return 100;
+ break;
+ case MICRO_BATT_STATUS_LOW:
+ return 50;
+ break;
+ case MICRO_BATT_STATUS_CRITICAL:
+ return 5;
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int get_status(struct power_supply *b)
+{
+ struct micro_battery *mb = dev_get_drvdata(b->dev.parent);
+
+ if (mb->flag == MICRO_BATT_STATUS_UNKNOWN)
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (mb->flag & MICRO_BATT_STATUS_FULL)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ if ((mb->flag & MICRO_BATT_STATUS_CHARGING) ||
+ (mb->flag & MICRO_BATT_STATUS_CHARGEMAIN))
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int micro_batt_get_property(struct power_supply *b,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct micro_battery *mb = dev_get_drvdata(b->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ switch (mb->chemistry) {
+ case MICRO_BATT_CHEM_NICD:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NiCd;
+ break;
+ case MICRO_BATT_CHEM_NIMH:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
+ break;
+ case MICRO_BATT_CHEM_LION:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case MICRO_BATT_CHEM_LIPOLY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LIPO;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ break;
+ };
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = get_status(b);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = 4700000;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_capacity(b);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = mb->temperature;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = mb->voltage;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static int micro_ac_get_property(struct power_supply *b,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct micro_battery *mb = dev_get_drvdata(b->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mb->ac;
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ return 0;
+}
+
+static enum power_supply_property micro_batt_power_props[] = {
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static const struct power_supply_desc micro_batt_power_desc = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = micro_batt_power_props,
+ .num_properties = ARRAY_SIZE(micro_batt_power_props),
+ .get_property = micro_batt_get_property,
+ .use_for_apm = 1,
+};
+
+static enum power_supply_property micro_ac_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc micro_ac_power_desc = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = micro_ac_power_props,
+ .num_properties = ARRAY_SIZE(micro_ac_power_props),
+ .get_property = micro_ac_get_property,
+};
+
+static struct power_supply *micro_batt_power, *micro_ac_power;
+
+static int micro_batt_probe(struct platform_device *pdev)
+{
+ struct micro_battery *mb;
+ int ret;
+
+ mb = devm_kzalloc(&pdev->dev, sizeof(*mb), GFP_KERNEL);
+ if (!mb)
+ return -ENOMEM;
+
+ mb->micro = dev_get_drvdata(pdev->dev.parent);
+ mb->wq = alloc_workqueue("ipaq-battery-wq", WQ_MEM_RECLAIM, 0);
+ if (!mb->wq)
+ return -ENOMEM;
+
+ INIT_DELAYED_WORK(&mb->update, micro_battery_work);
+ platform_set_drvdata(pdev, mb);
+ queue_delayed_work(mb->wq, &mb->update, 1);
+
+ micro_batt_power = power_supply_register(&pdev->dev,
+ &micro_batt_power_desc, NULL);
+ if (IS_ERR(micro_batt_power)) {
+ ret = PTR_ERR(micro_batt_power);
+ goto batt_err;
+ }
+
+ micro_ac_power = power_supply_register(&pdev->dev,
+ &micro_ac_power_desc, NULL);
+ if (IS_ERR(micro_ac_power)) {
+ ret = PTR_ERR(micro_ac_power);
+ goto ac_err;
+ }
+
+ dev_info(&pdev->dev, "iPAQ micro battery driver\n");
+ return 0;
+
+ac_err:
+ power_supply_unregister(micro_batt_power);
+batt_err:
+ cancel_delayed_work_sync(&mb->update);
+ destroy_workqueue(mb->wq);
+ return ret;
+}
+
+static int micro_batt_remove(struct platform_device *pdev)
+
+{
+ struct micro_battery *mb = platform_get_drvdata(pdev);
+
+ power_supply_unregister(micro_ac_power);
+ power_supply_unregister(micro_batt_power);
+ cancel_delayed_work_sync(&mb->update);
+ destroy_workqueue(mb->wq);
+
+ return 0;
+}
+
+static int __maybe_unused micro_batt_suspend(struct device *dev)
+{
+ struct micro_battery *mb = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&mb->update);
+ return 0;
+}
+
+static int __maybe_unused micro_batt_resume(struct device *dev)
+{
+ struct micro_battery *mb = dev_get_drvdata(dev);
+
+ queue_delayed_work(mb->wq, &mb->update, msecs_to_jiffies(BATT_PERIOD));
+ return 0;
+}
+
+static const struct dev_pm_ops micro_batt_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(micro_batt_suspend, micro_batt_resume)
+};
+
+static struct platform_driver micro_batt_device_driver = {
+ .driver = {
+ .name = "ipaq-micro-battery",
+ .pm = &micro_batt_dev_pm_ops,
+ },
+ .probe = micro_batt_probe,
+ .remove = micro_batt_remove,
+};
+module_platform_driver(micro_batt_device_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("driver for iPAQ Atmel micro battery");
+MODULE_ALIAS("platform:battery-ipaq-micro");
diff --git a/drivers/power/supply/isp1704_charger.c b/drivers/power/supply/isp1704_charger.c
new file mode 100644
index 0000000..4cd6899
--- /dev/null
+++ b/drivers/power/supply/isp1704_charger.c
@@ -0,0 +1,559 @@
+/*
+ * ISP1704 USB Charger Detection driver
+ *
+ * Copyright (C) 2010 Nokia Corporation
+ * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@gmail.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
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+
+#include <linux/usb/otg.h>
+#include <linux/usb/ulpi.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/power/isp1704_charger.h>
+
+/* Vendor specific Power Control register */
+#define ISP1704_PWR_CTRL 0x3d
+#define ISP1704_PWR_CTRL_SWCTRL (1 << 0)
+#define ISP1704_PWR_CTRL_DET_COMP (1 << 1)
+#define ISP1704_PWR_CTRL_BVALID_RISE (1 << 2)
+#define ISP1704_PWR_CTRL_BVALID_FALL (1 << 3)
+#define ISP1704_PWR_CTRL_DP_WKPU_EN (1 << 4)
+#define ISP1704_PWR_CTRL_VDAT_DET (1 << 5)
+#define ISP1704_PWR_CTRL_DPVSRC_EN (1 << 6)
+#define ISP1704_PWR_CTRL_HWDETECT (1 << 7)
+
+#define NXP_VENDOR_ID 0x04cc
+
+static u16 isp170x_id[] = {
+ 0x1704,
+ 0x1707,
+};
+
+struct isp1704_charger {
+ struct device *dev;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ struct usb_phy *phy;
+ struct notifier_block nb;
+ struct work_struct work;
+
+ /* properties */
+ char model[8];
+ unsigned present:1;
+ unsigned online:1;
+ unsigned current_max;
+};
+
+static inline int isp1704_read(struct isp1704_charger *isp, u32 reg)
+{
+ return usb_phy_io_read(isp->phy, reg);
+}
+
+static inline int isp1704_write(struct isp1704_charger *isp, u32 reg, u32 val)
+{
+ return usb_phy_io_write(isp->phy, val, reg);
+}
+
+/*
+ * Disable/enable the power from the isp1704 if a function for it
+ * has been provided with platform data.
+ */
+static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on)
+{
+ struct isp1704_charger_data *board = isp->dev->platform_data;
+
+ if (board && board->set_power)
+ board->set_power(on);
+ else if (board)
+ gpio_set_value(board->enable_gpio, on);
+}
+
+/*
+ * Determine is the charging port DCP (dedicated charger) or CDP (Host/HUB
+ * chargers).
+ *
+ * REVISIT: The method is defined in Battery Charging Specification and is
+ * applicable to any ULPI transceiver. Nothing isp170x specific here.
+ */
+static inline int isp1704_charger_type(struct isp1704_charger *isp)
+{
+ u8 reg;
+ u8 func_ctrl;
+ u8 otg_ctrl;
+ int type = POWER_SUPPLY_TYPE_USB_DCP;
+
+ func_ctrl = isp1704_read(isp, ULPI_FUNC_CTRL);
+ otg_ctrl = isp1704_read(isp, ULPI_OTG_CTRL);
+
+ /* disable pulldowns */
+ reg = ULPI_OTG_CTRL_DM_PULLDOWN | ULPI_OTG_CTRL_DP_PULLDOWN;
+ isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg);
+
+ /* full speed */
+ isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_XCVRSEL_MASK);
+ isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_FULL_SPEED);
+
+ /* Enable strong pull-up on DP (1.5K) and reset */
+ reg = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+ isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg);
+ usleep_range(1000, 2000);
+
+ reg = isp1704_read(isp, ULPI_DEBUG);
+ if ((reg & 3) != 3)
+ type = POWER_SUPPLY_TYPE_USB_CDP;
+
+ /* recover original state */
+ isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl);
+ isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl);
+
+ return type;
+}
+
+/*
+ * ISP1704 detects PS/2 adapters as charger. To make sure the detected charger
+ * is actually a dedicated charger, the following steps need to be taken.
+ */
+static inline int isp1704_charger_verify(struct isp1704_charger *isp)
+{
+ int ret = 0;
+ u8 r;
+
+ /* Reset the transceiver */
+ r = isp1704_read(isp, ULPI_FUNC_CTRL);
+ r |= ULPI_FUNC_CTRL_RESET;
+ isp1704_write(isp, ULPI_FUNC_CTRL, r);
+ usleep_range(1000, 2000);
+
+ /* Set normal mode */
+ r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK);
+ isp1704_write(isp, ULPI_FUNC_CTRL, r);
+
+ /* Clear the DP and DM pull-down bits */
+ r = ULPI_OTG_CTRL_DP_PULLDOWN | ULPI_OTG_CTRL_DM_PULLDOWN;
+ isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), r);
+
+ /* Enable strong pull-up on DP (1.5K) and reset */
+ r = ULPI_FUNC_CTRL_TERMSELECT | ULPI_FUNC_CTRL_RESET;
+ isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r);
+ usleep_range(1000, 2000);
+
+ /* Read the line state */
+ if (!isp1704_read(isp, ULPI_DEBUG)) {
+ /* Disable strong pull-up on DP (1.5K) */
+ isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_TERMSELECT);
+ return 1;
+ }
+
+ /* Is it a charger or PS/2 connection */
+
+ /* Enable weak pull-up resistor on DP */
+ isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+ /* Disable strong pull-up on DP (1.5K) */
+ isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL),
+ ULPI_FUNC_CTRL_TERMSELECT);
+
+ /* Enable weak pull-down resistor on DM */
+ isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL),
+ ULPI_OTG_CTRL_DM_PULLDOWN);
+
+ /* It's a charger if the line states are clear */
+ if (!(isp1704_read(isp, ULPI_DEBUG)))
+ ret = 1;
+
+ /* Disable weak pull-up resistor on DP */
+ isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_DP_WKPU_EN);
+
+ return ret;
+}
+
+static inline int isp1704_charger_detect(struct isp1704_charger *isp)
+{
+ unsigned long timeout;
+ u8 pwr_ctrl;
+ int ret = 0;
+
+ pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL);
+
+ /* set SW control bit in PWR_CTRL register */
+ isp1704_write(isp, ISP1704_PWR_CTRL,
+ ISP1704_PWR_CTRL_SWCTRL);
+
+ /* enable manual charger detection */
+ isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL),
+ ISP1704_PWR_CTRL_SWCTRL
+ | ISP1704_PWR_CTRL_DPVSRC_EN);
+ usleep_range(1000, 2000);
+
+ timeout = jiffies + msecs_to_jiffies(300);
+ do {
+ /* Check if there is a charger */
+ if (isp1704_read(isp, ISP1704_PWR_CTRL)
+ & ISP1704_PWR_CTRL_VDAT_DET) {
+ ret = isp1704_charger_verify(isp);
+ break;
+ }
+ } while (!time_after(jiffies, timeout) && isp->online);
+
+ /* recover original state */
+ isp1704_write(isp, ISP1704_PWR_CTRL, pwr_ctrl);
+
+ return ret;
+}
+
+static inline int isp1704_charger_detect_dcp(struct isp1704_charger *isp)
+{
+ if (isp1704_charger_detect(isp) &&
+ isp1704_charger_type(isp) == POWER_SUPPLY_TYPE_USB_DCP)
+ return true;
+ else
+ return false;
+}
+
+static void isp1704_charger_work(struct work_struct *data)
+{
+ struct isp1704_charger *isp =
+ container_of(data, struct isp1704_charger, work);
+ static DEFINE_MUTEX(lock);
+
+ mutex_lock(&lock);
+
+ switch (isp->phy->last_event) {
+ case USB_EVENT_VBUS:
+ /* do not call wall charger detection more times */
+ if (!isp->present) {
+ isp->online = true;
+ isp->present = 1;
+ isp1704_charger_set_power(isp, 1);
+
+ /* detect wall charger */
+ if (isp1704_charger_detect_dcp(isp)) {
+ isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_DCP;
+ isp->current_max = 1800;
+ } else {
+ isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ isp->current_max = 500;
+ }
+
+ /* enable data pullups */
+ if (isp->phy->otg->gadget)
+ usb_gadget_connect(isp->phy->otg->gadget);
+ }
+
+ if (isp->psy_desc.type != POWER_SUPPLY_TYPE_USB_DCP) {
+ /*
+ * Only 500mA here or high speed chirp
+ * handshaking may break
+ */
+ if (isp->current_max > 500)
+ isp->current_max = 500;
+
+ if (isp->current_max > 100)
+ isp->psy_desc.type = POWER_SUPPLY_TYPE_USB_CDP;
+ }
+ break;
+ case USB_EVENT_NONE:
+ isp->online = false;
+ isp->present = 0;
+ isp->current_max = 0;
+ isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+
+ /*
+ * Disable data pullups. We need to prevent the controller from
+ * enumerating.
+ *
+ * FIXME: This is here to allow charger detection with Host/HUB
+ * chargers. The pullups may be enabled elsewhere, so this can
+ * not be the final solution.
+ */
+ if (isp->phy->otg->gadget)
+ usb_gadget_disconnect(isp->phy->otg->gadget);
+
+ isp1704_charger_set_power(isp, 0);
+ break;
+ default:
+ goto out;
+ }
+
+ power_supply_changed(isp->psy);
+out:
+ mutex_unlock(&lock);
+}
+
+static int isp1704_notifier_call(struct notifier_block *nb,
+ unsigned long val, void *v)
+{
+ struct isp1704_charger *isp =
+ container_of(nb, struct isp1704_charger, nb);
+
+ schedule_work(&isp->work);
+
+ return NOTIFY_OK;
+}
+
+static int isp1704_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct isp1704_charger *isp = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = isp->present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = isp->online;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = isp->current_max;
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = isp->model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "NXP";
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static inline int isp1704_test_ulpi(struct isp1704_charger *isp)
+{
+ int vendor;
+ int product;
+ int i;
+ int ret = -ENODEV;
+
+ /* Test ULPI interface */
+ ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa);
+ if (ret < 0)
+ return ret;
+
+ ret = isp1704_read(isp, ULPI_SCRATCH);
+ if (ret < 0)
+ return ret;
+
+ if (ret != 0xaa)
+ return -ENODEV;
+
+ /* Verify the product and vendor id matches */
+ vendor = isp1704_read(isp, ULPI_VENDOR_ID_LOW);
+ vendor |= isp1704_read(isp, ULPI_VENDOR_ID_HIGH) << 8;
+ if (vendor != NXP_VENDOR_ID)
+ return -ENODEV;
+
+ product = isp1704_read(isp, ULPI_PRODUCT_ID_LOW);
+ product |= isp1704_read(isp, ULPI_PRODUCT_ID_HIGH) << 8;
+
+ for (i = 0; i < ARRAY_SIZE(isp170x_id); i++) {
+ if (product == isp170x_id[i]) {
+ sprintf(isp->model, "isp%x", product);
+ return product;
+ }
+ }
+
+ dev_err(isp->dev, "product id %x not matching known ids", product);
+
+ return -ENODEV;
+}
+
+static int isp1704_charger_probe(struct platform_device *pdev)
+{
+ struct isp1704_charger *isp;
+ int ret = -ENODEV;
+ struct power_supply_config psy_cfg = {};
+
+ struct isp1704_charger_data *pdata = dev_get_platdata(&pdev->dev);
+ struct device_node *np = pdev->dev.of_node;
+
+ if (np) {
+ int gpio = of_get_named_gpio(np, "nxp,enable-gpio", 0);
+
+ if (gpio < 0) {
+ dev_err(&pdev->dev, "missing DT GPIO nxp,enable-gpio\n");
+ return gpio;
+ }
+
+ pdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct isp1704_charger_data), GFP_KERNEL);
+ pdata->enable_gpio = gpio;
+
+ dev_info(&pdev->dev, "init gpio %d\n", pdata->enable_gpio);
+
+ ret = devm_gpio_request_one(&pdev->dev, pdata->enable_gpio,
+ GPIOF_OUT_INIT_HIGH, "isp1704_reset");
+ if (ret) {
+ dev_err(&pdev->dev, "gpio request failed\n");
+ goto fail0;
+ }
+ }
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "missing platform data!\n");
+ return -ENODEV;
+ }
+
+
+ isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL);
+ if (!isp)
+ return -ENOMEM;
+
+ if (np)
+ isp->phy = devm_usb_get_phy_by_phandle(&pdev->dev, "usb-phy", 0);
+ else
+ isp->phy = devm_usb_get_phy(&pdev->dev, USB_PHY_TYPE_USB2);
+
+ if (IS_ERR(isp->phy)) {
+ ret = PTR_ERR(isp->phy);
+ dev_err(&pdev->dev, "usb_get_phy failed\n");
+ goto fail0;
+ }
+
+ isp->dev = &pdev->dev;
+ platform_set_drvdata(pdev, isp);
+
+ isp1704_charger_set_power(isp, 1);
+
+ ret = isp1704_test_ulpi(isp);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "isp1704_test_ulpi failed\n");
+ goto fail1;
+ }
+
+ isp->psy_desc.name = "isp1704";
+ isp->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ isp->psy_desc.properties = power_props;
+ isp->psy_desc.num_properties = ARRAY_SIZE(power_props);
+ isp->psy_desc.get_property = isp1704_charger_get_property;
+
+ psy_cfg.drv_data = isp;
+
+ isp->psy = power_supply_register(isp->dev, &isp->psy_desc, &psy_cfg);
+ if (IS_ERR(isp->psy)) {
+ ret = PTR_ERR(isp->psy);
+ dev_err(&pdev->dev, "power_supply_register failed\n");
+ goto fail1;
+ }
+
+ /*
+ * REVISIT: using work in order to allow the usb notifications to be
+ * made atomically in the future.
+ */
+ INIT_WORK(&isp->work, isp1704_charger_work);
+
+ isp->nb.notifier_call = isp1704_notifier_call;
+
+ ret = usb_register_notifier(isp->phy, &isp->nb);
+ if (ret) {
+ dev_err(&pdev->dev, "usb_register_notifier failed\n");
+ goto fail2;
+ }
+
+ dev_info(isp->dev, "registered with product id %s\n", isp->model);
+
+ /*
+ * Taking over the D+ pullup.
+ *
+ * FIXME: The device will be disconnected if it was already
+ * enumerated. The charger driver should be always loaded before any
+ * gadget is loaded.
+ */
+ if (isp->phy->otg->gadget)
+ usb_gadget_disconnect(isp->phy->otg->gadget);
+
+ if (isp->phy->last_event == USB_EVENT_NONE)
+ isp1704_charger_set_power(isp, 0);
+
+ /* Detect charger if VBUS is valid (the cable was already plugged). */
+ if (isp->phy->last_event == USB_EVENT_VBUS &&
+ !isp->phy->otg->default_a)
+ schedule_work(&isp->work);
+
+ return 0;
+fail2:
+ power_supply_unregister(isp->psy);
+fail1:
+ isp1704_charger_set_power(isp, 0);
+fail0:
+ dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret);
+
+ return ret;
+}
+
+static int isp1704_charger_remove(struct platform_device *pdev)
+{
+ struct isp1704_charger *isp = platform_get_drvdata(pdev);
+
+ usb_unregister_notifier(isp->phy, &isp->nb);
+ power_supply_unregister(isp->psy);
+ isp1704_charger_set_power(isp, 0);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id omap_isp1704_of_match[] = {
+ { .compatible = "nxp,isp1704", },
+ { .compatible = "nxp,isp1707", },
+ {},
+};
+MODULE_DEVICE_TABLE(of, omap_isp1704_of_match);
+#endif
+
+static struct platform_driver isp1704_charger_driver = {
+ .driver = {
+ .name = "isp1704_charger",
+ .of_match_table = of_match_ptr(omap_isp1704_of_match),
+ },
+ .probe = isp1704_charger_probe,
+ .remove = isp1704_charger_remove,
+};
+
+module_platform_driver(isp1704_charger_driver);
+
+MODULE_ALIAS("platform:isp1704_charger");
+MODULE_AUTHOR("Nokia Corporation");
+MODULE_DESCRIPTION("ISP170x USB Charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/jz4740-battery.c b/drivers/power/supply/jz4740-battery.c
new file mode 100644
index 0000000..88f04f4
--- /dev/null
+++ b/drivers/power/supply/jz4740-battery.c
@@ -0,0 +1,425 @@
+/*
+ * Battery measurement code for Ingenic JZ SOC.
+ *
+ * Copyright (C) 2009 Jiejing Zhang <kzjeef@gmail.com>
+ * Copyright (C) 2010, Lars-Peter Clausen <lars@metafoo.de>
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.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/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/io.h>
+
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/gpio.h>
+#include <linux/mfd/core.h>
+#include <linux/power_supply.h>
+
+#include <linux/power/jz4740-battery.h>
+#include <linux/jz4740-adc.h>
+
+struct jz_battery {
+ struct jz_battery_platform_data *pdata;
+ struct platform_device *pdev;
+
+ void __iomem *base;
+
+ int irq;
+ int charge_irq;
+
+ const struct mfd_cell *cell;
+
+ int status;
+ long voltage;
+
+ struct completion read_completion;
+
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
+ struct delayed_work work;
+
+ struct mutex lock;
+};
+
+static inline struct jz_battery *psy_to_jz_battery(struct power_supply *psy)
+{
+ return power_supply_get_drvdata(psy);
+}
+
+static irqreturn_t jz_battery_irq_handler(int irq, void *devid)
+{
+ struct jz_battery *battery = devid;
+
+ complete(&battery->read_completion);
+ return IRQ_HANDLED;
+}
+
+static long jz_battery_read_voltage(struct jz_battery *battery)
+{
+ long t;
+ unsigned long val;
+ long voltage;
+
+ mutex_lock(&battery->lock);
+
+ reinit_completion(&battery->read_completion);
+
+ enable_irq(battery->irq);
+ battery->cell->enable(battery->pdev);
+
+ t = wait_for_completion_interruptible_timeout(&battery->read_completion,
+ HZ);
+
+ if (t > 0) {
+ val = readw(battery->base) & 0xfff;
+
+ if (battery->pdata->info.voltage_max_design <= 2500000)
+ val = (val * 78125UL) >> 7UL;
+ else
+ val = ((val * 924375UL) >> 9UL) + 33000;
+ voltage = (long)val;
+ } else {
+ voltage = t ? t : -ETIMEDOUT;
+ }
+
+ battery->cell->disable(battery->pdev);
+ disable_irq(battery->irq);
+
+ mutex_unlock(&battery->lock);
+
+ return voltage;
+}
+
+static int jz_battery_get_capacity(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+ int ret;
+ int voltage_span;
+
+ voltage = jz_battery_read_voltage(jz_battery);
+
+ if (voltage < 0)
+ return voltage;
+
+ voltage_span = info->voltage_max_design - info->voltage_min_design;
+ ret = ((voltage - info->voltage_min_design) * 100) / voltage_span;
+
+ if (ret > 100)
+ ret = 100;
+ else if (ret < 0)
+ ret = 0;
+
+ return ret;
+}
+
+static int jz_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+ struct power_supply_info *info = &jz_battery->pdata->info;
+ long voltage;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = jz_battery->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = jz_battery->pdata->info.technology;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (voltage < info->voltage_min_design)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = jz_battery_get_capacity(psy);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = jz_battery_read_voltage(jz_battery);
+ if (val->intval < 0)
+ return val->intval;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = info->voltage_max_design;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = info->voltage_min_design;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void jz_battery_external_power_changed(struct power_supply *psy)
+{
+ struct jz_battery *jz_battery = psy_to_jz_battery(psy);
+
+ mod_delayed_work(system_wq, &jz_battery->work, 0);
+}
+
+static irqreturn_t jz_battery_charge_irq(int irq, void *data)
+{
+ struct jz_battery *jz_battery = data;
+
+ mod_delayed_work(system_wq, &jz_battery->work, 0);
+
+ return IRQ_HANDLED;
+}
+
+static void jz_battery_update(struct jz_battery *jz_battery)
+{
+ int status;
+ long voltage;
+ bool has_changed = false;
+ int is_charging;
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ is_charging = gpio_get_value(jz_battery->pdata->gpio_charge);
+ is_charging ^= jz_battery->pdata->gpio_charge_active_low;
+ if (is_charging)
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+ if (status != jz_battery->status) {
+ jz_battery->status = status;
+ has_changed = true;
+ }
+ }
+
+ voltage = jz_battery_read_voltage(jz_battery);
+ if (voltage >= 0 && abs(voltage - jz_battery->voltage) > 50000) {
+ jz_battery->voltage = voltage;
+ has_changed = true;
+ }
+
+ if (has_changed)
+ power_supply_changed(jz_battery->battery);
+}
+
+static enum power_supply_property jz_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static void jz_battery_work(struct work_struct *work)
+{
+ /* Too small interval will increase system workload */
+ const int interval = HZ * 30;
+ struct jz_battery *jz_battery = container_of(work, struct jz_battery,
+ work.work);
+
+ jz_battery_update(jz_battery);
+ schedule_delayed_work(&jz_battery->work, interval);
+}
+
+static int jz_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct jz_battery *jz_battery;
+ struct power_supply_desc *battery_desc;
+ struct resource *mem;
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "No platform_data supplied\n");
+ return -ENXIO;
+ }
+
+ jz_battery = devm_kzalloc(&pdev->dev, sizeof(*jz_battery), GFP_KERNEL);
+ if (!jz_battery) {
+ dev_err(&pdev->dev, "Failed to allocate driver structure\n");
+ return -ENOMEM;
+ }
+
+ jz_battery->cell = mfd_get_cell(pdev);
+
+ jz_battery->irq = platform_get_irq(pdev, 0);
+ if (jz_battery->irq < 0) {
+ dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret);
+ return jz_battery->irq;
+ }
+
+ mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+
+ jz_battery->base = devm_ioremap_resource(&pdev->dev, mem);
+ if (IS_ERR(jz_battery->base))
+ return PTR_ERR(jz_battery->base);
+
+ battery_desc = &jz_battery->battery_desc;
+ battery_desc->name = pdata->info.name;
+ battery_desc->type = POWER_SUPPLY_TYPE_BATTERY;
+ battery_desc->properties = jz_battery_properties;
+ battery_desc->num_properties = ARRAY_SIZE(jz_battery_properties);
+ battery_desc->get_property = jz_battery_get_property;
+ battery_desc->external_power_changed =
+ jz_battery_external_power_changed;
+ battery_desc->use_for_apm = 1;
+
+ psy_cfg.drv_data = jz_battery;
+
+ jz_battery->pdata = pdata;
+ jz_battery->pdev = pdev;
+
+ init_completion(&jz_battery->read_completion);
+ mutex_init(&jz_battery->lock);
+
+ INIT_DELAYED_WORK(&jz_battery->work, jz_battery_work);
+
+ ret = request_irq(jz_battery->irq, jz_battery_irq_handler, 0, pdev->name,
+ jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request irq %d\n", ret);
+ return ret;
+ }
+ disable_irq(jz_battery->irq);
+
+ if (gpio_is_valid(pdata->gpio_charge)) {
+ ret = gpio_request(pdata->gpio_charge, dev_name(&pdev->dev));
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio request failed.\n");
+ goto err_free_irq;
+ }
+ ret = gpio_direction_input(pdata->gpio_charge);
+ if (ret) {
+ dev_err(&pdev->dev, "charger state gpio set direction failed.\n");
+ goto err_free_gpio;
+ }
+
+ jz_battery->charge_irq = gpio_to_irq(pdata->gpio_charge);
+
+ if (jz_battery->charge_irq >= 0) {
+ ret = request_irq(jz_battery->charge_irq,
+ jz_battery_charge_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&pdev->dev), jz_battery);
+ if (ret) {
+ dev_err(&pdev->dev, "Failed to request charge irq: %d\n", ret);
+ goto err_free_gpio;
+ }
+ }
+ } else {
+ jz_battery->charge_irq = -1;
+ }
+
+ if (jz_battery->pdata->info.voltage_max_design <= 2500000)
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB,
+ JZ_ADC_CONFIG_BAT_MB);
+ else
+ jz4740_adc_set_config(pdev->dev.parent, JZ_ADC_CONFIG_BAT_MB, 0);
+
+ jz_battery->battery = power_supply_register(&pdev->dev, battery_desc,
+ &psy_cfg);
+ if (IS_ERR(jz_battery->battery)) {
+ dev_err(&pdev->dev, "power supply battery register failed.\n");
+ ret = PTR_ERR(jz_battery->battery);
+ goto err_free_charge_irq;
+ }
+
+ platform_set_drvdata(pdev, jz_battery);
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+
+err_free_charge_irq:
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+err_free_gpio:
+ if (gpio_is_valid(pdata->gpio_charge))
+ gpio_free(jz_battery->pdata->gpio_charge);
+err_free_irq:
+ free_irq(jz_battery->irq, jz_battery);
+ return ret;
+}
+
+static int jz_battery_remove(struct platform_device *pdev)
+{
+ struct jz_battery *jz_battery = platform_get_drvdata(pdev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+
+ if (gpio_is_valid(jz_battery->pdata->gpio_charge)) {
+ if (jz_battery->charge_irq >= 0)
+ free_irq(jz_battery->charge_irq, jz_battery);
+ gpio_free(jz_battery->pdata->gpio_charge);
+ }
+
+ power_supply_unregister(jz_battery->battery);
+
+ free_irq(jz_battery->irq, jz_battery);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int jz_battery_suspend(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ cancel_delayed_work_sync(&jz_battery->work);
+ jz_battery->status = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ return 0;
+}
+
+static int jz_battery_resume(struct device *dev)
+{
+ struct jz_battery *jz_battery = dev_get_drvdata(dev);
+
+ schedule_delayed_work(&jz_battery->work, 0);
+
+ return 0;
+}
+
+static const struct dev_pm_ops jz_battery_pm_ops = {
+ .suspend = jz_battery_suspend,
+ .resume = jz_battery_resume,
+};
+
+#define JZ_BATTERY_PM_OPS (&jz_battery_pm_ops)
+#else
+#define JZ_BATTERY_PM_OPS NULL
+#endif
+
+static struct platform_driver jz_battery_driver = {
+ .probe = jz_battery_probe,
+ .remove = jz_battery_remove,
+ .driver = {
+ .name = "jz4740-battery",
+ .pm = JZ_BATTERY_PM_OPS,
+ },
+};
+
+module_platform_driver(jz_battery_driver);
+
+MODULE_ALIAS("platform:jz4740-battery");
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Lars-Peter Clausen <lars@metafoo.de>");
+MODULE_DESCRIPTION("JZ4740 SoC battery driver");
diff --git a/drivers/power/supply/lp8727_charger.c b/drivers/power/supply/lp8727_charger.c
new file mode 100644
index 0000000..042fb3da
--- /dev/null
+++ b/drivers/power/supply/lp8727_charger.c
@@ -0,0 +1,631 @@
+/*
+ * Driver for LP8727 Micro/Mini USB IC with integrated charger
+ *
+ * Copyright (C) 2011 Texas Instruments
+ * Copyright (C) 2011 National Semiconductor
+ *
+ * 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/slab.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/power_supply.h>
+#include <linux/platform_data/lp8727.h>
+#include <linux/of.h>
+
+#define LP8788_NUM_INTREGS 2
+#define DEFAULT_DEBOUNCE_MSEC 270
+
+/* Registers */
+#define LP8727_CTRL1 0x1
+#define LP8727_CTRL2 0x2
+#define LP8727_SWCTRL 0x3
+#define LP8727_INT1 0x4
+#define LP8727_INT2 0x5
+#define LP8727_STATUS1 0x6
+#define LP8727_STATUS2 0x7
+#define LP8727_CHGCTRL2 0x9
+
+/* CTRL1 register */
+#define LP8727_CP_EN BIT(0)
+#define LP8727_ADC_EN BIT(1)
+#define LP8727_ID200_EN BIT(4)
+
+/* CTRL2 register */
+#define LP8727_CHGDET_EN BIT(1)
+#define LP8727_INT_EN BIT(6)
+
+/* SWCTRL register */
+#define LP8727_SW_DM1_DM (0x0 << 0)
+#define LP8727_SW_DM1_HiZ (0x7 << 0)
+#define LP8727_SW_DP2_DP (0x0 << 3)
+#define LP8727_SW_DP2_HiZ (0x7 << 3)
+
+/* INT1 register */
+#define LP8727_IDNO (0xF << 0)
+#define LP8727_VBUS BIT(4)
+
+/* STATUS1 register */
+#define LP8727_CHGSTAT (3 << 4)
+#define LP8727_CHPORT BIT(6)
+#define LP8727_DCPORT BIT(7)
+#define LP8727_STAT_EOC 0x30
+
+/* STATUS2 register */
+#define LP8727_TEMP_STAT (3 << 5)
+#define LP8727_TEMP_SHIFT 5
+
+/* CHGCTRL2 register */
+#define LP8727_ICHG_SHIFT 4
+
+enum lp8727_dev_id {
+ LP8727_ID_NONE,
+ LP8727_ID_TA,
+ LP8727_ID_DEDICATED_CHG,
+ LP8727_ID_USB_CHG,
+ LP8727_ID_USB_DS,
+ LP8727_ID_MAX,
+};
+
+enum lp8727_die_temp {
+ LP8788_TEMP_75C,
+ LP8788_TEMP_95C,
+ LP8788_TEMP_115C,
+ LP8788_TEMP_135C,
+};
+
+struct lp8727_psy {
+ struct power_supply *ac;
+ struct power_supply *usb;
+ struct power_supply *batt;
+};
+
+struct lp8727_chg {
+ struct device *dev;
+ struct i2c_client *client;
+ struct mutex xfer_lock;
+ struct lp8727_psy *psy;
+ struct lp8727_platform_data *pdata;
+
+ /* Charger Data */
+ enum lp8727_dev_id devid;
+ struct lp8727_chg_param *chg_param;
+
+ /* Interrupt Handling */
+ int irq;
+ struct delayed_work work;
+ unsigned long debounce_jiffies;
+};
+
+static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len)
+{
+ s32 ret;
+
+ mutex_lock(&pchg->xfer_lock);
+ ret = i2c_smbus_read_i2c_block_data(pchg->client, reg, len, data);
+ mutex_unlock(&pchg->xfer_lock);
+
+ return (ret != len) ? -EIO : 0;
+}
+
+static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data)
+{
+ return lp8727_read_bytes(pchg, reg, data, 1);
+}
+
+static int lp8727_write_byte(struct lp8727_chg *pchg, u8 reg, u8 data)
+{
+ int ret;
+
+ mutex_lock(&pchg->xfer_lock);
+ ret = i2c_smbus_write_byte_data(pchg->client, reg, data);
+ mutex_unlock(&pchg->xfer_lock);
+
+ return ret;
+}
+
+static bool lp8727_is_charger_attached(const char *name, int id)
+{
+ if (!strcmp(name, "ac"))
+ return id == LP8727_ID_TA || id == LP8727_ID_DEDICATED_CHG;
+ else if (!strcmp(name, "usb"))
+ return id == LP8727_ID_USB_CHG;
+
+ return id >= LP8727_ID_TA && id <= LP8727_ID_USB_CHG;
+}
+
+static int lp8727_init_device(struct lp8727_chg *pchg)
+{
+ u8 val;
+ int ret;
+ u8 intstat[LP8788_NUM_INTREGS];
+
+ /* clear interrupts */
+ ret = lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS);
+ if (ret)
+ return ret;
+
+ val = LP8727_ID200_EN | LP8727_ADC_EN | LP8727_CP_EN;
+ ret = lp8727_write_byte(pchg, LP8727_CTRL1, val);
+ if (ret)
+ return ret;
+
+ val = LP8727_INT_EN | LP8727_CHGDET_EN;
+ return lp8727_write_byte(pchg, LP8727_CTRL2, val);
+}
+
+static int lp8727_is_dedicated_charger(struct lp8727_chg *pchg)
+{
+ u8 val;
+
+ lp8727_read_byte(pchg, LP8727_STATUS1, &val);
+ return val & LP8727_DCPORT;
+}
+
+static int lp8727_is_usb_charger(struct lp8727_chg *pchg)
+{
+ u8 val;
+
+ lp8727_read_byte(pchg, LP8727_STATUS1, &val);
+ return val & LP8727_CHPORT;
+}
+
+static inline void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw)
+{
+ lp8727_write_byte(pchg, LP8727_SWCTRL, sw);
+}
+
+static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin)
+{
+ struct lp8727_platform_data *pdata = pchg->pdata;
+ u8 devid = LP8727_ID_NONE;
+ u8 swctrl = LP8727_SW_DM1_HiZ | LP8727_SW_DP2_HiZ;
+
+ switch (id) {
+ case 0x5:
+ devid = LP8727_ID_TA;
+ pchg->chg_param = pdata ? pdata->ac : NULL;
+ break;
+ case 0xB:
+ if (lp8727_is_dedicated_charger(pchg)) {
+ pchg->chg_param = pdata ? pdata->ac : NULL;
+ devid = LP8727_ID_DEDICATED_CHG;
+ } else if (lp8727_is_usb_charger(pchg)) {
+ pchg->chg_param = pdata ? pdata->usb : NULL;
+ devid = LP8727_ID_USB_CHG;
+ swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP;
+ } else if (vbusin) {
+ devid = LP8727_ID_USB_DS;
+ swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP;
+ }
+ break;
+ default:
+ devid = LP8727_ID_NONE;
+ pchg->chg_param = NULL;
+ break;
+ }
+
+ pchg->devid = devid;
+ lp8727_ctrl_switch(pchg, swctrl);
+}
+
+static void lp8727_enable_chgdet(struct lp8727_chg *pchg)
+{
+ u8 val;
+
+ lp8727_read_byte(pchg, LP8727_CTRL2, &val);
+ val |= LP8727_CHGDET_EN;
+ lp8727_write_byte(pchg, LP8727_CTRL2, val);
+}
+
+static void lp8727_delayed_func(struct work_struct *_work)
+{
+ struct lp8727_chg *pchg = container_of(_work, struct lp8727_chg,
+ work.work);
+ u8 intstat[LP8788_NUM_INTREGS];
+ u8 idno;
+ u8 vbus;
+
+ if (lp8727_read_bytes(pchg, LP8727_INT1, intstat, LP8788_NUM_INTREGS)) {
+ dev_err(pchg->dev, "can not read INT registers\n");
+ return;
+ }
+
+ idno = intstat[0] & LP8727_IDNO;
+ vbus = intstat[0] & LP8727_VBUS;
+
+ lp8727_id_detection(pchg, idno, vbus);
+ lp8727_enable_chgdet(pchg);
+
+ power_supply_changed(pchg->psy->ac);
+ power_supply_changed(pchg->psy->usb);
+ power_supply_changed(pchg->psy->batt);
+}
+
+static irqreturn_t lp8727_isr_func(int irq, void *ptr)
+{
+ struct lp8727_chg *pchg = ptr;
+
+ schedule_delayed_work(&pchg->work, pchg->debounce_jiffies);
+ return IRQ_HANDLED;
+}
+
+static int lp8727_setup_irq(struct lp8727_chg *pchg)
+{
+ int ret;
+ int irq = pchg->client->irq;
+ unsigned delay_msec = pchg->pdata ? pchg->pdata->debounce_msec :
+ DEFAULT_DEBOUNCE_MSEC;
+
+ INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func);
+
+ if (irq <= 0) {
+ dev_warn(pchg->dev, "invalid irq number: %d\n", irq);
+ return 0;
+ }
+
+ ret = request_threaded_irq(irq, NULL, lp8727_isr_func,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ "lp8727_irq", pchg);
+
+ if (ret)
+ return ret;
+
+ pchg->irq = irq;
+ pchg->debounce_jiffies = msecs_to_jiffies(delay_msec);
+
+ return 0;
+}
+
+static void lp8727_release_irq(struct lp8727_chg *pchg)
+{
+ cancel_delayed_work_sync(&pchg->work);
+
+ if (pchg->irq)
+ free_irq(pchg->irq, pchg);
+}
+
+static enum power_supply_property lp8727_charger_prop[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property lp8727_battery_prop[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static char *battery_supplied_to[] = {
+ "main_batt",
+};
+
+static int lp8727_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent);
+
+ if (psp != POWER_SUPPLY_PROP_ONLINE)
+ return -EINVAL;
+
+ val->intval = lp8727_is_charger_attached(psy->desc->name, pchg->devid);
+
+ return 0;
+}
+
+static bool lp8727_is_high_temperature(enum lp8727_die_temp temp)
+{
+ switch (temp) {
+ case LP8788_TEMP_95C:
+ case LP8788_TEMP_115C:
+ case LP8788_TEMP_135C:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static int lp8727_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent);
+ struct lp8727_platform_data *pdata = pchg->pdata;
+ enum lp8727_die_temp temp;
+ u8 read;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid)) {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ lp8727_read_byte(pchg, LP8727_STATUS1, &read);
+
+ val->intval = (read & LP8727_CHGSTAT) == LP8727_STAT_EOC ?
+ POWER_SUPPLY_STATUS_FULL :
+ POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ lp8727_read_byte(pchg, LP8727_STATUS2, &read);
+ temp = (read & LP8727_TEMP_STAT) >> LP8727_TEMP_SHIFT;
+
+ val->intval = lp8727_is_high_temperature(temp) ?
+ POWER_SUPPLY_HEALTH_OVERHEAT :
+ POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->get_batt_present)
+ val->intval = pdata->get_batt_present();
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->get_batt_level)
+ val->intval = pdata->get_batt_level();
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->get_batt_capacity)
+ val->intval = pdata->get_batt_capacity();
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->get_batt_temp)
+ val->intval = pdata->get_batt_temp();
+ break;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static void lp8727_charger_changed(struct power_supply *psy)
+{
+ struct lp8727_chg *pchg = dev_get_drvdata(psy->dev.parent);
+ u8 eoc_level;
+ u8 ichg;
+ u8 val;
+
+ /* skip if no charger exists */
+ if (!lp8727_is_charger_attached(psy->desc->name, pchg->devid))
+ return;
+
+ /* update charging parameters */
+ if (pchg->chg_param) {
+ eoc_level = pchg->chg_param->eoc_level;
+ ichg = pchg->chg_param->ichg;
+ val = (ichg << LP8727_ICHG_SHIFT) | eoc_level;
+ lp8727_write_byte(pchg, LP8727_CHGCTRL2, val);
+ }
+}
+
+static const struct power_supply_desc lp8727_ac_desc = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = lp8727_charger_prop,
+ .num_properties = ARRAY_SIZE(lp8727_charger_prop),
+ .get_property = lp8727_charger_get_property,
+};
+
+static const struct power_supply_desc lp8727_usb_desc = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = lp8727_charger_prop,
+ .num_properties = ARRAY_SIZE(lp8727_charger_prop),
+ .get_property = lp8727_charger_get_property,
+};
+
+static const struct power_supply_desc lp8727_batt_desc = {
+ .name = "main_batt",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = lp8727_battery_prop,
+ .num_properties = ARRAY_SIZE(lp8727_battery_prop),
+ .get_property = lp8727_battery_get_property,
+ .external_power_changed = lp8727_charger_changed,
+};
+
+static int lp8727_register_psy(struct lp8727_chg *pchg)
+{
+ struct power_supply_config psy_cfg = {}; /* Only for ac and usb */
+ struct lp8727_psy *psy;
+
+ psy = devm_kzalloc(pchg->dev, sizeof(*psy), GFP_KERNEL);
+ if (!psy)
+ return -ENOMEM;
+
+ pchg->psy = psy;
+
+ psy_cfg.supplied_to = battery_supplied_to;
+ psy_cfg.num_supplicants = ARRAY_SIZE(battery_supplied_to);
+
+ psy->ac = power_supply_register(pchg->dev, &lp8727_ac_desc, &psy_cfg);
+ if (IS_ERR(psy->ac))
+ goto err_psy_ac;
+
+ psy->usb = power_supply_register(pchg->dev, &lp8727_usb_desc,
+ &psy_cfg);
+ if (IS_ERR(psy->usb))
+ goto err_psy_usb;
+
+ psy->batt = power_supply_register(pchg->dev, &lp8727_batt_desc, NULL);
+ if (IS_ERR(psy->batt))
+ goto err_psy_batt;
+
+ return 0;
+
+err_psy_batt:
+ power_supply_unregister(psy->usb);
+err_psy_usb:
+ power_supply_unregister(psy->ac);
+err_psy_ac:
+ return -EPERM;
+}
+
+static void lp8727_unregister_psy(struct lp8727_chg *pchg)
+{
+ struct lp8727_psy *psy = pchg->psy;
+
+ if (!psy)
+ return;
+
+ power_supply_unregister(psy->ac);
+ power_supply_unregister(psy->usb);
+ power_supply_unregister(psy->batt);
+}
+
+#ifdef CONFIG_OF
+static struct lp8727_chg_param
+*lp8727_parse_charge_pdata(struct device *dev, struct device_node *np)
+{
+ struct lp8727_chg_param *param;
+
+ param = devm_kzalloc(dev, sizeof(*param), GFP_KERNEL);
+ if (!param)
+ goto out;
+
+ of_property_read_u8(np, "eoc-level", (u8 *)&param->eoc_level);
+ of_property_read_u8(np, "charging-current", (u8 *)&param->ichg);
+out:
+ return param;
+}
+
+static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct device_node *child;
+ struct lp8727_platform_data *pdata;
+ const char *type;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec);
+
+ /* If charging parameter is not defined, just skip parsing the dt */
+ if (of_get_child_count(np) == 0)
+ return pdata;
+
+ for_each_child_of_node(np, child) {
+ of_property_read_string(child, "charger-type", &type);
+
+ if (!strcmp(type, "ac"))
+ pdata->ac = lp8727_parse_charge_pdata(dev, child);
+
+ if (!strcmp(type, "usb"))
+ pdata->usb = lp8727_parse_charge_pdata(dev, child);
+ }
+
+ return pdata;
+}
+#else
+static struct lp8727_platform_data *lp8727_parse_dt(struct device *dev)
+{
+ return NULL;
+}
+#endif
+
+static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id)
+{
+ struct lp8727_chg *pchg;
+ struct lp8727_platform_data *pdata;
+ int ret;
+
+ if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK))
+ return -EIO;
+
+ if (cl->dev.of_node) {
+ pdata = lp8727_parse_dt(&cl->dev);
+ if (IS_ERR(pdata))
+ return PTR_ERR(pdata);
+ } else {
+ pdata = dev_get_platdata(&cl->dev);
+ }
+
+ pchg = devm_kzalloc(&cl->dev, sizeof(*pchg), GFP_KERNEL);
+ if (!pchg)
+ return -ENOMEM;
+
+ pchg->client = cl;
+ pchg->dev = &cl->dev;
+ pchg->pdata = pdata;
+ i2c_set_clientdata(cl, pchg);
+
+ mutex_init(&pchg->xfer_lock);
+
+ ret = lp8727_init_device(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "i2c communication err: %d", ret);
+ return ret;
+ }
+
+ ret = lp8727_register_psy(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "power supplies register err: %d", ret);
+ return ret;
+ }
+
+ ret = lp8727_setup_irq(pchg);
+ if (ret) {
+ dev_err(pchg->dev, "irq handler err: %d", ret);
+ lp8727_unregister_psy(pchg);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int lp8727_remove(struct i2c_client *cl)
+{
+ struct lp8727_chg *pchg = i2c_get_clientdata(cl);
+
+ lp8727_release_irq(pchg);
+ lp8727_unregister_psy(pchg);
+ return 0;
+}
+
+static const struct of_device_id lp8727_dt_ids[] = {
+ { .compatible = "ti,lp8727", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, lp8727_dt_ids);
+
+static const struct i2c_device_id lp8727_ids[] = {
+ {"lp8727", 0},
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, lp8727_ids);
+
+static struct i2c_driver lp8727_driver = {
+ .driver = {
+ .name = "lp8727",
+ .of_match_table = of_match_ptr(lp8727_dt_ids),
+ },
+ .probe = lp8727_probe,
+ .remove = lp8727_remove,
+ .id_table = lp8727_ids,
+};
+module_i2c_driver(lp8727_driver);
+
+MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver");
+MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>, Daniel Jeong <daniel.jeong@ti.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/lp8788-charger.c b/drivers/power/supply/lp8788-charger.c
new file mode 100644
index 0000000..7321b72
--- /dev/null
+++ b/drivers/power/supply/lp8788-charger.c
@@ -0,0 +1,764 @@
+/*
+ * TI LP8788 MFD - battery charger driver
+ *
+ * Copyright 2012 Texas Instruments
+ *
+ * Author: Milo(Woogyom) Kim <milo.kim@ti.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/err.h>
+#include <linux/iio/consumer.h>
+#include <linux/interrupt.h>
+#include <linux/irqdomain.h>
+#include <linux/mfd/lp8788.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+/* register address */
+#define LP8788_CHG_STATUS 0x07
+#define LP8788_CHG_IDCIN 0x13
+#define LP8788_CHG_IBATT 0x14
+#define LP8788_CHG_VTERM 0x15
+#define LP8788_CHG_EOC 0x16
+
+/* mask/shift bits */
+#define LP8788_CHG_INPUT_STATE_M 0x03 /* Addr 07h */
+#define LP8788_CHG_STATE_M 0x3C
+#define LP8788_CHG_STATE_S 2
+#define LP8788_NO_BATT_M BIT(6)
+#define LP8788_BAD_BATT_M BIT(7)
+#define LP8788_CHG_IBATT_M 0x1F /* Addr 14h */
+#define LP8788_CHG_VTERM_M 0x0F /* Addr 15h */
+#define LP8788_CHG_EOC_LEVEL_M 0x30 /* Addr 16h */
+#define LP8788_CHG_EOC_LEVEL_S 4
+#define LP8788_CHG_EOC_TIME_M 0x0E
+#define LP8788_CHG_EOC_TIME_S 1
+#define LP8788_CHG_EOC_MODE_M BIT(0)
+
+#define LP8788_CHARGER_NAME "charger"
+#define LP8788_BATTERY_NAME "main_batt"
+
+#define LP8788_CHG_START 0x11
+#define LP8788_CHG_END 0x1C
+
+#define LP8788_ISEL_MAX 23
+#define LP8788_ISEL_STEP 50
+#define LP8788_VTERM_MIN 4100
+#define LP8788_VTERM_STEP 25
+#define LP8788_MAX_BATT_CAPACITY 100
+#define LP8788_MAX_CHG_IRQS 11
+
+enum lp8788_charging_state {
+ LP8788_OFF,
+ LP8788_WARM_UP,
+ LP8788_LOW_INPUT = 0x3,
+ LP8788_PRECHARGE,
+ LP8788_CC,
+ LP8788_CV,
+ LP8788_MAINTENANCE,
+ LP8788_BATTERY_FAULT,
+ LP8788_SYSTEM_SUPPORT = 0xC,
+ LP8788_HIGH_CURRENT = 0xF,
+ LP8788_MAX_CHG_STATE,
+};
+
+enum lp8788_charger_adc_sel {
+ LP8788_VBATT,
+ LP8788_BATT_TEMP,
+ LP8788_NUM_CHG_ADC,
+};
+
+enum lp8788_charger_input_state {
+ LP8788_SYSTEM_SUPPLY = 1,
+ LP8788_FULL_FUNCTION,
+};
+
+/*
+ * struct lp8788_chg_irq
+ * @which : lp8788 interrupt id
+ * @virq : Linux IRQ number from irq_domain
+ */
+struct lp8788_chg_irq {
+ enum lp8788_int_id which;
+ int virq;
+};
+
+/*
+ * struct lp8788_charger
+ * @lp : used for accessing the registers of mfd lp8788 device
+ * @charger : power supply driver for the battery charger
+ * @battery : power supply driver for the battery
+ * @charger_work : work queue for charger input interrupts
+ * @chan : iio channels for getting adc values
+ * eg) battery voltage, capacity and temperature
+ * @irqs : charger dedicated interrupts
+ * @num_irqs : total numbers of charger interrupts
+ * @pdata : charger platform specific data
+ */
+struct lp8788_charger {
+ struct lp8788 *lp;
+ struct power_supply *charger;
+ struct power_supply *battery;
+ struct work_struct charger_work;
+ struct iio_channel *chan[LP8788_NUM_CHG_ADC];
+ struct lp8788_chg_irq irqs[LP8788_MAX_CHG_IRQS];
+ int num_irqs;
+ struct lp8788_charger_platform_data *pdata;
+};
+
+static char *battery_supplied_to[] = {
+ LP8788_BATTERY_NAME,
+};
+
+static enum power_supply_property lp8788_charger_prop[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static enum power_supply_property lp8788_battery_prop[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static bool lp8788_is_charger_detected(struct lp8788_charger *pchg)
+{
+ u8 data;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
+ data &= LP8788_CHG_INPUT_STATE_M;
+
+ return data == LP8788_SYSTEM_SUPPLY || data == LP8788_FULL_FUNCTION;
+}
+
+static int lp8788_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent);
+ u8 read;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = lp8788_is_charger_detected(pchg);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ lp8788_read_byte(pchg->lp, LP8788_CHG_IDCIN, &read);
+ val->intval = LP8788_ISEL_STEP *
+ (min_t(int, read, LP8788_ISEL_MAX) + 1);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int lp8788_get_battery_status(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ enum lp8788_charging_state state;
+ u8 data;
+ int ret;
+
+ ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
+ if (ret)
+ return ret;
+
+ state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S;
+ switch (state) {
+ case LP8788_OFF:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case LP8788_PRECHARGE:
+ case LP8788_CC:
+ case LP8788_CV:
+ case LP8788_HIGH_CURRENT:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case LP8788_MAINTENANCE:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ }
+
+ return 0;
+}
+
+static int lp8788_get_battery_health(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ u8 data;
+ int ret;
+
+ ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
+ if (ret)
+ return ret;
+
+ if (data & LP8788_NO_BATT_M)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (data & LP8788_BAD_BATT_M)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ return 0;
+}
+
+static int lp8788_get_battery_present(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ u8 data;
+ int ret;
+
+ ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
+ if (ret)
+ return ret;
+
+ val->intval = !(data & LP8788_NO_BATT_M);
+ return 0;
+}
+
+static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result)
+{
+ struct iio_channel *channel = pchg->chan[LP8788_VBATT];
+
+ if (!channel)
+ return -EINVAL;
+
+ return iio_read_channel_processed(channel, result);
+}
+
+static int lp8788_get_battery_voltage(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ return lp8788_get_vbatt_adc(pchg, &val->intval);
+}
+
+static int lp8788_get_battery_capacity(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ struct lp8788 *lp = pchg->lp;
+ struct lp8788_charger_platform_data *pdata = pchg->pdata;
+ unsigned int max_vbatt;
+ int vbatt;
+ enum lp8788_charging_state state;
+ u8 data;
+ int ret;
+
+ if (!pdata)
+ return -EINVAL;
+
+ max_vbatt = pdata->max_vbatt_mv;
+ if (max_vbatt == 0)
+ return -EINVAL;
+
+ ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data);
+ if (ret)
+ return ret;
+
+ state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S;
+
+ if (state == LP8788_MAINTENANCE) {
+ val->intval = LP8788_MAX_BATT_CAPACITY;
+ } else {
+ ret = lp8788_get_vbatt_adc(pchg, &vbatt);
+ if (ret)
+ return ret;
+
+ val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt;
+ val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY);
+ }
+
+ return 0;
+}
+
+static int lp8788_get_battery_temperature(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP];
+ int result;
+ int ret;
+
+ if (!channel)
+ return -EINVAL;
+
+ ret = iio_read_channel_processed(channel, &result);
+ if (ret < 0)
+ return -EINVAL;
+
+ /* unit: 0.1 'C */
+ val->intval = result * 10;
+
+ return 0;
+}
+
+static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ u8 read;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read);
+ read &= LP8788_CHG_IBATT_M;
+ val->intval = LP8788_ISEL_STEP *
+ (min_t(int, read, LP8788_ISEL_MAX) + 1);
+
+ return 0;
+}
+
+static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg,
+ union power_supply_propval *val)
+{
+ u8 read;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read);
+ read &= LP8788_CHG_VTERM_M;
+ val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read;
+
+ return 0;
+}
+
+static int lp8788_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct lp8788_charger *pchg = dev_get_drvdata(psy->dev.parent);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return lp8788_get_battery_status(pchg, val);
+ case POWER_SUPPLY_PROP_HEALTH:
+ return lp8788_get_battery_health(pchg, val);
+ case POWER_SUPPLY_PROP_PRESENT:
+ return lp8788_get_battery_present(pchg, val);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return lp8788_get_battery_voltage(pchg, val);
+ case POWER_SUPPLY_PROP_CAPACITY:
+ return lp8788_get_battery_capacity(pchg, val);
+ case POWER_SUPPLY_PROP_TEMP:
+ return lp8788_get_battery_temperature(pchg, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return lp8788_get_battery_charging_current(pchg, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return lp8788_get_charging_termination_voltage(pchg, val);
+ default:
+ return -EINVAL;
+ }
+}
+
+static inline bool lp8788_is_valid_charger_register(u8 addr)
+{
+ return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END;
+}
+
+static int lp8788_update_charger_params(struct platform_device *pdev,
+ struct lp8788_charger *pchg)
+{
+ struct lp8788 *lp = pchg->lp;
+ struct lp8788_charger_platform_data *pdata = pchg->pdata;
+ struct lp8788_chg_param *param;
+ int i;
+ int ret;
+
+ if (!pdata || !pdata->chg_params) {
+ dev_info(&pdev->dev, "skip updating charger parameters\n");
+ return 0;
+ }
+
+ /* settting charging parameters */
+ for (i = 0; i < pdata->num_chg_params; i++) {
+ param = pdata->chg_params + i;
+
+ if (!param)
+ continue;
+
+ if (lp8788_is_valid_charger_register(param->addr)) {
+ ret = lp8788_write_byte(lp, param->addr, param->val);
+ if (ret)
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc lp8788_psy_charger_desc = {
+ .name = LP8788_CHARGER_NAME,
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = lp8788_charger_prop,
+ .num_properties = ARRAY_SIZE(lp8788_charger_prop),
+ .get_property = lp8788_charger_get_property,
+};
+
+static const struct power_supply_desc lp8788_psy_battery_desc = {
+ .name = LP8788_BATTERY_NAME,
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = lp8788_battery_prop,
+ .num_properties = ARRAY_SIZE(lp8788_battery_prop),
+ .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);
+ power_supply_unregister(pchg->charger);
+}
+
+static void lp8788_charger_event(struct work_struct *work)
+{
+ struct lp8788_charger *pchg =
+ container_of(work, struct lp8788_charger, charger_work);
+ struct lp8788_charger_platform_data *pdata = pchg->pdata;
+ enum lp8788_charger_event event = lp8788_is_charger_detected(pchg);
+
+ pdata->charger_event(pchg->lp, event);
+}
+
+static bool lp8788_find_irq_id(struct lp8788_charger *pchg, int virq, int *id)
+{
+ bool found = false;
+ int i;
+
+ for (i = 0; i < pchg->num_irqs; i++) {
+ if (pchg->irqs[i].virq == virq) {
+ *id = pchg->irqs[i].which;
+ found = true;
+ break;
+ }
+ }
+
+ return found;
+}
+
+static irqreturn_t lp8788_charger_irq_thread(int virq, void *ptr)
+{
+ struct lp8788_charger *pchg = ptr;
+ struct lp8788_charger_platform_data *pdata = pchg->pdata;
+ int id = -1;
+
+ if (!lp8788_find_irq_id(pchg, virq, &id))
+ return IRQ_NONE;
+
+ switch (id) {
+ case LP8788_INT_CHG_INPUT_STATE:
+ case LP8788_INT_CHG_STATE:
+ case LP8788_INT_EOC:
+ case LP8788_INT_BATT_LOW:
+ case LP8788_INT_NO_BATT:
+ power_supply_changed(pchg->charger);
+ power_supply_changed(pchg->battery);
+ break;
+ default:
+ break;
+ }
+
+ /* report charger dectection event if used */
+ if (!pdata)
+ goto irq_handled;
+
+ if (pdata->charger_event && id == LP8788_INT_CHG_INPUT_STATE)
+ schedule_work(&pchg->charger_work);
+
+irq_handled:
+ return IRQ_HANDLED;
+}
+
+static int lp8788_set_irqs(struct platform_device *pdev,
+ struct lp8788_charger *pchg, const char *name)
+{
+ struct resource *r;
+ struct irq_domain *irqdm = pchg->lp->irqdm;
+ int irq_start;
+ int irq_end;
+ int virq;
+ int nr_irq;
+ int i;
+ int ret;
+
+ /* no error even if no irq resource */
+ r = platform_get_resource_byname(pdev, IORESOURCE_IRQ, name);
+ if (!r)
+ return 0;
+
+ irq_start = r->start;
+ irq_end = r->end;
+
+ for (i = irq_start; i <= irq_end; i++) {
+ nr_irq = pchg->num_irqs;
+
+ virq = irq_create_mapping(irqdm, i);
+ pchg->irqs[nr_irq].virq = virq;
+ pchg->irqs[nr_irq].which = i;
+ pchg->num_irqs++;
+
+ ret = request_threaded_irq(virq, NULL,
+ lp8788_charger_irq_thread,
+ 0, name, pchg);
+ if (ret)
+ break;
+ }
+
+ if (i <= irq_end)
+ goto err_free_irq;
+
+ return 0;
+
+err_free_irq:
+ for (i = 0; i < pchg->num_irqs; i++)
+ free_irq(pchg->irqs[i].virq, pchg);
+ return ret;
+}
+
+static int lp8788_irq_register(struct platform_device *pdev,
+ struct lp8788_charger *pchg)
+{
+ const char *name[] = {
+ LP8788_CHG_IRQ, LP8788_PRSW_IRQ, LP8788_BATT_IRQ
+ };
+ int i;
+ int ret;
+
+ INIT_WORK(&pchg->charger_work, lp8788_charger_event);
+ pchg->num_irqs = 0;
+
+ for (i = 0; i < ARRAY_SIZE(name); i++) {
+ ret = lp8788_set_irqs(pdev, pchg, name[i]);
+ if (ret) {
+ dev_warn(&pdev->dev, "irq setup failed: %s\n", name[i]);
+ return ret;
+ }
+ }
+
+ if (pchg->num_irqs > LP8788_MAX_CHG_IRQS) {
+ dev_err(&pdev->dev, "invalid total number of irqs: %d\n",
+ pchg->num_irqs);
+ return -EINVAL;
+ }
+
+
+ return 0;
+}
+
+static void lp8788_irq_unregister(struct platform_device *pdev,
+ struct lp8788_charger *pchg)
+{
+ int i;
+ int irq;
+
+ for (i = 0; i < pchg->num_irqs; i++) {
+ irq = pchg->irqs[i].virq;
+ if (!irq)
+ continue;
+
+ free_irq(irq, pchg);
+ }
+}
+
+static void lp8788_setup_adc_channel(struct device *dev,
+ struct lp8788_charger *pchg)
+{
+ struct lp8788_charger_platform_data *pdata = pchg->pdata;
+ struct iio_channel *chan;
+
+ if (!pdata)
+ return;
+
+ /* ADC channel for battery voltage */
+ chan = iio_channel_get(dev, pdata->adc_vbatt);
+ pchg->chan[LP8788_VBATT] = IS_ERR(chan) ? NULL : chan;
+
+ /* ADC channel for battery temperature */
+ chan = iio_channel_get(dev, pdata->adc_batt_temp);
+ pchg->chan[LP8788_BATT_TEMP] = IS_ERR(chan) ? NULL : chan;
+}
+
+static void lp8788_release_adc_channel(struct lp8788_charger *pchg)
+{
+ int i;
+
+ for (i = 0; i < LP8788_NUM_CHG_ADC; i++) {
+ if (!pchg->chan[i])
+ continue;
+
+ iio_channel_release(pchg->chan[i]);
+ pchg->chan[i] = NULL;
+ }
+}
+
+static ssize_t lp8788_show_charger_status(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lp8788_charger *pchg = dev_get_drvdata(dev);
+ enum lp8788_charging_state state;
+ char *desc[LP8788_MAX_CHG_STATE] = {
+ [LP8788_OFF] = "CHARGER OFF",
+ [LP8788_WARM_UP] = "WARM UP",
+ [LP8788_LOW_INPUT] = "LOW INPUT STATE",
+ [LP8788_PRECHARGE] = "CHARGING - PRECHARGE",
+ [LP8788_CC] = "CHARGING - CC",
+ [LP8788_CV] = "CHARGING - CV",
+ [LP8788_MAINTENANCE] = "NO CHARGING - MAINTENANCE",
+ [LP8788_BATTERY_FAULT] = "BATTERY FAULT",
+ [LP8788_SYSTEM_SUPPORT] = "SYSTEM SUPPORT",
+ [LP8788_HIGH_CURRENT] = "HIGH CURRENT",
+ };
+ u8 data;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data);
+ state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S;
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]);
+}
+
+static ssize_t lp8788_show_eoc_time(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lp8788_charger *pchg = dev_get_drvdata(dev);
+ char *stime[] = { "400ms", "5min", "10min", "15min",
+ "20min", "25min", "30min" "No timeout" };
+ u8 val;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val);
+ val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S;
+
+ return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n",
+ stime[val]);
+}
+
+static ssize_t lp8788_show_eoc_level(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct lp8788_charger *pchg = dev_get_drvdata(dev);
+ char *abs_level[] = { "25mA", "49mA", "75mA", "98mA" };
+ char *relative_level[] = { "5%", "10%", "15%", "20%" };
+ char *level;
+ u8 val;
+ u8 mode;
+
+ lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val);
+
+ mode = val & LP8788_CHG_EOC_MODE_M;
+ val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S;
+ level = mode ? abs_level[val] : relative_level[val];
+
+ return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level);
+}
+
+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[] = {
+ &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,
+};
+
+static int lp8788_charger_probe(struct platform_device *pdev)
+{
+ struct lp8788 *lp = dev_get_drvdata(pdev->dev.parent);
+ struct lp8788_charger *pchg;
+ struct device *dev = &pdev->dev;
+ int ret;
+
+ pchg = devm_kzalloc(dev, sizeof(struct lp8788_charger), GFP_KERNEL);
+ if (!pchg)
+ return -ENOMEM;
+
+ pchg->lp = lp;
+ pchg->pdata = lp->pdata ? lp->pdata->chg_pdata : NULL;
+ platform_set_drvdata(pdev, pchg);
+
+ ret = lp8788_update_charger_params(pdev, pchg);
+ if (ret)
+ return ret;
+
+ lp8788_setup_adc_channel(&pdev->dev, pchg);
+
+ ret = lp8788_psy_register(pdev, pchg);
+ 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);
+
+ return 0;
+}
+
+static int lp8788_charger_remove(struct platform_device *pdev)
+{
+ struct lp8788_charger *pchg = platform_get_drvdata(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);
+
+ return 0;
+}
+
+static struct platform_driver lp8788_charger_driver = {
+ .probe = lp8788_charger_probe,
+ .remove = lp8788_charger_remove,
+ .driver = {
+ .name = LP8788_DEV_CHARGER,
+ },
+};
+module_platform_driver(lp8788_charger_driver);
+
+MODULE_DESCRIPTION("TI LP8788 Charger Driver");
+MODULE_AUTHOR("Milo Kim");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:lp8788-charger");
diff --git a/drivers/power/supply/ltc2941-battery-gauge.c b/drivers/power/supply/ltc2941-battery-gauge.c
new file mode 100644
index 0000000..4adf2ba
--- /dev/null
+++ b/drivers/power/supply/ltc2941-battery-gauge.c
@@ -0,0 +1,514 @@
+/*
+ * I2C client/driver for the Linear Technology LTC2941 and LTC2943
+ * Battery Gas Gauge IC
+ *
+ * Copyright (C) 2014 Topic Embedded Systems
+ *
+ * Author: Auryn Verwegen
+ * Author: Mike Looijmans
+ */
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/swab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#define I16_MSB(x) ((x >> 8) & 0xFF)
+#define I16_LSB(x) (x & 0xFF)
+
+#define LTC294X_WORK_DELAY 10 /* Update delay in seconds */
+
+#define LTC294X_MAX_VALUE 0xFFFF
+#define LTC294X_MID_SUPPLY 0x7FFF
+
+#define LTC2941_MAX_PRESCALER_EXP 7
+#define LTC2943_MAX_PRESCALER_EXP 6
+
+enum ltc294x_reg {
+ LTC294X_REG_STATUS = 0x00,
+ LTC294X_REG_CONTROL = 0x01,
+ LTC294X_REG_ACC_CHARGE_MSB = 0x02,
+ LTC294X_REG_ACC_CHARGE_LSB = 0x03,
+ LTC294X_REG_THRESH_HIGH_MSB = 0x04,
+ LTC294X_REG_THRESH_HIGH_LSB = 0x05,
+ LTC294X_REG_THRESH_LOW_MSB = 0x06,
+ LTC294X_REG_THRESH_LOW_LSB = 0x07,
+ LTC294X_REG_VOLTAGE_MSB = 0x08,
+ LTC294X_REG_VOLTAGE_LSB = 0x09,
+ LTC294X_REG_CURRENT_MSB = 0x0E,
+ LTC294X_REG_CURRENT_LSB = 0x0F,
+ LTC294X_REG_TEMPERATURE_MSB = 0x14,
+ LTC294X_REG_TEMPERATURE_LSB = 0x15,
+};
+
+#define LTC2943_REG_CONTROL_MODE_MASK (BIT(7) | BIT(6))
+#define LTC2943_REG_CONTROL_MODE_SCAN BIT(7)
+#define LTC294X_REG_CONTROL_PRESCALER_MASK (BIT(5) | BIT(4) | BIT(3))
+#define LTC294X_REG_CONTROL_SHUTDOWN_MASK (BIT(0))
+#define LTC294X_REG_CONTROL_PRESCALER_SET(x) \
+ ((x << 3) & LTC294X_REG_CONTROL_PRESCALER_MASK)
+#define LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED 0
+
+#define LTC2941_NUM_REGS 0x08
+#define LTC2943_NUM_REGS 0x18
+
+struct ltc294x_info {
+ struct i2c_client *client; /* I2C Client pointer */
+ struct power_supply *supply; /* Supply pointer */
+ struct power_supply_desc supply_desc; /* Supply description */
+ struct delayed_work work; /* Work scheduler */
+ int num_regs; /* Number of registers (chip type) */
+ int charge; /* Last charge register content */
+ int r_sense; /* mOhm */
+ int Qlsb; /* nAh */
+};
+
+static inline int convert_bin_to_uAh(
+ const struct ltc294x_info *info, int Q)
+{
+ return ((Q * (info->Qlsb / 10))) / 100;
+}
+
+static inline int convert_uAh_to_bin(
+ const struct ltc294x_info *info, int uAh)
+{
+ int Q;
+
+ Q = (uAh * 100) / (info->Qlsb/10);
+ return (Q < LTC294X_MAX_VALUE) ? Q : LTC294X_MAX_VALUE;
+}
+
+static int ltc294x_read_regs(struct i2c_client *client,
+ enum ltc294x_reg reg, u8 *buf, int num_regs)
+{
+ int ret;
+ struct i2c_msg msgs[2] = { };
+ u8 reg_start = reg;
+
+ msgs[0].addr = client->addr;
+ msgs[0].len = 1;
+ msgs[0].buf = &reg_start;
+
+ msgs[1].addr = client->addr;
+ msgs[1].len = num_regs;
+ msgs[1].buf = buf;
+ msgs[1].flags = I2C_M_RD;
+
+ ret = i2c_transfer(client->adapter, &msgs[0], 2);
+ if (ret < 0) {
+ dev_err(&client->dev, "ltc2941 read_reg failed!\n");
+ return ret;
+ }
+
+ dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",
+ __func__, reg, num_regs, *buf);
+
+ return 0;
+}
+
+static int ltc294x_write_regs(struct i2c_client *client,
+ enum ltc294x_reg reg, const u8 *buf, int num_regs)
+{
+ int ret;
+ u8 reg_start = reg;
+
+ ret = i2c_smbus_write_i2c_block_data(client, reg_start, num_regs, buf);
+ if (ret < 0) {
+ dev_err(&client->dev, "ltc2941 write_reg failed!\n");
+ return ret;
+ }
+
+ dev_dbg(&client->dev, "%s (%#x, %d) -> %#x\n",
+ __func__, reg, num_regs, *buf);
+
+ return 0;
+}
+
+static int ltc294x_reset(const struct ltc294x_info *info, int prescaler_exp)
+{
+ int ret;
+ u8 value;
+ u8 control;
+
+ /* Read status and control registers */
+ ret = ltc294x_read_regs(info->client, LTC294X_REG_CONTROL, &value, 1);
+ if (ret < 0) {
+ dev_err(&info->client->dev,
+ "Could not read registers from device\n");
+ goto error_exit;
+ }
+
+ control = LTC294X_REG_CONTROL_PRESCALER_SET(prescaler_exp) |
+ LTC294X_REG_CONTROL_ALCC_CONFIG_DISABLED;
+ /* Put the 2943 into "monitor" mode, so it measures every 10 sec */
+ if (info->num_regs == LTC2943_NUM_REGS)
+ control |= LTC2943_REG_CONTROL_MODE_SCAN;
+
+ if (value != control) {
+ ret = ltc294x_write_regs(info->client,
+ LTC294X_REG_CONTROL, &control, 1);
+ if (ret < 0) {
+ dev_err(&info->client->dev,
+ "Could not write register\n");
+ goto error_exit;
+ }
+ }
+
+ return 0;
+
+error_exit:
+ return ret;
+}
+
+static int ltc294x_read_charge_register(const struct ltc294x_info *info)
+{
+ int ret;
+ u8 datar[2];
+
+ ret = ltc294x_read_regs(info->client,
+ LTC294X_REG_ACC_CHARGE_MSB, &datar[0], 2);
+ if (ret < 0)
+ return ret;
+ return (datar[0] << 8) + datar[1];
+}
+
+static int ltc294x_get_charge_now(const struct ltc294x_info *info, int *val)
+{
+ int value = ltc294x_read_charge_register(info);
+
+ if (value < 0)
+ return value;
+ /* When r_sense < 0, this counts up when the battery discharges */
+ if (info->Qlsb < 0)
+ value -= 0xFFFF;
+ *val = convert_bin_to_uAh(info, value);
+ return 0;
+}
+
+static int ltc294x_set_charge_now(const struct ltc294x_info *info, int val)
+{
+ int ret;
+ u8 dataw[2];
+ u8 ctrl_reg;
+ s32 value;
+
+ value = convert_uAh_to_bin(info, val);
+ /* Direction depends on how sense+/- were connected */
+ if (info->Qlsb < 0)
+ value += 0xFFFF;
+ if ((value < 0) || (value > 0xFFFF)) /* input validation */
+ return -EINVAL;
+
+ /* Read control register */
+ ret = ltc294x_read_regs(info->client,
+ LTC294X_REG_CONTROL, &ctrl_reg, 1);
+ if (ret < 0)
+ return ret;
+ /* Disable analog section */
+ ctrl_reg |= LTC294X_REG_CONTROL_SHUTDOWN_MASK;
+ ret = ltc294x_write_regs(info->client,
+ LTC294X_REG_CONTROL, &ctrl_reg, 1);
+ if (ret < 0)
+ return ret;
+ /* Set new charge value */
+ dataw[0] = I16_MSB(value);
+ dataw[1] = I16_LSB(value);
+ ret = ltc294x_write_regs(info->client,
+ LTC294X_REG_ACC_CHARGE_MSB, &dataw[0], 2);
+ if (ret < 0)
+ goto error_exit;
+ /* Enable analog section */
+error_exit:
+ ctrl_reg &= ~LTC294X_REG_CONTROL_SHUTDOWN_MASK;
+ ret = ltc294x_write_regs(info->client,
+ LTC294X_REG_CONTROL, &ctrl_reg, 1);
+
+ return ret < 0 ? ret : 0;
+}
+
+static int ltc294x_get_charge_counter(
+ const struct ltc294x_info *info, int *val)
+{
+ int value = ltc294x_read_charge_register(info);
+
+ if (value < 0)
+ return value;
+ value -= LTC294X_MID_SUPPLY;
+ *val = convert_bin_to_uAh(info, value);
+ return 0;
+}
+
+static int ltc294x_get_voltage(const struct ltc294x_info *info, int *val)
+{
+ int ret;
+ u8 datar[2];
+ u32 value;
+
+ ret = ltc294x_read_regs(info->client,
+ LTC294X_REG_VOLTAGE_MSB, &datar[0], 2);
+ value = (datar[0] << 8) | datar[1];
+ *val = ((value * 23600) / 0xFFFF) * 1000; /* in uV */
+ return ret;
+}
+
+static int ltc294x_get_current(const struct ltc294x_info *info, int *val)
+{
+ int ret;
+ u8 datar[2];
+ s32 value;
+
+ ret = ltc294x_read_regs(info->client,
+ LTC294X_REG_CURRENT_MSB, &datar[0], 2);
+ value = (datar[0] << 8) | datar[1];
+ value -= 0x7FFF;
+ /* Value is in range -32k..+32k, r_sense is usually 10..50 mOhm,
+ * the formula below keeps everything in s32 range while preserving
+ * enough digits */
+ *val = 1000 * ((60000 * value) / (info->r_sense * 0x7FFF)); /* in uA */
+ return ret;
+}
+
+static int ltc294x_get_temperature(const struct ltc294x_info *info, int *val)
+{
+ int ret;
+ u8 datar[2];
+ u32 value;
+
+ ret = ltc294x_read_regs(info->client,
+ LTC294X_REG_TEMPERATURE_MSB, &datar[0], 2);
+ value = (datar[0] << 8) | datar[1];
+ /* Full-scale is 510 Kelvin, convert to centidegrees */
+ *val = (((51000 * value) / 0xFFFF) - 27215);
+ return ret;
+}
+
+static int ltc294x_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct ltc294x_info *info = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ return ltc294x_get_charge_now(info, &val->intval);
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ return ltc294x_get_charge_counter(info, &val->intval);
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ return ltc294x_get_voltage(info, &val->intval);
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ return ltc294x_get_current(info, &val->intval);
+ case POWER_SUPPLY_PROP_TEMP:
+ return ltc294x_get_temperature(info, &val->intval);
+ default:
+ return -EINVAL;
+ }
+}
+
+static int ltc294x_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct ltc294x_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ return ltc294x_set_charge_now(info, val->intval);
+ default:
+ return -EPERM;
+ }
+}
+
+static int ltc294x_property_is_writeable(
+ struct power_supply *psy, enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static void ltc294x_update(struct ltc294x_info *info)
+{
+ int charge = ltc294x_read_charge_register(info);
+
+ if (charge != info->charge) {
+ info->charge = charge;
+ power_supply_changed(info->supply);
+ }
+}
+
+static void ltc294x_work(struct work_struct *work)
+{
+ struct ltc294x_info *info;
+
+ info = container_of(work, struct ltc294x_info, work.work);
+ ltc294x_update(info);
+ schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
+}
+
+static enum power_supply_property ltc294x_properties[] = {
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+};
+
+static int ltc294x_i2c_remove(struct i2c_client *client)
+{
+ struct ltc294x_info *info = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&info->work);
+ power_supply_unregister(info->supply);
+ return 0;
+}
+
+static int ltc294x_i2c_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct power_supply_config psy_cfg = {};
+ struct ltc294x_info *info;
+ int ret;
+ u32 prescaler_exp;
+ s32 r_sense;
+ struct device_node *np;
+
+ info = devm_kzalloc(&client->dev, sizeof(*info), GFP_KERNEL);
+ if (info == NULL)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, info);
+
+ np = of_node_get(client->dev.of_node);
+
+ info->num_regs = id->driver_data;
+ info->supply_desc.name = np->name;
+
+ /* r_sense can be negative, when sense+ is connected to the battery
+ * instead of the sense-. This results in reversed measurements. */
+ ret = of_property_read_u32(np, "lltc,resistor-sense", &r_sense);
+ if (ret < 0) {
+ dev_err(&client->dev,
+ "Could not find lltc,resistor-sense in devicetree\n");
+ return ret;
+ }
+ info->r_sense = r_sense;
+
+ ret = of_property_read_u32(np, "lltc,prescaler-exponent",
+ &prescaler_exp);
+ if (ret < 0) {
+ dev_warn(&client->dev,
+ "lltc,prescaler-exponent not in devicetree\n");
+ prescaler_exp = LTC2941_MAX_PRESCALER_EXP;
+ }
+
+ if (info->num_regs == LTC2943_NUM_REGS) {
+ if (prescaler_exp > LTC2943_MAX_PRESCALER_EXP)
+ prescaler_exp = LTC2943_MAX_PRESCALER_EXP;
+ info->Qlsb = ((340 * 50000) / r_sense) /
+ (4096 / (1 << (2*prescaler_exp)));
+ } else {
+ if (prescaler_exp > LTC2941_MAX_PRESCALER_EXP)
+ prescaler_exp = LTC2941_MAX_PRESCALER_EXP;
+ info->Qlsb = ((85 * 50000) / r_sense) /
+ (128 / (1 << prescaler_exp));
+ }
+
+ info->client = client;
+ info->supply_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ info->supply_desc.properties = ltc294x_properties;
+ if (info->num_regs >= LTC294X_REG_TEMPERATURE_LSB)
+ info->supply_desc.num_properties =
+ ARRAY_SIZE(ltc294x_properties);
+ else if (info->num_regs >= LTC294X_REG_CURRENT_LSB)
+ info->supply_desc.num_properties =
+ ARRAY_SIZE(ltc294x_properties) - 1;
+ else if (info->num_regs >= LTC294X_REG_VOLTAGE_LSB)
+ info->supply_desc.num_properties =
+ ARRAY_SIZE(ltc294x_properties) - 2;
+ else
+ info->supply_desc.num_properties =
+ ARRAY_SIZE(ltc294x_properties) - 3;
+ info->supply_desc.get_property = ltc294x_get_property;
+ info->supply_desc.set_property = ltc294x_set_property;
+ info->supply_desc.property_is_writeable = ltc294x_property_is_writeable;
+ info->supply_desc.external_power_changed = NULL;
+
+ psy_cfg.drv_data = info;
+
+ INIT_DELAYED_WORK(&info->work, ltc294x_work);
+
+ ret = ltc294x_reset(info, prescaler_exp);
+ if (ret < 0) {
+ dev_err(&client->dev, "Communication with chip failed\n");
+ return ret;
+ }
+
+ info->supply = power_supply_register(&client->dev, &info->supply_desc,
+ &psy_cfg);
+ if (IS_ERR(info->supply)) {
+ dev_err(&client->dev, "failed to register ltc2941\n");
+ return PTR_ERR(info->supply);
+ } else {
+ schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int ltc294x_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ltc294x_info *info = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&info->work);
+ return 0;
+}
+
+static int ltc294x_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct ltc294x_info *info = i2c_get_clientdata(client);
+
+ schedule_delayed_work(&info->work, LTC294X_WORK_DELAY * HZ);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(ltc294x_pm_ops, ltc294x_suspend, ltc294x_resume);
+#define LTC294X_PM_OPS (&ltc294x_pm_ops)
+
+#else
+#define LTC294X_PM_OPS NULL
+#endif /* CONFIG_PM_SLEEP */
+
+
+static const struct i2c_device_id ltc294x_i2c_id[] = {
+ {"ltc2941", LTC2941_NUM_REGS},
+ {"ltc2943", LTC2943_NUM_REGS},
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, ltc294x_i2c_id);
+
+static struct i2c_driver ltc294x_driver = {
+ .driver = {
+ .name = "LTC2941",
+ .pm = LTC294X_PM_OPS,
+ },
+ .probe = ltc294x_i2c_probe,
+ .remove = ltc294x_i2c_remove,
+ .id_table = ltc294x_i2c_id,
+};
+module_i2c_driver(ltc294x_driver);
+
+MODULE_AUTHOR("Auryn Verwegen, Topic Embedded Systems");
+MODULE_AUTHOR("Mike Looijmans, Topic Embedded Products");
+MODULE_DESCRIPTION("LTC2941/LTC2943 Battery Gas Gauge IC driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c
new file mode 100644
index 0000000..449fc56
--- /dev/null
+++ b/drivers/power/supply/max14577_charger.c
@@ -0,0 +1,648 @@
+/*
+ * max14577_charger.c - Battery charger driver for the Maxim 14577/77836
+ *
+ * Copyright (C) 2013,2014 Samsung Electronics
+ * Krzysztof Kozlowski <krzk@kernel.org>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max14577-private.h>
+#include <linux/mfd/max14577.h>
+
+struct max14577_charger {
+ struct device *dev;
+ struct max14577 *max14577;
+ struct power_supply *charger;
+
+ struct max14577_charger_platform_data *pdata;
+};
+
+/*
+ * Helper function for mapping values of STATUS2/CHGTYP register on max14577
+ * and max77836 chipsets to enum maxim_muic_charger_type.
+ */
+static enum max14577_muic_charger_type maxim_get_charger_type(
+ enum maxim_device_type dev_type, u8 val) {
+ switch (val) {
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_USB:
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ return val;
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ case MAX14577_CHARGER_TYPE_RESERVED:
+ if (dev_type == MAXIM_DEVICE_TYPE_MAX77836)
+ val |= 0x8;
+ return val;
+ default:
+ WARN_ONCE(1, "max14577: Unsupported chgtyp register value 0x%02x", val);
+ return val;
+ }
+}
+
+static int max14577_get_charger_state(struct max14577_charger *chg, int *val)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int ret;
+ u8 reg_data;
+
+ /*
+ * Charging occurs only if:
+ * - CHGCTRL2/MBCHOSTEN == 1
+ * - STATUS2/CGMBC == 1
+ *
+ * TODO:
+ * - handle FULL after Top-off timer (EOC register may be off
+ * and the charger won't be charging although MBCHOSTEN is on)
+ * - handle properly dead-battery charging (respect timer)
+ * - handle timers (fast-charge and prequal) /MBCCHGERR/
+ */
+ ret = max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, &reg_data);
+ if (ret < 0)
+ goto out;
+
+ if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) {
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ goto out;
+ }
+
+ ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, &reg_data);
+ if (ret < 0)
+ goto out;
+
+ if (reg_data & STATUS3_CGMBC_MASK) {
+ /* Charger or USB-cable is connected */
+ if (reg_data & STATUS3_EOC_MASK)
+ *val = POWER_SUPPLY_STATUS_FULL;
+ else
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ goto out;
+ }
+
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+
+out:
+ return ret;
+}
+
+/*
+ * Supported charge types:
+ * - POWER_SUPPLY_CHARGE_TYPE_NONE
+ * - POWER_SUPPLY_CHARGE_TYPE_FAST
+ */
+static int max14577_get_charge_type(struct max14577_charger *chg, int *val)
+{
+ int ret, charging;
+
+ /*
+ * TODO: CHARGE_TYPE_TRICKLE (VCHGR_RC or EOC)?
+ * As spec says:
+ * [after reaching EOC interrupt]
+ * "When the battery is fully charged, the 30-minute (typ)
+ * top-off timer starts. The device continues to trickle
+ * charge the battery until the top-off timer runs out."
+ */
+ ret = max14577_get_charger_state(chg, &charging);
+ if (ret < 0)
+ return ret;
+
+ if (charging == POWER_SUPPLY_STATUS_CHARGING)
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+
+ return 0;
+}
+
+static int max14577_get_online(struct max14577_charger *chg, int *val)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+ int ret;
+ enum max14577_muic_charger_type chg_type;
+
+ ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
+ if (ret < 0)
+ return ret;
+
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data);
+ switch (chg_type) {
+ case MAX14577_CHARGER_TYPE_USB:
+ case MAX14577_CHARGER_TYPE_DEDICATED_CHG:
+ case MAX14577_CHARGER_TYPE_SPECIAL_500MA:
+ case MAX14577_CHARGER_TYPE_SPECIAL_1A:
+ case MAX14577_CHARGER_TYPE_DEAD_BATTERY:
+ case MAX77836_CHARGER_TYPE_SPECIAL_BIAS:
+ *val = 1;
+ break;
+ case MAX14577_CHARGER_TYPE_NONE:
+ case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT:
+ case MAX14577_CHARGER_TYPE_RESERVED:
+ case MAX77836_CHARGER_TYPE_RESERVED:
+ default:
+ *val = 0;
+ }
+
+ return 0;
+}
+
+/*
+ * Supported health statuses:
+ * - POWER_SUPPLY_HEALTH_DEAD
+ * - POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ * - POWER_SUPPLY_HEALTH_GOOD
+ */
+static int max14577_get_battery_health(struct max14577_charger *chg, int *val)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ int ret;
+ u8 reg_data;
+ enum max14577_muic_charger_type chg_type;
+
+ ret = max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, &reg_data);
+ if (ret < 0)
+ goto out;
+
+ reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT);
+ chg_type = maxim_get_charger_type(chg->max14577->dev_type, reg_data);
+ if (chg_type == MAX14577_CHARGER_TYPE_DEAD_BATTERY) {
+ *val = POWER_SUPPLY_HEALTH_DEAD;
+ goto out;
+ }
+
+ ret = max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, &reg_data);
+ if (ret < 0)
+ goto out;
+
+ if (reg_data & STATUS3_OVP_MASK) {
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ goto out;
+ }
+
+ /* Not dead, not overvoltage */
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+
+out:
+ return ret;
+}
+
+/*
+ * Always returns 1.
+ * The max14577 chip doesn't report any status of battery presence.
+ * Lets assume that it will always be used with some battery.
+ */
+static int max14577_get_present(struct max14577_charger *chg, int *val)
+{
+ *val = 1;
+
+ return 0;
+}
+
+static int max14577_set_fast_charge_timer(struct max14577_charger *chg,
+ unsigned long hours)
+{
+ u8 reg_data;
+
+ switch (hours) {
+ case 5 ... 7:
+ reg_data = hours - 3;
+ break;
+ case 0:
+ /* Disable */
+ reg_data = 0x7;
+ break;
+ default:
+ dev_err(chg->dev, "Wrong value for Fast-Charge Timer: %lu\n",
+ hours);
+ return -EINVAL;
+ }
+ reg_data <<= CHGCTRL1_TCHW_SHIFT;
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_REG_CHGCTRL1, CHGCTRL1_TCHW_MASK, reg_data);
+}
+
+static int max14577_init_constant_voltage(struct max14577_charger *chg,
+ unsigned int uvolt)
+{
+ u8 reg_data;
+
+ if (uvolt < MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN ||
+ uvolt > MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX)
+ return -EINVAL;
+
+ if (uvolt == 4200000)
+ reg_data = 0x0;
+ else if (uvolt == MAXIM_CHARGER_CONSTANT_VOLTAGE_MAX)
+ reg_data = 0x1f;
+ else if (uvolt <= 4280000) {
+ unsigned int val = uvolt;
+
+ val -= MAXIM_CHARGER_CONSTANT_VOLTAGE_MIN;
+ val /= MAXIM_CHARGER_CONSTANT_VOLTAGE_STEP;
+ if (uvolt <= 4180000)
+ reg_data = 0x1 + val;
+ else
+ reg_data = val; /* Fix for gap between 4.18V and 4.22V */
+ } else
+ return -EINVAL;
+
+ reg_data <<= CHGCTRL3_MBCCVWRC_SHIFT;
+
+ return max14577_write_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL3, reg_data);
+}
+
+static int max14577_init_eoc(struct max14577_charger *chg,
+ unsigned int uamp)
+{
+ unsigned int current_bits = 0xf;
+ u8 reg_data;
+
+ switch (chg->max14577->dev_type) {
+ case MAXIM_DEVICE_TYPE_MAX77836:
+ if (uamp < 5000)
+ return -EINVAL; /* Requested current is too low */
+
+ if (uamp >= 7500 && uamp < 10000)
+ current_bits = 0x0;
+ else if (uamp <= 50000) {
+ /* <5000, 7499> and <10000, 50000> */
+ current_bits = uamp / 5000;
+ } else {
+ uamp = min(uamp, 100000U) - 50000U;
+ current_bits = 0xa + uamp / 10000;
+ }
+ break;
+
+ case MAXIM_DEVICE_TYPE_MAX14577:
+ default:
+ if (uamp < MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN)
+ return -EINVAL; /* Requested current is too low */
+
+ uamp = min(uamp, MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX);
+ uamp -= MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN;
+ current_bits = uamp / MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP;
+ break;
+ }
+
+ reg_data = current_bits << CHGCTRL5_EOCS_SHIFT;
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL5, CHGCTRL5_EOCS_MASK,
+ reg_data);
+}
+
+static int max14577_init_fast_charge(struct max14577_charger *chg,
+ unsigned int uamp)
+{
+ u8 reg_data;
+ int ret;
+ const struct maxim_charger_current *limits =
+ &maxim_charger_currents[chg->max14577->dev_type];
+
+ ret = maxim_charger_calc_reg_current(limits, uamp, uamp, &reg_data);
+ if (ret) {
+ dev_err(chg->dev, "Wrong value for fast charge: %u\n", uamp);
+ return ret;
+ }
+
+ return max14577_update_reg(chg->max14577->regmap,
+ MAX14577_CHG_REG_CHG_CTRL4,
+ CHGCTRL4_MBCICHWRCL_MASK | CHGCTRL4_MBCICHWRCH_MASK,
+ reg_data);
+}
+
+/*
+ * Sets charger registers to proper and safe default values.
+ * Some of these values are equal to defaults in MAX14577E
+ * data sheet but there are minor differences.
+ */
+static int max14577_charger_reg_init(struct max14577_charger *chg)
+{
+ struct regmap *rmap = chg->max14577->regmap;
+ u8 reg_data;
+ int ret;
+
+ /*
+ * Charger-Type Manual Detection, default off (set CHGTYPMAN to 0)
+ * Charger-Detection Enable, default on (set CHGDETEN to 1)
+ * Combined mask of CHGDETEN and CHGTYPMAN will zero the CHGTYPMAN bit
+ */
+ reg_data = 0x1 << CDETCTRL1_CHGDETEN_SHIFT;
+ max14577_update_reg(rmap, MAX14577_REG_CDETCTRL1,
+ CDETCTRL1_CHGDETEN_MASK | CDETCTRL1_CHGTYPMAN_MASK,
+ reg_data);
+
+ /*
+ * Wall-Adapter Rapid Charge, default on
+ * Battery-Charger, default on
+ */
+ reg_data = 0x1 << CHGCTRL2_VCHGR_RC_SHIFT;
+ reg_data |= 0x1 << CHGCTRL2_MBCHOSTEN_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL2, reg_data);
+
+ /* Auto Charging Stop, default off */
+ reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data);
+
+ ret = max14577_init_constant_voltage(chg, chg->pdata->constant_uvolt);
+ if (ret)
+ return ret;
+
+ ret = max14577_init_eoc(chg, chg->pdata->eoc_uamp);
+ if (ret)
+ return ret;
+
+ ret = max14577_init_fast_charge(chg, chg->pdata->fast_charge_uamp);
+ if (ret)
+ return ret;
+
+ ret = max14577_set_fast_charge_timer(chg,
+ MAXIM_CHARGER_FAST_CHARGE_TIMER_DEFAULT);
+ if (ret)
+ return ret;
+
+ /* Initialize Overvoltage-Protection Threshold */
+ switch (chg->pdata->ovp_uvolt) {
+ case 7500000:
+ reg_data = 0x0;
+ break;
+ case 6000000:
+ case 6500000:
+ case 7000000:
+ reg_data = 0x1 + (chg->pdata->ovp_uvolt - 6000000) / 500000;
+ break;
+ default:
+ dev_err(chg->dev, "Wrong value for OVP: %u\n",
+ chg->pdata->ovp_uvolt);
+ return -EINVAL;
+ }
+ reg_data <<= CHGCTRL7_OTPCGHCVS_SHIFT;
+ max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data);
+
+ return 0;
+}
+
+/* Support property from charger */
+static enum power_supply_property max14577_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static const char * const model_names[] = {
+ [MAXIM_DEVICE_TYPE_UNKNOWN] = "MAX14577-like",
+ [MAXIM_DEVICE_TYPE_MAX14577] = "MAX14577",
+ [MAXIM_DEVICE_TYPE_MAX77836] = "MAX77836",
+};
+static const char *manufacturer = "Maxim Integrated";
+
+static int max14577_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max14577_charger *chg = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = max14577_get_charger_state(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = max14577_get_charge_type(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = max14577_get_battery_health(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max14577_get_present(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max14577_get_online(chg, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ BUILD_BUG_ON(ARRAY_SIZE(model_names) != MAXIM_DEVICE_TYPE_NUM);
+ val->strval = model_names[chg->max14577->dev_type];
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct power_supply_desc max14577_charger_desc = {
+ .name = "max14577-charger",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max14577_charger_props,
+ .num_properties = ARRAY_SIZE(max14577_charger_props),
+ .get_property = max14577_charger_get_property,
+};
+
+#ifdef CONFIG_OF
+static struct max14577_charger_platform_data *max14577_charger_dt_init(
+ struct platform_device *pdev)
+{
+ struct max14577_charger_platform_data *pdata;
+ struct device_node *np = pdev->dev.of_node;
+ int ret;
+
+ if (!np) {
+ dev_err(&pdev->dev, "No charger OF node\n");
+ return ERR_PTR(-EINVAL);
+ }
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return ERR_PTR(-ENOMEM);
+
+ ret = of_property_read_u32(np, "maxim,constant-uvolt",
+ &pdata->constant_uvolt);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,constant-uvolt field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,fast-charge-uamp",
+ &pdata->fast_charge_uamp);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,fast-charge-uamp field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,eoc-uamp", &pdata->eoc_uamp);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,eoc-uamp field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ ret = of_property_read_u32(np, "maxim,ovp-uvolt", &pdata->ovp_uvolt);
+ if (ret) {
+ dev_err(&pdev->dev, "Cannot parse maxim,ovp-uvolt field from DT\n");
+ return ERR_PTR(ret);
+ }
+
+ return pdata;
+}
+#else /* CONFIG_OF */
+static struct max14577_charger_platform_data *max14577_charger_dt_init(
+ struct platform_device *pdev)
+{
+ return NULL;
+}
+#endif /* CONFIG_OF */
+
+static ssize_t show_fast_charge_timer(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max14577_charger *chg = dev_get_drvdata(dev);
+ u8 reg_data;
+ int ret;
+ unsigned int val;
+
+ ret = max14577_read_reg(chg->max14577->regmap, MAX14577_REG_CHGCTRL1,
+ &reg_data);
+ if (ret)
+ return ret;
+
+ reg_data &= CHGCTRL1_TCHW_MASK;
+ reg_data >>= CHGCTRL1_TCHW_SHIFT;
+ switch (reg_data) {
+ case 0x2 ... 0x4:
+ val = reg_data + 3;
+ break;
+ case 0x7:
+ val = 0;
+ break;
+ default:
+ val = 5;
+ break;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static ssize_t store_fast_charge_timer(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct max14577_charger *chg = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = max14577_set_fast_charge_timer(chg, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static DEVICE_ATTR(fast_charge_timer, S_IRUGO | S_IWUSR,
+ show_fast_charge_timer, store_fast_charge_timer);
+
+static int max14577_charger_probe(struct platform_device *pdev)
+{
+ struct max14577_charger *chg;
+ struct power_supply_config psy_cfg = {};
+ struct max14577 *max14577 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg);
+ chg->dev = &pdev->dev;
+ chg->max14577 = max14577;
+
+ chg->pdata = max14577_charger_dt_init(pdev);
+ if (IS_ERR_OR_NULL(chg->pdata))
+ return PTR_ERR(chg->pdata);
+
+ ret = max14577_charger_reg_init(chg);
+ if (ret)
+ return ret;
+
+ ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: create sysfs entry\n");
+ return ret;
+ }
+
+ psy_cfg.drv_data = chg;
+ chg->charger = power_supply_register(&pdev->dev, &max14577_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(chg->charger)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ ret = PTR_ERR(chg->charger);
+ goto err;
+ }
+
+ /* Check for valid values for charger */
+ BUILD_BUG_ON(MAX14577_CHARGER_EOC_CURRENT_LIMIT_MIN +
+ MAX14577_CHARGER_EOC_CURRENT_LIMIT_STEP * 0xf !=
+ MAX14577_CHARGER_EOC_CURRENT_LIMIT_MAX);
+ return 0;
+
+err:
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
+
+ return ret;
+}
+
+static int max14577_charger_remove(struct platform_device *pdev)
+{
+ struct max14577_charger *chg = platform_get_drvdata(pdev);
+
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
+ power_supply_unregister(chg->charger);
+
+ return 0;
+}
+
+static const struct platform_device_id max14577_charger_id[] = {
+ { "max14577-charger", MAXIM_DEVICE_TYPE_MAX14577, },
+ { "max77836-charger", MAXIM_DEVICE_TYPE_MAX77836, },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, max14577_charger_id);
+
+static struct platform_driver max14577_charger_driver = {
+ .driver = {
+ .name = "max14577-charger",
+ },
+ .probe = max14577_charger_probe,
+ .remove = max14577_charger_remove,
+ .id_table = max14577_charger_id,
+};
+module_platform_driver(max14577_charger_driver);
+
+MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
+MODULE_DESCRIPTION("Maxim 14577/77836 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max17040_battery.c b/drivers/power/supply/max17040_battery.c
new file mode 100644
index 0000000..8689c80
--- /dev/null
+++ b/drivers/power/supply/max17040_battery.c
@@ -0,0 +1,305 @@
+/*
+ * max17040_battery.c
+ * fuel-gauge systems for lithium-ion (Li+) batteries
+ *
+ * Copyright (C) 2009 Samsung Electronics
+ * Minkyu Kang <mk7.kang@samsung.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/init.h>
+#include <linux/platform_device.h>
+#include <linux/mutex.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/power_supply.h>
+#include <linux/max17040_battery.h>
+#include <linux/slab.h>
+
+#define MAX17040_VCELL_MSB 0x02
+#define MAX17040_VCELL_LSB 0x03
+#define MAX17040_SOC_MSB 0x04
+#define MAX17040_SOC_LSB 0x05
+#define MAX17040_MODE_MSB 0x06
+#define MAX17040_MODE_LSB 0x07
+#define MAX17040_VER_MSB 0x08
+#define MAX17040_VER_LSB 0x09
+#define MAX17040_RCOMP_MSB 0x0C
+#define MAX17040_RCOMP_LSB 0x0D
+#define MAX17040_CMD_MSB 0xFE
+#define MAX17040_CMD_LSB 0xFF
+
+#define MAX17040_DELAY 1000
+#define MAX17040_BATTERY_FULL 95
+
+struct max17040_chip {
+ struct i2c_client *client;
+ struct delayed_work work;
+ struct power_supply *battery;
+ struct max17040_platform_data *pdata;
+
+ /* State Of Connect */
+ int online;
+ /* battery voltage */
+ int vcell;
+ /* battery capacity */
+ int soc;
+ /* State Of Charge */
+ int status;
+};
+
+static int max17040_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17040_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = chip->status;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = chip->vcell;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = chip->soc;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17040_write_reg(struct i2c_client *client, int reg, u8 value)
+{
+ int ret;
+
+ ret = i2c_smbus_write_byte_data(client, reg, value);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static int max17040_read_reg(struct i2c_client *client, int reg)
+{
+ int ret;
+
+ ret = i2c_smbus_read_byte_data(client, reg);
+
+ if (ret < 0)
+ dev_err(&client->dev, "%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static void max17040_reset(struct i2c_client *client)
+{
+ max17040_write_reg(client, MAX17040_CMD_MSB, 0x54);
+ max17040_write_reg(client, MAX17040_CMD_LSB, 0x00);
+}
+
+static void max17040_get_vcell(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_VCELL_MSB);
+ lsb = max17040_read_reg(client, MAX17040_VCELL_LSB);
+
+ chip->vcell = (msb << 4) + (lsb >> 4);
+}
+
+static void max17040_get_soc(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_SOC_MSB);
+ lsb = max17040_read_reg(client, MAX17040_SOC_LSB);
+
+ chip->soc = msb;
+}
+
+static void max17040_get_version(struct i2c_client *client)
+{
+ u8 msb;
+ u8 lsb;
+
+ msb = max17040_read_reg(client, MAX17040_VER_MSB);
+ lsb = max17040_read_reg(client, MAX17040_VER_LSB);
+
+ dev_info(&client->dev, "MAX17040 Fuel-Gauge Ver %d%d\n", msb, lsb);
+}
+
+static void max17040_get_online(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ if (chip->pdata && chip->pdata->battery_online)
+ chip->online = chip->pdata->battery_online();
+ else
+ chip->online = 1;
+}
+
+static void max17040_get_status(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ if (!chip->pdata || !chip->pdata->charger_online
+ || !chip->pdata->charger_enable) {
+ chip->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ return;
+ }
+
+ if (chip->pdata->charger_online()) {
+ if (chip->pdata->charger_enable())
+ chip->status = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ chip->status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ chip->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (chip->soc > MAX17040_BATTERY_FULL)
+ chip->status = POWER_SUPPLY_STATUS_FULL;
+}
+
+static void max17040_work(struct work_struct *work)
+{
+ struct max17040_chip *chip;
+
+ chip = container_of(work, struct max17040_chip, work.work);
+
+ max17040_get_vcell(chip->client);
+ max17040_get_soc(chip->client);
+ max17040_get_online(chip->client);
+ max17040_get_status(chip->client);
+
+ queue_delayed_work(system_power_efficient_wq, &chip->work,
+ MAX17040_DELAY);
+}
+
+static enum power_supply_property max17040_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static const struct power_supply_desc max17040_battery_desc = {
+ .name = "battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max17040_get_property,
+ .properties = max17040_battery_props,
+ .num_properties = ARRAY_SIZE(max17040_battery_props),
+};
+
+static int max17040_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct max17040_chip *chip;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->pdata = client->dev.platform_data;
+
+ i2c_set_clientdata(client, chip);
+ psy_cfg.drv_data = chip;
+
+ chip->battery = power_supply_register(&client->dev,
+ &max17040_battery_desc, &psy_cfg);
+ if (IS_ERR(chip->battery)) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ return PTR_ERR(chip->battery);
+ }
+
+ max17040_reset(client);
+ max17040_get_version(client);
+
+ INIT_DEFERRABLE_WORK(&chip->work, max17040_work);
+ queue_delayed_work(system_power_efficient_wq, &chip->work,
+ MAX17040_DELAY);
+
+ return 0;
+}
+
+static int max17040_remove(struct i2c_client *client)
+{
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ power_supply_unregister(chip->battery);
+ cancel_delayed_work(&chip->work);
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+
+static int max17040_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work(&chip->work);
+ return 0;
+}
+
+static int max17040_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct max17040_chip *chip = i2c_get_clientdata(client);
+
+ queue_delayed_work(system_power_efficient_wq, &chip->work,
+ MAX17040_DELAY);
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume);
+#define MAX17040_PM_OPS (&max17040_pm_ops)
+
+#else
+
+#define MAX17040_PM_OPS NULL
+
+#endif /* CONFIG_PM_SLEEP */
+
+static const struct i2c_device_id max17040_id[] = {
+ { "max17040" },
+ { "max77836-battery" },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17040_id);
+
+static struct i2c_driver max17040_i2c_driver = {
+ .driver = {
+ .name = "max17040",
+ .pm = MAX17040_PM_OPS,
+ },
+ .probe = max17040_probe,
+ .remove = max17040_remove,
+ .id_table = max17040_id,
+};
+module_i2c_driver(max17040_i2c_driver);
+
+MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>");
+MODULE_DESCRIPTION("MAX17040 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max17042_battery.c b/drivers/power/supply/max17042_battery.c
new file mode 100644
index 0000000..da7a75f
--- /dev/null
+++ b/drivers/power/supply/max17042_battery.c
@@ -0,0 +1,1019 @@
+/*
+ * Fuel gauge driver for Maxim 17042 / 8966 / 8997
+ * Note that Maxim 8966 and 8997 are mfd and this is its subdevice.
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.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 is based on max17040_battery.c
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/interrupt.h>
+#include <linux/pm.h>
+#include <linux/mod_devicetable.h>
+#include <linux/power_supply.h>
+#include <linux/power/max17042_battery.h>
+#include <linux/of.h>
+#include <linux/regmap.h>
+
+/* Status register bits */
+#define STATUS_POR_BIT (1 << 1)
+#define STATUS_BST_BIT (1 << 3)
+#define STATUS_VMN_BIT (1 << 8)
+#define STATUS_TMN_BIT (1 << 9)
+#define STATUS_SMN_BIT (1 << 10)
+#define STATUS_BI_BIT (1 << 11)
+#define STATUS_VMX_BIT (1 << 12)
+#define STATUS_TMX_BIT (1 << 13)
+#define STATUS_SMX_BIT (1 << 14)
+#define STATUS_BR_BIT (1 << 15)
+
+/* Interrupt mask bits */
+#define CONFIG_ALRT_BIT_ENBL (1 << 2)
+#define STATUS_INTR_SOCMIN_BIT (1 << 10)
+#define STATUS_INTR_SOCMAX_BIT (1 << 14)
+
+#define VFSOC0_LOCK 0x0000
+#define VFSOC0_UNLOCK 0x0080
+#define MODEL_UNLOCK1 0X0059
+#define MODEL_UNLOCK2 0X00C4
+#define MODEL_LOCK1 0X0000
+#define MODEL_LOCK2 0X0000
+
+#define dQ_ACC_DIV 0x4
+#define dP_ACC_100 0x1900
+#define dP_ACC_200 0x3200
+
+#define MAX17042_VMAX_TOLERANCE 50 /* 50 mV */
+
+struct max17042_chip {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct power_supply *battery;
+ enum max170xx_chip_type chip_type;
+ struct max17042_platform_data *pdata;
+ struct work_struct work;
+ int init_complete;
+};
+
+static enum power_supply_property max17042_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MIN,
+ POWER_SUPPLY_PROP_TEMP_ALERT_MAX,
+ POWER_SUPPLY_PROP_TEMP_MIN,
+ POWER_SUPPLY_PROP_TEMP_MAX,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+};
+
+static int max17042_get_temperature(struct max17042_chip *chip, int *temp)
+{
+ int ret;
+ u32 data;
+ struct regmap *map = chip->regmap;
+
+ ret = regmap_read(map, MAX17042_TEMP, &data);
+ if (ret < 0)
+ return ret;
+
+ *temp = data;
+ /* The value is signed. */
+ if (*temp & 0x8000) {
+ *temp = (0x7fff & ~*temp) + 1;
+ *temp *= -1;
+ }
+
+ /* The value is converted into deci-centigrade scale */
+ /* Units of LSB = 1 / 256 degree Celsius */
+ *temp = *temp * 10 / 256;
+ return 0;
+}
+
+static int max17042_get_battery_health(struct max17042_chip *chip, int *health)
+{
+ int temp, vavg, vbatt, ret;
+ u32 val;
+
+ ret = regmap_read(chip->regmap, MAX17042_AvgVCELL, &val);
+ if (ret < 0)
+ goto health_error;
+
+ /* bits [0-3] unused */
+ vavg = val * 625 / 8;
+ /* Convert to millivolts */
+ vavg /= 1000;
+
+ ret = regmap_read(chip->regmap, MAX17042_VCELL, &val);
+ if (ret < 0)
+ goto health_error;
+
+ /* bits [0-3] unused */
+ vbatt = val * 625 / 8;
+ /* Convert to millivolts */
+ vbatt /= 1000;
+
+ if (vavg < chip->pdata->vmin) {
+ *health = POWER_SUPPLY_HEALTH_DEAD;
+ goto out;
+ }
+
+ if (vbatt > chip->pdata->vmax + MAX17042_VMAX_TOLERANCE) {
+ *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ goto out;
+ }
+
+ ret = max17042_get_temperature(chip, &temp);
+ if (ret < 0)
+ goto health_error;
+
+ if (temp <= chip->pdata->temp_min) {
+ *health = POWER_SUPPLY_HEALTH_COLD;
+ goto out;
+ }
+
+ if (temp >= chip->pdata->temp_max) {
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ goto out;
+ }
+
+ *health = POWER_SUPPLY_HEALTH_GOOD;
+
+out:
+ return 0;
+
+health_error:
+ return ret;
+}
+
+static int max17042_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+ struct regmap *map = chip->regmap;
+ int ret;
+ u32 data;
+
+ if (!chip->init_complete)
+ return -EAGAIN;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = regmap_read(map, MAX17042_STATUS, &data);
+ if (ret < 0)
+ return ret;
+
+ if (data & MAX17042_STATUS_BattAbsent)
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ ret = regmap_read(map, MAX17042_Cycles, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ ret = regmap_read(map, MAX17042_MinMaxVolt, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 8;
+ val->intval *= 20000; /* Units of LSB = 20mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
+ ret = regmap_read(map, MAX17042_V_empty, &data);
+ else
+ ret = regmap_read(map, MAX17047_V_empty, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 7;
+ val->intval *= 10000; /* Units of LSB = 10mV */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = regmap_read(map, MAX17042_VCELL, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ ret = regmap_read(map, MAX17042_AvgVCELL, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ ret = regmap_read(map, MAX17042_OCVInternal, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 625 / 8;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = regmap_read(map, MAX17042_RepSOC, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data >> 8;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ ret = regmap_read(map, MAX17042_FullCAP, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 1000 / 2;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = regmap_read(map, MAX17042_QH, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data * 1000 / 2;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = max17042_get_temperature(chip, &val->intval);
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+ /* LSB is Alert Minimum. In deci-centigrade */
+ val->intval = (data & 0xff) * 10;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+ /* MSB is Alert Maximum. In deci-centigrade */
+ val->intval = (data >> 8) * 10;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MIN:
+ val->intval = chip->pdata->temp_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP_MAX:
+ val->intval = chip->pdata->temp_max;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = max17042_get_battery_health(chip, &val->intval);
+ if (ret < 0)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (chip->pdata->enable_current_sense) {
+ ret = regmap_read(map, MAX17042_Current, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data;
+ if (val->intval & 0x8000) {
+ /* Negative */
+ val->intval = ~val->intval & 0x7fff;
+ val->intval++;
+ val->intval *= -1;
+ }
+ val->intval *= 1562500 / chip->pdata->r_sns;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ if (chip->pdata->enable_current_sense) {
+ ret = regmap_read(map, MAX17042_AvgCurrent, &data);
+ if (ret < 0)
+ return ret;
+
+ val->intval = data;
+ if (val->intval & 0x8000) {
+ /* Negative */
+ val->intval = ~val->intval & 0x7fff;
+ val->intval++;
+ val->intval *= -1;
+ }
+ val->intval *= 1562500 / chip->pdata->r_sns;
+ } else {
+ return -EINVAL;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct max17042_chip *chip = power_supply_get_drvdata(psy);
+ struct regmap *map = chip->regmap;
+ int ret = 0;
+ u32 data;
+ int8_t temp;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+
+ /* Input in deci-centigrade, convert to centigrade */
+ temp = val->intval / 10;
+ /* force min < max */
+ if (temp >= (int8_t)(data >> 8))
+ temp = (int8_t)(data >> 8) - 1;
+ /* Write both MAX and MIN ALERT */
+ data = (data & 0xff00) + temp;
+ ret = regmap_write(map, MAX17042_TALRT_Th, data);
+ break;
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = regmap_read(map, MAX17042_TALRT_Th, &data);
+ if (ret < 0)
+ return ret;
+
+ /* Input in Deci-Centigrade, convert to centigrade */
+ temp = val->intval / 10;
+ /* force max > min */
+ if (temp <= (int8_t)(data & 0xff))
+ temp = (int8_t)(data & 0xff) + 1;
+ /* Write both MAX and MIN ALERT */
+ data = (data & 0xff) + (temp << 8);
+ ret = regmap_write(map, MAX17042_TALRT_Th, data);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int max17042_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ int ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MIN:
+ case POWER_SUPPLY_PROP_TEMP_ALERT_MAX:
+ ret = 1;
+ break;
+ default:
+ ret = 0;
+ }
+
+ return ret;
+}
+
+static int max17042_write_verify_reg(struct regmap *map, u8 reg, u32 value)
+{
+ int retries = 8;
+ int ret;
+ u32 read_value;
+
+ do {
+ ret = regmap_write(map, reg, value);
+ regmap_read(map, reg, &read_value);
+ if (read_value != value) {
+ ret = -EIO;
+ retries--;
+ }
+ } while (retries && read_value != value);
+
+ if (ret < 0)
+ pr_err("%s: err %d\n", __func__, ret);
+
+ return ret;
+}
+
+static inline void max17042_override_por(struct regmap *map,
+ u8 reg, u16 value)
+{
+ if (value)
+ regmap_write(map, reg, value);
+}
+
+static inline void max10742_unlock_model(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_UNLOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_UNLOCK2);
+}
+
+static inline void max10742_lock_model(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_MLOCKReg1, MODEL_LOCK1);
+ regmap_write(map, MAX17042_MLOCKReg2, MODEL_LOCK2);
+}
+
+static inline void max17042_write_model_data(struct max17042_chip *chip,
+ u8 addr, int size)
+{
+ struct regmap *map = chip->regmap;
+ int i;
+
+ for (i = 0; i < size; i++)
+ regmap_write(map, addr + i,
+ chip->pdata->config_data->cell_char_tbl[i]);
+}
+
+static inline void max17042_read_model_data(struct max17042_chip *chip,
+ u8 addr, u16 *data, int size)
+{
+ struct regmap *map = chip->regmap;
+ int i;
+ u32 tmp;
+
+ for (i = 0; i < size; i++) {
+ regmap_read(map, addr + i, &tmp);
+ data[i] = (u16)tmp;
+ }
+}
+
+static inline int max17042_model_data_compare(struct max17042_chip *chip,
+ u16 *data1, u16 *data2, int size)
+{
+ int i;
+
+ if (memcmp(data1, data2, size)) {
+ dev_err(&chip->client->dev, "%s compare failed\n", __func__);
+ for (i = 0; i < size; i++)
+ dev_info(&chip->client->dev, "0x%x, 0x%x",
+ data1[i], data2[i]);
+ dev_info(&chip->client->dev, "\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int max17042_init_model(struct max17042_chip *chip)
+{
+ int ret;
+ int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+ u16 *temp_data;
+
+ temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max10742_unlock_model(chip);
+ max17042_write_model_data(chip, MAX17042_MODELChrTbl,
+ table_size);
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+
+ ret = max17042_model_data_compare(
+ chip,
+ chip->pdata->config_data->cell_char_tbl,
+ temp_data,
+ table_size);
+
+ max10742_lock_model(chip);
+ kfree(temp_data);
+
+ return ret;
+}
+
+static int max17042_verify_model_lock(struct max17042_chip *chip)
+{
+ int i;
+ int table_size = ARRAY_SIZE(chip->pdata->config_data->cell_char_tbl);
+ u16 *temp_data;
+ int ret = 0;
+
+ temp_data = kcalloc(table_size, sizeof(*temp_data), GFP_KERNEL);
+ if (!temp_data)
+ return -ENOMEM;
+
+ max17042_read_model_data(chip, MAX17042_MODELChrTbl, temp_data,
+ table_size);
+ for (i = 0; i < table_size; i++)
+ if (temp_data[i])
+ ret = -EINVAL;
+
+ kfree(temp_data);
+ return ret;
+}
+
+static void max17042_write_config_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ regmap_write(map, MAX17042_CONFIG, config->config);
+ regmap_write(map, MAX17042_LearnCFG, config->learn_cfg);
+ regmap_write(map, MAX17042_FilterCFG,
+ config->filter_cfg);
+ regmap_write(map, MAX17042_RelaxCFG, config->relax_cfg);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17047 ||
+ chip->chip_type == MAXIM_DEVICE_TYPE_MAX17050)
+ regmap_write(map, MAX17047_FullSOCThr,
+ config->full_soc_thresh);
+}
+
+static void max17042_write_custom_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ max17042_write_verify_reg(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_write_verify_reg(map, MAX17042_TempCo, config->tcompc0);
+ max17042_write_verify_reg(map, MAX17042_ICHGTerm, config->ichgt_term);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042) {
+ regmap_write(map, MAX17042_EmptyTempCo, config->empty_tempco);
+ max17042_write_verify_reg(map, MAX17042_K_empty0,
+ config->kempty0);
+ } else {
+ max17042_write_verify_reg(map, MAX17047_QRTbl00,
+ config->qrtbl00);
+ max17042_write_verify_reg(map, MAX17047_QRTbl10,
+ config->qrtbl10);
+ max17042_write_verify_reg(map, MAX17047_QRTbl20,
+ config->qrtbl20);
+ max17042_write_verify_reg(map, MAX17047_QRTbl30,
+ config->qrtbl30);
+ }
+}
+
+static void max17042_update_capacity_regs(struct max17042_chip *chip)
+{
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
+ config->fullcap);
+ regmap_write(map, MAX17042_DesignCap, config->design_cap);
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
+ config->fullcapnom);
+}
+
+static void max17042_reset_vfsoc0_reg(struct max17042_chip *chip)
+{
+ unsigned int vfSoc;
+ struct regmap *map = chip->regmap;
+
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_UNLOCK);
+ max17042_write_verify_reg(map, MAX17042_VFSOC0, vfSoc);
+ regmap_write(map, MAX17042_VFSOC0Enable, VFSOC0_LOCK);
+}
+
+static void max17042_load_new_capacity_params(struct max17042_chip *chip)
+{
+ u32 full_cap0, rep_cap, dq_acc, vfSoc;
+ u32 rem_cap;
+
+ struct max17042_config_data *config = chip->pdata->config_data;
+ struct regmap *map = chip->regmap;
+
+ regmap_read(map, MAX17042_FullCAP0, &full_cap0);
+ regmap_read(map, MAX17042_VFSOC, &vfSoc);
+
+ /* fg_vfSoc needs to shifted by 8 bits to get the
+ * perc in 1% accuracy, to get the right rem_cap multiply
+ * full_cap0, fg_vfSoc and devide by 100
+ */
+ rem_cap = ((vfSoc >> 8) * full_cap0) / 100;
+ max17042_write_verify_reg(map, MAX17042_RemCap, rem_cap);
+
+ rep_cap = rem_cap;
+ max17042_write_verify_reg(map, MAX17042_RepCap, rep_cap);
+
+ /* Write dQ_acc to 200% of Capacity and dP_acc to 200% */
+ dq_acc = config->fullcap / dQ_ACC_DIV;
+ max17042_write_verify_reg(map, MAX17042_dQacc, dq_acc);
+ max17042_write_verify_reg(map, MAX17042_dPacc, dP_ACC_200);
+
+ max17042_write_verify_reg(map, MAX17042_FullCAP,
+ config->fullcap);
+ regmap_write(map, MAX17042_DesignCap,
+ config->design_cap);
+ max17042_write_verify_reg(map, MAX17042_FullCAPNom,
+ config->fullcapnom);
+ /* Update SOC register with new SOC */
+ regmap_write(map, MAX17042_RepSOC, vfSoc);
+}
+
+/*
+ * Block write all the override values coming from platform data.
+ * This function MUST be called before the POR initialization proceedure
+ * specified by maxim.
+ */
+static inline void max17042_override_por_values(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ struct max17042_config_data *config = chip->pdata->config_data;
+
+ max17042_override_por(map, MAX17042_TGAIN, config->tgain);
+ max17042_override_por(map, MAx17042_TOFF, config->toff);
+ max17042_override_por(map, MAX17042_CGAIN, config->cgain);
+ max17042_override_por(map, MAX17042_COFF, config->coff);
+
+ max17042_override_por(map, MAX17042_VALRT_Th, config->valrt_thresh);
+ max17042_override_por(map, MAX17042_TALRT_Th, config->talrt_thresh);
+ max17042_override_por(map, MAX17042_SALRT_Th,
+ config->soc_alrt_thresh);
+ max17042_override_por(map, MAX17042_CONFIG, config->config);
+ max17042_override_por(map, MAX17042_SHDNTIMER, config->shdntimer);
+
+ max17042_override_por(map, MAX17042_DesignCap, config->design_cap);
+ max17042_override_por(map, MAX17042_ICHGTerm, config->ichgt_term);
+
+ max17042_override_por(map, MAX17042_AtRate, config->at_rate);
+ max17042_override_por(map, MAX17042_LearnCFG, config->learn_cfg);
+ max17042_override_por(map, MAX17042_FilterCFG, config->filter_cfg);
+ max17042_override_por(map, MAX17042_RelaxCFG, config->relax_cfg);
+ max17042_override_por(map, MAX17042_MiscCFG, config->misc_cfg);
+ max17042_override_por(map, MAX17042_MaskSOC, config->masksoc);
+
+ max17042_override_por(map, MAX17042_FullCAP, config->fullcap);
+ max17042_override_por(map, MAX17042_FullCAPNom, config->fullcapnom);
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
+ max17042_override_por(map, MAX17042_SOC_empty,
+ config->socempty);
+ max17042_override_por(map, MAX17042_LAvg_empty, config->lavg_empty);
+ max17042_override_por(map, MAX17042_dQacc, config->dqacc);
+ max17042_override_por(map, MAX17042_dPacc, config->dpacc);
+
+ if (chip->chip_type == MAXIM_DEVICE_TYPE_MAX17042)
+ max17042_override_por(map, MAX17042_V_empty, config->vempty);
+ else
+ max17042_override_por(map, MAX17047_V_empty, config->vempty);
+ max17042_override_por(map, MAX17042_TempNom, config->temp_nom);
+ max17042_override_por(map, MAX17042_TempLim, config->temp_lim);
+ max17042_override_por(map, MAX17042_FCTC, config->fctc);
+ max17042_override_por(map, MAX17042_RCOMP0, config->rcomp0);
+ max17042_override_por(map, MAX17042_TempCo, config->tcompc0);
+ if (chip->chip_type) {
+ max17042_override_por(map, MAX17042_EmptyTempCo,
+ config->empty_tempco);
+ max17042_override_por(map, MAX17042_K_empty0,
+ config->kempty0);
+ }
+}
+
+static int max17042_init_chip(struct max17042_chip *chip)
+{
+ struct regmap *map = chip->regmap;
+ int ret;
+
+ max17042_override_por_values(chip);
+ /* After Power up, the MAX17042 requires 500mS in order
+ * to perform signal debouncing and initial SOC reporting
+ */
+ msleep(500);
+
+ /* Initialize configaration */
+ max17042_write_config_regs(chip);
+
+ /* write cell characterization data */
+ ret = max17042_init_model(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s init failed\n",
+ __func__);
+ return -EIO;
+ }
+
+ ret = max17042_verify_model_lock(chip);
+ if (ret) {
+ dev_err(&chip->client->dev, "%s lock verify failed\n",
+ __func__);
+ return -EIO;
+ }
+ /* write custom parameters */
+ max17042_write_custom_regs(chip);
+
+ /* update capacity params */
+ max17042_update_capacity_regs(chip);
+
+ /* delay must be atleast 350mS to allow VFSOC
+ * to be calculated from the new configuration
+ */
+ msleep(350);
+
+ /* reset vfsoc0 reg */
+ max17042_reset_vfsoc0_reg(chip);
+
+ /* load new capacity params */
+ max17042_load_new_capacity_params(chip);
+
+ /* Init complete, Clear the POR bit */
+ regmap_update_bits(map, MAX17042_STATUS, STATUS_POR_BIT, 0x0);
+ return 0;
+}
+
+static void max17042_set_soc_threshold(struct max17042_chip *chip, u16 off)
+{
+ struct regmap *map = chip->regmap;
+ u32 soc, soc_tr;
+
+ /* program interrupt thesholds such that we should
+ * get interrupt for every 'off' perc change in the soc
+ */
+ regmap_read(map, MAX17042_RepSOC, &soc);
+ soc >>= 8;
+ soc_tr = (soc + off) << 8;
+ soc_tr |= (soc - off);
+ regmap_write(map, MAX17042_SALRT_Th, soc_tr);
+}
+
+static irqreturn_t max17042_thread_handler(int id, void *dev)
+{
+ struct max17042_chip *chip = dev;
+ u32 val;
+
+ regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if ((val & STATUS_INTR_SOCMIN_BIT) ||
+ (val & STATUS_INTR_SOCMAX_BIT)) {
+ dev_info(&chip->client->dev, "SOC threshold INTR\n");
+ max17042_set_soc_threshold(chip, 1);
+ }
+
+ power_supply_changed(chip->battery);
+ return IRQ_HANDLED;
+}
+
+static void max17042_init_worker(struct work_struct *work)
+{
+ struct max17042_chip *chip = container_of(work,
+ struct max17042_chip, work);
+ int ret;
+
+ /* Initialize registers according to values from the platform data */
+ if (chip->pdata->enable_por_init && chip->pdata->config_data) {
+ ret = max17042_init_chip(chip);
+ if (ret)
+ return;
+ }
+
+ chip->init_complete = 1;
+}
+
+#ifdef CONFIG_OF
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ u32 prop;
+ struct max17042_platform_data *pdata;
+
+ if (!np)
+ return dev->platform_data;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ /*
+ * Require current sense resistor value to be specified for
+ * current-sense functionality to be enabled at all.
+ */
+ if (of_property_read_u32(np, "maxim,rsns-microohm", &prop) == 0) {
+ pdata->r_sns = prop;
+ pdata->enable_current_sense = true;
+ }
+
+ if (of_property_read_s32(np, "maxim,cold-temp", &pdata->temp_min))
+ pdata->temp_min = INT_MIN;
+ if (of_property_read_s32(np, "maxim,over-heat-temp", &pdata->temp_max))
+ pdata->temp_max = INT_MAX;
+ if (of_property_read_s32(np, "maxim,dead-volt", &pdata->vmin))
+ pdata->vmin = INT_MIN;
+ if (of_property_read_s32(np, "maxim,over-volt", &pdata->vmax))
+ pdata->vmax = INT_MAX;
+
+ return pdata;
+}
+#else
+static struct max17042_platform_data *
+max17042_get_pdata(struct device *dev)
+{
+ return dev->platform_data;
+}
+#endif
+
+static const struct regmap_config max17042_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 16,
+ .val_format_endian = REGMAP_ENDIAN_NATIVE,
+};
+
+static const struct power_supply_desc max17042_psy_desc = {
+ .name = "max170xx_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max17042_get_property,
+ .set_property = max17042_set_property,
+ .property_is_writeable = max17042_property_is_writeable,
+ .properties = max17042_battery_props,
+ .num_properties = ARRAY_SIZE(max17042_battery_props),
+};
+
+static const struct power_supply_desc max17042_no_current_sense_psy_desc = {
+ .name = "max170xx_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max17042_get_property,
+ .set_property = max17042_set_property,
+ .property_is_writeable = max17042_property_is_writeable,
+ .properties = max17042_battery_props,
+ .num_properties = ARRAY_SIZE(max17042_battery_props) - 2,
+};
+
+static int max17042_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ const struct power_supply_desc *max17042_desc = &max17042_psy_desc;
+ struct power_supply_config psy_cfg = {};
+ struct max17042_chip *chip;
+ int ret;
+ int i;
+ u32 val;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA))
+ return -EIO;
+
+ chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->regmap = devm_regmap_init_i2c(client, &max17042_regmap_config);
+ if (IS_ERR(chip->regmap)) {
+ dev_err(&client->dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ chip->pdata = max17042_get_pdata(&client->dev);
+ if (!chip->pdata) {
+ dev_err(&client->dev, "no platform data provided\n");
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, chip);
+ chip->chip_type = id->driver_data;
+ psy_cfg.drv_data = chip;
+
+ /* When current is not measured,
+ * CURRENT_NOW and CURRENT_AVG properties should be invisible. */
+ if (!chip->pdata->enable_current_sense)
+ max17042_desc = &max17042_no_current_sense_psy_desc;
+
+ if (chip->pdata->r_sns == 0)
+ chip->pdata->r_sns = MAX17042_DEFAULT_SNS_RESISTOR;
+
+ if (chip->pdata->init_data)
+ for (i = 0; i < chip->pdata->num_init_data; i++)
+ regmap_write(chip->regmap,
+ chip->pdata->init_data[i].addr,
+ chip->pdata->init_data[i].data);
+
+ if (!chip->pdata->enable_current_sense) {
+ regmap_write(chip->regmap, MAX17042_CGAIN, 0x0000);
+ regmap_write(chip->regmap, MAX17042_MiscCFG, 0x0003);
+ regmap_write(chip->regmap, MAX17042_LearnCFG, 0x0007);
+ }
+
+ chip->battery = devm_power_supply_register(&client->dev, max17042_desc,
+ &psy_cfg);
+ if (IS_ERR(chip->battery)) {
+ dev_err(&client->dev, "failed: power supply register\n");
+ return PTR_ERR(chip->battery);
+ }
+
+ if (client->irq) {
+ ret = devm_request_threaded_irq(&client->dev, client->irq,
+ NULL,
+ max17042_thread_handler,
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ chip->battery->desc->name,
+ chip);
+ if (!ret) {
+ regmap_update_bits(chip->regmap, MAX17042_CONFIG,
+ CONFIG_ALRT_BIT_ENBL,
+ CONFIG_ALRT_BIT_ENBL);
+ max17042_set_soc_threshold(chip, 1);
+ } else {
+ client->irq = 0;
+ dev_err(&client->dev, "%s(): cannot get IRQ\n",
+ __func__);
+ }
+ }
+
+ regmap_read(chip->regmap, MAX17042_STATUS, &val);
+ if (val & STATUS_POR_BIT) {
+ INIT_WORK(&chip->work, max17042_init_worker);
+ schedule_work(&chip->work);
+ } else {
+ chip->init_complete = 1;
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_PM_SLEEP
+static int max17042_suspend(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ /*
+ * disable the irq and enable irq_wake
+ * capability to the interrupt line.
+ */
+ if (chip->client->irq) {
+ disable_irq(chip->client->irq);
+ enable_irq_wake(chip->client->irq);
+ }
+
+ return 0;
+}
+
+static int max17042_resume(struct device *dev)
+{
+ struct max17042_chip *chip = dev_get_drvdata(dev);
+
+ if (chip->client->irq) {
+ disable_irq_wake(chip->client->irq);
+ enable_irq(chip->client->irq);
+ /* re-program the SOC thresholds to 1% change */
+ max17042_set_soc_threshold(chip, 1);
+ }
+
+ return 0;
+}
+#endif
+
+static SIMPLE_DEV_PM_OPS(max17042_pm_ops, max17042_suspend,
+ max17042_resume);
+
+#ifdef CONFIG_OF
+static const struct of_device_id max17042_dt_match[] = {
+ { .compatible = "maxim,max17042" },
+ { .compatible = "maxim,max17047" },
+ { .compatible = "maxim,max17050" },
+ { },
+};
+MODULE_DEVICE_TABLE(of, max17042_dt_match);
+#endif
+
+static const struct i2c_device_id max17042_id[] = {
+ { "max17042", MAXIM_DEVICE_TYPE_MAX17042 },
+ { "max17047", MAXIM_DEVICE_TYPE_MAX17047 },
+ { "max17050", MAXIM_DEVICE_TYPE_MAX17050 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, max17042_id);
+
+static struct i2c_driver max17042_i2c_driver = {
+ .driver = {
+ .name = "max17042",
+ .of_match_table = of_match_ptr(max17042_dt_match),
+ .pm = &max17042_pm_ops,
+ },
+ .probe = max17042_probe,
+ .id_table = max17042_id,
+};
+module_i2c_driver(max17042_i2c_driver);
+
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_DESCRIPTION("MAX17042 Fuel Gauge");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c
new file mode 100644
index 0000000..6c78884
--- /dev/null
+++ b/drivers/power/supply/max77693_charger.c
@@ -0,0 +1,771 @@
+/*
+ * max77693_charger.c - Battery charger driver for the Maxim 77693
+ *
+ * Copyright (C) 2014 Samsung Electronics
+ * Krzysztof Kozlowski <krzk@kernel.org>
+ *
+ * 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.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/mfd/max77693.h>
+#include <linux/mfd/max77693-common.h>
+#include <linux/mfd/max77693-private.h>
+
+#define MAX77693_CHARGER_NAME "max77693-charger"
+static const char *max77693_charger_model = "MAX77693";
+static const char *max77693_charger_manufacturer = "Maxim Integrated";
+
+struct max77693_charger {
+ struct device *dev;
+ struct max77693_dev *max77693;
+ struct power_supply *charger;
+
+ u32 constant_volt;
+ u32 min_system_volt;
+ u32 thermal_regulation_temp;
+ u32 batttery_overcurrent;
+ u32 charge_input_threshold_volt;
+};
+
+static int max77693_get_charger_state(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int data;
+
+ ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_DETAILS_01_CHG_MASK;
+ data >>= CHG_DETAILS_01_CHG_SHIFT;
+
+ switch (data) {
+ case MAX77693_CHARGING_PREQUALIFICATION:
+ case MAX77693_CHARGING_FAST_CONST_CURRENT:
+ case MAX77693_CHARGING_FAST_CONST_VOLTAGE:
+ case MAX77693_CHARGING_TOP_OFF:
+ /* In high temp the charging current is reduced, but still charging */
+ case MAX77693_CHARGING_HIGH_TEMP:
+ *val = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case MAX77693_CHARGING_DONE:
+ *val = POWER_SUPPLY_STATUS_FULL;
+ break;
+ case MAX77693_CHARGING_TIMER_EXPIRED:
+ case MAX77693_CHARGING_THERMISTOR_SUSPEND:
+ *val = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case MAX77693_CHARGING_OFF:
+ case MAX77693_CHARGING_OVER_TEMP:
+ case MAX77693_CHARGING_WATCHDOG_EXPIRED:
+ *val = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case MAX77693_CHARGING_RESERVED:
+ default:
+ *val = POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ return 0;
+}
+
+static int max77693_get_charge_type(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int data;
+
+ ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_DETAILS_01_CHG_MASK;
+ data >>= CHG_DETAILS_01_CHG_SHIFT;
+
+ switch (data) {
+ case MAX77693_CHARGING_PREQUALIFICATION:
+ /*
+ * Top-off: trickle or fast? In top-off the current varies between
+ * 100 and 250 mA. It is higher than prequalification current.
+ */
+ case MAX77693_CHARGING_TOP_OFF:
+ *val = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case MAX77693_CHARGING_FAST_CONST_CURRENT:
+ case MAX77693_CHARGING_FAST_CONST_VOLTAGE:
+ /* In high temp the charging current is reduced, but still charging */
+ case MAX77693_CHARGING_HIGH_TEMP:
+ *val = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case MAX77693_CHARGING_DONE:
+ case MAX77693_CHARGING_TIMER_EXPIRED:
+ case MAX77693_CHARGING_THERMISTOR_SUSPEND:
+ case MAX77693_CHARGING_OFF:
+ case MAX77693_CHARGING_OVER_TEMP:
+ case MAX77693_CHARGING_WATCHDOG_EXPIRED:
+ *val = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case MAX77693_CHARGING_RESERVED:
+ default:
+ *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+
+ return 0;
+}
+
+/*
+ * Supported health statuses:
+ * - POWER_SUPPLY_HEALTH_DEAD
+ * - POWER_SUPPLY_HEALTH_GOOD
+ * - POWER_SUPPLY_HEALTH_OVERVOLTAGE
+ * - POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE
+ * - POWER_SUPPLY_HEALTH_UNKNOWN
+ * - POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
+ */
+static int max77693_get_battery_health(struct regmap *regmap, int *val)
+{
+ int ret;
+ unsigned int data;
+
+ ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_DETAILS_01, &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_DETAILS_01_BAT_MASK;
+ data >>= CHG_DETAILS_01_BAT_SHIFT;
+
+ switch (data) {
+ case MAX77693_BATTERY_NOBAT:
+ *val = POWER_SUPPLY_HEALTH_DEAD;
+ break;
+ case MAX77693_BATTERY_PREQUALIFICATION:
+ case MAX77693_BATTERY_GOOD:
+ case MAX77693_BATTERY_LOWVOLTAGE:
+ *val = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case MAX77693_BATTERY_TIMER_EXPIRED:
+ /*
+ * Took longer to charge than expected, charging suspended.
+ * Damaged battery?
+ */
+ *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ break;
+ case MAX77693_BATTERY_OVERVOLTAGE:
+ *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+ case MAX77693_BATTERY_OVERCURRENT:
+ *val = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ case MAX77693_BATTERY_RESERVED:
+ default:
+ *val = POWER_SUPPLY_HEALTH_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int max77693_get_present(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ /*
+ * Read CHG_INT_OK register. High DETBAT bit here should be
+ * equal to value 0x0 in CHG_DETAILS_01/BAT field.
+ */
+ ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data);
+ if (ret < 0)
+ return ret;
+
+ *val = (data & CHG_INT_OK_DETBAT_MASK) ? 0 : 1;
+
+ return 0;
+}
+
+static int max77693_get_online(struct regmap *regmap, int *val)
+{
+ unsigned int data;
+ int ret;
+
+ ret = regmap_read(regmap, MAX77693_CHG_REG_CHG_INT_OK, &data);
+ if (ret < 0)
+ return ret;
+
+ *val = (data & CHG_INT_OK_CHGIN_MASK) ? 1 : 0;
+
+ return 0;
+}
+
+static enum power_supply_property max77693_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static int max77693_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max77693_charger *chg = power_supply_get_drvdata(psy);
+ struct regmap *regmap = chg->max77693->regmap;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = max77693_get_charger_state(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = max77693_get_charge_type(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = max77693_get_battery_health(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max77693_get_present(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max77693_get_online(regmap, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = max77693_charger_model;
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = max77693_charger_manufacturer;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return ret;
+}
+
+static const struct power_supply_desc max77693_charger_desc = {
+ .name = MAX77693_CHARGER_NAME,
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max77693_charger_props,
+ .num_properties = ARRAY_SIZE(max77693_charger_props),
+ .get_property = max77693_charger_get_property,
+};
+
+static ssize_t device_attr_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count,
+ int (*fn)(struct max77693_charger *, unsigned long))
+{
+ struct max77693_charger *chg = dev_get_drvdata(dev);
+ unsigned long val;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ ret = fn(chg, val);
+ if (ret)
+ return ret;
+
+ return count;
+}
+
+static ssize_t fast_charge_timer_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77693_charger *chg = dev_get_drvdata(dev);
+ unsigned int data, val;
+ int ret;
+
+ ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_01,
+ &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_CNFG_01_FCHGTIME_MASK;
+ data >>= CHG_CNFG_01_FCHGTIME_SHIFT;
+ switch (data) {
+ case 0x1 ... 0x7:
+ /* Starting from 4 hours, step by 2 hours */
+ val = 4 + (data - 1) * 2;
+ break;
+ case 0x0:
+ default:
+ val = 0;
+ break;
+ }
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static int max77693_set_fast_charge_timer(struct max77693_charger *chg,
+ unsigned long hours)
+{
+ unsigned int data;
+
+ /*
+ * 0x00 - disable
+ * 0x01 - 4h
+ * 0x02 - 6h
+ * ...
+ * 0x07 - 16h
+ * Round down odd values.
+ */
+ switch (hours) {
+ case 4 ... 16:
+ data = (hours - 4) / 2 + 1;
+ break;
+ case 0:
+ /* Disable */
+ data = 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ data <<= CHG_CNFG_01_FCHGTIME_SHIFT;
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_01,
+ CHG_CNFG_01_FCHGTIME_MASK, data);
+}
+
+static ssize_t fast_charge_timer_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return device_attr_store(dev, attr, buf, count,
+ max77693_set_fast_charge_timer);
+}
+
+static ssize_t top_off_threshold_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77693_charger *chg = dev_get_drvdata(dev);
+ unsigned int data, val;
+ int ret;
+
+ ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03,
+ &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_CNFG_03_TOITH_MASK;
+ data >>= CHG_CNFG_03_TOITH_SHIFT;
+
+ if (data <= 0x04)
+ val = 100000 + data * 25000;
+ else
+ val = data * 50000;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static int max77693_set_top_off_threshold_current(struct max77693_charger *chg,
+ unsigned long uamp)
+{
+ unsigned int data;
+
+ if (uamp < 100000 || uamp > 350000)
+ return -EINVAL;
+
+ if (uamp <= 200000)
+ data = (uamp - 100000) / 25000;
+ else
+ /* (200000, 350000> */
+ data = uamp / 50000;
+
+ data <<= CHG_CNFG_03_TOITH_SHIFT;
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_03,
+ CHG_CNFG_03_TOITH_MASK, data);
+}
+
+static ssize_t top_off_threshold_current_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return device_attr_store(dev, attr, buf, count,
+ max77693_set_top_off_threshold_current);
+}
+
+static ssize_t top_off_timer_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct max77693_charger *chg = dev_get_drvdata(dev);
+ unsigned int data, val;
+ int ret;
+
+ ret = regmap_read(chg->max77693->regmap, MAX77693_CHG_REG_CHG_CNFG_03,
+ &data);
+ if (ret < 0)
+ return ret;
+
+ data &= CHG_CNFG_03_TOTIME_MASK;
+ data >>= CHG_CNFG_03_TOTIME_SHIFT;
+
+ val = data * 10;
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n", val);
+}
+
+static int max77693_set_top_off_timer(struct max77693_charger *chg,
+ unsigned long minutes)
+{
+ unsigned int data;
+
+ if (minutes > 70)
+ return -EINVAL;
+
+ data = minutes / 10;
+ data <<= CHG_CNFG_03_TOTIME_SHIFT;
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_03,
+ CHG_CNFG_03_TOTIME_MASK, data);
+}
+
+static ssize_t top_off_timer_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ return device_attr_store(dev, attr, buf, count,
+ max77693_set_top_off_timer);
+}
+
+static DEVICE_ATTR_RW(fast_charge_timer);
+static DEVICE_ATTR_RW(top_off_threshold_current);
+static DEVICE_ATTR_RW(top_off_timer);
+
+static int max77693_set_constant_volt(struct max77693_charger *chg,
+ unsigned int uvolt)
+{
+ unsigned int data;
+
+ /*
+ * 0x00 - 3.650 V
+ * 0x01 - 3.675 V
+ * ...
+ * 0x1b - 4.325 V
+ * 0x1c - 4.340 V
+ * 0x1d - 4.350 V
+ * 0x1e - 4.375 V
+ * 0x1f - 4.400 V
+ */
+ if (uvolt >= 3650000 && uvolt < 4340000)
+ data = (uvolt - 3650000) / 25000;
+ else if (uvolt >= 4340000 && uvolt < 4350000)
+ data = 0x1c;
+ else if (uvolt >= 4350000 && uvolt <= 4400000)
+ data = 0x1d + (uvolt - 4350000) / 25000;
+ else {
+ dev_err(chg->dev, "Wrong value for charging constant voltage\n");
+ return -EINVAL;
+ }
+
+ data <<= CHG_CNFG_04_CHGCVPRM_SHIFT;
+
+ dev_dbg(chg->dev, "Charging constant voltage: %u (0x%x)\n", uvolt,
+ data);
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_04,
+ CHG_CNFG_04_CHGCVPRM_MASK, data);
+}
+
+static int max77693_set_min_system_volt(struct max77693_charger *chg,
+ unsigned int uvolt)
+{
+ unsigned int data;
+
+ if (uvolt < 3000000 || uvolt > 3700000) {
+ dev_err(chg->dev, "Wrong value for minimum system regulation voltage\n");
+ return -EINVAL;
+ }
+
+ data = (uvolt - 3000000) / 100000;
+
+ data <<= CHG_CNFG_04_MINVSYS_SHIFT;
+
+ dev_dbg(chg->dev, "Minimum system regulation voltage: %u (0x%x)\n",
+ uvolt, data);
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_04,
+ CHG_CNFG_04_MINVSYS_MASK, data);
+}
+
+static int max77693_set_thermal_regulation_temp(struct max77693_charger *chg,
+ unsigned int cels)
+{
+ unsigned int data;
+
+ switch (cels) {
+ case 70:
+ case 85:
+ case 100:
+ case 115:
+ data = (cels - 70) / 15;
+ break;
+ default:
+ dev_err(chg->dev, "Wrong value for thermal regulation loop temperature\n");
+ return -EINVAL;
+ }
+
+ data <<= CHG_CNFG_07_REGTEMP_SHIFT;
+
+ dev_dbg(chg->dev, "Thermal regulation loop temperature: %u (0x%x)\n",
+ cels, data);
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_07,
+ CHG_CNFG_07_REGTEMP_MASK, data);
+}
+
+static int max77693_set_batttery_overcurrent(struct max77693_charger *chg,
+ unsigned int uamp)
+{
+ unsigned int data;
+
+ if (uamp && (uamp < 2000000 || uamp > 3500000)) {
+ dev_err(chg->dev, "Wrong value for battery overcurrent\n");
+ return -EINVAL;
+ }
+
+ if (uamp)
+ data = ((uamp - 2000000) / 250000) + 1;
+ else
+ data = 0; /* disable */
+
+ data <<= CHG_CNFG_12_B2SOVRC_SHIFT;
+
+ dev_dbg(chg->dev, "Battery overcurrent: %u (0x%x)\n", uamp, data);
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_12,
+ CHG_CNFG_12_B2SOVRC_MASK, data);
+}
+
+static int max77693_set_charge_input_threshold_volt(struct max77693_charger *chg,
+ unsigned int uvolt)
+{
+ unsigned int data;
+
+ switch (uvolt) {
+ case 4300000:
+ data = 0x0;
+ break;
+ case 4700000:
+ case 4800000:
+ case 4900000:
+ data = (uvolt - 4700000) / 100000;
+ default:
+ dev_err(chg->dev, "Wrong value for charge input voltage regulation threshold\n");
+ return -EINVAL;
+ }
+
+ data <<= CHG_CNFG_12_VCHGINREG_SHIFT;
+
+ dev_dbg(chg->dev, "Charge input voltage regulation threshold: %u (0x%x)\n",
+ uvolt, data);
+
+ return regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_12,
+ CHG_CNFG_12_VCHGINREG_MASK, data);
+}
+
+/*
+ * Sets charger registers to proper and safe default values.
+ */
+static int max77693_reg_init(struct max77693_charger *chg)
+{
+ int ret;
+ unsigned int data;
+
+ /* Unlock charger register protection */
+ data = (0x3 << CHG_CNFG_06_CHGPROT_SHIFT);
+ ret = regmap_update_bits(chg->max77693->regmap,
+ MAX77693_CHG_REG_CHG_CNFG_06,
+ CHG_CNFG_06_CHGPROT_MASK, data);
+ if (ret) {
+ dev_err(chg->dev, "Error unlocking registers: %d\n", ret);
+ return ret;
+ }
+
+ ret = max77693_set_fast_charge_timer(chg, DEFAULT_FAST_CHARGE_TIMER);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_top_off_threshold_current(chg,
+ DEFAULT_TOP_OFF_THRESHOLD_CURRENT);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_top_off_timer(chg, DEFAULT_TOP_OFF_TIMER);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_constant_volt(chg, chg->constant_volt);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_min_system_volt(chg, chg->min_system_volt);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_thermal_regulation_temp(chg,
+ chg->thermal_regulation_temp);
+ if (ret)
+ return ret;
+
+ ret = max77693_set_batttery_overcurrent(chg, chg->batttery_overcurrent);
+ if (ret)
+ return ret;
+
+ return max77693_set_charge_input_threshold_volt(chg,
+ chg->charge_input_threshold_volt);
+}
+
+#ifdef CONFIG_OF
+static int max77693_dt_init(struct device *dev, struct max77693_charger *chg)
+{
+ struct device_node *np = dev->of_node;
+
+ if (!np) {
+ dev_err(dev, "no charger OF node\n");
+ return -EINVAL;
+ }
+
+ if (of_property_read_u32(np, "maxim,constant-microvolt",
+ &chg->constant_volt))
+ chg->constant_volt = DEFAULT_CONSTANT_VOLT;
+
+ if (of_property_read_u32(np, "maxim,min-system-microvolt",
+ &chg->min_system_volt))
+ chg->min_system_volt = DEFAULT_MIN_SYSTEM_VOLT;
+
+ if (of_property_read_u32(np, "maxim,thermal-regulation-celsius",
+ &chg->thermal_regulation_temp))
+ chg->thermal_regulation_temp = DEFAULT_THERMAL_REGULATION_TEMP;
+
+ if (of_property_read_u32(np, "maxim,battery-overcurrent-microamp",
+ &chg->batttery_overcurrent))
+ chg->batttery_overcurrent = DEFAULT_BATTERY_OVERCURRENT;
+
+ if (of_property_read_u32(np, "maxim,charge-input-threshold-microvolt",
+ &chg->charge_input_threshold_volt))
+ chg->charge_input_threshold_volt =
+ DEFAULT_CHARGER_INPUT_THRESHOLD_VOLT;
+
+ return 0;
+}
+#else /* CONFIG_OF */
+static int max77693_dt_init(struct device *dev, struct max77693_charger *chg)
+{
+ return 0;
+}
+#endif /* CONFIG_OF */
+
+static int max77693_charger_probe(struct platform_device *pdev)
+{
+ struct max77693_charger *chg;
+ struct power_supply_config psy_cfg = {};
+ struct max77693_dev *max77693 = dev_get_drvdata(pdev->dev.parent);
+ int ret;
+
+ chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, chg);
+ chg->dev = &pdev->dev;
+ chg->max77693 = max77693;
+
+ ret = max77693_dt_init(&pdev->dev, chg);
+ if (ret)
+ return ret;
+
+ ret = max77693_reg_init(chg);
+ if (ret)
+ return ret;
+
+ psy_cfg.drv_data = chg;
+
+ ret = device_create_file(&pdev->dev, &dev_attr_fast_charge_timer);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: create fast charge timer sysfs entry\n");
+ goto err;
+ }
+
+ ret = device_create_file(&pdev->dev,
+ &dev_attr_top_off_threshold_current);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: create top off current sysfs entry\n");
+ goto err;
+ }
+
+ ret = device_create_file(&pdev->dev, &dev_attr_top_off_timer);
+ if (ret) {
+ dev_err(&pdev->dev, "failed: create top off timer sysfs entry\n");
+ goto err;
+ }
+
+ chg->charger = power_supply_register(&pdev->dev,
+ &max77693_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(chg->charger)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ ret = PTR_ERR(chg->charger);
+ goto err;
+ }
+
+ return 0;
+
+err:
+ device_remove_file(&pdev->dev, &dev_attr_top_off_timer);
+ device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current);
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
+
+ return ret;
+}
+
+static int max77693_charger_remove(struct platform_device *pdev)
+{
+ struct max77693_charger *chg = platform_get_drvdata(pdev);
+
+ device_remove_file(&pdev->dev, &dev_attr_top_off_timer);
+ device_remove_file(&pdev->dev, &dev_attr_top_off_threshold_current);
+ device_remove_file(&pdev->dev, &dev_attr_fast_charge_timer);
+
+ power_supply_unregister(chg->charger);
+
+ return 0;
+}
+
+static const struct platform_device_id max77693_charger_id[] = {
+ { "max77693-charger", 0, },
+ { }
+};
+MODULE_DEVICE_TABLE(platform, max77693_charger_id);
+
+static struct platform_driver max77693_charger_driver = {
+ .driver = {
+ .name = "max77693-charger",
+ },
+ .probe = max77693_charger_probe,
+ .remove = max77693_charger_remove,
+ .id_table = max77693_charger_id,
+};
+module_platform_driver(max77693_charger_driver);
+
+MODULE_AUTHOR("Krzysztof Kozlowski <krzk@kernel.org>");
+MODULE_DESCRIPTION("Maxim 77693 charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max8903_charger.c b/drivers/power/supply/max8903_charger.c
new file mode 100644
index 0000000..fdc73d6
--- /dev/null
+++ b/drivers/power/supply/max8903_charger.c
@@ -0,0 +1,459 @@
+/*
+ * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.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
+ *
+ */
+
+#include <linux/gpio.h>
+#include <linux/interrupt.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_gpio.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/platform_device.h>
+#include <linux/power/max8903_charger.h>
+
+struct max8903_data {
+ struct max8903_pdata *pdata;
+ struct device *dev;
+ struct power_supply *psy;
+ struct power_supply_desc psy_desc;
+ bool fault;
+ bool usb_in;
+ bool ta_in;
+};
+
+static enum power_supply_property max8903_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* Charger status output */
+ POWER_SUPPLY_PROP_ONLINE, /* External power source */
+ POWER_SUPPLY_PROP_HEALTH, /* Fault or OK */
+};
+
+static int max8903_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8903_data *data = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ if (gpio_is_valid(data->pdata->chg)) {
+ if (gpio_get_value(data->pdata->chg) == 0)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (data->usb_in || data->ta_in)
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ if (data->usb_in || data->ta_in)
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ if (data->fault)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static irqreturn_t max8903_dcin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool ta_in;
+ enum power_supply_type old_type;
+
+ ta_in = gpio_get_value(pdata->dok) ? false : true;
+
+ if (ta_in == data->ta_in)
+ return IRQ_HANDLED;
+
+ data->ta_in = ta_in;
+
+ /* Set Current-Limit-Mode 1:DC 0:USB */
+ if (gpio_is_valid(pdata->dcm))
+ gpio_set_value(pdata->dcm, ta_in ? 1 : 0);
+
+ /* Charger Enable / Disable (cen is negated) */
+ if (gpio_is_valid(pdata->cen))
+ gpio_set_value(pdata->cen, ta_in ? 0 :
+ (data->usb_in ? 0 : 1));
+
+ dev_dbg(data->dev, "TA(DC-IN) Charger %s.\n", ta_in ?
+ "Connected" : "Disconnected");
+
+ old_type = data->psy_desc.type;
+
+ if (data->ta_in)
+ data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
+ else if (data->usb_in)
+ data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ else
+ data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ if (old_type != data->psy_desc.type)
+ power_supply_changed(data->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_usbin(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool usb_in;
+ enum power_supply_type old_type;
+
+ usb_in = gpio_get_value(pdata->uok) ? false : true;
+
+ if (usb_in == data->usb_in)
+ return IRQ_HANDLED;
+
+ data->usb_in = usb_in;
+
+ /* Do not touch Current-Limit-Mode */
+
+ /* Charger Enable / Disable (cen is negated) */
+ if (gpio_is_valid(pdata->cen))
+ gpio_set_value(pdata->cen, usb_in ? 0 :
+ (data->ta_in ? 0 : 1));
+
+ dev_dbg(data->dev, "USB Charger %s.\n", usb_in ?
+ "Connected" : "Disconnected");
+
+ old_type = data->psy_desc.type;
+
+ if (data->ta_in)
+ data->psy_desc.type = POWER_SUPPLY_TYPE_MAINS;
+ else if (data->usb_in)
+ data->psy_desc.type = POWER_SUPPLY_TYPE_USB;
+ else
+ data->psy_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+
+ if (old_type != data->psy_desc.type)
+ power_supply_changed(data->psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t max8903_fault(int irq, void *_data)
+{
+ struct max8903_data *data = _data;
+ struct max8903_pdata *pdata = data->pdata;
+ bool fault;
+
+ fault = gpio_get_value(pdata->flt) ? false : true;
+
+ if (fault == data->fault)
+ return IRQ_HANDLED;
+
+ data->fault = fault;
+
+ if (fault)
+ dev_err(data->dev, "Charger suffers a fault and stops.\n");
+ else
+ dev_err(data->dev, "Charger recovered from a fault.\n");
+
+ return IRQ_HANDLED;
+}
+
+static struct max8903_pdata *max8903_parse_dt_data(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct max8903_pdata *pdata = NULL;
+
+ if (!np)
+ return NULL;
+
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ pdata->dc_valid = false;
+ pdata->usb_valid = false;
+
+ pdata->cen = of_get_named_gpio(np, "cen-gpios", 0);
+ if (!gpio_is_valid(pdata->cen))
+ pdata->cen = -EINVAL;
+
+ pdata->chg = of_get_named_gpio(np, "chg-gpios", 0);
+ if (!gpio_is_valid(pdata->chg))
+ pdata->chg = -EINVAL;
+
+ pdata->flt = of_get_named_gpio(np, "flt-gpios", 0);
+ if (!gpio_is_valid(pdata->flt))
+ pdata->flt = -EINVAL;
+
+ pdata->usus = of_get_named_gpio(np, "usus-gpios", 0);
+ if (!gpio_is_valid(pdata->usus))
+ pdata->usus = -EINVAL;
+
+ pdata->dcm = of_get_named_gpio(np, "dcm-gpios", 0);
+ if (!gpio_is_valid(pdata->dcm))
+ pdata->dcm = -EINVAL;
+
+ pdata->dok = of_get_named_gpio(np, "dok-gpios", 0);
+ if (!gpio_is_valid(pdata->dok))
+ pdata->dok = -EINVAL;
+ else
+ pdata->dc_valid = true;
+
+ pdata->uok = of_get_named_gpio(np, "uok-gpios", 0);
+ if (!gpio_is_valid(pdata->uok))
+ pdata->uok = -EINVAL;
+ else
+ pdata->usb_valid = true;
+
+ return pdata;
+}
+
+static int max8903_setup_gpios(struct platform_device *pdev)
+{
+ struct max8903_data *data = platform_get_drvdata(pdev);
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ int ret = 0;
+ int gpio;
+ int ta_in = 0;
+ int usb_in = 0;
+
+ if (pdata->dc_valid) {
+ if (gpio_is_valid(pdata->dok)) {
+ ret = devm_gpio_request(dev, pdata->dok,
+ data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for dok: %d err %d\n",
+ pdata->dok, ret);
+ return ret;
+ }
+
+ gpio = pdata->dok; /* PULL_UPed Interrupt */
+ ta_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When DC is wired, DOK should be wired as well.\n");
+ return -EINVAL;
+ }
+ }
+
+ if (gpio_is_valid(pdata->dcm)) {
+ ret = devm_gpio_request(dev, pdata->dcm, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for dcm: %d err %d\n",
+ pdata->dcm, ret);
+ return ret;
+ }
+
+ gpio = pdata->dcm; /* Output */
+ gpio_set_value(gpio, ta_in);
+ }
+
+ if (pdata->usb_valid) {
+ if (gpio_is_valid(pdata->uok)) {
+ ret = devm_gpio_request(dev, pdata->uok,
+ data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for uok: %d err %d\n",
+ pdata->uok, ret);
+ return ret;
+ }
+
+ gpio = pdata->uok;
+ usb_in = gpio_get_value(gpio) ? 0 : 1;
+ } else {
+ dev_err(dev, "When USB is wired, UOK should be wired."
+ "as well.\n");
+ return -EINVAL;
+ }
+ }
+
+ if (gpio_is_valid(pdata->cen)) {
+ ret = devm_gpio_request(dev, pdata->cen, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for cen: %d err %d\n",
+ pdata->cen, ret);
+ return ret;
+ }
+
+ gpio_set_value(pdata->cen, (ta_in || usb_in) ? 0 : 1);
+ }
+
+ if (gpio_is_valid(pdata->chg)) {
+ ret = devm_gpio_request(dev, pdata->chg, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for chg: %d err %d\n",
+ pdata->chg, ret);
+ return ret;
+ }
+ }
+
+ if (gpio_is_valid(pdata->flt)) {
+ ret = devm_gpio_request(dev, pdata->flt, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for flt: %d err %d\n",
+ pdata->flt, ret);
+ return ret;
+ }
+ }
+
+ if (gpio_is_valid(pdata->usus)) {
+ ret = devm_gpio_request(dev, pdata->usus, data->psy_desc.name);
+ if (ret) {
+ dev_err(dev,
+ "Failed GPIO request for usus: %d err %d\n",
+ pdata->usus, ret);
+ return ret;
+ }
+ }
+
+ data->fault = false;
+ data->ta_in = ta_in;
+ data->usb_in = usb_in;
+
+ return 0;
+}
+
+static int max8903_probe(struct platform_device *pdev)
+{
+ struct max8903_data *data;
+ struct device *dev = &pdev->dev;
+ struct max8903_pdata *pdata = pdev->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ int ret = 0;
+
+ data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ if (IS_ENABLED(CONFIG_OF) && !pdata && dev->of_node)
+ pdata = max8903_parse_dt_data(dev);
+
+ if (!pdata) {
+ dev_err(dev, "No platform data.\n");
+ return -EINVAL;
+ }
+
+ pdev->dev.platform_data = pdata;
+ data->pdata = pdata;
+ data->dev = dev;
+ platform_set_drvdata(pdev, data);
+
+ if (pdata->dc_valid == false && pdata->usb_valid == false) {
+ dev_err(dev, "No valid power sources.\n");
+ return -EINVAL;
+ }
+
+ ret = max8903_setup_gpios(pdev);
+ if (ret)
+ return ret;
+
+ data->psy_desc.name = "max8903_charger";
+ data->psy_desc.type = (data->ta_in) ? POWER_SUPPLY_TYPE_MAINS :
+ ((data->usb_in) ? POWER_SUPPLY_TYPE_USB :
+ POWER_SUPPLY_TYPE_BATTERY);
+ data->psy_desc.get_property = max8903_get_property;
+ data->psy_desc.properties = max8903_charger_props;
+ data->psy_desc.num_properties = ARRAY_SIZE(max8903_charger_props);
+
+ psy_cfg.of_node = dev->of_node;
+ psy_cfg.drv_data = data;
+
+ data->psy = devm_power_supply_register(dev, &data->psy_desc, &psy_cfg);
+ if (IS_ERR(data->psy)) {
+ dev_err(dev, "failed: power supply register.\n");
+ return PTR_ERR(data->psy);
+ }
+
+ if (pdata->dc_valid) {
+ ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->dok),
+ NULL, max8903_dcin,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "MAX8903 DC IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for DC (%d)\n",
+ gpio_to_irq(pdata->dok), ret);
+ return ret;
+ }
+ }
+
+ if (pdata->usb_valid) {
+ ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->uok),
+ NULL, max8903_usbin,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "MAX8903 USB IN", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for USB (%d)\n",
+ gpio_to_irq(pdata->uok), ret);
+ return ret;
+ }
+ }
+
+ if (gpio_is_valid(pdata->flt)) {
+ ret = devm_request_threaded_irq(dev, gpio_to_irq(pdata->flt),
+ NULL, max8903_fault,
+ IRQF_TRIGGER_FALLING |
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "MAX8903 Fault", data);
+ if (ret) {
+ dev_err(dev, "Cannot request irq %d for Fault (%d)\n",
+ gpio_to_irq(pdata->flt), ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static const struct of_device_id max8903_match_ids[] = {
+ { .compatible = "maxim,max8903", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, max8903_match_ids);
+
+static struct platform_driver max8903_driver = {
+ .probe = max8903_probe,
+ .driver = {
+ .name = "max8903-charger",
+ .of_match_table = max8903_match_ids
+ },
+};
+
+module_platform_driver(max8903_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("MAX8903 Charger Driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_ALIAS("platform:max8903-charger");
diff --git a/drivers/power/supply/max8925_power.c b/drivers/power/supply/max8925_power.c
new file mode 100644
index 0000000..3b94620
--- /dev/null
+++ b/drivers/power/supply/max8925_power.c
@@ -0,0 +1,596 @@
+/*
+ * Battery driver for Maxim MAX8925
+ *
+ * Copyright (c) 2009-2010 Marvell International Ltd.
+ * Haojian Zhuang <haojian.zhuang@marvell.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/err.h>
+#include <linux/slab.h>
+#include <linux/of.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8925.h>
+
+/* registers in GPM */
+#define MAX8925_OUT5VEN 0x54
+#define MAX8925_OUT3VEN 0x58
+#define MAX8925_CHG_CNTL1 0x7c
+
+/* bits definition */
+#define MAX8925_CHG_STAT_VSYSLOW (1 << 0)
+#define MAX8925_CHG_STAT_MODE_MASK (3 << 2)
+#define MAX8925_CHG_STAT_EN_MASK (1 << 4)
+#define MAX8925_CHG_MBDET (1 << 1)
+#define MAX8925_CHG_AC_RANGE_MASK (3 << 6)
+
+/* registers in ADC */
+#define MAX8925_ADC_RES_CNFG1 0x06
+#define MAX8925_ADC_AVG_CNFG1 0x07
+#define MAX8925_ADC_ACQ_CNFG1 0x08
+#define MAX8925_ADC_ACQ_CNFG2 0x09
+/* 2 bytes registers in below. MSB is 1st, LSB is 2nd. */
+#define MAX8925_ADC_AUX2 0x62
+#define MAX8925_ADC_VCHG 0x64
+#define MAX8925_ADC_VBBATT 0x66
+#define MAX8925_ADC_VMBATT 0x68
+#define MAX8925_ADC_ISNS 0x6a
+#define MAX8925_ADC_THM 0x6c
+#define MAX8925_ADC_TDIE 0x6e
+#define MAX8925_CMD_AUX2 0xc8
+#define MAX8925_CMD_VCHG 0xd0
+#define MAX8925_CMD_VBBATT 0xd8
+#define MAX8925_CMD_VMBATT 0xe0
+#define MAX8925_CMD_ISNS 0xe8
+#define MAX8925_CMD_THM 0xf0
+#define MAX8925_CMD_TDIE 0xf8
+
+enum {
+ MEASURE_AUX2,
+ MEASURE_VCHG,
+ MEASURE_VBBATT,
+ MEASURE_VMBATT,
+ MEASURE_ISNS,
+ MEASURE_THM,
+ MEASURE_TDIE,
+ MEASURE_MAX,
+};
+
+struct max8925_power_info {
+ struct max8925_chip *chip;
+ struct i2c_client *gpm;
+ struct i2c_client *adc;
+
+ struct power_supply *ac;
+ struct power_supply *usb;
+ struct power_supply *battery;
+ int irq_base;
+ unsigned ac_online:1;
+ unsigned usb_online:1;
+ unsigned bat_online:1;
+ unsigned chg_mode:2;
+ unsigned batt_detect:1; /* detecing MB by ID pin */
+ unsigned topoff_threshold:2;
+ unsigned fast_charge:3;
+ unsigned no_temp_support:1;
+ unsigned no_insert_detect:1;
+
+ int (*set_charger) (int);
+};
+
+static int __set_charger(struct max8925_power_info *info, int enable)
+{
+ struct max8925_chip *chip = info->chip;
+ if (enable) {
+ /* enable charger in platform */
+ if (info->set_charger)
+ info->set_charger(1);
+ /* enable charger */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 0);
+ } else {
+ /* disable charge */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
+ if (info->set_charger)
+ info->set_charger(0);
+ }
+ dev_dbg(chip->dev, "%s\n", (enable) ? "Enable charger"
+ : "Disable charger");
+ return 0;
+}
+
+static irqreturn_t max8925_charger_handler(int irq, void *data)
+{
+ struct max8925_power_info *info = (struct max8925_power_info *)data;
+ struct max8925_chip *chip = info->chip;
+
+ switch (irq - chip->irq_base) {
+ case MAX8925_IRQ_VCHG_DC_R:
+ info->ac_online = 1;
+ __set_charger(info, 1);
+ dev_dbg(chip->dev, "Adapter inserted\n");
+ break;
+ case MAX8925_IRQ_VCHG_DC_F:
+ info->ac_online = 0;
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Adapter removed\n");
+ break;
+ case MAX8925_IRQ_VCHG_THM_OK_F:
+ /* Battery is not ready yet */
+ dev_dbg(chip->dev, "Battery temperature is out of range\n");
+ case MAX8925_IRQ_VCHG_DC_OVP:
+ dev_dbg(chip->dev, "Error detection\n");
+ __set_charger(info, 0);
+ break;
+ case MAX8925_IRQ_VCHG_THM_OK_R:
+ /* Battery is ready now */
+ dev_dbg(chip->dev, "Battery temperature is in range\n");
+ break;
+ case MAX8925_IRQ_VCHG_SYSLOW_R:
+ /* VSYS is low */
+ dev_info(chip->dev, "Sys power is too low\n");
+ break;
+ case MAX8925_IRQ_VCHG_SYSLOW_F:
+ dev_dbg(chip->dev, "Sys power is above low threshold\n");
+ break;
+ case MAX8925_IRQ_VCHG_DONE:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Charging is done\n");
+ break;
+ case MAX8925_IRQ_VCHG_TOPOFF:
+ dev_dbg(chip->dev, "Charging in top-off mode\n");
+ break;
+ case MAX8925_IRQ_VCHG_TMR_FAULT:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Safe timer is expired\n");
+ break;
+ case MAX8925_IRQ_VCHG_RST:
+ __set_charger(info, 0);
+ dev_dbg(chip->dev, "Charger is reset\n");
+ break;
+ }
+ return IRQ_HANDLED;
+}
+
+static int start_measure(struct max8925_power_info *info, int type)
+{
+ unsigned char buf[2] = {0, 0};
+ int meas_cmd;
+ int meas_reg = 0, ret;
+
+ switch (type) {
+ case MEASURE_VCHG:
+ meas_cmd = MAX8925_CMD_VCHG;
+ meas_reg = MAX8925_ADC_VCHG;
+ break;
+ case MEASURE_VBBATT:
+ meas_cmd = MAX8925_CMD_VBBATT;
+ meas_reg = MAX8925_ADC_VBBATT;
+ break;
+ case MEASURE_VMBATT:
+ meas_cmd = MAX8925_CMD_VMBATT;
+ meas_reg = MAX8925_ADC_VMBATT;
+ break;
+ case MEASURE_ISNS:
+ meas_cmd = MAX8925_CMD_ISNS;
+ meas_reg = MAX8925_ADC_ISNS;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ max8925_reg_write(info->adc, meas_cmd, 0);
+ max8925_bulk_read(info->adc, meas_reg, 2, buf);
+ ret = ((buf[0]<<8) | buf[1]) >> 4;
+
+ return ret;
+}
+
+static int max8925_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->ac_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->ac_online) {
+ ret = start_measure(info, MEASURE_VCHG);
+ if (ret >= 0) {
+ val->intval = ret * 2000; /* unit is uV */
+ goto out;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+out:
+ return ret;
+}
+
+static enum power_supply_property max8925_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static int max8925_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->usb_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->usb_online) {
+ ret = start_measure(info, MEASURE_VCHG);
+ if (ret >= 0) {
+ val->intval = ret * 2000; /* unit is uV */
+ goto out;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+out:
+ return ret;
+}
+
+static enum power_supply_property max8925_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static int max8925_bat_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8925_power_info *info = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = info->bat_online;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->bat_online) {
+ ret = start_measure(info, MEASURE_VMBATT);
+ if (ret >= 0) {
+ val->intval = ret * 2000; /* unit is uV */
+ ret = 0;
+ break;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (info->bat_online) {
+ ret = start_measure(info, MEASURE_ISNS);
+ if (ret >= 0) {
+ /* assume r_sns is 0.02 */
+ ret = ((ret * 6250) - 3125) /* uA */;
+ val->intval = 0;
+ if (ret > 0)
+ val->intval = ret; /* unit is mA */
+ ret = 0;
+ break;
+ }
+ }
+ ret = -ENODATA;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (!info->bat_online) {
+ ret = -ENODATA;
+ break;
+ }
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ ret = (ret & MAX8925_CHG_STAT_MODE_MASK) >> 2;
+ switch (ret) {
+ case 1:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case 0:
+ case 2:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case 3:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+ ret = 0;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (!info->bat_online) {
+ ret = -ENODATA;
+ break;
+ }
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ if (info->usb_online || info->ac_online) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ if (ret & MAX8925_CHG_STAT_EN_MASK)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ } else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ ret = 0;
+ break;
+ default:
+ ret = -ENODEV;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property max8925_battery_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
+static const struct power_supply_desc ac_desc = {
+ .name = "max8925-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = max8925_ac_props,
+ .num_properties = ARRAY_SIZE(max8925_ac_props),
+ .get_property = max8925_ac_get_prop,
+};
+
+static const struct power_supply_desc usb_desc = {
+ .name = "max8925-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = max8925_usb_props,
+ .num_properties = ARRAY_SIZE(max8925_usb_props),
+ .get_property = max8925_usb_get_prop,
+};
+
+static const struct power_supply_desc battery_desc = {
+ .name = "max8925-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = max8925_battery_props,
+ .num_properties = ARRAY_SIZE(max8925_battery_props),
+ .get_property = max8925_bat_get_prop,
+};
+
+#define REQUEST_IRQ(_irq, _name) \
+do { \
+ ret = request_threaded_irq(chip->irq_base + _irq, NULL, \
+ max8925_charger_handler, \
+ IRQF_ONESHOT, _name, info); \
+ if (ret) \
+ dev_err(chip->dev, "Failed to request IRQ #%d: %d\n", \
+ _irq, ret); \
+} while (0)
+
+static int max8925_init_charger(struct max8925_chip *chip,
+ struct max8925_power_info *info)
+{
+ int ret;
+
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_OVP, "ac-ovp");
+ if (!info->no_insert_detect) {
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_F, "ac-remove");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DC_R, "ac-insert");
+ }
+ if (!info->no_temp_support) {
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_R, "batt-temp-in-range");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_THM_OK_F, "batt-temp-out-range");
+ }
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_F, "vsys-high");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_SYSLOW_R, "vsys-low");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_RST, "charger-reset");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_DONE, "charger-done");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_TOPOFF, "charger-topoff");
+ REQUEST_IRQ(MAX8925_IRQ_VCHG_TMR_FAULT, "charger-timer-expire");
+
+ info->usb_online = 0;
+ info->bat_online = 0;
+
+ /* check for power - can miss interrupt at boot time */
+ if (start_measure(info, MEASURE_VCHG) * 2000 > 500000)
+ info->ac_online = 1;
+ else
+ info->ac_online = 0;
+
+ ret = max8925_reg_read(info->gpm, MAX8925_CHG_STATUS);
+ if (ret >= 0) {
+ /*
+ * If battery detection is enabled, ID pin of battery is
+ * connected to MBDET pin of MAX8925. It could be used to
+ * detect battery presence.
+ * Otherwise, we have to assume that battery is always on.
+ */
+ if (info->batt_detect)
+ info->bat_online = (ret & MAX8925_CHG_MBDET) ? 0 : 1;
+ else
+ info->bat_online = 1;
+ if (ret & MAX8925_CHG_AC_RANGE_MASK)
+ info->ac_online = 1;
+ else
+ info->ac_online = 0;
+ }
+ /* disable charge */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 1 << 7, 1 << 7);
+ /* set charging current in charge topoff mode */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 3 << 5,
+ info->topoff_threshold << 5);
+ /* set charing current in fast charge mode */
+ max8925_set_bits(info->gpm, MAX8925_CHG_CNTL1, 7, info->fast_charge);
+
+ return 0;
+}
+
+static int max8925_deinit_charger(struct max8925_power_info *info)
+{
+ struct max8925_chip *chip = info->chip;
+ int irq;
+
+ irq = chip->irq_base + MAX8925_IRQ_VCHG_DC_OVP;
+ for (; irq <= chip->irq_base + MAX8925_IRQ_VCHG_TMR_FAULT; irq++)
+ free_irq(irq, info);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static struct max8925_power_pdata *
+max8925_power_dt_init(struct platform_device *pdev)
+{
+ struct device_node *nproot = pdev->dev.parent->of_node;
+ struct device_node *np;
+ int batt_detect;
+ int topoff_threshold;
+ int fast_charge;
+ int no_temp_support;
+ int no_insert_detect;
+ struct max8925_power_pdata *pdata;
+
+ if (!nproot)
+ return pdev->dev.platform_data;
+
+ np = of_get_child_by_name(nproot, "charger");
+ if (!np) {
+ dev_err(&pdev->dev, "failed to find charger node\n");
+ return NULL;
+ }
+
+ pdata = devm_kzalloc(&pdev->dev,
+ sizeof(struct max8925_power_pdata),
+ GFP_KERNEL);
+ if (!pdata)
+ goto ret;
+
+ of_property_read_u32(np, "topoff-threshold", &topoff_threshold);
+ of_property_read_u32(np, "batt-detect", &batt_detect);
+ 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);
+
+ pdata->batt_detect = batt_detect;
+ pdata->fast_charge = fast_charge;
+ pdata->topoff_threshold = topoff_threshold;
+ pdata->no_insert_detect = no_insert_detect;
+ pdata->no_temp_support = no_temp_support;
+
+ret:
+ of_node_put(np);
+ return pdata;
+}
+#else
+static struct max8925_power_pdata *
+max8925_power_dt_init(struct platform_device *pdev)
+{
+ return pdev->dev.platform_data;
+}
+#endif
+
+static int max8925_power_probe(struct platform_device *pdev)
+{
+ struct max8925_chip *chip = dev_get_drvdata(pdev->dev.parent);
+ struct power_supply_config psy_cfg = {}; /* Only for ac and usb */
+ struct max8925_power_pdata *pdata = NULL;
+ struct max8925_power_info *info;
+ int ret;
+
+ pdata = max8925_power_dt_init(pdev);
+ if (!pdata) {
+ dev_err(&pdev->dev, "platform data isn't assigned to "
+ "power supply\n");
+ return -EINVAL;
+ }
+
+ info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info),
+ GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+ info->chip = chip;
+ info->gpm = chip->i2c;
+ info->adc = chip->adc;
+ platform_set_drvdata(pdev, info);
+
+ psy_cfg.supplied_to = pdata->supplied_to;
+ psy_cfg.num_supplicants = pdata->num_supplicants;
+
+ info->ac = power_supply_register(&pdev->dev, &ac_desc, &psy_cfg);
+ if (IS_ERR(info->ac)) {
+ ret = PTR_ERR(info->ac);
+ goto out;
+ }
+ info->ac->dev.parent = &pdev->dev;
+
+ info->usb = power_supply_register(&pdev->dev, &usb_desc, &psy_cfg);
+ if (IS_ERR(info->usb)) {
+ ret = PTR_ERR(info->usb);
+ goto out_unregister_ac;
+ }
+ info->usb->dev.parent = &pdev->dev;
+
+ info->battery = power_supply_register(&pdev->dev, &battery_desc, NULL);
+ if (IS_ERR(info->battery)) {
+ ret = PTR_ERR(info->battery);
+ goto out_unregister_usb;
+ }
+ info->battery->dev.parent = &pdev->dev;
+
+ info->batt_detect = pdata->batt_detect;
+ info->topoff_threshold = pdata->topoff_threshold;
+ info->fast_charge = pdata->fast_charge;
+ info->set_charger = pdata->set_charger;
+ info->no_temp_support = pdata->no_temp_support;
+ info->no_insert_detect = pdata->no_insert_detect;
+
+ max8925_init_charger(chip, info);
+ return 0;
+out_unregister_usb:
+ power_supply_unregister(info->usb);
+out_unregister_ac:
+ power_supply_unregister(info->ac);
+out:
+ return ret;
+}
+
+static int max8925_power_remove(struct platform_device *pdev)
+{
+ struct max8925_power_info *info = platform_get_drvdata(pdev);
+
+ if (info) {
+ power_supply_unregister(info->ac);
+ power_supply_unregister(info->usb);
+ power_supply_unregister(info->battery);
+ max8925_deinit_charger(info);
+ }
+ return 0;
+}
+
+static struct platform_driver max8925_power_driver = {
+ .probe = max8925_power_probe,
+ .remove = max8925_power_remove,
+ .driver = {
+ .name = "max8925-power",
+ },
+};
+
+module_platform_driver(max8925_power_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Power supply driver for MAX8925");
+MODULE_ALIAS("platform:max8925-power");
diff --git a/drivers/power/supply/max8997_charger.c b/drivers/power/supply/max8997_charger.c
new file mode 100644
index 0000000..0b2eab5
--- /dev/null
+++ b/drivers/power/supply/max8997_charger.c
@@ -0,0 +1,211 @@
+/*
+ * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966
+ *
+ * Copyright (C) 2011 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.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
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8997.h>
+#include <linux/mfd/max8997-private.h>
+
+struct charger_data {
+ struct device *dev;
+ struct max8997_dev *iodev;
+ struct power_supply *battery;
+};
+
+static enum power_supply_property max8997_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS, /* "FULL" or "NOT FULL" only. */
+ POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */
+ POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */
+};
+
+/* Note that the charger control is done by a current regulator "CHARGER" */
+static int max8997_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct charger_data *charger = power_supply_get_drvdata(psy);
+ struct i2c_client *i2c = charger->iodev->i2c;
+ int ret;
+ u8 reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ if ((reg & (1 << 0)) == 0x1)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ if ((reg & (1 << 2)) == 0x0)
+ val->intval = 1;
+
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = 0;
+ ret = max8997_read_reg(i2c, MAX8997_REG_STATUS4, &reg);
+ if (ret)
+ return ret;
+ /* DCINOK */
+ if (reg & (1 << 1))
+ val->intval = 1;
+
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc max8997_battery_desc = {
+ .name = "max8997_pmic",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max8997_battery_get_property,
+ .properties = max8997_battery_props,
+ .num_properties = ARRAY_SIZE(max8997_battery_props),
+};
+
+static int max8997_battery_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ struct charger_data *charger;
+ struct max8997_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8997_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct power_supply_config psy_cfg = {};
+
+ if (!pdata)
+ return -EINVAL;
+
+ if (pdata->eoc_mA) {
+ int val = (pdata->eoc_mA - 50) / 10;
+ if (val < 0)
+ val = 0;
+ if (val > 0xf)
+ val = 0xf;
+
+ ret = max8997_update_reg(iodev->i2c,
+ MAX8997_REG_MBCCTRL5, val, 0xf);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+ }
+
+ switch (pdata->timeout) {
+ case 5:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x2 << 4, 0x7 << 4);
+ break;
+ case 6:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x3 << 4, 0x7 << 4);
+ break;
+ case 7:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x4 << 4, 0x7 << 4);
+ break;
+ case 0:
+ ret = max8997_update_reg(iodev->i2c, MAX8997_REG_MBCCTRL1,
+ 0x7 << 4, 0x7 << 4);
+ break;
+ default:
+ dev_err(&pdev->dev, "incorrect timeout value (%d)\n",
+ pdata->timeout);
+ return -EINVAL;
+ }
+ if (ret < 0) {
+ dev_err(&pdev->dev, "Cannot use i2c bus.\n");
+ return ret;
+ }
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data),
+ GFP_KERNEL);
+ if (charger == NULL) {
+ dev_err(&pdev->dev, "Cannot allocate memory.\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, charger);
+
+
+ charger->dev = &pdev->dev;
+ charger->iodev = iodev;
+
+ psy_cfg.drv_data = charger;
+
+ charger->battery = power_supply_register(&pdev->dev,
+ &max8997_battery_desc,
+ &psy_cfg);
+ if (IS_ERR(charger->battery)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return PTR_ERR(charger->battery);
+ }
+
+ return 0;
+}
+
+static int max8997_battery_remove(struct platform_device *pdev)
+{
+ struct charger_data *charger = platform_get_drvdata(pdev);
+
+ power_supply_unregister(charger->battery);
+ return 0;
+}
+
+static const struct platform_device_id max8997_battery_id[] = {
+ { "max8997-battery", 0 },
+ { }
+};
+
+static struct platform_driver max8997_battery_driver = {
+ .driver = {
+ .name = "max8997-battery",
+ },
+ .probe = max8997_battery_probe,
+ .remove = max8997_battery_remove,
+ .id_table = max8997_battery_id,
+};
+
+static int __init max8997_battery_init(void)
+{
+ return platform_driver_register(&max8997_battery_driver);
+}
+subsys_initcall(max8997_battery_init);
+
+static void __exit max8997_battery_cleanup(void)
+{
+ platform_driver_unregister(&max8997_battery_driver);
+}
+module_exit(max8997_battery_cleanup);
+
+MODULE_DESCRIPTION("MAXIM 8997/8966 battery control driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/max8998_charger.c b/drivers/power/supply/max8998_charger.c
new file mode 100644
index 0000000..b64cf0f
--- /dev/null
+++ b/drivers/power/supply/max8998_charger.c
@@ -0,0 +1,202 @@
+/*
+ * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974
+ *
+ * Copyright (C) 2009-2010 Samsung Electronics
+ * MyungJoo Ham <myungjoo.ham@samsung.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
+ */
+
+#include <linux/err.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/max8998.h>
+#include <linux/mfd/max8998-private.h>
+
+struct max8998_battery_data {
+ struct device *dev;
+ struct max8998_dev *iodev;
+ struct power_supply *battery;
+};
+
+static enum power_supply_property max8998_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT, /* the presence of battery */
+ POWER_SUPPLY_PROP_ONLINE, /* charger is active or not */
+};
+
+/* Note that the charger control is done by a current regulator "CHARGER" */
+static int max8998_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct max8998_battery_data *max8998 = power_supply_get_drvdata(psy);
+ struct i2c_client *i2c = max8998->iodev->i2c;
+ int ret;
+ u8 reg;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, &reg);
+ if (ret)
+ return ret;
+ if (reg & (1 << 4))
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = max8998_read_reg(i2c, MAX8998_REG_STATUS2, &reg);
+ if (ret)
+ return ret;
+ if (reg & (1 << 3))
+ val->intval = 0;
+ else
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static const struct power_supply_desc max8998_battery_desc = {
+ .name = "max8998_pmic",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = max8998_battery_get_property,
+ .properties = max8998_battery_props,
+ .num_properties = ARRAY_SIZE(max8998_battery_props),
+};
+
+static int max8998_battery_probe(struct platform_device *pdev)
+{
+ struct max8998_dev *iodev = dev_get_drvdata(pdev->dev.parent);
+ struct max8998_platform_data *pdata = dev_get_platdata(iodev->dev);
+ struct power_supply_config psy_cfg = {};
+ struct max8998_battery_data *max8998;
+ struct i2c_client *i2c;
+ int ret = 0;
+
+ if (!pdata) {
+ dev_err(pdev->dev.parent, "No platform init data supplied\n");
+ return -ENODEV;
+ }
+
+ max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data),
+ GFP_KERNEL);
+ if (!max8998)
+ return -ENOMEM;
+
+ max8998->dev = &pdev->dev;
+ max8998->iodev = iodev;
+ platform_set_drvdata(pdev, max8998);
+ i2c = max8998->iodev->i2c;
+
+ /* Setup "End of Charge" */
+ /* If EOC value equals 0,
+ * remain value set from bootloader or default value */
+ if (pdata->eoc >= 10 && pdata->eoc <= 45) {
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1,
+ (pdata->eoc / 5 - 2) << 5, 0x7 << 5);
+ } else if (pdata->eoc == 0) {
+ dev_dbg(max8998->dev,
+ "EOC value not set: leave it unchanged.\n");
+ } else {
+ dev_err(max8998->dev, "Invalid EOC value\n");
+ return -EINVAL;
+ }
+
+ /* Setup Charge Restart Level */
+ switch (pdata->restart) {
+ case 100:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x1 << 3, 0x3 << 3);
+ break;
+ case 150:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x0 << 3, 0x3 << 3);
+ break;
+ case 200:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x2 << 3, 0x3 << 3);
+ break;
+ case -1:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR1, 0x3 << 3, 0x3 << 3);
+ break;
+ case 0:
+ dev_dbg(max8998->dev,
+ "Restart Level not set: leave it unchanged.\n");
+ break;
+ default:
+ dev_err(max8998->dev, "Invalid Restart Level\n");
+ return -EINVAL;
+ }
+
+ /* Setup Charge Full Timeout */
+ switch (pdata->timeout) {
+ case 5:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x0 << 4, 0x3 << 4);
+ break;
+ case 6:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x1 << 4, 0x3 << 4);
+ break;
+ case 7:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x2 << 4, 0x3 << 4);
+ break;
+ case -1:
+ max8998_update_reg(i2c, MAX8998_REG_CHGR2, 0x3 << 4, 0x3 << 4);
+ break;
+ case 0:
+ dev_dbg(max8998->dev,
+ "Full Timeout not set: leave it unchanged.\n");
+ break;
+ default:
+ dev_err(max8998->dev, "Invalid Full Timeout value\n");
+ return -EINVAL;
+ }
+
+ psy_cfg.drv_data = max8998;
+
+ max8998->battery = devm_power_supply_register(max8998->dev,
+ &max8998_battery_desc,
+ &psy_cfg);
+ if (IS_ERR(max8998->battery)) {
+ ret = PTR_ERR(max8998->battery);
+ dev_err(max8998->dev, "failed: power supply register: %d\n",
+ ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static const struct platform_device_id max8998_battery_id[] = {
+ { "max8998-battery", TYPE_MAX8998 },
+ { }
+};
+
+static struct platform_driver max8998_battery_driver = {
+ .driver = {
+ .name = "max8998-battery",
+ },
+ .probe = max8998_battery_probe,
+ .id_table = max8998_battery_id,
+};
+
+module_platform_driver(max8998_battery_driver);
+
+MODULE_DESCRIPTION("MAXIM 8998 battery control driver");
+MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:max8998-battery");
diff --git a/drivers/power/supply/olpc_battery.c b/drivers/power/supply/olpc_battery.c
new file mode 100644
index 0000000..9e29b13
--- /dev/null
+++ b/drivers/power/supply/olpc_battery.c
@@ -0,0 +1,692 @@
+/*
+ * Battery driver for One Laptop Per Child board.
+ *
+ * Copyright © 2006-2010 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/err.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/jiffies.h>
+#include <linux/sched.h>
+#include <linux/olpc-ec.h>
+#include <asm/olpc.h>
+
+
+#define EC_BAT_VOLTAGE 0x10 /* uint16_t, *9.76/32, mV */
+#define EC_BAT_CURRENT 0x11 /* int16_t, *15.625/120, mA */
+#define EC_BAT_ACR 0x12 /* int16_t, *6250/15, µAh */
+#define EC_BAT_TEMP 0x13 /* uint16_t, *100/256, °C */
+#define EC_AMB_TEMP 0x14 /* uint16_t, *100/256, °C */
+#define EC_BAT_STATUS 0x15 /* uint8_t, bitmask */
+#define EC_BAT_SOC 0x16 /* uint8_t, percentage */
+#define EC_BAT_SERIAL 0x17 /* uint8_t[6] */
+#define EC_BAT_EEPROM 0x18 /* uint8_t adr as input, uint8_t output */
+#define EC_BAT_ERRCODE 0x1f /* uint8_t, bitmask */
+
+#define BAT_STAT_PRESENT 0x01
+#define BAT_STAT_FULL 0x02
+#define BAT_STAT_LOW 0x04
+#define BAT_STAT_DESTROY 0x08
+#define BAT_STAT_AC 0x10
+#define BAT_STAT_CHARGING 0x20
+#define BAT_STAT_DISCHARGING 0x40
+#define BAT_STAT_TRICKLE 0x80
+
+#define BAT_ERR_INFOFAIL 0x02
+#define BAT_ERR_OVERVOLTAGE 0x04
+#define BAT_ERR_OVERTEMP 0x05
+#define BAT_ERR_GAUGESTOP 0x06
+#define BAT_ERR_OUT_OF_CONTROL 0x07
+#define BAT_ERR_ID_FAIL 0x09
+#define BAT_ERR_ACR_FAIL 0x10
+
+#define BAT_ADDR_MFR_TYPE 0x5F
+
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int olpc_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ uint8_t status;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
+ if (ret)
+ return ret;
+
+ val->intval = !!(status & BAT_STAT_AC);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property olpc_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc olpc_ac_desc = {
+ .name = "olpc-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = olpc_ac_props,
+ .num_properties = ARRAY_SIZE(olpc_ac_props),
+ .get_property = olpc_ac_get_prop,
+};
+
+static struct power_supply *olpc_ac;
+
+static char bat_serial[17]; /* Ick */
+
+static int olpc_bat_get_status(union power_supply_propval *val, uint8_t ec_byte)
+{
+ if (olpc_platform_info.ecver > 0x44) {
+ if (ec_byte & (BAT_STAT_CHARGING | BAT_STAT_TRICKLE))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (ec_byte & BAT_STAT_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else /* er,... */
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ /* Older EC didn't report charge/discharge bits */
+ if (!(ec_byte & BAT_STAT_AC)) /* No AC means discharging */
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else /* Not _necessarily_ true but EC doesn't tell all yet */
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ }
+
+ return 0;
+}
+
+static int olpc_bat_get_health(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte) {
+ case 0:
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+
+ case BAT_ERR_OVERTEMP:
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+
+ case BAT_ERR_OVERVOLTAGE:
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ break;
+
+ case BAT_ERR_INFOFAIL:
+ case BAT_ERR_OUT_OF_CONTROL:
+ case BAT_ERR_ID_FAIL:
+ case BAT_ERR_ACR_FAIL:
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+
+ default:
+ /* Eep. We don't know this failure code */
+ ret = -EIO;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_mfr(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte >> 4) {
+ case 1:
+ val->strval = "Gold Peak";
+ break;
+ case 2:
+ val->strval = "BYD";
+ break;
+ default:
+ val->strval = "Unknown";
+ break;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_tech(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ int ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ switch (ec_byte & 0xf) {
+ case 1:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_NiMH;
+ break;
+ case 2:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LiFe;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN;
+ break;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_charge_full_design(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ union power_supply_propval tech;
+ int ret, mfr;
+
+ ret = olpc_bat_get_tech(&tech);
+ if (ret)
+ return ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ mfr = ec_byte >> 4;
+
+ switch (tech.intval) {
+ case POWER_SUPPLY_TECHNOLOGY_NiMH:
+ switch (mfr) {
+ case 1: /* Gold Peak */
+ val->intval = 3000000*.8;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ case POWER_SUPPLY_TECHNOLOGY_LiFe:
+ switch (mfr) {
+ case 1: /* Gold Peak, fall through */
+ case 2: /* BYD */
+ val->intval = 2800000;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ default:
+ return -EIO;
+ }
+
+ return ret;
+}
+
+static int olpc_bat_get_charge_now(union power_supply_propval *val)
+{
+ uint8_t soc;
+ union power_supply_propval full;
+ int ret;
+
+ ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &soc, 1);
+ if (ret)
+ return ret;
+
+ ret = olpc_bat_get_charge_full_design(&full);
+ if (ret)
+ return ret;
+
+ val->intval = soc * (full.intval / 100);
+ return 0;
+}
+
+static int olpc_bat_get_voltage_max_design(union power_supply_propval *val)
+{
+ uint8_t ec_byte;
+ union power_supply_propval tech;
+ int mfr;
+ int ret;
+
+ ret = olpc_bat_get_tech(&tech);
+ if (ret)
+ return ret;
+
+ ec_byte = BAT_ADDR_MFR_TYPE;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ mfr = ec_byte >> 4;
+
+ switch (tech.intval) {
+ case POWER_SUPPLY_TECHNOLOGY_NiMH:
+ switch (mfr) {
+ case 1: /* Gold Peak */
+ val->intval = 6000000;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ case POWER_SUPPLY_TECHNOLOGY_LiFe:
+ switch (mfr) {
+ case 1: /* Gold Peak */
+ val->intval = 6400000;
+ break;
+ case 2: /* BYD */
+ val->intval = 6500000;
+ break;
+ default:
+ return -EIO;
+ }
+ break;
+
+ default:
+ return -EIO;
+ }
+
+ return ret;
+}
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+static int olpc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ __be16 ec_word;
+ uint8_t ec_byte;
+ __be64 ser_buf;
+
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+
+ /* Theoretically there's a race here -- the battery could be
+ removed immediately after we check whether it's present, and
+ then we query for some other property of the now-absent battery.
+ It doesn't matter though -- the EC will return the last-known
+ information, and it's as if we just ran that _little_ bit faster
+ and managed to read it out before the battery went away. */
+ if (!(ec_byte & (BAT_STAT_PRESENT | BAT_STAT_TRICKLE)) &&
+ psp != POWER_SUPPLY_PROP_PRESENT)
+ return -ENODEV;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = olpc_bat_get_status(val, ec_byte);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (ec_byte & BAT_STAT_TRICKLE)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else if (ec_byte & BAT_STAT_CHARGING)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(ec_byte & (BAT_STAT_PRESENT |
+ BAT_STAT_TRICKLE));
+ break;
+
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (ec_byte & BAT_STAT_DESTROY)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else {
+ ret = olpc_bat_get_health(val);
+ if (ret)
+ return ret;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ ret = olpc_bat_get_mfr(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ ret = olpc_bat_get_tech(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = olpc_ec_cmd(EC_BAT_VOLTAGE, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 9760L / 32;
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ ret = olpc_ec_cmd(EC_BAT_CURRENT, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 15625L / 120;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = olpc_ec_cmd(EC_BAT_SOC, NULL, 0, &ec_byte, 1);
+ if (ret)
+ return ret;
+ val->intval = ec_byte;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (ec_byte & BAT_STAT_FULL)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (ec_byte & BAT_STAT_LOW)
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ else
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ ret = olpc_bat_get_charge_full_design(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ ret = olpc_bat_get_charge_now(val);
+ if (ret)
+ return ret;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ ret = olpc_ec_cmd(EC_BAT_TEMP, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 100 / 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;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+ ret = olpc_ec_cmd(EC_BAT_ACR, NULL, 0, (void *)&ec_word, 2);
+ if (ret)
+ return ret;
+
+ val->intval = (s16)be16_to_cpu(ec_word) * 6250 / 15;
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = olpc_ec_cmd(EC_BAT_SERIAL, NULL, 0, (void *)&ser_buf, 8);
+ if (ret)
+ return ret;
+
+ sprintf(bat_serial, "%016llx", (long long)be64_to_cpu(ser_buf));
+ val->strval = bat_serial;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ ret = olpc_bat_get_voltage_max_design(val);
+ if (ret)
+ return ret;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property olpc_xo1_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TEMP_AMBIENT,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+};
+
+/* XO-1.5 does not have ambient temperature property */
+static enum power_supply_property olpc_xo15_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_CHARGE_COUNTER,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+};
+
+/* EEPROM reading goes completely around the power_supply API, sadly */
+
+#define EEPROM_START 0x20
+#define EEPROM_END 0x80
+#define EEPROM_SIZE (EEPROM_END - EEPROM_START)
+
+static ssize_t olpc_bat_eeprom_read(struct file *filp, struct kobject *kobj,
+ struct bin_attribute *attr, char *buf, loff_t off, size_t count)
+{
+ uint8_t ec_byte;
+ int ret;
+ int i;
+
+ for (i = 0; i < count; i++) {
+ ec_byte = EEPROM_START + off + i;
+ ret = olpc_ec_cmd(EC_BAT_EEPROM, &ec_byte, 1, &buf[i], 1);
+ if (ret) {
+ pr_err("olpc-battery: "
+ "EC_BAT_EEPROM cmd @ 0x%x failed - %d!\n",
+ ec_byte, ret);
+ return -EIO;
+ }
+ }
+
+ return count;
+}
+
+static struct bin_attribute olpc_bat_eeprom = {
+ .attr = {
+ .name = "eeprom",
+ .mode = S_IRUGO,
+ },
+ .size = EEPROM_SIZE,
+ .read = olpc_bat_eeprom_read,
+};
+
+/* Allow userspace to see the specific error value pulled from the EC */
+
+static ssize_t olpc_bat_error_read(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ uint8_t ec_byte;
+ ssize_t ret;
+
+ ret = olpc_ec_cmd(EC_BAT_ERRCODE, NULL, 0, &ec_byte, 1);
+ if (ret < 0)
+ return ret;
+
+ return sprintf(buf, "%d\n", ec_byte);
+}
+
+static struct device_attribute olpc_bat_error = {
+ .attr = {
+ .name = "error",
+ .mode = S_IRUGO,
+ },
+ .show = olpc_bat_error_read,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct power_supply_desc olpc_bat_desc = {
+ .name = "olpc-battery",
+ .get_property = olpc_bat_get_property,
+ .use_for_apm = 1,
+};
+
+static struct power_supply *olpc_bat;
+
+static int olpc_battery_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ if (device_may_wakeup(&olpc_ac->dev))
+ olpc_ec_wakeup_set(EC_SCI_SRC_ACPWR);
+ else
+ olpc_ec_wakeup_clear(EC_SCI_SRC_ACPWR);
+
+ if (device_may_wakeup(&olpc_bat->dev))
+ olpc_ec_wakeup_set(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+ | EC_SCI_SRC_BATERR);
+ else
+ olpc_ec_wakeup_clear(EC_SCI_SRC_BATTERY | EC_SCI_SRC_BATSOC
+ | EC_SCI_SRC_BATERR);
+
+ return 0;
+}
+
+static int olpc_battery_probe(struct platform_device *pdev)
+{
+ int ret;
+ uint8_t status;
+
+ /*
+ * We've seen a number of EC protocol changes; this driver requires
+ * the latest EC protocol, supported by 0x44 and above.
+ */
+ if (olpc_platform_info.ecver < 0x44) {
+ printk(KERN_NOTICE "OLPC EC version 0x%02x too old for "
+ "battery driver.\n", olpc_platform_info.ecver);
+ return -ENXIO;
+ }
+
+ ret = olpc_ec_cmd(EC_BAT_STATUS, NULL, 0, &status, 1);
+ if (ret)
+ return ret;
+
+ /* Ignore the status. It doesn't actually matter */
+
+ olpc_ac = power_supply_register(&pdev->dev, &olpc_ac_desc, NULL);
+ if (IS_ERR(olpc_ac))
+ return PTR_ERR(olpc_ac);
+
+ if (olpc_board_at_least(olpc_board_pre(0xd0))) { /* XO-1.5 */
+ olpc_bat_desc.properties = olpc_xo15_bat_props;
+ olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo15_bat_props);
+ } else { /* XO-1 */
+ olpc_bat_desc.properties = olpc_xo1_bat_props;
+ olpc_bat_desc.num_properties = ARRAY_SIZE(olpc_xo1_bat_props);
+ }
+
+ olpc_bat = power_supply_register(&pdev->dev, &olpc_bat_desc, NULL);
+ if (IS_ERR(olpc_bat)) {
+ ret = PTR_ERR(olpc_bat);
+ goto battery_failed;
+ }
+
+ ret = device_create_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
+ if (ret)
+ goto eeprom_failed;
+
+ ret = device_create_file(&olpc_bat->dev, &olpc_bat_error);
+ if (ret)
+ goto error_failed;
+
+ if (olpc_ec_wakeup_available()) {
+ device_set_wakeup_capable(&olpc_ac->dev, true);
+ device_set_wakeup_capable(&olpc_bat->dev, true);
+ }
+
+ return 0;
+
+error_failed:
+ device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
+eeprom_failed:
+ power_supply_unregister(olpc_bat);
+battery_failed:
+ power_supply_unregister(olpc_ac);
+ return ret;
+}
+
+static int olpc_battery_remove(struct platform_device *pdev)
+{
+ device_remove_file(&olpc_bat->dev, &olpc_bat_error);
+ device_remove_bin_file(&olpc_bat->dev, &olpc_bat_eeprom);
+ power_supply_unregister(olpc_bat);
+ power_supply_unregister(olpc_ac);
+ return 0;
+}
+
+static const struct of_device_id olpc_battery_ids[] = {
+ { .compatible = "olpc,xo1-battery" },
+ {}
+};
+MODULE_DEVICE_TABLE(of, olpc_battery_ids);
+
+static struct platform_driver olpc_battery_driver = {
+ .driver = {
+ .name = "olpc-battery",
+ .of_match_table = olpc_battery_ids,
+ },
+ .probe = olpc_battery_probe,
+ .remove = olpc_battery_remove,
+ .suspend = olpc_battery_suspend,
+};
+
+module_platform_driver(olpc_battery_driver);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine");
diff --git a/drivers/power/supply/pcf50633-charger.c b/drivers/power/supply/pcf50633-charger.c
new file mode 100644
index 0000000..d05597b
--- /dev/null
+++ b/drivers/power/supply/pcf50633-charger.c
@@ -0,0 +1,488 @@
+/* NXP PCF50633 Main Battery Charger Driver
+ *
+ * (C) 2006-2008 by Openmoko, Inc.
+ * Author: Balaji Rao <balajirrao@openmoko.org>
+ * All rights reserved.
+ *
+ * Broken down from monstrous PCF50633 driver mainly by
+ * Harald Welte, Andy Green and Werner Almesberger
+ *
+ * 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.
+ *
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/init.h>
+#include <linux/types.h>
+#include <linux/device.h>
+#include <linux/sysfs.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/pcf50633/core.h>
+#include <linux/mfd/pcf50633/mbc.h>
+
+struct pcf50633_mbc {
+ struct pcf50633 *pcf;
+
+ int adapter_online;
+ int usb_online;
+
+ struct power_supply *usb;
+ struct power_supply *adapter;
+ struct power_supply *ac;
+};
+
+int pcf50633_mbc_usb_curlim_set(struct pcf50633 *pcf, int ma)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+ int ret = 0;
+ u8 bits;
+ int charging_start = 1;
+ u8 mbcs2, chgmod;
+ unsigned int mbcc5;
+
+ if (ma >= 1000) {
+ bits = PCF50633_MBCC7_USB_1000mA;
+ ma = 1000;
+ } else if (ma >= 500) {
+ bits = PCF50633_MBCC7_USB_500mA;
+ ma = 500;
+ } else if (ma >= 100) {
+ bits = PCF50633_MBCC7_USB_100mA;
+ ma = 100;
+ } else {
+ bits = PCF50633_MBCC7_USB_SUSPEND;
+ charging_start = 0;
+ ma = 0;
+ }
+
+ ret = pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC7,
+ PCF50633_MBCC7_USB_MASK, bits);
+ if (ret)
+ dev_err(pcf->dev, "error setting usb curlim to %d mA\n", ma);
+ else
+ dev_info(pcf->dev, "usb curlim to %d mA\n", ma);
+
+ /*
+ * We limit the charging current to be the USB current limit.
+ * The reason is that on pcf50633, when it enters PMU Standby mode,
+ * which it does when the device goes "off", the USB current limit
+ * reverts to the variant default. In at least one common case, that
+ * default is 500mA. By setting the charging current to be the same
+ * as the USB limit we set here before PMU standby, we enforce it only
+ * using the correct amount of current even when the USB current limit
+ * gets reset to the wrong thing
+ */
+
+ if (mbc->pcf->pdata->charger_reference_current_ma) {
+ mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
+ if (mbcc5 > 255)
+ mbcc5 = 255;
+ pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
+ }
+
+ mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
+ chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
+
+ /* If chgmod == BATFULL, setting chgena has no effect.
+ * Datasheet says we need to set resume instead but when autoresume is
+ * used resume doesn't work. Clear and set chgena instead.
+ */
+ if (chgmod != PCF50633_MBCS2_MBC_BAT_FULL)
+ pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
+ else {
+ pcf50633_reg_clear_bits(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA);
+ pcf50633_reg_set_bit_mask(pcf, PCF50633_REG_MBCC1,
+ PCF50633_MBCC1_CHGENA, PCF50633_MBCC1_CHGENA);
+ }
+
+ power_supply_changed(mbc->usb);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_usb_curlim_set);
+
+int pcf50633_mbc_get_status(struct pcf50633 *pcf)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+ int status = 0;
+ u8 chgmod;
+
+ if (!mbc)
+ return 0;
+
+ chgmod = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2)
+ & PCF50633_MBCS2_MBC_MASK;
+
+ if (mbc->usb_online)
+ status |= PCF50633_MBC_USB_ONLINE;
+ if (chgmod == PCF50633_MBCS2_MBC_USB_PRE ||
+ chgmod == PCF50633_MBCS2_MBC_USB_PRE_WAIT ||
+ chgmod == PCF50633_MBCS2_MBC_USB_FAST ||
+ chgmod == PCF50633_MBCS2_MBC_USB_FAST_WAIT)
+ status |= PCF50633_MBC_USB_ACTIVE;
+ if (mbc->adapter_online)
+ status |= PCF50633_MBC_ADAPTER_ONLINE;
+ if (chgmod == PCF50633_MBCS2_MBC_ADP_PRE ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_PRE_WAIT ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_FAST ||
+ chgmod == PCF50633_MBCS2_MBC_ADP_FAST_WAIT)
+ status |= PCF50633_MBC_ADAPTER_ACTIVE;
+
+ return status;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_get_status);
+
+int pcf50633_mbc_get_usb_online_status(struct pcf50633 *pcf)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pcf->mbc_pdev);
+
+ if (!mbc)
+ return 0;
+
+ return mbc->usb_online;
+}
+EXPORT_SYMBOL_GPL(pcf50633_mbc_get_usb_online_status);
+
+static ssize_t
+show_chgmode(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+
+ u8 mbcs2 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCS2);
+ u8 chgmod = (mbcs2 & PCF50633_MBCS2_MBC_MASK);
+
+ return sprintf(buf, "%d\n", chgmod);
+}
+static DEVICE_ATTR(chgmode, S_IRUGO, show_chgmode, NULL);
+
+static ssize_t
+show_usblim(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+ unsigned int ma;
+
+ if (usblim == PCF50633_MBCC7_USB_1000mA)
+ ma = 1000;
+ else if (usblim == PCF50633_MBCC7_USB_500mA)
+ ma = 500;
+ else if (usblim == PCF50633_MBCC7_USB_100mA)
+ ma = 100;
+ else
+ ma = 0;
+
+ return sprintf(buf, "%u\n", ma);
+}
+
+static ssize_t set_usblim(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ unsigned long ma;
+ int ret;
+
+ ret = kstrtoul(buf, 10, &ma);
+ if (ret)
+ return ret;
+
+ pcf50633_mbc_usb_curlim_set(mbc->pcf, ma);
+
+ return count;
+}
+
+static DEVICE_ATTR(usb_curlim, S_IRUGO | S_IWUSR, show_usblim, set_usblim);
+
+static ssize_t
+show_chglim(struct device *dev, struct device_attribute *attr, char *buf)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ u8 mbcc5 = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC5);
+ unsigned int ma;
+
+ if (!mbc->pcf->pdata->charger_reference_current_ma)
+ return -ENODEV;
+
+ ma = (mbc->pcf->pdata->charger_reference_current_ma * mbcc5) >> 8;
+
+ return sprintf(buf, "%u\n", ma);
+}
+
+static ssize_t set_chglim(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t count)
+{
+ struct pcf50633_mbc *mbc = dev_get_drvdata(dev);
+ unsigned long ma;
+ unsigned int mbcc5;
+ int ret;
+
+ if (!mbc->pcf->pdata->charger_reference_current_ma)
+ return -ENODEV;
+
+ ret = kstrtoul(buf, 10, &ma);
+ if (ret)
+ return ret;
+
+ mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma;
+ if (mbcc5 > 255)
+ mbcc5 = 255;
+ pcf50633_reg_write(mbc->pcf, PCF50633_REG_MBCC5, mbcc5);
+
+ return count;
+}
+
+/*
+ * This attribute allows to change MBC charging limit on the fly
+ * independently of usb current limit. It also gets set automatically every
+ * time usb current limit is changed.
+ */
+static DEVICE_ATTR(chg_curlim, S_IRUGO | S_IWUSR, show_chglim, set_chglim);
+
+static struct attribute *pcf50633_mbc_sysfs_entries[] = {
+ &dev_attr_chgmode.attr,
+ &dev_attr_usb_curlim.attr,
+ &dev_attr_chg_curlim.attr,
+ NULL,
+};
+
+static struct attribute_group mbc_attr_group = {
+ .name = NULL, /* put in device directory */
+ .attrs = pcf50633_mbc_sysfs_entries,
+};
+
+static void
+pcf50633_mbc_irq_handler(int irq, void *data)
+{
+ struct pcf50633_mbc *mbc = data;
+
+ /* USB */
+ if (irq == PCF50633_IRQ_USBINS) {
+ mbc->usb_online = 1;
+ } else if (irq == PCF50633_IRQ_USBREM) {
+ mbc->usb_online = 0;
+ pcf50633_mbc_usb_curlim_set(mbc->pcf, 0);
+ }
+
+ /* Adapter */
+ if (irq == PCF50633_IRQ_ADPINS)
+ mbc->adapter_online = 1;
+ else if (irq == PCF50633_IRQ_ADPREM)
+ mbc->adapter_online = 0;
+
+ power_supply_changed(mbc->ac);
+ power_supply_changed(mbc->usb);
+ power_supply_changed(mbc->adapter);
+
+ if (mbc->pcf->pdata->mbc_event_callback)
+ mbc->pcf->pdata->mbc_event_callback(mbc->pcf, irq);
+}
+
+static int adapter_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->adapter_online;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->usb_online &&
+ (usblim <= PCF50633_MBCC7_USB_500mA);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static int ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pcf50633_mbc *mbc = power_supply_get_drvdata(psy);
+ int ret = 0;
+ u8 usblim = pcf50633_reg_read(mbc->pcf, PCF50633_REG_MBCC7) &
+ PCF50633_MBCC7_USB_MASK;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = mbc->usb_online &&
+ (usblim == PCF50633_MBCC7_USB_1000mA);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const u8 mbc_irq_handlers[] = {
+ PCF50633_IRQ_ADPINS,
+ PCF50633_IRQ_ADPREM,
+ PCF50633_IRQ_USBINS,
+ PCF50633_IRQ_USBREM,
+ PCF50633_IRQ_BATFULL,
+ PCF50633_IRQ_CHGHALT,
+ PCF50633_IRQ_THLIMON,
+ PCF50633_IRQ_THLIMOFF,
+ PCF50633_IRQ_USBLIMON,
+ PCF50633_IRQ_USBLIMOFF,
+ PCF50633_IRQ_LOWSYS,
+ PCF50633_IRQ_LOWBAT,
+};
+
+static const struct power_supply_desc pcf50633_mbc_adapter_desc = {
+ .name = "adapter",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = power_props,
+ .num_properties = ARRAY_SIZE(power_props),
+ .get_property = &adapter_get_property,
+};
+
+static const struct power_supply_desc pcf50633_mbc_usb_desc = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = power_props,
+ .num_properties = ARRAY_SIZE(power_props),
+ .get_property = usb_get_property,
+};
+
+static const struct power_supply_desc pcf50633_mbc_ac_desc = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = power_props,
+ .num_properties = ARRAY_SIZE(power_props),
+ .get_property = ac_get_property,
+};
+
+static int pcf50633_mbc_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = {};
+ struct pcf50633_mbc *mbc;
+ int ret;
+ int i;
+ u8 mbcs1;
+
+ mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL);
+ if (!mbc)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, mbc);
+ mbc->pcf = dev_to_pcf50633(pdev->dev.parent);
+
+ /* Set up IRQ handlers */
+ for (i = 0; i < ARRAY_SIZE(mbc_irq_handlers); i++)
+ pcf50633_register_irq(mbc->pcf, mbc_irq_handlers[i],
+ pcf50633_mbc_irq_handler, mbc);
+
+ psy_cfg.supplied_to = mbc->pcf->pdata->batteries;
+ psy_cfg.num_supplicants = mbc->pcf->pdata->num_batteries;
+ psy_cfg.drv_data = mbc;
+
+ /* Create power supplies */
+ mbc->adapter = power_supply_register(&pdev->dev,
+ &pcf50633_mbc_adapter_desc,
+ &psy_cfg);
+ if (IS_ERR(mbc->adapter)) {
+ dev_err(mbc->pcf->dev, "failed to register adapter\n");
+ ret = PTR_ERR(mbc->adapter);
+ return ret;
+ }
+
+ mbc->usb = power_supply_register(&pdev->dev, &pcf50633_mbc_usb_desc,
+ &psy_cfg);
+ if (IS_ERR(mbc->usb)) {
+ dev_err(mbc->pcf->dev, "failed to register usb\n");
+ power_supply_unregister(mbc->adapter);
+ ret = PTR_ERR(mbc->usb);
+ return ret;
+ }
+
+ mbc->ac = power_supply_register(&pdev->dev, &pcf50633_mbc_ac_desc,
+ &psy_cfg);
+ if (IS_ERR(mbc->ac)) {
+ dev_err(mbc->pcf->dev, "failed to register ac\n");
+ power_supply_unregister(mbc->adapter);
+ power_supply_unregister(mbc->usb);
+ ret = PTR_ERR(mbc->ac);
+ return ret;
+ }
+
+ ret = sysfs_create_group(&pdev->dev.kobj, &mbc_attr_group);
+ if (ret)
+ 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);
+ if (mbcs1 & PCF50633_MBCS1_ADAPTPRES)
+ pcf50633_mbc_irq_handler(PCF50633_IRQ_ADPINS, mbc);
+
+ return 0;
+}
+
+static int pcf50633_mbc_remove(struct platform_device *pdev)
+{
+ struct pcf50633_mbc *mbc = platform_get_drvdata(pdev);
+ int i;
+
+ /* Remove IRQ handlers */
+ 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);
+
+ return 0;
+}
+
+static struct platform_driver pcf50633_mbc_driver = {
+ .driver = {
+ .name = "pcf50633-mbc",
+ },
+ .probe = pcf50633_mbc_probe,
+ .remove = pcf50633_mbc_remove,
+};
+
+module_platform_driver(pcf50633_mbc_driver);
+
+MODULE_AUTHOR("Balaji Rao <balajirrao@openmoko.org>");
+MODULE_DESCRIPTION("PCF50633 mbc driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:pcf50633-mbc");
diff --git a/drivers/power/supply/pda_power.c b/drivers/power/supply/pda_power.c
new file mode 100644
index 0000000..dfe1ee8
--- /dev/null
+++ b/drivers/power/supply/pda_power.c
@@ -0,0 +1,514 @@
+/*
+ * Common power driver for PDAs and phones with one or two external
+ * power supplies (AC/USB) connected to main and backup batteries,
+ * and optional builtin charger.
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ *
+ * 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/platform_device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/notifier.h>
+#include <linux/power_supply.h>
+#include <linux/pda_power.h>
+#include <linux/regulator/consumer.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/usb/otg.h>
+
+static inline unsigned int get_irq_flags(struct resource *res)
+{
+ return IRQF_SHARED | (res->flags & IRQF_TRIGGER_MASK);
+}
+
+static struct device *dev;
+static struct pda_power_pdata *pdata;
+static struct resource *ac_irq, *usb_irq;
+static struct timer_list charger_timer;
+static struct timer_list supply_timer;
+static struct timer_list polling_timer;
+static int polling;
+static struct power_supply *pda_psy_ac, *pda_psy_usb;
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+static struct usb_phy *transceiver;
+static struct notifier_block otg_nb;
+#endif
+
+static struct regulator *ac_draw;
+
+enum {
+ PDA_PSY_OFFLINE = 0,
+ PDA_PSY_ONLINE = 1,
+ PDA_PSY_TO_CHANGE,
+};
+static int new_ac_status = -1;
+static int new_usb_status = -1;
+static int ac_status = -1;
+static int usb_status = -1;
+
+static int pda_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ if (psy->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ val->intval = pdata->is_ac_online ?
+ pdata->is_ac_online() : 0;
+ else
+ val->intval = pdata->is_usb_online ?
+ pdata->is_usb_online() : 0;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property pda_power_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static char *pda_power_supplied_to[] = {
+ "main-battery",
+ "backup-battery",
+};
+
+static const struct power_supply_desc pda_psy_ac_desc = {
+ .name = "ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = pda_power_props,
+ .num_properties = ARRAY_SIZE(pda_power_props),
+ .get_property = pda_power_get_property,
+};
+
+static const struct power_supply_desc pda_psy_usb_desc = {
+ .name = "usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = pda_power_props,
+ .num_properties = ARRAY_SIZE(pda_power_props),
+ .get_property = pda_power_get_property,
+};
+
+static void update_status(void)
+{
+ if (pdata->is_ac_online)
+ new_ac_status = !!pdata->is_ac_online();
+
+ if (pdata->is_usb_online)
+ new_usb_status = !!pdata->is_usb_online();
+}
+
+static void update_charger(void)
+{
+ static int regulator_enabled;
+ int max_uA = pdata->ac_max_uA;
+
+ if (pdata->set_charge) {
+ if (new_ac_status > 0) {
+ dev_dbg(dev, "charger on (AC)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_AC);
+ } else if (new_usb_status > 0) {
+ dev_dbg(dev, "charger on (USB)\n");
+ pdata->set_charge(PDA_POWER_CHARGE_USB);
+ } else {
+ dev_dbg(dev, "charger off\n");
+ pdata->set_charge(0);
+ }
+ } else if (ac_draw) {
+ if (new_ac_status > 0) {
+ regulator_set_current_limit(ac_draw, max_uA, max_uA);
+ if (!regulator_enabled) {
+ dev_dbg(dev, "charger on (AC)\n");
+ WARN_ON(regulator_enable(ac_draw));
+ regulator_enabled = 1;
+ }
+ } else {
+ if (regulator_enabled) {
+ dev_dbg(dev, "charger off\n");
+ WARN_ON(regulator_disable(ac_draw));
+ regulator_enabled = 0;
+ }
+ }
+ }
+}
+
+static void supply_timer_func(unsigned long unused)
+{
+ if (ac_status == PDA_PSY_TO_CHANGE) {
+ ac_status = new_ac_status;
+ power_supply_changed(pda_psy_ac);
+ }
+
+ if (usb_status == PDA_PSY_TO_CHANGE) {
+ usb_status = new_usb_status;
+ power_supply_changed(pda_psy_usb);
+ }
+}
+
+static void psy_changed(void)
+{
+ update_charger();
+
+ /*
+ * Okay, charger set. Now wait a bit before notifying supplicants,
+ * charge power should stabilize.
+ */
+ mod_timer(&supply_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_charger));
+}
+
+static void charger_timer_func(unsigned long unused)
+{
+ update_status();
+ psy_changed();
+}
+
+static irqreturn_t power_changed_isr(int irq, void *power_supply)
+{
+ if (power_supply == pda_psy_ac)
+ ac_status = PDA_PSY_TO_CHANGE;
+ else if (power_supply == pda_psy_usb)
+ usb_status = PDA_PSY_TO_CHANGE;
+ else
+ return IRQ_NONE;
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+ mod_timer(&charger_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+ return IRQ_HANDLED;
+}
+
+static void polling_timer_func(unsigned long unused)
+{
+ int changed = 0;
+
+ dev_dbg(dev, "polling...\n");
+
+ update_status();
+
+ if (!ac_irq && new_ac_status != ac_status) {
+ ac_status = PDA_PSY_TO_CHANGE;
+ changed = 1;
+ }
+
+ if (!usb_irq && new_usb_status != usb_status) {
+ usb_status = PDA_PSY_TO_CHANGE;
+ changed = 1;
+ }
+
+ if (changed)
+ psy_changed();
+
+ mod_timer(&polling_timer,
+ jiffies + msecs_to_jiffies(pdata->polling_interval));
+}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+static int otg_is_usb_online(void)
+{
+ return (transceiver->last_event == USB_EVENT_VBUS ||
+ transceiver->last_event == USB_EVENT_ENUMERATED);
+}
+
+static int otg_is_ac_online(void)
+{
+ return (transceiver->last_event == USB_EVENT_CHARGER);
+}
+
+static int otg_handle_notification(struct notifier_block *nb,
+ unsigned long event, void *unused)
+{
+ switch (event) {
+ case USB_EVENT_CHARGER:
+ ac_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_VBUS:
+ case USB_EVENT_ENUMERATED:
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ case USB_EVENT_NONE:
+ ac_status = PDA_PSY_TO_CHANGE;
+ usb_status = PDA_PSY_TO_CHANGE;
+ break;
+ default:
+ return NOTIFY_OK;
+ }
+
+ /*
+ * Wait a bit before reading ac/usb line status and setting charger,
+ * because ac/usb status readings may lag from irq.
+ */
+ mod_timer(&charger_timer,
+ jiffies + msecs_to_jiffies(pdata->wait_for_status));
+
+ return NOTIFY_OK;
+}
+#endif
+
+static int pda_power_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = {};
+ int ret = 0;
+
+ dev = &pdev->dev;
+
+ if (pdev->id != -1) {
+ dev_err(dev, "it's meaningless to register several "
+ "pda_powers; use id = -1\n");
+ ret = -EINVAL;
+ goto wrongid;
+ }
+
+ pdata = pdev->dev.platform_data;
+
+ if (pdata->init) {
+ ret = pdata->init(dev);
+ if (ret < 0)
+ goto init_failed;
+ }
+
+ ac_draw = regulator_get(dev, "ac_draw");
+ if (IS_ERR(ac_draw)) {
+ dev_dbg(dev, "couldn't get ac_draw regulator\n");
+ ac_draw = NULL;
+ }
+
+ update_status();
+ update_charger();
+
+ if (!pdata->wait_for_status)
+ pdata->wait_for_status = 500;
+
+ if (!pdata->wait_for_charger)
+ pdata->wait_for_charger = 500;
+
+ if (!pdata->polling_interval)
+ pdata->polling_interval = 2000;
+
+ if (!pdata->ac_max_uA)
+ pdata->ac_max_uA = 500000;
+
+ setup_timer(&charger_timer, charger_timer_func, 0);
+ setup_timer(&supply_timer, supply_timer_func, 0);
+
+ ac_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "ac");
+ usb_irq = platform_get_resource_byname(pdev, IORESOURCE_IRQ, "usb");
+
+ if (pdata->supplied_to) {
+ psy_cfg.supplied_to = pdata->supplied_to;
+ psy_cfg.num_supplicants = pdata->num_supplicants;
+ } else {
+ psy_cfg.supplied_to = pda_power_supplied_to;
+ psy_cfg.num_supplicants = ARRAY_SIZE(pda_power_supplied_to);
+ }
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+ transceiver = usb_get_phy(USB_PHY_TYPE_USB2);
+ if (!IS_ERR_OR_NULL(transceiver)) {
+ if (!pdata->is_usb_online)
+ pdata->is_usb_online = otg_is_usb_online;
+ if (!pdata->is_ac_online)
+ pdata->is_ac_online = otg_is_ac_online;
+ }
+#endif
+
+ if (pdata->is_ac_online) {
+ pda_psy_ac = power_supply_register(&pdev->dev,
+ &pda_psy_ac_desc, &psy_cfg);
+ if (IS_ERR(pda_psy_ac)) {
+ dev_err(dev, "failed to register %s power supply\n",
+ pda_psy_ac_desc.name);
+ ret = PTR_ERR(pda_psy_ac);
+ goto ac_supply_failed;
+ }
+
+ if (ac_irq) {
+ ret = request_irq(ac_irq->start, power_changed_isr,
+ get_irq_flags(ac_irq), ac_irq->name,
+ pda_psy_ac);
+ if (ret) {
+ dev_err(dev, "request ac irq failed\n");
+ goto ac_irq_failed;
+ }
+ } else {
+ polling = 1;
+ }
+ }
+
+ if (pdata->is_usb_online) {
+ pda_psy_usb = power_supply_register(&pdev->dev,
+ &pda_psy_usb_desc,
+ &psy_cfg);
+ if (IS_ERR(pda_psy_usb)) {
+ dev_err(dev, "failed to register %s power supply\n",
+ pda_psy_usb_desc.name);
+ ret = PTR_ERR(pda_psy_usb);
+ goto usb_supply_failed;
+ }
+
+ if (usb_irq) {
+ ret = request_irq(usb_irq->start, power_changed_isr,
+ get_irq_flags(usb_irq),
+ usb_irq->name, pda_psy_usb);
+ if (ret) {
+ dev_err(dev, "request usb irq failed\n");
+ goto usb_irq_failed;
+ }
+ } else {
+ polling = 1;
+ }
+ }
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) {
+ otg_nb.notifier_call = otg_handle_notification;
+ ret = usb_register_notifier(transceiver, &otg_nb);
+ if (ret) {
+ dev_err(dev, "failure to register otg notifier\n");
+ goto otg_reg_notifier_failed;
+ }
+ polling = 0;
+ }
+#endif
+
+ if (polling) {
+ dev_dbg(dev, "will poll for status\n");
+ setup_timer(&polling_timer, polling_timer_func, 0);
+ mod_timer(&polling_timer,
+ jiffies + msecs_to_jiffies(pdata->polling_interval));
+ }
+
+ if (ac_irq || usb_irq)
+ device_init_wakeup(&pdev->dev, 1);
+
+ return 0;
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+otg_reg_notifier_failed:
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, pda_psy_usb);
+#endif
+usb_irq_failed:
+ if (pdata->is_usb_online)
+ power_supply_unregister(pda_psy_usb);
+usb_supply_failed:
+ if (pdata->is_ac_online && ac_irq)
+ free_irq(ac_irq->start, pda_psy_ac);
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (!IS_ERR_OR_NULL(transceiver))
+ usb_put_phy(transceiver);
+#endif
+ac_irq_failed:
+ if (pdata->is_ac_online)
+ power_supply_unregister(pda_psy_ac);
+ac_supply_failed:
+ if (ac_draw) {
+ regulator_put(ac_draw);
+ ac_draw = NULL;
+ }
+ if (pdata->exit)
+ pdata->exit(dev);
+init_failed:
+wrongid:
+ return ret;
+}
+
+static int pda_power_remove(struct platform_device *pdev)
+{
+ if (pdata->is_usb_online && usb_irq)
+ free_irq(usb_irq->start, pda_psy_usb);
+ if (pdata->is_ac_online && ac_irq)
+ free_irq(ac_irq->start, pda_psy_ac);
+
+ if (polling)
+ del_timer_sync(&polling_timer);
+ del_timer_sync(&charger_timer);
+ del_timer_sync(&supply_timer);
+
+ if (pdata->is_usb_online)
+ power_supply_unregister(pda_psy_usb);
+ if (pdata->is_ac_online)
+ power_supply_unregister(pda_psy_ac);
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (!IS_ERR_OR_NULL(transceiver))
+ usb_put_phy(transceiver);
+#endif
+ if (ac_draw) {
+ regulator_put(ac_draw);
+ ac_draw = NULL;
+ }
+ if (pdata->exit)
+ pdata->exit(dev);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int ac_wakeup_enabled;
+static int usb_wakeup_enabled;
+
+static int pda_power_suspend(struct platform_device *pdev, pm_message_t state)
+{
+ if (pdata->suspend) {
+ int ret = pdata->suspend(state);
+
+ if (ret)
+ return ret;
+ }
+
+ if (device_may_wakeup(&pdev->dev)) {
+ if (ac_irq)
+ ac_wakeup_enabled = !enable_irq_wake(ac_irq->start);
+ if (usb_irq)
+ usb_wakeup_enabled = !enable_irq_wake(usb_irq->start);
+ }
+
+ return 0;
+}
+
+static int pda_power_resume(struct platform_device *pdev)
+{
+ if (device_may_wakeup(&pdev->dev)) {
+ if (usb_irq && usb_wakeup_enabled)
+ disable_irq_wake(usb_irq->start);
+ if (ac_irq && ac_wakeup_enabled)
+ disable_irq_wake(ac_irq->start);
+ }
+
+ if (pdata->resume)
+ return pdata->resume();
+
+ return 0;
+}
+#else
+#define pda_power_suspend NULL
+#define pda_power_resume NULL
+#endif /* CONFIG_PM */
+
+static struct platform_driver pda_power_pdrv = {
+ .driver = {
+ .name = "pda-power",
+ },
+ .probe = pda_power_probe,
+ .remove = pda_power_remove,
+ .suspend = pda_power_suspend,
+ .resume = pda_power_resume,
+};
+
+module_platform_driver(pda_power_pdrv);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anton Vorontsov <cbou@mail.ru>");
+MODULE_ALIAS("platform:pda-power");
diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c
new file mode 100644
index 0000000..78561b6
--- /dev/null
+++ b/drivers/power/supply/pm2301_charger.c
@@ -0,0 +1,1258 @@
+/*
+ * Copyright 2012 ST Ericsson.
+ *
+ * Power supply driver for ST Ericsson pm2xxx_charger charger
+ *
+ * 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/init.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/interrupt.h>
+#include <linux/delay.h>
+#include <linux/slab.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/consumer.h>
+#include <linux/err.h>
+#include <linux/i2c.h>
+#include <linux/workqueue.h>
+#include <linux/mfd/abx500/ab8500.h>
+#include <linux/mfd/abx500/ab8500-bm.h>
+#include <linux/mfd/abx500/ux500_chargalg.h>
+#include <linux/pm2301_charger.h>
+#include <linux/gpio.h>
+#include <linux/pm_runtime.h>
+#include <linux/pm.h>
+
+#include "pm2301_charger.h"
+
+#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \
+ struct pm2xxx_charger, ac_chg)
+#define SLEEP_MIN 50
+#define SLEEP_MAX 100
+#define PM2XXX_AUTOSUSPEND_DELAY 500
+
+static int pm2xxx_interrupt_registers[] = {
+ PM2XXX_REG_INT1,
+ PM2XXX_REG_INT2,
+ PM2XXX_REG_INT3,
+ PM2XXX_REG_INT4,
+ PM2XXX_REG_INT5,
+ PM2XXX_REG_INT6,
+};
+
+static enum power_supply_property pm2xxx_charger_ac_props[] = {
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+};
+
+static int pm2xxx_charger_voltage_map[] = {
+ 3500,
+ 3525,
+ 3550,
+ 3575,
+ 3600,
+ 3625,
+ 3650,
+ 3675,
+ 3700,
+ 3725,
+ 3750,
+ 3775,
+ 3800,
+ 3825,
+ 3850,
+ 3875,
+ 3900,
+ 3925,
+ 3950,
+ 3975,
+ 4000,
+ 4025,
+ 4050,
+ 4075,
+ 4100,
+ 4125,
+ 4150,
+ 4175,
+ 4200,
+ 4225,
+ 4250,
+ 4275,
+ 4300,
+};
+
+static int pm2xxx_charger_current_map[] = {
+ 200,
+ 200,
+ 400,
+ 600,
+ 800,
+ 1000,
+ 1200,
+ 1400,
+ 1600,
+ 1800,
+ 2000,
+ 2200,
+ 2400,
+ 2600,
+ 2800,
+ 3000,
+};
+
+static const struct i2c_device_id pm2xxx_ident[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+static void set_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) {
+ gpio_set_value(pm2->lpn_pin, 1);
+ usleep_range(SLEEP_MIN, SLEEP_MAX);
+ }
+}
+
+static void clear_lpn_pin(struct pm2xxx_charger *pm2)
+{
+ if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin))
+ gpio_set_value(pm2->lpn_pin, 0);
+}
+
+static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val)
+{
+ int ret;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error reading register at 0x%x\n", reg);
+ else
+ ret = 0;
+
+ pm_runtime_put_sync(pm2->dev);
+
+ return ret;
+}
+
+static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val)
+{
+ int ret;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg,
+ 1, &val);
+ if (ret < 0)
+ dev_err(pm2->dev, "Error writing register at 0x%x\n", reg);
+ else
+ ret = 0;
+
+ pm_runtime_put_sync(pm2->dev);
+
+ return ret;
+}
+
+static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Enable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA));
+
+ return ret;
+}
+
+static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2)
+{
+ int ret;
+
+ /* Disable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+
+ /* Disable charging */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2,
+ (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS));
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+
+static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_err(pm2->dev, "Overvoltage detected\n");
+ pm2->flags.ovv = true;
+ power_supply_changed(pm2->ac_chg.psy);
+
+ /* Schedule a new HW failure check */
+ queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0);
+
+ return 0;
+}
+
+static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev , "20 minutes watchdog expired\n");
+
+ pm2->ac.wd_expired = true;
+ power_supply_changed(pm2->ac_chg.psy);
+
+ return 0;
+}
+
+static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ int ret;
+
+ switch (val) {
+ case PM2XXX_INT1_ITVBATLOWR:
+ dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n");
+ /* Enable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
+ PM2XXX_SWCTRL_SW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+ break;
+
+ case PM2XXX_INT1_ITVBATLOWF:
+ dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n");
+ /* Disable SW EOC ctrl */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG,
+ PM2XXX_SWCTRL_HW);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ return ret;
+ }
+ break;
+
+ default:
+ dev_err(pm2->dev, "Unknown VBAT level\n");
+ }
+
+ return 0;
+}
+
+static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val)
+{
+ dev_dbg(pm2->dev, "battery disconnected\n");
+
+ return 0;
+}
+
+static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val)
+{
+ int ret;
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val);
+
+ if (ret < 0) {
+ dev_err(pm2->dev, "Charger detection failed\n");
+ goto out;
+ }
+
+ *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG);
+
+out:
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val)
+{
+
+ int ret;
+ u8 read_val;
+
+ /*
+ * Since we can't be sure that the events are received
+ * synchronously, we have the check if the main charger is
+ * connected by reading the interrupt source register.
+ */
+ ret = pm2xxx_charger_detection(pm2, &read_val);
+
+ if ((ret == 0) && read_val) {
+ pm2->ac.charger_connected = 1;
+ pm2->ac_conn = true;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+ }
+
+
+ return ret;
+}
+
+static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2,
+ int val)
+{
+ pm2->ac.charger_connected = 0;
+ queue_work(pm2->charger_wq, &pm2->ac_work);
+
+ return 0;
+}
+
+static int pm2_int_reg0(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT1_ITVBATLOWR) {
+ ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
+ PM2XXX_INT1_ITVBATLOWR);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (val & PM2XXX_INT1_ITVBATLOWF) {
+ ret = pm2xxx_charger_vbat_lsig_mngt(pm2,
+ PM2XXX_INT1_ITVBATLOWF);
+ if (ret < 0)
+ goto out;
+ }
+
+ if (val & PM2XXX_INT1_ITVBATDISCONNECT) {
+ ret = pm2xxx_charger_bat_disc_mngt(pm2,
+ PM2XXX_INT1_ITVBATDISCONNECT);
+ if (ret < 0)
+ goto out;
+ }
+out:
+ return ret;
+}
+
+static int pm2_int_reg1(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) {
+ dev_dbg(pm2->dev , "Main charger plugged\n");
+ ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG));
+ }
+
+ if (val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) {
+ dev_dbg(pm2->dev , "Main charger unplugged\n");
+ ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val &
+ (PM2XXX_INT2_ITVPWR1UNPLUG |
+ PM2XXX_INT2_ITVPWR2UNPLUG));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg2(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD)
+ ret = pm2xxx_charger_wd_exp_mngt(pm2, val);
+
+ if (val & (PM2XXX_INT3_ITCHPRECHARGEWD |
+ PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) {
+ dev_dbg(pm2->dev,
+ "Watchdog occurred for precharge, CC and CV charge\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg3(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT4_ITCHARGINGON)) {
+ dev_dbg(pm2->dev ,
+ "chargind operation has started\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVRESUME)) {
+ dev_dbg(pm2->dev,
+ "battery discharged down to VResume threshold\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITBATTFULL)) {
+ dev_dbg(pm2->dev , "battery fully detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITCVPHASE)) {
+ dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n");
+ }
+
+ if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) {
+ pm2->failure_case = VPWR_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV));
+ dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT)) {
+ ret = pm2xxx_charger_batt_therm_mngt(pm2, val &
+ (PM2XXX_INT4_S_ITBATTEMPCOLD |
+ PM2XXX_INT4_S_ITBATTEMPHOT));
+ dev_dbg(pm2->dev, "BTEMP is too Low/High\n");
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg4(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & PM2XXX_INT5_ITVSYSTEMOVV) {
+ pm2->failure_case = VSYSTEM_OVV;
+ ret = pm2xxx_charger_ovv_mngt(pm2, val &
+ PM2XXX_INT5_ITVSYSTEMOVV);
+ dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n");
+ }
+
+ if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) {
+ dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n");
+ ret = pm2xxx_charger_die_therm_mngt(pm2, val &
+ (PM2XXX_INT5_ITTHERMALWARNINGFALL |
+ PM2XXX_INT5_ITTHERMALWARNINGRISE |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL |
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE));
+ }
+
+ return ret;
+}
+
+static int pm2_int_reg5(void *pm2_data, int val)
+{
+ struct pm2xxx_charger *pm2 = pm2_data;
+ int ret = 0;
+
+ if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) {
+ dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n");
+ }
+
+ if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE |
+ PM2XXX_INT6_ITVPWR1VALIDRISE |
+ PM2XXX_INT6_ITVPWR2VALIDFALL |
+ PM2XXX_INT6_ITVPWR1VALIDFALL)) {
+ dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n");
+ }
+
+ return ret;
+}
+
+static irqreturn_t pm2xxx_irq_int(int irq, void *data)
+{
+ struct pm2xxx_charger *pm2 = data;
+ struct pm2xxx_interrupts *interrupt = pm2->pm2_int;
+ int i;
+
+ /* wake up the device */
+ pm_runtime_get_sync(pm2->dev);
+
+ do {
+ for (i = 0; i < PM2XXX_NUM_INT_REG; i++) {
+ pm2xxx_reg_read(pm2,
+ pm2xxx_interrupt_registers[i],
+ &(interrupt->reg[i]));
+
+ if (interrupt->reg[i] > 0)
+ interrupt->handler[i](pm2, interrupt->reg[i]);
+ }
+ } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0);
+
+ pm_runtime_mark_last_busy(pm2->dev);
+ pm_runtime_put_autosuspend(pm2->dev);
+
+ return IRQ_HANDLED;
+}
+
+static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+ u8 val;
+
+ if (pm2->ac.charger_connected && pm2->ac.charger_online) {
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto out;
+ }
+
+ if (val & PM2XXX_INT4_S_ITCVPHASE)
+ ret = PM2XXX_CONST_VOLT;
+ else
+ ret = PM2XXX_CONST_CURR;
+ }
+out:
+ return ret;
+}
+
+static int pm2xxx_current_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_current_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) {
+ if (curr < pm2xxx_charger_current_map[i])
+ return (i - 1);
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1;
+ if (curr == pm2xxx_charger_current_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_voltage_to_regval(int curr)
+{
+ int i;
+
+ if (curr < pm2xxx_charger_voltage_map[0])
+ return 0;
+
+ for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) {
+ if (curr < pm2xxx_charger_voltage_map[i])
+ return i - 1;
+ }
+
+ i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1;
+ if (curr == pm2xxx_charger_voltage_map[i])
+ return i;
+ else
+ return -EINVAL;
+}
+
+static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger,
+ int ich_out)
+{
+ int ret;
+ int curr_index;
+ struct pm2xxx_charger *pm2;
+ u8 val;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ curr_index = pm2xxx_current_to_regval(ich_out);
+ if (curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger current too high, charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret >= 0) {
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev,
+ "%s write failed\n", __func__);
+ }
+ }
+ else
+ dev_err(pm2->dev, "%s read failed\n", __func__);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pm2xxx_charger *pm2;
+
+ pm2 = to_pm2xxx_charger_ac_device_info(psy_to_ux500_charger(psy));
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (pm2->flags.mainextchnotok)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (pm2->ac.wd_expired)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else if (pm2->flags.main_thermal_prot)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (pm2->flags.ovv)
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = pm2->ac.charger_online;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = pm2->ac.charger_connected;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ pm2->ac.cv_active = pm2xxx_charger_get_ac_cv(pm2);
+ val->intval = pm2->ac.cv_active;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int pm2xxx_charging_init(struct pm2xxx_charger *pm2)
+{
+ int ret = 0;
+
+ /* enable CC and CV watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG3,
+ (PM2XXX_CH_WD_CV_PHASE_60MIN | PM2XXX_CH_WD_CC_PHASE_60MIN));
+ if( ret < 0)
+ return ret;
+
+ /* enable precharge watchdog */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG4,
+ PM2XXX_CH_WD_PRECH_PHASE_60MIN);
+
+ /* Disable auto timeout */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG5,
+ PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN);
+
+ /*
+ * EOC current level = 100mA
+ * Precharge current level = 100mA
+ * CC current level = 1000mA
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6,
+ (PM2XXX_DIR_CH_CC_CURRENT_1000MA |
+ PM2XXX_CH_PRECH_CURRENT_100MA |
+ PM2XXX_CH_EOC_CURRENT_100MA));
+
+ /*
+ * recharge threshold = 3.8V
+ * Precharge to CC threshold = 2.9V
+ */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG7,
+ (PM2XXX_CH_PRECH_VOL_2_9 | PM2XXX_CH_VRESUME_VOL_3_8));
+
+ /* float voltage charger level = 4.2V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8,
+ PM2XXX_CH_VOLT_4_2);
+
+ /* Voltage drop between VBAT and VSYS in HW charging = 300mV */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG9,
+ (PM2XXX_CH_150MV_DROP_300MV | PM2XXX_CHARCHING_INFO_DIS |
+ PM2XXX_CH_CC_REDUCED_CURRENT_IDENT |
+ PM2XXX_CH_CC_MODEDROP_DIS));
+
+ /* Input charger level of over voltage = 10V */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR2,
+ PM2XXX_VPWR2_OVV_10);
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_VOLT_VPWR1,
+ PM2XXX_VPWR1_OVV_10);
+
+ /* Input charger drop */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR2,
+ (PM2XXX_VPWR2_HW_OPT_DIS | PM2XXX_VPWR2_VALID_DIS |
+ PM2XXX_VPWR2_DROP_DIS));
+ ret = pm2xxx_reg_write(pm2, PM2XXX_INP_DROP_VPWR1,
+ (PM2XXX_VPWR1_HW_OPT_DIS | PM2XXX_VPWR1_VALID_DIS |
+ PM2XXX_VPWR1_DROP_DIS));
+
+ /* Disable battery low monitoring */
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG,
+ PM2XXX_VBAT_LOW_MONITORING_ENA);
+
+ return ret;
+}
+
+static int pm2xxx_charger_ac_en(struct ux500_charger *charger,
+ int enable, int vset, int iset)
+{
+ int ret;
+ int volt_index;
+ int curr_index;
+ u8 val;
+
+ struct pm2xxx_charger *pm2 = to_pm2xxx_charger_ac_device_info(charger);
+
+ if (enable) {
+ if (!pm2->ac.charger_connected) {
+ dev_dbg(pm2->dev, "AC charger not connected\n");
+ return -ENXIO;
+ }
+
+ dev_dbg(pm2->dev, "Enable AC: %dmV %dmA\n", vset, iset);
+ if (!pm2->vddadc_en_ac) {
+ ret = regulator_enable(pm2->regu);
+ if (ret)
+ dev_warn(pm2->dev,
+ "Failed to enable vddadc regulator\n");
+ else
+ pm2->vddadc_en_ac = true;
+ }
+
+ ret = pm2xxx_charging_init(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s charging init failed\n",
+ __func__);
+ goto error_occured;
+ }
+
+ volt_index = pm2xxx_voltage_to_regval(vset);
+ curr_index = pm2xxx_current_to_regval(iset);
+
+ if (volt_index < 0 || curr_index < 0) {
+ dev_err(pm2->dev,
+ "Charger voltage or current too high, "
+ "charging not started\n");
+ return -ENXIO;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG8, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_CH_VOLT_MASK;
+ val |= volt_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG8, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ ret = pm2xxx_reg_read(pm2, PM2XXX_BATT_CTRL_REG6, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ goto error_occured;
+ }
+ val &= ~PM2XXX_DIR_CH_CC_CURRENT_MASK;
+ val |= curr_index;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG6, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__);
+ goto error_occured;
+ }
+
+ if (!pm2->bat->enable_overshoot) {
+ ret = pm2xxx_reg_read(pm2, PM2XXX_LED_CTRL_REG, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n",
+ __func__);
+ goto error_occured;
+ }
+ val |= PM2XXX_ANTI_OVERSHOOT_EN;
+ ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx write failed\n",
+ __func__);
+ goto error_occured;
+ }
+ }
+
+ ret = pm2xxx_charging_enable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "Failed to enable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ pm2->ac.charger_online = 1;
+ } else {
+ pm2->ac.charger_online = 0;
+ pm2->ac.wd_expired = false;
+
+ /* Disable regulator if enabled */
+ if (pm2->vddadc_en_ac) {
+ regulator_disable(pm2->regu);
+ pm2->vddadc_en_ac = false;
+ }
+
+ ret = pm2xxx_charging_disable_mngt(pm2);
+ if (ret < 0) {
+ dev_err(pm2->dev, "failed to disable"
+ "pm2xxx ac charger\n");
+ goto error_occured;
+ }
+
+ dev_dbg(pm2->dev, "PM2301: " "Disabled AC charging\n");
+ }
+ power_supply_changed(pm2->ac_chg.psy);
+
+error_occured:
+ return ret;
+}
+
+static int pm2xxx_charger_watchdog_kick(struct ux500_charger *charger)
+{
+ int ret;
+ struct pm2xxx_charger *pm2;
+
+ if (charger->psy->desc->type == POWER_SUPPLY_TYPE_MAINS)
+ pm2 = to_pm2xxx_charger_ac_device_info(charger);
+ else
+ return -ENXIO;
+
+ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_WD_KICK, WD_TIMER);
+ if (ret)
+ dev_err(pm2->dev, "Failed to kick WD!\n");
+
+ return ret;
+}
+
+static void pm2xxx_charger_ac_work(struct work_struct *work)
+{
+ struct pm2xxx_charger *pm2 = container_of(work,
+ struct pm2xxx_charger, ac_work);
+
+
+ power_supply_changed(pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present");
+};
+
+static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work)
+{
+ u8 reg_value;
+
+ struct pm2xxx_charger *pm2 = container_of(work,
+ struct pm2xxx_charger, check_hw_failure_work.work);
+
+ if (pm2->flags.ovv) {
+ pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &reg_value);
+
+ if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV |
+ PM2XXX_INT4_S_ITVPWR2OVV))) {
+ pm2->flags.ovv = false;
+ power_supply_changed(pm2->ac_chg.psy);
+ }
+ }
+
+ /* If we still have a failure, schedule a new check */
+ if (pm2->flags.ovv) {
+ queue_delayed_work(pm2->charger_wq,
+ &pm2->check_hw_failure_work, round_jiffies(HZ));
+ }
+}
+
+static void pm2xxx_charger_check_main_thermal_prot_work(
+ struct work_struct *work)
+{
+ int ret;
+ u8 val;
+
+ struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger,
+ check_main_thermal_prot_work);
+
+ /* Check if die temp warning is still active */
+ ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val);
+ if (ret < 0) {
+ dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__);
+ return;
+ }
+ if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE
+ | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE))
+ pm2->flags.main_thermal_prot = true;
+ else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL
+ | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL))
+ pm2->flags.main_thermal_prot = false;
+
+ power_supply_changed(pm2->ac_chg.psy);
+}
+
+static struct pm2xxx_interrupts pm2xxx_int = {
+ .handler[0] = pm2_int_reg0,
+ .handler[1] = pm2_int_reg1,
+ .handler[2] = pm2_int_reg2,
+ .handler[3] = pm2_int_reg3,
+ .handler[4] = pm2_int_reg4,
+ .handler[5] = pm2_int_reg5,
+};
+
+static struct pm2xxx_irq pm2xxx_charger_irq[] = {
+ {"PM2XXX_IRQ_INT", pm2xxx_irq_int},
+};
+
+static int __maybe_unused pm2xxx_wall_charger_resume(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
+ set_lpn_pin(pm2);
+
+ /* If we still have a HW failure, schedule a new check */
+ if (pm2->flags.ovv)
+ queue_delayed_work(pm2->charger_wq,
+ &pm2->check_hw_failure_work, 0);
+
+ return 0;
+}
+
+static int __maybe_unused pm2xxx_wall_charger_suspend(struct device *dev)
+{
+ struct i2c_client *i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client);
+ clear_lpn_pin(pm2);
+
+ /* Cancel any pending HW failure check */
+ if (delayed_work_pending(&pm2->check_hw_failure_work))
+ cancel_delayed_work(&pm2->check_hw_failure_work);
+
+ flush_work(&pm2->ac_work);
+ flush_work(&pm2->check_main_thermal_prot_work);
+
+ return 0;
+}
+
+static int __maybe_unused pm2xxx_runtime_suspend(struct device *dev)
+{
+ struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
+ clear_lpn_pin(pm2);
+
+ return 0;
+}
+
+static int __maybe_unused pm2xxx_runtime_resume(struct device *dev)
+{
+ struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev);
+ struct pm2xxx_charger *pm2;
+
+ pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client);
+
+ if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0)
+ set_lpn_pin(pm2);
+
+ return 0;
+}
+
+static const struct dev_pm_ops pm2xxx_pm_ops __maybe_unused = {
+ SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend,
+ pm2xxx_wall_charger_resume)
+ SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL)
+};
+
+static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client,
+ const struct i2c_device_id *id)
+{
+ struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ struct pm2xxx_charger *pm2;
+ int ret = 0;
+ u8 val;
+ int i;
+
+ if (!pl_data) {
+ dev_err(&i2c_client->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL);
+ if (!pm2) {
+ dev_err(&i2c_client->dev, "pm2xxx_charger allocation failed\n");
+ return -ENOMEM;
+ }
+
+ /* get parent data */
+ pm2->dev = &i2c_client->dev;
+
+ pm2->pm2_int = &pm2xxx_int;
+
+ /* get charger spcific platform data */
+ if (!pl_data->wall_charger) {
+ dev_err(pm2->dev, "no charger platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->pdata = pl_data->wall_charger;
+
+ /* get battery specific platform data */
+ if (!pl_data->battery) {
+ dev_err(pm2->dev, "no battery platform data supplied\n");
+ ret = -EINVAL;
+ goto free_device_info;
+ }
+
+ pm2->bat = pl_data->battery;
+
+ if (!i2c_check_functionality(i2c_client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_READ_WORD_DATA)) {
+ ret = -ENODEV;
+ dev_info(pm2->dev, "pm2301 i2c_check_functionality failed\n");
+ goto free_device_info;
+ }
+
+ pm2->config.pm2xxx_i2c = i2c_client;
+ pm2->config.pm2xxx_id = (struct i2c_device_id *) id;
+ i2c_set_clientdata(i2c_client, pm2);
+
+ /* AC supply */
+ /* power_supply base class */
+ pm2->ac_chg_desc.name = pm2->pdata->label;
+ pm2->ac_chg_desc.type = POWER_SUPPLY_TYPE_MAINS;
+ pm2->ac_chg_desc.properties = pm2xxx_charger_ac_props;
+ pm2->ac_chg_desc.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props);
+ pm2->ac_chg_desc.get_property = pm2xxx_charger_ac_get_property;
+
+ psy_cfg.supplied_to = pm2->pdata->supplied_to;
+ psy_cfg.num_supplicants = pm2->pdata->num_supplicants;
+ /* pm2xxx_charger sub-class */
+ pm2->ac_chg.ops.enable = &pm2xxx_charger_ac_en;
+ pm2->ac_chg.ops.kick_wd = &pm2xxx_charger_watchdog_kick;
+ pm2->ac_chg.ops.update_curr = &pm2xxx_charger_update_charger_current;
+ pm2->ac_chg.max_out_volt = pm2xxx_charger_voltage_map[
+ ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1];
+ pm2->ac_chg.max_out_curr = pm2xxx_charger_current_map[
+ ARRAY_SIZE(pm2xxx_charger_current_map) - 1];
+ pm2->ac_chg.wdt_refresh = WD_KICK_INTERVAL;
+ pm2->ac_chg.enabled = true;
+ pm2->ac_chg.external = true;
+
+ /* Create a work queue for the charger */
+ pm2->charger_wq = alloc_ordered_workqueue("pm2xxx_charger_wq",
+ WQ_MEM_RECLAIM);
+ if (pm2->charger_wq == NULL) {
+ ret = -ENOMEM;
+ dev_err(pm2->dev, "failed to create work queue\n");
+ goto free_device_info;
+ }
+
+ /* Init work for charger detection */
+ INIT_WORK(&pm2->ac_work, pm2xxx_charger_ac_work);
+
+ /* Init work for checking HW status */
+ INIT_WORK(&pm2->check_main_thermal_prot_work,
+ pm2xxx_charger_check_main_thermal_prot_work);
+
+ /* Init work for HW failure check */
+ INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work,
+ pm2xxx_charger_check_hw_failure_work);
+
+ /*
+ * VDD ADC supply needs to be enabled from this driver when there
+ * is a charger connected to avoid erroneous BTEMP_HIGH/LOW
+ * interrupts during charging
+ */
+ pm2->regu = regulator_get(pm2->dev, "vddadc");
+ if (IS_ERR(pm2->regu)) {
+ ret = PTR_ERR(pm2->regu);
+ dev_err(pm2->dev, "failed to get vddadc regulator\n");
+ goto free_charger_wq;
+ }
+
+ /* Register AC charger class */
+ pm2->ac_chg.psy = power_supply_register(pm2->dev, &pm2->ac_chg_desc,
+ &psy_cfg);
+ if (IS_ERR(pm2->ac_chg.psy)) {
+ dev_err(pm2->dev, "failed to register AC charger\n");
+ ret = PTR_ERR(pm2->ac_chg.psy);
+ goto free_regulator;
+ }
+
+ /* Register interrupts */
+ ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number),
+ NULL,
+ pm2xxx_charger_irq[0].isr,
+ pm2->pdata->irq_type,
+ pm2xxx_charger_irq[0].name, pm2);
+
+ if (ret != 0) {
+ dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n",
+ pm2xxx_charger_irq[0].name,
+ gpio_to_irq(pm2->pdata->gpio_irq_number), ret);
+ goto unregister_pm2xxx_charger;
+ }
+
+ ret = pm_runtime_set_active(pm2->dev);
+ if (ret)
+ dev_err(pm2->dev, "set active Error\n");
+
+ pm_runtime_enable(pm2->dev);
+ pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY);
+ pm_runtime_use_autosuspend(pm2->dev);
+ pm_runtime_resume(pm2->dev);
+
+ /* pm interrupt can wake up system */
+ ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+ if (ret) {
+ dev_err(pm2->dev, "failed to set irq wake\n");
+ goto unregister_pm2xxx_interrupt;
+ }
+
+ mutex_init(&pm2->lock);
+
+ if (gpio_is_valid(pm2->pdata->lpn_gpio)) {
+ /* get lpn GPIO from platform data */
+ pm2->lpn_pin = pm2->pdata->lpn_gpio;
+
+ /*
+ * Charger detection mechanism requires pulling up the LPN pin
+ * while i2c communication if Charger is not connected
+ * LPN pin of PM2301 is GPIO60 of AB9540
+ */
+ ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio");
+
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n");
+ goto disable_pm2_irq_wake;
+ }
+ ret = gpio_direction_output(pm2->lpn_pin, 0);
+ if (ret < 0) {
+ dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n");
+ goto free_gpio;
+ }
+ set_lpn_pin(pm2);
+ }
+
+ /* read interrupt registers */
+ for (i = 0; i < PM2XXX_NUM_INT_REG; i++)
+ pm2xxx_reg_read(pm2,
+ pm2xxx_interrupt_registers[i],
+ &val);
+
+ ret = pm2xxx_charger_detection(pm2, &val);
+
+ if ((ret == 0) && val) {
+ pm2->ac.charger_connected = 1;
+ ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON,
+ AB8500_MAIN_CH_DET);
+ pm2->ac_conn = true;
+ power_supply_changed(pm2->ac_chg.psy);
+ sysfs_notify(&pm2->ac_chg.psy->dev.kobj, NULL, "present");
+ }
+
+ return 0;
+
+free_gpio:
+ if (gpio_is_valid(pm2->lpn_pin))
+ gpio_free(pm2->lpn_pin);
+disable_pm2_irq_wake:
+ disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+unregister_pm2xxx_interrupt:
+ /* disable interrupt */
+ free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
+unregister_pm2xxx_charger:
+ /* unregister power supply */
+ power_supply_unregister(pm2->ac_chg.psy);
+free_regulator:
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+free_charger_wq:
+ destroy_workqueue(pm2->charger_wq);
+free_device_info:
+ kfree(pm2);
+
+ return ret;
+}
+
+static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client)
+{
+ struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client);
+
+ /* Disable pm_runtime */
+ pm_runtime_disable(pm2->dev);
+ /* Disable AC charging */
+ pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0);
+
+ /* Disable wake by pm interrupt */
+ disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number));
+
+ /* Disable interrupts */
+ free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2);
+
+ /* Delete the work queue */
+ destroy_workqueue(pm2->charger_wq);
+
+ flush_scheduled_work();
+
+ /* disable the regulator */
+ regulator_put(pm2->regu);
+
+ power_supply_unregister(pm2->ac_chg.psy);
+
+ if (gpio_is_valid(pm2->lpn_pin))
+ gpio_free(pm2->lpn_pin);
+
+ kfree(pm2);
+
+ return 0;
+}
+
+static const struct i2c_device_id pm2xxx_id[] = {
+ { "pm2301", 0 },
+ { }
+};
+
+MODULE_DEVICE_TABLE(i2c, pm2xxx_id);
+
+static struct i2c_driver pm2xxx_charger_driver = {
+ .probe = pm2xxx_wall_charger_probe,
+ .remove = pm2xxx_wall_charger_remove,
+ .driver = {
+ .name = "pm2xxx-wall_charger",
+ .pm = IS_ENABLED(CONFIG_PM) ? &pm2xxx_pm_ops : NULL,
+ },
+ .id_table = pm2xxx_id,
+};
+
+static int __init pm2xxx_charger_init(void)
+{
+ return i2c_add_driver(&pm2xxx_charger_driver);
+}
+
+static void __exit pm2xxx_charger_exit(void)
+{
+ i2c_del_driver(&pm2xxx_charger_driver);
+}
+
+device_initcall_sync(pm2xxx_charger_init);
+module_exit(pm2xxx_charger_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay");
+MODULE_DESCRIPTION("PM2xxx charger management driver");
diff --git a/drivers/power/supply/pm2301_charger.h b/drivers/power/supply/pm2301_charger.h
new file mode 100644
index 0000000..24181cf
--- /dev/null
+++ b/drivers/power/supply/pm2301_charger.h
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) ST-Ericsson SA 2012
+ *
+ * PM2301 power supply interface
+ *
+ * License terms: GNU General Public License (GPL), version 2
+ */
+
+#ifndef PM2301_CHARGER_H
+#define PM2301_CHARGER_H
+
+/* Watchdog timeout constant */
+#define WD_TIMER 0x30 /* 4min */
+#define WD_KICK_INTERVAL (30 * HZ)
+
+#define PM2XXX_NUM_INT_REG 0x6
+
+/* Constant voltage/current */
+#define PM2XXX_CONST_CURR 0x0
+#define PM2XXX_CONST_VOLT 0x1
+
+/* Lowest charger voltage is 3.39V -> 0x4E */
+#define LOW_VOLT_REG 0x4E
+
+#define PM2XXX_BATT_CTRL_REG1 0x00
+#define PM2XXX_BATT_CTRL_REG2 0x01
+#define PM2XXX_BATT_CTRL_REG3 0x02
+#define PM2XXX_BATT_CTRL_REG4 0x03
+#define PM2XXX_BATT_CTRL_REG5 0x04
+#define PM2XXX_BATT_CTRL_REG6 0x05
+#define PM2XXX_BATT_CTRL_REG7 0x06
+#define PM2XXX_BATT_CTRL_REG8 0x07
+#define PM2XXX_NTC_CTRL_REG1 0x08
+#define PM2XXX_NTC_CTRL_REG2 0x09
+#define PM2XXX_BATT_CTRL_REG9 0x0A
+#define PM2XXX_BATT_STAT_REG1 0x0B
+#define PM2XXX_INP_VOLT_VPWR2 0x11
+#define PM2XXX_INP_DROP_VPWR2 0x13
+#define PM2XXX_INP_VOLT_VPWR1 0x15
+#define PM2XXX_INP_DROP_VPWR1 0x17
+#define PM2XXX_INP_MODE_VPWR 0x18
+#define PM2XXX_BATT_WD_KICK 0x70
+#define PM2XXX_DEV_VER_STAT 0x0C
+#define PM2XXX_THERM_WARN_CTRL_REG 0x20
+#define PM2XXX_BATT_DISC_REG 0x21
+#define PM2XXX_BATT_LOW_LEV_COMP_REG 0x22
+#define PM2XXX_BATT_LOW_LEV_VAL_REG 0x23
+#define PM2XXX_I2C_PAD_CTRL_REG 0x24
+#define PM2XXX_SW_CTRL_REG 0x26
+#define PM2XXX_LED_CTRL_REG 0x28
+
+#define PM2XXX_REG_INT1 0x40
+#define PM2XXX_MASK_REG_INT1 0x50
+#define PM2XXX_SRCE_REG_INT1 0x60
+#define PM2XXX_REG_INT2 0x41
+#define PM2XXX_MASK_REG_INT2 0x51
+#define PM2XXX_SRCE_REG_INT2 0x61
+#define PM2XXX_REG_INT3 0x42
+#define PM2XXX_MASK_REG_INT3 0x52
+#define PM2XXX_SRCE_REG_INT3 0x62
+#define PM2XXX_REG_INT4 0x43
+#define PM2XXX_MASK_REG_INT4 0x53
+#define PM2XXX_SRCE_REG_INT4 0x63
+#define PM2XXX_REG_INT5 0x44
+#define PM2XXX_MASK_REG_INT5 0x54
+#define PM2XXX_SRCE_REG_INT5 0x64
+#define PM2XXX_REG_INT6 0x45
+#define PM2XXX_MASK_REG_INT6 0x55
+#define PM2XXX_SRCE_REG_INT6 0x65
+
+#define VPWR_OVV 0x0
+#define VSYSTEM_OVV 0x1
+
+/* control Reg 1 */
+#define PM2XXX_CH_RESUME_EN 0x1
+#define PM2XXX_CH_RESUME_DIS 0x0
+
+/* control Reg 2 */
+#define PM2XXX_CH_AUTO_RESUME_EN 0X2
+#define PM2XXX_CH_AUTO_RESUME_DIS 0X0
+#define PM2XXX_CHARGER_ENA 0x4
+#define PM2XXX_CHARGER_DIS 0x0
+
+/* control Reg 3 */
+#define PM2XXX_CH_WD_CC_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_CC_PHASE_5MIN 0x1
+#define PM2XXX_CH_WD_CC_PHASE_10MIN 0x2
+#define PM2XXX_CH_WD_CC_PHASE_30MIN 0x3
+#define PM2XXX_CH_WD_CC_PHASE_60MIN 0x4
+#define PM2XXX_CH_WD_CC_PHASE_120MIN 0x5
+#define PM2XXX_CH_WD_CC_PHASE_240MIN 0x6
+#define PM2XXX_CH_WD_CC_PHASE_360MIN 0x7
+
+#define PM2XXX_CH_WD_CV_PHASE_OFF (0x0<<3)
+#define PM2XXX_CH_WD_CV_PHASE_5MIN (0x1<<3)
+#define PM2XXX_CH_WD_CV_PHASE_10MIN (0x2<<3)
+#define PM2XXX_CH_WD_CV_PHASE_30MIN (0x3<<3)
+#define PM2XXX_CH_WD_CV_PHASE_60MIN (0x4<<3)
+#define PM2XXX_CH_WD_CV_PHASE_120MIN (0x5<<3)
+#define PM2XXX_CH_WD_CV_PHASE_240MIN (0x6<<3)
+#define PM2XXX_CH_WD_CV_PHASE_360MIN (0x7<<3)
+
+/* control Reg 4 */
+#define PM2XXX_CH_WD_PRECH_PHASE_OFF 0x0
+#define PM2XXX_CH_WD_PRECH_PHASE_1MIN 0x1
+#define PM2XXX_CH_WD_PRECH_PHASE_5MIN 0x2
+#define PM2XXX_CH_WD_PRECH_PHASE_10MIN 0x3
+#define PM2XXX_CH_WD_PRECH_PHASE_30MIN 0x4
+#define PM2XXX_CH_WD_PRECH_PHASE_60MIN 0x5
+#define PM2XXX_CH_WD_PRECH_PHASE_120MIN 0x6
+#define PM2XXX_CH_WD_PRECH_PHASE_240MIN 0x7
+
+/* control Reg 5 */
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_NONE 0x0
+#define PM2XXX_CH_WD_AUTO_TIMEOUT_20MIN 0x1
+
+/* control Reg 6 */
+#define PM2XXX_DIR_CH_CC_CURRENT_MASK 0x0F
+#define PM2XXX_DIR_CH_CC_CURRENT_200MA 0x0
+#define PM2XXX_DIR_CH_CC_CURRENT_400MA 0x2
+#define PM2XXX_DIR_CH_CC_CURRENT_600MA 0x3
+#define PM2XXX_DIR_CH_CC_CURRENT_800MA 0x4
+#define PM2XXX_DIR_CH_CC_CURRENT_1000MA 0x5
+#define PM2XXX_DIR_CH_CC_CURRENT_1200MA 0x6
+#define PM2XXX_DIR_CH_CC_CURRENT_1400MA 0x7
+#define PM2XXX_DIR_CH_CC_CURRENT_1600MA 0x8
+#define PM2XXX_DIR_CH_CC_CURRENT_1800MA 0x9
+#define PM2XXX_DIR_CH_CC_CURRENT_2000MA 0xA
+#define PM2XXX_DIR_CH_CC_CURRENT_2200MA 0xB
+#define PM2XXX_DIR_CH_CC_CURRENT_2400MA 0xC
+#define PM2XXX_DIR_CH_CC_CURRENT_2600MA 0xD
+#define PM2XXX_DIR_CH_CC_CURRENT_2800MA 0xE
+#define PM2XXX_DIR_CH_CC_CURRENT_3000MA 0xF
+
+#define PM2XXX_CH_PRECH_CURRENT_MASK 0x30
+#define PM2XXX_CH_PRECH_CURRENT_25MA (0x0<<4)
+#define PM2XXX_CH_PRECH_CURRENT_50MA (0x1<<4)
+#define PM2XXX_CH_PRECH_CURRENT_75MA (0x2<<4)
+#define PM2XXX_CH_PRECH_CURRENT_100MA (0x3<<4)
+
+#define PM2XXX_CH_EOC_CURRENT_MASK 0xC0
+#define PM2XXX_CH_EOC_CURRENT_100MA (0x0<<6)
+#define PM2XXX_CH_EOC_CURRENT_150MA (0x1<<6)
+#define PM2XXX_CH_EOC_CURRENT_300MA (0x2<<6)
+#define PM2XXX_CH_EOC_CURRENT_400MA (0x3<<6)
+
+/* control Reg 7 */
+#define PM2XXX_CH_PRECH_VOL_2_5 0x0
+#define PM2XXX_CH_PRECH_VOL_2_7 0x1
+#define PM2XXX_CH_PRECH_VOL_2_9 0x2
+#define PM2XXX_CH_PRECH_VOL_3_1 0x3
+
+#define PM2XXX_CH_VRESUME_VOL_3_2 (0x0<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_4 (0x1<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_6 (0x2<<2)
+#define PM2XXX_CH_VRESUME_VOL_3_8 (0x3<<2)
+
+/* control Reg 8 */
+#define PM2XXX_CH_VOLT_MASK 0x3F
+#define PM2XXX_CH_VOLT_3_5 0x0
+#define PM2XXX_CH_VOLT_3_5225 0x1
+#define PM2XXX_CH_VOLT_3_6 0x4
+#define PM2XXX_CH_VOLT_3_7 0x8
+#define PM2XXX_CH_VOLT_4_0 0x14
+#define PM2XXX_CH_VOLT_4_175 0x1B
+#define PM2XXX_CH_VOLT_4_2 0x1C
+#define PM2XXX_CH_VOLT_4_275 0x1F
+#define PM2XXX_CH_VOLT_4_3 0x20
+
+/*NTC control register 1*/
+#define PM2XXX_BTEMP_HIGH_TH_45 0x0
+#define PM2XXX_BTEMP_HIGH_TH_50 0x1
+#define PM2XXX_BTEMP_HIGH_TH_55 0x2
+#define PM2XXX_BTEMP_HIGH_TH_60 0x3
+#define PM2XXX_BTEMP_HIGH_TH_65 0x4
+
+#define PM2XXX_BTEMP_LOW_TH_N5 (0x0<<3)
+#define PM2XXX_BTEMP_LOW_TH_0 (0x1<<3)
+#define PM2XXX_BTEMP_LOW_TH_5 (0x2<<3)
+#define PM2XXX_BTEMP_LOW_TH_10 (0x3<<3)
+
+/*NTC control register 2*/
+#define PM2XXX_NTC_BETA_COEFF_3477 0x0
+#define PM2XXX_NTC_BETA_COEFF_3964 0x1
+
+#define PM2XXX_NTC_RES_10K (0x0<<2)
+#define PM2XXX_NTC_RES_47K (0x1<<2)
+#define PM2XXX_NTC_RES_100K (0x2<<2)
+#define PM2XXX_NTC_RES_NO_NTC (0x3<<2)
+
+/* control Reg 9 */
+#define PM2XXX_CH_CC_MODEDROP_EN 1
+#define PM2XXX_CH_CC_MODEDROP_DIS 0
+
+#define PM2XXX_CH_CC_REDUCED_CURRENT_100MA (0x0<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_200MA (0x1<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_400MA (0x2<<1)
+#define PM2XXX_CH_CC_REDUCED_CURRENT_IDENT (0x3<<1)
+
+#define PM2XXX_CHARCHING_INFO_DIS (0<<3)
+#define PM2XXX_CHARCHING_INFO_EN (1<<3)
+
+#define PM2XXX_CH_150MV_DROP_300MV (0<<4)
+#define PM2XXX_CH_150MV_DROP_150MV (1<<4)
+
+
+/* charger status register */
+#define PM2XXX_CHG_STATUS_OFF 0x0
+#define PM2XXX_CHG_STATUS_ON 0x1
+#define PM2XXX_CHG_STATUS_FULL 0x2
+#define PM2XXX_CHG_STATUS_ERR 0x3
+#define PM2XXX_CHG_STATUS_WAIT 0x4
+#define PM2XXX_CHG_STATUS_NOBAT 0x5
+
+/* Input charger voltage VPWR2 */
+#define PM2XXX_VPWR2_OVV_6_0 0x0
+#define PM2XXX_VPWR2_OVV_6_3 0x1
+#define PM2XXX_VPWR2_OVV_10 0x2
+#define PM2XXX_VPWR2_OVV_NONE 0x3
+
+/* Input charger drop VPWR2 */
+#define PM2XXX_VPWR2_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR2_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR2_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR2_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR2_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR2_DROP_DIS (0x0<<2)
+
+/* Input charger voltage VPWR1 */
+#define PM2XXX_VPWR1_OVV_6_0 0x0
+#define PM2XXX_VPWR1_OVV_6_3 0x1
+#define PM2XXX_VPWR1_OVV_10 0x2
+#define PM2XXX_VPWR1_OVV_NONE 0x3
+
+/* Input charger drop VPWR1 */
+#define PM2XXX_VPWR1_HW_OPT_EN (0x1<<4)
+#define PM2XXX_VPWR1_HW_OPT_DIS (0x0<<4)
+
+#define PM2XXX_VPWR1_VALID_EN (0x1<<3)
+#define PM2XXX_VPWR1_VALID_DIS (0x0<<3)
+
+#define PM2XXX_VPWR1_DROP_EN (0x1<<2)
+#define PM2XXX_VPWR1_DROP_DIS (0x0<<2)
+
+/* Battery low level comparator control register */
+#define PM2XXX_VBAT_LOW_MONITORING_DIS 0x0
+#define PM2XXX_VBAT_LOW_MONITORING_ENA 0x1
+
+/* Battery low level value control register */
+#define PM2XXX_VBAT_LOW_LEVEL_2_3 0x0
+#define PM2XXX_VBAT_LOW_LEVEL_2_4 0x1
+#define PM2XXX_VBAT_LOW_LEVEL_2_5 0x2
+#define PM2XXX_VBAT_LOW_LEVEL_2_6 0x3
+#define PM2XXX_VBAT_LOW_LEVEL_2_7 0x4
+#define PM2XXX_VBAT_LOW_LEVEL_2_8 0x5
+#define PM2XXX_VBAT_LOW_LEVEL_2_9 0x6
+#define PM2XXX_VBAT_LOW_LEVEL_3_0 0x7
+#define PM2XXX_VBAT_LOW_LEVEL_3_1 0x8
+#define PM2XXX_VBAT_LOW_LEVEL_3_2 0x9
+#define PM2XXX_VBAT_LOW_LEVEL_3_3 0xA
+#define PM2XXX_VBAT_LOW_LEVEL_3_4 0xB
+#define PM2XXX_VBAT_LOW_LEVEL_3_5 0xC
+#define PM2XXX_VBAT_LOW_LEVEL_3_6 0xD
+#define PM2XXX_VBAT_LOW_LEVEL_3_7 0xE
+#define PM2XXX_VBAT_LOW_LEVEL_3_8 0xF
+#define PM2XXX_VBAT_LOW_LEVEL_3_9 0x10
+#define PM2XXX_VBAT_LOW_LEVEL_4_0 0x11
+#define PM2XXX_VBAT_LOW_LEVEL_4_1 0x12
+#define PM2XXX_VBAT_LOW_LEVEL_4_2 0x13
+
+/* SW CTRL */
+#define PM2XXX_SWCTRL_HW 0x0
+#define PM2XXX_SWCTRL_SW 0x1
+
+
+/* LED Driver Control */
+#define PM2XXX_LED_CURRENT_MASK 0x0C
+#define PM2XXX_LED_CURRENT_2_5MA (0X0<<2)
+#define PM2XXX_LED_CURRENT_1MA (0X1<<2)
+#define PM2XXX_LED_CURRENT_5MA (0X2<<2)
+#define PM2XXX_LED_CURRENT_10MA (0X3<<2)
+
+#define PM2XXX_LED_SELECT_MASK 0x02
+#define PM2XXX_LED_SELECT_EN (0X0<<1)
+#define PM2XXX_LED_SELECT_DIS (0X1<<1)
+
+#define PM2XXX_ANTI_OVERSHOOT_MASK 0x01
+#define PM2XXX_ANTI_OVERSHOOT_DIS 0X0
+#define PM2XXX_ANTI_OVERSHOOT_EN 0X1
+
+enum pm2xxx_reg_int1 {
+ PM2XXX_INT1_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_mask_reg_int1 {
+ PM2XXX_INT1_M_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_M_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_M_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_source_reg_int1 {
+ PM2XXX_INT1_S_ITVBATDISCONNECT = 0x02,
+ PM2XXX_INT1_S_ITVBATLOWR = 0x04,
+ PM2XXX_INT1_S_ITVBATLOWF = 0x08,
+};
+
+enum pm2xxx_reg_int2 {
+ PM2XXX_INT2_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_mask_reg_int2 {
+ PM2XXX_INT2_M_ITVPWR2PLUG = 0x01,
+ PM2XXX_INT2_M_ITVPWR2UNPLUG = 0x02,
+ PM2XXX_INT2_M_ITVPWR1PLUG = 0x04,
+ PM2XXX_INT2_M_ITVPWR1UNPLUG = 0x08,
+};
+
+enum pm2xxx_source_reg_int2 {
+ PM2XXX_INT2_S_ITVPWR2PLUG = 0x03,
+ PM2XXX_INT2_S_ITVPWR1PLUG = 0x0c,
+};
+
+enum pm2xxx_reg_int3 {
+ PM2XXX_INT3_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_ITCHCCWD = 0x02,
+ PM2XXX_INT3_ITCHCVWD = 0x04,
+ PM2XXX_INT3_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_mask_reg_int3 {
+ PM2XXX_INT3_M_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_M_ITCHCCWD = 0x02,
+ PM2XXX_INT3_M_ITCHCVWD = 0x04,
+ PM2XXX_INT3_M_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_source_reg_int3 {
+ PM2XXX_INT3_S_ITCHPRECHARGEWD = 0x01,
+ PM2XXX_INT3_S_ITCHCCWD = 0x02,
+ PM2XXX_INT3_S_ITCHCVWD = 0x04,
+ PM2XXX_INT3_S_ITAUTOTIMEOUTWD = 0x08,
+};
+
+enum pm2xxx_reg_int4 {
+ PM2XXX_INT4_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_ITVRESUME = 0x20,
+ PM2XXX_INT4_ITBATTFULL = 0x40,
+ PM2XXX_INT4_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_mask_reg_int4 {
+ PM2XXX_INT4_M_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_M_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_M_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_M_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_M_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_M_ITVRESUME = 0x20,
+ PM2XXX_INT4_M_ITBATTFULL = 0x40,
+ PM2XXX_INT4_M_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_source_reg_int4 {
+ PM2XXX_INT4_S_ITBATTEMPCOLD = 0x01,
+ PM2XXX_INT4_S_ITBATTEMPHOT = 0x02,
+ PM2XXX_INT4_S_ITVPWR2OVV = 0x04,
+ PM2XXX_INT4_S_ITVPWR1OVV = 0x08,
+ PM2XXX_INT4_S_ITCHARGINGON = 0x10,
+ PM2XXX_INT4_S_ITVRESUME = 0x20,
+ PM2XXX_INT4_S_ITBATTFULL = 0x40,
+ PM2XXX_INT4_S_ITCVPHASE = 0x80,
+};
+
+enum pm2xxx_reg_int5 {
+ PM2XXX_INT5_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_mask_reg_int5 {
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_M_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_M_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_M_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_M_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_source_reg_int5 {
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE = 0x01,
+ PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL = 0x02,
+ PM2XXX_INT5_S_ITTHERMALWARNINGRISE = 0x04,
+ PM2XXX_INT5_S_ITTHERMALWARNINGFALL = 0x08,
+ PM2XXX_INT5_S_ITVSYSTEMOVV = 0x10,
+};
+
+enum pm2xxx_reg_int6 {
+ PM2XXX_INT6_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_mask_reg_int6 {
+ PM2XXX_INT6_M_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_M_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_M_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_M_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_M_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_M_ITVPWR1VALIDFALL = 0x20,
+};
+
+enum pm2xxx_source_reg_int6 {
+ PM2XXX_INT6_S_ITVPWR2DROP = 0x01,
+ PM2XXX_INT6_S_ITVPWR1DROP = 0x02,
+ PM2XXX_INT6_S_ITVPWR2VALIDRISE = 0x04,
+ PM2XXX_INT6_S_ITVPWR2VALIDFALL = 0x08,
+ PM2XXX_INT6_S_ITVPWR1VALIDRISE = 0x10,
+ PM2XXX_INT6_S_ITVPWR1VALIDFALL = 0x20,
+};
+
+struct pm2xxx_charger_info {
+ int charger_connected;
+ int charger_online;
+ int cv_active;
+ bool wd_expired;
+};
+
+struct pm2xxx_charger_event_flags {
+ bool mainextchnotok;
+ bool main_thermal_prot;
+ bool ovv;
+ bool chgwdexp;
+};
+
+struct pm2xxx_interrupts {
+ u8 reg[PM2XXX_NUM_INT_REG];
+ int (*handler[PM2XXX_NUM_INT_REG])(void *, int);
+};
+
+struct pm2xxx_config {
+ struct i2c_client *pm2xxx_i2c;
+ struct i2c_device_id *pm2xxx_id;
+};
+
+struct pm2xxx_irq {
+ char *name;
+ irqreturn_t (*isr)(int irq, void *data);
+};
+
+struct pm2xxx_charger {
+ struct device *dev;
+ u8 chip_id;
+ bool vddadc_en_ac;
+ struct pm2xxx_config config;
+ bool ac_conn;
+ unsigned int gpio_irq;
+ int vbat;
+ int old_vbat;
+ int failure_case;
+ int failure_input_ovv;
+ unsigned int lpn_pin;
+ struct pm2xxx_interrupts *pm2_int;
+ struct regulator *regu;
+ struct pm2xxx_bm_data *bat;
+ struct mutex lock;
+ struct ab8500 *parent;
+ struct pm2xxx_charger_info ac;
+ struct pm2xxx_charger_platform_data *pdata;
+ struct workqueue_struct *charger_wq;
+ struct delayed_work check_vbat_work;
+ struct work_struct ac_work;
+ struct work_struct check_main_thermal_prot_work;
+ struct delayed_work check_hw_failure_work;
+ struct ux500_charger ac_chg;
+ struct power_supply_desc ac_chg_desc;
+ struct pm2xxx_charger_event_flags flags;
+};
+
+#endif /* PM2301_CHARGER_H */
diff --git a/drivers/power/supply/pmu_battery.c b/drivers/power/supply/pmu_battery.c
new file mode 100644
index 0000000..9c8d525
--- /dev/null
+++ b/drivers/power/supply/pmu_battery.c
@@ -0,0 +1,226 @@
+/*
+ * Battery class driver for Apple PMU
+ *
+ * Copyright © 2006 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/platform_device.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include <linux/adb.h>
+#include <linux/pmu.h>
+#include <linux/slab.h>
+
+static struct pmu_battery_dev {
+ struct power_supply *bat;
+ struct power_supply_desc bat_desc;
+ struct pmu_battery_info *pbi;
+ char name[16];
+ int propval;
+} *pbats[PMU_MAX_BATTERIES];
+
+#define to_pmu_battery_dev(x) power_supply_get_drvdata(x)
+
+/*********************************************************************
+ * Power
+ *********************************************************************/
+
+static int pmu_get_ac_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = (!!(pmu_power_flags & PMU_PWR_AC_PRESENT)) ||
+ (pmu_battery_count == 0);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property pmu_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static const struct power_supply_desc pmu_ac_desc = {
+ .name = "pmu-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = pmu_ac_props,
+ .num_properties = ARRAY_SIZE(pmu_ac_props),
+ .get_property = pmu_get_ac_prop,
+};
+
+static struct power_supply *pmu_ac;
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+static char *pmu_batt_types[] = {
+ "Smart", "Comet", "Hooper", "Unknown"
+};
+
+static char *pmu_bat_get_model_name(struct pmu_battery_info *pbi)
+{
+ switch (pbi->flags & PMU_BATT_TYPE_MASK) {
+ case PMU_BATT_TYPE_SMART:
+ return pmu_batt_types[0];
+ case PMU_BATT_TYPE_COMET:
+ return pmu_batt_types[1];
+ case PMU_BATT_TYPE_HOOPER:
+ return pmu_batt_types[2];
+ default: break;
+ }
+ return pmu_batt_types[3];
+}
+
+static int pmu_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct pmu_battery_dev *pbat = to_pmu_battery_dev(psy);
+ struct pmu_battery_info *pbi = pbat->pbi;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (pbi->flags & PMU_BATT_CHARGING)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else if (pmu_power_flags & PMU_PWR_AC_PRESENT)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(pbi->flags & PMU_BATT_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = pmu_bat_get_model_name(pbi);
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_AVG:
+ val->intval = pbi->charge * 1000; /* mWh -> µWh */
+ break;
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ val->intval = pbi->max_charge * 1000; /* mWh -> µWh */
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_AVG:
+ val->intval = pbi->amperage * 1000; /* mA -> µA */
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ val->intval = pbi->voltage * 1000; /* mV -> µV */
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ val->intval = pbi->time_remaining;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property pmu_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_ENERGY_AVG,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_CURRENT_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static struct platform_device *bat_pdev;
+
+static int __init pmu_bat_init(void)
+{
+ int ret = 0;
+ int i;
+
+ bat_pdev = platform_device_register_simple("pmu-battery",
+ 0, NULL, 0);
+ if (IS_ERR(bat_pdev)) {
+ ret = PTR_ERR(bat_pdev);
+ goto pdev_register_failed;
+ }
+
+ pmu_ac = power_supply_register(&bat_pdev->dev, &pmu_ac_desc, NULL);
+ if (IS_ERR(pmu_ac)) {
+ ret = PTR_ERR(pmu_ac);
+ goto ac_register_failed;
+ }
+
+ for (i = 0; i < pmu_battery_count; i++) {
+ struct power_supply_config psy_cfg = {};
+ struct pmu_battery_dev *pbat = kzalloc(sizeof(*pbat),
+ GFP_KERNEL);
+ if (!pbat)
+ break;
+
+ sprintf(pbat->name, "PMU_battery_%d", i);
+ pbat->bat_desc.name = pbat->name;
+ pbat->bat_desc.properties = pmu_bat_props;
+ pbat->bat_desc.num_properties = ARRAY_SIZE(pmu_bat_props);
+ pbat->bat_desc.get_property = pmu_bat_get_property;
+ pbat->pbi = &pmu_batteries[i];
+ psy_cfg.drv_data = pbat;
+
+ pbat->bat = power_supply_register(&bat_pdev->dev,
+ &pbat->bat_desc,
+ &psy_cfg);
+ if (IS_ERR(pbat->bat)) {
+ ret = PTR_ERR(pbat->bat);
+ kfree(pbat);
+ goto battery_register_failed;
+ }
+ pbats[i] = pbat;
+ }
+
+ goto success;
+
+battery_register_failed:
+ while (i--) {
+ if (!pbats[i])
+ continue;
+ power_supply_unregister(pbats[i]->bat);
+ kfree(pbats[i]);
+ }
+ power_supply_unregister(pmu_ac);
+ac_register_failed:
+ platform_device_unregister(bat_pdev);
+pdev_register_failed:
+success:
+ return ret;
+}
+
+static void __exit pmu_bat_exit(void)
+{
+ int i;
+
+ for (i = 0; i < PMU_MAX_BATTERIES; i++) {
+ if (!pbats[i])
+ continue;
+ power_supply_unregister(pbats[i]->bat);
+ kfree(pbats[i]);
+ }
+ power_supply_unregister(pmu_ac);
+ platform_device_unregister(bat_pdev);
+}
+
+module_init(pmu_bat_init);
+module_exit(pmu_bat_exit);
+
+MODULE_AUTHOR("David Woodhouse <dwmw2@infradead.org>");
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("PMU battery driver");
diff --git a/drivers/power/supply/power_supply.h b/drivers/power/supply/power_supply.h
new file mode 100644
index 0000000..cc439fd
--- /dev/null
+++ b/drivers/power/supply/power_supply.h
@@ -0,0 +1,42 @@
+/*
+ * Functions private to power supply class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+struct device;
+struct device_type;
+struct power_supply;
+
+#ifdef CONFIG_SYSFS
+
+extern void power_supply_init_attrs(struct device_type *dev_type);
+extern int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env);
+
+#else
+
+static inline void power_supply_init_attrs(struct device_type *dev_type) {}
+#define power_supply_uevent NULL
+
+#endif /* CONFIG_SYSFS */
+
+#ifdef CONFIG_LEDS_TRIGGERS
+
+extern void power_supply_update_leds(struct power_supply *psy);
+extern int power_supply_create_triggers(struct power_supply *psy);
+extern void power_supply_remove_triggers(struct power_supply *psy);
+
+#else
+
+static inline void power_supply_update_leds(struct power_supply *psy) {}
+static inline int power_supply_create_triggers(struct power_supply *psy)
+{ return 0; }
+static inline void power_supply_remove_triggers(struct power_supply *psy) {}
+
+#endif /* CONFIG_LEDS_TRIGGERS */
diff --git a/drivers/power/supply/power_supply_core.c b/drivers/power/supply/power_supply_core.c
new file mode 100644
index 0000000..a74d8ca
--- /dev/null
+++ b/drivers/power/supply/power_supply_core.c
@@ -0,0 +1,989 @@
+/*
+ * Universal power supply monitor class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/init.h>
+#include <linux/slab.h>
+#include <linux/device.h>
+#include <linux/notifier.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include <linux/thermal.h>
+#include "power_supply.h"
+
+/* exported for the APM Power driver, APM emulation */
+struct class *power_supply_class;
+EXPORT_SYMBOL_GPL(power_supply_class);
+
+ATOMIC_NOTIFIER_HEAD(power_supply_notifier);
+EXPORT_SYMBOL_GPL(power_supply_notifier);
+
+static struct device_type power_supply_dev_type;
+
+#define POWER_SUPPLY_DEFERRED_REGISTER_TIME msecs_to_jiffies(10)
+
+static bool __power_supply_is_supplied_by(struct power_supply *supplier,
+ struct power_supply *supply)
+{
+ int i;
+
+ if (!supply->supplied_from && !supplier->supplied_to)
+ return false;
+
+ /* Support both supplied_to and supplied_from modes */
+ if (supply->supplied_from) {
+ if (!supplier->desc->name)
+ return false;
+ for (i = 0; i < supply->num_supplies; i++)
+ if (!strcmp(supplier->desc->name, supply->supplied_from[i]))
+ return true;
+ } else {
+ if (!supply->desc->name)
+ return false;
+ for (i = 0; i < supplier->num_supplicants; i++)
+ if (!strcmp(supplier->supplied_to[i], supply->desc->name))
+ return true;
+ }
+
+ return false;
+}
+
+static int __power_supply_changed_work(struct device *dev, void *data)
+{
+ struct power_supply *psy = data;
+ struct power_supply *pst = dev_get_drvdata(dev);
+
+ if (__power_supply_is_supplied_by(psy, pst)) {
+ if (pst->desc->external_power_changed)
+ pst->desc->external_power_changed(pst);
+ }
+
+ return 0;
+}
+
+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__);
+
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ /*
+ * Check 'changed' here to avoid issues due to race between
+ * power_supply_changed() and this routine. In worst case
+ * power_supply_changed() can be called again just before we take above
+ * lock. During the first call of this routine we will mark 'changed' as
+ * false and it will stay false for the next call as well.
+ */
+ if (likely(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);
+ atomic_notifier_call_chain(&power_supply_notifier,
+ PSY_EVENT_PROP_CHANGED, psy);
+ kobject_uevent(&psy->dev.kobj, KOBJ_CHANGE);
+ spin_lock_irqsave(&psy->changed_lock, flags);
+ }
+
+ /*
+ * Hold the wakeup_source until all events are processed.
+ * power_supply_changed() might have called again and have set 'changed'
+ * to true.
+ */
+ if (likely(!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);
+
+/*
+ * Notify that power supply was registered after parent finished the probing.
+ *
+ * Often power supply is registered from driver's probe function. However
+ * calling power_supply_changed() directly from power_supply_register()
+ * would lead to execution of get_property() function provided by the driver
+ * too early - before the probe ends.
+ *
+ * Avoid that by waiting on parent's mutex.
+ */
+static void power_supply_deferred_register_work(struct work_struct *work)
+{
+ struct power_supply *psy = container_of(work, struct power_supply,
+ deferred_register_work.work);
+
+ if (psy->dev.parent)
+ mutex_lock(&psy->dev.parent->mutex);
+
+ power_supply_changed(psy);
+
+ if (psy->dev.parent)
+ mutex_unlock(&psy->dev.parent->mutex);
+}
+
+#ifdef CONFIG_OF
+#include <linux/of.h>
+
+static int __power_supply_populate_supplied_from(struct device *dev,
+ void *data)
+{
+ struct power_supply *psy = data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+ struct device_node *np;
+ int i = 0;
+
+ do {
+ np = of_parse_phandle(psy->of_node, "power-supplies", i++);
+ if (!np)
+ break;
+
+ if (np == epsy->of_node) {
+ dev_info(&psy->dev, "%s: Found supply : %s\n",
+ psy->desc->name, epsy->desc->name);
+ psy->supplied_from[i-1] = (char *)epsy->desc->name;
+ psy->num_supplies++;
+ of_node_put(np);
+ break;
+ }
+ of_node_put(np);
+ } while (np);
+
+ return 0;
+}
+
+static int power_supply_populate_supplied_from(struct power_supply *psy)
+{
+ int error;
+
+ error = class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_populate_supplied_from);
+
+ dev_dbg(&psy->dev, "%s %d\n", __func__, error);
+
+ return error;
+}
+
+static int __power_supply_find_supply_from_node(struct device *dev,
+ void *data)
+{
+ struct device_node *np = data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+
+ /* returning non-zero breaks out of class_for_each_device loop */
+ if (epsy->of_node == np)
+ return 1;
+
+ return 0;
+}
+
+static int power_supply_find_supply_from_node(struct device_node *supply_node)
+{
+ int error;
+
+ /*
+ * class_for_each_device() either returns its own errors or values
+ * returned by __power_supply_find_supply_from_node().
+ *
+ * __power_supply_find_supply_from_node() will return 0 (no match)
+ * or 1 (match).
+ *
+ * We return 0 if class_for_each_device() returned 1, -EPROBE_DEFER if
+ * it returned 0, or error as returned by it.
+ */
+ error = class_for_each_device(power_supply_class, NULL, supply_node,
+ __power_supply_find_supply_from_node);
+
+ return error ? (error == 1 ? 0 : error) : -EPROBE_DEFER;
+}
+
+static int power_supply_check_supplies(struct power_supply *psy)
+{
+ struct device_node *np;
+ int cnt = 0;
+
+ /* If there is already a list honor it */
+ if (psy->supplied_from && psy->num_supplies > 0)
+ return 0;
+
+ /* No device node found, nothing to do */
+ if (!psy->of_node)
+ return 0;
+
+ do {
+ int ret;
+
+ np = of_parse_phandle(psy->of_node, "power-supplies", cnt++);
+ if (!np)
+ break;
+
+ ret = power_supply_find_supply_from_node(np);
+ of_node_put(np);
+
+ if (ret) {
+ dev_dbg(&psy->dev, "Failed to find supply!\n");
+ return ret;
+ }
+ } while (np);
+
+ /* Missing valid "power-supplies" entries */
+ if (cnt == 1)
+ return 0;
+
+ /* All supplies found, allocate char ** array for filling */
+ psy->supplied_from = devm_kzalloc(&psy->dev, sizeof(psy->supplied_from),
+ GFP_KERNEL);
+ if (!psy->supplied_from) {
+ dev_err(&psy->dev, "Couldn't allocate memory for supply list\n");
+ return -ENOMEM;
+ }
+
+ *psy->supplied_from = devm_kzalloc(&psy->dev,
+ sizeof(char *) * (cnt - 1),
+ GFP_KERNEL);
+ if (!*psy->supplied_from) {
+ dev_err(&psy->dev, "Couldn't allocate memory for supply list\n");
+ return -ENOMEM;
+ }
+
+ return power_supply_populate_supplied_from(psy);
+}
+#else
+static inline int power_supply_check_supplies(struct power_supply *psy)
+{
+ return 0;
+}
+#endif
+
+static int __power_supply_am_i_supplied(struct device *dev, void *data)
+{
+ union power_supply_propval ret = {0,};
+ struct power_supply *psy = data;
+ struct power_supply *epsy = dev_get_drvdata(dev);
+
+ if (__power_supply_is_supplied_by(epsy, psy))
+ if (!epsy->desc->get_property(epsy, POWER_SUPPLY_PROP_ONLINE,
+ &ret))
+ return ret.intval;
+
+ return 0;
+}
+
+int power_supply_am_i_supplied(struct power_supply *psy)
+{
+ int error;
+
+ error = class_for_each_device(power_supply_class, NULL, psy,
+ __power_supply_am_i_supplied);
+
+ dev_dbg(&psy->dev, "%s %d\n", __func__, error);
+
+ return error;
+}
+EXPORT_SYMBOL_GPL(power_supply_am_i_supplied);
+
+static int __power_supply_is_system_supplied(struct device *dev, void *data)
+{
+ union power_supply_propval ret = {0,};
+ struct power_supply *psy = dev_get_drvdata(dev);
+ unsigned int *count = data;
+
+ (*count)++;
+ if (psy->desc->type != POWER_SUPPLY_TYPE_BATTERY)
+ if (!psy->desc->get_property(psy, POWER_SUPPLY_PROP_ONLINE,
+ &ret))
+ return ret.intval;
+
+ return 0;
+}
+
+int power_supply_is_system_supplied(void)
+{
+ int error;
+ unsigned int count = 0;
+
+ error = class_for_each_device(power_supply_class, NULL, &count,
+ __power_supply_is_system_supplied);
+
+ /*
+ * If no power class device was found at all, most probably we are
+ * running on a desktop system, so assume we are on mains power.
+ */
+ if (count == 0)
+ return 1;
+
+ return error;
+}
+EXPORT_SYMBOL_GPL(power_supply_is_system_supplied);
+
+int power_supply_set_battery_charged(struct power_supply *psy)
+{
+ if (atomic_read(&psy->use_cnt) >= 0 &&
+ psy->desc->type == POWER_SUPPLY_TYPE_BATTERY &&
+ psy->desc->set_charged) {
+ psy->desc->set_charged(psy);
+ return 0;
+ }
+
+ return -EINVAL;
+}
+EXPORT_SYMBOL_GPL(power_supply_set_battery_charged);
+
+static int power_supply_match_device_by_name(struct device *dev, const void *data)
+{
+ const char *name = data;
+ struct power_supply *psy = dev_get_drvdata(dev);
+
+ return strcmp(psy->desc->name, name) == 0;
+}
+
+/**
+ * power_supply_get_by_name() - Search for a power supply and returns its ref
+ * @name: Power supply name to fetch
+ *
+ * If power supply was found, it increases reference count for the
+ * internal power supply's device. The user should power_supply_put()
+ * after usage.
+ *
+ * Return: On success returns a reference to a power supply with
+ * matching name equals to @name, a NULL otherwise.
+ */
+struct power_supply *power_supply_get_by_name(const char *name)
+{
+ struct power_supply *psy = NULL;
+ struct device *dev = class_find_device(power_supply_class, NULL, name,
+ power_supply_match_device_by_name);
+
+ if (dev) {
+ psy = dev_get_drvdata(dev);
+ atomic_inc(&psy->use_cnt);
+ }
+
+ return psy;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_by_name);
+
+/**
+ * power_supply_put() - Drop reference obtained with power_supply_get_by_name
+ * @psy: Reference to put
+ *
+ * The reference to power supply should be put before unregistering
+ * the power supply.
+ */
+void power_supply_put(struct power_supply *psy)
+{
+ might_sleep();
+
+ atomic_dec(&psy->use_cnt);
+ put_device(&psy->dev);
+}
+EXPORT_SYMBOL_GPL(power_supply_put);
+
+#ifdef CONFIG_OF
+static int power_supply_match_device_node(struct device *dev, const void *data)
+{
+ return dev->parent && dev->parent->of_node == data;
+}
+
+/**
+ * power_supply_get_by_phandle() - Search for a power supply and returns its ref
+ * @np: Pointer to device node holding phandle property
+ * @phandle_name: Name of property holding a power supply name
+ *
+ * If power supply was found, it increases reference count for the
+ * internal power supply's device. The user should power_supply_put()
+ * after usage.
+ *
+ * Return: On success returns a reference to a power supply with
+ * matching name equals to value under @property, NULL or ERR_PTR otherwise.
+ */
+struct power_supply *power_supply_get_by_phandle(struct device_node *np,
+ const char *property)
+{
+ struct device_node *power_supply_np;
+ struct power_supply *psy = NULL;
+ struct device *dev;
+
+ power_supply_np = of_parse_phandle(np, property, 0);
+ if (!power_supply_np)
+ return ERR_PTR(-ENODEV);
+
+ dev = class_find_device(power_supply_class, NULL, power_supply_np,
+ power_supply_match_device_node);
+
+ of_node_put(power_supply_np);
+
+ if (dev) {
+ psy = dev_get_drvdata(dev);
+ atomic_inc(&psy->use_cnt);
+ }
+
+ return psy;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_by_phandle);
+
+static void devm_power_supply_put(struct device *dev, void *res)
+{
+ struct power_supply **psy = res;
+
+ power_supply_put(*psy);
+}
+
+/**
+ * devm_power_supply_get_by_phandle() - Resource managed version of
+ * power_supply_get_by_phandle()
+ * @dev: Pointer to device holding phandle property
+ * @phandle_name: Name of property holding a power supply phandle
+ *
+ * Return: On success returns a reference to a power supply with
+ * matching name equals to value under @property, NULL or ERR_PTR otherwise.
+ */
+struct power_supply *devm_power_supply_get_by_phandle(struct device *dev,
+ const char *property)
+{
+ struct power_supply **ptr, *psy;
+
+ if (!dev->of_node)
+ return ERR_PTR(-ENODEV);
+
+ ptr = devres_alloc(devm_power_supply_put, sizeof(*ptr), GFP_KERNEL);
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+
+ psy = power_supply_get_by_phandle(dev->of_node, property);
+ if (IS_ERR_OR_NULL(psy)) {
+ devres_free(ptr);
+ } else {
+ *ptr = psy;
+ devres_add(dev, ptr);
+ }
+ return psy;
+}
+EXPORT_SYMBOL_GPL(devm_power_supply_get_by_phandle);
+#endif /* CONFIG_OF */
+
+int power_supply_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ if (atomic_read(&psy->use_cnt) <= 0) {
+ if (!psy->initialized)
+ return -EAGAIN;
+ return -ENODEV;
+ }
+
+ return psy->desc->get_property(psy, psp, val);
+}
+EXPORT_SYMBOL_GPL(power_supply_get_property);
+
+int power_supply_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ if (atomic_read(&psy->use_cnt) <= 0 || !psy->desc->set_property)
+ return -ENODEV;
+
+ return psy->desc->set_property(psy, psp, val);
+}
+EXPORT_SYMBOL_GPL(power_supply_set_property);
+
+int power_supply_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ if (atomic_read(&psy->use_cnt) <= 0 ||
+ !psy->desc->property_is_writeable)
+ return -ENODEV;
+
+ return psy->desc->property_is_writeable(psy, psp);
+}
+EXPORT_SYMBOL_GPL(power_supply_property_is_writeable);
+
+void power_supply_external_power_changed(struct power_supply *psy)
+{
+ if (atomic_read(&psy->use_cnt) <= 0 ||
+ !psy->desc->external_power_changed)
+ return;
+
+ psy->desc->external_power_changed(psy);
+}
+EXPORT_SYMBOL_GPL(power_supply_external_power_changed);
+
+int power_supply_powers(struct power_supply *psy, struct device *dev)
+{
+ return sysfs_create_link(&psy->dev.kobj, &dev->kobj, "powers");
+}
+EXPORT_SYMBOL_GPL(power_supply_powers);
+
+static void power_supply_dev_release(struct device *dev)
+{
+ struct power_supply *psy = container_of(dev, struct power_supply, dev);
+ pr_debug("device: '%s': %s\n", dev_name(dev), __func__);
+ kfree(psy);
+}
+
+int power_supply_reg_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&power_supply_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(power_supply_reg_notifier);
+
+void power_supply_unreg_notifier(struct notifier_block *nb)
+{
+ atomic_notifier_chain_unregister(&power_supply_notifier, nb);
+}
+EXPORT_SYMBOL_GPL(power_supply_unreg_notifier);
+
+#ifdef CONFIG_THERMAL
+static int power_supply_read_temp(struct thermal_zone_device *tzd,
+ int *temp)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ WARN_ON(tzd == NULL);
+ psy = tzd->devdata;
+ ret = power_supply_get_property(psy, POWER_SUPPLY_PROP_TEMP, &val);
+ if (ret)
+ return ret;
+
+ /* Convert tenths of degree Celsius to milli degree Celsius. */
+ *temp = val.intval * 100;
+
+ return ret;
+}
+
+static struct thermal_zone_device_ops psy_tzd_ops = {
+ .get_temp = power_supply_read_temp,
+};
+
+static int psy_register_thermal(struct power_supply *psy)
+{
+ int i;
+
+ if (psy->desc->no_thermal)
+ return 0;
+
+ /* Register battery zone device psy reports temperature */
+ for (i = 0; i < psy->desc->num_properties; i++) {
+ if (psy->desc->properties[i] == POWER_SUPPLY_PROP_TEMP) {
+ psy->tzd = thermal_zone_device_register(psy->desc->name,
+ 0, 0, psy, &psy_tzd_ops, NULL, 0, 0);
+ return PTR_ERR_OR_ZERO(psy->tzd);
+ }
+ }
+ return 0;
+}
+
+static void psy_unregister_thermal(struct power_supply *psy)
+{
+ if (IS_ERR_OR_NULL(psy->tzd))
+ return;
+ thermal_zone_device_unregister(psy->tzd);
+}
+
+/* thermal cooling device callbacks */
+static int ps_get_max_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long *state)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = tcd->devdata;
+ ret = power_supply_get_property(psy,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val);
+ if (ret)
+ return ret;
+
+ *state = val.intval;
+
+ return ret;
+}
+
+static int ps_get_cur_chrage_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long *state)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = tcd->devdata;
+ ret = power_supply_get_property(psy,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
+ if (ret)
+ return ret;
+
+ *state = val.intval;
+
+ return ret;
+}
+
+static int ps_set_cur_charge_cntl_limit(struct thermal_cooling_device *tcd,
+ unsigned long state)
+{
+ struct power_supply *psy;
+ union power_supply_propval val;
+ int ret;
+
+ psy = tcd->devdata;
+ val.intval = state;
+ ret = psy->desc->set_property(psy,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val);
+
+ return ret;
+}
+
+static struct thermal_cooling_device_ops psy_tcd_ops = {
+ .get_max_state = ps_get_max_charge_cntl_limit,
+ .get_cur_state = ps_get_cur_chrage_cntl_limit,
+ .set_cur_state = ps_set_cur_charge_cntl_limit,
+};
+
+static int psy_register_cooler(struct power_supply *psy)
+{
+ int i;
+
+ /* Register for cooling device if psy can control charging */
+ for (i = 0; i < psy->desc->num_properties; i++) {
+ if (psy->desc->properties[i] ==
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) {
+ psy->tcd = thermal_cooling_device_register(
+ (char *)psy->desc->name,
+ psy, &psy_tcd_ops);
+ return PTR_ERR_OR_ZERO(psy->tcd);
+ }
+ }
+ return 0;
+}
+
+static void psy_unregister_cooler(struct power_supply *psy)
+{
+ if (IS_ERR_OR_NULL(psy->tcd))
+ return;
+ thermal_cooling_device_unregister(psy->tcd);
+}
+#else
+static int psy_register_thermal(struct power_supply *psy)
+{
+ return 0;
+}
+
+static void psy_unregister_thermal(struct power_supply *psy)
+{
+}
+
+static int psy_register_cooler(struct power_supply *psy)
+{
+ return 0;
+}
+
+static void psy_unregister_cooler(struct power_supply *psy)
+{
+}
+#endif
+
+static struct power_supply *__must_check
+__power_supply_register(struct device *parent,
+ const struct power_supply_desc *desc,
+ const struct power_supply_config *cfg,
+ bool ws)
+{
+ struct device *dev;
+ struct power_supply *psy;
+ int rc;
+
+ if (!parent)
+ pr_warn("%s: Expected proper parent device for '%s'\n",
+ __func__, desc->name);
+
+ psy = kzalloc(sizeof(*psy), GFP_KERNEL);
+ if (!psy)
+ return ERR_PTR(-ENOMEM);
+
+ dev = &psy->dev;
+
+ device_initialize(dev);
+
+ dev->class = power_supply_class;
+ dev->type = &power_supply_dev_type;
+ dev->parent = parent;
+ dev->release = power_supply_dev_release;
+ dev_set_drvdata(dev, psy);
+ psy->desc = desc;
+ if (cfg) {
+ psy->drv_data = cfg->drv_data;
+ psy->of_node = cfg->of_node;
+ psy->supplied_to = cfg->supplied_to;
+ psy->num_supplicants = cfg->num_supplicants;
+ }
+
+ rc = dev_set_name(dev, "%s", desc->name);
+ if (rc)
+ goto dev_set_name_failed;
+
+ INIT_WORK(&psy->changed_work, power_supply_changed_work);
+ INIT_DELAYED_WORK(&psy->deferred_register_work,
+ power_supply_deferred_register_work);
+
+ rc = power_supply_check_supplies(psy);
+ if (rc) {
+ dev_info(dev, "Not all required supplies found, defer probe\n");
+ goto check_supplies_failed;
+ }
+
+ spin_lock_init(&psy->changed_lock);
+ rc = device_init_wakeup(dev, ws);
+ if (rc)
+ goto wakeup_init_failed;
+
+ rc = device_add(dev);
+ if (rc)
+ goto device_add_failed;
+
+ rc = psy_register_thermal(psy);
+ if (rc)
+ goto register_thermal_failed;
+
+ rc = psy_register_cooler(psy);
+ if (rc)
+ goto register_cooler_failed;
+
+ rc = power_supply_create_triggers(psy);
+ if (rc)
+ goto create_triggers_failed;
+
+ /*
+ * Update use_cnt after any uevents (most notably from device_add()).
+ * We are here still during driver's probe but
+ * the power_supply_uevent() calls back driver's get_property
+ * method so:
+ * 1. Driver did not assigned the returned struct power_supply,
+ * 2. Driver could not finish initialization (anything in its probe
+ * after calling power_supply_register()).
+ */
+ atomic_inc(&psy->use_cnt);
+ psy->initialized = true;
+
+ queue_delayed_work(system_power_efficient_wq,
+ &psy->deferred_register_work,
+ POWER_SUPPLY_DEFERRED_REGISTER_TIME);
+
+ return psy;
+
+create_triggers_failed:
+ psy_unregister_cooler(psy);
+register_cooler_failed:
+ psy_unregister_thermal(psy);
+register_thermal_failed:
+ device_del(dev);
+device_add_failed:
+wakeup_init_failed:
+check_supplies_failed:
+dev_set_name_failed:
+ put_device(dev);
+ return ERR_PTR(rc);
+}
+
+/**
+ * power_supply_register() - Register new power supply
+ * @parent: Device to be a parent of power supply's device, usually
+ * the device which probe function calls this
+ * @desc: Description of power supply, must be valid through whole
+ * lifetime of this power supply
+ * @cfg: Run-time specific configuration accessed during registering,
+ * may be NULL
+ *
+ * Return: A pointer to newly allocated power_supply on success
+ * or ERR_PTR otherwise.
+ * Use power_supply_unregister() on returned power_supply pointer to release
+ * resources.
+ */
+struct power_supply *__must_check power_supply_register(struct device *parent,
+ const struct power_supply_desc *desc,
+ const struct power_supply_config *cfg)
+{
+ return __power_supply_register(parent, desc, cfg, true);
+}
+EXPORT_SYMBOL_GPL(power_supply_register);
+
+/**
+ * power_supply_register_no_ws() - Register new non-waking-source power supply
+ * @parent: Device to be a parent of power supply's device, usually
+ * the device which probe function calls this
+ * @desc: Description of power supply, must be valid through whole
+ * lifetime of this power supply
+ * @cfg: Run-time specific configuration accessed during registering,
+ * may be NULL
+ *
+ * Return: A pointer to newly allocated power_supply on success
+ * or ERR_PTR otherwise.
+ * Use power_supply_unregister() on returned power_supply pointer to release
+ * resources.
+ */
+struct power_supply *__must_check
+power_supply_register_no_ws(struct device *parent,
+ const struct power_supply_desc *desc,
+ const struct power_supply_config *cfg)
+{
+ return __power_supply_register(parent, desc, cfg, false);
+}
+EXPORT_SYMBOL_GPL(power_supply_register_no_ws);
+
+static void devm_power_supply_release(struct device *dev, void *res)
+{
+ struct power_supply **psy = res;
+
+ power_supply_unregister(*psy);
+}
+
+/**
+ * devm_power_supply_register() - Register managed power supply
+ * @parent: Device to be a parent of power supply's device, usually
+ * the device which probe function calls this
+ * @desc: Description of power supply, must be valid through whole
+ * lifetime of this power supply
+ * @cfg: Run-time specific configuration accessed during registering,
+ * may be NULL
+ *
+ * Return: A pointer to newly allocated power_supply on success
+ * or ERR_PTR otherwise.
+ * The returned power_supply pointer will be automatically unregistered
+ * on driver detach.
+ */
+struct power_supply *__must_check
+devm_power_supply_register(struct device *parent,
+ const struct power_supply_desc *desc,
+ const struct power_supply_config *cfg)
+{
+ struct power_supply **ptr, *psy;
+
+ ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);
+
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+ psy = __power_supply_register(parent, desc, cfg, true);
+ if (IS_ERR(psy)) {
+ devres_free(ptr);
+ } else {
+ *ptr = psy;
+ devres_add(parent, ptr);
+ }
+ return psy;
+}
+EXPORT_SYMBOL_GPL(devm_power_supply_register);
+
+/**
+ * devm_power_supply_register_no_ws() - Register managed non-waking-source power supply
+ * @parent: Device to be a parent of power supply's device, usually
+ * the device which probe function calls this
+ * @desc: Description of power supply, must be valid through whole
+ * lifetime of this power supply
+ * @cfg: Run-time specific configuration accessed during registering,
+ * may be NULL
+ *
+ * Return: A pointer to newly allocated power_supply on success
+ * or ERR_PTR otherwise.
+ * The returned power_supply pointer will be automatically unregistered
+ * on driver detach.
+ */
+struct power_supply *__must_check
+devm_power_supply_register_no_ws(struct device *parent,
+ const struct power_supply_desc *desc,
+ const struct power_supply_config *cfg)
+{
+ struct power_supply **ptr, *psy;
+
+ ptr = devres_alloc(devm_power_supply_release, sizeof(*ptr), GFP_KERNEL);
+
+ if (!ptr)
+ return ERR_PTR(-ENOMEM);
+ psy = __power_supply_register(parent, desc, cfg, false);
+ if (IS_ERR(psy)) {
+ devres_free(ptr);
+ } else {
+ *ptr = psy;
+ devres_add(parent, ptr);
+ }
+ return psy;
+}
+EXPORT_SYMBOL_GPL(devm_power_supply_register_no_ws);
+
+/**
+ * power_supply_unregister() - Remove this power supply from system
+ * @psy: Pointer to power supply to unregister
+ *
+ * Remove this power supply from the system. The resources of power supply
+ * will be freed here or on last power_supply_put() call.
+ */
+void power_supply_unregister(struct power_supply *psy)
+{
+ WARN_ON(atomic_dec_return(&psy->use_cnt));
+ cancel_work_sync(&psy->changed_work);
+ cancel_delayed_work_sync(&psy->deferred_register_work);
+ sysfs_remove_link(&psy->dev.kobj, "powers");
+ 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);
+
+void *power_supply_get_drvdata(struct power_supply *psy)
+{
+ return psy->drv_data;
+}
+EXPORT_SYMBOL_GPL(power_supply_get_drvdata);
+
+static int __init power_supply_class_init(void)
+{
+ power_supply_class = class_create(THIS_MODULE, "power_supply");
+
+ if (IS_ERR(power_supply_class))
+ return PTR_ERR(power_supply_class);
+
+ power_supply_class->dev_uevent = power_supply_uevent;
+ power_supply_init_attrs(&power_supply_dev_type);
+
+ return 0;
+}
+
+static void __exit power_supply_class_exit(void)
+{
+ class_destroy(power_supply_class);
+}
+
+subsys_initcall(power_supply_class_init);
+module_exit(power_supply_class_exit);
+
+MODULE_DESCRIPTION("Universal power supply monitor class");
+MODULE_AUTHOR("Ian Molton <spyro@f2s.com>, "
+ "Szabolcs Gyurko, "
+ "Anton Vorontsov <cbou@mail.ru>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/power_supply_leds.c b/drivers/power/supply/power_supply_leds.c
new file mode 100644
index 0000000..2277ad9
--- /dev/null
+++ b/drivers/power/supply/power_supply_leds.c
@@ -0,0 +1,170 @@
+/*
+ * LEDs triggers for power supply class
+ *
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/kernel.h>
+#include <linux/device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include "power_supply.h"
+
+/* Battery specific LEDs triggers. */
+
+static void power_supply_update_bat_leds(struct power_supply *psy)
+{
+ union power_supply_propval status;
+ unsigned long delay_on = 0;
+ unsigned long delay_off = 0;
+
+ if (power_supply_get_property(psy, POWER_SUPPLY_PROP_STATUS, &status))
+ return;
+
+ dev_dbg(&psy->dev, "%s %d\n", __func__, status.intval);
+
+ switch (status.intval) {
+ case POWER_SUPPLY_STATUS_FULL:
+ led_trigger_event(psy->charging_full_trig, LED_FULL);
+ led_trigger_event(psy->charging_trig, LED_OFF);
+ led_trigger_event(psy->full_trig, LED_FULL);
+ led_trigger_event(psy->charging_blink_full_solid_trig,
+ LED_FULL);
+ break;
+ case POWER_SUPPLY_STATUS_CHARGING:
+ led_trigger_event(psy->charging_full_trig, LED_FULL);
+ led_trigger_event(psy->charging_trig, LED_FULL);
+ led_trigger_event(psy->full_trig, LED_OFF);
+ led_trigger_blink(psy->charging_blink_full_solid_trig,
+ &delay_on, &delay_off);
+ break;
+ default:
+ led_trigger_event(psy->charging_full_trig, LED_OFF);
+ led_trigger_event(psy->charging_trig, LED_OFF);
+ led_trigger_event(psy->full_trig, LED_OFF);
+ led_trigger_event(psy->charging_blink_full_solid_trig,
+ LED_OFF);
+ break;
+ }
+}
+
+static int power_supply_create_bat_triggers(struct power_supply *psy)
+{
+ psy->charging_full_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging-or-full", psy->desc->name);
+ if (!psy->charging_full_trig_name)
+ goto charging_full_failed;
+
+ psy->charging_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging", psy->desc->name);
+ if (!psy->charging_trig_name)
+ goto charging_failed;
+
+ psy->full_trig_name = kasprintf(GFP_KERNEL, "%s-full", psy->desc->name);
+ if (!psy->full_trig_name)
+ goto full_failed;
+
+ psy->charging_blink_full_solid_trig_name = kasprintf(GFP_KERNEL,
+ "%s-charging-blink-full-solid", psy->desc->name);
+ if (!psy->charging_blink_full_solid_trig_name)
+ goto charging_blink_full_solid_failed;
+
+ led_trigger_register_simple(psy->charging_full_trig_name,
+ &psy->charging_full_trig);
+ led_trigger_register_simple(psy->charging_trig_name,
+ &psy->charging_trig);
+ led_trigger_register_simple(psy->full_trig_name,
+ &psy->full_trig);
+ led_trigger_register_simple(psy->charging_blink_full_solid_trig_name,
+ &psy->charging_blink_full_solid_trig);
+
+ return 0;
+
+charging_blink_full_solid_failed:
+ kfree(psy->full_trig_name);
+full_failed:
+ kfree(psy->charging_trig_name);
+charging_failed:
+ kfree(psy->charging_full_trig_name);
+charging_full_failed:
+ return -ENOMEM;
+}
+
+static void power_supply_remove_bat_triggers(struct power_supply *psy)
+{
+ led_trigger_unregister_simple(psy->charging_full_trig);
+ led_trigger_unregister_simple(psy->charging_trig);
+ led_trigger_unregister_simple(psy->full_trig);
+ led_trigger_unregister_simple(psy->charging_blink_full_solid_trig);
+ kfree(psy->charging_blink_full_solid_trig_name);
+ kfree(psy->full_trig_name);
+ kfree(psy->charging_trig_name);
+ kfree(psy->charging_full_trig_name);
+}
+
+/* Generated power specific LEDs triggers. */
+
+static void power_supply_update_gen_leds(struct power_supply *psy)
+{
+ union power_supply_propval online;
+
+ if (power_supply_get_property(psy, POWER_SUPPLY_PROP_ONLINE, &online))
+ return;
+
+ dev_dbg(&psy->dev, "%s %d\n", __func__, online.intval);
+
+ if (online.intval)
+ led_trigger_event(psy->online_trig, LED_FULL);
+ else
+ led_trigger_event(psy->online_trig, LED_OFF);
+}
+
+static int power_supply_create_gen_triggers(struct power_supply *psy)
+{
+ psy->online_trig_name = kasprintf(GFP_KERNEL, "%s-online",
+ psy->desc->name);
+ if (!psy->online_trig_name)
+ return -ENOMEM;
+
+ led_trigger_register_simple(psy->online_trig_name, &psy->online_trig);
+
+ return 0;
+}
+
+static void power_supply_remove_gen_triggers(struct power_supply *psy)
+{
+ led_trigger_unregister_simple(psy->online_trig);
+ kfree(psy->online_trig_name);
+}
+
+/* Choice what triggers to create&update. */
+
+void power_supply_update_leds(struct power_supply *psy)
+{
+ if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
+ power_supply_update_bat_leds(psy);
+ else
+ power_supply_update_gen_leds(psy);
+}
+
+int power_supply_create_triggers(struct power_supply *psy)
+{
+ if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
+ return power_supply_create_bat_triggers(psy);
+ return power_supply_create_gen_triggers(psy);
+}
+
+void power_supply_remove_triggers(struct power_supply *psy)
+{
+ if (psy->desc->type == POWER_SUPPLY_TYPE_BATTERY)
+ power_supply_remove_bat_triggers(psy);
+ else
+ power_supply_remove_gen_triggers(psy);
+}
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
new file mode 100644
index 0000000..bcde8d1
--- /dev/null
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -0,0 +1,337 @@
+/*
+ * Sysfs interface for the universal power supply monitor class
+ *
+ * Copyright © 2007 David Woodhouse <dwmw2@infradead.org>
+ * Copyright © 2007 Anton Vorontsov <cbou@mail.ru>
+ * Copyright © 2004 Szabolcs Gyurko
+ * Copyright © 2003 Ian Molton <spyro@f2s.com>
+ *
+ * Modified: 2004, Oct Szabolcs Gyurko
+ *
+ * You may use this code as per GPL version 2
+ */
+
+#include <linux/ctype.h>
+#include <linux/device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/stat.h>
+
+#include "power_supply.h"
+
+/*
+ * This is because the name "current" breaks the device attr macro.
+ * The "current" word resolves to "(get_current())" so instead of
+ * "current" "(get_current())" appears in the sysfs.
+ *
+ * The source of this definition is the device.h which calls __ATTR
+ * macro in sysfs.h which calls the __stringify macro.
+ *
+ * Only modification that the name is not tried to be resolved
+ * (as a macro let's say).
+ */
+
+#define POWER_SUPPLY_ATTR(_name) \
+{ \
+ .attr = { .name = #_name }, \
+ .show = power_supply_show_property, \
+ .store = power_supply_store_property, \
+}
+
+static struct device_attribute power_supply_attrs[];
+
+static ssize_t power_supply_show_property(struct device *dev,
+ struct device_attribute *attr,
+ char *buf) {
+ static char *type_text[] = {
+ "Unknown", "Battery", "UPS", "Mains", "USB",
+ "USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
+ "USB_PD", "USB_PD_DRP"
+ };
+ static char *status_text[] = {
+ "Unknown", "Charging", "Discharging", "Not charging", "Full"
+ };
+ static char *charge_type[] = {
+ "Unknown", "N/A", "Trickle", "Fast"
+ };
+ static char *health_text[] = {
+ "Unknown", "Good", "Overheat", "Dead", "Over voltage",
+ "Unspecified failure", "Cold", "Watchdog timer expire",
+ "Safety timer expire"
+ };
+ static char *technology_text[] = {
+ "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
+ "LiMn"
+ };
+ static char *capacity_level_text[] = {
+ "Unknown", "Critical", "Low", "Normal", "High", "Full"
+ };
+ static char *scope_text[] = {
+ "Unknown", "System", "Device"
+ };
+ ssize_t ret = 0;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ const ptrdiff_t off = attr - power_supply_attrs;
+ union power_supply_propval value;
+
+ if (off == POWER_SUPPLY_PROP_TYPE) {
+ value.intval = psy->desc->type;
+ } else {
+ ret = power_supply_get_property(psy, off, &value);
+
+ if (ret < 0) {
+ if (ret == -ENODATA)
+ dev_dbg(dev, "driver has no data for `%s' property\n",
+ attr->attr.name);
+ else if (ret != -ENODEV && ret != -EAGAIN)
+ dev_err(dev, "driver failed to report `%s' property: %zd\n",
+ attr->attr.name, ret);
+ return ret;
+ }
+ }
+
+ if (off == POWER_SUPPLY_PROP_STATUS)
+ return sprintf(buf, "%s\n", status_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE)
+ return sprintf(buf, "%s\n", charge_type[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_HEALTH)
+ return sprintf(buf, "%s\n", health_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TECHNOLOGY)
+ return sprintf(buf, "%s\n", technology_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
+ return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_TYPE)
+ return sprintf(buf, "%s\n", type_text[value.intval]);
+ else if (off == POWER_SUPPLY_PROP_SCOPE)
+ return sprintf(buf, "%s\n", scope_text[value.intval]);
+ else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
+ return sprintf(buf, "%s\n", value.strval);
+
+ return sprintf(buf, "%d\n", value.intval);
+}
+
+static ssize_t power_supply_store_property(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count) {
+ ssize_t ret;
+ struct power_supply *psy = dev_get_drvdata(dev);
+ const ptrdiff_t off = attr - power_supply_attrs;
+ union power_supply_propval value;
+ long long_val;
+
+ /* TODO: support other types than int */
+ ret = kstrtol(buf, 10, &long_val);
+ if (ret < 0)
+ return ret;
+
+ value.intval = long_val;
+
+ ret = power_supply_set_property(psy, off, &value);
+ if (ret < 0)
+ return ret;
+
+ return count;
+}
+
+/* Must be in the same order as POWER_SUPPLY_PROP_* */
+static struct device_attribute power_supply_attrs[] = {
+ /* Properties of type `int' */
+ POWER_SUPPLY_ATTR(status),
+ POWER_SUPPLY_ATTR(charge_type),
+ POWER_SUPPLY_ATTR(health),
+ POWER_SUPPLY_ATTR(present),
+ POWER_SUPPLY_ATTR(online),
+ POWER_SUPPLY_ATTR(authentic),
+ POWER_SUPPLY_ATTR(technology),
+ POWER_SUPPLY_ATTR(cycle_count),
+ POWER_SUPPLY_ATTR(voltage_max),
+ POWER_SUPPLY_ATTR(voltage_min),
+ POWER_SUPPLY_ATTR(voltage_max_design),
+ POWER_SUPPLY_ATTR(voltage_min_design),
+ POWER_SUPPLY_ATTR(voltage_now),
+ POWER_SUPPLY_ATTR(voltage_avg),
+ POWER_SUPPLY_ATTR(voltage_ocv),
+ POWER_SUPPLY_ATTR(voltage_boot),
+ POWER_SUPPLY_ATTR(current_max),
+ POWER_SUPPLY_ATTR(current_now),
+ POWER_SUPPLY_ATTR(current_avg),
+ POWER_SUPPLY_ATTR(current_boot),
+ POWER_SUPPLY_ATTR(power_now),
+ POWER_SUPPLY_ATTR(power_avg),
+ POWER_SUPPLY_ATTR(charge_full_design),
+ POWER_SUPPLY_ATTR(charge_empty_design),
+ POWER_SUPPLY_ATTR(charge_full),
+ POWER_SUPPLY_ATTR(charge_empty),
+ POWER_SUPPLY_ATTR(charge_now),
+ POWER_SUPPLY_ATTR(charge_avg),
+ POWER_SUPPLY_ATTR(charge_counter),
+ POWER_SUPPLY_ATTR(constant_charge_current),
+ POWER_SUPPLY_ATTR(constant_charge_current_max),
+ POWER_SUPPLY_ATTR(constant_charge_voltage),
+ POWER_SUPPLY_ATTR(constant_charge_voltage_max),
+ POWER_SUPPLY_ATTR(charge_control_limit),
+ POWER_SUPPLY_ATTR(charge_control_limit_max),
+ POWER_SUPPLY_ATTR(input_current_limit),
+ POWER_SUPPLY_ATTR(energy_full_design),
+ POWER_SUPPLY_ATTR(energy_empty_design),
+ POWER_SUPPLY_ATTR(energy_full),
+ POWER_SUPPLY_ATTR(energy_empty),
+ POWER_SUPPLY_ATTR(energy_now),
+ POWER_SUPPLY_ATTR(energy_avg),
+ POWER_SUPPLY_ATTR(capacity),
+ POWER_SUPPLY_ATTR(capacity_alert_min),
+ POWER_SUPPLY_ATTR(capacity_alert_max),
+ POWER_SUPPLY_ATTR(capacity_level),
+ POWER_SUPPLY_ATTR(temp),
+ POWER_SUPPLY_ATTR(temp_max),
+ POWER_SUPPLY_ATTR(temp_min),
+ POWER_SUPPLY_ATTR(temp_alert_min),
+ POWER_SUPPLY_ATTR(temp_alert_max),
+ POWER_SUPPLY_ATTR(temp_ambient),
+ POWER_SUPPLY_ATTR(temp_ambient_alert_min),
+ POWER_SUPPLY_ATTR(temp_ambient_alert_max),
+ POWER_SUPPLY_ATTR(time_to_empty_now),
+ POWER_SUPPLY_ATTR(time_to_empty_avg),
+ POWER_SUPPLY_ATTR(time_to_full_now),
+ POWER_SUPPLY_ATTR(time_to_full_avg),
+ POWER_SUPPLY_ATTR(type),
+ POWER_SUPPLY_ATTR(scope),
+ POWER_SUPPLY_ATTR(charge_term_current),
+ POWER_SUPPLY_ATTR(calibrate),
+ /* Properties of type `const char *' */
+ POWER_SUPPLY_ATTR(model_name),
+ POWER_SUPPLY_ATTR(manufacturer),
+ POWER_SUPPLY_ATTR(serial_number),
+};
+
+static struct attribute *
+__power_supply_attrs[ARRAY_SIZE(power_supply_attrs) + 1];
+
+static umode_t power_supply_attr_is_visible(struct kobject *kobj,
+ struct attribute *attr,
+ int attrno)
+{
+ struct device *dev = container_of(kobj, struct device, kobj);
+ struct power_supply *psy = dev_get_drvdata(dev);
+ umode_t mode = S_IRUSR | S_IRGRP | S_IROTH;
+ int i;
+
+ if (attrno == POWER_SUPPLY_PROP_TYPE)
+ return mode;
+
+ for (i = 0; i < psy->desc->num_properties; i++) {
+ int property = psy->desc->properties[i];
+
+ if (property == attrno) {
+ if (psy->desc->property_is_writeable &&
+ psy->desc->property_is_writeable(psy, property) > 0)
+ mode |= S_IWUSR;
+
+ return mode;
+ }
+ }
+
+ return 0;
+}
+
+static struct attribute_group power_supply_attr_group = {
+ .attrs = __power_supply_attrs,
+ .is_visible = power_supply_attr_is_visible,
+};
+
+static const struct attribute_group *power_supply_attr_groups[] = {
+ &power_supply_attr_group,
+ NULL,
+};
+
+void power_supply_init_attrs(struct device_type *dev_type)
+{
+ int i;
+
+ dev_type->groups = power_supply_attr_groups;
+
+ for (i = 0; i < ARRAY_SIZE(power_supply_attrs); i++)
+ __power_supply_attrs[i] = &power_supply_attrs[i].attr;
+}
+
+static char *kstruprdup(const char *str, gfp_t gfp)
+{
+ char *ret, *ustr;
+
+ ustr = ret = kmalloc(strlen(str) + 1, gfp);
+
+ if (!ret)
+ return NULL;
+
+ while (*str)
+ *ustr++ = toupper(*str++);
+
+ *ustr = 0;
+
+ return ret;
+}
+
+int power_supply_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct power_supply *psy = dev_get_drvdata(dev);
+ int ret = 0, j;
+ char *prop_buf;
+ char *attrname;
+
+ dev_dbg(dev, "uevent\n");
+
+ if (!psy || !psy->desc) {
+ dev_dbg(dev, "No power supply yet\n");
+ return ret;
+ }
+
+ dev_dbg(dev, "POWER_SUPPLY_NAME=%s\n", psy->desc->name);
+
+ ret = add_uevent_var(env, "POWER_SUPPLY_NAME=%s", psy->desc->name);
+ if (ret)
+ return ret;
+
+ prop_buf = (char *)get_zeroed_page(GFP_KERNEL);
+ if (!prop_buf)
+ return -ENOMEM;
+
+ for (j = 0; j < psy->desc->num_properties; j++) {
+ struct device_attribute *attr;
+ char *line;
+
+ attr = &power_supply_attrs[psy->desc->properties[j]];
+
+ ret = power_supply_show_property(dev, attr, prop_buf);
+ if (ret == -ENODEV || ret == -ENODATA) {
+ /* When a battery is absent, we expect -ENODEV. Don't abort;
+ send the uevent with at least the the PRESENT=0 property */
+ ret = 0;
+ continue;
+ }
+
+ if (ret < 0)
+ goto out;
+
+ line = strchr(prop_buf, '\n');
+ if (line)
+ *line = 0;
+
+ attrname = kstruprdup(attr->attr.name, GFP_KERNEL);
+ if (!attrname) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ dev_dbg(dev, "prop %s=%s\n", attrname, prop_buf);
+
+ ret = add_uevent_var(env, "POWER_SUPPLY_%s=%s", attrname, prop_buf);
+ kfree(attrname);
+ if (ret)
+ goto out;
+ }
+
+out:
+ free_page((unsigned long)prop_buf);
+
+ return ret;
+}
diff --git a/drivers/power/supply/qcom_smbb.c b/drivers/power/supply/qcom_smbb.c
new file mode 100644
index 0000000..b5896ba
--- /dev/null
+++ b/drivers/power/supply/qcom_smbb.c
@@ -0,0 +1,972 @@
+/* Copyright (c) 2014, Sony Mobile Communications Inc.
+ *
+ * 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.
+ *
+ * This driver is for the multi-block Switch-Mode Battery Charger and Boost
+ * (SMBB) hardware, found in Qualcomm PM8941 PMICs. The charger is an
+ * integrated, single-cell lithium-ion battery charger.
+ *
+ * Sub-components:
+ * - Charger core
+ * - Buck
+ * - DC charge-path
+ * - USB charge-path
+ * - Battery interface
+ * - Boost (not implemented)
+ * - Misc
+ * - HF-Buck
+ */
+
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/extcon.h>
+
+#define SMBB_CHG_VMAX 0x040
+#define SMBB_CHG_VSAFE 0x041
+#define SMBB_CHG_CFG 0x043
+#define SMBB_CHG_IMAX 0x044
+#define SMBB_CHG_ISAFE 0x045
+#define SMBB_CHG_VIN_MIN 0x047
+#define SMBB_CHG_CTRL 0x049
+#define CTRL_EN BIT(7)
+#define SMBB_CHG_VBAT_WEAK 0x052
+#define SMBB_CHG_IBAT_TERM_CHG 0x05b
+#define IBAT_TERM_CHG_IEOC BIT(7)
+#define IBAT_TERM_CHG_IEOC_BMS BIT(7)
+#define IBAT_TERM_CHG_IEOC_CHG 0
+#define SMBB_CHG_VBAT_DET 0x05d
+#define SMBB_CHG_TCHG_MAX_EN 0x060
+#define TCHG_MAX_EN BIT(7)
+#define SMBB_CHG_WDOG_TIME 0x062
+#define SMBB_CHG_WDOG_EN 0x065
+#define WDOG_EN BIT(7)
+
+#define SMBB_BUCK_REG_MODE 0x174
+#define BUCK_REG_MODE BIT(0)
+#define BUCK_REG_MODE_VBAT BIT(0)
+#define BUCK_REG_MODE_VSYS 0
+
+#define SMBB_BAT_PRES_STATUS 0x208
+#define PRES_STATUS_BAT_PRES BIT(7)
+#define SMBB_BAT_TEMP_STATUS 0x209
+#define TEMP_STATUS_OK BIT(7)
+#define TEMP_STATUS_HOT BIT(6)
+#define SMBB_BAT_BTC_CTRL 0x249
+#define BTC_CTRL_COMP_EN BIT(7)
+#define BTC_CTRL_COLD_EXT BIT(1)
+#define BTC_CTRL_HOT_EXT_N BIT(0)
+
+#define SMBB_USB_IMAX 0x344
+#define SMBB_USB_ENUM_TIMER_STOP 0x34e
+#define ENUM_TIMER_STOP BIT(0)
+#define SMBB_USB_SEC_ACCESS 0x3d0
+#define SEC_ACCESS_MAGIC 0xa5
+#define SMBB_USB_REV_BST 0x3ed
+#define REV_BST_CHG_GONE BIT(7)
+
+#define SMBB_DC_IMAX 0x444
+
+#define SMBB_MISC_REV2 0x601
+#define SMBB_MISC_BOOT_DONE 0x642
+#define BOOT_DONE BIT(7)
+
+#define STATUS_USBIN_VALID BIT(0) /* USB connection is valid */
+#define STATUS_DCIN_VALID BIT(1) /* DC connection is valid */
+#define STATUS_BAT_HOT BIT(2) /* Battery temp 1=Hot, 0=Cold */
+#define STATUS_BAT_OK BIT(3) /* Battery temp OK */
+#define STATUS_BAT_PRESENT BIT(4) /* Battery is present */
+#define STATUS_CHG_DONE BIT(5) /* Charge cycle is complete */
+#define STATUS_CHG_TRKL BIT(6) /* Trickle charging */
+#define STATUS_CHG_FAST BIT(7) /* Fast charging */
+#define STATUS_CHG_GONE BIT(8) /* No charger is connected */
+
+enum smbb_attr {
+ ATTR_BAT_ISAFE,
+ ATTR_BAT_IMAX,
+ ATTR_USBIN_IMAX,
+ ATTR_DCIN_IMAX,
+ ATTR_BAT_VSAFE,
+ ATTR_BAT_VMAX,
+ ATTR_BAT_VMIN,
+ ATTR_CHG_VDET,
+ ATTR_VIN_MIN,
+ _ATTR_CNT,
+};
+
+struct smbb_charger {
+ unsigned int revision;
+ unsigned int addr;
+ struct device *dev;
+ struct extcon_dev *edev;
+
+ bool dc_disabled;
+ bool jeita_ext_temp;
+ unsigned long status;
+ struct mutex statlock;
+
+ unsigned int attr[_ATTR_CNT];
+
+ struct power_supply *usb_psy;
+ struct power_supply *dc_psy;
+ struct power_supply *bat_psy;
+ struct regmap *regmap;
+};
+
+static const unsigned int smbb_usb_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_NONE,
+};
+
+static int smbb_vbat_weak_fn(unsigned int index)
+{
+ return 2100000 + index * 100000;
+}
+
+static int smbb_vin_fn(unsigned int index)
+{
+ if (index > 42)
+ return 5600000 + (index - 43) * 200000;
+ return 3400000 + index * 50000;
+}
+
+static int smbb_vmax_fn(unsigned int index)
+{
+ return 3240000 + index * 10000;
+}
+
+static int smbb_vbat_det_fn(unsigned int index)
+{
+ return 3240000 + index * 20000;
+}
+
+static int smbb_imax_fn(unsigned int index)
+{
+ if (index < 2)
+ return 100000 + index * 50000;
+ return index * 100000;
+}
+
+static int smbb_bat_imax_fn(unsigned int index)
+{
+ return index * 50000;
+}
+
+static unsigned int smbb_hw_lookup(unsigned int val, int (*fn)(unsigned int))
+{
+ unsigned int widx;
+ unsigned int sel;
+
+ for (widx = sel = 0; (*fn)(widx) <= val; ++widx)
+ sel = widx;
+
+ return sel;
+}
+
+static const struct smbb_charger_attr {
+ const char *name;
+ unsigned int reg;
+ unsigned int safe_reg;
+ unsigned int max;
+ unsigned int min;
+ unsigned int fail_ok;
+ int (*hw_fn)(unsigned int);
+} smbb_charger_attrs[] = {
+ [ATTR_BAT_ISAFE] = {
+ .name = "qcom,fast-charge-safe-current",
+ .reg = SMBB_CHG_ISAFE,
+ .max = 3000000,
+ .min = 200000,
+ .hw_fn = smbb_bat_imax_fn,
+ .fail_ok = 1,
+ },
+ [ATTR_BAT_IMAX] = {
+ .name = "qcom,fast-charge-current-limit",
+ .reg = SMBB_CHG_IMAX,
+ .safe_reg = SMBB_CHG_ISAFE,
+ .max = 3000000,
+ .min = 200000,
+ .hw_fn = smbb_bat_imax_fn,
+ },
+ [ATTR_DCIN_IMAX] = {
+ .name = "qcom,dc-current-limit",
+ .reg = SMBB_DC_IMAX,
+ .max = 2500000,
+ .min = 100000,
+ .hw_fn = smbb_imax_fn,
+ },
+ [ATTR_BAT_VSAFE] = {
+ .name = "qcom,fast-charge-safe-voltage",
+ .reg = SMBB_CHG_VSAFE,
+ .max = 5000000,
+ .min = 3240000,
+ .hw_fn = smbb_vmax_fn,
+ .fail_ok = 1,
+ },
+ [ATTR_BAT_VMAX] = {
+ .name = "qcom,fast-charge-high-threshold-voltage",
+ .reg = SMBB_CHG_VMAX,
+ .safe_reg = SMBB_CHG_VSAFE,
+ .max = 5000000,
+ .min = 3240000,
+ .hw_fn = smbb_vmax_fn,
+ },
+ [ATTR_BAT_VMIN] = {
+ .name = "qcom,fast-charge-low-threshold-voltage",
+ .reg = SMBB_CHG_VBAT_WEAK,
+ .max = 3600000,
+ .min = 2100000,
+ .hw_fn = smbb_vbat_weak_fn,
+ },
+ [ATTR_CHG_VDET] = {
+ .name = "qcom,auto-recharge-threshold-voltage",
+ .reg = SMBB_CHG_VBAT_DET,
+ .max = 5000000,
+ .min = 3240000,
+ .hw_fn = smbb_vbat_det_fn,
+ },
+ [ATTR_VIN_MIN] = {
+ .name = "qcom,minimum-input-voltage",
+ .reg = SMBB_CHG_VIN_MIN,
+ .max = 9600000,
+ .min = 4200000,
+ .hw_fn = smbb_vin_fn,
+ },
+ [ATTR_USBIN_IMAX] = {
+ .name = "usb-charge-current-limit",
+ .reg = SMBB_USB_IMAX,
+ .max = 2500000,
+ .min = 100000,
+ .hw_fn = smbb_imax_fn,
+ },
+};
+
+static int smbb_charger_attr_write(struct smbb_charger *chg,
+ enum smbb_attr which, unsigned int val)
+{
+ const struct smbb_charger_attr *prop;
+ unsigned int wval;
+ unsigned int out;
+ int rc;
+
+ prop = &smbb_charger_attrs[which];
+
+ if (val > prop->max || val < prop->min) {
+ dev_err(chg->dev, "value out of range for %s [%u:%u]\n",
+ prop->name, prop->min, prop->max);
+ return -EINVAL;
+ }
+
+ if (prop->safe_reg) {
+ rc = regmap_read(chg->regmap,
+ chg->addr + prop->safe_reg, &wval);
+ if (rc) {
+ dev_err(chg->dev,
+ "unable to read safe value for '%s'\n",
+ prop->name);
+ return rc;
+ }
+
+ wval = prop->hw_fn(wval);
+
+ if (val > wval) {
+ dev_warn(chg->dev,
+ "%s above safe value, clamping at %u\n",
+ prop->name, wval);
+ val = wval;
+ }
+ }
+
+ wval = smbb_hw_lookup(val, prop->hw_fn);
+
+ rc = regmap_write(chg->regmap, chg->addr + prop->reg, wval);
+ if (rc) {
+ dev_err(chg->dev, "unable to update %s", prop->name);
+ return rc;
+ }
+ out = prop->hw_fn(wval);
+ if (out != val) {
+ dev_warn(chg->dev,
+ "%s inaccurate, rounded to %u\n",
+ prop->name, out);
+ }
+
+ dev_dbg(chg->dev, "%s <= %d\n", prop->name, out);
+
+ chg->attr[which] = out;
+
+ return 0;
+}
+
+static int smbb_charger_attr_read(struct smbb_charger *chg,
+ enum smbb_attr which)
+{
+ const struct smbb_charger_attr *prop;
+ unsigned int val;
+ int rc;
+
+ prop = &smbb_charger_attrs[which];
+
+ rc = regmap_read(chg->regmap, chg->addr + prop->reg, &val);
+ if (rc) {
+ dev_err(chg->dev, "failed to read %s\n", prop->name);
+ return rc;
+ }
+ val = prop->hw_fn(val);
+ dev_dbg(chg->dev, "%s => %d\n", prop->name, val);
+
+ chg->attr[which] = val;
+
+ return 0;
+}
+
+static int smbb_charger_attr_parse(struct smbb_charger *chg,
+ enum smbb_attr which)
+{
+ const struct smbb_charger_attr *prop;
+ unsigned int val;
+ int rc;
+
+ prop = &smbb_charger_attrs[which];
+
+ rc = of_property_read_u32(chg->dev->of_node, prop->name, &val);
+ if (rc == 0) {
+ rc = smbb_charger_attr_write(chg, which, val);
+ if (!rc || !prop->fail_ok)
+ return rc;
+ }
+ return smbb_charger_attr_read(chg, which);
+}
+
+static void smbb_set_line_flag(struct smbb_charger *chg, int irq, int flag)
+{
+ bool state;
+ int ret;
+
+ ret = irq_get_irqchip_state(irq, IRQCHIP_STATE_LINE_LEVEL, &state);
+ if (ret < 0) {
+ dev_err(chg->dev, "failed to read irq line\n");
+ return;
+ }
+
+ mutex_lock(&chg->statlock);
+ if (state)
+ chg->status |= flag;
+ else
+ chg->status &= ~flag;
+ mutex_unlock(&chg->statlock);
+
+ dev_dbg(chg->dev, "status = %03lx\n", chg->status);
+}
+
+static irqreturn_t smbb_usb_valid_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_USBIN_VALID);
+ extcon_set_cable_state_(chg->edev, EXTCON_USB,
+ chg->status & STATUS_USBIN_VALID);
+ power_supply_changed(chg->usb_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_dc_valid_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_DCIN_VALID);
+ if (!chg->dc_disabled)
+ power_supply_changed(chg->dc_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_bat_temp_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+ unsigned int val;
+ int rc;
+
+ rc = regmap_read(chg->regmap, chg->addr + SMBB_BAT_TEMP_STATUS, &val);
+ if (rc)
+ return IRQ_HANDLED;
+
+ mutex_lock(&chg->statlock);
+ if (val & TEMP_STATUS_OK) {
+ chg->status |= STATUS_BAT_OK;
+ } else {
+ chg->status &= ~STATUS_BAT_OK;
+ if (val & TEMP_STATUS_HOT)
+ chg->status |= STATUS_BAT_HOT;
+ }
+ mutex_unlock(&chg->statlock);
+
+ power_supply_changed(chg->bat_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_bat_present_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_BAT_PRESENT);
+ power_supply_changed(chg->bat_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_chg_done_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_CHG_DONE);
+ power_supply_changed(chg->bat_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_chg_gone_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_CHG_GONE);
+ power_supply_changed(chg->bat_psy);
+ power_supply_changed(chg->usb_psy);
+ if (!chg->dc_disabled)
+ power_supply_changed(chg->dc_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_chg_fast_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_CHG_FAST);
+ power_supply_changed(chg->bat_psy);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t smbb_chg_trkl_handler(int irq, void *_data)
+{
+ struct smbb_charger *chg = _data;
+
+ smbb_set_line_flag(chg, irq, STATUS_CHG_TRKL);
+ power_supply_changed(chg->bat_psy);
+
+ return IRQ_HANDLED;
+}
+
+static const struct smbb_irq {
+ const char *name;
+ irqreturn_t (*handler)(int, void *);
+} smbb_charger_irqs[] = {
+ { "chg-done", smbb_chg_done_handler },
+ { "chg-fast", smbb_chg_fast_handler },
+ { "chg-trkl", smbb_chg_trkl_handler },
+ { "bat-temp-ok", smbb_bat_temp_handler },
+ { "bat-present", smbb_bat_present_handler },
+ { "chg-gone", smbb_chg_gone_handler },
+ { "usb-valid", smbb_usb_valid_handler },
+ { "dc-valid", smbb_dc_valid_handler },
+};
+
+static int smbb_usbin_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ mutex_lock(&chg->statlock);
+ val->intval = !(chg->status & STATUS_CHG_GONE) &&
+ (chg->status & STATUS_USBIN_VALID);
+ mutex_unlock(&chg->statlock);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ val->intval = chg->attr[ATTR_USBIN_IMAX];
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ val->intval = 2500000;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_usbin_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ rc = smbb_charger_attr_write(chg, ATTR_USBIN_IMAX,
+ val->intval);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_dcin_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ int rc = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ mutex_lock(&chg->statlock);
+ val->intval = !(chg->status & STATUS_CHG_GONE) &&
+ (chg->status & STATUS_DCIN_VALID);
+ mutex_unlock(&chg->statlock);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ val->intval = chg->attr[ATTR_DCIN_IMAX];
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX:
+ val->intval = 2500000;
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_dcin_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT:
+ rc = smbb_charger_attr_write(chg, ATTR_DCIN_IMAX,
+ val->intval);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_charger_writable_property(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ return psp == POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT;
+}
+
+static int smbb_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ unsigned long status;
+ int rc = 0;
+
+ mutex_lock(&chg->statlock);
+ status = chg->status;
+ mutex_unlock(&chg->statlock);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (status & STATUS_CHG_GONE)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (!(status & (STATUS_DCIN_VALID | STATUS_USBIN_VALID)))
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (status & STATUS_CHG_DONE)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (!(status & STATUS_BAT_OK))
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else if (status & (STATUS_CHG_FAST | STATUS_CHG_TRKL))
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else /* everything is ok for charging, but we are not... */
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ if (status & STATUS_BAT_OK)
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ else if (status & STATUS_BAT_HOT)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_COLD;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (status & STATUS_CHG_FAST)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (status & STATUS_CHG_TRKL)
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ else
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = !!(status & STATUS_BAT_PRESENT);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = chg->attr[ATTR_BAT_IMAX];
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = chg->attr[ATTR_BAT_VMAX];
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ /* this charger is a single-cell lithium-ion battery charger
+ * only. If you hook up some other technology, there will be
+ * fireworks.
+ */
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = 3000000; /* single-cell li-ion low end */
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_battery_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smbb_charger *chg = power_supply_get_drvdata(psy);
+ int rc;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = smbb_charger_attr_write(chg, ATTR_BAT_IMAX, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smbb_charger_attr_write(chg, ATTR_BAT_VMAX, val->intval);
+ break;
+ default:
+ rc = -EINVAL;
+ break;
+ }
+
+ return rc;
+}
+
+static int smbb_battery_writable_property(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+static enum power_supply_property smbb_charger_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT,
+ POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX,
+};
+
+static enum power_supply_property smbb_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+};
+
+static const struct reg_off_mask_default {
+ unsigned int offset;
+ unsigned int mask;
+ unsigned int value;
+ unsigned int rev_mask;
+} smbb_charger_setup[] = {
+ /* The bootloader is supposed to set this... make sure anyway. */
+ { SMBB_MISC_BOOT_DONE, BOOT_DONE, BOOT_DONE },
+
+ /* Disable software timer */
+ { SMBB_CHG_TCHG_MAX_EN, TCHG_MAX_EN, 0 },
+
+ /* Clear and disable watchdog */
+ { SMBB_CHG_WDOG_TIME, 0xff, 160 },
+ { SMBB_CHG_WDOG_EN, WDOG_EN, 0 },
+
+ /* Use charger based EoC detection */
+ { SMBB_CHG_IBAT_TERM_CHG, IBAT_TERM_CHG_IEOC, IBAT_TERM_CHG_IEOC_CHG },
+
+ /* Disable GSM PA load adjustment.
+ * The PA signal is incorrectly connected on v2.
+ */
+ { SMBB_CHG_CFG, 0xff, 0x00, BIT(3) },
+
+ /* Use VBAT (not VSYS) to compensate for IR drop during fast charging */
+ { SMBB_BUCK_REG_MODE, BUCK_REG_MODE, BUCK_REG_MODE_VBAT },
+
+ /* Enable battery temperature comparators */
+ { SMBB_BAT_BTC_CTRL, BTC_CTRL_COMP_EN, BTC_CTRL_COMP_EN },
+
+ /* Stop USB enumeration timer */
+ { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP },
+
+#if 0 /* FIXME supposedly only to disable hardware ARB termination */
+ { SMBB_USB_SEC_ACCESS, SEC_ACCESS_MAGIC },
+ { SMBB_USB_REV_BST, 0xff, REV_BST_CHG_GONE },
+#endif
+
+ /* Stop USB enumeration timer, again */
+ { SMBB_USB_ENUM_TIMER_STOP, ENUM_TIMER_STOP, ENUM_TIMER_STOP },
+
+ /* Enable charging */
+ { SMBB_CHG_CTRL, CTRL_EN, CTRL_EN },
+};
+
+static char *smbb_bif[] = { "smbb-bif" };
+
+static const struct power_supply_desc bat_psy_desc = {
+ .name = "smbb-bif",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = smbb_battery_properties,
+ .num_properties = ARRAY_SIZE(smbb_battery_properties),
+ .get_property = smbb_battery_get_property,
+ .set_property = smbb_battery_set_property,
+ .property_is_writeable = smbb_battery_writable_property,
+};
+
+static const struct power_supply_desc usb_psy_desc = {
+ .name = "smbb-usbin",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = smbb_charger_properties,
+ .num_properties = ARRAY_SIZE(smbb_charger_properties),
+ .get_property = smbb_usbin_get_property,
+ .set_property = smbb_usbin_set_property,
+ .property_is_writeable = smbb_charger_writable_property,
+};
+
+static const struct power_supply_desc dc_psy_desc = {
+ .name = "smbb-dcin",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = smbb_charger_properties,
+ .num_properties = ARRAY_SIZE(smbb_charger_properties),
+ .get_property = smbb_dcin_get_property,
+ .set_property = smbb_dcin_set_property,
+ .property_is_writeable = smbb_charger_writable_property,
+};
+
+static int smbb_charger_probe(struct platform_device *pdev)
+{
+ struct power_supply_config bat_cfg = {};
+ struct power_supply_config usb_cfg = {};
+ struct power_supply_config dc_cfg = {};
+ struct smbb_charger *chg;
+ int rc, i;
+
+ chg = devm_kzalloc(&pdev->dev, sizeof(*chg), GFP_KERNEL);
+ if (!chg)
+ return -ENOMEM;
+
+ chg->dev = &pdev->dev;
+ mutex_init(&chg->statlock);
+
+ chg->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chg->regmap) {
+ dev_err(&pdev->dev, "failed to locate regmap\n");
+ return -ENODEV;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &chg->addr);
+ if (rc) {
+ dev_err(&pdev->dev, "missing or invalid 'reg' property\n");
+ return rc;
+ }
+
+ rc = regmap_read(chg->regmap, chg->addr + SMBB_MISC_REV2, &chg->revision);
+ if (rc) {
+ dev_err(&pdev->dev, "unable to read revision\n");
+ return rc;
+ }
+
+ chg->revision += 1;
+ if (chg->revision != 2 && chg->revision != 3) {
+ dev_err(&pdev->dev, "v1 hardware not supported\n");
+ return -ENODEV;
+ }
+ dev_info(&pdev->dev, "Initializing SMBB rev %u", chg->revision);
+
+ chg->dc_disabled = of_property_read_bool(pdev->dev.of_node, "qcom,disable-dc");
+
+ for (i = 0; i < _ATTR_CNT; ++i) {
+ rc = smbb_charger_attr_parse(chg, i);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to parse/apply settings\n");
+ return rc;
+ }
+ }
+
+ bat_cfg.drv_data = chg;
+ bat_cfg.of_node = pdev->dev.of_node;
+ chg->bat_psy = devm_power_supply_register(&pdev->dev,
+ &bat_psy_desc,
+ &bat_cfg);
+ if (IS_ERR(chg->bat_psy)) {
+ dev_err(&pdev->dev, "failed to register battery\n");
+ return PTR_ERR(chg->bat_psy);
+ }
+
+ usb_cfg.drv_data = chg;
+ usb_cfg.supplied_to = smbb_bif;
+ usb_cfg.num_supplicants = ARRAY_SIZE(smbb_bif);
+ chg->usb_psy = devm_power_supply_register(&pdev->dev,
+ &usb_psy_desc,
+ &usb_cfg);
+ if (IS_ERR(chg->usb_psy)) {
+ dev_err(&pdev->dev, "failed to register USB power supply\n");
+ return PTR_ERR(chg->usb_psy);
+ }
+
+ chg->edev = devm_extcon_dev_allocate(&pdev->dev, smbb_usb_extcon_cable);
+ if (IS_ERR(chg->edev)) {
+ dev_err(&pdev->dev, "failed to allocate extcon device\n");
+ return -ENOMEM;
+ }
+
+ rc = devm_extcon_dev_register(&pdev->dev, chg->edev);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "failed to register extcon device\n");
+ return rc;
+ }
+
+ if (!chg->dc_disabled) {
+ dc_cfg.drv_data = chg;
+ dc_cfg.supplied_to = smbb_bif;
+ dc_cfg.num_supplicants = ARRAY_SIZE(smbb_bif);
+ chg->dc_psy = devm_power_supply_register(&pdev->dev,
+ &dc_psy_desc,
+ &dc_cfg);
+ if (IS_ERR(chg->dc_psy)) {
+ dev_err(&pdev->dev, "failed to register DC power supply\n");
+ return PTR_ERR(chg->dc_psy);
+ }
+ }
+
+ for (i = 0; i < ARRAY_SIZE(smbb_charger_irqs); ++i) {
+ int irq;
+
+ irq = platform_get_irq_byname(pdev, smbb_charger_irqs[i].name);
+ if (irq < 0) {
+ dev_err(&pdev->dev, "failed to get irq '%s'\n",
+ smbb_charger_irqs[i].name);
+ return irq;
+ }
+
+ smbb_charger_irqs[i].handler(irq, chg);
+
+ rc = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ smbb_charger_irqs[i].handler, IRQF_ONESHOT,
+ smbb_charger_irqs[i].name, chg);
+ if (rc) {
+ dev_err(&pdev->dev, "failed to request irq '%s'\n",
+ smbb_charger_irqs[i].name);
+ return rc;
+ }
+ }
+
+ chg->jeita_ext_temp = of_property_read_bool(pdev->dev.of_node,
+ "qcom,jeita-extended-temp-range");
+
+ /* Set temperature range to [35%:70%] or [25%:80%] accordingly */
+ rc = regmap_update_bits(chg->regmap, chg->addr + SMBB_BAT_BTC_CTRL,
+ BTC_CTRL_COLD_EXT | BTC_CTRL_HOT_EXT_N,
+ chg->jeita_ext_temp ?
+ BTC_CTRL_COLD_EXT :
+ BTC_CTRL_HOT_EXT_N);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "unable to set %s temperature range\n",
+ chg->jeita_ext_temp ? "JEITA extended" : "normal");
+ return rc;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(smbb_charger_setup); ++i) {
+ const struct reg_off_mask_default *r = &smbb_charger_setup[i];
+
+ if (r->rev_mask & BIT(chg->revision))
+ continue;
+
+ rc = regmap_update_bits(chg->regmap, chg->addr + r->offset,
+ r->mask, r->value);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "unable to initializing charging, bailing\n");
+ return rc;
+ }
+ }
+
+ platform_set_drvdata(pdev, chg);
+
+ return 0;
+}
+
+static int smbb_charger_remove(struct platform_device *pdev)
+{
+ struct smbb_charger *chg;
+
+ chg = platform_get_drvdata(pdev);
+
+ regmap_update_bits(chg->regmap, chg->addr + SMBB_CHG_CTRL, CTRL_EN, 0);
+
+ return 0;
+}
+
+static const struct of_device_id smbb_charger_id_table[] = {
+ { .compatible = "qcom,pm8941-charger" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, smbb_charger_id_table);
+
+static struct platform_driver smbb_charger_driver = {
+ .probe = smbb_charger_probe,
+ .remove = smbb_charger_remove,
+ .driver = {
+ .name = "qcom-smbb",
+ .of_match_table = smbb_charger_id_table,
+ },
+};
+module_platform_driver(smbb_charger_driver);
+
+MODULE_DESCRIPTION("Qualcomm Switch-Mode Battery Charger and Boost driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/rt5033_battery.c b/drivers/power/supply/rt5033_battery.c
new file mode 100644
index 0000000..bcdd830
--- /dev/null
+++ b/drivers/power/supply/rt5033_battery.c
@@ -0,0 +1,182 @@
+/*
+ * Fuel gauge driver for Richtek RT5033
+ *
+ * Copyright (C) 2014 Samsung Electronics, Co., Ltd.
+ * Author: Beomho Seo <beomho.seo@samsung.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 bythe Free Software Foundation.
+ */
+
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/rt5033-private.h>
+#include <linux/mfd/rt5033.h>
+
+static int rt5033_battery_get_capacity(struct i2c_client *client)
+{
+ struct rt5033_battery *battery = i2c_get_clientdata(client);
+ u32 msb;
+
+ regmap_read(battery->regmap, RT5033_FUEL_REG_SOC_H, &msb);
+
+ return msb;
+}
+
+static int rt5033_battery_get_present(struct i2c_client *client)
+{
+ struct rt5033_battery *battery = i2c_get_clientdata(client);
+ u32 val;
+
+ regmap_read(battery->regmap, RT5033_FUEL_REG_CONFIG_L, &val);
+
+ return (val & RT5033_FUEL_BAT_PRESENT) ? true : false;
+}
+
+static int rt5033_battery_get_watt_prop(struct i2c_client *client,
+ enum power_supply_property psp)
+{
+ struct rt5033_battery *battery = i2c_get_clientdata(client);
+ unsigned int regh, regl;
+ int ret;
+ u32 msb, lsb;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ regh = RT5033_FUEL_REG_VBAT_H;
+ regl = RT5033_FUEL_REG_VBAT_L;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ regh = RT5033_FUEL_REG_AVG_VOLT_H;
+ regl = RT5033_FUEL_REG_AVG_VOLT_L;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ regh = RT5033_FUEL_REG_OCV_H;
+ regl = RT5033_FUEL_REG_OCV_L;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ regmap_read(battery->regmap, regh, &msb);
+ regmap_read(battery->regmap, regl, &lsb);
+
+ ret = ((msb << 4) + (lsb >> 4)) * 1250 / 1000;
+
+ return ret;
+}
+
+static int rt5033_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt5033_battery *battery = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = rt5033_battery_get_watt_prop(battery->client,
+ psp);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = rt5033_battery_get_present(battery->client);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = rt5033_battery_get_capacity(battery->client);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property rt5033_battery_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_AVG,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY,
+};
+
+static const struct regmap_config rt5033_battery_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = RT5033_FUEL_REG_END,
+};
+
+static const struct power_supply_desc rt5033_battery_desc = {
+ .name = "rt5033-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = rt5033_battery_get_property,
+ .properties = rt5033_battery_props,
+ .num_properties = ARRAY_SIZE(rt5033_battery_props),
+};
+
+static int rt5033_battery_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent);
+ struct power_supply_config psy_cfg = {};
+ struct rt5033_battery *battery;
+ u32 ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE))
+ return -EIO;
+
+ battery = devm_kzalloc(&client->dev, sizeof(*battery), GFP_KERNEL);
+ if (!battery)
+ return -EINVAL;
+
+ battery->client = client;
+ battery->regmap = devm_regmap_init_i2c(client,
+ &rt5033_battery_regmap_config);
+ if (IS_ERR(battery->regmap)) {
+ dev_err(&client->dev, "Failed to initialize regmap\n");
+ return -EINVAL;
+ }
+
+ i2c_set_clientdata(client, battery);
+ psy_cfg.drv_data = battery;
+
+ battery->psy = power_supply_register(&client->dev,
+ &rt5033_battery_desc, &psy_cfg);
+ if (IS_ERR(battery->psy)) {
+ dev_err(&client->dev, "Failed to register power supply\n");
+ ret = PTR_ERR(battery->psy);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rt5033_battery_remove(struct i2c_client *client)
+{
+ struct rt5033_battery *battery = i2c_get_clientdata(client);
+
+ power_supply_unregister(battery->psy);
+
+ return 0;
+}
+
+static const struct i2c_device_id rt5033_battery_id[] = {
+ { "rt5033-battery", },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, rt5033_battery_id);
+
+static struct i2c_driver rt5033_battery_driver = {
+ .driver = {
+ .name = "rt5033-battery",
+ },
+ .probe = rt5033_battery_probe,
+ .remove = rt5033_battery_remove,
+ .id_table = rt5033_battery_id,
+};
+module_i2c_driver(rt5033_battery_driver);
+
+MODULE_DESCRIPTION("Richtek RT5033 fuel gauge driver");
+MODULE_AUTHOR("Beomho Seo <beomho.seo@samsung.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/rt9455_charger.c b/drivers/power/supply/rt9455_charger.c
new file mode 100644
index 0000000..cfdbde9
--- /dev/null
+++ b/drivers/power/supply/rt9455_charger.c
@@ -0,0 +1,1763 @@
+/*
+ * Driver for Richtek RT9455WSC battery charger.
+ *
+ * Copyright (C) 2015 Intel Corporation
+ *
+ * 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.
+ */
+
+#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/i2c.h>
+#include <linux/acpi.h>
+#include <linux/usb/phy.h>
+#include <linux/regmap.h>
+
+#define RT9455_MANUFACTURER "Richtek"
+#define RT9455_MODEL_NAME "RT9455"
+#define RT9455_DRIVER_NAME "rt9455-charger"
+
+#define RT9455_IRQ_NAME "interrupt"
+
+#define RT9455_PWR_RDY_DELAY 1 /* 1 second */
+#define RT9455_MAX_CHARGING_TIME 21600 /* 6 hrs */
+#define RT9455_BATT_PRESENCE_DELAY 60 /* 60 seconds */
+
+#define RT9455_CHARGE_MODE 0x00
+#define RT9455_BOOST_MODE 0x01
+
+#define RT9455_FAULT 0x03
+
+#define RT9455_IAICR_100MA 0x00
+#define RT9455_IAICR_500MA 0x01
+#define RT9455_IAICR_NO_LIMIT 0x03
+
+#define RT9455_CHARGE_DISABLE 0x00
+#define RT9455_CHARGE_ENABLE 0x01
+
+#define RT9455_PWR_FAULT 0x00
+#define RT9455_PWR_GOOD 0x01
+
+#define RT9455_REG_CTRL1 0x00 /* CTRL1 reg address */
+#define RT9455_REG_CTRL2 0x01 /* CTRL2 reg address */
+#define RT9455_REG_CTRL3 0x02 /* CTRL3 reg address */
+#define RT9455_REG_DEV_ID 0x03 /* DEV_ID reg address */
+#define RT9455_REG_CTRL4 0x04 /* CTRL4 reg address */
+#define RT9455_REG_CTRL5 0x05 /* CTRL5 reg address */
+#define RT9455_REG_CTRL6 0x06 /* CTRL6 reg address */
+#define RT9455_REG_CTRL7 0x07 /* CTRL7 reg address */
+#define RT9455_REG_IRQ1 0x08 /* IRQ1 reg address */
+#define RT9455_REG_IRQ2 0x09 /* IRQ2 reg address */
+#define RT9455_REG_IRQ3 0x0A /* IRQ3 reg address */
+#define RT9455_REG_MASK1 0x0B /* MASK1 reg address */
+#define RT9455_REG_MASK2 0x0C /* MASK2 reg address */
+#define RT9455_REG_MASK3 0x0D /* MASK3 reg address */
+
+enum rt9455_fields {
+ F_STAT, F_BOOST, F_PWR_RDY, F_OTG_PIN_POLARITY, /* CTRL1 reg fields */
+
+ F_IAICR, F_TE_SHDN_EN, F_HIGHER_OCP, F_TE, F_IAICR_INT, F_HIZ,
+ F_OPA_MODE, /* CTRL2 reg fields */
+
+ F_VOREG, F_OTG_PL, F_OTG_EN, /* CTRL3 reg fields */
+
+ F_VENDOR_ID, F_CHIP_REV, /* DEV_ID reg fields */
+
+ F_RST, /* CTRL4 reg fields */
+
+ F_TMR_EN, F_MIVR, F_IPREC, F_IEOC_PERCENTAGE, /* CTRL5 reg fields*/
+
+ F_IAICR_SEL, F_ICHRG, F_VPREC, /* CTRL6 reg fields */
+
+ F_BATD_EN, F_CHG_EN, F_VMREG, /* CTRL7 reg fields */
+
+ F_TSDI, F_VINOVPI, F_BATAB, /* IRQ1 reg fields */
+
+ F_CHRVPI, F_CHBATOVI, F_CHTERMI, F_CHRCHGI, F_CH32MI, F_CHTREGI,
+ F_CHMIVRI, /* IRQ2 reg fields */
+
+ F_BSTBUSOVI, F_BSTOLI, F_BSTLOWVI, F_BST32SI, /* IRQ3 reg fields */
+
+ F_TSDM, F_VINOVPIM, F_BATABM, /* MASK1 reg fields */
+
+ F_CHRVPIM, F_CHBATOVIM, F_CHTERMIM, F_CHRCHGIM, F_CH32MIM, F_CHTREGIM,
+ F_CHMIVRIM, /* MASK2 reg fields */
+
+ F_BSTVINOVIM, F_BSTOLIM, F_BSTLOWVIM, F_BST32SIM, /* MASK3 reg fields */
+
+ F_MAX_FIELDS
+};
+
+static const struct reg_field rt9455_reg_fields[] = {
+ [F_STAT] = REG_FIELD(RT9455_REG_CTRL1, 4, 5),
+ [F_BOOST] = REG_FIELD(RT9455_REG_CTRL1, 3, 3),
+ [F_PWR_RDY] = REG_FIELD(RT9455_REG_CTRL1, 2, 2),
+ [F_OTG_PIN_POLARITY] = REG_FIELD(RT9455_REG_CTRL1, 1, 1),
+
+ [F_IAICR] = REG_FIELD(RT9455_REG_CTRL2, 6, 7),
+ [F_TE_SHDN_EN] = REG_FIELD(RT9455_REG_CTRL2, 5, 5),
+ [F_HIGHER_OCP] = REG_FIELD(RT9455_REG_CTRL2, 4, 4),
+ [F_TE] = REG_FIELD(RT9455_REG_CTRL2, 3, 3),
+ [F_IAICR_INT] = REG_FIELD(RT9455_REG_CTRL2, 2, 2),
+ [F_HIZ] = REG_FIELD(RT9455_REG_CTRL2, 1, 1),
+ [F_OPA_MODE] = REG_FIELD(RT9455_REG_CTRL2, 0, 0),
+
+ [F_VOREG] = REG_FIELD(RT9455_REG_CTRL3, 2, 7),
+ [F_OTG_PL] = REG_FIELD(RT9455_REG_CTRL3, 1, 1),
+ [F_OTG_EN] = REG_FIELD(RT9455_REG_CTRL3, 0, 0),
+
+ [F_VENDOR_ID] = REG_FIELD(RT9455_REG_DEV_ID, 4, 7),
+ [F_CHIP_REV] = REG_FIELD(RT9455_REG_DEV_ID, 0, 3),
+
+ [F_RST] = REG_FIELD(RT9455_REG_CTRL4, 7, 7),
+
+ [F_TMR_EN] = REG_FIELD(RT9455_REG_CTRL5, 7, 7),
+ [F_MIVR] = REG_FIELD(RT9455_REG_CTRL5, 4, 5),
+ [F_IPREC] = REG_FIELD(RT9455_REG_CTRL5, 2, 3),
+ [F_IEOC_PERCENTAGE] = REG_FIELD(RT9455_REG_CTRL5, 0, 1),
+
+ [F_IAICR_SEL] = REG_FIELD(RT9455_REG_CTRL6, 7, 7),
+ [F_ICHRG] = REG_FIELD(RT9455_REG_CTRL6, 4, 6),
+ [F_VPREC] = REG_FIELD(RT9455_REG_CTRL6, 0, 2),
+
+ [F_BATD_EN] = REG_FIELD(RT9455_REG_CTRL7, 6, 6),
+ [F_CHG_EN] = REG_FIELD(RT9455_REG_CTRL7, 4, 4),
+ [F_VMREG] = REG_FIELD(RT9455_REG_CTRL7, 0, 3),
+
+ [F_TSDI] = REG_FIELD(RT9455_REG_IRQ1, 7, 7),
+ [F_VINOVPI] = REG_FIELD(RT9455_REG_IRQ1, 6, 6),
+ [F_BATAB] = REG_FIELD(RT9455_REG_IRQ1, 0, 0),
+
+ [F_CHRVPI] = REG_FIELD(RT9455_REG_IRQ2, 7, 7),
+ [F_CHBATOVI] = REG_FIELD(RT9455_REG_IRQ2, 5, 5),
+ [F_CHTERMI] = REG_FIELD(RT9455_REG_IRQ2, 4, 4),
+ [F_CHRCHGI] = REG_FIELD(RT9455_REG_IRQ2, 3, 3),
+ [F_CH32MI] = REG_FIELD(RT9455_REG_IRQ2, 2, 2),
+ [F_CHTREGI] = REG_FIELD(RT9455_REG_IRQ2, 1, 1),
+ [F_CHMIVRI] = REG_FIELD(RT9455_REG_IRQ2, 0, 0),
+
+ [F_BSTBUSOVI] = REG_FIELD(RT9455_REG_IRQ3, 7, 7),
+ [F_BSTOLI] = REG_FIELD(RT9455_REG_IRQ3, 6, 6),
+ [F_BSTLOWVI] = REG_FIELD(RT9455_REG_IRQ3, 5, 5),
+ [F_BST32SI] = REG_FIELD(RT9455_REG_IRQ3, 3, 3),
+
+ [F_TSDM] = REG_FIELD(RT9455_REG_MASK1, 7, 7),
+ [F_VINOVPIM] = REG_FIELD(RT9455_REG_MASK1, 6, 6),
+ [F_BATABM] = REG_FIELD(RT9455_REG_MASK1, 0, 0),
+
+ [F_CHRVPIM] = REG_FIELD(RT9455_REG_MASK2, 7, 7),
+ [F_CHBATOVIM] = REG_FIELD(RT9455_REG_MASK2, 5, 5),
+ [F_CHTERMIM] = REG_FIELD(RT9455_REG_MASK2, 4, 4),
+ [F_CHRCHGIM] = REG_FIELD(RT9455_REG_MASK2, 3, 3),
+ [F_CH32MIM] = REG_FIELD(RT9455_REG_MASK2, 2, 2),
+ [F_CHTREGIM] = REG_FIELD(RT9455_REG_MASK2, 1, 1),
+ [F_CHMIVRIM] = REG_FIELD(RT9455_REG_MASK2, 0, 0),
+
+ [F_BSTVINOVIM] = REG_FIELD(RT9455_REG_MASK3, 7, 7),
+ [F_BSTOLIM] = REG_FIELD(RT9455_REG_MASK3, 6, 6),
+ [F_BSTLOWVIM] = REG_FIELD(RT9455_REG_MASK3, 5, 5),
+ [F_BST32SIM] = REG_FIELD(RT9455_REG_MASK3, 3, 3),
+};
+
+#define GET_MASK(fid) (BIT(rt9455_reg_fields[fid].msb + 1) - \
+ BIT(rt9455_reg_fields[fid].lsb))
+
+/*
+ * Each array initialised below shows the possible real-world values for a
+ * group of bits belonging to RT9455 registers. The arrays are sorted in
+ * ascending order. The index of each real-world value represents the value
+ * that is encoded in the group of bits belonging to RT9455 registers.
+ */
+/* REG06[6:4] (ICHRG) in uAh */
+static const int rt9455_ichrg_values[] = {
+ 500000, 650000, 800000, 950000, 1100000, 1250000, 1400000, 1550000
+};
+
+/*
+ * When the charger is in charge mode, REG02[7:2] represent battery regulation
+ * voltage.
+ */
+/* REG02[7:2] (VOREG) in uV */
+static const int rt9455_voreg_values[] = {
+ 3500000, 3520000, 3540000, 3560000, 3580000, 3600000, 3620000, 3640000,
+ 3660000, 3680000, 3700000, 3720000, 3740000, 3760000, 3780000, 3800000,
+ 3820000, 3840000, 3860000, 3880000, 3900000, 3920000, 3940000, 3960000,
+ 3980000, 4000000, 4020000, 4040000, 4060000, 4080000, 4100000, 4120000,
+ 4140000, 4160000, 4180000, 4200000, 4220000, 4240000, 4260000, 4280000,
+ 4300000, 4330000, 4350000, 4370000, 4390000, 4410000, 4430000, 4450000,
+ 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000,
+ 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000, 4450000
+};
+
+/*
+ * When the charger is in boost mode, REG02[7:2] represent boost output
+ * voltage.
+ */
+/* REG02[7:2] (Boost output voltage) in uV */
+static const int rt9455_boost_voltage_values[] = {
+ 4425000, 4450000, 4475000, 4500000, 4525000, 4550000, 4575000, 4600000,
+ 4625000, 4650000, 4675000, 4700000, 4725000, 4750000, 4775000, 4800000,
+ 4825000, 4850000, 4875000, 4900000, 4925000, 4950000, 4975000, 5000000,
+ 5025000, 5050000, 5075000, 5100000, 5125000, 5150000, 5175000, 5200000,
+ 5225000, 5250000, 5275000, 5300000, 5325000, 5350000, 5375000, 5400000,
+ 5425000, 5450000, 5475000, 5500000, 5525000, 5550000, 5575000, 5600000,
+ 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
+ 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000, 5600000,
+};
+
+/* REG07[3:0] (VMREG) in uV */
+static const int rt9455_vmreg_values[] = {
+ 4200000, 4220000, 4240000, 4260000, 4280000, 4300000, 4320000, 4340000,
+ 4360000, 4380000, 4400000, 4430000, 4450000, 4450000, 4450000, 4450000
+};
+
+/* REG05[5:4] (IEOC_PERCENTAGE) */
+static const int rt9455_ieoc_percentage_values[] = {
+ 10, 30, 20, 30
+};
+
+/* REG05[1:0] (MIVR) in uV */
+static const int rt9455_mivr_values[] = {
+ 4000000, 4250000, 4500000, 5000000
+};
+
+/* REG05[1:0] (IAICR) in uA */
+static const int rt9455_iaicr_values[] = {
+ 100000, 500000, 1000000, 2000000
+};
+
+struct rt9455_info {
+ struct i2c_client *client;
+ struct regmap *regmap;
+ struct regmap_field *regmap_fields[F_MAX_FIELDS];
+ struct power_supply *charger;
+#if IS_ENABLED(CONFIG_USB_PHY)
+ struct usb_phy *usb_phy;
+ struct notifier_block nb;
+#endif
+ struct delayed_work pwr_rdy_work;
+ struct delayed_work max_charging_time_work;
+ struct delayed_work batt_presence_work;
+ u32 voreg;
+ u32 boost_voltage;
+};
+
+/*
+ * Iterate through each element of the 'tbl' array until an element whose value
+ * is greater than v is found. Return the index of the respective element,
+ * or the index of the last element in the array, if no such element is found.
+ */
+static unsigned int rt9455_find_idx(const int tbl[], int tbl_size, int v)
+{
+ int i;
+
+ /*
+ * No need to iterate until the last index in the table because
+ * if no element greater than v is found in the table,
+ * or if only the last element is greater than v,
+ * function returns the index of the last element.
+ */
+ for (i = 0; i < tbl_size - 1; i++)
+ if (v <= tbl[i])
+ return i;
+
+ return (tbl_size - 1);
+}
+
+static int rt9455_get_field_val(struct rt9455_info *info,
+ enum rt9455_fields field,
+ const int tbl[], int tbl_size, int *val)
+{
+ unsigned int v;
+ int ret;
+
+ ret = regmap_field_read(info->regmap_fields[field], &v);
+ if (ret)
+ return ret;
+
+ v = (v >= tbl_size) ? (tbl_size - 1) : v;
+ *val = tbl[v];
+
+ return 0;
+}
+
+static int rt9455_set_field_val(struct rt9455_info *info,
+ enum rt9455_fields field,
+ const int tbl[], int tbl_size, int val)
+{
+ unsigned int idx = rt9455_find_idx(tbl, tbl_size, val);
+
+ return regmap_field_write(info->regmap_fields[field], idx);
+}
+
+static int rt9455_register_reset(struct rt9455_info *info)
+{
+ struct device *dev = &info->client->dev;
+ unsigned int v;
+ int ret, limit = 100;
+
+ ret = regmap_field_write(info->regmap_fields[F_RST], 0x01);
+ if (ret) {
+ dev_err(dev, "Failed to set RST bit\n");
+ return ret;
+ }
+
+ /*
+ * To make sure that reset operation has finished, loop until RST bit
+ * is set to 0.
+ */
+ do {
+ ret = regmap_field_read(info->regmap_fields[F_RST], &v);
+ if (ret) {
+ dev_err(dev, "Failed to read RST bit\n");
+ return ret;
+ }
+
+ if (!v)
+ break;
+
+ usleep_range(10, 100);
+ } while (--limit);
+
+ if (!limit)
+ return -EIO;
+
+ return 0;
+}
+
+/* Charger power supply property routines */
+static enum power_supply_property rt9455_charger_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ 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_CHARGE_TERM_CURRENT,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+};
+
+static char *rt9455_charger_supplied_to[] = {
+ "main-battery",
+};
+
+static int rt9455_charger_get_status(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ unsigned int v, pwr_rdy;
+ int ret;
+
+ ret = regmap_field_read(info->regmap_fields[F_PWR_RDY],
+ &pwr_rdy);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
+ return ret;
+ }
+
+ /*
+ * If PWR_RDY bit is unset, the battery is discharging. Otherwise,
+ * STAT bits value must be checked.
+ */
+ if (!pwr_rdy) {
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read STAT bits\n");
+ return ret;
+ }
+
+ switch (v) {
+ case 0:
+ /*
+ * If PWR_RDY bit is set, but STAT bits value is 0, the charger
+ * may be in one of the following cases:
+ * 1. CHG_EN bit is 0.
+ * 2. CHG_EN bit is 1 but the battery is not connected.
+ * In any of these cases, POWER_SUPPLY_STATUS_NOT_CHARGING is
+ * returned.
+ */
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ return 0;
+ case 1:
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ return 0;
+ case 2:
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ return 0;
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ return 0;
+ }
+}
+
+static int rt9455_charger_get_health(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ struct device *dev = &info->client->dev;
+ unsigned int v;
+ int ret;
+
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &v);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ1 register\n");
+ return ret;
+ }
+
+ if (v & GET_MASK(F_TSDI)) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ return 0;
+ }
+ if (v & GET_MASK(F_VINOVPI)) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ return 0;
+ }
+ if (v & GET_MASK(F_BATAB)) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &v);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ2 register\n");
+ return ret;
+ }
+
+ if (v & GET_MASK(F_CHBATOVI)) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ if (v & GET_MASK(F_CH32MI)) {
+ val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ return 0;
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &v);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ3 register\n");
+ return ret;
+ }
+
+ if (v & GET_MASK(F_BSTBUSOVI)) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ if (v & GET_MASK(F_BSTOLI)) {
+ val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ return 0;
+ }
+ if (v & GET_MASK(F_BSTLOWVI)) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+ if (v & GET_MASK(F_BST32SI)) {
+ val->intval = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE;
+ return 0;
+ }
+
+ ret = regmap_field_read(info->regmap_fields[F_STAT], &v);
+ if (ret) {
+ dev_err(dev, "Failed to read STAT bits\n");
+ return ret;
+ }
+
+ if (v == RT9455_FAULT) {
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ return 0;
+ }
+
+ return 0;
+}
+
+static int rt9455_charger_get_battery_presence(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ unsigned int v;
+ int ret;
+
+ ret = regmap_field_read(info->regmap_fields[F_BATAB], &v);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read BATAB bit\n");
+ return ret;
+ }
+
+ /*
+ * Since BATAB is 1 when battery is NOT present and 0 otherwise,
+ * !BATAB is returned.
+ */
+ val->intval = !v;
+
+ return 0;
+}
+
+static int rt9455_charger_get_online(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ unsigned int v;
+ int ret;
+
+ ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &v);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read PWR_RDY bit\n");
+ return ret;
+ }
+
+ val->intval = (int)v;
+
+ return 0;
+}
+
+static int rt9455_charger_get_current(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ int curr;
+ int ret;
+
+ ret = rt9455_get_field_val(info, F_ICHRG,
+ rt9455_ichrg_values,
+ ARRAY_SIZE(rt9455_ichrg_values),
+ &curr);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read ICHRG value\n");
+ return ret;
+ }
+
+ val->intval = curr;
+
+ return 0;
+}
+
+static int rt9455_charger_get_current_max(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ int idx = ARRAY_SIZE(rt9455_ichrg_values) - 1;
+
+ val->intval = rt9455_ichrg_values[idx];
+
+ return 0;
+}
+
+static int rt9455_charger_get_voltage(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ int voltage;
+ int ret;
+
+ ret = rt9455_get_field_val(info, F_VOREG,
+ rt9455_voreg_values,
+ ARRAY_SIZE(rt9455_voreg_values),
+ &voltage);
+ if (ret) {
+ dev_err(&info->client->dev, "Failed to read VOREG value\n");
+ return ret;
+ }
+
+ val->intval = voltage;
+
+ return 0;
+}
+
+static int rt9455_charger_get_voltage_max(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ int idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
+
+ val->intval = rt9455_vmreg_values[idx];
+
+ return 0;
+}
+
+static int rt9455_charger_get_term_current(struct rt9455_info *info,
+ union power_supply_propval *val)
+{
+ struct device *dev = &info->client->dev;
+ int ichrg, ieoc_percentage, ret;
+
+ ret = rt9455_get_field_val(info, F_ICHRG,
+ rt9455_ichrg_values,
+ ARRAY_SIZE(rt9455_ichrg_values),
+ &ichrg);
+ if (ret) {
+ dev_err(dev, "Failed to read ICHRG value\n");
+ return ret;
+ }
+
+ ret = rt9455_get_field_val(info, F_IEOC_PERCENTAGE,
+ rt9455_ieoc_percentage_values,
+ ARRAY_SIZE(rt9455_ieoc_percentage_values),
+ &ieoc_percentage);
+ if (ret) {
+ dev_err(dev, "Failed to read IEOC value\n");
+ return ret;
+ }
+
+ val->intval = ichrg * ieoc_percentage / 100;
+
+ return 0;
+}
+
+static int rt9455_charger_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rt9455_info *info = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ return rt9455_charger_get_status(info, val);
+ case POWER_SUPPLY_PROP_HEALTH:
+ return rt9455_charger_get_health(info, val);
+ case POWER_SUPPLY_PROP_PRESENT:
+ return rt9455_charger_get_battery_presence(info, val);
+ case POWER_SUPPLY_PROP_ONLINE:
+ return rt9455_charger_get_online(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ return rt9455_charger_get_current(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ return rt9455_charger_get_current_max(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ return rt9455_charger_get_voltage(info, val);
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX:
+ return rt9455_charger_get_voltage_max(info, val);
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_SYSTEM;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT:
+ return rt9455_charger_get_term_current(info, val);
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = RT9455_MODEL_NAME;
+ return 0;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = RT9455_MANUFACTURER;
+ return 0;
+ default:
+ return -ENODATA;
+ }
+}
+
+static int rt9455_hw_init(struct rt9455_info *info, u32 ichrg,
+ u32 ieoc_percentage,
+ u32 mivr, u32 iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int idx, ret;
+
+ ret = rt9455_register_reset(info);
+ if (ret) {
+ dev_err(dev, "Power On Reset failed\n");
+ return ret;
+ }
+
+ /* Set TE bit in order to enable end of charge detection */
+ ret = regmap_field_write(info->regmap_fields[F_TE], 1);
+ if (ret) {
+ dev_err(dev, "Failed to set TE bit\n");
+ return ret;
+ }
+
+ /* Set TE_SHDN_EN bit in order to enable end of charge detection */
+ ret = regmap_field_write(info->regmap_fields[F_TE_SHDN_EN], 1);
+ if (ret) {
+ dev_err(dev, "Failed to set TE_SHDN_EN bit\n");
+ return ret;
+ }
+
+ /*
+ * Set BATD_EN bit in order to enable battery detection
+ * when charging is done
+ */
+ ret = regmap_field_write(info->regmap_fields[F_BATD_EN], 1);
+ if (ret) {
+ dev_err(dev, "Failed to set BATD_EN bit\n");
+ return ret;
+ }
+
+ /*
+ * Disable Safety Timer. In charge mode, this timer terminates charging
+ * if no read or write via I2C is done within 32 minutes. This timer
+ * avoids overcharging the baterry when the OS is not loaded and the
+ * charger is connected to a power source.
+ * In boost mode, this timer triggers BST32SI interrupt if no read or
+ * write via I2C is done within 32 seconds.
+ * When the OS is loaded and the charger driver is inserted, it is used
+ * delayed_work, named max_charging_time_work, to avoid overcharging
+ * the battery.
+ */
+ ret = regmap_field_write(info->regmap_fields[F_TMR_EN], 0x00);
+ if (ret) {
+ dev_err(dev, "Failed to disable Safety Timer\n");
+ return ret;
+ }
+
+ /* Set ICHRG to value retrieved from device-specific data */
+ ret = rt9455_set_field_val(info, F_ICHRG,
+ rt9455_ichrg_values,
+ ARRAY_SIZE(rt9455_ichrg_values), ichrg);
+ if (ret) {
+ dev_err(dev, "Failed to set ICHRG value\n");
+ return ret;
+ }
+
+ /* Set IEOC Percentage to value retrieved from device-specific data */
+ ret = rt9455_set_field_val(info, F_IEOC_PERCENTAGE,
+ rt9455_ieoc_percentage_values,
+ ARRAY_SIZE(rt9455_ieoc_percentage_values),
+ ieoc_percentage);
+ if (ret) {
+ dev_err(dev, "Failed to set IEOC Percentage value\n");
+ return ret;
+ }
+
+ /* Set VOREG to value retrieved from device-specific data */
+ ret = rt9455_set_field_val(info, F_VOREG,
+ rt9455_voreg_values,
+ ARRAY_SIZE(rt9455_voreg_values),
+ info->voreg);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG value\n");
+ return ret;
+ }
+
+ /* Set VMREG value to maximum (4.45V). */
+ idx = ARRAY_SIZE(rt9455_vmreg_values) - 1;
+ ret = rt9455_set_field_val(info, F_VMREG,
+ rt9455_vmreg_values,
+ ARRAY_SIZE(rt9455_vmreg_values),
+ rt9455_vmreg_values[idx]);
+ if (ret) {
+ dev_err(dev, "Failed to set VMREG value\n");
+ return ret;
+ }
+
+ /*
+ * Set MIVR to value retrieved from device-specific data.
+ * If no value is specified, default value for MIVR is 4.5V.
+ */
+ if (mivr == -1)
+ mivr = 4500000;
+
+ ret = rt9455_set_field_val(info, F_MIVR,
+ rt9455_mivr_values,
+ ARRAY_SIZE(rt9455_mivr_values), mivr);
+ if (ret) {
+ dev_err(dev, "Failed to set MIVR value\n");
+ return ret;
+ }
+
+ /*
+ * Set IAICR to value retrieved from device-specific data.
+ * If no value is specified, default value for IAICR is 500 mA.
+ */
+ if (iaicr == -1)
+ iaicr = 500000;
+
+ ret = rt9455_set_field_val(info, F_IAICR,
+ rt9455_iaicr_values,
+ ARRAY_SIZE(rt9455_iaicr_values), iaicr);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR value\n");
+ return ret;
+ }
+
+ /*
+ * Set IAICR_INT bit so that IAICR value is determined by IAICR bits
+ * and not by OTG pin.
+ */
+ ret = regmap_field_write(info->regmap_fields[F_IAICR_INT], 0x01);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR_INT bit\n");
+ return ret;
+ }
+
+ /*
+ * Disable CHMIVRI interrupt. Because the driver sets MIVR value,
+ * CHMIVRI is triggered, but there is no action to be taken by the
+ * driver when CHMIVRI is triggered.
+ */
+ ret = regmap_field_write(info->regmap_fields[F_CHMIVRIM], 0x01);
+ if (ret) {
+ dev_err(dev, "Failed to mask CHMIVRI interrupt\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+/*
+ * Before setting the charger into boost mode, boost output voltage is
+ * set. This is needed because boost output voltage may differ from battery
+ * regulation voltage. F_VOREG bits represent either battery regulation voltage
+ * or boost output voltage, depending on the mode the charger is. Both battery
+ * regulation voltage and boost output voltage are read from DT/ACPI during
+ * probe.
+ */
+static int rt9455_set_boost_voltage_before_boost_mode(struct rt9455_info *info)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ ret = rt9455_set_field_val(info, F_VOREG,
+ rt9455_boost_voltage_values,
+ ARRAY_SIZE(rt9455_boost_voltage_values),
+ info->boost_voltage);
+ if (ret) {
+ dev_err(dev, "Failed to set boost output voltage value\n");
+ return ret;
+ }
+
+ return 0;
+}
+#endif
+
+/*
+ * Before setting the charger into charge mode, battery regulation voltage is
+ * set. This is needed because boost output voltage may differ from battery
+ * regulation voltage. F_VOREG bits represent either battery regulation voltage
+ * or boost output voltage, depending on the mode the charger is. Both battery
+ * regulation voltage and boost output voltage are read from DT/ACPI during
+ * probe.
+ */
+static int rt9455_set_voreg_before_charge_mode(struct rt9455_info *info)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ ret = rt9455_set_field_val(info, F_VOREG,
+ rt9455_voreg_values,
+ ARRAY_SIZE(rt9455_voreg_values),
+ info->voreg);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG value\n");
+ return ret;
+ }
+
+ return 0;
+}
+
+static int rt9455_irq_handler_check_irq1_register(struct rt9455_info *info,
+ bool *_is_battery_absent,
+ bool *_alert_userspace)
+{
+ unsigned int irq1, mask1, mask2;
+ struct device *dev = &info->client->dev;
+ bool is_battery_absent = false;
+ bool alert_userspace = false;
+ int ret;
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ1 register\n");
+ return ret;
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
+ if (ret) {
+ dev_err(dev, "Failed to read MASK1 register\n");
+ return ret;
+ }
+
+ if (irq1 & GET_MASK(F_TSDI)) {
+ dev_err(dev, "Thermal shutdown fault occurred\n");
+ alert_userspace = true;
+ }
+
+ if (irq1 & GET_MASK(F_VINOVPI)) {
+ dev_err(dev, "Overvoltage input occurred\n");
+ alert_userspace = true;
+ }
+
+ if (irq1 & GET_MASK(F_BATAB)) {
+ dev_err(dev, "Battery absence occurred\n");
+ is_battery_absent = true;
+ alert_userspace = true;
+
+ if ((mask1 & GET_MASK(F_BATABM)) == 0) {
+ ret = regmap_field_write(info->regmap_fields[F_BATABM],
+ 0x01);
+ if (ret) {
+ dev_err(dev, "Failed to mask BATAB interrupt\n");
+ return ret;
+ }
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
+ if (ret) {
+ dev_err(dev, "Failed to read MASK2 register\n");
+ return ret;
+ }
+
+ if (mask2 & GET_MASK(F_CHTERMIM)) {
+ ret = regmap_field_write(
+ info->regmap_fields[F_CHTERMIM], 0x00);
+ if (ret) {
+ dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
+ return ret;
+ }
+ }
+
+ if (mask2 & GET_MASK(F_CHRCHGIM)) {
+ ret = regmap_field_write(
+ info->regmap_fields[F_CHRCHGIM], 0x00);
+ if (ret) {
+ dev_err(dev, "Failed to unmask CHRCHGI interrupt\n");
+ return ret;
+ }
+ }
+
+ /*
+ * When the battery is absent, max_charging_time_work is
+ * cancelled, since no charging is done.
+ */
+ cancel_delayed_work_sync(&info->max_charging_time_work);
+ /*
+ * Since no interrupt is triggered when the battery is
+ * reconnected, max_charging_time_work is not rescheduled.
+ * Therefore, batt_presence_work is scheduled to check whether
+ * the battery is still absent or not.
+ */
+ queue_delayed_work(system_power_efficient_wq,
+ &info->batt_presence_work,
+ RT9455_BATT_PRESENCE_DELAY * HZ);
+ }
+
+ *_is_battery_absent = is_battery_absent;
+
+ if (alert_userspace)
+ *_alert_userspace = alert_userspace;
+
+ return 0;
+}
+
+static int rt9455_irq_handler_check_irq2_register(struct rt9455_info *info,
+ bool is_battery_absent,
+ bool *_alert_userspace)
+{
+ unsigned int irq2, mask2;
+ struct device *dev = &info->client->dev;
+ bool alert_userspace = false;
+ int ret;
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ2, &irq2);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ2 register\n");
+ return ret;
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_MASK2, &mask2);
+ if (ret) {
+ dev_err(dev, "Failed to read MASK2 register\n");
+ return ret;
+ }
+
+ if (irq2 & GET_MASK(F_CHRVPI)) {
+ dev_dbg(dev, "Charger fault occurred\n");
+ /*
+ * CHRVPI bit is set in 2 cases:
+ * 1. when the power source is connected to the charger.
+ * 2. when the power source is disconnected from the charger.
+ * To identify the case, PWR_RDY bit is checked. Because
+ * PWR_RDY bit is set / cleared after CHRVPI interrupt is
+ * triggered, it is used delayed_work to later read PWR_RDY bit.
+ * Also, do not set to true alert_userspace, because there is no
+ * need to notify userspace when CHRVPI interrupt has occurred.
+ * Userspace will be notified after PWR_RDY bit is read.
+ */
+ queue_delayed_work(system_power_efficient_wq,
+ &info->pwr_rdy_work,
+ RT9455_PWR_RDY_DELAY * HZ);
+ }
+ if (irq2 & GET_MASK(F_CHBATOVI)) {
+ dev_err(dev, "Battery OVP occurred\n");
+ alert_userspace = true;
+ }
+ if (irq2 & GET_MASK(F_CHTERMI)) {
+ dev_dbg(dev, "Charge terminated\n");
+ if (!is_battery_absent) {
+ if ((mask2 & GET_MASK(F_CHTERMIM)) == 0) {
+ ret = regmap_field_write(
+ info->regmap_fields[F_CHTERMIM], 0x01);
+ if (ret) {
+ dev_err(dev, "Failed to mask CHTERMI interrupt\n");
+ return ret;
+ }
+ /*
+ * Update MASK2 value, since CHTERMIM bit is
+ * set.
+ */
+ mask2 = mask2 | GET_MASK(F_CHTERMIM);
+ }
+ cancel_delayed_work_sync(&info->max_charging_time_work);
+ alert_userspace = true;
+ }
+ }
+ if (irq2 & GET_MASK(F_CHRCHGI)) {
+ dev_dbg(dev, "Recharge request\n");
+ ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+ RT9455_CHARGE_ENABLE);
+ if (ret) {
+ dev_err(dev, "Failed to enable charging\n");
+ return ret;
+ }
+ if (mask2 & GET_MASK(F_CHTERMIM)) {
+ ret = regmap_field_write(
+ info->regmap_fields[F_CHTERMIM], 0x00);
+ if (ret) {
+ dev_err(dev, "Failed to unmask CHTERMI interrupt\n");
+ return ret;
+ }
+ /* Update MASK2 value, since CHTERMIM bit is cleared. */
+ mask2 = mask2 & ~GET_MASK(F_CHTERMIM);
+ }
+ if (!is_battery_absent) {
+ /*
+ * No need to check whether the charger is connected to
+ * power source when CHRCHGI is received, since CHRCHGI
+ * is not triggered if the charger is not connected to
+ * the power source.
+ */
+ queue_delayed_work(system_power_efficient_wq,
+ &info->max_charging_time_work,
+ RT9455_MAX_CHARGING_TIME * HZ);
+ alert_userspace = true;
+ }
+ }
+ if (irq2 & GET_MASK(F_CH32MI)) {
+ dev_err(dev, "Charger fault. 32 mins timeout occurred\n");
+ alert_userspace = true;
+ }
+ if (irq2 & GET_MASK(F_CHTREGI)) {
+ dev_warn(dev,
+ "Charger warning. Thermal regulation loop active\n");
+ alert_userspace = true;
+ }
+ if (irq2 & GET_MASK(F_CHMIVRI)) {
+ dev_dbg(dev,
+ "Charger warning. Input voltage MIVR loop active\n");
+ }
+
+ if (alert_userspace)
+ *_alert_userspace = alert_userspace;
+
+ return 0;
+}
+
+static int rt9455_irq_handler_check_irq3_register(struct rt9455_info *info,
+ bool *_alert_userspace)
+{
+ unsigned int irq3, mask3;
+ struct device *dev = &info->client->dev;
+ bool alert_userspace = false;
+ int ret;
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ3, &irq3);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ3 register\n");
+ return ret;
+ }
+
+ ret = regmap_read(info->regmap, RT9455_REG_MASK3, &mask3);
+ if (ret) {
+ dev_err(dev, "Failed to read MASK3 register\n");
+ return ret;
+ }
+
+ if (irq3 & GET_MASK(F_BSTBUSOVI)) {
+ dev_err(dev, "Boost fault. Overvoltage input occurred\n");
+ alert_userspace = true;
+ }
+ if (irq3 & GET_MASK(F_BSTOLI)) {
+ dev_err(dev, "Boost fault. Overload\n");
+ alert_userspace = true;
+ }
+ if (irq3 & GET_MASK(F_BSTLOWVI)) {
+ dev_err(dev, "Boost fault. Battery voltage too low\n");
+ alert_userspace = true;
+ }
+ if (irq3 & GET_MASK(F_BST32SI)) {
+ dev_err(dev, "Boost fault. 32 seconds timeout occurred.\n");
+ alert_userspace = true;
+ }
+
+ if (alert_userspace) {
+ dev_info(dev, "Boost fault occurred, therefore the charger goes into charge mode\n");
+ ret = rt9455_set_voreg_before_charge_mode(info);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+ return ret;
+ }
+ ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+ RT9455_CHARGE_MODE);
+ if (ret) {
+ dev_err(dev, "Failed to set charger in charge mode\n");
+ return ret;
+ }
+ *_alert_userspace = alert_userspace;
+ }
+
+ return 0;
+}
+
+static irqreturn_t rt9455_irq_handler_thread(int irq, void *data)
+{
+ struct rt9455_info *info = data;
+ struct device *dev;
+ bool alert_userspace = false;
+ bool is_battery_absent = false;
+ unsigned int status;
+ int ret;
+
+ if (!info)
+ return IRQ_NONE;
+
+ dev = &info->client->dev;
+
+ if (irq != info->client->irq) {
+ dev_err(dev, "Interrupt is not for RT9455 charger\n");
+ return IRQ_NONE;
+ }
+
+ ret = regmap_field_read(info->regmap_fields[F_STAT], &status);
+ if (ret) {
+ dev_err(dev, "Failed to read STAT bits\n");
+ return IRQ_HANDLED;
+ }
+ dev_dbg(dev, "Charger status is %d\n", status);
+
+ /*
+ * Each function that processes an IRQ register receives as output
+ * parameter alert_userspace pointer. alert_userspace is set to true
+ * in such a function only if an interrupt has occurred in the
+ * respective interrupt register. This way, it is avoided the following
+ * case: interrupt occurs only in IRQ1 register,
+ * rt9455_irq_handler_check_irq1_register() function sets to true
+ * alert_userspace, but rt9455_irq_handler_check_irq2_register()
+ * and rt9455_irq_handler_check_irq3_register() functions set to false
+ * alert_userspace and power_supply_changed() is never called.
+ */
+ ret = rt9455_irq_handler_check_irq1_register(info, &is_battery_absent,
+ &alert_userspace);
+ if (ret) {
+ dev_err(dev, "Failed to handle IRQ1 register\n");
+ return IRQ_HANDLED;
+ }
+
+ ret = rt9455_irq_handler_check_irq2_register(info, is_battery_absent,
+ &alert_userspace);
+ if (ret) {
+ dev_err(dev, "Failed to handle IRQ2 register\n");
+ return IRQ_HANDLED;
+ }
+
+ ret = rt9455_irq_handler_check_irq3_register(info, &alert_userspace);
+ if (ret) {
+ dev_err(dev, "Failed to handle IRQ3 register\n");
+ return IRQ_HANDLED;
+ }
+
+ if (alert_userspace) {
+ /*
+ * Sometimes, an interrupt occurs while rt9455_probe() function
+ * is executing and power_supply_register() is not yet called.
+ * Do not call power_supply_changed() in this case.
+ */
+ if (info->charger)
+ power_supply_changed(info->charger);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static int rt9455_discover_charger(struct rt9455_info *info, u32 *ichrg,
+ u32 *ieoc_percentage,
+ u32 *mivr, u32 *iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ if (!dev->of_node && !ACPI_HANDLE(dev)) {
+ dev_err(dev, "No support for either device tree or ACPI\n");
+ return -EINVAL;
+ }
+ /*
+ * ICHRG, IEOC_PERCENTAGE, VOREG and boost output voltage are mandatory
+ * parameters.
+ */
+ ret = device_property_read_u32(dev, "richtek,output-charge-current",
+ ichrg);
+ if (ret) {
+ dev_err(dev, "Error: missing \"output-charge-current\" property\n");
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "richtek,end-of-charge-percentage",
+ ieoc_percentage);
+ if (ret) {
+ dev_err(dev, "Error: missing \"end-of-charge-percentage\" property\n");
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev,
+ "richtek,battery-regulation-voltage",
+ &info->voreg);
+ if (ret) {
+ dev_err(dev, "Error: missing \"battery-regulation-voltage\" property\n");
+ return ret;
+ }
+
+ ret = device_property_read_u32(dev, "richtek,boost-output-voltage",
+ &info->boost_voltage);
+ if (ret) {
+ dev_err(dev, "Error: missing \"boost-output-voltage\" property\n");
+ return ret;
+ }
+
+ /*
+ * MIVR and IAICR are optional parameters. Do not return error if one of
+ * them is not present in ACPI table or device tree specification.
+ */
+ device_property_read_u32(dev, "richtek,min-input-voltage-regulation",
+ mivr);
+ device_property_read_u32(dev, "richtek,avg-input-current-regulation",
+ iaicr);
+
+ return 0;
+}
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+static int rt9455_usb_event_none(struct rt9455_info *info,
+ u8 opa_mode, u8 iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ if (opa_mode == RT9455_BOOST_MODE) {
+ ret = rt9455_set_voreg_before_charge_mode(info);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+ return ret;
+ }
+ /*
+ * If the charger is in boost mode, and it has received
+ * USB_EVENT_NONE, this means the consumer device powered by the
+ * charger is not connected anymore.
+ * In this case, the charger goes into charge mode.
+ */
+ dev_dbg(dev, "USB_EVENT_NONE received, therefore the charger goes into charge mode\n");
+ ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+ RT9455_CHARGE_MODE);
+ if (ret) {
+ dev_err(dev, "Failed to set charger in charge mode\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ dev_dbg(dev, "USB_EVENT_NONE received, therefore IAICR is set to its minimum value\n");
+ if (iaicr != RT9455_IAICR_100MA) {
+ ret = regmap_field_write(info->regmap_fields[F_IAICR],
+ RT9455_IAICR_100MA);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR value\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_vbus(struct rt9455_info *info,
+ u8 opa_mode, u8 iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ if (opa_mode == RT9455_BOOST_MODE) {
+ ret = rt9455_set_voreg_before_charge_mode(info);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+ return ret;
+ }
+ /*
+ * If the charger is in boost mode, and it has received
+ * USB_EVENT_VBUS, this means the consumer device powered by the
+ * charger is not connected anymore.
+ * In this case, the charger goes into charge mode.
+ */
+ dev_dbg(dev, "USB_EVENT_VBUS received, therefore the charger goes into charge mode\n");
+ ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+ RT9455_CHARGE_MODE);
+ if (ret) {
+ dev_err(dev, "Failed to set charger in charge mode\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ dev_dbg(dev, "USB_EVENT_VBUS received, therefore IAICR is set to 500 mA\n");
+ if (iaicr != RT9455_IAICR_500MA) {
+ ret = regmap_field_write(info->regmap_fields[F_IAICR],
+ RT9455_IAICR_500MA);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR value\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_id(struct rt9455_info *info,
+ u8 opa_mode, u8 iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ if (opa_mode == RT9455_CHARGE_MODE) {
+ ret = rt9455_set_boost_voltage_before_boost_mode(info);
+ if (ret) {
+ dev_err(dev, "Failed to set boost output voltage before entering boost mode\n");
+ return ret;
+ }
+ /*
+ * If the charger is in charge mode, and it has received
+ * USB_EVENT_ID, this means a consumer device is connected and
+ * it should be powered by the charger.
+ * In this case, the charger goes into boost mode.
+ */
+ dev_dbg(dev, "USB_EVENT_ID received, therefore the charger goes into boost mode\n");
+ ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+ RT9455_BOOST_MODE);
+ if (ret) {
+ dev_err(dev, "Failed to set charger in boost mode\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ dev_dbg(dev, "USB_EVENT_ID received, therefore IAICR is set to its minimum value\n");
+ if (iaicr != RT9455_IAICR_100MA) {
+ ret = regmap_field_write(info->regmap_fields[F_IAICR],
+ RT9455_IAICR_100MA);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR value\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ return NOTIFY_OK;
+}
+
+static int rt9455_usb_event_charger(struct rt9455_info *info,
+ u8 opa_mode, u8 iaicr)
+{
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ if (opa_mode == RT9455_BOOST_MODE) {
+ ret = rt9455_set_voreg_before_charge_mode(info);
+ if (ret) {
+ dev_err(dev, "Failed to set VOREG before entering charge mode\n");
+ return ret;
+ }
+ /*
+ * If the charger is in boost mode, and it has received
+ * USB_EVENT_CHARGER, this means the consumer device powered by
+ * the charger is not connected anymore.
+ * In this case, the charger goes into charge mode.
+ */
+ dev_dbg(dev, "USB_EVENT_CHARGER received, therefore the charger goes into charge mode\n");
+ ret = regmap_field_write(info->regmap_fields[F_OPA_MODE],
+ RT9455_CHARGE_MODE);
+ if (ret) {
+ dev_err(dev, "Failed to set charger in charge mode\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ dev_dbg(dev, "USB_EVENT_CHARGER received, therefore IAICR is set to no current limit\n");
+ if (iaicr != RT9455_IAICR_NO_LIMIT) {
+ ret = regmap_field_write(info->regmap_fields[F_IAICR],
+ RT9455_IAICR_NO_LIMIT);
+ if (ret) {
+ dev_err(dev, "Failed to set IAICR value\n");
+ return NOTIFY_DONE;
+ }
+ }
+
+ return NOTIFY_OK;
+}
+
+static int rt9455_usb_event(struct notifier_block *nb,
+ unsigned long event, void *power)
+{
+ struct rt9455_info *info = container_of(nb, struct rt9455_info, nb);
+ struct device *dev = &info->client->dev;
+ unsigned int opa_mode, iaicr;
+ int ret;
+
+ /*
+ * Determine whether the charger is in charge mode
+ * or in boost mode.
+ */
+ ret = regmap_field_read(info->regmap_fields[F_OPA_MODE],
+ &opa_mode);
+ if (ret) {
+ dev_err(dev, "Failed to read OPA_MODE value\n");
+ return NOTIFY_DONE;
+ }
+
+ ret = regmap_field_read(info->regmap_fields[F_IAICR],
+ &iaicr);
+ if (ret) {
+ dev_err(dev, "Failed to read IAICR value\n");
+ return NOTIFY_DONE;
+ }
+
+ dev_dbg(dev, "Received USB event %lu\n", event);
+ switch (event) {
+ case USB_EVENT_NONE:
+ return rt9455_usb_event_none(info, opa_mode, iaicr);
+ case USB_EVENT_VBUS:
+ return rt9455_usb_event_vbus(info, opa_mode, iaicr);
+ case USB_EVENT_ID:
+ return rt9455_usb_event_id(info, opa_mode, iaicr);
+ case USB_EVENT_CHARGER:
+ return rt9455_usb_event_charger(info, opa_mode, iaicr);
+ default:
+ dev_err(dev, "Unknown USB event\n");
+ }
+ return NOTIFY_DONE;
+}
+#endif
+
+static void rt9455_pwr_rdy_work_callback(struct work_struct *work)
+{
+ struct rt9455_info *info = container_of(work, struct rt9455_info,
+ pwr_rdy_work.work);
+ struct device *dev = &info->client->dev;
+ unsigned int pwr_rdy;
+ int ret;
+
+ ret = regmap_field_read(info->regmap_fields[F_PWR_RDY], &pwr_rdy);
+ if (ret) {
+ dev_err(dev, "Failed to read PWR_RDY bit\n");
+ return;
+ }
+ switch (pwr_rdy) {
+ case RT9455_PWR_FAULT:
+ dev_dbg(dev, "Charger disconnected from power source\n");
+ cancel_delayed_work_sync(&info->max_charging_time_work);
+ break;
+ case RT9455_PWR_GOOD:
+ dev_dbg(dev, "Charger connected to power source\n");
+ ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+ RT9455_CHARGE_ENABLE);
+ if (ret) {
+ dev_err(dev, "Failed to enable charging\n");
+ return;
+ }
+ queue_delayed_work(system_power_efficient_wq,
+ &info->max_charging_time_work,
+ RT9455_MAX_CHARGING_TIME * HZ);
+ break;
+ }
+ /*
+ * Notify userspace that the charger has been either connected to or
+ * disconnected from the power source.
+ */
+ power_supply_changed(info->charger);
+}
+
+static void rt9455_max_charging_time_work_callback(struct work_struct *work)
+{
+ struct rt9455_info *info = container_of(work, struct rt9455_info,
+ max_charging_time_work.work);
+ struct device *dev = &info->client->dev;
+ int ret;
+
+ dev_err(dev, "Battery has been charging for at least 6 hours and is not yet fully charged. Battery is dead, therefore charging is disabled.\n");
+ ret = regmap_field_write(info->regmap_fields[F_CHG_EN],
+ RT9455_CHARGE_DISABLE);
+ if (ret)
+ dev_err(dev, "Failed to disable charging\n");
+}
+
+static void rt9455_batt_presence_work_callback(struct work_struct *work)
+{
+ struct rt9455_info *info = container_of(work, struct rt9455_info,
+ batt_presence_work.work);
+ struct device *dev = &info->client->dev;
+ unsigned int irq1, mask1;
+ int ret;
+
+ ret = regmap_read(info->regmap, RT9455_REG_IRQ1, &irq1);
+ if (ret) {
+ dev_err(dev, "Failed to read IRQ1 register\n");
+ return;
+ }
+
+ /*
+ * If the battery is still absent, batt_presence_work is rescheduled.
+ * Otherwise, max_charging_time is scheduled.
+ */
+ if (irq1 & GET_MASK(F_BATAB)) {
+ queue_delayed_work(system_power_efficient_wq,
+ &info->batt_presence_work,
+ RT9455_BATT_PRESENCE_DELAY * HZ);
+ } else {
+ queue_delayed_work(system_power_efficient_wq,
+ &info->max_charging_time_work,
+ RT9455_MAX_CHARGING_TIME * HZ);
+
+ ret = regmap_read(info->regmap, RT9455_REG_MASK1, &mask1);
+ if (ret) {
+ dev_err(dev, "Failed to read MASK1 register\n");
+ return;
+ }
+
+ if (mask1 & GET_MASK(F_BATABM)) {
+ ret = regmap_field_write(info->regmap_fields[F_BATABM],
+ 0x00);
+ if (ret)
+ dev_err(dev, "Failed to unmask BATAB interrupt\n");
+ }
+ /*
+ * Notify userspace that the battery is now connected to the
+ * charger.
+ */
+ power_supply_changed(info->charger);
+ }
+}
+
+static const struct power_supply_desc rt9455_charger_desc = {
+ .name = RT9455_DRIVER_NAME,
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = rt9455_charger_properties,
+ .num_properties = ARRAY_SIZE(rt9455_charger_properties),
+ .get_property = rt9455_charger_get_property,
+};
+
+static bool rt9455_is_writeable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case RT9455_REG_DEV_ID:
+ case RT9455_REG_IRQ1:
+ case RT9455_REG_IRQ2:
+ case RT9455_REG_IRQ3:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static bool rt9455_is_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case RT9455_REG_DEV_ID:
+ case RT9455_REG_CTRL5:
+ case RT9455_REG_CTRL6:
+ return false;
+ default:
+ return true;
+ }
+}
+
+static const struct regmap_config rt9455_regmap_config = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .writeable_reg = rt9455_is_writeable_reg,
+ .volatile_reg = rt9455_is_volatile_reg,
+ .max_register = RT9455_REG_MASK3,
+ .cache_type = REGCACHE_RBTREE,
+};
+
+static int rt9455_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 rt9455_info *info;
+ struct power_supply_config rt9455_charger_config = {};
+ /*
+ * Mandatory device-specific data values. Also, VOREG and boost output
+ * voltage are mandatory values, but they are stored in rt9455_info
+ * structure.
+ */
+ u32 ichrg, ieoc_percentage;
+ /* Optional device-specific data values. */
+ u32 mivr = -1, iaicr = -1;
+ int i, ret;
+
+ if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) {
+ dev_err(dev, "No support for SMBUS_BYTE_DATA\n");
+ return -ENODEV;
+ }
+ info = devm_kzalloc(dev, sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return -ENOMEM;
+
+ info->client = client;
+ i2c_set_clientdata(client, info);
+
+ info->regmap = devm_regmap_init_i2c(client,
+ &rt9455_regmap_config);
+ if (IS_ERR(info->regmap)) {
+ dev_err(dev, "Failed to initialize register map\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < F_MAX_FIELDS; i++) {
+ info->regmap_fields[i] =
+ devm_regmap_field_alloc(dev, info->regmap,
+ rt9455_reg_fields[i]);
+ if (IS_ERR(info->regmap_fields[i])) {
+ dev_err(dev,
+ "Failed to allocate regmap field = %d\n", i);
+ return PTR_ERR(info->regmap_fields[i]);
+ }
+ }
+
+ ret = rt9455_discover_charger(info, &ichrg, &ieoc_percentage,
+ &mivr, &iaicr);
+ if (ret) {
+ dev_err(dev, "Failed to discover charger\n");
+ return ret;
+ }
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+ info->usb_phy = devm_usb_get_phy(dev, USB_PHY_TYPE_USB2);
+ if (IS_ERR(info->usb_phy)) {
+ dev_err(dev, "Failed to get USB transceiver\n");
+ } else {
+ info->nb.notifier_call = rt9455_usb_event;
+ ret = usb_register_notifier(info->usb_phy, &info->nb);
+ if (ret) {
+ dev_err(dev, "Failed to register USB notifier\n");
+ /*
+ * If usb_register_notifier() fails, set notifier_call
+ * to NULL, to avoid calling usb_unregister_notifier().
+ */
+ info->nb.notifier_call = NULL;
+ }
+ }
+#endif
+
+ INIT_DEFERRABLE_WORK(&info->pwr_rdy_work, rt9455_pwr_rdy_work_callback);
+ INIT_DEFERRABLE_WORK(&info->max_charging_time_work,
+ rt9455_max_charging_time_work_callback);
+ INIT_DEFERRABLE_WORK(&info->batt_presence_work,
+ rt9455_batt_presence_work_callback);
+
+ rt9455_charger_config.of_node = dev->of_node;
+ rt9455_charger_config.drv_data = info;
+ rt9455_charger_config.supplied_to = rt9455_charger_supplied_to;
+ rt9455_charger_config.num_supplicants =
+ ARRAY_SIZE(rt9455_charger_supplied_to);
+ ret = devm_request_threaded_irq(dev, client->irq, NULL,
+ rt9455_irq_handler_thread,
+ IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+ RT9455_DRIVER_NAME, info);
+ if (ret) {
+ dev_err(dev, "Failed to register IRQ handler\n");
+ goto put_usb_notifier;
+ }
+
+ ret = rt9455_hw_init(info, ichrg, ieoc_percentage, mivr, iaicr);
+ if (ret) {
+ dev_err(dev, "Failed to set charger to its default values\n");
+ goto put_usb_notifier;
+ }
+
+ info->charger = devm_power_supply_register(dev, &rt9455_charger_desc,
+ &rt9455_charger_config);
+ if (IS_ERR(info->charger)) {
+ dev_err(dev, "Failed to register charger\n");
+ ret = PTR_ERR(info->charger);
+ goto put_usb_notifier;
+ }
+
+ return 0;
+
+put_usb_notifier:
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (info->nb.notifier_call) {
+ usb_unregister_notifier(info->usb_phy, &info->nb);
+ info->nb.notifier_call = NULL;
+ }
+#endif
+ return ret;
+}
+
+static int rt9455_remove(struct i2c_client *client)
+{
+ int ret;
+ struct rt9455_info *info = i2c_get_clientdata(client);
+
+ ret = rt9455_register_reset(info);
+ if (ret)
+ dev_err(&info->client->dev, "Failed to set charger to its default values\n");
+
+#if IS_ENABLED(CONFIG_USB_PHY)
+ if (info->nb.notifier_call)
+ usb_unregister_notifier(info->usb_phy, &info->nb);
+#endif
+
+ cancel_delayed_work_sync(&info->pwr_rdy_work);
+ cancel_delayed_work_sync(&info->max_charging_time_work);
+ cancel_delayed_work_sync(&info->batt_presence_work);
+
+ return ret;
+}
+
+static const struct i2c_device_id rt9455_i2c_id_table[] = {
+ { RT9455_DRIVER_NAME, 0 },
+ { },
+};
+MODULE_DEVICE_TABLE(i2c, rt9455_i2c_id_table);
+
+static const struct of_device_id rt9455_of_match[] = {
+ { .compatible = "richtek,rt9455", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, rt9455_of_match);
+
+static const struct acpi_device_id rt9455_i2c_acpi_match[] = {
+ { "RT945500", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(acpi, rt9455_i2c_acpi_match);
+
+static struct i2c_driver rt9455_driver = {
+ .probe = rt9455_probe,
+ .remove = rt9455_remove,
+ .id_table = rt9455_i2c_id_table,
+ .driver = {
+ .name = RT9455_DRIVER_NAME,
+ .of_match_table = of_match_ptr(rt9455_of_match),
+ .acpi_match_table = ACPI_PTR(rt9455_i2c_acpi_match),
+ },
+};
+module_i2c_driver(rt9455_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anda-Maria Nicolae <anda-maria.nicolae@intel.com>");
+MODULE_DESCRIPTION("Richtek RT9455 Charger Driver");
diff --git a/drivers/power/supply/rx51_battery.c b/drivers/power/supply/rx51_battery.c
new file mode 100644
index 0000000..af9383d
--- /dev/null
+++ b/drivers/power/supply/rx51_battery.c
@@ -0,0 +1,297 @@
+/*
+ * Nokia RX-51 battery driver
+ *
+ * Copyright (C) 2012 Pali Rohár <pali.rohar@gmail.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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <linux/module.h>
+#include <linux/param.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/i2c/twl4030-madc.h>
+#include <linux/iio/consumer.h>
+#include <linux/of.h>
+
+struct rx51_device_info {
+ struct device *dev;
+ struct power_supply *bat;
+ struct power_supply_desc bat_desc;
+ struct iio_channel *channel_temp;
+ struct iio_channel *channel_bsi;
+ struct iio_channel *channel_vbat;
+};
+
+/*
+ * Read ADCIN channel value, code copied from maemo kernel
+ */
+static int rx51_battery_read_adc(struct iio_channel *channel)
+{
+ int val, err;
+ err = iio_read_channel_average_raw(channel, &val);
+ if (err < 0)
+ return err;
+ return val;
+}
+
+/*
+ * Read ADCIN channel 12 (voltage) and convert RAW value to micro voltage
+ * This conversion formula was extracted from maemo program bsi-read
+ */
+static int rx51_battery_read_voltage(struct rx51_device_info *di)
+{
+ int voltage = rx51_battery_read_adc(di->channel_vbat);
+
+ if (voltage < 0) {
+ dev_err(di->dev, "Could not read ADC: %d\n", voltage);
+ return voltage;
+ }
+
+ return 1000 * (10000 * voltage / 1705);
+}
+
+/*
+ * Temperature look-up tables
+ * TEMP = (1/(t1 + 1/298) - 273.15)
+ * Where t1 = (1/B) * ln((RAW_ADC_U * 2.5)/(R * I * 255))
+ * Formula is based on experimental data, RX-51 CAL data, maemo program bme
+ * and formula from da9052 driver with values R = 100, B = 3380, I = 0.00671
+ */
+
+/*
+ * Table1 (temperature for first 25 RAW values)
+ * Usage: TEMP = rx51_temp_table1[RAW]
+ * RAW is between 1 and 24
+ * TEMP is between 201 C and 55 C
+ */
+static u8 rx51_temp_table1[] = {
+ 255, 201, 159, 138, 124, 114, 106, 99, 94, 89, 85, 82, 78, 75,
+ 73, 70, 68, 66, 64, 62, 61, 59, 57, 56, 55
+};
+
+/*
+ * Table2 (lowest RAW value for temperature)
+ * Usage: RAW = rx51_temp_table2[TEMP-rx51_temp_table2_first]
+ * TEMP is between 53 C and -32 C
+ * RAW is between 25 and 993
+ */
+#define rx51_temp_table2_first 53
+static u16 rx51_temp_table2[] = {
+ 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 39,
+ 40, 41, 43, 44, 46, 48, 49, 51, 53, 55, 57, 59, 61, 64,
+ 66, 69, 71, 74, 77, 80, 83, 86, 90, 94, 97, 101, 106, 110,
+ 115, 119, 125, 130, 136, 141, 148, 154, 161, 168, 176, 184, 202, 211,
+ 221, 231, 242, 254, 266, 279, 293, 308, 323, 340, 357, 375, 395, 415,
+ 437, 460, 485, 511, 539, 568, 600, 633, 669, 706, 747, 790, 836, 885,
+ 937, 993, 1024
+};
+
+/*
+ * Read ADCIN channel 0 (battery temp) and convert value to tenths of Celsius
+ * Use Temperature look-up tables for conversation
+ */
+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(di->channel_temp);
+
+ if (raw < 0)
+ dev_err(di->dev, "Could not read ADC: %d\n", raw);
+
+ /* Zero and negative values are undefined */
+ if (raw <= 0)
+ return INT_MAX;
+
+ /* ADC channels are 10 bit, higher value are undefined */
+ if (raw >= (1 << 10))
+ return INT_MIN;
+
+ /* First check for temperature in first direct table */
+ if (raw < ARRAY_SIZE(rx51_temp_table1))
+ return rx51_temp_table1[raw] * 10;
+
+ /* Binary search RAW value in second inverse table */
+ while (max - min > 1) {
+ int mid = (max + min) / 2;
+ if (rx51_temp_table2[mid] <= raw)
+ min = mid;
+ else if (rx51_temp_table2[mid] > raw)
+ max = mid;
+ if (rx51_temp_table2[mid] == raw)
+ break;
+ }
+
+ return (rx51_temp_table2_first - min) * 10;
+}
+
+/*
+ * Read ADCIN channel 4 (BSI) and convert RAW value to micro Ah
+ * This conversion formula was extracted from maemo program bsi-read
+ */
+static int rx51_battery_read_capacity(struct rx51_device_info *di)
+{
+ int capacity = rx51_battery_read_adc(di->channel_bsi);
+
+ if (capacity < 0) {
+ dev_err(di->dev, "Could not read ADC: %d\n", capacity);
+ return capacity;
+ }
+
+ return 1280 * (1200 * capacity)/(1024 - capacity);
+}
+
+/*
+ * Return power_supply property
+ */
+static int rx51_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct rx51_device_info *di = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = 4200000;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = rx51_battery_read_voltage(di) ? 1 : 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = rx51_battery_read_voltage(di);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = rx51_battery_read_temperature(di);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = rx51_battery_read_capacity(di);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if (val->intval == INT_MAX || val->intval == INT_MIN)
+ return -EINVAL;
+
+ return 0;
+}
+
+static enum power_supply_property rx51_battery_props[] = {
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+};
+
+static int rx51_battery_probe(struct platform_device *pdev)
+{
+ struct power_supply_config psy_cfg = {};
+ struct rx51_device_info *di;
+ int ret;
+
+ di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL);
+ if (!di)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, di);
+
+ di->dev = &pdev->dev;
+ di->bat_desc.name = "rx51-battery";
+ di->bat_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ di->bat_desc.properties = rx51_battery_props;
+ di->bat_desc.num_properties = ARRAY_SIZE(rx51_battery_props);
+ di->bat_desc.get_property = rx51_battery_get_property;
+
+ psy_cfg.drv_data = di;
+
+ di->channel_temp = iio_channel_get(di->dev, "temp");
+ if (IS_ERR(di->channel_temp)) {
+ ret = PTR_ERR(di->channel_temp);
+ goto error;
+ }
+
+ di->channel_bsi = iio_channel_get(di->dev, "bsi");
+ if (IS_ERR(di->channel_bsi)) {
+ ret = PTR_ERR(di->channel_bsi);
+ goto error_channel_temp;
+ }
+
+ di->channel_vbat = iio_channel_get(di->dev, "vbat");
+ if (IS_ERR(di->channel_vbat)) {
+ ret = PTR_ERR(di->channel_vbat);
+ goto error_channel_bsi;
+ }
+
+ di->bat = power_supply_register(di->dev, &di->bat_desc, &psy_cfg);
+ if (IS_ERR(di->bat)) {
+ ret = PTR_ERR(di->bat);
+ goto error_channel_vbat;
+ }
+
+ return 0;
+
+error_channel_vbat:
+ iio_channel_release(di->channel_vbat);
+error_channel_bsi:
+ iio_channel_release(di->channel_bsi);
+error_channel_temp:
+ iio_channel_release(di->channel_temp);
+error:
+
+ return ret;
+}
+
+static int rx51_battery_remove(struct platform_device *pdev)
+{
+ struct rx51_device_info *di = platform_get_drvdata(pdev);
+
+ power_supply_unregister(di->bat);
+
+ iio_channel_release(di->channel_vbat);
+ iio_channel_release(di->channel_bsi);
+ iio_channel_release(di->channel_temp);
+
+ return 0;
+}
+
+#ifdef CONFIG_OF
+static const struct of_device_id n900_battery_of_match[] = {
+ {.compatible = "nokia,n900-battery", },
+ { },
+};
+MODULE_DEVICE_TABLE(of, n900_battery_of_match);
+#endif
+
+static struct platform_driver rx51_battery_driver = {
+ .probe = rx51_battery_probe,
+ .remove = rx51_battery_remove,
+ .driver = {
+ .name = "rx51-battery",
+ .of_match_table = of_match_ptr(n900_battery_of_match),
+ },
+};
+module_platform_driver(rx51_battery_driver);
+
+MODULE_ALIAS("platform:rx51-battery");
+MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>");
+MODULE_DESCRIPTION("Nokia RX-51 battery driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/s3c_adc_battery.c b/drivers/power/supply/s3c_adc_battery.c
new file mode 100644
index 0000000..0ffe5cd
--- /dev/null
+++ b/drivers/power/supply/s3c_adc_battery.c
@@ -0,0 +1,459 @@
+/*
+ * iPAQ h1930/h1940/rx1950 battery controller driver
+ * Copyright (c) Vasily Khoruzhick
+ * Based on h1940_battery.c by Arnaud Patard
+ *
+ * This file is subject to the terms and conditions of the GNU General Public
+ * License. See the file COPYING in the main directory of this archive for
+ * more details.
+ *
+ */
+
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/leds.h>
+#include <linux/gpio.h>
+#include <linux/err.h>
+#include <linux/timer.h>
+#include <linux/jiffies.h>
+#include <linux/s3c_adc_battery.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/module.h>
+
+#include <plat/adc.h>
+
+#define BAT_POLL_INTERVAL 10000 /* ms */
+#define JITTER_DELAY 500 /* ms */
+
+struct s3c_adc_bat {
+ struct power_supply *psy;
+ struct s3c_adc_client *client;
+ struct s3c_adc_bat_pdata *pdata;
+ int volt_value;
+ int cur_value;
+ unsigned int timestamp;
+ int level;
+ int status;
+ int cable_plugged:1;
+};
+
+static struct delayed_work bat_work;
+
+static void s3c_adc_bat_ext_power_changed(struct power_supply *psy)
+{
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+}
+
+static int gather_samples(struct s3c_adc_client *client, int num, int channel)
+{
+ int value, i;
+
+ /* default to 1 if nothing is set */
+ if (num < 1)
+ num = 1;
+
+ value = 0;
+ for (i = 0; i < num; i++)
+ value += s3c_adc_read(client, channel);
+ value /= num;
+
+ return value;
+}
+
+static enum power_supply_property s3c_adc_backup_bat_props[] = {
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+};
+
+static int s3c_adc_backup_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s3c_adc_bat *bat = power_supply_get_drvdata(psy);
+
+ if (!bat) {
+ dev_err(&psy->dev, "%s: no battery infos ?!\n", __func__);
+ return -EINVAL;
+ }
+
+ if (bat->volt_value < 0 ||
+ jiffies_to_msecs(jiffies - bat->timestamp) >
+ BAT_POLL_INTERVAL) {
+ bat->volt_value = gather_samples(bat->client,
+ bat->pdata->backup_volt_samples,
+ bat->pdata->backup_volt_channel);
+ bat->volt_value *= bat->pdata->backup_volt_mult;
+ bat->timestamp = jiffies;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = bat->volt_value;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ val->intval = bat->pdata->backup_volt_min;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->pdata->backup_volt_max;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct power_supply_desc backup_bat_desc = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s3c_adc_backup_bat_props,
+ .num_properties = ARRAY_SIZE(s3c_adc_backup_bat_props),
+ .get_property = s3c_adc_backup_bat_get_property,
+ .use_for_apm = 1,
+};
+
+static struct s3c_adc_bat backup_bat;
+
+static enum power_supply_property s3c_adc_main_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+static int calc_full_volt(int volt_val, int cur_val, int impedance)
+{
+ return volt_val + cur_val * impedance / 1000;
+}
+
+static int charge_finished(struct s3c_adc_bat *bat)
+{
+ return bat->pdata->gpio_inverted ?
+ !gpio_get_value(bat->pdata->gpio_charge_finished) :
+ gpio_get_value(bat->pdata->gpio_charge_finished);
+}
+
+static int s3c_adc_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct s3c_adc_bat *bat = power_supply_get_drvdata(psy);
+
+ int new_level;
+ int full_volt;
+ const struct s3c_adc_bat_thresh *lut;
+ unsigned int lut_size;
+
+ if (!bat) {
+ dev_err(&psy->dev, "no battery infos ?!\n");
+ return -EINVAL;
+ }
+
+ lut = bat->pdata->lut_noac;
+ lut_size = bat->pdata->lut_noac_cnt;
+
+ if (bat->volt_value < 0 || bat->cur_value < 0 ||
+ jiffies_to_msecs(jiffies - bat->timestamp) >
+ BAT_POLL_INTERVAL) {
+ bat->volt_value = gather_samples(bat->client,
+ bat->pdata->volt_samples,
+ bat->pdata->volt_channel) * bat->pdata->volt_mult;
+ bat->cur_value = gather_samples(bat->client,
+ bat->pdata->current_samples,
+ bat->pdata->current_channel) * bat->pdata->current_mult;
+ bat->timestamp = jiffies;
+ }
+
+ if (bat->cable_plugged &&
+ ((bat->pdata->gpio_charge_finished < 0) ||
+ !charge_finished(bat))) {
+ lut = bat->pdata->lut_acin;
+ lut_size = bat->pdata->lut_acin_cnt;
+ }
+
+ new_level = 100000;
+ full_volt = calc_full_volt((bat->volt_value / 1000),
+ (bat->cur_value / 1000), bat->pdata->internal_impedance);
+
+ if (full_volt < calc_full_volt(lut->volt, lut->cur,
+ bat->pdata->internal_impedance)) {
+ lut_size--;
+ while (lut_size--) {
+ int lut_volt1;
+ int lut_volt2;
+
+ lut_volt1 = calc_full_volt(lut[0].volt, lut[0].cur,
+ bat->pdata->internal_impedance);
+ lut_volt2 = calc_full_volt(lut[1].volt, lut[1].cur,
+ bat->pdata->internal_impedance);
+ if (full_volt < lut_volt1 && full_volt >= lut_volt2) {
+ new_level = (lut[1].level +
+ (lut[0].level - lut[1].level) *
+ (full_volt - lut_volt2) /
+ (lut_volt1 - lut_volt2)) * 1000;
+ break;
+ }
+ new_level = lut[1].level * 1000;
+ lut++;
+ }
+ }
+
+ bat->level = new_level;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (bat->pdata->gpio_charge_finished < 0)
+ val->intval = bat->level == 100000 ?
+ POWER_SUPPLY_STATUS_FULL : bat->status;
+ else
+ val->intval = bat->status;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = 100000;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_EMPTY_DESIGN:
+ val->intval = 0;
+ return 0;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = bat->level;
+ return 0;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = bat->volt_value;
+ return 0;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = bat->cur_value;
+ return 0;
+ default:
+ return -EINVAL;
+ }
+}
+
+static const struct power_supply_desc main_bat_desc = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = s3c_adc_main_bat_props,
+ .num_properties = ARRAY_SIZE(s3c_adc_main_bat_props),
+ .get_property = s3c_adc_bat_get_property,
+ .external_power_changed = s3c_adc_bat_ext_power_changed,
+ .use_for_apm = 1,
+};
+
+static struct s3c_adc_bat main_bat;
+
+static void s3c_adc_bat_work(struct work_struct *work)
+{
+ struct s3c_adc_bat *bat = &main_bat;
+ int is_charged;
+ int is_plugged;
+ static int was_plugged;
+
+ is_plugged = power_supply_am_i_supplied(bat->psy);
+ bat->cable_plugged = is_plugged;
+ if (is_plugged != was_plugged) {
+ was_plugged = is_plugged;
+ if (is_plugged) {
+ if (bat->pdata->enable_charger)
+ bat->pdata->enable_charger();
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ } else {
+ if (bat->pdata->disable_charger)
+ bat->pdata->disable_charger();
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+ } else {
+ if ((bat->pdata->gpio_charge_finished >= 0) && is_plugged) {
+ is_charged = charge_finished(&main_bat);
+ if (is_charged) {
+ if (bat->pdata->disable_charger)
+ bat->pdata->disable_charger();
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ if (bat->pdata->enable_charger)
+ bat->pdata->enable_charger();
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ }
+ }
+
+ power_supply_changed(bat->psy);
+}
+
+static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id)
+{
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+ return IRQ_HANDLED;
+}
+
+static int s3c_adc_bat_probe(struct platform_device *pdev)
+{
+ struct s3c_adc_client *client;
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+ int ret;
+
+ client = s3c_adc_register(pdev, NULL, NULL, 0);
+ if (IS_ERR(client)) {
+ dev_err(&pdev->dev, "cannot register adc\n");
+ return PTR_ERR(client);
+ }
+
+ platform_set_drvdata(pdev, client);
+
+ main_bat.client = client;
+ main_bat.pdata = pdata;
+ main_bat.volt_value = -1;
+ main_bat.cur_value = -1;
+ main_bat.cable_plugged = 0;
+ main_bat.status = POWER_SUPPLY_STATUS_DISCHARGING;
+
+ main_bat.psy = power_supply_register(&pdev->dev, &main_bat_desc, NULL);
+ if (IS_ERR(main_bat.psy)) {
+ ret = PTR_ERR(main_bat.psy);
+ goto err_reg_main;
+ }
+ if (pdata->backup_volt_mult) {
+ const struct power_supply_config psy_cfg
+ = { .drv_data = &backup_bat, };
+
+ backup_bat.client = client;
+ backup_bat.pdata = pdev->dev.platform_data;
+ backup_bat.volt_value = -1;
+ backup_bat.psy = power_supply_register(&pdev->dev,
+ &backup_bat_desc,
+ &psy_cfg);
+ if (IS_ERR(backup_bat.psy)) {
+ ret = PTR_ERR(backup_bat.psy);
+ goto err_reg_backup;
+ }
+ }
+
+ INIT_DELAYED_WORK(&bat_work, s3c_adc_bat_work);
+
+ if (pdata->gpio_charge_finished >= 0) {
+ ret = gpio_request(pdata->gpio_charge_finished, "charged");
+ if (ret)
+ goto err_gpio;
+
+ ret = request_irq(gpio_to_irq(pdata->gpio_charge_finished),
+ s3c_adc_bat_charged,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "battery charged", NULL);
+ if (ret)
+ goto err_irq;
+ }
+
+ if (pdata->init) {
+ ret = pdata->init();
+ if (ret)
+ goto err_platform;
+ }
+
+ dev_info(&pdev->dev, "successfully loaded\n");
+ device_init_wakeup(&pdev->dev, 1);
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+
+ return 0;
+
+err_platform:
+ if (pdata->gpio_charge_finished >= 0)
+ free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
+err_irq:
+ if (pdata->gpio_charge_finished >= 0)
+ gpio_free(pdata->gpio_charge_finished);
+err_gpio:
+ if (pdata->backup_volt_mult)
+ power_supply_unregister(backup_bat.psy);
+err_reg_backup:
+ power_supply_unregister(main_bat.psy);
+err_reg_main:
+ return ret;
+}
+
+static int s3c_adc_bat_remove(struct platform_device *pdev)
+{
+ struct s3c_adc_client *client = platform_get_drvdata(pdev);
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ power_supply_unregister(main_bat.psy);
+ if (pdata->backup_volt_mult)
+ power_supply_unregister(backup_bat.psy);
+
+ s3c_adc_release(client);
+
+ if (pdata->gpio_charge_finished >= 0) {
+ free_irq(gpio_to_irq(pdata->gpio_charge_finished), NULL);
+ gpio_free(pdata->gpio_charge_finished);
+ }
+
+ cancel_delayed_work(&bat_work);
+
+ if (pdata->exit)
+ pdata->exit();
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int s3c_adc_bat_suspend(struct platform_device *pdev,
+ pm_message_t state)
+{
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata->gpio_charge_finished >= 0) {
+ if (device_may_wakeup(&pdev->dev))
+ enable_irq_wake(
+ gpio_to_irq(pdata->gpio_charge_finished));
+ else {
+ disable_irq(gpio_to_irq(pdata->gpio_charge_finished));
+ main_bat.pdata->disable_charger();
+ }
+ }
+
+ return 0;
+}
+
+static int s3c_adc_bat_resume(struct platform_device *pdev)
+{
+ struct s3c_adc_bat_pdata *pdata = pdev->dev.platform_data;
+
+ if (pdata->gpio_charge_finished >= 0) {
+ if (device_may_wakeup(&pdev->dev))
+ disable_irq_wake(
+ gpio_to_irq(pdata->gpio_charge_finished));
+ else
+ enable_irq(gpio_to_irq(pdata->gpio_charge_finished));
+ }
+
+ /* Schedule timer to check current status */
+ schedule_delayed_work(&bat_work,
+ msecs_to_jiffies(JITTER_DELAY));
+
+ return 0;
+}
+#else
+#define s3c_adc_bat_suspend NULL
+#define s3c_adc_bat_resume NULL
+#endif
+
+static struct platform_driver s3c_adc_bat_driver = {
+ .driver = {
+ .name = "s3c-adc-battery",
+ },
+ .probe = s3c_adc_bat_probe,
+ .remove = s3c_adc_bat_remove,
+ .suspend = s3c_adc_bat_suspend,
+ .resume = s3c_adc_bat_resume,
+};
+
+module_platform_driver(s3c_adc_bat_driver);
+
+MODULE_AUTHOR("Vasily Khoruzhick <anarsoul@gmail.com>");
+MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c
new file mode 100644
index 0000000..8bb2eb3
--- /dev/null
+++ b/drivers/power/supply/sbs-battery.c
@@ -0,0 +1,934 @@
+/*
+ * Gas Gauge driver for SBS Compliant Batteries
+ *
+ * Copyright (c) 2010, NVIDIA Corporation.
+ *
+ * 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.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/power_supply.h>
+#include <linux/i2c.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/gpio/consumer.h>
+#include <linux/of.h>
+#include <linux/stat.h>
+
+#include <linux/power/sbs-battery.h>
+
+enum {
+ REG_MANUFACTURER_DATA,
+ REG_TEMPERATURE,
+ REG_VOLTAGE,
+ REG_CURRENT,
+ REG_CAPACITY,
+ REG_TIME_TO_EMPTY,
+ REG_TIME_TO_FULL,
+ REG_STATUS,
+ REG_CAPACITY_LEVEL,
+ REG_CYCLE_COUNT,
+ REG_SERIAL_NUMBER,
+ REG_REMAINING_CAPACITY,
+ REG_REMAINING_CAPACITY_CHARGE,
+ REG_FULL_CHARGE_CAPACITY,
+ REG_FULL_CHARGE_CAPACITY_CHARGE,
+ REG_DESIGN_CAPACITY,
+ REG_DESIGN_CAPACITY_CHARGE,
+ REG_DESIGN_VOLTAGE_MIN,
+ REG_DESIGN_VOLTAGE_MAX,
+ REG_MANUFACTURER,
+ REG_MODEL_NAME,
+};
+
+/* Battery Mode defines */
+#define BATTERY_MODE_OFFSET 0x03
+#define BATTERY_MODE_MASK 0x8000
+enum sbs_battery_mode {
+ BATTERY_MODE_AMPS,
+ BATTERY_MODE_WATTS
+};
+
+/* manufacturer access defines */
+#define MANUFACTURER_ACCESS_STATUS 0x0006
+#define MANUFACTURER_ACCESS_SLEEP 0x0011
+
+/* battery status value bits */
+#define BATTERY_INITIALIZED 0x80
+#define BATTERY_DISCHARGING 0x40
+#define BATTERY_FULL_CHARGED 0x20
+#define BATTERY_FULL_DISCHARGED 0x10
+
+/* min_value and max_value are only valid for numerical data */
+#define SBS_DATA(_psp, _addr, _min_value, _max_value) { \
+ .psp = _psp, \
+ .addr = _addr, \
+ .min_value = _min_value, \
+ .max_value = _max_value, \
+}
+
+static const struct chip_data {
+ enum power_supply_property psp;
+ u8 addr;
+ int min_value;
+ int max_value;
+} sbs_data[] = {
+ [REG_MANUFACTURER_DATA] =
+ SBS_DATA(POWER_SUPPLY_PROP_PRESENT, 0x00, 0, 65535),
+ [REG_TEMPERATURE] =
+ SBS_DATA(POWER_SUPPLY_PROP_TEMP, 0x08, 0, 65535),
+ [REG_VOLTAGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_NOW, 0x09, 0, 20000),
+ [REG_CURRENT] =
+ SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767),
+ [REG_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0D, 0, 100),
+ [REG_REMAINING_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_NOW, 0x0F, 0, 65535),
+ [REG_REMAINING_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_NOW, 0x0F, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL, 0x10, 0, 65535),
+ [REG_FULL_CHARGE_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL, 0x10, 0, 65535),
+ [REG_TIME_TO_EMPTY] =
+ SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG, 0x12, 0, 65535),
+ [REG_TIME_TO_FULL] =
+ SBS_DATA(POWER_SUPPLY_PROP_TIME_TO_FULL_AVG, 0x13, 0, 65535),
+ [REG_STATUS] =
+ SBS_DATA(POWER_SUPPLY_PROP_STATUS, 0x16, 0, 65535),
+ [REG_CAPACITY_LEVEL] =
+ SBS_DATA(POWER_SUPPLY_PROP_CAPACITY_LEVEL, 0x16, 0, 65535),
+ [REG_CYCLE_COUNT] =
+ SBS_DATA(POWER_SUPPLY_PROP_CYCLE_COUNT, 0x17, 0, 65535),
+ [REG_DESIGN_CAPACITY] =
+ SBS_DATA(POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN, 0x18, 0, 65535),
+ [REG_DESIGN_CAPACITY_CHARGE] =
+ SBS_DATA(POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, 0x18, 0, 65535),
+ [REG_DESIGN_VOLTAGE_MIN] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, 0x19, 0, 65535),
+ [REG_DESIGN_VOLTAGE_MAX] =
+ SBS_DATA(POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, 0x19, 0, 65535),
+ [REG_SERIAL_NUMBER] =
+ SBS_DATA(POWER_SUPPLY_PROP_SERIAL_NUMBER, 0x1C, 0, 65535),
+ /* Properties of type `const char *' */
+ [REG_MANUFACTURER] =
+ SBS_DATA(POWER_SUPPLY_PROP_MANUFACTURER, 0x20, 0, 65535),
+ [REG_MODEL_NAME] =
+ SBS_DATA(POWER_SUPPLY_PROP_MODEL_NAME, 0x21, 0, 65535)
+};
+
+static enum power_supply_property sbs_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_ENERGY_NOW,
+ POWER_SUPPLY_PROP_ENERGY_FULL,
+ POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ /* Properties of type `const char *' */
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_MODEL_NAME
+};
+
+struct sbs_info {
+ struct i2c_client *client;
+ struct power_supply *power_supply;
+ bool is_present;
+ struct gpio_desc *gpio_detect;
+ bool enable_detection;
+ int last_state;
+ int poll_time;
+ u32 i2c_retry_count;
+ u32 poll_retry_count;
+ struct delayed_work work;
+ int ignore_changes;
+};
+
+static char model_name[I2C_SMBUS_BLOCK_MAX + 1];
+static char manufacturer[I2C_SMBUS_BLOCK_MAX + 1];
+static bool force_load;
+
+static int sbs_read_word_data(struct i2c_client *client, u8 address)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ retries = chip->i2c_retry_count;
+
+ while (retries > 0) {
+ ret = i2c_smbus_read_word_data(client, address);
+ if (ret >= 0)
+ break;
+ retries--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ return le16_to_cpu(ret);
+}
+
+static int sbs_read_string_data(struct i2c_client *client, u8 address,
+ char *values)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0, block_length = 0;
+ int retries_length = 1, retries_block = 1;
+ u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1];
+
+ retries_length = chip->i2c_retry_count;
+ retries_block = chip->i2c_retry_count;
+
+ /* Adapter needs to support these two functions */
+ if (!i2c_check_functionality(client->adapter,
+ I2C_FUNC_SMBUS_BYTE_DATA |
+ I2C_FUNC_SMBUS_I2C_BLOCK)){
+ return -ENODEV;
+ }
+
+ /* Get the length of block data */
+ while (retries_length > 0) {
+ ret = i2c_smbus_read_byte_data(client, address);
+ if (ret >= 0)
+ break;
+ retries_length--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ /* block_length does not include NULL terminator */
+ block_length = ret;
+ if (block_length > I2C_SMBUS_BLOCK_MAX) {
+ dev_err(&client->dev,
+ "%s: Returned block_length is longer than 0x%x\n",
+ __func__, I2C_SMBUS_BLOCK_MAX);
+ return -EINVAL;
+ }
+
+ /* Get the block data */
+ while (retries_block > 0) {
+ ret = i2c_smbus_read_i2c_block_data(
+ client, address,
+ block_length + 1, block_buffer);
+ if (ret >= 0)
+ break;
+ retries_block--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c read at address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ /* block_buffer[0] == block_length */
+ memcpy(values, block_buffer + 1, block_length);
+ values[block_length] = '\0';
+
+ return le16_to_cpu(ret);
+}
+
+static int sbs_write_word_data(struct i2c_client *client, u8 address,
+ u16 value)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret = 0;
+ int retries = 1;
+
+ retries = chip->i2c_retry_count;
+
+ while (retries > 0) {
+ ret = i2c_smbus_write_word_data(client, address,
+ le16_to_cpu(value));
+ if (ret >= 0)
+ break;
+ retries--;
+ }
+
+ if (ret < 0) {
+ dev_dbg(&client->dev,
+ "%s: i2c write to address 0x%x failed\n",
+ __func__, address);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int sbs_get_battery_presence_and_health(
+ struct i2c_client *client, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ struct sbs_info *chip = i2c_get_clientdata(client);
+
+ if (psp == POWER_SUPPLY_PROP_PRESENT && chip->gpio_detect) {
+ ret = gpiod_get_value_cansleep(chip->gpio_detect);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ chip->is_present = val->intval;
+ return ret;
+ }
+
+ /*
+ * Write to ManufacturerAccess with ManufacturerAccess command
+ * and then read the status. Do not check for error on the write
+ * since not all batteries implement write access to this command,
+ * while others mandate it.
+ */
+ sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_STATUS);
+
+ ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr);
+ if (ret < 0) {
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ val->intval = 0; /* battery removed */
+ return ret;
+ }
+
+ if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value ||
+ ret > sbs_data[REG_MANUFACTURER_DATA].max_value) {
+ val->intval = 0;
+ return 0;
+ }
+
+ /* Mask the upper nibble of 2nd byte and
+ * lower byte of response then
+ * shift the result by 8 to get status*/
+ ret &= 0x0F00;
+ ret >>= 8;
+ if (psp == POWER_SUPPLY_PROP_PRESENT) {
+ if (ret == 0x0F)
+ /* battery removed */
+ val->intval = 0;
+ else
+ val->intval = 1;
+ } else if (psp == POWER_SUPPLY_PROP_HEALTH) {
+ if (ret == 0x09)
+ val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ else if (ret == 0x0B)
+ val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (ret == 0x0C)
+ val->intval = POWER_SUPPLY_HEALTH_DEAD;
+ else
+ val->intval = POWER_SUPPLY_HEALTH_GOOD;
+ }
+
+ return 0;
+}
+
+static int sbs_get_battery_property(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+ s32 ret;
+
+ ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ /* returned values are 16 bit */
+ if (sbs_data[reg_offset].min_value < 0)
+ ret = (s16)ret;
+
+ if (ret >= sbs_data[reg_offset].min_value &&
+ ret <= sbs_data[reg_offset].max_value) {
+ val->intval = ret;
+ if (psp == POWER_SUPPLY_PROP_CAPACITY_LEVEL) {
+ if (!(ret & BATTERY_INITIALIZED))
+ val->intval =
+ POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ else if (ret & BATTERY_FULL_CHARGED)
+ val->intval =
+ POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ else if (ret & BATTERY_FULL_DISCHARGED)
+ val->intval =
+ POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ else
+ val->intval =
+ POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ return 0;
+ } else if (psp != POWER_SUPPLY_PROP_STATUS) {
+ return 0;
+ }
+
+ if (ret & BATTERY_FULL_CHARGED)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & BATTERY_DISCHARGING)
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (chip->poll_time == 0)
+ chip->last_state = val->intval;
+ else if (chip->last_state != val->intval) {
+ cancel_delayed_work_sync(&chip->work);
+ power_supply_changed(chip->power_supply);
+ chip->poll_time = 0;
+ }
+ } else {
+ if (psp == POWER_SUPPLY_PROP_STATUS)
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ else
+ val->intval = 0;
+ }
+
+ return 0;
+}
+
+static int sbs_get_battery_string_property(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp, char *val)
+{
+ s32 ret;
+
+ ret = sbs_read_string_data(client, sbs_data[reg_offset].addr, val);
+
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static void sbs_unit_adjustment(struct i2c_client *client,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+#define BASE_UNIT_CONVERSION 1000
+#define BATTERY_MODE_CAP_MULT_WATT (10 * BASE_UNIT_CONVERSION)
+#define TIME_UNIT_CONVERSION 60
+#define TEMP_KELVIN_TO_CELSIUS 2731
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ /* sbs provides energy in units of 10mWh.
+ * Convert to µWh
+ */
+ val->intval *= BATTERY_MODE_CAP_MULT_WATT;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval *= BASE_UNIT_CONVERSION;
+ break;
+
+ case POWER_SUPPLY_PROP_TEMP:
+ /* sbs provides battery temperature in 0.1K
+ * so convert it to 0.1°C
+ */
+ val->intval -= TEMP_KELVIN_TO_CELSIUS;
+ break;
+
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ /* sbs provides time to empty and time to full in minutes.
+ * Convert to seconds
+ */
+ val->intval *= TIME_UNIT_CONVERSION;
+ break;
+
+ default:
+ dev_dbg(&client->dev,
+ "%s: no need for unit conversion %d\n", __func__, psp);
+ }
+}
+
+static enum sbs_battery_mode sbs_set_battery_mode(struct i2c_client *client,
+ enum sbs_battery_mode mode)
+{
+ int ret, original_val;
+
+ original_val = sbs_read_word_data(client, BATTERY_MODE_OFFSET);
+ if (original_val < 0)
+ return original_val;
+
+ if ((original_val & BATTERY_MODE_MASK) == mode)
+ return mode;
+
+ if (mode == BATTERY_MODE_AMPS)
+ ret = original_val & ~BATTERY_MODE_MASK;
+ else
+ ret = original_val | BATTERY_MODE_MASK;
+
+ ret = sbs_write_word_data(client, BATTERY_MODE_OFFSET, ret);
+ if (ret < 0)
+ return ret;
+
+ return original_val & BATTERY_MODE_MASK;
+}
+
+static int sbs_get_battery_capacity(struct i2c_client *client,
+ int reg_offset, enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ s32 ret;
+ enum sbs_battery_mode mode = BATTERY_MODE_WATTS;
+
+ if (power_supply_is_amp_property(psp))
+ mode = BATTERY_MODE_AMPS;
+
+ mode = sbs_set_battery_mode(client, mode);
+ if (mode < 0)
+ return mode;
+
+ ret = sbs_read_word_data(client, sbs_data[reg_offset].addr);
+ if (ret < 0)
+ return ret;
+
+ if (psp == POWER_SUPPLY_PROP_CAPACITY) {
+ /* sbs spec says that this can be >100 %
+ * even if max value is 100 % */
+ val->intval = min(ret, 100);
+ } else
+ val->intval = ret;
+
+ ret = sbs_set_battery_mode(client, mode);
+ if (ret < 0)
+ return ret;
+
+ return 0;
+}
+
+static char sbs_serial[5];
+static int sbs_get_battery_serial_number(struct i2c_client *client,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = sbs_read_word_data(client, sbs_data[REG_SERIAL_NUMBER].addr);
+ if (ret < 0)
+ return ret;
+
+ ret = sprintf(sbs_serial, "%04x", ret);
+ val->strval = sbs_serial;
+
+ return 0;
+}
+
+static int sbs_get_property_index(struct i2c_client *client,
+ enum power_supply_property psp)
+{
+ int count;
+ for (count = 0; count < ARRAY_SIZE(sbs_data); count++)
+ if (psp == sbs_data[count].psp)
+ return count;
+
+ dev_warn(&client->dev,
+ "%s: Invalid Property - %d\n", __func__, psp);
+
+ return -EINVAL;
+}
+
+static int sbs_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct sbs_info *chip = power_supply_get_drvdata(psy);
+ struct i2c_client *client = chip->client;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = sbs_get_battery_presence_and_health(client, psp, val);
+ if (psp == POWER_SUPPLY_PROP_PRESENT)
+ return 0;
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ goto done; /* don't trigger power_supply_changed()! */
+
+ case POWER_SUPPLY_PROP_ENERGY_NOW:
+ case POWER_SUPPLY_PROP_ENERGY_FULL:
+ case POWER_SUPPLY_PROP_ENERGY_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_capacity(client, ret, psp, val);
+ break;
+
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ ret = sbs_get_battery_serial_number(client, val);
+ break;
+
+ case POWER_SUPPLY_PROP_STATUS:
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ case POWER_SUPPLY_PROP_TEMP:
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_property(client, ret, psp, val);
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_string_property(client, ret, psp,
+ model_name);
+ val->strval = model_name;
+ break;
+
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ ret = sbs_get_property_index(client, psp);
+ if (ret < 0)
+ break;
+
+ ret = sbs_get_battery_string_property(client, ret, psp,
+ manufacturer);
+ val->strval = manufacturer;
+ break;
+
+ default:
+ dev_err(&client->dev,
+ "%s: INVALID property\n", __func__);
+ return -EINVAL;
+ }
+
+ if (!chip->enable_detection)
+ goto done;
+
+ if (!chip->gpio_detect &&
+ chip->is_present != (ret >= 0)) {
+ chip->is_present = (ret >= 0);
+ power_supply_changed(chip->power_supply);
+ }
+
+done:
+ if (!ret) {
+ /* Convert units to match requirements for power supply class */
+ sbs_unit_adjustment(client, psp, val);
+ }
+
+ dev_dbg(&client->dev,
+ "%s: property = %d, value = %x\n", __func__, psp, val->intval);
+
+ if (ret && chip->is_present)
+ return ret;
+
+ /* battery not present, so return NODATA for properties */
+ if (ret)
+ return -ENODATA;
+
+ return 0;
+}
+
+static irqreturn_t sbs_irq(int irq, void *devid)
+{
+ struct sbs_info *chip = devid;
+ struct power_supply *battery = chip->power_supply;
+ int ret;
+
+ ret = gpiod_get_value_cansleep(chip->gpio_detect);
+ if (ret < 0)
+ return ret;
+ chip->is_present = ret;
+ power_supply_changed(battery);
+
+ return IRQ_HANDLED;
+}
+
+static void sbs_external_power_changed(struct power_supply *psy)
+{
+ struct sbs_info *chip = power_supply_get_drvdata(psy);
+
+ if (chip->ignore_changes > 0) {
+ chip->ignore_changes--;
+ return;
+ }
+
+ /* cancel outstanding work */
+ cancel_delayed_work_sync(&chip->work);
+
+ schedule_delayed_work(&chip->work, HZ);
+ chip->poll_time = chip->poll_retry_count;
+}
+
+static void sbs_delayed_work(struct work_struct *work)
+{
+ struct sbs_info *chip;
+ s32 ret;
+
+ chip = container_of(work, struct sbs_info, work.work);
+
+ ret = sbs_read_word_data(chip->client, sbs_data[REG_STATUS].addr);
+ /* if the read failed, give up on this work */
+ if (ret < 0) {
+ chip->poll_time = 0;
+ return;
+ }
+
+ if (ret & BATTERY_FULL_CHARGED)
+ ret = POWER_SUPPLY_STATUS_FULL;
+ else if (ret & BATTERY_DISCHARGING)
+ ret = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ ret = POWER_SUPPLY_STATUS_CHARGING;
+
+ if (chip->last_state != ret) {
+ chip->poll_time = 0;
+ power_supply_changed(chip->power_supply);
+ return;
+ }
+ if (chip->poll_time > 0) {
+ schedule_delayed_work(&chip->work, HZ);
+ chip->poll_time--;
+ return;
+ }
+}
+
+static const struct power_supply_desc sbs_default_desc = {
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = sbs_properties,
+ .num_properties = ARRAY_SIZE(sbs_properties),
+ .get_property = sbs_get_property,
+ .external_power_changed = sbs_external_power_changed,
+};
+
+static int sbs_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ struct sbs_info *chip;
+ struct power_supply_desc *sbs_desc;
+ struct sbs_platform_data *pdata = client->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+ int rc;
+ int irq;
+
+ sbs_desc = devm_kmemdup(&client->dev, &sbs_default_desc,
+ sizeof(*sbs_desc), GFP_KERNEL);
+ if (!sbs_desc)
+ return -ENOMEM;
+
+ sbs_desc->name = devm_kasprintf(&client->dev, GFP_KERNEL, "sbs-%s",
+ dev_name(&client->dev));
+ if (!sbs_desc->name)
+ return -ENOMEM;
+
+ chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->client = client;
+ chip->enable_detection = false;
+ psy_cfg.of_node = client->dev.of_node;
+ psy_cfg.drv_data = chip;
+ /* ignore first notification of external change, it is generated
+ * from the power_supply_register call back
+ */
+ chip->ignore_changes = 1;
+ chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN;
+
+ /* use pdata if available, fall back to DT properties,
+ * or hardcoded defaults if not
+ */
+ rc = of_property_read_u32(client->dev.of_node, "sbs,i2c-retry-count",
+ &chip->i2c_retry_count);
+ if (rc)
+ chip->i2c_retry_count = 0;
+
+ rc = of_property_read_u32(client->dev.of_node, "sbs,poll-retry-count",
+ &chip->poll_retry_count);
+ if (rc)
+ chip->poll_retry_count = 0;
+
+ if (pdata) {
+ chip->poll_retry_count = pdata->poll_retry_count;
+ chip->i2c_retry_count = pdata->i2c_retry_count;
+ }
+ chip->i2c_retry_count = chip->i2c_retry_count + 1;
+
+ chip->gpio_detect = devm_gpiod_get_optional(&client->dev,
+ "sbs,battery-detect", GPIOD_IN);
+ if (IS_ERR(chip->gpio_detect)) {
+ dev_err(&client->dev, "Failed to get gpio: %ld\n",
+ PTR_ERR(chip->gpio_detect));
+ return PTR_ERR(chip->gpio_detect);
+ }
+
+ i2c_set_clientdata(client, chip);
+
+ if (!chip->gpio_detect)
+ goto skip_gpio;
+
+ irq = gpiod_to_irq(chip->gpio_detect);
+ if (irq <= 0) {
+ dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq);
+ goto skip_gpio;
+ }
+
+ rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ dev_name(&client->dev), chip);
+ if (rc) {
+ dev_warn(&client->dev, "Failed to request irq: %d\n", rc);
+ goto skip_gpio;
+ }
+
+skip_gpio:
+ /*
+ * Before we register, we might need to make sure we can actually talk
+ * to the battery.
+ */
+ if (!(force_load || chip->gpio_detect)) {
+ rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr);
+
+ if (rc < 0) {
+ dev_err(&client->dev, "%s: Failed to get device status\n",
+ __func__);
+ goto exit_psupply;
+ }
+ }
+
+ chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc,
+ &psy_cfg);
+ if (IS_ERR(chip->power_supply)) {
+ dev_err(&client->dev,
+ "%s: Failed to register power supply\n", __func__);
+ rc = PTR_ERR(chip->power_supply);
+ goto exit_psupply;
+ }
+
+ dev_info(&client->dev,
+ "%s: battery gas gauge device registered\n", client->name);
+
+ INIT_DELAYED_WORK(&chip->work, sbs_delayed_work);
+
+ chip->enable_detection = true;
+
+ return 0;
+
+exit_psupply:
+ return rc;
+}
+
+static int sbs_remove(struct i2c_client *client)
+{
+ struct sbs_info *chip = i2c_get_clientdata(client);
+
+ cancel_delayed_work_sync(&chip->work);
+
+ return 0;
+}
+
+#if defined CONFIG_PM_SLEEP
+
+static int sbs_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct sbs_info *chip = i2c_get_clientdata(client);
+
+ if (chip->poll_time > 0)
+ cancel_delayed_work_sync(&chip->work);
+
+ /*
+ * Write to manufacturer access with sleep command.
+ * Support is manufacturer dependend, so ignore errors.
+ */
+ sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr,
+ MANUFACTURER_ACCESS_SLEEP);
+
+ return 0;
+}
+
+static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL);
+#define SBS_PM_OPS (&sbs_pm_ops)
+
+#else
+#define SBS_PM_OPS NULL
+#endif
+
+static const struct i2c_device_id sbs_id[] = {
+ { "bq20z75", 0 },
+ { "sbs-battery", 1 },
+ {}
+};
+MODULE_DEVICE_TABLE(i2c, sbs_id);
+
+static const struct of_device_id sbs_dt_ids[] = {
+ { .compatible = "sbs,sbs-battery" },
+ { .compatible = "ti,bq20z75" },
+ { }
+};
+MODULE_DEVICE_TABLE(of, sbs_dt_ids);
+
+static struct i2c_driver sbs_battery_driver = {
+ .probe = sbs_probe,
+ .remove = sbs_remove,
+ .id_table = sbs_id,
+ .driver = {
+ .name = "sbs-battery",
+ .of_match_table = sbs_dt_ids,
+ .pm = SBS_PM_OPS,
+ },
+};
+module_i2c_driver(sbs_battery_driver);
+
+MODULE_DESCRIPTION("SBS battery monitor driver");
+MODULE_LICENSE("GPL");
+
+module_param(force_load, bool, S_IRUSR | S_IRGRP | S_IROTH);
+MODULE_PARM_DESC(force_load,
+ "Attempt to load the driver even if no battery is connected");
diff --git a/drivers/power/supply/smb347-charger.c b/drivers/power/supply/smb347-charger.c
new file mode 100644
index 0000000..072c518
--- /dev/null
+++ b/drivers/power/supply/smb347-charger.c
@@ -0,0 +1,1334 @@
+/*
+ * Summit Microelectronics SMB347 Battery Charger Driver
+ *
+ * Copyright (C) 2011, Intel Corporation
+ *
+ * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com>
+ * Mika Westerberg <mika.westerberg@linux.intel.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/err.h>
+#include <linux/gpio.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/i2c.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/power/smb347-charger.h>
+#include <linux/regmap.h>
+
+/*
+ * Configuration registers. These are mirrored to volatile RAM and can be
+ * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be
+ * reloaded from non-volatile registers after POR.
+ */
+#define CFG_CHARGE_CURRENT 0x00
+#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0
+#define CFG_CHARGE_CURRENT_FCC_SHIFT 5
+#define CFG_CHARGE_CURRENT_PCC_MASK 0x18
+#define CFG_CHARGE_CURRENT_PCC_SHIFT 3
+#define CFG_CHARGE_CURRENT_TC_MASK 0x07
+#define CFG_CURRENT_LIMIT 0x01
+#define CFG_CURRENT_LIMIT_DC_MASK 0xf0
+#define CFG_CURRENT_LIMIT_DC_SHIFT 4
+#define CFG_CURRENT_LIMIT_USB_MASK 0x0f
+#define CFG_FLOAT_VOLTAGE 0x03
+#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0
+#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6
+#define CFG_STAT 0x05
+#define CFG_STAT_DISABLED BIT(5)
+#define CFG_STAT_ACTIVE_HIGH BIT(7)
+#define CFG_PIN 0x06
+#define CFG_PIN_EN_CTRL_MASK 0x60
+#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40
+#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60
+#define CFG_PIN_EN_APSD_IRQ BIT(1)
+#define CFG_PIN_EN_CHARGER_ERROR BIT(2)
+#define CFG_THERM 0x07
+#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03
+#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0
+#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c
+#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2
+#define CFG_THERM_MONITOR_DISABLED BIT(4)
+#define CFG_SYSOK 0x08
+#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2)
+#define CFG_OTHER 0x09
+#define CFG_OTHER_RID_MASK 0xc0
+#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0
+#define CFG_OTG 0x0a
+#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30
+#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4
+#define CFG_OTG_CC_COMPENSATION_MASK 0xc0
+#define CFG_OTG_CC_COMPENSATION_SHIFT 6
+#define CFG_TEMP_LIMIT 0x0b
+#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03
+#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0
+#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c
+#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2
+#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30
+#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4
+#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0
+#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6
+#define CFG_FAULT_IRQ 0x0c
+#define CFG_FAULT_IRQ_DCIN_UV BIT(2)
+#define CFG_STATUS_IRQ 0x0d
+#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4)
+#define CFG_STATUS_IRQ_CHARGE_TIMEOUT BIT(7)
+#define CFG_ADDRESS 0x0e
+
+/* Command registers */
+#define CMD_A 0x30
+#define CMD_A_CHG_ENABLED BIT(1)
+#define CMD_A_SUSPEND_ENABLED BIT(2)
+#define CMD_A_ALLOW_WRITE BIT(7)
+#define CMD_B 0x31
+#define CMD_C 0x33
+
+/* Interrupt Status registers */
+#define IRQSTAT_A 0x35
+#define IRQSTAT_C 0x37
+#define IRQSTAT_C_TERMINATION_STAT BIT(0)
+#define IRQSTAT_C_TERMINATION_IRQ BIT(1)
+#define IRQSTAT_C_TAPER_IRQ BIT(3)
+#define IRQSTAT_D 0x38
+#define IRQSTAT_D_CHARGE_TIMEOUT_STAT BIT(2)
+#define IRQSTAT_D_CHARGE_TIMEOUT_IRQ BIT(3)
+#define IRQSTAT_E 0x39
+#define IRQSTAT_E_USBIN_UV_STAT BIT(0)
+#define IRQSTAT_E_USBIN_UV_IRQ BIT(1)
+#define IRQSTAT_E_DCIN_UV_STAT BIT(4)
+#define IRQSTAT_E_DCIN_UV_IRQ BIT(5)
+#define IRQSTAT_F 0x3a
+
+/* Status registers */
+#define STAT_A 0x3b
+#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f
+#define STAT_B 0x3c
+#define STAT_C 0x3d
+#define STAT_C_CHG_ENABLED BIT(0)
+#define STAT_C_HOLDOFF_STAT BIT(3)
+#define STAT_C_CHG_MASK 0x06
+#define STAT_C_CHG_SHIFT 1
+#define STAT_C_CHG_TERM BIT(5)
+#define STAT_C_CHARGER_ERROR BIT(6)
+#define STAT_E 0x3f
+
+#define SMB347_MAX_REGISTER 0x3f
+
+/**
+ * struct smb347_charger - smb347 charger instance
+ * @lock: protects concurrent access to online variables
+ * @dev: pointer to device
+ * @regmap: pointer to driver regmap
+ * @mains: power_supply instance for AC/DC power
+ * @usb: power_supply instance for USB power
+ * @battery: power_supply instance for battery
+ * @mains_online: is AC/DC input connected
+ * @usb_online: is USB input connected
+ * @charging_enabled: is charging enabled
+ * @pdata: pointer to platform data
+ */
+struct smb347_charger {
+ struct mutex lock;
+ struct device *dev;
+ struct regmap *regmap;
+ struct power_supply *mains;
+ struct power_supply *usb;
+ struct power_supply *battery;
+ bool mains_online;
+ bool usb_online;
+ bool charging_enabled;
+ const struct smb347_charger_platform_data *pdata;
+};
+
+/* Fast charge current in uA */
+static const unsigned int fcc_tbl[] = {
+ 700000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2000000,
+ 2200000,
+ 2500000,
+};
+
+/* Pre-charge current in uA */
+static const unsigned int pcc_tbl[] = {
+ 100000,
+ 150000,
+ 200000,
+ 250000,
+};
+
+/* Termination current in uA */
+static const unsigned int tc_tbl[] = {
+ 37500,
+ 50000,
+ 100000,
+ 150000,
+ 200000,
+ 250000,
+ 500000,
+ 600000,
+};
+
+/* Input current limit in uA */
+static const unsigned int icl_tbl[] = {
+ 300000,
+ 500000,
+ 700000,
+ 900000,
+ 1200000,
+ 1500000,
+ 1800000,
+ 2000000,
+ 2200000,
+ 2500000,
+};
+
+/* Charge current compensation in uA */
+static const unsigned int ccc_tbl[] = {
+ 250000,
+ 700000,
+ 900000,
+ 1200000,
+};
+
+/* Convert register value to current using lookup table */
+static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val)
+{
+ if (val >= size)
+ return -EINVAL;
+ return tbl[val];
+}
+
+/* Convert current to register value using lookup table */
+static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val)
+{
+ size_t i;
+
+ for (i = 0; i < size; i++)
+ if (val < tbl[i])
+ break;
+ return i > 0 ? i - 1 : -EINVAL;
+}
+
+/**
+ * smb347_update_ps_status - refreshes the power source status
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function checks whether any power source is connected to the charger and
+ * updates internal state accordingly. If there is a change to previous state
+ * function returns %1, otherwise %0 and negative errno in case of errror.
+ */
+static int smb347_update_ps_status(struct smb347_charger *smb)
+{
+ bool usb = false;
+ bool dc = false;
+ unsigned int val;
+ int ret;
+
+ ret = regmap_read(smb->regmap, IRQSTAT_E, &val);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Dc and usb are set depending on whether they are enabled in
+ * platform data _and_ whether corresponding undervoltage is set.
+ */
+ if (smb->pdata->use_mains)
+ dc = !(val & IRQSTAT_E_DCIN_UV_STAT);
+ if (smb->pdata->use_usb)
+ usb = !(val & IRQSTAT_E_USBIN_UV_STAT);
+
+ mutex_lock(&smb->lock);
+ ret = smb->mains_online != dc || smb->usb_online != usb;
+ smb->mains_online = dc;
+ smb->usb_online = usb;
+ mutex_unlock(&smb->lock);
+
+ return ret;
+}
+
+/*
+ * smb347_is_ps_online - returns whether input power source is connected
+ * @smb: pointer to smb347 charger instance
+ *
+ * Returns %true if input power source is connected. Note that this is
+ * dependent on what platform has configured for usable power sources. For
+ * example if USB is disabled, this will return %false even if the USB cable
+ * is connected.
+ */
+static bool smb347_is_ps_online(struct smb347_charger *smb)
+{
+ bool ret;
+
+ mutex_lock(&smb->lock);
+ ret = smb->usb_online || smb->mains_online;
+ mutex_unlock(&smb->lock);
+
+ return ret;
+}
+
+/**
+ * smb347_charging_status - returns status of charging
+ * @smb: pointer to smb347 charger instance
+ *
+ * Function returns charging status. %0 means no charging is in progress,
+ * %1 means pre-charging, %2 fast-charging and %3 taper-charging.
+ */
+static int smb347_charging_status(struct smb347_charger *smb)
+{
+ unsigned int val;
+ int ret;
+
+ if (!smb347_is_ps_online(smb))
+ return 0;
+
+ ret = regmap_read(smb->regmap, STAT_C, &val);
+ if (ret < 0)
+ return 0;
+
+ return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT;
+}
+
+static int smb347_charging_set(struct smb347_charger *smb, bool enable)
+{
+ int ret = 0;
+
+ if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) {
+ dev_dbg(smb->dev, "charging enable/disable in SW disabled\n");
+ return 0;
+ }
+
+ mutex_lock(&smb->lock);
+ if (smb->charging_enabled != enable) {
+ ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED,
+ enable ? CMD_A_CHG_ENABLED : 0);
+ if (!ret)
+ smb->charging_enabled = enable;
+ }
+ mutex_unlock(&smb->lock);
+ return ret;
+}
+
+static inline int smb347_charging_enable(struct smb347_charger *smb)
+{
+ return smb347_charging_set(smb, true);
+}
+
+static inline int smb347_charging_disable(struct smb347_charger *smb)
+{
+ return smb347_charging_set(smb, false);
+}
+
+static int smb347_start_stop_charging(struct smb347_charger *smb)
+{
+ int ret;
+
+ /*
+ * Depending on whether valid power source is connected or not, we
+ * disable or enable the charging. We do it manually because it
+ * depends on how the platform has configured the valid inputs.
+ */
+ if (smb347_is_ps_online(smb)) {
+ ret = smb347_charging_enable(smb);
+ if (ret < 0)
+ dev_err(smb->dev, "failed to enable charging\n");
+ } else {
+ ret = smb347_charging_disable(smb);
+ if (ret < 0)
+ dev_err(smb->dev, "failed to disable charging\n");
+ }
+
+ return ret;
+}
+
+static int smb347_set_charge_current(struct smb347_charger *smb)
+{
+ int ret;
+
+ if (smb->pdata->max_charge_current) {
+ ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl),
+ smb->pdata->max_charge_current);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
+ CFG_CHARGE_CURRENT_FCC_MASK,
+ ret << CFG_CHARGE_CURRENT_FCC_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->pre_charge_current) {
+ ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl),
+ smb->pdata->pre_charge_current);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
+ CFG_CHARGE_CURRENT_PCC_MASK,
+ ret << CFG_CHARGE_CURRENT_PCC_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->termination_current) {
+ ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl),
+ smb->pdata->termination_current);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT,
+ CFG_CHARGE_CURRENT_TC_MASK, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smb347_set_current_limits(struct smb347_charger *smb)
+{
+ int ret;
+
+ if (smb->pdata->mains_current_limit) {
+ ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+ smb->pdata->mains_current_limit);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT,
+ CFG_CURRENT_LIMIT_DC_MASK,
+ ret << CFG_CURRENT_LIMIT_DC_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->usb_hc_current_limit) {
+ ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl),
+ smb->pdata->usb_hc_current_limit);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT,
+ CFG_CURRENT_LIMIT_USB_MASK, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smb347_set_voltage_limits(struct smb347_charger *smb)
+{
+ int ret;
+
+ if (smb->pdata->pre_to_fast_voltage) {
+ ret = smb->pdata->pre_to_fast_voltage;
+
+ /* uV */
+ ret = clamp_val(ret, 2400000, 3000000) - 2400000;
+ ret /= 200000;
+
+ ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE,
+ CFG_FLOAT_VOLTAGE_THRESHOLD_MASK,
+ ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->max_charge_voltage) {
+ ret = smb->pdata->max_charge_voltage;
+
+ /* uV */
+ ret = clamp_val(ret, 3500000, 4500000) - 3500000;
+ ret /= 20000;
+
+ ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE,
+ CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret);
+ if (ret < 0)
+ return ret;
+ }
+
+ return 0;
+}
+
+static int smb347_set_temp_limits(struct smb347_charger *smb)
+{
+ bool enable_therm_monitor = false;
+ int ret = 0;
+ int val;
+
+ if (smb->pdata->chip_temp_threshold) {
+ val = smb->pdata->chip_temp_threshold;
+
+ /* degree C */
+ val = clamp_val(val, 100, 130) - 100;
+ val /= 10;
+
+ ret = regmap_update_bits(smb->regmap, CFG_OTG,
+ CFG_OTG_TEMP_THRESHOLD_MASK,
+ val << CFG_OTG_TEMP_THRESHOLD_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->soft_cold_temp_limit;
+
+ val = clamp_val(val, 0, 15);
+ val /= 5;
+ /* this goes from higher to lower so invert the value */
+ val = ~val & 0x3;
+
+ ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
+ CFG_TEMP_LIMIT_SOFT_COLD_MASK,
+ val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->soft_hot_temp_limit;
+
+ val = clamp_val(val, 40, 55) - 40;
+ val /= 5;
+
+ ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
+ CFG_TEMP_LIMIT_SOFT_HOT_MASK,
+ val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->hard_cold_temp_limit;
+
+ val = clamp_val(val, -5, 10) + 5;
+ val /= 5;
+ /* this goes from higher to lower so invert the value */
+ val = ~val & 0x3;
+
+ ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
+ CFG_TEMP_LIMIT_HARD_COLD_MASK,
+ val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ enable_therm_monitor = true;
+ }
+
+ if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) {
+ val = smb->pdata->hard_hot_temp_limit;
+
+ val = clamp_val(val, 50, 65) - 50;
+ val /= 5;
+
+ ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT,
+ CFG_TEMP_LIMIT_HARD_HOT_MASK,
+ val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ enable_therm_monitor = true;
+ }
+
+ /*
+ * If any of the temperature limits are set, we also enable the
+ * thermistor monitoring.
+ *
+ * When soft limits are hit, the device will start to compensate
+ * current and/or voltage depending on the configuration.
+ *
+ * When hard limit is hit, the device will suspend charging
+ * depending on the configuration.
+ */
+ if (enable_therm_monitor) {
+ ret = regmap_update_bits(smb->regmap, CFG_THERM,
+ CFG_THERM_MONITOR_DISABLED, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->suspend_on_hard_temp_limit) {
+ ret = regmap_update_bits(smb->regmap, CFG_SYSOK,
+ CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->soft_temp_limit_compensation !=
+ SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) {
+ val = smb->pdata->soft_temp_limit_compensation & 0x3;
+
+ ret = regmap_update_bits(smb->regmap, CFG_THERM,
+ CFG_THERM_SOFT_HOT_COMPENSATION_MASK,
+ val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT);
+ if (ret < 0)
+ return ret;
+
+ ret = regmap_update_bits(smb->regmap, CFG_THERM,
+ CFG_THERM_SOFT_COLD_COMPENSATION_MASK,
+ val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (smb->pdata->charge_current_compensation) {
+ val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl),
+ smb->pdata->charge_current_compensation);
+ if (val < 0)
+ return val;
+
+ ret = regmap_update_bits(smb->regmap, CFG_OTG,
+ CFG_OTG_CC_COMPENSATION_MASK,
+ (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT);
+ if (ret < 0)
+ return ret;
+ }
+
+ return ret;
+}
+
+/*
+ * smb347_set_writable - enables/disables writing to non-volatile registers
+ * @smb: pointer to smb347 charger instance
+ *
+ * You can enable/disable writing to the non-volatile configuration
+ * registers by calling this function.
+ *
+ * Returns %0 on success and negative errno in case of failure.
+ */
+static int smb347_set_writable(struct smb347_charger *smb, bool writable)
+{
+ return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE,
+ writable ? CMD_A_ALLOW_WRITE : 0);
+}
+
+static int smb347_hw_init(struct smb347_charger *smb)
+{
+ unsigned int val;
+ int ret;
+
+ ret = smb347_set_writable(smb, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Program the platform specific configuration values to the device
+ * first.
+ */
+ ret = smb347_set_charge_current(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_set_current_limits(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_set_voltage_limits(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_set_temp_limits(smb);
+ if (ret < 0)
+ goto fail;
+
+ /* If USB charging is disabled we put the USB in suspend mode */
+ if (!smb->pdata->use_usb) {
+ ret = regmap_update_bits(smb->regmap, CMD_A,
+ CMD_A_SUSPEND_ENABLED,
+ CMD_A_SUSPEND_ENABLED);
+ if (ret < 0)
+ goto fail;
+ }
+
+ /*
+ * If configured by platform data, we enable hardware Auto-OTG
+ * support for driving VBUS. Otherwise we disable it.
+ */
+ ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK,
+ smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0);
+ if (ret < 0)
+ goto fail;
+
+ /*
+ * Make the charging functionality controllable by a write to the
+ * command register unless pin control is specified in the platform
+ * data.
+ */
+ switch (smb->pdata->enable_control) {
+ case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW:
+ val = CFG_PIN_EN_CTRL_ACTIVE_LOW;
+ break;
+ case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH:
+ val = CFG_PIN_EN_CTRL_ACTIVE_HIGH;
+ break;
+ default:
+ val = 0;
+ break;
+ }
+
+ ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK,
+ val);
+ if (ret < 0)
+ goto fail;
+
+ /* Disable Automatic Power Source Detection (APSD) interrupt. */
+ ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_update_ps_status(smb);
+ if (ret < 0)
+ goto fail;
+
+ ret = smb347_start_stop_charging(smb);
+
+fail:
+ smb347_set_writable(smb, false);
+ return ret;
+}
+
+static irqreturn_t smb347_interrupt(int irq, void *data)
+{
+ struct smb347_charger *smb = data;
+ unsigned int stat_c, irqstat_c, irqstat_d, irqstat_e;
+ bool handled = false;
+ int ret;
+
+ ret = regmap_read(smb->regmap, STAT_C, &stat_c);
+ if (ret < 0) {
+ dev_warn(smb->dev, "reading STAT_C failed\n");
+ return IRQ_NONE;
+ }
+
+ ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c);
+ if (ret < 0) {
+ dev_warn(smb->dev, "reading IRQSTAT_C failed\n");
+ return IRQ_NONE;
+ }
+
+ ret = regmap_read(smb->regmap, IRQSTAT_D, &irqstat_d);
+ if (ret < 0) {
+ dev_warn(smb->dev, "reading IRQSTAT_D failed\n");
+ return IRQ_NONE;
+ }
+
+ ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e);
+ if (ret < 0) {
+ dev_warn(smb->dev, "reading IRQSTAT_E failed\n");
+ return IRQ_NONE;
+ }
+
+ /*
+ * If we get charger error we report the error back to user.
+ * If the error is recovered charging will resume again.
+ */
+ if (stat_c & STAT_C_CHARGER_ERROR) {
+ dev_err(smb->dev, "charging stopped due to charger error\n");
+ power_supply_changed(smb->battery);
+ handled = true;
+ }
+
+ /*
+ * If we reached the termination current the battery is charged and
+ * we can update the status now. Charging is automatically
+ * disabled by the hardware.
+ */
+ if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) {
+ if (irqstat_c & IRQSTAT_C_TERMINATION_STAT)
+ power_supply_changed(smb->battery);
+ dev_dbg(smb->dev, "going to HW maintenance mode\n");
+ handled = true;
+ }
+
+ /*
+ * If we got a charger timeout INT that means the charge
+ * full is not detected with in charge timeout value.
+ */
+ if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_IRQ) {
+ dev_dbg(smb->dev, "total Charge Timeout INT received\n");
+
+ if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT)
+ dev_warn(smb->dev, "charging stopped due to timeout\n");
+ power_supply_changed(smb->battery);
+ handled = true;
+ }
+
+ /*
+ * If we got an under voltage interrupt it means that AC/USB input
+ * was connected or disconnected.
+ */
+ if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) {
+ if (smb347_update_ps_status(smb) > 0) {
+ smb347_start_stop_charging(smb);
+ if (smb->pdata->use_mains)
+ power_supply_changed(smb->mains);
+ if (smb->pdata->use_usb)
+ power_supply_changed(smb->usb);
+ }
+ handled = true;
+ }
+
+ return handled ? IRQ_HANDLED : IRQ_NONE;
+}
+
+static int smb347_irq_set(struct smb347_charger *smb, bool enable)
+{
+ int ret;
+
+ ret = smb347_set_writable(smb, true);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * Enable/disable interrupts for:
+ * - under voltage
+ * - termination current reached
+ * - charger timeout
+ * - charger error
+ */
+ ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff,
+ enable ? CFG_FAULT_IRQ_DCIN_UV : 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff,
+ enable ? (CFG_STATUS_IRQ_TERMINATION_OR_TAPER |
+ CFG_STATUS_IRQ_CHARGE_TIMEOUT) : 0);
+ if (ret < 0)
+ goto fail;
+
+ ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR,
+ enable ? CFG_PIN_EN_CHARGER_ERROR : 0);
+fail:
+ smb347_set_writable(smb, false);
+ return ret;
+}
+
+static inline int smb347_irq_enable(struct smb347_charger *smb)
+{
+ return smb347_irq_set(smb, true);
+}
+
+static inline int smb347_irq_disable(struct smb347_charger *smb)
+{
+ return smb347_irq_set(smb, false);
+}
+
+static int smb347_irq_init(struct smb347_charger *smb,
+ struct i2c_client *client)
+{
+ const struct smb347_charger_platform_data *pdata = smb->pdata;
+ int ret, irq = gpio_to_irq(pdata->irq_gpio);
+
+ ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name);
+ if (ret < 0)
+ goto fail;
+
+ ret = request_threaded_irq(irq, NULL, smb347_interrupt,
+ IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
+ client->name, smb);
+ if (ret < 0)
+ goto fail_gpio;
+
+ ret = smb347_set_writable(smb, true);
+ if (ret < 0)
+ goto fail_irq;
+
+ /*
+ * Configure the STAT output to be suitable for interrupts: disable
+ * all other output (except interrupts) and make it active low.
+ */
+ ret = regmap_update_bits(smb->regmap, CFG_STAT,
+ CFG_STAT_ACTIVE_HIGH | CFG_STAT_DISABLED,
+ CFG_STAT_DISABLED);
+ if (ret < 0)
+ goto fail_readonly;
+
+ smb347_set_writable(smb, false);
+ client->irq = irq;
+ return 0;
+
+fail_readonly:
+ smb347_set_writable(smb, false);
+fail_irq:
+ free_irq(irq, smb);
+fail_gpio:
+ gpio_free(pdata->irq_gpio);
+fail:
+ client->irq = 0;
+ return ret;
+}
+
+/*
+ * Returns the constant charge current programmed
+ * into the charger in uA.
+ */
+static int get_const_charge_current(struct smb347_charger *smb)
+{
+ int ret, intval;
+ unsigned int v;
+
+ if (!smb347_is_ps_online(smb))
+ return -ENODATA;
+
+ ret = regmap_read(smb->regmap, STAT_B, &v);
+ if (ret < 0)
+ return ret;
+
+ /*
+ * The current value is composition of FCC and PCC values
+ * and we can detect which table to use from bit 5.
+ */
+ if (v & 0x20) {
+ intval = hw_to_current(fcc_tbl, ARRAY_SIZE(fcc_tbl), v & 7);
+ } else {
+ v >>= 3;
+ intval = hw_to_current(pcc_tbl, ARRAY_SIZE(pcc_tbl), v & 7);
+ }
+
+ return intval;
+}
+
+/*
+ * Returns the constant charge voltage programmed
+ * into the charger in uV.
+ */
+static int get_const_charge_voltage(struct smb347_charger *smb)
+{
+ int ret, intval;
+ unsigned int v;
+
+ if (!smb347_is_ps_online(smb))
+ return -ENODATA;
+
+ ret = regmap_read(smb->regmap, STAT_A, &v);
+ if (ret < 0)
+ return ret;
+
+ v &= STAT_A_FLOAT_VOLTAGE_MASK;
+ if (v > 0x3d)
+ v = 0x3d;
+
+ intval = 3500000 + v * 20000;
+
+ return intval;
+}
+
+static int smb347_mains_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = smb->mains_online;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = get_const_charge_voltage(smb);
+ if (ret < 0)
+ return ret;
+ else
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = get_const_charge_current(smb);
+ if (ret < 0)
+ return ret;
+ else
+ val->intval = ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_mains_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+};
+
+static int smb347_usb_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb = power_supply_get_drvdata(psy);
+ int ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = smb->usb_online;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+ ret = get_const_charge_voltage(smb);
+ if (ret < 0)
+ return ret;
+ else
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT:
+ ret = get_const_charge_current(smb);
+ if (ret < 0)
+ return ret;
+ else
+ val->intval = ret;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_usb_properties[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+};
+
+static int smb347_get_charging_status(struct smb347_charger *smb)
+{
+ int ret, status;
+ unsigned int val;
+
+ if (!smb347_is_ps_online(smb))
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ ret = regmap_read(smb->regmap, STAT_C, &val);
+ if (ret < 0)
+ return ret;
+
+ if ((val & STAT_C_CHARGER_ERROR) ||
+ (val & STAT_C_HOLDOFF_STAT)) {
+ /*
+ * set to NOT CHARGING upon charger error
+ * or charging has stopped.
+ */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ if ((val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT) {
+ /*
+ * set to charging if battery is in pre-charge,
+ * fast charge or taper charging mode.
+ */
+ status = POWER_SUPPLY_STATUS_CHARGING;
+ } else if (val & STAT_C_CHG_TERM) {
+ /*
+ * set the status to FULL if battery is not in pre
+ * charge, fast charge or taper charging mode AND
+ * charging is terminated at least once.
+ */
+ status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ /*
+ * in this case no charger error or termination
+ * occured but charging is not in progress!!!
+ */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ }
+ }
+
+ return status;
+}
+
+static int smb347_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smb347_charger *smb = power_supply_get_drvdata(psy);
+ const struct smb347_charger_platform_data *pdata = smb->pdata;
+ int ret;
+
+ ret = smb347_update_ps_status(smb);
+ if (ret < 0)
+ return ret;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = smb347_get_charging_status(smb);
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ if (!smb347_is_ps_online(smb))
+ return -ENODATA;
+
+ /*
+ * We handle trickle and pre-charging the same, and taper
+ * and none the same.
+ */
+ switch (smb347_charging_status(smb)) {
+ case 1:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case 2:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+ break;
+
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = pdata->battery_info.technology;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = pdata->battery_info.voltage_min_design;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = pdata->battery_info.voltage_max_design;
+ break;
+
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = pdata->battery_info.charge_full_design;
+ break;
+
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = pdata->battery_info.name;
+ break;
+
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property smb347_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static bool smb347_volatile_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case IRQSTAT_A:
+ case IRQSTAT_C:
+ case IRQSTAT_E:
+ case IRQSTAT_F:
+ case STAT_A:
+ case STAT_B:
+ case STAT_C:
+ case STAT_E:
+ return true;
+ }
+
+ return false;
+}
+
+static bool smb347_readable_reg(struct device *dev, unsigned int reg)
+{
+ switch (reg) {
+ case CFG_CHARGE_CURRENT:
+ case CFG_CURRENT_LIMIT:
+ case CFG_FLOAT_VOLTAGE:
+ case CFG_STAT:
+ case CFG_PIN:
+ case CFG_THERM:
+ case CFG_SYSOK:
+ case CFG_OTHER:
+ case CFG_OTG:
+ case CFG_TEMP_LIMIT:
+ case CFG_FAULT_IRQ:
+ case CFG_STATUS_IRQ:
+ case CFG_ADDRESS:
+ case CMD_A:
+ case CMD_B:
+ case CMD_C:
+ return true;
+ }
+
+ return smb347_volatile_reg(dev, reg);
+}
+
+static const struct regmap_config smb347_regmap = {
+ .reg_bits = 8,
+ .val_bits = 8,
+ .max_register = SMB347_MAX_REGISTER,
+ .volatile_reg = smb347_volatile_reg,
+ .readable_reg = smb347_readable_reg,
+};
+
+static const struct power_supply_desc smb347_mains_desc = {
+ .name = "smb347-mains",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = smb347_mains_get_property,
+ .properties = smb347_mains_properties,
+ .num_properties = ARRAY_SIZE(smb347_mains_properties),
+};
+
+static const struct power_supply_desc smb347_usb_desc = {
+ .name = "smb347-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .get_property = smb347_usb_get_property,
+ .properties = smb347_usb_properties,
+ .num_properties = ARRAY_SIZE(smb347_usb_properties),
+};
+
+static const struct power_supply_desc smb347_battery_desc = {
+ .name = "smb347-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = smb347_battery_get_property,
+ .properties = smb347_battery_properties,
+ .num_properties = ARRAY_SIZE(smb347_battery_properties),
+};
+
+static int smb347_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ static char *battery[] = { "smb347-battery" };
+ const struct smb347_charger_platform_data *pdata;
+ struct power_supply_config mains_usb_cfg = {}, battery_cfg = {};
+ struct device *dev = &client->dev;
+ struct smb347_charger *smb;
+ int ret;
+
+ pdata = dev->platform_data;
+ if (!pdata)
+ return -EINVAL;
+
+ if (!pdata->use_mains && !pdata->use_usb)
+ return -EINVAL;
+
+ smb = devm_kzalloc(dev, sizeof(*smb), GFP_KERNEL);
+ if (!smb)
+ return -ENOMEM;
+
+ i2c_set_clientdata(client, smb);
+
+ mutex_init(&smb->lock);
+ smb->dev = &client->dev;
+ smb->pdata = pdata;
+
+ smb->regmap = devm_regmap_init_i2c(client, &smb347_regmap);
+ if (IS_ERR(smb->regmap))
+ return PTR_ERR(smb->regmap);
+
+ ret = smb347_hw_init(smb);
+ if (ret < 0)
+ return ret;
+
+ mains_usb_cfg.supplied_to = battery;
+ mains_usb_cfg.num_supplicants = ARRAY_SIZE(battery);
+ mains_usb_cfg.drv_data = smb;
+ if (smb->pdata->use_mains) {
+ smb->mains = power_supply_register(dev, &smb347_mains_desc,
+ &mains_usb_cfg);
+ if (IS_ERR(smb->mains))
+ return PTR_ERR(smb->mains);
+ }
+
+ if (smb->pdata->use_usb) {
+ smb->usb = power_supply_register(dev, &smb347_usb_desc,
+ &mains_usb_cfg);
+ if (IS_ERR(smb->usb)) {
+ if (smb->pdata->use_mains)
+ power_supply_unregister(smb->mains);
+ return PTR_ERR(smb->usb);
+ }
+ }
+
+ battery_cfg.drv_data = smb;
+ smb->battery = power_supply_register(dev, &smb347_battery_desc,
+ &battery_cfg);
+ if (IS_ERR(smb->battery)) {
+ if (smb->pdata->use_usb)
+ power_supply_unregister(smb->usb);
+ if (smb->pdata->use_mains)
+ power_supply_unregister(smb->mains);
+ return PTR_ERR(smb->battery);
+ }
+
+ /*
+ * Interrupt pin is optional. If it is connected, we setup the
+ * interrupt support here.
+ */
+ if (pdata->irq_gpio >= 0) {
+ ret = smb347_irq_init(smb, client);
+ if (ret < 0) {
+ dev_warn(dev, "failed to initialize IRQ: %d\n", ret);
+ dev_warn(dev, "disabling IRQ support\n");
+ } else {
+ smb347_irq_enable(smb);
+ }
+ }
+
+ return 0;
+}
+
+static int smb347_remove(struct i2c_client *client)
+{
+ struct smb347_charger *smb = i2c_get_clientdata(client);
+
+ if (client->irq) {
+ smb347_irq_disable(smb);
+ free_irq(client->irq, smb);
+ gpio_free(smb->pdata->irq_gpio);
+ }
+
+ power_supply_unregister(smb->battery);
+ if (smb->pdata->use_usb)
+ power_supply_unregister(smb->usb);
+ if (smb->pdata->use_mains)
+ power_supply_unregister(smb->mains);
+ return 0;
+}
+
+static const struct i2c_device_id smb347_id[] = {
+ { "smb347", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, smb347_id);
+
+static struct i2c_driver smb347_driver = {
+ .driver = {
+ .name = "smb347",
+ },
+ .probe = smb347_probe,
+ .remove = smb347_remove,
+ .id_table = smb347_id,
+};
+
+module_i2c_driver(smb347_driver);
+
+MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>");
+MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>");
+MODULE_DESCRIPTION("SMB347 battery charger driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/test_power.c b/drivers/power/supply/test_power.c
new file mode 100644
index 0000000..57246cd
--- /dev/null
+++ b/drivers/power/supply/test_power.c
@@ -0,0 +1,533 @@
+/*
+ * Power supply driver for testing.
+ *
+ * Copyright 2010 Anton Vorontsov <cbouatmailru@gmail.com>
+ *
+ * Dynamic module parameter code from the Virtual Battery Driver
+ * Copyright (C) 2008 Pylone, Inc.
+ * By: Masashi YOKOTA <yokota@pylone.jp>
+ * Originally found here:
+ * http://downloads.pylone.jp/src/virtual_battery/virtual_battery-0.0.1.tar.bz2
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/vermagic.h>
+
+enum test_power_id {
+ TEST_AC,
+ TEST_BATTERY,
+ TEST_USB,
+ TEST_POWER_NUM,
+};
+
+static int ac_online = 1;
+static int usb_online = 1;
+static int battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+static int battery_health = POWER_SUPPLY_HEALTH_GOOD;
+static int battery_present = 1; /* true */
+static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION;
+static int battery_capacity = 50;
+static int battery_voltage = 3300;
+
+static bool module_initialized;
+
+static int test_power_get_ac_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = ac_online;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int test_power_get_usb_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = usb_online;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int test_power_get_battery_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_MODEL_NAME:
+ val->strval = "Test battery";
+ break;
+ case POWER_SUPPLY_PROP_MANUFACTURER:
+ val->strval = "Linux";
+ break;
+ case POWER_SUPPLY_PROP_SERIAL_NUMBER:
+ val->strval = UTS_RELEASE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = battery_status;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = battery_health;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = battery_present;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = battery_technology;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = battery_capacity;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = 100;
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+ case POWER_SUPPLY_PROP_TIME_TO_FULL_NOW:
+ val->intval = 3600;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = 26;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = battery_voltage;
+ break;
+ default:
+ pr_info("%s: some properties deliberately report errors.\n",
+ __func__);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static enum power_supply_property test_power_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static enum power_supply_property test_power_battery_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+ POWER_SUPPLY_PROP_TIME_TO_FULL_NOW,
+ POWER_SUPPLY_PROP_MODEL_NAME,
+ POWER_SUPPLY_PROP_MANUFACTURER,
+ POWER_SUPPLY_PROP_SERIAL_NUMBER,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+static char *test_power_ac_supplied_to[] = {
+ "test_battery",
+};
+
+static struct power_supply *test_power_supplies[TEST_POWER_NUM];
+
+static const struct power_supply_desc test_power_desc[] = {
+ [TEST_AC] = {
+ .name = "test_ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = test_power_ac_props,
+ .num_properties = ARRAY_SIZE(test_power_ac_props),
+ .get_property = test_power_get_ac_property,
+ },
+ [TEST_BATTERY] = {
+ .name = "test_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = test_power_battery_props,
+ .num_properties = ARRAY_SIZE(test_power_battery_props),
+ .get_property = test_power_get_battery_property,
+ },
+ [TEST_USB] = {
+ .name = "test_usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = test_power_ac_props,
+ .num_properties = ARRAY_SIZE(test_power_ac_props),
+ .get_property = test_power_get_usb_property,
+ },
+};
+
+static const struct power_supply_config test_power_configs[] = {
+ {
+ /* test_ac */
+ .supplied_to = test_power_ac_supplied_to,
+ .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to),
+ }, {
+ /* test_battery */
+ }, {
+ /* test_usb */
+ .supplied_to = test_power_ac_supplied_to,
+ .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to),
+ },
+};
+
+static int __init test_power_init(void)
+{
+ int i;
+ int ret;
+
+ BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_supplies));
+ BUILD_BUG_ON(TEST_POWER_NUM != ARRAY_SIZE(test_power_configs));
+
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) {
+ test_power_supplies[i] = power_supply_register(NULL,
+ &test_power_desc[i],
+ &test_power_configs[i]);
+ if (IS_ERR(test_power_supplies[i])) {
+ pr_err("%s: failed to register %s\n", __func__,
+ test_power_desc[i].name);
+ ret = PTR_ERR(test_power_supplies[i]);
+ goto failed;
+ }
+ }
+
+ module_initialized = true;
+ return 0;
+failed:
+ while (--i >= 0)
+ power_supply_unregister(test_power_supplies[i]);
+ return ret;
+}
+module_init(test_power_init);
+
+static void __exit test_power_exit(void)
+{
+ int i;
+
+ /* Let's see how we handle changes... */
+ ac_online = 0;
+ usb_online = 0;
+ battery_status = POWER_SUPPLY_STATUS_DISCHARGING;
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++)
+ power_supply_changed(test_power_supplies[i]);
+ pr_info("%s: 'changed' event sent, sleeping for 10 seconds...\n",
+ __func__);
+ ssleep(10);
+
+ for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++)
+ power_supply_unregister(test_power_supplies[i]);
+
+ module_initialized = false;
+}
+module_exit(test_power_exit);
+
+
+
+#define MAX_KEYLENGTH 256
+struct battery_property_map {
+ int value;
+ char const *key;
+};
+
+static struct battery_property_map map_ac_online[] = {
+ { 0, "off" },
+ { 1, "on" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_status[] = {
+ { POWER_SUPPLY_STATUS_CHARGING, "charging" },
+ { POWER_SUPPLY_STATUS_DISCHARGING, "discharging" },
+ { POWER_SUPPLY_STATUS_NOT_CHARGING, "not-charging" },
+ { POWER_SUPPLY_STATUS_FULL, "full" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_health[] = {
+ { POWER_SUPPLY_HEALTH_GOOD, "good" },
+ { POWER_SUPPLY_HEALTH_OVERHEAT, "overheat" },
+ { POWER_SUPPLY_HEALTH_DEAD, "dead" },
+ { POWER_SUPPLY_HEALTH_OVERVOLTAGE, "overvoltage" },
+ { POWER_SUPPLY_HEALTH_UNSPEC_FAILURE, "failure" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_present[] = {
+ { 0, "false" },
+ { 1, "true" },
+ { -1, NULL },
+};
+
+static struct battery_property_map map_technology[] = {
+ { POWER_SUPPLY_TECHNOLOGY_NiMH, "NiMH" },
+ { POWER_SUPPLY_TECHNOLOGY_LION, "LION" },
+ { POWER_SUPPLY_TECHNOLOGY_LIPO, "LIPO" },
+ { POWER_SUPPLY_TECHNOLOGY_LiFe, "LiFe" },
+ { POWER_SUPPLY_TECHNOLOGY_NiCd, "NiCd" },
+ { POWER_SUPPLY_TECHNOLOGY_LiMn, "LiMn" },
+ { -1, NULL },
+};
+
+
+static int map_get_value(struct battery_property_map *map, const char *key,
+ int def_val)
+{
+ char buf[MAX_KEYLENGTH];
+ int cr;
+
+ strncpy(buf, key, MAX_KEYLENGTH);
+ buf[MAX_KEYLENGTH-1] = '\0';
+
+ cr = strnlen(buf, MAX_KEYLENGTH) - 1;
+ if (cr < 0)
+ return def_val;
+ if (buf[cr] == '\n')
+ buf[cr] = '\0';
+
+ while (map->key) {
+ if (strncasecmp(map->key, buf, MAX_KEYLENGTH) == 0)
+ return map->value;
+ map++;
+ }
+
+ return def_val;
+}
+
+
+static const char *map_get_key(struct battery_property_map *map, int value,
+ const char *def_key)
+{
+ while (map->key) {
+ if (map->value == value)
+ return map->key;
+ map++;
+ }
+
+ return def_key;
+}
+
+static inline void signal_power_supply_changed(struct power_supply *psy)
+{
+ if (module_initialized)
+ power_supply_changed(psy);
+}
+
+static int param_set_ac_online(const char *key, const struct kernel_param *kp)
+{
+ ac_online = map_get_value(map_ac_online, key, ac_online);
+ signal_power_supply_changed(test_power_supplies[TEST_AC]);
+ return 0;
+}
+
+static int param_get_ac_online(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_ac_online, ac_online, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_usb_online(const char *key, const struct kernel_param *kp)
+{
+ usb_online = map_get_value(map_ac_online, key, usb_online);
+ signal_power_supply_changed(test_power_supplies[TEST_USB]);
+ return 0;
+}
+
+static int param_get_usb_online(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_ac_online, usb_online, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_status(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_status = map_get_value(map_status, key, battery_status);
+ signal_power_supply_changed(test_power_supplies[TEST_BATTERY]);
+ return 0;
+}
+
+static int param_get_battery_status(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_status, battery_status, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_health(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_health = map_get_value(map_health, key, battery_health);
+ signal_power_supply_changed(test_power_supplies[TEST_BATTERY]);
+ return 0;
+}
+
+static int param_get_battery_health(char *buffer, const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_health, battery_health, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_present(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_present = map_get_value(map_present, key, battery_present);
+ signal_power_supply_changed(test_power_supplies[TEST_AC]);
+ return 0;
+}
+
+static int param_get_battery_present(char *buffer,
+ const struct kernel_param *kp)
+{
+ strcpy(buffer, map_get_key(map_present, battery_present, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_technology(const char *key,
+ const struct kernel_param *kp)
+{
+ battery_technology = map_get_value(map_technology, key,
+ battery_technology);
+ signal_power_supply_changed(test_power_supplies[TEST_BATTERY]);
+ return 0;
+}
+
+static int param_get_battery_technology(char *buffer,
+ const struct kernel_param *kp)
+{
+ strcpy(buffer,
+ map_get_key(map_technology, battery_technology, "unknown"));
+ return strlen(buffer);
+}
+
+static int param_set_battery_capacity(const char *key,
+ const struct kernel_param *kp)
+{
+ int tmp;
+
+ if (1 != sscanf(key, "%d", &tmp))
+ return -EINVAL;
+
+ battery_capacity = tmp;
+ signal_power_supply_changed(test_power_supplies[TEST_BATTERY]);
+ return 0;
+}
+
+#define param_get_battery_capacity param_get_int
+
+static int param_set_battery_voltage(const char *key,
+ const struct kernel_param *kp)
+{
+ int tmp;
+
+ if (1 != sscanf(key, "%d", &tmp))
+ return -EINVAL;
+
+ battery_voltage = tmp;
+ signal_power_supply_changed(test_power_supplies[TEST_BATTERY]);
+ return 0;
+}
+
+#define param_get_battery_voltage param_get_int
+
+static const struct kernel_param_ops param_ops_ac_online = {
+ .set = param_set_ac_online,
+ .get = param_get_ac_online,
+};
+
+static const struct kernel_param_ops param_ops_usb_online = {
+ .set = param_set_usb_online,
+ .get = param_get_usb_online,
+};
+
+static const struct kernel_param_ops param_ops_battery_status = {
+ .set = param_set_battery_status,
+ .get = param_get_battery_status,
+};
+
+static const struct kernel_param_ops param_ops_battery_present = {
+ .set = param_set_battery_present,
+ .get = param_get_battery_present,
+};
+
+static const struct kernel_param_ops param_ops_battery_technology = {
+ .set = param_set_battery_technology,
+ .get = param_get_battery_technology,
+};
+
+static const struct kernel_param_ops param_ops_battery_health = {
+ .set = param_set_battery_health,
+ .get = param_get_battery_health,
+};
+
+static const struct kernel_param_ops param_ops_battery_capacity = {
+ .set = param_set_battery_capacity,
+ .get = param_get_battery_capacity,
+};
+
+static const struct kernel_param_ops param_ops_battery_voltage = {
+ .set = param_set_battery_voltage,
+ .get = param_get_battery_voltage,
+};
+
+#define param_check_ac_online(name, p) __param_check(name, p, void);
+#define param_check_usb_online(name, p) __param_check(name, p, void);
+#define param_check_battery_status(name, p) __param_check(name, p, void);
+#define param_check_battery_present(name, p) __param_check(name, p, void);
+#define param_check_battery_technology(name, p) __param_check(name, p, void);
+#define param_check_battery_health(name, p) __param_check(name, p, void);
+#define param_check_battery_capacity(name, p) __param_check(name, p, void);
+#define param_check_battery_voltage(name, p) __param_check(name, p, void);
+
+
+module_param(ac_online, ac_online, 0644);
+MODULE_PARM_DESC(ac_online, "AC charging state <on|off>");
+
+module_param(usb_online, usb_online, 0644);
+MODULE_PARM_DESC(usb_online, "USB charging state <on|off>");
+
+module_param(battery_status, battery_status, 0644);
+MODULE_PARM_DESC(battery_status,
+ "battery status <charging|discharging|not-charging|full>");
+
+module_param(battery_present, battery_present, 0644);
+MODULE_PARM_DESC(battery_present,
+ "battery presence state <good|overheat|dead|overvoltage|failure>");
+
+module_param(battery_technology, battery_technology, 0644);
+MODULE_PARM_DESC(battery_technology,
+ "battery technology <NiMH|LION|LIPO|LiFe|NiCd|LiMn>");
+
+module_param(battery_health, battery_health, 0644);
+MODULE_PARM_DESC(battery_health,
+ "battery health state <good|overheat|dead|overvoltage|failure>");
+
+module_param(battery_capacity, battery_capacity, 0644);
+MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)");
+
+module_param(battery_voltage, battery_voltage, 0644);
+MODULE_PARM_DESC(battery_voltage, "battery voltage (millivolts)");
+
+MODULE_DESCRIPTION("Power supply driver for testing");
+MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>");
+MODULE_LICENSE("GPL");
diff --git a/drivers/power/supply/tosa_battery.c b/drivers/power/supply/tosa_battery.c
new file mode 100644
index 0000000..6e88c1b
--- /dev/null
+++ b/drivers/power/supply/tosa_battery.c
@@ -0,0 +1,470 @@
+/*
+ * Battery and Power Management code for the Sharp SL-6000x
+ *
+ * Copyright (c) 2005 Dirk Opfer
+ * Copyright (c) 2008 Dmitry Baryshkov
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/power_supply.h>
+#include <linux/wm97xx.h>
+#include <linux/delay.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+
+#include <asm/mach-types.h>
+#include <mach/tosa.h>
+
+static DEFINE_MUTEX(bat_lock); /* protects gpio pins */
+static struct work_struct bat_work;
+
+struct tosa_bat {
+ int status;
+ struct power_supply *psy;
+ int full_chrg;
+
+ struct mutex work_lock; /* protects data */
+
+ bool (*is_present)(struct tosa_bat *bat);
+ int gpio_full;
+ int gpio_charge_off;
+
+ int technology;
+
+ int gpio_bat;
+ int adc_bat;
+ int adc_bat_divider;
+ int bat_max;
+ int bat_min;
+
+ int gpio_temp;
+ int adc_temp;
+ int adc_temp_divider;
+};
+
+static struct tosa_bat tosa_bat_main;
+static struct tosa_bat tosa_bat_jacket;
+
+static unsigned long tosa_read_bat(struct tosa_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_bat < 0 || bat->adc_bat < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_bat, 1);
+ msleep(5);
+ value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent),
+ bat->adc_bat);
+ gpio_set_value(bat->gpio_bat, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 1000000 / bat->adc_bat_divider;
+
+ return value;
+}
+
+static unsigned long tosa_read_temp(struct tosa_bat *bat)
+{
+ unsigned long value = 0;
+
+ if (bat->gpio_temp < 0 || bat->adc_temp < 0)
+ return 0;
+
+ mutex_lock(&bat_lock);
+ gpio_set_value(bat->gpio_temp, 1);
+ msleep(5);
+ value = wm97xx_read_aux_adc(dev_get_drvdata(bat->psy->dev.parent),
+ bat->adc_temp);
+ gpio_set_value(bat->gpio_temp, 0);
+ mutex_unlock(&bat_lock);
+
+ value = value * 10000 / bat->adc_temp_divider;
+
+ return value;
+}
+
+static int tosa_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ int ret = 0;
+ struct tosa_bat *bat = power_supply_get_drvdata(psy);
+
+ if (bat->is_present && !bat->is_present(bat)
+ && psp != POWER_SUPPLY_PROP_PRESENT) {
+ return -ENODEV;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat->status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = bat->technology;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = tosa_read_bat(bat);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (bat->full_chrg == -1)
+ val->intval = bat->bat_max;
+ else
+ val->intval = bat->full_chrg;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = bat->bat_max;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN:
+ val->intval = bat->bat_min;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = tosa_read_temp(bat);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = bat->is_present ? bat->is_present(bat) : 1;
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static bool tosa_jacket_bat_is_present(struct tosa_bat *bat)
+{
+ return gpio_get_value(TOSA_GPIO_JACKET_DETECT) == 0;
+}
+
+static void tosa_bat_external_power_changed(struct power_supply *psy)
+{
+ schedule_work(&bat_work);
+}
+
+static irqreturn_t tosa_bat_gpio_isr(int irq, void *data)
+{
+ pr_info("tosa_bat_gpio irq\n");
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+static void tosa_bat_update(struct tosa_bat *bat)
+{
+ int old;
+ struct power_supply *psy = bat->psy;
+
+ mutex_lock(&bat->work_lock);
+
+ old = bat->status;
+
+ if (bat->is_present && !bat->is_present(bat)) {
+ printk(KERN_NOTICE "%s not present\n", psy->desc->name);
+ bat->status = POWER_SUPPLY_STATUS_UNKNOWN;
+ bat->full_chrg = -1;
+ } else if (power_supply_am_i_supplied(psy)) {
+ if (bat->status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ gpio_set_value(bat->gpio_charge_off, 0);
+ mdelay(15);
+ }
+
+ if (gpio_get_value(bat->gpio_full)) {
+ if (old == POWER_SUPPLY_STATUS_CHARGING ||
+ bat->full_chrg == -1)
+ bat->full_chrg = tosa_read_bat(bat);
+
+ gpio_set_value(bat->gpio_charge_off, 1);
+ bat->status = POWER_SUPPLY_STATUS_FULL;
+ } else {
+ gpio_set_value(bat->gpio_charge_off, 0);
+ bat->status = POWER_SUPPLY_STATUS_CHARGING;
+ }
+ } else {
+ gpio_set_value(bat->gpio_charge_off, 1);
+ bat->status = POWER_SUPPLY_STATUS_DISCHARGING;
+ }
+
+ if (old != bat->status)
+ power_supply_changed(psy);
+
+ mutex_unlock(&bat->work_lock);
+}
+
+static void tosa_bat_work(struct work_struct *work)
+{
+ tosa_bat_update(&tosa_bat_main);
+ tosa_bat_update(&tosa_bat_jacket);
+}
+
+
+static enum power_supply_property tosa_bat_main_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static enum power_supply_property tosa_bat_bu_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+static const struct power_supply_desc tosa_bat_main_desc = {
+ .name = "main-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_main_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_main_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+ .use_for_apm = 1,
+};
+
+static const struct power_supply_desc tosa_bat_jacket_desc = {
+ .name = "jacket-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_main_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_main_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+};
+
+static const struct power_supply_desc tosa_bat_bu_desc = {
+ .name = "backup-battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = tosa_bat_bu_props,
+ .num_properties = ARRAY_SIZE(tosa_bat_bu_props),
+ .get_property = tosa_bat_get_property,
+ .external_power_changed = tosa_bat_external_power_changed,
+};
+
+static struct tosa_bat tosa_bat_main = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = NULL,
+
+ .gpio_full = TOSA_GPIO_BAT0_CRG,
+ .gpio_charge_off = TOSA_GPIO_CHARGE_OFF,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = TOSA_GPIO_BAT0_V_ON,
+ .adc_bat = WM97XX_AUX_ID3,
+ .adc_bat_divider = 414,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = TOSA_GPIO_BAT1_TH_ON,
+ .adc_temp = WM97XX_AUX_ID2,
+ .adc_temp_divider = 10000,
+};
+
+static struct tosa_bat tosa_bat_jacket = {
+ .status = POWER_SUPPLY_STATUS_DISCHARGING,
+ .full_chrg = -1,
+ .psy = NULL,
+
+ .is_present = tosa_jacket_bat_is_present,
+ .gpio_full = TOSA_GPIO_BAT1_CRG,
+ .gpio_charge_off = TOSA_GPIO_CHARGE_OFF_JC,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LIPO,
+
+ .gpio_bat = TOSA_GPIO_BAT1_V_ON,
+ .adc_bat = WM97XX_AUX_ID3,
+ .adc_bat_divider = 414,
+ .bat_max = 4310000,
+ .bat_min = 1551 * 1000000 / 414,
+
+ .gpio_temp = TOSA_GPIO_BAT0_TH_ON,
+ .adc_temp = WM97XX_AUX_ID2,
+ .adc_temp_divider = 10000,
+};
+
+static struct tosa_bat tosa_bat_bu = {
+ .status = POWER_SUPPLY_STATUS_UNKNOWN,
+ .full_chrg = -1,
+ .psy = NULL,
+
+ .gpio_full = -1,
+ .gpio_charge_off = -1,
+
+ .technology = POWER_SUPPLY_TECHNOLOGY_LiMn,
+
+ .gpio_bat = TOSA_GPIO_BU_CHRG_ON,
+ .adc_bat = WM97XX_AUX_ID4,
+ .adc_bat_divider = 1266,
+
+ .gpio_temp = -1,
+ .adc_temp = -1,
+ .adc_temp_divider = -1,
+};
+
+static struct gpio tosa_bat_gpios[] = {
+ { TOSA_GPIO_CHARGE_OFF, GPIOF_OUT_INIT_HIGH, "main charge off" },
+ { TOSA_GPIO_CHARGE_OFF_JC, GPIOF_OUT_INIT_HIGH, "jacket charge off" },
+ { TOSA_GPIO_BAT_SW_ON, GPIOF_OUT_INIT_LOW, "battery switch" },
+ { TOSA_GPIO_BAT0_V_ON, GPIOF_OUT_INIT_LOW, "main battery" },
+ { TOSA_GPIO_BAT1_V_ON, GPIOF_OUT_INIT_LOW, "jacket battery" },
+ { TOSA_GPIO_BAT1_TH_ON, GPIOF_OUT_INIT_LOW, "main battery temp" },
+ { TOSA_GPIO_BAT0_TH_ON, GPIOF_OUT_INIT_LOW, "jacket battery temp" },
+ { TOSA_GPIO_BU_CHRG_ON, GPIOF_OUT_INIT_LOW, "backup battery" },
+ { TOSA_GPIO_BAT0_CRG, GPIOF_IN, "main battery full" },
+ { TOSA_GPIO_BAT1_CRG, GPIOF_IN, "jacket battery full" },
+ { TOSA_GPIO_BAT0_LOW, GPIOF_IN, "main battery low" },
+ { TOSA_GPIO_BAT1_LOW, GPIOF_IN, "jacket battery low" },
+ { TOSA_GPIO_JACKET_DETECT, GPIOF_IN, "jacket detect" },
+};
+
+#ifdef CONFIG_PM
+static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state)
+{
+ /* flush all pending status updates */
+ flush_work(&bat_work);
+ return 0;
+}
+
+static int tosa_bat_resume(struct platform_device *dev)
+{
+ /* things may have changed while we were away */
+ schedule_work(&bat_work);
+ return 0;
+}
+#else
+#define tosa_bat_suspend NULL
+#define tosa_bat_resume NULL
+#endif
+
+static int tosa_bat_probe(struct platform_device *dev)
+{
+ int ret;
+ struct power_supply_config main_psy_cfg = {},
+ jacket_psy_cfg = {},
+ bu_psy_cfg = {};
+
+ if (!machine_is_tosa())
+ return -ENODEV;
+
+ ret = gpio_request_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ if (ret)
+ return ret;
+
+ mutex_init(&tosa_bat_main.work_lock);
+ mutex_init(&tosa_bat_jacket.work_lock);
+
+ INIT_WORK(&bat_work, tosa_bat_work);
+
+ main_psy_cfg.drv_data = &tosa_bat_main;
+ tosa_bat_main.psy = power_supply_register(&dev->dev,
+ &tosa_bat_main_desc,
+ &main_psy_cfg);
+ if (IS_ERR(tosa_bat_main.psy)) {
+ ret = PTR_ERR(tosa_bat_main.psy);
+ goto err_psy_reg_main;
+ }
+
+ jacket_psy_cfg.drv_data = &tosa_bat_jacket;
+ tosa_bat_jacket.psy = power_supply_register(&dev->dev,
+ &tosa_bat_jacket_desc,
+ &jacket_psy_cfg);
+ if (IS_ERR(tosa_bat_jacket.psy)) {
+ ret = PTR_ERR(tosa_bat_jacket.psy);
+ goto err_psy_reg_jacket;
+ }
+
+ bu_psy_cfg.drv_data = &tosa_bat_bu;
+ tosa_bat_bu.psy = power_supply_register(&dev->dev, &tosa_bat_bu_desc,
+ &bu_psy_cfg);
+ if (IS_ERR(tosa_bat_bu.psy)) {
+ ret = PTR_ERR(tosa_bat_bu.psy);
+ goto err_psy_reg_bu;
+ }
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "main full", &tosa_bat_main);
+ if (ret)
+ goto err_req_main;
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "jacket full", &tosa_bat_jacket);
+ if (ret)
+ goto err_req_jacket;
+
+ ret = request_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT),
+ tosa_bat_gpio_isr,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "jacket detect", &tosa_bat_jacket);
+ if (!ret) {
+ schedule_work(&bat_work);
+ return 0;
+ }
+
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
+err_req_jacket:
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
+err_req_main:
+ power_supply_unregister(tosa_bat_bu.psy);
+err_psy_reg_bu:
+ power_supply_unregister(tosa_bat_jacket.psy);
+err_psy_reg_jacket:
+ power_supply_unregister(tosa_bat_main.psy);
+err_psy_reg_main:
+
+ /* see comment in tosa_bat_remove */
+ cancel_work_sync(&bat_work);
+
+ gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ return ret;
+}
+
+static int tosa_bat_remove(struct platform_device *dev)
+{
+ free_irq(gpio_to_irq(TOSA_GPIO_JACKET_DETECT), &tosa_bat_jacket);
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT1_CRG), &tosa_bat_jacket);
+ free_irq(gpio_to_irq(TOSA_GPIO_BAT0_CRG), &tosa_bat_main);
+
+ power_supply_unregister(tosa_bat_bu.psy);
+ power_supply_unregister(tosa_bat_jacket.psy);
+ power_supply_unregister(tosa_bat_main.psy);
+
+ /*
+ * Now cancel the bat_work. We won't get any more schedules,
+ * since all sources (isr and external_power_changed) are
+ * unregistered now.
+ */
+ cancel_work_sync(&bat_work);
+ gpio_free_array(tosa_bat_gpios, ARRAY_SIZE(tosa_bat_gpios));
+ return 0;
+}
+
+static struct platform_driver tosa_bat_driver = {
+ .driver.name = "wm97xx-battery",
+ .driver.owner = THIS_MODULE,
+ .probe = tosa_bat_probe,
+ .remove = tosa_bat_remove,
+ .suspend = tosa_bat_suspend,
+ .resume = tosa_bat_resume,
+};
+
+module_platform_driver(tosa_bat_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Dmitry Baryshkov");
+MODULE_DESCRIPTION("Tosa battery driver");
+MODULE_ALIAS("platform:wm97xx-battery");
diff --git a/drivers/power/supply/tps65090-charger.c b/drivers/power/supply/tps65090-charger.c
new file mode 100644
index 0000000..1b4b5e0
--- /dev/null
+++ b/drivers/power/supply/tps65090-charger.c
@@ -0,0 +1,370 @@
+/*
+ * Battery charger driver for TI's tps65090
+ *
+ * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved.
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/freezer.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/module.h>
+#include <linux/of_device.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/tps65090.h>
+
+#define TPS65090_CHARGER_ENABLE BIT(0)
+#define TPS65090_VACG BIT(1)
+#define TPS65090_NOITERM BIT(5)
+
+#define POLL_INTERVAL (HZ * 2) /* Used when no irq */
+
+struct tps65090_charger {
+ struct device *dev;
+ int ac_online;
+ int prev_ac_online;
+ int irq;
+ struct task_struct *poll_task;
+ bool passive_mode;
+ struct power_supply *ac;
+ struct tps65090_platform_data *pdata;
+};
+
+static enum power_supply_property tps65090_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int tps65090_low_chrg_current(struct tps65090_charger *charger)
+{
+ int ret;
+
+ if (charger->passive_mode)
+ return 0;
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5,
+ TPS65090_NOITERM);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL5);
+ return ret;
+ }
+ return 0;
+}
+
+static int tps65090_enable_charging(struct tps65090_charger *charger)
+{
+ int ret;
+ uint8_t ctrl0 = 0;
+
+ if (charger->passive_mode)
+ return 0;
+
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0,
+ &ctrl0);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL0,
+ (ctrl0 | TPS65090_CHARGER_ENABLE));
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error writing in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+ return 0;
+}
+
+static int tps65090_config_charger(struct tps65090_charger *charger)
+{
+ uint8_t intrmask = 0;
+ int ret;
+
+ if (charger->passive_mode)
+ return 0;
+
+ if (charger->pdata->enable_low_current_chrg) {
+ ret = tps65090_low_chrg_current(charger);
+ if (ret < 0) {
+ dev_err(charger->dev,
+ "error configuring low charge current\n");
+ return ret;
+ }
+ }
+
+ /* Enable the VACG interrupt for AC power detect */
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_MASK,
+ &intrmask);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error reading in register 0x%x\n",
+ __func__, TPS65090_REG_INTR_MASK);
+ return ret;
+ }
+
+ ret = tps65090_write(charger->dev->parent, TPS65090_REG_INTR_MASK,
+ (intrmask | TPS65090_VACG));
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): error writing in register 0x%x\n",
+ __func__, TPS65090_REG_CG_CTRL0);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps65090_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct tps65090_charger *charger = power_supply_get_drvdata(psy);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE) {
+ val->intval = charger->ac_online;
+ charger->prev_ac_online = charger->ac_online;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static irqreturn_t tps65090_charger_isr(int irq, void *dev_id)
+{
+ struct tps65090_charger *charger = dev_id;
+ int ret;
+ uint8_t status1 = 0;
+ uint8_t intrsts = 0;
+
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_STATUS1,
+ &status1);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n",
+ __func__, TPS65090_REG_CG_STATUS1);
+ return IRQ_HANDLED;
+ }
+ msleep(75);
+ ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_STS,
+ &intrsts);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n",
+ __func__, TPS65090_REG_INTR_STS);
+ return IRQ_HANDLED;
+ }
+
+ if (intrsts & TPS65090_VACG) {
+ ret = tps65090_enable_charging(charger);
+ if (ret < 0)
+ return IRQ_HANDLED;
+ charger->ac_online = 1;
+ } else {
+ charger->ac_online = 0;
+ }
+
+ /* Clear interrupts. */
+ if (!charger->passive_mode) {
+ ret = tps65090_write(charger->dev->parent,
+ TPS65090_REG_INTR_STS, 0x00);
+ if (ret < 0) {
+ dev_err(charger->dev,
+ "%s(): Error in writing reg 0x%x\n",
+ __func__, TPS65090_REG_INTR_STS);
+ }
+ }
+
+ if (charger->prev_ac_online != charger->ac_online)
+ power_supply_changed(charger->ac);
+
+ return IRQ_HANDLED;
+}
+
+static struct tps65090_platform_data *
+ tps65090_parse_dt_charger_data(struct platform_device *pdev)
+{
+ struct tps65090_platform_data *pdata;
+ struct device_node *np = pdev->dev.of_node;
+ unsigned int prop;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata) {
+ dev_err(&pdev->dev, "Memory alloc for tps65090_pdata failed\n");
+ return NULL;
+ }
+
+ prop = of_property_read_bool(np, "ti,enable-low-current-chrg");
+ pdata->enable_low_current_chrg = prop;
+
+ pdata->irq_base = -1;
+
+ return pdata;
+
+}
+
+static int tps65090_charger_poll_task(void *data)
+{
+ set_freezable();
+
+ while (!kthread_should_stop()) {
+ schedule_timeout_interruptible(POLL_INTERVAL);
+ try_to_freeze();
+ tps65090_charger_isr(-1, data);
+ }
+ return 0;
+}
+
+static const struct power_supply_desc tps65090_charger_desc = {
+ .name = "tps65090-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = tps65090_ac_get_property,
+ .properties = tps65090_ac_props,
+ .num_properties = ARRAY_SIZE(tps65090_ac_props),
+};
+
+static int tps65090_charger_probe(struct platform_device *pdev)
+{
+ struct tps65090_charger *cdata;
+ struct tps65090_platform_data *pdata;
+ struct power_supply_config psy_cfg = {};
+ uint8_t status1 = 0;
+ int ret;
+ int irq;
+
+ pdata = dev_get_platdata(pdev->dev.parent);
+
+ if (IS_ENABLED(CONFIG_OF) && !pdata && pdev->dev.of_node)
+ pdata = tps65090_parse_dt_charger_data(pdev);
+
+ if (!pdata) {
+ dev_err(&pdev->dev, "%s():no platform data available\n",
+ __func__);
+ return -ENODEV;
+ }
+
+ cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL);
+ if (!cdata) {
+ dev_err(&pdev->dev, "failed to allocate memory status\n");
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, cdata);
+
+ cdata->dev = &pdev->dev;
+ cdata->pdata = pdata;
+
+ psy_cfg.supplied_to = pdata->supplied_to;
+ psy_cfg.num_supplicants = pdata->num_supplicants;
+ psy_cfg.of_node = pdev->dev.of_node;
+ psy_cfg.drv_data = cdata;
+
+ cdata->ac = power_supply_register(&pdev->dev, &tps65090_charger_desc,
+ &psy_cfg);
+ if (IS_ERR(cdata->ac)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return PTR_ERR(cdata->ac);
+ }
+
+ irq = platform_get_irq(pdev, 0);
+ if (irq < 0)
+ irq = -ENXIO;
+ cdata->irq = irq;
+
+ ret = tps65090_config_charger(cdata);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "charger config failed, err %d\n", ret);
+ goto fail_unregister_supply;
+ }
+
+ /* Check for charger presence */
+ ret = tps65090_read(cdata->dev->parent, TPS65090_REG_CG_STATUS1,
+ &status1);
+ if (ret < 0) {
+ dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__,
+ TPS65090_REG_CG_STATUS1);
+ goto fail_unregister_supply;
+ }
+
+ if (status1 != 0) {
+ ret = tps65090_enable_charging(cdata);
+ if (ret < 0) {
+ dev_err(cdata->dev, "error enabling charger\n");
+ goto fail_unregister_supply;
+ }
+ cdata->ac_online = 1;
+ power_supply_changed(cdata->ac);
+ }
+
+ if (irq != -ENXIO) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ tps65090_charger_isr, 0, "tps65090-charger", cdata);
+ if (ret) {
+ dev_err(cdata->dev,
+ "Unable to register irq %d err %d\n", irq,
+ ret);
+ goto fail_unregister_supply;
+ }
+ } else {
+ cdata->poll_task = kthread_run(tps65090_charger_poll_task,
+ cdata, "ktps65090charger");
+ cdata->passive_mode = true;
+ if (IS_ERR(cdata->poll_task)) {
+ ret = PTR_ERR(cdata->poll_task);
+ dev_err(cdata->dev,
+ "Unable to run kthread err %d\n", ret);
+ goto fail_unregister_supply;
+ }
+ }
+
+ return 0;
+
+fail_unregister_supply:
+ power_supply_unregister(cdata->ac);
+
+ return ret;
+}
+
+static int tps65090_charger_remove(struct platform_device *pdev)
+{
+ struct tps65090_charger *cdata = platform_get_drvdata(pdev);
+
+ if (cdata->irq == -ENXIO)
+ kthread_stop(cdata->poll_task);
+ power_supply_unregister(cdata->ac);
+
+ return 0;
+}
+
+static const struct of_device_id of_tps65090_charger_match[] = {
+ { .compatible = "ti,tps65090-charger", },
+ { /* end */ }
+};
+MODULE_DEVICE_TABLE(of, of_tps65090_charger_match);
+
+static struct platform_driver tps65090_charger_driver = {
+ .driver = {
+ .name = "tps65090-charger",
+ .of_match_table = of_tps65090_charger_match,
+ },
+ .probe = tps65090_charger_probe,
+ .remove = tps65090_charger_remove,
+};
+module_platform_driver(tps65090_charger_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>");
+MODULE_DESCRIPTION("tps65090 battery charger driver");
diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c
new file mode 100644
index 0000000..9fd019f
--- /dev/null
+++ b/drivers/power/supply/tps65217_charger.c
@@ -0,0 +1,295 @@
+/*
+ * Battery charger driver for TI's tps65217
+ *
+ * Copyright (c) 2015, Collabora Ltd.
+
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+
+ * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Battery charger driver for TI's tps65217
+ */
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/power_supply.h>
+
+#include <linux/mfd/core.h>
+#include <linux/mfd/tps65217.h>
+
+#define POLL_INTERVAL (HZ * 2)
+
+struct tps65217_charger {
+ struct tps65217 *tps;
+ struct device *dev;
+ struct power_supply *ac;
+
+ int ac_online;
+ int prev_ac_online;
+
+ struct task_struct *poll_task;
+
+ int irq;
+};
+
+static enum power_supply_property tps65217_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+};
+
+static int tps65217_config_charger(struct tps65217_charger *charger)
+{
+ int ret;
+
+ dev_dbg(charger->dev, "%s\n", __func__);
+
+ /*
+ * tps65217 rev. G, p. 31 (see p. 32 for NTC schematic)
+ *
+ * The device can be configured to support a 100k NTC (B = 3960) by
+ * setting the the NTC_TYPE bit in register CHGCONFIG1 to 1. However it
+ * is not recommended to do so. In sleep mode, the charger continues
+ * charging the battery, but all register values are reset to default
+ * values. Therefore, the charger would get the wrong temperature
+ * information. If 100k NTC setting is required, please contact the
+ * factory.
+ *
+ * ATTENTION, conflicting information, from p. 46
+ *
+ * NTC TYPE (for battery temperature measurement)
+ * 0 – 100k (curve 1, B = 3960)
+ * 1 – 10k (curve 2, B = 3480) (default on reset)
+ *
+ */
+ ret = tps65217_clear_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
+ TPS65217_CHGCONFIG1_NTC_TYPE,
+ TPS65217_PROTECT_NONE);
+ if (ret) {
+ dev_err(charger->dev,
+ "failed to set 100k NTC setting: %d\n", ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int tps65217_enable_charging(struct tps65217_charger *charger)
+{
+ int ret;
+
+ /* charger already enabled */
+ if (charger->ac_online)
+ return 0;
+
+ dev_dbg(charger->dev, "%s: enable charging\n", __func__);
+ ret = tps65217_set_bits(charger->tps, TPS65217_REG_CHGCONFIG1,
+ TPS65217_CHGCONFIG1_CHG_EN,
+ TPS65217_CHGCONFIG1_CHG_EN,
+ TPS65217_PROTECT_NONE);
+ if (ret) {
+ dev_err(charger->dev,
+ "%s: Error in writing CHG_EN in reg 0x%x: %d\n",
+ __func__, TPS65217_REG_CHGCONFIG1, ret);
+ return ret;
+ }
+
+ charger->ac_online = 1;
+
+ return 0;
+}
+
+static int tps65217_ac_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct tps65217_charger *charger = power_supply_get_drvdata(psy);
+
+ if (psp == POWER_SUPPLY_PROP_ONLINE) {
+ val->intval = charger->ac_online;
+ return 0;
+ }
+ return -EINVAL;
+}
+
+static irqreturn_t tps65217_charger_irq(int irq, void *dev)
+{
+ int ret, val;
+ struct tps65217_charger *charger = dev;
+
+ charger->prev_ac_online = charger->ac_online;
+
+ ret = tps65217_reg_read(charger->tps, TPS65217_REG_STATUS, &val);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
+ __func__, TPS65217_REG_STATUS);
+ return IRQ_HANDLED;
+ }
+
+ dev_dbg(charger->dev, "%s: 0x%x\n", __func__, val);
+
+ /* check for AC status bit */
+ if (val & TPS65217_STATUS_ACPWR) {
+ ret = tps65217_enable_charging(charger);
+ if (ret) {
+ dev_err(charger->dev,
+ "failed to enable charger: %d\n", ret);
+ return IRQ_HANDLED;
+ }
+ } else {
+ charger->ac_online = 0;
+ }
+
+ if (charger->prev_ac_online != charger->ac_online)
+ power_supply_changed(charger->ac);
+
+ ret = tps65217_reg_read(charger->tps, TPS65217_REG_CHGCONFIG0, &val);
+ if (ret < 0) {
+ dev_err(charger->dev, "%s: Error in reading reg 0x%x\n",
+ __func__, TPS65217_REG_CHGCONFIG0);
+ return IRQ_HANDLED;
+ }
+
+ if (val & TPS65217_CHGCONFIG0_ACTIVE)
+ dev_dbg(charger->dev, "%s: charger is charging\n", __func__);
+ else
+ dev_dbg(charger->dev,
+ "%s: charger is NOT charging\n", __func__);
+
+ return IRQ_HANDLED;
+}
+
+static int tps65217_charger_poll_task(void *data)
+{
+ set_freezable();
+
+ while (!kthread_should_stop()) {
+ schedule_timeout_interruptible(POLL_INTERVAL);
+ try_to_freeze();
+ tps65217_charger_irq(-1, data);
+ }
+ return 0;
+}
+
+static const struct power_supply_desc tps65217_charger_desc = {
+ .name = "tps65217-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .get_property = tps65217_ac_get_property,
+ .properties = tps65217_ac_props,
+ .num_properties = ARRAY_SIZE(tps65217_ac_props),
+};
+
+static int tps65217_charger_probe(struct platform_device *pdev)
+{
+ struct tps65217 *tps = dev_get_drvdata(pdev->dev.parent);
+ struct tps65217_charger *charger;
+ struct power_supply_config cfg = {};
+ int irq;
+ int ret;
+
+ dev_dbg(&pdev->dev, "%s\n", __func__);
+
+ charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL);
+ if (!charger)
+ return -ENOMEM;
+
+ platform_set_drvdata(pdev, charger);
+ charger->tps = tps;
+ charger->dev = &pdev->dev;
+
+ cfg.of_node = pdev->dev.of_node;
+ cfg.drv_data = charger;
+
+ charger->ac = devm_power_supply_register(&pdev->dev,
+ &tps65217_charger_desc,
+ &cfg);
+ if (IS_ERR(charger->ac)) {
+ dev_err(&pdev->dev, "failed: power supply register\n");
+ return PTR_ERR(charger->ac);
+ }
+
+ irq = platform_get_irq_byname(pdev, "AC");
+ if (irq < 0)
+ irq = -ENXIO;
+ charger->irq = irq;
+
+ ret = tps65217_config_charger(charger);
+ if (ret < 0) {
+ dev_err(charger->dev, "charger config failed, err %d\n", ret);
+ return ret;
+ }
+
+ if (irq != -ENXIO) {
+ ret = devm_request_threaded_irq(&pdev->dev, irq, NULL,
+ tps65217_charger_irq,
+ 0, "tps65217-charger",
+ charger);
+ if (ret) {
+ dev_err(charger->dev,
+ "Unable to register irq %d err %d\n", irq,
+ ret);
+ return ret;
+ }
+
+ /* Check current state */
+ tps65217_charger_irq(irq, charger);
+ } else {
+ charger->poll_task = kthread_run(tps65217_charger_poll_task,
+ charger, "ktps65217charger");
+ if (IS_ERR(charger->poll_task)) {
+ ret = PTR_ERR(charger->poll_task);
+ dev_err(charger->dev,
+ "Unable to run kthread err %d\n", ret);
+ return ret;
+ }
+ }
+
+ return 0;
+}
+
+static int tps65217_charger_remove(struct platform_device *pdev)
+{
+ struct tps65217_charger *charger = platform_get_drvdata(pdev);
+
+ if (charger->irq == -ENXIO)
+ kthread_stop(charger->poll_task);
+
+ return 0;
+}
+
+static const struct of_device_id tps65217_charger_match_table[] = {
+ { .compatible = "ti,tps65217-charger", },
+ { /* sentinel */ }
+};
+MODULE_DEVICE_TABLE(of, tps65217_charger_match_table);
+
+static struct platform_driver tps65217_charger_driver = {
+ .probe = tps65217_charger_probe,
+ .remove = tps65217_charger_remove,
+ .driver = {
+ .name = "tps65217-charger",
+ .of_match_table = of_match_ptr(tps65217_charger_match_table),
+ },
+
+};
+module_platform_driver(tps65217_charger_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Enric Balletbo Serra <enric.balletbo@collabora.com>");
+MODULE_DESCRIPTION("TPS65217 battery charger driver");
diff --git a/drivers/power/supply/twl4030_charger.c b/drivers/power/supply/twl4030_charger.c
new file mode 100644
index 0000000..bcd4dc3
--- /dev/null
+++ b/drivers/power/supply/twl4030_charger.c
@@ -0,0 +1,1162 @@
+/*
+ * TWL4030/TPS65950 BCI (Battery Charger Interface) driver
+ *
+ * Copyright (C) 2010 Gražvydas Ignotas <notasas@gmail.com>
+ *
+ * based on twl4030_bci_battery.c by TI
+ * Copyright (C) 2008 Texas Instruments, Inc.
+ *
+ * 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.
+ */
+
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/err.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+#include <linux/i2c/twl.h>
+#include <linux/power_supply.h>
+#include <linux/notifier.h>
+#include <linux/usb/otg.h>
+#include <linux/iio/consumer.h>
+
+#define TWL4030_BCIMDEN 0x00
+#define TWL4030_BCIMDKEY 0x01
+#define TWL4030_BCIMSTATEC 0x02
+#define TWL4030_BCIICHG 0x08
+#define TWL4030_BCIVAC 0x0a
+#define TWL4030_BCIVBUS 0x0c
+#define TWL4030_BCIMFSTS3 0x0F
+#define TWL4030_BCIMFSTS4 0x10
+#define TWL4030_BCICTL1 0x23
+#define TWL4030_BB_CFG 0x12
+#define TWL4030_BCIIREF1 0x27
+#define TWL4030_BCIIREF2 0x28
+#define TWL4030_BCIMFKEY 0x11
+#define TWL4030_BCIMFEN3 0x14
+#define TWL4030_BCIMFTH8 0x1d
+#define TWL4030_BCIMFTH9 0x1e
+#define TWL4030_BCIWDKEY 0x21
+
+#define TWL4030_BCIMFSTS1 0x01
+
+#define TWL4030_BCIAUTOWEN BIT(5)
+#define TWL4030_CONFIG_DONE BIT(4)
+#define TWL4030_CVENAC BIT(2)
+#define TWL4030_BCIAUTOUSB BIT(1)
+#define TWL4030_BCIAUTOAC BIT(0)
+#define TWL4030_CGAIN BIT(5)
+#define TWL4030_USBFASTMCHG BIT(2)
+#define TWL4030_STS_VBUS BIT(7)
+#define TWL4030_STS_USB_ID BIT(2)
+#define TWL4030_BBCHEN BIT(4)
+#define TWL4030_BBSEL_MASK 0x0c
+#define TWL4030_BBSEL_2V5 0x00
+#define TWL4030_BBSEL_3V0 0x04
+#define TWL4030_BBSEL_3V1 0x08
+#define TWL4030_BBSEL_3V2 0x0c
+#define TWL4030_BBISEL_MASK 0x03
+#define TWL4030_BBISEL_25uA 0x00
+#define TWL4030_BBISEL_150uA 0x01
+#define TWL4030_BBISEL_500uA 0x02
+#define TWL4030_BBISEL_1000uA 0x03
+
+#define TWL4030_BATSTSPCHG BIT(2)
+#define TWL4030_BATSTSMCHG BIT(6)
+
+/* BCI interrupts */
+#define TWL4030_WOVF BIT(0) /* Watchdog overflow */
+#define TWL4030_TMOVF BIT(1) /* Timer overflow */
+#define TWL4030_ICHGHIGH BIT(2) /* Battery charge current high */
+#define TWL4030_ICHGLOW BIT(3) /* Battery cc. low / FSM state change */
+#define TWL4030_ICHGEOC BIT(4) /* Battery current end-of-charge */
+#define TWL4030_TBATOR2 BIT(5) /* Battery temperature out of range 2 */
+#define TWL4030_TBATOR1 BIT(6) /* Battery temperature out of range 1 */
+#define TWL4030_BATSTS BIT(7) /* Battery status */
+
+#define TWL4030_VBATLVL BIT(0) /* VBAT level */
+#define TWL4030_VBATOV BIT(1) /* VBAT overvoltage */
+#define TWL4030_VBUSOV BIT(2) /* VBUS overvoltage */
+#define TWL4030_ACCHGOV BIT(3) /* Ac charger overvoltage */
+
+#define TWL4030_MSTATEC_USB BIT(4)
+#define TWL4030_MSTATEC_AC BIT(5)
+#define TWL4030_MSTATEC_MASK 0x0f
+#define TWL4030_MSTATEC_QUICK1 0x02
+#define TWL4030_MSTATEC_QUICK7 0x07
+#define TWL4030_MSTATEC_COMPLETE1 0x0b
+#define TWL4030_MSTATEC_COMPLETE4 0x0e
+
+/*
+ * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
+ * then AC is available.
+ */
+static inline int ac_available(struct iio_channel *channel_vac)
+{
+ int val, err;
+
+ if (!channel_vac)
+ return 0;
+
+ err = iio_read_channel_processed(channel_vac, &val);
+ if (err < 0)
+ return 0;
+ return val > 4500;
+}
+
+static bool allow_usb;
+module_param(allow_usb, bool, 0644);
+MODULE_PARM_DESC(allow_usb, "Allow USB charge drawing default current");
+
+struct twl4030_bci {
+ struct device *dev;
+ struct power_supply *ac;
+ struct power_supply *usb;
+ struct usb_phy *transceiver;
+ struct notifier_block usb_nb;
+ struct work_struct work;
+ int irq_chg;
+ int irq_bci;
+ int usb_enabled;
+
+ /*
+ * ichg_* and *_cur values in uA. If any are 'large', we set
+ * CGAIN to '1' which doubles the range for half the
+ * precision.
+ */
+ unsigned int ichg_eoc, ichg_lo, ichg_hi;
+ unsigned int usb_cur, ac_cur;
+ struct iio_channel *channel_vac;
+ bool ac_is_active;
+ int usb_mode, ac_mode; /* charging mode requested */
+#define CHARGE_OFF 0
+#define CHARGE_AUTO 1
+#define CHARGE_LINEAR 2
+
+ /* When setting the USB current we slowly increase the
+ * requested current until target is reached or the voltage
+ * drops below 4.75V. In the latter case we step back one
+ * step.
+ */
+ unsigned int usb_cur_target;
+ struct delayed_work current_worker;
+#define USB_CUR_STEP 20000 /* 20mA at a time */
+#define USB_MIN_VOLT 4750000 /* 4.75V */
+#define USB_CUR_DELAY msecs_to_jiffies(100)
+#define USB_MAX_CURRENT 1700000 /* TWL4030 caps at 1.7A */
+
+ unsigned long event;
+};
+
+/* strings for 'usb_mode' values */
+static char *modes[] = { "off", "auto", "continuous" };
+
+/*
+ * clear and set bits on an given register on a given module
+ */
+static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg)
+{
+ u8 val = 0;
+ int ret;
+
+ ret = twl_i2c_read_u8(mod_no, &val, reg);
+ if (ret)
+ return ret;
+
+ val &= ~clear;
+ val |= set;
+
+ return twl_i2c_write_u8(mod_no, val, reg);
+}
+
+static int twl4030_bci_read(u8 reg, u8 *val)
+{
+ return twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, val, reg);
+}
+
+static int twl4030_clear_set_boot_bci(u8 clear, u8 set)
+{
+ return twl4030_clear_set(TWL_MODULE_PM_MASTER, clear,
+ TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set,
+ TWL4030_PM_MASTER_BOOT_BCI);
+}
+
+static int twl4030bci_read_adc_val(u8 reg)
+{
+ int ret, temp;
+ u8 val;
+
+ /* read MSB */
+ ret = twl4030_bci_read(reg + 1, &val);
+ if (ret)
+ return ret;
+
+ temp = (int)(val & 0x03) << 8;
+
+ /* read LSB */
+ ret = twl4030_bci_read(reg, &val);
+ if (ret)
+ return ret;
+
+ return temp | val;
+}
+
+/*
+ * Check if Battery Pack was present
+ */
+static int twl4030_is_battery_present(struct twl4030_bci *bci)
+{
+ int ret;
+ u8 val = 0;
+
+ /* Battery presence in Main charge? */
+ ret = twl_i2c_read_u8(TWL_MODULE_MAIN_CHARGE, &val, TWL4030_BCIMFSTS3);
+ if (ret)
+ return ret;
+ if (val & TWL4030_BATSTSMCHG)
+ return 0;
+
+ /*
+ * OK, It could be that bootloader did not enable main charger,
+ * pre-charge is h/w auto. So, Battery presence in Pre-charge?
+ */
+ ret = twl_i2c_read_u8(TWL4030_MODULE_PRECHARGE, &val,
+ TWL4030_BCIMFSTS1);
+ if (ret)
+ return ret;
+ if (val & TWL4030_BATSTSPCHG)
+ return 0;
+
+ return -ENODEV;
+}
+
+/*
+ * TI provided formulas:
+ * CGAIN == 0: ICHG = (BCIICHG * 1.7) / (2^10 - 1) - 0.85
+ * CGAIN == 1: ICHG = (BCIICHG * 3.4) / (2^10 - 1) - 1.7
+ * Here we use integer approximation of:
+ * CGAIN == 0: val * 1.6618 - 0.85 * 1000
+ * CGAIN == 1: (val * 1.6618 - 0.85 * 1000) * 2
+ */
+/*
+ * convert twl register value for currents into uA
+ */
+static int regval2ua(int regval, bool cgain)
+{
+ if (cgain)
+ return (regval * 16618 - 8500 * 1000) / 5;
+ else
+ return (regval * 16618 - 8500 * 1000) / 10;
+}
+
+/*
+ * convert uA currents into twl register value
+ */
+static int ua2regval(int ua, bool cgain)
+{
+ int ret;
+ if (cgain)
+ ua /= 2;
+ ret = (ua * 10 + 8500 * 1000) / 16618;
+ /* rounding problems */
+ if (ret < 512)
+ ret = 512;
+ return ret;
+}
+
+static int twl4030_charger_update_current(struct twl4030_bci *bci)
+{
+ int status;
+ int cur;
+ unsigned reg, cur_reg;
+ u8 bcictl1, oldreg, fullreg;
+ bool cgain = false;
+ u8 boot_bci;
+
+ /*
+ * If AC (Accessory Charger) voltage exceeds 4.5V (MADC 11)
+ * and AC is enabled, set current for 'ac'
+ */
+ if (ac_available(bci->channel_vac)) {
+ cur = bci->ac_cur;
+ bci->ac_is_active = true;
+ } else {
+ cur = bci->usb_cur;
+ bci->ac_is_active = false;
+ if (cur > bci->usb_cur_target) {
+ cur = bci->usb_cur_target;
+ bci->usb_cur = cur;
+ }
+ if (cur < bci->usb_cur_target)
+ schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
+ }
+
+ /* First, check thresholds and see if cgain is needed */
+ if (bci->ichg_eoc >= 200000)
+ cgain = true;
+ if (bci->ichg_lo >= 400000)
+ cgain = true;
+ if (bci->ichg_hi >= 820000)
+ cgain = true;
+ if (cur > 852000)
+ cgain = true;
+
+ status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+ if (status < 0)
+ return status;
+ if (twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &boot_bci,
+ TWL4030_PM_MASTER_BOOT_BCI) < 0)
+ boot_bci = 0;
+ boot_bci &= 7;
+
+ if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN))
+ /* Need to turn for charging while we change the
+ * CGAIN bit. Leave it off while everything is
+ * updated.
+ */
+ twl4030_clear_set_boot_bci(boot_bci, 0);
+
+ /*
+ * For ichg_eoc, the hardware only supports reg values matching
+ * 100XXXX000, and requires the XXXX be stored in the high nibble
+ * of TWL4030_BCIMFTH8.
+ */
+ reg = ua2regval(bci->ichg_eoc, cgain);
+ if (reg > 0x278)
+ reg = 0x278;
+ if (reg < 0x200)
+ reg = 0x200;
+ reg = (reg >> 3) & 0xf;
+ fullreg = reg << 4;
+
+ /*
+ * For ichg_lo, reg value must match 10XXXX0000.
+ * XXXX is stored in low nibble of TWL4030_BCIMFTH8.
+ */
+ reg = ua2regval(bci->ichg_lo, cgain);
+ if (reg > 0x2F0)
+ reg = 0x2F0;
+ if (reg < 0x200)
+ reg = 0x200;
+ reg = (reg >> 4) & 0xf;
+ fullreg |= reg;
+
+ /* ichg_eoc and ichg_lo live in same register */
+ status = twl4030_bci_read(TWL4030_BCIMFTH8, &oldreg);
+ if (status < 0)
+ return status;
+ if (oldreg != fullreg) {
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xF4,
+ TWL4030_BCIMFKEY);
+ if (status < 0)
+ return status;
+ twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+ fullreg, TWL4030_BCIMFTH8);
+ }
+
+ /* ichg_hi threshold must be 1XXXX01100 (I think) */
+ reg = ua2regval(bci->ichg_hi, cgain);
+ if (reg > 0x3E0)
+ reg = 0x3E0;
+ if (reg < 0x200)
+ reg = 0x200;
+ fullreg = (reg >> 5) & 0xF;
+ fullreg <<= 4;
+ status = twl4030_bci_read(TWL4030_BCIMFTH9, &oldreg);
+ if (status < 0)
+ return status;
+ if ((oldreg & 0xF0) != fullreg) {
+ fullreg |= (oldreg & 0x0F);
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+ TWL4030_BCIMFKEY);
+ if (status < 0)
+ return status;
+ twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+ fullreg, TWL4030_BCIMFTH9);
+ }
+
+ /*
+ * And finally, set the current. This is stored in
+ * two registers.
+ */
+ reg = ua2regval(cur, cgain);
+ /* we have only 10 bits */
+ if (reg > 0x3ff)
+ reg = 0x3ff;
+ status = twl4030_bci_read(TWL4030_BCIIREF1, &oldreg);
+ if (status < 0)
+ return status;
+ cur_reg = oldreg;
+ status = twl4030_bci_read(TWL4030_BCIIREF2, &oldreg);
+ if (status < 0)
+ return status;
+ cur_reg |= oldreg << 8;
+ if (reg != oldreg) {
+ /* disable write protection for one write access for
+ * BCIIREF */
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+ TWL4030_BCIMFKEY);
+ if (status < 0)
+ return status;
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+ (reg & 0x100) ? 3 : 2,
+ TWL4030_BCIIREF2);
+ if (status < 0)
+ return status;
+ /* disable write protection for one write access for
+ * BCIIREF */
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xE7,
+ TWL4030_BCIMFKEY);
+ if (status < 0)
+ return status;
+ status = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+ reg & 0xff,
+ TWL4030_BCIIREF1);
+ }
+ if ((!!cgain) != !!(bcictl1 & TWL4030_CGAIN)) {
+ /* Flip CGAIN and re-enable charging */
+ bcictl1 ^= TWL4030_CGAIN;
+ twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE,
+ bcictl1, TWL4030_BCICTL1);
+ twl4030_clear_set_boot_bci(0, boot_bci);
+ }
+ return 0;
+}
+
+static int twl4030_charger_get_current(void);
+
+static void twl4030_current_worker(struct work_struct *data)
+{
+ int v, curr;
+ int res;
+ struct twl4030_bci *bci = container_of(data, struct twl4030_bci,
+ current_worker.work);
+
+ res = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
+ if (res < 0)
+ v = 0;
+ else
+ /* BCIVBUS uses ADCIN8, 7/1023 V/step */
+ v = res * 6843;
+ curr = twl4030_charger_get_current();
+
+ dev_dbg(bci->dev, "v=%d cur=%d limit=%d target=%d\n", v, curr,
+ bci->usb_cur, bci->usb_cur_target);
+
+ if (v < USB_MIN_VOLT) {
+ /* Back up and stop adjusting. */
+ bci->usb_cur -= USB_CUR_STEP;
+ bci->usb_cur_target = bci->usb_cur;
+ } else if (bci->usb_cur >= bci->usb_cur_target ||
+ bci->usb_cur + USB_CUR_STEP > USB_MAX_CURRENT) {
+ /* Reached target and voltage is OK - stop */
+ return;
+ } else {
+ bci->usb_cur += USB_CUR_STEP;
+ schedule_delayed_work(&bci->current_worker, USB_CUR_DELAY);
+ }
+ twl4030_charger_update_current(bci);
+}
+
+/*
+ * Enable/Disable USB Charge functionality.
+ */
+static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable)
+{
+ int ret;
+
+ if (bci->usb_mode == CHARGE_OFF)
+ enable = false;
+ if (enable && !IS_ERR_OR_NULL(bci->transceiver)) {
+
+ twl4030_charger_update_current(bci);
+
+ /* Need to keep phy powered */
+ if (!bci->usb_enabled) {
+ pm_runtime_get_sync(bci->transceiver->dev);
+ bci->usb_enabled = 1;
+ }
+
+ if (bci->usb_mode == CHARGE_AUTO)
+ /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */
+ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB);
+
+ /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */
+ ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0,
+ TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4);
+ if (bci->usb_mode == CHARGE_LINEAR) {
+ twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC|TWL4030_CVENAC, 0);
+ /* Watch dog key: WOVF acknowledge */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x33,
+ TWL4030_BCIWDKEY);
+ /* 0x24 + EKEY6: off mode */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
+ TWL4030_BCIMDKEY);
+ /* EKEY2: Linear charge: USB path */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x26,
+ TWL4030_BCIMDKEY);
+ /* WDKEY5: stop watchdog count */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf3,
+ TWL4030_BCIWDKEY);
+ /* enable MFEN3 access */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x9c,
+ TWL4030_BCIMFKEY);
+ /* ICHGEOCEN - end-of-charge monitor (current < 80mA)
+ * (charging continues)
+ * ICHGLOWEN - current level monitor (charge continues)
+ * don't monitor over-current or heat save
+ */
+ ret = twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0xf0,
+ TWL4030_BCIMFEN3);
+ }
+ } else {
+ ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0);
+ ret |= twl_i2c_write_u8(TWL_MODULE_MAIN_CHARGE, 0x2a,
+ TWL4030_BCIMDKEY);
+ if (bci->usb_enabled) {
+ pm_runtime_mark_last_busy(bci->transceiver->dev);
+ pm_runtime_put_autosuspend(bci->transceiver->dev);
+ bci->usb_enabled = 0;
+ }
+ bci->usb_cur = 0;
+ }
+
+ return ret;
+}
+
+/*
+ * Enable/Disable AC Charge funtionality.
+ */
+static int twl4030_charger_enable_ac(struct twl4030_bci *bci, bool enable)
+{
+ int ret;
+
+ if (bci->ac_mode == CHARGE_OFF)
+ enable = false;
+
+ if (enable)
+ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOAC);
+ else
+ ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOAC, 0);
+
+ return ret;
+}
+
+/*
+ * Enable/Disable charging of Backup Battery.
+ */
+static int twl4030_charger_enable_backup(int uvolt, int uamp)
+{
+ int ret;
+ u8 flags;
+
+ if (uvolt < 2500000 ||
+ uamp < 25) {
+ /* disable charging of backup battery */
+ ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER,
+ TWL4030_BBCHEN, 0, TWL4030_BB_CFG);
+ return ret;
+ }
+
+ flags = TWL4030_BBCHEN;
+ if (uvolt >= 3200000)
+ flags |= TWL4030_BBSEL_3V2;
+ else if (uvolt >= 3100000)
+ flags |= TWL4030_BBSEL_3V1;
+ else if (uvolt >= 3000000)
+ flags |= TWL4030_BBSEL_3V0;
+ else
+ flags |= TWL4030_BBSEL_2V5;
+
+ if (uamp >= 1000)
+ flags |= TWL4030_BBISEL_1000uA;
+ else if (uamp >= 500)
+ flags |= TWL4030_BBISEL_500uA;
+ else if (uamp >= 150)
+ flags |= TWL4030_BBISEL_150uA;
+ else
+ flags |= TWL4030_BBISEL_25uA;
+
+ ret = twl4030_clear_set(TWL_MODULE_PM_RECEIVER,
+ TWL4030_BBSEL_MASK | TWL4030_BBISEL_MASK,
+ flags,
+ TWL4030_BB_CFG);
+
+ return ret;
+}
+
+/*
+ * TWL4030 CHG_PRES (AC charger presence) events
+ */
+static irqreturn_t twl4030_charger_interrupt(int irq, void *arg)
+{
+ struct twl4030_bci *bci = arg;
+
+ dev_dbg(bci->dev, "CHG_PRES irq\n");
+ /* reset current on each 'plug' event */
+ bci->ac_cur = 500000;
+ twl4030_charger_update_current(bci);
+ power_supply_changed(bci->ac);
+ power_supply_changed(bci->usb);
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * TWL4030 BCI monitoring events
+ */
+static irqreturn_t twl4030_bci_interrupt(int irq, void *arg)
+{
+ struct twl4030_bci *bci = arg;
+ u8 irqs1, irqs2;
+ int ret;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs1,
+ TWL4030_INTERRUPTS_BCIISR1A);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ ret = twl_i2c_read_u8(TWL4030_MODULE_INTERRUPTS, &irqs2,
+ TWL4030_INTERRUPTS_BCIISR2A);
+ if (ret < 0)
+ return IRQ_HANDLED;
+
+ dev_dbg(bci->dev, "BCI irq %02x %02x\n", irqs2, irqs1);
+
+ if (irqs1 & (TWL4030_ICHGLOW | TWL4030_ICHGEOC)) {
+ /* charger state change, inform the core */
+ power_supply_changed(bci->ac);
+ power_supply_changed(bci->usb);
+ }
+ twl4030_charger_update_current(bci);
+
+ /* various monitoring events, for now we just log them here */
+ if (irqs1 & (TWL4030_TBATOR2 | TWL4030_TBATOR1))
+ dev_warn(bci->dev, "battery temperature out of range\n");
+
+ if (irqs1 & TWL4030_BATSTS)
+ dev_crit(bci->dev, "battery disconnected\n");
+
+ if (irqs2 & TWL4030_VBATOV)
+ dev_crit(bci->dev, "VBAT overvoltage\n");
+
+ if (irqs2 & TWL4030_VBUSOV)
+ dev_crit(bci->dev, "VBUS overvoltage\n");
+
+ if (irqs2 & TWL4030_ACCHGOV)
+ dev_crit(bci->dev, "Ac charger overvoltage\n");
+
+ return IRQ_HANDLED;
+}
+
+/*
+ * Provide "max_current" attribute in sysfs.
+ */
+static ssize_t
+twl4030_bci_max_current_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+ int cur = 0;
+ int status = 0;
+ status = kstrtoint(buf, 10, &cur);
+ if (status)
+ return status;
+ if (cur < 0)
+ return -EINVAL;
+ if (dev == &bci->ac->dev)
+ bci->ac_cur = cur;
+ else
+ bci->usb_cur_target = cur;
+
+ twl4030_charger_update_current(bci);
+ return n;
+}
+
+/*
+ * sysfs max_current show
+ */
+static ssize_t twl4030_bci_max_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int status = 0;
+ int cur = -1;
+ u8 bcictl1;
+ struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+
+ if (dev == &bci->ac->dev) {
+ if (!bci->ac_is_active)
+ cur = bci->ac_cur;
+ } else {
+ if (bci->ac_is_active)
+ cur = bci->usb_cur_target;
+ }
+ if (cur < 0) {
+ cur = twl4030bci_read_adc_val(TWL4030_BCIIREF1);
+ if (cur < 0)
+ return cur;
+ status = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+ if (status < 0)
+ return status;
+ cur = regval2ua(cur, bcictl1 & TWL4030_CGAIN);
+ }
+ return scnprintf(buf, PAGE_SIZE, "%u\n", cur);
+}
+
+static DEVICE_ATTR(max_current, 0644, twl4030_bci_max_current_show,
+ twl4030_bci_max_current_store);
+
+static void twl4030_bci_usb_work(struct work_struct *data)
+{
+ struct twl4030_bci *bci = container_of(data, struct twl4030_bci, work);
+
+ switch (bci->event) {
+ case USB_EVENT_VBUS:
+ case USB_EVENT_CHARGER:
+ twl4030_charger_enable_usb(bci, true);
+ break;
+ case USB_EVENT_NONE:
+ twl4030_charger_enable_usb(bci, false);
+ break;
+ }
+}
+
+static int twl4030_bci_usb_ncb(struct notifier_block *nb, unsigned long val,
+ void *priv)
+{
+ struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb);
+
+ dev_dbg(bci->dev, "OTG notify %lu\n", val);
+
+ /* reset current on each 'plug' event */
+ if (allow_usb)
+ bci->usb_cur_target = 500000;
+ else
+ bci->usb_cur_target = 100000;
+
+ bci->event = val;
+ schedule_work(&bci->work);
+
+ return NOTIFY_OK;
+}
+
+/*
+ * sysfs charger enabled store
+ */
+static ssize_t
+twl4030_bci_mode_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t n)
+{
+ struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+ int mode;
+ int status;
+
+ if (sysfs_streq(buf, modes[0]))
+ mode = 0;
+ else if (sysfs_streq(buf, modes[1]))
+ mode = 1;
+ else if (sysfs_streq(buf, modes[2]))
+ mode = 2;
+ else
+ return -EINVAL;
+ if (dev == &bci->ac->dev) {
+ if (mode == 2)
+ return -EINVAL;
+ twl4030_charger_enable_ac(bci, false);
+ bci->ac_mode = mode;
+ status = twl4030_charger_enable_ac(bci, true);
+ } else {
+ twl4030_charger_enable_usb(bci, false);
+ bci->usb_mode = mode;
+ status = twl4030_charger_enable_usb(bci, true);
+ }
+ return (status == 0) ? n : status;
+}
+
+/*
+ * sysfs charger enabled show
+ */
+static ssize_t
+twl4030_bci_mode_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct twl4030_bci *bci = dev_get_drvdata(dev->parent);
+ int len = 0;
+ int i;
+ int mode = bci->usb_mode;
+
+ if (dev == &bci->ac->dev)
+ mode = bci->ac_mode;
+
+ for (i = 0; i < ARRAY_SIZE(modes); i++)
+ if (mode == i)
+ len += snprintf(buf+len, PAGE_SIZE-len,
+ "[%s] ", modes[i]);
+ else
+ len += snprintf(buf+len, PAGE_SIZE-len,
+ "%s ", modes[i]);
+ buf[len-1] = '\n';
+ return len;
+}
+static DEVICE_ATTR(mode, 0644, twl4030_bci_mode_show,
+ twl4030_bci_mode_store);
+
+static int twl4030_charger_get_current(void)
+{
+ int curr;
+ int ret;
+ u8 bcictl1;
+
+ curr = twl4030bci_read_adc_val(TWL4030_BCIICHG);
+ if (curr < 0)
+ return curr;
+
+ ret = twl4030_bci_read(TWL4030_BCICTL1, &bcictl1);
+ if (ret)
+ return ret;
+
+ return regval2ua(curr, bcictl1 & TWL4030_CGAIN);
+}
+
+/*
+ * Returns the main charge FSM state
+ * Or < 0 on failure.
+ */
+static int twl4030bci_state(struct twl4030_bci *bci)
+{
+ int ret;
+ u8 state;
+
+ ret = twl4030_bci_read(TWL4030_BCIMSTATEC, &state);
+ if (ret) {
+ pr_err("twl4030_bci: error reading BCIMSTATEC\n");
+ return ret;
+ }
+
+ dev_dbg(bci->dev, "state: %02x\n", state);
+
+ return state;
+}
+
+static int twl4030_bci_state_to_status(int state)
+{
+ state &= TWL4030_MSTATEC_MASK;
+ if (TWL4030_MSTATEC_QUICK1 <= state && state <= TWL4030_MSTATEC_QUICK7)
+ return POWER_SUPPLY_STATUS_CHARGING;
+ else if (TWL4030_MSTATEC_COMPLETE1 <= state &&
+ state <= TWL4030_MSTATEC_COMPLETE4)
+ return POWER_SUPPLY_STATUS_FULL;
+ else
+ return POWER_SUPPLY_STATUS_NOT_CHARGING;
+}
+
+static int twl4030_bci_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct twl4030_bci *bci = dev_get_drvdata(psy->dev.parent);
+ int is_charging;
+ int state;
+ int ret;
+
+ state = twl4030bci_state(bci);
+ if (state < 0)
+ return state;
+
+ if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ is_charging = state & TWL4030_MSTATEC_USB;
+ else
+ is_charging = state & TWL4030_MSTATEC_AC;
+ if (!is_charging) {
+ u8 s;
+ twl4030_bci_read(TWL4030_BCIMDEN, &s);
+ if (psy->desc->type == POWER_SUPPLY_TYPE_USB)
+ is_charging = s & 1;
+ else
+ is_charging = s & 2;
+ if (is_charging)
+ /* A little white lie */
+ state = TWL4030_MSTATEC_QUICK1;
+ }
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (is_charging)
+ val->intval = twl4030_bci_state_to_status(state);
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ /* charging must be active for meaningful result */
+ if (!is_charging)
+ return -ENODATA;
+ if (psy->desc->type == POWER_SUPPLY_TYPE_USB) {
+ ret = twl4030bci_read_adc_val(TWL4030_BCIVBUS);
+ if (ret < 0)
+ return ret;
+ /* BCIVBUS uses ADCIN8, 7/1023 V/step */
+ val->intval = ret * 6843;
+ } else {
+ ret = twl4030bci_read_adc_val(TWL4030_BCIVAC);
+ if (ret < 0)
+ return ret;
+ /* BCIVAC uses ADCIN11, 10/1023 V/step */
+ val->intval = ret * 9775;
+ }
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ if (!is_charging)
+ return -ENODATA;
+ /* current measurement is shared between AC and USB */
+ ret = twl4030_charger_get_current();
+ if (ret < 0)
+ return ret;
+ val->intval = ret;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = is_charging &&
+ twl4030_bci_state_to_status(state) !=
+ POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static enum power_supply_property twl4030_charger_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+};
+
+#ifdef CONFIG_OF
+static const struct twl4030_bci_platform_data *
+twl4030_bci_parse_dt(struct device *dev)
+{
+ struct device_node *np = dev->of_node;
+ struct twl4030_bci_platform_data *pdata;
+ u32 num;
+
+ if (!np)
+ return NULL;
+ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return pdata;
+
+ if (of_property_read_u32(np, "ti,bb-uvolt", &num) == 0)
+ pdata->bb_uvolt = num;
+ if (of_property_read_u32(np, "ti,bb-uamp", &num) == 0)
+ pdata->bb_uamp = num;
+ return pdata;
+}
+#else
+static inline const struct twl4030_bci_platform_data *
+twl4030_bci_parse_dt(struct device *dev)
+{
+ return NULL;
+}
+#endif
+
+static const struct power_supply_desc twl4030_bci_ac_desc = {
+ .name = "twl4030_ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = twl4030_charger_props,
+ .num_properties = ARRAY_SIZE(twl4030_charger_props),
+ .get_property = twl4030_bci_get_property,
+};
+
+static const struct power_supply_desc twl4030_bci_usb_desc = {
+ .name = "twl4030_usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = twl4030_charger_props,
+ .num_properties = ARRAY_SIZE(twl4030_charger_props),
+ .get_property = twl4030_bci_get_property,
+};
+
+static int twl4030_bci_probe(struct platform_device *pdev)
+{
+ struct twl4030_bci *bci;
+ const struct twl4030_bci_platform_data *pdata = pdev->dev.platform_data;
+ int ret;
+ u32 reg;
+
+ bci = devm_kzalloc(&pdev->dev, sizeof(*bci), GFP_KERNEL);
+ if (bci == NULL)
+ return -ENOMEM;
+
+ if (!pdata)
+ pdata = twl4030_bci_parse_dt(&pdev->dev);
+
+ bci->ichg_eoc = 80100; /* Stop charging when current drops to here */
+ bci->ichg_lo = 241000; /* Low threshold */
+ bci->ichg_hi = 500000; /* High threshold */
+ bci->ac_cur = 500000; /* 500mA */
+ if (allow_usb)
+ bci->usb_cur_target = 500000; /* 500mA */
+ else
+ bci->usb_cur_target = 100000; /* 100mA */
+ bci->usb_mode = CHARGE_AUTO;
+ bci->ac_mode = CHARGE_AUTO;
+
+ bci->dev = &pdev->dev;
+ bci->irq_chg = platform_get_irq(pdev, 0);
+ bci->irq_bci = platform_get_irq(pdev, 1);
+
+ /* Only proceed further *IF* battery is physically present */
+ ret = twl4030_is_battery_present(bci);
+ if (ret) {
+ dev_crit(&pdev->dev, "Battery was not detected:%d\n", ret);
+ return ret;
+ }
+
+ platform_set_drvdata(pdev, bci);
+
+ bci->ac = devm_power_supply_register(&pdev->dev, &twl4030_bci_ac_desc,
+ NULL);
+ if (IS_ERR(bci->ac)) {
+ ret = PTR_ERR(bci->ac);
+ dev_err(&pdev->dev, "failed to register ac: %d\n", ret);
+ return ret;
+ }
+
+ bci->usb = devm_power_supply_register(&pdev->dev, &twl4030_bci_usb_desc,
+ NULL);
+ if (IS_ERR(bci->usb)) {
+ ret = PTR_ERR(bci->usb);
+ dev_err(&pdev->dev, "failed to register usb: %d\n", ret);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, bci->irq_chg, NULL,
+ twl4030_charger_interrupt, IRQF_ONESHOT, pdev->name,
+ bci);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq %d, status %d\n",
+ bci->irq_chg, ret);
+ return ret;
+ }
+
+ ret = devm_request_threaded_irq(&pdev->dev, bci->irq_bci, NULL,
+ twl4030_bci_interrupt, IRQF_ONESHOT, pdev->name, bci);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "could not request irq %d, status %d\n",
+ bci->irq_bci, ret);
+ return ret;
+ }
+
+ bci->channel_vac = iio_channel_get(&pdev->dev, "vac");
+ if (IS_ERR(bci->channel_vac)) {
+ bci->channel_vac = NULL;
+ dev_warn(&pdev->dev, "could not request vac iio channel");
+ }
+
+ INIT_WORK(&bci->work, twl4030_bci_usb_work);
+ INIT_DELAYED_WORK(&bci->current_worker, twl4030_current_worker);
+
+ bci->usb_nb.notifier_call = twl4030_bci_usb_ncb;
+ if (bci->dev->of_node) {
+ struct device_node *phynode;
+
+ phynode = of_find_compatible_node(bci->dev->of_node->parent,
+ NULL, "ti,twl4030-usb");
+ if (phynode)
+ bci->transceiver = devm_usb_get_phy_by_node(
+ bci->dev, phynode, &bci->usb_nb);
+ }
+
+ /* Enable interrupts now. */
+ reg = ~(u32)(TWL4030_ICHGLOW | TWL4030_ICHGEOC | TWL4030_TBATOR2 |
+ TWL4030_TBATOR1 | TWL4030_BATSTS);
+ ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
+ TWL4030_INTERRUPTS_BCIIMR1A);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
+ goto fail;
+ }
+
+ reg = ~(u32)(TWL4030_VBATOV | TWL4030_VBUSOV | TWL4030_ACCHGOV);
+ ret = twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, reg,
+ TWL4030_INTERRUPTS_BCIIMR2A);
+ if (ret < 0)
+ dev_warn(&pdev->dev, "failed to unmask interrupts: %d\n", ret);
+
+ twl4030_charger_update_current(bci);
+ if (device_create_file(&bci->usb->dev, &dev_attr_max_current))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+ if (device_create_file(&bci->usb->dev, &dev_attr_mode))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+ if (device_create_file(&bci->ac->dev, &dev_attr_mode))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+ if (device_create_file(&bci->ac->dev, &dev_attr_max_current))
+ dev_warn(&pdev->dev, "could not create sysfs file\n");
+
+ twl4030_charger_enable_ac(bci, true);
+ if (!IS_ERR_OR_NULL(bci->transceiver))
+ twl4030_bci_usb_ncb(&bci->usb_nb,
+ bci->transceiver->last_event,
+ NULL);
+ else
+ twl4030_charger_enable_usb(bci, false);
+ if (pdata)
+ twl4030_charger_enable_backup(pdata->bb_uvolt,
+ pdata->bb_uamp);
+ else
+ twl4030_charger_enable_backup(0, 0);
+
+ return 0;
+fail:
+ iio_channel_release(bci->channel_vac);
+
+ return ret;
+}
+
+static int __exit twl4030_bci_remove(struct platform_device *pdev)
+{
+ struct twl4030_bci *bci = platform_get_drvdata(pdev);
+
+ twl4030_charger_enable_ac(bci, false);
+ twl4030_charger_enable_usb(bci, false);
+ twl4030_charger_enable_backup(0, 0);
+
+ iio_channel_release(bci->channel_vac);
+
+ device_remove_file(&bci->usb->dev, &dev_attr_max_current);
+ device_remove_file(&bci->usb->dev, &dev_attr_mode);
+ device_remove_file(&bci->ac->dev, &dev_attr_max_current);
+ device_remove_file(&bci->ac->dev, &dev_attr_mode);
+ /* mask interrupts */
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
+ TWL4030_INTERRUPTS_BCIIMR1A);
+ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff,
+ TWL4030_INTERRUPTS_BCIIMR2A);
+
+ return 0;
+}
+
+static const struct of_device_id twl_bci_of_match[] = {
+ {.compatible = "ti,twl4030-bci", },
+ { }
+};
+MODULE_DEVICE_TABLE(of, twl_bci_of_match);
+
+static struct platform_driver twl4030_bci_driver = {
+ .probe = twl4030_bci_probe,
+ .driver = {
+ .name = "twl4030_bci",
+ .of_match_table = of_match_ptr(twl_bci_of_match),
+ },
+ .remove = __exit_p(twl4030_bci_remove),
+};
+module_platform_driver(twl4030_bci_driver);
+
+MODULE_AUTHOR("Gražvydas Ignotas");
+MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:twl4030_bci");
diff --git a/drivers/power/supply/twl4030_madc_battery.c b/drivers/power/supply/twl4030_madc_battery.c
new file mode 100644
index 0000000..f5817e4
--- /dev/null
+++ b/drivers/power/supply/twl4030_madc_battery.c
@@ -0,0 +1,278 @@
+/*
+ * 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>
+#include <linux/iio/consumer.h>
+
+struct twl4030_madc_battery {
+ struct power_supply *psy;
+ struct twl4030_madc_bat_platform_data *pdata;
+ struct iio_channel *channel_temp;
+ struct iio_channel *channel_ichg;
+ struct iio_channel *channel_vbat;
+};
+
+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(struct iio_channel *channel)
+{
+ int val, err;
+ err = iio_read_channel_processed(channel, &val);
+ if (err < 0)
+ return err;
+
+ return val;
+}
+
+static int twl4030_madc_bat_get_charging_status(struct twl4030_madc_battery *bt)
+{
+ return (madc_read(bt->channel_ichg) > 0) ? 1 : 0;
+}
+
+static int twl4030_madc_bat_get_voltage(struct twl4030_madc_battery *bt)
+{
+ return madc_read(bt->channel_vbat);
+}
+
+static int twl4030_madc_bat_get_current(struct twl4030_madc_battery *bt)
+{
+ return madc_read(bt->channel_ichg) * 1000;
+}
+
+static int twl4030_madc_bat_get_temp(struct twl4030_madc_battery *bt)
+{
+ return madc_read(bt->channel_temp) * 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(bat))
+ 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 = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage(bat)) > 95)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else {
+ if (twl4030_madc_bat_get_charging_status(bat))
+ 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(bat) * 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(bat);
+ 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(bat));
+ 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(bat));
+ 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(bat);
+ break;
+ case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: {
+ int percent = twl4030_madc_bat_voltscale(bat,
+ twl4030_madc_bat_get_voltage(bat));
+ /* 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)
+{
+ power_supply_changed(psy);
+}
+
+static const struct power_supply_desc twl4030_madc_bat_desc = {
+ .name = "twl4030_battery",
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .properties = twl4030_madc_bat_props,
+ .num_properties = ARRAY_SIZE(twl4030_madc_bat_props),
+ .get_property = twl4030_madc_bat_get_property,
+ .external_power_changed = twl4030_madc_bat_ext_changed,
+
+};
+
+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;
+ struct power_supply_config psy_cfg = {};
+ int ret = 0;
+
+ twl4030_madc_bat = devm_kzalloc(&pdev->dev, sizeof(*twl4030_madc_bat),
+ GFP_KERNEL);
+ if (!twl4030_madc_bat)
+ return -ENOMEM;
+
+ twl4030_madc_bat->channel_temp = iio_channel_get(&pdev->dev, "temp");
+ if (IS_ERR(twl4030_madc_bat->channel_temp)) {
+ ret = PTR_ERR(twl4030_madc_bat->channel_temp);
+ goto err;
+ }
+
+ twl4030_madc_bat->channel_ichg = iio_channel_get(&pdev->dev, "ichg");
+ if (IS_ERR(twl4030_madc_bat->channel_ichg)) {
+ ret = PTR_ERR(twl4030_madc_bat->channel_ichg);
+ goto err_temp;
+ }
+
+ twl4030_madc_bat->channel_vbat = iio_channel_get(&pdev->dev, "vbat");
+ if (IS_ERR(twl4030_madc_bat->channel_vbat)) {
+ ret = PTR_ERR(twl4030_madc_bat->channel_vbat);
+ goto err_ichg;
+ }
+
+ /* 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);
+ psy_cfg.drv_data = twl4030_madc_bat;
+ twl4030_madc_bat->psy = power_supply_register(&pdev->dev,
+ &twl4030_madc_bat_desc,
+ &psy_cfg);
+ if (IS_ERR(twl4030_madc_bat->psy)) {
+ ret = PTR_ERR(twl4030_madc_bat->psy);
+ goto err_vbat;
+ }
+
+ return 0;
+
+err_vbat:
+ iio_channel_release(twl4030_madc_bat->channel_vbat);
+err_ichg:
+ iio_channel_release(twl4030_madc_bat->channel_ichg);
+err_temp:
+ iio_channel_release(twl4030_madc_bat->channel_temp);
+err:
+ return ret;
+}
+
+static int twl4030_madc_battery_remove(struct platform_device *pdev)
+{
+ struct twl4030_madc_battery *bat = platform_get_drvdata(pdev);
+
+ power_supply_unregister(bat->psy);
+
+ iio_channel_release(bat->channel_vbat);
+ iio_channel_release(bat->channel_ichg);
+ iio_channel_release(bat->channel_temp);
+
+ 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");
+MODULE_ALIAS("platform:twl4030_madc_battery");
diff --git a/drivers/power/supply/wm831x_backup.c b/drivers/power/supply/wm831x_backup.c
new file mode 100644
index 0000000..2e33109
--- /dev/null
+++ b/drivers/power/supply/wm831x_backup.c
@@ -0,0 +1,225 @@
+/*
+ * Backup battery driver for Wolfson Microelectronics wm831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * 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/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/pmu.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+struct wm831x_backup {
+ struct wm831x *wm831x;
+ struct power_supply *backup;
+ struct power_supply_desc backup_desc;
+ char name[20];
+};
+
+static int wm831x_backup_read_voltage(struct wm831x *wm831x,
+ enum wm831x_auxadc src,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_auxadc_read_uv(wm831x, src);
+ if (ret >= 0)
+ val->intval = ret;
+
+ return ret;
+}
+
+/*********************************************************************
+ * Backup supply properties
+ *********************************************************************/
+
+static void wm831x_config_backup(struct wm831x *wm831x)
+{
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_backup_pdata *pdata;
+ int ret, reg;
+
+ if (!wm831x_pdata || !wm831x_pdata->backup) {
+ dev_warn(wm831x->dev,
+ "No backup battery charger configuration\n");
+ return;
+ }
+
+ pdata = wm831x_pdata->backup;
+
+ reg = 0;
+
+ if (pdata->charger_enable)
+ reg |= WM831X_BKUP_CHG_ENA | WM831X_BKUP_BATT_DET_ENA;
+ if (pdata->no_constant_voltage)
+ reg |= WM831X_BKUP_CHG_MODE;
+
+ switch (pdata->vlim) {
+ case 2500:
+ break;
+ case 3100:
+ reg |= WM831X_BKUP_CHG_VLIM;
+ break;
+ default:
+ dev_err(wm831x->dev, "Invalid backup voltage limit %dmV\n",
+ pdata->vlim);
+ }
+
+ switch (pdata->ilim) {
+ case 100:
+ break;
+ case 200:
+ reg |= 1;
+ break;
+ case 300:
+ reg |= 2;
+ break;
+ case 400:
+ reg |= 3;
+ break;
+ default:
+ dev_err(wm831x->dev, "Invalid backup current limit %duA\n",
+ pdata->ilim);
+ }
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+ return;
+ }
+
+ ret = wm831x_set_bits(wm831x, WM831X_BACKUP_CHARGER_CONTROL,
+ WM831X_BKUP_CHG_ENA_MASK |
+ WM831X_BKUP_CHG_MODE_MASK |
+ WM831X_BKUP_BATT_DET_ENA_MASK |
+ WM831X_BKUP_CHG_VLIM_MASK |
+ WM831X_BKUP_CHG_ILIM_MASK,
+ reg);
+ if (ret != 0)
+ dev_err(wm831x->dev,
+ "Failed to set backup charger config: %d\n", ret);
+
+ wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_backup_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_backup *devdata = dev_get_drvdata(psy->dev.parent);
+ struct wm831x *wm831x = devdata->wm831x;
+ int ret = 0;
+
+ ret = wm831x_reg_read(wm831x, WM831X_BACKUP_CHARGER_CONTROL);
+ if (ret < 0)
+ return ret;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ if (ret & WM831X_BKUP_CHG_STS)
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ else
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_backup_read_voltage(wm831x, WM831X_AUX_BKUP_BATT,
+ val);
+ break;
+
+ case POWER_SUPPLY_PROP_PRESENT:
+ if (ret & WM831X_BKUP_CHG_STS)
+ val->intval = 1;
+ else
+ val->intval = 0;
+ break;
+
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_backup_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_PRESENT,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static int wm831x_backup_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_backup *devdata;
+
+ devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup),
+ GFP_KERNEL);
+ if (devdata == NULL)
+ return -ENOMEM;
+
+ devdata->wm831x = wm831x;
+ platform_set_drvdata(pdev, devdata);
+
+ /* We ignore configuration failures since we can still read
+ * back the status without enabling the charger (which may
+ * already be enabled anyway).
+ */
+ wm831x_config_backup(wm831x);
+
+ if (wm831x_pdata && wm831x_pdata->wm831x_num)
+ snprintf(devdata->name, sizeof(devdata->name),
+ "wm831x-backup.%d", wm831x_pdata->wm831x_num);
+ else
+ snprintf(devdata->name, sizeof(devdata->name),
+ "wm831x-backup");
+
+ devdata->backup_desc.name = devdata->name;
+ devdata->backup_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ devdata->backup_desc.properties = wm831x_backup_props;
+ devdata->backup_desc.num_properties = ARRAY_SIZE(wm831x_backup_props);
+ devdata->backup_desc.get_property = wm831x_backup_get_prop;
+ devdata->backup = power_supply_register(&pdev->dev,
+ &devdata->backup_desc, NULL);
+
+ return PTR_ERR_OR_ZERO(devdata->backup);
+}
+
+static int wm831x_backup_remove(struct platform_device *pdev)
+{
+ struct wm831x_backup *devdata = platform_get_drvdata(pdev);
+
+ power_supply_unregister(devdata->backup);
+
+ return 0;
+}
+
+static struct platform_driver wm831x_backup_driver = {
+ .probe = wm831x_backup_probe,
+ .remove = wm831x_backup_remove,
+ .driver = {
+ .name = "wm831x-backup",
+ },
+};
+
+module_platform_driver(wm831x_backup_driver);
+
+MODULE_DESCRIPTION("Backup battery charger driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-backup");
diff --git a/drivers/power/supply/wm831x_power.c b/drivers/power/supply/wm831x_power.c
new file mode 100644
index 0000000..7082301
--- /dev/null
+++ b/drivers/power/supply/wm831x_power.c
@@ -0,0 +1,673 @@
+/*
+ * PMU driver for Wolfson Microelectronics wm831x PMICs
+ *
+ * Copyright 2009 Wolfson Microelectronics PLC.
+ *
+ * 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/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+
+#include <linux/mfd/wm831x/core.h>
+#include <linux/mfd/wm831x/auxadc.h>
+#include <linux/mfd/wm831x/pmu.h>
+#include <linux/mfd/wm831x/pdata.h>
+
+struct wm831x_power {
+ struct wm831x *wm831x;
+ struct power_supply *wall;
+ struct power_supply *usb;
+ struct power_supply *battery;
+ struct power_supply_desc wall_desc;
+ struct power_supply_desc usb_desc;
+ struct power_supply_desc battery_desc;
+ char wall_name[20];
+ char usb_name[20];
+ char battery_name[20];
+ bool have_battery;
+};
+
+static int wm831x_power_check_online(struct wm831x *wm831x, int supply,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & supply)
+ val->intval = 1;
+ else
+ val->intval = 0;
+
+ return 0;
+}
+
+static int wm831x_power_read_voltage(struct wm831x *wm831x,
+ enum wm831x_auxadc src,
+ union power_supply_propval *val)
+{
+ int ret;
+
+ ret = wm831x_auxadc_read_uv(wm831x, src);
+ if (ret >= 0)
+ val->intval = ret;
+
+ return ret;
+}
+
+/*********************************************************************
+ * WALL Power
+ *********************************************************************/
+static int wm831x_wall_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_WALL, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_WALL, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_wall_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * USB Power
+ *********************************************************************/
+static int wm831x_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_USB, val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_USB, val);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+struct chg_map {
+ int val;
+ int reg_val;
+};
+
+static struct chg_map trickle_ilims[] = {
+ { 50, 0 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 100, 1 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 150, 2 << WM831X_CHG_TRKL_ILIM_SHIFT },
+ { 200, 3 << WM831X_CHG_TRKL_ILIM_SHIFT },
+};
+
+static struct chg_map vsels[] = {
+ { 4050, 0 << WM831X_CHG_VSEL_SHIFT },
+ { 4100, 1 << WM831X_CHG_VSEL_SHIFT },
+ { 4150, 2 << WM831X_CHG_VSEL_SHIFT },
+ { 4200, 3 << WM831X_CHG_VSEL_SHIFT },
+};
+
+static struct chg_map fast_ilims[] = {
+ { 0, 0 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 50, 1 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 100, 2 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 150, 3 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 200, 4 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 250, 5 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 300, 6 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 350, 7 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 400, 8 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 450, 9 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 500, 10 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 600, 11 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 700, 12 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 800, 13 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 900, 14 << WM831X_CHG_FAST_ILIM_SHIFT },
+ { 1000, 15 << WM831X_CHG_FAST_ILIM_SHIFT },
+};
+
+static struct chg_map eoc_iterms[] = {
+ { 20, 0 << WM831X_CHG_ITERM_SHIFT },
+ { 30, 1 << WM831X_CHG_ITERM_SHIFT },
+ { 40, 2 << WM831X_CHG_ITERM_SHIFT },
+ { 50, 3 << WM831X_CHG_ITERM_SHIFT },
+ { 60, 4 << WM831X_CHG_ITERM_SHIFT },
+ { 70, 5 << WM831X_CHG_ITERM_SHIFT },
+ { 80, 6 << WM831X_CHG_ITERM_SHIFT },
+ { 90, 7 << WM831X_CHG_ITERM_SHIFT },
+};
+
+static struct chg_map chg_times[] = {
+ { 60, 0 << WM831X_CHG_TIME_SHIFT },
+ { 90, 1 << WM831X_CHG_TIME_SHIFT },
+ { 120, 2 << WM831X_CHG_TIME_SHIFT },
+ { 150, 3 << WM831X_CHG_TIME_SHIFT },
+ { 180, 4 << WM831X_CHG_TIME_SHIFT },
+ { 210, 5 << WM831X_CHG_TIME_SHIFT },
+ { 240, 6 << WM831X_CHG_TIME_SHIFT },
+ { 270, 7 << WM831X_CHG_TIME_SHIFT },
+ { 300, 8 << WM831X_CHG_TIME_SHIFT },
+ { 330, 9 << WM831X_CHG_TIME_SHIFT },
+ { 360, 10 << WM831X_CHG_TIME_SHIFT },
+ { 390, 11 << WM831X_CHG_TIME_SHIFT },
+ { 420, 12 << WM831X_CHG_TIME_SHIFT },
+ { 450, 13 << WM831X_CHG_TIME_SHIFT },
+ { 480, 14 << WM831X_CHG_TIME_SHIFT },
+ { 510, 15 << WM831X_CHG_TIME_SHIFT },
+};
+
+static void wm831x_battey_apply_config(struct wm831x *wm831x,
+ struct chg_map *map, int count, int val,
+ int *reg, const char *name,
+ const char *units)
+{
+ int i;
+
+ for (i = 0; i < count; i++)
+ if (val == map[i].val)
+ break;
+ if (i == count) {
+ dev_err(wm831x->dev, "Invalid %s %d%s\n",
+ name, val, units);
+ } else {
+ *reg |= map[i].reg_val;
+ dev_dbg(wm831x->dev, "Set %s of %d%s\n", name, val, units);
+ }
+}
+
+static void wm831x_config_battery(struct wm831x *wm831x)
+{
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_battery_pdata *pdata;
+ int ret, reg1, reg2;
+
+ if (!wm831x_pdata || !wm831x_pdata->battery) {
+ dev_warn(wm831x->dev,
+ "No battery charger configuration\n");
+ return;
+ }
+
+ pdata = wm831x_pdata->battery;
+
+ reg1 = 0;
+ reg2 = 0;
+
+ if (!pdata->enable) {
+ dev_info(wm831x->dev, "Battery charger disabled\n");
+ return;
+ }
+
+ reg1 |= WM831X_CHG_ENA;
+ if (pdata->off_mask)
+ reg2 |= WM831X_CHG_OFF_MSK;
+ if (pdata->fast_enable)
+ reg1 |= WM831X_CHG_FAST;
+
+ wm831x_battey_apply_config(wm831x, trickle_ilims,
+ ARRAY_SIZE(trickle_ilims),
+ pdata->trickle_ilim, &reg2,
+ "trickle charge current limit", "mA");
+
+ wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels),
+ pdata->vsel, &reg2,
+ "target voltage", "mV");
+
+ wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims),
+ pdata->fast_ilim, &reg2,
+ "fast charge current limit", "mA");
+
+ wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms),
+ pdata->eoc_iterm, &reg1,
+ "end of charge current threshold", "mA");
+
+ wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times),
+ pdata->timeout, &reg2,
+ "charger timeout", "min");
+
+ ret = wm831x_reg_unlock(wm831x);
+ if (ret != 0) {
+ dev_err(wm831x->dev, "Failed to unlock registers: %d\n", ret);
+ return;
+ }
+
+ ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_1,
+ WM831X_CHG_ENA_MASK |
+ WM831X_CHG_FAST_MASK |
+ WM831X_CHG_ITERM_MASK,
+ reg1);
+ if (ret != 0)
+ dev_err(wm831x->dev, "Failed to set charger control 1: %d\n",
+ ret);
+
+ ret = wm831x_set_bits(wm831x, WM831X_CHARGER_CONTROL_2,
+ WM831X_CHG_OFF_MSK |
+ WM831X_CHG_TIME_MASK |
+ WM831X_CHG_FAST_ILIM_MASK |
+ WM831X_CHG_TRKL_ILIM_MASK |
+ WM831X_CHG_VSEL_MASK,
+ reg2);
+ if (ret != 0)
+ dev_err(wm831x->dev, "Failed to set charger control 2: %d\n",
+ ret);
+
+ wm831x_reg_lock(wm831x);
+}
+
+static int wm831x_bat_check_status(struct wm831x *wm831x, int *status)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_SYSTEM_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & WM831X_PWR_SRC_BATT) {
+ *status = POWER_SUPPLY_STATUS_DISCHARGING;
+ return 0;
+ }
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_OFF:
+ *status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ break;
+ case WM831X_CHG_STATE_TRICKLE:
+ case WM831X_CHG_STATE_FAST:
+ *status = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+
+ default:
+ *status = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_check_type(struct wm831x *wm831x, int *type)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_TRICKLE:
+ case WM831X_CHG_STATE_TRICKLE_OT:
+ *type = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ break;
+ case WM831X_CHG_STATE_FAST:
+ case WM831X_CHG_STATE_FAST_OT:
+ *type = POWER_SUPPLY_CHARGE_TYPE_FAST;
+ break;
+ default:
+ *type = POWER_SUPPLY_CHARGE_TYPE_NONE;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_check_health(struct wm831x *wm831x, int *health)
+{
+ int ret;
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_STATUS);
+ if (ret < 0)
+ return ret;
+
+ if (ret & WM831X_BATT_HOT_STS) {
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ return 0;
+ }
+
+ if (ret & WM831X_BATT_COLD_STS) {
+ *health = POWER_SUPPLY_HEALTH_COLD;
+ return 0;
+ }
+
+ if (ret & WM831X_BATT_OV_STS) {
+ *health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ return 0;
+ }
+
+ switch (ret & WM831X_CHG_STATE_MASK) {
+ case WM831X_CHG_STATE_TRICKLE_OT:
+ case WM831X_CHG_STATE_FAST_OT:
+ *health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ break;
+ case WM831X_CHG_STATE_DEFECTIVE:
+ *health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ break;
+ default:
+ *health = POWER_SUPPLY_HEALTH_GOOD;
+ break;
+ }
+
+ return 0;
+}
+
+static int wm831x_bat_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm831x_power *wm831x_power = dev_get_drvdata(psy->dev.parent);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ ret = wm831x_bat_check_status(wm831x, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ ret = wm831x_power_check_online(wm831x, WM831X_PWR_SRC_BATT,
+ val);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ ret = wm831x_power_read_voltage(wm831x, WM831X_AUX_BATT, val);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ ret = wm831x_bat_check_health(wm831x, &val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ ret = wm831x_bat_check_type(wm831x, &val->intval);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm831x_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static const char *wm831x_bat_irqs[] = {
+ "BATT HOT",
+ "BATT COLD",
+ "BATT FAIL",
+ "OV",
+ "END",
+ "TO",
+ "MODE",
+ "START",
+};
+
+static irqreturn_t wm831x_bat_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ dev_dbg(wm831x->dev, "Battery status changed: %d\n", irq);
+
+ /* The battery charger is autonomous so we don't need to do
+ * anything except kick user space */
+ if (wm831x_power->have_battery)
+ power_supply_changed(wm831x_power->battery);
+
+ return IRQ_HANDLED;
+}
+
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static irqreturn_t wm831x_syslo_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ /* Not much we can actually *do* but tell people for
+ * posterity, we're probably about to run out of power. */
+ dev_crit(wm831x->dev, "SYSVDD under voltage\n");
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wm831x_pwr_src_irq(int irq, void *data)
+{
+ struct wm831x_power *wm831x_power = data;
+ struct wm831x *wm831x = wm831x_power->wm831x;
+
+ dev_dbg(wm831x->dev, "Power source changed\n");
+
+ /* Just notify for everything - little harm in overnotifying. */
+ if (wm831x_power->have_battery)
+ power_supply_changed(wm831x_power->battery);
+ power_supply_changed(wm831x_power->usb);
+ power_supply_changed(wm831x_power->wall);
+
+ return IRQ_HANDLED;
+}
+
+static int wm831x_power_probe(struct platform_device *pdev)
+{
+ struct wm831x *wm831x = dev_get_drvdata(pdev->dev.parent);
+ struct wm831x_pdata *wm831x_pdata = wm831x->dev->platform_data;
+ struct wm831x_power *power;
+ int ret, irq, i;
+
+ power = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_power),
+ GFP_KERNEL);
+ if (power == NULL)
+ return -ENOMEM;
+
+ power->wm831x = wm831x;
+ platform_set_drvdata(pdev, power);
+
+ if (wm831x_pdata && wm831x_pdata->wm831x_num) {
+ snprintf(power->wall_name, sizeof(power->wall_name),
+ "wm831x-wall.%d", wm831x_pdata->wm831x_num);
+ snprintf(power->battery_name, sizeof(power->wall_name),
+ "wm831x-battery.%d", wm831x_pdata->wm831x_num);
+ snprintf(power->usb_name, sizeof(power->wall_name),
+ "wm831x-usb.%d", wm831x_pdata->wm831x_num);
+ } else {
+ snprintf(power->wall_name, sizeof(power->wall_name),
+ "wm831x-wall");
+ snprintf(power->battery_name, sizeof(power->wall_name),
+ "wm831x-battery");
+ snprintf(power->usb_name, sizeof(power->wall_name),
+ "wm831x-usb");
+ }
+
+ /* We ignore configuration failures since we can still read back
+ * the status without enabling the charger.
+ */
+ wm831x_config_battery(wm831x);
+
+ power->wall_desc.name = power->wall_name;
+ power->wall_desc.type = POWER_SUPPLY_TYPE_MAINS;
+ power->wall_desc.properties = wm831x_wall_props;
+ power->wall_desc.num_properties = ARRAY_SIZE(wm831x_wall_props);
+ power->wall_desc.get_property = wm831x_wall_get_prop;
+ power->wall = power_supply_register(&pdev->dev, &power->wall_desc,
+ NULL);
+ if (IS_ERR(power->wall)) {
+ ret = PTR_ERR(power->wall);
+ goto err;
+ }
+
+ power->usb_desc.name = power->usb_name,
+ power->usb_desc.type = POWER_SUPPLY_TYPE_USB;
+ power->usb_desc.properties = wm831x_usb_props;
+ power->usb_desc.num_properties = ARRAY_SIZE(wm831x_usb_props);
+ power->usb_desc.get_property = wm831x_usb_get_prop;
+ power->usb = power_supply_register(&pdev->dev, &power->usb_desc, NULL);
+ if (IS_ERR(power->usb)) {
+ ret = PTR_ERR(power->usb);
+ goto err_wall;
+ }
+
+ ret = wm831x_reg_read(wm831x, WM831X_CHARGER_CONTROL_1);
+ if (ret < 0)
+ goto err_wall;
+ power->have_battery = ret & WM831X_CHG_ENA;
+
+ if (power->have_battery) {
+ power->battery_desc.name = power->battery_name;
+ power->battery_desc.properties = wm831x_bat_props;
+ power->battery_desc.num_properties = ARRAY_SIZE(wm831x_bat_props);
+ power->battery_desc.get_property = wm831x_bat_get_prop;
+ power->battery_desc.use_for_apm = 1;
+ power->battery = power_supply_register(&pdev->dev,
+ &power->battery_desc,
+ NULL);
+ if (IS_ERR(power->battery)) {
+ ret = PTR_ERR(power->battery);
+ goto err_usb;
+ }
+ }
+
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO"));
+ ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "System power low",
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request SYSLO IRQ %d: %d\n",
+ irq, ret);
+ goto err_battery;
+ }
+
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC"));
+ ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT, "Power source",
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev, "Failed to request PWR SRC IRQ %d: %d\n",
+ irq, ret);
+ goto err_syslo;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+ irq = wm831x_irq(wm831x,
+ platform_get_irq_byname(pdev,
+ wm831x_bat_irqs[i]));
+ ret = request_threaded_irq(irq, NULL, wm831x_bat_irq,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ wm831x_bat_irqs[i],
+ power);
+ if (ret != 0) {
+ dev_err(&pdev->dev,
+ "Failed to request %s IRQ %d: %d\n",
+ wm831x_bat_irqs[i], irq, ret);
+ goto err_bat_irq;
+ }
+ }
+
+ return ret;
+
+err_bat_irq:
+ --i;
+ for (; i >= 0; i--) {
+ irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]);
+ free_irq(irq, power);
+ }
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC"));
+ free_irq(irq, power);
+err_syslo:
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO"));
+ free_irq(irq, power);
+err_battery:
+ if (power->have_battery)
+ power_supply_unregister(power->battery);
+err_usb:
+ power_supply_unregister(power->usb);
+err_wall:
+ power_supply_unregister(power->wall);
+err:
+ return ret;
+}
+
+static int wm831x_power_remove(struct platform_device *pdev)
+{
+ struct wm831x_power *wm831x_power = platform_get_drvdata(pdev);
+ struct wm831x *wm831x = wm831x_power->wm831x;
+ int irq, i;
+
+ for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) {
+ irq = wm831x_irq(wm831x,
+ platform_get_irq_byname(pdev,
+ wm831x_bat_irqs[i]));
+ free_irq(irq, wm831x_power);
+ }
+
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC"));
+ free_irq(irq, wm831x_power);
+
+ irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO"));
+ free_irq(irq, wm831x_power);
+
+ if (wm831x_power->have_battery)
+ power_supply_unregister(wm831x_power->battery);
+ power_supply_unregister(wm831x_power->wall);
+ power_supply_unregister(wm831x_power->usb);
+ return 0;
+}
+
+static struct platform_driver wm831x_power_driver = {
+ .probe = wm831x_power_probe,
+ .remove = wm831x_power_remove,
+ .driver = {
+ .name = "wm831x-power",
+ },
+};
+
+module_platform_driver(wm831x_power_driver);
+
+MODULE_DESCRIPTION("Power supply driver for WM831x PMICs");
+MODULE_AUTHOR("Mark Brown <broonie@opensource.wolfsonmicro.com>");
+MODULE_LICENSE("GPL");
+MODULE_ALIAS("platform:wm831x-power");
diff --git a/drivers/power/supply/wm8350_power.c b/drivers/power/supply/wm8350_power.c
new file mode 100644
index 0000000..5c58806
--- /dev/null
+++ b/drivers/power/supply/wm8350_power.c
@@ -0,0 +1,540 @@
+/*
+ * Battery driver for wm8350 PMIC
+ *
+ * Copyright 2007, 2008 Wolfson Microelectronics PLC.
+ *
+ * Based on OLPC Battery Driver
+ *
+ * Copyright 2006 David Woodhouse <dwmw2@infradead.org>
+ *
+ * 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/err.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/mfd/wm8350/supply.h>
+#include <linux/mfd/wm8350/core.h>
+#include <linux/mfd/wm8350/comparator.h>
+
+static int wm8350_read_battery_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_BATT, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+static int wm8350_read_line_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_LINE, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+static int wm8350_read_usb_uvolts(struct wm8350 *wm8350)
+{
+ return wm8350_read_auxadc(wm8350, WM8350_AUXADC_USB, 0, 0)
+ * WM8350_AUX_COEFF;
+}
+
+#define WM8350_BATT_SUPPLY 1
+#define WM8350_USB_SUPPLY 2
+#define WM8350_LINE_SUPPLY 4
+
+static inline int wm8350_charge_time_min(struct wm8350 *wm8350, int min)
+{
+ if (!wm8350->power.rev_g_coeff)
+ return (((min - 30) / 15) & 0xf) << 8;
+ else
+ return (((min - 30) / 30) & 0xf) << 8;
+}
+
+static int wm8350_get_supplies(struct wm8350 *wm8350)
+{
+ u16 sm, ov, co, chrg;
+ int supplies = 0;
+
+ sm = wm8350_reg_read(wm8350, WM8350_STATE_MACHINE_STATUS);
+ ov = wm8350_reg_read(wm8350, WM8350_MISC_OVERRIDES);
+ co = wm8350_reg_read(wm8350, WM8350_COMPARATOR_OVERRIDES);
+ chrg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
+
+ /* USB_SM */
+ sm = (sm & WM8350_USB_SM_MASK) >> WM8350_USB_SM_SHIFT;
+
+ /* CHG_ISEL */
+ chrg &= WM8350_CHG_ISEL_MASK;
+
+ /* If the USB state machine is active then we're using that with or
+ * without battery, otherwise check for wall supply */
+ if (((sm == WM8350_USB_SM_100_SLV) ||
+ (sm == WM8350_USB_SM_500_SLV) ||
+ (sm == WM8350_USB_SM_STDBY_SLV))
+ && !(ov & WM8350_USB_LIMIT_OVRDE))
+ supplies = WM8350_USB_SUPPLY;
+ else if (((sm == WM8350_USB_SM_100_SLV) ||
+ (sm == WM8350_USB_SM_500_SLV) ||
+ (sm == WM8350_USB_SM_STDBY_SLV))
+ && (ov & WM8350_USB_LIMIT_OVRDE) && (chrg == 0))
+ supplies = WM8350_USB_SUPPLY | WM8350_BATT_SUPPLY;
+ else if (co & WM8350_WALL_FB_OVRDE)
+ supplies = WM8350_LINE_SUPPLY;
+ else
+ supplies = WM8350_BATT_SUPPLY;
+
+ return supplies;
+}
+
+static int wm8350_charger_config(struct wm8350 *wm8350,
+ struct wm8350_charger_policy *policy)
+{
+ u16 reg, eoc_mA, fast_limit_mA;
+
+ if (!policy) {
+ dev_warn(wm8350->dev,
+ "No charger policy, charger not configured.\n");
+ return -EINVAL;
+ }
+
+ /* make sure USB fast charge current is not > 500mA */
+ if (policy->fast_limit_USB_mA > 500) {
+ dev_err(wm8350->dev, "USB fast charge > 500mA\n");
+ return -EINVAL;
+ }
+
+ eoc_mA = WM8350_CHG_EOC_mA(policy->eoc_mA);
+
+ wm8350_reg_unlock(wm8350);
+
+ reg = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1)
+ & WM8350_CHG_ENA_R168;
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
+ reg | eoc_mA | policy->trickle_start_mV |
+ WM8350_CHG_TRICKLE_TEMP_CHOKE |
+ WM8350_CHG_TRICKLE_USB_CHOKE |
+ WM8350_CHG_FAST_USB_THROTTLE);
+
+ if (wm8350_get_supplies(wm8350) & WM8350_USB_SUPPLY) {
+ fast_limit_mA =
+ WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_USB_mA);
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
+ policy->charge_mV | policy->trickle_charge_USB_mA |
+ fast_limit_mA | wm8350_charge_time_min(wm8350,
+ policy->charge_timeout));
+
+ } else {
+ fast_limit_mA =
+ WM8350_CHG_FAST_LIMIT_mA(policy->fast_limit_mA);
+ wm8350_reg_write(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2,
+ policy->charge_mV | policy->trickle_charge_mA |
+ fast_limit_mA | wm8350_charge_time_min(wm8350,
+ policy->charge_timeout));
+ }
+
+ wm8350_reg_lock(wm8350);
+ return 0;
+}
+
+static int wm8350_batt_status(struct wm8350 *wm8350)
+{
+ u16 state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2);
+ state &= WM8350_CHG_STS_MASK;
+
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ case WM8350_CHG_STS_TRICKLE:
+ case WM8350_CHG_STS_FAST:
+ return POWER_SUPPLY_STATUS_CHARGING;
+
+ default:
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+}
+
+static ssize_t charger_state_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(dev);
+ char *charge;
+ int state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
+ WM8350_CHG_STS_MASK;
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ charge = "Charger Off";
+ break;
+ case WM8350_CHG_STS_TRICKLE:
+ charge = "Trickle Charging";
+ break;
+ case WM8350_CHG_STS_FAST:
+ charge = "Fast Charging";
+ break;
+ default:
+ return 0;
+ }
+
+ return sprintf(buf, "%s\n", charge);
+}
+
+static DEVICE_ATTR(charger_state, 0444, charger_state_show, NULL);
+
+static irqreturn_t wm8350_charger_handler(int irq, void *data)
+{
+ struct wm8350 *wm8350 = data;
+ struct wm8350_power *power = &wm8350->power;
+ struct wm8350_charger_policy *policy = power->policy;
+
+ switch (irq - wm8350->irq_base) {
+ case WM8350_IRQ_CHG_BAT_FAIL:
+ dev_err(wm8350->dev, "battery failed\n");
+ break;
+ case WM8350_IRQ_CHG_TO:
+ dev_err(wm8350->dev, "charger timeout\n");
+ power_supply_changed(power->battery);
+ break;
+
+ case WM8350_IRQ_CHG_BAT_HOT:
+ case WM8350_IRQ_CHG_BAT_COLD:
+ case WM8350_IRQ_CHG_START:
+ case WM8350_IRQ_CHG_END:
+ power_supply_changed(power->battery);
+ break;
+
+ case WM8350_IRQ_CHG_FAST_RDY:
+ dev_dbg(wm8350->dev, "fast charger ready\n");
+ wm8350_charger_config(wm8350, policy);
+ wm8350_reg_unlock(wm8350);
+ wm8350_set_bits(wm8350, WM8350_BATTERY_CHARGER_CONTROL_1,
+ WM8350_CHG_FAST);
+ wm8350_reg_lock(wm8350);
+ break;
+
+ case WM8350_IRQ_CHG_VBATT_LT_3P9:
+ dev_warn(wm8350->dev, "battery < 3.9V\n");
+ break;
+ case WM8350_IRQ_CHG_VBATT_LT_3P1:
+ dev_warn(wm8350->dev, "battery < 3.1V\n");
+ break;
+ case WM8350_IRQ_CHG_VBATT_LT_2P85:
+ dev_warn(wm8350->dev, "battery < 2.85V\n");
+ break;
+
+ /* Supply change. We will overnotify but it should do
+ * no harm. */
+ case WM8350_IRQ_EXT_USB_FB:
+ case WM8350_IRQ_EXT_WALL_FB:
+ wm8350_charger_config(wm8350, policy);
+ case WM8350_IRQ_EXT_BAT_FB: /* Fall through */
+ power_supply_changed(power->battery);
+ power_supply_changed(power->usb);
+ power_supply_changed(power->ac);
+ break;
+
+ default:
+ dev_err(wm8350->dev, "Unknown interrupt %d\n", irq);
+ }
+
+ return IRQ_HANDLED;
+}
+
+/*********************************************************************
+ * AC Power
+ *********************************************************************/
+static int wm8350_ac_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_LINE_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_line_uvolts(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property wm8350_ac_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * USB Power
+ *********************************************************************/
+static int wm8350_usb_get_prop(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_USB_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_usb_uvolts(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+ return ret;
+}
+
+static enum power_supply_property wm8350_usb_props[] = {
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+};
+
+/*********************************************************************
+ * Battery properties
+ *********************************************************************/
+
+static int wm8350_bat_check_health(struct wm8350 *wm8350)
+{
+ u16 reg;
+
+ if (wm8350_read_battery_uvolts(wm8350) < 2850000)
+ return POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+
+ reg = wm8350_reg_read(wm8350, WM8350_CHARGER_OVERRIDES);
+ if (reg & WM8350_CHG_BATT_HOT_OVRDE)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+
+ if (reg & WM8350_CHG_BATT_COLD_OVRDE)
+ return POWER_SUPPLY_HEALTH_COLD;
+
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static int wm8350_bat_get_charge_type(struct wm8350 *wm8350)
+{
+ int state;
+
+ state = wm8350_reg_read(wm8350, WM8350_BATTERY_CHARGER_CONTROL_2) &
+ WM8350_CHG_STS_MASK;
+ switch (state) {
+ case WM8350_CHG_STS_OFF:
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ case WM8350_CHG_STS_TRICKLE:
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+ case WM8350_CHG_STS_FAST:
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ default:
+ return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+ }
+}
+
+static int wm8350_bat_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm8350 *wm8350 = dev_get_drvdata(psy->dev.parent);
+ int ret = 0;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = wm8350_batt_status(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = !!(wm8350_get_supplies(wm8350) &
+ WM8350_BATT_SUPPLY);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = wm8350_read_battery_uvolts(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = wm8350_bat_check_health(wm8350);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = wm8350_bat_get_charge_type(wm8350);
+ break;
+ default:
+ ret = -EINVAL;
+ break;
+ }
+
+ return ret;
+}
+
+static enum power_supply_property wm8350_bat_props[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+};
+
+static const struct power_supply_desc wm8350_ac_desc = {
+ .name = "wm8350-ac",
+ .type = POWER_SUPPLY_TYPE_MAINS,
+ .properties = wm8350_ac_props,
+ .num_properties = ARRAY_SIZE(wm8350_ac_props),
+ .get_property = wm8350_ac_get_prop,
+};
+
+static const struct power_supply_desc wm8350_battery_desc = {
+ .name = "wm8350-battery",
+ .properties = wm8350_bat_props,
+ .num_properties = ARRAY_SIZE(wm8350_bat_props),
+ .get_property = wm8350_bat_get_property,
+ .use_for_apm = 1,
+};
+
+static const struct power_supply_desc wm8350_usb_desc = {
+ .name = "wm8350-usb",
+ .type = POWER_SUPPLY_TYPE_USB,
+ .properties = wm8350_usb_props,
+ .num_properties = ARRAY_SIZE(wm8350_usb_props),
+ .get_property = wm8350_usb_get_prop,
+};
+
+/*********************************************************************
+ * Initialisation
+ *********************************************************************/
+
+static void wm8350_init_charger(struct wm8350 *wm8350)
+{
+ /* register our interest in charger events */
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT,
+ wm8350_charger_handler, 0, "Battery hot", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD,
+ wm8350_charger_handler, 0, "Battery cold", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL,
+ wm8350_charger_handler, 0, "Battery fail", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_TO,
+ wm8350_charger_handler, 0,
+ "Charger timeout", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_END,
+ wm8350_charger_handler, 0,
+ "Charge end", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_START,
+ wm8350_charger_handler, 0,
+ "Charge start", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_FAST_RDY,
+ wm8350_charger_handler, 0,
+ "Fast charge ready", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9,
+ wm8350_charger_handler, 0,
+ "Battery <3.9V", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1,
+ wm8350_charger_handler, 0,
+ "Battery <3.1V", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85,
+ wm8350_charger_handler, 0,
+ "Battery <2.85V", wm8350);
+
+ /* and supply change events */
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_USB_FB,
+ wm8350_charger_handler, 0, "USB", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_WALL_FB,
+ wm8350_charger_handler, 0, "Wall", wm8350);
+ wm8350_register_irq(wm8350, WM8350_IRQ_EXT_BAT_FB,
+ wm8350_charger_handler, 0, "Battery", wm8350);
+}
+
+static void free_charger_irq(struct wm8350 *wm8350)
+{
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_HOT, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_COLD, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_BAT_FAIL, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_TO, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_END, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_START, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P9, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_3P1, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_CHG_VBATT_LT_2P85, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_USB_FB, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_WALL_FB, wm8350);
+ wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350);
+}
+
+static int wm8350_power_probe(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+ struct wm8350_power *power = &wm8350->power;
+ struct wm8350_charger_policy *policy = power->policy;
+ int ret;
+
+ power->ac = power_supply_register(&pdev->dev, &wm8350_ac_desc, NULL);
+ if (IS_ERR(power->ac))
+ return PTR_ERR(power->ac);
+
+ power->battery = power_supply_register(&pdev->dev, &wm8350_battery_desc,
+ NULL);
+ if (IS_ERR(power->battery)) {
+ ret = PTR_ERR(power->battery);
+ goto battery_failed;
+ }
+
+ power->usb = power_supply_register(&pdev->dev, &wm8350_usb_desc, NULL);
+ if (IS_ERR(power->usb)) {
+ ret = PTR_ERR(power->usb);
+ goto usb_failed;
+ }
+
+ ret = device_create_file(&pdev->dev, &dev_attr_charger_state);
+ if (ret < 0)
+ dev_warn(wm8350->dev, "failed to add charge sysfs: %d\n", ret);
+ ret = 0;
+
+ wm8350_init_charger(wm8350);
+ if (wm8350_charger_config(wm8350, policy) == 0) {
+ wm8350_reg_unlock(wm8350);
+ wm8350_set_bits(wm8350, WM8350_POWER_MGMT_5, WM8350_CHG_ENA);
+ wm8350_reg_lock(wm8350);
+ }
+
+ return ret;
+
+usb_failed:
+ power_supply_unregister(power->battery);
+battery_failed:
+ power_supply_unregister(power->ac);
+
+ return ret;
+}
+
+static int wm8350_power_remove(struct platform_device *pdev)
+{
+ struct wm8350 *wm8350 = platform_get_drvdata(pdev);
+ struct wm8350_power *power = &wm8350->power;
+
+ free_charger_irq(wm8350);
+ device_remove_file(&pdev->dev, &dev_attr_charger_state);
+ power_supply_unregister(power->battery);
+ power_supply_unregister(power->ac);
+ power_supply_unregister(power->usb);
+ return 0;
+}
+
+static struct platform_driver wm8350_power_driver = {
+ .probe = wm8350_power_probe,
+ .remove = wm8350_power_remove,
+ .driver = {
+ .name = "wm8350-power",
+ },
+};
+
+module_platform_driver(wm8350_power_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("Power supply driver for WM8350");
+MODULE_ALIAS("platform:wm8350-power");
diff --git a/drivers/power/supply/wm97xx_battery.c b/drivers/power/supply/wm97xx_battery.c
new file mode 100644
index 0000000..6285626
--- /dev/null
+++ b/drivers/power/supply/wm97xx_battery.c
@@ -0,0 +1,297 @@
+/*
+ * Battery measurement code for WM97xx
+ *
+ * based on tosa_battery.c
+ *
+ * Copyright (C) 2008 Marek Vasut <marek.vasut@gmail.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/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/wm97xx.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/gpio.h>
+#include <linux/irq.h>
+#include <linux/slab.h>
+
+static struct work_struct bat_work;
+static DEFINE_MUTEX(work_lock);
+static int bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+static enum power_supply_property *prop;
+
+static unsigned long wm97xx_read_bat(struct power_supply *bat_ps)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent),
+ pdata->batt_aux) * pdata->batt_mult /
+ pdata->batt_div;
+}
+
+static unsigned long wm97xx_read_temp(struct power_supply *bat_ps)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ return wm97xx_read_aux_adc(dev_get_drvdata(bat_ps->dev.parent),
+ pdata->temp_aux) * pdata->temp_mult /
+ pdata->temp_div;
+}
+
+static int wm97xx_bat_get_property(struct power_supply *bat_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = bat_status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = pdata->batt_tech;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (pdata->batt_aux >= 0)
+ val->intval = wm97xx_read_bat(bat_ps);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ if (pdata->temp_aux >= 0)
+ val->intval = wm97xx_read_temp(bat_ps);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (pdata->max_voltage >= 0)
+ val->intval = pdata->max_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (pdata->min_voltage >= 0)
+ val->intval = pdata->min_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static void wm97xx_bat_external_power_changed(struct power_supply *bat_ps)
+{
+ schedule_work(&bat_work);
+}
+
+static void wm97xx_bat_update(struct power_supply *bat_ps)
+{
+ int old_status = bat_status;
+ struct wm97xx_pdata *wmdata = bat_ps->dev.parent->platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ mutex_lock(&work_lock);
+
+ bat_status = (pdata->charge_gpio >= 0) ?
+ (gpio_get_value(pdata->charge_gpio) ?
+ POWER_SUPPLY_STATUS_DISCHARGING :
+ POWER_SUPPLY_STATUS_CHARGING) :
+ POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (old_status != bat_status) {
+ pr_debug("%s: %i -> %i\n", bat_ps->desc->name, old_status,
+ bat_status);
+ power_supply_changed(bat_ps);
+ }
+
+ mutex_unlock(&work_lock);
+}
+
+static struct power_supply *bat_psy;
+static struct power_supply_desc bat_psy_desc = {
+ .type = POWER_SUPPLY_TYPE_BATTERY,
+ .get_property = wm97xx_bat_get_property,
+ .external_power_changed = wm97xx_bat_external_power_changed,
+ .use_for_apm = 1,
+};
+
+static void wm97xx_bat_work(struct work_struct *work)
+{
+ wm97xx_bat_update(bat_psy);
+}
+
+static irqreturn_t wm97xx_chrg_irq(int irq, void *data)
+{
+ schedule_work(&bat_work);
+ return IRQ_HANDLED;
+}
+
+#ifdef CONFIG_PM
+static int wm97xx_bat_suspend(struct device *dev)
+{
+ flush_work(&bat_work);
+ return 0;
+}
+
+static int wm97xx_bat_resume(struct device *dev)
+{
+ schedule_work(&bat_work);
+ return 0;
+}
+
+static const struct dev_pm_ops wm97xx_bat_pm_ops = {
+ .suspend = wm97xx_bat_suspend,
+ .resume = wm97xx_bat_resume,
+};
+#endif
+
+static int wm97xx_bat_probe(struct platform_device *dev)
+{
+ int ret = 0;
+ int props = 1; /* POWER_SUPPLY_PROP_PRESENT */
+ int i = 0;
+ struct wm97xx_pdata *wmdata = dev->dev.platform_data;
+ struct wm97xx_batt_pdata *pdata;
+
+ if (!wmdata) {
+ dev_err(&dev->dev, "No platform data supplied\n");
+ return -EINVAL;
+ }
+
+ pdata = wmdata->batt_pdata;
+
+ if (dev->id != -1)
+ return -EINVAL;
+
+ if (!pdata) {
+ dev_err(&dev->dev, "No platform_data supplied\n");
+ return -EINVAL;
+ }
+
+ if (gpio_is_valid(pdata->charge_gpio)) {
+ ret = gpio_request(pdata->charge_gpio, "BATT CHRG");
+ if (ret)
+ goto err;
+ ret = gpio_direction_input(pdata->charge_gpio);
+ if (ret)
+ goto err2;
+ ret = request_irq(gpio_to_irq(pdata->charge_gpio),
+ wm97xx_chrg_irq, 0,
+ "AC Detect", dev);
+ if (ret)
+ goto err2;
+ props++; /* POWER_SUPPLY_PROP_STATUS */
+ }
+
+ if (pdata->batt_tech >= 0)
+ props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */
+ if (pdata->temp_aux >= 0)
+ props++; /* POWER_SUPPLY_PROP_TEMP */
+ if (pdata->batt_aux >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
+ if (pdata->max_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
+ if (pdata->min_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
+
+ prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
+ if (!prop) {
+ ret = -ENOMEM;
+ goto err3;
+ }
+
+ prop[i++] = POWER_SUPPLY_PROP_PRESENT;
+ if (pdata->charge_gpio >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_STATUS;
+ if (pdata->batt_tech >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
+ if (pdata->temp_aux >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TEMP;
+ if (pdata->batt_aux >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ if (pdata->max_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ if (pdata->min_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+
+ INIT_WORK(&bat_work, wm97xx_bat_work);
+
+ if (!pdata->batt_name) {
+ dev_info(&dev->dev, "Please consider setting proper battery "
+ "name in platform definition file, falling "
+ "back to name \"wm97xx-batt\"\n");
+ bat_psy_desc.name = "wm97xx-batt";
+ } else
+ bat_psy_desc.name = pdata->batt_name;
+
+ bat_psy_desc.properties = prop;
+ bat_psy_desc.num_properties = props;
+
+ bat_psy = power_supply_register(&dev->dev, &bat_psy_desc, NULL);
+ if (!IS_ERR(bat_psy)) {
+ schedule_work(&bat_work);
+ } else {
+ ret = PTR_ERR(bat_psy);
+ goto err4;
+ }
+
+ return 0;
+err4:
+ kfree(prop);
+err3:
+ if (gpio_is_valid(pdata->charge_gpio))
+ free_irq(gpio_to_irq(pdata->charge_gpio), dev);
+err2:
+ if (gpio_is_valid(pdata->charge_gpio))
+ gpio_free(pdata->charge_gpio);
+err:
+ return ret;
+}
+
+static int wm97xx_bat_remove(struct platform_device *dev)
+{
+ struct wm97xx_pdata *wmdata = dev->dev.platform_data;
+ struct wm97xx_batt_pdata *pdata = wmdata->batt_pdata;
+
+ if (pdata && gpio_is_valid(pdata->charge_gpio)) {
+ free_irq(gpio_to_irq(pdata->charge_gpio), dev);
+ gpio_free(pdata->charge_gpio);
+ }
+ cancel_work_sync(&bat_work);
+ power_supply_unregister(bat_psy);
+ kfree(prop);
+ return 0;
+}
+
+static struct platform_driver wm97xx_bat_driver = {
+ .driver = {
+ .name = "wm97xx-battery",
+#ifdef CONFIG_PM
+ .pm = &wm97xx_bat_pm_ops,
+#endif
+ },
+ .probe = wm97xx_bat_probe,
+ .remove = wm97xx_bat_remove,
+};
+
+module_platform_driver(wm97xx_bat_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Marek Vasut <marek.vasut@gmail.com>");
+MODULE_DESCRIPTION("WM97xx battery driver");
diff --git a/drivers/power/supply/z2_battery.c b/drivers/power/supply/z2_battery.c
new file mode 100644
index 0000000..8a43b49
--- /dev/null
+++ b/drivers/power/supply/z2_battery.c
@@ -0,0 +1,330 @@
+/*
+ * Battery measurement code for Zipit Z2
+ *
+ * Copyright (C) 2009 Peter Edwards <sweetlilmre@gmail.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/gpio.h>
+#include <linux/i2c.h>
+#include <linux/interrupt.h>
+#include <linux/irq.h>
+#include <linux/power_supply.h>
+#include <linux/slab.h>
+#include <linux/z2_battery.h>
+
+#define Z2_DEFAULT_NAME "Z2"
+
+struct z2_charger {
+ struct z2_battery_info *info;
+ int bat_status;
+ struct i2c_client *client;
+ struct power_supply *batt_ps;
+ struct power_supply_desc batt_ps_desc;
+ struct mutex work_lock;
+ struct work_struct bat_work;
+};
+
+static unsigned long z2_read_bat(struct z2_charger *charger)
+{
+ int data;
+ data = i2c_smbus_read_byte_data(charger->client,
+ charger->info->batt_I2C_reg);
+ if (data < 0)
+ return 0;
+
+ return data * charger->info->batt_mult / charger->info->batt_div;
+}
+
+static int z2_batt_get_property(struct power_supply *batt_ps,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct z2_charger *charger = power_supply_get_drvdata(batt_ps);
+ struct z2_battery_info *info = charger->info;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = charger->bat_status;
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = info->batt_tech;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ if (info->batt_I2C_reg >= 0)
+ val->intval = z2_read_bat(charger);
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ if (info->max_voltage >= 0)
+ val->intval = info->max_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (info->min_voltage >= 0)
+ val->intval = info->min_voltage;
+ else
+ return -EINVAL;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = 1;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static void z2_batt_ext_power_changed(struct power_supply *batt_ps)
+{
+ struct z2_charger *charger = power_supply_get_drvdata(batt_ps);
+
+ schedule_work(&charger->bat_work);
+}
+
+static void z2_batt_update(struct z2_charger *charger)
+{
+ int old_status = charger->bat_status;
+ struct z2_battery_info *info;
+
+ info = charger->info;
+
+ mutex_lock(&charger->work_lock);
+
+ charger->bat_status = (info->charge_gpio >= 0) ?
+ (gpio_get_value(info->charge_gpio) ?
+ POWER_SUPPLY_STATUS_CHARGING :
+ POWER_SUPPLY_STATUS_DISCHARGING) :
+ POWER_SUPPLY_STATUS_UNKNOWN;
+
+ if (old_status != charger->bat_status) {
+ pr_debug("%s: %i -> %i\n", charger->batt_ps->desc->name,
+ old_status,
+ charger->bat_status);
+ power_supply_changed(charger->batt_ps);
+ }
+
+ mutex_unlock(&charger->work_lock);
+}
+
+static void z2_batt_work(struct work_struct *work)
+{
+ struct z2_charger *charger;
+ charger = container_of(work, struct z2_charger, bat_work);
+ z2_batt_update(charger);
+}
+
+static irqreturn_t z2_charge_switch_irq(int irq, void *devid)
+{
+ struct z2_charger *charger = devid;
+ schedule_work(&charger->bat_work);
+ return IRQ_HANDLED;
+}
+
+static int z2_batt_ps_init(struct z2_charger *charger, int props)
+{
+ int i = 0;
+ enum power_supply_property *prop;
+ struct z2_battery_info *info = charger->info;
+
+ if (info->charge_gpio >= 0)
+ props++; /* POWER_SUPPLY_PROP_STATUS */
+ if (info->batt_tech >= 0)
+ props++; /* POWER_SUPPLY_PROP_TECHNOLOGY */
+ if (info->batt_I2C_reg >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_NOW */
+ if (info->max_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MAX */
+ if (info->min_voltage >= 0)
+ props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */
+
+ prop = kzalloc(props * sizeof(*prop), GFP_KERNEL);
+ if (!prop)
+ return -ENOMEM;
+
+ prop[i++] = POWER_SUPPLY_PROP_PRESENT;
+ if (info->charge_gpio >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_STATUS;
+ if (info->batt_tech >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_TECHNOLOGY;
+ if (info->batt_I2C_reg >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_NOW;
+ if (info->max_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MAX;
+ if (info->min_voltage >= 0)
+ prop[i++] = POWER_SUPPLY_PROP_VOLTAGE_MIN;
+
+ if (!info->batt_name) {
+ dev_info(&charger->client->dev,
+ "Please consider setting proper battery "
+ "name in platform definition file, falling "
+ "back to name \" Z2_DEFAULT_NAME \"\n");
+ charger->batt_ps_desc.name = Z2_DEFAULT_NAME;
+ } else
+ charger->batt_ps_desc.name = info->batt_name;
+
+ charger->batt_ps_desc.properties = prop;
+ charger->batt_ps_desc.num_properties = props;
+ charger->batt_ps_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ charger->batt_ps_desc.get_property = z2_batt_get_property;
+ charger->batt_ps_desc.external_power_changed =
+ z2_batt_ext_power_changed;
+ charger->batt_ps_desc.use_for_apm = 1;
+
+ return 0;
+}
+
+static int z2_batt_probe(struct i2c_client *client,
+ const struct i2c_device_id *id)
+{
+ int ret = 0;
+ int props = 1; /* POWER_SUPPLY_PROP_PRESENT */
+ struct z2_charger *charger;
+ struct z2_battery_info *info = client->dev.platform_data;
+ struct power_supply_config psy_cfg = {};
+
+ if (info == NULL) {
+ dev_err(&client->dev,
+ "Please set platform device platform_data"
+ " to a valid z2_battery_info pointer!\n");
+ return -EINVAL;
+ }
+
+ charger = kzalloc(sizeof(*charger), GFP_KERNEL);
+ if (charger == NULL)
+ return -ENOMEM;
+
+ charger->bat_status = POWER_SUPPLY_STATUS_UNKNOWN;
+ charger->info = info;
+ charger->client = client;
+ i2c_set_clientdata(client, charger);
+ psy_cfg.drv_data = charger;
+
+ mutex_init(&charger->work_lock);
+
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+ ret = gpio_request(info->charge_gpio, "BATT CHRG");
+ if (ret)
+ goto err;
+
+ ret = gpio_direction_input(info->charge_gpio);
+ if (ret)
+ goto err2;
+
+ irq_set_irq_type(gpio_to_irq(info->charge_gpio),
+ IRQ_TYPE_EDGE_BOTH);
+ ret = request_irq(gpio_to_irq(info->charge_gpio),
+ z2_charge_switch_irq, 0,
+ "AC Detect", charger);
+ if (ret)
+ goto err3;
+ }
+
+ ret = z2_batt_ps_init(charger, props);
+ if (ret)
+ goto err3;
+
+ INIT_WORK(&charger->bat_work, z2_batt_work);
+
+ charger->batt_ps = power_supply_register(&client->dev,
+ &charger->batt_ps_desc,
+ &psy_cfg);
+ if (IS_ERR(charger->batt_ps)) {
+ ret = PTR_ERR(charger->batt_ps);
+ goto err4;
+ }
+
+ schedule_work(&charger->bat_work);
+
+ return 0;
+
+err4:
+ kfree(charger->batt_ps_desc.properties);
+err3:
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+ free_irq(gpio_to_irq(info->charge_gpio), charger);
+err2:
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio))
+ gpio_free(info->charge_gpio);
+err:
+ kfree(charger);
+ return ret;
+}
+
+static int z2_batt_remove(struct i2c_client *client)
+{
+ struct z2_charger *charger = i2c_get_clientdata(client);
+ struct z2_battery_info *info = charger->info;
+
+ cancel_work_sync(&charger->bat_work);
+ power_supply_unregister(charger->batt_ps);
+
+ kfree(charger->batt_ps_desc.properties);
+ if (info->charge_gpio >= 0 && gpio_is_valid(info->charge_gpio)) {
+ free_irq(gpio_to_irq(info->charge_gpio), charger);
+ gpio_free(info->charge_gpio);
+ }
+
+ kfree(charger);
+
+ return 0;
+}
+
+#ifdef CONFIG_PM
+static int z2_batt_suspend(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct z2_charger *charger = i2c_get_clientdata(client);
+
+ flush_work(&charger->bat_work);
+ return 0;
+}
+
+static int z2_batt_resume(struct device *dev)
+{
+ struct i2c_client *client = to_i2c_client(dev);
+ struct z2_charger *charger = i2c_get_clientdata(client);
+
+ schedule_work(&charger->bat_work);
+ return 0;
+}
+
+static const struct dev_pm_ops z2_battery_pm_ops = {
+ .suspend = z2_batt_suspend,
+ .resume = z2_batt_resume,
+};
+
+#define Z2_BATTERY_PM_OPS (&z2_battery_pm_ops)
+
+#else
+#define Z2_BATTERY_PM_OPS (NULL)
+#endif
+
+static const struct i2c_device_id z2_batt_id[] = {
+ { "aer915", 0 },
+ { }
+};
+MODULE_DEVICE_TABLE(i2c, z2_batt_id);
+
+static struct i2c_driver z2_batt_driver = {
+ .driver = {
+ .name = "z2-battery",
+ .pm = Z2_BATTERY_PM_OPS
+ },
+ .probe = z2_batt_probe,
+ .remove = z2_batt_remove,
+ .id_table = z2_batt_id,
+};
+module_i2c_driver(z2_batt_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>");
+MODULE_DESCRIPTION("Zipit Z2 battery driver");
OpenPOWER on IntegriCloud