From 8c0984e5a75337df513047ec92a6c09d78e3e5cd Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 17 Jun 2016 13:54:32 +0200 Subject: power: move power supply drivers to power/supply This moves all power supply drivers from drivers/power/ to drivers/power/supply/. The intention is a cleaner source tree, since drivers/power/ also contains frameworks unrelated to power supply, like adaptive voltage scaling. Signed-off-by: Sebastian Reichel diff --git a/Documentation/power/power_supply_class.txt b/Documentation/power/power_supply_class.txt index 82dacc0..0c72588 100644 --- a/Documentation/power/power_supply_class.txt +++ b/Documentation/power/power_supply_class.txt @@ -39,8 +39,8 @@ kind of power supply, and can process/present them to a user in consistent manner. Results for different power supplies and machines are also directly comparable. -See drivers/power/ds2760_battery.c and drivers/power/pda_power.c for the -example how to declare and handle attributes. +See drivers/power/supply/ds2760_battery.c and drivers/power/supply/pda_power.c +for the example how to declare and handle attributes. Units diff --git a/MAINTAINERS b/MAINTAINERS index 20bb1d0..ca66b41 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3751,8 +3751,8 @@ F: drivers/leds/leds-da90??.c F: drivers/mfd/da903x.c F: drivers/mfd/da90??-*.c F: drivers/mfd/da91??-*.c -F: drivers/power/da9052-battery.c -F: drivers/power/da91??-*.c +F: drivers/power/supply/da9052-battery.c +F: drivers/power/supply/da91??-*.c F: drivers/regulator/da903x.c F: drivers/regulator/da9???-regulator.[ch] F: drivers/rtc/rtc-da90??.c @@ -7451,8 +7451,8 @@ MAXIM MUIC CHARGER DRIVERS FOR EXYNOS BASED BOARDS M: Krzysztof Kozlowski L: linux-pm@vger.kernel.org S: Supported -F: drivers/power/max14577_charger.c -F: drivers/power/max77693_charger.c +F: drivers/power/supply/max14577_charger.c +F: drivers/power/supply/max77693_charger.c MAXIM MAX77802 MULTIFUNCTION PMIC DEVICE DRIVERS M: Javier Martinez Canillas @@ -8323,11 +8323,11 @@ R: Pali Rohár F: include/linux/power/bq2415x_charger.h F: include/linux/power/bq27xxx_battery.h F: include/linux/power/isp1704_charger.h -F: drivers/power/bq2415x_charger.c -F: drivers/power/bq27xxx_battery.c -F: drivers/power/bq27xxx_battery_i2c.c -F: drivers/power/isp1704_charger.c -F: drivers/power/rx51_battery.c +F: drivers/power/supply/bq2415x_charger.c +F: drivers/power/supply/bq27xxx_battery.c +F: drivers/power/supply/bq27xxx_battery_i2c.c +F: drivers/power/supply/isp1704_charger.c +F: drivers/power/supply/rx51_battery.c NTB DRIVER CORE M: Jon Mason @@ -9334,8 +9334,7 @@ S: Maintained F: Documentation/devicetree/bindings/power/ F: Documentation/devicetree/bindings/power_supply/ F: include/linux/power_supply.h -F: drivers/power/ -X: drivers/power/avs/ +F: drivers/power/supply/ POWER STATE COORDINATION INTERFACE (PSCI) M: Mark Rutland @@ -10322,8 +10321,8 @@ F: drivers/thunderbolt/ TI BQ27XXX POWER SUPPLY DRIVER R: Andrew F. Davis F: include/linux/power/bq27xxx_battery.h -F: drivers/power/bq27xxx_battery.c -F: drivers/power/bq27xxx_battery_i2c.c +F: drivers/power/supply/bq27xxx_battery.c +F: drivers/power/supply/bq27xxx_battery_i2c.c TIMEKEEPING, CLOCKSOURCE CORE, NTP, ALARMTIMER M: John Stultz @@ -11329,6 +11328,16 @@ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lee/mfd.git S: Supported F: drivers/mfd/syscon.c +SYSTEM RESET/SHUTDOWN DRIVERS +M: Sebastian Reichel +M: Dmitry Eremin-Solenikov +M: David Woodhouse +L: linux-pm@vger.kernel.org +T: git git://git.infradead.org/battery-2.6.git +S: Maintained +F: Documentation/devicetree/bindings/power/reset/ +F: drivers/power/reset/ + SYSV FILESYSTEM M: Christoph Hellwig S: Maintained @@ -11677,7 +11686,7 @@ F: include/linux/platform_data/lp855x.h TI LP8727 CHARGER DRIVER M: Milo Kim S: Maintained -F: drivers/power/lp8727_charger.c +F: drivers/power/supply/lp8727_charger.c F: include/linux/platform_data/lp8727.h TI LP8788 MFD DRIVER @@ -11686,7 +11695,7 @@ S: Maintained F: drivers/iio/adc/lp8788_adc.c F: drivers/leds/leds-lp8788.c F: drivers/mfd/lp8788*.c -F: drivers/power/lp8788-charger.c +F: drivers/power/supply/lp8788-charger.c F: drivers/regulator/lp8788-*.c F: include/linux/mfd/lp8788*.h @@ -12762,7 +12771,7 @@ F: drivers/input/touchscreen/wm97*.c F: drivers/mfd/arizona* F: drivers/mfd/wm*.c F: drivers/mfd/cs47l24* -F: drivers/power/wm83*.c +F: drivers/power/supply/wm83*.c F: drivers/rtc/rtc-wm83*.c F: drivers/regulator/wm8*.c F: drivers/video/backlight/wm83*_bl.c diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c deleted file mode 100644 index 63c57dc..0000000 --- a/drivers/power/88pm860x_battery.c +++ /dev/null @@ -1,1021 +0,0 @@ -/* - * Battery driver for Marvell 88PM860x PMIC - * - * Copyright (c) 2012 Marvell International Ltd. - * Author: Jett Zhou - * Haojian Zhuang - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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/88pm860x_charger.c b/drivers/power/88pm860x_charger.c deleted file mode 100644 index 2b82e44..0000000 --- a/drivers/power/88pm860x_charger.c +++ /dev/null @@ -1,760 +0,0 @@ -/* - * Battery driver for Marvell 88PM860x PMIC - * - * Copyright (c) 2012 Marvell International Ltd. - * Author: Jett Zhou - * Haojian Zhuang - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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/Kconfig b/drivers/power/Kconfig index acd4a15..63454b5 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,517 +1,3 @@ -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 - -source "drivers/power/reset/Kconfig" source "drivers/power/avs/Kconfig" +source "drivers/power/reset/Kconfig" +source "drivers/power/supply/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index e46b75d..ff35c71 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -1,76 +1,3 @@ -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_POWER_AVS) += avs/ -obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o -obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o -obj-$(CONFIG_CHARGER_TPS65217) += tps65217_charger.o obj-$(CONFIG_POWER_RESET) += reset/ -obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o -obj-$(CONFIG_AXP288_CHARGER) += axp288_charger.o +obj-$(CONFIG_POWER_SUPPLY) += supply/ diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c deleted file mode 100644 index d298645..0000000 --- a/drivers/power/ab8500_bmdata.c +++ /dev/null @@ -1,605 +0,0 @@ -#include -#include -#include -#include -#include -#include - -/* - * 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/ab8500_btemp.c b/drivers/power/ab8500_btemp.c deleted file mode 100644 index bf2e5dd..0000000 --- a/drivers/power/ab8500_btemp.c +++ /dev/null @@ -1,1212 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Battery temperature driver for AB8500 - * - * License Terms: GNU General Public License v2 - * Author: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 = - create_singlethread_workqueue("ab8500_btemp_wq"); - 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/ab8500_charger.c b/drivers/power/ab8500_charger.c deleted file mode 100644 index 30de5d4..0000000 --- a/drivers/power/ab8500_charger.c +++ /dev/null @@ -1,3765 +0,0 @@ -/* - * Copyright (C) ST-Ericsson SA 2012 - * - * Charger driver for AB8500 - * - * License Terms: GNU General Public License v2 - * Author: - * Johan Palsson - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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, ®_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, ®_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, - ®_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, ®_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, ®_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, ®_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, ®_value); - else - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_USBCH_STAT2_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 = - create_singlethread_workqueue("ab8500_charger_wq"); - 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/ab8500_fg.c b/drivers/power/ab8500_fg.c deleted file mode 100644 index 5a36cf8..0000000 --- a/drivers/power/ab8500_fg.c +++ /dev/null @@ -1,3272 +0,0 @@ -/* - * 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 - * Karl Komierowski - * Arun R Murthy - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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) -{ - struct ab8500_fg *fg; - - if (list_empty(&ab8500_fg_list)) - return NULL; - - fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); - return fg; -} - -/* 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, ®_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, - ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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 <> */ - -#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 = create_singlethread_workqueue("ab8500_fg_wq"); - 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/abx500_chargalg.c b/drivers/power/abx500_chargalg.c deleted file mode 100644 index d9104b1..0000000 --- a/drivers/power/abx500_chargalg.c +++ /dev/null @@ -1,2166 +0,0 @@ -/* - * 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 - * Karl Komierowski - * Arun R Murthy - * Author: Imre Sunyi - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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, ¶m); - 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, ¶m); - 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 <> */ - -#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 = - create_singlethread_workqueue("abx500_chargalg_wq"); - 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/act8945a_charger.c b/drivers/power/act8945a_charger.c deleted file mode 100644 index b5c00e4..0000000 --- a/drivers/power/act8945a_charger.c +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Power supply driver for the Active-semi ACT8945A PMIC - * - * Copyright (C) 2015 Atmel Corporation - * - * Author: Wenyou Yang - * - * 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 -#include -#include -#include -#include -#include - -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 regmap *regmap; - bool battery_temperature; -}; - -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; - - if (state == APCH_STATE_CSTATE_EOC) { - if (status & APCH_STATUS_CHGDAT) - *val = POWER_SUPPLY_STATUS_FULL; - else - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - } else if ((state == APCH_STATE_CSTATE_FAST) || - (state == APCH_STATE_CSTATE_PRE)) { - *val = POWER_SUPPLY_STATUS_CHARGING; - } else { - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; - } - - return 0; -} - -static int act8945a_get_charge_type(struct regmap *regmap, int *val) -{ - int ret; - unsigned int state; - - 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: - case APCH_STATE_CSTATE_DISABLED: - default: - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; - } - - return 0; -} - -static int act8945a_get_battery_health(struct act8945a_charger *charger, - struct regmap *regmap, int *val) -{ - int ret; - unsigned int status; - - ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); - if (ret < 0) - return ret; - - if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT)) - *val = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (!(status & APCH_STATUS_INDAT)) - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (status & APCH_STATUS_TIMRDAT) - *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else - *val = POWER_SUPPLY_HEALTH_GOOD; - - 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_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(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 const struct power_supply_desc act8945a_charger_desc = { - .name = "act8945a-charger", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = act8945a_charger_get_property, - .properties = act8945a_charger_props, - .num_properties = ARRAY_SIZE(act8945a_charger_props), -}; - -#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; - enum of_gpio_flags flags; - struct regmap *regmap = charger->regmap; - - u32 total_time_out; - u32 pre_time_out; - u32 input_voltage_threshold; - int chglev_pin; - - unsigned int value = 0; - - if (!np) { - dev_err(dev, "no charger of node\n"); - return -EINVAL; - } - - charger->battery_temperature = of_property_read_bool(np, - "active-semi,check-battery-temperature"); - - chglev_pin = of_get_named_gpio_flags(np, - "active-semi,chglev-gpios", 0, &flags); - - if (gpio_is_valid(chglev_pin)) { - gpio_set_value(chglev_pin, - ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); - } - - 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 *psy; - struct power_supply_config psy_cfg = {}; - int 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.parent, charger); - if (ret) - return ret; - - psy_cfg.of_node = pdev->dev.parent->of_node; - psy_cfg.drv_data = charger; - - psy = devm_power_supply_register(&pdev->dev, - &act8945a_charger_desc, - &psy_cfg); - if (IS_ERR(psy)) { - dev_err(&pdev->dev, "failed to register power supply\n"); - return PTR_ERR(psy); - } - - return 0; -} - -static struct platform_driver act8945a_charger_driver = { - .driver = { - .name = "act8945a-charger", - }, - .probe = act8945a_charger_probe, -}; -module_platform_driver(act8945a_charger_driver); - -MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver"); -MODULE_AUTHOR("Wenyou Yang "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/apm_power.c b/drivers/power/apm_power.c deleted file mode 100644 index 9d1a7fb..0000000 --- a/drivers/power/apm_power.c +++ /dev/null @@ -1,376 +0,0 @@ -/* - * Copyright © 2007 Anton Vorontsov - * Copyright © 2007 Eugeny Boger - * - * Author: Eugeny Boger - * - * 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 -#include -#include -#include - - -#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 "); -MODULE_DESCRIPTION("APM emulation driver for battery monitoring class"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/axp20x_usb_power.c b/drivers/power/axp20x_usb_power.c deleted file mode 100644 index 6af6feb..0000000 --- a/drivers/power/axp20x_usb_power.c +++ /dev/null @@ -1,294 +0,0 @@ -/* - * AXP20x PMIC USB power supply status driver - * - * Copyright (C) 2015 Hans de Goede - * Copyright (C) 2014 Bruno Prémont - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("AXP20x PMIC USB power supply status driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/axp288_charger.c b/drivers/power/axp288_charger.c deleted file mode 100644 index 4030eeb..0000000 --- a/drivers/power/axp288_charger.c +++ /dev/null @@ -1,970 +0,0 @@ -/* - * axp288_charger.c - X-power AXP288 PMIC Charger driver - * - * Copyright (C) 2014 Intel Corporation - * Author: Ramakrishna Pallala - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("X-power AXP288 Charger Driver"); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/axp288_fuel_gauge.c b/drivers/power/axp288_fuel_gauge.c deleted file mode 100644 index 50c0110..0000000 --- a/drivers/power/axp288_fuel_gauge.c +++ /dev/null @@ -1,1155 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_AUTHOR("Todd Brandt "); -MODULE_DESCRIPTION("Xpower AXP288 Fuel Gauge Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c deleted file mode 100644 index 73e2f0b..0000000 --- a/drivers/power/bq2415x_charger.c +++ /dev/null @@ -1,1815 +0,0 @@ -/* - * bq2415x charger driver - * - * Copyright (C) 2011-2013 Pali Rohár - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -/* 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 = ® - 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", ®, &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 "); -MODULE_DESCRIPTION("bq2415x charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c deleted file mode 100644 index f5746b9..0000000 --- a/drivers/power/bq24190_charger.c +++ /dev/null @@ -1,1546 +0,0 @@ -/* - * Driver for the TI bq24190 battery charger. - * - * Author: Mark A. Greer - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include - - -#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 VBATintval = 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 "); -MODULE_DESCRIPTION("TI BQ24190 Charger Driver"); diff --git a/drivers/power/bq24257_charger.c b/drivers/power/bq24257_charger.c deleted file mode 100644 index 1fea2c7..0000000 --- a/drivers/power/bq24257_charger.c +++ /dev/null @@ -1,1196 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#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 = 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 = bq24257_power_supply_init(bq); - if (ret < 0) { - dev_err(dev, "Failed to register power supply\n"); - 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 "); -MODULE_DESCRIPTION("bq24257 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq24735-charger.c b/drivers/power/bq24735-charger.c deleted file mode 100644 index fa454c1..0000000 --- a/drivers/power/bq24735-charger.c +++ /dev/null @@ -1,500 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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; - 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) -{ - struct bq24735_platform *pdata = charger->pdata; - int ret; - - if (pdata->status_gpio_valid) { - ret = gpio_get_value_cansleep(pdata->status_gpio); - return ret ^= pdata->status_gpio_active_low == 0; - } 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 = 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; - enum of_gpio_flags flags; - - pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); - if (!pdata) { - dev_err(&client->dev, - "Memory alloc for bq24735 pdata failed\n"); - return NULL; - } - - pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", - 0, &flags); - - if (flags & OF_GPIO_ACTIVE_LOW) - pdata->status_gpio_active_low = 1; - - 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); - - if (gpio_is_valid(charger->pdata->status_gpio)) { - ret = devm_gpio_request(&client->dev, - charger->pdata->status_gpio, - name); - if (ret) { - dev_err(&client->dev, - "Failed GPIO request for GPIO %d: %d\n", - charger->pdata->status_gpio, ret); - } - - charger->pdata->status_gpio_valid = !ret; - } - - if (!charger->pdata->status_gpio_valid - || 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 "); -MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/bq25890_charger.c b/drivers/power/bq25890_charger.c deleted file mode 100644 index f993a55..0000000 --- a/drivers/power/bq25890_charger.c +++ /dev/null @@ -1,994 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#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 "); -MODULE_DESCRIPTION("bq25890 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27xxx_battery.c b/drivers/power/bq27xxx_battery.c deleted file mode 100644 index 323d05a..0000000 --- a/drivers/power/bq27xxx_battery.c +++ /dev/null @@ -1,1102 +0,0 @@ -/* - * BQ27xxx battery driver - * - * Copyright (C) 2008 Rodolfo Giometti - * Copyright (C) 2008 Eurotech S.p.A. - * Copyright (C) 2010-2011 Lars-Peter Clausen - * Copyright (C) 2011 Pali Rohár - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 unsigned int poll_interval = 360; -module_param(poll_interval, uint, 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; - - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); - 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, false); - 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); - - 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_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 "); -MODULE_DESCRIPTION("BQ27xxx battery monitor driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq27xxx_battery_i2c.c b/drivers/power/bq27xxx_battery_i2c.c deleted file mode 100644 index 85d4ea2..0000000 --- a/drivers/power/bq27xxx_battery_i2c.c +++ /dev/null @@ -1,205 +0,0 @@ -/* - * BQ27xxx battery monitor I2C driver - * - * Copyright (C) 2015 Texas Instruments Incorporated - http://www.ti.com/ - * Andrew F. Davis - * - * 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 -#include -#include -#include - -#include - -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 = ® - 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 "); -MODULE_DESCRIPTION("BQ27xxx battery monitor i2c driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c deleted file mode 100644 index e664ca7..0000000 --- a/drivers/power/charger-manager.c +++ /dev/null @@ -1,2074 +0,0 @@ -/* - * Copyright (C) 2011 Samsung Electronics Co., Ltd. - * MyungJoo Ham - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * 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 "); -MODULE_DESCRIPTION("Charger Manager"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c deleted file mode 100644 index 3a0bc60..0000000 --- a/drivers/power/collie_battery.c +++ /dev/null @@ -1,422 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -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/da9030_battery.c b/drivers/power/da9030_battery.c deleted file mode 100644 index 5ca0f4d..0000000 --- a/drivers/power/da9030_battery.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Battery charger driver for Dialog Semiconductor DA9030 - * - * Copyright (C) 2008 Compulab, Ltd. - * Mike Rapoport - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#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/da9052-battery.c b/drivers/power/da9052-battery.c deleted file mode 100644 index 830ec46..0000000 --- a/drivers/power/da9052-battery.c +++ /dev/null @@ -1,669 +0,0 @@ -/* - * Batttery Driver for Dialog DA9052 PMICs - * - * Copyright(c) 2011 Dialog Semiconductor Ltd. - * - * Author: David Dajun Chen - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -/* 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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:da9052-bat"); diff --git a/drivers/power/da9150-charger.c b/drivers/power/da9150-charger.c deleted file mode 100644 index 6009981..0000000 --- a/drivers/power/da9150-charger.c +++ /dev/null @@ -1,694 +0,0 @@ -/* - * DA9150 Charger Driver - * - * Copyright (c) 2014 Dialog Semiconductor - * - * Author: Adam Thomson - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/da9150-fg.c b/drivers/power/da9150-fg.c deleted file mode 100644 index 8b8ce97..0000000 --- a/drivers/power/da9150-fg.c +++ /dev/null @@ -1,579 +0,0 @@ -/* - * DA9150 Fuel-Gauge Driver - * - * Copyright (c) 2015 Dialog Semiconductor - * - * Author: Adam Thomson - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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 "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c deleted file mode 100644 index 80f73cc..0000000 --- a/drivers/power/ds2760_battery.c +++ /dev/null @@ -1,647 +0,0 @@ -/* - * 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 - * February 2007 - * - * Matt Reimer - * April 2004, 2005, 2007 - * - * Szabolcs Gyurko - * September 2004 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#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 = create_singlethread_workqueue(dev_name(&pdev->dev)); - 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 , " - "Matt Reimer , " - "Anton Vorontsov "); -MODULE_DESCRIPTION("ds2760 battery driver"); diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c deleted file mode 100644 index d3743d0..0000000 --- a/drivers/power/ds2780_battery.c +++ /dev/null @@ -1,838 +0,0 @@ -/* - * 1-wire client/driver for the Maxim/Dallas DS2780 Stand-Alone Fuel Gauge IC - * - * Copyright (C) 2010 Indesign, LLC - * - * Author: Clifton Barnes - * - * 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 -#include -#include -#include -#include -#include -#include - -#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, ¤t_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, ¤t_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, ¤t_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 "); -MODULE_DESCRIPTION("Maxim/Dallas DS2780 Stand-Alone Fuel Gauage IC driver"); -MODULE_ALIAS("platform:ds2780-battery"); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c deleted file mode 100644 index c368002..0000000 --- a/drivers/power/ds2781_battery.c +++ /dev/null @@ -1,839 +0,0 @@ -/* - * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC - * - * Author: Renata Sayakhova - * - * 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 -#include -#include -#include -#include -#include -#include - -#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, ¤t_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, ¤t_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, ¤t_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 "); -MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver"); -MODULE_ALIAS("platform:ds2781-battery"); - diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c deleted file mode 100644 index a1b7e05..0000000 --- a/drivers/power/ds2782_battery.c +++ /dev/null @@ -1,475 +0,0 @@ -/* - * 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 - * - * UEvent sending added by Evgeny Romanov - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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, ¤t_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/generic-adc-battery.c b/drivers/power/generic-adc-battery.c deleted file mode 100644 index edb36bf..0000000 --- a/drivers/power/generic-adc-battery.c +++ /dev/null @@ -1,432 +0,0 @@ -/* - * Generic battery driver code using IIO - * Copyright (C) 2012, Anish Kumar - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("generic battery driver using IIO"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/goldfish_battery.c b/drivers/power/goldfish_battery.c deleted file mode 100644 index f5c525e..0000000 --- a/drivers/power/goldfish_battery.c +++ /dev/null @@ -1,256 +0,0 @@ -/* - * 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -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/gpio-charger.c b/drivers/power/gpio-charger.c deleted file mode 100644 index c5869b1..0000000 --- a/drivers/power/gpio-charger.c +++ /dev/null @@ -1,280 +0,0 @@ -/* - * Copyright (C) 2010, Lars-Peter Clausen - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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 "); -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/intel_mid_battery.c b/drivers/power/intel_mid_battery.c deleted file mode 100644 index 9fa4acc..0000000 --- a/drivers/power/intel_mid_battery.c +++ /dev/null @@ -1,796 +0,0 @@ -/* - * 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 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 = - create_singlethread_workqueue(dev_name(dev)); - 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 "); -MODULE_DESCRIPTION("Intel Moorestown PMIC Battery Driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/ipaq_micro_battery.c b/drivers/power/ipaq_micro_battery.c deleted file mode 100644 index 35b01c7..0000000 --- a/drivers/power/ipaq_micro_battery.c +++ /dev/null @@ -1,316 +0,0 @@ -/* - * 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 - * Author : Linus Walleij - * - */ - -#include -#include -#include -#include -#include -#include - -#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 = create_singlethread_workqueue("ipaq-battery-wq"); - 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, - µ_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, - µ_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 = µ_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/isp1704_charger.c b/drivers/power/isp1704_charger.c deleted file mode 100644 index 4cd6899..0000000 --- a/drivers/power/isp1704_charger.c +++ /dev/null @@ -1,559 +0,0 @@ -/* - * ISP1704 USB Charger Detection driver - * - * Copyright (C) 2010 Nokia Corporation - * Copyright (C) 2012 - 2013 Pali Rohár - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -/* 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/jz4740-battery.c b/drivers/power/jz4740-battery.c deleted file mode 100644 index 88f04f4..0000000 --- a/drivers/power/jz4740-battery.c +++ /dev/null @@ -1,425 +0,0 @@ -/* - * Battery measurement code for Ingenic JZ SOC. - * - * Copyright (C) 2009 Jiejing Zhang - * Copyright (C) 2010, Lars-Peter Clausen - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut -* - * 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 -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -#include -#include - -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 "); -MODULE_DESCRIPTION("JZ4740 SoC battery driver"); diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c deleted file mode 100644 index 042fb3da..0000000 --- a/drivers/power/lp8727_charger.c +++ /dev/null @@ -1,631 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include - -#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 *)¶m->eoc_level); - of_property_read_u8(np, "charging-current", (u8 *)¶m->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 , Daniel Jeong "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c deleted file mode 100644 index 7321b72..0000000 --- a/drivers/power/lp8788-charger.c +++ /dev/null @@ -1,764 +0,0 @@ -/* - * TI LP8788 MFD - battery charger driver - * - * Copyright 2012 Texas Instruments - * - * Author: Milo(Woogyom) Kim - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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/ltc2941-battery-gauge.c b/drivers/power/ltc2941-battery-gauge.c deleted file mode 100644 index 4adf2ba..0000000 --- a/drivers/power/ltc2941-battery-gauge.c +++ /dev/null @@ -1,514 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#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 = ®_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 (<c294x_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/max14577_charger.c b/drivers/power/max14577_charger.c deleted file mode 100644 index a36bcaf..0000000 --- a/drivers/power/max14577_charger.c +++ /dev/null @@ -1,648 +0,0 @@ -/* - * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 - * - * Copyright (C) 2013,2014 Samsung Electronics - * Krzysztof Kozlowski - * - * 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 -#include -#include -#include -#include - -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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, - ®_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 "); -MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c deleted file mode 100644 index 8689c80..0000000 --- a/drivers/power/max17040_battery.c +++ /dev/null @@ -1,305 +0,0 @@ -/* - * max17040_battery.c - * fuel-gauge systems for lithium-ion (Li+) batteries - * - * Copyright (C) 2009 Samsung Electronics - * Minkyu Kang - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c deleted file mode 100644 index 9c65f13..0000000 --- a/drivers/power/max17042_battery.c +++ /dev/null @@ -1,1016 +0,0 @@ -/* - * 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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, u32 *data, int size) -{ - struct regmap *map = chip->regmap; - int i; - - for (i = 0; i < size; i++) - regmap_read(map, addr + i, &data[i]); -} - -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); - u32 *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, - (u16 *)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); - u32 *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 "); -MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max77693_charger.c b/drivers/power/max77693_charger.c deleted file mode 100644 index 060cab5..0000000 --- a/drivers/power/max77693_charger.c +++ /dev/null @@ -1,771 +0,0 @@ -/* - * max77693_charger.c - Battery charger driver for the Maxim 77693 - * - * Copyright (C) 2014 Samsung Electronics - * Krzysztof Kozlowski - * - * 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 -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Maxim 77693 charger driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c deleted file mode 100644 index fdc73d6..0000000 --- a/drivers/power/max8903_charger.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * max8903_charger.c - Maxim 8903 USB/Adapter Charger Driver - * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_ALIAS("platform:max8903-charger"); diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c deleted file mode 100644 index 3b94620..0000000 --- a/drivers/power/max8925_power.c +++ /dev/null @@ -1,596 +0,0 @@ -/* - * Battery driver for Maxim MAX8925 - * - * Copyright (c) 2009-2010 Marvell International Ltd. - * Haojian Zhuang - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -/* 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/max8997_charger.c b/drivers/power/max8997_charger.c deleted file mode 100644 index 0b2eab5..0000000 --- a/drivers/power/max8997_charger.c +++ /dev/null @@ -1,211 +0,0 @@ -/* - * max8997_charger.c - Power supply consumer driver for the Maxim 8997/8966 - * - * Copyright (C) 2011 Samsung Electronics - * MyungJoo Ham - * - * 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 -#include -#include -#include -#include -#include -#include - -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, ®); - 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, ®); - 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, ®); - 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 "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c deleted file mode 100644 index b64cf0f..0000000 --- a/drivers/power/max8998_charger.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * max8998_charger.c - Power supply consumer driver for the Maxim 8998/LP3974 - * - * Copyright (C) 2009-2010 Samsung Electronics - * MyungJoo Ham - * - * 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 -#include -#include -#include -#include -#include -#include - -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, ®); - 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, ®); - 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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:max8998-battery"); diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c deleted file mode 100644 index 9e29b13..0000000 --- a/drivers/power/olpc_battery.c +++ /dev/null @@ -1,692 +0,0 @@ -/* - * Battery driver for One Laptop Per Child board. - * - * Copyright © 2006-2010 David Woodhouse - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -#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 "); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("Battery driver for One Laptop Per Child 'XO' machine"); diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c deleted file mode 100644 index d05597b..0000000 --- a/drivers/power/pcf50633-charger.c +++ /dev/null @@ -1,488 +0,0 @@ -/* NXP PCF50633 Main Battery Charger Driver - * - * (C) 2006-2008 by Openmoko, Inc. - * Author: Balaji Rao - * 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 -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -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 "); -MODULE_DESCRIPTION("PCF50633 mbc driver"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:pcf50633-mbc"); diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c deleted file mode 100644 index dfe1ee8..0000000 --- a/drivers/power/pda_power.c +++ /dev/null @@ -1,514 +0,0 @@ -/* - * 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 - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_ALIAS("platform:pda-power"); diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c deleted file mode 100644 index fb62ed3..0000000 --- a/drivers/power/pm2301_charger.c +++ /dev/null @@ -1,1257 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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, ®_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 = create_singlethread_workqueue("pm2xxx_charger_wq"); - 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/pm2301_charger.h b/drivers/power/pm2301_charger.h deleted file mode 100644 index 24181cf..0000000 --- a/drivers/power/pm2301_charger.h +++ /dev/null @@ -1,493 +0,0 @@ -/* - * 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/pmu_battery.c b/drivers/power/pmu_battery.c deleted file mode 100644 index 9c8d525..0000000 --- a/drivers/power/pmu_battery.c +++ /dev/null @@ -1,226 +0,0 @@ -/* - * Battery class driver for Apple PMU - * - * Copyright © 2006 David Woodhouse - * - * 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 -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_LICENSE("GPL"); -MODULE_DESCRIPTION("PMU battery driver"); diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h deleted file mode 100644 index cc439fd..0000000 --- a/drivers/power/power_supply.h +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Functions private to power supply class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * 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/power_supply_core.c b/drivers/power/power_supply_core.c deleted file mode 100644 index a74d8ca..0000000 --- a/drivers/power/power_supply_core.c +++ /dev/null @@ -1,989 +0,0 @@ -/* - * Universal power supply monitor class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#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 - -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 , " - "Szabolcs Gyurko, " - "Anton Vorontsov "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c deleted file mode 100644 index 2277ad9..0000000 --- a/drivers/power/power_supply_leds.c +++ /dev/null @@ -1,170 +0,0 @@ -/* - * LEDs triggers for power supply class - * - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include - -#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/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c deleted file mode 100644 index bcde8d1..0000000 --- a/drivers/power/power_supply_sysfs.c +++ /dev/null @@ -1,337 +0,0 @@ -/* - * Sysfs interface for the universal power supply monitor class - * - * Copyright © 2007 David Woodhouse - * Copyright © 2007 Anton Vorontsov - * Copyright © 2004 Szabolcs Gyurko - * Copyright © 2003 Ian Molton - * - * Modified: 2004, Oct Szabolcs Gyurko - * - * You may use this code as per GPL version 2 - */ - -#include -#include -#include -#include -#include - -#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/qcom_smbb.c b/drivers/power/qcom_smbb.c deleted file mode 100644 index b5896ba..0000000 --- a/drivers/power/qcom_smbb.c +++ /dev/null @@ -1,972 +0,0 @@ -/* 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/rt5033_battery.c b/drivers/power/rt5033_battery.c deleted file mode 100644 index bcdd830..0000000 --- a/drivers/power/rt5033_battery.c +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Fuel gauge driver for Richtek RT5033 - * - * Copyright (C) 2014 Samsung Electronics, Co., Ltd. - * Author: Beomho Seo - * - * 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 -#include -#include -#include -#include - -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 "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/rt9455_charger.c b/drivers/power/rt9455_charger.c deleted file mode 100644 index cfdbde9..0000000 --- a/drivers/power/rt9455_charger.c +++ /dev/null @@ -1,1763 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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 "); -MODULE_DESCRIPTION("Richtek RT9455 Charger Driver"); diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c deleted file mode 100644 index af9383d..0000000 --- a/drivers/power/rx51_battery.c +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Nokia RX-51 battery driver - * - * Copyright (C) 2012 Pali Rohár - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_DESCRIPTION("Nokia RX-51 battery driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c deleted file mode 100644 index 0ffe5cd..0000000 --- a/drivers/power/s3c_adc_battery.c +++ /dev/null @@ -1,459 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 "); -MODULE_DESCRIPTION("iPAQ H1930/H1940/RX1950 battery controller driver"); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c deleted file mode 100644 index 768b9fc..0000000 --- a/drivers/power/sbs-battery.c +++ /dev/null @@ -1,998 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -enum { - REG_MANUFACTURER_DATA, - REG_TEMPERATURE, - REG_VOLTAGE, - REG_CURRENT, - REG_CAPACITY, - REG_TIME_TO_EMPTY, - REG_TIME_TO_FULL, - REG_STATUS, - 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_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_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_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; - struct sbs_platform_data *pdata; - bool is_present; - bool gpio_detect; - bool enable_detection; - int irq; - int last_state; - int poll_time; - 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; - - if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); - - 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]; - - if (chip->pdata) { - retries_length = max(chip->pdata->i2c_retry_count + 1, 1); - retries_block = max(chip->pdata->i2c_retry_count + 1, 1); - } - - /* 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; - - if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); - - 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 = gpio_get_value(chip->pdata->battery_detect); - if (ret == chip->pdata->battery_detect_present) - val->intval = 1; - else - val->intval = 0; - chip->is_present = val->intval; - return ret; - } - - /* Write to ManufacturerAccess with - * ManufacturerAccess command and then - * read the status */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); - if (ret < 0) { - if (psp == POWER_SUPPLY_PROP_PRESENT) - val->intval = 0; /* battery removed */ - return ret; - } - - ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); - if (ret < 0) - 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_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_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 power_supply *battery = devid; - - 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->pdata->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; - } -} - -#if defined(CONFIG_OF) - -#include -#include - -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 sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) -{ - struct device_node *of_node = client->dev.of_node; - struct sbs_platform_data *pdata = client->dev.platform_data; - enum of_gpio_flags gpio_flags; - int rc; - u32 prop; - - /* verify this driver matches this device */ - if (!of_node) - return NULL; - - /* if platform data is set, honor it */ - if (pdata) - return pdata; - - /* first make sure at least one property is set, otherwise - * it won't change behavior from running without pdata. - */ - if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && - !of_get_property(of_node, "sbs,poll-retry-count", NULL) && - !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) - goto of_out; - - pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), - GFP_KERNEL); - if (!pdata) - goto of_out; - - rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); - if (!rc) - pdata->i2c_retry_count = prop; - - rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); - if (!rc) - pdata->poll_retry_count = prop; - - if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { - pdata->battery_detect = -1; - goto of_out; - } - - pdata->battery_detect = of_get_named_gpio_flags(of_node, - "sbs,battery-detect-gpios", 0, &gpio_flags); - - if (gpio_flags & OF_GPIO_ACTIVE_LOW) - pdata->battery_detect_present = 0; - else - pdata->battery_detect_present = 1; - -of_out: - return pdata; -} -#else -static struct sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) -{ - return client->dev.platform_data; -} -#endif - -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 = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); - if (!chip) - return -ENOMEM; - - chip->client = client; - chip->enable_detection = false; - chip->gpio_detect = 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; - - pdata = sbs_of_populate_pdata(client); - - if (pdata) { - chip->gpio_detect = gpio_is_valid(pdata->battery_detect); - chip->pdata = pdata; - } - - i2c_set_clientdata(client, chip); - - if (!chip->gpio_detect) - goto skip_gpio; - - rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); - if (rc) { - dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); - chip->gpio_detect = false; - goto skip_gpio; - } - - rc = gpio_direction_input(pdata->battery_detect); - if (rc) { - dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - irq = gpio_to_irq(pdata->battery_detect); - if (irq <= 0) { - dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - rc = request_irq(irq, sbs_irq, - IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&client->dev), chip->power_supply); - if (rc) { - dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - chip->irq = irq; - -skip_gpio: - /* - * Before we register, we might need to make sure we can actually talk - * to the battery. - */ - if (!force_load) { - 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 = 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: - if (chip->irq) - free_irq(chip->irq, chip->power_supply); - if (chip->gpio_detect) - gpio_free(pdata->battery_detect); - - kfree(chip); - - return rc; -} - -static int sbs_remove(struct i2c_client *client) -{ - struct sbs_info *chip = i2c_get_clientdata(client); - - if (chip->irq) - free_irq(chip->irq, chip->power_supply); - if (chip->gpio_detect) - gpio_free(chip->pdata->battery_detect); - - power_supply_unregister(chip->power_supply); - - cancel_delayed_work_sync(&chip->work); - - kfree(chip); - chip = NULL; - - 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); - s32 ret; - - if (chip->poll_time > 0) - cancel_delayed_work_sync(&chip->work); - - /* write to manufacturer access with sleep command */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_SLEEP); - if (chip->is_present && ret < 0) - return ret; - - 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 struct i2c_driver sbs_battery_driver = { - .probe = sbs_probe, - .remove = sbs_remove, - .id_table = sbs_id, - .driver = { - .name = "sbs-battery", - .of_match_table = of_match_ptr(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/smb347-charger.c b/drivers/power/smb347-charger.c deleted file mode 100644 index 072c518..0000000 --- a/drivers/power/smb347-charger.c +++ /dev/null @@ -1,1334 +0,0 @@ -/* - * Summit Microelectronics SMB347 Battery Charger Driver - * - * Copyright (C) 2011, Intel Corporation - * - * Authors: Bruce E. Robertson - * Mika Westerberg - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -/* - * 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 "); -MODULE_AUTHOR("Mika Westerberg "); -MODULE_DESCRIPTION("SMB347 battery charger driver"); -MODULE_LICENSE("GPL"); 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 + * Haojian Zhuang + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + * Haojian Zhuang + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include + +/* + * 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..bf2e5dd --- /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 + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = + create_singlethread_workqueue("ab8500_btemp_wq"); + 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..30de5d4 --- /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 + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, ®_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, ®_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, + ®_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, ®_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, ®_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, ®_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, ®_value); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT2_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 = + create_singlethread_workqueue("ab8500_charger_wq"); + 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..5a36cf8 --- /dev/null +++ b/drivers/power/supply/ab8500_fg.c @@ -0,0 +1,3272 @@ +/* + * 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 + * Karl Komierowski + * Arun R Murthy + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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) +{ + struct ab8500_fg *fg; + + if (list_empty(&ab8500_fg_list)) + return NULL; + + fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); + return fg; +} + +/* 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, ®_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, + ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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 <> */ + +#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 = create_singlethread_workqueue("ab8500_fg_wq"); + 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..d9104b1 --- /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 + * Karl Komierowski + * Arun R Murthy + * Author: Imre Sunyi + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, ¶m); + 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, ¶m); + 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 <> */ + +#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 = + create_singlethread_workqueue("abx500_chargalg_wq"); + 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..b5c00e4 --- /dev/null +++ b/drivers/power/supply/act8945a_charger.c @@ -0,0 +1,359 @@ +/* + * Power supply driver for the Active-semi ACT8945A PMIC + * + * Copyright (C) 2015 Atmel Corporation + * + * Author: Wenyou Yang + * + * 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 +#include +#include +#include +#include +#include + +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 regmap *regmap; + bool battery_temperature; +}; + +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; + + if (state == APCH_STATE_CSTATE_EOC) { + if (status & APCH_STATUS_CHGDAT) + *val = POWER_SUPPLY_STATUS_FULL; + else + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + } else if ((state == APCH_STATE_CSTATE_FAST) || + (state == APCH_STATE_CSTATE_PRE)) { + *val = POWER_SUPPLY_STATUS_CHARGING; + } else { + *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + + return 0; +} + +static int act8945a_get_charge_type(struct regmap *regmap, int *val) +{ + int ret; + unsigned int state; + + 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: + case APCH_STATE_CSTATE_DISABLED: + default: + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + return 0; +} + +static int act8945a_get_battery_health(struct act8945a_charger *charger, + struct regmap *regmap, int *val) +{ + int ret; + unsigned int status; + + ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); + if (ret < 0) + return ret; + + if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT)) + *val = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (!(status & APCH_STATUS_INDAT)) + *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + else if (status & APCH_STATUS_TIMRDAT) + *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + else + *val = POWER_SUPPLY_HEALTH_GOOD; + + 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_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(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 const struct power_supply_desc act8945a_charger_desc = { + .name = "act8945a-charger", + .type = POWER_SUPPLY_TYPE_BATTERY, + .get_property = act8945a_charger_get_property, + .properties = act8945a_charger_props, + .num_properties = ARRAY_SIZE(act8945a_charger_props), +}; + +#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; + enum of_gpio_flags flags; + struct regmap *regmap = charger->regmap; + + u32 total_time_out; + u32 pre_time_out; + u32 input_voltage_threshold; + int chglev_pin; + + unsigned int value = 0; + + if (!np) { + dev_err(dev, "no charger of node\n"); + return -EINVAL; + } + + charger->battery_temperature = of_property_read_bool(np, + "active-semi,check-battery-temperature"); + + chglev_pin = of_get_named_gpio_flags(np, + "active-semi,chglev-gpios", 0, &flags); + + if (gpio_is_valid(chglev_pin)) { + gpio_set_value(chglev_pin, + ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); + } + + 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 *psy; + struct power_supply_config psy_cfg = {}; + int 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.parent, charger); + if (ret) + return ret; + + psy_cfg.of_node = pdev->dev.parent->of_node; + psy_cfg.drv_data = charger; + + psy = devm_power_supply_register(&pdev->dev, + &act8945a_charger_desc, + &psy_cfg); + if (IS_ERR(psy)) { + dev_err(&pdev->dev, "failed to register power supply\n"); + return PTR_ERR(psy); + } + + return 0; +} + +static struct platform_driver act8945a_charger_driver = { + .driver = { + .name = "act8945a-charger", + }, + .probe = act8945a_charger_probe, +}; +module_platform_driver(act8945a_charger_driver); + +MODULE_DESCRIPTION("Active-semi ACT8945A ActivePath charger driver"); +MODULE_AUTHOR("Wenyou Yang "); +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 + * Copyright © 2007 Eugeny Boger + * + * Author: Eugeny Boger + * + * 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 +#include +#include +#include + + +#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 "); +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 + * Copyright (C) 2014 Bruno Prémont + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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..4030eeb --- /dev/null +++ b/drivers/power/supply/axp288_charger.c @@ -0,0 +1,970 @@ +/* + * axp288_charger.c - X-power AXP288 PMIC Charger driver + * + * Copyright (C) 2014 Intel Corporation + * Author: Ramakrishna Pallala + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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..50c0110 --- /dev/null +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -0,0 +1,1155 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +MODULE_AUTHOR("Todd Brandt "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/* 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 = ® + 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", ®, &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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include + + +#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 VBATintval = 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 "); +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..1fea2c7 --- /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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 = 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 = bq24257_power_supply_init(bq); + if (ret < 0) { + dev_err(dev, "Failed to register power supply\n"); + 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 "); +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..fa454c1 --- /dev/null +++ b/drivers/power/supply/bq24735-charger.c @@ -0,0 +1,500 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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; + 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) +{ + struct bq24735_platform *pdata = charger->pdata; + int ret; + + if (pdata->status_gpio_valid) { + ret = gpio_get_value_cansleep(pdata->status_gpio); + return ret ^= pdata->status_gpio_active_low == 0; + } 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 = 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; + enum of_gpio_flags flags; + + pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&client->dev, + "Memory alloc for bq24735 pdata failed\n"); + return NULL; + } + + pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", + 0, &flags); + + if (flags & OF_GPIO_ACTIVE_LOW) + pdata->status_gpio_active_low = 1; + + 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); + + if (gpio_is_valid(charger->pdata->status_gpio)) { + ret = devm_gpio_request(&client->dev, + charger->pdata->status_gpio, + name); + if (ret) { + dev_err(&client->dev, + "Failed GPIO request for GPIO %d: %d\n", + charger->pdata->status_gpio, ret); + } + + charger->pdata->status_gpio_valid = !ret; + } + + if (!charger->pdata->status_gpio_valid + || 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 "); +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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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 "); +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..323d05a --- /dev/null +++ b/drivers/power/supply/bq27xxx_battery.c @@ -0,0 +1,1102 @@ +/* + * BQ27xxx battery driver + * + * Copyright (C) 2008 Rodolfo Giometti + * Copyright (C) 2008 Eurotech S.p.A. + * Copyright (C) 2010-2011 Lars-Peter Clausen + * Copyright (C) 2011 Pali Rohár + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 unsigned int poll_interval = 360; +module_param(poll_interval, uint, 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; + + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + 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, false); + 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); + + 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_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 "); +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 + * + * 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 +#include +#include +#include + +#include + +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 = ® + 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/* 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 "); +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..369ab00 --- /dev/null +++ b/drivers/power/supply/ds2760_battery.c @@ -0,0 +1,647 @@ +/* + * 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 + * February 2007 + * + * Matt Reimer + * April 2004, 2005, 2007 + * + * Szabolcs Gyurko + * September 2004 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = create_singlethread_workqueue(dev_name(&pdev->dev)); + 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 , " + "Matt Reimer , " + "Anton Vorontsov "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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, ¤t_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, ¤t_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, ¤t_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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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, ¤t_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, ¤t_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, ¤t_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 "); +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 + * + * UEvent sending added by Evgeny Romanov + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ¤t_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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +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 "); +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..9fa4acc --- /dev/null +++ b/drivers/power/supply/intel_mid_battery.c @@ -0,0 +1,796 @@ +/* + * 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 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 = + create_singlethread_workqueue(dev_name(dev)); + 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 "); +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..35b01c7 --- /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 + * Author : Linus Walleij + * + */ + +#include +#include +#include +#include +#include +#include + +#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 = create_singlethread_workqueue("ipaq-battery-wq"); + 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, + µ_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, + µ_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 = µ_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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +/* 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 + * Copyright (C) 2010, Lars-Peter Clausen + * + * based on tosa_battery.c + * + * Copyright (C) 2008 Marek Vasut +* + * 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 +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + +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 "); +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 +#include +#include +#include +#include +#include +#include + +#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 *)¶m->eoc_level); + of_property_read_u8(np, "charging-current", (u8 *)¶m->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 , Daniel Jeong "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#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 = ®_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 (<c294x_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..a36bcaf --- /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 + * + * 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 +#include +#include +#include +#include + +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, ®_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, ®_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, ®_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, ®_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, ®_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, ®_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, + ®_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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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..9c65f13 --- /dev/null +++ b/drivers/power/supply/max17042_battery.c @@ -0,0 +1,1016 @@ +/* + * 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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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, u32 *data, int size) +{ + struct regmap *map = chip->regmap; + int i; + + for (i = 0; i < size; i++) + regmap_read(map, addr + i, &data[i]); +} + +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); + u32 *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, + (u16 *)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); + u32 *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 "); +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..060cab5 --- /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 + * + * 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 +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/* 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +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, ®); + 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, ®); + 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, ®); + 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include + +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, ®); + 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, ®); + 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 "); +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 + * 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 +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +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..fb62ed3 --- /dev/null +++ b/drivers/power/supply/pm2301_charger.c @@ -0,0 +1,1257 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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, ®_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 = create_singlethread_workqueue("pm2xxx_charger_wq"); + 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 + * + * 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 +#include +#include +#include +#include +#include +#include + +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 "); +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 + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * 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 + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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 + +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 , " + "Szabolcs Gyurko, " + "Anton Vorontsov "); +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 + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include + +#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 + * Copyright © 2007 Anton Vorontsov + * Copyright © 2004 Szabolcs Gyurko + * Copyright © 2003 Ian Molton + * + * Modified: 2004, Oct Szabolcs Gyurko + * + * You may use this code as per GPL version 2 + */ + +#include +#include +#include +#include +#include + +#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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * 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 +#include +#include +#include +#include + +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 "); +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +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 "); +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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +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..768b9fc --- /dev/null +++ b/drivers/power/supply/sbs-battery.c @@ -0,0 +1,998 @@ +/* + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +enum { + REG_MANUFACTURER_DATA, + REG_TEMPERATURE, + REG_VOLTAGE, + REG_CURRENT, + REG_CAPACITY, + REG_TIME_TO_EMPTY, + REG_TIME_TO_FULL, + REG_STATUS, + 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_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_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_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; + struct sbs_platform_data *pdata; + bool is_present; + bool gpio_detect; + bool enable_detection; + int irq; + int last_state; + int poll_time; + 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; + + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); + + 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]; + + if (chip->pdata) { + retries_length = max(chip->pdata->i2c_retry_count + 1, 1); + retries_block = max(chip->pdata->i2c_retry_count + 1, 1); + } + + /* 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; + + if (chip->pdata) + retries = max(chip->pdata->i2c_retry_count + 1, 1); + + 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 = gpio_get_value(chip->pdata->battery_detect); + if (ret == chip->pdata->battery_detect_present) + val->intval = 1; + else + val->intval = 0; + chip->is_present = val->intval; + return ret; + } + + /* Write to ManufacturerAccess with + * ManufacturerAccess command and then + * read the status */ + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_STATUS); + if (ret < 0) { + if (psp == POWER_SUPPLY_PROP_PRESENT) + val->intval = 0; /* battery removed */ + return ret; + } + + ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); + if (ret < 0) + 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_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_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 power_supply *battery = devid; + + 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->pdata->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; + } +} + +#if defined(CONFIG_OF) + +#include +#include + +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 sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + struct device_node *of_node = client->dev.of_node; + struct sbs_platform_data *pdata = client->dev.platform_data; + enum of_gpio_flags gpio_flags; + int rc; + u32 prop; + + /* verify this driver matches this device */ + if (!of_node) + return NULL; + + /* if platform data is set, honor it */ + if (pdata) + return pdata; + + /* first make sure at least one property is set, otherwise + * it won't change behavior from running without pdata. + */ + if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && + !of_get_property(of_node, "sbs,poll-retry-count", NULL) && + !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) + goto of_out; + + pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), + GFP_KERNEL); + if (!pdata) + goto of_out; + + rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); + if (!rc) + pdata->i2c_retry_count = prop; + + rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); + if (!rc) + pdata->poll_retry_count = prop; + + if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { + pdata->battery_detect = -1; + goto of_out; + } + + pdata->battery_detect = of_get_named_gpio_flags(of_node, + "sbs,battery-detect-gpios", 0, &gpio_flags); + + if (gpio_flags & OF_GPIO_ACTIVE_LOW) + pdata->battery_detect_present = 0; + else + pdata->battery_detect_present = 1; + +of_out: + return pdata; +} +#else +static struct sbs_platform_data *sbs_of_populate_pdata( + struct i2c_client *client) +{ + return client->dev.platform_data; +} +#endif + +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 = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->client = client; + chip->enable_detection = false; + chip->gpio_detect = 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; + + pdata = sbs_of_populate_pdata(client); + + if (pdata) { + chip->gpio_detect = gpio_is_valid(pdata->battery_detect); + chip->pdata = pdata; + } + + i2c_set_clientdata(client, chip); + + if (!chip->gpio_detect) + goto skip_gpio; + + rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); + if (rc) { + dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); + chip->gpio_detect = false; + goto skip_gpio; + } + + rc = gpio_direction_input(pdata->battery_detect); + if (rc) { + dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + irq = gpio_to_irq(pdata->battery_detect); + if (irq <= 0) { + dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + rc = request_irq(irq, sbs_irq, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + dev_name(&client->dev), chip->power_supply); + if (rc) { + dev_warn(&client->dev, "Failed to request irq: %d\n", rc); + gpio_free(pdata->battery_detect); + chip->gpio_detect = false; + goto skip_gpio; + } + + chip->irq = irq; + +skip_gpio: + /* + * Before we register, we might need to make sure we can actually talk + * to the battery. + */ + if (!force_load) { + 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 = 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: + if (chip->irq) + free_irq(chip->irq, chip->power_supply); + if (chip->gpio_detect) + gpio_free(pdata->battery_detect); + + kfree(chip); + + return rc; +} + +static int sbs_remove(struct i2c_client *client) +{ + struct sbs_info *chip = i2c_get_clientdata(client); + + if (chip->irq) + free_irq(chip->irq, chip->power_supply); + if (chip->gpio_detect) + gpio_free(chip->pdata->battery_detect); + + power_supply_unregister(chip->power_supply); + + cancel_delayed_work_sync(&chip->work); + + kfree(chip); + chip = NULL; + + 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); + s32 ret; + + if (chip->poll_time > 0) + cancel_delayed_work_sync(&chip->work); + + /* write to manufacturer access with sleep command */ + ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + MANUFACTURER_ACCESS_SLEEP); + if (chip->is_present && ret < 0) + return ret; + + 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 struct i2c_driver sbs_battery_driver = { + .probe = sbs_probe, + .remove = sbs_remove, + .id_table = sbs_id, + .driver = { + .name = "sbs-battery", + .of_match_table = of_match_ptr(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 + * Mika Westerberg + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_AUTHOR("Mika Westerberg "); +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 + * + * Dynamic module parameter code from the Virtual Battery Driver + * Copyright (C) 2008 Pylone, Inc. + * By: Masashi YOKOTA + * 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 +#include +#include +#include +#include +#include + +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 "); + +module_param(usb_online, usb_online, 0644); +MODULE_PARM_DESC(usb_online, "USB charging state "); + +module_param(battery_status, battery_status, 0644); +MODULE_PARM_DESC(battery_status, + "battery status "); + +module_param(battery_present, battery_present, 0644); +MODULE_PARM_DESC(battery_present, + "battery presence state "); + +module_param(battery_technology, battery_technology, 0644); +MODULE_PARM_DESC(battery_technology, + "battery technology "); + +module_param(battery_health, battery_health, 0644); +MODULE_PARM_DESC(battery_health, + "battery health state "); + +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 "); +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 +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +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 . + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#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 "); +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..73dfae4 --- /dev/null +++ b/drivers/power/supply/tps65217_charger.c @@ -0,0 +1,268 @@ +/* + * 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 . + */ + +/* + * Battery charger driver for TI's tps65217 + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; +}; + +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 ret; + + dev_dbg(&pdev->dev, "%s\n", __func__); + + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + 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); + } + + ret = tps65217_config_charger(charger); + if (ret < 0) { + dev_err(charger->dev, "charger config failed, err %d\n", ret); + return ret; + } + + 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); + + 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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 + * + * Based on dumb driver for gta01 battery + * Copyright 2009 Openmoko, Inc + * Balaji Rao + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +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 +#include +#include +#include +#include + +#include +#include +#include +#include + +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 "); +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 +#include +#include +#include +#include + +#include +#include +#include +#include + +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, ®2, + "trickle charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels), + pdata->vsel, ®2, + "target voltage", "mV"); + + wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims), + pdata->fast_ilim, ®2, + "fast charge current limit", "mA"); + + wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms), + pdata->eoc_iterm, ®1, + "end of charge current threshold", "mA"); + + wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times), + pdata->timeout, ®2, + "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 "); +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 + * + * 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 +#include +#include +#include +#include +#include +#include + +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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 "); +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..b201e3f --- /dev/null +++ b/drivers/power/supply/z2_battery.c @@ -0,0 +1,331 @@ +/* + * Battery measurement code for Zipit Z2 + * + * Copyright (C) 2009 Peter Edwards + * + * 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 +#include +#include +#include +#include +#include +#include +#include + +#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", + .owner = THIS_MODULE, + .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 "); +MODULE_DESCRIPTION("Zipit Z2 battery driver"); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c deleted file mode 100644 index 57246cd..0000000 --- a/drivers/power/test_power.c +++ /dev/null @@ -1,533 +0,0 @@ -/* - * Power supply driver for testing. - * - * Copyright 2010 Anton Vorontsov - * - * Dynamic module parameter code from the Virtual Battery Driver - * Copyright (C) 2008 Pylone, Inc. - * By: Masashi YOKOTA - * 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 -#include -#include -#include -#include -#include - -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 "); - -module_param(usb_online, usb_online, 0644); -MODULE_PARM_DESC(usb_online, "USB charging state "); - -module_param(battery_status, battery_status, 0644); -MODULE_PARM_DESC(battery_status, - "battery status "); - -module_param(battery_present, battery_present, 0644); -MODULE_PARM_DESC(battery_present, - "battery presence state "); - -module_param(battery_technology, battery_technology, 0644); -MODULE_PARM_DESC(battery_technology, - "battery technology "); - -module_param(battery_health, battery_health, 0644); -MODULE_PARM_DESC(battery_health, - "battery health state "); - -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 "); -MODULE_LICENSE("GPL"); diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c deleted file mode 100644 index 6e88c1b..0000000 --- a/drivers/power/tosa_battery.c +++ /dev/null @@ -1,470 +0,0 @@ -/* - * 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 -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -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/tps65090-charger.c b/drivers/power/tps65090-charger.c deleted file mode 100644 index 1b4b5e0..0000000 --- a/drivers/power/tps65090-charger.c +++ /dev/null @@ -1,370 +0,0 @@ -/* - * 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 . - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#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 "); -MODULE_DESCRIPTION("tps65090 battery charger driver"); diff --git a/drivers/power/tps65217_charger.c b/drivers/power/tps65217_charger.c deleted file mode 100644 index 73dfae4..0000000 --- a/drivers/power/tps65217_charger.c +++ /dev/null @@ -1,268 +0,0 @@ -/* - * 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 . - */ - -/* - * Battery charger driver for TI's tps65217 - */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#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; -}; - -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 ret; - - dev_dbg(&pdev->dev, "%s\n", __func__); - - charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); - if (!charger) - return -ENOMEM; - - 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); - } - - ret = tps65217_config_charger(charger); - if (ret < 0) { - dev_err(charger->dev, "charger config failed, err %d\n", ret); - return ret; - } - - 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); - - 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 "); -MODULE_DESCRIPTION("TPS65217 battery charger driver"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c deleted file mode 100644 index bcd4dc3..0000000 --- a/drivers/power/twl4030_charger.c +++ /dev/null @@ -1,1162 +0,0 @@ -/* - * TWL4030/TPS65950 BCI (Battery Charger Interface) driver - * - * Copyright (C) 2010 Gražvydas Ignotas - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#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/twl4030_madc_battery.c b/drivers/power/twl4030_madc_battery.c deleted file mode 100644 index f5817e4..0000000 --- a/drivers/power/twl4030_madc_battery.c +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Dumb driver for LiIon batteries using TWL4030 madc. - * - * Copyright 2013 Golden Delicious Computers - * Lukas Märdian - * - * Based on dumb driver for gta01 battery - * Copyright 2009 Openmoko, Inc - * Balaji Rao - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_DESCRIPTION("twl4030_madc battery driver"); -MODULE_ALIAS("platform:twl4030_madc_battery"); diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c deleted file mode 100644 index 2e33109..0000000 --- a/drivers/power/wm831x_backup.c +++ /dev/null @@ -1,225 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include -#include -#include -#include - -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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:wm831x-backup"); diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c deleted file mode 100644 index 7082301..0000000 --- a/drivers/power/wm831x_power.c +++ /dev/null @@ -1,673 +0,0 @@ -/* - * 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 -#include -#include -#include -#include - -#include -#include -#include -#include - -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, ®2, - "trickle charge current limit", "mA"); - - wm831x_battey_apply_config(wm831x, vsels, ARRAY_SIZE(vsels), - pdata->vsel, ®2, - "target voltage", "mV"); - - wm831x_battey_apply_config(wm831x, fast_ilims, ARRAY_SIZE(fast_ilims), - pdata->fast_ilim, ®2, - "fast charge current limit", "mA"); - - wm831x_battey_apply_config(wm831x, eoc_iterms, ARRAY_SIZE(eoc_iterms), - pdata->eoc_iterm, ®1, - "end of charge current threshold", "mA"); - - wm831x_battey_apply_config(wm831x, chg_times, ARRAY_SIZE(chg_times), - pdata->timeout, ®2, - "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 "); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:wm831x-power"); diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c deleted file mode 100644 index 5c58806..0000000 --- a/drivers/power/wm8350_power.c +++ /dev/null @@ -1,540 +0,0 @@ -/* - * Battery driver for wm8350 PMIC - * - * Copyright 2007, 2008 Wolfson Microelectronics PLC. - * - * Based on OLPC Battery Driver - * - * Copyright 2006 David Woodhouse - * - * 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 -#include -#include -#include -#include -#include -#include - -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/wm97xx_battery.c b/drivers/power/wm97xx_battery.c deleted file mode 100644 index c2f09ed..0000000 --- a/drivers/power/wm97xx_battery.c +++ /dev/null @@ -1,299 +0,0 @@ -/* - * linux/drivers/power/wm97xx_battery.c - * - * Battery measurement code for WM97xx - * - * based on tosa_battery.c - * - * Copyright (C) 2008 Marek Vasut - * - * 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 -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 "); -MODULE_DESCRIPTION("WM97xx battery driver"); diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c deleted file mode 100644 index b201e3f..0000000 --- a/drivers/power/z2_battery.c +++ /dev/null @@ -1,331 +0,0 @@ -/* - * Battery measurement code for Zipit Z2 - * - * Copyright (C) 2009 Peter Edwards - * - * 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 -#include -#include -#include -#include -#include -#include -#include - -#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", - .owner = THIS_MODULE, - .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 "); -MODULE_DESCRIPTION("Zipit Z2 battery driver"); -- cgit v0.10.2 From f3332532463fef7d56507c08b33993db398d7a91 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Fri, 17 Jun 2016 14:12:47 +0200 Subject: power/supply: unify DT documentation Currently the power supply device tree documentation is spread in .../bindings/power_supply and .../bindings/power. This unifies the files for chargers and battery fuel gauges in .../bindings/power/supply and the ones for system reset/shutdown in .../bindings/power/reset (same structure as used for the drivers itself). Signed-off-by: Sebastian Reichel diff --git a/Documentation/devicetree/bindings/power/act8945a-charger.txt b/Documentation/devicetree/bindings/power/act8945a-charger.txt deleted file mode 100644 index bea254c..0000000 --- a/Documentation/devicetree/bindings/power/act8945a-charger.txt +++ /dev/null @@ -1,35 +0,0 @@ -Device-Tree bindings for charger of Active-semi ACT8945A Multi-Function Device - -Required properties: - - compatible: "active-semi,act8945a", please refer to ../mfd/act8945a.txt. - - active-semi,chglev-gpios: charge current level phandle with args - as described in ../gpio/gpio.txt. - -Optional properties: - - active-semi,check-battery-temperature: boolean to check the battery - temperature or not. - - active-semi,input-voltage-threshold-microvolt: unit: mV; - Specifies the charger's input over-voltage threshold value; - The value can be: 6600, 7000, 7500, 8000; default: 6600 - - active-semi,precondition-timeout: unit: minutes; - Specifies the charger's PRECONDITION safety timer setting value; - The value can be: 40, 60, 80, 0; If 0, it means to disable this timer; - default: 40. - - active-semi,total-timeout: unit: hours; - Specifies the charger's total safety timer setting value; - The value can be: 3, 4, 5, 0; If 0, it means to disable this timer; - default: 3. - -Example: - pmic@5b { - compatible = "active-semi,act8945a"; - reg = <0x5b>; - status = "okay"; - - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_charger_chglev>; - active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; - active-semi,input-voltage-threshold-microvolt = <6600>; - active-semi,precondition-timeout = <40>; - active-semi,total-timeout = <3>; - }; diff --git a/Documentation/devicetree/bindings/power/bq2415x.txt b/Documentation/devicetree/bindings/power/bq2415x.txt deleted file mode 100644 index d0327f0..0000000 --- a/Documentation/devicetree/bindings/power/bq2415x.txt +++ /dev/null @@ -1,47 +0,0 @@ -Binding for TI bq2415x Li-Ion Charger - -Required properties: -- compatible: Should contain one of the following: - * "ti,bq24150" - * "ti,bq24150" - * "ti,bq24150a" - * "ti,bq24151" - * "ti,bq24151a" - * "ti,bq24152" - * "ti,bq24153" - * "ti,bq24153a" - * "ti,bq24155" - * "ti,bq24156" - * "ti,bq24156a" - * "ti,bq24158" -- reg: integer, i2c address of the device. -- ti,current-limit: integer, initial maximum current charger can pull - from power supply in mA. -- ti,weak-battery-voltage: integer, weak battery voltage threshold in mV. - The chip will use slow precharge if battery voltage - is below this value. -- ti,battery-regulation-voltage: integer, maximum charging voltage in mV. -- ti,charge-current: integer, maximum charging current in mA. -- ti,termination-current: integer, charge will be terminated when current in - constant-voltage phase drops below this value (in mA). -- ti,resistor-sense: integer, value of sensing resistor in milliohm. - -Optional properties: -- ti,usb-charger-detection: phandle to usb charger detection device. - (required for auto mode) - -Example from Nokia N900: - -bq24150a { - compatible = "ti,bq24150a"; - reg = <0x6b>; - - ti,current-limit = <100>; - ti,weak-battery-voltage = <3400>; - ti,battery-regulation-voltage = <4200>; - ti,charge-current = <650>; - ti,termination-current = <100>; - ti,resistor-sense = <68>; - - ti,usb-charger-detection = <&isp1704>; -}; diff --git a/Documentation/devicetree/bindings/power/bq24257.txt b/Documentation/devicetree/bindings/power/bq24257.txt deleted file mode 100644 index d693702..0000000 --- a/Documentation/devicetree/bindings/power/bq24257.txt +++ /dev/null @@ -1,64 +0,0 @@ -Binding for TI bq24250/bq24251/bq24257 Li-Ion Charger - -Required properties: -- compatible: Should contain one of the following: - * "ti,bq24250" - * "ti,bq24251" - * "ti,bq24257" -- reg: integer, i2c address of the device. -- interrupt-parent: Should be the phandle for the interrupt controller. Use in - conjunction with "interrupts". -- interrupts: Interrupt mapping for GPIO IRQ (configure for both edges). Use in - conjunction with "interrupt-parent". -- ti,battery-regulation-voltage: integer, maximum charging voltage in uV. -- ti,charge-current: integer, maximum charging current in uA. -- ti,termination-current: integer, charge will be terminated when current in - constant-voltage phase drops below this value (in uA). - -Optional properties: -- pg-gpios: GPIO used for connecting the bq2425x device PG (Power Good) pin. - This pin is not available on all devices however it should be used if - possible as this is the recommended way to obtain the charger's input PG - state. If this pin is not specified a software-based approach for PG - detection is used. -- ti,current-limit: The maximum current to be drawn from the charger's input - (in uA). If this property is not specified, the input limit current is - set automatically using USB D+/D- signal based charger type detection. - If the hardware does not support the D+/D- based detection, a default - of 500,000 is used (=500mA) instead. -- ti,ovp-voltage: Configures the over voltage protection voltage (in uV). If - not specified a default of 6,5000,000 (=6.5V) is used. -- ti,in-dpm-voltage: Configures the threshold input voltage for the dynamic - power path management (in uV). If not specified a default of 4,360,000 - (=4.36V) is used. - -Example: - -bq24257 { - compatible = "ti,bq24257"; - reg = <0x6a>; - interrupt-parent = <&gpio1>; - interrupts = <16 IRQ_TYPE_EDGE_BOTH>; - - pg-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>; - - ti,battery-regulation-voltage = <4200000>; - ti,charge-current = <1000000>; - ti,termination-current = <50000>; -}; - -Example: - -bq24250 { - compatible = "ti,bq24250"; - reg = <0x6a>; - interrupt-parent = <&gpio1>; - interrupts = <16 IRQ_TYPE_EDGE_BOTH>; - - ti,battery-regulation-voltage = <4200000>; - ti,charge-current = <500000>; - ti,termination-current = <50000>; - ti,current-limit = <900000>; - ti,ovp-voltage = <9500000>; - ti,in-dpm-voltage = <4440000>; -}; diff --git a/Documentation/devicetree/bindings/power/bq25890.txt b/Documentation/devicetree/bindings/power/bq25890.txt deleted file mode 100644 index c9dd17d..0000000 --- a/Documentation/devicetree/bindings/power/bq25890.txt +++ /dev/null @@ -1,46 +0,0 @@ -Binding for TI bq25890 Li-Ion Charger - -Required properties: -- compatible: Should contain one of the following: - * "ti,bq25890" -- reg: integer, i2c address of the device. -- ti,battery-regulation-voltage: integer, maximum charging voltage (in uV); -- ti,charge-current: integer, maximum charging current (in uA); -- ti,termination-current: integer, charge will be terminated when current in - constant-voltage phase drops below this value (in uA); -- ti,precharge-current: integer, maximum charge current during precharge - phase (in uA); -- ti,minimum-sys-voltage: integer, when battery is charging and it is below - minimum system voltage, the system will be regulated above - minimum-sys-voltage setting (in uV); -- ti,boost-voltage: integer, VBUS voltage level in boost mode (in uV); -- ti,boost-max-current: integer, maximum allowed current draw in boost mode - (in uA). - -Optional properties: -- ti,boost-low-freq: boolean, if present boost mode frequency will be 500kHz, - otherwise 1.5MHz; -- ti,use-ilim-pin: boolean, if present the ILIM resistor will be used and the - input current will be the lower between the resistor setting and the IINLIM - register setting; -- ti,thermal-regulation-threshold: integer, temperature above which the charge - current is lowered, to avoid overheating (in degrees Celsius). If omitted, - the default setting will be used (120 degrees); - -Example: - -bq25890 { - compatible = "ti,bq25890"; - reg = <0x6a>; - - ti,battery-regulation-voltage = <4200000>; - ti,charge-current = <1000000>; - ti,termination-current = <50000>; - ti,precharge-current = <128000>; - ti,minimum-sys-voltage = <3600000>; - ti,boost-voltage = <5000000>; - ti,boost-max-current = <1000000>; - - ti,use-ilim-pin; - ti,thermal-regulation-threshold = <120>; -}; diff --git a/Documentation/devicetree/bindings/power/da9150-charger.txt b/Documentation/devicetree/bindings/power/da9150-charger.txt deleted file mode 100644 index f390666..0000000 --- a/Documentation/devicetree/bindings/power/da9150-charger.txt +++ /dev/null @@ -1,26 +0,0 @@ -Dialog Semiconductor DA9150 Charger Power Supply bindings - -Required properties: -- compatible: "dlg,da9150-charger" for DA9150 Charger Power Supply - -Optional properties: -- io-channels: List of phandle and IIO specifier pairs -- io-channel-names: List of channel names used by charger - ["CHAN_IBUS", "CHAN_VBUS", "CHAN_TJUNC", "CHAN_VBAT"] - (See Documentation/devicetree/bindings/iio/iio-bindings.txt for further info) - - -Example: - - da9150-charger { - compatible = "dlg,da9150-charger"; - - io-channels = <&gpadc 0>, - <&gpadc 2>, - <&gpadc 8>, - <&gpadc 5>; - io-channel-names = "CHAN_IBUS", - "CHAN_VBUS", - "CHAN_TJUNC", - "CHAN_VBAT"; - }; diff --git a/Documentation/devicetree/bindings/power/da9150-fg.txt b/Documentation/devicetree/bindings/power/da9150-fg.txt deleted file mode 100644 index 00236fe..0000000 --- a/Documentation/devicetree/bindings/power/da9150-fg.txt +++ /dev/null @@ -1,23 +0,0 @@ -Dialog Semiconductor DA9150 Fuel-Gauge Power Supply bindings - -Required properties: -- compatible: "dlg,da9150-fuel-gauge" for DA9150 Fuel-Gauge Power Supply - -Optional properties: -- dlg,update-interval: Interval time (milliseconds) between battery level checks. -- dlg,warn-soc-level: Battery discharge level (%) where warning event raised. - [1 - 100] -- dlg,crit-soc-level: Battery discharge level (%) where critical event raised. - This value should be lower than the warning level. - [1 - 100] - - -Example: - - fuel-gauge { - compatible = "dlg,da9150-fuel-gauge"; - - dlg,update-interval = <10000>; - dlg,warn-soc-level = /bits/ 8 <15>; - dlg,crit-soc-level = /bits/ 8 <5>; - }; diff --git a/Documentation/devicetree/bindings/power/isp1704.txt b/Documentation/devicetree/bindings/power/isp1704.txt deleted file mode 100644 index fa35969..0000000 --- a/Documentation/devicetree/bindings/power/isp1704.txt +++ /dev/null @@ -1,17 +0,0 @@ -Binding for NXP ISP1704 USB Charger Detection - -Required properties: -- compatible: Should contain one of the following: - * "nxp,isp1704" -- nxp,enable-gpio: Should contain a phandle + gpio-specifier - to the GPIO pin connected to the chip's enable pin. -- usb-phy: Should contain a phandle to the USB PHY - the ISP1704 is connected to. - -Example: - -isp1704 { - compatible = "nxp,isp1704"; - nxp,enable-gpio = <&gpio3 3 GPIO_ACTIVE_LOW>; - usb-phy = <&usb2_phy>; -}; diff --git a/Documentation/devicetree/bindings/power/ltc2941.txt b/Documentation/devicetree/bindings/power/ltc2941.txt deleted file mode 100644 index ea42ae1..0000000 --- a/Documentation/devicetree/bindings/power/ltc2941.txt +++ /dev/null @@ -1,27 +0,0 @@ -binding for LTC2941 and LTC2943 battery gauges - -Both the LTC2941 and LTC2943 measure battery capacity. -The LTC2943 is compatible with the LTC2941, it adds voltage and -temperature monitoring, and uses a slightly different conversion -formula for the charge counter. - -Required properties: -- compatible: Should contain "ltc2941" or "ltc2943" which also indicates the - type of I2C chip attached. -- reg: The 7-bit I2C address. -- lltc,resistor-sense: The sense resistor value in milli-ohms. Can be a 32-bit - negative value when the battery has been connected to the wrong end of the - resistor. -- lltc,prescaler-exponent: The prescaler exponent as explained in the datasheet. - This determines the range and accuracy of the gauge. The value is programmed - into the chip only if it differs from the current setting. The setting is - lost when the battery is disconnected. - -Example from the Topic Miami Florida board: - - fuelgauge: ltc2943@64 { - compatible = "ltc2943"; - reg = <0x64>; - lltc,resistor-sense = <15>; - lltc,prescaler-exponent = <5>; /* 2^(2*5) = 1024 */ - }; diff --git a/Documentation/devicetree/bindings/power/qcom,coincell-charger.txt b/Documentation/devicetree/bindings/power/qcom,coincell-charger.txt deleted file mode 100644 index 7478992..0000000 --- a/Documentation/devicetree/bindings/power/qcom,coincell-charger.txt +++ /dev/null @@ -1,48 +0,0 @@ -Qualcomm Coincell Charger: - -The hardware block controls charging for a coincell or capacitor that is -used to provide power backup for certain features of the power management -IC (PMIC) - -- compatible: - Usage: required - Value type: - Definition: must be: "qcom,pm8941-coincell" - -- reg: - Usage: required - Value type: - Definition: base address of the coincell charger registers - -- qcom,rset-ohms: - Usage: required - Value type: - Definition: resistance (in ohms) for current-limiting resistor - must be one of: 800, 1200, 1700, 2100 - -- qcom,vset-millivolts: - Usage: required - Value type: - Definition: voltage (in millivolts) to apply for charging - must be one of: 2500, 3000, 3100, 3200 - -- qcom,charger-disable: - Usage: optional - Value type: - Definition: defining this property disables charging - -This charger is a sub-node of one of the 8941 PMIC blocks, and is specified -as a child node in DTS of that node. See ../mfd/qcom,spmi-pmic.txt and -../mfd/qcom-pm8xxx.txt - -Example: - - pm8941@0 { - coincell@2800 { - compatible = "qcom,pm8941-coincell"; - reg = <0x2800>; - - qcom,rset-ohms = <2100>; - qcom,vset-millivolts = <3000>; - }; - }; diff --git a/Documentation/devicetree/bindings/power/reset/axxia-reset.txt b/Documentation/devicetree/bindings/power/reset/axxia-reset.txt new file mode 100644 index 0000000..47e720d --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/axxia-reset.txt @@ -0,0 +1,20 @@ +Axxia Restart Driver + +This driver can do reset of the Axxia SoC. It uses the registers in the syscon +block to initiate a chip reset. + +Required Properties: + -compatible: "lsi,axm55xx-reset" + -syscon: phandle to the syscon node. + +Example: + + syscon: syscon@2010030000 { + compatible = "lsi,axxia-syscon", "syscon"; + reg = <0x20 0x10030000 0 0x2000>; + }; + + reset: reset@2010031000 { + compatible = "lsi,axm55xx-reset"; + syscon = <&syscon>; + }; diff --git a/Documentation/devicetree/bindings/power/reset/imx-snvs-poweroff.txt b/Documentation/devicetree/bindings/power/reset/imx-snvs-poweroff.txt new file mode 100644 index 0000000..dc7c9ba --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/imx-snvs-poweroff.txt @@ -0,0 +1,23 @@ +i.mx6 Poweroff Driver + +SNVS_LPCR in SNVS module can power off the whole system by pull +PMIC_ON_REQ low if PMIC_ON_REQ is connected with external PMIC. +If you don't want to use PMIC_ON_REQ as power on/off control, +please set status='disabled' to disable this driver. + +Required Properties: +-compatible: "fsl,sec-v4.0-poweroff" +-reg: Specifies the physical address of the SNVS_LPCR register + +Example: + snvs@020cc000 { + compatible = "fsl,sec-v4.0-mon", "simple-bus"; + #address-cells = <1>; + #size-cells = <1>; + ranges = <0 0x020cc000 0x4000>; + ..... + snvs_poweroff: snvs-poweroff@38 { + compatible = "fsl,sec-v4.0-poweroff"; + reg = <0x38 0x4>; + }; + } diff --git a/Documentation/devicetree/bindings/power/reset/msm-poweroff.txt b/Documentation/devicetree/bindings/power/reset/msm-poweroff.txt new file mode 100644 index 0000000..ce44ad3 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/msm-poweroff.txt @@ -0,0 +1,17 @@ +MSM Restart Driver + +A power supply hold (ps-hold) bit is set to power the msm chipsets. +Clearing that bit allows us to restart/poweroff. The difference +between poweroff and restart is determined by unique power manager IC +settings. + +Required Properties: +-compatible: "qcom,pshold" +-reg: Specifies the physical address of the ps-hold register + +Example: + + restart@fc4ab000 { + compatible = "qcom,pshold"; + reg = <0xfc4ab000 0x4>; + }; diff --git a/Documentation/devicetree/bindings/power/reset/qnap-poweroff.txt b/Documentation/devicetree/bindings/power/reset/qnap-poweroff.txt new file mode 100644 index 0000000..af25e77 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/qnap-poweroff.txt @@ -0,0 +1,16 @@ +* QNAP Power Off + +QNAP NAS devices have a microcontroller controlling the main power +supply. This microcontroller is connected to UART1 of the Kirkwood and +Orion5x SoCs. Sending the character 'A', at 19200 baud, tells the +microcontroller to turn the power off. This driver adds a handler to +pm_power_off which is called to turn the power off. + +Synology NAS devices use a similar scheme, but a different baud rate, +9600, and a different character, '1'. + +Required Properties: +- compatible: Should be "qnap,power-off" or "synology,power-off" + +- reg: Address and length of the register set for UART1 +- clocks: tclk clock diff --git a/Documentation/devicetree/bindings/power/reset/restart-poweroff.txt b/Documentation/devicetree/bindings/power/reset/restart-poweroff.txt new file mode 100644 index 0000000..5776e68 --- /dev/null +++ b/Documentation/devicetree/bindings/power/reset/restart-poweroff.txt @@ -0,0 +1,8 @@ +* Restart Power Off + +Buffalo Linkstation LS-XHL and LS-CHLv2, and other devices power off +by restarting and letting u-boot keep hold of the machine until the +user presses a button. + +Required Properties: +- compatible: Should be "restart-poweroff" diff --git a/Documentation/devicetree/bindings/power/rt9455_charger.txt b/Documentation/devicetree/bindings/power/rt9455_charger.txt deleted file mode 100644 index 5d9ad5c..0000000 --- a/Documentation/devicetree/bindings/power/rt9455_charger.txt +++ /dev/null @@ -1,48 +0,0 @@ -Binding for Richtek rt9455 battery charger - -Required properties: -- compatible: it should contain one of the following: - "richtek,rt9455". -- reg: integer, i2c address of the device. -- interrupt-parent: the phandle for the interrupt controller that - services interrupts for this device. -- interrupts: interrupt mapping for GPIO IRQ, it should be - configured with IRQ_TYPE_LEVEL_LOW flag. -- richtek,output-charge-current: integer, output current from the charger to the - battery, in uA. -- richtek,end-of-charge-percentage: integer, percent of the output charge current. - When the current in constant-voltage phase drops - below output_charge_current x end-of-charge-percentage, - charge is terminated. -- richtek,battery-regulation-voltage: integer, maximum battery voltage in uV. -- richtek,boost-output-voltage: integer, maximum voltage provided to consumer - devices, when the charger is in boost mode, in uV. - -Optional properties: -- richtek,min-input-voltage-regulation: integer, input voltage level in uV, used to - decrease voltage level when the over current - of the input power source occurs. - This prevents input voltage drop due to insufficient - current provided by the power source. - Default: 4500000 uV (4.5V) -- richtek,avg-input-current-regulation: integer, input current value in uA drained by the - charger from the power source. - Default: 500000 uA (500mA) - -Example: - -rt9455@22 { - compatible = "richtek,rt9455"; - reg = <0x22>; - - interrupt-parent = <&gpio1>; - interrupts = <0 IRQ_TYPE_LEVEL_LOW>; - - richtek,output-charge-current = <500000>; - richtek,end-of-charge-percentage = <10>; - richtek,battery-regulation-voltage = <4200000>; - richtek,boost-output-voltage = <5050000>; - - richtek,min-input-voltage-regulation = <4500000>; - richtek,avg-input-current-regulation = <500000>; -}; diff --git a/Documentation/devicetree/bindings/power/rx51-battery.txt b/Documentation/devicetree/bindings/power/rx51-battery.txt deleted file mode 100644 index 9043845..0000000 --- a/Documentation/devicetree/bindings/power/rx51-battery.txt +++ /dev/null @@ -1,25 +0,0 @@ -Binding for Nokia N900 battery - -The Nokia N900 battery status can be read via the TWL4030's A/D converter. - -Required properties: -- compatible: Should contain one of the following: - * "nokia,n900-battery" -- io-channels: Should contain IIO channel specifiers - for each element in io-channel-names. -- io-channel-names: Should contain the following values: - * "temp" - The ADC channel for temperature reading - * "bsi" - The ADC channel for battery size identification - * "vbat" - The ADC channel to measure the battery voltage - -Example from Nokia N900: - -battery: n900-battery { - compatible = "nokia,n900-battery"; - io-channels = <&twl4030_madc 0>, - <&twl4030_madc 4>, - <&twl4030_madc 12>; - io-channel-names = "temp", - "bsi", - "vbat"; -}; diff --git a/Documentation/devicetree/bindings/power/supply/ab8500/btemp.txt b/Documentation/devicetree/bindings/power/supply/ab8500/btemp.txt new file mode 100644 index 0000000..0ba1bcc --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ab8500/btemp.txt @@ -0,0 +1,16 @@ +=== AB8500 Battery Temperature Monitor Driver === + +The properties below describes the node for btemp driver. + +Required Properties: +- compatible = Shall be: "stericsson,ab8500-btemp" +- battery = Shall be battery specific information + + Example: + ab8500_btemp { + compatible = "stericsson,ab8500-btemp"; + battery = <&ab8500_battery>; + }; + +For information on battery specific node, Ref: +Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power/supply/ab8500/chargalg.txt b/Documentation/devicetree/bindings/power/supply/ab8500/chargalg.txt new file mode 100644 index 0000000..ef53283 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ab8500/chargalg.txt @@ -0,0 +1,16 @@ +=== AB8500 Charging Algorithm Driver === + +The properties below describes the node for chargalg driver. + +Required Properties: +- compatible = Shall be: "stericsson,ab8500-chargalg" +- battery = Shall be battery specific information + +Example: +ab8500_chargalg { + compatible = "stericsson,ab8500-chargalg"; + battery = <&ab8500_battery>; +}; + +For information on battery specific node, Ref: +Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power/supply/ab8500/charger.txt b/Documentation/devicetree/bindings/power/supply/ab8500/charger.txt new file mode 100644 index 0000000..6bdbb08 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ab8500/charger.txt @@ -0,0 +1,25 @@ +=== AB8500 Charger Driver === + +Required Properties: +- compatible = Shall be "stericsson,ab8500-charger" +- battery = Shall be battery specific information + Example: + ab8500_charger { + compatible = "stericsson,ab8500-charger"; + battery = <&ab8500_battery>; + }; + +- vddadc-supply: Supply for USB and Main charger + Example: + ab8500-charger { + vddadc-supply = <&ab8500_ldo_tvout_reg>; + } +- autopower_cfg: + Boolean value depicting the presence of 'automatic poweron after powerloss' + Example: + ab8500-charger { + autopower_cfg; + }; + +For information on battery specific node, Ref: +Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power/supply/ab8500/fg.txt b/Documentation/devicetree/bindings/power/supply/ab8500/fg.txt new file mode 100644 index 0000000..ccafcb9 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ab8500/fg.txt @@ -0,0 +1,58 @@ +=== AB8500 Fuel Gauge Driver === + +AB8500 is a mixed signal multimedia and power management +device comprising: power and energy-management-module, +wall-charger, usb-charger, audio codec, general purpose adc, +tvout, clock management and sim card interface. + +Fuelgauge support is part of energy-management-modules, other +components of this module are: +main-charger, usb-combo-charger and battery-temperature-monitoring. + +The properties below describes the node for fuelgauge driver. + +Required Properties: +- compatible = This shall be: "stericsson,ab8500-fg" +- battery = Shall be battery specific information + Example: + ab8500_fg { + compatible = "stericsson,ab8500-fg"; + battery = <&ab8500_battery>; + }; + +dependent node: + ab8500_battery: ab8500_battery { + }; + This node will provide information on 'thermistor interface' and + 'battery technology type' used. + +Properties of this node are: +thermistor-on-batctrl: + A boolean value indicating thermistor interface to battery + + Note: + 'btemp' and 'batctrl' are the pins interfaced for battery temperature + measurement, 'btemp' signal is used when NTC(negative temperature + coefficient) resister is interfaced external to battery whereas + 'batctrl' pin is used when NTC resister is internal to battery. + + Example: + ab8500_battery: ab8500_battery { + thermistor-on-batctrl; + }; + indicates: NTC resister is internal to battery, 'batctrl' is used + for thermal measurement. + + The absence of property 'thermal-on-batctrl' indicates + NTC resister is external to battery and 'btemp' signal is used + for thermal measurement. + +battery-type: + This shall be the battery manufacturing technology type, + allowed types are: + "UNKNOWN" "NiMH" "LION" "LIPO" "LiFe" "NiCd" "LiMn" + Example: + ab8500_battery: ab8500_battery { + stericsson,battery-type = "LIPO"; + } + diff --git a/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt b/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt new file mode 100644 index 0000000..bea254c --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt @@ -0,0 +1,35 @@ +Device-Tree bindings for charger of Active-semi ACT8945A Multi-Function Device + +Required properties: + - compatible: "active-semi,act8945a", please refer to ../mfd/act8945a.txt. + - active-semi,chglev-gpios: charge current level phandle with args + as described in ../gpio/gpio.txt. + +Optional properties: + - active-semi,check-battery-temperature: boolean to check the battery + temperature or not. + - active-semi,input-voltage-threshold-microvolt: unit: mV; + Specifies the charger's input over-voltage threshold value; + The value can be: 6600, 7000, 7500, 8000; default: 6600 + - active-semi,precondition-timeout: unit: minutes; + Specifies the charger's PRECONDITION safety timer setting value; + The value can be: 40, 60, 80, 0; If 0, it means to disable this timer; + default: 40. + - active-semi,total-timeout: unit: hours; + Specifies the charger's total safety timer setting value; + The value can be: 3, 4, 5, 0; If 0, it means to disable this timer; + default: 3. + +Example: + pmic@5b { + compatible = "active-semi,act8945a"; + reg = <0x5b>; + status = "okay"; + + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_charger_chglev>; + active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; + active-semi,input-voltage-threshold-microvolt = <6600>; + active-semi,precondition-timeout = <40>; + active-semi,total-timeout = <3>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/axp20x_usb_power.txt b/Documentation/devicetree/bindings/power/supply/axp20x_usb_power.txt new file mode 100644 index 0000000..f1d7bee --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/axp20x_usb_power.txt @@ -0,0 +1,35 @@ +AXP20x USB power supply + +Required Properties: +-compatible: One of: "x-powers,axp202-usb-power-supply" + "x-powers,axp221-usb-power-supply" + +This node is a subnode of the axp20x PMIC. + +Example: + +axp209: pmic@34 { + compatible = "x-powers,axp209"; + reg = <0x34>; + interrupt-parent = <&nmi_intc>; + interrupts = <0 IRQ_TYPE_LEVEL_LOW>; + interrupt-controller; + #interrupt-cells = <1>; + + regulators { + x-powers,dcdc-freq = <1500>; + + vdd_cpu: dcdc2 { + regulator-always-on; + regulator-min-microvolt = <1000000>; + regulator-max-microvolt = <1450000>; + regulator-name = "vdd-cpu"; + }; + + ... + }; + + usb-power-supply: usb-power-supply { + compatible = "x-powers,axp202-usb-power-supply"; + }; +}; diff --git a/Documentation/devicetree/bindings/power/supply/bq2415x.txt b/Documentation/devicetree/bindings/power/supply/bq2415x.txt new file mode 100644 index 0000000..d0327f0 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/bq2415x.txt @@ -0,0 +1,47 @@ +Binding for TI bq2415x Li-Ion Charger + +Required properties: +- compatible: Should contain one of the following: + * "ti,bq24150" + * "ti,bq24150" + * "ti,bq24150a" + * "ti,bq24151" + * "ti,bq24151a" + * "ti,bq24152" + * "ti,bq24153" + * "ti,bq24153a" + * "ti,bq24155" + * "ti,bq24156" + * "ti,bq24156a" + * "ti,bq24158" +- reg: integer, i2c address of the device. +- ti,current-limit: integer, initial maximum current charger can pull + from power supply in mA. +- ti,weak-battery-voltage: integer, weak battery voltage threshold in mV. + The chip will use slow precharge if battery voltage + is below this value. +- ti,battery-regulation-voltage: integer, maximum charging voltage in mV. +- ti,charge-current: integer, maximum charging current in mA. +- ti,termination-current: integer, charge will be terminated when current in + constant-voltage phase drops below this value (in mA). +- ti,resistor-sense: integer, value of sensing resistor in milliohm. + +Optional properties: +- ti,usb-charger-detection: phandle to usb charger detection device. + (required for auto mode) + +Example from Nokia N900: + +bq24150a { + compatible = "ti,bq24150a"; + reg = <0x6b>; + + ti,current-limit = <100>; + ti,weak-battery-voltage = <3400>; + ti,battery-regulation-voltage = <4200>; + ti,charge-current = <650>; + ti,termination-current = <100>; + ti,resistor-sense = <68>; + + ti,usb-charger-detection = <&isp1704>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/bq24257.txt b/Documentation/devicetree/bindings/power/supply/bq24257.txt new file mode 100644 index 0000000..d693702 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/bq24257.txt @@ -0,0 +1,64 @@ +Binding for TI bq24250/bq24251/bq24257 Li-Ion Charger + +Required properties: +- compatible: Should contain one of the following: + * "ti,bq24250" + * "ti,bq24251" + * "ti,bq24257" +- reg: integer, i2c address of the device. +- interrupt-parent: Should be the phandle for the interrupt controller. Use in + conjunction with "interrupts". +- interrupts: Interrupt mapping for GPIO IRQ (configure for both edges). Use in + conjunction with "interrupt-parent". +- ti,battery-regulation-voltage: integer, maximum charging voltage in uV. +- ti,charge-current: integer, maximum charging current in uA. +- ti,termination-current: integer, charge will be terminated when current in + constant-voltage phase drops below this value (in uA). + +Optional properties: +- pg-gpios: GPIO used for connecting the bq2425x device PG (Power Good) pin. + This pin is not available on all devices however it should be used if + possible as this is the recommended way to obtain the charger's input PG + state. If this pin is not specified a software-based approach for PG + detection is used. +- ti,current-limit: The maximum current to be drawn from the charger's input + (in uA). If this property is not specified, the input limit current is + set automatically using USB D+/D- signal based charger type detection. + If the hardware does not support the D+/D- based detection, a default + of 500,000 is used (=500mA) instead. +- ti,ovp-voltage: Configures the over voltage protection voltage (in uV). If + not specified a default of 6,5000,000 (=6.5V) is used. +- ti,in-dpm-voltage: Configures the threshold input voltage for the dynamic + power path management (in uV). If not specified a default of 4,360,000 + (=4.36V) is used. + +Example: + +bq24257 { + compatible = "ti,bq24257"; + reg = <0x6a>; + interrupt-parent = <&gpio1>; + interrupts = <16 IRQ_TYPE_EDGE_BOTH>; + + pg-gpios = <&gpio1 28 GPIO_ACTIVE_HIGH>; + + ti,battery-regulation-voltage = <4200000>; + ti,charge-current = <1000000>; + ti,termination-current = <50000>; +}; + +Example: + +bq24250 { + compatible = "ti,bq24250"; + reg = <0x6a>; + interrupt-parent = <&gpio1>; + interrupts = <16 IRQ_TYPE_EDGE_BOTH>; + + ti,battery-regulation-voltage = <4200000>; + ti,charge-current = <500000>; + ti,termination-current = <50000>; + ti,current-limit = <900000>; + ti,ovp-voltage = <9500000>; + ti,in-dpm-voltage = <4440000>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/bq25890.txt b/Documentation/devicetree/bindings/power/supply/bq25890.txt new file mode 100644 index 0000000..c9dd17d --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/bq25890.txt @@ -0,0 +1,46 @@ +Binding for TI bq25890 Li-Ion Charger + +Required properties: +- compatible: Should contain one of the following: + * "ti,bq25890" +- reg: integer, i2c address of the device. +- ti,battery-regulation-voltage: integer, maximum charging voltage (in uV); +- ti,charge-current: integer, maximum charging current (in uA); +- ti,termination-current: integer, charge will be terminated when current in + constant-voltage phase drops below this value (in uA); +- ti,precharge-current: integer, maximum charge current during precharge + phase (in uA); +- ti,minimum-sys-voltage: integer, when battery is charging and it is below + minimum system voltage, the system will be regulated above + minimum-sys-voltage setting (in uV); +- ti,boost-voltage: integer, VBUS voltage level in boost mode (in uV); +- ti,boost-max-current: integer, maximum allowed current draw in boost mode + (in uA). + +Optional properties: +- ti,boost-low-freq: boolean, if present boost mode frequency will be 500kHz, + otherwise 1.5MHz; +- ti,use-ilim-pin: boolean, if present the ILIM resistor will be used and the + input current will be the lower between the resistor setting and the IINLIM + register setting; +- ti,thermal-regulation-threshold: integer, temperature above which the charge + current is lowered, to avoid overheating (in degrees Celsius). If omitted, + the default setting will be used (120 degrees); + +Example: + +bq25890 { + compatible = "ti,bq25890"; + reg = <0x6a>; + + ti,battery-regulation-voltage = <4200000>; + ti,charge-current = <1000000>; + ti,termination-current = <50000>; + ti,precharge-current = <128000>; + ti,minimum-sys-voltage = <3600000>; + ti,boost-voltage = <5000000>; + ti,boost-max-current = <1000000>; + + ti,use-ilim-pin; + ti,thermal-regulation-threshold = <120>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/charger-manager.txt b/Documentation/devicetree/bindings/power/supply/charger-manager.txt new file mode 100644 index 0000000..ec4fe9d --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/charger-manager.txt @@ -0,0 +1,81 @@ +charger-manager bindings +~~~~~~~~~~~~~~~~~~~~~~~~ + +Required properties : + - compatible : "charger-manager" + - <>-supply : for regulator consumer + - cm-num-chargers : number of chargers + - cm-chargers : name of chargers + - cm-fuel-gauge : name of battery fuel gauge + - subnode : + - cm-regulator-name : name of charger regulator + - subnode : + - cm-cable-name : name of charger cable + - cm-cable-extcon : name of extcon dev +(optional) - cm-cable-min : minimum current of cable +(optional) - cm-cable-max : maximum current of cable + +Optional properties : + - cm-name : charger manager's name (default : "battery") + - cm-poll-mode : polling mode (enum polling_modes) + - cm-poll-interval : polling interval + - cm-battery-stat : battery status (enum data_source) + - cm-fullbatt-* : data for full battery checking + - cm-thermal-zone : name of external thermometer's thermal zone + - cm-battery-* : threshold battery temperature for charging + -cold : critical cold temperature of battery for charging + -cold-in-minus : flag that cold temperature is in minus degrees + -hot : critical hot temperature of battery for charging + -temp-diff : temperature difference to allow recharging + - cm-dis/charging-max = limits of charging duration + +Example : + charger-manager@0 { + compatible = "charger-manager"; + chg-reg-supply = <&charger_regulator>; + + cm-name = "battery"; + /* Always polling ON : 30s */ + cm-poll-mode = <1>; + cm-poll-interval = <30000>; + + cm-fullbatt-vchkdrop-ms = <30000>; + cm-fullbatt-vchkdrop-volt = <150000>; + cm-fullbatt-soc = <100>; + + cm-battery-stat = <3>; + + cm-num-chargers = <3>; + cm-chargers = "charger0", "charger1", "charger2"; + + cm-fuel-gauge = "fuelgauge0"; + + cm-thermal-zone = "thermal_zone.1" + /* in deci centigrade */ + cm-battery-cold = <50>; + cm-battery-cold-in-minus; + cm-battery-hot = <800>; + cm-battery-temp-diff = <100>; + + /* Allow charging for 5hr */ + cm-charging-max = <18000000>; + /* Allow discharging for 2hr */ + cm-discharging-max = <7200000>; + + regulator@0 { + cm-regulator-name = "chg-reg"; + cable@0 { + cm-cable-name = "USB"; + cm-cable-extcon = "extcon-dev.0"; + cm-cable-min = <475000>; + cm-cable-max = <500000>; + }; + cable@1 { + cm-cable-name = "TA"; + cm-cable-extcon = "extcon-dev.0"; + cm-cable-min = <650000>; + cm-cable-max = <675000>; + }; + }; + + }; diff --git a/Documentation/devicetree/bindings/power/supply/da9150-charger.txt b/Documentation/devicetree/bindings/power/supply/da9150-charger.txt new file mode 100644 index 0000000..f390666 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/da9150-charger.txt @@ -0,0 +1,26 @@ +Dialog Semiconductor DA9150 Charger Power Supply bindings + +Required properties: +- compatible: "dlg,da9150-charger" for DA9150 Charger Power Supply + +Optional properties: +- io-channels: List of phandle and IIO specifier pairs +- io-channel-names: List of channel names used by charger + ["CHAN_IBUS", "CHAN_VBUS", "CHAN_TJUNC", "CHAN_VBAT"] + (See Documentation/devicetree/bindings/iio/iio-bindings.txt for further info) + + +Example: + + da9150-charger { + compatible = "dlg,da9150-charger"; + + io-channels = <&gpadc 0>, + <&gpadc 2>, + <&gpadc 8>, + <&gpadc 5>; + io-channel-names = "CHAN_IBUS", + "CHAN_VBUS", + "CHAN_TJUNC", + "CHAN_VBAT"; + }; diff --git a/Documentation/devicetree/bindings/power/supply/da9150-fg.txt b/Documentation/devicetree/bindings/power/supply/da9150-fg.txt new file mode 100644 index 0000000..00236fe --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/da9150-fg.txt @@ -0,0 +1,23 @@ +Dialog Semiconductor DA9150 Fuel-Gauge Power Supply bindings + +Required properties: +- compatible: "dlg,da9150-fuel-gauge" for DA9150 Fuel-Gauge Power Supply + +Optional properties: +- dlg,update-interval: Interval time (milliseconds) between battery level checks. +- dlg,warn-soc-level: Battery discharge level (%) where warning event raised. + [1 - 100] +- dlg,crit-soc-level: Battery discharge level (%) where critical event raised. + This value should be lower than the warning level. + [1 - 100] + + +Example: + + fuel-gauge { + compatible = "dlg,da9150-fuel-gauge"; + + dlg,update-interval = <10000>; + dlg,warn-soc-level = /bits/ 8 <15>; + dlg,crit-soc-level = /bits/ 8 <5>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/gpio-charger.txt b/Documentation/devicetree/bindings/power/supply/gpio-charger.txt new file mode 100644 index 0000000..adbb5dc --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/gpio-charger.txt @@ -0,0 +1,27 @@ +gpio-charger + +Required properties : + - compatible : "gpio-charger" + - gpios : GPIO indicating the charger presence. + See GPIO binding in bindings/gpio/gpio.txt . + - charger-type : power supply type, one of + unknown + battery + ups + mains + usb-sdp (USB standard downstream port) + usb-dcp (USB dedicated charging port) + usb-cdp (USB charging downstream port) + usb-aca (USB accessory charger adapter) + +Example: + + usb_charger: charger { + compatible = "gpio-charger"; + charger-type = "usb-sdp"; + gpios = <&gpf0 2 0 0 0>; + } + + battery { + power-supplies = <&usb_charger>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/isp1704.txt b/Documentation/devicetree/bindings/power/supply/isp1704.txt new file mode 100644 index 0000000..fa35969 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/isp1704.txt @@ -0,0 +1,17 @@ +Binding for NXP ISP1704 USB Charger Detection + +Required properties: +- compatible: Should contain one of the following: + * "nxp,isp1704" +- nxp,enable-gpio: Should contain a phandle + gpio-specifier + to the GPIO pin connected to the chip's enable pin. +- usb-phy: Should contain a phandle to the USB PHY + the ISP1704 is connected to. + +Example: + +isp1704 { + compatible = "nxp,isp1704"; + nxp,enable-gpio = <&gpio3 3 GPIO_ACTIVE_LOW>; + usb-phy = <&usb2_phy>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/lp8727_charger.txt b/Documentation/devicetree/bindings/power/supply/lp8727_charger.txt new file mode 100644 index 0000000..2246bc5 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/lp8727_charger.txt @@ -0,0 +1,44 @@ +Binding for TI/National Semiconductor LP8727 Charger + +Required properties: +- compatible: "ti,lp8727" +- reg: I2C slave address 27h + +Optional properties: +- interrupt-parent: interrupt controller node (see interrupt binding[0]) +- interrupts: interrupt specifier (see interrupt binding[0]) +- debounce-ms: interrupt debounce time. (u32) + +AC and USB charging parameters +- charger-type: "ac" or "usb" (string) +- eoc-level: value of 'enum lp8727_eoc_level' (u8) +- charging-current: value of 'enum lp8727_ichg' (u8) + +[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt + +Example) + +lp8727@27 { + compatible = "ti,lp8727"; + reg = <0x27>; + + /* GPIO 134 is used for LP8728 interrupt pin */ + interrupt-parent = <&gpio5>; /* base = 128 */ + interrupts = <6 0x2>; /* offset = 6, falling edge type */ + + debounce-ms = <300>; + + /* AC charger: 5% EOC and 500mA charging current */ + ac { + charger-type = "ac"; + eoc-level = /bits/ 8 <0>; + charging-current = /bits/ 8 <4>; + }; + + /* USB charger: 10% EOC and 400mA charging current */ + usb { + charger-type = "usb"; + eoc-level = /bits/ 8 <1>; + charging-current = /bits/ 8 <2>; + }; +}; diff --git a/Documentation/devicetree/bindings/power/supply/ltc2941.txt b/Documentation/devicetree/bindings/power/supply/ltc2941.txt new file mode 100644 index 0000000..ea42ae1 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ltc2941.txt @@ -0,0 +1,27 @@ +binding for LTC2941 and LTC2943 battery gauges + +Both the LTC2941 and LTC2943 measure battery capacity. +The LTC2943 is compatible with the LTC2941, it adds voltage and +temperature monitoring, and uses a slightly different conversion +formula for the charge counter. + +Required properties: +- compatible: Should contain "ltc2941" or "ltc2943" which also indicates the + type of I2C chip attached. +- reg: The 7-bit I2C address. +- lltc,resistor-sense: The sense resistor value in milli-ohms. Can be a 32-bit + negative value when the battery has been connected to the wrong end of the + resistor. +- lltc,prescaler-exponent: The prescaler exponent as explained in the datasheet. + This determines the range and accuracy of the gauge. The value is programmed + into the chip only if it differs from the current setting. The setting is + lost when the battery is disconnected. + +Example from the Topic Miami Florida board: + + fuelgauge: ltc2943@64 { + compatible = "ltc2943"; + reg = <0x64>; + lltc,resistor-sense = <15>; + lltc,prescaler-exponent = <5>; /* 2^(2*5) = 1024 */ + }; diff --git a/Documentation/devicetree/bindings/power/supply/max17042_battery.txt b/Documentation/devicetree/bindings/power/supply/max17042_battery.txt new file mode 100644 index 0000000..3f3894a --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/max17042_battery.txt @@ -0,0 +1,31 @@ +max17042_battery +~~~~~~~~~~~~~~~~ + +Required properties : + - compatible : "maxim,max17042" + +Optional properties : + - maxim,rsns-microohm : Resistance of rsns resistor in micro Ohms + (datasheet-recommended value is 10000). + Defining this property enables current-sense functionality. + +Optional threshold properties : + If skipped the condition won't be reported. + - maxim,cold-temp : Temperature threshold to report battery + as cold (in tenths of degree Celsius). + - maxim,over-heat-temp : Temperature threshold to report battery + as over heated (in tenths of degree Celsius). + - maxim,dead-volt : Voltage threshold to report battery + as dead (in mV). + - maxim,over-volt : Voltage threshold to report battery + as over voltage (in mV). + +Example: + + battery-charger@36 { + compatible = "maxim,max17042"; + reg = <0x36>; + maxim,rsns-microohm = <10000>; + maxim,over-heat-temp = <600>; + maxim,over-volt = <4300>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/max8925_batter.txt b/Documentation/devicetree/bindings/power/supply/max8925_batter.txt new file mode 100644 index 0000000..d7e3e0c --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/max8925_batter.txt @@ -0,0 +1,18 @@ +max8925-battery bindings +~~~~~~~~~~~~~~~~ + +Optional properties : + - batt-detect: whether support battery detect + - topoff-threshold: set charging current in topoff mode + - fast-charge: set charging current in fast mode + - no-temp-support: whether support temperature protection detect + - no-insert-detect: whether support insert detect + +Example: + charger { + batt-detect = <0>; + topoff-threshold = <1>; + fast-charge = <7>; + no-temp-support = <0>; + no-insert-detect = <0>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/olpc_battery.txt b/Documentation/devicetree/bindings/power/supply/olpc_battery.txt new file mode 100644 index 0000000..c8901b3 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/olpc_battery.txt @@ -0,0 +1,5 @@ +OLPC battery +~~~~~~~~~~~~ + +Required properties: + - compatible : "olpc,xo1-battery" diff --git a/Documentation/devicetree/bindings/power/supply/power_supply.txt b/Documentation/devicetree/bindings/power/supply/power_supply.txt new file mode 100644 index 0000000..8391bfa --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/power_supply.txt @@ -0,0 +1,23 @@ +Power Supply Core Support + +Optional Properties: + - power-supplies : This property is added to a supply in order to list the + devices which supply it power, referenced by their phandles. + +Example: + + usb-charger: power@e { + compatible = "some,usb-charger"; + ... + }; + + ac-charger: power@c { + compatible = "some,ac-charger"; + ... + }; + + battery@b { + compatible = "some,battery"; + ... + power-supplies = <&usb-charger>, <&ac-charger>; + }; diff --git a/Documentation/devicetree/bindings/power/supply/qcom,coincell-charger.txt b/Documentation/devicetree/bindings/power/supply/qcom,coincell-charger.txt new file mode 100644 index 0000000..7478992 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/qcom,coincell-charger.txt @@ -0,0 +1,48 @@ +Qualcomm Coincell Charger: + +The hardware block controls charging for a coincell or capacitor that is +used to provide power backup for certain features of the power management +IC (PMIC) + +- compatible: + Usage: required + Value type: + Definition: must be: "qcom,pm8941-coincell" + +- reg: + Usage: required + Value type: + Definition: base address of the coincell charger registers + +- qcom,rset-ohms: + Usage: required + Value type: + Definition: resistance (in ohms) for current-limiting resistor + must be one of: 800, 1200, 1700, 2100 + +- qcom,vset-millivolts: + Usage: required + Value type: + Definition: voltage (in millivolts) to apply for charging + must be one of: 2500, 3000, 3100, 3200 + +- qcom,charger-disable: + Usage: optional + Value type: + Definition: defining this property disables charging + +This charger is a sub-node of one of the 8941 PMIC blocks, and is specified +as a child node in DTS of that node. See ../mfd/qcom,spmi-pmic.txt and +../mfd/qcom-pm8xxx.txt + +Example: + + pm8941@0 { + coincell@2800 { + compatible = "qcom,pm8941-coincell"; + reg = <0x2800>; + + qcom,rset-ohms = <2100>; + qcom,vset-millivolts = <3000>; + }; + }; diff --git a/Documentation/devicetree/bindings/power/supply/qcom_smbb.txt b/Documentation/devicetree/bindings/power/supply/qcom_smbb.txt new file mode 100644 index 0000000..65b88fa --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/qcom_smbb.txt @@ -0,0 +1,131 @@ +Qualcomm Switch-Mode Battery Charger and Boost + +PROPERTIES +- compatible: + Usage: required + Value type: + Description: Must be one of: + - "qcom,pm8941-charger" + +- reg: + Usage: required + Value type: + Description: Base address of registers for SMBB block + +- interrupts: + Usage: required + Value type: + Description: The format of the specifier is defined by the binding document + describing the node's interrupt parent. Must contain one + specifier for each of the following interrupts, in order: + - charge done + - charge fast mode + - charge trickle mode + - battery temperature ok + - battery present + - charger disconnected + - USB-in valid + - DC-in valid + +- interrupt-names: + Usage: required + Value type: + Description: Must contain the following list, strictly ordered: + "chg-done", + "chg-fast", + "chg-trkl", + "bat-temp-ok", + "bat-present", + "chg-gone", + "usb-valid", + "dc-valid" + +- qcom,fast-charge-current-limit: + Usage: optional (default: 1A, or pre-configured value) + Value type: ; uA; range [100mA : 3A] + Description: Maximum charge current; May be clamped to safety limits. + +- qcom,fast-charge-low-threshold-voltage: + Usage: optional (default: 3.2V, or pre-configured value) + Value type: ; uV; range [2.1V : 3.6V] + Description: Battery voltage limit above which fast charging may operate; + Below this value linear or switch-mode auto-trickle-charging + will operate. + +- qcom,fast-charge-high-threshold-voltage: + Usage: optional (default: 4.2V, or pre-configured value) + Value type: ; uV; range [3.24V : 5V] + Description: Battery voltage limit below which fast charging may operate; + The fast charger will attempt to charge the battery to this + voltage. May be clamped to safety limits. + +- qcom,fast-charge-safe-voltage: + Usage: optional (default: 4.2V, or pre-configured value) + Value type: ; uV; range [3.24V : 5V] + Description: Maximum safe battery voltage; May be pre-set by bootloader, in + which case, setting this will harmlessly fail. The property + 'fast-charge-high-watermark' will be clamped by this value. + +- qcom,fast-charge-safe-current: + Usage: optional (default: 1A, or pre-configured value) + Value type: ; uA; range [100mA : 3A] + Description: Maximum safe battery charge current; May pre-set by bootloader, + in which case, setting this will harmlessly fail. The property + 'qcom,fast-charge-current-limit' will be clamped by this value. + +- qcom,auto-recharge-threshold-voltage: + Usage: optional (default: 4.1V, or pre-configured value) + Value type: ; uV; range [3.24V : 5V] + Description: Battery voltage limit below which auto-recharge functionality + will restart charging after end-of-charge; The high cutoff + limit for auto-recharge is 5% above this value. + +- qcom,minimum-input-voltage: + Usage: optional (default: 4.3V, or pre-configured value) + Value type: ; uV; range [4.2V : 9.6V] + Description: Input voltage level above which charging may operate + +- qcom,dc-current-limit: + Usage: optional (default: 100mA, or pre-configured value) + Value type: ; uA; range [100mA : 2.5A] + Description: Default DC charge current limit + +- qcom,disable-dc: + Usage: optional (default: false) + Value type: boolean: or + Description: Disable DC charger + +- qcom,jeita-extended-temp-range: + Usage: optional (default: false) + Value type: boolean: or + Description: Enable JEITA extended temperature range; This does *not* + adjust the maximum charge voltage or current in the extended + temperature range. It only allows charging when the battery + is in the extended temperature range. Voltage/current + regulation must be done externally to fully comply with + the JEITA safety guidelines if this flag is set. + +EXAMPLE +charger@1000 { + compatible = "qcom,pm8941-charger"; + reg = <0x1000 0x700>; + interrupts = <0x0 0x10 7 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x10 5 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x10 4 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x12 1 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x12 0 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x13 2 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x13 1 IRQ_TYPE_EDGE_BOTH>, + <0x0 0x14 1 IRQ_TYPE_EDGE_BOTH>; + interrupt-names = "chg-done", + "chg-fast", + "chg-trkl", + "bat-temp-ok", + "bat-present", + "chg-gone", + "usb-valid", + "dc-valid"; + + qcom,fast-charge-current-limit = <1000000>; + qcom,dc-charge-current-limit = <1000000>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/rt9455_charger.txt b/Documentation/devicetree/bindings/power/supply/rt9455_charger.txt new file mode 100644 index 0000000..5d9ad5c --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/rt9455_charger.txt @@ -0,0 +1,48 @@ +Binding for Richtek rt9455 battery charger + +Required properties: +- compatible: it should contain one of the following: + "richtek,rt9455". +- reg: integer, i2c address of the device. +- interrupt-parent: the phandle for the interrupt controller that + services interrupts for this device. +- interrupts: interrupt mapping for GPIO IRQ, it should be + configured with IRQ_TYPE_LEVEL_LOW flag. +- richtek,output-charge-current: integer, output current from the charger to the + battery, in uA. +- richtek,end-of-charge-percentage: integer, percent of the output charge current. + When the current in constant-voltage phase drops + below output_charge_current x end-of-charge-percentage, + charge is terminated. +- richtek,battery-regulation-voltage: integer, maximum battery voltage in uV. +- richtek,boost-output-voltage: integer, maximum voltage provided to consumer + devices, when the charger is in boost mode, in uV. + +Optional properties: +- richtek,min-input-voltage-regulation: integer, input voltage level in uV, used to + decrease voltage level when the over current + of the input power source occurs. + This prevents input voltage drop due to insufficient + current provided by the power source. + Default: 4500000 uV (4.5V) +- richtek,avg-input-current-regulation: integer, input current value in uA drained by the + charger from the power source. + Default: 500000 uA (500mA) + +Example: + +rt9455@22 { + compatible = "richtek,rt9455"; + reg = <0x22>; + + interrupt-parent = <&gpio1>; + interrupts = <0 IRQ_TYPE_LEVEL_LOW>; + + richtek,output-charge-current = <500000>; + richtek,end-of-charge-percentage = <10>; + richtek,battery-regulation-voltage = <4200000>; + richtek,boost-output-voltage = <5050000>; + + richtek,min-input-voltage-regulation = <4500000>; + richtek,avg-input-current-regulation = <500000>; +}; diff --git a/Documentation/devicetree/bindings/power/supply/rx51-battery.txt b/Documentation/devicetree/bindings/power/supply/rx51-battery.txt new file mode 100644 index 0000000..9043845 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/rx51-battery.txt @@ -0,0 +1,25 @@ +Binding for Nokia N900 battery + +The Nokia N900 battery status can be read via the TWL4030's A/D converter. + +Required properties: +- compatible: Should contain one of the following: + * "nokia,n900-battery" +- io-channels: Should contain IIO channel specifiers + for each element in io-channel-names. +- io-channel-names: Should contain the following values: + * "temp" - The ADC channel for temperature reading + * "bsi" - The ADC channel for battery size identification + * "vbat" - The ADC channel to measure the battery voltage + +Example from Nokia N900: + +battery: n900-battery { + compatible = "nokia,n900-battery"; + io-channels = <&twl4030_madc 0>, + <&twl4030_madc 4>, + <&twl4030_madc 12>; + io-channel-names = "temp", + "bsi", + "vbat"; +}; diff --git a/Documentation/devicetree/bindings/power/supply/sbs_sbs-battery.txt b/Documentation/devicetree/bindings/power/supply/sbs_sbs-battery.txt new file mode 100644 index 0000000..c40e892 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/sbs_sbs-battery.txt @@ -0,0 +1,23 @@ +SBS sbs-battery +~~~~~~~~~~ + +Required properties : + - compatible : "sbs,sbs-battery" + +Optional properties : + - sbs,i2c-retry-count : The number of times to retry i2c transactions on i2c + IO failure. + - sbs,poll-retry-count : The number of times to try looking for new status + after an external change notification. + - sbs,battery-detect-gpios : The gpio which signals battery detection and + a flag specifying its polarity. + +Example: + + bq20z75@b { + compatible = "sbs,sbs-battery"; + reg = < 0xb >; + sbs,i2c-retry-count = <2>; + sbs,poll-retry-count = <10>; + sbs,battery-detect-gpios = <&gpio-controller 122 1>; + } diff --git a/Documentation/devicetree/bindings/power/supply/ti,bq24735.txt b/Documentation/devicetree/bindings/power/supply/ti,bq24735.txt new file mode 100644 index 0000000..3bf5575 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/ti,bq24735.txt @@ -0,0 +1,35 @@ +TI BQ24735 Charge Controller +~~~~~~~~~~ + +Required properties : + - compatible : "ti,bq24735" + +Optional properties : + - interrupts : Specify the interrupt to be used to trigger when the AC + adapter is either plugged in or removed. + - ti,ac-detect-gpios : This GPIO is optionally used to read the AC adapter + presence. This is a Host GPIO that is configured as an input and + connected to the bq24735. + - ti,charge-current : Used to control and set the charging current. This value + must be between 128mA and 8.128A with a 64mA step resolution. The POR value + is 0x0000h. This number is in mA (e.g. 8192), see spec for more information + about the ChargeCurrent (0x14h) register. + - ti,charge-voltage : Used to control and set the charging voltage. This value + must be between 1.024V and 19.2V with a 16mV step resolution. The POR value + is 0x0000h. This number is in mV (e.g. 19200), see spec for more information + about the ChargeVoltage (0x15h) register. + - ti,input-current : Used to control and set the charger input current. This + value must be between 128mA and 8.064A with a 128mA step resolution. The + POR value is 0x1000h. This number is in mA (e.g. 8064), see the spec for + more information about the InputCurrent (0x3fh) register. + - ti,external-control : Indicates that the charger is configured externally + and that the host should not attempt to enable/disable charging or set the + charge voltage/current. + +Example: + + bq24735@9 { + compatible = "ti,bq24735"; + reg = <0x9>; + ti,ac-detect-gpios = <&gpio 72 0x1>; + } diff --git a/Documentation/devicetree/bindings/power/supply/tps65090.txt b/Documentation/devicetree/bindings/power/supply/tps65090.txt new file mode 100644 index 0000000..8e5e0d3 --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/tps65090.txt @@ -0,0 +1,17 @@ +TPS65090 Frontend PMU with Switchmode Charger + +Required Properties: +-compatible: "ti,tps65090-charger" + +Optional Properties: +-ti,enable-low-current-chrg: Enables charging when a low current is detected + while the default logic is to stop charging. + +This node is a subnode of the tps65090 PMIC. + +Example: + + tps65090-charger { + compatible = "ti,tps65090-charger"; + ti,enable-low-current-chrg; + }; diff --git a/Documentation/devicetree/bindings/power/supply/tps65217_charger.txt b/Documentation/devicetree/bindings/power/supply/tps65217_charger.txt new file mode 100644 index 0000000..98d131a --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/tps65217_charger.txt @@ -0,0 +1,12 @@ +TPS65217 Charger + +Required Properties: +-compatible: "ti,tps65217-charger" + +This node is a subnode of the tps65217 PMIC. + +Example: + + tps65217-charger { + compatible = "ti,tps65090-charger"; + }; diff --git a/Documentation/devicetree/bindings/power/supply/twl-charger.txt b/Documentation/devicetree/bindings/power/supply/twl-charger.txt new file mode 100644 index 0000000..3b4ea1b --- /dev/null +++ b/Documentation/devicetree/bindings/power/supply/twl-charger.txt @@ -0,0 +1,30 @@ +TWL BCI (Battery Charger Interface) + +The battery charger needs to interact with the USB phy in order +to know when charging is permissible, and when there is a connection +or disconnection. + +The choice of phy cannot be configured at a hardware level, so there +is no value in explicit configuration in device-tree. Rather +if there is a sibling of the BCI node which is compatible with +"ti,twl4030-usb", then that is used to determine when and how +use USB power for charging. + +Required properties: +- compatible: + - "ti,twl4030-bci" +- interrupts: two interrupt lines from the TWL SIH (secondary + interrupt handler) - interrupts 9 and 2. + +Optional properties: +- ti,bb-uvolt: microvolts for charging the backup battery. +- ti,bb-uamp: microamps for charging the backup battery. + +Examples: + +bci { + compatible = "ti,twl4030-bci"; + interrupts = <9>, <2>; + ti,bb-uvolt = <3200000>; + ti,bb-uamp = <150>; +}; diff --git a/Documentation/devicetree/bindings/power/twl-charger.txt b/Documentation/devicetree/bindings/power/twl-charger.txt deleted file mode 100644 index 3b4ea1b..0000000 --- a/Documentation/devicetree/bindings/power/twl-charger.txt +++ /dev/null @@ -1,30 +0,0 @@ -TWL BCI (Battery Charger Interface) - -The battery charger needs to interact with the USB phy in order -to know when charging is permissible, and when there is a connection -or disconnection. - -The choice of phy cannot be configured at a hardware level, so there -is no value in explicit configuration in device-tree. Rather -if there is a sibling of the BCI node which is compatible with -"ti,twl4030-usb", then that is used to determine when and how -use USB power for charging. - -Required properties: -- compatible: - - "ti,twl4030-bci" -- interrupts: two interrupt lines from the TWL SIH (secondary - interrupt handler) - interrupts 9 and 2. - -Optional properties: -- ti,bb-uvolt: microvolts for charging the backup battery. -- ti,bb-uamp: microamps for charging the backup battery. - -Examples: - -bci { - compatible = "ti,twl4030-bci"; - interrupts = <9>, <2>; - ti,bb-uvolt = <3200000>; - ti,bb-uamp = <150>; -}; diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt b/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt deleted file mode 100644 index 0ba1bcc..0000000 --- a/Documentation/devicetree/bindings/power_supply/ab8500/btemp.txt +++ /dev/null @@ -1,16 +0,0 @@ -=== AB8500 Battery Temperature Monitor Driver === - -The properties below describes the node for btemp driver. - -Required Properties: -- compatible = Shall be: "stericsson,ab8500-btemp" -- battery = Shall be battery specific information - - Example: - ab8500_btemp { - compatible = "stericsson,ab8500-btemp"; - battery = <&ab8500_battery>; - }; - -For information on battery specific node, Ref: -Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt b/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt deleted file mode 100644 index ef53283..0000000 --- a/Documentation/devicetree/bindings/power_supply/ab8500/chargalg.txt +++ /dev/null @@ -1,16 +0,0 @@ -=== AB8500 Charging Algorithm Driver === - -The properties below describes the node for chargalg driver. - -Required Properties: -- compatible = Shall be: "stericsson,ab8500-chargalg" -- battery = Shall be battery specific information - -Example: -ab8500_chargalg { - compatible = "stericsson,ab8500-chargalg"; - battery = <&ab8500_battery>; -}; - -For information on battery specific node, Ref: -Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt b/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt deleted file mode 100644 index 6bdbb08..0000000 --- a/Documentation/devicetree/bindings/power_supply/ab8500/charger.txt +++ /dev/null @@ -1,25 +0,0 @@ -=== AB8500 Charger Driver === - -Required Properties: -- compatible = Shall be "stericsson,ab8500-charger" -- battery = Shall be battery specific information - Example: - ab8500_charger { - compatible = "stericsson,ab8500-charger"; - battery = <&ab8500_battery>; - }; - -- vddadc-supply: Supply for USB and Main charger - Example: - ab8500-charger { - vddadc-supply = <&ab8500_ldo_tvout_reg>; - } -- autopower_cfg: - Boolean value depicting the presence of 'automatic poweron after powerloss' - Example: - ab8500-charger { - autopower_cfg; - }; - -For information on battery specific node, Ref: -Documentation/devicetree/bindings/power_supply/ab8500/fg.txt diff --git a/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt b/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt deleted file mode 100644 index ccafcb9..0000000 --- a/Documentation/devicetree/bindings/power_supply/ab8500/fg.txt +++ /dev/null @@ -1,58 +0,0 @@ -=== AB8500 Fuel Gauge Driver === - -AB8500 is a mixed signal multimedia and power management -device comprising: power and energy-management-module, -wall-charger, usb-charger, audio codec, general purpose adc, -tvout, clock management and sim card interface. - -Fuelgauge support is part of energy-management-modules, other -components of this module are: -main-charger, usb-combo-charger and battery-temperature-monitoring. - -The properties below describes the node for fuelgauge driver. - -Required Properties: -- compatible = This shall be: "stericsson,ab8500-fg" -- battery = Shall be battery specific information - Example: - ab8500_fg { - compatible = "stericsson,ab8500-fg"; - battery = <&ab8500_battery>; - }; - -dependent node: - ab8500_battery: ab8500_battery { - }; - This node will provide information on 'thermistor interface' and - 'battery technology type' used. - -Properties of this node are: -thermistor-on-batctrl: - A boolean value indicating thermistor interface to battery - - Note: - 'btemp' and 'batctrl' are the pins interfaced for battery temperature - measurement, 'btemp' signal is used when NTC(negative temperature - coefficient) resister is interfaced external to battery whereas - 'batctrl' pin is used when NTC resister is internal to battery. - - Example: - ab8500_battery: ab8500_battery { - thermistor-on-batctrl; - }; - indicates: NTC resister is internal to battery, 'batctrl' is used - for thermal measurement. - - The absence of property 'thermal-on-batctrl' indicates - NTC resister is external to battery and 'btemp' signal is used - for thermal measurement. - -battery-type: - This shall be the battery manufacturing technology type, - allowed types are: - "UNKNOWN" "NiMH" "LION" "LIPO" "LiFe" "NiCd" "LiMn" - Example: - ab8500_battery: ab8500_battery { - stericsson,battery-type = "LIPO"; - } - diff --git a/Documentation/devicetree/bindings/power_supply/axp20x_usb_power.txt b/Documentation/devicetree/bindings/power_supply/axp20x_usb_power.txt deleted file mode 100644 index f1d7bee..0000000 --- a/Documentation/devicetree/bindings/power_supply/axp20x_usb_power.txt +++ /dev/null @@ -1,35 +0,0 @@ -AXP20x USB power supply - -Required Properties: --compatible: One of: "x-powers,axp202-usb-power-supply" - "x-powers,axp221-usb-power-supply" - -This node is a subnode of the axp20x PMIC. - -Example: - -axp209: pmic@34 { - compatible = "x-powers,axp209"; - reg = <0x34>; - interrupt-parent = <&nmi_intc>; - interrupts = <0 IRQ_TYPE_LEVEL_LOW>; - interrupt-controller; - #interrupt-cells = <1>; - - regulators { - x-powers,dcdc-freq = <1500>; - - vdd_cpu: dcdc2 { - regulator-always-on; - regulator-min-microvolt = <1000000>; - regulator-max-microvolt = <1450000>; - regulator-name = "vdd-cpu"; - }; - - ... - }; - - usb-power-supply: usb-power-supply { - compatible = "x-powers,axp202-usb-power-supply"; - }; -}; diff --git a/Documentation/devicetree/bindings/power_supply/axxia-reset.txt b/Documentation/devicetree/bindings/power_supply/axxia-reset.txt deleted file mode 100644 index 47e720d..0000000 --- a/Documentation/devicetree/bindings/power_supply/axxia-reset.txt +++ /dev/null @@ -1,20 +0,0 @@ -Axxia Restart Driver - -This driver can do reset of the Axxia SoC. It uses the registers in the syscon -block to initiate a chip reset. - -Required Properties: - -compatible: "lsi,axm55xx-reset" - -syscon: phandle to the syscon node. - -Example: - - syscon: syscon@2010030000 { - compatible = "lsi,axxia-syscon", "syscon"; - reg = <0x20 0x10030000 0 0x2000>; - }; - - reset: reset@2010031000 { - compatible = "lsi,axm55xx-reset"; - syscon = <&syscon>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/charger-manager.txt b/Documentation/devicetree/bindings/power_supply/charger-manager.txt deleted file mode 100644 index ec4fe9d..0000000 --- a/Documentation/devicetree/bindings/power_supply/charger-manager.txt +++ /dev/null @@ -1,81 +0,0 @@ -charger-manager bindings -~~~~~~~~~~~~~~~~~~~~~~~~ - -Required properties : - - compatible : "charger-manager" - - <>-supply : for regulator consumer - - cm-num-chargers : number of chargers - - cm-chargers : name of chargers - - cm-fuel-gauge : name of battery fuel gauge - - subnode : - - cm-regulator-name : name of charger regulator - - subnode : - - cm-cable-name : name of charger cable - - cm-cable-extcon : name of extcon dev -(optional) - cm-cable-min : minimum current of cable -(optional) - cm-cable-max : maximum current of cable - -Optional properties : - - cm-name : charger manager's name (default : "battery") - - cm-poll-mode : polling mode (enum polling_modes) - - cm-poll-interval : polling interval - - cm-battery-stat : battery status (enum data_source) - - cm-fullbatt-* : data for full battery checking - - cm-thermal-zone : name of external thermometer's thermal zone - - cm-battery-* : threshold battery temperature for charging - -cold : critical cold temperature of battery for charging - -cold-in-minus : flag that cold temperature is in minus degrees - -hot : critical hot temperature of battery for charging - -temp-diff : temperature difference to allow recharging - - cm-dis/charging-max = limits of charging duration - -Example : - charger-manager@0 { - compatible = "charger-manager"; - chg-reg-supply = <&charger_regulator>; - - cm-name = "battery"; - /* Always polling ON : 30s */ - cm-poll-mode = <1>; - cm-poll-interval = <30000>; - - cm-fullbatt-vchkdrop-ms = <30000>; - cm-fullbatt-vchkdrop-volt = <150000>; - cm-fullbatt-soc = <100>; - - cm-battery-stat = <3>; - - cm-num-chargers = <3>; - cm-chargers = "charger0", "charger1", "charger2"; - - cm-fuel-gauge = "fuelgauge0"; - - cm-thermal-zone = "thermal_zone.1" - /* in deci centigrade */ - cm-battery-cold = <50>; - cm-battery-cold-in-minus; - cm-battery-hot = <800>; - cm-battery-temp-diff = <100>; - - /* Allow charging for 5hr */ - cm-charging-max = <18000000>; - /* Allow discharging for 2hr */ - cm-discharging-max = <7200000>; - - regulator@0 { - cm-regulator-name = "chg-reg"; - cable@0 { - cm-cable-name = "USB"; - cm-cable-extcon = "extcon-dev.0"; - cm-cable-min = <475000>; - cm-cable-max = <500000>; - }; - cable@1 { - cm-cable-name = "TA"; - cm-cable-extcon = "extcon-dev.0"; - cm-cable-min = <650000>; - cm-cable-max = <675000>; - }; - }; - - }; diff --git a/Documentation/devicetree/bindings/power_supply/gpio-charger.txt b/Documentation/devicetree/bindings/power_supply/gpio-charger.txt deleted file mode 100644 index adbb5dc..0000000 --- a/Documentation/devicetree/bindings/power_supply/gpio-charger.txt +++ /dev/null @@ -1,27 +0,0 @@ -gpio-charger - -Required properties : - - compatible : "gpio-charger" - - gpios : GPIO indicating the charger presence. - See GPIO binding in bindings/gpio/gpio.txt . - - charger-type : power supply type, one of - unknown - battery - ups - mains - usb-sdp (USB standard downstream port) - usb-dcp (USB dedicated charging port) - usb-cdp (USB charging downstream port) - usb-aca (USB accessory charger adapter) - -Example: - - usb_charger: charger { - compatible = "gpio-charger"; - charger-type = "usb-sdp"; - gpios = <&gpf0 2 0 0 0>; - } - - battery { - power-supplies = <&usb_charger>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/imx-snvs-poweroff.txt b/Documentation/devicetree/bindings/power_supply/imx-snvs-poweroff.txt deleted file mode 100644 index dc7c9ba..0000000 --- a/Documentation/devicetree/bindings/power_supply/imx-snvs-poweroff.txt +++ /dev/null @@ -1,23 +0,0 @@ -i.mx6 Poweroff Driver - -SNVS_LPCR in SNVS module can power off the whole system by pull -PMIC_ON_REQ low if PMIC_ON_REQ is connected with external PMIC. -If you don't want to use PMIC_ON_REQ as power on/off control, -please set status='disabled' to disable this driver. - -Required Properties: --compatible: "fsl,sec-v4.0-poweroff" --reg: Specifies the physical address of the SNVS_LPCR register - -Example: - snvs@020cc000 { - compatible = "fsl,sec-v4.0-mon", "simple-bus"; - #address-cells = <1>; - #size-cells = <1>; - ranges = <0 0x020cc000 0x4000>; - ..... - snvs_poweroff: snvs-poweroff@38 { - compatible = "fsl,sec-v4.0-poweroff"; - reg = <0x38 0x4>; - }; - } diff --git a/Documentation/devicetree/bindings/power_supply/lp8727_charger.txt b/Documentation/devicetree/bindings/power_supply/lp8727_charger.txt deleted file mode 100644 index 2246bc5..0000000 --- a/Documentation/devicetree/bindings/power_supply/lp8727_charger.txt +++ /dev/null @@ -1,44 +0,0 @@ -Binding for TI/National Semiconductor LP8727 Charger - -Required properties: -- compatible: "ti,lp8727" -- reg: I2C slave address 27h - -Optional properties: -- interrupt-parent: interrupt controller node (see interrupt binding[0]) -- interrupts: interrupt specifier (see interrupt binding[0]) -- debounce-ms: interrupt debounce time. (u32) - -AC and USB charging parameters -- charger-type: "ac" or "usb" (string) -- eoc-level: value of 'enum lp8727_eoc_level' (u8) -- charging-current: value of 'enum lp8727_ichg' (u8) - -[0]: Documentation/devicetree/bindings/interrupt-controller/interrupts.txt - -Example) - -lp8727@27 { - compatible = "ti,lp8727"; - reg = <0x27>; - - /* GPIO 134 is used for LP8728 interrupt pin */ - interrupt-parent = <&gpio5>; /* base = 128 */ - interrupts = <6 0x2>; /* offset = 6, falling edge type */ - - debounce-ms = <300>; - - /* AC charger: 5% EOC and 500mA charging current */ - ac { - charger-type = "ac"; - eoc-level = /bits/ 8 <0>; - charging-current = /bits/ 8 <4>; - }; - - /* USB charger: 10% EOC and 400mA charging current */ - usb { - charger-type = "usb"; - eoc-level = /bits/ 8 <1>; - charging-current = /bits/ 8 <2>; - }; -}; diff --git a/Documentation/devicetree/bindings/power_supply/max17042_battery.txt b/Documentation/devicetree/bindings/power_supply/max17042_battery.txt deleted file mode 100644 index 3f3894a..0000000 --- a/Documentation/devicetree/bindings/power_supply/max17042_battery.txt +++ /dev/null @@ -1,31 +0,0 @@ -max17042_battery -~~~~~~~~~~~~~~~~ - -Required properties : - - compatible : "maxim,max17042" - -Optional properties : - - maxim,rsns-microohm : Resistance of rsns resistor in micro Ohms - (datasheet-recommended value is 10000). - Defining this property enables current-sense functionality. - -Optional threshold properties : - If skipped the condition won't be reported. - - maxim,cold-temp : Temperature threshold to report battery - as cold (in tenths of degree Celsius). - - maxim,over-heat-temp : Temperature threshold to report battery - as over heated (in tenths of degree Celsius). - - maxim,dead-volt : Voltage threshold to report battery - as dead (in mV). - - maxim,over-volt : Voltage threshold to report battery - as over voltage (in mV). - -Example: - - battery-charger@36 { - compatible = "maxim,max17042"; - reg = <0x36>; - maxim,rsns-microohm = <10000>; - maxim,over-heat-temp = <600>; - maxim,over-volt = <4300>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/max8925_batter.txt b/Documentation/devicetree/bindings/power_supply/max8925_batter.txt deleted file mode 100644 index d7e3e0c..0000000 --- a/Documentation/devicetree/bindings/power_supply/max8925_batter.txt +++ /dev/null @@ -1,18 +0,0 @@ -max8925-battery bindings -~~~~~~~~~~~~~~~~ - -Optional properties : - - batt-detect: whether support battery detect - - topoff-threshold: set charging current in topoff mode - - fast-charge: set charging current in fast mode - - no-temp-support: whether support temperature protection detect - - no-insert-detect: whether support insert detect - -Example: - charger { - batt-detect = <0>; - topoff-threshold = <1>; - fast-charge = <7>; - no-temp-support = <0>; - no-insert-detect = <0>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/msm-poweroff.txt b/Documentation/devicetree/bindings/power_supply/msm-poweroff.txt deleted file mode 100644 index ce44ad3..0000000 --- a/Documentation/devicetree/bindings/power_supply/msm-poweroff.txt +++ /dev/null @@ -1,17 +0,0 @@ -MSM Restart Driver - -A power supply hold (ps-hold) bit is set to power the msm chipsets. -Clearing that bit allows us to restart/poweroff. The difference -between poweroff and restart is determined by unique power manager IC -settings. - -Required Properties: --compatible: "qcom,pshold" --reg: Specifies the physical address of the ps-hold register - -Example: - - restart@fc4ab000 { - compatible = "qcom,pshold"; - reg = <0xfc4ab000 0x4>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/olpc_battery.txt b/Documentation/devicetree/bindings/power_supply/olpc_battery.txt deleted file mode 100644 index c8901b3..0000000 --- a/Documentation/devicetree/bindings/power_supply/olpc_battery.txt +++ /dev/null @@ -1,5 +0,0 @@ -OLPC battery -~~~~~~~~~~~~ - -Required properties: - - compatible : "olpc,xo1-battery" diff --git a/Documentation/devicetree/bindings/power_supply/power_supply.txt b/Documentation/devicetree/bindings/power_supply/power_supply.txt deleted file mode 100644 index 8391bfa..0000000 --- a/Documentation/devicetree/bindings/power_supply/power_supply.txt +++ /dev/null @@ -1,23 +0,0 @@ -Power Supply Core Support - -Optional Properties: - - power-supplies : This property is added to a supply in order to list the - devices which supply it power, referenced by their phandles. - -Example: - - usb-charger: power@e { - compatible = "some,usb-charger"; - ... - }; - - ac-charger: power@c { - compatible = "some,ac-charger"; - ... - }; - - battery@b { - compatible = "some,battery"; - ... - power-supplies = <&usb-charger>, <&ac-charger>; - }; diff --git a/Documentation/devicetree/bindings/power_supply/qcom_smbb.txt b/Documentation/devicetree/bindings/power_supply/qcom_smbb.txt deleted file mode 100644 index 65b88fa..0000000 --- a/Documentation/devicetree/bindings/power_supply/qcom_smbb.txt +++ /dev/null @@ -1,131 +0,0 @@ -Qualcomm Switch-Mode Battery Charger and Boost - -PROPERTIES -- compatible: - Usage: required - Value type: - Description: Must be one of: - - "qcom,pm8941-charger" - -- reg: - Usage: required - Value type: - Description: Base address of registers for SMBB block - -- interrupts: - Usage: required - Value type: - Description: The format of the specifier is defined by the binding document - describing the node's interrupt parent. Must contain one - specifier for each of the following interrupts, in order: - - charge done - - charge fast mode - - charge trickle mode - - battery temperature ok - - battery present - - charger disconnected - - USB-in valid - - DC-in valid - -- interrupt-names: - Usage: required - Value type: - Description: Must contain the following list, strictly ordered: - "chg-done", - "chg-fast", - "chg-trkl", - "bat-temp-ok", - "bat-present", - "chg-gone", - "usb-valid", - "dc-valid" - -- qcom,fast-charge-current-limit: - Usage: optional (default: 1A, or pre-configured value) - Value type: ; uA; range [100mA : 3A] - Description: Maximum charge current; May be clamped to safety limits. - -- qcom,fast-charge-low-threshold-voltage: - Usage: optional (default: 3.2V, or pre-configured value) - Value type: ; uV; range [2.1V : 3.6V] - Description: Battery voltage limit above which fast charging may operate; - Below this value linear or switch-mode auto-trickle-charging - will operate. - -- qcom,fast-charge-high-threshold-voltage: - Usage: optional (default: 4.2V, or pre-configured value) - Value type: ; uV; range [3.24V : 5V] - Description: Battery voltage limit below which fast charging may operate; - The fast charger will attempt to charge the battery to this - voltage. May be clamped to safety limits. - -- qcom,fast-charge-safe-voltage: - Usage: optional (default: 4.2V, or pre-configured value) - Value type: ; uV; range [3.24V : 5V] - Description: Maximum safe battery voltage; May be pre-set by bootloader, in - which case, setting this will harmlessly fail. The property - 'fast-charge-high-watermark' will be clamped by this value. - -- qcom,fast-charge-safe-current: - Usage: optional (default: 1A, or pre-configured value) - Value type: ; uA; range [100mA : 3A] - Description: Maximum safe battery charge current; May pre-set by bootloader, - in which case, setting this will harmlessly fail. The property - 'qcom,fast-charge-current-limit' will be clamped by this value. - -- qcom,auto-recharge-threshold-voltage: - Usage: optional (default: 4.1V, or pre-configured value) - Value type: ; uV; range [3.24V : 5V] - Description: Battery voltage limit below which auto-recharge functionality - will restart charging after end-of-charge; The high cutoff - limit for auto-recharge is 5% above this value. - -- qcom,minimum-input-voltage: - Usage: optional (default: 4.3V, or pre-configured value) - Value type: ; uV; range [4.2V : 9.6V] - Description: Input voltage level above which charging may operate - -- qcom,dc-current-limit: - Usage: optional (default: 100mA, or pre-configured value) - Value type: ; uA; range [100mA : 2.5A] - Description: Default DC charge current limit - -- qcom,disable-dc: - Usage: optional (default: false) - Value type: boolean: or - Description: Disable DC charger - -- qcom,jeita-extended-temp-range: - Usage: optional (default: false) - Value type: boolean: or - Description: Enable JEITA extended temperature range; This does *not* - adjust the maximum charge voltage or current in the extended - temperature range. It only allows charging when the battery - is in the extended temperature range. Voltage/current - regulation must be done externally to fully comply with - the JEITA safety guidelines if this flag is set. - -EXAMPLE -charger@1000 { - compatible = "qcom,pm8941-charger"; - reg = <0x1000 0x700>; - interrupts = <0x0 0x10 7 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x10 5 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x10 4 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x12 1 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x12 0 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x13 2 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x13 1 IRQ_TYPE_EDGE_BOTH>, - <0x0 0x14 1 IRQ_TYPE_EDGE_BOTH>; - interrupt-names = "chg-done", - "chg-fast", - "chg-trkl", - "bat-temp-ok", - "bat-present", - "chg-gone", - "usb-valid", - "dc-valid"; - - qcom,fast-charge-current-limit = <1000000>; - qcom,dc-charge-current-limit = <1000000>; -}; diff --git a/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt b/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt deleted file mode 100644 index af25e77..0000000 --- a/Documentation/devicetree/bindings/power_supply/qnap-poweroff.txt +++ /dev/null @@ -1,16 +0,0 @@ -* QNAP Power Off - -QNAP NAS devices have a microcontroller controlling the main power -supply. This microcontroller is connected to UART1 of the Kirkwood and -Orion5x SoCs. Sending the character 'A', at 19200 baud, tells the -microcontroller to turn the power off. This driver adds a handler to -pm_power_off which is called to turn the power off. - -Synology NAS devices use a similar scheme, but a different baud rate, -9600, and a different character, '1'. - -Required Properties: -- compatible: Should be "qnap,power-off" or "synology,power-off" - -- reg: Address and length of the register set for UART1 -- clocks: tclk clock diff --git a/Documentation/devicetree/bindings/power_supply/restart-poweroff.txt b/Documentation/devicetree/bindings/power_supply/restart-poweroff.txt deleted file mode 100644 index 5776e68..0000000 --- a/Documentation/devicetree/bindings/power_supply/restart-poweroff.txt +++ /dev/null @@ -1,8 +0,0 @@ -* Restart Power Off - -Buffalo Linkstation LS-XHL and LS-CHLv2, and other devices power off -by restarting and letting u-boot keep hold of the machine until the -user presses a button. - -Required Properties: -- compatible: Should be "restart-poweroff" diff --git a/Documentation/devicetree/bindings/power_supply/sbs_sbs-battery.txt b/Documentation/devicetree/bindings/power_supply/sbs_sbs-battery.txt deleted file mode 100644 index c40e892..0000000 --- a/Documentation/devicetree/bindings/power_supply/sbs_sbs-battery.txt +++ /dev/null @@ -1,23 +0,0 @@ -SBS sbs-battery -~~~~~~~~~~ - -Required properties : - - compatible : "sbs,sbs-battery" - -Optional properties : - - sbs,i2c-retry-count : The number of times to retry i2c transactions on i2c - IO failure. - - sbs,poll-retry-count : The number of times to try looking for new status - after an external change notification. - - sbs,battery-detect-gpios : The gpio which signals battery detection and - a flag specifying its polarity. - -Example: - - bq20z75@b { - compatible = "sbs,sbs-battery"; - reg = < 0xb >; - sbs,i2c-retry-count = <2>; - sbs,poll-retry-count = <10>; - sbs,battery-detect-gpios = <&gpio-controller 122 1>; - } diff --git a/Documentation/devicetree/bindings/power_supply/ti,bq24735.txt b/Documentation/devicetree/bindings/power_supply/ti,bq24735.txt deleted file mode 100644 index 3bf5575..0000000 --- a/Documentation/devicetree/bindings/power_supply/ti,bq24735.txt +++ /dev/null @@ -1,35 +0,0 @@ -TI BQ24735 Charge Controller -~~~~~~~~~~ - -Required properties : - - compatible : "ti,bq24735" - -Optional properties : - - interrupts : Specify the interrupt to be used to trigger when the AC - adapter is either plugged in or removed. - - ti,ac-detect-gpios : This GPIO is optionally used to read the AC adapter - presence. This is a Host GPIO that is configured as an input and - connected to the bq24735. - - ti,charge-current : Used to control and set the charging current. This value - must be between 128mA and 8.128A with a 64mA step resolution. The POR value - is 0x0000h. This number is in mA (e.g. 8192), see spec for more information - about the ChargeCurrent (0x14h) register. - - ti,charge-voltage : Used to control and set the charging voltage. This value - must be between 1.024V and 19.2V with a 16mV step resolution. The POR value - is 0x0000h. This number is in mV (e.g. 19200), see spec for more information - about the ChargeVoltage (0x15h) register. - - ti,input-current : Used to control and set the charger input current. This - value must be between 128mA and 8.064A with a 128mA step resolution. The - POR value is 0x1000h. This number is in mA (e.g. 8064), see the spec for - more information about the InputCurrent (0x3fh) register. - - ti,external-control : Indicates that the charger is configured externally - and that the host should not attempt to enable/disable charging or set the - charge voltage/current. - -Example: - - bq24735@9 { - compatible = "ti,bq24735"; - reg = <0x9>; - ti,ac-detect-gpios = <&gpio 72 0x1>; - } diff --git a/Documentation/devicetree/bindings/power_supply/tps65090.txt b/Documentation/devicetree/bindings/power_supply/tps65090.txt deleted file mode 100644 index 8e5e0d3..0000000 --- a/Documentation/devicetree/bindings/power_supply/tps65090.txt +++ /dev/null @@ -1,17 +0,0 @@ -TPS65090 Frontend PMU with Switchmode Charger - -Required Properties: --compatible: "ti,tps65090-charger" - -Optional Properties: --ti,enable-low-current-chrg: Enables charging when a low current is detected - while the default logic is to stop charging. - -This node is a subnode of the tps65090 PMIC. - -Example: - - tps65090-charger { - compatible = "ti,tps65090-charger"; - ti,enable-low-current-chrg; - }; diff --git a/Documentation/devicetree/bindings/power_supply/tps65217_charger.txt b/Documentation/devicetree/bindings/power_supply/tps65217_charger.txt deleted file mode 100644 index 98d131a..0000000 --- a/Documentation/devicetree/bindings/power_supply/tps65217_charger.txt +++ /dev/null @@ -1,12 +0,0 @@ -TPS65217 Charger - -Required Properties: --compatible: "ti,tps65217-charger" - -This node is a subnode of the tps65217 PMIC. - -Example: - - tps65217-charger { - compatible = "ti,tps65090-charger"; - }; diff --git a/MAINTAINERS b/MAINTAINERS index ca66b41..c7d6762 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9331,8 +9331,7 @@ M: David Woodhouse L: linux-pm@vger.kernel.org T: git git://git.infradead.org/battery-2.6.git S: Maintained -F: Documentation/devicetree/bindings/power/ -F: Documentation/devicetree/bindings/power_supply/ +F: Documentation/devicetree/bindings/power/supply/ F: include/linux/power_supply.h F: drivers/power/supply/ -- cgit v0.10.2 From 47d7d5ed68d877269003a392b7008905d65650bb Mon Sep 17 00:00:00 2001 From: Marcin Niestroj Date: Mon, 20 Jun 2016 12:50:54 +0200 Subject: power_supply: tps65217-charger: Add support for IRQs Make use of IRQ resources defined in tps65217 mfd code. If they are valid we use them instead separate poll task, in order to define AC power state. Signed-off-by: Marcin Niestroj Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/tps65217_charger.c b/drivers/power/supply/tps65217_charger.c index 73dfae4..c8c4a0c 100644 --- a/drivers/power/supply/tps65217_charger.c +++ b/drivers/power/supply/tps65217_charger.c @@ -46,6 +46,8 @@ struct tps65217_charger { int prev_ac_online; struct task_struct *poll_task; + + int irq; }; static enum power_supply_property tps65217_ac_props[] = { @@ -198,6 +200,7 @@ 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__); @@ -220,18 +223,40 @@ static int tps65217_charger_probe(struct platform_device *pdev) 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; } - 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; + 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; @@ -241,7 +266,8 @@ static int tps65217_charger_remove(struct platform_device *pdev) { struct tps65217_charger *charger = platform_get_drvdata(pdev); - kthread_stop(charger->poll_task); + if (charger->irq == -ENXIO) + kthread_stop(charger->poll_task); return 0; } -- cgit v0.10.2 From e4a404a081df1abe95a06ab24b7c76d8cf02402f Mon Sep 17 00:00:00 2001 From: "H. Nikolaus Schaller" Date: Mon, 18 Jul 2016 18:12:09 +0200 Subject: power:bq27xxx: 27000/10 read FLAGS register as single MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The bq27000 and bq27010 have a single byte FLAGS register. Other gauges have 16 bit FLAGS registers. For reading the FLAGS register it is sufficient to read the single register instead of reading RSOC at the next higher address as well and then ignore the high byte. This does not change functionality but optimizes i2c and hdq traffic. Signed-off-by: H. Nikolaus Schaller Acked-by: Pali Rohár Acked-by: Andrew F. Davis Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 323d05a..3f57dd5 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -644,8 +644,9 @@ static bool bq27xxx_battery_dead(struct bq27xxx_device_info *di, u16 flags) 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, false); + 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; @@ -745,7 +746,7 @@ static int bq27xxx_battery_current(struct bq27xxx_device_info *di, } if (di->chip == BQ27000 || di->chip == BQ27010) { - flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, false); + flags = bq27xxx_read(di, BQ27XXX_REG_FLAGS, true); if (flags & BQ27000_FLAG_CHGS) { dev_dbg(di->dev, "negative current!\n"); curr = -curr; -- cgit v0.10.2 From 9239a86f0976b58d3da7a2261ed659ac9eba0f25 Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Mon, 25 Jul 2016 10:42:57 +0800 Subject: power: sbs-battery: Use devm_kzalloc to alloc data Use devm_kzalloc to allow memory to be freed automatically on driver probe failure or removal. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 768b9fc..20f3be6 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -819,7 +819,7 @@ static int sbs_probe(struct i2c_client *client, if (!sbs_desc->name) return -ENOMEM; - chip = kzalloc(sizeof(struct sbs_info), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(struct sbs_info), GFP_KERNEL); if (!chip) return -ENOMEM; @@ -920,8 +920,6 @@ exit_psupply: if (chip->gpio_detect) gpio_free(pdata->battery_detect); - kfree(chip); - return rc; } @@ -938,9 +936,6 @@ static int sbs_remove(struct i2c_client *client) cancel_delayed_work_sync(&chip->work); - kfree(chip); - chip = NULL; - return 0; } -- cgit v0.10.2 From d2cec82c28802da31596b395ad292cb8f132fd63 Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Mon, 25 Jul 2016 10:42:58 +0800 Subject: power: sbs-battery: Request threaded irq and fix dev callback cookie Currently the battery detect gpio can not be used with a chained interrupt controller that requires threaded irq handlers. Use threaded irq instead. In addition this was not going to be working at present because chip->power_supply is assigned after the request irq call. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 20f3be6..f9012ed 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -162,7 +162,6 @@ struct sbs_info { bool is_present; bool gpio_detect; bool enable_detection; - int irq; int last_state; int poll_time; struct delayed_work work; @@ -661,7 +660,8 @@ done: static irqreturn_t sbs_irq(int irq, void *devid) { - struct power_supply *battery = devid; + struct sbs_info *chip = devid; + struct power_supply *battery = chip->power_supply; power_supply_changed(battery); @@ -869,9 +869,9 @@ static int sbs_probe(struct i2c_client *client, goto skip_gpio; } - rc = request_irq(irq, sbs_irq, + rc = devm_request_threaded_irq(&client->dev, irq, NULL, sbs_irq, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - dev_name(&client->dev), chip->power_supply); + dev_name(&client->dev), chip); if (rc) { dev_warn(&client->dev, "Failed to request irq: %d\n", rc); gpio_free(pdata->battery_detect); @@ -879,8 +879,6 @@ static int sbs_probe(struct i2c_client *client, goto skip_gpio; } - chip->irq = irq; - skip_gpio: /* * Before we register, we might need to make sure we can actually talk @@ -915,8 +913,6 @@ skip_gpio: return 0; exit_psupply: - if (chip->irq) - free_irq(chip->irq, chip->power_supply); if (chip->gpio_detect) gpio_free(pdata->battery_detect); @@ -927,8 +923,6 @@ static int sbs_remove(struct i2c_client *client) { struct sbs_info *chip = i2c_get_clientdata(client); - if (chip->irq) - free_irq(chip->irq, chip->power_supply); if (chip->gpio_detect) gpio_free(chip->pdata->battery_detect); -- cgit v0.10.2 From 492ff9d8f5fa6ad44288050238b7961d457a239d Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Mon, 25 Jul 2016 10:42:59 +0800 Subject: power: sbs-battery: Use devm_power_supply_register Use devm_power_supply_register instead of power_supply_register. Remove call to power_supply_unregister. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index f9012ed..31f3e33 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -894,7 +894,7 @@ skip_gpio: } } - chip->power_supply = power_supply_register(&client->dev, sbs_desc, + chip->power_supply = devm_power_supply_register(&client->dev, sbs_desc, &psy_cfg); if (IS_ERR(chip->power_supply)) { dev_err(&client->dev, @@ -926,8 +926,6 @@ static int sbs_remove(struct i2c_client *client) if (chip->gpio_detect) gpio_free(chip->pdata->battery_detect); - power_supply_unregister(chip->power_supply); - cancel_delayed_work_sync(&chip->work); return 0; -- cgit v0.10.2 From 957cb720518341abf19009044f240a6342bdd039 Mon Sep 17 00:00:00 2001 From: Joshua Clayton Date: Thu, 11 Aug 2016 09:59:12 -0700 Subject: sbs-battery: add ability to get battery capacity Battery capacity level is a standard feature of sbs battery That can be used to tell what the remainig battery capacity is, and can tell if the battery has not been calibrated/initialized, which makes the capacity and charging/discharging percentages invalid. Signed-off-by: Joshua Clayton Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 31f3e33..e1dfb3e 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -41,6 +41,7 @@ enum { REG_TIME_TO_EMPTY, REG_TIME_TO_FULL, REG_STATUS, + REG_CAPACITY_LEVEL, REG_CYCLE_COUNT, REG_SERIAL_NUMBER, REG_REMAINING_CAPACITY, @@ -68,6 +69,7 @@ enum sbs_battery_mode { #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 @@ -110,6 +112,8 @@ static const struct chip_data { 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] = @@ -131,6 +135,7 @@ static const struct chip_data { 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, @@ -376,8 +381,23 @@ static int sbs_get_battery_property(struct i2c_client *client, if (ret >= sbs_data[reg_offset].min_value && ret <= sbs_data[reg_offset].max_value) { val->intval = ret; - if (psp != POWER_SUPPLY_PROP_STATUS) + 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; @@ -589,6 +609,7 @@ static int sbs_get_property(struct power_supply *psy, 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: -- cgit v0.10.2 From c1a9634f1aaf5e10c23f8890ea2e64c61d48cb44 Mon Sep 17 00:00:00 2001 From: Bjorn Andersson Date: Wed, 3 Aug 2016 22:04:05 -0700 Subject: power: reset: reboot-mode: Add managed resource API Provide managed resource version of reboot_mode_register() and reboot_mode_unregister() to simplify implementations. Signed-off-by: Bjorn Andersson Tested-by: John Stultz Signed-off-by: Sebastian Reichel diff --git a/Documentation/driver-model/devres.txt b/Documentation/driver-model/devres.txt index b0d775d..b1ff0b7 100644 --- a/Documentation/driver-model/devres.txt +++ b/Documentation/driver-model/devres.txt @@ -342,6 +342,10 @@ PINCTRL devm_pinctrl_register() devm_pinctrl_unregister() +POWER + devm_reboot_mode_register() + devm_reboot_mode_unregister() + PWM devm_pwm_get() devm_pwm_put() diff --git a/drivers/power/reset/reboot-mode.c b/drivers/power/reset/reboot-mode.c index 2dfbbce..fb51218 100644 --- a/drivers/power/reset/reboot-mode.c +++ b/drivers/power/reset/reboot-mode.c @@ -135,6 +135,65 @@ int reboot_mode_unregister(struct reboot_mode_driver *reboot) } EXPORT_SYMBOL_GPL(reboot_mode_unregister); +static void devm_reboot_mode_release(struct device *dev, void *res) +{ + reboot_mode_unregister(*(struct reboot_mode_driver **)res); +} + +/** + * devm_reboot_mode_register() - resource managed reboot_mode_register() + * @dev: device to associate this resource with + * @reboot: reboot mode driver + * + * Returns: 0 on success or a negative error code on failure. + */ +int devm_reboot_mode_register(struct device *dev, + struct reboot_mode_driver *reboot) +{ + struct reboot_mode_driver **dr; + int rc; + + dr = devres_alloc(devm_reboot_mode_release, sizeof(*dr), GFP_KERNEL); + if (!dr) + return -ENOMEM; + + rc = reboot_mode_register(reboot); + if (rc) { + devres_free(dr); + return rc; + } + + *dr = reboot; + devres_add(dev, dr); + + return 0; +} +EXPORT_SYMBOL_GPL(devm_reboot_mode_register); + +static int devm_reboot_mode_match(struct device *dev, void *res, void *data) +{ + struct reboot_mode_driver **p = res; + + if (WARN_ON(!p || !*p)) + return 0; + + return *p == data; +} + +/** + * devm_reboot_mode_unregister() - resource managed reboot_mode_unregister() + * @dev: device to associate this resource with + * @reboot: reboot mode driver + */ +void devm_reboot_mode_unregister(struct device *dev, + struct reboot_mode_driver *reboot) +{ + WARN_ON(devres_release(dev, + devm_reboot_mode_release, + devm_reboot_mode_match, reboot)); +} +EXPORT_SYMBOL_GPL(devm_reboot_mode_unregister); + MODULE_AUTHOR("Andy Yan Date: Wed, 3 Aug 2016 22:04:06 -0700 Subject: power: reset: syscon-reboot-mode: Use managed resource API Use the managed resource version of reboot_mode_register(). Signed-off-by: Bjorn Andersson Tested-by: John Stultz Signed-off-by: Sebastian Reichel diff --git a/drivers/power/reset/syscon-reboot-mode.c b/drivers/power/reset/syscon-reboot-mode.c index 9e1cba5..1ecb51d 100644 --- a/drivers/power/reset/syscon-reboot-mode.c +++ b/drivers/power/reset/syscon-reboot-mode.c @@ -53,8 +53,6 @@ static int syscon_reboot_mode_probe(struct platform_device *pdev) syscon_rbm->reboot.write = syscon_reboot_mode_write; syscon_rbm->mask = 0xffffffff; - dev_set_drvdata(&pdev->dev, syscon_rbm); - syscon_rbm->map = syscon_node_to_regmap(pdev->dev.parent->of_node); if (IS_ERR(syscon_rbm->map)) return PTR_ERR(syscon_rbm->map); @@ -65,20 +63,13 @@ static int syscon_reboot_mode_probe(struct platform_device *pdev) of_property_read_u32(pdev->dev.of_node, "mask", &syscon_rbm->mask); - ret = reboot_mode_register(&syscon_rbm->reboot); + ret = devm_reboot_mode_register(&pdev->dev, &syscon_rbm->reboot); if (ret) dev_err(&pdev->dev, "can't register reboot mode\n"); return ret; } -static int syscon_reboot_mode_remove(struct platform_device *pdev) -{ - struct syscon_reboot_mode *syscon_rbm = dev_get_drvdata(&pdev->dev); - - return reboot_mode_unregister(&syscon_rbm->reboot); -} - static const struct of_device_id syscon_reboot_mode_of_match[] = { { .compatible = "syscon-reboot-mode" }, {} @@ -86,7 +77,6 @@ static const struct of_device_id syscon_reboot_mode_of_match[] = { static struct platform_driver syscon_reboot_mode_driver = { .probe = syscon_reboot_mode_probe, - .remove = syscon_reboot_mode_remove, .driver = { .name = "syscon-reboot-mode", .of_match_table = syscon_reboot_mode_of_match, -- cgit v0.10.2 From 1bbd3d282557cf5e544cc749d577bd7cefe929f4 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sat, 13 Aug 2016 09:06:22 +0000 Subject: power: z2_battery: remove .owner field for driver Remove .owner field if calls are used which set it automatically. Generated by: scripts/coccinelle/api/platform_no_drv_owner.cocci Signed-off-by: Wei Yongjun Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/z2_battery.c b/drivers/power/supply/z2_battery.c index b201e3f..8a43b49 100644 --- a/drivers/power/supply/z2_battery.c +++ b/drivers/power/supply/z2_battery.c @@ -317,7 +317,6 @@ MODULE_DEVICE_TABLE(i2c, z2_batt_id); static struct i2c_driver z2_batt_driver = { .driver = { .name = "z2-battery", - .owner = THIS_MODULE, .pm = Z2_BATTERY_PM_OPS }, .probe = z2_batt_probe, -- cgit v0.10.2 From ad7656c75faebb43cf1102756c503668309d666f Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sat, 13 Aug 2016 09:06:47 +0000 Subject: power: axp288_fuel_gauge: remove duplicated include from axp288_fuel_gauge.c Remove duplicated include. Signed-off-by: Wei Yongjun Acked-by: Chen-Yu Tsai Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/axp288_fuel_gauge.c b/drivers/power/supply/axp288_fuel_gauge.c index 50c0110..5bdde69 100644 --- a/drivers/power/supply/axp288_fuel_gauge.c +++ b/drivers/power/supply/axp288_fuel_gauge.c @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include -- cgit v0.10.2 From 2ee565c934b7aa3ad84dcc3735fb2359026866a0 Mon Sep 17 00:00:00 2001 From: Wei Yongjun Date: Sat, 13 Aug 2016 09:07:07 +0000 Subject: power: axp288_charger: remove duplicated include from axp288_charger.c Remove duplicated include. Signed-off-by: Wei Yongjun Acked-by: Chen-Yu Tsai Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/axp288_charger.c b/drivers/power/supply/axp288_charger.c index 4030eeb..75b8e0c 100644 --- a/drivers/power/supply/axp288_charger.c +++ b/drivers/power/supply/axp288_charger.c @@ -23,7 +23,6 @@ #include #include #include -#include #include #include #include -- cgit v0.10.2 From 0b9992f76f65532be8727977bd6997aa55e1340e Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 21:46:10 +0530 Subject: power: abx500_chargalg: Remove deprecated create_singlethread_workqueue alloc_ordered_workqueue() with WQ_MEM_RECLAIM set replaces deprecated create_singlethread_workqueue(). This is the identity conversion. The workqueue "chargalg_wq" is used for running the charging algorithm. It has multiple workitems viz &di->chargalg_periodic_work, &di->chargalg_wd_work, &di->chargalg_work per abx500_chargalg, which require ordering. It has been identity converted. Also, WQ_MEM_RECLAIM has been set to ensure forward progress under memory pressure. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/abx500_chargalg.c b/drivers/power/supply/abx500_chargalg.c index d9104b1..a4411d6 100644 --- a/drivers/power/supply/abx500_chargalg.c +++ b/drivers/power/supply/abx500_chargalg.c @@ -2091,8 +2091,8 @@ static int abx500_chargalg_probe(struct platform_device *pdev) abx500_chargalg_maintenance_timer_expired; /* Create a work queue for the chargalg */ - di->chargalg_wq = - create_singlethread_workqueue("abx500_chargalg_wq"); + 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; -- cgit v0.10.2 From a8dd5b6868dd8ecb79f741dd94ac25a46ac19ba4 Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 21:47:35 +0530 Subject: power: ab8500_btemp: Remove deprecated create_singlethread_workqueue The workqueue "btemp_wq" is used for measuring the temperature periodically. It queues a single workitem (btemp_periodic_work) and hence doesn't require ordering. Thus, the deprecated create_singlethread_workqueue() instance has been replaced with alloc_workqueue(). The WQ_MEM_RECLAIM flag has been set to ensure forward progress under memory pressure. Since there is a single work item, explicit concurrency limit is unnecessary here. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ab8500_btemp.c b/drivers/power/supply/ab8500_btemp.c index bf2e5dd..6ffdc18 100644 --- a/drivers/power/supply/ab8500_btemp.c +++ b/drivers/power/supply/ab8500_btemp.c @@ -1095,7 +1095,7 @@ static int ab8500_btemp_probe(struct platform_device *pdev) /* Create a work queue for the btemp */ di->btemp_wq = - create_singlethread_workqueue("ab8500_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; -- cgit v0.10.2 From d8a69251fb58a756e2dd00cc5c7f1b54d383e203 Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 21:48:43 +0530 Subject: power: pm2301_charger: Remove deprecated create_singlethread_workqueue alloc_ordered_workqueue() with WQ_MEM_RECLAIM set replaces deprecated create_singlethread_workqueue(). This is the identity conversion. The workqueue "charger_wq" is used for running all the charger related tasks. This involves charger detection, checking for HW failure and HW status. This workqueue has been identity converted. It queues multiple workitems viz &pm2->check_main_thermal_prot_work, &pm2->check_hw_failure_work, &pm2->ac_work. Hence, the deprecated create_singlethread_workqueue() instance has been replaced with a dedicated ordered workqueue. The WQ_MEM_RECLAIM flag has been set to ensure forward progress under memory pressure. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/pm2301_charger.c b/drivers/power/supply/pm2301_charger.c index fb62ed3..78561b6 100644 --- a/drivers/power/supply/pm2301_charger.c +++ b/drivers/power/supply/pm2301_charger.c @@ -1054,7 +1054,8 @@ static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, pm2->ac_chg.external = true; /* Create a work queue for the charger */ - pm2->charger_wq = create_singlethread_workqueue("pm2xxx_charger_wq"); + 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"); -- cgit v0.10.2 From 87f818b35c3007d1014541c07688ab29443a22c2 Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 21:50:11 +0530 Subject: power: intel_mid_battery: Remove deprecated create_singlethread_workqueue The workqueue "monitor_wqueue" is used to monitor the PMIC battery status. It queues a single work item (pbi->monitor_battery) and hence doesn't require ordering. Hence, alloc_workqueue has been used to replace the deprecated create_singlethread_workqueue instance. Since PMIC battery status needs to be monitored for any change, the WQ_MEM_RECLAIM flag has been set to ensure forward progress under memory pressure. Since there is a single work item, explicit concurrency limit is unnecessary here. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/intel_mid_battery.c b/drivers/power/supply/intel_mid_battery.c index 9fa4acc..dc7feef 100644 --- a/drivers/power/supply/intel_mid_battery.c +++ b/drivers/power/supply/intel_mid_battery.c @@ -689,8 +689,7 @@ static int probe(int irq, struct device *dev) /* 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 = - create_singlethread_workqueue(dev_name(dev)); + 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; -- cgit v0.10.2 From 9df82628265857e1c491c1ca0ace353e658457ea Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 21:51:23 +0530 Subject: power: ab8500_charger: Remove deprecated create_singlethread_workqueue alloc_ordered_workqueue() with WQ_MEM_RECLAIM set replaces deprecated create_singlethread_workqueue(). This is the identity conversion. The workqueue "charger_wq" is used for the IRQs and checking HW state of the charger. It has been identity converted. It has multiple work items viz usb_charger_attached_work, kick_wd_work, check_vbat_work, check_hw_failure_work, usb_charger_attached_work, ac_work, ac_charger_attached_work, attach_work and check_usbchgnotok_work, which require execution ordering. Hence, a dedicated ordered workqueue has been used here. The WQ_MEM_RECLAIM flag has also been set to ensure forward progress under memory pressure. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ab8500_charger.c b/drivers/power/supply/ab8500_charger.c index 30de5d4..5cee9aa 100644 --- a/drivers/power/supply/ab8500_charger.c +++ b/drivers/power/supply/ab8500_charger.c @@ -3540,8 +3540,8 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->usb_state.usb_current = -1; /* Create a work queue for the charger */ - di->charger_wq = - create_singlethread_workqueue("ab8500_charger_wq"); + 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; -- cgit v0.10.2 From 829f0e97cc03b0c834cc5f86ff72b2ec389020ef Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 22:03:53 +0530 Subject: power: ipaq_micro_battery: Remove deprecated create_singlethread_workqueue The workqueue "wq" is used for handling battery related tasks. It has a single work item viz &mb->update and hence it doesn't require execution ordering. Hence, alloc_workqueue has been used to replace the deprecated create_singlethread_workqueue instance. The WQ_MEM_RECLAIM flag has been set to ensure forward progress under memory pressure. Since there is a single work item, explicit concurrency limit is unnecessary here. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ipaq_micro_battery.c b/drivers/power/supply/ipaq_micro_battery.c index 35b01c7..4af7b77 100644 --- a/drivers/power/supply/ipaq_micro_battery.c +++ b/drivers/power/supply/ipaq_micro_battery.c @@ -235,7 +235,7 @@ static int micro_batt_probe(struct platform_device *pdev) return -ENOMEM; mb->micro = dev_get_drvdata(pdev->dev.parent); - mb->wq = create_singlethread_workqueue("ipaq-battery-wq"); + mb->wq = alloc_workqueue("ipaq-battery-wq", WQ_MEM_RECLAIM, 0); if (!mb->wq) return -ENOMEM; -- cgit v0.10.2 From 1c53f3709cbc0da9fbf83bb10b2e3633ade05875 Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 22:05:00 +0530 Subject: power: ab8500_fg: Remove deprecated create_singlethread_workqueue alloc_ordered_workqueue() with WQ_MEM_RECLAIM set replaces deprecated create_singlethread_workqueue(). This is the identity conversion. The workqueue "fg_wq" is used for running the FG algorithm periodically. It has been identity converted. It has multiple work items viz fg_periodic_work, fg_low_bat_work, fg_reinit_work, fg_work, fg_acc_cur_work and fg_check_hw_failure_work, which require execution ordering. Hence, a dedicated ordered workqueue has been used here. The WQ_MEM_RECLAIM flag has been set to guarantee forward progress under memory pressure. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 5a36cf8..199f2db 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -3096,7 +3096,7 @@ static int ab8500_fg_probe(struct platform_device *pdev) ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_INIT); /* Create a work queue for running the FG algorithm */ - di->fg_wq = create_singlethread_workqueue("ab8500_fg_wq"); + 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; -- cgit v0.10.2 From b732ace40a1d5ea643ee9c28116e829ae950fe8f Mon Sep 17 00:00:00 2001 From: Bhaktipriya Shridhar Date: Sat, 13 Aug 2016 22:06:33 +0530 Subject: power: ds2760_battery: Remove deprecated create_singlethread_workqueue alloc_ordered_workqueue() with WQ_MEM_RECLAIM set replaces deprecated create_singlethread_workqueue(). This is the identity conversion. The workqueue "monitor_wqueue" is used to monitor the battery status. It has been identity converted. It queues multiple work items viz &di->monitor_work, &di->set_charged_work, which require execution ordering. Hence, alloc_workqueue has been used to replace the deprecated create_singlethread_workqueue instance. WQ_MEM_RECLAIM flag has been set to ensure forward progress under memory pressure. Signed-off-by: Bhaktipriya Shridhar Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ds2760_battery.c b/drivers/power/supply/ds2760_battery.c index 369ab00..1722568 100644 --- a/drivers/power/supply/ds2760_battery.c +++ b/drivers/power/supply/ds2760_battery.c @@ -566,7 +566,8 @@ static int ds2760_battery_probe(struct platform_device *pdev) INIT_DELAYED_WORK(&di->monitor_work, ds2760_battery_work); INIT_DELAYED_WORK(&di->set_charged_work, ds2760_battery_set_charged_work); - di->monitor_wqueue = create_singlethread_workqueue(dev_name(&pdev->dev)); + di->monitor_wqueue = alloc_ordered_workqueue(dev_name(&pdev->dev), + WQ_MEM_RECLAIM); if (!di->monitor_wqueue) { retval = -ESRCH; goto workqueue_failed; -- cgit v0.10.2 From 1c4593edbd4a893691fa826c36e71946a5d54c1d Mon Sep 17 00:00:00 2001 From: Krzysztof Kozlowski Date: Wed, 17 Aug 2016 14:07:43 +0200 Subject: power: supply: Change Krzysztof Kozlowski's email to kernel.org Change my email address to kernel.org instead of Samsung one for the purpose of any future contact. The copyrights remain untouched and are attributed to Samsung. Signed-off-by: Krzysztof Kozlowski Signed-off-by: Sebastian Reichel diff --git a/Documentation/ABI/testing/sysfs-class-power b/Documentation/ABI/testing/sysfs-class-power index fa05719..f85ce9e 100644 --- a/Documentation/ABI/testing/sysfs-class-power +++ b/Documentation/ABI/testing/sysfs-class-power @@ -22,7 +22,7 @@ Description: What: /sys/class/power_supply/max14577-charger/device/fast_charge_timer Date: October 2014 KernelVersion: 3.18.0 -Contact: Krzysztof Kozlowski +Contact: Krzysztof Kozlowski Description: This entry shows and sets the maximum time the max14577 charger operates in fast-charge mode. When the timer expires @@ -36,7 +36,7 @@ Description: What: /sys/class/power_supply/max77693-charger/device/fast_charge_timer Date: January 2015 KernelVersion: 3.19.0 -Contact: Krzysztof Kozlowski +Contact: Krzysztof Kozlowski Description: This entry shows and sets the maximum time the max77693 charger operates in fast-charge mode. When the timer expires @@ -50,7 +50,7 @@ Description: What: /sys/class/power_supply/max77693-charger/device/top_off_threshold_current Date: January 2015 KernelVersion: 3.19.0 -Contact: Krzysztof Kozlowski +Contact: Krzysztof Kozlowski Description: This entry shows and sets the charging current threshold for entering top-off charging mode. When charging current in fast @@ -65,7 +65,7 @@ Description: What: /sys/class/power_supply/max77693-charger/device/top_off_timer Date: January 2015 KernelVersion: 3.19.0 -Contact: Krzysztof Kozlowski +Contact: Krzysztof Kozlowski Description: This entry shows and sets the maximum time the max77693 charger operates in top-off charge mode. When the timer expires diff --git a/drivers/power/supply/max14577_charger.c b/drivers/power/supply/max14577_charger.c index a36bcaf..449fc56 100644 --- a/drivers/power/supply/max14577_charger.c +++ b/drivers/power/supply/max14577_charger.c @@ -2,7 +2,7 @@ * max14577_charger.c - Battery charger driver for the Maxim 14577/77836 * * Copyright (C) 2013,2014 Samsung Electronics - * Krzysztof Kozlowski + * Krzysztof Kozlowski * * 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 @@ -643,6 +643,6 @@ static struct platform_driver max14577_charger_driver = { }; module_platform_driver(max14577_charger_driver); -MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_AUTHOR("Krzysztof Kozlowski "); MODULE_DESCRIPTION("Maxim 14577/77836 charger driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/supply/max77693_charger.c b/drivers/power/supply/max77693_charger.c index 060cab5..6c78884 100644 --- a/drivers/power/supply/max77693_charger.c +++ b/drivers/power/supply/max77693_charger.c @@ -2,7 +2,7 @@ * max77693_charger.c - Battery charger driver for the Maxim 77693 * * Copyright (C) 2014 Samsung Electronics - * Krzysztof Kozlowski + * Krzysztof Kozlowski * * 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 @@ -766,6 +766,6 @@ static struct platform_driver max77693_charger_driver = { }; module_platform_driver(max77693_charger_driver); -MODULE_AUTHOR("Krzysztof Kozlowski "); +MODULE_AUTHOR("Krzysztof Kozlowski "); MODULE_DESCRIPTION("Maxim 77693 charger driver"); MODULE_LICENSE("GPL"); -- cgit v0.10.2 From ae0f74be6e3db97b23f33be2219044576525176a Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Sun, 28 Aug 2016 19:34:46 +0200 Subject: power: bq24735-charger: Assume not charging when charger is missing When the charger is missing (disconnected), it is safe to assume that the charger chip is no charging. This is especially relevant when a status GPIO is present and the charger is getting disconnected. bq24735_charger_is_charging will be triggered due to the interrupt then, it will attempt to read whether it is charging through i2c, which will fail as the charger is disconnected. This also fixes that specific issue. Signed-off-by: Paul Kocialkowski Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index fa454c1..dc460bb 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -201,8 +201,12 @@ static bool bq24735_charger_is_present(struct bq24735 *charger) static int bq24735_charger_is_charging(struct bq24735 *charger) { - int ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT); + int ret; + + if (!bq24735_charger_is_present(charger)) + return 0; + ret = bq24735_read_word(charger->client, BQ24735_CHG_OPT); if (ret < 0) return ret; -- cgit v0.10.2 From 58db95056759f2d9e4a41cb3d26007561a801135 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 17 Aug 2016 14:23:05 +0200 Subject: MAINTAINERS: update power-supply git-tree When I took over the power-supply tree, its git tree was hosted on git.infradead.org. Since a few kernel releases I also maintain a copy of the repository on git.kernel.org, that is prefered by Linus, so let's switch over officially. Signed-off-By: Sebastian Reichel diff --git a/MAINTAINERS b/MAINTAINERS index c7d6762..923b1388 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9329,7 +9329,7 @@ M: Sebastian Reichel M: Dmitry Eremin-Solenikov M: David Woodhouse L: linux-pm@vger.kernel.org -T: git git://git.infradead.org/battery-2.6.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained F: Documentation/devicetree/bindings/power/supply/ F: include/linux/power_supply.h @@ -11332,7 +11332,7 @@ M: Sebastian Reichel M: Dmitry Eremin-Solenikov M: David Woodhouse L: linux-pm@vger.kernel.org -T: git git://git.infradead.org/battery-2.6.git +T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained F: Documentation/devicetree/bindings/power/reset/ F: drivers/power/reset/ -- cgit v0.10.2 From 2fe921758f6ebbfd1df73bcf0a9af6a682cef2b5 Mon Sep 17 00:00:00 2001 From: Sebastian Reichel Date: Wed, 17 Aug 2016 14:32:38 +0200 Subject: MAINTAINERS: Remove power-supply co-maintainers This documents the status quo and keeps everyones cc/to-list a bit shorter. Signed-off-by: Sebastian Reichel diff --git a/CREDITS b/CREDITS index 2a3fbcd..2cb28a3 100644 --- a/CREDITS +++ b/CREDITS @@ -1090,6 +1090,10 @@ S: 6350 Stoneridge Mall Road S: Pleasanton, CA 94588 S: USA +N: Dmitry Eremin-Solenikov +E: dbaryshkov@gmail.com +D: Power Supply Maintainer from v3.14 - v3.15 + N: Doug Evans E: dje@cygnus.com D: Wrote Xenix FS (part of standard kernel since 0.99.15) diff --git a/MAINTAINERS b/MAINTAINERS index 923b1388..9074e68 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9326,8 +9326,6 @@ F: drivers/powercap/ POWER SUPPLY CLASS/SUBSYSTEM and DRIVERS M: Sebastian Reichel -M: Dmitry Eremin-Solenikov -M: David Woodhouse L: linux-pm@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained @@ -11329,8 +11327,6 @@ F: drivers/mfd/syscon.c SYSTEM RESET/SHUTDOWN DRIVERS M: Sebastian Reichel -M: Dmitry Eremin-Solenikov -M: David Woodhouse L: linux-pm@vger.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/sre/linux-power-supply.git S: Maintained -- cgit v0.10.2 From 5da643b26d280e1b061e18b92bc7d5709ee8034e Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:19:50 +0800 Subject: power: supply: act8945a_charger: Achieve properties from its node Since the act8945a_charger is regarded as a sub-device, all properties will be assigned to its own device node. All properties can be achieved from its own node, instead of from its parent device. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index b5c00e4..3f486c9 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -328,11 +328,11 @@ static int act8945a_charger_probe(struct platform_device *pdev) return -EINVAL; } - ret = act8945a_charger_config(pdev->dev.parent, charger); + ret = act8945a_charger_config(&pdev->dev, charger); if (ret) return ret; - psy_cfg.of_node = pdev->dev.parent->of_node; + psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = charger; psy = devm_power_supply_register(&pdev->dev, -- cgit v0.10.2 From 6b021fc91038201ed44e99ae32b6a93c6e8be1f4 Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:19:51 +0800 Subject: power: supply: act8945a_charger: Remove "battery_temperature" Remove "battery_temperature" member, it is redundant, it is the hardware's responsibility to handle TH pin properly. It is unnecessary to use the dt property to check if there is a battery temperature monitor or not. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 3f486c9..af00668 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -76,7 +76,6 @@ static const char *act8945a_charger_manufacturer = "Active-semi"; struct act8945a_charger { struct regmap *regmap; - bool battery_temperature; }; static int act8945a_get_charger_state(struct regmap *regmap, int *val) @@ -138,8 +137,7 @@ static int act8945a_get_charge_type(struct regmap *regmap, int *val) return 0; } -static int act8945a_get_battery_health(struct act8945a_charger *charger, - struct regmap *regmap, int *val) +static int act8945a_get_battery_health(struct regmap *regmap, int *val) { int ret; unsigned int status; @@ -148,7 +146,7 @@ static int act8945a_get_battery_health(struct act8945a_charger *charger, if (ret < 0) return ret; - if (charger->battery_temperature && !(status & APCH_STATUS_TEMPDAT)) + if (!(status & APCH_STATUS_TEMPDAT)) *val = POWER_SUPPLY_HEALTH_OVERHEAT; else if (!(status & APCH_STATUS_INDAT)) *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; @@ -188,8 +186,7 @@ static int act8945a_charger_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_TECHNOLOGY_LION; break; case POWER_SUPPLY_PROP_HEALTH: - ret = act8945a_get_battery_health(charger, - regmap, &val->intval); + ret = act8945a_get_battery_health(regmap, &val->intval); break; case POWER_SUPPLY_PROP_MODEL_NAME: val->strval = act8945a_charger_model; @@ -235,9 +232,6 @@ static int act8945a_charger_config(struct device *dev, return -EINVAL; } - charger->battery_temperature = of_property_read_bool(np, - "active-semi,check-battery-temperature"); - chglev_pin = of_get_named_gpio_flags(np, "active-semi,chglev-gpios", 0, &flags); -- cgit v0.10.2 From 1f0ba4067af4bda55b7304e74fc20a245912b728 Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:19:52 +0800 Subject: power: supply: act8945a_charger: Improve state handling When get the property, first check the charger state machine, then check the status bit to decide what value is assigned to the corresponding property. Retain the SUSCHG bit of REG 0x71 when configure the timers to avoid losting the charger suspending info after boot. Signed-off-by: Wenyou Yang Signed-off-by: Fengguang Wu Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index af00668..a6bbaff 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -94,16 +94,24 @@ static int act8945a_get_charger_state(struct regmap *regmap, int *val) state &= APCH_STATE_CSTATE; state >>= APCH_STATE_CSTATE_SHIFT; - if (state == APCH_STATE_CSTATE_EOC) { + 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; - } else if ((state == APCH_STATE_CSTATE_FAST) || - (state == APCH_STATE_CSTATE_PRE)) { - *val = POWER_SUPPLY_STATUS_CHARGING; - } else { - *val = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; } return 0; @@ -112,7 +120,11 @@ static int act8945a_get_charger_state(struct regmap *regmap, int *val) static int act8945a_get_charge_type(struct regmap *regmap, int *val) { int ret; - unsigned int state; + 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) @@ -129,9 +141,15 @@ static int act8945a_get_charge_type(struct regmap *regmap, int *val) *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: - *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + if (!(status & APCH_STATUS_INDAT)) + *val = POWER_SUPPLY_CHARGE_TYPE_NONE; + else + *val = POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + break; } return 0; @@ -140,20 +158,45 @@ static int act8945a_get_charge_type(struct regmap *regmap, int *val) static int act8945a_get_battery_health(struct regmap *regmap, int *val) { int ret; - unsigned int status; + unsigned int status, state, config; ret = regmap_read(regmap, ACT8945A_APCH_STATUS, &status); if (ret < 0) return ret; - if (!(status & APCH_STATUS_TEMPDAT)) - *val = POWER_SUPPLY_HEALTH_OVERHEAT; - else if (!(status & APCH_STATUS_INDAT)) - *val = POWER_SUPPLY_HEALTH_OVERVOLTAGE; - else if (status & APCH_STATUS_TIMRDAT) - *val = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; - else + 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; } @@ -224,7 +267,9 @@ static int act8945a_charger_config(struct device *dev, u32 pre_time_out; u32 input_voltage_threshold; int chglev_pin; + int ret; + unsigned int tmp; unsigned int value = 0; if (!np) { @@ -232,6 +277,15 @@ static int act8945a_charger_config(struct device *dev, 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"); + } + chglev_pin = of_get_named_gpio_flags(np, "active-semi,chglev-gpios", 0, &flags); -- cgit v0.10.2 From a09209acd6a808794bdd7866af3678d1fd1d90e7 Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:19:53 +0800 Subject: power: supply: act8945a_charger: Add status change update support Add the charger status change interrupt support, it will report the power supply changed event. This interrupt is generated by one of the conditions as below: - the state machine jumps out of or into the EOC state - the CHGIN input voltage goes out of or into the valid range. - the battery temperature goes out of or into the valid range. - the PRECHARGE time-out occurs. - the total charge time-out occurs. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index a6bbaff..e129255 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -10,9 +10,11 @@ * published by the Free Software Foundation. * */ +#include #include #include #include +#include #include #include #include @@ -75,7 +77,11 @@ static const char *act8945a_charger_manufacturer = "Active-semi"; #define APCH_STATE_CSTATE_PRE 0x03 struct act8945a_charger { + struct power_supply *psy; struct regmap *regmap; + struct work_struct work; + + bool init_done; }; static int act8945a_get_charger_state(struct regmap *regmap, int *val) @@ -252,6 +258,47 @@ static const struct power_supply_desc act8945a_charger_desc = { .num_properties = ARRAY_SIZE(act8945a_charger_props), }; +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 void act8945a_work(struct work_struct *work) +{ + struct act8945a_charger *charger = + container_of(work, struct act8945a_charger, work); + + 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 @@ -362,9 +409,8 @@ static int act8945a_charger_config(struct device *dev, static int act8945a_charger_probe(struct platform_device *pdev) { struct act8945a_charger *charger; - struct power_supply *psy; struct power_supply_config psy_cfg = {}; - int ret; + int irq, ret; charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); if (!charger) @@ -380,17 +426,51 @@ static int act8945a_charger_probe(struct platform_device *pdev) 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; + } + psy_cfg.of_node = pdev->dev.of_node; psy_cfg.drv_data = charger; - psy = devm_power_supply_register(&pdev->dev, - &act8945a_charger_desc, - &psy_cfg); - if (IS_ERR(psy)) { + charger->psy = devm_power_supply_register(&pdev->dev, + &act8945a_charger_desc, + &psy_cfg); + if (IS_ERR(charger->psy)) { dev_err(&pdev->dev, "failed to register power supply\n"); - return PTR_ERR(psy); + 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; } @@ -399,6 +479,7 @@ static struct platform_driver act8945a_charger_driver = { .name = "act8945a-charger", }, .probe = act8945a_charger_probe, + .remove = act8945a_charger_remove, }; module_platform_driver(act8945a_charger_driver); -- cgit v0.10.2 From 10ca08b07bc572ff39bd95148085994e9958b10b Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:19:54 +0800 Subject: power: supply: act8945a_charger: Fix the power supply type The power supply type property is varying as the external power supply changes. It is not a constant. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index e129255..48775f8 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -78,6 +78,7 @@ static const char *act8945a_charger_manufacturer = "Active-semi"; struct act8945a_charger { struct power_supply *psy; + struct power_supply_desc desc; struct regmap *regmap; struct work_struct work; @@ -250,14 +251,6 @@ static int act8945a_charger_get_property(struct power_supply *psy, return ret; } -static const struct power_supply_desc act8945a_charger_desc = { - .name = "act8945a-charger", - .type = POWER_SUPPLY_TYPE_BATTERY, - .get_property = act8945a_charger_get_property, - .properties = act8945a_charger_props, - .num_properties = ARRAY_SIZE(act8945a_charger_props), -}; - static int act8945a_enable_interrupt(struct act8945a_charger *charger) { struct regmap *regmap = charger->regmap; @@ -281,11 +274,39 @@ static int act8945a_enable_interrupt(struct act8945a_charger *charger) 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); } @@ -440,11 +461,20 @@ static int act8945a_charger_probe(struct platform_device *pdev) 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, - &act8945a_charger_desc, + &charger->desc, &psy_cfg); if (IS_ERR(charger->psy)) { dev_err(&pdev->dev, "failed to register power supply\n"); -- cgit v0.10.2 From 30953650801a8c83fc96acdc032e0a0dbb018b07 Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 25 Aug 2016 15:40:26 +0800 Subject: doc: bindings: power: act8945a-charger: Update properties. Due the driver improvements, update the properties, - Remove "active-semi,check-battery-temperature" property. - Add the properties, "active-semi,irq_gpio" and "active-semi,lbo-gpios". - As act8945a-charger is regarded as a sub-device, update the compatible and examples. Signed-off-by: Wenyou Yang Acked-by: Rob Herring Signed-off-by: Sebastian Reichel diff --git a/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt b/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt index bea254c..de78d76 100644 --- a/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt +++ b/Documentation/devicetree/bindings/power/supply/act8945a-charger.txt @@ -1,13 +1,18 @@ Device-Tree bindings for charger of Active-semi ACT8945A Multi-Function Device Required properties: - - compatible: "active-semi,act8945a", please refer to ../mfd/act8945a.txt. + - compatible: "active-semi,act8945a-charger". - active-semi,chglev-gpios: charge current level phandle with args as described in ../gpio/gpio.txt. + - active-semi,lbo-gpios: specify the low battery voltage detect phandle + with args as as described in ../gpio/gpio.txt. + - interrupts: where a is the interrupt number and b is a + field that represents an encoding of the sense and level + information for the interrupt. + - interrupt-parent: the phandle for the interrupt controller that + services interrupts for this device. Optional properties: - - active-semi,check-battery-temperature: boolean to check the battery - temperature or not. - active-semi,input-voltage-threshold-microvolt: unit: mV; Specifies the charger's input over-voltage threshold value; The value can be: 6600, 7000, 7500, 8000; default: 6600 @@ -26,10 +31,18 @@ Example: reg = <0x5b>; status = "okay"; - pinctrl-names = "default"; - pinctrl-0 = <&pinctrl_charger_chglev>; - active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; - active-semi,input-voltage-threshold-microvolt = <6600>; - active-semi,precondition-timeout = <40>; - active-semi,total-timeout = <3>; + charger { + compatible = "active-semi,act8945a-charger"; + pinctrl-names = "default"; + pinctrl-0 = <&pinctrl_charger_chglev &pinctrl_charger_lbo &pinctrl_charger_irq>; + interrupt-parent = <&pioA>; + interrupts = <45 GPIO_ACTIVE_LOW>; + + active-semi,chglev-gpios = <&pioA 12 GPIO_ACTIVE_HIGH>; + active-semi,lbo-gpios = <&pioA 72 GPIO_ACTIVE_LOW>; + active-semi,input-voltage-threshold-microvolt = <6600>; + active-semi,precondition-timeout = <40>; + active-semi,total-timeout = <3>; + status = "okay"; + }; }; -- cgit v0.10.2 From 369eba0986d503302784f55f1020129bf802ae72 Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 1 Sep 2016 17:29:58 +0800 Subject: power: supply: act8945a_charger: Add capacity level property Add the power supply capacity level property, it corresponds to POWER_SUPPLY_CAPACITY_LEVEL_*. It also utilizes the precision voltage detector function module to catch the low battery voltage. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 48775f8..2f87599 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -18,6 +18,7 @@ #include #include #include +#include static const char *act8945a_charger_model = "ACT8945A"; static const char *act8945a_charger_manufacturer = "Active-semi"; @@ -83,6 +84,7 @@ struct act8945a_charger { struct work_struct work; bool init_done; + struct gpio_desc *lbo_gpio; }; static int act8945a_get_charger_state(struct regmap *regmap, int *val) @@ -208,11 +210,67 @@ static int act8945a_get_battery_health(struct regmap *regmap, int *val) 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; +} + 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_MODEL_NAME, POWER_SUPPLY_PROP_MANUFACTURER }; @@ -238,6 +296,10 @@ static int act8945a_charger_get_property(struct power_supply *psy, 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_MODEL_NAME: val->strval = act8945a_charger_model; break; @@ -335,7 +397,7 @@ static int act8945a_charger_config(struct device *dev, u32 pre_time_out; u32 input_voltage_threshold; int chglev_pin; - int ret; + int err, ret; unsigned int tmp; unsigned int value = 0; @@ -354,6 +416,21 @@ static int act8945a_charger_config(struct device *dev, 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"); + chglev_pin = of_get_named_gpio_flags(np, "active-semi,chglev-gpios", 0, &flags); -- cgit v0.10.2 From 528e3504123d0281c613b83ca46aaf2dd7c3f45e Mon Sep 17 00:00:00 2001 From: Wenyou Yang Date: Thu, 1 Sep 2016 17:29:59 +0800 Subject: power: supply: act8945a_charger: Add max current property Add the power supply's current max property, POWER_SUPPLY_PROP_CURRENT_MAX. Signed-off-by: Wenyou Yang Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/act8945a_charger.c b/drivers/power/supply/act8945a_charger.c index 2f87599..d1eb2e3 100644 --- a/drivers/power/supply/act8945a_charger.c +++ b/drivers/power/supply/act8945a_charger.c @@ -13,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -85,6 +84,7 @@ struct act8945a_charger { bool init_done; struct gpio_desc *lbo_gpio; + struct gpio_desc *chglev_gpio; }; static int act8945a_get_charger_state(struct regmap *regmap, int *val) @@ -265,12 +265,80 @@ static int act8945a_get_capacity_level(struct act8945a_charger *charger, 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 }; @@ -300,6 +368,10 @@ static int act8945a_charger_get_property(struct power_supply *psy, 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; @@ -390,13 +462,11 @@ static int act8945a_charger_config(struct device *dev, struct act8945a_charger *charger) { struct device_node *np = dev->of_node; - enum of_gpio_flags flags; struct regmap *regmap = charger->regmap; u32 total_time_out; u32 pre_time_out; u32 input_voltage_threshold; - int chglev_pin; int err, ret; unsigned int tmp; @@ -431,12 +501,13 @@ static int act8945a_charger_config(struct device *dev, if (ret) dev_info(dev, "failed to request gpio \"lbo\" IRQ\n"); - chglev_pin = of_get_named_gpio_flags(np, - "active-semi,chglev-gpios", 0, &flags); - - if (gpio_is_valid(chglev_pin)) { - gpio_set_value(chglev_pin, - ((flags == OF_GPIO_ACTIVE_LOW) ? 0 : 1)); + 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, -- cgit v0.10.2 From 3b5dd3a49496220b35af83c96e3d2ff5716550ae Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Thu, 1 Sep 2016 15:50:52 +0800 Subject: power: supply: sbs-battery: Use gpio_desc and sleeping calls for battery detect Switch to using new gpio_desc interface and devm gpio get calls to automatically manage gpio resource. Use gpiod_get_value which handles active high / low calls. If gpio_detect is set then force loading of the driver as it is reasonable to assume that the battery may not be present. Update the is_present flag immediately in the IRQ. Remove legacy gpio specification from platform data. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index e1dfb3e..8b4c6a8 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include @@ -165,7 +165,7 @@ struct sbs_info { struct power_supply *power_supply; struct sbs_platform_data *pdata; bool is_present; - bool gpio_detect; + struct gpio_desc *gpio_detect; bool enable_detection; int last_state; int poll_time; @@ -306,13 +306,11 @@ static int sbs_get_battery_presence_and_health( s32 ret; struct sbs_info *chip = i2c_get_clientdata(client); - if (psp == POWER_SUPPLY_PROP_PRESENT && - chip->gpio_detect) { - ret = gpio_get_value(chip->pdata->battery_detect); - if (ret == chip->pdata->battery_detect_present) - val->intval = 1; - else - val->intval = 0; + 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; } @@ -683,7 +681,12 @@ 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; @@ -750,12 +753,11 @@ static const struct of_device_id sbs_dt_ids[] = { }; MODULE_DEVICE_TABLE(of, sbs_dt_ids); -static struct sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) +static struct sbs_platform_data *sbs_of_populate_pdata(struct sbs_info *chip) { + struct i2c_client *client = chip->client; struct device_node *of_node = client->dev.of_node; - struct sbs_platform_data *pdata = client->dev.platform_data; - enum of_gpio_flags gpio_flags; + struct sbs_platform_data *pdata; int rc; u32 prop; @@ -763,22 +765,17 @@ static struct sbs_platform_data *sbs_of_populate_pdata( if (!of_node) return NULL; - /* if platform data is set, honor it */ - if (pdata) - return pdata; - /* first make sure at least one property is set, otherwise * it won't change behavior from running without pdata. */ if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && - !of_get_property(of_node, "sbs,poll-retry-count", NULL) && - !of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) + !of_get_property(of_node, "sbs,poll-retry-count", NULL)) goto of_out; pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), GFP_KERNEL); if (!pdata) - goto of_out; + return ERR_PTR(-ENOMEM); rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); if (!rc) @@ -788,27 +785,14 @@ static struct sbs_platform_data *sbs_of_populate_pdata( if (!rc) pdata->poll_retry_count = prop; - if (!of_get_property(of_node, "sbs,battery-detect-gpios", NULL)) { - pdata->battery_detect = -1; - goto of_out; - } - - pdata->battery_detect = of_get_named_gpio_flags(of_node, - "sbs,battery-detect-gpios", 0, &gpio_flags); - - if (gpio_flags & OF_GPIO_ACTIVE_LOW) - pdata->battery_detect_present = 0; - else - pdata->battery_detect_present = 1; - of_out: return pdata; } #else static struct sbs_platform_data *sbs_of_populate_pdata( - struct i2c_client *client) + struct sbs_info *client) { - return client->dev.platform_data; + return ERR_PTR(-ENOSYS); } #endif @@ -846,7 +830,6 @@ static int sbs_probe(struct i2c_client *client, chip->client = client; chip->enable_detection = false; - chip->gpio_detect = false; psy_cfg.of_node = client->dev.of_node; psy_cfg.drv_data = chip; /* ignore first notification of external change, it is generated @@ -855,11 +838,20 @@ static int sbs_probe(struct i2c_client *client, chip->ignore_changes = 1; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; - pdata = sbs_of_populate_pdata(client); + if (!pdata) + pdata = sbs_of_populate_pdata(chip); + + if (IS_ERR(pdata)) + return PTR_ERR(pdata); + + chip->pdata = pdata; - if (pdata) { - chip->gpio_detect = gpio_is_valid(pdata->battery_detect); - chip->pdata = pdata; + 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); @@ -867,26 +859,9 @@ static int sbs_probe(struct i2c_client *client, if (!chip->gpio_detect) goto skip_gpio; - rc = gpio_request(pdata->battery_detect, dev_name(&client->dev)); - if (rc) { - dev_warn(&client->dev, "Failed to request gpio: %d\n", rc); - chip->gpio_detect = false; - goto skip_gpio; - } - - rc = gpio_direction_input(pdata->battery_detect); - if (rc) { - dev_warn(&client->dev, "Failed to get gpio as input: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; - goto skip_gpio; - } - - irq = gpio_to_irq(pdata->battery_detect); + irq = gpiod_to_irq(chip->gpio_detect); if (irq <= 0) { dev_warn(&client->dev, "Failed to get gpio as irq: %d\n", irq); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; goto skip_gpio; } @@ -895,8 +870,6 @@ static int sbs_probe(struct i2c_client *client, dev_name(&client->dev), chip); if (rc) { dev_warn(&client->dev, "Failed to request irq: %d\n", rc); - gpio_free(pdata->battery_detect); - chip->gpio_detect = false; goto skip_gpio; } @@ -905,7 +878,7 @@ skip_gpio: * Before we register, we might need to make sure we can actually talk * to the battery. */ - if (!force_load) { + if (!(force_load || chip->gpio_detect)) { rc = sbs_read_word_data(client, sbs_data[REG_STATUS].addr); if (rc < 0) { @@ -934,9 +907,6 @@ skip_gpio: return 0; exit_psupply: - if (chip->gpio_detect) - gpio_free(pdata->battery_detect); - return rc; } @@ -944,9 +914,6 @@ static int sbs_remove(struct i2c_client *client) { struct sbs_info *chip = i2c_get_clientdata(client); - if (chip->gpio_detect) - gpio_free(chip->pdata->battery_detect); - cancel_delayed_work_sync(&chip->work); return 0; diff --git a/include/linux/power/sbs-battery.h b/include/linux/power/sbs-battery.h index 2b0a9d9..811f1a0 100644 --- a/include/linux/power/sbs-battery.h +++ b/include/linux/power/sbs-battery.h @@ -26,15 +26,11 @@ /** * struct sbs_platform_data - platform data for sbs devices - * @battery_detect: GPIO which is used to detect battery presence - * @battery_detect_present: gpio state when battery is present (0 / 1) * @i2c_retry_count: # of times to retry on i2c IO failure * @poll_retry_count: # of times to retry looking for new status after * external change notification */ struct sbs_platform_data { - int battery_detect; - int battery_detect_present; int i2c_retry_count; int poll_retry_count; }; -- cgit v0.10.2 From c65a8b51123a14f6960e4238bfa4673d54ee183a Mon Sep 17 00:00:00 2001 From: Paul Kocialkowski Date: Sat, 3 Sep 2016 00:09:53 +0200 Subject: power: supply: bq24735-charger: Request status GPIO with initial input setup This requests the status GPIO with initial input setup. It is required to read the GPIO status at probe time and thus correctly avoid sending I2C messages when AC is not plugged. When requesting the GPIO without initial input setup, it always reads 0 which causes probe to fail as it assumes the charger is connected, sends I2C messages and fails. While at it, this switches the driver over to gpiod API. Signed-off-by: Paul Kocialkowski Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/bq24735-charger.c b/drivers/power/supply/bq24735-charger.c index dc460bb..eb7783b 100644 --- a/drivers/power/supply/bq24735-charger.c +++ b/drivers/power/supply/bq24735-charger.c @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include @@ -49,6 +49,7 @@ struct bq24735 { struct i2c_client *client; struct bq24735_platform *pdata; struct mutex lock; + struct gpio_desc *status_gpio; bool charging; }; @@ -177,12 +178,8 @@ static int bq24735_config_charger(struct bq24735 *charger) static bool bq24735_charger_is_present(struct bq24735 *charger) { - struct bq24735_platform *pdata = charger->pdata; - int ret; - - if (pdata->status_gpio_valid) { - ret = gpio_get_value_cansleep(pdata->status_gpio); - return ret ^= pdata->status_gpio_active_low == 0; + if (charger->status_gpio) { + return !gpiod_get_value_cansleep(charger->status_gpio); } else { int ac = 0; @@ -308,7 +305,6 @@ static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client) struct device_node *np = client->dev.of_node; u32 val; int ret; - enum of_gpio_flags flags; pdata = devm_kzalloc(&client->dev, sizeof(*pdata), GFP_KERNEL); if (!pdata) { @@ -317,12 +313,6 @@ static struct bq24735_platform *bq24735_parse_dt_data(struct i2c_client *client) return NULL; } - pdata->status_gpio = of_get_named_gpio_flags(np, "ti,ac-detect-gpios", - 0, &flags); - - if (flags & OF_GPIO_ACTIVE_LOW) - pdata->status_gpio_active_low = 1; - ret = of_property_read_u32(np, "ti,charge-current", &val); if (!ret) pdata->charge_current = val; @@ -396,21 +386,16 @@ static int bq24735_charger_probe(struct i2c_client *client, i2c_set_clientdata(client, charger); - if (gpio_is_valid(charger->pdata->status_gpio)) { - ret = devm_gpio_request(&client->dev, - charger->pdata->status_gpio, - name); - if (ret) { - dev_err(&client->dev, - "Failed GPIO request for GPIO %d: %d\n", - charger->pdata->status_gpio, ret); - } - - charger->pdata->status_gpio_valid = !ret; + 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->pdata->status_gpio_valid - || bq24735_charger_is_present(charger)) { + 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", diff --git a/include/linux/power/bq24735-charger.h b/include/linux/power/bq24735-charger.h index 6b750c1a..b04be59 100644 --- a/include/linux/power/bq24735-charger.h +++ b/include/linux/power/bq24735-charger.h @@ -28,10 +28,6 @@ struct bq24735_platform { const char *name; - int status_gpio; - int status_gpio_active_low; - bool status_gpio_valid; - bool ext_control; char **supplied_to; -- cgit v0.10.2 From 9edeaada19a21eb669ae0dfe749be88f1810ea92 Mon Sep 17 00:00:00 2001 From: Arnd Bergmann Date: Tue, 6 Sep 2016 15:16:33 +0200 Subject: power: supply: sbs-battery: simplify DT parsing After the change to use the gpio descriptor interface, we get a warning if -Wmaybe-uninitialized is added back to the build flags (it is currently disabled: drivers/power/supply/sbs-battery.c: In function 'sbs_probe': drivers/power/supply/sbs-battery.c:760:28: error: 'pdata' may be used uninitialized in this function [-Werror=maybe-uninitialized] The problem is that if neither the DT properties nor a platform_data pointer are provided, the chip->pdata pointer gets set to an uninitialized value. Looking at the code some more, I found that the sbs_of_populate_pdata function is more complex than necessary and has confusing calling conventions of possibly returning a valid pointer, a NULL pointer or an ERR_PTR pointer (in addition to the uninitialized pointer). To fix all of that, this gets rid of the chip->pdata pointer and simply moves the two integers into the sbs_info structure. This makes it much clearer from reading sbs_probe() what the precedence of the three possible values are (pdata, DT, hardcoded defaults) and completely avoids the #ifdef CONFIG_OF guards as of_property_read_u32() gets replaced with a compile-time stub when that is disabled, and returns an error if passed a NULL of_node pointer. Signed-off-by: Arnd Bergmann Fixes: 3b5dd3a49496 ("power: supply: sbs-battery: Use gpio_desc and sleeping calls for battery detect") Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 8b4c6a8..248a5dd 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -169,6 +169,8 @@ struct sbs_info { bool enable_detection; int last_state; int poll_time; + int i2c_retry_count; + int poll_retry_count; struct delayed_work work; int ignore_changes; }; @@ -184,7 +186,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) int retries = 1; if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); + retries = max(chip->i2c_retry_count + 1, 1); while (retries > 0) { ret = i2c_smbus_read_word_data(client, address); @@ -212,8 +214,8 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; if (chip->pdata) { - retries_length = max(chip->pdata->i2c_retry_count + 1, 1); - retries_block = max(chip->pdata->i2c_retry_count + 1, 1); + retries_length = max(chip->i2c_retry_count + 1, 1); + retries_block = max(chip->i2c_retry_count + 1, 1); } /* Adapter needs to support these two functions */ @@ -279,7 +281,7 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address, int retries = 1; if (chip->pdata) - retries = max(chip->pdata->i2c_retry_count + 1, 1); + retries = max(chip->i2c_retry_count + 1, 1); while (retries > 0) { ret = i2c_smbus_write_word_data(client, address, @@ -741,61 +743,6 @@ static void sbs_delayed_work(struct work_struct *work) } } -#if defined(CONFIG_OF) - -#include -#include - -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 sbs_platform_data *sbs_of_populate_pdata(struct sbs_info *chip) -{ - struct i2c_client *client = chip->client; - struct device_node *of_node = client->dev.of_node; - struct sbs_platform_data *pdata; - int rc; - u32 prop; - - /* verify this driver matches this device */ - if (!of_node) - return NULL; - - /* first make sure at least one property is set, otherwise - * it won't change behavior from running without pdata. - */ - if (!of_get_property(of_node, "sbs,i2c-retry-count", NULL) && - !of_get_property(of_node, "sbs,poll-retry-count", NULL)) - goto of_out; - - pdata = devm_kzalloc(&client->dev, sizeof(struct sbs_platform_data), - GFP_KERNEL); - if (!pdata) - return ERR_PTR(-ENOMEM); - - rc = of_property_read_u32(of_node, "sbs,i2c-retry-count", &prop); - if (!rc) - pdata->i2c_retry_count = prop; - - rc = of_property_read_u32(of_node, "sbs,poll-retry-count", &prop); - if (!rc) - pdata->poll_retry_count = prop; - -of_out: - return pdata; -} -#else -static struct sbs_platform_data *sbs_of_populate_pdata( - struct sbs_info *client) -{ - return ERR_PTR(-ENOSYS); -} -#endif - static const struct power_supply_desc sbs_default_desc = { .type = POWER_SUPPLY_TYPE_BATTERY, .properties = sbs_properties, @@ -838,13 +785,23 @@ static int sbs_probe(struct i2c_client *client, chip->ignore_changes = 1; chip->last_state = POWER_SUPPLY_STATUS_UNKNOWN; - if (!pdata) - pdata = sbs_of_populate_pdata(chip); - - if (IS_ERR(pdata)) - return PTR_ERR(pdata); - - chip->pdata = pdata; + /* 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 = 1; + + 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->gpio_detect = devm_gpiod_get_optional(&client->dev, "sbs,battery-detect", GPIOD_IN); @@ -953,13 +910,20 @@ static const struct i2c_device_id sbs_id[] = { }; 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 = of_match_ptr(sbs_dt_ids), + .of_match_table = sbs_dt_ids, .pm = SBS_PM_OPS, }, }; -- cgit v0.10.2 From 0610735928ee47870e083d5901caa371089216f1 Mon Sep 17 00:00:00 2001 From: Georges Savoundararadj Date: Wed, 7 Sep 2016 18:38:15 -0700 Subject: power: bq24257: Fix use of uninitialized pointer bq->charger bq->charger is initialized in bq24257_power_supply_init. Therefore, bq24257_power_supply_init should be called before the registration of the IRQ handler bq24257_irq_handler_thread that calls power_supply_changed(bq->charger). Signed-off-by: Georges Savoundararadj Cc: Aurelien Chanot Cc: Andreas Dannenberg Cc: Sebastian Reichel Cc: David Woodhouse Fixes: 2219a935963e ("power_supply: Add TI BQ24257 charger driver") Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/bq24257_charger.c b/drivers/power/supply/bq24257_charger.c index 1fea2c7..6fc31bd 100644 --- a/drivers/power/supply/bq24257_charger.c +++ b/drivers/power/supply/bq24257_charger.c @@ -1068,6 +1068,12 @@ static int bq24257_probe(struct i2c_client *client, 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 | @@ -1078,12 +1084,6 @@ static int bq24257_probe(struct i2c_client *client, return ret; } - ret = bq24257_power_supply_init(bq); - if (ret < 0) { - dev_err(dev, "Failed to register power supply\n"); - 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"); -- cgit v0.10.2 From 17c6d3979e5bbff1de36a4e89015fa09ac495d27 Mon Sep 17 00:00:00 2001 From: Guenter Roeck Date: Thu, 8 Sep 2016 19:10:00 -0700 Subject: sbs-battery: make writes to ManufacturerAccess optional According to the Smart Battery Data Specification, the use of ManufacturerAcess (register 0x0) is implementation-defined. It appears that some batteries use writes to this register in order to implement certain functionality, but others may simply NAK all writes to it. As a result, write failures to ManufacturerAccess should not be used as an indicator of battery presence, nor as a failure to enter sleep mode. The failed write access was seen with SANYO AP13J3K. Cc: Brian Norris Signed-off-by: Guenter Roeck Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index 248a5dd..bc7acdf 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -317,21 +317,22 @@ static int sbs_get_battery_presence_and_health( return ret; } - /* Write to ManufacturerAccess with - * ManufacturerAccess command and then - * read the status */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, - MANUFACTURER_ACCESS_STATUS); + /* + * 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; } - ret = sbs_read_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr); - if (ret < 0) - return ret; - if (ret < sbs_data[REG_MANUFACTURER_DATA].min_value || ret > sbs_data[REG_MANUFACTURER_DATA].max_value) { val->intval = 0; @@ -882,16 +883,16 @@ static int sbs_suspend(struct device *dev) { struct i2c_client *client = to_i2c_client(dev); struct sbs_info *chip = i2c_get_clientdata(client); - s32 ret; if (chip->poll_time > 0) cancel_delayed_work_sync(&chip->work); - /* write to manufacturer access with sleep command */ - ret = sbs_write_word_data(client, sbs_data[REG_MANUFACTURER_DATA].addr, + /* + * 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); - if (chip->is_present && ret < 0) - return ret; return 0; } -- cgit v0.10.2 From 1dff6ce0262bc42beb801527bd4725c6b8af0683 Mon Sep 17 00:00:00 2001 From: Colin Ian King Date: Mon, 12 Sep 2016 14:05:04 +0100 Subject: power: reset: add in missing white space in error message text A dev_err message spans two lines and the literal string is missing a white space between words. Add the white space. Signed-off-by: Colin Ian King Signed-off-by: Sebastian Reichel diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c index c70f1bf..0938085 100644 --- a/drivers/power/reset/keystone-reset.c +++ b/drivers/power/reset/keystone-reset.c @@ -139,7 +139,7 @@ static int rsctrl_probe(struct platform_device *pdev) } if (val >= WDT_MUX_NUMBER) { - dev_err(dev, "ti,wdt-list property can contain" + dev_err(dev, "ti,wdt-list property can contain " "only numbers < 4\n"); return -EINVAL; } -- cgit v0.10.2 From f04f7aef7f6aafdc0ba54c6b9670cd11f9d9200a Mon Sep 17 00:00:00 2001 From: Masahiro Yamada Date: Tue, 13 Sep 2016 03:23:21 +0900 Subject: power: supply: ab8500: cleanup with list_first_entry_or_null() The combo of list_empty() check and return list_first_entry() can be replaced with list_first_entry_or_null(). Signed-off-by: Masahiro Yamada Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/ab8500_fg.c b/drivers/power/supply/ab8500_fg.c index 199f2db..2199f67 100644 --- a/drivers/power/supply/ab8500_fg.c +++ b/drivers/power/supply/ab8500_fg.c @@ -245,13 +245,8 @@ static LIST_HEAD(ab8500_fg_list); */ struct ab8500_fg *ab8500_fg_get(void) { - struct ab8500_fg *fg; - - if (list_empty(&ab8500_fg_list)) - return NULL; - - fg = list_first_entry(&ab8500_fg_list, struct ab8500_fg, node); - return fg; + return list_first_entry_or_null(&ab8500_fg_list, struct ab8500_fg, + node); } /* Main battery properties */ -- cgit v0.10.2 From 896af83ef69b0a325816fe270440baa27238f531 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Wed, 14 Sep 2016 16:25:39 +0530 Subject: power: reset: xgene-reboot: Unmap region obtained by of_iomap Free memory mapping, if probe is not successful. Signed-off-by: Arvind Yadav Signed-off-by: Sebastian Reichel diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c index f07e93c..73c3d93 100644 --- a/drivers/power/reset/xgene-reboot.c +++ b/drivers/power/reset/xgene-reboot.c @@ -81,8 +81,10 @@ static int xgene_reboot_probe(struct platform_device *pdev) ctx->restart_handler.notifier_call = xgene_restart_handler; ctx->restart_handler.priority = 128; err = register_restart_handler(&ctx->restart_handler); - if (err) + if (err) { + iounmap(ctx->csr); dev_err(dev, "cannot register restart handler (err=%d)\n", err); + } return err; } -- cgit v0.10.2 From 7531be5cdfb1e81d25c299be0bcf8e615474e227 Mon Sep 17 00:00:00 2001 From: Arvind Yadav Date: Wed, 14 Sep 2016 16:35:31 +0530 Subject: power: reset: zx-reboot: Unmap region obtained by of_iomap Free memory mapping, if probe is not successful. Signed-off-by: Arvind Yadav Signed-off-by: Sebastian Reichel diff --git a/drivers/power/reset/zx-reboot.c b/drivers/power/reset/zx-reboot.c index a5b0096..b0b1eb3 100644 --- a/drivers/power/reset/zx-reboot.c +++ b/drivers/power/reset/zx-reboot.c @@ -58,9 +58,12 @@ static int zx_reboot_probe(struct platform_device *pdev) } err = register_restart_handler(&zx_restart_nb); - if (err) + if (err) { + iounmap(base); + iounmap(pcu_base); dev_err(&pdev->dev, "Register restart handler failed(err=%d)\n", err); + } return err; } -- cgit v0.10.2 From 8ad5d85efd813281046cb87366c350321a2d64ca Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 14 Sep 2016 14:27:54 +0100 Subject: power: reset: st-poweroff: Remove obsolete platforms. This patch removes support for STiH415/6 SoC's from the st-poweroff driver, as support for these platforms is being removed from the kernel. Signed-off-by: Peter Griffin Cc: Signed-off-by: Sebastian Reichel diff --git a/drivers/power/reset/st-poweroff.c b/drivers/power/reset/st-poweroff.c index a488877..2046b31 100644 --- a/drivers/power/reset/st-poweroff.c +++ b/drivers/power/reset/st-poweroff.c @@ -28,28 +28,6 @@ struct reset_syscfg { unsigned int mask_rst_msk; }; -/* STiH415 */ -#define STIH415_SYSCFG_11 0x2c -#define STIH415_SYSCFG_15 0x3c - -static struct reset_syscfg stih415_reset = { - .offset_rst = STIH415_SYSCFG_11, - .mask_rst = BIT(0), - .offset_rst_msk = STIH415_SYSCFG_15, - .mask_rst_msk = BIT(0) -}; - -/* STiH416 */ -#define STIH416_SYSCFG_500 0x7d0 -#define STIH416_SYSCFG_504 0x7e0 - -static struct reset_syscfg stih416_reset = { - .offset_rst = STIH416_SYSCFG_500, - .mask_rst = BIT(0), - .offset_rst_msk = STIH416_SYSCFG_504, - .mask_rst_msk = BIT(0) -}; - /* STiH407 */ #define STIH407_SYSCFG_4000 0x0 #define STIH407_SYSCFG_4008 0x20 @@ -61,16 +39,6 @@ static struct reset_syscfg stih407_reset = { .mask_rst_msk = BIT(0) }; -/* STiD127 */ -#define STID127_SYSCFG_700 0x0 -#define STID127_SYSCFG_773 0x124 - -static struct reset_syscfg stid127_reset = { - .offset_rst = STID127_SYSCFG_773, - .mask_rst = BIT(0), - .offset_rst_msk = STID127_SYSCFG_700, - .mask_rst_msk = BIT(8) -}; static struct reset_syscfg *st_restart_syscfg; @@ -99,17 +67,8 @@ static struct notifier_block st_restart_nb = { static const struct of_device_id st_reset_of_match[] = { { - .compatible = "st,stih415-restart", - .data = (void *)&stih415_reset, - }, { - .compatible = "st,stih416-restart", - .data = (void *)&stih416_reset, - }, { .compatible = "st,stih407-restart", .data = (void *)&stih407_reset, - }, { - .compatible = "st,stid127-restart", - .data = (void *)&stid127_reset, }, {} }; -- cgit v0.10.2 From bb1e41ba324fe4bb71be3ef8d0eda29f0ba44ffa Mon Sep 17 00:00:00 2001 From: Peter Griffin Date: Wed, 14 Sep 2016 14:27:55 +0100 Subject: power: reset: st: Remove obsolete platforms from dt doc This patch removes support for STiH415/6 SoC's from the st-restart dt binding documentation, as support for these platforms is being removed from the kernel. It also updates the dt example to a currently supported platform. Signed-off-by: Peter Griffin Cc: Signed-off-by: Sebastian Reichel diff --git a/Documentation/devicetree/bindings/power/reset/st-reset.txt b/Documentation/devicetree/bindings/power/reset/st-reset.txt index 809af54..83734dc 100644 --- a/Documentation/devicetree/bindings/power/reset/st-reset.txt +++ b/Documentation/devicetree/bindings/power/reset/st-reset.txt @@ -1,11 +1,12 @@ *Device-Tree bindings for ST SW reset functionality Required properties: -- compatible: should be "st,-restart". +- compatible: should be "stih407-restart". - st,syscfg: should be a phandle of the syscfg node. Example node: restart { - compatible = "st,stih416-restart"; - st,syscfg = <&syscfg_sbc>; + compatible = "st,stih407-restart"; + st,syscfg = <&syscfg_sbc_reg>; + status = "okay"; }; -- cgit v0.10.2 From 389958bb6be8b08c9f6d350dcaa9fc127123eada Mon Sep 17 00:00:00 2001 From: Phil Reid Date: Tue, 20 Sep 2016 09:01:12 +0800 Subject: power: supply: sbs-battery: Cleanup removal of chip->pdata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There where still a few lingering references to pdata after commit power: supply: sbs-battery: simplify DT parsing. Remove pdata from struct·sbs_info and conditional checks to ser if this was set from the i2c read / write functions. Instead of call max in each function for incrementing poll_retry_count do it once in the probe function. Fixup null pointer dereference in to pdata in sbs_external_power_changed. Change retry counts to u32 to avoid need for max. Signed-off-by: Phil Reid Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/sbs-battery.c b/drivers/power/supply/sbs-battery.c index bc7acdf..8bb2eb3 100644 --- a/drivers/power/supply/sbs-battery.c +++ b/drivers/power/supply/sbs-battery.c @@ -163,14 +163,13 @@ static enum power_supply_property sbs_properties[] = { struct sbs_info { struct i2c_client *client; struct power_supply *power_supply; - struct sbs_platform_data *pdata; bool is_present; struct gpio_desc *gpio_detect; bool enable_detection; int last_state; int poll_time; - int i2c_retry_count; - int poll_retry_count; + u32 i2c_retry_count; + u32 poll_retry_count; struct delayed_work work; int ignore_changes; }; @@ -185,8 +184,7 @@ static int sbs_read_word_data(struct i2c_client *client, u8 address) s32 ret = 0; int retries = 1; - if (chip->pdata) - retries = max(chip->i2c_retry_count + 1, 1); + retries = chip->i2c_retry_count; while (retries > 0) { ret = i2c_smbus_read_word_data(client, address); @@ -213,10 +211,8 @@ static int sbs_read_string_data(struct i2c_client *client, u8 address, int retries_length = 1, retries_block = 1; u8 block_buffer[I2C_SMBUS_BLOCK_MAX + 1]; - if (chip->pdata) { - retries_length = max(chip->i2c_retry_count + 1, 1); - retries_block = max(chip->i2c_retry_count + 1, 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, @@ -280,8 +276,7 @@ static int sbs_write_word_data(struct i2c_client *client, u8 address, s32 ret = 0; int retries = 1; - if (chip->pdata) - retries = max(chip->i2c_retry_count + 1, 1); + retries = chip->i2c_retry_count; while (retries > 0) { ret = i2c_smbus_write_word_data(client, address, @@ -708,7 +703,7 @@ static void sbs_external_power_changed(struct power_supply *psy) cancel_delayed_work_sync(&chip->work); schedule_delayed_work(&chip->work, HZ); - chip->poll_time = chip->pdata->poll_retry_count; + chip->poll_time = chip->poll_retry_count; } static void sbs_delayed_work(struct work_struct *work) @@ -792,7 +787,7 @@ static int sbs_probe(struct i2c_client *client, rc = of_property_read_u32(client->dev.of_node, "sbs,i2c-retry-count", &chip->i2c_retry_count); if (rc) - chip->i2c_retry_count = 1; + chip->i2c_retry_count = 0; rc = of_property_read_u32(client->dev.of_node, "sbs,poll-retry-count", &chip->poll_retry_count); @@ -803,6 +798,7 @@ static int sbs_probe(struct i2c_client *client, 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); diff --git a/include/linux/power/sbs-battery.h b/include/linux/power/sbs-battery.h index 811f1a0..519b8b4 100644 --- a/include/linux/power/sbs-battery.h +++ b/include/linux/power/sbs-battery.h @@ -31,8 +31,8 @@ * external change notification */ struct sbs_platform_data { - int i2c_retry_count; - int poll_retry_count; + u32 i2c_retry_count; + u32 poll_retry_count; }; #endif -- cgit v0.10.2 From 1d72706f0485b58e151b5a7584c4c65d66670587 Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Mon, 19 Sep 2016 20:43:02 -0700 Subject: power: supply: bq27xxx_battery: allow kernel poll_interval parameter runtime update Fix issue with poll_interval being not updated till the previous interval expired. Cc: Tony Lindgren Cc: Liam Breck Signed-off-by: Matt Ranostay Signed-off-by: Sebastian Reichel diff --git a/drivers/power/supply/bq27xxx_battery.c b/drivers/power/supply/bq27xxx_battery.c index 3f57dd5..3b0dbc6 100644 --- a/drivers/power/supply/bq27xxx_battery.c +++ b/drivers/power/supply/bq27xxx_battery.c @@ -39,6 +39,7 @@ #include #include +#include #include #include #include @@ -390,8 +391,35 @@ static struct { 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(poll_interval, uint, 0644); +module_param_cb(poll_interval, ¶m_ops_poll_interval, &poll_interval, 0644); MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - 0 disables polling"); @@ -972,6 +1000,10 @@ int bq27xxx_battery_setup(struct bq27xxx_device_info *di) 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); @@ -990,6 +1022,10 @@ void bq27xxx_battery_teardown(struct bq27xxx_device_info *di) 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); diff --git a/include/linux/power/bq27xxx_battery.h b/include/linux/power/bq27xxx_battery.h index b50c049..e30deb0 100644 --- a/include/linux/power/bq27xxx_battery.h +++ b/include/linux/power/bq27xxx_battery.h @@ -58,6 +58,7 @@ struct bq27xxx_device_info { unsigned long last_update; struct delayed_work work; struct power_supply *bat; + struct list_head list; struct mutex lock; u8 *regs; }; -- cgit v0.10.2