diff options
Diffstat (limited to 'drivers/power')
75 files changed, 29070 insertions, 1058 deletions
diff --git a/drivers/power/88pm860x_battery.c b/drivers/power/88pm860x_battery.c new file mode 100644 index 00000000000..dfcda3a4940 --- /dev/null +++ b/drivers/power/88pm860x_battery.c @@ -0,0 +1,1035 @@ +/* + * Battery driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2012 Marvell International Ltd. + * Author: Jett Zhou <jtzhou@marvell.com> + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/string.h> +#include <linux/power_supply.h> +#include <linux/mfd/88pm860x.h> +#include <linux/delay.h> + +/* bit definitions of Status Query Interface 2 */ +#define STATUS2_CHG (1 << 2) +#define STATUS2_BAT (1 << 3) +#define STATUS2_VBUS (1 << 4) + +/* bit definitions of Measurement Enable 1 Register */ +#define MEAS1_TINT (1 << 3) +#define MEAS1_GP1 (1 << 5) + +/* bit definitions of Measurement Enable 3 Register */ +#define MEAS3_IBAT (1 << 0) +#define MEAS3_BAT_DET (1 << 1) +#define MEAS3_CC (1 << 2) + +/* bit definitions of Measurement Off Time Register */ +#define MEAS_OFF_SLEEP_EN (1 << 1) + +/* bit definitions of GPADC Bias Current 2 Register */ +#define GPBIAS2_GPADC1_SET (2 << 4) +/* GPADC1 Bias Current value in uA unit */ +#define GPBIAS2_GPADC1_UA ((GPBIAS2_GPADC1_SET >> 4) * 5 + 1) + +/* bit definitions of GPADC Misc 1 Register */ +#define GPMISC1_GPADC_EN (1 << 0) + +/* bit definitions of Charger Control 6 Register */ +#define CC6_BAT_DET_GPADC1 1 + +/* bit definitions of Coulomb Counter Reading Register */ +#define CCNT_AVG_SEL (4 << 3) + +/* bit definitions of RTC miscellaneous Register1 */ +#define RTC_SOC_5LSB (0x1F << 3) + +/* bit definitions of RTC Register1 */ +#define RTC_SOC_3MSB (0x7) + +/* bit definitions of Power up Log register */ +#define BAT_WU_LOG (1<<6) + +/* coulomb counter index */ +#define CCNT_POS1 0 +#define CCNT_POS2 1 +#define CCNT_NEG1 2 +#define CCNT_NEG2 3 +#define CCNT_SPOS 4 +#define CCNT_SNEG 5 + +/* OCV -- Open Circuit Voltage */ +#define OCV_MODE_ACTIVE 0 +#define OCV_MODE_SLEEP 1 + +/* Vbat range of CC for measuring Rbat */ +#define LOW_BAT_THRESHOLD 3600 +#define VBATT_RESISTOR_MIN 3800 +#define VBATT_RESISTOR_MAX 4100 + +/* TBAT for batt, TINT for chip itself */ +#define PM860X_TEMP_TINT (0) +#define PM860X_TEMP_TBAT (1) + +/* + * Battery temperature based on NTC resistor, defined + * corresponding resistor value -- Ohm / C degeree. + */ +#define TBAT_NEG_25D 127773 /* -25 */ +#define TBAT_NEG_10D 54564 /* -10 */ +#define TBAT_0D 32330 /* 0 */ +#define TBAT_10D 19785 /* 10 */ +#define TBAT_20D 12468 /* 20 */ +#define TBAT_30D 8072 /* 30 */ +#define TBAT_40D 5356 /* 40 */ + +struct pm860x_battery_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct device *dev; + + struct power_supply battery; + struct mutex lock; + int status; + int irq_cc; + int irq_batt; + int max_capacity; + int resistor; /* Battery Internal Resistor */ + int last_capacity; + int start_soc; + unsigned present:1; + unsigned temp_type:1; /* TINT or TBAT */ +}; + +struct ccnt { + unsigned long long int pos; + unsigned long long int neg; + unsigned int spos; + unsigned int sneg; + + int total_chg; /* mAh(3.6C) */ + int total_dischg; /* mAh(3.6C) */ +}; + +/* + * State of Charge. + * The first number is mAh(=3.6C), and the second number is percent point. + */ +static int array_soc[][2] = { + {4170, 100}, {4154, 99}, {4136, 98}, {4122, 97}, {4107, 96}, + {4102, 95}, {4088, 94}, {4081, 93}, {4070, 92}, {4060, 91}, + {4053, 90}, {4044, 89}, {4035, 88}, {4028, 87}, {4019, 86}, + {4013, 85}, {4006, 84}, {3995, 83}, {3987, 82}, {3982, 81}, + {3976, 80}, {3968, 79}, {3962, 78}, {3954, 77}, {3946, 76}, + {3941, 75}, {3934, 74}, {3929, 73}, {3922, 72}, {3916, 71}, + {3910, 70}, {3904, 69}, {3898, 68}, {3892, 67}, {3887, 66}, + {3880, 65}, {3874, 64}, {3868, 63}, {3862, 62}, {3854, 61}, + {3849, 60}, {3843, 59}, {3840, 58}, {3833, 57}, {3829, 56}, + {3824, 55}, {3818, 54}, {3815, 53}, {3810, 52}, {3808, 51}, + {3804, 50}, {3801, 49}, {3798, 48}, {3796, 47}, {3792, 46}, + {3789, 45}, {3785, 44}, {3784, 43}, {3782, 42}, {3780, 41}, + {3777, 40}, {3776, 39}, {3774, 38}, {3772, 37}, {3771, 36}, + {3769, 35}, {3768, 34}, {3764, 33}, {3763, 32}, {3760, 31}, + {3760, 30}, {3754, 29}, {3750, 28}, {3749, 27}, {3744, 26}, + {3740, 25}, {3734, 24}, {3732, 23}, {3728, 22}, {3726, 21}, + {3720, 20}, {3716, 19}, {3709, 18}, {3703, 17}, {3698, 16}, + {3692, 15}, {3683, 14}, {3675, 13}, {3670, 12}, {3665, 11}, + {3661, 10}, {3649, 9}, {3637, 8}, {3622, 7}, {3609, 6}, + {3580, 5}, {3558, 4}, {3540, 3}, {3510, 2}, {3429, 1}, +}; + +static struct ccnt ccnt_data; + +/* + * register 1 bit[7:0] -- bit[11:4] of measured value of voltage + * register 0 bit[3:0] -- bit[3:0] of measured value of voltage + */ +static int measure_12bit_voltage(struct pm860x_battery_info *info, + int offset, int *data) +{ + unsigned char buf[2]; + int ret; + + ret = pm860x_bulk_read(info->i2c, offset, 2, buf); + if (ret < 0) + return ret; + + *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); + /* V_MEAS(mV) = data * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xfff) * 9 * 25) >> 9; + return 0; +} + +static int measure_vbatt(struct pm860x_battery_info *info, int state, + int *data) +{ + unsigned char buf[5]; + int ret; + + switch (state) { + case OCV_MODE_ACTIVE: + ret = measure_12bit_voltage(info, PM8607_VBAT_MEAS1, data); + if (ret) + return ret; + /* V_BATT_MEAS(mV) = value * 3 * 1.8 * 1000 / (2^12) */ + *data *= 3; + break; + case OCV_MODE_SLEEP: + /* + * voltage value of VBATT in sleep mode is saved in different + * registers. + * bit[11:10] -- bit[7:6] of LDO9(0x18) + * bit[9:8] -- bit[7:6] of LDO8(0x17) + * bit[7:6] -- bit[7:6] of LDO7(0x16) + * bit[5:4] -- bit[7:6] of LDO6(0x15) + * bit[3:0] -- bit[7:4] of LDO5(0x14) + */ + ret = pm860x_bulk_read(info->i2c, PM8607_LDO5, 5, buf); + if (ret < 0) + return ret; + ret = ((buf[4] >> 6) << 10) | ((buf[3] >> 6) << 8) + | ((buf[2] >> 6) << 6) | ((buf[1] >> 6) << 4) + | (buf[0] >> 4); + /* V_BATT_MEAS(mV) = data * 3 * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xff) * 27 * 25) >> 9; + break; + default: + return -EINVAL; + } + return 0; +} + +/* + * Return value is signed data. + * Negative value means discharging, and positive value means charging. + */ +static int measure_current(struct pm860x_battery_info *info, int *data) +{ + unsigned char buf[2]; + short s; + int ret; + + ret = pm860x_bulk_read(info->i2c, PM8607_IBAT_MEAS1, 2, buf); + if (ret < 0) + return ret; + + s = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); + /* current(mA) = value * 0.125 */ + *data = s >> 3; + return 0; +} + +static int set_charger_current(struct pm860x_battery_info *info, int data, + int *old) +{ + int ret; + + if (data < 50 || data > 1600 || !old) + return -EINVAL; + + data = ((data - 50) / 50) & 0x1f; + *old = pm860x_reg_read(info->i2c, PM8607_CHG_CTRL2); + *old = (*old & 0x1f) * 50 + 50; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, data); + if (ret < 0) + return ret; + return 0; +} + +static int read_ccnt(struct pm860x_battery_info *info, int offset, + int *ccnt) +{ + unsigned char buf[2]; + int ret; + + ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7, offset & 7); + if (ret < 0) + goto out; + ret = pm860x_bulk_read(info->i2c, PM8607_CCNT_MEAS1, 2, buf); + if (ret < 0) + goto out; + *ccnt = ((buf[0] & 0xff) << 8) | (buf[1] & 0xff); + return 0; +out: + return ret; +} + +static int calc_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) +{ + unsigned int sum; + int ret; + int data; + + ret = read_ccnt(info, CCNT_POS1, &data); + if (ret) + goto out; + sum = data & 0xffff; + ret = read_ccnt(info, CCNT_POS2, &data); + if (ret) + goto out; + sum |= (data & 0xffff) << 16; + ccnt->pos += sum; + + ret = read_ccnt(info, CCNT_NEG1, &data); + if (ret) + goto out; + sum = data & 0xffff; + ret = read_ccnt(info, CCNT_NEG2, &data); + if (ret) + goto out; + sum |= (data & 0xffff) << 16; + sum = ~sum + 1; /* since it's negative */ + ccnt->neg += sum; + + ret = read_ccnt(info, CCNT_SPOS, &data); + if (ret) + goto out; + ccnt->spos += data; + ret = read_ccnt(info, CCNT_SNEG, &data); + if (ret) + goto out; + + /* + * charge(mAh) = count * 1.6984 * 1e(-8) + * = count * 16984 * 1.024 * 1.024 * 1.024 / (2 ^ 40) + * = count * 18236 / (2 ^ 40) + */ + ccnt->total_chg = (int) ((ccnt->pos * 18236) >> 40); + ccnt->total_dischg = (int) ((ccnt->neg * 18236) >> 40); + return 0; +out: + return ret; +} + +static int clear_ccnt(struct pm860x_battery_info *info, struct ccnt *ccnt) +{ + int data; + + memset(ccnt, 0, sizeof(*ccnt)); + /* read to clear ccnt */ + read_ccnt(info, CCNT_POS1, &data); + read_ccnt(info, CCNT_POS2, &data); + read_ccnt(info, CCNT_NEG1, &data); + read_ccnt(info, CCNT_NEG2, &data); + read_ccnt(info, CCNT_SPOS, &data); + read_ccnt(info, CCNT_SNEG, &data); + return 0; +} + +/* Calculate Open Circuit Voltage */ +static int calc_ocv(struct pm860x_battery_info *info, int *ocv) +{ + int ret; + int i; + int data; + int vbatt_avg; + int vbatt_sum; + int ibatt_avg; + int ibatt_sum; + + if (!ocv) + return -EINVAL; + + for (i = 0, ibatt_sum = 0, vbatt_sum = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + vbatt_sum += data; + ret = measure_current(info, &data); + if (ret) + goto out; + ibatt_sum += data; + } + vbatt_avg = vbatt_sum / 10; + ibatt_avg = ibatt_sum / 10; + + mutex_lock(&info->lock); + if (info->present) + *ocv = vbatt_avg - ibatt_avg * info->resistor / 1000; + else + *ocv = vbatt_avg; + mutex_unlock(&info->lock); + dev_dbg(info->dev, "VBAT average:%d, OCV:%d\n", vbatt_avg, *ocv); + return 0; +out: + return ret; +} + +/* Calculate State of Charge (percent points) */ +static int calc_soc(struct pm860x_battery_info *info, int state, int *soc) +{ + int i; + int ocv; + int count; + int ret = -EINVAL; + + if (!soc) + return -EINVAL; + + switch (state) { + case OCV_MODE_ACTIVE: + ret = calc_ocv(info, &ocv); + break; + case OCV_MODE_SLEEP: + ret = measure_vbatt(info, OCV_MODE_SLEEP, &ocv); + break; + } + if (ret) + return ret; + + count = ARRAY_SIZE(array_soc); + if (ocv < array_soc[count - 1][0]) { + *soc = 0; + return 0; + } + + for (i = 0; i < count; i++) { + if (ocv >= array_soc[i][0]) { + *soc = array_soc[i][1]; + break; + } + } + return 0; +} + +static irqreturn_t pm860x_coulomb_handler(int irq, void *data) +{ + struct pm860x_battery_info *info = data; + + calc_ccnt(info, &ccnt_data); + return IRQ_HANDLED; +} + +static irqreturn_t pm860x_batt_handler(int irq, void *data) +{ + struct pm860x_battery_info *info = data; + int ret; + + mutex_lock(&info->lock); + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret & STATUS2_BAT) { + info->present = 1; + info->temp_type = PM860X_TEMP_TBAT; + } else { + info->present = 0; + info->temp_type = PM860X_TEMP_TINT; + } + mutex_unlock(&info->lock); + /* clear ccnt since battery is attached or dettached */ + clear_ccnt(info, &ccnt_data); + return IRQ_HANDLED; +} + +static void pm860x_init_battery(struct pm860x_battery_info *info) +{ + unsigned char buf[2]; + int ret; + int data; + int bat_remove; + int soc; + + /* measure enable on GPADC1 */ + data = MEAS1_GP1; + if (info->temp_type == PM860X_TEMP_TINT) + data |= MEAS1_TINT; + ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN1, data, data); + if (ret) + goto out; + + /* measure enable on IBAT, BAT_DET, CC. IBAT is depend on CC. */ + data = MEAS3_IBAT | MEAS3_BAT_DET | MEAS3_CC; + ret = pm860x_set_bits(info->i2c, PM8607_MEAS_EN3, data, data); + if (ret) + goto out; + + /* measure disable CC in sleep time */ + ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME1, 0x82); + if (ret) + goto out; + ret = pm860x_reg_write(info->i2c, PM8607_MEAS_OFF_TIME2, 0x6c); + if (ret) + goto out; + + /* enable GPADC */ + ret = pm860x_set_bits(info->i2c, PM8607_GPADC_MISC1, + GPMISC1_GPADC_EN, GPMISC1_GPADC_EN); + if (ret < 0) + goto out; + + /* detect battery via GPADC1 */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, + CC6_BAT_DET_GPADC1, CC6_BAT_DET_GPADC1); + if (ret < 0) + goto out; + + ret = pm860x_set_bits(info->i2c, PM8607_CCNT, 7 << 3, + CCNT_AVG_SEL); + if (ret < 0) + goto out; + + /* set GPADC1 bias */ + ret = pm860x_set_bits(info->i2c, PM8607_GP_BIAS2, 0xF << 4, + GPBIAS2_GPADC1_SET); + if (ret < 0) + goto out; + + /* check whether battery present) */ + mutex_lock(&info->lock); + ret = pm860x_reg_read(info->i2c, PM8607_STATUS_2); + if (ret < 0) { + mutex_unlock(&info->lock); + goto out; + } + if (ret & STATUS2_BAT) { + info->present = 1; + info->temp_type = PM860X_TEMP_TBAT; + } else { + info->present = 0; + info->temp_type = PM860X_TEMP_TINT; + } + mutex_unlock(&info->lock); + + calc_soc(info, OCV_MODE_ACTIVE, &soc); + + data = pm860x_reg_read(info->i2c, PM8607_POWER_UP_LOG); + bat_remove = data & BAT_WU_LOG; + + dev_dbg(info->dev, "battery wake up? %s\n", + bat_remove != 0 ? "yes" : "no"); + + /* restore SOC from RTC domain register */ + if (bat_remove == 0) { + buf[0] = pm860x_reg_read(info->i2c, PM8607_RTC_MISC2); + buf[1] = pm860x_reg_read(info->i2c, PM8607_RTC1); + data = ((buf[1] & 0x3) << 5) | ((buf[0] >> 3) & 0x1F); + if (data > soc + 15) + info->start_soc = soc; + else if (data < soc - 15) + info->start_soc = soc; + else + info->start_soc = data; + dev_dbg(info->dev, "soc_rtc %d, soc_ocv :%d\n", data, soc); + } else { + pm860x_set_bits(info->i2c, PM8607_POWER_UP_LOG, + BAT_WU_LOG, BAT_WU_LOG); + info->start_soc = soc; + } + info->last_capacity = info->start_soc; + dev_dbg(info->dev, "init soc : %d\n", info->last_capacity); +out: + return; +} + +static void set_temp_threshold(struct pm860x_battery_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 8) / 1800; + pm860x_reg_write(info->i2c, PM8607_GPADC1_HIGHTH, data); + dev_dbg(info->dev, "TEMP_HIGHTH : min: %d, 0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 8) / 1800; + pm860x_reg_write(info->i2c, PM8607_GPADC1_LOWTH, data); + dev_dbg(info->dev, "TEMP_LOWTH:max : %d, 0x%x\n", max, data); +} + +static int measure_temp(struct pm860x_battery_info *info, int *data) +{ + int ret; + int temp; + int min; + int max; + + if (info->temp_type == PM860X_TEMP_TINT) { + ret = measure_12bit_voltage(info, PM8607_TINT_MEAS1, data); + if (ret) + return ret; + *data = (*data - 884) * 1000 / 3611; + } else { + ret = measure_12bit_voltage(info, PM8607_GPADC1_MEAS1, data); + if (ret) + return ret; + /* meausered Vtbat(mV) / Ibias_current(11uA)*/ + *data = (*data * 1000) / GPBIAS2_GPADC1_UA; + + if (*data > TBAT_NEG_25D) { + temp = -30; /* over cold , suppose -30 roughly */ + max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, 0, max); + } else if (*data > TBAT_NEG_10D) { + temp = -15; /* -15 degree, code */ + max = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, 0, max); + } else if (*data > TBAT_0D) { + temp = -5; /* -5 degree */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_10D) { + temp = 5; /* in range of (0, 10) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_20D) { + temp = 15; /* in range of (10, 20) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_30D) { + temp = 25; /* in range of (20, 30) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else if (*data > TBAT_40D) { + temp = 35; /* in range of (30, 40) */ + min = TBAT_NEG_10D * GPBIAS2_GPADC1_UA / 1000; + max = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, max); + } else { + min = TBAT_40D * GPBIAS2_GPADC1_UA / 1000; + set_temp_threshold(info, min, 0); + temp = 45; /* over heat ,suppose 45 roughly */ + } + + dev_dbg(info->dev, "temp_C:%d C,temp_mv:%d mv\n", temp, *data); + *data = temp; + } + return 0; +} + +static int calc_resistor(struct pm860x_battery_info *info) +{ + int vbatt_sum1; + int vbatt_sum2; + int chg_current; + int ibatt_sum1; + int ibatt_sum2; + int data; + int ret; + int i; + + ret = measure_current(info, &data); + /* make sure that charging is launched by data > 0 */ + if (ret || data < 0) + goto out; + + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + /* calculate resistor only in CC charge mode */ + if (data < VBATT_RESISTOR_MIN || data > VBATT_RESISTOR_MAX) + goto out; + + /* current is saved */ + if (set_charger_current(info, 500, &chg_current)) + goto out; + + /* + * set charge current as 500mA, wait about 500ms till charging + * process is launched and stable with the newer charging current. + */ + msleep(500); + + for (i = 0, vbatt_sum1 = 0, ibatt_sum1 = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out_meas; + vbatt_sum1 += data; + ret = measure_current(info, &data); + if (ret) + goto out_meas; + + if (data < 0) + ibatt_sum1 = ibatt_sum1 - data; /* discharging */ + else + ibatt_sum1 = ibatt_sum1 + data; /* charging */ + } + + if (set_charger_current(info, 100, &ret)) + goto out_meas; + /* + * set charge current as 100mA, wait about 500ms till charging + * process is launched and stable with the newer charging current. + */ + msleep(500); + + for (i = 0, vbatt_sum2 = 0, ibatt_sum2 = 0; i < 10; i++) { + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out_meas; + vbatt_sum2 += data; + ret = measure_current(info, &data); + if (ret) + goto out_meas; + + if (data < 0) + ibatt_sum2 = ibatt_sum2 - data; /* discharging */ + else + ibatt_sum2 = ibatt_sum2 + data; /* charging */ + } + + /* restore current setting */ + if (set_charger_current(info, chg_current, &ret)) + goto out_meas; + + if ((vbatt_sum1 > vbatt_sum2) && (ibatt_sum1 > ibatt_sum2) && + (ibatt_sum2 > 0)) { + /* calculate resistor in discharging case */ + data = 1000 * (vbatt_sum1 - vbatt_sum2) + / (ibatt_sum1 - ibatt_sum2); + if ((data - info->resistor > 0) && + (data - info->resistor < info->resistor)) + info->resistor = data; + if ((info->resistor - data > 0) && + (info->resistor - data < data)) + info->resistor = data; + } + return 0; + +out_meas: + set_charger_current(info, chg_current, &ret); +out: + return -EINVAL; +} + +static int calc_capacity(struct pm860x_battery_info *info, int *cap) +{ + int ret; + int data; + int ibat; + int cap_ocv = 0; + int cap_cc = 0; + + ret = calc_ccnt(info, &ccnt_data); + if (ret) + goto out; +soc: + data = info->max_capacity * info->start_soc / 100; + if (ccnt_data.total_dischg - ccnt_data.total_chg <= data) { + cap_cc = + data + ccnt_data.total_chg - ccnt_data.total_dischg; + } else { + clear_ccnt(info, &ccnt_data); + calc_soc(info, OCV_MODE_ACTIVE, &info->start_soc); + dev_dbg(info->dev, "restart soc = %d !\n", + info->start_soc); + goto soc; + } + + cap_cc = cap_cc * 100 / info->max_capacity; + if (cap_cc < 0) + cap_cc = 0; + else if (cap_cc > 100) + cap_cc = 100; + + dev_dbg(info->dev, "%s, last cap : %d", __func__, + info->last_capacity); + + ret = measure_current(info, &ibat); + if (ret) + goto out; + /* Calculate the capacity when discharging(ibat < 0) */ + if (ibat < 0) { + ret = calc_soc(info, OCV_MODE_ACTIVE, &cap_ocv); + if (ret) + cap_ocv = info->last_capacity; + ret = measure_vbatt(info, OCV_MODE_ACTIVE, &data); + if (ret) + goto out; + if (data <= LOW_BAT_THRESHOLD) { + /* choose the lower capacity value to report + * between vbat and CC when vbat < 3.6v; + * than 3.6v; + */ + *cap = min(cap_ocv, cap_cc); + } else { + /* when detect vbat > 3.6v, but cap_cc < 15,and + * cap_ocv is 10% larger than cap_cc, we can think + * CC have some accumulation error, switch to OCV + * to estimate capacity; + * */ + if (cap_cc < 15 && cap_ocv - cap_cc > 10) + *cap = cap_ocv; + else + *cap = cap_cc; + } + /* when discharging, make sure current capacity + * is lower than last*/ + if (*cap > info->last_capacity) + *cap = info->last_capacity; + } else { + *cap = cap_cc; + } + info->last_capacity = *cap; + + dev_dbg(info->dev, "%s, cap_ocv:%d cap_cc:%d, cap:%d\n", + (ibat < 0) ? "discharging" : "charging", + cap_ocv, cap_cc, *cap); + /* + * store the current capacity to RTC domain register, + * after next power up , it will be restored. + */ + pm860x_set_bits(info->i2c, PM8607_RTC_MISC2, RTC_SOC_5LSB, + (*cap & 0x1F) << 3); + pm860x_set_bits(info->i2c, PM8607_RTC1, RTC_SOC_3MSB, + ((*cap >> 5) & 0x3)); + return 0; +out: + return ret; +} + +static void pm860x_external_power_changed(struct power_supply *psy) +{ + struct pm860x_battery_info *info; + + info = container_of(psy, struct pm860x_battery_info, battery); + 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 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); + + info->battery.name = "battery-monitor"; + info->battery.type = POWER_SUPPLY_TYPE_BATTERY; + info->battery.properties = pm860x_batt_props; + info->battery.num_properties = ARRAY_SIZE(pm860x_batt_props); + info->battery.get_property = pm860x_batt_get_prop; + info->battery.set_property = pm860x_batt_set_prop; + info->battery.external_power_changed = pm860x_external_power_changed; + + 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 */ + + ret = power_supply_register(&pdev->dev, &info->battery); + if (ret) + return ret; + info->battery.dev->parent = &pdev->dev; + + ret = request_threaded_irq(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); + goto out_reg; + } + + ret = request_threaded_irq(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); + goto out_coulomb; + } + + + return 0; + +out_coulomb: + free_irq(info->irq_cc, info); +out_reg: + power_supply_unregister(&info->battery); + return ret; +} + +static int pm860x_battery_remove(struct platform_device *pdev) +{ + struct pm860x_battery_info *info = platform_get_drvdata(pdev); + + free_irq(info->irq_batt, info); + free_irq(info->irq_cc, info); + power_supply_unregister(&info->battery); + 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", + .owner = THIS_MODULE, + .pm = &pm860x_battery_pm_ops, + }, + .probe = pm860x_battery_probe, + .remove = pm860x_battery_remove, +}; +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 new file mode 100644 index 00000000000..de029bbc1cc --- /dev/null +++ b/drivers/power/88pm860x_charger.c @@ -0,0 +1,743 @@ +/* + * Battery driver for Marvell 88PM860x PMIC + * + * Copyright (c) 2012 Marvell International Ltd. + * Author: Jett Zhou <jtzhou@marvell.com> + * Haojian Zhuang <haojian.zhuang@marvell.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/mfd/88pm860x.h> +#include <linux/delay.h> +#include <linux/uaccess.h> +#include <asm/div64.h> + +/* bit definitions of Status Query Interface 2 */ +#define STATUS2_CHG (1 << 2) + +/* bit definitions of Reset Out Register */ +#define RESET_SW_PD (1 << 7) + +/* bit definitions of PreReg 1 */ +#define PREREG1_90MA (0x0) +#define PREREG1_180MA (0x1) +#define PREREG1_450MA (0x4) +#define PREREG1_540MA (0x5) +#define PREREG1_1350MA (0xE) +#define PREREG1_VSYS_4_5V (3 << 4) + +/* bit definitions of Charger Control 1 Register */ +#define CC1_MODE_OFF (0) +#define CC1_MODE_PRECHARGE (1) +#define CC1_MODE_FASTCHARGE (2) +#define CC1_MODE_PULSECHARGE (3) +#define CC1_ITERM_20MA (0 << 2) +#define CC1_ITERM_60MA (2 << 2) +#define CC1_VFCHG_4_2V (9 << 4) + +/* bit definitions of Charger Control 2 Register */ +#define CC2_ICHG_100MA (0x1) +#define CC2_ICHG_500MA (0x9) +#define CC2_ICHG_1000MA (0x13) + +/* bit definitions of Charger Control 3 Register */ +#define CC3_180MIN_TIMEOUT (0x6 << 4) +#define CC3_270MIN_TIMEOUT (0x7 << 4) +#define CC3_360MIN_TIMEOUT (0xA << 4) +#define CC3_DISABLE_TIMEOUT (0xF << 4) + +/* bit definitions of Charger Control 4 Register */ +#define CC4_IPRE_40MA (7) +#define CC4_VPCHG_3_2V (3 << 4) +#define CC4_IFCHG_MON_EN (1 << 6) +#define CC4_BTEMP_MON_EN (1 << 7) + +/* bit definitions of Charger Control 6 Register */ +#define CC6_BAT_OV_EN (1 << 2) +#define CC6_BAT_UV_EN (1 << 3) +#define CC6_UV_VBAT_SET (0x3 << 6) /* 2.8v */ + +/* bit definitions of Charger Control 7 Register */ +#define CC7_BAT_REM_EN (1 << 3) +#define CC7_IFSM_EN (1 << 7) + +/* bit definitions of Measurement Enable 1 Register */ +#define MEAS1_VBAT (1 << 0) + +/* bit definitions of Measurement Enable 3 Register */ +#define MEAS3_IBAT_EN (1 << 0) +#define MEAS3_CC_EN (1 << 2) + +#define FSM_INIT 0 +#define FSM_DISCHARGE 1 +#define FSM_PRECHARGE 2 +#define FSM_FASTCHARGE 3 + +#define PRECHARGE_THRESHOLD 3100 +#define POWEROFF_THRESHOLD 3400 +#define CHARGE_THRESHOLD 4000 +#define DISCHARGE_THRESHOLD 4180 + +/* over-temperature on PM8606 setting */ +#define OVER_TEMP_FLAG (1 << 6) +#define OVTEMP_AUTORECOVER (1 << 3) + +/* over-voltage protect on vchg setting mv */ +#define VCHG_NORMAL_LOW 4200 +#define VCHG_NORMAL_CHECK 5800 +#define VCHG_NORMAL_HIGH 6000 +#define VCHG_OVP_LOW 5500 + +struct pm860x_charger_info { + struct pm860x_chip *chip; + struct i2c_client *i2c; + struct i2c_client *i2c_8606; + struct device *dev; + + struct power_supply usb; + struct mutex lock; + int irq_nums; + int irq[7]; + unsigned state:3; /* fsm state */ + unsigned online:1; /* usb charger */ + unsigned present:1; /* battery present */ + unsigned allowed:1; +}; + +static char *pm860x_supplied_to[] = { + "battery-monitor", +}; + +static int measure_vchg(struct pm860x_charger_info *info, int *data) +{ + unsigned char buf[2]; + int ret = 0; + + ret = pm860x_bulk_read(info->i2c, PM8607_VCHG_MEAS1, 2, buf); + if (ret < 0) + return ret; + + *data = ((buf[0] & 0xff) << 4) | (buf[1] & 0x0f); + /* V_BATT_MEAS(mV) = value * 5 * 1.8 * 1000 / (2^12) */ + *data = ((*data & 0xfff) * 9 * 125) >> 9; + + dev_dbg(info->dev, "%s, vchg: %d mv\n", __func__, *data); + + return ret; +} + +static void set_vchg_threshold(struct pm860x_charger_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) * / 5 / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 5) / 1125; + pm860x_reg_write(info->i2c, PM8607_VCHG_LOWTH, data); + dev_dbg(info->dev, "VCHG_LOWTH:%dmv, 0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 5) / 1125; + pm860x_reg_write(info->i2c, PM8607_VCHG_HIGHTH, data); + dev_dbg(info->dev, "VCHG_HIGHTH:%dmv, 0x%x\n", max, data); + +} + +static void set_vbatt_threshold(struct pm860x_charger_info *info, + int min, int max) +{ + int data; + + /* (tmp << 8) * 3 / 1800 */ + if (min <= 0) + data = 0; + else + data = (min << 5) / 675; + pm860x_reg_write(info->i2c, PM8607_VBAT_LOWTH, data); + dev_dbg(info->dev, "VBAT Min:%dmv, LOWTH:0x%x\n", min, data); + + if (max <= 0) + data = 0xff; + else + data = (max << 5) / 675; + pm860x_reg_write(info->i2c, PM8607_VBAT_HIGHTH, data); + dev_dbg(info->dev, "VBAT Max:%dmv, HIGHTH:0x%x\n", max, data); + + return; +} + +static int start_precharge(struct pm860x_charger_info *info) +{ + int ret; + + dev_dbg(info->dev, "Start Pre-charging!\n"); + set_vbatt_threshold(info, 0, 0); + + ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, + PREREG1_1350MA | PREREG1_VSYS_4_5V); + if (ret < 0) + goto out; + /* stop charging */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_OFF); + if (ret < 0) + goto out; + /* set 270 minutes timeout */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), + CC3_270MIN_TIMEOUT); + if (ret < 0) + goto out; + /* set precharge current, termination voltage, IBAT & TBAT monitor */ + ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL4, + CC4_IPRE_40MA | CC4_VPCHG_3_2V | + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, + CC7_BAT_REM_EN | CC7_IFSM_EN, + CC7_BAT_REM_EN | CC7_IFSM_EN); + if (ret < 0) + goto out; + /* trigger precharge */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_PRECHARGE); +out: + return ret; +} + +static int start_fastcharge(struct pm860x_charger_info *info) +{ + int ret; + + dev_dbg(info->dev, "Start Fast-charging!\n"); + + /* set fastcharge termination current & voltage, disable charging */ + ret = pm860x_reg_write(info->i2c, PM8607_CHG_CTRL1, + CC1_MODE_OFF | CC1_ITERM_60MA | + CC1_VFCHG_4_2V); + if (ret < 0) + goto out; + ret = pm860x_reg_write(info->i2c_8606, PM8606_PREREGULATORA, + PREREG1_540MA | PREREG1_VSYS_4_5V); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL2, 0x1f, + CC2_ICHG_500MA); + if (ret < 0) + goto out; + /* set 270 minutes timeout */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL3, (0xf << 4), + CC3_270MIN_TIMEOUT); + if (ret < 0) + goto out; + /* set IBAT & TBAT monitor */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL4, + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN, + CC4_IFCHG_MON_EN | CC4_BTEMP_MON_EN); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL6, + CC6_BAT_OV_EN | CC6_BAT_UV_EN | + CC6_UV_VBAT_SET, + CC6_BAT_OV_EN | CC6_BAT_UV_EN | + CC6_UV_VBAT_SET); + if (ret < 0) + goto out; + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL7, + CC7_BAT_REM_EN | CC7_IFSM_EN, + CC7_BAT_REM_EN | CC7_IFSM_EN); + if (ret < 0) + goto out; + /* launch fast-charge */ + ret = pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, + CC1_MODE_FASTCHARGE); + /* vchg threshold setting */ + set_vchg_threshold(info, VCHG_NORMAL_LOW, VCHG_NORMAL_HIGH); +out: + return ret; +} + +static void stop_charge(struct pm860x_charger_info *info, int vbatt) +{ + dev_dbg(info->dev, "Stop charging!\n"); + pm860x_set_bits(info->i2c, PM8607_CHG_CTRL1, 3, CC1_MODE_OFF); + if (vbatt > CHARGE_THRESHOLD && info->online) + set_vbatt_threshold(info, CHARGE_THRESHOLD, 0); +} + +static void power_off_notification(struct pm860x_charger_info *info) +{ + dev_dbg(info->dev, "Power-off notification!\n"); +} + +static int set_charging_fsm(struct pm860x_charger_info *info) +{ + struct power_supply *psy; + union power_supply_propval data; + unsigned char fsm_state[][16] = { "init", "discharge", "precharge", + "fastcharge", + }; + int ret; + int vbatt; + + psy = power_supply_get_by_name(pm860x_supplied_to[0]); + if (!psy) + return -EINVAL; + ret = psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &data); + if (ret) + return ret; + vbatt = data.intval / 1000; + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_PRESENT, &data); + if (ret) + return ret; + + 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) + goto out; + ret = psy->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: + 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 = psy->get_property(psy, POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); + if (ret) + goto out; + 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; + if (vbatt > CHARGE_THRESHOLD && ret & STATUS2_CHG) + psy->set_property(psy, POWER_SUPPLY_PROP_CHARGE_FULL, &val); + +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 = + dev_get_drvdata(psy->dev->parent); + + 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 int pm860x_charger_probe(struct platform_device *pdev) +{ + struct pm860x_chip *chip = dev_get_drvdata(pdev->dev.parent); + 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); + + info->usb.name = "usb"; + info->usb.type = POWER_SUPPLY_TYPE_USB; + info->usb.supplied_to = pm860x_supplied_to; + info->usb.num_supplicants = ARRAY_SIZE(pm860x_supplied_to); + info->usb.properties = pm860x_usb_props; + info->usb.num_properties = ARRAY_SIZE(pm860x_usb_props); + info->usb.get_property = pm860x_usb_get_prop; + ret = power_supply_register(&pdev->dev, &info->usb); + if (ret) + 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: + 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); + free_irq(info->irq[0], info); + 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", + .owner = THIS_MODULE, + }, + .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 3a8daf85874..ba697512307 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -1,5 +1,5 @@ menuconfig POWER_SUPPLY - tristate "Power supply class support" + 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 @@ -29,6 +29,13 @@ config APM_POWER 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 @@ -62,6 +69,12 @@ config TEST_POWER 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_DS2760 tristate "DS2760 battery driver (HP iPAQ & others)" depends on W1 && W1_SLAVE_DS2760 @@ -76,6 +89,20 @@ config BATTERY_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 @@ -125,6 +152,7 @@ config BATTERY_SBS config BATTERY_BQ27x00 tristate "BQ27x00 battery driver" + depends on I2C || I2C=n help Say Y here to enable support for batteries with BQ27x00 (I2C/HDQ) chips. @@ -153,7 +181,6 @@ config BATTERY_DA9030 config BATTERY_DA9052 tristate "Dialog DA9052 Battery" depends on PMIC_DA9052 - depends on BROKEN help Say Y here to enable support for batteries charger integrated into DA9052 PMIC. @@ -167,14 +194,16 @@ config BATTERY_MAX17040 to operate with a single lithium cell config BATTERY_MAX17042 - tristate "Maxim MAX17042/8997/8966 Fuel Gauge" + 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. + with MAX17042. This driver also supports max17047/50 chips which are + improved version of max17042. config BATTERY_Z2 tristate "Z2 battery driver" @@ -188,6 +217,19 @@ config BATTERY_S3C_ADC help Say Y here to enable support for iPAQ h1930/h1940/rx1950 battery +config BATTERY_TWL4030_MADC + tristate "TWL4030 MADC battery driver" + depends on TWL4030_MADC + help + Say Y here to enable this dumb driver for batteries managed + through the TWL4030 MADC. + +config CHARGER_88PM860X + tristate "Marvell 88PM860x Charger driver" + depends on MFD_88PM860X && BATTERY_88PM860X + help + Say Y here to enable charger for Marvell 88PM860x chip. + config CHARGER_PCF50633 tristate "NXP PCF50633 MBC" depends on MFD_PCF50633 @@ -212,16 +254,22 @@ config BATTERY_INTEL_MID 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_OTG_UTILS + depends on USB_PHY 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" - depends on GENERIC_HARDIRQS help Say Y to enable support for the MAX8903 DC-DC charger and sysfs. The driver supports controlling charger-enable and current-limit @@ -235,11 +283,19 @@ config CHARGER_TWL4030 Say Y here to enable support for TWL4030 Battery Charge Interface. config CHARGER_LP8727 - tristate "National Semiconductor LP8727 charger driver" + 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 @@ -253,6 +309,7 @@ config CHARGER_GPIO config CHARGER_MANAGER bool "Battery charger manager for multiple chargers" depends on REGULATOR && RTC_CLASS + select EXTCON help Say Y to enable charger-manager support, which allows multiple chargers attached to a battery and multiple batteries attached to a @@ -260,6 +317,13 @@ config CHARGER_MANAGER runtime and in suspend-to-RAM by waking up the system periodically with help of suspend_again support. +config CHARGER_MAX14577 + tristate "Maxim MAX14577 MUIC battery charger driver" + depends on MFD_MAX14577 + help + Say Y to enable support for the battery charger control sysfs and + platform data of MAX14577 MUICs. + config CHARGER_MAX8997 tristate "Maxim MAX8997/MAX8966 PMIC battery charger driver" depends on MFD_MAX8997 && REGULATOR_MAX8997 @@ -274,4 +338,59 @@ config CHARGER_MAX8998 Say Y to enable support for the battery charger control sysfs and platform data of MAX8998/LP3974 PMICs. +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 && GPIOLIB + help + Say Y to enable support for the TI BQ24190 battery charger. + +config CHARGER_BQ24735 + tristate "TI BQ24735 battery charger support" + depends on I2C && GPIOLIB + help + Say Y to enable support for the TI BQ24735 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 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. + +source "drivers/power/reset/Kconfig" + endif # POWER_SUPPLY + +source "drivers/power/avs/Kconfig" diff --git a/drivers/power/Makefile b/drivers/power/Makefile index e429008eaf1..ee54a3e4c90 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -5,6 +5,7 @@ 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 @@ -14,9 +15,12 @@ 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_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_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 @@ -30,14 +34,27 @@ obj-$(CONFIG_BATTERY_MAX17040) += max17040_battery.o obj-$(CONFIG_BATTERY_MAX17042) += max17042_battery.o obj-$(CONFIG_BATTERY_Z2) += z2_battery.o obj-$(CONFIG_BATTERY_S3C_ADC) += s3c_adc_battery.o +obj-$(CONFIG_BATTERY_TWL4030_MADC) += twl4030_madc_battery.o +obj-$(CONFIG_CHARGER_88PM860X) += 88pm860x_charger.o obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o 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_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o +obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o +obj-$(CONFIG_CHARGER_BQ24190) += bq24190_charger.o +obj-$(CONFIG_CHARGER_BQ24735) += bq24735-charger.o +obj-$(CONFIG_POWER_AVS) += avs/ +obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o +obj-$(CONFIG_POWER_RESET) += reset/ diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c new file mode 100644 index 00000000000..d2986453309 --- /dev/null +++ b/drivers/power/ab8500_bmdata.c @@ -0,0 +1,605 @@ +#include <linux/export.h> +#include <linux/power_supply.h> +#include <linux/of.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-bm.h> + +/* + * These are the defined batteries that uses a NTC and ID resistor placed + * inside of the battery pack. + * Note that the res_to_temp table must be strictly sorted by falling resistance + * values to work. + */ +const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = { + {-5, 53407}, + { 0, 48594}, + { 5, 43804}, + {10, 39188}, + {15, 34870}, + {20, 30933}, + {25, 27422}, + {30, 24347}, + {35, 21694}, + {40, 19431}, + {45, 17517}, + {50, 15908}, + {55, 14561}, + {60, 13437}, + {65, 12500}, +}; +EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor); + +const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_a_size); + +const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = { + {-5, 200000}, + { 0, 159024}, + { 5, 151921}, + {10, 144300}, + {15, 136424}, + {20, 128565}, + {25, 120978}, + {30, 113875}, + {35, 107397}, + {40, 101629}, + {45, 96592}, + {50, 92253}, + {55, 88569}, + {60, 85461}, + {65, 82869}, +}; +EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor); + +const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_b_size); + +static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = { + {4171, 100}, + {4114, 95}, + {4009, 83}, + {3947, 74}, + {3907, 67}, + {3863, 59}, + {3830, 56}, + {3813, 53}, + {3791, 46}, + {3771, 33}, + {3754, 25}, + {3735, 20}, + {3717, 17}, + {3681, 13}, + {3664, 8}, + {3651, 6}, + {3635, 5}, + {3560, 3}, + {3408, 1}, + {3247, 0}, +}; + +static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = { + {4161, 100}, + {4124, 98}, + {4044, 90}, + {4003, 85}, + {3966, 80}, + {3933, 75}, + {3888, 67}, + {3849, 60}, + {3813, 55}, + {3787, 47}, + {3772, 30}, + {3751, 25}, + {3718, 20}, + {3681, 16}, + {3660, 14}, + {3589, 10}, + {3546, 7}, + {3495, 4}, + {3404, 2}, + {3250, 0}, +}; + +static const struct abx500_v_to_cap cap_tbl[] = { + {4186, 100}, + {4163, 99}, + {4114, 95}, + {4068, 90}, + {3990, 80}, + {3926, 70}, + {3898, 65}, + {3866, 60}, + {3833, 55}, + {3812, 50}, + {3787, 40}, + {3768, 30}, + {3747, 25}, + {3730, 20}, + {3705, 15}, + {3699, 14}, + {3684, 12}, + {3672, 9}, + {3657, 7}, + {3638, 6}, + {3556, 4}, + {3424, 2}, + {3317, 1}, + {3094, 0}, +}; + +/* + * Note that the res_to_temp table must be strictly sorted by falling + * resistance values to work. + */ +static const struct abx500_res_to_temp temp_tbl[] = { + {-5, 214834}, + { 0, 162943}, + { 5, 124820}, + {10, 96520}, + {15, 75306}, + {20, 59254}, + {25, 47000}, + {30, 37566}, + {35, 30245}, + {40, 24520}, + {45, 20010}, + {50, 16432}, + {55, 13576}, + {60, 11280}, + {65, 9425}, +}; + +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { + { 40, 120}, + { 30, 135}, + { 20, 165}, + { 10, 230}, + { 00, 325}, + {-10, 445}, + {-20, 595}, +}; + +/* + * Note that the batres_vs_temp table must be strictly sorted by falling + * temperature values to work. + */ +static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { + { 60, 300}, + { 30, 300}, + { 20, 300}, + { 10, 300}, + { 00, 300}, + {-10, 300}, + {-20, 300}, +}; + +/* battery resistance table for LI ION 9100 battery */ +static const struct batres_vs_temp temp_to_batres_tbl_9100[] = { + { 60, 180}, + { 30, 180}, + { 20, 180}, + { 10, 180}, + { 00, 180}, + {-10, 180}, + {-20, 180}, +}; + +static struct abx500_battery_type bat_type_thermistor[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_cap = 95, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 53407, + .resis_low = 12500, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_a_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_a_thermistor), + .v_to_cap_tbl = cap_tbl_a_thermistor, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 200000, + .resis_low = 82869, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3600, + .termination_vol = 4150, + .termination_curr = 80, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_b_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_b_thermistor), + .v_to_cap_tbl = cap_tbl_b_thermistor, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +}; + +static struct abx500_battery_type bat_type_ext_thermistor[] = { + [BATTERY_UNKNOWN] = { + /* First element always represent the UNKNOWN battery */ + .name = POWER_SUPPLY_TECHNOLOGY_UNKNOWN, + .resis_high = 0, + .resis_low = 0, + .battery_resistance = 300, + .charge_full_design = 612, + .nominal_voltage = 3700, + .termination_vol = 4050, + .termination_curr = 200, + .recharge_cap = 95, + .normal_cur_lvl = 400, + .normal_vol_lvl = 4100, + .maint_a_cur_lvl = 400, + .maint_a_vol_lvl = 4050, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 400, + .maint_b_vol_lvl = 4000, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +/* + * These are the batteries that doesn't have an internal NTC resistor to measure + * its temperature. The temperature in this case is measure with a NTC placed + * near the battery but on the PCB. + */ + { + .name = POWER_SUPPLY_TECHNOLOGY_LIPO, + .resis_high = 76000, + .resis_low = 53000, + .battery_resistance = 300, + .charge_full_design = 900, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 30000, + .resis_low = 10000, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, + { + .name = POWER_SUPPLY_TECHNOLOGY_LION, + .resis_high = 95000, + .resis_low = 76001, + .battery_resistance = 300, + .charge_full_design = 950, + .nominal_voltage = 3700, + .termination_vol = 4150, + .termination_curr = 100, + .recharge_cap = 95, + .normal_cur_lvl = 700, + .normal_vol_lvl = 4200, + .maint_a_cur_lvl = 600, + .maint_a_vol_lvl = 4150, + .maint_a_chg_timer_h = 60, + .maint_b_cur_lvl = 600, + .maint_b_vol_lvl = 4100, + .maint_b_chg_timer_h = 200, + .low_high_cur_lvl = 300, + .low_high_vol_lvl = 4000, + .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl), + .r_to_t_tbl = temp_tbl, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl), + .v_to_cap_tbl = cap_tbl, + .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), + .batres_tbl = temp_to_batres_tbl_thermistor, + }, +}; + +static const struct abx500_bm_capacity_levels cap_levels = { + .critical = 2, + .low = 10, + .normal = 70, + .high = 95, + .full = 100, +}; + +static const struct abx500_fg_parameters fg = { + .recovery_sleep_timer = 10, + .recovery_total_time = 100, + .init_timer = 1, + .init_discard_time = 5, + .init_total_time = 40, + .high_curr_time = 60, + .accu_charging = 30, + .accu_high_curr = 30, + .high_curr_threshold = 50, + .lowbat_threshold = 3100, + .battok_falling_th_sel0 = 2860, + .battok_raising_th_sel1 = 2860, + .maint_thres = 95, + .user_cap_limit = 15, + .pcut_enable = 1, + .pcut_max_time = 127, + .pcut_flag_time = 112, + .pcut_max_restart = 15, + .pcut_debounce_time = 2, +}; + +static const struct abx500_maxim_parameters ab8500_maxi_params = { + .ena_maxi = true, + .chg_curr = 910, + .wait_cycles = 10, + .charger_curr_step = 100, +}; + +static const struct abx500_maxim_parameters abx540_maxi_params = { + .ena_maxi = true, + .chg_curr = 3000, + .wait_cycles = 10, + .charger_curr_step = 200, +}; + +static const struct abx500_bm_charger_parameters chg = { + .usb_volt_max = 5500, + .usb_curr_max = 1500, + .ac_volt_max = 7500, + .ac_curr_max = 1500, +}; + +/* + * This array maps the raw hex value to charger output current used by the + * AB8500 values + */ +static int ab8500_charge_output_curr_map[] = { + 100, 200, 300, 400, 500, 600, 700, 800, + 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500, +}; + +static int ab8540_charge_output_curr_map[] = { + 0, 0, 0, 75, 100, 125, 150, 175, + 200, 225, 250, 275, 300, 325, 350, 375, + 400, 425, 450, 475, 500, 525, 550, 575, + 600, 625, 650, 675, 700, 725, 750, 775, + 800, 825, 850, 875, 900, 925, 950, 975, + 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, + 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375, + 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000, +}; + +/* + * This array maps the raw hex value to charger input current used by the + * AB8500 values + */ +static int ab8500_charge_input_curr_map[] = { + 50, 98, 193, 290, 380, 450, 500, 600, + 700, 800, 900, 1000, 1100, 1300, 1400, 1500, +}; + +static int ab8540_charge_input_curr_map[] = { + 25, 50, 75, 100, 125, 150, 175, 200, + 225, 250, 275, 300, 325, 350, 375, 400, + 425, 450, 475, 500, 525, 550, 575, 600, + 625, 650, 675, 700, 725, 750, 775, 800, + 825, 850, 875, 900, 925, 950, 975, 1000, + 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500, +}; + +struct abx500_bm_data ab8500_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, + .capacity_scaling = false, + .adc_therm = ABx500_ADC_THERM_BATCTRL, + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type_thermistor, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 34, + .chg_output_curr = ab8500_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map), + .maxi = &ab8500_maxi_params, + .chg_params = &chg, + .fg_params = &fg, + .chg_input_curr = ab8500_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map), +}; + +struct abx500_bm_data ab8540_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, + .capacity_scaling = false, + .adc_therm = ABx500_ADC_THERM_BATCTRL, + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type_thermistor, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 0, + .maxi = &abx540_maxi_params, + .chg_params = &chg, + .fg_params = &fg, + .chg_output_curr = ab8540_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map), + .chg_input_curr = ab8540_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map), +}; + +int ab8500_bm_of_probe(struct device *dev, + struct device_node *np, + struct abx500_bm_data *bm) +{ + const struct batres_vs_temp *tmp_batres_tbl; + struct device_node *battery_node; + const char *btech; + int i; + + /* get phandle to 'battery-info' node */ + battery_node = of_parse_phandle(np, "battery", 0); + if (!battery_node) { + dev_err(dev, "battery node or reference missing\n"); + return -EINVAL; + } + + btech = of_get_property(battery_node, "stericsson,battery-type", NULL); + if (!btech) { + dev_warn(dev, "missing property battery-name/type\n"); + return -EINVAL; + } + + if (strncmp(btech, "LION", 4) == 0) { + bm->no_maintenance = true; + bm->chg_unknown_bat = true; + bm->bat_type[BATTERY_UNKNOWN].charge_full_design = 2600; + bm->bat_type[BATTERY_UNKNOWN].termination_vol = 4150; + bm->bat_type[BATTERY_UNKNOWN].recharge_cap = 95; + bm->bat_type[BATTERY_UNKNOWN].normal_cur_lvl = 520; + bm->bat_type[BATTERY_UNKNOWN].normal_vol_lvl = 4200; + } + + if (of_property_read_bool(battery_node, "thermistor-on-batctrl")) { + if (strncmp(btech, "LION", 4) == 0) + tmp_batres_tbl = temp_to_batres_tbl_9100; + else + tmp_batres_tbl = temp_to_batres_tbl_thermistor; + } else { + bm->n_btypes = 4; + bm->bat_type = bat_type_ext_thermistor; + bm->adc_therm = ABx500_ADC_THERM_BATTEMP; + tmp_batres_tbl = temp_to_batres_tbl_ext_thermistor; + } + + /* select the battery resolution table */ + for (i = 0; i < bm->n_btypes; ++i) + bm->bat_type[i].batres_tbl = tmp_batres_tbl; + + of_node_put(battery_node); + + return 0; +} diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c new file mode 100644 index 00000000000..7f9a4547dcc --- /dev/null +++ b/drivers/power/ab8500_btemp.c @@ -0,0 +1,1219 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Battery temperature driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/jiffies.h> +#include <linux/of.h> +#include <linux/mfd/core.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> + +#define VTVOUT_V 1800 + +#define BTEMP_THERMAL_LOW_LIMIT -10 +#define BTEMP_THERMAL_MED_LIMIT 0 +#define BTEMP_THERMAL_HIGH_LIMIT_52 52 +#define BTEMP_THERMAL_HIGH_LIMIT_57 57 +#define BTEMP_THERMAL_HIGH_LIMIT_62 62 + +#define BTEMP_BATCTRL_CURR_SRC_7UA 7 +#define BTEMP_BATCTRL_CURR_SRC_20UA 20 + +#define BTEMP_BATCTRL_CURR_SRC_16UA 16 +#define BTEMP_BATCTRL_CURR_SRC_18UA 18 + +#define BTEMP_BATCTRL_CURR_SRC_60UA 60 +#define BTEMP_BATCTRL_CURR_SRC_120UA 120 + +#define to_ab8500_btemp_device_info(x) container_of((x), \ + struct ab8500_btemp, btemp_psy); + +/** + * 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; + + di = to_ab8500_btemp_device_info(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; + struct ab8500_btemp *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_btemp_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->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 = to_ab8500_btemp_device_info(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 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 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; + + /* BTEMP supply */ + di->btemp_psy.name = "ab8500_btemp"; + di->btemp_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->btemp_psy.properties = ab8500_btemp_props; + di->btemp_psy.num_properties = ARRAY_SIZE(ab8500_btemp_props); + di->btemp_psy.get_property = ab8500_btemp_get_property; + di->btemp_psy.supplied_to = supply_interface; + di->btemp_psy.num_supplicants = ARRAY_SIZE(supply_interface); + di->btemp_psy.external_power_changed = + ab8500_btemp_external_power_changed; + + + /* 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 */ + ret = power_supply_register(di->dev, &di->btemp_psy); + if (ret) { + dev_err(di->dev, "failed to register BTEMP psy\n"); + 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", + .owner = THIS_MODULE, + .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 new file mode 100644 index 00000000000..19110aa613a --- /dev/null +++ b/drivers/power/ab8500_charger.c @@ -0,0 +1,3757 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * + * Charger driver for AB8500 + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/notifier.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/of.h> +#include <linux/mfd/core.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/usb/otg.h> +#include <linux/mutex.h> + +/* Charger constants */ +#define NO_PW_CONN 0 +#define AC_PW_CONN 1 +#define USB_PW_CONN 2 + +#define MAIN_WDOG_ENA 0x01 +#define MAIN_WDOG_KICK 0x02 +#define MAIN_WDOG_DIS 0x00 +#define CHARG_WD_KICK 0x01 +#define MAIN_CH_ENA 0x01 +#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 +#define USB_CH_ENA 0x01 +#define USB_CHG_NO_OVERSHOOT_ENA_N 0x02 +#define MAIN_CH_DET 0x01 +#define MAIN_CH_CV_ON 0x04 +#define USB_CH_CV_ON 0x08 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 +#define OTP_ENABLE_WD 0x01 +#define DROP_COUNT_RESET 0x01 +#define USB_CH_DET 0x01 + +#define MAIN_CH_INPUT_CURR_SHIFT 4 +#define VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2 +#define AUTO_VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F +#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */ + +#define LED_INDICATOR_PWM_ENA 0x01 +#define LED_INDICATOR_PWM_DIS 0x00 +#define LED_IND_CUR_5MA 0x04 +#define LED_INDICATOR_PWM_DUTY_252_256 0xBF + +/* HW failure constants */ +#define MAIN_CH_TH_PROT 0x02 +#define VBUS_CH_NOK 0x08 +#define USB_CH_TH_PROT 0x02 +#define VBUS_OVV_TH 0x01 +#define MAIN_CH_NOK 0x01 +#define VBUS_DET 0x80 + +#define MAIN_CH_STATUS2_MAINCHGDROP 0x80 +#define MAIN_CH_STATUS2_MAINCHARGERDETDBNC 0x40 +#define USB_CH_VBUSDROP 0x40 +#define USB_CH_VBUSDETDBNC 0x01 + +/* UsbLineStatus register bit masks */ +#define AB8500_USB_LINK_STATUS 0x78 +#define AB8505_USB_LINK_STATUS 0xF8 +#define AB8500_STD_HOST_SUSP 0x18 +#define USB_LINK_STATUS_SHIFT 3 + +/* Watchdog timeout constant */ +#define WD_TIMER 0x30 /* 4min */ +#define WD_KICK_INTERVAL (60 * HZ) + +/* Lowest charger voltage is 3.39V -> 0x4E */ +#define LOW_VOLT_REG 0x4E + +/* Step up/down delay in us */ +#define STEP_UDELAY 1000 + +#define CHARGER_STATUS_POLL 10 /* in ms */ + +#define CHG_WD_INTERVAL (60 * HZ) + +#define AB8500_SW_CONTROL_FALLBACK 0x03 +/* Wait for enumeration before charing in us */ +#define WAIT_ACA_RID_ENUMERATION (5 * 1000) +/*External charger control*/ +#define AB8500_SYS_CHARGER_CONTROL_REG 0x52 +#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03 +#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07 + +/* UsbLineStatus register - usb types */ +enum ab8500_charger_link_status { + USB_STAT_NOT_CONFIGURED, + USB_STAT_STD_HOST_NC, + USB_STAT_STD_HOST_C_NS, + USB_STAT_STD_HOST_C_S, + USB_STAT_HOST_CHG_NM, + USB_STAT_HOST_CHG_HS, + USB_STAT_HOST_CHG_HS_CHIRP, + USB_STAT_DEDICATED_CHG, + USB_STAT_ACA_RID_A, + USB_STAT_ACA_RID_B, + USB_STAT_ACA_RID_C_NM, + USB_STAT_ACA_RID_C_HS, + USB_STAT_ACA_RID_C_HS_CHIRP, + USB_STAT_HM_IDGND, + USB_STAT_RESERVED, + USB_STAT_NOT_VALID_LINK, + USB_STAT_PHY_EN, + USB_STAT_SUP_NO_IDGND_VBUS, + USB_STAT_SUP_IDGND_VBUS, + USB_STAT_CHARGER_LINE_1, + USB_STAT_CARKIT_1, + USB_STAT_CARKIT_2, + USB_STAT_ACA_DOCK_CHARGER, +}; + +enum ab8500_usb_state { + AB8500_BM_USB_STATE_RESET_HS, /* HighSpeed Reset */ + AB8500_BM_USB_STATE_RESET_FS, /* FullSpeed/LowSpeed Reset */ + AB8500_BM_USB_STATE_CONFIGURED, + AB8500_BM_USB_STATE_SUSPEND, + AB8500_BM_USB_STATE_RESUME, + AB8500_BM_USB_STATE_MAX, +}; + +/* VBUS input current limits supported in AB8500 in mA */ +#define USB_CH_IP_CUR_LVL_0P05 50 +#define USB_CH_IP_CUR_LVL_0P09 98 +#define USB_CH_IP_CUR_LVL_0P19 193 +#define USB_CH_IP_CUR_LVL_0P29 290 +#define USB_CH_IP_CUR_LVL_0P38 380 +#define USB_CH_IP_CUR_LVL_0P45 450 +#define USB_CH_IP_CUR_LVL_0P5 500 +#define USB_CH_IP_CUR_LVL_0P6 600 +#define USB_CH_IP_CUR_LVL_0P7 700 +#define USB_CH_IP_CUR_LVL_0P8 800 +#define USB_CH_IP_CUR_LVL_0P9 900 +#define USB_CH_IP_CUR_LVL_1P0 1000 +#define USB_CH_IP_CUR_LVL_1P1 1100 +#define USB_CH_IP_CUR_LVL_1P3 1300 +#define USB_CH_IP_CUR_LVL_1P4 1400 +#define USB_CH_IP_CUR_LVL_1P5 1500 + +#define VBAT_TRESH_IP_CUR_RED 3800 + +#define to_ab8500_charger_usb_device_info(x) container_of((x), \ + struct ab8500_charger, usb_chg) +#define to_ab8500_charger_ac_device_info(x) container_of((x), \ + struct ab8500_charger, ac_chg) + +/** + * struct ab8500_charger_interrupts - ab8500 interupts + * @name: name of the interrupt + * @isr function pointer to the isr + */ +struct ab8500_charger_interrupts { + char *name; + irqreturn_t (*isr)(int irq, void *data); +}; + +struct ab8500_charger_info { + int charger_connected; + int charger_online; + int charger_voltage; + int cv_active; + bool wd_expired; + int charger_current; +}; + +struct ab8500_charger_event_flags { + bool mainextchnotok; + bool main_thermal_prot; + bool usb_thermal_prot; + bool vbus_ovv; + bool usbchargernotok; + bool chgwdexp; + bool vbus_collapse; + bool vbus_drop_end; +}; + +struct ab8500_charger_usb_state { + int usb_current; + int usb_current_tmp; + enum ab8500_usb_state state; + enum ab8500_usb_state state_tmp; + spinlock_t usb_lock; +}; + +struct ab8500_charger_max_usb_in_curr { + int usb_type_max; + int set_max; + int calculated_max; +}; + +/** + * struct ab8500_charger - ab8500 Charger device information + * @dev: Pointer to the structure device + * @vbus_detected: VBUS detected + * @vbus_detected_start: + * VBUS detected during startup + * @ac_conn: This will be true when the AC charger has been plugged + * @vddadc_en_ac: Indicate if VDD ADC supply is enabled because AC + * charger is enabled + * @vddadc_en_usb: Indicate if VDD ADC supply is enabled because USB + * charger is enabled + * @vbat Battery voltage + * @old_vbat Previously measured battery voltage + * @usb_device_is_unrecognised USB device is unrecognised by the hardware + * @autopower Indicate if we should have automatic pwron after pwrloss + * @autopower_cfg platform specific power config support for "pwron after pwrloss" + * @invalid_charger_detect_state State when forcing AB to use invalid charger + * @is_aca_rid: Incicate if accessory is ACA type + * @current_stepping_sessions: + * Counter for current stepping sessions + * @parent: Pointer to the struct ab8500 + * @gpadc: Pointer to the struct gpadc + * @bm: Platform specific battery management information + * @flags: Structure for information about events triggered + * @usb_state: Structure for usb stack information + * @max_usb_in_curr: Max USB charger input current + * @ac_chg: AC charger power supply + * @usb_chg: USB charger power supply + * @ac: Structure that holds the AC charger properties + * @usb: Structure that holds the USB charger properties + * @regu: Pointer to the struct regulator + * @charger_wq: Work queue for the IRQs and checking HW state + * @usb_ipt_crnt_lock: Lock to protect VBUS input current setting from mutuals + * @pm_lock: Lock to prevent system to suspend + * @check_vbat_work Work for checking vbat threshold to adjust vbus current + * @check_hw_failure_work: Work for checking HW state + * @check_usbchgnotok_work: Work for checking USB charger not ok status + * @kick_wd_work: Work for kicking the charger watchdog in case + * of ABB rev 1.* due to the watchog logic bug + * @ac_charger_attached_work: Work for checking if AC charger is still + * connected + * @usb_charger_attached_work: Work for checking if USB charger is still + * connected + * @ac_work: Work for checking AC charger connection + * @detect_usb_type_work: Work for detecting the USB type connected + * @usb_link_status_work: Work for checking the new USB link status + * @usb_state_changed_work: Work for checking USB state + * @attach_work: Work for detecting USB type + * @vbus_drop_end_work: Work for detecting VBUS drop end + * @check_main_thermal_prot_work: + * Work for checking Main thermal status + * @check_usb_thermal_prot_work: + * Work for checking USB thermal status + * @charger_attached_mutex: For controlling the wakelock + */ +struct ab8500_charger { + struct device *dev; + bool vbus_detected; + bool vbus_detected_start; + bool ac_conn; + bool vddadc_en_ac; + bool vddadc_en_usb; + int vbat; + int old_vbat; + bool usb_device_is_unrecognised; + bool autopower; + bool autopower_cfg; + int invalid_charger_detect_state; + int is_aca_rid; + atomic_t current_stepping_sessions; + struct ab8500 *parent; + struct ab8500_gpadc *gpadc; + struct abx500_bm_data *bm; + struct ab8500_charger_event_flags flags; + struct ab8500_charger_usb_state usb_state; + struct ab8500_charger_max_usb_in_curr max_usb_in_curr; + struct ux500_charger ac_chg; + struct ux500_charger usb_chg; + struct ab8500_charger_info ac; + struct ab8500_charger_info usb; + struct regulator *regu; + struct workqueue_struct *charger_wq; + struct mutex usb_ipt_crnt_lock; + struct delayed_work check_vbat_work; + struct delayed_work check_hw_failure_work; + struct delayed_work check_usbchgnotok_work; + struct delayed_work kick_wd_work; + struct delayed_work usb_state_changed_work; + struct delayed_work attach_work; + struct delayed_work ac_charger_attached_work; + struct delayed_work usb_charger_attached_work; + struct delayed_work vbus_drop_end_work; + struct work_struct ac_work; + struct work_struct detect_usb_type_work; + struct work_struct usb_link_status_work; + struct work_struct check_main_thermal_prot_work; + struct work_struct check_usb_thermal_prot_work; + struct usb_phy *usb_phy; + struct notifier_block nb; + struct mutex charger_attached_mutex; +}; + +/* AC properties */ +static enum power_supply_property ab8500_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* USB properties */ +static enum power_supply_property ab8500_charger_usb_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_CURRENT_AVG, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_AVG, + POWER_SUPPLY_PROP_CURRENT_NOW, +}; + +/* + * Function for enabling and disabling sw fallback mode + * should always be disabled when no charger is connected. + */ +static void ab8500_enable_disable_sw_fallback(struct ab8500_charger *di, + bool fallback) +{ + u8 val; + u8 reg; + u8 bank; + u8 bit; + int ret; + + dev_dbg(di->dev, "SW Fallback: %d\n", fallback); + + if (is_ab8500(di->parent)) { + bank = 0x15; + reg = 0x0; + bit = 3; + } else { + bank = AB8500_SYS_CTRL1_BLOCK; + reg = AB8500_SW_CONTROL_FALLBACK; + bit = 0; + } + + /* read the register containing fallback bit */ + ret = abx500_get_register_interruptible(di->dev, bank, reg, &val); + if (ret < 0) { + dev_err(di->dev, "%d read failed\n", __LINE__); + return; + } + + if (is_ab8500(di->parent)) { + /* enable the OPT emulation registers */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x2); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + goto disable_otp; + } + } + + if (fallback) + val |= (1 << bit); + else + val &= ~(1 << bit); + + /* write back the changed fallback bit value to register */ + ret = abx500_set_register_interruptible(di->dev, bank, reg, val); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + } + +disable_otp: + if (is_ab8500(di->parent)) { + /* disable the set OTP registers again */ + ret = abx500_set_register_interruptible(di->dev, 0x11, 0x00, 0x0); + if (ret) { + dev_err(di->dev, "%d write failed\n", __LINE__); + } + } +} + +/** + * ab8500_power_supply_changed - a wrapper with local extentions for + * power_supply_changed + * @di: pointer to the ab8500_charger structure + * @psy: pointer to power_supply_that have changed. + * + */ +static void ab8500_power_supply_changed(struct ab8500_charger *di, + struct power_supply *psy) +{ + if (di->autopower_cfg) { + if (!di->usb.charger_connected && + !di->ac.charger_connected && + di->autopower) { + di->autopower = false; + ab8500_enable_disable_sw_fallback(di, false); + } else if (!di->autopower && + (di->ac.charger_connected || + di->usb.charger_connected)) { + di->autopower = true; + ab8500_enable_disable_sw_fallback(di, true); + } + } + power_supply_changed(psy); +} + +static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, + bool connected) +{ + if (connected != di->usb.charger_connected) { + dev_dbg(di->dev, "USB connected:%i\n", connected); + di->usb.charger_connected = connected; + + if (!connected) + di->flags.vbus_drop_end = false; + + sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present"); + + if (connected) { + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); + } else { + cancel_delayed_work_sync(&di->usb_charger_attached_work); + mutex_lock(&di->charger_attached_mutex); + mutex_unlock(&di->charger_attached_mutex); + } + } +} + +/** + * ab8500_charger_get_ac_voltage() - get ac charger voltage + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger voltage (on success) + */ +static int ab8500_charger_get_ac_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->ac.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed,\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_ac_cv() - check if the main charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_ac_cv(struct ab8500_charger *di) +{ + u8 val; + int ret = 0; + + /* Only check CV mode if the charger is online */ + if (di->ac.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & MAIN_CH_CV_ON) + ret = 1; + else + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_get_vbus_voltage() - get vbus voltage + * @di: pointer to the ab8500_charger structure + * + * This function returns the vbus voltage. + * Returns vbus voltage (on success) + */ +static int ab8500_charger_get_vbus_voltage(struct ab8500_charger *di) +{ + int vch; + + /* Only measure voltage if the charger is connected */ + if (di->usb.charger_connected) { + vch = ab8500_gpadc_convert(di->gpadc, VBUS_V); + if (vch < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + vch = 0; + } + return vch; +} + +/** + * ab8500_charger_get_usb_current() - get usb charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the usb charger current. + * Returns usb current (on success) and error code on failure + */ +static int ab8500_charger_get_usb_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->usb.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, USB_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_get_ac_current() - get ac charger current + * @di: pointer to the ab8500_charger structure + * + * This function returns the ac charger current. + * Returns ac current (on success) and error code on failure. + */ +static int ab8500_charger_get_ac_current(struct ab8500_charger *di) +{ + int ich; + + /* Only measure current if the charger is online */ + if (di->ac.charger_online) { + ich = ab8500_gpadc_convert(di->gpadc, MAIN_CHARGER_C); + if (ich < 0) + dev_err(di->dev, "%s gpadc conv failed\n", __func__); + } else { + ich = 0; + } + return ich; +} + +/** + * ab8500_charger_usb_cv() - check if the usb charger is in CV mode + * @di: pointer to the ab8500_charger structure + * + * Returns ac charger CV mode (on success) else error code + */ +static int ab8500_charger_usb_cv(struct ab8500_charger *di) +{ + int ret; + u8 val; + + /* Only check CV mode if the charger is online */ + if (di->usb.charger_online) { + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return 0; + } + + if (val & USB_CH_CV_ON) + ret = 1; + else + ret = 0; + } else { + ret = 0; + } + + return ret; +} + +/** + * ab8500_charger_detect_chargers() - Detect the connected chargers + * @di: pointer to the ab8500_charger structure + * @probe: if probe, don't delay and wait for HW + * + * Returns the type of charger connected. + * For USB it will not mean we can actually charge from it + * but that there is a USB cable connected that we have to + * identify. This is used during startup when we don't get + * interrupts of the charger detection + * + * Returns an integer value, that means, + * NO_PW_CONN no power supply is connected + * AC_PW_CONN if the AC power supply is connected + * USB_PW_CONN if the USB power supply is connected + * AC_PW_CONN + USB_PW_CONN if USB and AC power supplies are both connected + */ +static int ab8500_charger_detect_chargers(struct ab8500_charger *di, bool probe) +{ + int result = NO_PW_CONN; + int ret; + u8 val; + + /* Check for AC charger */ + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_STATUS1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (val & MAIN_CH_DET) + result = AC_PW_CONN; + + /* Check for USB charger */ + + if (!probe) { + /* + * AB8500 says VBUS_DET_DBNC1 & VBUS_DET_DBNC100 + * when disconnecting ACA even though no + * charger was connected. Try waiting a little + * longer than the 100 ms of VBUS_DET_DBNC100... + */ + msleep(110); + } + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + dev_dbg(di->dev, + "%s AB8500_CH_USBCH_STAT1_REG %x\n", __func__, + val); + if ((val & VBUS_DET_DBNC1) && (val & VBUS_DET_DBNC100)) + result |= USB_PW_CONN; + + return result; +} + +/** + * ab8500_charger_max_usb_curr() - get the max curr for the USB type + * @di: pointer to the ab8500_charger structure + * @link_status: the identified USB type + * + * Get the maximum current that is allowed to be drawn from the host + * based on the USB type. + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, + enum ab8500_charger_link_status link_status) +{ + int ret = 0; + + di->usb_device_is_unrecognised = false; + + /* + * Platform only supports USB 2.0. + * This means that charging current from USB source + * is maximum 500 mA. Every occurence of USB_STAT_*_HOST_* + * should set USB_CH_IP_CUR_LVL_0P5. + */ + + switch (link_status) { + case USB_STAT_STD_HOST_NC: + case USB_STAT_STD_HOST_C_NS: + case USB_STAT_STD_HOST_C_S: + dev_dbg(di->dev, "USB Type - Standard host is " + "detected through USB driver\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_HOST_CHG_HS_CHIRP: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_HOST_CHG_HS: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_C_HS: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_A: + /* + * Dedicated charger level minus maximum current accessory + * can consume (900mA). Closest level is 500mA + */ + dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 1; + break; + case USB_STAT_ACA_RID_B: + /* + * Dedicated charger level minus 120mA (20mA for ACA and + * 100mA for potential accessory). Closest level is 1300mA + */ + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, + di->max_usb_in_curr.usb_type_max); + di->is_aca_rid = 1; + break; + case USB_STAT_HOST_CHG_NM: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + di->is_aca_rid = 0; + break; + case USB_STAT_DEDICATED_CHG: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; + di->is_aca_rid = 0; + break; + case USB_STAT_ACA_RID_C_HS_CHIRP: + case USB_STAT_ACA_RID_C_NM: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; + di->is_aca_rid = 1; + break; + case USB_STAT_NOT_CONFIGURED: + if (di->vbus_detected) { + di->usb_device_is_unrecognised = true; + dev_dbg(di->dev, "USB Type - Legacy charger.\n"); + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_1P5; + break; + } + case USB_STAT_HM_IDGND: + dev_err(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + case USB_STAT_RESERVED: + if (is_ab8500(di->parent)) { + di->flags.vbus_collapse = true; + dev_err(di->dev, "USB Type - USB_STAT_RESERVED " + "VBUS has collapsed\n"); + ret = -ENXIO; + break; + } else { + dev_dbg(di->dev, "USB Type - Charging not allowed\n"); + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_0P05; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, + di->max_usb_in_curr.usb_type_max); + ret = -ENXIO; + break; + } + case USB_STAT_CARKIT_1: + case USB_STAT_CARKIT_2: + case USB_STAT_ACA_DOCK_CHARGER: + case USB_STAT_CHARGER_LINE_1: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, + di->max_usb_in_curr.usb_type_max); + break; + case USB_STAT_NOT_VALID_LINK: + dev_err(di->dev, "USB Type invalid - try charging anyway\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + break; + + default: + dev_err(di->dev, "USB Type - Unknown\n"); + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -ENXIO; + break; + }; + + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; + dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", + link_status, di->max_usb_in_curr.set_max); + + return ret; +} + +/** + * ab8500_charger_read_usb_type() - read the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_read_usb_type(struct ab8500_charger *di) +{ + int ret; + u8 val; + + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + /* get the USB type */ + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/** + * ab8500_charger_detect_usb_type() - get the type of usb connected + * @di: pointer to the ab8500_charger structure + * + * Detect the type of the plugged USB + * Returns error code in case of failure else 0 on success + */ +static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) +{ + int i, ret; + u8 val; + + /* + * On getting the VBUS rising edge detect interrupt there + * is a 250ms delay after which the register UsbLineStatus + * is filled with valid data. + */ + for (i = 0; i < 10; i++) { + msleep(250); + ret = abx500_get_register_interruptible(di->dev, + AB8500_INTERRUPT, AB8500_IT_SOURCE21_REG, + &val); + dev_dbg(di->dev, "%s AB8500_IT_SOURCE21_REG %x\n", + __func__, val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); + if (ret < 0) { + dev_err(di->dev, "%s ab8500 read failed\n", __func__); + return ret; + } + dev_dbg(di->dev, "%s AB8500_USB_LINE_STAT_REG %x\n", __func__, + val); + /* + * Until the IT source register is read the UsbLineStatus + * register is not updated, hence doing the same + * Revisit this: + */ + + /* get the USB type */ + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + if (val) + break; + } + ret = ab8500_charger_max_usb_curr(di, + (enum ab8500_charger_link_status) val); + + return ret; +} + +/* + * This array maps the raw hex value to charger voltage used by the AB8500 + * Values taken from the UM0836 + */ +static int ab8500_charger_voltage_map[] = { + 3500 , + 3525 , + 3550 , + 3575 , + 3600 , + 3625 , + 3650 , + 3675 , + 3700 , + 3725 , + 3750 , + 3775 , + 3800 , + 3825 , + 3850 , + 3875 , + 3900 , + 3925 , + 3950 , + 3975 , + 4000 , + 4025 , + 4050 , + 4060 , + 4070 , + 4080 , + 4090 , + 4100 , + 4110 , + 4120 , + 4130 , + 4140 , + 4150 , + 4160 , + 4170 , + 4180 , + 4190 , + 4200 , + 4210 , + 4220 , + 4230 , + 4240 , + 4250 , + 4260 , + 4270 , + 4280 , + 4290 , + 4300 , + 4310 , + 4320 , + 4330 , + 4340 , + 4350 , + 4360 , + 4370 , + 4380 , + 4390 , + 4400 , + 4410 , + 4420 , + 4430 , + 4440 , + 4450 , + 4460 , + 4470 , + 4480 , + 4490 , + 4500 , + 4510 , + 4520 , + 4530 , + 4540 , + 4550 , + 4560 , + 4570 , + 4580 , + 4590 , + 4600 , +}; + +static int ab8500_voltage_to_regval(int voltage) +{ + int i; + + /* Special case for voltage below 3.5V */ + if (voltage < ab8500_charger_voltage_map[0]) + return LOW_VOLT_REG; + + for (i = 1; i < ARRAY_SIZE(ab8500_charger_voltage_map); i++) { + if (voltage < ab8500_charger_voltage_map[i]) + return i - 1; + } + + /* If not last element, return error */ + i = ARRAY_SIZE(ab8500_charger_voltage_map) - 1; + if (voltage == ab8500_charger_voltage_map[i]) + return i; + else + return -1; +} + +static int ab8500_current_to_regval(struct ab8500_charger *di, int curr) +{ + int i; + + if (curr < di->bm->chg_output_curr[0]) + return 0; + + for (i = 0; i < di->bm->n_chg_out_curr; i++) { + if (curr < di->bm->chg_output_curr[i]) + return i - 1; + } + + /* If not last element, return error */ + i = di->bm->n_chg_out_curr - 1; + if (curr == di->bm->chg_output_curr[i]) + return i; + else + return -1; +} + +static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr) +{ + int i; + + if (curr < di->bm->chg_input_curr[0]) + return 0; + + for (i = 0; i < di->bm->n_chg_in_curr; i++) { + if (curr < di->bm->chg_input_curr[i]) + return i - 1; + } + + /* If not last element, return error */ + i = di->bm->n_chg_in_curr - 1; + if (curr == di->bm->chg_input_curr[i]) + return i; + else + return -1; +} + +/** + * ab8500_charger_get_usb_cur() - get usb current + * @di: pointer to the ab8500_charger structre + * + * The usb stack provides the maximum current that can be drawn from + * the standard usb host. This will be in mA. + * This function converts current in mA to a value that can be written + * to the register. Returns -1 if charging is not allowed + */ +static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) +{ + int ret = 0; + switch (di->usb_state.usb_current) { + case 100: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09; + break; + case 200: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19; + break; + case 300: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29; + break; + case 400: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38; + break; + case 500: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; + break; + default: + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -EPERM; + break; + }; + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; + return ret; +} + +/** + * ab8500_charger_check_continue_stepping() - Check to allow stepping + * @di: pointer to the ab8500_charger structure + * @reg: select what charger register to check + * + * Check if current stepping should be allowed to continue. + * Checks if charger source has not collapsed. If it has, further stepping + * is not allowed. + */ +static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di, + int reg) +{ + if (reg == AB8500_USBCH_IPT_CRNTLVL_REG) + return !di->flags.vbus_drop_end; + else + return true; +} + +/** + * ab8500_charger_set_current() - set charger current + * @di: pointer to the ab8500_charger structure + * @ich: charger current, in mA + * @reg: select what charger register to set + * + * Set charger current. + * There is no state machine in the AB to step up/down the charger + * current to avoid dips and spikes on MAIN, VBUS and VBAT when + * charging is started. Instead we need to implement + * this charger current step-up/down here. + * Returns error code in case of failure else 0(on success) + */ +static int ab8500_charger_set_current(struct ab8500_charger *di, + int ich, int reg) +{ + int ret = 0; + int curr_index, prev_curr_index, shift_value, i; + u8 reg_value; + u32 step_udelay; + bool no_stepping = false; + + atomic_inc(&di->current_stepping_sessions); + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + reg, ®_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.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.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.type == POWER_SUPPLY_TYPE_MAINS) + di = to_ab8500_charger_ac_device_info(charger); + else if (charger->psy.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.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.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; + struct ab8500_charger *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + struct ux500_charger *usb_chg; + + usb_chg = (struct ux500_charger *)data; + psy = &usb_chg->psy; + + di = to_ab8500_charger_usb_device_info(usb_chg); + + ext = dev_get_drvdata(dev); + + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + switch (ext->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 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 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 supply */ + /* power_supply base class */ + di->ac_chg.psy.name = "ab8500_ac"; + di->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + di->ac_chg.psy.properties = ab8500_charger_ac_props; + di->ac_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_ac_props); + di->ac_chg.psy.get_property = ab8500_charger_ac_get_property; + di->ac_chg.psy.supplied_to = supply_interface; + di->ac_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface), + /* 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 */ + /* power_supply base class */ + di->usb_chg.psy.name = "ab8500_usb"; + di->usb_chg.psy.type = POWER_SUPPLY_TYPE_USB; + di->usb_chg.psy.properties = ab8500_charger_usb_props; + di->usb_chg.psy.num_properties = ARRAY_SIZE(ab8500_charger_usb_props); + di->usb_chg.psy.get_property = ab8500_charger_usb_get_property; + di->usb_chg.psy.supplied_to = supply_interface; + di->usb_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface), + /* 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) { + ret = power_supply_register(di->dev, &di->ac_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register AC charger\n"); + goto free_charger_wq; + } + } + + /* Register USB charger class */ + if (di->usb_chg.enabled) { + ret = power_supply_register(di->dev, &di->usb_chg.psy); + if (ret) { + dev_err(di->dev, "failed to register USB charger\n"); + 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", + .owner = THIS_MODULE, + .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 new file mode 100644 index 00000000000..3cb4178e397 --- /dev/null +++ b/drivers/power/ab8500_fg.c @@ -0,0 +1,3303 @@ +/* + * Copyright (C) ST-Ericsson AB 2012 + * + * Main and Back-up battery management driver. + * + * Note: Backup battery management is required in case of Li-Ion battery and not + * for capacitive battery. HREF boards have capacitive battery and hence backup + * battery management is not used and the supported code is available in this + * driver. + * + * License Terms: GNU General Public License v2 + * Author: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/kobject.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/time.h> +#include <linux/of.h> +#include <linux/completion.h> +#include <linux/mfd/core.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/kernel.h> + +#define MILLI_TO_MICRO 1000 +#define FG_LSB_IN_MA 1627 +#define QLSB_NANO_AMP_HOURS_X10 1071 +#define INS_CURR_TIMEOUT (3 * HZ) + +#define SEC_TO_SAMPLE(S) (S * 4) + +#define NBR_AVG_SAMPLES 20 + +#define LOW_BAT_CHECK_INTERVAL (HZ / 16) /* 62.5 ms */ + +#define VALID_CAPACITY_SEC (45 * 60) /* 45 minutes */ +#define BATT_OK_MIN 2360 /* mV */ +#define BATT_OK_INCREMENT 50 /* mV */ +#define BATT_OK_MAX_NR_INCREMENTS 0xE + +/* FG constants */ +#define BATT_OVV 0x01 + +#define interpolate(x, x1, y1, x2, y2) \ + ((y1) + ((((y2) - (y1)) * ((x) - (x1))) / ((x2) - (x1)))); + +#define to_ab8500_fg_device_info(x) container_of((x), \ + struct ab8500_fg, fg_psy); + +/** + * 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]; + __kernel_time_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 timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + do { + avg->sum += sample - avg->samples[avg->pos]; + avg->samples[avg->pos] = sample; + avg->time_stamps[avg->pos] = ts.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 (ts.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 timespec ts; + struct ab8500_fg_avg_cap *avg = &di->avg_cap; + + getnstimeofday(&ts); + + for (i = 0; i < NBR_AVG_SAMPLES; i++) { + avg->samples[i] = sample; + avg->time_stamps[i] = ts.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; + int 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", + ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ); + 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; + int 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", + ((INS_CURR_TIMEOUT - timeout) * 1000) / HZ); + 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; + + di = to_ab8500_fg_device_info(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; + struct ab8500_fg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_ab8500_fg_device_info(psy); + + /* + * For all psy where the name of your driver + * appears in any supplied_to + */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + + if (!psy_found) + return 0; + + /* Go through all properties for the psy */ + for (j = 0; j < ext->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + if (ext->get_property(ext, prop, &ret)) + continue; + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + switch (ext->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->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->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 = to_ab8500_fg_device_info(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)); + } +} + +/** + * ab8500_fg_reinit() - forces FG algorithm to reinitialize with current values + * + * This function can be used to force the FG algorithm to recalculate a new + * voltage based battery capacity. + */ +void ab8500_fg_reinit(void) +{ + struct ab8500_fg *di = ab8500_fg_get(); + /* User won't be notified if a null pointer returned. */ + if (di != NULL) + queue_delayed_work(di->fg_wq, &di->fg_reinit_work, 0); +} + +/* 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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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; + + di = to_ab8500_fg_device_info(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 device *dev) +{ + unsigned int i, j; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++) + if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j])) + goto sysfs_psy_create_attrs_failed_ab8505; + } + return 0; +sysfs_psy_create_attrs_failed_ab8505: + dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n"); + while (j--) + device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); + + return -EIO; +} + +static void ab8500_fg_sysfs_psy_remove_attrs(struct device *dev) +{ + unsigned int i; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + (void)device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); + } +} + +/* Exposure to the sysfs interface <<END>> */ + +#if defined(CONFIG_PM) +static int ab8500_fg_resume(struct platform_device *pdev) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + /* + * Change state if we're not charging. If we're charging we will wake + * up on the FG IRQ + */ + if (!di->flags.charging) { + ab8500_fg_discharge_state_to(di, AB8500_FG_DISCHARGE_WAKEUP); + queue_work(di->fg_wq, &di->fg_work); + } + + return 0; +} + +static int ab8500_fg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct ab8500_fg *di = platform_get_drvdata(pdev); + + flush_delayed_work(&di->fg_periodic_work); + flush_work(&di->fg_work); + flush_work(&di->fg_acc_cur_work); + flush_delayed_work(&di->fg_reinit_work); + flush_delayed_work(&di->fg_low_bat_work); + flush_delayed_work(&di->fg_check_hw_failure_work); + + /* + * If the FG is enabled we will disable it before going to suspend + * only if we're not charging + */ + if (di->flags.fg_enabled && !di->flags.charging) + ab8500_fg_coulomb_counter(di, false); + + return 0; +} +#else +#define ab8500_fg_suspend NULL +#define ab8500_fg_resume NULL +#endif + +static int ab8500_fg_remove(struct platform_device *pdev) +{ + int ret = 0; + struct ab8500_fg *di = platform_get_drvdata(pdev); + + list_del(&di->node); + + /* Disable coulomb counter */ + ret = ab8500_fg_coulomb_counter(di, false); + if (ret) + dev_err(di->dev, "failed to disable coulomb counter\n"); + + destroy_workqueue(di->fg_wq); + ab8500_fg_sysfs_exit(di); + + flush_scheduled_work(); + ab8500_fg_sysfs_psy_remove_attrs(di->fg_psy.dev); + power_supply_unregister(&di->fg_psy); + return ret; +} + +/* ab8500 fg driver interrupts and their respective isr */ +static struct ab8500_fg_interrupts ab8500_fg_irq[] = { + {"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}, + {"CCEOC", ab8500_fg_cc_data_end_handler}, +}; + +static char *supply_interface[] = { + "ab8500_chargalg", + "ab8500_usb", +}; + +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 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"); + + di->fg_psy.name = "ab8500_fg"; + di->fg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->fg_psy.properties = ab8500_fg_props; + di->fg_psy.num_properties = ARRAY_SIZE(ab8500_fg_props); + di->fg_psy.get_property = ab8500_fg_get_property; + di->fg_psy.supplied_to = supply_interface; + di->fg_psy.num_supplicants = ARRAY_SIZE(supply_interface), + di->fg_psy.external_power_changed = ab8500_fg_external_power_changed; + + 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 */ + ret = power_supply_register(di->dev, &di->fg_psy); + if (ret) { + dev_err(di->dev, "failed to register FG psy\n"); + 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 interrupts */ + for (i = 0; i < ARRAY_SIZE(ab8500_fg_irq); i++) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].name); + ret = request_threaded_irq(irq, NULL, ab8500_fg_irq[i].isr, + IRQF_SHARED | IRQF_NO_SUSPEND, + ab8500_fg_irq[i].name, di); + + if (ret != 0) { + dev_err(di->dev, "failed to request %s IRQ %d: %d\n" + , ab8500_fg_irq[i].name, irq, ret); + goto free_irq; + } + dev_dbg(di->dev, "Requested %s IRQ %d: %d\n", + ab8500_fg_irq[i].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->fg_psy.dev); + 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 successfully registered irqs */ + for (i = i - 1; i >= 0; i--) { + irq = platform_get_irq_byname(pdev, ab8500_fg_irq[i].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", + .owner = THIS_MODULE, + .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 new file mode 100644 index 00000000000..6d2723664a0 --- /dev/null +++ b/drivers/power/abx500_chargalg.c @@ -0,0 +1,2169 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Copyright (c) 2012 Sony Mobile Communications AB + * + * Charging algorithm driver for abx500 variants + * + * License Terms: GNU General Public License v2 + * Authors: + * Johan Palsson <johan.palsson@stericsson.com> + * Karl Komierowski <karl.komierowski@stericsson.com> + * Arun R Murthy <arun.murthy@stericsson.com> + * Author: Imre Sunyi <imre.sunyi@sonymobile.com> + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/hrtimer.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/completion.h> +#include <linux/workqueue.h> +#include <linux/kobject.h> +#include <linux/of.h> +#include <linux/mfd/core.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/notifier.h> + +/* Watchdog kick interval */ +#define CHG_WD_INTERVAL (6 * HZ) + +/* End-of-charge criteria counter */ +#define EOC_COND_CNT 10 + +/* One hour expressed in seconds */ +#define ONE_HOUR_IN_SECONDS 3600 + +/* Five minutes expressed in seconds */ +#define FIVE_MINUTES_IN_SECONDS 300 + +/* Plus margin for the low battery threshold */ +#define BAT_PLUS_MARGIN (100) + +#define CHARGALG_CURR_STEP_LOW 0 +#define CHARGALG_CURR_STEP_HIGH 100 + +#define to_abx500_chargalg_device_info(x) container_of((x), \ + struct abx500_chargalg, chargalg_psy); + +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; + struct abx500_chargalg *di; + union power_supply_propval ret; + int i, j; + bool psy_found = false; + bool capacity_updated = false; + + psy = (struct power_supply *)data; + ext = dev_get_drvdata(dev); + di = to_abx500_chargalg_device_info(psy); + /* For all psy where the driver name appears in any supplied_to */ + for (i = 0; i < ext->num_supplicants; i++) { + if (!strcmp(ext->supplied_to[i], psy->name)) + psy_found = true; + } + if (!psy_found) + 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 (!ext->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->num_properties; j++) { + enum power_supply_property prop; + prop = ext->properties[j]; + + /* Initialize chargers if not already done */ + if (!di->ac_chg && + ext->type == POWER_SUPPLY_TYPE_MAINS) + di->ac_chg = psy_to_ux500_charger(ext); + else if (!di->usb_chg && + ext->type == POWER_SUPPLY_TYPE_USB) + di->usb_chg = psy_to_ux500_charger(ext); + + if (ext->get_property(ext, prop, &ret)) + continue; + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + switch (ext->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->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->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->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->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->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->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->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 = to_abx500_chargalg_device_info(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; + + di = to_abx500_chargalg_device_info(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 <<END>> */ + +#if defined(CONFIG_PM) +static int abx500_chargalg_resume(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* Kick charger watchdog if charging (any charger online) */ + if (di->chg_info.online_chg) + queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); + + /* + * Run the charging algorithm directly to be sure we don't + * do it too seldom + */ + queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); + + return 0; +} + +static int abx500_chargalg_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + if (di->chg_info.online_chg) + cancel_delayed_work_sync(&di->chargalg_wd_work); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + + return 0; +} +#else +#define abx500_chargalg_suspend NULL +#define abx500_chargalg_resume NULL +#endif + +static int abx500_chargalg_remove(struct platform_device *pdev) +{ + struct abx500_chargalg *di = platform_get_drvdata(pdev); + + /* sysfs interface to enable/disbale charging from user space */ + abx500_chargalg_sysfs_exit(di); + + hrtimer_cancel(&di->safety_timer); + hrtimer_cancel(&di->maintenance_timer); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + cancel_delayed_work_sync(&di->chargalg_wd_work); + cancel_work_sync(&di->chargalg_work); + + /* Delete the work queue */ + destroy_workqueue(di->chargalg_wq); + + power_supply_unregister(&di->chargalg_psy); + + return 0; +} + +static char *supply_interface[] = { + "ab8500_fg", +}; + +static 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 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); + + /* chargalg supply */ + di->chargalg_psy.name = "abx500_chargalg"; + di->chargalg_psy.type = POWER_SUPPLY_TYPE_BATTERY; + di->chargalg_psy.properties = abx500_chargalg_props; + di->chargalg_psy.num_properties = ARRAY_SIZE(abx500_chargalg_props); + di->chargalg_psy.get_property = abx500_chargalg_get_property; + di->chargalg_psy.supplied_to = supply_interface; + di->chargalg_psy.num_supplicants = ARRAY_SIZE(supply_interface), + di->chargalg_psy.external_power_changed = + abx500_chargalg_external_power_changed; + + /* 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 */ + ret = power_supply_register(di->dev, &di->chargalg_psy); + if (ret) { + dev_err(di->dev, "failed to register chargalg psy\n"); + 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", + .owner = THIS_MODULE, + .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/apm_power.c b/drivers/power/apm_power.c index 8a612dec913..39763015b36 100644 --- a/drivers/power/apm_power.c +++ b/drivers/power/apm_power.c @@ -10,6 +10,7 @@ */ #include <linux/module.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/apm-emulation.h> diff --git a/drivers/power/avs/Kconfig b/drivers/power/avs/Kconfig new file mode 100644 index 00000000000..2a1008b6112 --- /dev/null +++ b/drivers/power/avs/Kconfig @@ -0,0 +1,12 @@ +menuconfig POWER_AVS + bool "Adaptive Voltage Scaling class support" + help + AVS is a power management technique which finely controls the + operating voltage of a device in order to optimize (i.e. reduce) + its power consumption. + At a given operating point the voltage is adapted depending on + static factors (chip manufacturing process) and dynamic factors + (temperature depending performance). + AVS is also called SmartReflex on OMAP devices. + + Say Y here to enable Adaptive Voltage Scaling class support. diff --git a/drivers/power/avs/Makefile b/drivers/power/avs/Makefile new file mode 100644 index 00000000000..0843386a6c1 --- /dev/null +++ b/drivers/power/avs/Makefile @@ -0,0 +1 @@ +obj-$(CONFIG_POWER_AVS_OMAP) += smartreflex.o diff --git a/drivers/power/avs/smartreflex.c b/drivers/power/avs/smartreflex.c new file mode 100644 index 00000000000..db9973bb53f --- /dev/null +++ b/drivers/power/avs/smartreflex.c @@ -0,0 +1,1078 @@ +/* + * OMAP SmartReflex Voltage Control + * + * Author: Thara Gopinath <thara@ti.com> + * + * Copyright (C) 2012 Texas Instruments, Inc. + * Thara Gopinath <thara@ti.com> + * + * Copyright (C) 2008 Nokia Corporation + * Kalle Jokiniemi + * + * Copyright (C) 2007 Texas Instruments, Inc. + * Lesly A M <x0080970@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/debugfs.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/pm_runtime.h> +#include <linux/power/smartreflex.h> + +#define DRIVER_NAME "smartreflex" +#define SMARTREFLEX_NAME_LEN 32 +#define NVALUE_NAME_LEN 40 +#define SR_DISABLE_TIMEOUT 200 + +/* sr_list contains all the instances of smartreflex module */ +static LIST_HEAD(sr_list); + +static struct omap_sr_class_data *sr_class; +static struct omap_sr_pmic_data *sr_pmic_data; +static struct dentry *sr_dbg_dir; + +static inline void sr_write_reg(struct omap_sr *sr, unsigned offset, u32 value) +{ + __raw_writel(value, (sr->base + offset)); +} + +static inline void sr_modify_reg(struct omap_sr *sr, unsigned offset, u32 mask, + u32 value) +{ + u32 reg_val; + + /* + * Smartreflex error config register is special as it contains + * certain status bits which if written a 1 into means a clear + * of those bits. So in order to make sure no accidental write of + * 1 happens to those status bits, do a clear of them in the read + * value. This mean this API doesn't rewrite values in these bits + * if they are currently set, but does allow the caller to write + * those bits. + */ + if (sr->ip_type == SR_TYPE_V1 && offset == ERRCONFIG_V1) + mask |= ERRCONFIG_STATUS_V1_MASK; + else if (sr->ip_type == SR_TYPE_V2 && offset == ERRCONFIG_V2) + mask |= ERRCONFIG_VPBOUNDINTST_V2; + + reg_val = __raw_readl(sr->base + offset); + reg_val &= ~mask; + + value &= mask; + + reg_val |= value; + + __raw_writel(reg_val, (sr->base + offset)); +} + +static inline u32 sr_read_reg(struct omap_sr *sr, unsigned offset) +{ + return __raw_readl(sr->base + offset); +} + +static struct omap_sr *_sr_lookup(struct voltagedomain *voltdm) +{ + struct omap_sr *sr_info; + + if (!voltdm) { + pr_err("%s: Null voltage domain passed!\n", __func__); + return ERR_PTR(-EINVAL); + } + + list_for_each_entry(sr_info, &sr_list, node) { + if (voltdm == sr_info->voltdm) + return sr_info; + } + + return ERR_PTR(-ENODATA); +} + +static irqreturn_t sr_interrupt(int irq, void *data) +{ + struct omap_sr *sr_info = data; + u32 status = 0; + + switch (sr_info->ip_type) { + case SR_TYPE_V1: + /* Read the status bits */ + status = sr_read_reg(sr_info, ERRCONFIG_V1); + + /* Clear them by writing back */ + sr_write_reg(sr_info, ERRCONFIG_V1, status); + break; + case SR_TYPE_V2: + /* Read the status bits */ + status = sr_read_reg(sr_info, IRQSTATUS); + + /* Clear them by writing back */ + sr_write_reg(sr_info, IRQSTATUS, status); + break; + default: + dev_err(&sr_info->pdev->dev, "UNKNOWN IP type %d\n", + sr_info->ip_type); + return IRQ_NONE; + } + + if (sr_class->notify) + sr_class->notify(sr_info, status); + + return IRQ_HANDLED; +} + +static void sr_set_clk_length(struct omap_sr *sr) +{ + struct clk *fck; + u32 fclk_speed; + + fck = clk_get(&sr->pdev->dev, "fck"); + + if (IS_ERR(fck)) { + dev_err(&sr->pdev->dev, "%s: unable to get fck for device %s\n", + __func__, dev_name(&sr->pdev->dev)); + return; + } + + fclk_speed = clk_get_rate(fck); + clk_put(fck); + + switch (fclk_speed) { + case 12000000: + sr->clk_length = SRCLKLENGTH_12MHZ_SYSCLK; + break; + case 13000000: + sr->clk_length = SRCLKLENGTH_13MHZ_SYSCLK; + break; + case 19200000: + sr->clk_length = SRCLKLENGTH_19MHZ_SYSCLK; + break; + case 26000000: + sr->clk_length = SRCLKLENGTH_26MHZ_SYSCLK; + break; + case 38400000: + sr->clk_length = SRCLKLENGTH_38MHZ_SYSCLK; + break; + default: + dev_err(&sr->pdev->dev, "%s: Invalid fclk rate: %d\n", + __func__, fclk_speed); + break; + } +} + +static void sr_start_vddautocomp(struct omap_sr *sr) +{ + if (!sr_class || !(sr_class->enable) || !(sr_class->configure)) { + dev_warn(&sr->pdev->dev, + "%s: smartreflex class driver not registered\n", + __func__); + return; + } + + if (!sr_class->enable(sr)) + sr->autocomp_active = true; +} + +static void sr_stop_vddautocomp(struct omap_sr *sr) +{ + if (!sr_class || !(sr_class->disable)) { + dev_warn(&sr->pdev->dev, + "%s: smartreflex class driver not registered\n", + __func__); + return; + } + + if (sr->autocomp_active) { + sr_class->disable(sr, 1); + sr->autocomp_active = false; + } +} + +/* + * This function handles the intializations which have to be done + * only when both sr device and class driver regiter has + * completed. This will be attempted to be called from both sr class + * driver register and sr device intializtion API's. Only one call + * will ultimately succeed. + * + * Currently this function registers interrupt handler for a particular SR + * if smartreflex class driver is already registered and has + * requested for interrupts and the SR interrupt line in present. + */ +static int sr_late_init(struct omap_sr *sr_info) +{ + struct omap_sr_data *pdata = sr_info->pdev->dev.platform_data; + int ret = 0; + + if (sr_class->notify && sr_class->notify_flags && sr_info->irq) { + ret = devm_request_irq(&sr_info->pdev->dev, sr_info->irq, + sr_interrupt, 0, sr_info->name, sr_info); + if (ret) + goto error; + disable_irq(sr_info->irq); + } + + if (pdata && pdata->enable_on_init) + sr_start_vddautocomp(sr_info); + + return ret; + +error: + list_del(&sr_info->node); + dev_err(&sr_info->pdev->dev, "%s: ERROR in registering" + "interrupt handler. Smartreflex will" + "not function as desired\n", __func__); + + return ret; +} + +static void sr_v1_disable(struct omap_sr *sr) +{ + int timeout = 0; + int errconf_val = ERRCONFIG_MCUACCUMINTST | ERRCONFIG_MCUVALIDINTST | + ERRCONFIG_MCUBOUNDINTST; + + /* Enable MCUDisableAcknowledge interrupt */ + sr_modify_reg(sr, ERRCONFIG_V1, + ERRCONFIG_MCUDISACKINTEN, ERRCONFIG_MCUDISACKINTEN); + + /* SRCONFIG - disable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0x0); + + /* Disable all other SR interrupts and clear the status as needed */ + if (sr_read_reg(sr, ERRCONFIG_V1) & ERRCONFIG_VPBOUNDINTST_V1) + errconf_val |= ERRCONFIG_VPBOUNDINTST_V1; + sr_modify_reg(sr, ERRCONFIG_V1, + (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUVALIDINTEN | + ERRCONFIG_MCUBOUNDINTEN | ERRCONFIG_VPBOUNDINTEN_V1), + errconf_val); + + /* + * Wait for SR to be disabled. + * wait until ERRCONFIG.MCUDISACKINTST = 1. Typical latency is 1us. + */ + sr_test_cond_timeout((sr_read_reg(sr, ERRCONFIG_V1) & + ERRCONFIG_MCUDISACKINTST), SR_DISABLE_TIMEOUT, + timeout); + + if (timeout >= SR_DISABLE_TIMEOUT) + dev_warn(&sr->pdev->dev, "%s: Smartreflex disable timedout\n", + __func__); + + /* Disable MCUDisableAcknowledge interrupt & clear pending interrupt */ + sr_modify_reg(sr, ERRCONFIG_V1, ERRCONFIG_MCUDISACKINTEN, + ERRCONFIG_MCUDISACKINTST); +} + +static void sr_v2_disable(struct omap_sr *sr) +{ + int timeout = 0; + + /* Enable MCUDisableAcknowledge interrupt */ + sr_write_reg(sr, IRQENABLE_SET, IRQENABLE_MCUDISABLEACKINT); + + /* SRCONFIG - disable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, 0x0); + + /* + * Disable all other SR interrupts and clear the status + * write to status register ONLY on need basis - only if status + * is set. + */ + if (sr_read_reg(sr, ERRCONFIG_V2) & ERRCONFIG_VPBOUNDINTST_V2) + sr_modify_reg(sr, ERRCONFIG_V2, ERRCONFIG_VPBOUNDINTEN_V2, + ERRCONFIG_VPBOUNDINTST_V2); + else + sr_modify_reg(sr, ERRCONFIG_V2, ERRCONFIG_VPBOUNDINTEN_V2, + 0x0); + sr_write_reg(sr, IRQENABLE_CLR, (IRQENABLE_MCUACCUMINT | + IRQENABLE_MCUVALIDINT | + IRQENABLE_MCUBOUNDSINT)); + sr_write_reg(sr, IRQSTATUS, (IRQSTATUS_MCUACCUMINT | + IRQSTATUS_MCVALIDINT | + IRQSTATUS_MCBOUNDSINT)); + + /* + * Wait for SR to be disabled. + * wait until IRQSTATUS.MCUDISACKINTST = 1. Typical latency is 1us. + */ + sr_test_cond_timeout((sr_read_reg(sr, IRQSTATUS) & + IRQSTATUS_MCUDISABLEACKINT), SR_DISABLE_TIMEOUT, + timeout); + + if (timeout >= SR_DISABLE_TIMEOUT) + dev_warn(&sr->pdev->dev, "%s: Smartreflex disable timedout\n", + __func__); + + /* Disable MCUDisableAcknowledge interrupt & clear pending interrupt */ + sr_write_reg(sr, IRQENABLE_CLR, IRQENABLE_MCUDISABLEACKINT); + sr_write_reg(sr, IRQSTATUS, IRQSTATUS_MCUDISABLEACKINT); +} + +static struct omap_sr_nvalue_table *sr_retrieve_nvalue_row( + struct omap_sr *sr, u32 efuse_offs) +{ + int i; + + if (!sr->nvalue_table) { + dev_warn(&sr->pdev->dev, "%s: Missing ntarget value table\n", + __func__); + return NULL; + } + + for (i = 0; i < sr->nvalue_count; i++) { + if (sr->nvalue_table[i].efuse_offs == efuse_offs) + return &sr->nvalue_table[i]; + } + + return NULL; +} + +/* Public Functions */ + +/** + * sr_configure_errgen() - Configures the SmartReflex to perform AVS using the + * error generator module. + * @sr: SR module to be configured. + * + * This API is to be called from the smartreflex class driver to + * configure the error generator module inside the smartreflex module. + * SR settings if using the ERROR module inside Smartreflex. + * SR CLASS 3 by default uses only the ERROR module where as + * SR CLASS 2 can choose between ERROR module and MINMAXAVG + * module. Returns 0 on success and error value in case of failure. + */ +int sr_configure_errgen(struct omap_sr *sr) +{ + u32 sr_config, sr_errconfig, errconfig_offs; + u32 vpboundint_en, vpboundint_st; + u32 senp_en = 0, senn_en = 0; + u8 senp_shift, senn_shift; + + if (!sr) { + pr_warn("%s: NULL omap_sr from %pF\n", __func__, + (void *)_RET_IP_); + return -EINVAL; + } + + if (!sr->clk_length) + sr_set_clk_length(sr); + + senp_en = sr->senp_mod; + senn_en = sr->senn_mod; + + sr_config = (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | + SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN; + + switch (sr->ip_type) { + case SR_TYPE_V1: + sr_config |= SRCONFIG_DELAYCTRL; + senn_shift = SRCONFIG_SENNENABLE_V1_SHIFT; + senp_shift = SRCONFIG_SENPENABLE_V1_SHIFT; + errconfig_offs = ERRCONFIG_V1; + vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V1; + vpboundint_st = ERRCONFIG_VPBOUNDINTST_V1; + break; + case SR_TYPE_V2: + senn_shift = SRCONFIG_SENNENABLE_V2_SHIFT; + senp_shift = SRCONFIG_SENPENABLE_V2_SHIFT; + errconfig_offs = ERRCONFIG_V2; + vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V2; + vpboundint_st = ERRCONFIG_VPBOUNDINTST_V2; + break; + default: + dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex" + "module without specifying the ip\n", __func__); + return -EINVAL; + } + + sr_config |= ((senn_en << senn_shift) | (senp_en << senp_shift)); + sr_write_reg(sr, SRCONFIG, sr_config); + sr_errconfig = (sr->err_weight << ERRCONFIG_ERRWEIGHT_SHIFT) | + (sr->err_maxlimit << ERRCONFIG_ERRMAXLIMIT_SHIFT) | + (sr->err_minlimit << ERRCONFIG_ERRMINLIMIT_SHIFT); + sr_modify_reg(sr, errconfig_offs, (SR_ERRWEIGHT_MASK | + SR_ERRMAXLIMIT_MASK | SR_ERRMINLIMIT_MASK), + sr_errconfig); + + /* Enabling the interrupts if the ERROR module is used */ + sr_modify_reg(sr, errconfig_offs, (vpboundint_en | vpboundint_st), + vpboundint_en); + + return 0; +} + +/** + * sr_disable_errgen() - Disables SmartReflex AVS module's errgen component + * @sr: SR module to be configured. + * + * This API is to be called from the smartreflex class driver to + * disable the error generator module inside the smartreflex module. + * + * Returns 0 on success and error value in case of failure. + */ +int sr_disable_errgen(struct omap_sr *sr) +{ + u32 errconfig_offs; + u32 vpboundint_en, vpboundint_st; + + if (!sr) { + pr_warn("%s: NULL omap_sr from %pF\n", __func__, + (void *)_RET_IP_); + return -EINVAL; + } + + switch (sr->ip_type) { + case SR_TYPE_V1: + errconfig_offs = ERRCONFIG_V1; + vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V1; + vpboundint_st = ERRCONFIG_VPBOUNDINTST_V1; + break; + case SR_TYPE_V2: + errconfig_offs = ERRCONFIG_V2; + vpboundint_en = ERRCONFIG_VPBOUNDINTEN_V2; + vpboundint_st = ERRCONFIG_VPBOUNDINTST_V2; + break; + default: + dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex" + "module without specifying the ip\n", __func__); + return -EINVAL; + } + + /* Disable the Sensor and errorgen */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SENENABLE | SRCONFIG_ERRGEN_EN, 0); + + /* + * Disable the interrupts of ERROR module + * NOTE: modify is a read, modify,write - an implicit OCP barrier + * which is required is present here - sequencing is critical + * at this point (after errgen is disabled, vpboundint disable) + */ + sr_modify_reg(sr, errconfig_offs, vpboundint_en | vpboundint_st, 0); + + return 0; +} + +/** + * sr_configure_minmax() - Configures the SmartReflex to perform AVS using the + * minmaxavg module. + * @sr: SR module to be configured. + * + * This API is to be called from the smartreflex class driver to + * configure the minmaxavg module inside the smartreflex module. + * SR settings if using the ERROR module inside Smartreflex. + * SR CLASS 3 by default uses only the ERROR module where as + * SR CLASS 2 can choose between ERROR module and MINMAXAVG + * module. Returns 0 on success and error value in case of failure. + */ +int sr_configure_minmax(struct omap_sr *sr) +{ + u32 sr_config, sr_avgwt; + u32 senp_en = 0, senn_en = 0; + u8 senp_shift, senn_shift; + + if (!sr) { + pr_warn("%s: NULL omap_sr from %pF\n", __func__, + (void *)_RET_IP_); + return -EINVAL; + } + + if (!sr->clk_length) + sr_set_clk_length(sr); + + senp_en = sr->senp_mod; + senn_en = sr->senn_mod; + + sr_config = (sr->clk_length << SRCONFIG_SRCLKLENGTH_SHIFT) | + SRCONFIG_SENENABLE | + (sr->accum_data << SRCONFIG_ACCUMDATA_SHIFT); + + switch (sr->ip_type) { + case SR_TYPE_V1: + sr_config |= SRCONFIG_DELAYCTRL; + senn_shift = SRCONFIG_SENNENABLE_V1_SHIFT; + senp_shift = SRCONFIG_SENPENABLE_V1_SHIFT; + break; + case SR_TYPE_V2: + senn_shift = SRCONFIG_SENNENABLE_V2_SHIFT; + senp_shift = SRCONFIG_SENPENABLE_V2_SHIFT; + break; + default: + dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex" + "module without specifying the ip\n", __func__); + return -EINVAL; + } + + sr_config |= ((senn_en << senn_shift) | (senp_en << senp_shift)); + sr_write_reg(sr, SRCONFIG, sr_config); + sr_avgwt = (sr->senp_avgweight << AVGWEIGHT_SENPAVGWEIGHT_SHIFT) | + (sr->senn_avgweight << AVGWEIGHT_SENNAVGWEIGHT_SHIFT); + sr_write_reg(sr, AVGWEIGHT, sr_avgwt); + + /* + * Enabling the interrupts if MINMAXAVG module is used. + * TODO: check if all the interrupts are mandatory + */ + switch (sr->ip_type) { + case SR_TYPE_V1: + sr_modify_reg(sr, ERRCONFIG_V1, + (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUVALIDINTEN | + ERRCONFIG_MCUBOUNDINTEN), + (ERRCONFIG_MCUACCUMINTEN | ERRCONFIG_MCUACCUMINTST | + ERRCONFIG_MCUVALIDINTEN | ERRCONFIG_MCUVALIDINTST | + ERRCONFIG_MCUBOUNDINTEN | ERRCONFIG_MCUBOUNDINTST)); + break; + case SR_TYPE_V2: + sr_write_reg(sr, IRQSTATUS, + IRQSTATUS_MCUACCUMINT | IRQSTATUS_MCVALIDINT | + IRQSTATUS_MCBOUNDSINT | IRQSTATUS_MCUDISABLEACKINT); + sr_write_reg(sr, IRQENABLE_SET, + IRQENABLE_MCUACCUMINT | IRQENABLE_MCUVALIDINT | + IRQENABLE_MCUBOUNDSINT | IRQENABLE_MCUDISABLEACKINT); + break; + default: + dev_err(&sr->pdev->dev, "%s: Trying to Configure smartreflex" + "module without specifying the ip\n", __func__); + return -EINVAL; + } + + return 0; +} + +/** + * sr_enable() - Enables the smartreflex module. + * @sr: pointer to which the SR module to be configured belongs to. + * @volt: The voltage at which the Voltage domain associated with + * the smartreflex module is operating at. + * This is required only to program the correct Ntarget value. + * + * This API is to be called from the smartreflex class driver to + * enable a smartreflex module. Returns 0 on success. Returns error + * value if the voltage passed is wrong or if ntarget value is wrong. + */ +int sr_enable(struct omap_sr *sr, unsigned long volt) +{ + struct omap_volt_data *volt_data; + struct omap_sr_nvalue_table *nvalue_row; + int ret; + + if (!sr) { + pr_warn("%s: NULL omap_sr from %pF\n", __func__, + (void *)_RET_IP_); + return -EINVAL; + } + + volt_data = omap_voltage_get_voltdata(sr->voltdm, volt); + + if (IS_ERR(volt_data)) { + dev_warn(&sr->pdev->dev, "%s: Unable to get voltage table" + "for nominal voltage %ld\n", __func__, volt); + return PTR_ERR(volt_data); + } + + nvalue_row = sr_retrieve_nvalue_row(sr, volt_data->sr_efuse_offs); + + if (!nvalue_row) { + dev_warn(&sr->pdev->dev, "%s: failure getting SR data for this voltage %ld\n", + __func__, volt); + return -ENODATA; + } + + /* errminlimit is opp dependent and hence linked to voltage */ + sr->err_minlimit = nvalue_row->errminlimit; + + pm_runtime_get_sync(&sr->pdev->dev); + + /* Check if SR is already enabled. If yes do nothing */ + if (sr_read_reg(sr, SRCONFIG) & SRCONFIG_SRENABLE) + return 0; + + /* Configure SR */ + ret = sr_class->configure(sr); + if (ret) + return ret; + + sr_write_reg(sr, NVALUERECIPROCAL, nvalue_row->nvalue); + + /* SRCONFIG - enable SR */ + sr_modify_reg(sr, SRCONFIG, SRCONFIG_SRENABLE, SRCONFIG_SRENABLE); + return 0; +} + +/** + * sr_disable() - Disables the smartreflex module. + * @sr: pointer to which the SR module to be configured belongs to. + * + * This API is to be called from the smartreflex class driver to + * disable a smartreflex module. + */ +void sr_disable(struct omap_sr *sr) +{ + if (!sr) { + pr_warn("%s: NULL omap_sr from %pF\n", __func__, + (void *)_RET_IP_); + return; + } + + /* Check if SR clocks are already disabled. If yes do nothing */ + if (pm_runtime_suspended(&sr->pdev->dev)) + return; + + /* + * Disable SR if only it is indeed enabled. Else just + * disable the clocks. + */ + if (sr_read_reg(sr, SRCONFIG) & SRCONFIG_SRENABLE) { + switch (sr->ip_type) { + case SR_TYPE_V1: + sr_v1_disable(sr); + break; + case SR_TYPE_V2: + sr_v2_disable(sr); + break; + default: + dev_err(&sr->pdev->dev, "UNKNOWN IP type %d\n", + sr->ip_type); + } + } + + pm_runtime_put_sync_suspend(&sr->pdev->dev); +} + +/** + * sr_register_class() - API to register a smartreflex class parameters. + * @class_data: The structure containing various sr class specific data. + * + * This API is to be called by the smartreflex class driver to register itself + * with the smartreflex driver during init. Returns 0 on success else the + * error value. + */ +int sr_register_class(struct omap_sr_class_data *class_data) +{ + struct omap_sr *sr_info; + + if (!class_data) { + pr_warning("%s:, Smartreflex class data passed is NULL\n", + __func__); + return -EINVAL; + } + + if (sr_class) { + pr_warning("%s: Smartreflex class driver already registered\n", + __func__); + return -EBUSY; + } + + sr_class = class_data; + + /* + * Call into late init to do intializations that require + * both sr driver and sr class driver to be initiallized. + */ + list_for_each_entry(sr_info, &sr_list, node) + sr_late_init(sr_info); + + return 0; +} + +/** + * omap_sr_enable() - API to enable SR clocks and to call into the + * registered smartreflex class enable API. + * @voltdm: VDD pointer to which the SR module to be configured belongs to. + * + * This API is to be called from the kernel in order to enable + * a particular smartreflex module. This API will do the initial + * configurations to turn on the smartreflex module and in turn call + * into the registered smartreflex class enable API. + */ +void omap_sr_enable(struct voltagedomain *voltdm) +{ + struct omap_sr *sr = _sr_lookup(voltdm); + + if (IS_ERR(sr)) { + pr_warning("%s: omap_sr struct for voltdm not found\n", __func__); + return; + } + + if (!sr->autocomp_active) + return; + + if (!sr_class || !(sr_class->enable) || !(sr_class->configure)) { + dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not" + "registered\n", __func__); + return; + } + + sr_class->enable(sr); +} + +/** + * omap_sr_disable() - API to disable SR without resetting the voltage + * processor voltage + * @voltdm: VDD pointer to which the SR module to be configured belongs to. + * + * This API is to be called from the kernel in order to disable + * a particular smartreflex module. This API will in turn call + * into the registered smartreflex class disable API. This API will tell + * the smartreflex class disable not to reset the VP voltage after + * disabling smartreflex. + */ +void omap_sr_disable(struct voltagedomain *voltdm) +{ + struct omap_sr *sr = _sr_lookup(voltdm); + + if (IS_ERR(sr)) { + pr_warning("%s: omap_sr struct for voltdm not found\n", __func__); + return; + } + + if (!sr->autocomp_active) + return; + + if (!sr_class || !(sr_class->disable)) { + dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not" + "registered\n", __func__); + return; + } + + sr_class->disable(sr, 0); +} + +/** + * omap_sr_disable_reset_volt() - API to disable SR and reset the + * voltage processor voltage + * @voltdm: VDD pointer to which the SR module to be configured belongs to. + * + * This API is to be called from the kernel in order to disable + * a particular smartreflex module. This API will in turn call + * into the registered smartreflex class disable API. This API will tell + * the smartreflex class disable to reset the VP voltage after + * disabling smartreflex. + */ +void omap_sr_disable_reset_volt(struct voltagedomain *voltdm) +{ + struct omap_sr *sr = _sr_lookup(voltdm); + + if (IS_ERR(sr)) { + pr_warning("%s: omap_sr struct for voltdm not found\n", __func__); + return; + } + + if (!sr->autocomp_active) + return; + + if (!sr_class || !(sr_class->disable)) { + dev_warn(&sr->pdev->dev, "%s: smartreflex class driver not" + "registered\n", __func__); + return; + } + + sr_class->disable(sr, 1); +} + +/** + * omap_sr_register_pmic() - API to register pmic specific info. + * @pmic_data: The structure containing pmic specific data. + * + * This API is to be called from the PMIC specific code to register with + * smartreflex driver pmic specific info. Currently the only info required + * is the smartreflex init on the PMIC side. + */ +void omap_sr_register_pmic(struct omap_sr_pmic_data *pmic_data) +{ + if (!pmic_data) { + pr_warning("%s: Trying to register NULL PMIC data structure" + "with smartreflex\n", __func__); + return; + } + + sr_pmic_data = pmic_data; +} + +/* PM Debug FS entries to enable and disable smartreflex. */ +static int omap_sr_autocomp_show(void *data, u64 *val) +{ + struct omap_sr *sr_info = data; + + if (!sr_info) { + pr_warning("%s: omap_sr struct not found\n", __func__); + return -EINVAL; + } + + *val = sr_info->autocomp_active; + + return 0; +} + +static int omap_sr_autocomp_store(void *data, u64 val) +{ + struct omap_sr *sr_info = data; + + if (!sr_info) { + pr_warning("%s: omap_sr struct not found\n", __func__); + return -EINVAL; + } + + /* Sanity check */ + if (val > 1) { + pr_warning("%s: Invalid argument %lld\n", __func__, val); + return -EINVAL; + } + + /* control enable/disable only if there is a delta in value */ + if (sr_info->autocomp_active != val) { + if (!val) + sr_stop_vddautocomp(sr_info); + else + sr_start_vddautocomp(sr_info); + } + + return 0; +} + +DEFINE_SIMPLE_ATTRIBUTE(pm_sr_fops, omap_sr_autocomp_show, + omap_sr_autocomp_store, "%llu\n"); + +static int __init omap_sr_probe(struct platform_device *pdev) +{ + struct omap_sr *sr_info; + struct omap_sr_data *pdata = pdev->dev.platform_data; + struct resource *mem, *irq; + struct dentry *nvalue_dir; + int i, ret = 0; + + sr_info = devm_kzalloc(&pdev->dev, sizeof(struct omap_sr), GFP_KERNEL); + if (!sr_info) { + dev_err(&pdev->dev, "%s: unable to allocate sr_info\n", + __func__); + return -ENOMEM; + } + + sr_info->name = devm_kzalloc(&pdev->dev, + SMARTREFLEX_NAME_LEN, GFP_KERNEL); + if (!sr_info->name) { + dev_err(&pdev->dev, "%s: unable to allocate SR instance name\n", + __func__); + return -ENOMEM; + } + + platform_set_drvdata(pdev, sr_info); + + if (!pdata) { + dev_err(&pdev->dev, "%s: platform data missing\n", __func__); + return -EINVAL; + } + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + sr_info->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(sr_info->base)) { + dev_err(&pdev->dev, "%s: ioremap fail\n", __func__); + return PTR_ERR(sr_info->base); + } + + irq = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + + pm_runtime_enable(&pdev->dev); + pm_runtime_irq_safe(&pdev->dev); + + snprintf(sr_info->name, SMARTREFLEX_NAME_LEN, "%s", pdata->name); + + sr_info->pdev = pdev; + sr_info->srid = pdev->id; + sr_info->voltdm = pdata->voltdm; + sr_info->nvalue_table = pdata->nvalue_table; + sr_info->nvalue_count = pdata->nvalue_count; + sr_info->senn_mod = pdata->senn_mod; + sr_info->senp_mod = pdata->senp_mod; + sr_info->err_weight = pdata->err_weight; + sr_info->err_maxlimit = pdata->err_maxlimit; + sr_info->accum_data = pdata->accum_data; + sr_info->senn_avgweight = pdata->senn_avgweight; + sr_info->senp_avgweight = pdata->senp_avgweight; + sr_info->autocomp_active = false; + sr_info->ip_type = pdata->ip_type; + + if (irq) + sr_info->irq = irq->start; + + sr_set_clk_length(sr_info); + + list_add(&sr_info->node, &sr_list); + + /* + * Call into late init to do intializations that require + * both sr driver and sr class driver to be initiallized. + */ + if (sr_class) { + ret = sr_late_init(sr_info); + if (ret) { + pr_warning("%s: Error in SR late init\n", __func__); + goto err_list_del; + } + } + + dev_info(&pdev->dev, "%s: SmartReflex driver initialized\n", __func__); + if (!sr_dbg_dir) { + sr_dbg_dir = debugfs_create_dir("smartreflex", NULL); + if (IS_ERR_OR_NULL(sr_dbg_dir)) { + ret = PTR_ERR(sr_dbg_dir); + pr_err("%s:sr debugfs dir creation failed(%d)\n", + __func__, ret); + goto err_list_del; + } + } + + sr_info->dbg_dir = debugfs_create_dir(sr_info->name, sr_dbg_dir); + if (IS_ERR_OR_NULL(sr_info->dbg_dir)) { + dev_err(&pdev->dev, "%s: Unable to create debugfs directory\n", + __func__); + ret = PTR_ERR(sr_info->dbg_dir); + goto err_debugfs; + } + + (void) debugfs_create_file("autocomp", S_IRUGO | S_IWUSR, + sr_info->dbg_dir, (void *)sr_info, &pm_sr_fops); + (void) debugfs_create_x32("errweight", S_IRUGO, sr_info->dbg_dir, + &sr_info->err_weight); + (void) debugfs_create_x32("errmaxlimit", S_IRUGO, sr_info->dbg_dir, + &sr_info->err_maxlimit); + + nvalue_dir = debugfs_create_dir("nvalue", sr_info->dbg_dir); + if (IS_ERR_OR_NULL(nvalue_dir)) { + dev_err(&pdev->dev, "%s: Unable to create debugfs directory" + "for n-values\n", __func__); + ret = PTR_ERR(nvalue_dir); + goto err_debugfs; + } + + if (sr_info->nvalue_count == 0 || !sr_info->nvalue_table) { + dev_warn(&pdev->dev, "%s: %s: No Voltage table for the corresponding vdd. Cannot create debugfs entries for n-values\n", + __func__, sr_info->name); + + ret = -ENODATA; + goto err_debugfs; + } + + for (i = 0; i < sr_info->nvalue_count; i++) { + char name[NVALUE_NAME_LEN + 1]; + + snprintf(name, sizeof(name), "volt_%lu", + sr_info->nvalue_table[i].volt_nominal); + (void) debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir, + &(sr_info->nvalue_table[i].nvalue)); + snprintf(name, sizeof(name), "errminlimit_%lu", + sr_info->nvalue_table[i].volt_nominal); + (void) debugfs_create_x32(name, S_IRUGO | S_IWUSR, nvalue_dir, + &(sr_info->nvalue_table[i].errminlimit)); + + } + + return ret; + +err_debugfs: + debugfs_remove_recursive(sr_info->dbg_dir); +err_list_del: + list_del(&sr_info->node); + return ret; +} + +static int omap_sr_remove(struct platform_device *pdev) +{ + struct omap_sr_data *pdata = pdev->dev.platform_data; + struct omap_sr *sr_info; + + if (!pdata) { + dev_err(&pdev->dev, "%s: platform data missing\n", __func__); + return -EINVAL; + } + + sr_info = _sr_lookup(pdata->voltdm); + if (IS_ERR(sr_info)) { + dev_warn(&pdev->dev, "%s: omap_sr struct not found\n", + __func__); + return PTR_ERR(sr_info); + } + + if (sr_info->autocomp_active) + sr_stop_vddautocomp(sr_info); + if (sr_info->dbg_dir) + debugfs_remove_recursive(sr_info->dbg_dir); + + pm_runtime_disable(&pdev->dev); + list_del(&sr_info->node); + return 0; +} + +static void omap_sr_shutdown(struct platform_device *pdev) +{ + struct omap_sr_data *pdata = pdev->dev.platform_data; + struct omap_sr *sr_info; + + if (!pdata) { + dev_err(&pdev->dev, "%s: platform data missing\n", __func__); + return; + } + + sr_info = _sr_lookup(pdata->voltdm); + if (IS_ERR(sr_info)) { + dev_warn(&pdev->dev, "%s: omap_sr struct not found\n", + __func__); + return; + } + + if (sr_info->autocomp_active) + sr_stop_vddautocomp(sr_info); + + return; +} + +static struct platform_driver smartreflex_driver = { + .remove = omap_sr_remove, + .shutdown = omap_sr_shutdown, + .driver = { + .name = DRIVER_NAME, + }, +}; + +static int __init sr_init(void) +{ + int ret = 0; + + /* + * sr_init is a late init. If by then a pmic specific API is not + * registered either there is no need for anything to be done on + * the PMIC side or somebody has forgotten to register a PMIC + * handler. Warn for the second condition. + */ + if (sr_pmic_data && sr_pmic_data->sr_pmic_init) + sr_pmic_data->sr_pmic_init(); + else + pr_warning("%s: No PMIC hook to init smartreflex\n", __func__); + + ret = platform_driver_probe(&smartreflex_driver, omap_sr_probe); + if (ret) { + pr_err("%s: platform driver register failed for SR\n", + __func__); + return ret; + } + + return 0; +} +late_initcall(sr_init); + +static void __exit sr_exit(void) +{ + platform_driver_unregister(&smartreflex_driver); +} +module_exit(sr_exit); + +MODULE_DESCRIPTION("OMAP Smartreflex Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); +MODULE_AUTHOR("Texas Instruments Inc"); diff --git a/drivers/power/bq2415x_charger.c b/drivers/power/bq2415x_charger.c new file mode 100644 index 00000000000..79a37f6d330 --- /dev/null +++ b/drivers/power/bq2415x_charger.c @@ -0,0 +1,1740 @@ +/* + * bq2415x charger driver + * + * Copyright (C) 2011-2013 Pali Rohár <pali.rohar@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * 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. + */ + +/* + * 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 + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/param.h> +#include <linux/err.h> +#include <linux/workqueue.h> +#include <linux/sysfs.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> +#include <linux/i2c.h> +#include <linux/slab.h> + +#include <linux/power/bq2415x_charger.h> + +/* timeout for resetting chip timer */ +#define BQ2415X_TIMER_TIMEOUT 10 + +#define BQ2415X_REG_STATUS 0x00 +#define BQ2415X_REG_CONTROL 0x01 +#define BQ2415X_REG_VOLTAGE 0x02 +#define BQ2415X_REG_VENDER 0x03 +#define BQ2415X_REG_CURRENT 0x04 + +/* reset state for all registers */ +#define BQ2415X_RESET_STATUS BIT(6) +#define BQ2415X_RESET_CONTROL (BIT(4)|BIT(5)) +#define BQ2415X_RESET_VOLTAGE (BIT(1)|BIT(3)) +#define BQ2415X_RESET_CURRENT (BIT(0)|BIT(3)|BIT(7)) + +/* status register */ +#define BQ2415X_BIT_TMR_RST 7 +#define BQ2415X_BIT_OTG 7 +#define BQ2415X_BIT_EN_STAT 6 +#define BQ2415X_MASK_STAT (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_STAT 4 +#define BQ2415X_BIT_BOOST 3 +#define BQ2415X_MASK_FAULT (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_FAULT 0 + +/* control register */ +#define BQ2415X_MASK_LIMIT (BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_LIMIT 6 +#define BQ2415X_MASK_VLOWV (BIT(4)|BIT(5)) +#define BQ2415X_SHIFT_VLOWV 4 +#define BQ2415X_BIT_TE 3 +#define BQ2415X_BIT_CE 2 +#define BQ2415X_BIT_HZ_MODE 1 +#define BQ2415X_BIT_OPA_MODE 0 + +/* voltage register */ +#define BQ2415X_MASK_VO (BIT(2)|BIT(3)|BIT(4)|BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VO 2 +#define BQ2415X_BIT_OTG_PL 1 +#define BQ2415X_BIT_OTG_EN 0 + +/* vender register */ +#define BQ2415X_MASK_VENDER (BIT(5)|BIT(6)|BIT(7)) +#define BQ2415X_SHIFT_VENDER 5 +#define BQ2415X_MASK_PN (BIT(3)|BIT(4)) +#define BQ2415X_SHIFT_PN 3 +#define BQ2415X_MASK_REVISION (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_REVISION 0 + +/* current register */ +#define BQ2415X_MASK_RESET BIT(7) +#define BQ2415X_MASK_VI_CHRG (BIT(4)|BIT(5)|BIT(6)) +#define BQ2415X_SHIFT_VI_CHRG 4 +/* N/A BIT(3) */ +#define BQ2415X_MASK_VI_TERM (BIT(0)|BIT(1)|BIT(2)) +#define BQ2415X_SHIFT_VI_TERM 0 + + +enum bq2415x_command { + BQ2415X_TIMER_RESET, + BQ2415X_OTG_STATUS, + BQ2415X_STAT_PIN_STATUS, + BQ2415X_STAT_PIN_ENABLE, + BQ2415X_STAT_PIN_DISABLE, + BQ2415X_CHARGE_STATUS, + BQ2415X_BOOST_STATUS, + BQ2415X_FAULT_STATUS, + + BQ2415X_CHARGE_TERMINATION_STATUS, + BQ2415X_CHARGE_TERMINATION_ENABLE, + BQ2415X_CHARGE_TERMINATION_DISABLE, + BQ2415X_CHARGER_STATUS, + BQ2415X_CHARGER_ENABLE, + BQ2415X_CHARGER_DISABLE, + BQ2415X_HIGH_IMPEDANCE_STATUS, + BQ2415X_HIGH_IMPEDANCE_ENABLE, + BQ2415X_HIGH_IMPEDANCE_DISABLE, + BQ2415X_BOOST_MODE_STATUS, + BQ2415X_BOOST_MODE_ENABLE, + BQ2415X_BOOST_MODE_DISABLE, + + BQ2415X_OTG_LEVEL, + BQ2415X_OTG_ACTIVATE_HIGH, + BQ2415X_OTG_ACTIVATE_LOW, + BQ2415X_OTG_PIN_STATUS, + BQ2415X_OTG_PIN_ENABLE, + BQ2415X_OTG_PIN_DISABLE, + + BQ2415X_VENDER_CODE, + BQ2415X_PART_NUMBER, + BQ2415X_REVISION, +}; + +enum bq2415x_chip { + BQUNKNOWN, + BQ24150, + BQ24150A, + BQ24151, + BQ24151A, + BQ24152, + BQ24153, + BQ24153A, + BQ24155, + BQ24156, + BQ24156A, + BQ24158, +}; + +static char *bq2415x_chip_name[] = { + "unknown", + "bq24150", + "bq24150a", + "bq24151", + "bq24151a", + "bq24152", + "bq24153", + "bq24153a", + "bq24155", + "bq24156", + "bq24156a", + "bq24158", +}; + +struct bq2415x_device { + struct device *dev; + struct bq2415x_platform_data init_data; + struct power_supply charger; + struct delayed_work work; + struct power_supply *notify_psy; + struct notifier_block nb; + enum bq2415x_mode reported_mode;/* mode reported by hook function */ + enum bq2415x_mode mode; /* current 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; + else + 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; + else + return BQ24151; + case 1: + if (bq->chip == BQ24150A || + bq->chip == BQ24152 || + bq->chip == BQ24155) + return bq->chip; + else + return BQ24150; + case 2: + if (bq->chip == BQ24153A) + return bq->chip; + else + return BQ24153; + default: + return BQUNKNOWN; + } + break; + + case 0x6a: + switch (ret) { + case 0: + if (bq->chip == BQ24156A) + return bq->chip; + else + return BQ24156; + case 2: + 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; + else + return -1; + case BQ24153: + case BQ24153A: + case BQ24156: + case BQ24156A: + case BQ24158: + if (ret == 3) + return 0; + else if (ret == 1) + return 1; + else + return -1; + case BQ24155: + if (ret == 3) + return 3; + else + 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 -ENOSYS; + + 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 -ENOSYS; + + 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 -ENOSYS; + + 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 -ENOSYS; + + 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 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; + enum bq2415x_mode mode; + union power_supply_propval prop; + int ret; + int mA; + + if (val != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if (psy != bq->notify_psy) + return NOTIFY_OK; + + dev_dbg(bq->dev, "notifier call was called\n"); + + ret = psy->get_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (ret != 0) + return NOTIFY_OK; + + mA = prop.intval; + + 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 NOTIFY_OK; + + bq->reported_mode = mode; + + /* if automode is not enabled do not tell about reported_mode */ + if (bq->automode < 1) + return NOTIFY_OK; + + sysfs_notify(&bq->charger.dev->kobj, NULL, "reported_mode"); + bq2415x_set_mode(bq, bq->reported_mode); + + 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->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 = container_of(psy, struct bq2415x_device, + charger); + 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]; + + bq->charger.name = bq->name; + bq->charger.type = POWER_SUPPLY_TYPE_USB; + bq->charger.properties = bq2415x_power_supply_props; + bq->charger.num_properties = ARRAY_SIZE(bq2415x_power_supply_props); + bq->charger.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; + } + + ret = power_supply_register(bq->dev, &bq->charger); + if (ret) { + kfree(bq->model); + return ret; + } + + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + + 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 = container_of(psy, struct bq2415x_device, + charger); + enum bq2415x_mode mode; + int ret = 0; + + if (strncmp(buf, "auto", 4) == 0) { + if (bq->automode < 0) + return -ENOSYS; + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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 = container_of(psy, struct bq2415x_device, + charger); + 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; + struct bq2415x_device *bq; + struct device_node *np = client->dev.of_node; + struct bq2415x_platform_data *pdata = client->dev.platform_data; + + if (!np && !pdata) { + dev_err(&client->dev, "platform data missing\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; + + name = kasprintf(GFP_KERNEL, "%s-%d", id->name, 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) { + dev_err(&client->dev, "failed to allocate device data\n"); + ret = -ENOMEM; + goto error_2; + } + + if (np) { + bq->notify_psy = power_supply_get_by_phandle(np, "ti,usb-charger-detection"); + + if (!bq->notify_psy) + return -EPROBE_DEFER; + } + else if (pdata->notify_device) + bq->notify_psy = power_supply_get_by_name(pdata->notify_device); + else + bq->notify_psy = NULL; + + i2c_set_clientdata(client, bq); + + bq->id = num; + bq->dev = &client->dev; + bq->chip = 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) { + ret = of_property_read_u32(np, "ti,current-limit", + &bq->init_data.current_limit); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,weak-battery-voltage", + &bq->init_data.weak_battery_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,battery-regulation-voltage", + &bq->init_data.battery_regulation_voltage); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,charge-current", + &bq->init_data.charge_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,termination-current", + &bq->init_data.termination_current); + if (ret) + return ret; + ret = of_property_read_u32(np, "ti,resistor-sense", + &bq->init_data.resistor_sense); + if (ret) + return ret; + } 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_psy) { + 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_5; + } + + /* Query for initial reported_mode and set it */ + bq2415x_notifier_call(&bq->nb, PSY_EVENT_PROP_CHANGED, bq->notify_psy); + bq2415x_set_mode(bq, bq->reported_mode); + + bq->automode = 1; + dev_info(bq->dev, "automode enabled\n"); + } else { + bq->automode = -1; + dev_info(bq->dev, "automode not supported\n"); + } + + INIT_DELAYED_WORK(&bq->work, bq2415x_timer_work); + bq2415x_set_autotimer(bq, 1); + + dev_info(bq->dev, "driver registered\n"); + return 0; + +error_5: +error_4: + bq2415x_sysfs_exit(bq); +error_3: + bq2415x_power_supply_exit(bq); +error_2: + 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->notify_psy) + power_supply_unreg_notifier(&bq->nb); + + 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 }, + { "bq24158", BQ24158 }, + {}, +}; +MODULE_DEVICE_TABLE(i2c, bq2415x_i2c_id_table); + +static struct i2c_driver bq2415x_driver = { + .driver = { + .name = "bq2415x-charger", + }, + .probe = bq2415x_probe, + .remove = bq2415x_remove, + .id_table = bq2415x_i2c_id_table, +}; +module_i2c_driver(bq2415x_driver); + +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); +MODULE_DESCRIPTION("bq2415x charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/bq24190_charger.c b/drivers/power/bq24190_charger.c new file mode 100644 index 00000000000..ad3ff8fbfbb --- /dev/null +++ b/drivers/power/bq24190_charger.c @@ -0,0 +1,1549 @@ +/* + * Driver for the TI bq24190 battery charger. + * + * Author: Mark A. Greer <mgreer@animalcreek.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/of_irq.h> +#include <linux/of_device.h> +#include <linux/pm_runtime.h> +#include <linux/power_supply.h> +#include <linux/gpio.h> +#include <linux/i2c.h> + +#include <linux/power/bq24190_charger.h> + + +#define BQ24190_MANUFACTURER "Texas Instruments" + +#define BQ24190_REG_ISC 0x00 /* Input Source Control */ +#define BQ24190_REG_ISC_EN_HIZ_MASK BIT(7) +#define BQ24190_REG_ISC_EN_HIZ_SHIFT 7 +#define BQ24190_REG_ISC_VINDPM_MASK (BIT(6) | BIT(5) | BIT(4) | \ + BIT(3)) +#define BQ24190_REG_ISC_VINDPM_SHIFT 3 +#define BQ24190_REG_ISC_IINLIM_MASK (BIT(2) | BIT(1) | BIT(0)) +#define BQ24190_REG_ISC_IINLIM_SHIFT 0 + +#define BQ24190_REG_POC 0x01 /* Power-On Configuration */ +#define BQ24190_REG_POC_RESET_MASK BIT(7) +#define BQ24190_REG_POC_RESET_SHIFT 7 +#define BQ24190_REG_POC_WDT_RESET_MASK BIT(6) +#define BQ24190_REG_POC_WDT_RESET_SHIFT 6 +#define BQ24190_REG_POC_CHG_CONFIG_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_POC_CHG_CONFIG_SHIFT 4 +#define BQ24190_REG_POC_SYS_MIN_MASK (BIT(3) | BIT(2) | BIT(1)) +#define BQ24190_REG_POC_SYS_MIN_SHIFT 1 +#define BQ24190_REG_POC_BOOST_LIM_MASK BIT(0) +#define BQ24190_REG_POC_BOOST_LIM_SHIFT 0 + +#define BQ24190_REG_CCC 0x02 /* Charge Current Control */ +#define BQ24190_REG_CCC_ICHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_CCC_ICHG_SHIFT 2 +#define BQ24190_REG_CCC_FORCE_20PCT_MASK BIT(0) +#define BQ24190_REG_CCC_FORCE_20PCT_SHIFT 0 + +#define BQ24190_REG_PCTCC 0x03 /* Pre-charge/Termination Current Cntl */ +#define BQ24190_REG_PCTCC_IPRECHG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4)) +#define BQ24190_REG_PCTCC_IPRECHG_SHIFT 4 +#define BQ24190_REG_PCTCC_ITERM_MASK (BIT(3) | BIT(2) | BIT(1) | \ + BIT(0)) +#define BQ24190_REG_PCTCC_ITERM_SHIFT 0 + +#define BQ24190_REG_CVC 0x04 /* Charge Voltage Control */ +#define BQ24190_REG_CVC_VREG_MASK (BIT(7) | BIT(6) | BIT(5) | \ + BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_CVC_VREG_SHIFT 2 +#define BQ24190_REG_CVC_BATLOWV_MASK BIT(1) +#define BQ24190_REG_CVC_BATLOWV_SHIFT 1 +#define BQ24190_REG_CVC_VRECHG_MASK BIT(0) +#define BQ24190_REG_CVC_VRECHG_SHIFT 0 + +#define BQ24190_REG_CTTC 0x05 /* Charge Term/Timer Control */ +#define BQ24190_REG_CTTC_EN_TERM_MASK BIT(7) +#define BQ24190_REG_CTTC_EN_TERM_SHIFT 7 +#define BQ24190_REG_CTTC_TERM_STAT_MASK BIT(6) +#define BQ24190_REG_CTTC_TERM_STAT_SHIFT 6 +#define BQ24190_REG_CTTC_WATCHDOG_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_CTTC_WATCHDOG_SHIFT 4 +#define BQ24190_REG_CTTC_EN_TIMER_MASK BIT(3) +#define BQ24190_REG_CTTC_EN_TIMER_SHIFT 3 +#define BQ24190_REG_CTTC_CHG_TIMER_MASK (BIT(2) | BIT(1)) +#define BQ24190_REG_CTTC_CHG_TIMER_SHIFT 1 +#define BQ24190_REG_CTTC_JEITA_ISET_MASK BIT(0) +#define BQ24190_REG_CTTC_JEITA_ISET_SHIFT 0 + +#define BQ24190_REG_ICTRC 0x06 /* IR Comp/Thermal Regulation Control */ +#define BQ24190_REG_ICTRC_BAT_COMP_MASK (BIT(7) | BIT(6) | BIT(5)) +#define BQ24190_REG_ICTRC_BAT_COMP_SHIFT 5 +#define BQ24190_REG_ICTRC_VCLAMP_MASK (BIT(4) | BIT(3) | BIT(2)) +#define BQ24190_REG_ICTRC_VCLAMP_SHIFT 2 +#define BQ24190_REG_ICTRC_TREG_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_ICTRC_TREG_SHIFT 0 + +#define BQ24190_REG_MOC 0x07 /* Misc. Operation Control */ +#define BQ24190_REG_MOC_DPDM_EN_MASK BIT(7) +#define BQ24190_REG_MOC_DPDM_EN_SHIFT 7 +#define BQ24190_REG_MOC_TMR2X_EN_MASK BIT(6) +#define BQ24190_REG_MOC_TMR2X_EN_SHIFT 6 +#define BQ24190_REG_MOC_BATFET_DISABLE_MASK BIT(5) +#define BQ24190_REG_MOC_BATFET_DISABLE_SHIFT 5 +#define BQ24190_REG_MOC_JEITA_VSET_MASK BIT(4) +#define BQ24190_REG_MOC_JEITA_VSET_SHIFT 4 +#define BQ24190_REG_MOC_INT_MASK_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_MOC_INT_MASK_SHIFT 0 + +#define BQ24190_REG_SS 0x08 /* System Status */ +#define BQ24190_REG_SS_VBUS_STAT_MASK (BIT(7) | BIT(6)) +#define BQ24190_REG_SS_VBUS_STAT_SHIFT 6 +#define BQ24190_REG_SS_CHRG_STAT_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_SS_CHRG_STAT_SHIFT 4 +#define BQ24190_REG_SS_DPM_STAT_MASK BIT(3) +#define BQ24190_REG_SS_DPM_STAT_SHIFT 3 +#define BQ24190_REG_SS_PG_STAT_MASK BIT(2) +#define BQ24190_REG_SS_PG_STAT_SHIFT 2 +#define BQ24190_REG_SS_THERM_STAT_MASK BIT(1) +#define BQ24190_REG_SS_THERM_STAT_SHIFT 1 +#define BQ24190_REG_SS_VSYS_STAT_MASK BIT(0) +#define BQ24190_REG_SS_VSYS_STAT_SHIFT 0 + +#define BQ24190_REG_F 0x09 /* Fault */ +#define BQ24190_REG_F_WATCHDOG_FAULT_MASK BIT(7) +#define BQ24190_REG_F_WATCHDOG_FAULT_SHIFT 7 +#define BQ24190_REG_F_BOOST_FAULT_MASK BIT(6) +#define BQ24190_REG_F_BOOST_FAULT_SHIFT 6 +#define BQ24190_REG_F_CHRG_FAULT_MASK (BIT(5) | BIT(4)) +#define BQ24190_REG_F_CHRG_FAULT_SHIFT 4 +#define BQ24190_REG_F_BAT_FAULT_MASK BIT(3) +#define BQ24190_REG_F_BAT_FAULT_SHIFT 3 +#define BQ24190_REG_F_NTC_FAULT_MASK (BIT(2) | BIT(1) | BIT(0)) +#define BQ24190_REG_F_NTC_FAULT_SHIFT 0 + +#define BQ24190_REG_VPRS 0x0A /* Vendor/Part/Revision Status */ +#define BQ24190_REG_VPRS_PN_MASK (BIT(5) | BIT(4) | BIT(3)) +#define BQ24190_REG_VPRS_PN_SHIFT 3 +#define BQ24190_REG_VPRS_PN_24190 0x4 +#define BQ24190_REG_VPRS_PN_24192 0x5 /* Also 24193 */ +#define BQ24190_REG_VPRS_PN_24192I 0x3 +#define BQ24190_REG_VPRS_TS_PROFILE_MASK BIT(2) +#define BQ24190_REG_VPRS_TS_PROFILE_SHIFT 2 +#define BQ24190_REG_VPRS_DEV_REG_MASK (BIT(1) | BIT(0)) +#define BQ24190_REG_VPRS_DEV_REG_SHIFT 0 + +/* + * The FAULT register is latched by the bq24190 (except for NTC_FAULT) + * so the first read after a fault returns the latched value and subsequent + * reads return the current value. In order to return the fault status + * to the user, have the interrupt handler save the reg's value and retrieve + * it in the appropriate health/status routine. Each routine has its own + * flag indicating whether it should use the value stored by the last run + * of the interrupt handler or do an actual reg read. That way each routine + * can report back whatever fault may have occured. + */ +struct bq24190_dev_info { + struct i2c_client *client; + struct device *dev; + struct power_supply charger; + struct power_supply battery; + char model_name[I2C_NAME_SIZE]; + kernel_ulong_t model; + unsigned int gpio_int; + unsigned int irq; + struct mutex f_reg_lock; + bool first_time; + bool charger_health_valid; + bool battery_health_valid; + bool battery_status_valid; + u8 f_reg; + u8 ss_reg; + u8 watchdog; +}; + +/* + * The tables below provide a 2-way mapping for the value that goes in + * the register field and the real-world value that it represents. + * The index of the array is the value that goes in the register; the + * number at that index in the array is the real-world value that it + * represents. + */ +/* REG02[7:2] (ICHG) in uAh */ +static const int bq24190_ccc_ichg_values[] = { + 512000, 576000, 640000, 704000, 768000, 832000, 896000, 960000, + 1024000, 1088000, 1152000, 1216000, 1280000, 1344000, 1408000, 1472000, + 1536000, 1600000, 1664000, 1728000, 1792000, 1856000, 1920000, 1984000, + 2048000, 2112000, 2176000, 2240000, 2304000, 2368000, 2432000, 2496000, + 2560000, 2624000, 2688000, 2752000, 2816000, 2880000, 2944000, 3008000, + 3072000, 3136000, 3200000, 3264000, 3328000, 3392000, 3456000, 3520000, + 3584000, 3648000, 3712000, 3776000, 3840000, 3904000, 3968000, 4032000, + 4096000, 4160000, 4224000, 4288000, 4352000, 4416000, 4480000, 4544000 +}; + +/* REG04[7:2] (VREG) in uV */ +static const int bq24190_cvc_vreg_values[] = { + 3504000, 3520000, 3536000, 3552000, 3568000, 3584000, 3600000, 3616000, + 3632000, 3648000, 3664000, 3680000, 3696000, 3712000, 3728000, 3744000, + 3760000, 3776000, 3792000, 3808000, 3824000, 3840000, 3856000, 3872000, + 3888000, 3904000, 3920000, 3936000, 3952000, 3968000, 3984000, 4000000, + 4016000, 4032000, 4048000, 4064000, 4080000, 4096000, 4112000, 4128000, + 4144000, 4160000, 4176000, 4192000, 4208000, 4224000, 4240000, 4256000, + 4272000, 4288000, 4304000, 4320000, 4336000, 4352000, 4368000, 4384000, + 4400000 +}; + +/* REG06[1:0] (TREG) in tenths of degrees Celcius */ +static const int bq24190_ictrc_treg_values[] = { + 600, 800, 1000, 1200 +}; + +/* + * Return the index in 'tbl' of greatest value that is less than or equal to + * 'val'. The index range returned is 0 to 'tbl_size' - 1. Assumes that + * the values in 'tbl' are sorted from smallest to largest and 'tbl_size' + * is less than 2^8. + */ +static u8 bq24190_find_idx(const int tbl[], int tbl_size, int v) +{ + int i; + + for (i = 1; i < tbl_size; i++) + if (v < tbl[i]) + break; + + return i - 1; +} + +/* Basic driver I/O routines */ + +static int bq24190_read(struct bq24190_dev_info *bdi, u8 reg, u8 *data) +{ + int ret; + + ret = i2c_smbus_read_byte_data(bdi->client, reg); + if (ret < 0) + return ret; + + *data = ret; + return 0; +} + +static int bq24190_write(struct bq24190_dev_info *bdi, u8 reg, u8 data) +{ + return i2c_smbus_write_byte_data(bdi->client, reg, data); +} + +static int bq24190_read_mask(struct bq24190_dev_info *bdi, u8 reg, + u8 mask, u8 shift, u8 *data) +{ + u8 v; + int ret; + + ret = bq24190_read(bdi, reg, &v); + if (ret < 0) + return ret; + + v &= mask; + v >>= shift; + *data = v; + + return 0; +} + +static int bq24190_write_mask(struct bq24190_dev_info *bdi, u8 reg, + u8 mask, u8 shift, u8 data) +{ + u8 v; + int ret; + + ret = bq24190_read(bdi, reg, &v); + if (ret < 0) + return ret; + + v &= ~mask; + v |= ((data << shift) & mask); + + return bq24190_write(bdi, reg, v); +} + +static int bq24190_get_field_val(struct bq24190_dev_info *bdi, + u8 reg, u8 mask, u8 shift, + const int tbl[], int tbl_size, + int *val) +{ + u8 v; + int ret; + + ret = bq24190_read_mask(bdi, reg, mask, shift, &v); + if (ret < 0) + return ret; + + v = (v >= tbl_size) ? (tbl_size - 1) : v; + *val = tbl[v]; + + return 0; +} + +static int bq24190_set_field_val(struct bq24190_dev_info *bdi, + u8 reg, u8 mask, u8 shift, + const int tbl[], int tbl_size, + int val) +{ + u8 idx; + + idx = bq24190_find_idx(tbl, tbl_size, val); + + return bq24190_write_mask(bdi, reg, mask, shift, idx); +} + +#ifdef CONFIG_SYSFS +/* + * There are a numerous options that are configurable on the bq24190 + * that go well beyond what the power_supply properties provide access to. + * Provide sysfs access to them so they can be examined and possibly modified + * on the fly. They will be provided for the charger power_supply object only + * and will be prefixed by 'f_' to make them easier to recognize. + */ + +#define BQ24190_SYSFS_FIELD(_name, r, f, m, store) \ +{ \ + .attr = __ATTR(f_##_name, m, bq24190_sysfs_show, store), \ + .reg = BQ24190_REG_##r, \ + .mask = BQ24190_REG_##r##_##f##_MASK, \ + .shift = BQ24190_REG_##r##_##f##_SHIFT, \ +} + +#define BQ24190_SYSFS_FIELD_RW(_name, r, f) \ + BQ24190_SYSFS_FIELD(_name, r, f, S_IWUSR | S_IRUGO, \ + bq24190_sysfs_store) + +#define BQ24190_SYSFS_FIELD_RO(_name, r, f) \ + BQ24190_SYSFS_FIELD(_name, r, f, S_IRUGO, NULL) + +static ssize_t bq24190_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t bq24190_sysfs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count); + +struct bq24190_sysfs_field_info { + struct device_attribute attr; + u8 reg; + u8 mask; + u8 shift; +}; + +/* On i386 ptrace-abi.h defines SS that breaks the macro calls below. */ +#undef SS + +static struct bq24190_sysfs_field_info bq24190_sysfs_field_tbl[] = { + /* sysfs name reg field in reg */ + BQ24190_SYSFS_FIELD_RW(en_hiz, ISC, EN_HIZ), + BQ24190_SYSFS_FIELD_RW(vindpm, ISC, VINDPM), + BQ24190_SYSFS_FIELD_RW(iinlim, ISC, IINLIM), + BQ24190_SYSFS_FIELD_RW(chg_config, POC, CHG_CONFIG), + BQ24190_SYSFS_FIELD_RW(sys_min, POC, SYS_MIN), + BQ24190_SYSFS_FIELD_RW(boost_lim, POC, BOOST_LIM), + BQ24190_SYSFS_FIELD_RW(ichg, CCC, ICHG), + BQ24190_SYSFS_FIELD_RW(force_20_pct, CCC, FORCE_20PCT), + BQ24190_SYSFS_FIELD_RW(iprechg, PCTCC, IPRECHG), + BQ24190_SYSFS_FIELD_RW(iterm, PCTCC, ITERM), + BQ24190_SYSFS_FIELD_RW(vreg, CVC, VREG), + BQ24190_SYSFS_FIELD_RW(batlowv, CVC, BATLOWV), + BQ24190_SYSFS_FIELD_RW(vrechg, CVC, VRECHG), + BQ24190_SYSFS_FIELD_RW(en_term, CTTC, EN_TERM), + BQ24190_SYSFS_FIELD_RW(term_stat, CTTC, TERM_STAT), + BQ24190_SYSFS_FIELD_RO(watchdog, CTTC, WATCHDOG), + BQ24190_SYSFS_FIELD_RW(en_timer, CTTC, EN_TIMER), + BQ24190_SYSFS_FIELD_RW(chg_timer, CTTC, CHG_TIMER), + BQ24190_SYSFS_FIELD_RW(jeta_iset, CTTC, JEITA_ISET), + BQ24190_SYSFS_FIELD_RW(bat_comp, ICTRC, BAT_COMP), + BQ24190_SYSFS_FIELD_RW(vclamp, ICTRC, VCLAMP), + BQ24190_SYSFS_FIELD_RW(treg, ICTRC, TREG), + BQ24190_SYSFS_FIELD_RW(dpdm_en, MOC, DPDM_EN), + BQ24190_SYSFS_FIELD_RW(tmr2x_en, MOC, TMR2X_EN), + BQ24190_SYSFS_FIELD_RW(batfet_disable, MOC, BATFET_DISABLE), + BQ24190_SYSFS_FIELD_RW(jeita_vset, MOC, JEITA_VSET), + BQ24190_SYSFS_FIELD_RO(int_mask, MOC, INT_MASK), + BQ24190_SYSFS_FIELD_RO(vbus_stat, SS, VBUS_STAT), + BQ24190_SYSFS_FIELD_RO(chrg_stat, SS, CHRG_STAT), + BQ24190_SYSFS_FIELD_RO(dpm_stat, SS, DPM_STAT), + BQ24190_SYSFS_FIELD_RO(pg_stat, SS, PG_STAT), + BQ24190_SYSFS_FIELD_RO(therm_stat, SS, THERM_STAT), + BQ24190_SYSFS_FIELD_RO(vsys_stat, SS, VSYS_STAT), + BQ24190_SYSFS_FIELD_RO(watchdog_fault, F, WATCHDOG_FAULT), + BQ24190_SYSFS_FIELD_RO(boost_fault, F, BOOST_FAULT), + BQ24190_SYSFS_FIELD_RO(chrg_fault, F, CHRG_FAULT), + BQ24190_SYSFS_FIELD_RO(bat_fault, F, BAT_FAULT), + BQ24190_SYSFS_FIELD_RO(ntc_fault, F, NTC_FAULT), + BQ24190_SYSFS_FIELD_RO(pn, VPRS, PN), + BQ24190_SYSFS_FIELD_RO(ts_profile, VPRS, TS_PROFILE), + BQ24190_SYSFS_FIELD_RO(dev_reg, VPRS, DEV_REG), +}; + +static struct attribute * + bq24190_sysfs_attrs[ARRAY_SIZE(bq24190_sysfs_field_tbl) + 1]; + +static const struct attribute_group bq24190_sysfs_attr_group = { + .attrs = bq24190_sysfs_attrs, +}; + +static void bq24190_sysfs_init_attrs(void) +{ + int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); + + for (i = 0; i < limit; i++) + bq24190_sysfs_attrs[i] = &bq24190_sysfs_field_tbl[i].attr.attr; + + bq24190_sysfs_attrs[limit] = NULL; /* Has additional entry for this */ +} + +static struct bq24190_sysfs_field_info *bq24190_sysfs_field_lookup( + const char *name) +{ + int i, limit = ARRAY_SIZE(bq24190_sysfs_field_tbl); + + for (i = 0; i < limit; i++) + if (!strcmp(name, bq24190_sysfs_field_tbl[i].attr.attr.name)) + break; + + if (i >= limit) + return NULL; + + return &bq24190_sysfs_field_tbl[i]; +} + +static ssize_t bq24190_sysfs_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, charger); + struct bq24190_sysfs_field_info *info; + int ret; + u8 v; + + info = bq24190_sysfs_field_lookup(attr->attr.name); + if (!info) + return -EINVAL; + + ret = bq24190_read_mask(bdi, info->reg, info->mask, info->shift, &v); + if (ret) + return ret; + + return scnprintf(buf, PAGE_SIZE, "%hhx\n", v); +} + +static ssize_t bq24190_sysfs_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + struct power_supply *psy = dev_get_drvdata(dev); + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, charger); + struct bq24190_sysfs_field_info *info; + int ret; + u8 v; + + info = bq24190_sysfs_field_lookup(attr->attr.name); + if (!info) + return -EINVAL; + + ret = kstrtou8(buf, 0, &v); + if (ret < 0) + return ret; + + ret = bq24190_write_mask(bdi, info->reg, info->mask, info->shift, v); + if (ret) + return ret; + + return count; +} + +static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) +{ + bq24190_sysfs_init_attrs(); + + return sysfs_create_group(&bdi->charger.dev->kobj, + &bq24190_sysfs_attr_group); +} + +static void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) +{ + sysfs_remove_group(&bdi->charger.dev->kobj, &bq24190_sysfs_attr_group); +} +#else +static int bq24190_sysfs_create_group(struct bq24190_dev_info *bdi) +{ + return 0; +} + +static inline void bq24190_sysfs_remove_group(struct bq24190_dev_info *bdi) {} +#endif + +/* + * According to the "Host Mode and default Mode" section of the + * manual, a write to any register causes the bq24190 to switch + * from default mode to host mode. It will switch back to default + * mode after a WDT timeout unless the WDT is turned off as well. + * So, by simply turning off the WDT, we accomplish both with the + * same write. + */ +static int bq24190_set_mode_host(struct bq24190_dev_info *bdi) +{ + int ret; + u8 v; + + ret = bq24190_read(bdi, BQ24190_REG_CTTC, &v); + if (ret < 0) + return ret; + + bdi->watchdog = ((v & BQ24190_REG_CTTC_WATCHDOG_MASK) >> + BQ24190_REG_CTTC_WATCHDOG_SHIFT); + v &= ~BQ24190_REG_CTTC_WATCHDOG_MASK; + + return bq24190_write(bdi, BQ24190_REG_CTTC, v); +} + +static int bq24190_register_reset(struct bq24190_dev_info *bdi) +{ + int ret, limit = 100; + u8 v; + + /* Reset the registers */ + ret = bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_RESET_MASK, + BQ24190_REG_POC_RESET_SHIFT, + 0x1); + if (ret < 0) + return ret; + + /* Reset bit will be cleared by hardware so poll until it is */ + do { + ret = bq24190_read_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_RESET_MASK, + BQ24190_REG_POC_RESET_SHIFT, + &v); + if (ret < 0) + return ret; + + if (!v) + break; + + udelay(10); + } while (--limit); + + if (!limit) + return -EIO; + + return 0; +} + +/* Charger power supply property routines */ + +static int bq24190_charger_get_charge_type(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int type, ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, + &v); + if (ret < 0) + return ret; + + /* If POC[CHG_CONFIG] (REG01[5:4]) == 0, charge is disabled */ + if (!v) { + type = POWER_SUPPLY_CHARGE_TYPE_NONE; + } else { + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, + &v); + if (ret < 0) + return ret; + + type = (v) ? POWER_SUPPLY_CHARGE_TYPE_TRICKLE : + POWER_SUPPLY_CHARGE_TYPE_FAST; + } + + val->intval = type; + + return 0; +} + +static int bq24190_charger_set_charge_type(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + u8 chg_config, force_20pct, en_term; + int ret; + + /* + * According to the "Termination when REG02[0] = 1" section of + * the bq24190 manual, the trickle charge could be less than the + * termination current so it recommends turning off the termination + * function. + * + * Note: AFAICT from the datasheet, the user will have to manually + * turn off the charging when in 20% mode. If its not turned off, + * there could be battery damage. So, use this mode at your own risk. + */ + switch (val->intval) { + case POWER_SUPPLY_CHARGE_TYPE_NONE: + chg_config = 0x0; + break; + case POWER_SUPPLY_CHARGE_TYPE_TRICKLE: + chg_config = 0x1; + force_20pct = 0x1; + en_term = 0x0; + break; + case POWER_SUPPLY_CHARGE_TYPE_FAST: + chg_config = 0x1; + force_20pct = 0x0; + en_term = 0x1; + break; + default: + return -EINVAL; + } + + if (chg_config) { /* Enabling the charger */ + ret = bq24190_write_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, + force_20pct); + if (ret < 0) + return ret; + + ret = bq24190_write_mask(bdi, BQ24190_REG_CTTC, + BQ24190_REG_CTTC_EN_TERM_MASK, + BQ24190_REG_CTTC_EN_TERM_SHIFT, + en_term); + if (ret < 0) + return ret; + } + + return bq24190_write_mask(bdi, BQ24190_REG_POC, + BQ24190_REG_POC_CHG_CONFIG_MASK, + BQ24190_REG_POC_CHG_CONFIG_SHIFT, chg_config); +} + +static int bq24190_charger_get_health(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int health, ret; + + mutex_lock(&bdi->f_reg_lock); + + if (bdi->charger_health_valid) { + v = bdi->f_reg; + bdi->charger_health_valid = false; + mutex_unlock(&bdi->f_reg_lock); + } else { + mutex_unlock(&bdi->f_reg_lock); + + ret = bq24190_read(bdi, BQ24190_REG_F, &v); + if (ret < 0) + return ret; + } + + if (v & BQ24190_REG_F_BOOST_FAULT_MASK) { + /* + * This could be over-current or over-voltage but there's + * no way to tell which. Return 'OVERVOLTAGE' since there + * isn't an 'OVERCURRENT' value defined that we can return + * even if it was over-current. + */ + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else { + v &= BQ24190_REG_F_CHRG_FAULT_MASK; + v >>= BQ24190_REG_F_CHRG_FAULT_SHIFT; + + switch (v) { + case 0x0: /* Normal */ + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x1: /* Input Fault (VBUS OVP or VBAT<VBUS<3.8V) */ + /* + * This could be over-voltage or under-voltage + * and there's no way to tell which. Instead + * of looking foolish and returning 'OVERVOLTAGE' + * when its really under-voltage, just return + * 'UNSPEC_FAILURE'. + */ + health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + break; + case 0x2: /* Thermal Shutdown */ + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + case 0x3: /* Charge Safety Timer Expiration */ + health = POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE; + break; + default: + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + } + + val->intval = health; + + return 0; +} + +static int bq24190_charger_get_online(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_SS, + BQ24190_REG_SS_PG_STAT_MASK, + BQ24190_REG_SS_PG_STAT_SHIFT, &v); + if (ret < 0) + return ret; + + val->intval = v; + return 0; +} + +static int bq24190_charger_get_current(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int curr, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, + bq24190_ccc_ichg_values, + ARRAY_SIZE(bq24190_ccc_ichg_values), &curr); + if (ret < 0) + return ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); + if (ret < 0) + return ret; + + /* If FORCE_20PCT is enabled, then current is 20% of ICHG value */ + if (v) + curr /= 5; + + val->intval = curr; + return 0; +} + +static int bq24190_charger_get_current_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(bq24190_ccc_ichg_values) - 1; + + val->intval = bq24190_ccc_ichg_values[idx]; + return 0; +} + +static int bq24190_charger_set_current(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + u8 v; + int ret, curr = val->intval; + + ret = bq24190_read_mask(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_FORCE_20PCT_MASK, + BQ24190_REG_CCC_FORCE_20PCT_SHIFT, &v); + if (ret < 0) + return ret; + + /* If FORCE_20PCT is enabled, have to multiply value passed in by 5 */ + if (v) + curr *= 5; + + return bq24190_set_field_val(bdi, BQ24190_REG_CCC, + BQ24190_REG_CCC_ICHG_MASK, BQ24190_REG_CCC_ICHG_SHIFT, + bq24190_ccc_ichg_values, + ARRAY_SIZE(bq24190_ccc_ichg_values), curr); +} + +static int bq24190_charger_get_voltage(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int voltage, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_CVC, + BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, + bq24190_cvc_vreg_values, + ARRAY_SIZE(bq24190_cvc_vreg_values), &voltage); + if (ret < 0) + return ret; + + val->intval = voltage; + return 0; +} + +static int bq24190_charger_get_voltage_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int idx = ARRAY_SIZE(bq24190_cvc_vreg_values) - 1; + + val->intval = bq24190_cvc_vreg_values[idx]; + return 0; +} + +static int bq24190_charger_set_voltage(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_set_field_val(bdi, BQ24190_REG_CVC, + BQ24190_REG_CVC_VREG_MASK, BQ24190_REG_CVC_VREG_SHIFT, + bq24190_cvc_vreg_values, + ARRAY_SIZE(bq24190_cvc_vreg_values), val->intval); +} + +static int bq24190_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, charger); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq24190_charger_get_charge_type(bdi, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq24190_charger_get_health(bdi, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_charger_get_online(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq24190_charger_get_current(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + ret = bq24190_charger_get_current_max(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq24190_charger_get_voltage(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + ret = bq24190_charger_get_voltage_max(bdi, val); + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + ret = 0; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = bdi->model_name; + ret = 0; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = BQ24190_MANUFACTURER; + ret = 0; + break; + default: + ret = -ENODATA; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_charger_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, charger); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + ret = bq24190_charger_set_charge_type(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + ret = bq24190_charger_set_current(bdi, val); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = bq24190_charger_set_voltage(bdi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_charger_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_CHARGE_TYPE: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property bq24190_charger_properties[] = { + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_SCOPE, + POWER_SUPPLY_PROP_MODEL_NAME, + POWER_SUPPLY_PROP_MANUFACTURER, +}; + +static char *bq24190_charger_supplied_to[] = { + "main-battery", +}; + +static void bq24190_charger_init(struct power_supply *charger) +{ + charger->name = "bq24190-charger"; + charger->type = POWER_SUPPLY_TYPE_USB; + charger->properties = bq24190_charger_properties; + charger->num_properties = ARRAY_SIZE(bq24190_charger_properties); + charger->supplied_to = bq24190_charger_supplied_to; + charger->num_supplies = ARRAY_SIZE(bq24190_charger_supplied_to); + charger->get_property = bq24190_charger_get_property; + charger->set_property = bq24190_charger_set_property; + charger->property_is_writeable = bq24190_charger_property_is_writeable; +} + +/* Battery power supply property routines */ + +static int bq24190_battery_get_status(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 ss_reg, chrg_fault; + int status, ret; + + mutex_lock(&bdi->f_reg_lock); + + if (bdi->battery_status_valid) { + chrg_fault = bdi->f_reg; + bdi->battery_status_valid = false; + mutex_unlock(&bdi->f_reg_lock); + } else { + mutex_unlock(&bdi->f_reg_lock); + + ret = bq24190_read(bdi, BQ24190_REG_F, &chrg_fault); + if (ret < 0) + return ret; + } + + chrg_fault &= BQ24190_REG_F_CHRG_FAULT_MASK; + chrg_fault >>= BQ24190_REG_F_CHRG_FAULT_SHIFT; + + ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); + if (ret < 0) + return ret; + + /* + * The battery must be discharging when any of these are true: + * - there is no good power source; + * - there is a charge fault. + * Could also be discharging when in "supplement mode" but + * there is no way to tell when its in that mode. + */ + if (!(ss_reg & BQ24190_REG_SS_PG_STAT_MASK) || chrg_fault) { + status = POWER_SUPPLY_STATUS_DISCHARGING; + } else { + ss_reg &= BQ24190_REG_SS_CHRG_STAT_MASK; + ss_reg >>= BQ24190_REG_SS_CHRG_STAT_SHIFT; + + switch (ss_reg) { + case 0x0: /* Not Charging */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + case 0x1: /* Pre-charge */ + case 0x2: /* Fast Charging */ + status = POWER_SUPPLY_STATUS_CHARGING; + break; + case 0x3: /* Charge Termination Done */ + status = POWER_SUPPLY_STATUS_FULL; + break; + default: + ret = -EIO; + } + } + + if (!ret) + val->intval = status; + + return ret; +} + +static int bq24190_battery_get_health(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 v; + int health, ret; + + mutex_lock(&bdi->f_reg_lock); + + if (bdi->battery_health_valid) { + v = bdi->f_reg; + bdi->battery_health_valid = false; + mutex_unlock(&bdi->f_reg_lock); + } else { + mutex_unlock(&bdi->f_reg_lock); + + ret = bq24190_read(bdi, BQ24190_REG_F, &v); + if (ret < 0) + return ret; + } + + if (v & BQ24190_REG_F_BAT_FAULT_MASK) { + health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + } else { + v &= BQ24190_REG_F_NTC_FAULT_MASK; + v >>= BQ24190_REG_F_NTC_FAULT_SHIFT; + + switch (v) { + case 0x0: /* Normal */ + health = POWER_SUPPLY_HEALTH_GOOD; + break; + case 0x1: /* TS1 Cold */ + case 0x3: /* TS2 Cold */ + case 0x5: /* Both Cold */ + health = POWER_SUPPLY_HEALTH_COLD; + break; + case 0x2: /* TS1 Hot */ + case 0x4: /* TS2 Hot */ + case 0x6: /* Both Hot */ + health = POWER_SUPPLY_HEALTH_OVERHEAT; + break; + default: + health = POWER_SUPPLY_HEALTH_UNKNOWN; + } + } + + val->intval = health; + return 0; +} + +static int bq24190_battery_get_online(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + u8 batfet_disable; + int ret; + + ret = bq24190_read_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, &batfet_disable); + if (ret < 0) + return ret; + + val->intval = !batfet_disable; + return 0; +} + +static int bq24190_battery_set_online(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_write_mask(bdi, BQ24190_REG_MOC, + BQ24190_REG_MOC_BATFET_DISABLE_MASK, + BQ24190_REG_MOC_BATFET_DISABLE_SHIFT, !val->intval); +} + +static int bq24190_battery_get_temp_alert_max(struct bq24190_dev_info *bdi, + union power_supply_propval *val) +{ + int temp, ret; + + ret = bq24190_get_field_val(bdi, BQ24190_REG_ICTRC, + BQ24190_REG_ICTRC_TREG_MASK, + BQ24190_REG_ICTRC_TREG_SHIFT, + bq24190_ictrc_treg_values, + ARRAY_SIZE(bq24190_ictrc_treg_values), &temp); + if (ret < 0) + return ret; + + val->intval = temp; + return 0; +} + +static int bq24190_battery_set_temp_alert_max(struct bq24190_dev_info *bdi, + const union power_supply_propval *val) +{ + return bq24190_set_field_val(bdi, BQ24190_REG_ICTRC, + BQ24190_REG_ICTRC_TREG_MASK, + BQ24190_REG_ICTRC_TREG_SHIFT, + bq24190_ictrc_treg_values, + ARRAY_SIZE(bq24190_ictrc_treg_values), val->intval); +} + +static int bq24190_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, battery); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_get_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + ret = bq24190_battery_get_status(bdi, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq24190_battery_get_health(bdi, val); + break; + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_battery_get_online(bdi, val); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + /* Could be Li-on or Li-polymer but no way to tell which */ + val->intval = POWER_SUPPLY_TECHNOLOGY_UNKNOWN; + ret = 0; + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = bq24190_battery_get_temp_alert_max(bdi, val); + break; + case POWER_SUPPLY_PROP_SCOPE: + val->intval = POWER_SUPPLY_SCOPE_SYSTEM; + ret = 0; + break; + default: + ret = -ENODATA; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_battery_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct bq24190_dev_info *bdi = + container_of(psy, struct bq24190_dev_info, battery); + int ret; + + dev_dbg(bdi->dev, "prop: %d\n", psp); + + pm_runtime_put_sync(bdi->dev); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + ret = bq24190_battery_set_online(bdi, val); + break; + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = bq24190_battery_set_temp_alert_max(bdi, val); + break; + default: + ret = -EINVAL; + } + + pm_runtime_put_sync(bdi->dev); + return ret; +} + +static int bq24190_battery_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + int ret; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + case POWER_SUPPLY_PROP_TEMP_ALERT_MAX: + ret = 1; + break; + default: + ret = 0; + } + + return ret; +} + +static enum power_supply_property bq24190_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_TEMP_ALERT_MAX, + POWER_SUPPLY_PROP_SCOPE, +}; + +static void bq24190_battery_init(struct power_supply *battery) +{ + battery->name = "bq24190-battery"; + battery->type = POWER_SUPPLY_TYPE_BATTERY; + battery->properties = bq24190_battery_properties; + battery->num_properties = ARRAY_SIZE(bq24190_battery_properties); + battery->get_property = bq24190_battery_get_property; + battery->set_property = bq24190_battery_set_property; + battery->property_is_writeable = bq24190_battery_property_is_writeable; +} + +static irqreturn_t bq24190_irq_handler_thread(int irq, void *data) +{ + struct bq24190_dev_info *bdi = data; + bool alert_userspace = false; + u8 ss_reg, f_reg; + int ret; + + pm_runtime_get_sync(bdi->dev); + + ret = bq24190_read(bdi, BQ24190_REG_SS, &ss_reg); + if (ret < 0) { + dev_err(bdi->dev, "Can't read SS reg: %d\n", ret); + goto out; + } + + if (ss_reg != bdi->ss_reg) { + /* + * The device is in host mode so when PG_STAT goes from 1->0 + * (i.e., power removed) HIZ needs to be disabled. + */ + if ((bdi->ss_reg & BQ24190_REG_SS_PG_STAT_MASK) && + !(ss_reg & BQ24190_REG_SS_PG_STAT_MASK)) { + ret = bq24190_write_mask(bdi, BQ24190_REG_ISC, + BQ24190_REG_ISC_EN_HIZ_MASK, + BQ24190_REG_ISC_EN_HIZ_SHIFT, + 0); + if (ret < 0) + dev_err(bdi->dev, "Can't access ISC reg: %d\n", + ret); + } + + bdi->ss_reg = ss_reg; + alert_userspace = true; + } + + mutex_lock(&bdi->f_reg_lock); + + ret = bq24190_read(bdi, BQ24190_REG_F, &f_reg); + if (ret < 0) { + mutex_unlock(&bdi->f_reg_lock); + dev_err(bdi->dev, "Can't read F reg: %d\n", ret); + goto out; + } + + if (f_reg != bdi->f_reg) { + bdi->f_reg = f_reg; + bdi->charger_health_valid = true; + bdi->battery_health_valid = true; + bdi->battery_status_valid = true; + + alert_userspace = true; + } + + mutex_unlock(&bdi->f_reg_lock); + + /* + * Sometimes bq24190 gives a steady trickle of interrupts even + * though the watchdog timer is turned off and neither the STATUS + * nor FAULT registers have changed. Weed out these sprurious + * interrupts so userspace isn't alerted for no reason. + * In addition, the chip always generates an interrupt after + * register reset so we should ignore that one (the very first + * interrupt received). + */ + if (alert_userspace && !bdi->first_time) { + power_supply_changed(&bdi->charger); + power_supply_changed(&bdi->battery); + bdi->first_time = false; + } + +out: + pm_runtime_put_sync(bdi->dev); + + dev_dbg(bdi->dev, "ss_reg: 0x%02x, f_reg: 0x%02x\n", ss_reg, f_reg); + + return IRQ_HANDLED; +} + +static int bq24190_hw_init(struct bq24190_dev_info *bdi) +{ + u8 v; + int ret; + + pm_runtime_get_sync(bdi->dev); + + /* First check that the device really is what its supposed to be */ + ret = bq24190_read_mask(bdi, BQ24190_REG_VPRS, + BQ24190_REG_VPRS_PN_MASK, + BQ24190_REG_VPRS_PN_SHIFT, + &v); + if (ret < 0) + goto out; + + if (v != bdi->model) { + ret = -ENODEV; + goto out; + } + + ret = bq24190_register_reset(bdi); + if (ret < 0) + goto out; + + ret = bq24190_set_mode_host(bdi); +out: + pm_runtime_put_sync(bdi->dev); + return ret; +} + +#ifdef CONFIG_OF +static int bq24190_setup_dt(struct bq24190_dev_info *bdi) +{ + bdi->irq = irq_of_parse_and_map(bdi->dev->of_node, 0); + if (bdi->irq <= 0) + return -1; + + return 0; +} +#else +static int bq24190_setup_dt(struct bq24190_dev_info *bdi) +{ + return -1; +} +#endif + +static int bq24190_setup_pdata(struct bq24190_dev_info *bdi, + struct bq24190_platform_data *pdata) +{ + int ret; + + if (!gpio_is_valid(pdata->gpio_int)) + return -1; + + ret = gpio_request(pdata->gpio_int, dev_name(bdi->dev)); + if (ret < 0) + return -1; + + ret = gpio_direction_input(pdata->gpio_int); + if (ret < 0) + goto out; + + bdi->irq = gpio_to_irq(pdata->gpio_int); + if (!bdi->irq) + goto out; + + bdi->gpio_int = pdata->gpio_int; + return 0; + +out: + gpio_free(pdata->gpio_int); + return -1; +} + +static int bq24190_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); + struct device *dev = &client->dev; + struct bq24190_platform_data *pdata = client->dev.platform_data; + struct bq24190_dev_info *bdi; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) { + dev_err(dev, "No support for SMBUS_BYTE_DATA\n"); + return -ENODEV; + } + + bdi = devm_kzalloc(dev, sizeof(*bdi), GFP_KERNEL); + if (!bdi) { + dev_err(dev, "Can't alloc bdi struct\n"); + return -ENOMEM; + } + + bdi->client = client; + bdi->dev = dev; + bdi->model = id->driver_data; + strncpy(bdi->model_name, id->name, I2C_NAME_SIZE); + mutex_init(&bdi->f_reg_lock); + bdi->first_time = true; + bdi->charger_health_valid = false; + bdi->battery_health_valid = false; + bdi->battery_status_valid = false; + + i2c_set_clientdata(client, bdi); + + if (dev->of_node) + ret = bq24190_setup_dt(bdi); + else + ret = bq24190_setup_pdata(bdi, pdata); + + if (ret) { + dev_err(dev, "Can't get irq info\n"); + return -EINVAL; + } + + ret = devm_request_threaded_irq(dev, bdi->irq, NULL, + bq24190_irq_handler_thread, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "bq24190-charger", bdi); + if (ret < 0) { + dev_err(dev, "Can't set up irq handler\n"); + goto out1; + } + + pm_runtime_enable(dev); + pm_runtime_resume(dev); + + ret = bq24190_hw_init(bdi); + if (ret < 0) { + dev_err(dev, "Hardware init failed\n"); + goto out2; + } + + bq24190_charger_init(&bdi->charger); + + ret = power_supply_register(dev, &bdi->charger); + if (ret) { + dev_err(dev, "Can't register charger\n"); + goto out2; + } + + bq24190_battery_init(&bdi->battery); + + ret = power_supply_register(dev, &bdi->battery); + if (ret) { + dev_err(dev, "Can't register battery\n"); + goto out3; + } + + ret = bq24190_sysfs_create_group(bdi); + if (ret) { + dev_err(dev, "Can't create sysfs entries\n"); + goto out4; + } + + return 0; + +out4: + power_supply_unregister(&bdi->battery); +out3: + power_supply_unregister(&bdi->charger); +out2: + pm_runtime_disable(dev); +out1: + if (bdi->gpio_int) + gpio_free(bdi->gpio_int); + + return ret; +} + +static int bq24190_remove(struct i2c_client *client) +{ + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + bq24190_sysfs_remove_group(bdi); + power_supply_unregister(&bdi->battery); + power_supply_unregister(&bdi->charger); + pm_runtime_disable(bdi->dev); + + if (bdi->gpio_int) + gpio_free(bdi->gpio_int); + + return 0; +} + +#ifdef CONFIG_PM_SLEEP +static int bq24190_pm_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + return 0; +} + +static int bq24190_pm_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct bq24190_dev_info *bdi = i2c_get_clientdata(client); + + bdi->charger_health_valid = false; + bdi->battery_health_valid = false; + bdi->battery_status_valid = false; + + pm_runtime_get_sync(bdi->dev); + bq24190_register_reset(bdi); + pm_runtime_put_sync(bdi->dev); + + /* Things may have changed while suspended so alert upper layer */ + power_supply_changed(&bdi->charger); + power_supply_changed(&bdi->battery); + + return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(bq24190_pm_ops, bq24190_pm_suspend, bq24190_pm_resume); + +/* + * Only support the bq24190 right now. The bq24192, bq24192i, and bq24193 + * are similar but not identical so the driver needs to be extended to + * support them. + */ +static const struct i2c_device_id bq24190_i2c_ids[] = { + { "bq24190", BQ24190_REG_VPRS_PN_24190 }, + { }, +}; + +#ifdef CONFIG_OF +static const struct of_device_id bq24190_of_match[] = { + { .compatible = "ti,bq24190", }, + { }, +}; +MODULE_DEVICE_TABLE(of, bq24190_of_match); +#else +static const struct of_device_id bq24190_of_match[] = { + { }, +}; +#endif + +static struct i2c_driver bq24190_driver = { + .probe = bq24190_probe, + .remove = bq24190_remove, + .id_table = bq24190_i2c_ids, + .driver = { + .name = "bq24190-charger", + .owner = THIS_MODULE, + .pm = &bq24190_pm_ops, + .of_match_table = of_match_ptr(bq24190_of_match), + }, +}; +module_i2c_driver(bq24190_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Mark A. Greer <mgreer@animalcreek.com>"); +MODULE_ALIAS("i2c:bq24190-charger"); +MODULE_DESCRIPTION("TI BQ24190 Charger Driver"); diff --git a/drivers/power/bq24735-charger.c b/drivers/power/bq24735-charger.c new file mode 100644 index 00000000000..d022b823305 --- /dev/null +++ b/drivers/power/bq24735-charger.c @@ -0,0 +1,419 @@ +/* + * Battery charger driver for TI BQ24735 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#include <linux/power/bq24735-charger.h> + +#define BQ24735_CHG_OPT 0x12 +#define BQ24735_CHG_OPT_CHARGE_DISABLE (1 << 0) +#define BQ24735_CHG_OPT_AC_PRESENT (1 << 4) +#define BQ24735_CHARGE_CURRENT 0x14 +#define BQ24735_CHARGE_CURRENT_MASK 0x1fc0 +#define BQ24735_CHARGE_VOLTAGE 0x15 +#define BQ24735_CHARGE_VOLTAGE_MASK 0x7ff0 +#define BQ24735_INPUT_CURRENT 0x3f +#define BQ24735_INPUT_CURRENT_MASK 0x1f80 +#define BQ24735_MANUFACTURER_ID 0xfe +#define BQ24735_DEVICE_ID 0xff + +struct bq24735 { + struct power_supply charger; + struct i2c_client *client; + struct bq24735_platform *pdata; +}; + +static inline struct bq24735 *to_bq24735(struct power_supply *psy) +{ + return container_of(psy, struct bq24735, charger); +} + +static enum power_supply_property bq24735_charger_properties[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +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) +{ + 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) +{ + 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->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 irqreturn_t bq24735_charger_isr(int irq, void *devid) +{ + struct power_supply *psy = devid; + struct bq24735 *charger = to_bq24735(psy); + + if (bq24735_charger_is_present(charger)) + bq24735_enable_charging(charger); + else + bq24735_disable_charging(charger); + + 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; + + charger = container_of(psy, struct bq24735, charger); + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = bq24735_charger_is_present(charger) ? 1 : 0; + break; + default: + return -EINVAL; + } + + 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; + + 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 *supply; + char *name; + + charger = devm_kzalloc(&client->dev, sizeof(*charger), GFP_KERNEL); + if (!charger) + return -ENOMEM; + + 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 = kasprintf(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 = &charger->charger; + + supply->name = name; + supply->type = POWER_SUPPLY_TYPE_MAINS; + supply->properties = bq24735_charger_properties; + supply->num_properties = ARRAY_SIZE(bq24735_charger_properties); + supply->get_property = bq24735_charger_get_property; + supply->supplied_to = charger->pdata->supplied_to; + supply->num_supplicants = charger->pdata->num_supplicants; + supply->of_node = client->dev.of_node; + + i2c_set_clientdata(client, charger); + + ret = bq24735_read_word(client, BQ24735_MANUFACTURER_ID); + if (ret < 0) { + dev_err(&client->dev, "Failed to read manufacturer id : %d\n", + ret); + goto err_free_name; + } else if (ret != 0x0040) { + dev_err(&client->dev, + "manufacturer id mismatch. 0x0040 != 0x%04x\n", ret); + ret = -ENODEV; + goto err_free_name; + } + + ret = bq24735_read_word(client, BQ24735_DEVICE_ID); + if (ret < 0) { + dev_err(&client->dev, "Failed to read device id : %d\n", ret); + goto err_free_name; + } else if (ret != 0x000B) { + dev_err(&client->dev, + "device id mismatch. 0x000b != 0x%04x\n", ret); + ret = -ENODEV; + goto err_free_name; + } + + 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; + } + + ret = bq24735_config_charger(charger); + if (ret < 0) { + dev_err(&client->dev, "failed in configuring charger"); + goto err_free_name; + } + + /* 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"); + goto err_free_name; + } + } + + ret = power_supply_register(&client->dev, supply); + if (ret < 0) { + dev_err(&client->dev, "Failed to register power supply: %d\n", + ret); + goto err_free_name; + } + + 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->name, supply); + if (ret) { + dev_err(&client->dev, + "Unable to register IRQ %d err %d\n", + client->irq, ret); + goto err_unregister_supply; + } + } + + return 0; +err_unregister_supply: + power_supply_unregister(supply); +err_free_name: + if (name != charger->pdata->name) + kfree(name); + + return ret; +} + +static int bq24735_charger_remove(struct i2c_client *client) +{ + struct bq24735 *charger = i2c_get_clientdata(client); + + if (charger->client->irq) + devm_free_irq(&charger->client->dev, charger->client->irq, + &charger->charger); + + power_supply_unregister(&charger->charger); + + if (charger->charger.name != charger->pdata->name) + kfree(charger->charger.name); + + 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", + .owner = THIS_MODULE, + .of_match_table = bq24735_match_ids, + }, + .probe = bq24735_charger_probe, + .remove = bq24735_charger_remove, + .id_table = bq24735_charger_id, +}; + +module_i2c_driver(bq24735_charger_driver); + +MODULE_DESCRIPTION("bq24735 battery charging driver"); +MODULE_AUTHOR("Darbha Sriharsha <dsriharsha@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/bq27x00_battery.c b/drivers/power/bq27x00_battery.c index 1ed6ea0bad6..b309713b63b 100644 --- a/drivers/power/bq27x00_battery.c +++ b/drivers/power/bq27x00_battery.c @@ -22,6 +22,7 @@ * Datasheets: * http://focus.ti.com/docs/prod/folders/print/bq27000.html * http://focus.ti.com/docs/prod/folders/print/bq27500.html + * http://www.ti.com/product/bq27425-g1 */ #include <linux/module.h> @@ -47,10 +48,11 @@ #define BQ27x00_REG_TTE 0x16 #define BQ27x00_REG_TTF 0x18 #define BQ27x00_REG_TTECP 0x26 -#define BQ27x00_REG_NAC 0x0C /* Nominal available capaciy */ +#define BQ27x00_REG_NAC 0x0C /* Nominal available capacity */ #define BQ27x00_REG_LMD 0x12 /* Last measured discharge */ #define BQ27x00_REG_CYCT 0x2A /* Cycle count total */ -#define BQ27x00_REG_AE 0x22 /* Available enery */ +#define BQ27x00_REG_AE 0x22 /* Available energy */ +#define BQ27x00_POWER_AVG 0x24 #define BQ27000_REG_RSOC 0x0B /* Relative State-of-Charge */ #define BQ27000_REG_ILMD 0x76 /* Initial last measured discharge */ @@ -66,15 +68,21 @@ #define BQ27500_FLAG_SOCF BIT(1) /* State-of-Charge threshold final */ #define BQ27500_FLAG_SOC1 BIT(2) /* State-of-Charge threshold 1 */ #define BQ27500_FLAG_FC BIT(9) +#define BQ27500_FLAG_OTC BIT(15) + +/* bq27425 register addresses are same as bq27x00 addresses minus 4 */ +#define BQ27425_REG_OFFSET 0x04 +#define BQ27425_REG_SOC 0x18 /* Register address plus offset */ #define BQ27000_RS 20 /* Resistor sense */ +#define BQ27x00_POWER_CONSTANT (256 * 29200 / 1000) struct bq27x00_device_info; struct bq27x00_access_methods { int (*read)(struct bq27x00_device_info *di, u8 reg, bool single); }; -enum bq27x00_chip { BQ27000, BQ27500 }; +enum bq27x00_chip { BQ27000, BQ27500, BQ27425}; struct bq27x00_reg_cache { int temperature; @@ -86,6 +94,8 @@ struct bq27x00_reg_cache { int capacity; int energy; int flags; + int power_avg; + int health; }; struct bq27x00_device_info { @@ -123,6 +133,22 @@ static enum power_supply_property bq27x00_battery_props[] = { 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, +}; + +static enum power_supply_property bq27425_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, }; static unsigned int poll_interval = 360; @@ -137,10 +163,24 @@ MODULE_PARM_DESC(poll_interval, "battery poll interval in seconds - " \ static inline int bq27x00_read(struct bq27x00_device_info *di, u8 reg, bool single) { + if (di->chip == BQ27425) + return di->bus.read(di, reg - BQ27425_REG_OFFSET, single); return di->bus.read(di, reg, single); } /* + * Higher versions of the chip like BQ27425 and BQ27500 + * differ from BQ27000 and BQ27200 in calculation of certain + * parameters. Hence we need to check for the chip type. + */ +static bool bq27xxx_is_chip_version_higher(struct bq27x00_device_info *di) +{ + if (di->chip == BQ27425 || di->chip == BQ27500) + return true; + return false; +} + +/* * Return the battery Relative State-of-Charge * Or < 0 if something fails. */ @@ -150,6 +190,8 @@ static int bq27x00_battery_read_rsoc(struct bq27x00_device_info *di) if (di->chip == BQ27500) rsoc = bq27x00_read(di, BQ27500_REG_SOC, false); + else if (di->chip == BQ27425) + rsoc = bq27x00_read(di, BQ27425_REG_SOC, false); else rsoc = bq27x00_read(di, BQ27000_REG_RSOC, true); @@ -174,7 +216,7 @@ static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg) return charge; } - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) charge *= 1000; else charge = charge * 3570 / BQ27000_RS; @@ -188,6 +230,14 @@ static int bq27x00_battery_read_charge(struct bq27x00_device_info *di, u8 reg) */ static inline int bq27x00_battery_read_nac(struct bq27x00_device_info *di) { + int flags; + bool is_bq27500 = di->chip == BQ27500; + bool is_higher = bq27xxx_is_chip_version_higher(di); + + flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); + if (flags >= 0 && !is_higher && (flags & BQ27000_FLAG_CI)) + return -ENODATA; + return bq27x00_battery_read_charge(di, BQ27x00_REG_NAC); } @@ -208,7 +258,7 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) { int ilmd; - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) ilmd = bq27x00_read(di, BQ27500_REG_DCAP, false); else ilmd = bq27x00_read(di, BQ27000_REG_ILMD, true); @@ -218,7 +268,7 @@ static int bq27x00_battery_read_ilmd(struct bq27x00_device_info *di) return ilmd; } - if (di->chip == BQ27500) + if (bq27xxx_is_chip_version_higher(di)) ilmd *= 1000; else ilmd = ilmd * 256 * 3570 / BQ27000_RS; @@ -249,7 +299,7 @@ static int bq27x00_battery_read_energy(struct bq27x00_device_info *di) } /* - * Return the battery temperature in tenths of degree Celsius + * Return the battery temperature in tenths of degree Kelvin * Or < 0 if something fails. */ static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) @@ -262,10 +312,8 @@ static int bq27x00_battery_read_temperature(struct bq27x00_device_info *di) return temp; } - if (di->chip == BQ27500) - temp -= 2731; - else - temp = ((temp * 5) - 5463) / 2; + if (!bq27xxx_is_chip_version_higher(di)) + temp = 5 * temp / 2; return temp; } @@ -306,14 +354,70 @@ static int bq27x00_battery_read_time(struct bq27x00_device_info *di, u8 reg) return tval * 60; } +/* + * Read a power avg register. + * Return < 0 if something fails. + */ +static int bq27x00_battery_read_pwr_avg(struct bq27x00_device_info *di, u8 reg) +{ + int tval; + + tval = bq27x00_read(di, reg, false); + if (tval < 0) { + dev_err(di->dev, "error reading power avg rgister %02x: %d\n", + reg, tval); + return tval; + } + + if (di->chip == BQ27500) + return tval; + else + return (tval * BQ27x00_POWER_CONSTANT) / BQ27000_RS; +} + +/* + * Read flag register. + * Return < 0 if something fails. + */ +static int bq27x00_battery_read_health(struct bq27x00_device_info *di) +{ + int tval; + + tval = bq27x00_read(di, BQ27x00_REG_FLAGS, false); + if (tval < 0) { + dev_err(di->dev, "error reading flag register:%d\n", tval); + return tval; + } + + if ((di->chip == BQ27500)) { + if (tval & BQ27500_FLAG_SOCF) + tval = POWER_SUPPLY_HEALTH_DEAD; + else if (tval & BQ27500_FLAG_OTC) + tval = POWER_SUPPLY_HEALTH_OVERHEAT; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } else { + if (tval & BQ27000_FLAG_EDV1) + tval = POWER_SUPPLY_HEALTH_DEAD; + else + tval = POWER_SUPPLY_HEALTH_GOOD; + return tval; + } + + return -1; +} + static void bq27x00_update(struct bq27x00_device_info *di) { struct bq27x00_reg_cache cache = {0, }; bool is_bq27500 = di->chip == BQ27500; + bool is_bq27425 = di->chip == BQ27425; cache.flags = bq27x00_read(di, BQ27x00_REG_FLAGS, !is_bq27500); if (cache.flags >= 0) { - if (!is_bq27500 && (cache.flags & BQ27000_FLAG_CI)) { + if (!is_bq27500 && !is_bq27425 + && (cache.flags & BQ27000_FLAG_CI)) { dev_info(di->dev, "battery is not calibrated! ignoring capacity values\n"); cache.capacity = -ENODATA; cache.energy = -ENODATA; @@ -321,16 +425,29 @@ static void bq27x00_update(struct bq27x00_device_info *di) cache.time_to_empty_avg = -ENODATA; cache.time_to_full = -ENODATA; cache.charge_full = -ENODATA; + cache.health = -ENODATA; } else { cache.capacity = bq27x00_battery_read_rsoc(di); - cache.energy = bq27x00_battery_read_energy(di); - cache.time_to_empty = bq27x00_battery_read_time(di, BQ27x00_REG_TTE); - cache.time_to_empty_avg = bq27x00_battery_read_time(di, BQ27x00_REG_TTECP); - cache.time_to_full = bq27x00_battery_read_time(di, BQ27x00_REG_TTF); + if (!is_bq27425) { + cache.energy = bq27x00_battery_read_energy(di); + cache.time_to_empty = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTE); + cache.time_to_empty_avg = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTECP); + cache.time_to_full = + bq27x00_battery_read_time(di, + BQ27x00_REG_TTF); + } cache.charge_full = bq27x00_battery_read_lmd(di); + cache.health = bq27x00_battery_read_health(di); } cache.temperature = bq27x00_battery_read_temperature(di); - cache.cycle_count = bq27x00_battery_read_cyct(di); + if (!is_bq27425) + cache.cycle_count = bq27x00_battery_read_cyct(di); + cache.power_avg = + bq27x00_battery_read_pwr_avg(di, BQ27x00_POWER_AVG); /* We only have to read charge design full once */ if (di->charge_design_full <= 0) @@ -376,7 +493,7 @@ static int bq27x00_battery_current(struct bq27x00_device_info *di, return curr; } - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { /* bq27500 returns signed value */ val->intval = (int)((s16)curr) * 1000; } else { @@ -397,7 +514,7 @@ static int bq27x00_battery_status(struct bq27x00_device_info *di, { int status; - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { if (di->cache.flags & BQ27500_FLAG_FC) status = POWER_SUPPLY_STATUS_FULL; else if (di->cache.flags & BQ27500_FLAG_DSC) @@ -425,7 +542,7 @@ static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, { int level; - if (di->chip == BQ27500) { + if (bq27xxx_is_chip_version_higher(di)) { if (di->cache.flags & BQ27500_FLAG_FC) level = POWER_SUPPLY_CAPACITY_LEVEL_FULL; else if (di->cache.flags & BQ27500_FLAG_SOC1) @@ -451,7 +568,7 @@ static int bq27x00_battery_capacity_level(struct bq27x00_device_info *di, } /* - * Return the battery Voltage in milivolts + * Return the battery Voltage in millivolts * Or < 0 if something fails. */ static int bq27x00_battery_voltage(struct bq27x00_device_info *di, @@ -522,6 +639,8 @@ static int bq27x00_battery_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_TEMP: ret = bq27x00_simple_value(di->cache.temperature, val); + if (ret == 0) + val->intval -= 2731; break; case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: ret = bq27x00_simple_value(di->cache.time_to_empty, val); @@ -550,6 +669,12 @@ static int bq27x00_battery_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_ENERGY_NOW: ret = bq27x00_simple_value(di->cache.energy, val); break; + case POWER_SUPPLY_PROP_POWER_AVG: + ret = bq27x00_simple_value(di->cache.power_avg, val); + break; + case POWER_SUPPLY_PROP_HEALTH: + ret = bq27x00_simple_value(di->cache.health, val); + break; default: return -EINVAL; } @@ -570,8 +695,13 @@ static int bq27x00_powersupply_init(struct bq27x00_device_info *di) int ret; di->bat.type = POWER_SUPPLY_TYPE_BATTERY; - di->bat.properties = bq27x00_battery_props; - di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); + if (di->chip == BQ27425) { + di->bat.properties = bq27425_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27425_battery_props); + } else { + di->bat.properties = bq27x00_battery_props; + di->bat.num_properties = ARRAY_SIZE(bq27x00_battery_props); + } di->bat.get_property = bq27x00_battery_get_property; di->bat.external_power_changed = bq27x00_external_power_changed; @@ -661,14 +791,11 @@ static int bq27x00_battery_probe(struct i2c_client *client, int retval = 0; /* Get new ID for the new battery device */ - retval = idr_pre_get(&battery_id, GFP_KERNEL); - if (retval == 0) - return -ENOMEM; mutex_lock(&battery_mutex); - retval = idr_get_new(&battery_id, client, &num); + num = idr_alloc(&battery_id, client, 0, 0, GFP_KERNEL); mutex_unlock(&battery_mutex); - if (retval < 0) - return retval; + if (num < 0) + return num; name = kasprintf(GFP_KERNEL, "%s-%d", id->name, num); if (!name) { @@ -690,7 +817,8 @@ static int bq27x00_battery_probe(struct i2c_client *client, di->bat.name = name; di->bus.read = &bq27x00_read_i2c; - if (bq27x00_powersupply_init(di)) + retval = bq27x00_powersupply_init(di); + if (retval) goto batt_failed_3; i2c_set_clientdata(client, di); @@ -729,6 +857,7 @@ static int bq27x00_battery_remove(struct i2c_client *client) static const struct i2c_device_id bq27x00_id[] = { { "bq27200", BQ27000 }, /* bq27200 is same as bq27000, but with i2c */ { "bq27500", BQ27500 }, + { "bq27425", BQ27425 }, {}, }; MODULE_DEVICE_TABLE(i2c, bq27x00_id); @@ -800,7 +929,7 @@ static int bq27000_read_platform(struct bq27x00_device_info *di, u8 reg, return pdata->read(dev, reg); } -static int __devinit bq27000_battery_probe(struct platform_device *pdev) +static int bq27000_battery_probe(struct platform_device *pdev) { struct bq27x00_device_info *di; struct bq27000_platform_data *pdata = pdev->dev.platform_data; @@ -837,19 +966,17 @@ static int __devinit bq27000_battery_probe(struct platform_device *pdev) return 0; err_free: - platform_set_drvdata(pdev, NULL); kfree(di); return ret; } -static int __devexit bq27000_battery_remove(struct platform_device *pdev) +static int bq27000_battery_remove(struct platform_device *pdev) { struct bq27x00_device_info *di = platform_get_drvdata(pdev); bq27x00_powersupply_unregister(di); - platform_set_drvdata(pdev, NULL); kfree(di); return 0; @@ -857,7 +984,7 @@ static int __devexit bq27000_battery_remove(struct platform_device *pdev) static struct platform_driver bq27000_battery_driver = { .probe = bq27000_battery_probe, - .remove = __devexit_p(bq27000_battery_remove), + .remove = bq27000_battery_remove, .driver = { .name = "bq27000-battery", .owner = THIS_MODULE, diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 88fd9710bda..9e4dab46eef 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -12,6 +12,8 @@ * published by the Free Software Foundation. **/ +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + #include <linux/io.h> #include <linux/module.h> #include <linux/irq.h> @@ -22,6 +24,28 @@ #include <linux/platform_device.h> #include <linux/power/charger-manager.h> #include <linux/regulator/consumer.h> +#include <linux/sysfs.h> +#include <linux/of.h> +#include <linux/thermal.h> + +/* + * Default termperature threshold for charging. + * Every temperature units are in tenth of centigrade. + */ +#define CM_DEFAULT_RECHARGE_TEMP_DIFF 50 +#define CM_DEFAULT_CHARGE_TEMP_MAX 500 + +static const char * const default_event_names[] = { + [CM_EVENT_UNKNOWN] = "Unknown", + [CM_EVENT_BATT_FULL] = "Battery Full", + [CM_EVENT_BATT_IN] = "Battery Inserted", + [CM_EVENT_BATT_OUT] = "Battery Pulled Out", + [CM_EVENT_BATT_OVERHEAT] = "Battery Overheat", + [CM_EVENT_BATT_COLD] = "Battery Cold", + [CM_EVENT_EXT_PWR_IN_OUT] = "External Power Attach/Detach", + [CM_EVENT_CHG_START_STOP] = "Charging Start/Stop", + [CM_EVENT_OTHERS] = "Other battery events" +}; /* * Regard CM_JIFFIES_SMALL jiffies is small enough to ignore for @@ -57,6 +81,12 @@ static bool cm_suspended; static bool cm_rtc_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 */ + /* Global charger-manager description */ static struct charger_global_desc *g_desc; /* init with setup_charger_manager */ @@ -71,6 +101,11 @@ static bool is_batt_present(struct charger_manager *cm) int i, ret; switch (cm->desc->battery_present) { + case CM_BATTERY_PRESENT: + present = true; + break; + case CM_NO_BATTERY: + break; case CM_FUEL_GAUGE: ret = cm->fuel_gauge->get_property(cm->fuel_gauge, POWER_SUPPLY_PROP_PRESENT, &val); @@ -134,12 +169,11 @@ static int get_batt_uV(struct charger_manager *cm, int *uV) union power_supply_propval val; int ret; - if (cm->fuel_gauge) - ret = cm->fuel_gauge->get_property(cm->fuel_gauge, - POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); - else + if (!cm->fuel_gauge) return -ENODEV; + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &val); if (ret) return ret; @@ -174,8 +208,8 @@ static bool is_charging(struct charger_manager *cm) cm->charger_stat[i], POWER_SUPPLY_PROP_ONLINE, &val); if (ret) { - dev_warn(cm->dev, "Cannot read ONLINE value from %s.\n", - cm->desc->psy_charger_stat[i]); + dev_warn(cm->dev, "Cannot read ONLINE value from %s\n", + cm->desc->psy_charger_stat[i]); continue; } if (val.intval == 0) @@ -189,8 +223,8 @@ static bool is_charging(struct charger_manager *cm) cm->charger_stat[i], POWER_SUPPLY_PROP_STATUS, &val); if (ret) { - dev_warn(cm->dev, "Cannot read STATUS value from %s.\n", - cm->desc->psy_charger_stat[i]); + 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 || @@ -207,6 +241,51 @@ static bool is_charging(struct charger_manager *cm) } /** + * 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; + int ret = 0; + int uV; + + /* If there is no battery, it cannot be charged */ + if (!is_batt_present(cm)) + return false; + + if (cm->fuel_gauge && desc->fullbatt_full_capacity > 0) { + val.intval = 0; + + /* Not full if capacity of fuel gauge isn't full */ + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CHARGE_FULL, &val); + if (!ret && val.intval > desc->fullbatt_full_capacity) + return true; + } + + /* 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) + return true; + } + + /* Full, if the capacity is more than fullbatt_soc */ + if (cm->fuel_gauge && desc->fullbatt_soc > 0) { + val.intval = 0; + + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_CAPACITY, &val); + if (!ret && val.intval >= desc->fullbatt_soc) + return true; + } + + return false; +} + +/** * is_polling_required - Return true if need to continue polling for this CM. * @cm: the Charger Manager representing the battery. */ @@ -223,7 +302,7 @@ static bool is_polling_required(struct charger_manager *cm) return is_charging(cm); default: dev_warn(cm->dev, "Incorrect polling_mode (%d)\n", - cm->desc->polling_mode); + cm->desc->polling_mode); } return false; @@ -245,32 +324,60 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) struct charger_desc *desc = cm->desc; /* Ignore if it's redundent command */ - if (enable && cm->charger_enabled) - return 0; - if (!enable && !cm->charger_enabled) + if (enable == cm->charger_enabled) return 0; if (enable) { if (cm->emergency_stop) return -EAGAIN; - err = regulator_bulk_enable(desc->num_charger_regulators, - desc->charger_regulators); + + /* + * 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 */ - err = regulator_bulk_disable(desc->num_charger_regulators, - desc->charger_regulators); - 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].supply); + dev_warn(cm->dev, "Disable regulator(%s) forcibly\n", + desc->charger_regulators[i].regulator_name); } } } @@ -282,6 +389,26 @@ static int try_charger_enable(struct charger_manager *cm, bool enable) } /** + * 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. @@ -309,9 +436,7 @@ static void uevent_notify(struct charger_manager *cm, const char *event) if (!strncmp(env_str_save, event, UEVENT_BUF_SIZE)) return; /* Duplicated. */ - else - strncpy(env_str_save, event, UEVENT_BUF_SIZE); - + strncpy(env_str_save, event, UEVENT_BUF_SIZE); return; } @@ -335,7 +460,149 @@ static void uevent_notify(struct charger_manager *cm, const char *event) strncpy(env_str, event, UEVENT_BUF_SIZE); kobject_uevent(&cm->dev->kobj, KOBJ_CHANGE); - dev_info(cm->dev, event); + 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(struct charger_manager *cm, + int *temp) +{ + int ret; + + if (!cm->desc->measure_battery_temp) + return -ENODEV; + +#ifdef CONFIG_THERMAL + ret = thermal_zone_get_temp(cm->tzd_batt, (unsigned long *)temp); + if (!ret) + /* Calibrate temperature unit */ + *temp /= 100; +#else + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, + (union power_supply_propval *)temp); +#endif + 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; } /** @@ -347,28 +614,57 @@ static void uevent_notify(struct charger_manager *cm, const char *event) */ static bool _cm_monitor(struct charger_manager *cm) { - struct charger_desc *desc = cm->desc; - int temp = desc->temperature_out_of_range(&cm->last_temp_mC); + int temp_alrt; - dev_dbg(cm->dev, "monitoring (%2.2d.%3.3dC)\n", - cm->last_temp_mC / 1000, cm->last_temp_mC % 1000); + temp_alrt = cm_check_thermal_status(cm); - /* It has been stopped or charging already */ - if (!!temp == !!cm->emergency_stop) + /* It has been stopped already */ + if (temp_alrt && cm->emergency_stop) return false; - if (temp) { - cm->emergency_stop = temp; - if (!try_charger_enable(cm, false)) { - if (temp > 0) - uevent_notify(cm, "OVERHEAT"); - else - uevent_notify(cm, "COLD"); - } + /* + * 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 (!try_charger_enable(cm, true)) - uevent_notify(cm, "CHARGING"); + if (is_ext_pwr_online(cm)) { + if (!try_charger_enable(cm, true)) + uevent_notify(cm, "CHARGING"); + } } return true; @@ -387,14 +683,143 @@ static bool cm_monitor(void) mutex_lock(&cm_list_mtx); - list_for_each_entry(cm, &cm_list, entry) - stop = stop || _cm_monitor(cm); + 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) @@ -402,7 +827,8 @@ static int charger_get_property(struct power_supply *psy, struct charger_manager *cm = container_of(psy, struct charger_manager, charger_psy); struct charger_desc *desc = cm->desc; - int i, ret = 0, uV; + int ret = 0; + int uV; switch (psp) { case POWER_SUPPLY_PROP_STATUS: @@ -428,29 +854,15 @@ static int charger_get_property(struct power_supply *psy, val->intval = 0; break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = get_batt_uV(cm, &i); - val->intval = i; + ret = get_batt_uV(cm, &val->intval); break; case POWER_SUPPLY_PROP_CURRENT_NOW: ret = cm->fuel_gauge->get_property(cm->fuel_gauge, POWER_SUPPLY_PROP_CURRENT_NOW, val); break; case POWER_SUPPLY_PROP_TEMP: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (!desc->measure_battery_temp) - ret = -ENODEV; - break; case POWER_SUPPLY_PROP_TEMP_AMBIENT: - /* in thenth of centigrade */ - if (cm->last_temp_mC == INT_MIN) - desc->temperature_out_of_range(&cm->last_temp_mC); - val->intval = cm->last_temp_mC / 100; - if (desc->measure_battery_temp) - ret = -ENODEV; - break; + return cm_get_battery_temperature(cm, &val->intval); case POWER_SUPPLY_PROP_CAPACITY: if (!cm->fuel_gauge) { ret = -ENODEV; @@ -504,47 +916,10 @@ static int charger_get_property(struct power_supply *psy, val->intval = 0; break; case POWER_SUPPLY_PROP_CHARGE_FULL: - if (cm->fuel_gauge) { - if (cm->fuel_gauge->get_property(cm->fuel_gauge, - POWER_SUPPLY_PROP_CHARGE_FULL, val) == 0) - break; - } - - if (is_ext_pwr_online(cm)) { - /* Not full if it's charging. */ - if (is_charging(cm)) { - val->intval = 0; - break; - } - /* - * Full if it's powered but not charging andi - * not forced stop by emergency - */ - if (!cm->emergency_stop) { - val->intval = 1; - break; - } - } - - /* Full if it's over the fullbatt voltage */ - ret = get_batt_uV(cm, &uV); - if (!ret && desc->fullbatt_uV > 0 && uV >= desc->fullbatt_uV && - !is_charging(cm)) { + if (is_full_charged(cm)) val->intval = 1; - break; - } - - /* Full if the cap is 100 */ - if (cm->fuel_gauge) { - ret = cm->fuel_gauge->get_property(cm->fuel_gauge, - POWER_SUPPLY_PROP_CAPACITY, val); - if (!ret && val->intval >= 100 && !is_charging(cm)) { - val->intval = 1; - break; - } - } - - val->intval = 0; + else + val->intval = 0; ret = 0; break; case POWER_SUPPLY_PROP_CHARGE_NOW: @@ -616,6 +991,21 @@ static bool cm_setup_timer(void) 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; @@ -627,7 +1017,7 @@ static bool cm_setup_timer(void) mutex_unlock(&cm_list_mtx); if (wakeup_ms < UINT_MAX && wakeup_ms > 0) { - pr_info("Charger Manager wakeup timer: %u ms.\n", wakeup_ms); + pr_info("Charger Manager wakeup timer: %u ms\n", wakeup_ms); if (rtc_dev) { struct rtc_wkalrm tmp; unsigned long time, now; @@ -660,8 +1050,7 @@ static bool cm_setup_timer(void) ret = false; } - pr_info("Waking up after %lu secs.\n", - time - now); + pr_info("Waking up after %lu secs\n", time - now); rtc_time_to_tm(time, &tmp.time); rtc_set_alarm(rtc_dev, &tmp); @@ -675,6 +1064,23 @@ static bool cm_setup_timer(void) return false; } +static void _cm_fbchk_in_suspend(struct charger_manager *cm) +{ + unsigned long jiffy_now = jiffies; + + if (!cm->fullbatt_vchk_jiffies_at) + return; + + if (g_desc && g_desc->assume_timer_stops_in_suspend) + jiffy_now += msecs_to_jiffies(cm_suspend_duration_ms); + + /* Execute now if it's going to be executed not too long after */ + jiffy_now += CM_JIFFIES_SMALL; + + if (time_after_eq(jiffy_now, cm->fullbatt_vchk_jiffies_at)) + fullbatt_vchk(&cm->fullbatt_vchk_work.work); +} + /** * cm_suspend_again - Determine whether suspend again or not * @@ -696,9 +1102,13 @@ bool cm_suspend_again(void) ret = true; mutex_lock(&cm_list_mtx); list_for_each_entry(cm, &cm_list, entry) { + _cm_fbchk_in_suspend(cm); + if (cm->status_save_ext_pwr_inserted != is_ext_pwr_online(cm) || - cm->status_save_batt != is_batt_present(cm)) + cm->status_save_batt != is_batt_present(cm)) { ret = false; + break; + } } mutex_unlock(&cm_list_mtx); @@ -735,7 +1145,7 @@ int setup_charger_manager(struct charger_global_desc *gd) g_desc = NULL; if (!gd->rtc_only_wakeup) { - pr_err("The callback rtc_only_wakeup is not given.\n"); + pr_err("The callback rtc_only_wakeup is not given\n"); return -EINVAL; } @@ -746,7 +1156,7 @@ int setup_charger_manager(struct charger_global_desc *gd) /* Retry at probe. RTC may be not registered yet */ } } else { - pr_warn("No wakeup timer is given for charger manager." + pr_warn("No wakeup timer is given for charger manager. " "In-suspend monitoring won't work.\n"); } @@ -755,126 +1165,618 @@ int setup_charger_manager(struct charger_global_desc *gd) } EXPORT_SYMBOL_GPL(setup_charger_manager); +/** + * 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 charger_desc *desc = cm->desc; + union power_supply_propval val; + int ret; + + /* Verify whether fuel gauge provides battery temperature */ + ret = cm->fuel_gauge->get_property(cm->fuel_gauge, + POWER_SUPPLY_PROP_TEMP, &val); + + if (!ret) { + cm->charger_psy.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP; + cm->charger_psy.num_properties++; + cm->desc->measure_battery_temp = true; + } +#ifdef CONFIG_THERMAL + cm->tzd_batt = cm->fuel_gauge->tzd; + + 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.properties[cm->charger_psy.num_properties] = + POWER_SUPPLY_PROP_TEMP_AMBIENT; + cm->charger_psy.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 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) + 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 (struct charger_desc *)dev_get_platdata(&pdev->dev); +} + static int charger_manager_probe(struct platform_device *pdev) { - struct charger_desc *desc = dev_get_platdata(&pdev->dev); + 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; if (g_desc && !rtc_dev && g_desc->rtc_name) { rtc_dev = rtc_class_open(g_desc->rtc_name); if (IS_ERR_OR_NULL(rtc_dev)) { rtc_dev = NULL; - dev_err(&pdev->dev, "Cannot get RTC %s.\n", + dev_err(&pdev->dev, "Cannot get RTC %s\n", g_desc->rtc_name); - ret = -ENODEV; - goto err_alloc; + return -ENODEV; } } if (!desc) { - dev_err(&pdev->dev, "No platform data (desc) found.\n"); - ret = -ENODEV; - goto err_alloc; + dev_err(&pdev->dev, "No platform data (desc) found\n"); + return -ENODEV; } - cm = kzalloc(sizeof(struct charger_manager), GFP_KERNEL); - if (!cm) { - dev_err(&pdev->dev, "Cannot allocate memory.\n"); - ret = -ENOMEM; - goto err_alloc; - } + 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 = kzalloc(sizeof(struct charger_desc), GFP_KERNEL); - if (!cm->desc) { - dev_err(&pdev->dev, "Cannot allocate memory.\n"); - ret = -ENOMEM; - goto err_alloc_desc; + cm->desc = desc; + + /* + * 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"); } - memcpy(cm->desc, desc, sizeof(struct charger_desc)); - cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ if (!desc->charger_regulators || desc->num_charger_regulators < 1) { - ret = -EINVAL; - dev_err(&pdev->dev, "charger_regulators undefined.\n"); - goto err_no_charger; + 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"); - ret = -EINVAL; - goto err_no_charger_stat; + dev_err(&pdev->dev, "No power supply defined\n"); + return -EINVAL; } /* Counting index only */ while (desc->psy_charger_stat[i]) i++; - cm->charger_stat = kzalloc(sizeof(struct power_supply *) * (i + 1), - GFP_KERNEL); - if (!cm->charger_stat) { - ret = -ENOMEM; - goto err_no_charger_stat; - } + cm->charger_stat = devm_kzalloc(&pdev->dev, + sizeof(struct power_supply *) * i, GFP_KERNEL); + if (!cm->charger_stat) + return -ENOMEM; for (i = 0; desc->psy_charger_stat[i]; i++) { cm->charger_stat[i] = power_supply_get_by_name( desc->psy_charger_stat[i]); if (!cm->charger_stat[i]) { - dev_err(&pdev->dev, "Cannot find power supply " - "\"%s\"\n", - desc->psy_charger_stat[i]); - ret = -ENODEV; - goto err_chg_stat; + dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", + desc->psy_charger_stat[i]); + return -ENODEV; } } cm->fuel_gauge = power_supply_get_by_name(desc->psy_fuel_gauge); if (!cm->fuel_gauge) { dev_err(&pdev->dev, "Cannot find power supply \"%s\"\n", - desc->psy_fuel_gauge); - ret = -ENODEV; - goto err_chg_stat; + desc->psy_fuel_gauge); + return -ENODEV; } 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"); - ret = -EINVAL; - goto err_chg_stat; + return -EINVAL; } - if (!desc->temperature_out_of_range) { - dev_err(&pdev->dev, "there is no temperature_out_of_range\n"); - ret = -EINVAL; - goto err_chg_stat; + 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, &psy_default, - sizeof(psy_default)); - if (!desc->psy_name) { - strncpy(cm->psy_name_buf, psy_default.name, - PSY_NAME_MAX); - } else { + memcpy(&cm->charger_psy, &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.name = cm->psy_name_buf; /* Allocate for psy properties because they may vary */ - cm->charger_psy.properties = kzalloc(sizeof(enum power_supply_property) + cm->charger_psy.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.properties) { - dev_err(&pdev->dev, "Cannot allocate for psy properties.\n"); - ret = -ENOMEM; - goto err_chg_stat; - } + NUM_CHARGER_PSY_OPTIONAL), GFP_KERNEL); + if (!cm->charger_psy.properties) + return -ENOMEM; + memcpy(cm->charger_psy.properties, default_charger_props, sizeof(enum power_supply_property) * ARRAY_SIZE(default_charger_props)); @@ -894,35 +1796,35 @@ static int charger_manager_probe(struct platform_device *pdev) POWER_SUPPLY_PROP_CURRENT_NOW; cm->charger_psy.num_properties++; } - if (!desc->measure_battery_temp) { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP_AMBIENT; - cm->charger_psy.num_properties++; - } - if (desc->measure_battery_temp) { - cm->charger_psy.properties[cm->charger_psy.num_properties] = - POWER_SUPPLY_PROP_TEMP; - cm->charger_psy.num_properties++; + + ret = cm_init_thermal_data(cm); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize thermal data\n"); + cm->desc->measure_battery_temp = false; } + INIT_DELAYED_WORK(&cm->fullbatt_vchk_work, fullbatt_vchk); + ret = power_supply_register(NULL, &cm->charger_psy); if (ret) { - dev_err(&pdev->dev, "Cannot register charger-manager with" - " name \"%s\".\n", cm->charger_psy.name); - goto err_register; + dev_err(&pdev->dev, "Cannot register charger-manager with name \"%s\"\n", + cm->charger_psy.name); + return ret; } - ret = regulator_bulk_get(&pdev->dev, desc->num_charger_regulators, - desc->charger_regulators); - if (ret) { - dev_err(&pdev->dev, "Cannot get charger regulators.\n"); - goto err_bulk_get; + /* 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; } - ret = try_charger_enable(cm, true); - if (ret) { - dev_err(&pdev->dev, "Cannot enable charger regulators\n"); - goto err_chg_enable; + /* 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 */ @@ -930,46 +1832,75 @@ static int charger_manager_probe(struct platform_device *pdev) 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); + + schedule_work(&setup_polling); + return 0; -err_chg_enable: - if (desc->charger_regulators) - regulator_bulk_free(desc->num_charger_regulators, - desc->charger_regulators); -err_bulk_get: +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); -err_register: - kfree(cm->charger_psy.properties); -err_chg_stat: - kfree(cm->charger_stat); -err_no_charger_stat: -err_no_charger: - kfree(cm->desc); -err_alloc_desc: - kfree(cm); -err_alloc: + return ret; } -static int __devexit charger_manager_remove(struct platform_device *pdev) +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); - if (desc->charger_regulators) - regulator_bulk_free(desc->num_charger_regulators, - desc->charger_regulators); + 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); - kfree(cm->charger_psy.properties); - kfree(cm->charger_stat); - kfree(cm->desc); - kfree(cm); + + try_charger_enable(cm, false); return 0; } @@ -980,11 +1911,21 @@ static const struct platform_device_id charger_manager_id[] = { }; 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 int cm_suspend_prepare(struct device *dev) { - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_manager *cm = dev_get_drvdata(dev); if (!cm_suspended) { if (rtc_dev) { @@ -1007,6 +1948,7 @@ static int cm_suspend_prepare(struct device *dev) cm_suspended = true; } + cancel_delayed_work(&cm->fullbatt_vchk_work); cm->status_save_ext_pwr_inserted = is_ext_pwr_online(cm); cm->status_save_batt = is_batt_present(cm); @@ -1020,9 +1962,7 @@ static int cm_suspend_prepare(struct device *dev) static void cm_suspend_complete(struct device *dev) { - struct platform_device *pdev = container_of(dev, struct platform_device, - dev); - struct charger_manager *cm = platform_get_drvdata(pdev); + struct charger_manager *cm = dev_get_drvdata(dev); if (cm_suspended) { if (rtc_dev) { @@ -1036,11 +1976,40 @@ static void cm_suspend_complete(struct device *dev) cm_rtc_set = false; } + /* 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 if + * assume_timer_stops_in_suspend is active + */ + if (g_desc && g_desc->assume_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); uevent_notify(cm, NULL); } static const struct dev_pm_ops charger_manager_pm = { .prepare = cm_suspend_prepare, + .suspend_noirq = cm_suspend_noirq, .complete = cm_suspend_complete, }; @@ -1049,24 +2018,100 @@ static struct platform_driver charger_manager_driver = { .name = "charger-manager", .owner = THIS_MODULE, .pm = &charger_manager_pm, + .of_match_table = charger_manager_match, }, .probe = charger_manager_probe, - .remove = __devexit_p(charger_manager_remove), + .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); +/** + * find_power_supply - find the associated power_supply of charger + * @cm: the Charger Manager representing the battery + * @psy: pointer to instance of charger's power_supply + */ +static bool find_power_supply(struct charger_manager *cm, + struct power_supply *psy) +{ + int i; + bool found = false; + + for (i = 0; cm->charger_stat[i]; i++) { + if (psy == cm->charger_stat[i]) { + found = true; + break; + } + } + + return found; +} + +/** + * 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) { + found_power_supply = find_power_supply(cm, psy); + if (found_power_supply) + break; + } + mutex_unlock(&cm_list_mtx); + + if (!found_power_supply) + return; + + switch (type) { + case CM_EVENT_BATT_FULL: + fullbatt_handler(cm); + break; + case CM_EVENT_BATT_OUT: + battout_handler(cm); + break; + case CM_EVENT_BATT_IN: + case CM_EVENT_EXT_PWR_IN_OUT ... CM_EVENT_CHG_START_STOP: + misc_event_handler(cm, type); + break; + case CM_EVENT_UNKNOWN: + case CM_EVENT_OTHERS: + uevent_notify(cm, msg ? msg : default_event_names[type]); + break; + default: + dev_err(cm->dev, "%s: type not specified\n", __func__); + break; + } +} +EXPORT_SYMBOL_GPL(cm_notify_event); + MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_DESCRIPTION("Charger Manager"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/collie_battery.c b/drivers/power/collie_battery.c index 74c6b23aeab..d02ae02a759 100644 --- a/drivers/power/collie_battery.c +++ b/drivers/power/collie_battery.c @@ -287,10 +287,10 @@ static struct gpio collie_batt_gpios[] = { }; #ifdef CONFIG_PM -static int collie_bat_suspend(struct ucb1x00_dev *dev, pm_message_t state) +static int collie_bat_suspend(struct ucb1x00_dev *dev) { /* flush all pending status updates */ - flush_work_sync(&bat_work); + flush_work(&bat_work); return 0; } @@ -305,7 +305,7 @@ static int collie_bat_resume(struct ucb1x00_dev *dev) #define collie_bat_resume NULL #endif -static int __devinit collie_bat_probe(struct ucb1x00_dev *dev) +static int collie_bat_probe(struct ucb1x00_dev *dev) { int ret; @@ -349,7 +349,7 @@ err_psy_reg_main: return ret; } -static void __devexit collie_bat_remove(struct ucb1x00_dev *dev) +static void collie_bat_remove(struct ucb1x00_dev *dev) { free_irq(gpio_to_irq(COLLIE_GPIO_CO), &collie_bat_main); @@ -367,7 +367,7 @@ static void __devexit collie_bat_remove(struct ucb1x00_dev *dev) static struct ucb1x00_driver collie_bat_driver = { .add = collie_bat_probe, - .remove = __devexit_p(collie_bat_remove), + .remove = collie_bat_remove, .suspend = collie_bat_suspend, .resume = collie_bat_resume, }; diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c index 3fd3e95d2b8..ae6c41835ee 100644 --- a/drivers/power/da9030_battery.c +++ b/drivers/power/da9030_battery.c @@ -22,6 +22,7 @@ #include <linux/debugfs.h> #include <linux/seq_file.h> +#include <linux/notifier.h> #define DA9030_FAULT_LOG 0x0a #define DA9030_FAULT_LOG_OVER_TEMP (1 << 7) @@ -187,8 +188,8 @@ static const struct file_operations bat_debug_fops = { static struct dentry *da9030_bat_create_debugfs(struct da9030_charger *charger) { - charger->debug_file = debugfs_create_file("charger", 0666, 0, charger, - &bat_debug_fops); + charger->debug_file = debugfs_create_file("charger", 0666, NULL, + charger, &bat_debug_fops); return charger->debug_file; } @@ -504,7 +505,7 @@ static int da9030_battery_probe(struct platform_device *pdev) pdata->charge_millivolt > 4350) return -EINVAL; - charger = kzalloc(sizeof(*charger), GFP_KERNEL); + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); if (charger == NULL) return -ENOMEM; @@ -556,8 +557,6 @@ err_notifier: cancel_delayed_work(&charger->work); err_charger_init: - kfree(charger); - return ret; } @@ -574,8 +573,6 @@ static int da9030_battery_remove(struct platform_device *dev) da9030_set_charge(charger, 0); power_supply_unregister(&charger->psy); - kfree(charger); - return 0; } diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c index e8ea47a53de..f8f4c0f7c17 100644 --- a/drivers/power/da9052-battery.c +++ b/drivers/power/da9052-battery.c @@ -327,7 +327,7 @@ static int da9052_bat_interpolate(int vbat_lower, int vbat_upper, return tmp; } -unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) +static unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) { int i; @@ -337,7 +337,7 @@ unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) 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; i++) { + 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; @@ -345,6 +345,13 @@ unsigned char da9052_determine_vc_tbl_index(unsigned char adc_temp) && (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) @@ -433,8 +440,10 @@ static int da9052_bat_check_health(struct da9052_battery *bat, int *health) static irqreturn_t da9052_bat_irq(int irq, void *data) { struct da9052_battery *bat = data; + int virq; - irq -= bat->da9052->irq_base; + virq = regmap_irq_get_virq(bat->da9052->irq_data, irq); + irq -= virq; if (irq == DA9052_IRQ_CHGEND) bat->status = POWER_SUPPLY_STATUS_FULL; @@ -560,7 +569,7 @@ static struct power_supply template_battery = { .get_property = da9052_bat_get_property, }; -static const char *const da9052_bat_irqs[] = { +static char *da9052_bat_irqs[] = { "BATT TEMP", "DCIN DET", "DCIN REM", @@ -569,15 +578,24 @@ static const char *const da9052_bat_irqs[] = { "CHG END", }; -static s32 __devinit da9052_bat_probe(struct platform_device *pdev) +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; int ret; - int irq; int i; - bat = kzalloc(sizeof(struct da9052_battery), GFP_KERNEL); + bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery), + GFP_KERNEL); if (!bat) return -ENOMEM; @@ -595,15 +613,14 @@ static s32 __devinit da9052_bat_probe(struct platform_device *pdev) bat->psy.use_for_apm = 1; for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { - irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); - ret = request_threaded_irq(bat->da9052->irq_base + irq, - NULL, da9052_bat_irq, - IRQF_TRIGGER_LOW | IRQF_ONESHOT, - da9052_bat_irqs[i], bat); + 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: %d\n", - da9052_bat_irqs[i], irq, ret); + "DA9052 failed to request %s IRQ: %d\n", + da9052_bat_irqs[i], ret); goto err; } } @@ -612,26 +629,23 @@ static s32 __devinit da9052_bat_probe(struct platform_device *pdev) if (ret) goto err; + platform_set_drvdata(pdev, bat); return 0; err: - for (; i >= 0; i--) { - irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); - free_irq(bat->da9052->irq_base + irq, bat); - } - kfree(bat); + while (--i >= 0) + da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); + return ret; } -static int __devexit da9052_bat_remove(struct platform_device *pdev) +static int da9052_bat_remove(struct platform_device *pdev) { int i; - int irq; struct da9052_battery *bat = platform_get_drvdata(pdev); - for (i = 0; i < ARRAY_SIZE(da9052_bat_irqs); i++) { - irq = platform_get_irq_byname(pdev, da9052_bat_irqs[i]); - free_irq(bat->da9052->irq_base + irq, bat); - } + 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; @@ -639,24 +653,13 @@ static int __devexit da9052_bat_remove(struct platform_device *pdev) static struct platform_driver da9052_bat_driver = { .probe = da9052_bat_probe, - .remove = __devexit_p(da9052_bat_remove), + .remove = da9052_bat_remove, .driver = { .name = "da9052-bat", .owner = THIS_MODULE, }, }; - -static int __init da9052_bat_init(void) -{ - return platform_driver_register(&da9052_bat_driver); -} -module_init(da9052_bat_init); - -static void __exit da9052_bat_exit(void) -{ - platform_driver_unregister(&da9052_bat_driver); -} -module_exit(da9052_bat_exit); +module_platform_driver(da9052_bat_driver); MODULE_DESCRIPTION("DA9052 BAT Device Driver"); MODULE_AUTHOR("David Dajun Chen <dchen@diasemi.com>"); diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index 076e211a40b..85b4e6eca0b 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -355,8 +355,7 @@ static void ds2760_battery_external_power_changed(struct power_supply *psy) dev_dbg(di->dev, "%s\n", __func__); - cancel_delayed_work(&di->monitor_work); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); + mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ/10); } @@ -401,8 +400,7 @@ static void ds2760_battery_set_charged(struct power_supply *psy) /* postpone the actual work by 20 secs. This is for debouncing GPIO * signals and to let the current value settle. See AN4188. */ - cancel_delayed_work(&di->set_charged_work); - queue_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); + mod_delayed_work(di->monitor_wqueue, &di->set_charged_work, HZ * 20); } static int ds2760_battery_get_property(struct power_supply *psy, @@ -514,7 +512,7 @@ static int ds2760_battery_probe(struct platform_device *pdev) int retval = 0; struct ds2760_device_info *di; - di = kzalloc(sizeof(*di), GFP_KERNEL); + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (!di) { retval = -ENOMEM; goto di_alloc_failed; @@ -578,7 +576,6 @@ static int ds2760_battery_probe(struct platform_device *pdev) workqueue_failed: power_supply_unregister(&di->bat); batt_failed: - kfree(di); di_alloc_failed: success: return retval; @@ -592,7 +589,6 @@ static int ds2760_battery_remove(struct platform_device *pdev) cancel_delayed_work_sync(&di->set_charged_work); destroy_workqueue(di->monitor_wqueue); power_supply_unregister(&di->bat); - kfree(di); return 0; } @@ -616,8 +612,7 @@ static int ds2760_battery_resume(struct platform_device *pdev) di->charge_status = POWER_SUPPLY_STATUS_UNKNOWN; power_supply_changed(&di->bat); - cancel_delayed_work(&di->monitor_work); - queue_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); + mod_delayed_work(di->monitor_wqueue, &di->monitor_work, HZ); return 0; } diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c index de31cae1ba5..9f418fa879e 100644 --- a/drivers/power/ds2780_battery.c +++ b/drivers/power/ds2780_battery.c @@ -39,7 +39,6 @@ struct ds2780_device_info { struct device *dev; struct power_supply bat; struct device *w1_dev; - struct task_struct *mutex_holder; }; enum current_types { @@ -64,10 +63,7 @@ static inline struct power_supply *to_power_supply(struct device *dev) static inline int ds2780_battery_io(struct ds2780_device_info *dev_info, char *buf, int addr, size_t count, int io) { - if (dev_info->mutex_holder == current) - return w1_ds2780_io_nolock(dev_info->w1_dev, buf, addr, count, io); - else - return w1_ds2780_io(dev_info->w1_dev, buf, addr, count, 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, @@ -759,12 +755,12 @@ static const struct attribute_group ds2780_attr_group = { .attrs = ds2780_attributes, }; -static int __devinit ds2780_battery_probe(struct platform_device *pdev) +static int ds2780_battery_probe(struct platform_device *pdev) { int ret = 0; struct ds2780_device_info *dev_info; - dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); if (!dev_info) { ret = -ENOMEM; goto fail; @@ -779,12 +775,11 @@ static int __devinit ds2780_battery_probe(struct platform_device *pdev) dev_info->bat.properties = ds2780_battery_props; dev_info->bat.num_properties = ARRAY_SIZE(ds2780_battery_props); dev_info->bat.get_property = ds2780_battery_get_property; - dev_info->mutex_holder = current; ret = power_supply_register(&pdev->dev, &dev_info->bat); if (ret) { dev_err(dev_info->dev, "failed to register battery\n"); - goto fail_free_info; + goto fail; } ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); @@ -809,8 +804,6 @@ static int __devinit ds2780_battery_probe(struct platform_device *pdev) goto fail_remove_bin_file; } - dev_info->mutex_holder = NULL; - return 0; fail_remove_bin_file: @@ -820,24 +813,19 @@ fail_remove_group: sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); fail_unregister: power_supply_unregister(&dev_info->bat); -fail_free_info: - kfree(dev_info); fail: return ret; } -static int __devexit ds2780_battery_remove(struct platform_device *pdev) +static int ds2780_battery_remove(struct platform_device *pdev) { struct ds2780_device_info *dev_info = platform_get_drvdata(pdev); - dev_info->mutex_holder = current; - /* remove attributes */ sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); power_supply_unregister(&dev_info->bat); - kfree(dev_info); return 0; } @@ -846,7 +834,7 @@ static struct platform_driver ds2780_battery_driver = { .name = "ds2780-battery", }, .probe = ds2780_battery_probe, - .remove = __devexit_p(ds2780_battery_remove), + .remove = ds2780_battery_remove, }; module_platform_driver(ds2780_battery_driver); diff --git a/drivers/power/ds2781_battery.c b/drivers/power/ds2781_battery.c new file mode 100644 index 00000000000..0a5acc6fc6f --- /dev/null +++ b/drivers/power/ds2781_battery.c @@ -0,0 +1,838 @@ +/* + * 1-wire client/driver for the Maxim/Dallas DS2781 Stand-Alone Fuel Gauge IC + * + * Author: Renata Sayakhova <renata@oktetlabs.ru> + * + * Based on ds2780_battery drivers + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/param.h> +#include <linux/pm.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/idr.h> + +#include "../w1/w1.h" +#include "../w1/slaves/w1_ds2781.h" + +/* Current unit measurement in uA for a 1 milli-ohm sense resistor */ +#define DS2781_CURRENT_UNITS 1563 +/* Charge unit measurement in uAh for a 1 milli-ohm sense resistor */ +#define DS2781_CHARGE_UNITS 6250 +/* Number of bytes in user EEPROM space */ +#define DS2781_USER_EEPROM_SIZE (DS2781_EEPROM_BLOCK0_END - \ + DS2781_EEPROM_BLOCK0_START + 1) +/* Number of bytes in parameter EEPROM space */ +#define DS2781_PARAM_EEPROM_SIZE (DS2781_EEPROM_BLOCK1_END - \ + DS2781_EEPROM_BLOCK1_START + 1) + +struct ds2781_device_info { + struct device *dev; + struct power_supply bat; + struct 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 container_of(psy, struct ds2781_device_info, bat); +} + +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); + + count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off); + + 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; + + count = min_t(loff_t, count, DS2781_PARAM_EEPROM_SIZE - off); + + 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); + + count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off); + + 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; + + count = min_t(loff_t, count, DS2781_USER_EEPROM_SIZE - off); + + 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) +{ + 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.name = dev_name(&pdev->dev); + dev_info->bat.type = POWER_SUPPLY_TYPE_BATTERY; + dev_info->bat.properties = ds2781_battery_props; + dev_info->bat.num_properties = ARRAY_SIZE(ds2781_battery_props); + dev_info->bat.get_property = ds2781_battery_get_property; + + ret = power_supply_register(&pdev->dev, &dev_info->bat); + if (ret) { + dev_err(dev_info->dev, "failed to register battery\n"); + 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 */ + sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2781_attr_group); + + power_supply_unregister(&dev_info->bat); + + return 0; +} + +static struct platform_driver ds2781_battery_driver = { + .driver = { + .name = "ds2781-battery", + }, + .probe = ds2781_battery_probe, + .remove = ds2781_battery_remove, +}; +module_platform_driver(ds2781_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Renata Sayakhova <renata@oktetlabs.ru>"); +MODULE_DESCRIPTION("Maxim/Dallas DS2781 Stand-Alone Fuel Gauage IC driver"); +MODULE_ALIAS("platform:ds2781-battery"); + diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c index bfbce5de49d..041f9b638d2 100644 --- a/drivers/power/ds2782_battery.c +++ b/drivers/power/ds2782_battery.c @@ -7,6 +7,8 @@ * * DS2786 added by Yulia Vilensky <vilensky@compulab.co.il> * + * UEvent sending added by Evgeny Romanov <romanov@neurosoft.ru> + * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. @@ -19,6 +21,7 @@ #include <linux/errno.h> #include <linux/swab.h> #include <linux/i2c.h> +#include <linux/delay.h> #include <linux/idr.h> #include <linux/power_supply.h> #include <linux/slab.h> @@ -40,6 +43,8 @@ #define DS2786_CURRENT_UNITS 25 +#define DS278x_DELAY 1000 + struct ds278x_info; struct ds278x_battery_ops { @@ -54,8 +59,11 @@ struct ds278x_info { struct i2c_client *client; struct power_supply battery; 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); @@ -80,13 +88,13 @@ static inline int ds278x_read_reg16(struct ds278x_info *info, int reg_msb, { int ret; - ret = swab16(i2c_smbus_read_word_data(info->client, reg_msb)); + 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 = ret; + *val = swab16(ret); return 0; } @@ -184,7 +192,7 @@ static int ds2786_get_voltage(struct ds278x_info *info, int *voltage_uV) /* * Voltage is measured in units of 1.22mV. The voltage is stored as - * a 10-bit number plus sign, in the upper bits of a 16-bit register + * 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) @@ -220,6 +228,8 @@ static int ds278x_get_status(struct ds278x_info *info, int *status) if (err) return err; + info->capacity = capacity; + if (capacity == 100) *status = POWER_SUPPLY_STATUS_FULL; else if (current_uA == 0) @@ -267,6 +277,27 @@ static int ds278x_battery_get_property(struct power_supply *psy, 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, @@ -295,10 +326,39 @@ static int ds278x_battery_remove(struct i2c_client *client) 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; +} + +static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume); +#define DS278X_BATTERY_PM_OPS (&ds278x_battery_pm_ops) + +#else +#define DS278X_BATTERY_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ + enum ds278x_num_id { DS2782 = 0, DS2786, @@ -335,17 +395,12 @@ static int ds278x_battery_probe(struct i2c_client *client, } /* Get an ID for this battery */ - ret = idr_pre_get(&battery_id, GFP_KERNEL); - if (ret == 0) { - ret = -ENOMEM; - goto fail_id; - } - mutex_lock(&battery_lock); - ret = idr_get_new(&battery_id, client, &num); + 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) { @@ -368,10 +423,17 @@ static int ds278x_battery_probe(struct i2c_client *client, info->ops = &ds278x_ops[id->driver_data]; ds278x_power_supply_init(&info->battery); + info->capacity = 100; + info->status = POWER_SUPPLY_STATUS_FULL; + + INIT_DELAYED_WORK(&info->bat_work, ds278x_bat_work); + ret = power_supply_register(&client->dev, &info->battery); if (ret) { dev_err(&client->dev, "failed to register battery\n"); goto fail_register; + } else { + schedule_delayed_work(&info->bat_work, DS278x_DELAY); } return 0; @@ -398,23 +460,13 @@ 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, }; - -static int __init ds278x_init(void) -{ - return i2c_add_driver(&ds278x_battery_driver); -} -module_init(ds278x_init); - -static void __exit ds278x_exit(void) -{ - i2c_del_driver(&ds278x_battery_driver); -} -module_exit(ds278x_exit); +module_i2c_driver(ds278x_battery_driver); MODULE_AUTHOR("Ryan Mallon"); MODULE_DESCRIPTION("Maxim/Dallas DS2782 Stand-Alone Fuel Gauage IC driver"); diff --git a/drivers/power/generic-adc-battery.c b/drivers/power/generic-adc-battery.c new file mode 100644 index 00000000000..59a1421f928 --- /dev/null +++ b/drivers/power/generic-adc-battery.c @@ -0,0 +1,427 @@ +/* + * Generic battery driver code using IIO + * Copyright (C) 2012, Anish Kumar <anish198519851985@gmail.com> + * based on jz4740-battery.c + * based on s3c_adc_battery.c + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file COPYING in the main directory of this archive for + * more details. + * + */ +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/gpio.h> +#include <linux/err.h> +#include <linux/timer.h> +#include <linux/jiffies.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/iio/consumer.h> +#include <linux/iio/types.h> +#include <linux/power/generic-adc-battery.h> + +#define JITTER_DEFAULT 10 /* hope 10ms is enough */ + +enum gab_chan_type { + GAB_VOLTAGE = 0, + GAB_CURRENT, + GAB_POWER, + GAB_MAX_CHAN_TYPE +}; + +/* + * gab_chan_name suggests the standard channel names for commonly used + * channel types. + */ +static const char *const gab_chan_name[] = { + [GAB_VOLTAGE] = "voltage", + [GAB_CURRENT] = "current", + [GAB_POWER] = "power", +}; + +struct gab { + struct power_supply psy; + struct 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 container_of(psy, struct gab, 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: + 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 = container_of(work, struct 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 *psy; + 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 = &adc_bat->psy; + psy->name = pdata->battery_info.name; + + /* bootup default values for the battery */ + adc_bat->cable_plugged = false; + adc_bat->status = POWER_SUPPLY_STATUS_DISCHARGING; + psy->type = POWER_SUPPLY_TYPE_BATTERY; + psy->get_property = gab_get_property; + psy->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->properties = kcalloc(ARRAY_SIZE(gab_props) + + ARRAY_SIZE(gab_chan_name), + sizeof(*psy->properties), GFP_KERNEL); + if (!psy->properties) { + ret = -ENOMEM; + goto first_mem_fail; + } + + memcpy(psy->properties, gab_props, sizeof(gab_props)); + properties = (enum power_supply_property *) + ((char *)psy->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->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->num_properties = ARRAY_SIZE(gab_props) + index; + + ret = power_supply_register(&pdev->dev, psy); + if (ret) + 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(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->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.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", + .owner = THIS_MODULE, + .pm = GAB_PM_OPS + }, + .probe = gab_probe, + .remove = gab_remove, +}; +module_platform_driver(gab_driver); + +MODULE_AUTHOR("anish kumar <anish198519851985@gmail.com>"); +MODULE_DESCRIPTION("generic battery driver using IIO"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/goldfish_battery.c b/drivers/power/goldfish_battery.c new file mode 100644 index 00000000000..29eba88a296 --- /dev/null +++ b/drivers/power/goldfish_battery.c @@ -0,0 +1,236 @@ +/* + * Power supply driver for the goldfish emulator + * + * Copyright (C) 2008 Google, Inc. + * Copyright (C) 2012 Intel, Inc. + * Copyright (C) 2013 Intel, Inc. + * Author: Mike Lockwood <lockwood@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/err.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/types.h> +#include <linux/pci.h> +#include <linux/interrupt.h> +#include <linux/io.h> + +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 = container_of(psy, + struct goldfish_battery_data, ac); + 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 = container_of(psy, + struct goldfish_battery_data, battery); + 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 int goldfish_battery_probe(struct platform_device *pdev) +{ + int ret; + struct resource *r; + struct goldfish_battery_data *data; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + spin_lock_init(&data->lock); + + data->battery.properties = goldfish_battery_props; + data->battery.num_properties = ARRAY_SIZE(goldfish_battery_props); + data->battery.get_property = goldfish_battery_get_property; + data->battery.name = "battery"; + data->battery.type = POWER_SUPPLY_TYPE_BATTERY; + + data->ac.properties = goldfish_ac_props; + data->ac.num_properties = ARRAY_SIZE(goldfish_ac_props); + data->ac.get_property = goldfish_ac_get_property; + data->ac.name = "ac"; + data->ac.type = POWER_SUPPLY_TYPE_MAINS; + + 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; + + ret = power_supply_register(&pdev->dev, &data->ac); + if (ret) + return ret; + + ret = power_supply_register(&pdev->dev, &data->battery); + if (ret) { + power_supply_unregister(&data->ac); + return ret; + } + + 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 struct platform_driver goldfish_battery_device = { + .probe = goldfish_battery_probe, + .remove = goldfish_battery_remove, + .driver = { + .name = "goldfish-battery" + } +}; +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 index 8672c9177dd..a0024b25219 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -28,6 +28,7 @@ struct gpio_charger { const struct gpio_charger_platform_data *pdata; unsigned int irq; + bool wakeup_enabled; struct power_supply charger; }; @@ -54,7 +55,7 @@ static int gpio_charger_get_property(struct power_supply *psy, switch (psp) { case POWER_SUPPLY_PROP_ONLINE: - val->intval = gpio_get_value(pdata->gpio); + val->intval = gpio_get_value_cansleep(pdata->gpio); val->intval ^= pdata->gpio_active_low; break; default: @@ -68,7 +69,7 @@ static enum power_supply_property gpio_charger_properties[] = { POWER_SUPPLY_PROP_ONLINE, }; -static int __devinit gpio_charger_probe(struct platform_device *pdev) +static int gpio_charger_probe(struct platform_device *pdev) { const struct gpio_charger_platform_data *pdata = pdev->dev.platform_data; struct gpio_charger *gpio_charger; @@ -86,7 +87,8 @@ static int __devinit gpio_charger_probe(struct platform_device *pdev) return -EINVAL; } - gpio_charger = kzalloc(sizeof(*gpio_charger), GFP_KERNEL); + 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; @@ -135,16 +137,17 @@ static int __devinit gpio_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, gpio_charger); + device_init_wakeup(&pdev->dev, 1); + return 0; err_gpio_free: gpio_free(pdata->gpio); err_free: - kfree(gpio_charger); return ret; } -static int __devexit gpio_charger_remove(struct platform_device *pdev) +static int gpio_charger_remove(struct platform_device *pdev) { struct gpio_charger *gpio_charger = platform_get_drvdata(pdev); @@ -155,29 +158,40 @@ static int __devexit gpio_charger_remove(struct platform_device *pdev) gpio_free(gpio_charger->pdata->gpio); - platform_set_drvdata(pdev, NULL); - kfree(gpio_charger); - 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 (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, NULL, gpio_charger_resume); +static SIMPLE_DEV_PM_OPS(gpio_charger_pm_ops, + gpio_charger_suspend, gpio_charger_resume); static struct platform_driver gpio_charger_driver = { .probe = gpio_charger_probe, - .remove = __devexit_p(gpio_charger_remove), + .remove = gpio_charger_remove, .driver = { .name = "gpio-charger", .owner = THIS_MODULE, diff --git a/drivers/power/intel_mid_battery.c b/drivers/power/intel_mid_battery.c index d09649706bd..4520811168a 100644 --- a/drivers/power/intel_mid_battery.c +++ b/drivers/power/intel_mid_battery.c @@ -649,7 +649,7 @@ static void pmic_battery_handle_intrpt(struct work_struct *work) * PMIC battery initializes its internal data structue and other * infrastructure components for it to work as expected. */ -static __devinit int probe(int irq, struct device *dev) +static int probe(int irq, struct device *dev) { int retval = 0; struct pmic_power_module_info *pbi; @@ -739,7 +739,7 @@ wqueue_failed: return retval; } -static int __devinit platform_pmic_battery_probe(struct platform_device *pdev) +static int platform_pmic_battery_probe(struct platform_device *pdev) { return probe(pdev->id, &pdev->dev); } @@ -754,9 +754,9 @@ static int __devinit platform_pmic_battery_probe(struct platform_device *pdev) * pmic_battery_probe. */ -static int __devexit platform_pmic_battery_remove(struct platform_device *pdev) +static int platform_pmic_battery_remove(struct platform_device *pdev) { - struct pmic_power_module_info *pbi = dev_get_drvdata(&pdev->dev); + struct pmic_power_module_info *pbi = platform_get_drvdata(pdev); free_irq(pbi->irq, pbi); cancel_delayed_work_sync(&pbi->monitor_battery); @@ -776,7 +776,7 @@ static struct platform_driver platform_pmic_battery_driver = { .owner = THIS_MODULE, }, .probe = platform_pmic_battery_probe, - .remove = __devexit_p(platform_pmic_battery_remove), + .remove = platform_pmic_battery_remove, }; module_platform_driver(platform_pmic_battery_driver); diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index b806667b59a..0b4cf9d6329 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -2,6 +2,7 @@ * ISP1704 USB Charger Detection driver * * Copyright (C) 2010 Nokia Corporation + * Copyright (C) 2012 - 2013 Pali Rohár <pali.rohar@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,6 +29,8 @@ #include <linux/platform_device.h> #include <linux/power_supply.h> #include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_gpio.h> #include <linux/usb/otg.h> #include <linux/usb/ulpi.h> @@ -56,7 +59,7 @@ static u16 isp170x_id[] = { struct isp1704_charger { struct device *dev; struct power_supply psy; - struct otg_transceiver *otg; + struct usb_phy *phy; struct notifier_block nb; struct work_struct work; @@ -65,12 +68,18 @@ struct isp1704_charger { unsigned present:1; unsigned online:1; unsigned current_max; - - /* temp storage variables */ - unsigned long event; - unsigned max_power; }; +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 val, u32 reg) +{ + 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. @@ -81,6 +90,8 @@ static void isp1704_charger_set_power(struct isp1704_charger *isp, bool on) if (board && board->set_power) board->set_power(on); + else if (board) + gpio_set_value(board->enable_gpio, on); } /* @@ -97,31 +108,31 @@ static inline int isp1704_charger_type(struct isp1704_charger *isp) u8 otg_ctrl; int type = POWER_SUPPLY_TYPE_USB_DCP; - func_ctrl = otg_io_read(isp->otg, ULPI_FUNC_CTRL); - otg_ctrl = otg_io_read(isp->otg, ULPI_OTG_CTRL); + 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; - otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), reg); + isp1704_write(isp, ULPI_CLR(ULPI_OTG_CTRL), reg); /* full speed */ - otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), ULPI_FUNC_CTRL_XCVRSEL_MASK); - otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), + 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; - otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), reg); + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), reg); usleep_range(1000, 2000); - reg = otg_io_read(isp->otg, ULPI_DEBUG); + reg = isp1704_read(isp, ULPI_DEBUG); if ((reg & 3) != 3) type = POWER_SUPPLY_TYPE_USB_CDP; /* recover original state */ - otg_io_write(isp->otg, ULPI_FUNC_CTRL, func_ctrl); - otg_io_write(isp->otg, ULPI_OTG_CTRL, otg_ctrl); + isp1704_write(isp, ULPI_FUNC_CTRL, func_ctrl); + isp1704_write(isp, ULPI_OTG_CTRL, otg_ctrl); return type; } @@ -136,28 +147,28 @@ static inline int isp1704_charger_verify(struct isp1704_charger *isp) u8 r; /* Reset the transceiver */ - r = otg_io_read(isp->otg, ULPI_FUNC_CTRL); + r = isp1704_read(isp, ULPI_FUNC_CTRL); r |= ULPI_FUNC_CTRL_RESET; - otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + isp1704_write(isp, ULPI_FUNC_CTRL, r); usleep_range(1000, 2000); /* Set normal mode */ r &= ~(ULPI_FUNC_CTRL_RESET | ULPI_FUNC_CTRL_OPMODE_MASK); - otg_io_write(isp->otg, ULPI_FUNC_CTRL, r); + 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; - otg_io_write(isp->otg, ULPI_CLR(ULPI_OTG_CTRL), r); + 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; - otg_io_write(isp->otg, ULPI_SET(ULPI_FUNC_CTRL), r); + isp1704_write(isp, ULPI_SET(ULPI_FUNC_CTRL), r); usleep_range(1000, 2000); /* Read the line state */ - if (!otg_io_read(isp->otg, ULPI_DEBUG)) { + if (!isp1704_read(isp, ULPI_DEBUG)) { /* Disable strong pull-up on DP (1.5K) */ - otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), ULPI_FUNC_CTRL_TERMSELECT); return 1; } @@ -165,23 +176,23 @@ static inline int isp1704_charger_verify(struct isp1704_charger *isp) /* Is it a charger or PS/2 connection */ /* Enable weak pull-up resistor on DP */ - otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), + isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), ISP1704_PWR_CTRL_DP_WKPU_EN); /* Disable strong pull-up on DP (1.5K) */ - otg_io_write(isp->otg, ULPI_CLR(ULPI_FUNC_CTRL), + isp1704_write(isp, ULPI_CLR(ULPI_FUNC_CTRL), ULPI_FUNC_CTRL_TERMSELECT); /* Enable weak pull-down resistor on DM */ - otg_io_write(isp->otg, ULPI_SET(ULPI_OTG_CTRL), + isp1704_write(isp, ULPI_SET(ULPI_OTG_CTRL), ULPI_OTG_CTRL_DM_PULLDOWN); /* It's a charger if the line states are clear */ - if (!(otg_io_read(isp->otg, ULPI_DEBUG))) + if (!(isp1704_read(isp, ULPI_DEBUG))) ret = 1; /* Disable weak pull-up resistor on DP */ - otg_io_write(isp->otg, ULPI_CLR(ISP1704_PWR_CTRL), + isp1704_write(isp, ULPI_CLR(ISP1704_PWR_CTRL), ISP1704_PWR_CTRL_DP_WKPU_EN); return ret; @@ -193,14 +204,14 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) u8 pwr_ctrl; int ret = 0; - pwr_ctrl = otg_io_read(isp->otg, ISP1704_PWR_CTRL); + pwr_ctrl = isp1704_read(isp, ISP1704_PWR_CTRL); /* set SW control bit in PWR_CTRL register */ - otg_io_write(isp->otg, ISP1704_PWR_CTRL, + isp1704_write(isp, ISP1704_PWR_CTRL, ISP1704_PWR_CTRL_SWCTRL); /* enable manual charger detection */ - otg_io_write(isp->otg, ULPI_SET(ISP1704_PWR_CTRL), + isp1704_write(isp, ULPI_SET(ISP1704_PWR_CTRL), ISP1704_PWR_CTRL_SWCTRL | ISP1704_PWR_CTRL_DPVSRC_EN); usleep_range(1000, 2000); @@ -208,7 +219,7 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) timeout = jiffies + msecs_to_jiffies(300); do { /* Check if there is a charger */ - if (otg_io_read(isp->otg, ISP1704_PWR_CTRL) + if (isp1704_read(isp, ISP1704_PWR_CTRL) & ISP1704_PWR_CTRL_VDAT_DET) { ret = isp1704_charger_verify(isp); break; @@ -216,61 +227,64 @@ static inline int isp1704_charger_detect(struct isp1704_charger *isp) } while (!time_after(jiffies, timeout) && isp->online); /* recover original state */ - otg_io_write(isp->otg, ISP1704_PWR_CTRL, pwr_ctrl); + 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) { - int detect; - unsigned long event; - unsigned power; struct isp1704_charger *isp = container_of(data, struct isp1704_charger, work); static DEFINE_MUTEX(lock); - event = isp->event; - power = isp->max_power; - mutex_lock(&lock); - if (event != USB_EVENT_NONE) - isp1704_charger_set_power(isp, 1); - - switch (event) { + switch (isp->phy->last_event) { case USB_EVENT_VBUS: - isp->online = true; - - /* detect charger */ - detect = isp1704_charger_detect(isp); + /* 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.type = POWER_SUPPLY_TYPE_USB_DCP; + isp->current_max = 1800; + } else { + isp->psy.type = POWER_SUPPLY_TYPE_USB; + isp->current_max = 500; + } - if (detect) { - isp->present = detect; - isp->psy.type = isp1704_charger_type(isp); + /* enable data pullups */ + if (isp->phy->otg->gadget) + usb_gadget_connect(isp->phy->otg->gadget); } - switch (isp->psy.type) { - case POWER_SUPPLY_TYPE_USB_DCP: - isp->current_max = 1800; - break; - case POWER_SUPPLY_TYPE_USB_CDP: + if (isp->psy.type != POWER_SUPPLY_TYPE_USB_DCP) { /* * Only 500mA here or high speed chirp * handshaking may break */ - isp->current_max = 500; - /* FALLTHROUGH */ - case POWER_SUPPLY_TYPE_USB: - default: - /* enable data pullups */ - if (isp->otg->gadget) - usb_gadget_connect(isp->otg->gadget); + if (isp->current_max > 500) + isp->current_max = 500; + + if (isp->current_max > 100) + isp->psy.type = POWER_SUPPLY_TYPE_USB_CDP; } break; case USB_EVENT_NONE: isp->online = false; - isp->current_max = 0; isp->present = 0; isp->current_max = 0; isp->psy.type = POWER_SUPPLY_TYPE_USB; @@ -283,17 +297,11 @@ static void isp1704_charger_work(struct work_struct *data) * chargers. The pullups may be enabled elsewhere, so this can * not be the final solution. */ - if (isp->otg->gadget) - usb_gadget_disconnect(isp->otg->gadget); + if (isp->phy->otg->gadget) + usb_gadget_disconnect(isp->phy->otg->gadget); isp1704_charger_set_power(isp, 0); break; - case USB_EVENT_ENUMERATED: - if (isp->present) - isp->current_max = 1800; - else - isp->current_max = power; - break; default: goto out; } @@ -304,16 +312,11 @@ out: } static int isp1704_notifier_call(struct notifier_block *nb, - unsigned long event, void *power) + unsigned long val, void *v) { struct isp1704_charger *isp = container_of(nb, struct isp1704_charger, nb); - isp->event = event; - - if (power) - isp->max_power = *((unsigned *)power); - schedule_work(&isp->work); return NOTIFY_OK; @@ -364,11 +367,11 @@ static inline int isp1704_test_ulpi(struct isp1704_charger *isp) int ret = -ENODEV; /* Test ULPI interface */ - ret = otg_io_write(isp->otg, ULPI_SCRATCH, 0xaa); + ret = isp1704_write(isp, ULPI_SCRATCH, 0xaa); if (ret < 0) return ret; - ret = otg_io_read(isp->otg, ULPI_SCRATCH); + ret = isp1704_read(isp, ULPI_SCRATCH); if (ret < 0) return ret; @@ -376,13 +379,13 @@ static inline int isp1704_test_ulpi(struct isp1704_charger *isp) return -ENODEV; /* Verify the product and vendor id matches */ - vendor = otg_io_read(isp->otg, ULPI_VENDOR_ID_LOW); - vendor |= otg_io_read(isp->otg, ULPI_VENDOR_ID_HIGH) << 8; + 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 = otg_io_read(isp->otg, ULPI_PRODUCT_ID_LOW); - product |= otg_io_read(isp->otg, ULPI_PRODUCT_ID_HIGH) << 8; + 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]) { @@ -396,18 +399,51 @@ static inline int isp1704_test_ulpi(struct isp1704_charger *isp) return -ENODEV; } -static int __devinit isp1704_charger_probe(struct platform_device *pdev) +static int isp1704_charger_probe(struct platform_device *pdev) { struct isp1704_charger *isp; int ret = -ENODEV; - isp = kzalloc(sizeof *isp, GFP_KERNEL); + 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) + 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) + 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; - isp->otg = otg_get_transceiver(); - if (!isp->otg) + 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); goto fail0; + } isp->dev = &pdev->dev; platform_set_drvdata(pdev, isp); @@ -429,14 +465,14 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev) goto fail1; /* - * REVISIT: using work in order to allow the otg notifications to be + * 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 = otg_register_notifier(isp->otg, &isp->nb); + ret = usb_register_notifier(isp->phy, &isp->nb); if (ret) goto fail2; @@ -449,49 +485,54 @@ static int __devinit isp1704_charger_probe(struct platform_device *pdev) * enumerated. The charger driver should be always loaded before any * gadget is loaded. */ - if (isp->otg->gadget) - usb_gadget_disconnect(isp->otg->gadget); + 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). */ - ret = otg_io_read(isp->otg, ULPI_USB_INT_STS); - isp1704_charger_set_power(isp, 0); - if ((ret & ULPI_INT_VBUS_VALID) && !isp->otg->default_a) { - isp->event = USB_EVENT_VBUS; + 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: - otg_put_transceiver(isp->otg); + isp1704_charger_set_power(isp, 0); fail0: - kfree(isp); - dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); return ret; } -static int __devexit isp1704_charger_remove(struct platform_device *pdev) +static int isp1704_charger_remove(struct platform_device *pdev) { struct isp1704_charger *isp = platform_get_drvdata(pdev); - otg_unregister_notifier(isp->otg, &isp->nb); + usb_unregister_notifier(isp->phy, &isp->nb); power_supply_unregister(&isp->psy); - otg_put_transceiver(isp->otg); isp1704_charger_set_power(isp, 0); - kfree(isp); return 0; } +#ifdef CONFIG_OF +static const struct of_device_id omap_isp1704_of_match[] = { + { .compatible = "nxp,isp1704", }, + {}, +}; +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 = __devexit_p(isp1704_charger_remove), + .remove = isp1704_charger_remove, }; module_platform_driver(isp1704_charger_driver); diff --git a/drivers/power/jz4740-battery.c b/drivers/power/jz4740-battery.c index 8dbc7bfaab1..6c8931d4ad6 100644 --- a/drivers/power/jz4740-battery.c +++ b/drivers/power/jz4740-battery.c @@ -22,6 +22,7 @@ #include <linux/io.h> #include <linux/delay.h> +#include <linux/err.h> #include <linux/gpio.h> #include <linux/mfd/core.h> #include <linux/power_supply.h> @@ -33,7 +34,6 @@ struct jz_battery { struct jz_battery_platform_data *pdata; struct platform_device *pdev; - struct resource *mem; void __iomem *base; int irq; @@ -73,7 +73,7 @@ static long jz_battery_read_voltage(struct jz_battery *battery) mutex_lock(&battery->lock); - INIT_COMPLETION(battery->read_completion); + reinit_completion(&battery->read_completion); enable_irq(battery->irq); battery->cell->enable(battery->pdev); @@ -173,16 +173,14 @@ static void jz_battery_external_power_changed(struct power_supply *psy) { struct jz_battery *jz_battery = psy_to_jz_battery(psy); - cancel_delayed_work(&jz_battery->work); - schedule_delayed_work(&jz_battery->work, 0); + 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; - cancel_delayed_work(&jz_battery->work); - schedule_delayed_work(&jz_battery->work, 0); + mod_delayed_work(system_wq, &jz_battery->work, 0); return IRQ_HANDLED; } @@ -240,19 +238,20 @@ static void jz_battery_work(struct work_struct *work) schedule_delayed_work(&jz_battery->work, interval); } -static int __devinit jz_battery_probe(struct platform_device *pdev) +static int jz_battery_probe(struct platform_device *pdev) { int ret = 0; struct jz_battery_platform_data *pdata = pdev->dev.parent->platform_data; struct jz_battery *jz_battery; struct power_supply *battery; + struct resource *mem; if (!pdata) { dev_err(&pdev->dev, "No platform_data supplied\n"); return -ENXIO; } - jz_battery = kzalloc(sizeof(*jz_battery), GFP_KERNEL); + 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; @@ -262,33 +261,15 @@ static int __devinit jz_battery_probe(struct platform_device *pdev) jz_battery->irq = platform_get_irq(pdev, 0); if (jz_battery->irq < 0) { - ret = jz_battery->irq; dev_err(&pdev->dev, "Failed to get platform irq: %d\n", ret); - goto err_free; + return jz_battery->irq; } - jz_battery->mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (!jz_battery->mem) { - ret = -ENOENT; - dev_err(&pdev->dev, "Failed to get platform mmio resource\n"); - goto err_free; - } + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); - jz_battery->mem = request_mem_region(jz_battery->mem->start, - resource_size(jz_battery->mem), pdev->name); - if (!jz_battery->mem) { - ret = -EBUSY; - dev_err(&pdev->dev, "Failed to request mmio memory region\n"); - goto err_free; - } - - jz_battery->base = ioremap_nocache(jz_battery->mem->start, - resource_size(jz_battery->mem)); - if (!jz_battery->base) { - ret = -EBUSY; - dev_err(&pdev->dev, "Failed to ioremap mmio memory\n"); - goto err_release_mem_region; - } + jz_battery->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(jz_battery->base)) + return PTR_ERR(jz_battery->base); battery = &jz_battery->battery; battery->name = pdata->info.name; @@ -311,7 +292,7 @@ static int __devinit jz_battery_probe(struct platform_device *pdev) jz_battery); if (ret) { dev_err(&pdev->dev, "Failed to request irq %d\n", ret); - goto err_iounmap; + return ret; } disable_irq(jz_battery->irq); @@ -368,17 +349,10 @@ err_free_gpio: gpio_free(jz_battery->pdata->gpio_charge); err_free_irq: free_irq(jz_battery->irq, jz_battery); -err_iounmap: - platform_set_drvdata(pdev, NULL); - iounmap(jz_battery->base); -err_release_mem_region: - release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); -err_free: - kfree(jz_battery); return ret; } -static int __devexit jz_battery_remove(struct platform_device *pdev) +static int jz_battery_remove(struct platform_device *pdev) { struct jz_battery *jz_battery = platform_get_drvdata(pdev); @@ -394,10 +368,6 @@ static int __devexit jz_battery_remove(struct platform_device *pdev) free_irq(jz_battery->irq, jz_battery); - iounmap(jz_battery->base); - release_mem_region(jz_battery->mem->start, resource_size(jz_battery->mem)); - kfree(jz_battery); - return 0; } @@ -433,7 +403,7 @@ static const struct dev_pm_ops jz_battery_pm_ops = { static struct platform_driver jz_battery_driver = { .probe = jz_battery_probe, - .remove = __devexit_p(jz_battery_remove), + .remove = jz_battery_remove, .driver = { .name = "jz4740-battery", .owner = THIS_MODULE, diff --git a/drivers/power/lp8727_charger.c b/drivers/power/lp8727_charger.c index c53dd1292f8..32de636dcd7 100644 --- a/drivers/power/lp8727_charger.c +++ b/drivers/power/lp8727_charger.c @@ -1,6 +1,7 @@ /* - * Driver for LP8727 Micro/Mini USB IC with intergrated charger + * 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 @@ -14,63 +15,68 @@ #include <linux/interrupt.h> #include <linux/i2c.h> #include <linux/power_supply.h> -#include <linux/lp8727.h> +#include <linux/platform_data/lp8727.h> +#include <linux/of.h> -#define DEBOUNCE_MSEC 270 +#define LP8788_NUM_INTREGS 2 +#define DEFAULT_DEBOUNCE_MSEC 270 /* Registers */ -#define CTRL1 0x1 -#define CTRL2 0x2 -#define SWCTRL 0x3 -#define INT1 0x4 -#define INT2 0x5 -#define STATUS1 0x6 -#define STATUS2 0x7 -#define CHGCTRL2 0x9 +#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 CP_EN (1 << 0) -#define ADC_EN (1 << 1) -#define ID200_EN (1 << 4) +#define LP8727_CP_EN BIT(0) +#define LP8727_ADC_EN BIT(1) +#define LP8727_ID200_EN BIT(4) /* CTRL2 register */ -#define CHGDET_EN (1 << 1) -#define INT_EN (1 << 6) +#define LP8727_CHGDET_EN BIT(1) +#define LP8727_INT_EN BIT(6) /* SWCTRL register */ -#define SW_DM1_DM (0x0 << 0) -#define SW_DM1_U1 (0x1 << 0) -#define SW_DM1_HiZ (0x7 << 0) -#define SW_DP2_DP (0x0 << 3) -#define SW_DP2_U2 (0x1 << 3) -#define SW_DP2_HiZ (0x7 << 3) +#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 IDNO (0xF << 0) -#define VBUS (1 << 4) +#define LP8727_IDNO (0xF << 0) +#define LP8727_VBUS BIT(4) /* STATUS1 register */ -#define CHGSTAT (3 << 4) -#define CHPORT (1 << 6) -#define DCPORT (1 << 7) +#define LP8727_CHGSTAT (3 << 4) +#define LP8727_CHPORT BIT(6) +#define LP8727_DCPORT BIT(7) +#define LP8727_STAT_EOC 0x30 /* STATUS2 register */ -#define TEMP_STAT (3 << 5) +#define LP8727_TEMP_STAT (3 << 5) +#define LP8727_TEMP_SHIFT 5 + +/* CHGCTRL2 register */ +#define LP8727_ICHG_SHIFT 4 enum lp8727_dev_id { - ID_NONE, - ID_TA, - ID_DEDICATED_CHG, - ID_USB_CHG, - ID_USB_DS, - ID_MAX, + LP8727_ID_NONE, + LP8727_ID_TA, + LP8727_ID_DEDICATED_CHG, + LP8727_ID_USB_CHG, + LP8727_ID_USB_DS, + LP8727_ID_MAX, }; -enum lp8727_chg_stat { - PRECHG, - CC, - CV, - EOC, +enum lp8727_die_temp { + LP8788_TEMP_75C, + LP8788_TEMP_95C, + LP8788_TEMP_115C, + LP8788_TEMP_135C, }; struct lp8727_psy { @@ -83,15 +89,20 @@ struct lp8727_chg { struct device *dev; struct i2c_client *client; struct mutex xfer_lock; - struct delayed_work work; - struct workqueue_struct *irqthread; - struct lp8727_platform_data *pdata; struct lp8727_psy *psy; - struct lp8727_chg_param *chg_parm; + 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_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) +static int lp8727_read_bytes(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) { s32 ret; @@ -102,100 +113,100 @@ static int lp8727_i2c_read(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) return (ret != len) ? -EIO : 0; } -static int lp8727_i2c_write(struct lp8727_chg *pchg, u8 reg, u8 *data, u8 len) +static inline int lp8727_read_byte(struct lp8727_chg *pchg, u8 reg, u8 *data) { - s32 ret; + 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_i2c_block_data(pchg->client, reg, len, data); + ret = i2c_smbus_write_byte_data(pchg->client, reg, data); mutex_unlock(&pchg->xfer_lock); return ret; } -static inline int lp8727_i2c_read_byte(struct lp8727_chg *pchg, u8 reg, - u8 *data) +static bool lp8727_is_charger_attached(const char *name, int id) { - return lp8727_i2c_read(pchg, reg, data, 1); -} + 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; -static inline int lp8727_i2c_write_byte(struct lp8727_chg *pchg, u8 reg, - u8 *data) -{ - return lp8727_i2c_write(pchg, reg, data, 1); -} - -static int lp8727_is_charger_attached(const char *name, int id) -{ - if (name) { - if (!strcmp(name, "ac")) - return (id == ID_TA || id == ID_DEDICATED_CHG) ? 1 : 0; - else if (!strcmp(name, "usb")) - return (id == ID_USB_CHG) ? 1 : 0; - } - - return (id >= ID_TA && id <= ID_USB_CHG) ? 1 : 0; + return id >= LP8727_ID_TA && id <= LP8727_ID_USB_CHG; } -static void lp8727_init_device(struct lp8727_chg *pchg) +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 = ID200_EN | ADC_EN | CP_EN; - if (lp8727_i2c_write_byte(pchg, CTRL1, &val)) - dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL1); + val = LP8727_ID200_EN | LP8727_ADC_EN | LP8727_CP_EN; + ret = lp8727_write_byte(pchg, LP8727_CTRL1, val); + if (ret) + return ret; - val = INT_EN | CHGDET_EN; - if (lp8727_i2c_write_byte(pchg, CTRL2, &val)) - dev_err(pchg->dev, "i2c write err : addr=0x%.2x\n", CTRL2); + 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_i2c_read_byte(pchg, STATUS1, &val); - return (val & DCPORT); + + lp8727_read_byte(pchg, LP8727_STATUS1, &val); + return val & LP8727_DCPORT; } static int lp8727_is_usb_charger(struct lp8727_chg *pchg) { u8 val; - lp8727_i2c_read_byte(pchg, STATUS1, &val); - return (val & CHPORT); + + lp8727_read_byte(pchg, LP8727_STATUS1, &val); + return val & LP8727_CHPORT; } -static void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) +static inline void lp8727_ctrl_switch(struct lp8727_chg *pchg, u8 sw) { - u8 val = sw; - lp8727_i2c_write_byte(pchg, SWCTRL, &val); + lp8727_write_byte(pchg, LP8727_SWCTRL, sw); } static void lp8727_id_detection(struct lp8727_chg *pchg, u8 id, int vbusin) { - u8 devid = ID_NONE; - u8 swctrl = SW_DM1_HiZ | SW_DP2_HiZ; + 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 = ID_TA; - pchg->chg_parm = &pchg->pdata->ac; + devid = LP8727_ID_TA; + pchg->chg_param = pdata ? pdata->ac : NULL; break; case 0xB: if (lp8727_is_dedicated_charger(pchg)) { - pchg->chg_parm = &pchg->pdata->ac; - devid = ID_DEDICATED_CHG; + pchg->chg_param = pdata ? pdata->ac : NULL; + devid = LP8727_ID_DEDICATED_CHG; } else if (lp8727_is_usb_charger(pchg)) { - pchg->chg_parm = &pchg->pdata->usb; - devid = ID_USB_CHG; - swctrl = SW_DM1_DM | SW_DP2_DP; + 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 = ID_USB_DS; - swctrl = SW_DM1_DM | SW_DP2_DP; + devid = LP8727_ID_USB_DS; + swctrl = LP8727_SW_DM1_DM | LP8727_SW_DP2_DP; } break; default: - devid = ID_NONE; - pchg->chg_parm = NULL; + devid = LP8727_ID_NONE; + pchg->chg_param = NULL; break; } @@ -207,24 +218,26 @@ static void lp8727_enable_chgdet(struct lp8727_chg *pchg) { u8 val; - lp8727_i2c_read_byte(pchg, CTRL2, &val); - val |= CHGDET_EN; - lp8727_i2c_write_byte(pchg, CTRL2, &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) { - u8 intstat[2], idno, vbus; - struct lp8727_chg *pchg = - container_of(_work, struct lp8727_chg, work.work); + struct lp8727_chg *pchg = container_of(_work, struct lp8727_chg, + work.work); + u8 intstat[LP8788_NUM_INTREGS]; + u8 idno; + u8 vbus; - if (lp8727_i2c_read(pchg, INT1, intstat, 2)) { + 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] & IDNO; - vbus = intstat[0] & VBUS; + idno = intstat[0] & LP8727_IDNO; + vbus = intstat[0] & LP8727_VBUS; lp8727_id_detection(pchg, idno, vbus); lp8727_enable_chgdet(pchg); @@ -237,27 +250,44 @@ static void lp8727_delayed_func(struct work_struct *_work) static irqreturn_t lp8727_isr_func(int irq, void *ptr) { struct lp8727_chg *pchg = ptr; - unsigned long delay = msecs_to_jiffies(DEBOUNCE_MSEC); - - queue_delayed_work(pchg->irqthread, &pchg->work, delay); + schedule_delayed_work(&pchg->work, pchg->debounce_jiffies); return IRQ_HANDLED; } -static void lp8727_intr_config(struct lp8727_chg *pchg) +static int lp8727_setup_irq(struct lp8727_chg *pchg) { - INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); + int ret; + int irq = pchg->client->irq; + unsigned delay_msec = pchg->pdata ? pchg->pdata->debounce_msec : + DEFAULT_DEBOUNCE_MSEC; - pchg->irqthread = create_singlethread_workqueue("lp8727-irqthd"); - if (!pchg->irqthread) - dev_err(pchg->dev, "can not create thread for lp8727\n"); + INIT_DELAYED_WORK(&pchg->work, lp8727_delayed_func); - if (request_threaded_irq(pchg->client->irq, - NULL, - lp8727_isr_func, - IRQF_TRIGGER_FALLING, "lp8727_irq", pchg)) { - dev_err(pchg->dev, "lp8727 irq can not be registered\n"); + 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[] = { @@ -283,55 +313,83 @@ static int lp8727_charger_get_property(struct power_supply *psy, { struct lp8727_chg *pchg = dev_get_drvdata(psy->dev->parent); - if (psp == POWER_SUPPLY_PROP_ONLINE) - val->intval = lp8727_is_charger_attached(psy->name, - pchg->devid); + if (psp != POWER_SUPPLY_PROP_ONLINE) + return -EINVAL; + + val->intval = lp8727_is_charger_attached(psy->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->name, pchg->devid)) { - lp8727_i2c_read_byte(pchg, STATUS1, &read); - if (((read & CHGSTAT) >> 4) == EOC) - val->intval = POWER_SUPPLY_STATUS_FULL; - else - val->intval = POWER_SUPPLY_STATUS_CHARGING; - } else { + if (!lp8727_is_charger_attached(psy->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_i2c_read_byte(pchg, STATUS2, &read); - read = (read & TEMP_STAT) >> 5; - if (read >= 0x1 && read <= 0x3) - val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; - else - val->intval = POWER_SUPPLY_HEALTH_GOOD; + 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 (pchg->pdata->get_batt_present) - val->intval = pchg->pdata->get_batt_present(); + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_present) + val->intval = pdata->get_batt_present(); break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - if (pchg->pdata->get_batt_level) - val->intval = pchg->pdata->get_batt_level(); + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_level) + val->intval = pdata->get_batt_level(); break; case POWER_SUPPLY_PROP_CAPACITY: - if (pchg->pdata->get_batt_capacity) - val->intval = pchg->pdata->get_batt_capacity(); + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_capacity) + val->intval = pdata->get_batt_capacity(); break; case POWER_SUPPLY_PROP_TEMP: - if (pchg->pdata->get_batt_temp) - val->intval = pchg->pdata->get_batt_temp(); + if (!pdata) + return -EINVAL; + + if (pdata->get_batt_temp) + val->intval = pdata->get_batt_temp(); break; default: break; @@ -343,16 +401,20 @@ static int lp8727_battery_get_property(struct power_supply *psy, 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; - u8 eoc_level, ichg; - - if (lp8727_is_charger_attached(psy->name, pchg->devid)) { - if (pchg->chg_parm) { - eoc_level = pchg->chg_parm->eoc_level; - ichg = pchg->chg_parm->ichg; - val = (ichg << 4) | eoc_level; - lp8727_i2c_write_byte(pchg, CHGCTRL2, &val); - } + + /* skip if no charger exists */ + if (!lp8727_is_charger_attached(psy->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); } } @@ -360,9 +422,9 @@ static int lp8727_register_psy(struct lp8727_chg *pchg) { struct lp8727_psy *psy; - psy = kzalloc(sizeof(*psy), GFP_KERNEL); + psy = devm_kzalloc(pchg->dev, sizeof(*psy), GFP_KERNEL); if (!psy) - goto err_mem; + return -ENOMEM; pchg->psy = psy; @@ -375,7 +437,7 @@ static int lp8727_register_psy(struct lp8727_chg *pchg) psy->ac.num_supplicants = ARRAY_SIZE(battery_supplied_to); if (power_supply_register(pchg->dev, &psy->ac)) - goto err_psy; + goto err_psy_ac; psy->usb.name = "usb"; psy->usb.type = POWER_SUPPLY_TYPE_USB; @@ -386,7 +448,7 @@ static int lp8727_register_psy(struct lp8727_chg *pchg) psy->usb.num_supplicants = ARRAY_SIZE(battery_supplied_to); if (power_supply_register(pchg->dev, &psy->usb)) - goto err_psy; + goto err_psy_usb; psy->batt.name = "main_batt"; psy->batt.type = POWER_SUPPLY_TYPE_BATTERY; @@ -396,14 +458,15 @@ static int lp8727_register_psy(struct lp8727_chg *pchg) psy->batt.external_power_changed = lp8727_charger_changed; if (power_supply_register(pchg->dev, &psy->batt)) - goto err_psy; + goto err_psy_batt; return 0; -err_mem: - return -ENOMEM; -err_psy: - kfree(psy); +err_psy_batt: + power_supply_unregister(&psy->usb); +err_psy_usb: + power_supply_unregister(&psy->ac); +err_psy_ac: return -EPERM; } @@ -417,9 +480,62 @@ static void lp8727_unregister_psy(struct lp8727_chg *pchg) power_supply_unregister(&psy->ac); power_supply_unregister(&psy->usb); power_supply_unregister(&psy->batt); - kfree(psy); } +#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 int 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; + + /* If charging parameter is not defined, just skip parsing the dt */ + if (of_get_child_count(np) == 0) + goto out; + + pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + of_property_read_u32(np, "debounce-ms", &pdata->debounce_msec); + + 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); + } + + dev->platform_data = pdata; +out: + return 0; +} +#else +static int lp8727_parse_dt(struct device *dev) +{ + return 0; +} +#endif + static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) { struct lp8727_chg *pchg; @@ -428,7 +544,13 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) if (!i2c_check_functionality(cl->adapter, I2C_FUNC_SMBUS_I2C_BLOCK)) return -EIO; - pchg = kzalloc(sizeof(*pchg), GFP_KERNEL); + if (cl->dev.of_node) { + ret = lp8727_parse_dt(&cl->dev); + if (ret) + return ret; + } + + pchg = devm_kzalloc(&cl->dev, sizeof(*pchg), GFP_KERNEL); if (!pchg) return -ENOMEM; @@ -439,57 +561,60 @@ static int lp8727_probe(struct i2c_client *cl, const struct i2c_device_id *id) mutex_init(&pchg->xfer_lock); - lp8727_init_device(pchg); - lp8727_intr_config(pchg); + 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, - "can not register power supplies. err=%d", ret); + 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 __devexit lp8727_remove(struct i2c_client *cl) +static int lp8727_remove(struct i2c_client *cl) { struct lp8727_chg *pchg = i2c_get_clientdata(cl); + lp8727_release_irq(pchg); lp8727_unregister_psy(pchg); - free_irq(pchg->client->irq, pchg); - flush_workqueue(pchg->irqthread); - destroy_workqueue(pchg->irqthread); - kfree(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 = __devexit_p(lp8727_remove), + .remove = lp8727_remove, .id_table = lp8727_ids, }; +module_i2c_driver(lp8727_driver); -static int __init lp8727_init(void) -{ - return i2c_add_driver(&lp8727_driver); -} - -static void __exit lp8727_exit(void) -{ - i2c_del_driver(&lp8727_driver); -} - -module_init(lp8727_init); -module_exit(lp8727_exit); - -MODULE_DESCRIPTION("National Semiconductor LP8727 charger driver"); -MODULE_AUTHOR - ("Woogyom Kim <milo.kim@ti.com>, Daniel Jeong <daniel.jeong@ti.com>"); +MODULE_DESCRIPTION("TI/National Semiconductor LP8727 charger driver"); +MODULE_AUTHOR("Milo Kim <milo.kim@ti.com>, Daniel Jeong <daniel.jeong@ti.com>"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c new file mode 100644 index 00000000000..ed49b50b220 --- /dev/null +++ b/drivers/power/lp8788-charger.c @@ -0,0 +1,751 @@ +/* + * TI LP8788 MFD - battery charger driver + * + * Copyright 2012 Texas Instruments + * + * Author: Milo(Woogyom) Kim <milo.kim@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ + +#include <linux/err.h> +#include <linux/iio/consumer.h> +#include <linux/interrupt.h> +#include <linux/irqdomain.h> +#include <linux/mfd/lp8788.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/workqueue.h> + +/* register address */ +#define LP8788_CHG_STATUS 0x07 +#define LP8788_CHG_IDCIN 0x13 +#define LP8788_CHG_IBATT 0x14 +#define LP8788_CHG_VTERM 0x15 +#define LP8788_CHG_EOC 0x16 + +/* mask/shift bits */ +#define LP8788_CHG_INPUT_STATE_M 0x03 /* Addr 07h */ +#define LP8788_CHG_STATE_M 0x3C +#define LP8788_CHG_STATE_S 2 +#define LP8788_NO_BATT_M BIT(6) +#define LP8788_BAD_BATT_M BIT(7) +#define LP8788_CHG_IBATT_M 0x1F /* Addr 14h */ +#define LP8788_CHG_VTERM_M 0x0F /* Addr 15h */ +#define LP8788_CHG_EOC_LEVEL_M 0x30 /* Addr 16h */ +#define LP8788_CHG_EOC_LEVEL_S 4 +#define LP8788_CHG_EOC_TIME_M 0x0E +#define LP8788_CHG_EOC_TIME_S 1 +#define LP8788_CHG_EOC_MODE_M BIT(0) + +#define LP8788_CHARGER_NAME "charger" +#define LP8788_BATTERY_NAME "main_batt" + +#define LP8788_CHG_START 0x11 +#define LP8788_CHG_END 0x1C + +#define LP8788_ISEL_MAX 23 +#define LP8788_ISEL_STEP 50 +#define LP8788_VTERM_MIN 4100 +#define LP8788_VTERM_STEP 25 +#define LP8788_MAX_BATT_CAPACITY 100 +#define LP8788_MAX_CHG_IRQS 11 + +enum lp8788_charging_state { + LP8788_OFF, + LP8788_WARM_UP, + LP8788_LOW_INPUT = 0x3, + LP8788_PRECHARGE, + LP8788_CC, + LP8788_CV, + LP8788_MAINTENANCE, + LP8788_BATTERY_FAULT, + LP8788_SYSTEM_SUPPORT = 0xC, + LP8788_HIGH_CURRENT = 0xF, + LP8788_MAX_CHG_STATE, +}; + +enum lp8788_charger_adc_sel { + LP8788_VBATT, + LP8788_BATT_TEMP, + LP8788_NUM_CHG_ADC, +}; + +enum lp8788_charger_input_state { + LP8788_SYSTEM_SUPPLY = 1, + LP8788_FULL_FUNCTION, +}; + +/* + * struct lp8788_chg_irq + * @which : lp8788 interrupt id + * @virq : Linux IRQ number from irq_domain + */ +struct lp8788_chg_irq { + enum lp8788_int_id which; + int virq; +}; + +/* + * struct lp8788_charger + * @lp : used for accessing the registers of mfd lp8788 device + * @charger : power supply driver for the battery charger + * @battery : power supply driver for the battery + * @charger_work : work queue for charger input interrupts + * @chan : iio channels for getting adc values + * eg) battery voltage, capacity and temperature + * @irqs : charger dedicated interrupts + * @num_irqs : total numbers of charger interrupts + * @pdata : charger platform specific data + */ +struct lp8788_charger { + struct lp8788 *lp; + struct power_supply charger; + struct power_supply battery; + struct work_struct charger_work; + struct iio_channel *chan[LP8788_NUM_CHG_ADC]; + struct lp8788_chg_irq irqs[LP8788_MAX_CHG_IRQS]; + int num_irqs; + struct lp8788_charger_platform_data *pdata; +}; + +static char *battery_supplied_to[] = { + LP8788_BATTERY_NAME, +}; + +static enum power_supply_property lp8788_charger_prop[] = { + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static enum power_supply_property lp8788_battery_prop[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX, + POWER_SUPPLY_PROP_TEMP, +}; + +static bool lp8788_is_charger_detected(struct lp8788_charger *pchg) +{ + u8 data; + + lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + data &= LP8788_CHG_INPUT_STATE_M; + + return data == LP8788_SYSTEM_SUPPLY || data == LP8788_FULL_FUNCTION; +} + +static int lp8788_charger_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8788_charger *pchg = dev_get_drvdata(psy->dev->parent); + u8 read; + + switch (psp) { + case POWER_SUPPLY_PROP_ONLINE: + val->intval = lp8788_is_charger_detected(pchg); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + lp8788_read_byte(pchg->lp, LP8788_CHG_IDCIN, &read); + val->intval = LP8788_ISEL_STEP * + (min_t(int, read, LP8788_ISEL_MAX) + 1); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int lp8788_get_battery_status(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + enum lp8788_charging_state state; + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; + switch (state) { + case LP8788_OFF: + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case LP8788_PRECHARGE: + case LP8788_CC: + case LP8788_CV: + case LP8788_HIGH_CURRENT: + val->intval = POWER_SUPPLY_STATUS_CHARGING; + break; + case LP8788_MAINTENANCE: + val->intval = POWER_SUPPLY_STATUS_FULL; + break; + default: + val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING; + break; + } + + return 0; +} + +static int lp8788_get_battery_health(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + if (data & LP8788_NO_BATT_M) + val->intval = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + else if (data & LP8788_BAD_BATT_M) + val->intval = POWER_SUPPLY_HEALTH_DEAD; + else + val->intval = POWER_SUPPLY_HEALTH_GOOD; + + return 0; +} + +static int lp8788_get_battery_present(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 data; + int ret; + + ret = lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + val->intval = !(data & LP8788_NO_BATT_M); + return 0; +} + +static int lp8788_get_vbatt_adc(struct lp8788_charger *pchg, int *result) +{ + struct iio_channel *channel = pchg->chan[LP8788_VBATT]; + + if (!channel) + return -EINVAL; + + return iio_read_channel_processed(channel, result); +} + +static int lp8788_get_battery_voltage(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + return lp8788_get_vbatt_adc(pchg, &val->intval); +} + +static int lp8788_get_battery_capacity(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + struct lp8788 *lp = pchg->lp; + struct lp8788_charger_platform_data *pdata = pchg->pdata; + unsigned int max_vbatt; + int vbatt; + enum lp8788_charging_state state; + u8 data; + int ret; + + if (!pdata) + return -EINVAL; + + max_vbatt = pdata->max_vbatt_mv; + if (max_vbatt == 0) + return -EINVAL; + + ret = lp8788_read_byte(lp, LP8788_CHG_STATUS, &data); + if (ret) + return ret; + + state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; + + if (state == LP8788_MAINTENANCE) { + val->intval = LP8788_MAX_BATT_CAPACITY; + } else { + ret = lp8788_get_vbatt_adc(pchg, &vbatt); + if (ret) + return ret; + + val->intval = (vbatt * LP8788_MAX_BATT_CAPACITY) / max_vbatt; + val->intval = min(val->intval, LP8788_MAX_BATT_CAPACITY); + } + + return 0; +} + +static int lp8788_get_battery_temperature(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + struct iio_channel *channel = pchg->chan[LP8788_BATT_TEMP]; + int result; + int ret; + + if (!channel) + return -EINVAL; + + ret = iio_read_channel_processed(channel, &result); + if (ret < 0) + return -EINVAL; + + /* unit: 0.1 'C */ + val->intval = result * 10; + + return 0; +} + +static int lp8788_get_battery_charging_current(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 read; + + lp8788_read_byte(pchg->lp, LP8788_CHG_IBATT, &read); + read &= LP8788_CHG_IBATT_M; + val->intval = LP8788_ISEL_STEP * + (min_t(int, read, LP8788_ISEL_MAX) + 1); + + return 0; +} + +static int lp8788_get_charging_termination_voltage(struct lp8788_charger *pchg, + union power_supply_propval *val) +{ + u8 read; + + lp8788_read_byte(pchg->lp, LP8788_CHG_VTERM, &read); + read &= LP8788_CHG_VTERM_M; + val->intval = LP8788_VTERM_MIN + LP8788_VTERM_STEP * read; + + return 0; +} + +static int lp8788_battery_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct lp8788_charger *pchg = dev_get_drvdata(psy->dev->parent); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + return lp8788_get_battery_status(pchg, val); + case POWER_SUPPLY_PROP_HEALTH: + return lp8788_get_battery_health(pchg, val); + case POWER_SUPPLY_PROP_PRESENT: + return lp8788_get_battery_present(pchg, val); + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + return lp8788_get_battery_voltage(pchg, val); + case POWER_SUPPLY_PROP_CAPACITY: + return lp8788_get_battery_capacity(pchg, val); + case POWER_SUPPLY_PROP_TEMP: + return lp8788_get_battery_temperature(pchg, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT: + return lp8788_get_battery_charging_current(pchg, val); + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE_MAX: + return lp8788_get_charging_termination_voltage(pchg, val); + default: + return -EINVAL; + } +} + +static inline bool lp8788_is_valid_charger_register(u8 addr) +{ + return addr >= LP8788_CHG_START && addr <= LP8788_CHG_END; +} + +static int lp8788_update_charger_params(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + struct lp8788 *lp = pchg->lp; + struct lp8788_charger_platform_data *pdata = pchg->pdata; + struct lp8788_chg_param *param; + int i; + int ret; + + if (!pdata || !pdata->chg_params) { + dev_info(&pdev->dev, "skip updating charger parameters\n"); + return 0; + } + + /* settting charging parameters */ + for (i = 0; i < pdata->num_chg_params; i++) { + param = pdata->chg_params + i; + + if (!param) + continue; + + if (lp8788_is_valid_charger_register(param->addr)) { + ret = lp8788_write_byte(lp, param->addr, param->val); + if (ret) + return ret; + } + } + + return 0; +} + +static int lp8788_psy_register(struct platform_device *pdev, + struct lp8788_charger *pchg) +{ + pchg->charger.name = LP8788_CHARGER_NAME; + pchg->charger.type = POWER_SUPPLY_TYPE_MAINS; + pchg->charger.properties = lp8788_charger_prop; + pchg->charger.num_properties = ARRAY_SIZE(lp8788_charger_prop); + pchg->charger.get_property = lp8788_charger_get_property; + pchg->charger.supplied_to = battery_supplied_to; + pchg->charger.num_supplicants = ARRAY_SIZE(battery_supplied_to); + + if (power_supply_register(&pdev->dev, &pchg->charger)) + return -EPERM; + + pchg->battery.name = LP8788_BATTERY_NAME; + pchg->battery.type = POWER_SUPPLY_TYPE_BATTERY; + pchg->battery.properties = lp8788_battery_prop; + pchg->battery.num_properties = ARRAY_SIZE(lp8788_battery_prop); + pchg->battery.get_property = lp8788_battery_get_property; + + if (power_supply_register(&pdev->dev, &pchg->battery)) + 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; + 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, + .owner = THIS_MODULE, + }, +}; +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/max14577_charger.c b/drivers/power/max14577_charger.c new file mode 100644 index 00000000000..fad2a75b360 --- /dev/null +++ b/drivers/power/max14577_charger.c @@ -0,0 +1,311 @@ +/* + * Battery charger driver for the Maxim 14577 + * + * Copyright (C) 2013 Samsung Electronics + * Krzysztof Kozlowski <k.kozlowski@samsung.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/max14577-private.h> + +struct max14577_charger { + struct device *dev; + struct max14577 *max14577; + struct power_supply charger; + + unsigned int charging_state; + unsigned int battery_state; +}; + +static int max14577_get_charger_state(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_STATUS_DISCHARGING; + 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/ + */ + max14577_read_reg(rmap, MAX14577_CHG_REG_CHG_CTRL2, ®_data); + if ((reg_data & CHGCTRL2_MBCHOSTEN_MASK) == 0) + goto state_set; + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_CGMBC_MASK) { + /* Charger or USB-cable is connected */ + if (reg_data & STATUS3_EOC_MASK) + state = POWER_SUPPLY_STATUS_FULL; + else + state = POWER_SUPPLY_STATUS_CHARGING; + goto state_set; + } + +state_set: + chg->charging_state = state; + return state; +} + +/* + * Supported charge types: + * - POWER_SUPPLY_CHARGE_TYPE_NONE + * - POWER_SUPPLY_CHARGE_TYPE_FAST + */ +static int max14577_get_charge_type(struct max14577_charger *chg) +{ + /* + * 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." + */ + if (max14577_get_charger_state(chg) == POWER_SUPPLY_STATUS_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int max14577_get_online(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + switch (reg_data) { + 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: + return 1; + case MAX14577_CHARGER_TYPE_NONE: + case MAX14577_CHARGER_TYPE_DOWNSTREAM_PORT: + case MAX14577_CHARGER_TYPE_RESERVED: + default: + 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) +{ + struct regmap *rmap = chg->max14577->regmap; + int state = POWER_SUPPLY_HEALTH_GOOD; + u8 reg_data; + + max14577_read_reg(rmap, MAX14577_MUIC_REG_STATUS2, ®_data); + reg_data = ((reg_data & STATUS2_CHGTYP_MASK) >> STATUS2_CHGTYP_SHIFT); + if (reg_data == MAX14577_CHARGER_TYPE_DEAD_BATTERY) { + state = POWER_SUPPLY_HEALTH_DEAD; + goto state_set; + } + + max14577_read_reg(rmap, MAX14577_CHG_REG_STATUS3, ®_data); + if (reg_data & STATUS3_OVP_MASK) { + state = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + goto state_set; + } + +state_set: + chg->battery_state = state; + return state; +} + +/* + * 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) +{ + return 1; +} + +/* + * 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 void max14577_charger_reg_init(struct max14577_charger *chg) +{ + struct regmap *rmap = chg->max14577->regmap; + u8 reg_data; + + /* + * 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); + + /* Battery Fast-Charge Timer, from SM-V700: 6hrs */ + reg_data = 0x3 << CHGCTRL1_TCHW_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL1, 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); + + /* Battery-Charger Constant Voltage (CV) Mode, from SM-V700: 4.35V */ + reg_data = 0xf << CHGCTRL3_MBCCVWRC_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL3, reg_data); + + /* + * Fast Battery-Charge Current Low, default 200-950mA + * Fast Battery-Charge Current High, from SM-V700: 450mA + */ + reg_data = 0x1 << CHGCTRL4_MBCICHWRCL_SHIFT; + reg_data |= 0x5 << CHGCTRL4_MBCICHWRCH_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL4, reg_data); + + /* End-of-Charge Current, from SM-V700: 50mA */ + reg_data = 0x0 << CHGCTRL5_EOCS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL5, reg_data); + + /* Auto Charging Stop, default off */ + reg_data = 0x0 << CHGCTRL6_AUTOSTOP_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL6, reg_data); + + /* Overvoltage-Protection Threshold, from SM-V700: 6.5V */ + reg_data = 0x2 << CHGCTRL7_OTPCGHCVS_SHIFT; + max14577_write_reg(rmap, MAX14577_REG_CHGCTRL7, reg_data); +} + +/* 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 *model_name = "MAX14577"; +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 = container_of(psy, + struct max14577_charger, + charger); + int ret = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = max14577_get_charger_state(chg); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = max14577_get_charge_type(chg); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = max14577_get_battery_health(chg); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = max14577_get_present(chg); + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = max14577_get_online(chg); + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = model_name; + break; + case POWER_SUPPLY_PROP_MANUFACTURER: + val->strval = manufacturer; + break; + default: + return -EINVAL; + } + + return ret; +} + +static int max14577_charger_probe(struct platform_device *pdev) +{ + struct max14577_charger *chg; + 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; + + max14577_charger_reg_init(chg); + + chg->charger.name = "max14577-charger", + chg->charger.type = POWER_SUPPLY_TYPE_BATTERY, + chg->charger.properties = max14577_charger_props, + chg->charger.num_properties = ARRAY_SIZE(max14577_charger_props), + chg->charger.get_property = max14577_charger_get_property, + + ret = power_supply_register(&pdev->dev, &chg->charger); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return ret; + } + + return 0; +} + +static int max14577_charger_remove(struct platform_device *pdev) +{ + struct max14577_charger *chg = platform_get_drvdata(pdev); + + power_supply_unregister(&chg->charger); + + return 0; +} + +static struct platform_driver max14577_charger_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "max14577-charger", + }, + .probe = max14577_charger_probe, + .remove = max14577_charger_remove, +}; +module_platform_driver(max14577_charger_driver); + +MODULE_AUTHOR("Krzysztof Kozlowski <k.kozlowski@samsung.com>"); +MODULE_DESCRIPTION("MAXIM 14577 charger driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 2f2f9a6f54f..0fbac861080 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -148,7 +148,7 @@ static void max17040_get_online(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - if (chip->pdata->battery_online) + if (chip->pdata && chip->pdata->battery_online) chip->online = chip->pdata->battery_online(); else chip->online = 1; @@ -158,7 +158,8 @@ static void max17040_get_status(struct i2c_client *client) { struct max17040_chip *chip = i2c_get_clientdata(client); - if (!chip->pdata->charger_online || !chip->pdata->charger_enable) { + if (!chip->pdata || !chip->pdata->charger_online + || !chip->pdata->charger_enable) { chip->status = POWER_SUPPLY_STATUS_UNKNOWN; return; } @@ -197,7 +198,7 @@ static enum power_supply_property max17040_battery_props[] = { POWER_SUPPLY_PROP_CAPACITY, }; -static int __devinit max17040_probe(struct i2c_client *client, +static int max17040_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); @@ -207,7 +208,7 @@ static int __devinit max17040_probe(struct i2c_client *client, if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE)) return -EIO; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; @@ -225,54 +226,55 @@ static int __devinit max17040_probe(struct i2c_client *client, ret = power_supply_register(&client->dev, &chip->battery); if (ret) { dev_err(&client->dev, "failed: power supply register\n"); - kfree(chip); return ret; } max17040_reset(client); max17040_get_version(client); - INIT_DELAYED_WORK_DEFERRABLE(&chip->work, max17040_work); + INIT_DEFERRABLE_WORK(&chip->work, max17040_work); schedule_delayed_work(&chip->work, MAX17040_DELAY); return 0; } -static int __devexit max17040_remove(struct i2c_client *client) +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); - kfree(chip); return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP -static int max17040_suspend(struct i2c_client *client, - pm_message_t state) +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 i2c_client *client) +static int max17040_resume(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct max17040_chip *chip = i2c_get_clientdata(client); schedule_delayed_work(&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_suspend NULL -#define max17040_resume NULL +#define MAX17040_PM_OPS NULL -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM_SLEEP */ static const struct i2c_device_id max17040_id[] = { { "max17040", 0 }, @@ -283,25 +285,13 @@ MODULE_DEVICE_TABLE(i2c, max17040_id); static struct i2c_driver max17040_i2c_driver = { .driver = { .name = "max17040", + .pm = MAX17040_PM_OPS, }, .probe = max17040_probe, - .remove = __devexit_p(max17040_remove), - .suspend = max17040_suspend, - .resume = max17040_resume, + .remove = max17040_remove, .id_table = max17040_id, }; - -static int __init max17040_init(void) -{ - return i2c_add_driver(&max17040_i2c_driver); -} -module_init(max17040_init); - -static void __exit max17040_exit(void) -{ - i2c_del_driver(&max17040_i2c_driver); -} -module_exit(max17040_exit); +module_i2c_driver(max17040_i2c_driver); MODULE_AUTHOR("Minkyu Kang <mk7.kang@samsung.com>"); MODULE_DESCRIPTION("MAX17040 Fuel Gauge"); diff --git a/drivers/power/max17042_battery.c b/drivers/power/max17042_battery.c index 86acee2f988..66da691c41c 100644 --- a/drivers/power/max17042_battery.c +++ b/drivers/power/max17042_battery.c @@ -26,45 +26,56 @@ #include <linux/module.h> #include <linux/slab.h> #include <linux/i2c.h> +#include <linux/delay.h> +#include <linux/interrupt.h> +#include <linux/pm.h> #include <linux/mod_devicetable.h> #include <linux/power_supply.h> #include <linux/power/max17042_battery.h> +#include <linux/of.h> +#include <linux/regmap.h> + +/* Status register bits */ +#define STATUS_POR_BIT (1 << 1) +#define STATUS_BST_BIT (1 << 3) +#define STATUS_VMN_BIT (1 << 8) +#define STATUS_TMN_BIT (1 << 9) +#define STATUS_SMN_BIT (1 << 10) +#define STATUS_BI_BIT (1 << 11) +#define STATUS_VMX_BIT (1 << 12) +#define STATUS_TMX_BIT (1 << 13) +#define STATUS_SMX_BIT (1 << 14) +#define STATUS_BR_BIT (1 << 15) + +/* Interrupt mask bits */ +#define CONFIG_ALRT_BIT_ENBL (1 << 2) +#define STATUS_INTR_SOCMIN_BIT (1 << 10) +#define STATUS_INTR_SOCMAX_BIT (1 << 14) + +#define VFSOC0_LOCK 0x0000 +#define VFSOC0_UNLOCK 0x0080 +#define MODEL_UNLOCK1 0X0059 +#define MODEL_UNLOCK2 0X00C4 +#define MODEL_LOCK1 0X0000 +#define MODEL_LOCK2 0X0000 + +#define dQ_ACC_DIV 0x4 +#define dP_ACC_100 0x1900 +#define dP_ACC_200 0x3200 + +#define MAX17042_IC_VERSION 0x0092 +#define MAX17047_IC_VERSION 0x00AC /* same for max17050 */ 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 int max17042_write_reg(struct i2c_client *client, u8 reg, u16 value) -{ - int ret = i2c_smbus_write_word_data(client, reg, value); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static int max17042_read_reg(struct i2c_client *client, u8 reg) -{ - int ret = i2c_smbus_read_word_data(client, reg); - - if (ret < 0) - dev_err(&client->dev, "%s: err %d\n", __func__, ret); - - return ret; -} - -static void max17042_set_reg(struct i2c_client *client, - struct max17042_reg_data *data, int size) -{ - int i; - - for (i = 0; i < size; i++) - max17042_write_reg(client, data[i].addr, data[i].data); -} - static enum power_supply_property max17042_battery_props[] = { POWER_SUPPLY_PROP_PRESENT, POWER_SUPPLY_PROP_CYCLE_COUNT, @@ -72,8 +83,10 @@ static enum power_supply_property max17042_battery_props[] = { 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_CURRENT_NOW, POWER_SUPPLY_PROP_CURRENT_AVG, @@ -85,79 +98,98 @@ static int max17042_get_property(struct power_supply *psy, { struct max17042_chip *chip = container_of(psy, struct max17042_chip, battery); + struct regmap *map = chip->regmap; int ret; + u32 data; + + if (!chip->init_complete) + return -EAGAIN; switch (psp) { case POWER_SUPPLY_PROP_PRESENT: - ret = max17042_read_reg(chip->client, MAX17042_STATUS); + ret = regmap_read(map, MAX17042_STATUS, &data); if (ret < 0) return ret; - if (ret & MAX17042_STATUS_BattAbsent) + if (data & MAX17042_STATUS_BattAbsent) val->intval = 0; else val->intval = 1; break; case POWER_SUPPLY_PROP_CYCLE_COUNT: - ret = max17042_read_reg(chip->client, MAX17042_Cycles); + ret = regmap_read(map, MAX17042_Cycles, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; break; case POWER_SUPPLY_PROP_VOLTAGE_MAX: - ret = max17042_read_reg(chip->client, MAX17042_MinMaxVolt); + ret = regmap_read(map, MAX17042_MinMaxVolt, &data); if (ret < 0) return ret; - val->intval = ret >> 8; + val->intval = data >> 8; val->intval *= 20000; /* Units of LSB = 20mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: - ret = max17042_read_reg(chip->client, MAX17042_V_empty); + if (chip->chip_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 = ret >> 7; + val->intval = data >> 7; val->intval *= 10000; /* Units of LSB = 10mV */ break; case POWER_SUPPLY_PROP_VOLTAGE_NOW: - ret = max17042_read_reg(chip->client, MAX17042_VCELL); + ret = regmap_read(map, MAX17042_VCELL, &data); if (ret < 0) return ret; - val->intval = ret * 625 / 8; + val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_VOLTAGE_AVG: - ret = max17042_read_reg(chip->client, MAX17042_AvgVCELL); + 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 = ret * 625 / 8; + val->intval = data * 625 / 8; break; case POWER_SUPPLY_PROP_CAPACITY: - ret = max17042_read_reg(chip->client, MAX17042_SOC); + ret = regmap_read(map, MAX17042_RepSOC, &data); if (ret < 0) return ret; - val->intval = ret >> 8; + val->intval = data >> 8; break; case POWER_SUPPLY_PROP_CHARGE_FULL: - ret = max17042_read_reg(chip->client, MAX17042_RepSOC); + ret = regmap_read(map, MAX17042_FullCAP, &data); if (ret < 0) return ret; - if ((ret >> 8) >= MAX17042_BATTERY_FULL) - val->intval = 1; - else if (ret >= 0) - val->intval = 0; + 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_read_reg(chip->client, MAX17042_TEMP); + ret = regmap_read(map, MAX17042_TEMP, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; /* The value is signed. */ if (val->intval & 0x8000) { val->intval = (0x7fff & ~val->intval) + 1; @@ -169,11 +201,11 @@ static int max17042_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CURRENT_NOW: if (chip->pdata->enable_current_sense) { - ret = max17042_read_reg(chip->client, MAX17042_Current); + ret = regmap_read(map, MAX17042_Current, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; if (val->intval & 0x8000) { /* Negative */ val->intval = ~val->intval & 0x7fff; @@ -187,12 +219,11 @@ static int max17042_get_property(struct power_supply *psy, break; case POWER_SUPPLY_PROP_CURRENT_AVG: if (chip->pdata->enable_current_sense) { - ret = max17042_read_reg(chip->client, - MAX17042_AvgCurrent); + ret = regmap_read(map, MAX17042_AvgCurrent, &data); if (ret < 0) return ret; - val->intval = ret; + val->intval = data; if (val->intval & 0x8000) { /* Negative */ val->intval = ~val->intval & 0x7fff; @@ -210,26 +241,473 @@ static int max17042_get_property(struct power_supply *psy, return 0; } -static int __devinit max17042_probe(struct i2c_client *client, +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 == MAX17047) + 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 == 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 == 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 == 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; + int val; + + 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_read(map, MAX17042_STATUS, &val); + regmap_write(map, MAX17042_STATUS, val & (~STATUS_POR_BIT)); + 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; + } + + return pdata; +} +#else +static struct max17042_platform_data * +max17042_get_pdata(struct device *dev) +{ + return dev->platform_data; +} +#endif + +static struct regmap_config max17042_regmap_config = { + .reg_bits = 8, + .val_bits = 16, + .val_format_endian = REGMAP_ENDIAN_NATIVE, +}; + +static int max17042_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct i2c_adapter *adapter = to_i2c_adapter(client->dev.parent); struct max17042_chip *chip; int ret; + int i; + u32 val; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_WORD_DATA)) return -EIO; - chip = kzalloc(sizeof(*chip), GFP_KERNEL); + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); if (!chip) return -ENOMEM; chip->client = client; - chip->pdata = client->dev.platform_data; + 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->battery.name = "max17042_battery"; + regmap_read(chip->regmap, MAX17042_DevName, &val); + if (val == MAX17042_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17042 detected\n"); + chip->chip_type = MAX17042; + } else if (val == MAX17047_IC_VERSION) { + dev_dbg(&client->dev, "chip type max17047/50 detected\n"); + chip->chip_type = MAX17047; + } else { + dev_err(&client->dev, "device version mismatch: %x\n", val); + return -EIO; + } + + chip->battery.name = "max170xx_battery"; chip->battery.type = POWER_SUPPLY_TYPE_BATTERY; chip->battery.get_property = max17042_get_property; chip->battery.properties = max17042_battery_props; @@ -243,38 +721,111 @@ static int __devinit max17042_probe(struct i2c_client *client, 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); + } + ret = power_supply_register(&client->dev, &chip->battery); if (ret) { dev_err(&client->dev, "failed: power supply register\n"); - kfree(chip); return ret; } - /* Initialize registers according to values from the platform data */ - if (chip->pdata->init_data) - max17042_set_reg(client, chip->pdata->init_data, - chip->pdata->num_init_data); + if (client->irq) { + ret = request_threaded_irq(client->irq, NULL, + max17042_thread_handler, + IRQF_TRIGGER_FALLING | IRQF_ONESHOT, + chip->battery.name, chip); + if (!ret) { + regmap_read(chip->regmap, MAX17042_CONFIG, &val); + val |= CONFIG_ALRT_BIT_ENBL; + regmap_write(chip->regmap, MAX17042_CONFIG, val); + max17042_set_soc_threshold(chip, 1); + } else { + client->irq = 0; + dev_err(&client->dev, "%s(): cannot get IRQ\n", + __func__); + } + } - if (!chip->pdata->enable_current_sense) { - max17042_write_reg(client, MAX17042_CGAIN, 0x0000); - max17042_write_reg(client, MAX17042_MiscCFG, 0x0003); - max17042_write_reg(client, MAX17042_LearnCFG, 0x0007); + 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; } -static int __devexit max17042_remove(struct i2c_client *client) +static int max17042_remove(struct i2c_client *client) { struct max17042_chip *chip = i2c_get_clientdata(client); + if (client->irq) + free_irq(client->irq, chip); power_supply_unregister(&chip->battery); - kfree(chip); 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", 0 }, + { "max17047", 1 }, + { "max17050", 2 }, { } }; MODULE_DEVICE_TABLE(i2c, max17042_id); @@ -282,23 +833,14 @@ 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, - .remove = __devexit_p(max17042_remove), + .remove = max17042_remove, .id_table = max17042_id, }; - -static int __init max17042_init(void) -{ - return i2c_add_driver(&max17042_i2c_driver); -} -module_init(max17042_init); - -static void __exit max17042_exit(void) -{ - i2c_del_driver(&max17042_i2c_driver); -} -module_exit(max17042_exit); +module_i2c_driver(max17042_i2c_driver); MODULE_AUTHOR("MyungJoo Ham <myungjoo.ham@samsung.com>"); MODULE_DESCRIPTION("MAX17042 Fuel Gauge"); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c index 3e23f43e98a..08f0d7909b6 100644 --- a/drivers/power/max8903_charger.c +++ b/drivers/power/max8903_charger.c @@ -179,7 +179,7 @@ static irqreturn_t max8903_fault(int irq, void *_data) return IRQ_HANDLED; } -static __devinit int max8903_probe(struct platform_device *pdev) +static int max8903_probe(struct platform_device *pdev) { struct max8903_data *data; struct device *dev = &pdev->dev; @@ -189,7 +189,7 @@ static __devinit int max8903_probe(struct platform_device *pdev) int ta_in = 0; int usb_in = 0; - data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); if (data == NULL) { dev_err(dev, "Cannot allocate memory.\n"); return -ENOMEM; @@ -341,11 +341,10 @@ err_dc_irq: err_psy: power_supply_unregister(&data->psy); err: - kfree(data); return ret; } -static __devexit int max8903_remove(struct platform_device *pdev) +static int max8903_remove(struct platform_device *pdev) { struct max8903_data *data = platform_get_drvdata(pdev); @@ -359,7 +358,6 @@ static __devexit int max8903_remove(struct platform_device *pdev) if (pdata->dc_valid) free_irq(gpio_to_irq(pdata->dok), data); power_supply_unregister(&data->psy); - kfree(data); } return 0; @@ -367,7 +365,7 @@ static __devexit int max8903_remove(struct platform_device *pdev) static struct platform_driver max8903_driver = { .probe = max8903_probe, - .remove = __devexit_p(max8903_remove), + .remove = max8903_remove, .driver = { .name = "max8903-charger", .owner = THIS_MODULE, diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index daa333bd7eb..b4513f284bb 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -12,6 +12,7 @@ #include <linux/module.h> #include <linux/err.h> #include <linux/slab.h> +#include <linux/of.h> #include <linux/i2c.h> #include <linux/interrupt.h> #include <linux/platform_device.h> @@ -356,7 +357,7 @@ do { \ _irq, ret); \ } while (0) -static __devinit int max8925_init_charger(struct max8925_chip *chip, +static int max8925_init_charger(struct max8925_chip *chip, struct max8925_power_info *info) { int ret; @@ -414,7 +415,7 @@ static __devinit int max8925_init_charger(struct max8925_chip *chip, return 0; } -static __devexit int max8925_deinit_charger(struct max8925_power_info *info) +static int max8925_deinit_charger(struct max8925_power_info *info) { struct max8925_chip *chip = info->chip; int irq; @@ -426,21 +427,71 @@ static __devexit int max8925_deinit_charger(struct max8925_power_info *info) return 0; } -static __devinit int max8925_power_probe(struct platform_device *pdev) +#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_find_node_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); + + 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); + of_node_put(np); + + 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; + + 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 max8925_power_pdata *pdata = NULL; struct max8925_power_info *info; int ret; - pdata = pdev->dev.platform_data; + pdata = max8925_power_dt_init(pdev); if (!pdata) { dev_err(&pdev->dev, "platform data isn't assigned to " "power supply\n"); return -EINVAL; } - info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL); + info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info), + GFP_KERNEL); if (!info) return -ENOMEM; info->chip = chip; @@ -497,11 +548,10 @@ out_battery: out_usb: power_supply_unregister(&info->ac); out: - kfree(info); return ret; } -static __devexit int max8925_power_remove(struct platform_device *pdev) +static int max8925_power_remove(struct platform_device *pdev) { struct max8925_power_info *info = platform_get_drvdata(pdev); @@ -510,14 +560,13 @@ static __devexit int max8925_power_remove(struct platform_device *pdev) power_supply_unregister(&info->usb); power_supply_unregister(&info->battery); max8925_deinit_charger(info); - kfree(info); } return 0; } static struct platform_driver max8925_power_driver = { .probe = max8925_power_probe, - .remove = __devexit_p(max8925_power_remove), + .remove = max8925_power_remove, .driver = { .name = "max8925-power", }, diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c index 6e88c5d026b..4bdedfed936 100644 --- a/drivers/power/max8997_charger.c +++ b/drivers/power/max8997_charger.c @@ -86,7 +86,7 @@ static int max8997_battery_get_property(struct power_supply *psy, return 0; } -static __devinit int max8997_battery_probe(struct platform_device *pdev) +static int max8997_battery_probe(struct platform_device *pdev) { int ret = 0; struct charger_data *charger; @@ -138,7 +138,8 @@ static __devinit int max8997_battery_probe(struct platform_device *pdev) return ret; } - charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL); + charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data), + GFP_KERNEL); if (charger == NULL) { dev_err(&pdev->dev, "Cannot allocate memory.\n"); return -ENOMEM; @@ -158,21 +159,17 @@ static __devinit int max8997_battery_probe(struct platform_device *pdev) ret = power_supply_register(&pdev->dev, &charger->battery); if (ret) { dev_err(&pdev->dev, "failed: power supply register\n"); - goto err; + return ret; } return 0; -err: - kfree(charger); - return ret; } -static int __devexit max8997_battery_remove(struct platform_device *pdev) +static int max8997_battery_remove(struct platform_device *pdev) { struct charger_data *charger = platform_get_drvdata(pdev); power_supply_unregister(&charger->battery); - kfree(charger); return 0; } @@ -187,7 +184,7 @@ static struct platform_driver max8997_battery_driver = { .owner = THIS_MODULE, }, .probe = max8997_battery_probe, - .remove = __devexit_p(max8997_battery_remove), + .remove = max8997_battery_remove, .id_table = max8997_battery_id, }; diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c index 9b3f2bf56e7..5017470c2fc 100644 --- a/drivers/power/max8998_charger.c +++ b/drivers/power/max8998_charger.c @@ -19,7 +19,6 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ -#include <linux/module.h> #include <linux/err.h> #include <linux/module.h> #include <linux/slab.h> @@ -76,7 +75,7 @@ static int max8998_battery_get_property(struct power_supply *psy, return 0; } -static __devinit int max8998_battery_probe(struct platform_device *pdev) +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); @@ -89,7 +88,8 @@ static __devinit int max8998_battery_probe(struct platform_device *pdev) return -ENODEV; } - max8998 = kzalloc(sizeof(struct max8998_battery_data), GFP_KERNEL); + max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data), + GFP_KERNEL); if (!max8998) return -ENOMEM; @@ -175,16 +175,14 @@ static __devinit int max8998_battery_probe(struct platform_device *pdev) return 0; err: - kfree(max8998); return ret; } -static int __devexit max8998_battery_remove(struct platform_device *pdev) +static int max8998_battery_remove(struct platform_device *pdev) { struct max8998_battery_data *max8998 = platform_get_drvdata(pdev); power_supply_unregister(&max8998->battery); - kfree(max8998); return 0; } @@ -200,7 +198,7 @@ static struct platform_driver max8998_battery_driver = { .owner = THIS_MODULE, }, .probe = max8998_battery_probe, - .remove = __devexit_p(max8998_battery_remove), + .remove = max8998_battery_remove, .id_table = max8998_battery_id, }; diff --git a/drivers/power/olpc_battery.c b/drivers/power/olpc_battery.c index 7385092f9bc..1ec810ada5e 100644 --- a/drivers/power/olpc_battery.c +++ b/drivers/power/olpc_battery.c @@ -17,6 +17,7 @@ #include <linux/power_supply.h> #include <linux/jiffies.h> #include <linux/sched.h> +#include <linux/olpc-ec.h> #include <asm/olpc.h> @@ -231,11 +232,9 @@ static int olpc_bat_get_charge_full_design(union power_supply_propval *val) case POWER_SUPPLY_TECHNOLOGY_LiFe: switch (mfr) { - case 1: /* Gold Peak */ - val->intval = 2800000; - break; + case 1: /* Gold Peak, fall through */ case 2: /* BYD */ - val->intval = 3100000; + val->intval = 2800000; break; default: return -EIO; @@ -267,6 +266,55 @@ static int olpc_bat_get_charge_now(union power_supply_propval *val) 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 *********************************************************************/ @@ -401,6 +449,11 @@ static int olpc_bat_get_property(struct power_supply *psy, 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; @@ -428,6 +481,7 @@ static enum power_supply_property olpc_xo1_bat_props[] = { 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 */ @@ -449,6 +503,7 @@ static enum power_supply_property olpc_xo15_bat_props[] = { 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 */ @@ -543,7 +598,7 @@ static int olpc_battery_suspend(struct platform_device *pdev, return 0; } -static int __devinit olpc_battery_probe(struct platform_device *pdev) +static int olpc_battery_probe(struct platform_device *pdev) { int ret; uint8_t status; @@ -604,7 +659,7 @@ battery_failed: return ret; } -static int __devexit olpc_battery_remove(struct platform_device *pdev) +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); @@ -613,7 +668,7 @@ static int __devexit olpc_battery_remove(struct platform_device *pdev) return 0; } -static const struct of_device_id olpc_battery_ids[] __devinitconst = { +static const struct of_device_id olpc_battery_ids[] = { { .compatible = "olpc,xo1-battery" }, {} }; @@ -626,7 +681,7 @@ static struct platform_driver olpc_battery_driver = { .of_match_table = olpc_battery_ids, }, .probe = olpc_battery_probe, - .remove = __devexit_p(olpc_battery_remove), + .remove = olpc_battery_remove, .suspend = olpc_battery_suspend, }; diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index 3d1e9efb6f5..771c4f0fb8a 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -191,9 +191,9 @@ static ssize_t set_usblim(struct device *dev, unsigned long ma; int ret; - ret = strict_strtoul(buf, 10, &ma); + ret = kstrtoul(buf, 10, &ma); if (ret) - return -EINVAL; + return ret; pcf50633_mbc_usb_curlim_set(mbc->pcf, ma); @@ -228,9 +228,9 @@ static ssize_t set_chglim(struct device *dev, if (!mbc->pcf->pdata->charger_reference_current_ma) return -ENODEV; - ret = strict_strtoul(buf, 10, &ma); + ret = kstrtoul(buf, 10, &ma); if (ret) - return -EINVAL; + return ret; mbcc5 = (ma << 8) / mbc->pcf->pdata->charger_reference_current_ma; if (mbcc5 > 255) @@ -366,14 +366,14 @@ static const u8 mbc_irq_handlers[] = { PCF50633_IRQ_LOWBAT, }; -static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) +static int pcf50633_mbc_probe(struct platform_device *pdev) { struct pcf50633_mbc *mbc; int ret; int i; u8 mbcs1; - mbc = kzalloc(sizeof(*mbc), GFP_KERNEL); + mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL); if (!mbc) return -ENOMEM; @@ -413,7 +413,6 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) ret = power_supply_register(&pdev->dev, &mbc->adapter); if (ret) { dev_err(mbc->pcf->dev, "failed to register adapter\n"); - kfree(mbc); return ret; } @@ -421,7 +420,6 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) if (ret) { dev_err(mbc->pcf->dev, "failed to register usb\n"); power_supply_unregister(&mbc->adapter); - kfree(mbc); return ret; } @@ -430,7 +428,6 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) dev_err(mbc->pcf->dev, "failed to register ac\n"); power_supply_unregister(&mbc->adapter); power_supply_unregister(&mbc->usb); - kfree(mbc); return ret; } @@ -447,7 +444,7 @@ static int __devinit pcf50633_mbc_probe(struct platform_device *pdev) return 0; } -static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) +static int pcf50633_mbc_remove(struct platform_device *pdev) { struct pcf50633_mbc *mbc = platform_get_drvdata(pdev); int i; @@ -461,8 +458,6 @@ static int __devexit pcf50633_mbc_remove(struct platform_device *pdev) power_supply_unregister(&mbc->adapter); power_supply_unregister(&mbc->ac); - kfree(mbc); - return 0; } @@ -471,7 +466,7 @@ static struct platform_driver pcf50633_mbc_driver = { .name = "pcf50633-mbc", }, .probe = pcf50633_mbc_probe, - .remove = __devexit_p(pcf50633_mbc_remove), + .remove = pcf50633_mbc_remove, }; module_platform_driver(pcf50633_mbc_driver); diff --git a/drivers/power/pda_power.c b/drivers/power/pda_power.c index fd49689738a..0c52e2a0d90 100644 --- a/drivers/power/pda_power.c +++ b/drivers/power/pda_power.c @@ -24,11 +24,7 @@ static inline unsigned int get_irq_flags(struct resource *res) { - unsigned int flags = IRQF_SAMPLE_RANDOM | IRQF_SHARED; - - flags |= res->flags & IRQF_TRIGGER_MASK; - - return flags; + return IRQF_SHARED | (res->flags & IRQF_TRIGGER_MASK); } static struct device *dev; @@ -39,8 +35,8 @@ static struct timer_list supply_timer; static struct timer_list polling_timer; static int polling; -#ifdef CONFIG_USB_OTG_UTILS -static struct otg_transceiver *transceiver; +#if IS_ENABLED(CONFIG_USB_PHY) +static struct usb_phy *transceiver; static struct notifier_block otg_nb; #endif @@ -134,13 +130,13 @@ static void update_charger(void) regulator_set_current_limit(ac_draw, max_uA, max_uA); if (!regulator_enabled) { dev_dbg(dev, "charger on (AC)\n"); - regulator_enable(ac_draw); + WARN_ON(regulator_enable(ac_draw)); regulator_enabled = 1; } } else { if (regulator_enabled) { dev_dbg(dev, "charger off\n"); - regulator_disable(ac_draw); + WARN_ON(regulator_disable(ac_draw)); regulator_enabled = 0; } } @@ -222,7 +218,7 @@ static void polling_timer_func(unsigned long unused) jiffies + msecs_to_jiffies(pdata->polling_interval)); } -#ifdef CONFIG_USB_OTG_UTILS +#if IS_ENABLED(CONFIG_USB_PHY) static int otg_is_usb_online(void) { return (transceiver->last_event == USB_EVENT_VBUS || @@ -285,6 +281,12 @@ static int pda_power_probe(struct platform_device *pdev) 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(); @@ -313,20 +315,13 @@ static int pda_power_probe(struct platform_device *pdev) pda_psy_usb.num_supplicants = pdata->num_supplicants; } - 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; - ret = PTR_ERR(ac_draw); - } - -#ifdef CONFIG_USB_OTG_UTILS - transceiver = otg_get_transceiver(); - if (transceiver && !pdata->is_usb_online) { - pdata->is_usb_online = otg_is_usb_online; - } - if (transceiver && !pdata->is_ac_online) { - pdata->is_ac_online = otg_is_ac_online; +#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 @@ -372,10 +367,10 @@ static int pda_power_probe(struct platform_device *pdev) } } -#ifdef CONFIG_USB_OTG_UTILS - if (transceiver && pdata->use_otg_notifier) { +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver) && pdata->use_otg_notifier) { otg_nb.notifier_call = otg_handle_notification; - ret = otg_register_notifier(transceiver, &otg_nb); + ret = usb_register_notifier(transceiver, &otg_nb); if (ret) { dev_err(dev, "failure to register otg notifier\n"); goto otg_reg_notifier_failed; @@ -396,7 +391,7 @@ static int pda_power_probe(struct platform_device *pdev) return 0; -#ifdef CONFIG_USB_OTG_UTILS +#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); @@ -407,9 +402,9 @@ usb_irq_failed: usb_supply_failed: if (pdata->is_ac_online && ac_irq) free_irq(ac_irq->start, &pda_psy_ac); -#ifdef CONFIG_USB_OTG_UTILS - if (transceiver) - otg_put_transceiver(transceiver); +#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) @@ -442,9 +437,9 @@ static int pda_power_remove(struct platform_device *pdev) power_supply_unregister(&pda_psy_usb); if (pdata->is_ac_online) power_supply_unregister(&pda_psy_ac); -#ifdef CONFIG_USB_OTG_UTILS - if (transceiver) - otg_put_transceiver(transceiver); +#if IS_ENABLED(CONFIG_USB_PHY) + if (!IS_ERR_OR_NULL(transceiver)) + usb_put_phy(transceiver); #endif if (ac_draw) { regulator_put(ac_draw); diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c new file mode 100644 index 00000000000..62c15af58c9 --- /dev/null +++ b/drivers/power/pm2301_charger.c @@ -0,0 +1,1269 @@ +/* + * Copyright 2012 ST Ericsson. + * + * Power supply driver for ST Ericsson pm2xxx_charger charger + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/init.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/regulator/consumer.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/workqueue.h> +#include <linux/mfd/abx500/ab8500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ux500_chargalg.h> +#include <linux/pm2301_charger.h> +#include <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/pm.h> + +#include "pm2301_charger.h" + +#define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ + struct pm2xxx_charger, ac_chg) +#define SLEEP_MIN 50 +#define SLEEP_MAX 100 +#define PM2XXX_AUTOSUSPEND_DELAY 500 + +static int pm2xxx_interrupt_registers[] = { + PM2XXX_REG_INT1, + PM2XXX_REG_INT2, + PM2XXX_REG_INT3, + PM2XXX_REG_INT4, + PM2XXX_REG_INT5, + PM2XXX_REG_INT6, +}; + +static enum power_supply_property pm2xxx_charger_ac_props[] = { + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_VOLTAGE_AVG, +}; + +static int pm2xxx_charger_voltage_map[] = { + 3500, + 3525, + 3550, + 3575, + 3600, + 3625, + 3650, + 3675, + 3700, + 3725, + 3750, + 3775, + 3800, + 3825, + 3850, + 3875, + 3900, + 3925, + 3950, + 3975, + 4000, + 4025, + 4050, + 4075, + 4100, + 4125, + 4150, + 4175, + 4200, + 4225, + 4250, + 4275, + 4300, +}; + +static int pm2xxx_charger_current_map[] = { + 200, + 200, + 400, + 600, + 800, + 1000, + 1200, + 1400, + 1600, + 1800, + 2000, + 2200, + 2400, + 2600, + 2800, + 3000, +}; + +static const struct i2c_device_id pm2xxx_ident[] = { + { "pm2301", 0 }, + { } +}; + +static void set_lpn_pin(struct pm2xxx_charger *pm2) +{ + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { + gpio_set_value(pm2->lpn_pin, 1); + usleep_range(SLEEP_MIN, SLEEP_MAX); + } +} + +static void clear_lpn_pin(struct pm2xxx_charger *pm2) +{ + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) + gpio_set_value(pm2->lpn_pin, 0); +} + +static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) +{ + int ret; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, + 1, val); + if (ret < 0) + dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); + else + ret = 0; + + pm_runtime_put_sync(pm2->dev); + + return ret; +} + +static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) +{ + int ret; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, + 1, &val); + if (ret < 0) + dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); + else + ret = 0; + + pm_runtime_put_sync(pm2->dev); + + return ret; +} + +static int pm2xxx_charging_enable_mngt(struct pm2xxx_charger *pm2) +{ + int ret; + + /* Enable charging */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, + (PM2XXX_CH_AUTO_RESUME_EN | PM2XXX_CHARGER_ENA)); + + return ret; +} + +static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2) +{ + int ret; + + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + + /* Disable charging */ + ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, + (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + + return 0; +} + +static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) +{ + queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); + + return 0; +} + + +static int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) +{ + queue_work(pm2->charger_wq, &pm2->check_main_thermal_prot_work); + + return 0; +} + +static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_err(pm2->dev, "Overvoltage detected\n"); + pm2->flags.ovv = true; + power_supply_changed(&pm2->ac_chg.psy); + + /* Schedule a new HW failure check */ + queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0); + + return 0; +} + +static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_dbg(pm2->dev , "20 minutes watchdog expired\n"); + + pm2->ac.wd_expired = true; + power_supply_changed(&pm2->ac_chg.psy); + + return 0; +} + +static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) +{ + int ret; + + switch (val) { + case PM2XXX_INT1_ITVBATLOWR: + dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); + /* Enable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_SW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + break; + + case PM2XXX_INT1_ITVBATLOWF: + dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + break; + + default: + dev_err(pm2->dev, "Unknown VBAT level\n"); + } + + return 0; +} + +static int pm2xxx_charger_bat_disc_mngt(struct pm2xxx_charger *pm2, int val) +{ + dev_dbg(pm2->dev, "battery disconnected\n"); + + return 0; +} + +static int pm2xxx_charger_detection(struct pm2xxx_charger *pm2, u8 *val) +{ + int ret; + + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT2, val); + + if (ret < 0) { + dev_err(pm2->dev, "Charger detection failed\n"); + goto out; + } + + *val &= (PM2XXX_INT2_S_ITVPWR1PLUG | PM2XXX_INT2_S_ITVPWR2PLUG); + +out: + return ret; +} + +static int pm2xxx_charger_itv_pwr_plug_mngt(struct pm2xxx_charger *pm2, int val) +{ + + int ret; + u8 read_val; + + /* + * Since we can't be sure that the events are received + * synchronously, we have the check if the main charger is + * connected by reading the interrupt source register. + */ + ret = pm2xxx_charger_detection(pm2, &read_val); + + if ((ret == 0) && read_val) { + pm2->ac.charger_connected = 1; + pm2->ac_conn = true; + queue_work(pm2->charger_wq, &pm2->ac_work); + } + + + return ret; +} + +static int pm2xxx_charger_itv_pwr_unplug_mngt(struct pm2xxx_charger *pm2, + int val) +{ + pm2->ac.charger_connected = 0; + queue_work(pm2->charger_wq, &pm2->ac_work); + + return 0; +} + +static int pm2_int_reg0(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT1_ITVBATLOWR) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWR); + if (ret < 0) + goto out; + } + + if (val & PM2XXX_INT1_ITVBATLOWF) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWF); + if (ret < 0) + goto out; + } + + if (val & PM2XXX_INT1_ITVBATDISCONNECT) { + ret = pm2xxx_charger_bat_disc_mngt(pm2, + PM2XXX_INT1_ITVBATDISCONNECT); + if (ret < 0) + goto out; + } +out: + return ret; +} + +static int pm2_int_reg1(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)) { + dev_dbg(pm2->dev , "Main charger plugged\n"); + ret = pm2xxx_charger_itv_pwr_plug_mngt(pm2, val & + (PM2XXX_INT2_ITVPWR1PLUG | PM2XXX_INT2_ITVPWR2PLUG)); + } + + if (val & + (PM2XXX_INT2_ITVPWR1UNPLUG | PM2XXX_INT2_ITVPWR2UNPLUG)) { + dev_dbg(pm2->dev , "Main charger unplugged\n"); + ret = pm2xxx_charger_itv_pwr_unplug_mngt(pm2, val & + (PM2XXX_INT2_ITVPWR1UNPLUG | + PM2XXX_INT2_ITVPWR2UNPLUG)); + } + + return ret; +} + +static int pm2_int_reg2(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT3_ITAUTOTIMEOUTWD) + ret = pm2xxx_charger_wd_exp_mngt(pm2, val); + + if (val & (PM2XXX_INT3_ITCHPRECHARGEWD | + PM2XXX_INT3_ITCHCCWD | PM2XXX_INT3_ITCHCVWD)) { + dev_dbg(pm2->dev, + "Watchdog occurred for precharge, CC and CV charge\n"); + } + + return ret; +} + +static int pm2_int_reg3(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT4_ITCHARGINGON)) { + dev_dbg(pm2->dev , + "chargind operation has started\n"); + } + + if (val & (PM2XXX_INT4_ITVRESUME)) { + dev_dbg(pm2->dev, + "battery discharged down to VResume threshold\n"); + } + + if (val & (PM2XXX_INT4_ITBATTFULL)) { + dev_dbg(pm2->dev , "battery fully detected\n"); + } + + if (val & (PM2XXX_INT4_ITCVPHASE)) { + dev_dbg(pm2->dev, "CV phase enter with 0.5C charging\n"); + } + + if (val & (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)) { + pm2->failure_case = VPWR_OVV; + ret = pm2xxx_charger_ovv_mngt(pm2, val & + (PM2XXX_INT4_ITVPWR2OVV | PM2XXX_INT4_ITVPWR1OVV)); + dev_dbg(pm2->dev, "VPWR/VSYSTEM overvoltage detected\n"); + } + + if (val & (PM2XXX_INT4_S_ITBATTEMPCOLD | + PM2XXX_INT4_S_ITBATTEMPHOT)) { + ret = pm2xxx_charger_batt_therm_mngt(pm2, val & + (PM2XXX_INT4_S_ITBATTEMPCOLD | + PM2XXX_INT4_S_ITBATTEMPHOT)); + dev_dbg(pm2->dev, "BTEMP is too Low/High\n"); + } + + return ret; +} + +static int pm2_int_reg4(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & PM2XXX_INT5_ITVSYSTEMOVV) { + pm2->failure_case = VSYSTEM_OVV; + ret = pm2xxx_charger_ovv_mngt(pm2, val & + PM2XXX_INT5_ITVSYSTEMOVV); + dev_dbg(pm2->dev, "VSYSTEM overvoltage detected\n"); + } + + if (val & (PM2XXX_INT5_ITTHERMALWARNINGFALL | + PM2XXX_INT5_ITTHERMALWARNINGRISE | + PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | + PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)) { + dev_dbg(pm2->dev, "BTEMP die temperature is too Low/High\n"); + ret = pm2xxx_charger_die_therm_mngt(pm2, val & + (PM2XXX_INT5_ITTHERMALWARNINGFALL | + PM2XXX_INT5_ITTHERMALWARNINGRISE | + PM2XXX_INT5_ITTHERMALSHUTDOWNFALL | + PM2XXX_INT5_ITTHERMALSHUTDOWNRISE)); + } + + return ret; +} + +static int pm2_int_reg5(void *pm2_data, int val) +{ + struct pm2xxx_charger *pm2 = pm2_data; + int ret = 0; + + if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { + dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); + } + + if (val & (PM2XXX_INT6_ITVPWR2VALIDRISE | + PM2XXX_INT6_ITVPWR1VALIDRISE | + PM2XXX_INT6_ITVPWR2VALIDFALL | + PM2XXX_INT6_ITVPWR1VALIDFALL)) { + dev_dbg(pm2->dev, "Falling/Rising edge on WPWR1/2\n"); + } + + return ret; +} + +static irqreturn_t pm2xxx_irq_int(int irq, void *data) +{ + struct pm2xxx_charger *pm2 = data; + struct pm2xxx_interrupts *interrupt = pm2->pm2_int; + int i; + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + do { + for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { + pm2xxx_reg_read(pm2, + pm2xxx_interrupt_registers[i], + &(interrupt->reg[i])); + + if (interrupt->reg[i] > 0) + interrupt->handler[i](pm2, interrupt->reg[i]); + } + } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0); + + pm_runtime_mark_last_busy(pm2->dev); + pm_runtime_put_autosuspend(pm2->dev); + + return IRQ_HANDLED; +} + +static int pm2xxx_charger_get_ac_cv(struct pm2xxx_charger *pm2) +{ + int ret = 0; + u8 val; + + if (pm2->ac.charger_connected && pm2->ac.charger_online) { + + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + goto out; + } + + if (val & PM2XXX_INT4_S_ITCVPHASE) + ret = PM2XXX_CONST_VOLT; + else + ret = PM2XXX_CONST_CURR; + } +out: + return ret; +} + +static int pm2xxx_current_to_regval(int curr) +{ + int i; + + if (curr < pm2xxx_charger_current_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_current_map); i++) { + if (curr < pm2xxx_charger_current_map[i]) + return (i - 1); + } + + i = ARRAY_SIZE(pm2xxx_charger_current_map) - 1; + if (curr == pm2xxx_charger_current_map[i]) + return i; + else + return -EINVAL; +} + +static int pm2xxx_voltage_to_regval(int curr) +{ + int i; + + if (curr < pm2xxx_charger_voltage_map[0]) + return 0; + + for (i = 1; i < ARRAY_SIZE(pm2xxx_charger_voltage_map); i++) { + if (curr < pm2xxx_charger_voltage_map[i]) + return i - 1; + } + + i = ARRAY_SIZE(pm2xxx_charger_voltage_map) - 1; + if (curr == pm2xxx_charger_voltage_map[i]) + return i; + else + return -EINVAL; +} + +static int pm2xxx_charger_update_charger_current(struct ux500_charger *charger, + int ich_out) +{ + int ret; + int curr_index; + struct pm2xxx_charger *pm2; + u8 val; + + if (charger->psy.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.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}, +}; + +#ifdef CONFIG_PM + +#ifdef CONFIG_PM_SLEEP + +static int 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 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; +} + +#endif + +#ifdef CONFIG_PM_RUNTIME + +static int 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 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; +} + +#endif + +static const struct dev_pm_ops pm2xxx_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend, + pm2xxx_wall_charger_resume) + SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL) +}; +#define PM2XXX_PM_OPS (&pm2xxx_pm_ops) +#else +#define PM2XXX_PM_OPS NULL +#endif + +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 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.psy.name = pm2->pdata->label; + pm2->ac_chg.psy.type = POWER_SUPPLY_TYPE_MAINS; + pm2->ac_chg.psy.properties = pm2xxx_charger_ac_props; + pm2->ac_chg.psy.num_properties = ARRAY_SIZE(pm2xxx_charger_ac_props); + pm2->ac_chg.psy.get_property = pm2xxx_charger_ac_get_property; + pm2->ac_chg.psy.supplied_to = pm2->pdata->supplied_to; + pm2->ac_chg.psy.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 */ + ret = power_supply_register(pm2->dev, &pm2->ac_chg.psy); + if (ret) { + dev_err(pm2->dev, "failed to register AC charger\n"); + 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", + .owner = THIS_MODULE, + .pm = PM2XXX_PM_OPS, + }, + .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_ALIAS("i2c:pm2xxx-charger"); +MODULE_DESCRIPTION("PM2xxx charger management driver"); diff --git a/drivers/power/pm2301_charger.h b/drivers/power/pm2301_charger.h new file mode 100644 index 00000000000..8ce3cc0195d --- /dev/null +++ b/drivers/power/pm2301_charger.h @@ -0,0 +1,492 @@ +/* + * 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 pm2xxx_charger_event_flags flags; +}; + +#endif /* PM2301_CHARGER_H */ diff --git a/drivers/power/power_supply.h b/drivers/power/power_supply.h index 018de2b2699..cc439fd89d8 100644 --- a/drivers/power/power_supply.h +++ b/drivers/power/power_supply.h @@ -10,6 +10,10 @@ * 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); diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 6ad61272678..5a5a24e7d43 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -15,69 +15,253 @@ #include <linux/init.h> #include <linux/slab.h> #include <linux/device.h> +#include <linux/notifier.h> #include <linux/err.h> #include <linux/power_supply.h> +#include <linux/thermal.h> #include "power_supply.h" /* exported for the APM Power driver, APM emulation */ struct class *power_supply_class; EXPORT_SYMBOL_GPL(power_supply_class); +ATOMIC_NOTIFIER_HEAD(power_supply_notifier); +EXPORT_SYMBOL_GPL(power_supply_notifier); + static struct device_type power_supply_dev_type; +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->name) + return false; + for (i = 0; i < supply->num_supplies; i++) + if (!strcmp(supplier->name, supply->supplied_from[i])) + return true; + } else { + if (!supply->name) + return false; + for (i = 0; i < supplier->num_supplicants; i++) + if (!strcmp(supplier->supplied_to[i], supply->name)) + return true; + } + + return false; +} + static int __power_supply_changed_work(struct device *dev, void *data) { struct power_supply *psy = (struct power_supply *)data; struct power_supply *pst = dev_get_drvdata(dev); - int i; - for (i = 0; i < psy->num_supplicants; i++) - if (!strcmp(psy->supplied_to[i], pst->name)) { - if (pst->external_power_changed) - pst->external_power_changed(pst); - } + if (__power_supply_is_supplied_by(psy, pst)) { + if (pst->external_power_changed) + pst->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__); - class_for_each_device(power_supply_class, NULL, psy, - __power_supply_changed_work); - - power_supply_update_leds(psy); - - kobject_uevent(&psy->dev->kobj, KOBJ_CHANGE); + spin_lock_irqsave(&psy->changed_lock, flags); + if (psy->changed) { + psy->changed = false; + spin_unlock_irqrestore(&psy->changed_lock, flags); + class_for_each_device(power_supply_class, NULL, psy, + __power_supply_changed_work); + power_supply_update_leds(psy); + 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); + } + /* + * Dependent power supplies (e.g. battery) may have changed state + * as a result of this event, so poll again and hold the + * wakeup_source until all events are processed. + */ + if (!psy->changed) + pm_relax(psy->dev); + spin_unlock_irqrestore(&psy->changed_lock, flags); } void power_supply_changed(struct power_supply *psy) { + unsigned long flags; + dev_dbg(psy->dev, "%s\n", __func__); + spin_lock_irqsave(&psy->changed_lock, flags); + psy->changed = true; + pm_stay_awake(psy->dev); + spin_unlock_irqrestore(&psy->changed_lock, flags); schedule_work(&psy->changed_work); } EXPORT_SYMBOL_GPL(power_supply_changed); +#ifdef CONFIG_OF +#include <linux/of.h> + +static int __power_supply_populate_supplied_from(struct device *dev, + void *data) +{ + struct power_supply *psy = (struct power_supply *)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) + continue; + + if (np == epsy->of_node) { + dev_info(psy->dev, "%s: Found supply : %s\n", + psy->name, epsy->name); + psy->supplied_from[i-1] = (char *)epsy->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 = (struct device_node *)data; + struct power_supply *epsy = dev_get_drvdata(dev); + + /* return error breaks out of class_for_each_device loop */ + if (epsy->of_node == np) + return -EINVAL; + + return 0; +} + +static int power_supply_find_supply_from_node(struct device_node *supply_node) +{ + int error; + struct device *dev; + struct class_dev_iter iter; + + /* + * Use iterator to see if any other device is registered. + * This is required since class_for_each_device returns 0 + * if there are no devices registered. + */ + class_dev_iter_init(&iter, power_supply_class, NULL, NULL); + dev = class_dev_iter_next(&iter); + + if (!dev) + return -EPROBE_DEFER; + + /* + * We have to treat the return value as inverted, because if + * we return error on not found, then it won't continue looking. + * So we trick it by returning error on success to stop looking + * once the matching device is found. + */ + error = class_for_each_device(power_supply_class, NULL, supply_node, + __power_supply_find_supply_from_node); + + return error ? 0 : -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) + continue; + + ret = power_supply_find_supply_from_node(np); + if (ret) { + dev_dbg(psy->dev, "Failed to find supply, defer!\n"); + of_node_put(np); + return -EPROBE_DEFER; + } + of_node_put(np); + } while (np); + + /* 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, + 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 = (struct power_supply *)data; struct power_supply *epsy = dev_get_drvdata(dev); - int i; - for (i = 0; i < epsy->num_supplicants; i++) { - if (!strcmp(epsy->supplied_to[i], psy->name)) { - if (epsy->get_property(epsy, - POWER_SUPPLY_PROP_ONLINE, &ret)) - continue; + if (__power_supply_is_supplied_by(epsy, psy)) + if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) { if (ret.intval) return ret.intval; } - } + return 0; } @@ -140,7 +324,7 @@ int power_supply_set_battery_charged(struct power_supply *psy) } EXPORT_SYMBOL_GPL(power_supply_set_battery_charged); -static int power_supply_match_device_by_name(struct device *dev, void *data) +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); @@ -148,7 +332,7 @@ static int power_supply_match_device_by_name(struct device *dev, void *data) return strcmp(psy->name, name) == 0; } -struct power_supply *power_supply_get_by_name(char *name) +struct power_supply *power_supply_get_by_name(const char *name) { struct device *dev = class_find_device(power_supply_class, NULL, name, power_supply_match_device_by_name); @@ -157,6 +341,32 @@ struct power_supply *power_supply_get_by_name(char *name) } EXPORT_SYMBOL_GPL(power_supply_get_by_name); +#ifdef CONFIG_OF +static int power_supply_match_device_node(struct device *dev, const void *data) +{ + return dev->parent && dev->parent->of_node == data; +} + +struct power_supply *power_supply_get_by_phandle(struct device_node *np, + const char *property) +{ + struct device_node *power_supply_np; + 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); + + return dev ? dev_get_drvdata(dev) : NULL; +} +EXPORT_SYMBOL_GPL(power_supply_get_by_phandle); +#endif /* CONFIG_OF */ + int power_supply_powers(struct power_supply *psy, struct device *dev) { return sysfs_create_link(&psy->dev->kobj, &dev->kobj, "powers"); @@ -169,7 +379,165 @@ static void power_supply_dev_release(struct device *dev) kfree(dev); } -int power_supply_register(struct device *parent, struct power_supply *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, + unsigned long *temp) +{ + struct power_supply *psy; + union power_supply_propval val; + int ret; + + WARN_ON(tzd == NULL); + psy = tzd->devdata; + ret = psy->get_property(psy, POWER_SUPPLY_PROP_TEMP, &val); + + /* Convert tenths of degree Celsius to milli degree Celsius. */ + if (!ret) + *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; + + /* Register battery zone device psy reports temperature */ + for (i = 0; i < psy->num_properties; i++) { + if (psy->properties[i] == POWER_SUPPLY_PROP_TEMP) { + psy->tzd = thermal_zone_device_register(psy->name, 0, 0, + psy, &psy_tzd_ops, NULL, 0, 0); + if (IS_ERR(psy->tzd)) + return PTR_ERR(psy->tzd); + break; + } + } + 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 = psy->get_property(psy, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, &val); + if (!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 = psy->get_property(psy, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, &val); + if (!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->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->num_properties; i++) { + if (psy->properties[i] == + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT) { + psy->tcd = thermal_cooling_device_register( + (char *)psy->name, + psy, &psy_tcd_ops); + if (IS_ERR(psy->tcd)) + return PTR_ERR(psy->tcd); + break; + } + } + 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 + +int __power_supply_register(struct device *parent, struct power_supply *psy, bool ws) { struct device *dev; int rc; @@ -187,16 +555,35 @@ int power_supply_register(struct device *parent, struct power_supply *psy) dev_set_drvdata(dev, psy); psy->dev = dev; + rc = dev_set_name(dev, "%s", psy->name); + if (rc) + goto dev_set_name_failed; + INIT_WORK(&psy->changed_work, power_supply_changed_work); - rc = kobject_set_name(&dev->kobj, "%s", psy->name); + 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 kobject_set_name_failed; + 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; @@ -206,20 +593,40 @@ int power_supply_register(struct device *parent, struct power_supply *psy) goto success; create_triggers_failed: + psy_unregister_cooler(psy); +register_cooler_failed: + psy_unregister_thermal(psy); +register_thermal_failed: device_del(dev); -kobject_set_name_failed: device_add_failed: +wakeup_init_failed: +check_supplies_failed: +dev_set_name_failed: put_device(dev); success: return rc; } + +int power_supply_register(struct device *parent, struct power_supply *psy) +{ + return __power_supply_register(parent, psy, true); +} EXPORT_SYMBOL_GPL(power_supply_register); +int power_supply_register_no_ws(struct device *parent, struct power_supply *psy) +{ + return __power_supply_register(parent, psy, false); +} +EXPORT_SYMBOL_GPL(power_supply_register_no_ws); + void power_supply_unregister(struct power_supply *psy) { cancel_work_sync(&psy->changed_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); diff --git a/drivers/power/power_supply_leds.c b/drivers/power/power_supply_leds.c index da25eb94e5c..995f966ed5b 100644 --- a/drivers/power/power_supply_leds.c +++ b/drivers/power/power_supply_leds.c @@ -11,6 +11,7 @@ */ #include <linux/kernel.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/slab.h> diff --git a/drivers/power/power_supply_sysfs.c b/drivers/power/power_supply_sysfs.c index b52b57ca308..44420d1e909 100644 --- a/drivers/power/power_supply_sysfs.c +++ b/drivers/power/power_supply_sysfs.c @@ -12,6 +12,7 @@ */ #include <linux/ctype.h> +#include <linux/device.h> #include <linux/power_supply.h> #include <linux/slab.h> #include <linux/stat.h> @@ -54,7 +55,8 @@ static ssize_t power_supply_show_property(struct device *dev, }; static char *health_text[] = { "Unknown", "Good", "Overheat", "Dead", "Over voltage", - "Unspecified failure", "Cold", + "Unspecified failure", "Cold", "Watchdog timer expire", + "Safety timer expire" }; static char *technology_text[] = { "Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd", @@ -116,7 +118,7 @@ static ssize_t power_supply_store_property(struct device *dev, long long_val; /* TODO: support other types than int */ - ret = strict_strtol(buf, 10, &long_val); + ret = kstrtol(buf, 10, &long_val); if (ret < 0) return ret; @@ -137,6 +139,7 @@ static struct device_attribute power_supply_attrs[] = { 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), @@ -145,6 +148,7 @@ static struct device_attribute power_supply_attrs[] = { POWER_SUPPLY_ATTR(voltage_min_design), POWER_SUPPLY_ATTR(voltage_now), POWER_SUPPLY_ATTR(voltage_avg), + POWER_SUPPLY_ATTR(voltage_ocv), POWER_SUPPLY_ATTR(current_max), POWER_SUPPLY_ATTR(current_now), POWER_SUPPLY_ATTR(current_avg), @@ -157,6 +161,12 @@ static struct device_attribute power_supply_attrs[] = { 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(energy_full_design), POWER_SUPPLY_ATTR(energy_empty_design), POWER_SUPPLY_ATTR(energy_full), @@ -164,9 +174,15 @@ static struct device_attribute power_supply_attrs[] = { 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_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), diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig new file mode 100644 index 00000000000..bdcf5173e37 --- /dev/null +++ b/drivers/power/reset/Kconfig @@ -0,0 +1,82 @@ +menuconfig POWER_RESET + bool "Board level reset or power off" + help + Provides a number of drivers which either reset a complete board + or shut it down, by manipulating the main power supply on the board. + + Say Y here to enable board reset and power off + +config POWER_RESET_AS3722 + bool "ams AS3722 power-off driver" + depends on MFD_AS3722 && POWER_RESET + help + This driver supports turning off board via a ams AS3722 power-off. + +config POWER_RESET_AXXIA + bool "LSI Axxia reset driver" + depends on POWER_RESET && ARCH_AXXIA + help + This driver supports restart for Axxia SoC. + + Say Y if you have an Axxia family SoC. + +config POWER_RESET_GPIO + bool "GPIO power-off driver" + depends on OF_GPIO && POWER_RESET + help + This driver supports turning off your board via a GPIO line. + If your board needs a GPIO high/low to power down, say Y and + create a binding in your devicetree. + +config POWER_RESET_MSM + bool "Qualcomm MSM power-off driver" + depends on POWER_RESET && ARCH_QCOM + help + Power off and restart support for Qualcomm boards. + +config POWER_RESET_QNAP + bool "QNAP power-off driver" + depends on OF_GPIO && POWER_RESET && PLAT_ORION + help + This driver supports turning off QNAP NAS devices by sending + commands to the microcontroller which controls the main power. + + Say Y if you have a QNAP NAS. + +config POWER_RESET_RESTART + bool "Restart power-off driver" + depends on ARM + help + Some boards don't actually have the ability to power off. + Instead they restart, and u-boot holds the SoC until the + user presses a key. u-boot then boots into Linux. + +config POWER_RESET_SUN6I + bool "Allwinner A31 SoC reset driver" + depends on ARCH_SUNXI + depends on POWER_RESET + help + Reboot support for the Allwinner A31 SoCs. + +config POWER_RESET_VEXPRESS + bool "ARM Versatile Express power-off and reset driver" + depends on ARM || ARM64 + depends on POWER_RESET && VEXPRESS_CONFIG + help + Power off and reset support for the ARM Ltd. Versatile + Express boards. + +config POWER_RESET_XGENE + bool "APM SoC X-Gene reset driver" + depends on ARM64 + depends on POWER_RESET + help + Reboot support for the APM SoC X-Gene Eval boards. + +config POWER_RESET_KEYSTONE + bool "Keystone reset driver" + depends on ARCH_KEYSTONE + select MFD_SYSCON + help + Reboot support for the KEYSTONE SoCs. + diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile new file mode 100644 index 00000000000..dde2e8bbac5 --- /dev/null +++ b/drivers/power/reset/Makefile @@ -0,0 +1,10 @@ +obj-$(CONFIG_POWER_RESET_AS3722) += as3722-poweroff.o +obj-$(CONFIG_POWER_RESET_AXXIA) += axxia-reset.o +obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o +obj-$(CONFIG_POWER_RESET_MSM) += msm-poweroff.o +obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o +obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_SUN6I) += sun6i-reboot.o +obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o +obj-$(CONFIG_POWER_RESET_XGENE) += xgene-reboot.o +obj-$(CONFIG_POWER_RESET_KEYSTONE) += keystone-reset.o diff --git a/drivers/power/reset/as3722-poweroff.c b/drivers/power/reset/as3722-poweroff.c new file mode 100644 index 00000000000..684971199bd --- /dev/null +++ b/drivers/power/reset/as3722-poweroff.c @@ -0,0 +1,96 @@ +/* + * Power off driver for ams AS3722 device. + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + * + * Author: Laxman Dewangan <ldewangan@nvidia.com> + * + * 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. + */ + +#include <linux/mfd/as3722.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +struct as3722_poweroff { + struct device *dev; + struct as3722 *as3722; +}; + +static struct as3722_poweroff *as3722_pm_poweroff; + +static void as3722_pm_power_off(void) +{ + int ret; + + if (!as3722_pm_poweroff) { + pr_err("AS3722 poweroff is not initialised\n"); + return; + } + + ret = as3722_update_bits(as3722_pm_poweroff->as3722, + AS3722_RESET_CONTROL_REG, AS3722_POWER_OFF, AS3722_POWER_OFF); + if (ret < 0) + dev_err(as3722_pm_poweroff->dev, + "RESET_CONTROL_REG update failed, %d\n", ret); +} + +static int as3722_poweroff_probe(struct platform_device *pdev) +{ + struct as3722_poweroff *as3722_poweroff; + struct device_node *np = pdev->dev.parent->of_node; + + if (!np) + return -EINVAL; + + if (!of_property_read_bool(np, "ams,system-power-controller")) + return 0; + + as3722_poweroff = devm_kzalloc(&pdev->dev, sizeof(*as3722_poweroff), + GFP_KERNEL); + if (!as3722_poweroff) + return -ENOMEM; + + as3722_poweroff->as3722 = dev_get_drvdata(pdev->dev.parent); + as3722_poweroff->dev = &pdev->dev; + as3722_pm_poweroff = as3722_poweroff; + if (!pm_power_off) + pm_power_off = as3722_pm_power_off; + + return 0; +} + +static int as3722_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == as3722_pm_power_off) + pm_power_off = NULL; + as3722_pm_poweroff = NULL; + + return 0; +} + +static struct platform_driver as3722_poweroff_driver = { + .driver = { + .name = "as3722-power-off", + .owner = THIS_MODULE, + }, + .probe = as3722_poweroff_probe, + .remove = as3722_poweroff_remove, +}; + +module_platform_driver(as3722_poweroff_driver); + +MODULE_DESCRIPTION("Power off driver for ams AS3722 PMIC Device"); +MODULE_ALIAS("platform:as3722-power-off"); +MODULE_AUTHOR("Laxman Dewangan <ldewangan@nvidia.com>"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/axxia-reset.c b/drivers/power/reset/axxia-reset.c new file mode 100644 index 00000000000..3b1f8d60178 --- /dev/null +++ b/drivers/power/reset/axxia-reset.c @@ -0,0 +1,88 @@ +/* + * Reset driver for Axxia devices + * + * Copyright (C) 2014 LSI + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#include <linux/init.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/mfd/syscon.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> +#include <linux/regmap.h> + +#include <asm/system_misc.h> + + +#define SC_CRIT_WRITE_KEY 0x1000 +#define SC_LATCH_ON_RESET 0x1004 +#define SC_RESET_CONTROL 0x1008 +#define RSTCTL_RST_ZERO (1<<3) +#define RSTCTL_RST_FAB (1<<2) +#define RSTCTL_RST_CHIP (1<<1) +#define RSTCTL_RST_SYS (1<<0) +#define SC_EFUSE_INT_STATUS 0x180c +#define EFUSE_READ_DONE (1<<31) + +static struct regmap *syscon; + +static void do_axxia_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + /* Access Key (0xab) */ + regmap_write(syscon, SC_CRIT_WRITE_KEY, 0xab); + /* Select internal boot from 0xffff0000 */ + regmap_write(syscon, SC_LATCH_ON_RESET, 0x00000040); + /* Assert ResetReadDone (to avoid hanging in boot ROM) */ + regmap_write(syscon, SC_EFUSE_INT_STATUS, EFUSE_READ_DONE); + /* Assert chip reset */ + regmap_update_bits(syscon, SC_RESET_CONTROL, + RSTCTL_RST_CHIP, RSTCTL_RST_CHIP); +} + +static int axxia_reset_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + + syscon = syscon_regmap_lookup_by_phandle(dev->of_node, "syscon"); + if (IS_ERR(syscon)) { + pr_err("%s: syscon lookup failed\n", dev->of_node->name); + return PTR_ERR(syscon); + } + + arm_pm_restart = do_axxia_restart; + + return 0; +} + +static const struct of_device_id of_axxia_reset_match[] = { + { .compatible = "lsi,axm55xx-reset", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_axxia_reset_match); + +static struct platform_driver axxia_reset_driver = { + .probe = axxia_reset_probe, + .driver = { + .name = "axxia-reset", + .of_match_table = of_match_ptr(of_axxia_reset_match), + }, +}; + +static int __init axxia_reset_init(void) +{ + return platform_driver_register(&axxia_reset_driver); +} +device_initcall(axxia_reset_init); diff --git a/drivers/power/reset/gpio-poweroff.c b/drivers/power/reset/gpio-poweroff.c new file mode 100644 index 00000000000..e290d48ddd9 --- /dev/null +++ b/drivers/power/reset/gpio-poweroff.c @@ -0,0 +1,126 @@ +/* + * Toggles a GPIO pin to power down a device + * + * Jamie Lentin <jm@lentin.co.uk> + * Andrew Lunn <andrew@lunn.ch> + * + * Copyright (C) 2012 Jamie Lentin + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/of_platform.h> +#include <linux/of_gpio.h> +#include <linux/module.h> + +/* + * Hold configuration here, cannot be more than one instance of the driver + * since pm_power_off itself is global. + */ +static int gpio_num = -1; +static int gpio_active_low; + +static void gpio_poweroff_do_poweroff(void) +{ + BUG_ON(!gpio_is_valid(gpio_num)); + + /* drive it active, also inactive->active edge */ + gpio_direction_output(gpio_num, !gpio_active_low); + mdelay(100); + /* drive inactive, also active->inactive edge */ + gpio_set_value(gpio_num, gpio_active_low); + mdelay(100); + + /* drive it active, also inactive->active edge */ + gpio_set_value(gpio_num, !gpio_active_low); + + /* give it some time */ + mdelay(3000); + + WARN_ON(1); +} + +static int gpio_poweroff_probe(struct platform_device *pdev) +{ + enum of_gpio_flags flags; + bool input = false; + int ret; + + /* If a pm_power_off function has already been added, leave it alone */ + if (pm_power_off != NULL) { + pr_err("%s: pm_power_off function already registered", + __func__); + return -EBUSY; + } + + gpio_num = of_get_gpio_flags(pdev->dev.of_node, 0, &flags); + if (!gpio_is_valid(gpio_num)) + return gpio_num; + + gpio_active_low = flags & OF_GPIO_ACTIVE_LOW; + + input = of_property_read_bool(pdev->dev.of_node, "input"); + + ret = gpio_request(gpio_num, "poweroff-gpio"); + if (ret) { + pr_err("%s: Could not get GPIO %d", __func__, gpio_num); + return ret; + } + if (input) { + if (gpio_direction_input(gpio_num)) { + pr_err("Could not set direction of GPIO %d to input", + gpio_num); + goto err; + } + } else { + if (gpio_direction_output(gpio_num, gpio_active_low)) { + pr_err("Could not set direction of GPIO %d", gpio_num); + goto err; + } + } + + pm_power_off = &gpio_poweroff_do_poweroff; + return 0; + +err: + gpio_free(gpio_num); + return -ENODEV; +} + +static int gpio_poweroff_remove(struct platform_device *pdev) +{ + gpio_free(gpio_num); + if (pm_power_off == &gpio_poweroff_do_poweroff) + pm_power_off = NULL; + + return 0; +} + +static const struct of_device_id of_gpio_poweroff_match[] = { + { .compatible = "gpio-poweroff", }, + {}, +}; + +static struct platform_driver gpio_poweroff_driver = { + .probe = gpio_poweroff_probe, + .remove = gpio_poweroff_remove, + .driver = { + .name = "poweroff-gpio", + .owner = THIS_MODULE, + .of_match_table = of_gpio_poweroff_match, + }, +}; + +module_platform_driver(gpio_poweroff_driver); + +MODULE_AUTHOR("Jamie Lentin <jm@lentin.co.uk>"); +MODULE_DESCRIPTION("GPIO poweroff driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:poweroff-gpio"); diff --git a/drivers/power/reset/keystone-reset.c b/drivers/power/reset/keystone-reset.c new file mode 100644 index 00000000000..408a18fd91c --- /dev/null +++ b/drivers/power/reset/keystone-reset.c @@ -0,0 +1,166 @@ +/* + * TI keystone reboot driver + * + * Copyright (C) 2014 Texas Instruments Incorporated. http://www.ti.com/ + * + * Author: Ivan Khoronzhuk <ivan.khoronzhuk@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/io.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <linux/regmap.h> +#include <asm/system_misc.h> +#include <linux/mfd/syscon.h> +#include <linux/of_platform.h> + +#define RSTYPE_RG 0x0 +#define RSCTRL_RG 0x4 +#define RSCFG_RG 0x8 +#define RSISO_RG 0xc + +#define RSCTRL_KEY_MASK 0x0000ffff +#define RSCTRL_RESET_MASK BIT(16) +#define RSCTRL_KEY 0x5a69 + +#define RSMUX_OMODE_MASK 0xe +#define RSMUX_OMODE_RESET_ON 0xa +#define RSMUX_OMODE_RESET_OFF 0x0 +#define RSMUX_LOCK_MASK 0x1 +#define RSMUX_LOCK_SET 0x1 + +#define RSCFG_RSTYPE_SOFT 0x300f +#define RSCFG_RSTYPE_HARD 0x0 + +#define WDT_MUX_NUMBER 0x4 + +static int rspll_offset; +static struct regmap *pllctrl_regs; + +/** + * rsctrl_enable_rspll_write - enable access to RSCTRL, RSCFG + * To be able to access to RSCTRL, RSCFG registers + * we have to write a key before + */ +static inline int rsctrl_enable_rspll_write(void) +{ + return regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, + RSCTRL_KEY_MASK, RSCTRL_KEY); +} + +static void rsctrl_restart(enum reboot_mode mode, const char *cmd) +{ + /* enable write access to RSTCTRL */ + rsctrl_enable_rspll_write(); + + /* reset the SOC */ + regmap_update_bits(pllctrl_regs, rspll_offset + RSCTRL_RG, + RSCTRL_RESET_MASK, 0); +} + +static struct of_device_id rsctrl_of_match[] = { + {.compatible = "ti,keystone-reset", }, + {}, +}; + +static int rsctrl_probe(struct platform_device *pdev) +{ + int i; + int ret; + u32 val; + unsigned int rg; + u32 rsmux_offset; + struct regmap *devctrl_regs; + struct device *dev = &pdev->dev; + struct device_node *np = dev->of_node; + + if (!np) + return -ENODEV; + + /* get regmaps */ + pllctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-pll"); + if (IS_ERR(pllctrl_regs)) + return PTR_ERR(pllctrl_regs); + + devctrl_regs = syscon_regmap_lookup_by_phandle(np, "ti,syscon-dev"); + if (IS_ERR(devctrl_regs)) + return PTR_ERR(devctrl_regs); + + ret = of_property_read_u32_index(np, "ti,syscon-pll", 1, &rspll_offset); + if (ret) { + dev_err(dev, "couldn't read the reset pll offset!\n"); + return -EINVAL; + } + + ret = of_property_read_u32_index(np, "ti,syscon-dev", 1, &rsmux_offset); + if (ret) { + dev_err(dev, "couldn't read the rsmux offset!\n"); + return -EINVAL; + } + + /* set soft/hard reset */ + val = of_property_read_bool(np, "ti,soft-reset"); + val = val ? RSCFG_RSTYPE_SOFT : RSCFG_RSTYPE_HARD; + + ret = rsctrl_enable_rspll_write(); + if (ret) + return ret; + + ret = regmap_write(pllctrl_regs, rspll_offset + RSCFG_RG, val); + if (ret) + return ret; + + arm_pm_restart = rsctrl_restart; + + /* disable a reset isolation for all module clocks */ + ret = regmap_write(pllctrl_regs, rspll_offset + RSISO_RG, 0); + if (ret) + return ret; + + /* enable a reset for watchdogs from wdt-list */ + for (i = 0; i < WDT_MUX_NUMBER; i++) { + ret = of_property_read_u32_index(np, "ti,wdt-list", i, &val); + if (ret == -EOVERFLOW && !i) { + dev_err(dev, "ti,wdt-list property has to contain at" + "least one entry\n"); + return -EINVAL; + } else if (ret) { + break; + } + + if (val >= WDT_MUX_NUMBER) { + dev_err(dev, "ti,wdt-list property can contain" + "only numbers < 4\n"); + return -EINVAL; + } + + rg = rsmux_offset + val * 4; + + ret = regmap_update_bits(devctrl_regs, rg, RSMUX_OMODE_MASK, + RSMUX_OMODE_RESET_ON | + RSMUX_LOCK_SET); + if (ret) + return ret; + } + + return 0; +} + +static struct platform_driver rsctrl_driver = { + .probe = rsctrl_probe, + .driver = { + .owner = THIS_MODULE, + .name = KBUILD_MODNAME, + .of_match_table = rsctrl_of_match, + }, +}; +module_platform_driver(rsctrl_driver); + +MODULE_AUTHOR("Ivan Khoronzhuk <ivan.khoronzhuk@ti.com>"); +MODULE_DESCRIPTION("Texas Instruments keystone reset driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" KBUILD_MODNAME); diff --git a/drivers/power/reset/msm-poweroff.c b/drivers/power/reset/msm-poweroff.c new file mode 100644 index 00000000000..774f9a3b310 --- /dev/null +++ b/drivers/power/reset/msm-poweroff.c @@ -0,0 +1,73 @@ +/* Copyright (c) 2013, The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/reboot.h> + +#include <asm/system_misc.h> + +static void __iomem *msm_ps_hold; + +static void do_msm_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + writel(0, msm_ps_hold); + mdelay(10000); +} + +static void do_msm_poweroff(void) +{ + /* TODO: Add poweroff capability */ + do_msm_restart(REBOOT_HARD, NULL); +} + +static int msm_restart_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *mem; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + msm_ps_hold = devm_ioremap_resource(dev, mem); + if (IS_ERR(msm_ps_hold)) + return PTR_ERR(msm_ps_hold); + + pm_power_off = do_msm_poweroff; + arm_pm_restart = do_msm_restart; + return 0; +} + +static const struct of_device_id of_msm_restart_match[] = { + { .compatible = "qcom,pshold", }, + {}, +}; +MODULE_DEVICE_TABLE(of, of_msm_restart_match); + +static struct platform_driver msm_restart_driver = { + .probe = msm_restart_probe, + .driver = { + .name = "msm-restart", + .of_match_table = of_match_ptr(of_msm_restart_match), + }, +}; + +static int __init msm_restart_init(void) +{ + return platform_driver_register(&msm_restart_driver); +} +device_initcall(msm_restart_init); diff --git a/drivers/power/reset/qnap-poweroff.c b/drivers/power/reset/qnap-poweroff.c new file mode 100644 index 00000000000..a75db7f8a92 --- /dev/null +++ b/drivers/power/reset/qnap-poweroff.c @@ -0,0 +1,141 @@ +/* + * QNAP Turbo NAS Board power off. Can also be used on Synology devices. + * + * Copyright (C) 2012 Andrew Lunn <andrew@lunn.ch> + * + * Based on the code from: + * + * Copyright (C) 2009 Martin Michlmayr <tbm@cyrius.com> + * Copyright (C) 2008 Byron Bradley <byron.bbradley@gmail.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version + * 2 of the License, or (at your option) any later version. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/serial_reg.h> +#include <linux/kallsyms.h> +#include <linux/of.h> +#include <linux/io.h> +#include <linux/clk.h> + +#define UART1_REG(x) (base + ((UART_##x) << 2)) + +struct power_off_cfg { + u32 baud; + char cmd; +}; + +static const struct power_off_cfg qnap_power_off_cfg = { + .baud = 19200, + .cmd = 'A', +}; + +static const struct power_off_cfg synology_power_off_cfg = { + .baud = 9600, + .cmd = '1', +}; + +static const struct of_device_id qnap_power_off_of_match_table[] = { + { .compatible = "qnap,power-off", + .data = &qnap_power_off_cfg, + }, + { .compatible = "synology,power-off", + .data = &synology_power_off_cfg, + }, + {} +}; +MODULE_DEVICE_TABLE(of, qnap_power_off_of_match_table); + +static void __iomem *base; +static unsigned long tclk; +static const struct power_off_cfg *cfg; + +static void qnap_power_off(void) +{ + const unsigned divisor = ((tclk + (8 * cfg->baud)) / (16 * cfg->baud)); + + pr_err("%s: triggering power-off...\n", __func__); + + /* hijack UART1 and reset into sane state */ + writel(0x83, UART1_REG(LCR)); + writel(divisor & 0xff, UART1_REG(DLL)); + writel((divisor >> 8) & 0xff, UART1_REG(DLM)); + writel(0x03, UART1_REG(LCR)); + writel(0x00, UART1_REG(IER)); + writel(0x00, UART1_REG(FCR)); + writel(0x00, UART1_REG(MCR)); + + /* send the power-off command to PIC */ + writel(cfg->cmd, UART1_REG(TX)); +} + +static int qnap_power_off_probe(struct platform_device *pdev) +{ + struct device_node *np = pdev->dev.of_node; + struct resource *res; + struct clk *clk; + char symname[KSYM_NAME_LEN]; + + const struct of_device_id *match = + of_match_node(qnap_power_off_of_match_table, np); + cfg = match->data; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "Missing resource"); + return -EINVAL; + } + + base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (!base) { + dev_err(&pdev->dev, "Unable to map resource"); + return -EINVAL; + } + + /* We need to know tclk in order to calculate the UART divisor */ + clk = devm_clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "Clk missing"); + return PTR_ERR(clk); + } + + tclk = clk_get_rate(clk); + + /* Check that nothing else has already setup a handler */ + if (pm_power_off) { + lookup_symbol_name((ulong)pm_power_off, symname); + dev_err(&pdev->dev, + "pm_power_off already claimed %p %s", + pm_power_off, symname); + return -EBUSY; + } + pm_power_off = qnap_power_off; + + return 0; +} + +static int qnap_power_off_remove(struct platform_device *pdev) +{ + pm_power_off = NULL; + return 0; +} + +static struct platform_driver qnap_power_off_driver = { + .probe = qnap_power_off_probe, + .remove = qnap_power_off_remove, + .driver = { + .owner = THIS_MODULE, + .name = "qnap_power_off", + .of_match_table = of_match_ptr(qnap_power_off_of_match_table), + }, +}; +module_platform_driver(qnap_power_off_driver); + +MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch>"); +MODULE_DESCRIPTION("QNAP Power off driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/reset/restart-poweroff.c b/drivers/power/reset/restart-poweroff.c new file mode 100644 index 00000000000..5758033e0c1 --- /dev/null +++ b/drivers/power/reset/restart-poweroff.c @@ -0,0 +1,66 @@ +/* + * Power off by restarting and let u-boot keep hold of the machine + * until the user presses a button for example. + * + * Andrew Lunn <andrew@lunn.ch> + * + * Copyright (C) 2012 Andrew Lunn + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/of_platform.h> +#include <linux/module.h> +#include <linux/reboot.h> +#include <asm/system_misc.h> + +static void restart_poweroff_do_poweroff(void) +{ + arm_pm_restart(REBOOT_HARD, NULL); +} + +static int restart_poweroff_probe(struct platform_device *pdev) +{ + /* If a pm_power_off function has already been added, leave it alone */ + if (pm_power_off != NULL) { + dev_err(&pdev->dev, + "pm_power_off function already registered"); + return -EBUSY; + } + + pm_power_off = &restart_poweroff_do_poweroff; + return 0; +} + +static int restart_poweroff_remove(struct platform_device *pdev) +{ + if (pm_power_off == &restart_poweroff_do_poweroff) + pm_power_off = NULL; + + return 0; +} + +static const struct of_device_id of_restart_poweroff_match[] = { + { .compatible = "restart-poweroff", }, + {}, +}; + +static struct platform_driver restart_poweroff_driver = { + .probe = restart_poweroff_probe, + .remove = restart_poweroff_remove, + .driver = { + .name = "poweroff-restart", + .owner = THIS_MODULE, + .of_match_table = of_restart_poweroff_match, + }, +}; +module_platform_driver(restart_poweroff_driver); + +MODULE_AUTHOR("Andrew Lunn <andrew@lunn.ch"); +MODULE_DESCRIPTION("restart poweroff driver"); +MODULE_LICENSE("GPLv2"); +MODULE_ALIAS("platform:poweroff-restart"); diff --git a/drivers/power/reset/sun6i-reboot.c b/drivers/power/reset/sun6i-reboot.c new file mode 100644 index 00000000000..af2cd7ff2fe --- /dev/null +++ b/drivers/power/reset/sun6i-reboot.c @@ -0,0 +1,85 @@ +/* + * Allwinner A31 SoCs reset code + * + * Copyright (C) 2012-2014 Maxime Ripard + * + * Maxime Ripard <maxime.ripard@free-electrons.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/reboot.h> + +#include <asm/system_misc.h> + +#define SUN6I_WATCHDOG1_IRQ_REG 0x00 +#define SUN6I_WATCHDOG1_CTRL_REG 0x10 +#define SUN6I_WATCHDOG1_CTRL_RESTART BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_REG 0x14 +#define SUN6I_WATCHDOG1_CONFIG_RESTART BIT(0) +#define SUN6I_WATCHDOG1_CONFIG_IRQ BIT(1) +#define SUN6I_WATCHDOG1_MODE_REG 0x18 +#define SUN6I_WATCHDOG1_MODE_ENABLE BIT(0) + +static void __iomem *wdt_base; + +static void sun6i_wdt_restart(enum reboot_mode mode, const char *cmd) +{ + if (!wdt_base) + return; + + /* Disable interrupts */ + writel(0, wdt_base + SUN6I_WATCHDOG1_IRQ_REG); + + /* We want to disable the IRQ and just reset the whole system */ + writel(SUN6I_WATCHDOG1_CONFIG_RESTART, + wdt_base + SUN6I_WATCHDOG1_CONFIG_REG); + + /* Enable timer. The default and lowest interval value is 0.5s */ + writel(SUN6I_WATCHDOG1_MODE_ENABLE, + wdt_base + SUN6I_WATCHDOG1_MODE_REG); + + /* Restart the watchdog. */ + writel(SUN6I_WATCHDOG1_CTRL_RESTART, + wdt_base + SUN6I_WATCHDOG1_CTRL_REG); + + while (1) { + mdelay(5); + writel(SUN6I_WATCHDOG1_MODE_ENABLE, + wdt_base + SUN6I_WATCHDOG1_MODE_REG); + } +} + +static int sun6i_reboot_probe(struct platform_device *pdev) +{ + wdt_base = of_iomap(pdev->dev.of_node, 0); + if (!wdt_base) { + WARN(1, "failed to map watchdog base address"); + return -ENODEV; + } + + arm_pm_restart = sun6i_wdt_restart; + + return 0; +} + +static struct of_device_id sun6i_reboot_of_match[] = { + { .compatible = "allwinner,sun6i-a31-wdt" }, + {} +}; + +static struct platform_driver sun6i_reboot_driver = { + .probe = sun6i_reboot_probe, + .driver = { + .name = "sun6i-reboot", + .of_match_table = sun6i_reboot_of_match, + }, +}; +module_platform_driver(sun6i_reboot_driver); diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c new file mode 100644 index 00000000000..4dc102e2b23 --- /dev/null +++ b/drivers/power/reset/vexpress-poweroff.c @@ -0,0 +1,147 @@ +/* + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include <linux/delay.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/stat.h> +#include <linux/vexpress.h> + +#include <asm/system_misc.h> + +static void vexpress_reset_do(struct device *dev, const char *what) +{ + int err = -ENOENT; + struct regmap *reg = dev_get_drvdata(dev); + + if (reg) { + err = regmap_write(reg, 0, 0); + if (!err) + mdelay(1000); + } + + dev_emerg(dev, "Unable to %s (%d)\n", what, err); +} + +static struct device *vexpress_power_off_device; + +static void vexpress_power_off(void) +{ + vexpress_reset_do(vexpress_power_off_device, "power off"); +} + +static struct device *vexpress_restart_device; + +static void vexpress_restart(enum reboot_mode reboot_mode, const char *cmd) +{ + vexpress_reset_do(vexpress_restart_device, "restart"); +} + +static ssize_t vexpress_reset_active_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", vexpress_restart_device == dev); +} + +static ssize_t vexpress_reset_active_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long value; + int err = kstrtol(buf, 0, &value); + + if (!err && value) + vexpress_restart_device = dev; + + return err ? err : count; +} + +DEVICE_ATTR(active, S_IRUGO | S_IWUSR, vexpress_reset_active_show, + vexpress_reset_active_store); + + +enum vexpress_reset_func { FUNC_RESET, FUNC_SHUTDOWN, FUNC_REBOOT }; + +static struct of_device_id vexpress_reset_of_match[] = { + { + .compatible = "arm,vexpress-reset", + .data = (void *)FUNC_RESET, + }, { + .compatible = "arm,vexpress-shutdown", + .data = (void *)FUNC_SHUTDOWN + }, { + .compatible = "arm,vexpress-reboot", + .data = (void *)FUNC_REBOOT + }, + {} +}; + +static int vexpress_reset_probe(struct platform_device *pdev) +{ + enum vexpress_reset_func func; + const struct of_device_id *match = + of_match_device(vexpress_reset_of_match, &pdev->dev); + struct regmap *regmap; + + if (match) + func = (enum vexpress_reset_func)match->data; + else + func = pdev->id_entry->driver_data; + + regmap = devm_regmap_init_vexpress_config(&pdev->dev); + if (IS_ERR(regmap)) + return PTR_ERR(regmap); + dev_set_drvdata(&pdev->dev, regmap); + + switch (func) { + case FUNC_SHUTDOWN: + vexpress_power_off_device = &pdev->dev; + pm_power_off = vexpress_power_off; + break; + case FUNC_RESET: + if (!vexpress_restart_device) + vexpress_restart_device = &pdev->dev; + arm_pm_restart = vexpress_restart; + device_create_file(&pdev->dev, &dev_attr_active); + break; + case FUNC_REBOOT: + vexpress_restart_device = &pdev->dev; + arm_pm_restart = vexpress_restart; + device_create_file(&pdev->dev, &dev_attr_active); + break; + }; + + return 0; +} + +static const struct platform_device_id vexpress_reset_id_table[] = { + { .name = "vexpress-reset", .driver_data = FUNC_RESET, }, + { .name = "vexpress-shutdown", .driver_data = FUNC_SHUTDOWN, }, + { .name = "vexpress-reboot", .driver_data = FUNC_REBOOT, }, + {} +}; + +static struct platform_driver vexpress_reset_driver = { + .probe = vexpress_reset_probe, + .driver = { + .name = "vexpress-reset", + .of_match_table = vexpress_reset_of_match, + }, + .id_table = vexpress_reset_id_table, +}; + +static int __init vexpress_reset_init(void) +{ + return platform_driver_register(&vexpress_reset_driver); +} +device_initcall(vexpress_reset_init); diff --git a/drivers/power/reset/xgene-reboot.c b/drivers/power/reset/xgene-reboot.c new file mode 100644 index 00000000000..ecd55f81b9d --- /dev/null +++ b/drivers/power/reset/xgene-reboot.c @@ -0,0 +1,103 @@ +/* + * AppliedMicro X-Gene SoC Reboot Driver + * + * Copyright (c) 2013, Applied Micro Circuits Corporation + * Author: Feng Kan <fkan@apm.com> + * Author: Loc Ho <lho@apm.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, + * MA 02111-1307 USA + * + * This driver provides system reboot functionality for APM X-Gene SoC. + * For system shutdown, this is board specify. If a board designer + * implements GPIO shutdown, use the gpio-poweroff.c driver. + */ +#include <linux/io.h> +#include <linux/of_device.h> +#include <linux/of_address.h> +#include <linux/platform_device.h> +#include <linux/stat.h> +#include <linux/slab.h> +#include <asm/system_misc.h> + +struct xgene_reboot_context { + struct platform_device *pdev; + void *csr; + u32 mask; +}; + +static struct xgene_reboot_context *xgene_restart_ctx; + +static void xgene_restart(char str, const char *cmd) +{ + struct xgene_reboot_context *ctx = xgene_restart_ctx; + unsigned long timeout; + + /* Issue the reboot */ + if (ctx) + writel(ctx->mask, ctx->csr); + + timeout = jiffies + HZ; + while (time_before(jiffies, timeout)) + cpu_relax(); + + dev_emerg(&ctx->pdev->dev, "Unable to restart system\n"); +} + +static int xgene_reboot_probe(struct platform_device *pdev) +{ + struct xgene_reboot_context *ctx; + + ctx = devm_kzalloc(&pdev->dev, sizeof(*ctx), GFP_KERNEL); + if (!ctx) { + dev_err(&pdev->dev, "out of memory for context\n"); + return -ENODEV; + } + + ctx->csr = of_iomap(pdev->dev.of_node, 0); + if (!ctx->csr) { + devm_kfree(&pdev->dev, ctx); + dev_err(&pdev->dev, "can not map resource\n"); + return -ENODEV; + } + + if (of_property_read_u32(pdev->dev.of_node, "mask", &ctx->mask)) + ctx->mask = 0xFFFFFFFF; + + ctx->pdev = pdev; + arm_pm_restart = xgene_restart; + xgene_restart_ctx = ctx; + + return 0; +} + +static struct of_device_id xgene_reboot_of_match[] = { + { .compatible = "apm,xgene-reboot" }, + {} +}; + +static struct platform_driver xgene_reboot_driver = { + .probe = xgene_reboot_probe, + .driver = { + .name = "xgene-reboot", + .of_match_table = xgene_reboot_of_match, + }, +}; + +static int __init xgene_reboot_init(void) +{ + return platform_driver_register(&xgene_reboot_driver); +} +device_initcall(xgene_reboot_init); diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c new file mode 100644 index 00000000000..1bc5857b8bd --- /dev/null +++ b/drivers/power/rx51_battery.c @@ -0,0 +1,251 @@ +/* + * Nokia RX-51 battery driver + * + * Copyright (C) 2012 Pali Rohár <pali.rohar@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/i2c/twl4030-madc.h> + +/* RX51 specific channels */ +#define TWL4030_MADC_BTEMP_RX51 TWL4030_MADC_ADCIN0 +#define TWL4030_MADC_BCI_RX51 TWL4030_MADC_ADCIN4 + +struct rx51_device_info { + struct device *dev; + struct power_supply bat; +}; + +/* + * Read ADCIN channel value, code copied from maemo kernel + */ +static int rx51_battery_read_adc(int channel) +{ + struct twl4030_madc_request req; + + req.channels = channel; + req.do_avg = 1; + req.method = TWL4030_MADC_SW1; + req.func_cb = NULL; + req.type = TWL4030_MADC_WAIT; + req.raw = true; + + if (twl4030_madc_conversion(&req) <= 0) + return -ENODATA; + + return req.rbuf[ffs(channel) - 1]; +} + +/* + * 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(TWL4030_MADC_VBAT); + + if (voltage < 0) + 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(TWL4030_MADC_BTEMP_RX51); + + /* 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(TWL4030_MADC_BCI_RX51); + + if (capacity < 0) + 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 = container_of((psy), + struct rx51_device_info, bat); + + 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 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->bat.name = dev_name(&pdev->dev); + di->bat.type = POWER_SUPPLY_TYPE_BATTERY; + di->bat.properties = rx51_battery_props; + di->bat.num_properties = ARRAY_SIZE(rx51_battery_props); + di->bat.get_property = rx51_battery_get_property; + + ret = power_supply_register(di->dev, &di->bat); + if (ret) + return ret; + + return 0; +} + +static int rx51_battery_remove(struct platform_device *pdev) +{ + struct rx51_device_info *di = platform_get_drvdata(pdev); + + power_supply_unregister(&di->bat); + + return 0; +} + +static struct platform_driver rx51_battery_driver = { + .probe = rx51_battery_probe, + .remove = rx51_battery_remove, + .driver = { + .name = "rx51-battery", + .owner = THIS_MODULE, + }, +}; +module_platform_driver(rx51_battery_driver); + +MODULE_ALIAS("platform:rx51-battery"); +MODULE_AUTHOR("Pali Rohár <pali.rohar@gmail.com>"); +MODULE_DESCRIPTION("Nokia RX-51 battery driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c index 8b804a56675..5948ce058bd 100644 --- a/drivers/power/s3c_adc_battery.c +++ b/drivers/power/s3c_adc_battery.c @@ -145,14 +145,17 @@ static int s3c_adc_bat_get_property(struct power_supply *psy, int new_level; int full_volt; - const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac; - unsigned int lut_size = bat->pdata->lut_noac_cnt; + 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) { @@ -286,7 +289,7 @@ static irqreturn_t s3c_adc_bat_charged(int irq, void *dev_id) return IRQ_HANDLED; } -static int __devinit s3c_adc_bat_probe(struct platform_device *pdev) +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; diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index 9ff8af069da..b5f2a76b6cd 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -27,6 +27,7 @@ #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/gpio.h> +#include <linux/of.h> #include <linux/power/sbs-battery.h> @@ -89,7 +90,7 @@ static const struct chip_data { [REG_CURRENT] = SBS_DATA(POWER_SUPPLY_PROP_CURRENT_NOW, 0x0A, -32768, 32767), [REG_CAPACITY] = - SBS_DATA(POWER_SUPPLY_PROP_CAPACITY, 0x0E, 0, 100), + 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] = @@ -469,7 +470,7 @@ static int sbs_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_TECHNOLOGY: val->intval = POWER_SUPPLY_TECHNOLOGY_LION; - break; + goto done; /* don't trigger power_supply_changed()! */ case POWER_SUPPLY_PROP_ENERGY_NOW: case POWER_SUPPLY_PROP_ENERGY_FULL: @@ -667,7 +668,6 @@ of_out: return pdata; } #else -#define sbs_dt_ids NULL static struct sbs_platform_data *sbs_of_populate_pdata( struct i2c_client *client) { @@ -675,7 +675,7 @@ static struct sbs_platform_data *sbs_of_populate_pdata( } #endif -static int __devinit sbs_probe(struct i2c_client *client, +static int sbs_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct sbs_info *chip; @@ -704,6 +704,7 @@ static int __devinit sbs_probe(struct i2c_client *client, chip->power_supply.properties = sbs_properties; chip->power_supply.num_properties = ARRAY_SIZE(sbs_properties); chip->power_supply.get_property = sbs_get_property; + chip->power_supply.of_node = client->dev.of_node; /* ignore first notification of external change, it is generated * from the power_supply_register call back */ @@ -759,6 +760,16 @@ static int __devinit sbs_probe(struct i2c_client *client, chip->irq = irq; skip_gpio: + /* + * Before we register, we need to make sure we can actually talk + * to the battery. + */ + 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; + } rc = power_supply_register(&client->dev, &chip->power_supply); if (rc) { @@ -790,7 +801,7 @@ exit_free_name: return rc; } -static int __devexit sbs_remove(struct i2c_client *client) +static int sbs_remove(struct i2c_client *client) { struct sbs_info *chip = i2c_get_clientdata(client); @@ -810,10 +821,11 @@ static int __devexit sbs_remove(struct i2c_client *client) return 0; } -#if defined CONFIG_PM -static int sbs_suspend(struct i2c_client *client, - pm_message_t state) +#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; @@ -828,11 +840,13 @@ static int sbs_suspend(struct i2c_client *client, return 0; } + +static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); +#define SBS_PM_OPS (&sbs_pm_ops) + #else -#define sbs_suspend NULL +#define SBS_PM_OPS NULL #endif -/* any smbus transaction will wake up sbs */ -#define sbs_resume NULL static const struct i2c_device_id sbs_id[] = { { "bq20z75", 0 }, @@ -843,27 +857,15 @@ MODULE_DEVICE_TABLE(i2c, sbs_id); static struct i2c_driver sbs_battery_driver = { .probe = sbs_probe, - .remove = __devexit_p(sbs_remove), - .suspend = sbs_suspend, - .resume = sbs_resume, + .remove = sbs_remove, .id_table = sbs_id, .driver = { .name = "sbs-battery", - .of_match_table = sbs_dt_ids, + .of_match_table = of_match_ptr(sbs_dt_ids), + .pm = SBS_PM_OPS, }, }; - -static int __init sbs_battery_init(void) -{ - return i2c_add_driver(&sbs_battery_driver); -} -module_init(sbs_battery_init); - -static void __exit sbs_battery_exit(void) -{ - i2c_del_driver(&sbs_battery_driver); -} -module_exit(sbs_battery_exit); +module_i2c_driver(sbs_battery_driver); MODULE_DESCRIPTION("SBS battery monitor driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/power/smb347-charger.c b/drivers/power/smb347-charger.c new file mode 100644 index 00000000000..acf84e80fe9 --- /dev/null +++ b/drivers/power/smb347-charger.c @@ -0,0 +1,1326 @@ +/* + * Summit Microelectronics SMB347 Battery Charger Driver + * + * Copyright (C) 2011, Intel Corporation + * + * Authors: Bruce E. Robertson <bruce.e.robertson@intel.com> + * Mika Westerberg <mika.westerberg@linux.intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/err.h> +#include <linux/gpio.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <linux/power_supply.h> +#include <linux/power/smb347-charger.h> +#include <linux/regmap.h> + +/* + * Configuration registers. These are mirrored to volatile RAM and can be + * written once %CMD_A_ALLOW_WRITE is set in %CMD_A register. They will be + * reloaded from non-volatile registers after POR. + */ +#define CFG_CHARGE_CURRENT 0x00 +#define CFG_CHARGE_CURRENT_FCC_MASK 0xe0 +#define CFG_CHARGE_CURRENT_FCC_SHIFT 5 +#define CFG_CHARGE_CURRENT_PCC_MASK 0x18 +#define CFG_CHARGE_CURRENT_PCC_SHIFT 3 +#define CFG_CHARGE_CURRENT_TC_MASK 0x07 +#define CFG_CURRENT_LIMIT 0x01 +#define CFG_CURRENT_LIMIT_DC_MASK 0xf0 +#define CFG_CURRENT_LIMIT_DC_SHIFT 4 +#define CFG_CURRENT_LIMIT_USB_MASK 0x0f +#define CFG_FLOAT_VOLTAGE 0x03 +#define CFG_FLOAT_VOLTAGE_FLOAT_MASK 0x3f +#define CFG_FLOAT_VOLTAGE_THRESHOLD_MASK 0xc0 +#define CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT 6 +#define CFG_STAT 0x05 +#define CFG_STAT_DISABLED BIT(5) +#define CFG_STAT_ACTIVE_HIGH BIT(7) +#define CFG_PIN 0x06 +#define CFG_PIN_EN_CTRL_MASK 0x60 +#define CFG_PIN_EN_CTRL_ACTIVE_HIGH 0x40 +#define CFG_PIN_EN_CTRL_ACTIVE_LOW 0x60 +#define CFG_PIN_EN_APSD_IRQ BIT(1) +#define CFG_PIN_EN_CHARGER_ERROR BIT(2) +#define CFG_THERM 0x07 +#define CFG_THERM_SOFT_HOT_COMPENSATION_MASK 0x03 +#define CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT 0 +#define CFG_THERM_SOFT_COLD_COMPENSATION_MASK 0x0c +#define CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT 2 +#define CFG_THERM_MONITOR_DISABLED BIT(4) +#define CFG_SYSOK 0x08 +#define CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED BIT(2) +#define CFG_OTHER 0x09 +#define CFG_OTHER_RID_MASK 0xc0 +#define CFG_OTHER_RID_ENABLED_AUTO_OTG 0xc0 +#define CFG_OTG 0x0a +#define CFG_OTG_TEMP_THRESHOLD_MASK 0x30 +#define CFG_OTG_TEMP_THRESHOLD_SHIFT 4 +#define CFG_OTG_CC_COMPENSATION_MASK 0xc0 +#define CFG_OTG_CC_COMPENSATION_SHIFT 6 +#define CFG_TEMP_LIMIT 0x0b +#define CFG_TEMP_LIMIT_SOFT_HOT_MASK 0x03 +#define CFG_TEMP_LIMIT_SOFT_HOT_SHIFT 0 +#define CFG_TEMP_LIMIT_SOFT_COLD_MASK 0x0c +#define CFG_TEMP_LIMIT_SOFT_COLD_SHIFT 2 +#define CFG_TEMP_LIMIT_HARD_HOT_MASK 0x30 +#define CFG_TEMP_LIMIT_HARD_HOT_SHIFT 4 +#define CFG_TEMP_LIMIT_HARD_COLD_MASK 0xc0 +#define CFG_TEMP_LIMIT_HARD_COLD_SHIFT 6 +#define CFG_FAULT_IRQ 0x0c +#define CFG_FAULT_IRQ_DCIN_UV BIT(2) +#define CFG_STATUS_IRQ 0x0d +#define CFG_STATUS_IRQ_TERMINATION_OR_TAPER BIT(4) +#define CFG_STATUS_IRQ_CHARGE_TIMEOUT BIT(7) +#define CFG_ADDRESS 0x0e + +/* Command registers */ +#define CMD_A 0x30 +#define CMD_A_CHG_ENABLED BIT(1) +#define CMD_A_SUSPEND_ENABLED BIT(2) +#define CMD_A_ALLOW_WRITE BIT(7) +#define CMD_B 0x31 +#define CMD_C 0x33 + +/* Interrupt Status registers */ +#define IRQSTAT_A 0x35 +#define IRQSTAT_C 0x37 +#define IRQSTAT_C_TERMINATION_STAT BIT(0) +#define IRQSTAT_C_TERMINATION_IRQ BIT(1) +#define IRQSTAT_C_TAPER_IRQ BIT(3) +#define IRQSTAT_D 0x38 +#define IRQSTAT_D_CHARGE_TIMEOUT_STAT BIT(2) +#define IRQSTAT_D_CHARGE_TIMEOUT_IRQ BIT(3) +#define IRQSTAT_E 0x39 +#define IRQSTAT_E_USBIN_UV_STAT BIT(0) +#define IRQSTAT_E_USBIN_UV_IRQ BIT(1) +#define IRQSTAT_E_DCIN_UV_STAT BIT(4) +#define IRQSTAT_E_DCIN_UV_IRQ BIT(5) +#define IRQSTAT_F 0x3a + +/* Status registers */ +#define STAT_A 0x3b +#define STAT_A_FLOAT_VOLTAGE_MASK 0x3f +#define STAT_B 0x3c +#define STAT_C 0x3d +#define STAT_C_CHG_ENABLED BIT(0) +#define STAT_C_HOLDOFF_STAT BIT(3) +#define STAT_C_CHG_MASK 0x06 +#define STAT_C_CHG_SHIFT 1 +#define STAT_C_CHG_TERM BIT(5) +#define STAT_C_CHARGER_ERROR BIT(6) +#define STAT_E 0x3f + +#define SMB347_MAX_REGISTER 0x3f + +/** + * struct smb347_charger - smb347 charger instance + * @lock: protects concurrent access to online variables + * @dev: pointer to device + * @regmap: pointer to driver regmap + * @mains: power_supply instance for AC/DC power + * @usb: power_supply instance for USB power + * @battery: power_supply instance for battery + * @mains_online: is AC/DC input connected + * @usb_online: is USB input connected + * @charging_enabled: is charging enabled + * @pdata: pointer to platform data + */ +struct smb347_charger { + struct mutex lock; + struct device *dev; + struct regmap *regmap; + struct power_supply mains; + struct power_supply usb; + struct power_supply battery; + bool mains_online; + bool usb_online; + bool charging_enabled; + const struct smb347_charger_platform_data *pdata; +}; + +/* Fast charge current in uA */ +static const unsigned int fcc_tbl[] = { + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Pre-charge current in uA */ +static const unsigned int pcc_tbl[] = { + 100000, + 150000, + 200000, + 250000, +}; + +/* Termination current in uA */ +static const unsigned int tc_tbl[] = { + 37500, + 50000, + 100000, + 150000, + 200000, + 250000, + 500000, + 600000, +}; + +/* Input current limit in uA */ +static const unsigned int icl_tbl[] = { + 300000, + 500000, + 700000, + 900000, + 1200000, + 1500000, + 1800000, + 2000000, + 2200000, + 2500000, +}; + +/* Charge current compensation in uA */ +static const unsigned int ccc_tbl[] = { + 250000, + 700000, + 900000, + 1200000, +}; + +/* Convert register value to current using lookup table */ +static int hw_to_current(const unsigned int *tbl, size_t size, unsigned int val) +{ + if (val >= size) + return -EINVAL; + return tbl[val]; +} + +/* Convert current to register value using lookup table */ +static int current_to_hw(const unsigned int *tbl, size_t size, unsigned int val) +{ + size_t i; + + for (i = 0; i < size; i++) + if (val < tbl[i]) + break; + return i > 0 ? i - 1 : -EINVAL; +} + +/** + * smb347_update_ps_status - refreshes the power source status + * @smb: pointer to smb347 charger instance + * + * Function checks whether any power source is connected to the charger and + * updates internal state accordingly. If there is a change to previous state + * function returns %1, otherwise %0 and negative errno in case of errror. + */ +static int smb347_update_ps_status(struct smb347_charger *smb) +{ + bool usb = false; + bool dc = false; + unsigned int val; + int ret; + + ret = regmap_read(smb->regmap, IRQSTAT_E, &val); + if (ret < 0) + return ret; + + /* + * Dc and usb are set depending on whether they are enabled in + * platform data _and_ whether corresponding undervoltage is set. + */ + if (smb->pdata->use_mains) + dc = !(val & IRQSTAT_E_DCIN_UV_STAT); + if (smb->pdata->use_usb) + usb = !(val & IRQSTAT_E_USBIN_UV_STAT); + + mutex_lock(&smb->lock); + ret = smb->mains_online != dc || smb->usb_online != usb; + smb->mains_online = dc; + smb->usb_online = usb; + mutex_unlock(&smb->lock); + + return ret; +} + +/* + * smb347_is_ps_online - returns whether input power source is connected + * @smb: pointer to smb347 charger instance + * + * Returns %true if input power source is connected. Note that this is + * dependent on what platform has configured for usable power sources. For + * example if USB is disabled, this will return %false even if the USB cable + * is connected. + */ +static bool smb347_is_ps_online(struct smb347_charger *smb) +{ + bool ret; + + mutex_lock(&smb->lock); + ret = smb->usb_online || smb->mains_online; + mutex_unlock(&smb->lock); + + return ret; +} + +/** + * smb347_charging_status - returns status of charging + * @smb: pointer to smb347 charger instance + * + * Function returns charging status. %0 means no charging is in progress, + * %1 means pre-charging, %2 fast-charging and %3 taper-charging. + */ +static int smb347_charging_status(struct smb347_charger *smb) +{ + unsigned int val; + int ret; + + if (!smb347_is_ps_online(smb)) + return 0; + + ret = regmap_read(smb->regmap, STAT_C, &val); + if (ret < 0) + return 0; + + return (val & STAT_C_CHG_MASK) >> STAT_C_CHG_SHIFT; +} + +static int smb347_charging_set(struct smb347_charger *smb, bool enable) +{ + int ret = 0; + + if (smb->pdata->enable_control != SMB347_CHG_ENABLE_SW) { + dev_dbg(smb->dev, "charging enable/disable in SW disabled\n"); + return 0; + } + + mutex_lock(&smb->lock); + if (smb->charging_enabled != enable) { + ret = regmap_update_bits(smb->regmap, CMD_A, CMD_A_CHG_ENABLED, + enable ? CMD_A_CHG_ENABLED : 0); + if (!ret) + smb->charging_enabled = enable; + } + mutex_unlock(&smb->lock); + return ret; +} + +static inline int smb347_charging_enable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, true); +} + +static inline int smb347_charging_disable(struct smb347_charger *smb) +{ + return smb347_charging_set(smb, false); +} + +static int smb347_start_stop_charging(struct smb347_charger *smb) +{ + int ret; + + /* + * Depending on whether valid power source is connected or not, we + * disable or enable the charging. We do it manually because it + * depends on how the platform has configured the valid inputs. + */ + if (smb347_is_ps_online(smb)) { + ret = smb347_charging_enable(smb); + if (ret < 0) + dev_err(smb->dev, "failed to enable charging\n"); + } else { + ret = smb347_charging_disable(smb); + if (ret < 0) + dev_err(smb->dev, "failed to disable charging\n"); + } + + return ret; +} + +static int smb347_set_charge_current(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->max_charge_current) { + ret = current_to_hw(fcc_tbl, ARRAY_SIZE(fcc_tbl), + smb->pdata->max_charge_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_FCC_MASK, + ret << CFG_CHARGE_CURRENT_FCC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->pre_charge_current) { + ret = current_to_hw(pcc_tbl, ARRAY_SIZE(pcc_tbl), + smb->pdata->pre_charge_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_PCC_MASK, + ret << CFG_CHARGE_CURRENT_PCC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->termination_current) { + ret = current_to_hw(tc_tbl, ARRAY_SIZE(tc_tbl), + smb->pdata->termination_current); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CHARGE_CURRENT, + CFG_CHARGE_CURRENT_TC_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_current_limits(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->mains_current_limit) { + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->mains_current_limit); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_DC_MASK, + ret << CFG_CURRENT_LIMIT_DC_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->usb_hc_current_limit) { + ret = current_to_hw(icl_tbl, ARRAY_SIZE(icl_tbl), + smb->pdata->usb_hc_current_limit); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_CURRENT_LIMIT, + CFG_CURRENT_LIMIT_USB_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_voltage_limits(struct smb347_charger *smb) +{ + int ret; + + if (smb->pdata->pre_to_fast_voltage) { + ret = smb->pdata->pre_to_fast_voltage; + + /* uV */ + ret = clamp_val(ret, 2400000, 3000000) - 2400000; + ret /= 200000; + + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_THRESHOLD_MASK, + ret << CFG_FLOAT_VOLTAGE_THRESHOLD_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->max_charge_voltage) { + ret = smb->pdata->max_charge_voltage; + + /* uV */ + ret = clamp_val(ret, 3500000, 4500000) - 3500000; + ret /= 20000; + + ret = regmap_update_bits(smb->regmap, CFG_FLOAT_VOLTAGE, + CFG_FLOAT_VOLTAGE_FLOAT_MASK, ret); + if (ret < 0) + return ret; + } + + return 0; +} + +static int smb347_set_temp_limits(struct smb347_charger *smb) +{ + bool enable_therm_monitor = false; + int ret = 0; + int val; + + if (smb->pdata->chip_temp_threshold) { + val = smb->pdata->chip_temp_threshold; + + /* degree C */ + val = clamp_val(val, 100, 130) - 100; + val /= 10; + + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_TEMP_THRESHOLD_MASK, + val << CFG_OTG_TEMP_THRESHOLD_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->soft_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_cold_temp_limit; + + val = clamp_val(val, 0, 15); + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_COLD_MASK, + val << CFG_TEMP_LIMIT_SOFT_COLD_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->soft_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->soft_hot_temp_limit; + + val = clamp_val(val, 40, 55) - 40; + val /= 5; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_SOFT_HOT_MASK, + val << CFG_TEMP_LIMIT_SOFT_HOT_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_cold_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_cold_temp_limit; + + val = clamp_val(val, -5, 10) + 5; + val /= 5; + /* this goes from higher to lower so invert the value */ + val = ~val & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_COLD_MASK, + val << CFG_TEMP_LIMIT_HARD_COLD_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + if (smb->pdata->hard_hot_temp_limit != SMB347_TEMP_USE_DEFAULT) { + val = smb->pdata->hard_hot_temp_limit; + + val = clamp_val(val, 50, 65) - 50; + val /= 5; + + ret = regmap_update_bits(smb->regmap, CFG_TEMP_LIMIT, + CFG_TEMP_LIMIT_HARD_HOT_MASK, + val << CFG_TEMP_LIMIT_HARD_HOT_SHIFT); + if (ret < 0) + return ret; + + enable_therm_monitor = true; + } + + /* + * If any of the temperature limits are set, we also enable the + * thermistor monitoring. + * + * When soft limits are hit, the device will start to compensate + * current and/or voltage depending on the configuration. + * + * When hard limit is hit, the device will suspend charging + * depending on the configuration. + */ + if (enable_therm_monitor) { + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_MONITOR_DISABLED, 0); + if (ret < 0) + return ret; + } + + if (smb->pdata->suspend_on_hard_temp_limit) { + ret = regmap_update_bits(smb->regmap, CFG_SYSOK, + CFG_SYSOK_SUSPEND_HARD_LIMIT_DISABLED, 0); + if (ret < 0) + return ret; + } + + if (smb->pdata->soft_temp_limit_compensation != + SMB347_SOFT_TEMP_COMPENSATE_DEFAULT) { + val = smb->pdata->soft_temp_limit_compensation & 0x3; + + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_HOT_COMPENSATION_MASK, + val << CFG_THERM_SOFT_HOT_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + + ret = regmap_update_bits(smb->regmap, CFG_THERM, + CFG_THERM_SOFT_COLD_COMPENSATION_MASK, + val << CFG_THERM_SOFT_COLD_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + } + + if (smb->pdata->charge_current_compensation) { + val = current_to_hw(ccc_tbl, ARRAY_SIZE(ccc_tbl), + smb->pdata->charge_current_compensation); + if (val < 0) + return val; + + ret = regmap_update_bits(smb->regmap, CFG_OTG, + CFG_OTG_CC_COMPENSATION_MASK, + (val & 0x3) << CFG_OTG_CC_COMPENSATION_SHIFT); + if (ret < 0) + return ret; + } + + return ret; +} + +/* + * smb347_set_writable - enables/disables writing to non-volatile registers + * @smb: pointer to smb347 charger instance + * + * You can enable/disable writing to the non-volatile configuration + * registers by calling this function. + * + * Returns %0 on success and negative errno in case of failure. + */ +static int smb347_set_writable(struct smb347_charger *smb, bool writable) +{ + return regmap_update_bits(smb->regmap, CMD_A, CMD_A_ALLOW_WRITE, + writable ? CMD_A_ALLOW_WRITE : 0); +} + +static int smb347_hw_init(struct smb347_charger *smb) +{ + unsigned int val; + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Program the platform specific configuration values to the device + * first. + */ + ret = smb347_set_charge_current(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_current_limits(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_voltage_limits(smb); + if (ret < 0) + goto fail; + + ret = smb347_set_temp_limits(smb); + if (ret < 0) + goto fail; + + /* If USB charging is disabled we put the USB in suspend mode */ + if (!smb->pdata->use_usb) { + ret = regmap_update_bits(smb->regmap, CMD_A, + CMD_A_SUSPEND_ENABLED, + CMD_A_SUSPEND_ENABLED); + if (ret < 0) + goto fail; + } + + /* + * If configured by platform data, we enable hardware Auto-OTG + * support for driving VBUS. Otherwise we disable it. + */ + ret = regmap_update_bits(smb->regmap, CFG_OTHER, CFG_OTHER_RID_MASK, + smb->pdata->use_usb_otg ? CFG_OTHER_RID_ENABLED_AUTO_OTG : 0); + if (ret < 0) + goto fail; + + /* + * Make the charging functionality controllable by a write to the + * command register unless pin control is specified in the platform + * data. + */ + switch (smb->pdata->enable_control) { + case SMB347_CHG_ENABLE_PIN_ACTIVE_LOW: + val = CFG_PIN_EN_CTRL_ACTIVE_LOW; + break; + case SMB347_CHG_ENABLE_PIN_ACTIVE_HIGH: + val = CFG_PIN_EN_CTRL_ACTIVE_HIGH; + break; + default: + val = 0; + break; + } + + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CTRL_MASK, + val); + if (ret < 0) + goto fail; + + /* Disable Automatic Power Source Detection (APSD) interrupt. */ + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_APSD_IRQ, 0); + if (ret < 0) + goto fail; + + ret = smb347_update_ps_status(smb); + if (ret < 0) + goto fail; + + ret = smb347_start_stop_charging(smb); + +fail: + smb347_set_writable(smb, false); + return ret; +} + +static irqreturn_t smb347_interrupt(int irq, void *data) +{ + struct smb347_charger *smb = data; + unsigned int stat_c, irqstat_c, irqstat_d, irqstat_e; + bool handled = false; + int ret; + + ret = regmap_read(smb->regmap, STAT_C, &stat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading STAT_C failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_C, &irqstat_c); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_C failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_D, &irqstat_d); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_D failed\n"); + return IRQ_NONE; + } + + ret = regmap_read(smb->regmap, IRQSTAT_E, &irqstat_e); + if (ret < 0) { + dev_warn(smb->dev, "reading IRQSTAT_E failed\n"); + return IRQ_NONE; + } + + /* + * If we get charger error we report the error back to user. + * If the error is recovered charging will resume again. + */ + if (stat_c & STAT_C_CHARGER_ERROR) { + dev_err(smb->dev, "charging stopped due to charger error\n"); + power_supply_changed(&smb->battery); + handled = true; + } + + /* + * If we reached the termination current the battery is charged and + * we can update the status now. Charging is automatically + * disabled by the hardware. + */ + if (irqstat_c & (IRQSTAT_C_TERMINATION_IRQ | IRQSTAT_C_TAPER_IRQ)) { + if (irqstat_c & IRQSTAT_C_TERMINATION_STAT) + power_supply_changed(&smb->battery); + dev_dbg(smb->dev, "going to HW maintenance mode\n"); + handled = true; + } + + /* + * If we got a charger timeout INT that means the charge + * full is not detected with in charge timeout value. + */ + if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_IRQ) { + dev_dbg(smb->dev, "total Charge Timeout INT received\n"); + + if (irqstat_d & IRQSTAT_D_CHARGE_TIMEOUT_STAT) + dev_warn(smb->dev, "charging stopped due to timeout\n"); + power_supply_changed(&smb->battery); + handled = true; + } + + /* + * If we got an under voltage interrupt it means that AC/USB input + * was connected or disconnected. + */ + if (irqstat_e & (IRQSTAT_E_USBIN_UV_IRQ | IRQSTAT_E_DCIN_UV_IRQ)) { + if (smb347_update_ps_status(smb) > 0) { + smb347_start_stop_charging(smb); + if (smb->pdata->use_mains) + power_supply_changed(&smb->mains); + if (smb->pdata->use_usb) + power_supply_changed(&smb->usb); + } + handled = true; + } + + return handled ? IRQ_HANDLED : IRQ_NONE; +} + +static int smb347_irq_set(struct smb347_charger *smb, bool enable) +{ + int ret; + + ret = smb347_set_writable(smb, true); + if (ret < 0) + return ret; + + /* + * Enable/disable interrupts for: + * - under voltage + * - termination current reached + * - charger timeout + * - charger error + */ + ret = regmap_update_bits(smb->regmap, CFG_FAULT_IRQ, 0xff, + enable ? CFG_FAULT_IRQ_DCIN_UV : 0); + if (ret < 0) + goto fail; + + ret = regmap_update_bits(smb->regmap, CFG_STATUS_IRQ, 0xff, + enable ? (CFG_STATUS_IRQ_TERMINATION_OR_TAPER | + CFG_STATUS_IRQ_CHARGE_TIMEOUT) : 0); + if (ret < 0) + goto fail; + + ret = regmap_update_bits(smb->regmap, CFG_PIN, CFG_PIN_EN_CHARGER_ERROR, + enable ? CFG_PIN_EN_CHARGER_ERROR : 0); +fail: + smb347_set_writable(smb, false); + return ret; +} + +static inline int smb347_irq_enable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, true); +} + +static inline int smb347_irq_disable(struct smb347_charger *smb) +{ + return smb347_irq_set(smb, false); +} + +static int smb347_irq_init(struct smb347_charger *smb, + struct i2c_client *client) +{ + const struct smb347_charger_platform_data *pdata = smb->pdata; + int ret, irq = gpio_to_irq(pdata->irq_gpio); + + ret = gpio_request_one(pdata->irq_gpio, GPIOF_IN, client->name); + if (ret < 0) + goto fail; + + ret = request_threaded_irq(irq, NULL, smb347_interrupt, + IRQF_TRIGGER_FALLING, 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 = + container_of(psy, struct smb347_charger, mains); + 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 = + container_of(psy, struct smb347_charger, usb); + 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 = + container_of(psy, struct smb347_charger, battery); + 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 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 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; + + if (smb->pdata->use_mains) { + smb->mains.name = "smb347-mains"; + smb->mains.type = POWER_SUPPLY_TYPE_MAINS; + smb->mains.get_property = smb347_mains_get_property; + smb->mains.properties = smb347_mains_properties; + smb->mains.num_properties = ARRAY_SIZE(smb347_mains_properties); + smb->mains.supplied_to = battery; + smb->mains.num_supplicants = ARRAY_SIZE(battery); + ret = power_supply_register(dev, &smb->mains); + if (ret < 0) + return ret; + } + + if (smb->pdata->use_usb) { + smb->usb.name = "smb347-usb"; + smb->usb.type = POWER_SUPPLY_TYPE_USB; + smb->usb.get_property = smb347_usb_get_property; + smb->usb.properties = smb347_usb_properties; + smb->usb.num_properties = ARRAY_SIZE(smb347_usb_properties); + smb->usb.supplied_to = battery; + smb->usb.num_supplicants = ARRAY_SIZE(battery); + ret = power_supply_register(dev, &smb->usb); + if (ret < 0) { + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); + return ret; + } + } + + smb->battery.name = "smb347-battery"; + smb->battery.type = POWER_SUPPLY_TYPE_BATTERY; + smb->battery.get_property = smb347_battery_get_property; + smb->battery.properties = smb347_battery_properties; + smb->battery.num_properties = ARRAY_SIZE(smb347_battery_properties); + + + ret = power_supply_register(dev, &smb->battery); + if (ret < 0) { + if (smb->pdata->use_usb) + power_supply_unregister(&smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); + return ret; + } + + /* + * Interrupt pin is optional. If it is connected, we setup the + * interrupt support here. + */ + if (pdata->irq_gpio >= 0) { + ret = smb347_irq_init(smb, client); + if (ret < 0) { + dev_warn(dev, "failed to initialize IRQ: %d\n", ret); + dev_warn(dev, "disabling IRQ support\n"); + } else { + smb347_irq_enable(smb); + } + } + + return 0; +} + +static int smb347_remove(struct i2c_client *client) +{ + struct smb347_charger *smb = i2c_get_clientdata(client); + + if (client->irq) { + smb347_irq_disable(smb); + free_irq(client->irq, smb); + gpio_free(smb->pdata->irq_gpio); + } + + power_supply_unregister(&smb->battery); + if (smb->pdata->use_usb) + power_supply_unregister(&smb->usb); + if (smb->pdata->use_mains) + power_supply_unregister(&smb->mains); + return 0; +} + +static const struct i2c_device_id smb347_id[] = { + { "smb347", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, smb347_id); + +static struct i2c_driver smb347_driver = { + .driver = { + .name = "smb347", + }, + .probe = smb347_probe, + .remove = smb347_remove, + .id_table = smb347_id, +}; + +module_i2c_driver(smb347_driver); + +MODULE_AUTHOR("Bruce E. Robertson <bruce.e.robertson@intel.com>"); +MODULE_AUTHOR("Mika Westerberg <mika.westerberg@linux.intel.com>"); +MODULE_DESCRIPTION("SMB347 battery charger driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("i2c:smb347"); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c index b527c93bf2f..0152f35dca5 100644 --- a/drivers/power/test_power.c +++ b/drivers/power/test_power.c @@ -22,11 +22,15 @@ #include <linux/vermagic.h> 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, @@ -42,6 +46,20 @@ static int test_power_get_ac_property(struct power_supply *psy, 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) @@ -86,6 +104,12 @@ static int test_power_get_battery_property(struct power_supply *psy, 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__); @@ -114,6 +138,8 @@ static enum power_supply_property test_power_battery_props[] = { 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[] = { @@ -135,6 +161,14 @@ static struct power_supply test_power_supplies[] = { .properties = test_power_battery_props, .num_properties = ARRAY_SIZE(test_power_battery_props), .get_property = test_power_get_battery_property, + }, { + .name = "test_usb", + .type = POWER_SUPPLY_TYPE_USB, + .supplied_to = test_power_ac_supplied_to, + .num_supplicants = ARRAY_SIZE(test_power_ac_supplied_to), + .properties = test_power_ac_props, + .num_properties = ARRAY_SIZE(test_power_ac_props), + .get_property = test_power_get_usb_property, }, }; @@ -153,6 +187,7 @@ static int __init test_power_init(void) } } + module_initialized = true; return 0; failed: while (--i >= 0) @@ -167,6 +202,7 @@ static void __exit test_power_exit(void) /* 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]); @@ -176,6 +212,8 @@ static void __exit test_power_exit(void) 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); @@ -188,8 +226,8 @@ struct battery_property_map { }; static struct battery_property_map map_ac_online[] = { - { 0, "on" }, - { 1, "off" }, + { 0, "off" }, + { 1, "on" }, { -1, NULL }, }; @@ -262,10 +300,16 @@ static const char *map_get_key(struct battery_property_map *map, int value, 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); - power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[0]); return 0; } @@ -275,11 +319,24 @@ static int param_get_ac_online(char *buffer, const struct kernel_param *kp) 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[2]); + 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); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -293,7 +350,7 @@ static int param_set_battery_health(const char *key, const struct kernel_param *kp) { battery_health = map_get_value(map_health, key, battery_health); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -307,7 +364,7 @@ static int param_set_battery_present(const char *key, const struct kernel_param *kp) { battery_present = map_get_value(map_present, key, battery_present); - power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[0]); return 0; } @@ -323,7 +380,7 @@ static int param_set_battery_technology(const char *key, { battery_technology = map_get_value(map_technology, key, battery_technology); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -344,19 +401,37 @@ static int param_set_battery_capacity(const char *key, return -EINVAL; battery_capacity = tmp; - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); 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[1]); + return 0; +} + +#define param_get_battery_voltage param_get_int static struct kernel_param_ops param_ops_ac_online = { .set = param_set_ac_online, .get = param_get_ac_online, }; +static struct kernel_param_ops param_ops_usb_online = { + .set = param_set_usb_online, + .get = param_get_usb_online, +}; + static struct kernel_param_ops param_ops_battery_status = { .set = param_set_battery_status, .get = param_get_battery_status, @@ -382,18 +457,27 @@ static struct kernel_param_ops param_ops_battery_capacity = { .get = param_get_battery_capacity, }; +static struct kernel_param_ops param_ops_battery_voltage = { + .set = param_set_battery_voltage, + .get = param_get_battery_voltage, +}; #define param_check_ac_online(name, p) __param_check(name, p, void); +#define param_check_usb_online(name, p) __param_check(name, p, void); #define param_check_battery_status(name, p) __param_check(name, p, void); #define param_check_battery_present(name, p) __param_check(name, p, void); #define param_check_battery_technology(name, p) __param_check(name, p, void); #define param_check_battery_health(name, p) __param_check(name, p, void); #define param_check_battery_capacity(name, p) __param_check(name, p, void); +#define param_check_battery_voltage(name, p) __param_check(name, p, void); module_param(ac_online, ac_online, 0644); MODULE_PARM_DESC(ac_online, "AC charging state <on|off>"); +module_param(usb_online, usb_online, 0644); +MODULE_PARM_DESC(usb_online, "USB charging state <on|off>"); + module_param(battery_status, battery_status, 0644); MODULE_PARM_DESC(battery_status, "battery status <charging|discharging|not-charging|full>"); @@ -413,6 +497,8 @@ MODULE_PARM_DESC(battery_health, module_param(battery_capacity, battery_capacity, 0644); MODULE_PARM_DESC(battery_capacity, "battery capacity (percentage)"); +module_param(battery_voltage, battery_voltage, 0644); +MODULE_PARM_DESC(battery_voltage, "battery voltage (millivolts)"); MODULE_DESCRIPTION("Power supply driver for testing"); MODULE_AUTHOR("Anton Vorontsov <cbouatmailru@gmail.com>"); diff --git a/drivers/power/tosa_battery.c b/drivers/power/tosa_battery.c index 28bbe7e094e..f4d80df627c 100644 --- a/drivers/power/tosa_battery.c +++ b/drivers/power/tosa_battery.c @@ -150,7 +150,7 @@ static void tosa_bat_external_power_changed(struct power_supply *psy) static irqreturn_t tosa_bat_gpio_isr(int irq, void *data) { - pr_info("tosa_bat_gpio irq: %d\n", gpio_get_value(irq_to_gpio(irq))); + pr_info("tosa_bat_gpio irq\n"); schedule_work(&bat_work); return IRQ_HANDLED; } @@ -327,7 +327,7 @@ static struct gpio tosa_bat_gpios[] = { static int tosa_bat_suspend(struct platform_device *dev, pm_message_t state) { /* flush all pending status updates */ - flush_work_sync(&bat_work); + flush_work(&bat_work); return 0; } @@ -342,7 +342,7 @@ static int tosa_bat_resume(struct platform_device *dev) #define tosa_bat_resume NULL #endif -static int __devinit tosa_bat_probe(struct platform_device *dev) +static int tosa_bat_probe(struct platform_device *dev) { int ret; @@ -409,7 +409,7 @@ err_psy_reg_main: return ret; } -static int __devexit tosa_bat_remove(struct platform_device *dev) +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); @@ -433,7 +433,7 @@ static struct platform_driver tosa_bat_driver = { .driver.name = "wm97xx-battery", .driver.owner = THIS_MODULE, .probe = tosa_bat_probe, - .remove = __devexit_p(tosa_bat_remove), + .remove = tosa_bat_remove, .suspend = tosa_bat_suspend, .resume = tosa_bat_resume, }; diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c new file mode 100644 index 00000000000..1685f63b9e5 --- /dev/null +++ b/drivers/power/tps65090-charger.c @@ -0,0 +1,323 @@ +/* + * Battery charger driver for TI's tps65090 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ +#include <linux/delay.h> +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> + +#include <linux/mfd/tps65090.h> + +#define TPS65090_CHARGER_ENABLE BIT(0) +#define TPS65090_VACG BIT(1) +#define TPS65090_NOITERM BIT(5) + +struct tps65090_charger { + struct device *dev; + int ac_online; + int prev_ac_online; + int irq; + 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; + + 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; + + 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->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 = container_of(psy, + struct tps65090_charger, ac); + + 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. */ + 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_probe(struct platform_device *pdev) +{ + struct tps65090_charger *cdata; + struct tps65090_platform_data *pdata; + 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; + + cdata->ac.name = "tps65090-ac"; + cdata->ac.type = POWER_SUPPLY_TYPE_MAINS; + cdata->ac.get_property = tps65090_ac_get_property; + cdata->ac.properties = tps65090_ac_props; + cdata->ac.num_properties = ARRAY_SIZE(tps65090_ac_props); + cdata->ac.supplied_to = pdata->supplied_to; + cdata->ac.num_supplicants = pdata->num_supplicants; + cdata->ac.of_node = pdev->dev.of_node; + + ret = power_supply_register(&pdev->dev, &cdata->ac); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_warn(&pdev->dev, "Unable to get charger irq = %d\n", irq); + ret = irq; + goto fail_unregister_supply; + } + + cdata->irq = irq; + + 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; + } + + 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); + } + + 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); + + power_supply_unregister(&cdata->ac); + + return 0; +} + +static struct of_device_id of_tps65090_charger_match[] = { + { .compatible = "ti,tps65090-charger", }, + { /* end */ } +}; + +static struct platform_driver tps65090_charger_driver = { + .driver = { + .name = "tps65090-charger", + .of_match_table = of_tps65090_charger_match, + .owner = THIS_MODULE, + }, + .probe = tps65090_charger_probe, + .remove = tps65090_charger_remove, +}; +module_platform_driver(tps65090_charger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>"); +MODULE_DESCRIPTION("tps65090 battery charger driver"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index 54b9198fa57..f14108844e1 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -15,12 +15,14 @@ #include <linux/init.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/err.h> #include <linux/platform_device.h> #include <linux/interrupt.h> #include <linux/i2c/twl.h> #include <linux/power_supply.h> #include <linux/notifier.h> #include <linux/usb/otg.h> +#include <linux/regulator/machine.h> #define TWL4030_BCIMSTATEC 0x02 #define TWL4030_BCIICHG 0x08 @@ -28,6 +30,7 @@ #define TWL4030_BCIVBUS 0x0c #define TWL4030_BCIMFSTS4 0x10 #define TWL4030_BCICTL1 0x23 +#define TWL4030_BB_CFG 0x12 #define TWL4030_BCIAUTOWEN BIT(5) #define TWL4030_CONFIG_DONE BIT(4) @@ -37,6 +40,17 @@ #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 /* BCI interrupts */ #define TWL4030_WOVF BIT(0) /* Watchdog overflow */ @@ -69,11 +83,13 @@ struct twl4030_bci { struct device *dev; struct power_supply ac; struct power_supply usb; - struct otg_transceiver *transceiver; - struct notifier_block otg_nb; + struct usb_phy *transceiver; + struct notifier_block usb_nb; struct work_struct work; int irq_chg; int irq_bci; + struct regulator *usb_reg; + int usb_enabled; unsigned long event; }; @@ -98,12 +114,12 @@ static int twl4030_clear_set(u8 mod_no, u8 clear, u8 set, u8 reg) static int twl4030_bci_read(u8 reg, u8 *val) { - return twl_i2c_read_u8(TWL4030_MODULE_MAIN_CHARGE, val, reg); + 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(TWL4030_MODULE_PM_MASTER, 0, + return twl4030_clear_set(TWL_MODULE_PM_MASTER, clear, TWL4030_CONFIG_DONE | TWL4030_BCIAUTOWEN | set, TWL4030_PM_MASTER_BOOT_BCI); } @@ -136,7 +152,7 @@ static int twl4030_bci_have_vbus(struct twl4030_bci *bci) int ret; u8 hwsts; - ret = twl_i2c_read_u8(TWL4030_MODULE_PM_MASTER, &hwsts, + ret = twl_i2c_read_u8(TWL_MODULE_PM_MASTER, &hwsts, TWL4030_PM_MASTER_STS_HW_CONDITIONS); if (ret < 0) return 0; @@ -151,14 +167,14 @@ static int twl4030_bci_have_vbus(struct twl4030_bci *bci) } /* - * Enable/Disable USB Charge funtionality. + * Enable/Disable USB Charge functionality. */ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) { int ret; if (enable) { - /* Check for USB charger conneted */ + /* Check for USB charger connected */ if (!twl4030_bci_have_vbus(bci)) return -ENODEV; @@ -171,16 +187,31 @@ static int twl4030_charger_enable_usb(struct twl4030_bci *bci, bool enable) return -EACCES; } + /* Need to keep regulator on */ + if (!bci->usb_enabled) { + ret = regulator_enable(bci->usb_reg); + if (ret) { + dev_err(bci->dev, + "Failed to enable regulator\n"); + return ret; + } + bci->usb_enabled = 1; + } + /* forcing the field BCIAUTOUSB (BOOT_BCI[1]) to 1 */ ret = twl4030_clear_set_boot_bci(0, TWL4030_BCIAUTOUSB); if (ret < 0) return ret; /* forcing USBFASTMCHG(BCIMFSTS4[2]) to 1 */ - ret = twl4030_clear_set(TWL4030_MODULE_MAIN_CHARGE, 0, + ret = twl4030_clear_set(TWL_MODULE_MAIN_CHARGE, 0, TWL4030_USBFASTMCHG, TWL4030_BCIMFSTS4); } else { ret = twl4030_clear_set_boot_bci(TWL4030_BCIAUTOUSB, 0); + if (bci->usb_enabled) { + regulator_disable(bci->usb_reg); + bci->usb_enabled = 0; + } } return ret; @@ -202,6 +233,49 @@ static int twl4030_charger_enable_ac(bool enable) } /* + * 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) @@ -279,7 +353,7 @@ static void twl4030_bci_usb_work(struct work_struct *data) 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, otg_nb); + struct twl4030_bci *bci = container_of(nb, struct twl4030_bci, usb_nb); dev_dbg(bci->dev, "OTG notify %lu\n", val); @@ -421,9 +495,38 @@ static enum power_supply_property twl4030_charger_props[] = { 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 int __init 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; @@ -431,6 +534,9 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) if (bci == NULL) return -ENOMEM; + if (!pdata) + pdata = twl4030_bci_parse_dt(&pdev->dev); + bci->dev = &pdev->dev; bci->irq_chg = platform_get_irq(pdev, 0); bci->irq_bci = platform_get_irq(pdev, 1); @@ -455,6 +561,8 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) bci->usb.num_properties = ARRAY_SIZE(twl4030_charger_props); bci->usb.get_property = twl4030_bci_get_property; + bci->usb_reg = regulator_get(bci->dev, "bci3v1"); + ret = power_supply_register(&pdev->dev, &bci->usb); if (ret) { dev_err(&pdev->dev, "failed to register usb: %d\n", ret); @@ -462,7 +570,8 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) } ret = request_threaded_irq(bci->irq_chg, NULL, - twl4030_charger_interrupt, 0, pdev->name, bci); + 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); @@ -470,7 +579,7 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) } ret = request_threaded_irq(bci->irq_bci, NULL, - twl4030_bci_interrupt, 0, pdev->name, bci); + 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); @@ -479,10 +588,10 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) INIT_WORK(&bci->work, twl4030_bci_usb_work); - bci->transceiver = otg_get_transceiver(); - if (bci->transceiver != NULL) { - bci->otg_nb.notifier_call = twl4030_bci_usb_ncb; - otg_register_notifier(bci->transceiver, &bci->otg_nb); + bci->transceiver = usb_get_phy(USB_PHY_TYPE_USB2); + if (!IS_ERR_OR_NULL(bci->transceiver)) { + bci->usb_nb.notifier_call = twl4030_bci_usb_ncb; + usb_register_notifier(bci->transceiver, &bci->usb_nb); } /* Enable interrupts now. */ @@ -503,13 +612,18 @@ static int __init twl4030_bci_probe(struct platform_device *pdev) twl4030_charger_enable_ac(true); twl4030_charger_enable_usb(bci, true); + if (pdata) + twl4030_charger_enable_backup(pdata->bb_uvolt, + pdata->bb_uamp); + else + twl4030_charger_enable_backup(0, 0); return 0; fail_unmask_interrupts: - if (bci->transceiver != NULL) { - otg_unregister_notifier(bci->transceiver, &bci->otg_nb); - otg_put_transceiver(bci->transceiver); + if (!IS_ERR_OR_NULL(bci->transceiver)) { + usb_unregister_notifier(bci->transceiver, &bci->usb_nb); + usb_put_phy(bci->transceiver); } free_irq(bci->irq_bci, bci); fail_bci_irq: @@ -519,7 +633,6 @@ fail_chg_irq: fail_register_usb: power_supply_unregister(&bci->ac); fail_register_ac: - platform_set_drvdata(pdev, NULL); kfree(bci); return ret; @@ -531,6 +644,7 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) twl4030_charger_enable_ac(false); twl4030_charger_enable_usb(bci, false); + twl4030_charger_enable_backup(0, 0); /* mask interrupts */ twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, @@ -538,39 +652,35 @@ static int __exit twl4030_bci_remove(struct platform_device *pdev) twl_i2c_write_u8(TWL4030_MODULE_INTERRUPTS, 0xff, TWL4030_INTERRUPTS_BCIIMR2A); - if (bci->transceiver != NULL) { - otg_unregister_notifier(bci->transceiver, &bci->otg_nb); - otg_put_transceiver(bci->transceiver); + if (!IS_ERR_OR_NULL(bci->transceiver)) { + usb_unregister_notifier(bci->transceiver, &bci->usb_nb); + usb_put_phy(bci->transceiver); } free_irq(bci->irq_bci, bci); free_irq(bci->irq_chg, bci); power_supply_unregister(&bci->usb); power_supply_unregister(&bci->ac); - platform_set_drvdata(pdev, NULL); kfree(bci); 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 = { .driver = { .name = "twl4030_bci", .owner = THIS_MODULE, + .of_match_table = of_match_ptr(twl_bci_of_match), }, .remove = __exit_p(twl4030_bci_remove), }; -static int __init twl4030_bci_init(void) -{ - return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe); -} -module_init(twl4030_bci_init); - -static void __exit twl4030_bci_exit(void) -{ - platform_driver_unregister(&twl4030_bci_driver); -} -module_exit(twl4030_bci_exit); +module_platform_driver_probe(twl4030_bci_driver, twl4030_bci_probe); MODULE_AUTHOR("Gražvydas Ignotas"); MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); diff --git a/drivers/power/twl4030_madc_battery.c b/drivers/power/twl4030_madc_battery.c new file mode 100644 index 00000000000..7ef445a6cfa --- /dev/null +++ b/drivers/power/twl4030_madc_battery.c @@ -0,0 +1,245 @@ +/* + * Dumb driver for LiIon batteries using TWL4030 madc. + * + * Copyright 2013 Golden Delicious Computers + * Lukas Märdian <lukas@goldelico.com> + * + * Based on dumb driver for gta01 battery + * Copyright 2009 Openmoko, Inc + * Balaji Rao <balajirrao@openmoko.org> + */ + +#include <linux/module.h> +#include <linux/param.h> +#include <linux/delay.h> +#include <linux/workqueue.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/slab.h> +#include <linux/sort.h> +#include <linux/i2c/twl4030-madc.h> +#include <linux/power/twl4030_madc_battery.h> + +struct twl4030_madc_battery { + struct power_supply psy; + struct twl4030_madc_bat_platform_data *pdata; +}; + +static enum power_supply_property twl4030_madc_bat_props[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW, +}; + +static int madc_read(int index) +{ + struct twl4030_madc_request req; + int val; + + req.channels = index; + req.method = TWL4030_MADC_SW2; + req.type = TWL4030_MADC_WAIT; + req.do_avg = 0; + req.raw = false; + req.func_cb = NULL; + + val = twl4030_madc_conversion(&req); + if (val < 0) + return val; + + return req.rbuf[ffs(index) - 1]; +} + +static int twl4030_madc_bat_get_charging_status(void) +{ + return (madc_read(TWL4030_MADC_ICHG) > 0) ? 1 : 0; +} + +static int twl4030_madc_bat_get_voltage(void) +{ + return madc_read(TWL4030_MADC_VBAT); +} + +static int twl4030_madc_bat_get_current(void) +{ + return madc_read(TWL4030_MADC_ICHG) * 1000; +} + +static int twl4030_madc_bat_get_temp(void) +{ + return madc_read(TWL4030_MADC_BTEMP) * 10; +} + +static int twl4030_madc_bat_voltscale(struct twl4030_madc_battery *bat, + int volt) +{ + struct twl4030_madc_bat_calibration *calibration; + int i, res = 0; + + /* choose charging curve */ + if (twl4030_madc_bat_get_charging_status()) + calibration = bat->pdata->charging; + else + calibration = bat->pdata->discharging; + + if (volt > calibration[0].voltage) { + res = calibration[0].level; + } else { + for (i = 0; calibration[i+1].voltage >= 0; i++) { + if (volt <= calibration[i].voltage && + volt >= calibration[i+1].voltage) { + /* interval found - interpolate within range */ + res = calibration[i].level - + ((calibration[i].voltage - volt) * + (calibration[i].level - + calibration[i+1].level)) / + (calibration[i].voltage - + calibration[i+1].voltage); + break; + } + } + } + return res; +} + +static int twl4030_madc_bat_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct twl4030_madc_battery *bat = container_of(psy, + struct twl4030_madc_battery, psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + if (twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage()) > 95) + val->intval = POWER_SUPPLY_STATUS_FULL; + else { + if (twl4030_madc_bat_get_charging_status()) + val->intval = POWER_SUPPLY_STATUS_CHARGING; + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = twl4030_madc_bat_get_voltage() * 1000; + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = twl4030_madc_bat_get_current(); + break; + case POWER_SUPPLY_PROP_PRESENT: + /* assume battery is always present */ + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: { + int percent = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage()); + val->intval = (percent * bat->pdata->capacity) / 100; + break; + } + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage()); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = bat->pdata->capacity; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = twl4030_madc_bat_get_temp(); + break; + case POWER_SUPPLY_PROP_TIME_TO_EMPTY_NOW: { + int percent = twl4030_madc_bat_voltscale(bat, + twl4030_madc_bat_get_voltage()); + /* in mAh */ + int chg = (percent * (bat->pdata->capacity/1000))/100; + + /* assume discharge with 400 mA (ca. 1.5W) */ + val->intval = (3600l * chg) / 400; + break; + } + default: + return -EINVAL; + } + + return 0; +} + +static void twl4030_madc_bat_ext_changed(struct power_supply *psy) +{ + struct twl4030_madc_battery *bat = container_of(psy, + struct twl4030_madc_battery, psy); + + power_supply_changed(&bat->psy); +} + +static int twl4030_cmp(const void *a, const void *b) +{ + return ((struct twl4030_madc_bat_calibration *)b)->voltage - + ((struct twl4030_madc_bat_calibration *)a)->voltage; +} + +static int twl4030_madc_battery_probe(struct platform_device *pdev) +{ + struct twl4030_madc_battery *twl4030_madc_bat; + struct twl4030_madc_bat_platform_data *pdata = pdev->dev.platform_data; + + twl4030_madc_bat = kzalloc(sizeof(*twl4030_madc_bat), GFP_KERNEL); + if (!twl4030_madc_bat) + return -ENOMEM; + + twl4030_madc_bat->psy.name = "twl4030_battery"; + twl4030_madc_bat->psy.type = POWER_SUPPLY_TYPE_BATTERY; + twl4030_madc_bat->psy.properties = twl4030_madc_bat_props; + twl4030_madc_bat->psy.num_properties = + ARRAY_SIZE(twl4030_madc_bat_props); + twl4030_madc_bat->psy.get_property = twl4030_madc_bat_get_property; + twl4030_madc_bat->psy.external_power_changed = + twl4030_madc_bat_ext_changed; + + /* sort charging and discharging calibration data */ + sort(pdata->charging, pdata->charging_size, + sizeof(struct twl4030_madc_bat_calibration), + twl4030_cmp, NULL); + sort(pdata->discharging, pdata->discharging_size, + sizeof(struct twl4030_madc_bat_calibration), + twl4030_cmp, NULL); + + twl4030_madc_bat->pdata = pdata; + platform_set_drvdata(pdev, twl4030_madc_bat); + power_supply_register(&pdev->dev, &twl4030_madc_bat->psy); + + return 0; +} + +static int twl4030_madc_battery_remove(struct platform_device *pdev) +{ + struct twl4030_madc_battery *bat = platform_get_drvdata(pdev); + + power_supply_unregister(&bat->psy); + kfree(bat); + + return 0; +} + +static struct platform_driver twl4030_madc_battery_driver = { + .driver = { + .name = "twl4030_madc_battery", + }, + .probe = twl4030_madc_battery_probe, + .remove = twl4030_madc_battery_remove, +}; +module_platform_driver(twl4030_madc_battery_driver); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Lukas Märdian <lukas@goldelico.com>"); +MODULE_DESCRIPTION("twl4030_madc battery driver"); diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c index 6243e697512..56fb509f4be 100644 --- a/drivers/power/wm831x_backup.c +++ b/drivers/power/wm831x_backup.c @@ -161,7 +161,7 @@ static enum power_supply_property wm831x_backup_props[] = { * Initialisation *********************************************************************/ -static __devinit int wm831x_backup_probe(struct platform_device *pdev) +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; @@ -169,7 +169,8 @@ static __devinit int wm831x_backup_probe(struct platform_device *pdev) struct power_supply *backup; int ret; - devdata = kzalloc(sizeof(struct wm831x_backup), GFP_KERNEL); + devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup), + GFP_KERNEL); if (devdata == NULL) return -ENOMEM; @@ -197,30 +198,22 @@ static __devinit int wm831x_backup_probe(struct platform_device *pdev) backup->num_properties = ARRAY_SIZE(wm831x_backup_props); backup->get_property = wm831x_backup_get_prop; ret = power_supply_register(&pdev->dev, backup); - if (ret) - goto err_kmalloc; return ret; - -err_kmalloc: - kfree(devdata); - return ret; } -static __devexit int wm831x_backup_remove(struct platform_device *pdev) +static int wm831x_backup_remove(struct platform_device *pdev) { struct wm831x_backup *devdata = platform_get_drvdata(pdev); power_supply_unregister(&devdata->backup); - kfree(devdata->backup.name); - kfree(devdata); return 0; } static struct platform_driver wm831x_backup_driver = { .probe = wm831x_backup_probe, - .remove = __devexit_p(wm831x_backup_remove), + .remove = wm831x_backup_remove, .driver = { .name = "wm831x-backup", }, diff --git a/drivers/power/wm831x_power.c b/drivers/power/wm831x_power.c index 987332b71d8..3bed2f55cf7 100644 --- a/drivers/power/wm831x_power.c +++ b/drivers/power/wm831x_power.c @@ -489,7 +489,7 @@ static irqreturn_t wm831x_pwr_src_irq(int irq, void *data) return IRQ_HANDLED; } -static __devinit int wm831x_power_probe(struct platform_device *pdev) +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; @@ -565,7 +565,7 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev) goto err_usb; } - irq = platform_get_irq_byname(pdev, "SYSLO"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); ret = request_threaded_irq(irq, NULL, wm831x_syslo_irq, IRQF_TRIGGER_RISING, "System power low", power); @@ -575,7 +575,7 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev) goto err_battery; } - irq = platform_get_irq_byname(pdev, "PWR SRC"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); ret = request_threaded_irq(irq, NULL, wm831x_pwr_src_irq, IRQF_TRIGGER_RISING, "Power source", power); @@ -586,7 +586,9 @@ static __devinit int wm831x_power_probe(struct platform_device *pdev) } for (i = 0; i < ARRAY_SIZE(wm831x_bat_irqs); i++) { - irq = platform_get_irq_byname(pdev, 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, wm831x_bat_irqs[i], @@ -606,10 +608,10 @@ err_bat_irq: irq = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); free_irq(irq, power); } - irq = platform_get_irq_byname(pdev, "PWR SRC"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); free_irq(irq, power); err_syslo: - irq = platform_get_irq_byname(pdev, "SYSLO"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); free_irq(irq, power); err_battery: if (power->have_battery) @@ -623,20 +625,23 @@ err_kmalloc: return ret; } -static __devexit int wm831x_power_remove(struct platform_device *pdev) +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 = platform_get_irq_byname(pdev, wm831x_bat_irqs[i]); + irq = wm831x_irq(wm831x, + platform_get_irq_byname(pdev, + wm831x_bat_irqs[i])); free_irq(irq, wm831x_power); } - irq = platform_get_irq_byname(pdev, "PWR SRC"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "PWR SRC")); free_irq(irq, wm831x_power); - irq = platform_get_irq_byname(pdev, "SYSLO"); + irq = wm831x_irq(wm831x, platform_get_irq_byname(pdev, "SYSLO")); free_irq(irq, wm831x_power); if (wm831x_power->have_battery) @@ -649,7 +654,7 @@ static __devexit int wm831x_power_remove(struct platform_device *pdev) static struct platform_driver wm831x_power_driver = { .probe = wm831x_power_probe, - .remove = __devexit_p(wm831x_power_remove), + .remove = wm831x_power_remove, .driver = { .name = "wm831x-power", }, diff --git a/drivers/power/wm8350_power.c b/drivers/power/wm8350_power.c index fae04d38465..b3607e2906d 100644 --- a/drivers/power/wm8350_power.c +++ b/drivers/power/wm8350_power.c @@ -442,7 +442,7 @@ static void free_charger_irq(struct wm8350 *wm8350) wm8350_free_irq(wm8350, WM8350_IRQ_EXT_BAT_FB, wm8350); } -static __devinit int wm8350_power_probe(struct platform_device *pdev) +static int wm8350_power_probe(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct wm8350_power *power = &wm8350->power; @@ -501,7 +501,7 @@ battery_failed: return ret; } -static __devexit int wm8350_power_remove(struct platform_device *pdev) +static int wm8350_power_remove(struct platform_device *pdev) { struct wm8350 *wm8350 = platform_get_drvdata(pdev); struct wm8350_power *power = &wm8350->power; @@ -516,7 +516,7 @@ static __devexit int wm8350_power_remove(struct platform_device *pdev) static struct platform_driver wm8350_power_driver = { .probe = wm8350_power_probe, - .remove = __devexit_p(wm8350_power_remove), + .remove = wm8350_power_remove, .driver = { .name = "wm8350-power", }, diff --git a/drivers/power/wm97xx_battery.c b/drivers/power/wm97xx_battery.c index d2d4c08c681..58f7348e6c2 100644 --- a/drivers/power/wm97xx_battery.c +++ b/drivers/power/wm97xx_battery.c @@ -146,7 +146,7 @@ static irqreturn_t wm97xx_chrg_irq(int irq, void *data) #ifdef CONFIG_PM static int wm97xx_bat_suspend(struct device *dev) { - flush_work_sync(&bat_work); + flush_work(&bat_work); return 0; } @@ -162,7 +162,7 @@ static const struct dev_pm_ops wm97xx_bat_pm_ops = { }; #endif -static int __devinit wm97xx_bat_probe(struct platform_device *dev) +static int wm97xx_bat_probe(struct platform_device *dev) { int ret = 0; int props = 1; /* POWER_SUPPLY_PROP_PRESENT */ @@ -212,8 +212,10 @@ static int __devinit wm97xx_bat_probe(struct platform_device *dev) props++; /* POWER_SUPPLY_PROP_VOLTAGE_MIN */ prop = kzalloc(props * sizeof(*prop), GFP_KERNEL); - if (!prop) + if (!prop) { + ret = -ENOMEM; goto err3; + } prop[i++] = POWER_SUPPLY_PROP_PRESENT; if (pdata->charge_gpio >= 0) @@ -261,7 +263,7 @@ err: return ret; } -static int __devexit wm97xx_bat_remove(struct platform_device *dev) +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; @@ -285,7 +287,7 @@ static struct platform_driver wm97xx_bat_driver = { #endif }, .probe = wm97xx_bat_probe, - .remove = __devexit_p(wm97xx_bat_remove), + .remove = wm97xx_bat_remove, }; module_platform_driver(wm97xx_bat_driver); diff --git a/drivers/power/z2_battery.c b/drivers/power/z2_battery.c index 636ebb2a0e8..814d2e31f0c 100644 --- a/drivers/power/z2_battery.c +++ b/drivers/power/z2_battery.c @@ -180,7 +180,7 @@ static int z2_batt_ps_init(struct z2_charger *charger, int props) return 0; } -static int __devinit z2_batt_probe(struct i2c_client *client, +static int z2_batt_probe(struct i2c_client *client, const struct i2c_device_id *id) { int ret = 0; @@ -251,7 +251,7 @@ err: return ret; } -static int __devexit z2_batt_remove(struct i2c_client *client) +static int z2_batt_remove(struct i2c_client *client) { struct z2_charger *charger = i2c_get_clientdata(client); struct z2_battery_info *info = charger->info; @@ -276,7 +276,7 @@ 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_sync(&charger->bat_work); + flush_work(&charger->bat_work); return 0; } @@ -313,22 +313,10 @@ static struct i2c_driver z2_batt_driver = { .pm = Z2_BATTERY_PM_OPS }, .probe = z2_batt_probe, - .remove = __devexit_p(z2_batt_remove), + .remove = z2_batt_remove, .id_table = z2_batt_id, }; - -static int __init z2_batt_init(void) -{ - return i2c_add_driver(&z2_batt_driver); -} - -static void __exit z2_batt_exit(void) -{ - i2c_del_driver(&z2_batt_driver); -} - -module_init(z2_batt_init); -module_exit(z2_batt_exit); +module_i2c_driver(z2_batt_driver); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Peter Edwards <sweetlilmre@gmail.com>"); |
