diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile index a0732c1a5eb..c5ce829a571 100644 --- a/arch/arm/mach-msm/Makefile +++ b/arch/arm/mach-msm/Makefile @@ -280,7 +280,7 @@ obj-$(CONFIG_MACH_MSM8960_FLUID) += board-8960-all.o board-8960-regulator.o obj-$(CONFIG_MACH_MSM8930_CDP) += board-8930-all.o board-8930-regulator-pm8038.o board-8930-regulator-pm8917.o obj-$(CONFIG_MACH_MSM8930_MTP) += board-8930-all.o board-8930-regulator-pm8038.o board-8930-regulator-pm8917.o obj-$(CONFIG_MACH_MSM8930_FLUID) += board-8930-all.o board-8930-regulator-pm8038.o board-8930-regulator-pm8917.o -obj-$(CONFIG_PM8921_BMS) += bms-batterydata.o bms-batterydata-desay.o +obj-$(CONFIG_PM8921_BMS) += bms-batterydata.o bms-batterydata-desay.o batterydata-lib.o obj-$(CONFIG_MACH_APQ8064_CDP) += board-8064-all.o board-8064-regulator.o obj-$(CONFIG_MACH_APQ8064_MTP) += board-8064-all.o board-8064-regulator.o obj-$(CONFIG_MACH_APQ8064_LIQUID) += board-8064-all.o board-8064-regulator.o diff --git a/arch/arm/mach-msm/batterydata-lib.c b/arch/arm/mach-msm/batterydata-lib.c new file mode 100644 index 00000000000..2be591c0aab --- /dev/null +++ b/arch/arm/mach-msm/batterydata-lib.c @@ -0,0 +1,338 @@ +/* Copyright (c) 2012, 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include + +int linear_interpolate(int y0, int x0, int y1, int x1, int x) +{ + if (y0 == y1 || x == x0) + return y0; + if (x1 == x0 || x == x1) + return y1; + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} + +int is_between(int left, int right, int value) +{ + if (left >= right && left >= value && value >= right) + return 1; + if (left <= right && left <= value && value <= right) + return 1; + return 0; +} + +static int interpolate_single_lut(struct single_row_lut *lut, int x) +{ + int i, result; + + if (x < lut->x[0]) { + pr_debug("x %d less than known range return y = %d lut = %pS\n", + x, lut->y[0], lut); + return lut->y[0]; + } + if (x > lut->x[lut->cols - 1]) { + pr_debug("x %d more than known range return y = %d lut = %pS\n", + x, lut->y[lut->cols - 1], lut); + return lut->y[lut->cols - 1]; + } + + for (i = 0; i < lut->cols; i++) + if (x <= lut->x[i]) + break; + if (x == lut->x[i]) { + result = lut->y[i]; + } else { + result = linear_interpolate( + lut->y[i - 1], + lut->x[i - 1], + lut->y[i], + lut->x[i], + x); + } + return result; +} + +int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp) +{ + /* batt_temp is in tenths of degC - convert it to degC for lookups */ + batt_temp = batt_temp/10; + return interpolate_single_lut(fcc_temp_lut, batt_temp); +} + +int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut, + int cycles) +{ + /* + * sf table could be null when no battery aging data is available, in + * that case return 100% + */ + if (fcc_sf_lut) + return interpolate_single_lut(fcc_sf_lut, cycles); + else + return 100; +} + +int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc) +{ + int i, scalefactorrow1, scalefactorrow2, scalefactor, rows, cols; + int row1 = 0; + int row2 = 0; + + /* + * sf table could be null when no battery aging data is available, in + * that case return 100% + */ + if (!sf_lut) + return 100; + + rows = sf_lut->rows; + cols = sf_lut->cols; + if (pc > sf_lut->percent[0]) { + pr_debug("pc %d greater than known pc ranges for sfd\n", pc); + row1 = 0; + row2 = 0; + } + if (pc < sf_lut->percent[rows - 1]) { + pr_debug("pc %d less than known pc ranges for sf\n", pc); + row1 = rows - 1; + row2 = rows - 1; + } + for (i = 0; i < rows; i++) { + if (pc == sf_lut->percent[i]) { + row1 = i; + row2 = i; + break; + } + if (pc > sf_lut->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + + if (row_entry < sf_lut->row_entries[0]) + row_entry = sf_lut->row_entries[0]; + if (row_entry > sf_lut->row_entries[cols - 1]) + row_entry = sf_lut->row_entries[cols - 1]; + + for (i = 0; i < cols; i++) + if (row_entry <= sf_lut->row_entries[i]) + break; + if (row_entry == sf_lut->row_entries[i]) { + scalefactor = linear_interpolate( + sf_lut->sf[row1][i], + sf_lut->percent[row1], + sf_lut->sf[row2][i], + sf_lut->percent[row2], + pc); + return scalefactor; + } + + scalefactorrow1 = linear_interpolate( + sf_lut->sf[row1][i - 1], + sf_lut->row_entries[i - 1], + sf_lut->sf[row1][i], + sf_lut->row_entries[i], + row_entry); + + scalefactorrow2 = linear_interpolate( + sf_lut->sf[row2][i - 1], + sf_lut->row_entries[i - 1], + sf_lut->sf[row2][i], + sf_lut->row_entries[i], + row_entry); + + scalefactor = linear_interpolate( + scalefactorrow1, + sf_lut->percent[row1], + scalefactorrow2, + sf_lut->percent[row2], + pc); + + return scalefactor; +} + +/* get ocv given a soc -- reverse lookup */ +int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int pc) +{ + int i, ocvrow1, ocvrow2, ocv, rows, cols; + int row1 = 0; + int row2 = 0; + + rows = pc_temp_ocv->rows; + cols = pc_temp_ocv->cols; + if (pc > pc_temp_ocv->percent[0]) { + pr_debug("pc %d greater than known pc ranges for sfd\n", pc); + row1 = 0; + row2 = 0; + } + if (pc < pc_temp_ocv->percent[rows - 1]) { + pr_debug("pc %d less than known pc ranges for sf\n", pc); + row1 = rows - 1; + row2 = rows - 1; + } + for (i = 0; i < rows; i++) { + if (pc == pc_temp_ocv->percent[i]) { + row1 = i; + row2 = i; + break; + } + if (pc > pc_temp_ocv->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + + if (batt_temp_degc < pc_temp_ocv->temp[0]) + batt_temp_degc = pc_temp_ocv->temp[0]; + if (batt_temp_degc > pc_temp_ocv->temp[cols - 1]) + batt_temp_degc = pc_temp_ocv->temp[cols - 1]; + + for (i = 0; i < cols; i++) + if (batt_temp_degc <= pc_temp_ocv->temp[i]) + break; + if (batt_temp_degc == pc_temp_ocv->temp[i]) { + ocv = linear_interpolate( + pc_temp_ocv->ocv[row1][i], + pc_temp_ocv->percent[row1], + pc_temp_ocv->ocv[row2][i], + pc_temp_ocv->percent[row2], + pc); + return ocv; + } + + ocvrow1 = linear_interpolate( + pc_temp_ocv->ocv[row1][i - 1], + pc_temp_ocv->temp[i - 1], + pc_temp_ocv->ocv[row1][i], + pc_temp_ocv->temp[i], + batt_temp_degc); + + ocvrow2 = linear_interpolate( + pc_temp_ocv->ocv[row2][i - 1], + pc_temp_ocv->temp[i - 1], + pc_temp_ocv->ocv[row2][i], + pc_temp_ocv->temp[i], + batt_temp_degc); + + ocv = linear_interpolate( + ocvrow1, + pc_temp_ocv->percent[row1], + ocvrow2, + pc_temp_ocv->percent[row2], + pc); + + return ocv; +} + +int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int ocv) +{ + int i, j, pcj, pcj_minus_one, pc; + int rows = pc_temp_ocv->rows; + int cols = pc_temp_ocv->cols; + + if (batt_temp_degc < pc_temp_ocv->temp[0]) { + pr_debug("batt_temp %d < known temp range\n", batt_temp_degc); + batt_temp_degc = pc_temp_ocv->temp[0]; + } + + if (batt_temp_degc > pc_temp_ocv->temp[cols - 1]) { + pr_debug("batt_temp %d > known temp range\n", batt_temp_degc); + batt_temp_degc = pc_temp_ocv->temp[cols - 1]; + } + + for (j = 0; j < cols; j++) + if (batt_temp_degc <= pc_temp_ocv->temp[j]) + break; + if (batt_temp_degc == pc_temp_ocv->temp[j]) { + /* found an exact match for temp in the table */ + if (ocv >= pc_temp_ocv->ocv[0][j]) + return pc_temp_ocv->percent[0]; + if (ocv <= pc_temp_ocv->ocv[rows - 1][j]) + return pc_temp_ocv->percent[rows - 1]; + for (i = 0; i < rows; i++) { + if (ocv >= pc_temp_ocv->ocv[i][j]) { + if (ocv == pc_temp_ocv->ocv[i][j]) + return pc_temp_ocv->percent[i]; + pc = linear_interpolate( + pc_temp_ocv->percent[i], + pc_temp_ocv->ocv[i][j], + pc_temp_ocv->percent[i - 1], + pc_temp_ocv->ocv[i - 1][j], + ocv); + return pc; + } + } + } + + /* + * batt_temp_degc is within temperature for + * column j-1 and j + */ + if (ocv >= pc_temp_ocv->ocv[0][j]) + return pc_temp_ocv->percent[0]; + if (ocv <= pc_temp_ocv->ocv[rows - 1][j - 1]) + return pc_temp_ocv->percent[rows - 1]; + + pcj_minus_one = 0; + pcj = 0; + for (i = 0; i < rows-1; i++) { + if (pcj == 0 + && is_between(pc_temp_ocv->ocv[i][j], + pc_temp_ocv->ocv[i+1][j], ocv)) { + pcj = linear_interpolate( + pc_temp_ocv->percent[i], + pc_temp_ocv->ocv[i][j], + pc_temp_ocv->percent[i + 1], + pc_temp_ocv->ocv[i+1][j], + ocv); + } + + if (pcj_minus_one == 0 + && is_between(pc_temp_ocv->ocv[i][j-1], + pc_temp_ocv->ocv[i+1][j-1], ocv)) { + pcj_minus_one = linear_interpolate( + pc_temp_ocv->percent[i], + pc_temp_ocv->ocv[i][j-1], + pc_temp_ocv->percent[i + 1], + pc_temp_ocv->ocv[i+1][j-1], + ocv); + } + + if (pcj && pcj_minus_one) { + pc = linear_interpolate( + pcj_minus_one, + pc_temp_ocv->temp[j-1], + pcj, + pc_temp_ocv->temp[j], + batt_temp_degc); + return pc; + } + } + + if (pcj) + return pcj; + + if (pcj_minus_one) + return pcj_minus_one; + + pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%\n", + ocv, batt_temp_degc); + return 100; +} diff --git a/arch/arm/mach-msm/bms-batterydata-desay.c b/arch/arm/mach-msm/bms-batterydata-desay.c index f362a72f325..d9fa06123a4 100644 --- a/arch/arm/mach-msm/bms-batterydata-desay.c +++ b/arch/arm/mach-msm/bms-batterydata-desay.c @@ -10,7 +10,7 @@ * GNU General Public License for more details. */ -#include +#include static struct single_row_lut desay_5200_fcc_temp = { .x = {-20, 0, 25, 40}, @@ -76,7 +76,7 @@ static struct sf_lut desay_5200_pc_sf = { }, }; -struct pm8921_bms_battery_data desay_5200_data = { +struct bms_battery_data desay_5200_data = { .fcc = 5200, .fcc_temp_lut = &desay_5200_fcc_temp, .fcc_sf_lut = &desay_5200_fcc_sf, diff --git a/arch/arm/mach-msm/bms-batterydata.c b/arch/arm/mach-msm/bms-batterydata.c index 81ab1217829..fb4f96776de 100644 --- a/arch/arm/mach-msm/bms-batterydata.c +++ b/arch/arm/mach-msm/bms-batterydata.c @@ -10,7 +10,7 @@ * GNU General Public License for more details. */ -#include +#include static struct single_row_lut fcc_temp = { .x = {-20, 0, 25, 40, 65}, @@ -99,7 +99,7 @@ static struct sf_lut rbatt_sf = { } }; -struct pm8921_bms_battery_data palladium_1500_data = { +struct bms_battery_data palladium_1500_data = { .fcc = 1500, .fcc_temp_lut = &fcc_temp, .pc_temp_ocv_lut = &pc_temp_ocv, diff --git a/arch/arm/mach-msm/board-8064-pmic.c b/arch/arm/mach-msm/board-8064-pmic.c index 9874cb692f6..c9a1a5e0379 100644 --- a/arch/arm/mach-msm/board-8064-pmic.c +++ b/arch/arm/mach-msm/board-8064-pmic.c @@ -382,16 +382,17 @@ static int apq8064_pm8921_therm_mitigation[] = { #define CHG_TERM_MA 100 static struct pm8921_charger_platform_data apq8064_pm8921_chg_pdata __devinitdata = { - .safety_time = 180, .update_time = 60000, .max_voltage = MAX_VOLTAGE_MV, .min_voltage = 3200, .uvd_thresh_voltage = 4050, - .alarm_voltage = 3400, - .resume_voltage_delta = 100, + .alarm_low_mv = 3400, + .alarm_high_mv = 4000, + .resume_voltage_delta = 60, + .resume_charge_percent = 99, .term_current = CHG_TERM_MA, .cool_temp = 10, - .warm_temp = 40, + .warm_temp = 45, .temp_check_period = 1, .max_bat_chg_current = 1100, .cool_bat_chg_current = 350, @@ -404,20 +405,22 @@ apq8064_pm8921_chg_pdata __devinitdata = { static struct pm8xxx_ccadc_platform_data apq8064_pm8xxx_ccadc_pdata = { - .r_sense = 10, + .r_sense_uohm = 10000, .calib_delay_ms = 600000, }; static struct pm8921_bms_platform_data apq8064_pm8921_bms_pdata __devinitdata = { .battery_type = BATT_UNKNOWN, - .r_sense = 10, + .r_sense_uohm = 10000, .v_cutoff = 3400, .max_voltage_uv = MAX_VOLTAGE_MV * 1000, .rconn_mohm = 18, .shutdown_soc_valid_limit = 20, .adjust_soc_low_threshold = 25, .chg_term_ua = CHG_TERM_MA * 1000, + .normal_voltage_calc_ms = 20000, + .low_voltage_calc_ms = 1000, }; static struct pm8921_platform_data @@ -497,4 +500,7 @@ void __init apq8064_init_pmic(void) } else if (machine_is_apq8064_cdp()) { apq8064_pm8921_chg_pdata.has_dc_supply = true; } + + if (!machine_is_apq8064_mtp() && !machine_is_apq8064_liquid()) + apq8064_pm8921_chg_pdata.battery_less_hardware = 1; } diff --git a/arch/arm/mach-msm/board-8064-regulator.c b/arch/arm/mach-msm/board-8064-regulator.c index 4ae811d0d68..125e7da03d0 100644 --- a/arch/arm/mach-msm/board-8064-regulator.c +++ b/arch/arm/mach-msm/board-8064-regulator.c @@ -90,6 +90,7 @@ VREG_CONSUMERS(L13) = { }; VREG_CONSUMERS(L14) = { REGULATOR_SUPPLY("8921_l14", NULL), + REGULATOR_SUPPLY("vreg_xoadc", "pm8921-charger"), }; VREG_CONSUMERS(L15) = { REGULATOR_SUPPLY("8921_l15", NULL), diff --git a/arch/arm/mach-msm/board-8930-pmic.c b/arch/arm/mach-msm/board-8930-pmic.c index 4bc1842393b..f997a03c298 100644 --- a/arch/arm/mach-msm/board-8930-pmic.c +++ b/arch/arm/mach-msm/board-8930-pmic.c @@ -313,16 +313,17 @@ static int pm8921_therm_mitigation[] = { #define MAX_VOLTAGE_MV 4200 #define CHG_TERM_MA 100 static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = { - .safety_time = 180, .update_time = 60000, .max_voltage = MAX_VOLTAGE_MV, .min_voltage = 3200, .uvd_thresh_voltage = 4050, - .alarm_voltage = 3400, - .resume_voltage_delta = 100, + .alarm_low_mv = 3400, + .alarm_high_mv = 4000, + .resume_voltage_delta = 60, + .resume_charge_percent = 99, .term_current = CHG_TERM_MA, .cool_temp = 10, - .warm_temp = 40, + .warm_temp = 45, .temp_check_period = 1, .max_bat_chg_current = 1100, .cool_bat_chg_current = 350, @@ -434,7 +435,7 @@ static struct pm8xxx_led_platform_data pm8xxx_leds_pdata = { }; static struct pm8xxx_ccadc_platform_data pm8xxx_ccadc_pdata = { - .r_sense = 10, + .r_sense_uohm = 10000, .calib_delay_ms = 600000, }; @@ -462,13 +463,15 @@ static struct pm8xxx_spk_platform_data pm8xxx_spk_pdata = { static struct pm8921_bms_platform_data pm8921_bms_pdata __devinitdata = { .battery_type = BATT_UNKNOWN, - .r_sense = 10, + .r_sense_uohm = 10000, .v_cutoff = 3400, .max_voltage_uv = MAX_VOLTAGE_MV * 1000, .shutdown_soc_valid_limit = 20, .adjust_soc_low_threshold = 25, .chg_term_ua = CHG_TERM_MA * 1000, .rconn_mohm = 18, + .normal_voltage_calc_ms = 20000, + .low_voltage_calc_ms = 1000, }; static struct pm8038_platform_data pm8038_platform_data __devinitdata = { @@ -590,4 +593,7 @@ void __init msm8930_init_pmic(void) else if (machine_is_msm8930_cdp()) pm8921_chg_pdata.has_dc_supply = true; } + + if (!machine_is_msm8930_mtp()) + pm8921_chg_pdata.battery_less_hardware = 1; } diff --git a/arch/arm/mach-msm/board-8960-pmic.c b/arch/arm/mach-msm/board-8960-pmic.c index 44d19d4b164..abf88e17483 100644 --- a/arch/arm/mach-msm/board-8960-pmic.c +++ b/arch/arm/mach-msm/board-8960-pmic.c @@ -396,16 +396,17 @@ static int pm8921_therm_mitigation[] = { #define MAX_VOLTAGE_MV 4200 #define CHG_TERM_MA 100 static struct pm8921_charger_platform_data pm8921_chg_pdata __devinitdata = { - .safety_time = 180, .update_time = 60000, .max_voltage = MAX_VOLTAGE_MV, .min_voltage = 3200, .uvd_thresh_voltage = 4050, - .alarm_voltage = 3400, - .resume_voltage_delta = 100, + .alarm_low_mv = 3400, + .alarm_high_mv = 4000, + .resume_voltage_delta = 60, + .resume_charge_percent = 99, .term_current = CHG_TERM_MA, .cool_temp = 10, - .warm_temp = 40, + .warm_temp = 45, .temp_check_period = 1, .max_bat_chg_current = 1100, .cool_bat_chg_current = 350, @@ -423,13 +424,15 @@ static struct pm8xxx_misc_platform_data pm8xxx_misc_pdata = { static struct pm8921_bms_platform_data pm8921_bms_pdata __devinitdata = { .battery_type = BATT_UNKNOWN, - .r_sense = 10, + .r_sense_uohm = 10000, .v_cutoff = 3400, .max_voltage_uv = MAX_VOLTAGE_MV * 1000, .rconn_mohm = 18, .shutdown_soc_valid_limit = 20, .adjust_soc_low_threshold = 25, .chg_term_ua = CHG_TERM_MA * 1000, + .normal_voltage_calc_ms = 20000, + .low_voltage_calc_ms = 1000, }; #define PM8921_LC_LED_MAX_CURRENT 4 /* I = 4mA */ @@ -551,7 +554,7 @@ static struct pm8xxx_led_platform_data pm8xxx_leds_pdata = { }; static struct pm8xxx_ccadc_platform_data pm8xxx_ccadc_pdata = { - .r_sense = 10, + .r_sense_uohm = 10000, .calib_delay_ms = 600000, }; @@ -610,4 +613,8 @@ void __init msm8960_init_pmic(void) if (machine_is_msm8960_fluid()) pm8921_bms_pdata.rconn_mohm = 20; + + if (!machine_is_msm8960_fluid() && !machine_is_msm8960_liquid() + && !machine_is_msm8960_mtp()) + pm8921_chg_pdata.battery_less_hardware = 1; } diff --git a/arch/arm/mach-msm/board-8960-regulator.c b/arch/arm/mach-msm/board-8960-regulator.c index 8fc26ea4678..b666ae9ec0c 100644 --- a/arch/arm/mach-msm/board-8960-regulator.c +++ b/arch/arm/mach-msm/board-8960-regulator.c @@ -89,6 +89,7 @@ VREG_CONSUMERS(L12) = { VREG_CONSUMERS(L14) = { REGULATOR_SUPPLY("8921_l14", NULL), REGULATOR_SUPPLY("pa_therm", "pm8xxx-adc"), + REGULATOR_SUPPLY("vreg_xoadc", "pm8921-charger"), }; VREG_CONSUMERS(L15) = { REGULATOR_SUPPLY("8921_l15", NULL), diff --git a/drivers/mfd/pm8018-core.c b/drivers/mfd/pm8018-core.c index b1b64cb9d1a..a91152fdb59 100644 --- a/drivers/mfd/pm8018-core.c +++ b/drivers/mfd/pm8018-core.c @@ -45,7 +45,6 @@ #define PM8018_REVISION_MASK 0x000F #define REG_PM8018_PON_CNTRL_3 0x01D -#define PM8018_RESTART_REASON_MASK 0x07 #define SINGLE_IRQ_RESOURCE(_name, _irq) \ { \ @@ -61,6 +60,7 @@ struct pm8018 { struct mfd_cell *mfd_regulators; struct pm8xxx_regulator_core_platform_data *regulator_cdata; u32 rev_registers; + u8 restart_reason; }; static int pm8018_readb(const struct device *dev, u16 addr, u8 *val) @@ -125,6 +125,14 @@ static int pm8018_get_revision(const struct device *dev) return pmic->rev_registers & PM8018_REVISION_MASK; } +static u8 pm8018_restart_reason(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8018_drvdata = dev_get_drvdata(dev); + const struct pm8018 *pmic = pm8018_drvdata->pm_chip_data; + + return pmic->restart_reason; +} + static struct pm8xxx_drvdata pm8018_drvdata = { .pmic_readb = pm8018_readb, .pmic_writeb = pm8018_writeb, @@ -133,6 +141,7 @@ static struct pm8xxx_drvdata pm8018_drvdata = { .pmic_read_irq_stat = pm8018_read_irq_stat, .pmic_get_version = pm8018_get_version, .pmic_get_revision = pm8018_get_revision, + .pmic_restart_reason = pm8018_restart_reason, }; static const struct resource gpio_cell_resources[] __devinitconst = { @@ -516,17 +525,6 @@ bail: return ret; } -static const char * const pm8018_restart_reason[] = { - [0] = "Unknown", - [1] = "Triggered from CBL (external charger)", - [2] = "Triggered from KPD (power key press)", - [3] = "Triggered from CHG (usb charger insertion)", - [4] = "Triggered from SMPL (sudden momentary power loss)", - [5] = "Triggered from RTC (real time clock)", - [6] = "Triggered by Hard Reset", - [7] = "Triggered by General Purpose Trigger", -}; - static const char * const pm8018_rev_names[] = { [PM8XXX_REVISION_8018_TEST] = "test", [PM8XXX_REVISION_8018_1p0] = "1.0", @@ -594,8 +592,9 @@ static int __devinit pm8018_probe(struct platform_device *pdev) pr_err("Cannot read restart reason rc=%d\n", rc); goto err_read_rev; } - val &= PM8018_RESTART_REASON_MASK; - pr_info("PMIC Restart Reason: %s\n", pm8018_restart_reason[val]); + val &= PM8XXX_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8xxx_restart_reason_str[val]); + pmic->restart_reason = val; rc = pm8018_add_subdevices(pdata, pmic); if (rc) { diff --git a/drivers/mfd/pm8038-core.c b/drivers/mfd/pm8038-core.c index c3203c672a1..479fc02c8fe 100644 --- a/drivers/mfd/pm8038-core.c +++ b/drivers/mfd/pm8038-core.c @@ -49,7 +49,6 @@ #define PM8038_REVISION_MASK 0x000F #define REG_PM8038_PON_CNTRL_3 0x01D -#define PM8038_RESTART_REASON_MASK 0x07 #define SINGLE_IRQ_RESOURCE(_name, _irq) \ { \ @@ -65,6 +64,7 @@ struct pm8038 { struct mfd_cell *mfd_regulators; struct pm8xxx_regulator_core_platform_data *regulator_cdata; u32 rev_registers; + u8 restart_reason; }; static int pm8038_readb(const struct device *dev, u16 addr, u8 *val) @@ -129,6 +129,14 @@ static int pm8038_get_revision(const struct device *dev) return pmic->rev_registers & PM8038_REVISION_MASK; } +static u8 pm8038_restart_reason(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8038_drvdata = dev_get_drvdata(dev); + const struct pm8038 *pmic = pm8038_drvdata->pm_chip_data; + + return pmic->restart_reason; +} + static struct pm8xxx_drvdata pm8038_drvdata = { .pmic_readb = pm8038_readb, .pmic_writeb = pm8038_writeb, @@ -137,6 +145,7 @@ static struct pm8xxx_drvdata pm8038_drvdata = { .pmic_read_irq_stat = pm8038_read_irq_stat, .pmic_get_version = pm8038_get_version, .pmic_get_revision = pm8038_get_revision, + .pmic_restart_reason = pm8038_restart_reason, }; static const struct resource gpio_cell_resources[] __devinitconst = { @@ -702,6 +711,8 @@ pm8038_add_subdevices(const struct pm8038_platform_data *pdata, } if (pdata->ccadc_pdata) { + pdata->ccadc_pdata->ccadc_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; ccadc_cell.platform_data = pdata->ccadc_pdata; ccadc_cell.pdata_size = sizeof(struct pm8xxx_ccadc_platform_data); @@ -723,17 +734,6 @@ bail: return ret; } -static const char * const pm8038_restart_reason[] = { - [0] = "Unknown", - [1] = "Triggered from CBL (external charger)", - [2] = "Triggered from KPD (power key press)", - [3] = "Triggered from CHG (usb charger insertion)", - [4] = "Triggered from SMPL (sudden momentary power loss)", - [5] = "Triggered from RTC (real time clock)", - [6] = "Triggered by Hard Reset", - [7] = "Triggered by General Purpose Trigger", -}; - static const char * const pm8038_rev_names[] = { [PM8XXX_REVISION_8038_TEST] = "test", [PM8XXX_REVISION_8038_1p0] = "1.0", @@ -802,8 +802,9 @@ static int __devinit pm8038_probe(struct platform_device *pdev) pr_err("Cannot read restart reason rc=%d\n", rc); goto err_read_rev; } - val &= PM8038_RESTART_REASON_MASK; - pr_info("PMIC Restart Reason: %s\n", pm8038_restart_reason[val]); + val &= PM8XXX_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8xxx_restart_reason_str[val]); + pmic->restart_reason = val; rc = pm8038_add_subdevices(pdata, pmic); if (rc) { diff --git a/drivers/mfd/pm8921-core.c b/drivers/mfd/pm8921-core.c index f39a19f9420..685bb320688 100644 --- a/drivers/mfd/pm8921-core.c +++ b/drivers/mfd/pm8921-core.c @@ -63,6 +63,7 @@ struct pm8921 { struct mfd_cell *mfd_regulators; struct pm8xxx_regulator_core_platform_data *regulator_cdata; u32 rev_registers; + u8 restart_reason; }; static int pm8921_readb(const struct device *dev, u16 addr, u8 *val) @@ -133,6 +134,14 @@ static int pm8921_get_revision(const struct device *dev) return pmic->rev_registers & PM8921_REVISION_MASK; } +static u8 pm8921_restart_reason(const struct device *dev) +{ + const struct pm8xxx_drvdata *pm8921_drvdata = dev_get_drvdata(dev); + const struct pm8921 *pmic = pm8921_drvdata->pm_chip_data; + + return pmic->restart_reason; +} + static struct pm8xxx_drvdata pm8921_drvdata = { .pmic_readb = pm8921_readb, .pmic_writeb = pm8921_writeb, @@ -141,6 +150,7 @@ static struct pm8xxx_drvdata pm8921_drvdata = { .pmic_read_irq_stat = pm8921_read_irq_stat, .pmic_get_version = pm8921_get_version, .pmic_get_revision = pm8921_get_revision, + .pmic_restart_reason = pm8921_restart_reason, }; static struct resource gpio_cell_resources[] = { @@ -794,6 +804,8 @@ pm8921_add_subdevices(const struct pm8921_platform_data *pdata, } if (pdata->ccadc_pdata) { + pdata->ccadc_pdata->ccadc_cdata.batt_temp_channel + = CHANNEL_BATT_THERM; ccadc_cell.platform_data = pdata->ccadc_pdata; ccadc_cell.pdata_size = sizeof(struct pm8xxx_ccadc_platform_data); @@ -815,17 +827,6 @@ bail: return ret; } -static const char * const pm8921_restart_reason[] = { - [0] = "Unknown", - [1] = "Triggered from CBL (external charger)", - [2] = "Triggered from KPD (power key press)", - [3] = "Triggered from CHG (usb charger insertion)", - [4] = "Triggered from SMPL (sudden momentary power loss)", - [5] = "Triggered from RTC (real time clock)", - [6] = "Triggered by Hard Reset", - [7] = "Triggered by General Purpose Trigger", -}; - static const char * const pm8921_rev_names[] = { [PM8XXX_REVISION_8921_TEST] = "test", [PM8XXX_REVISION_8921_1p0] = "1.0", @@ -918,8 +919,9 @@ static int __devinit pm8921_probe(struct platform_device *pdev) pr_err("Cannot read restart reason rc=%d\n", rc); goto err_read_rev; } - val &= PM8921_RESTART_REASON_MASK; - pr_info("PMIC Restart Reason: %s\n", pm8921_restart_reason[val]); + val &= PM8XXX_RESTART_REASON_MASK; + pr_info("PMIC Restart Reason: %s\n", pm8xxx_restart_reason_str[val]); + pmic->restart_reason = val; rc = pm8921_add_subdevices(pdata, pmic); if (rc) { diff --git a/drivers/power/ltc4088-charger.c b/drivers/power/ltc4088-charger.c index dbc75cd463a..58503cf1a5f 100644 --- a/drivers/power/ltc4088-charger.c +++ b/drivers/power/ltc4088-charger.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2011, Code Aurora Forum. All rights reserved. + * Copyright (c) 2011-2012, Code Aurora Forum. 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 @@ -206,9 +206,11 @@ static int pm_power_set_property(struct power_supply *psy, struct ltc4088_chg_chip *chip; if (psy->type == POWER_SUPPLY_TYPE_USB) { - chip = container_of(psy, struct ltc4088_chg_chip, - usb_psy); + chip = container_of(psy, struct ltc4088_chg_chip, usb_psy); switch (psp) { + case POWER_SUPPLY_PROP_TYPE: + psy.type = val->intval; + break; case POWER_SUPPLY_PROP_ONLINE: ltc4088_set_charging(chip, val->intval); break; diff --git a/drivers/power/pm8921-bms.c b/drivers/power/pm8921-bms.c index c19015f6490..f9ed816a01c 100644 --- a/drivers/power/pm8921-bms.c +++ b/drivers/power/pm8921-bms.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011-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 @@ -10,8 +10,8 @@ * GNU General Public License for more details. * */ -#define pr_fmt(fmt) "%s: " fmt, __func__ +#define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -54,6 +55,9 @@ #define TEMP_IAVG_STORAGE 0x105 #define TEMP_IAVG_STORAGE_USE_MASK 0x0F +#define PON_CNTRL_6 0x018 +#define WD_BIT BIT(7) + enum pmic_bms_interrupts { PM8921_BMS_SBI_WRITE_OK, PM8921_BMS_CC_THR, @@ -86,7 +90,7 @@ struct pm8921_soc_params { struct pm8921_bms_chip { struct device *dev; struct dentry *dent; - unsigned int r_sense; + int r_sense_uohm; unsigned int v_cutoff; unsigned int fcc; struct single_row_lut *fcc_temp_lut; @@ -96,7 +100,8 @@ struct pm8921_bms_chip { struct sf_lut *rbatt_sf_lut; int delta_rbatt_mohm; struct work_struct calib_hkadc_work; - struct delayed_work calib_hkadc_delayed_work; + unsigned long last_calib_time; + int last_calib_temp; struct mutex calib_mutex; unsigned int revision; unsigned int xoadc_v0625_usb_present; @@ -126,9 +131,10 @@ struct pm8921_bms_chip { int default_rbatt_mohm; int amux_2_trim_delta; uint16_t prev_last_good_ocv_raw; - unsigned int rconn_mohm; + int rconn_mohm; struct mutex last_ocv_uv_mutex; int last_ocv_uv; + int last_ocv_temp_decidegc; int pon_ocv_uv; int last_cc_uah; unsigned long tm_sec; @@ -137,6 +143,7 @@ struct pm8921_bms_chip { int shutdown_iavg_ua; struct delayed_work calculate_soc_delayed_work; struct timespec t_soc_queried; + unsigned long last_recalc_time; int shutdown_soc_valid_limit; int ignore_shutdown_soc; int prev_iavg_ua; @@ -147,6 +154,14 @@ struct pm8921_bms_chip { int ibat_at_cv_ua; int soc_at_cv; int prev_chg_soc; + struct power_supply *batt_psy; + bool low_voltage_wake_lock_held; + struct wake_lock low_voltage_wake_lock; + int soc_calc_period; + int normal_voltage_calc_ms; + int low_voltage_calc_ms; + int imax_ua; + struct wake_lock soc_wake_lock; }; /* @@ -222,7 +237,6 @@ module_param_cb(bms_end_percent, &bms_ro_param_ops, &bms_end_percent, 0644); module_param_cb(bms_end_ocv_uv, &bms_ro_param_ops, &bms_end_ocv_uv, 0644); module_param_cb(bms_end_cc_uah, &bms_ro_param_ops, &bms_end_cc_uah, 0644); -static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp); static void readjust_fcc_table(void) { struct single_row_lut *temp, *old; @@ -239,7 +253,7 @@ static void readjust_fcc_table(void) return; } - fcc = interpolate_fcc(the_chip, last_real_fcc_batt_temp); + fcc = interpolate_fcc(the_chip->fcc_temp_lut, last_real_fcc_batt_temp); temp->cols = the_chip->fcc_temp_lut->cols; for (i = 0; i < the_chip->fcc_temp_lut->cols; i++) { @@ -346,13 +360,18 @@ static int pm_bms_masked_write(struct pm8921_bms_chip *chip, u16 addr, return 0; } -static int usb_chg_plugged_in(void) +static int usb_chg_plugged_in(struct pm8921_bms_chip *chip) { int val = pm8921_is_usb_chg_plugged_in(); - /* treat as if usb is not present in case of error */ - if (val == -EINVAL) - val = 0; + /* if the charger driver was not initialized, use the restart reason */ + if (val == -EINVAL) { + if (pm8xxx_restart_reason(chip->dev->parent) + == PM8XXX_RESTART_CHG) + val = 1; + else + val = 0; + } return val; } @@ -492,15 +511,15 @@ static s64 cc_to_microvolt(struct pm8921_bms_chip *chip, s64 cc) #define SLEEP_CLK_HZ 32764 #define SECONDS_PER_HOUR 3600 /** - * ccmicrovolt_to_nvh - + * ccmicrovolt_to_uvh - * @cc_uv: coulumb counter converted to uV * - * RETURNS: coulumb counter based charge in nVh - * (nano Volt Hour) + * RETURNS: coulumb counter based charge in uVh + * (micro Volt Hour) */ -static s64 ccmicrovolt_to_nvh(s64 cc_uv) +static s64 ccmicrovolt_to_uvh(s64 cc_uv) { - return div_s64(cc_uv * CC_READING_TICKS * 1000, + return div_s64(cc_uv * CC_READING_TICKS, SLEEP_CLK_HZ * SECONDS_PER_HOUR); } @@ -510,6 +529,7 @@ static int read_cc(struct pm8921_bms_chip *chip, int *result) int rc; uint16_t msw, lsw; + *result = 0; rc = pm_bms_read_output_data(chip, CC_LSB, &lsw); if (rc) { pr_err("fail to read CC_LSB rc = %d\n", rc); @@ -576,344 +596,6 @@ static int read_vsense_avg(struct pm8921_bms_chip *chip, int *result) return 0; } -static int linear_interpolate(int y0, int x0, int y1, int x1, int x) -{ - if (y0 == y1 || x == x0) - return y0; - if (x1 == x0 || x == x1) - return y1; - - return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); -} - -static int interpolate_single_lut(struct single_row_lut *lut, int x) -{ - int i, result; - - if (x < lut->x[0]) { - pr_debug("x %d less than known range return y = %d lut = %pS\n", - x, lut->y[0], lut); - return lut->y[0]; - } - if (x > lut->x[lut->cols - 1]) { - pr_debug("x %d more than known range return y = %d lut = %pS\n", - x, lut->y[lut->cols - 1], lut); - return lut->y[lut->cols - 1]; - } - - for (i = 0; i < lut->cols; i++) - if (x <= lut->x[i]) - break; - if (x == lut->x[i]) { - result = lut->y[i]; - } else { - result = linear_interpolate( - lut->y[i - 1], - lut->x[i - 1], - lut->y[i], - lut->x[i], - x); - } - return result; -} - -static int interpolate_fcc(struct pm8921_bms_chip *chip, int batt_temp) -{ - /* batt_temp is in tenths of degC - convert it to degC for lookups */ - batt_temp = batt_temp/10; - return interpolate_single_lut(chip->fcc_temp_lut, batt_temp); -} - -static int interpolate_fcc_adjusted(struct pm8921_bms_chip *chip, int batt_temp) -{ - /* batt_temp is in tenths of degC - convert it to degC for lookups */ - batt_temp = batt_temp/10; - return interpolate_single_lut(chip->adjusted_fcc_temp_lut, batt_temp); -} - -static int interpolate_scalingfactor_fcc(struct pm8921_bms_chip *chip, - int cycles) -{ - /* - * sf table could be null when no battery aging data is available, in - * that case return 100% - */ - if (chip->fcc_sf_lut) - return interpolate_single_lut(chip->fcc_sf_lut, cycles); - else - return 100; -} - -static int interpolate_scalingfactor(struct pm8921_bms_chip *chip, - struct sf_lut *sf_lut, - int row_entry, int pc) -{ - int i, scalefactorrow1, scalefactorrow2, scalefactor; - int rows, cols; - int row1 = 0; - int row2 = 0; - - /* - * sf table could be null when no battery aging data is available, in - * that case return 100% - */ - if (!sf_lut) - return 100; - - rows = sf_lut->rows; - cols = sf_lut->cols; - if (pc > sf_lut->percent[0]) { - pr_debug("pc %d greater than known pc ranges for sfd\n", pc); - row1 = 0; - row2 = 0; - } - if (pc < sf_lut->percent[rows - 1]) { - pr_debug("pc %d less than known pc ranges for sf", pc); - row1 = rows - 1; - row2 = rows - 1; - } - for (i = 0; i < rows; i++) { - if (pc == sf_lut->percent[i]) { - row1 = i; - row2 = i; - break; - } - if (pc > sf_lut->percent[i]) { - row1 = i - 1; - row2 = i; - break; - } - } - - if (row_entry < sf_lut->row_entries[0]) - row_entry = sf_lut->row_entries[0]; - if (row_entry > sf_lut->row_entries[cols - 1]) - row_entry = sf_lut->row_entries[cols - 1]; - - for (i = 0; i < cols; i++) - if (row_entry <= sf_lut->row_entries[i]) - break; - if (row_entry == sf_lut->row_entries[i]) { - scalefactor = linear_interpolate( - sf_lut->sf[row1][i], - sf_lut->percent[row1], - sf_lut->sf[row2][i], - sf_lut->percent[row2], - pc); - return scalefactor; - } - - scalefactorrow1 = linear_interpolate( - sf_lut->sf[row1][i - 1], - sf_lut->row_entries[i - 1], - sf_lut->sf[row1][i], - sf_lut->row_entries[i], - row_entry); - - scalefactorrow2 = linear_interpolate( - sf_lut->sf[row2][i - 1], - sf_lut->row_entries[i - 1], - sf_lut->sf[row2][i], - sf_lut->row_entries[i], - row_entry); - - scalefactor = linear_interpolate( - scalefactorrow1, - sf_lut->percent[row1], - scalefactorrow2, - sf_lut->percent[row2], - pc); - - return scalefactor; -} - -static int is_between(int left, int right, int value) -{ - if (left >= right && left >= value && value >= right) - return 1; - if (left <= right && left <= value && value <= right) - return 1; - - return 0; -} - -/* get ocv given a soc -- reverse lookup */ -static int interpolate_ocv(struct pm8921_bms_chip *chip, - int batt_temp_degc, int pc) -{ - int i, ocvrow1, ocvrow2, ocv; - int rows, cols; - int row1 = 0; - int row2 = 0; - - rows = chip->pc_temp_ocv_lut->rows; - cols = chip->pc_temp_ocv_lut->cols; - if (pc > chip->pc_temp_ocv_lut->percent[0]) { - pr_debug("pc %d greater than known pc ranges for sfd\n", pc); - row1 = 0; - row2 = 0; - } - if (pc < chip->pc_temp_ocv_lut->percent[rows - 1]) { - pr_debug("pc %d less than known pc ranges for sf\n", pc); - row1 = rows - 1; - row2 = rows - 1; - } - for (i = 0; i < rows; i++) { - if (pc == chip->pc_temp_ocv_lut->percent[i]) { - row1 = i; - row2 = i; - break; - } - if (pc > chip->pc_temp_ocv_lut->percent[i]) { - row1 = i - 1; - row2 = i; - break; - } - } - - if (batt_temp_degc < chip->pc_temp_ocv_lut->temp[0]) - batt_temp_degc = chip->pc_temp_ocv_lut->temp[0]; - if (batt_temp_degc > chip->pc_temp_ocv_lut->temp[cols - 1]) - batt_temp_degc = chip->pc_temp_ocv_lut->temp[cols - 1]; - - for (i = 0; i < cols; i++) - if (batt_temp_degc <= chip->pc_temp_ocv_lut->temp[i]) - break; - if (batt_temp_degc == chip->pc_temp_ocv_lut->temp[i]) { - ocv = linear_interpolate( - chip->pc_temp_ocv_lut->ocv[row1][i], - chip->pc_temp_ocv_lut->percent[row1], - chip->pc_temp_ocv_lut->ocv[row2][i], - chip->pc_temp_ocv_lut->percent[row2], - pc); - return ocv; - } - - ocvrow1 = linear_interpolate( - chip->pc_temp_ocv_lut->ocv[row1][i - 1], - chip->pc_temp_ocv_lut->temp[i - 1], - chip->pc_temp_ocv_lut->ocv[row1][i], - chip->pc_temp_ocv_lut->temp[i], - batt_temp_degc); - - ocvrow2 = linear_interpolate( - chip->pc_temp_ocv_lut->ocv[row2][i - 1], - chip->pc_temp_ocv_lut->temp[i - 1], - chip->pc_temp_ocv_lut->ocv[row2][i], - chip->pc_temp_ocv_lut->temp[i], - batt_temp_degc); - - ocv = linear_interpolate( - ocvrow1, - chip->pc_temp_ocv_lut->percent[row1], - ocvrow2, - chip->pc_temp_ocv_lut->percent[row2], - pc); - - return ocv; -} - -static int interpolate_pc(struct pm8921_bms_chip *chip, - int batt_temp, int ocv) -{ - int i, j, pcj, pcj_minus_one, pc; - int rows = chip->pc_temp_ocv_lut->rows; - int cols = chip->pc_temp_ocv_lut->cols; - - /* batt_temp is in tenths of degC - convert it to degC for lookups */ - batt_temp = batt_temp/10; - - if (batt_temp < chip->pc_temp_ocv_lut->temp[0]) { - pr_debug("batt_temp %d < known temp range for pc\n", batt_temp); - batt_temp = chip->pc_temp_ocv_lut->temp[0]; - } - if (batt_temp > chip->pc_temp_ocv_lut->temp[cols - 1]) { - pr_debug("batt_temp %d > known temp range for pc\n", batt_temp); - batt_temp = chip->pc_temp_ocv_lut->temp[cols - 1]; - } - - for (j = 0; j < cols; j++) - if (batt_temp <= chip->pc_temp_ocv_lut->temp[j]) - break; - if (batt_temp == chip->pc_temp_ocv_lut->temp[j]) { - /* found an exact match for temp in the table */ - if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j]) - return chip->pc_temp_ocv_lut->percent[0]; - if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j]) - return chip->pc_temp_ocv_lut->percent[rows - 1]; - for (i = 0; i < rows; i++) { - if (ocv >= chip->pc_temp_ocv_lut->ocv[i][j]) { - if (ocv == chip->pc_temp_ocv_lut->ocv[i][j]) - return - chip->pc_temp_ocv_lut->percent[i]; - pc = linear_interpolate( - chip->pc_temp_ocv_lut->percent[i], - chip->pc_temp_ocv_lut->ocv[i][j], - chip->pc_temp_ocv_lut->percent[i - 1], - chip->pc_temp_ocv_lut->ocv[i - 1][j], - ocv); - return pc; - } - } - } - - /* - * batt_temp is within temperature for - * column j-1 and j - */ - if (ocv >= chip->pc_temp_ocv_lut->ocv[0][j]) - return chip->pc_temp_ocv_lut->percent[0]; - if (ocv <= chip->pc_temp_ocv_lut->ocv[rows - 1][j - 1]) - return chip->pc_temp_ocv_lut->percent[rows - 1]; - - pcj_minus_one = 0; - pcj = 0; - for (i = 0; i < rows-1; i++) { - if (pcj == 0 - && is_between(chip->pc_temp_ocv_lut->ocv[i][j], - chip->pc_temp_ocv_lut->ocv[i+1][j], ocv)) { - pcj = linear_interpolate( - chip->pc_temp_ocv_lut->percent[i], - chip->pc_temp_ocv_lut->ocv[i][j], - chip->pc_temp_ocv_lut->percent[i + 1], - chip->pc_temp_ocv_lut->ocv[i+1][j], - ocv); - } - - if (pcj_minus_one == 0 - && is_between(chip->pc_temp_ocv_lut->ocv[i][j-1], - chip->pc_temp_ocv_lut->ocv[i+1][j-1], ocv)) { - - pcj_minus_one = linear_interpolate( - chip->pc_temp_ocv_lut->percent[i], - chip->pc_temp_ocv_lut->ocv[i][j-1], - chip->pc_temp_ocv_lut->percent[i + 1], - chip->pc_temp_ocv_lut->ocv[i+1][j-1], - ocv); - } - - if (pcj && pcj_minus_one) { - pc = linear_interpolate( - pcj_minus_one, - chip->pc_temp_ocv_lut->temp[j-1], - pcj, - chip->pc_temp_ocv_lut->temp[j], - batt_temp); - return pc; - } - } - - if (pcj) - return pcj; - - if (pcj_minus_one) - return pcj_minus_one; - - pr_debug("%d ocv wasn't found for temp %d in the LUT returning 100%%", - ocv, batt_temp); - return 100; -} - #define BMS_MODE_BIT BIT(6) #define EN_VBAT_BIT BIT(5) #define OVERRIDE_MODE_DELAY_MS 20 @@ -944,11 +626,11 @@ int override_mode_simultaneous_battery_voltage_and_current(int *ibat_ua, mutex_unlock(&the_chip->bms_output_lock); - usb_chg = usb_chg_plugged_in(); + usb_chg = usb_chg_plugged_in(the_chip); convert_vbatt_raw_to_uv(the_chip, usb_chg, vbat_raw, vbat_uv); convert_vsense_to_uv(the_chip, vsense_raw, &vsense_uv); - *ibat_ua = vsense_uv * 1000 / (int)the_chip->r_sense; + *ibat_ua = div_s64((s64)vsense_uv * 1000000LL, the_chip->r_sense_uohm); pr_debug("vsense_raw = 0x%x vbat_raw = 0x%x" " ibat_ua = %d vbat_uv = %d\n", @@ -957,23 +639,106 @@ int override_mode_simultaneous_battery_voltage_and_current(int *ibat_ua, return 0; } -#define MBG_TRANSIENT_ERROR_RAW 51 -static void adjust_pon_ocv_raw(struct pm8921_bms_chip *chip, - struct pm8921_soc_params *raw) +#define MBG_TRANSIENT_ERROR_UV 15000 +static void adjust_pon_ocv(struct pm8921_bms_chip *chip, int *uv) { - /* in 8921 parts the PON ocv is taken when the MBG is not settled. + /* + * In 8921 parts the PON ocv is taken when the MBG is not settled. * decrease the pon ocv by 15mV raw value to account for it * Since a 1/3rd of vbatt is supplied to the adc the raw value * needs to be adjusted by 5mV worth bits */ - if (raw->last_good_ocv_raw >= MBG_TRANSIENT_ERROR_RAW) - raw->last_good_ocv_raw -= MBG_TRANSIENT_ERROR_RAW; + if (*uv >= MBG_TRANSIENT_ERROR_UV) + *uv -= MBG_TRANSIENT_ERROR_UV; } +#define SEL_ALT_OREG_BIT BIT(2) +static int ocv_ir_compensation(struct pm8921_bms_chip *chip, int ocv) +{ + int compensated_ocv; + int ibatt_ua; + int rbatt_mohm = chip->default_rbatt_mohm + chip->rconn_mohm; + + pm_bms_masked_write(chip, BMS_TEST1, + SEL_ALT_OREG_BIT, SEL_ALT_OREG_BIT); + + /* since the SEL_ALT_OREG_BIT is set this will give us VSENSE_OCV */ + pm8921_bms_get_battery_current(&ibatt_ua); + compensated_ocv = ocv + div_s64((s64)ibatt_ua * rbatt_mohm, 1000); + pr_debug("comp ocv = %d, ocv = %d, ibatt_ua = %d, rbatt_mohm = %d\n", + compensated_ocv, ocv, ibatt_ua, rbatt_mohm); + + pm_bms_masked_write(chip, BMS_TEST1, SEL_ALT_OREG_BIT, 0); + return compensated_ocv; +} + +#define RESET_CC_BIT BIT(3) +static int reset_cc(struct pm8921_bms_chip *chip) +{ + int rc; + + rc = pm_bms_masked_write(chip, BMS_TEST1, RESET_CC_BIT, RESET_CC_BIT); + if (rc < 0) { + pr_err("err setting cc reset rc = %d\n", rc); + return rc; + } + + /* sleep 100uS for the coulomb counter to reset */ + udelay(100); + + rc = pm_bms_masked_write(chip, BMS_TEST1, RESET_CC_BIT, 0); + if (rc < 0) + pr_err("err clearing cc reset rc = %d\n", rc); + return rc; +} + +static int estimate_ocv(struct pm8921_bms_chip *chip) +{ + int ibat_ua, vbat_uv, ocv_est_uv; + int rc; + + int rbatt_mohm = chip->default_rbatt_mohm + chip->rconn_mohm; + + rc = pm8921_bms_get_simultaneous_battery_voltage_and_current( + &ibat_ua, + &vbat_uv); + if (rc) { + pr_err("simultaneous failed rc = %d\n", rc); + return rc; + } + + ocv_est_uv = vbat_uv + (ibat_ua * rbatt_mohm) / 1000; + pr_debug("estimated pon ocv = %d\n", ocv_est_uv); + return ocv_est_uv; +} + +static bool is_warm_restart(struct pm8921_bms_chip *chip) +{ + u8 reg; + int rc; + + rc = pm8xxx_readb(chip->dev->parent, PON_CNTRL_6, ®); + if (rc) { + pr_err("err reading pon 6 rc = %d\n", rc); + return false; + } + return reg & WD_BIT; +} +/* + * This reflects what should the CC readings should be for + * a 5mAh discharge. This value is dependent on + * CC_RESOLUTION_N, CC_RESOLUTION_D, CC_READING_TICKS + * and rsense + */ +#define CC_RAW_5MAH 0x00110000 +#define MIN_OCV_UV 2000000 +#define OCV_RAW_UNINITIALIZED 0xFFFF static int read_soc_params_raw(struct pm8921_bms_chip *chip, - struct pm8921_soc_params *raw) + struct pm8921_soc_params *raw, + int batt_temp_decidegc) { int usb_chg; + int est_ocv_uv; mutex_lock(&chip->bms_output_lock); pm_bms_lock_output_data(chip); @@ -985,20 +750,52 @@ static int read_soc_params_raw(struct pm8921_bms_chip *chip, pm_bms_unlock_output_data(chip); mutex_unlock(&chip->bms_output_lock); - usb_chg = usb_chg_plugged_in(); + usb_chg = usb_chg_plugged_in(chip); - if (chip->prev_last_good_ocv_raw == 0) { + if (chip->prev_last_good_ocv_raw == OCV_RAW_UNINITIALIZED) { chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw; - adjust_pon_ocv_raw(chip, raw); + convert_vbatt_raw_to_uv(chip, usb_chg, raw->last_good_ocv_raw, &raw->last_good_ocv_uv); + adjust_pon_ocv(chip, &raw->last_good_ocv_uv); + raw->last_good_ocv_uv = ocv_ir_compensation(chip, + raw->last_good_ocv_uv); chip->last_ocv_uv = raw->last_good_ocv_uv; + + if (is_warm_restart(chip) + || raw->cc > CC_RAW_5MAH + || (raw->last_good_ocv_uv < MIN_OCV_UV + && raw->cc > 0)) { + /* + * The CC value is higher than 5mAh. + * The phone started without going through a pon + * sequence + * OR + * The ocv was very small and there was no + * charging in the bootloader + * - reset the CC and take an ocv again + */ + pr_debug("cc_raw = 0x%x may be > 5mAh(0x%x)\n", + raw->cc, CC_RAW_5MAH); + pr_debug("ocv_uv = %d ocv_raw = 0x%x may be < 2V\n", + chip->last_ocv_uv, + raw->last_good_ocv_raw); + est_ocv_uv = estimate_ocv(chip); + if (est_ocv_uv > 0) { + raw->last_good_ocv_uv = est_ocv_uv; + chip->last_ocv_uv = est_ocv_uv; + reset_cc(chip); + raw->cc = 0; + } + } + chip->last_ocv_temp_decidegc = batt_temp_decidegc; pr_debug("PON_OCV_UV = %d\n", chip->last_ocv_uv); } else if (chip->prev_last_good_ocv_raw != raw->last_good_ocv_raw) { chip->prev_last_good_ocv_raw = raw->last_good_ocv_raw; convert_vbatt_raw_to_uv(chip, usb_chg, raw->last_good_ocv_raw, &raw->last_good_ocv_uv); chip->last_ocv_uv = raw->last_good_ocv_uv; + chip->last_ocv_temp_decidegc = batt_temp_decidegc; /* forget the old cc value upon ocv */ chip->last_cc_uah = 0; } else { @@ -1007,7 +804,7 @@ static int read_soc_params_raw(struct pm8921_bms_chip *chip, /* fake a high OCV if we are just done charging */ if (chip->ocv_reading_at_100 != raw->last_good_ocv_raw) { - chip->ocv_reading_at_100 = 0; + chip->ocv_reading_at_100 = OCV_RAW_UNINITIALIZED; chip->cc_reading_at_100 = 0; } else { /* @@ -1016,6 +813,7 @@ static int read_soc_params_raw(struct pm8921_bms_chip *chip, */ raw->last_good_ocv_uv = chip->max_voltage_uv; chip->last_ocv_uv = chip->max_voltage_uv; + chip->last_ocv_temp_decidegc = batt_temp_decidegc; } pr_debug("0p625 = %duV\n", chip->xoadc_v0625); pr_debug("1p25 = %duV\n", chip->xoadc_v125); @@ -1037,7 +835,7 @@ static int get_rbatt(struct pm8921_bms_chip *chip, int soc_rbatt, int batt_temp) } /* Convert the batt_temp to DegC from deciDegC */ batt_temp = batt_temp / 10; - scalefactor = interpolate_scalingfactor(chip, chip->rbatt_sf_lut, + scalefactor = interpolate_scalingfactor(chip->rbatt_sf_lut, batt_temp, soc_rbatt); pr_debug("rbatt sf = %d for batt_temp = %d, soc_rbatt = %d\n", scalefactor, batt_temp, soc_rbatt); @@ -1064,16 +862,18 @@ static int calculate_fcc_uah(struct pm8921_bms_chip *chip, int batt_temp, int initfcc, result, scalefactor = 0; if (chip->adjusted_fcc_temp_lut == NULL) { - initfcc = interpolate_fcc(chip, batt_temp); + initfcc = interpolate_fcc(chip->fcc_temp_lut, batt_temp); - scalefactor = interpolate_scalingfactor_fcc(chip, chargecycles); + scalefactor = interpolate_scalingfactor_fcc(chip->fcc_sf_lut, + chargecycles); /* Multiply the initial FCC value by the scale factor. */ result = (initfcc * scalefactor * 1000) / 100; pr_debug("fcc = %d uAh\n", result); return result; } else { - return 1000 * interpolate_fcc_adjusted(chip, batt_temp); + return 1000 * interpolate_fcc(chip->adjusted_fcc_temp_lut, + batt_temp); } } @@ -1115,18 +915,20 @@ static int adc_based_ocv(struct pm8921_bms_chip *chip, int *ocv) return 0; } -static int calculate_pc(struct pm8921_bms_chip *chip, int ocv_uv, int batt_temp, - int chargecycles) +static int calculate_pc(struct pm8921_bms_chip *chip, int ocv_uv, + int batt_temp_decidegc, int chargecycles) { int pc, scalefactor; - pc = interpolate_pc(chip, batt_temp, ocv_uv / 1000); + pc = interpolate_pc(chip->pc_temp_ocv_lut, + batt_temp_decidegc / 10, ocv_uv / 1000); pr_debug("pc = %u for ocv = %dmicroVolts batt_temp = %d\n", - pc, ocv_uv, batt_temp); + pc, ocv_uv, batt_temp_decidegc); - scalefactor = interpolate_scalingfactor(chip, - chip->pc_sf_lut, chargecycles, pc); - pr_debug("scalefactor = %u batt_temp = %d\n", scalefactor, batt_temp); + scalefactor = interpolate_scalingfactor(chip->pc_sf_lut, + chargecycles, pc); + pr_debug("scalefactor = %u batt_temp = %d\n", + scalefactor, batt_temp_decidegc); /* Multiply the initial FCC value by the scale factor. */ pc = (pc * scalefactor) / 100; @@ -1145,7 +947,7 @@ static int calculate_pc(struct pm8921_bms_chip *chip, int ocv_uv, int batt_temp, */ static void calculate_cc_uah(struct pm8921_bms_chip *chip, int cc, int *val) { - int64_t cc_voltage_uv, cc_nvh, cc_uah; + int64_t cc_voltage_uv, cc_uvh, cc_uah; cc_voltage_uv = cc; cc_voltage_uv -= chip->cc_reading_at_100; @@ -1155,12 +957,28 @@ static void calculate_cc_uah(struct pm8921_bms_chip *chip, int cc, int *val) cc_voltage_uv = cc_to_microvolt(chip, cc_voltage_uv); cc_voltage_uv = pm8xxx_cc_adjust_for_gain(cc_voltage_uv); pr_debug("cc_voltage_uv = %lld microvolts\n", cc_voltage_uv); - cc_nvh = ccmicrovolt_to_nvh(cc_voltage_uv); - pr_debug("cc_nvh = %lld nano_volt_hour\n", cc_nvh); - cc_uah = div_s64(cc_nvh, chip->r_sense); + cc_uvh = ccmicrovolt_to_uvh(cc_voltage_uv); + pr_debug("cc_uvh = %lld micro_volt_hour\n", cc_uvh); + cc_uah = div_s64(cc_uvh * 1000000LL, chip->r_sense_uohm); *val = cc_uah; } +int pm8921_bms_cc_uah(int *cc_uah) +{ + int cc; + + *cc_uah = 0; + + if (!the_chip) + return -EINVAL; + + read_cc(the_chip, &cc); + calculate_cc_uah(the_chip, cc, cc_uah); + + return 0; +} +EXPORT_SYMBOL(pm8921_bms_cc_uah); + static int calculate_termination_uuc(struct pm8921_bms_chip *chip, int batt_temp, int chargecycles, int fcc_uah, int i_ma, @@ -1178,7 +996,8 @@ static int calculate_termination_uuc(struct pm8921_bms_chip *chip, int uuc_rbatt_uv; for (i = 0; i <= 100; i++) { - ocv_mv = interpolate_ocv(chip, batt_temp_degc, i); + ocv_mv = interpolate_ocv(chip->pc_temp_ocv_lut, + batt_temp_degc, i); rbatt_mohm = get_rbatt(chip, i, batt_temp); unusable_uv = (rbatt_mohm * i_ma) + (chip->v_cutoff * 1000); delta_uv = ocv_mv * 1000 - unusable_uv; @@ -1209,33 +1028,39 @@ static int calculate_termination_uuc(struct pm8921_bms_chip *chip, return uuc; } +#define TIME_PER_PERCENT_UUC 60 static int adjust_uuc(struct pm8921_bms_chip *chip, int fcc_uah, int new_pc_unusable, int new_uuc, int batt_temp, int rbatt, - int *iavg_ma) + int *iavg_ma, + int delta_time_s) { int new_unusable_mv; int batt_temp_degc = batt_temp / 10; + int max_percent_change; + + max_percent_change = max(delta_time_s / TIME_PER_PERCENT_UUC, 1); if (chip->prev_pc_unusable == -EINVAL - || abs(chip->prev_pc_unusable - new_pc_unusable) <= 1) { + || abs(chip->prev_pc_unusable - new_pc_unusable) + <= max_percent_change) { chip->prev_pc_unusable = new_pc_unusable; return new_uuc; } /* the uuc is trying to change more than 1% restrict it */ if (new_pc_unusable > chip->prev_pc_unusable) - chip->prev_pc_unusable++; + chip->prev_pc_unusable += max_percent_change; else - chip->prev_pc_unusable--; - + chip->prev_pc_unusable -= max_percent_change; + chip->prev_pc_unusable = clamp(chip->prev_pc_unusable, 0, 100); new_uuc = (fcc_uah * chip->prev_pc_unusable) / 100; /* also find update the iavg_ma accordingly */ - new_unusable_mv = interpolate_ocv(chip, batt_temp_degc, - chip->prev_pc_unusable); + new_unusable_mv = interpolate_ocv(chip->pc_temp_ocv_lut, + batt_temp_degc, chip->prev_pc_unusable); if (new_unusable_mv < chip->v_cutoff) new_unusable_mv = chip->v_cutoff; @@ -1249,75 +1074,89 @@ static int adjust_uuc(struct pm8921_bms_chip *chip, int fcc_uah, return new_uuc; } -static void calculate_iavg_ua(struct pm8921_bms_chip *chip, int cc_uah, - int *iavg_ua, int *delta_time_s) +static int get_current_time(unsigned long *now_tm_sec) { - int delta_cc_uah; struct rtc_time tm; struct rtc_device *rtc; - unsigned long now_tm_sec = 0; - int rc = 0; - - /* if anything fails report the previous iavg_ua */ - *iavg_ua = chip->prev_iavg_ua; + int rc; rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); if (rtc == NULL) { pr_err("%s: unable to open rtc device (%s)\n", __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); - goto out; + return -EINVAL; } rc = rtc_read_time(rtc, &tm); if (rc) { pr_err("Error reading rtc device (%s) : %d\n", CONFIG_RTC_HCTOSYS_DEVICE, rc); - goto out; + return rc; } rc = rtc_valid_tm(&tm); if (rc) { pr_err("Invalid RTC time (%s): %d\n", CONFIG_RTC_HCTOSYS_DEVICE, rc); - goto out; + return rc; } - rtc_tm_to_time(&tm, &now_tm_sec); + rtc_tm_to_time(&tm, now_tm_sec); - if (chip->tm_sec == 0) { - *delta_time_s = 0; + return 0; +} + +static int calculate_delta_time(struct pm8921_bms_chip *chip, int *delta_time_s) +{ + unsigned long now_tm_sec = 0; + + /* default to delta time = 0 if anything fails */ + *delta_time_s = 0; + + get_current_time(&now_tm_sec); + + *delta_time_s = (now_tm_sec - chip->tm_sec); + pr_debug("tm_sec = %ld, now_tm_sec = %ld delta_s = %d\n", + chip->tm_sec, now_tm_sec, *delta_time_s); + + /* remember this time */ + chip->tm_sec = now_tm_sec; + return 0; +} + +static void calculate_iavg_ua(struct pm8921_bms_chip *chip, int cc_uah, + int *iavg_ua, int delta_time_s) +{ + int delta_cc_uah = 0; + + /* if anything fails report the previous iavg_ua */ + *iavg_ua = chip->prev_iavg_ua; + + if (chip->last_cc_uah == INT_MIN) { pm8921_bms_get_battery_current(iavg_ua); goto out; } - *delta_time_s = (now_tm_sec - chip->tm_sec); - /* use the previous iavg if called within 15 seconds */ - if (*delta_time_s < 15) { + if (delta_time_s < 15) { *iavg_ua = chip->prev_iavg_ua; goto out; } delta_cc_uah = cc_uah - chip->last_cc_uah; - *iavg_ua = div_s64((s64)delta_cc_uah * 3600, *delta_time_s); - - pr_debug("tm_sec = %ld, now_tm_sec = %ld delta_s = %d delta_cc = %d iavg_ua = %d\n", - chip->tm_sec, now_tm_sec, - *delta_time_s, delta_cc_uah, (int)*iavg_ua); + *iavg_ua = div_s64((s64)delta_cc_uah * 3600, delta_time_s); out: + pr_debug("delta_cc = %d iavg_ua = %d\n", delta_cc_uah, (int)*iavg_ua); /* remember the iavg */ chip->prev_iavg_ua = *iavg_ua; /* remember cc_uah */ chip->last_cc_uah = cc_uah; - - /* remember this time */ - chip->tm_sec = now_tm_sec; } #define IAVG_SAMPLES 16 -#define CHARGING_IAVG_MA 250 +#define MIN_IAVG_MA 250 #define MIN_SECONDS_FOR_VALID_SAMPLE 20 static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip, int rbatt, int fcc_uah, int cc_uah, @@ -1338,7 +1177,7 @@ static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip, * samples with the the shutdown_iavg_ua */ if (firsttime && chip->shutdown_iavg_ua != 0) { - pr_emerg("Using shutdown_iavg_ua = %d in all samples\n", + pr_debug("Using shutdown_iavg_ua = %d in all samples\n", chip->shutdown_iavg_ua); for (i = 0; i < IAVG_SAMPLES; i++) iavg_samples[i] = chip->shutdown_iavg_ua; @@ -1351,8 +1190,8 @@ static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip, * if we are charging use a nominal avg current so that we keep * a reasonable UUC while charging */ - if (iavg_ma < 0) - iavg_ma = CHARGING_IAVG_MA; + if (iavg_ma < MIN_IAVG_MA) + iavg_ma = MIN_IAVG_MA; iavg_samples[iavg_index] = iavg_ma; iavg_index = (iavg_index + 1) % IAVG_SAMPLES; iavg_num_samples++; @@ -1376,9 +1215,9 @@ static int calculate_unusable_charge_uah(struct pm8921_bms_chip *chip, &pc_unusable); pr_debug("iavg = %d uuc_iavg = %d\n", iavg_ma, uuc_uah_iavg); - /* restrict the uuc such that it can increase only by one percent */ + /* restrict the uuc change to one percent per 60 seconds */ uuc_uah_iavg = adjust_uuc(chip, fcc_uah, pc_unusable, uuc_uah_iavg, - batt_temp, rbatt, &iavg_ma); + batt_temp, rbatt, &iavg_ma, delta_time_s); /* find out what the avg current should be for this uuc */ chip->prev_uuc_iavg_ma = iavg_ma; @@ -1393,10 +1232,11 @@ static int calculate_remaining_charge_uah(struct pm8921_bms_chip *chip, int fcc_uah, int batt_temp, int chargecycles) { - int ocv, pc; + int ocv, pc, batt_temp_decidegc; ocv = raw->last_good_ocv_uv; - pc = calculate_pc(chip, ocv, batt_temp, chargecycles); + batt_temp_decidegc = chip->last_ocv_temp_decidegc; + pc = calculate_pc(chip, ocv, batt_temp_decidegc, chargecycles); pr_debug("ocv = %d pc = %d\n", ocv, pc); return (fcc_uah * pc) / 100; } @@ -1409,11 +1249,17 @@ static void calculate_soc_params(struct pm8921_bms_chip *chip, int *remaining_charge_uah, int *cc_uah, int *rbatt, - int *iavg_ua, - int *delta_time_s) + int *iavg_ua) { int soc_rbatt; + int delta_time_s; + int rc; + rc = calculate_delta_time(chip, &delta_time_s); + if (rc) { + pr_err("Failed to get delta time from RTC: %d\n", rc); + delta_time_s = 0; + } *fcc_uah = calculate_fcc_uah(chip, batt_temp, chargecycles); pr_debug("FCC = %uuAh batt_temp = %d, cycles = %d\n", *fcc_uah, batt_temp, chargecycles); @@ -1441,7 +1287,7 @@ static void calculate_soc_params(struct pm8921_bms_chip *chip, *unusable_charge_uah = calculate_unusable_charge_uah(chip, *rbatt, *fcc_uah, *cc_uah, soc_rbatt, batt_temp, chargecycles, *iavg_ua, - *delta_time_s); + delta_time_s); pr_debug("UUC = %uuAh\n", *unusable_charge_uah); } @@ -1456,7 +1302,6 @@ static int calculate_real_fcc_uah(struct pm8921_bms_chip *chip, int real_fcc_uah; int rbatt; int iavg_ua; - int delta_time_s; calculate_soc_params(chip, raw, batt_temp, chargecycles, &fcc_uah, @@ -1464,8 +1309,7 @@ static int calculate_real_fcc_uah(struct pm8921_bms_chip *chip, &remaining_charge_uah, &cc_uah, &rbatt, - &iavg_ua, - &delta_time_s); + &iavg_ua); real_fcc_uah = remaining_charge_uah - cc_uah; *ret_fcc_uah = fcc_uah; @@ -1526,11 +1370,11 @@ static void find_ocv_for_soc(struct pm8921_bms_chip *chip, pc = DIV_ROUND_CLOSEST((int)rc * 100, fcc_uah); pc = clamp(pc, 0, 100); - ocv = interpolate_ocv(chip, batt_temp_degc, pc); + ocv = interpolate_ocv(chip->pc_temp_ocv_lut, batt_temp_degc, pc); pr_debug("s_soc = %d, fcc = %d uuc = %d rc = %d, pc = %d, ocv mv = %d\n", shutdown_soc, fcc_uah, uuc_uah, (int)rc, pc, ocv); - new_pc = interpolate_pc(chip, batt_temp_degc, ocv); + new_pc = interpolate_pc(chip->pc_temp_ocv_lut, batt_temp_degc, ocv); pr_debug("test revlookup pc = %d for ocv = %d\n", new_pc, ocv); while (abs(new_pc - pc) > 1) { @@ -1540,7 +1384,8 @@ static void find_ocv_for_soc(struct pm8921_bms_chip *chip, delta_mv = -1 * delta_mv; ocv = ocv + delta_mv; - new_pc = interpolate_pc(chip, batt_temp_degc, ocv); + new_pc = interpolate_pc(chip->pc_temp_ocv_lut, + batt_temp_degc, ocv); pr_debug("test revlookup pc = %d for ocv = %d\n", new_pc, ocv); } @@ -1575,6 +1420,13 @@ static void adjust_rc_and_uuc_for_specific_soc( *ret_rc = rc_uah; *ret_uuc = uuc_uah; } + +static void calc_current_max(struct pm8921_bms_chip *chip, int ocv_uv, + int rbatt_mohm) +{ + chip->imax_ua = 1000 * (ocv_uv - chip->v_cutoff * 1000) / rbatt_mohm; +} + static int bound_soc(int soc) { soc = max(0, soc); @@ -1588,14 +1440,16 @@ static int charging_adjustments(struct pm8921_bms_chip *chip, int fcc_uah, int cc_uah, int uuc_uah) { int chg_soc; + int vbat_batt_terminal_uv = vbat_uv + + (ibat_ua * chip->rconn_mohm) / 1000; if (chip->soc_at_cv == -EINVAL) { /* In constant current charging return the calc soc */ - if (vbat_uv <= chip->max_voltage_uv) + if (vbat_batt_terminal_uv <= chip->max_voltage_uv) pr_debug("CC CHG SOC %d\n", soc); /* Note the CC to CV point */ - if (vbat_uv >= chip->max_voltage_uv) { + if (vbat_batt_terminal_uv >= chip->max_voltage_uv) { chip->soc_at_cv = soc; chip->prev_chg_soc = soc; chip->ibat_at_cv_ua = ibat_ua; @@ -1611,18 +1465,20 @@ static int charging_adjustments(struct pm8921_bms_chip *chip, */ /* - * if voltage lessened (possibly because of a system load) - * keep reporting the prev chg soc + * if voltage lessened by more than 10mV (possibly because of + * a sudden increase in system load) keep reporting the prev chg soc */ - if (vbat_uv <= chip->max_voltage_uv) { - pr_debug("vbat %d < max = %d CC CHG SOC %d\n", - vbat_uv, chip->max_voltage_uv, chip->prev_chg_soc); + if (vbat_batt_terminal_uv <= chip->max_voltage_uv - 10000) { + pr_debug("vbat_terminals %d < max = %d CC CHG SOC %d\n", + vbat_batt_terminal_uv, + chip->max_voltage_uv, chip->prev_chg_soc); return chip->prev_chg_soc; } chg_soc = linear_interpolate(chip->soc_at_cv, chip->ibat_at_cv_ua, - 100, -100000, + 100, -1 * chip->chg_term_ua, ibat_ua); + chg_soc = bound_soc(chg_soc); /* always report a higher soc */ if (chg_soc > chip->prev_chg_soc) { @@ -1645,6 +1501,30 @@ static int charging_adjustments(struct pm8921_bms_chip *chip, return chip->prev_chg_soc; } +static void very_low_voltage_check(struct pm8921_bms_chip *chip, + int ibat_ua, int vbat_uv) +{ + /* + * if battery is very low (v_cutoff voltage + 20mv) hold + * a wakelock untill soc = 0% + */ + if (vbat_uv <= (chip->v_cutoff + 20) * 1000 + && !chip->low_voltage_wake_lock_held) { + pr_debug("voltage = %d low holding wakelock\n", vbat_uv); + wake_lock(&chip->low_voltage_wake_lock); + chip->low_voltage_wake_lock_held = 1; + chip->soc_calc_period = chip->low_voltage_calc_ms; + } + + if (vbat_uv > (chip->v_cutoff + 20) * 1000 + && chip->low_voltage_wake_lock_held) { + pr_debug("voltage = %d releasing wakelock\n", vbat_uv); + chip->low_voltage_wake_lock_held = 0; + wake_unlock(&chip->low_voltage_wake_lock); + chip->soc_calc_period = chip->normal_voltage_calc_ms; + } +} + static int last_soc_est = -EINVAL; static int adjust_soc(struct pm8921_bms_chip *chip, int soc, int batt_temp, int chargecycles, @@ -1669,16 +1549,18 @@ static int adjust_soc(struct pm8921_bms_chip *chip, int soc, goto out; } + very_low_voltage_check(chip, ibat_ua, vbat_uv); delta_ocv_uv_limit = DIV_ROUND_CLOSEST(ibat_ua, 1000); ocv_est_uv = vbat_uv + (ibat_ua * rbatt)/1000; + calc_current_max(chip, ocv_est_uv, rbatt); pc_est = calculate_pc(chip, ocv_est_uv, batt_temp, last_chargecycles); soc_est = div_s64((s64)fcc_uah * pc_est - uuc_uah*100, (s64)fcc_uah - uuc_uah); soc_est = bound_soc(soc_est); - if (ibat_ua < 0) { + if (ibat_ua < 0 && pm8921_is_batfet_closed()) { soc = charging_adjustments(chip, soc, vbat_uv, ibat_ua, batt_temp, chargecycles, fcc_uah, cc_uah, uuc_uah); @@ -1709,16 +1591,18 @@ static int adjust_soc(struct pm8921_bms_chip *chip, int soc, last_soc_est = soc_est; pc = calculate_pc(chip, chip->last_ocv_uv, - batt_temp, last_chargecycles); + chip->last_ocv_temp_decidegc, last_chargecycles); if (pc > 0) { pc_new = calculate_pc(chip, chip->last_ocv_uv - (++m * 1000), - batt_temp, last_chargecycles); + chip->last_ocv_temp_decidegc, + last_chargecycles); while (pc_new == pc) { /* start taking 10mV steps */ m = m + 10; pc_new = calculate_pc(chip, chip->last_ocv_uv - (m * 1000), - batt_temp, last_chargecycles); + chip->last_ocv_temp_decidegc, + last_chargecycles); } } else { /* @@ -1751,7 +1635,7 @@ static int adjust_soc(struct pm8921_bms_chip *chip, int soc, /* calculate the soc based on this new ocv */ pc_new = calculate_pc(chip, chip->last_ocv_uv, - batt_temp, last_chargecycles); + chip->last_ocv_temp_decidegc, last_chargecycles); rc_new_uah = (fcc_uah * pc_new) / 100; soc_new = (rc_new_uah - cc_uah - uuc_uah)*100 / (fcc_uah - uuc_uah); soc_new = bound_soc(soc_new); @@ -1876,12 +1760,10 @@ static int scale_soc_while_chg(struct pm8921_bms_chip *chip, if (the_chip->start_percent == -EINVAL) return prev_soc; - /* if soc is called in quick succession return the last soc */ - if (delta_time_us < USEC_PER_SEC) - return prev_soc; - chg_time_sec = DIV_ROUND_UP(the_chip->charge_time_us, USEC_PER_SEC); catch_up_sec = DIV_ROUND_UP(the_chip->catch_up_time_us, USEC_PER_SEC); + if (catch_up_sec == 0) + return new_soc; pr_debug("cts= %d catch_up_sec = %d\n", chg_time_sec, catch_up_sec); /* @@ -1918,6 +1800,116 @@ static bool is_shutdown_soc_within_limits(struct pm8921_bms_chip *chip, int soc) return 1; } + +static void update_power_supply(struct pm8921_bms_chip *chip) +{ + if (chip->batt_psy == NULL || chip->batt_psy < 0) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (chip->batt_psy > 0) + power_supply_changed(chip->batt_psy); +} + +static int get_batt_temp(struct pm8921_bms_chip *chip, int *batt_temp) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading batt_temp_channel = %d, rc = %d\n", + chip->batt_temp_channel, rc); + return rc; + } + *batt_temp = result.physical; + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + return 0; +} + +#define MIN_DELTA_625_UV 1000 +static void calib_hkadc(struct pm8921_bms_chip *chip) +{ + int voltage, rc; + struct pm8xxx_adc_chan_result result; + int usb_chg; + int this_delta; + + mutex_lock(&chip->calib_mutex); + rc = pm8xxx_adc_read(the_chip->ref1p25v_channel, &result); + if (rc) { + pr_err("ADC failed for 1.25volts rc = %d\n", rc); + goto out; + } + voltage = xoadc_reading_to_microvolt(result.adc_code); + + pr_debug("result 1.25v = 0x%x, voltage = %duV adc_meas = %lld\n", + result.adc_code, voltage, result.measurement); + + chip->xoadc_v125 = voltage; + + rc = pm8xxx_adc_read(the_chip->ref625mv_channel, &result); + if (rc) { + pr_err("ADC failed for 1.25volts rc = %d\n", rc); + goto out; + } + voltage = xoadc_reading_to_microvolt(result.adc_code); + + usb_chg = usb_chg_plugged_in(chip); + pr_debug("result 0.625V = 0x%x, voltage = %duV adc_meas = %lld usb_chg = %d\n", + result.adc_code, voltage, result.measurement, + usb_chg); + + if (usb_chg) + chip->xoadc_v0625_usb_present = voltage; + else + chip->xoadc_v0625_usb_absent = voltage; + + chip->xoadc_v0625 = voltage; + if (chip->xoadc_v0625_usb_present && chip->xoadc_v0625_usb_absent) { + this_delta = chip->xoadc_v0625_usb_present + - chip->xoadc_v0625_usb_absent; + pr_debug("this_delta= %duV\n", this_delta); + if (this_delta > MIN_DELTA_625_UV) + last_usb_cal_delta_uv = this_delta; + pr_debug("625V_present= %d, 625V_absent= %d, delta = %duV\n", + chip->xoadc_v0625_usb_present, + chip->xoadc_v0625_usb_absent, + last_usb_cal_delta_uv); + } + pr_debug("calibration batt_temp = %d\n", chip->last_calib_temp); +out: + mutex_unlock(&chip->calib_mutex); +} + +#define HKADC_CALIB_DELAY_S 600 +#define HKADC_CALIB_DELTA_TEMP 20 +static void calib_hkadc_check(struct pm8921_bms_chip *chip, int batt_temp) +{ + unsigned long time_since_last_calib; + unsigned long tm_now_sec; + int delta_temp; + int rc; + + rc = get_current_time(&tm_now_sec); + if (rc) { + pr_err("Could not read current time: %d\n", rc); + return; + } + if (tm_now_sec > chip->last_calib_time) { + time_since_last_calib = tm_now_sec - chip->last_calib_time; + delta_temp = abs(chip->last_calib_temp - batt_temp); + pr_debug("time since last calib: %lu, temp diff = %d\n", + time_since_last_calib, delta_temp); + if (time_since_last_calib >= HKADC_CALIB_DELAY_S + || delta_temp > HKADC_CALIB_DELTA_TEMP) { + chip->last_calib_temp = batt_temp; + chip->last_calib_time = tm_now_sec; + calib_hkadc(chip); + } + } +} + /* * Remaining Usable Charge = remaining_charge (charge at ocv instance) * - coloumb counter charge @@ -1933,22 +1925,22 @@ static int calculate_state_of_charge(struct pm8921_bms_chip *chip, int cc_uah; int rbatt; int iavg_ua; - int delta_time_s; int new_ocv; int new_rc_uah; int new_ucc_uah; int new_rbatt; int shutdown_soc; + int new_calculated_soc; static int firsttime = 1; + calib_hkadc_check(chip, batt_temp); calculate_soc_params(chip, raw, batt_temp, chargecycles, &fcc_uah, &unusable_charge_uah, &remaining_charge_uah, &cc_uah, &rbatt, - &iavg_ua, - &delta_time_s); + &iavg_ua); /* calculate remaining usable charge */ remaining_usable_charge_uah = remaining_charge_uah @@ -1957,7 +1949,7 @@ static int calculate_state_of_charge(struct pm8921_bms_chip *chip, pr_debug("RUC = %duAh\n", remaining_usable_charge_uah); if (fcc_uah - unusable_charge_uah <= 0) { - pr_warn("FCC = %duAh, UUC = %duAh forcing soc = 0\n", + pr_debug("FCC = %duAh, UUC = %duAh forcing soc = 0\n", fcc_uah, unusable_charge_uah); soc = 0; } else { @@ -2000,13 +1992,13 @@ static int calculate_state_of_charge(struct pm8921_bms_chip *chip, soc = 100; if (soc < 0) { - pr_err("bad rem_usb_chg = %d rem_chg %d," + pr_debug("bad rem_usb_chg = %d rem_chg %d," "cc_uah %d, unusb_chg %d\n", remaining_usable_charge_uah, remaining_charge_uah, cc_uah, unusable_charge_uah); - pr_err("for bad rem_usb_chg last_ocv_uv = %d" + pr_debug("for bad rem_usb_chg last_ocv_uv = %d" "chargecycles = %d, batt_temp = %d" "fcc = %d soc =%d\n", chip->last_ocv_uv, chargecycles, batt_temp, @@ -2056,45 +2048,48 @@ static int calculate_state_of_charge(struct pm8921_bms_chip *chip, mutex_unlock(&soc_invalidation_mutex); pr_debug("SOC before adjustment = %d\n", soc); - calculated_soc = adjust_soc(chip, soc, batt_temp, chargecycles, + new_calculated_soc = adjust_soc(chip, soc, batt_temp, chargecycles, rbatt, fcc_uah, unusable_charge_uah, cc_uah); - pr_debug("calculated SOC = %d\n", calculated_soc); + pr_debug("calculated SOC = %d\n", new_calculated_soc); + if (new_calculated_soc != calculated_soc) + update_power_supply(chip); + + calculated_soc = new_calculated_soc; firsttime = 0; + get_current_time(&chip->last_recalc_time); return calculated_soc; } -#define CALCULATE_SOC_MS 20000 +static int recalculate_soc(struct pm8921_bms_chip *chip) +{ + int batt_temp; + struct pm8921_soc_params raw; + int soc; + + wake_lock(&the_chip->soc_wake_lock); + get_batt_temp(chip, &batt_temp); + + mutex_lock(&chip->last_ocv_uv_mutex); + read_soc_params_raw(chip, &raw, batt_temp); + + soc = calculate_state_of_charge(chip, &raw, + batt_temp, last_chargecycles); + mutex_unlock(&chip->last_ocv_uv_mutex); + wake_unlock(&the_chip->soc_wake_lock); + return soc; +} + static void calculate_soc_work(struct work_struct *work) { struct pm8921_bms_chip *chip = container_of(work, struct pm8921_bms_chip, calculate_soc_delayed_work.work); - int batt_temp, rc; - struct pm8xxx_adc_chan_result result; - struct pm8921_soc_params raw; - int soc; - - rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - chip->batt_temp_channel, rc); - return; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, - result.measurement); - batt_temp = (int)result.physical; - - mutex_lock(&chip->last_ocv_uv_mutex); - read_soc_params_raw(chip, &raw); - - soc = calculate_state_of_charge(chip, &raw, - batt_temp, last_chargecycles); - mutex_unlock(&chip->last_ocv_uv_mutex); + recalculate_soc(chip); schedule_delayed_work(&chip->calculate_soc_delayed_work, round_jiffies_relative(msecs_to_jiffies - (CALCULATE_SOC_MS))); + (chip->soc_calc_period))); } static int report_state_of_charge(struct pm8921_bms_chip *chip) @@ -2102,24 +2097,14 @@ static int report_state_of_charge(struct pm8921_bms_chip *chip) int soc = calculated_soc; int delta_time_us; struct timespec now; - struct pm8xxx_adc_chan_result result; int batt_temp; - int rc; if (bms_fake_battery != -EINVAL) { pr_debug("Returning Fake SOC = %d%%\n", bms_fake_battery); return bms_fake_battery; } - rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - the_chip->batt_temp_channel, rc); - return rc; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, - result.measurement); - batt_temp = (int)result.physical; + get_batt_temp(chip, &batt_temp); do_posix_clock_monotonic_gettime(&now); if (chip->t_soc_queried.tv_sec != 0) { @@ -2179,10 +2164,6 @@ void pm8921_bms_invalidate_shutdown_soc(void) { int calculate_soc = 0; struct pm8921_bms_chip *chip = the_chip; - int batt_temp, rc; - struct pm8xxx_adc_chan_result result; - struct pm8921_soc_params raw; - int soc; pr_debug("Invalidating shutdown soc - the battery was removed\n"); if (shutdown_soc_invalid) @@ -2203,81 +2184,10 @@ void pm8921_bms_invalidate_shutdown_soc(void) mutex_unlock(&soc_invalidation_mutex); if (!calculate_soc) return; - - rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - chip->batt_temp_channel, rc); - return; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, - result.measurement); - batt_temp = (int)result.physical; - - mutex_lock(&chip->last_ocv_uv_mutex); - read_soc_params_raw(chip, &raw); - - soc = calculate_state_of_charge(chip, &raw, - batt_temp, last_chargecycles); - mutex_unlock(&chip->last_ocv_uv_mutex); + recalculate_soc(chip); } EXPORT_SYMBOL(pm8921_bms_invalidate_shutdown_soc); -#define MIN_DELTA_625_UV 1000 -static void calib_hkadc(struct pm8921_bms_chip *chip) -{ - int voltage, rc; - struct pm8xxx_adc_chan_result result; - int usb_chg; - int this_delta; - - mutex_lock(&chip->calib_mutex); - rc = pm8xxx_adc_read(the_chip->ref1p25v_channel, &result); - if (rc) { - pr_err("ADC failed for 1.25volts rc = %d\n", rc); - goto out; - } - voltage = xoadc_reading_to_microvolt(result.adc_code); - - pr_debug("result 1.25v = 0x%x, voltage = %duV adc_meas = %lld\n", - result.adc_code, voltage, result.measurement); - - chip->xoadc_v125 = voltage; - - rc = pm8xxx_adc_read(the_chip->ref625mv_channel, &result); - if (rc) { - pr_err("ADC failed for 1.25volts rc = %d\n", rc); - goto out; - } - voltage = xoadc_reading_to_microvolt(result.adc_code); - - usb_chg = usb_chg_plugged_in(); - pr_debug("result 0.625V = 0x%x, voltage = %duV adc_meas = %lld " - "usb_chg = %d\n", - result.adc_code, voltage, result.measurement, - usb_chg); - - if (usb_chg) - chip->xoadc_v0625_usb_present = voltage; - else - chip->xoadc_v0625_usb_absent = voltage; - - chip->xoadc_v0625 = voltage; - if (chip->xoadc_v0625_usb_present && chip->xoadc_v0625_usb_absent) { - this_delta = chip->xoadc_v0625_usb_present - - chip->xoadc_v0625_usb_absent; - pr_debug("this_delta= %duV\n", this_delta); - if (this_delta > MIN_DELTA_625_UV) - last_usb_cal_delta_uv = this_delta; - pr_debug("625V_present= %d, 625V_absent= %d, delta = %duV\n", - chip->xoadc_v0625_usb_present, - chip->xoadc_v0625_usb_absent, - last_usb_cal_delta_uv); - } -out: - mutex_unlock(&chip->calib_mutex); -} - static void calibrate_hkadc_work(struct work_struct *work) { struct pm8921_bms_chip *chip = container_of(work, @@ -2291,19 +2201,6 @@ void pm8921_bms_calibrate_hkadc(void) schedule_work(&the_chip->calib_hkadc_work); } -#define HKADC_CALIB_DELAY_MS 600000 -static void calibrate_hkadc_delayed_work(struct work_struct *work) -{ - struct pm8921_bms_chip *chip = container_of(work, - struct pm8921_bms_chip, - calib_hkadc_delayed_work.work); - - calib_hkadc(chip); - schedule_delayed_work(&chip->calib_hkadc_delayed_work, - round_jiffies_relative(msecs_to_jiffies - (HKADC_CALIB_DELAY_MS))); -} - int pm8921_bms_get_vsense_avg(int *result) { int rc = -EINVAL; @@ -2323,27 +2220,35 @@ EXPORT_SYMBOL(pm8921_bms_get_vsense_avg); int pm8921_bms_get_battery_current(int *result_ua) { - int vsense; + int vsense_uv; + int rc = 0; + *result_ua = 0; if (!the_chip) { pr_err("called before initialization\n"); return -EINVAL; } - if (the_chip->r_sense == 0) { + if (the_chip->r_sense_uohm == 0) { pr_err("r_sense is zero\n"); return -EINVAL; } mutex_lock(&the_chip->bms_output_lock); pm_bms_lock_output_data(the_chip); - read_vsense_avg(the_chip, &vsense); + rc = read_vsense_avg(the_chip, &vsense_uv); pm_bms_unlock_output_data(the_chip); mutex_unlock(&the_chip->bms_output_lock); - pr_debug("vsense=%duV\n", vsense); + if (rc) { + pr_err("Unable to read vsense average\n"); + goto error_vsense; + } + pr_debug("vsense=%duV\n", vsense_uv); /* cast for signed division */ - *result_ua = vsense * 1000 / (int)the_chip->r_sense; + *result_ua = div_s64(vsense_uv * 1000000LL, the_chip->r_sense_uohm); pr_debug("ibat=%duA\n", *result_ua); - return 0; + +error_vsense: + return rc; } EXPORT_SYMBOL(pm8921_bms_get_battery_current); @@ -2358,71 +2263,26 @@ int pm8921_bms_get_percent_charge(void) } EXPORT_SYMBOL_GPL(pm8921_bms_get_percent_charge); -int pm8921_bms_get_rbatt(void) +int pm8921_bms_get_current_max(void) { - int batt_temp, rc; - struct pm8xxx_adc_chan_result result; - struct pm8921_soc_params raw; - int fcc_uah; - int unusable_charge_uah; - int remaining_charge_uah; - int cc_uah; - int rbatt; - int iavg_ua; - int delta_time_s; - if (!the_chip) { pr_err("called before initialization\n"); return -EINVAL; } - - rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - the_chip->batt_temp_channel, rc); - return rc; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, - result.measurement); - batt_temp = (int)result.physical; - - mutex_lock(&the_chip->last_ocv_uv_mutex); - - read_soc_params_raw(the_chip, &raw); - - calculate_soc_params(the_chip, &raw, batt_temp, last_chargecycles, - &fcc_uah, - &unusable_charge_uah, - &remaining_charge_uah, - &cc_uah, - &rbatt, - &iavg_ua, - &delta_time_s); - mutex_unlock(&the_chip->last_ocv_uv_mutex); - - return rbatt; + return the_chip->imax_ua; } -EXPORT_SYMBOL_GPL(pm8921_bms_get_rbatt); +EXPORT_SYMBOL_GPL(pm8921_bms_get_current_max); int pm8921_bms_get_fcc(void) { - int batt_temp, rc; - struct pm8xxx_adc_chan_result result; + int batt_temp; if (!the_chip) { pr_err("called before initialization\n"); return -EINVAL; } - rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - the_chip->batt_temp_channel, rc); - return rc; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx", result.physical, - result.measurement); - batt_temp = (int)result.physical; + get_batt_temp(the_chip, &batt_temp); return calculate_fcc_uah(the_chip, batt_temp, last_chargecycles); } EXPORT_SYMBOL_GPL(pm8921_bms_get_fcc); @@ -2436,9 +2296,12 @@ EXPORT_SYMBOL_GPL(pm8921_bms_get_fcc); void pm8921_bms_charging_began(void) { struct pm8921_soc_params raw; + int batt_temp; + + get_batt_temp(the_chip, &batt_temp); mutex_lock(&the_chip->last_ocv_uv_mutex); - read_soc_params_raw(the_chip, &raw); + read_soc_params_raw(the_chip, &raw, batt_temp); mutex_unlock(&the_chip->last_ocv_uv_mutex); the_chip->start_percent = report_state_of_charge(the_chip); @@ -2461,26 +2324,17 @@ EXPORT_SYMBOL_GPL(pm8921_bms_charging_began); #define MIN_START_PERCENT_FOR_LEARNING 30 void pm8921_bms_charging_end(int is_battery_full) { - int batt_temp, rc; - struct pm8xxx_adc_chan_result result; + int batt_temp; struct pm8921_soc_params raw; if (the_chip == NULL) return; - rc = pm8xxx_adc_read(the_chip->batt_temp_channel, &result); - if (rc) { - pr_err("error reading adc channel = %d, rc = %d\n", - the_chip->batt_temp_channel, rc); - return; - } - pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, - result.measurement); - batt_temp = (int)result.physical; + get_batt_temp(the_chip, &batt_temp); mutex_lock(&the_chip->last_ocv_uv_mutex); - read_soc_params_raw(the_chip, &raw); + read_soc_params_raw(the_chip, &raw, batt_temp); calculate_cc_uah(the_chip, raw.cc, &bms_end_cc_uah); @@ -2623,12 +2477,6 @@ static irqreturn_t pm8921_bms_good_ocv_handler(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t pm8921_bms_vsense_avg_handler(int irq, void *data) -{ - pr_debug("irq = %d triggered", irq); - return IRQ_HANDLED; -} - struct pm_bms_irq_init_data { unsigned int irq_id; char *name; @@ -2657,8 +2505,6 @@ struct pm_bms_irq_init_data bms_irq_data[] = { pm8921_bms_ocv_for_r_handler), BMS_IRQ(PM8921_BMS_GOOD_OCV, IRQF_TRIGGER_RISING, pm8921_bms_good_ocv_handler), - BMS_IRQ(PM8921_BMS_VSENSE_AVG, IRQF_TRIGGER_RISING, - pm8921_bms_vsense_avg_handler), }; static void free_irqs(struct pm8921_bms_chip *chip) @@ -2738,7 +2584,7 @@ static void check_initial_ocv(struct pm8921_bms_chip *chip) */ ocv_uv = 0; pm_bms_read_output_data(chip, LAST_GOOD_OCV_VALUE, &ocv_raw); - usb_chg = usb_chg_plugged_in(); + usb_chg = usb_chg_plugged_in(chip); rc = convert_vbatt_raw_to_uv(chip, usb_chg, ocv_raw, &ocv_uv); if (rc || ocv_uv == 0) { rc = adc_based_ocv(chip, &ocv_uv); @@ -2881,7 +2727,7 @@ static int get_calc(void *data, u64 * val) int ibat_ua, vbat_uv; struct pm8921_soc_params raw; - read_soc_params_raw(the_chip, &raw); + read_soc_params_raw(the_chip, &raw, 300); *val = 0; @@ -2946,7 +2792,9 @@ static int get_reading(void *data, u64 * val) int ret = 0; struct pm8921_soc_params raw; - read_soc_params_raw(the_chip, &raw); + mutex_lock(&the_chip->bms_output_lock); + read_soc_params_raw(the_chip, &raw, 300); + mutex_unlock(&the_chip->bms_output_lock); *val = 0; @@ -3157,7 +3005,7 @@ static int __devinit pm8921_bms_probe(struct platform_device *pdev) mutex_init(&chip->bms_output_lock); mutex_init(&chip->last_ocv_uv_mutex); chip->dev = &pdev->dev; - chip->r_sense = pdata->r_sense; + chip->r_sense_uohm = pdata->r_sense_uohm; chip->v_cutoff = pdata->v_cutoff; chip->max_voltage_uv = pdata->max_voltage_uv; chip->chg_term_ua = pdata->chg_term_ua; @@ -3165,13 +3013,23 @@ static int __devinit pm8921_bms_probe(struct platform_device *pdev) chip->rconn_mohm = pdata->rconn_mohm; chip->start_percent = -EINVAL; chip->end_percent = -EINVAL; + chip->last_cc_uah = INT_MIN; + chip->ocv_reading_at_100 = OCV_RAW_UNINITIALIZED; + chip->prev_last_good_ocv_raw = OCV_RAW_UNINITIALIZED; chip->shutdown_soc_valid_limit = pdata->shutdown_soc_valid_limit; chip->adjust_soc_low_threshold = pdata->adjust_soc_low_threshold; + + chip->normal_voltage_calc_ms = pdata->normal_voltage_calc_ms; + chip->low_voltage_calc_ms = pdata->low_voltage_calc_ms; + + chip->soc_calc_period = pdata->normal_voltage_calc_ms; + if (chip->adjust_soc_low_threshold >= 45) chip->adjust_soc_low_threshold = 45; chip->prev_pc_unusable = -EINVAL; chip->soc_at_cv = -EINVAL; + chip->imax_ua = -EINVAL; chip->ignore_shutdown_soc = pdata->ignore_shutdown_soc; rc = set_battery_data(chip); @@ -3200,18 +3058,21 @@ static int __devinit pm8921_bms_probe(struct platform_device *pdev) mutex_init(&chip->calib_mutex); INIT_WORK(&chip->calib_hkadc_work, calibrate_hkadc_work); - INIT_DELAYED_WORK(&chip->calib_hkadc_delayed_work, - calibrate_hkadc_delayed_work); INIT_DELAYED_WORK(&chip->calculate_soc_delayed_work, calculate_soc_work); + wake_lock_init(&chip->soc_wake_lock, + WAKE_LOCK_SUSPEND, "pm8921_soc_lock"); rc = request_irqs(chip, pdev); if (rc) { pr_err("couldn't register interrupts rc = %d\n", rc); - goto free_chip; + goto destroy_soc_wl; } + wake_lock_init(&chip->low_voltage_wake_lock, + WAKE_LOCK_SUSPEND, "pm8921_bms_low"); + rc = pm8921_bms_hw_init(chip); if (rc) { pr_err("couldn't init hardware rc = %d\n", rc); @@ -3231,24 +3092,27 @@ static int __devinit pm8921_bms_probe(struct platform_device *pdev) } check_initial_ocv(chip); - /* start periodic hkadc calibration */ - schedule_delayed_work(&chip->calib_hkadc_delayed_work, 0); - /* enable the vbatt reading interrupts for scheduling hkadc calib */ pm8921_bms_enable_irq(chip, PM8921_BMS_GOOD_OCV); pm8921_bms_enable_irq(chip, PM8921_BMS_OCV_FOR_R); calculate_soc_work(&(chip->calculate_soc_delayed_work.work)); - get_battery_uvolts(chip, &vbatt); - pr_info("OK battery_capacity_at_boot=%d volt = %d ocv = %d\n", + rc = get_battery_uvolts(chip, &vbatt); + if (!rc) + pr_info("OK battery_capacity_at_boot=%d volt = %d ocv = %d\n", pm8921_bms_get_percent_charge(), vbatt, chip->last_ocv_uv); + else + pr_info("Unable to read battery voltage at boot\n"); return 0; free_irqs: + wake_lock_destroy(&chip->low_voltage_wake_lock); free_irqs(chip); +destroy_soc_wl: + wake_lock_destroy(&chip->soc_wake_lock); free_chip: kfree(chip); return rc; @@ -3266,12 +3130,42 @@ static int __devexit pm8921_bms_remove(struct platform_device *pdev) return 0; } +static int pm8921_bms_resume(struct device *dev) +{ + int rc; + unsigned long time_since_last_recalc; + unsigned long tm_now_sec; + + rc = get_current_time(&tm_now_sec); + if (rc) { + pr_err("Could not read current time: %d\n", rc); + return 0; + } + if (tm_now_sec > the_chip->last_recalc_time) { + time_since_last_recalc = tm_now_sec - + the_chip->last_recalc_time; + pr_debug("Time since last recalc: %lu\n", + time_since_last_recalc); + if (time_since_last_recalc >= the_chip->soc_calc_period) { + the_chip->last_recalc_time = tm_now_sec; + recalculate_soc(the_chip); + } + } + + return 0; +} + +static const struct dev_pm_ops pm8921_bms_pm_ops = { + .resume = pm8921_bms_resume, +}; + static struct platform_driver pm8921_bms_driver = { .probe = pm8921_bms_probe, .remove = __devexit_p(pm8921_bms_remove), .driver = { .name = PM8921_BMS_DEV_NAME, .owner = THIS_MODULE, + .pm = &pm8921_bms_pm_ops, }, }; diff --git a/drivers/power/pm8921-charger.c b/drivers/power/pm8921-charger.c index fc5ecb7d214..061be695b31 100644 --- a/drivers/power/pm8921-charger.c +++ b/drivers/power/pm8921-charger.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011-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 @@ -21,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -28,11 +29,13 @@ #include #include #include +#include #include #include #define CHG_BUCK_CLOCK_CTRL 0x14 +#define CHG_BUCK_CLOCK_CTRL_8038 0xD #define PBL_ACCESS1 0x04 #define PBL_ACCESS2 0x05 @@ -85,6 +88,8 @@ #define EOC_CHECK_PERIOD_MS 10000 /* check for USB unplug every 200 msecs */ #define UNPLUG_CHECK_WAIT_PERIOD_MS 200 +#define UNPLUG_CHECK_RAMP_MS 25 +#define USB_TRIM_ENTRIES 16 enum chg_fsm_state { FSM_STATE_OFF_0 = 0, @@ -115,13 +120,6 @@ struct fsm_state_to_batt_status { int batt_state; }; -static int pm8921_battery_gauge_alarm_notify(struct notifier_block *nb, - unsigned long status, void *unused); - -static struct notifier_block alarm_notifier = { - .notifier_call = pm8921_battery_gauge_alarm_notify, -}; - static struct fsm_state_to_batt_status map[] = { {FSM_STATE_OFF_0, POWER_SUPPLY_STATUS_UNKNOWN}, {FSM_STATE_BATFETDET_START_12, POWER_SUPPLY_STATUS_UNKNOWN}, @@ -207,12 +205,12 @@ struct bms_notify { * @dc_present: present status of dc * @usb_charger_current: usb current to charge the battery with used when * the usb path is enabled or charging is resumed - * @safety_time: max time for which charging will happen * @update_time: how frequently the userland needs to be updated * @max_voltage_mv: the max volts the batt should be charged up to * @min_voltage_mv: the min battery voltage before turning the FETon * @uvd_voltage_mv: (PM8917 only) the falling UVD threshold voltage - * @alarm_voltage_mv: the battery alarm voltage + * @alarm_low_mv: the battery alarm voltage low + * @alarm_high_mv: the battery alarm voltage high * @cool_temp_dc: the cool temp threshold in deciCelcius * @warm_temp_dc: the warm temp threshold in deciCelcius * @resume_voltage_delta: the voltage delta from vdd max at which the @@ -227,13 +225,14 @@ struct pm8921_chg_chip { unsigned int usb_charger_current; unsigned int max_bat_chg_current; unsigned int pmic_chg_irq[PM_CHG_MAX_INTS]; - unsigned int safety_time; unsigned int ttrkl_time; unsigned int update_time; unsigned int max_voltage_mv; unsigned int min_voltage_mv; unsigned int uvd_voltage_mv; - unsigned int alarm_voltage_mv; + unsigned int safe_current_ma; + unsigned int alarm_low_mv; + unsigned int alarm_high_mv; int cool_temp_dc; int warm_temp_dc; unsigned int temp_check_period; @@ -244,6 +243,7 @@ struct pm8921_chg_chip { unsigned int is_bat_cool; unsigned int is_bat_warm; unsigned int resume_voltage_delta; + int resume_charge_percent; unsigned int term_current; unsigned int vbat_channel; unsigned int batt_temp_channel; @@ -254,12 +254,13 @@ struct pm8921_chg_chip { struct power_supply batt_psy; struct dentry *dent; struct bms_notify bms_notify; - bool keep_btm_on_suspend; + int *usb_trim_table; + struct regulator *vreg_xoadc; bool ext_charging; bool ext_charge_done; bool iusb_fine_res; - bool dc_unplug_check; - bool disable_hw_clock_switching; + bool final_kickstart; + bool lockup_lpm_wrkarnd; DECLARE_BITMAP(enabled_irqs, PM_CHG_MAX_INTS); struct work_struct battery_id_valid_work; int64_t batt_id_min; @@ -275,6 +276,7 @@ struct pm8921_chg_chip { struct delayed_work eoc_work; struct delayed_work unplug_check_work; struct delayed_work vin_collapse_check_work; + struct delayed_work btc_override_work; struct wake_lock eoc_wake_lock; enum pm8921_chg_cold_thr cold_thr; enum pm8921_chg_hot_thr hot_thr; @@ -284,6 +286,14 @@ struct pm8921_chg_chip { bool has_dc_supply; u8 active_path; int recent_reported_soc; + int battery_less_hardware; + int ibatmax_max_adj_ma; + int btc_override; + int btc_override_cold_decidegc; + int btc_override_hot_decidegc; + int btc_delay_ms; + bool btc_panic_if_cant_stop_chg; + int stop_chg_upon_expiry; }; /* user space parameter to limit usb current */ @@ -299,7 +309,70 @@ static int thermal_mitigation; static struct pm8921_chg_chip *the_chip; -static struct pm8xxx_adc_arb_btm_param btm_config; +static DEFINE_SPINLOCK(lpm_lock); +#define LPM_ENABLE_BIT BIT(2) +static int pm8921_chg_set_lpm(struct pm8921_chg_chip *chip, int enable) +{ + int rc; + u8 reg; + + rc = pm8xxx_readb(chip->dev->parent, CHG_CNTRL, ®); + if (rc) { + pr_err("pm8xxx_readb failed: addr=%03X, rc=%d\n", + CHG_CNTRL, rc); + return rc; + } + reg &= ~LPM_ENABLE_BIT; + reg |= (enable ? LPM_ENABLE_BIT : 0); + + rc = pm8xxx_writeb(chip->dev->parent, CHG_CNTRL, reg); + if (rc) { + pr_err("pm_chg_write failed: addr=%03X, rc=%d\n", + CHG_CNTRL, rc); + return rc; + } + + return rc; +} + +static int pm_chg_write(struct pm8921_chg_chip *chip, u16 addr, u8 reg) +{ + int rc; + unsigned long flags = 0; + + /* Disable LPM */ + if (chip->lockup_lpm_wrkarnd) { + spin_lock_irqsave(&lpm_lock, flags); + + /* + * This write could have initiated right after a previous write. + * Allow time to settle to go in to lpm from the previous write + */ + udelay(200); + rc = pm8921_chg_set_lpm(chip, 0); + if (rc) + goto lpm_err; + + /* Wait to come out of LPM */ + udelay(200); + } + + rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + if (rc) { + pr_err("pm_chg_write failed: addr=%03X, rc=%d\n", addr, rc); + goto lpm_err; + } + + /* Enable LPM */ + if (chip->lockup_lpm_wrkarnd) + rc = pm8921_chg_set_lpm(chip, 1); + +lpm_err: + if (chip->lockup_lpm_wrkarnd) + spin_unlock_irqrestore(&lpm_lock, flags); + + return rc; +} static int pm_chg_masked_write(struct pm8921_chg_chip *chip, u16 addr, u8 mask, u8 val) @@ -314,9 +387,9 @@ static int pm_chg_masked_write(struct pm8921_chg_chip *chip, u16 addr, } reg &= ~mask; reg |= val & mask; - rc = pm8xxx_writeb(chip->dev->parent, addr, reg); + rc = pm_chg_write(chip, addr, reg); if (rc) { - pr_err("pm8xxx_writeb failed: addr=%03X, rc=%d\n", addr, rc); + pr_err("pm_chg_write failed: addr=%03X, rc=%d\n", addr, rc); return rc; } return 0; @@ -328,6 +401,23 @@ static int pm_chg_get_rt_status(struct pm8921_chg_chip *chip, int irq_id) chip->pmic_chg_irq[irq_id]); } +static int is_chg_on_bat(struct pm8921_chg_chip *chip) +{ + return !(pm_chg_get_rt_status(chip, DCIN_VALID_IRQ) + || pm_chg_get_rt_status(chip, USBIN_VALID_IRQ)); +} + +static void pm8921_chg_bypass_bat_gone_debounce(struct pm8921_chg_chip *chip, + int bypass) +{ + int rc; + + rc = pm_chg_write(chip, COMPARATOR_OVERRIDE, bypass ? 0x89 : 0x88); + if (rc) { + pr_err("Failed to set bypass bit to %d rc=%d\n", bypass, rc); + } +} + /* Treat OverVoltage/UnderVoltage as source missing */ static int is_usb_chg_plugged_in(struct pm8921_chg_chip *chip) { @@ -353,14 +443,14 @@ static int pm_chg_get_fsm_state(struct pm8921_chg_chip *chip) int err, ret = 0; temp = CAPTURE_FSM_STATE_CMD; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + err = pm_chg_write(chip, CHG_TEST, temp); if (err) { pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); return err; } temp = READ_BANK_7; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + err = pm_chg_write(chip, CHG_TEST, temp); if (err) { pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); return err; @@ -375,7 +465,7 @@ static int pm_chg_get_fsm_state(struct pm8921_chg_chip *chip) ret = temp & 0xF; temp = READ_BANK_4; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + err = pm_chg_write(chip, CHG_TEST, temp); if (err) { pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); return err; @@ -398,7 +488,7 @@ static int pm_chg_get_regulation_loop(struct pm8921_chg_chip *chip) int err; temp = READ_BANK_6; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + err = pm_chg_write(chip, CHG_TEST, temp); if (err) { pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); return err; @@ -480,7 +570,7 @@ static int __pm_chg_vddmax_set(struct pm8921_chg_chip *chip, int voltage) } pr_debug("voltage=%d setting %02x\n", voltage, temp); - return pm8xxx_writeb(chip->dev->parent, CHG_VDD_MAX, temp); + return pm_chg_write(chip, CHG_VDD_MAX, temp); } static int pm_chg_vddmax_get(struct pm8921_chg_chip *chip, int *voltage) @@ -628,10 +718,26 @@ static int pm_chg_uvd_threshold_set(struct pm8921_chg_chip *chip, int thresh_mv) } #define PM8921_CHG_IBATMAX_MIN 325 -#define PM8921_CHG_IBATMAX_MAX 2000 +#define PM8921_CHG_IBATMAX_MAX 3025 #define PM8921_CHG_I_MIN_MA 225 #define PM8921_CHG_I_STEP_MA 50 #define PM8921_CHG_I_MASK 0x3F +static int pm_chg_ibatmax_get(struct pm8921_chg_chip *chip, int *ibat_ma) +{ + u8 temp; + int rc; + + rc = pm8xxx_readb(chip->dev->parent, CHG_IBAT_MAX, &temp); + if (rc) { + pr_err("rc = %d while reading ibat max\n", rc); + *ibat_ma = 0; + return rc; + } + *ibat_ma = (int)(temp & PM8921_CHG_I_MASK) * PM8921_CHG_I_STEP_MA + + PM8921_CHG_I_MIN_MA; + return 0; +} + static int pm_chg_ibatmax_set(struct pm8921_chg_chip *chip, int chg_current) { u8 temp; @@ -703,6 +809,46 @@ struct usb_ma_limit_entry { u8 value; }; +/* USB Trim tables */ +static int usb_trim_8038_table[USB_TRIM_ENTRIES] = { + 0x0, + 0x0, + -0x9, + 0x0, + -0xD, + 0x0, + -0x10, + -0x11, + 0x0, + 0x0, + -0x25, + 0x0, + -0x28, + 0x0, + -0x32, + 0x0 +}; + +static int usb_trim_8917_table[USB_TRIM_ENTRIES] = { + 0x0, + 0x0, + 0xA, + 0xC, + 0x10, + 0x10, + 0x13, + 0x14, + 0x13, + 0x3, + 0x1A, + 0x1D, + 0x1D, + 0x21, + 0x24, + 0x26 +}; + +/* Maximum USB setting table */ static struct usb_ma_limit_entry usb_ma_table[] = { {100, 0x0}, {200, 0x1}, @@ -722,18 +868,113 @@ static struct usb_ma_limit_entry usb_ma_table[] = { {1600, 0xF}, }; +#define REG_SBI_CONFIG 0x04F +#define PAGE3_ENABLE_MASK 0x6 +#define USB_OVP_TRIM_MASK 0x3F +#define USB_OVP_TRIM_PM8917_MASK 0x7F +#define USB_OVP_TRIM_MIN 0x00 +#define REG_USB_OVP_TRIM_ORIG_LSB 0x10A +#define REG_USB_OVP_TRIM_ORIG_MSB 0x09C +#define REG_USB_OVP_TRIM_PM8917 0x2B5 +#define REG_USB_OVP_TRIM_PM8917_BIT BIT(0) +static int pm_chg_usb_trim(struct pm8921_chg_chip *chip, int index) +{ + u8 temp, sbi_config, msb, lsb, mask; + s8 trim; + int rc = 0; + static u8 usb_trim_reg_orig = 0xFF; + + /* No trim data for PM8921 */ + if (!chip->usb_trim_table) + return 0; + + if (usb_trim_reg_orig == 0xFF) { + rc = pm8xxx_readb(chip->dev->parent, + REG_USB_OVP_TRIM_ORIG_MSB, &msb); + if (rc) { + pr_err("error = %d reading sbi config reg\n", rc); + return rc; + } + + rc = pm8xxx_readb(chip->dev->parent, + REG_USB_OVP_TRIM_ORIG_LSB, &lsb); + if (rc) { + pr_err("error = %d reading sbi config reg\n", rc); + return rc; + } + + msb = msb >> 5; + lsb = lsb >> 5; + usb_trim_reg_orig = msb << 3 | lsb; + + if (pm8xxx_get_version(chip->dev->parent) + == PM8XXX_VERSION_8917) { + rc = pm8xxx_readb(chip->dev->parent, + REG_USB_OVP_TRIM_PM8917, &msb); + if (rc) { + pr_err("error = %d reading config reg\n", rc); + return rc; + } + + msb = msb & REG_USB_OVP_TRIM_PM8917_BIT; + usb_trim_reg_orig |= msb << 6; + } + } + + /* use the original trim value */ + trim = usb_trim_reg_orig; + + trim += chip->usb_trim_table[index]; + if (trim < 0) + trim = 0; + + pr_debug("trim_orig %d write 0x%x index=%d value 0x%x to USB_OVP_TRIM\n", + usb_trim_reg_orig, trim, index, chip->usb_trim_table[index]); + + rc = pm8xxx_readb(chip->dev->parent, REG_SBI_CONFIG, &sbi_config); + if (rc) { + pr_err("error = %d reading sbi config reg\n", rc); + return rc; + } + + temp = sbi_config | PAGE3_ENABLE_MASK; + rc = pm_chg_write(chip, REG_SBI_CONFIG, temp); + if (rc) { + pr_err("error = %d writing sbi config reg\n", rc); + return rc; + } + + mask = USB_OVP_TRIM_MASK; + + if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917) + mask = USB_OVP_TRIM_PM8917_MASK; + + rc = pm_chg_masked_write(chip, USB_OVP_TRIM, mask, trim); + if (rc) { + pr_err("error = %d writing USB_OVP_TRIM\n", rc); + return rc; + } + + rc = pm_chg_write(chip, REG_SBI_CONFIG, sbi_config); + if (rc) { + pr_err("error = %d writing sbi config reg\n", rc); + return rc; + } + return rc; +} + #define PM8921_CHG_IUSB_MASK 0x1C #define PM8921_CHG_IUSB_SHIFT 2 #define PM8921_CHG_IUSB_MAX 7 #define PM8921_CHG_IUSB_MIN 0 #define PM8917_IUSB_FINE_RES BIT(0) -static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int reg_val) +static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int index) { - u8 temp, fineres; + u8 temp, fineres, reg_val; int rc; - fineres = PM8917_IUSB_FINE_RES & usb_ma_table[reg_val].value; - reg_val = usb_ma_table[reg_val].value >> 1; + reg_val = usb_ma_table[index].value >> 1; + fineres = PM8917_IUSB_FINE_RES & usb_ma_table[index].value; if (reg_val < PM8921_CHG_IUSB_MIN || reg_val > PM8921_CHG_IUSB_MAX) { pr_err("bad mA=%d asked to set\n", reg_val); @@ -758,17 +999,25 @@ static int pm_chg_iusbmax_set(struct pm8921_chg_chip *chip, int reg_val) if (fineres) { rc = pm_chg_masked_write(chip, IUSB_FINE_RES, PM8917_IUSB_FINE_RES, fineres); - if (rc) + if (rc) { pr_err("Failed to write ISUB_FINE_RES rc=%d\n", rc); + return rc; + } } } else { rc = pm_chg_masked_write(chip, PBL_ACCESS2, PM8921_CHG_IUSB_MASK, temp); - if (rc) + if (rc) { pr_err("Failed to write PBL_ACCESS2 rc=%d\n", rc); + return rc; + } } + rc = pm_chg_usb_trim(chip, index); + if (rc) + pr_err("unable to set usb trim rc = %d\n", rc); + return rc; } @@ -801,6 +1050,11 @@ static int pm_chg_iusbmax_get(struct pm8921_chg_chip *chip, int *mA) break; } + if (i < 0) { + pr_err("can't find %d in usb_ma_table. Use min.\n", temp); + i = 0; + } + *mA = usb_ma_table[i].usb_ma; return rc; @@ -831,7 +1085,7 @@ static int pm_chg_tchg_max_set(struct pm8921_chg_chip *chip, int minutes) temp); } -#define PM8921_CHG_TTRKL_MASK 0x1F +#define PM8921_CHG_TTRKL_MASK 0x3F #define PM8921_CHG_TTRKL_MIN 1 #define PM8921_CHG_TTRKL_MAX 64 static int pm_chg_ttrkl_max_set(struct pm8921_chg_chip *chip, int minutes) @@ -980,55 +1234,6 @@ static int pm_chg_led_src_config(struct pm8921_chg_chip *chip, PM8921_CHG_LED_SRC_CONFIG_MASK, temp); } -static void disable_input_voltage_regulation(struct pm8921_chg_chip *chip) -{ - u8 temp; - int rc; - - rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70); - if (rc) { - pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc); - return; - } - rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp); - if (rc) { - pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc); - return; - } - /* set the input voltage disable bit and the write bit */ - temp |= 0x81; - rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp); - if (rc) { - pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc); - return; - } -} - -static void enable_input_voltage_regulation(struct pm8921_chg_chip *chip) -{ - u8 temp; - int rc; - - rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x70); - if (rc) { - pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc); - return; - } - rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp); - if (rc) { - pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc); - return; - } - /* unset the input voltage disable bit */ - temp &= 0xFE; - /* set the write bit */ - temp |= 0x80; - rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, temp); - if (rc) { - pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc); - return; - } -} static int64_t read_battery_id(struct pm8921_chg_chip *chip) { @@ -1199,6 +1404,8 @@ static int pm_power_get_property_mains(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) { + int type; + /* Check if called before init */ if (!the_chip) return -EINVAL; @@ -1213,29 +1420,16 @@ static int pm_power_get_property_mains(struct power_supply *psy, return 0; } - if (charging_disabled) - return 0; - - /* check external charger first before the dc path */ - if (is_ext_charging(the_chip)) { - val->intval = 1; - return 0; - } - - if (pm_is_chg_charge_dis(the_chip)) { - val->intval = 0; - return 0; - } - if (the_chip->dc_present) { val->intval = 1; return 0; } - /* USB with max current greater than 500 mA connected */ - if (usb_target_ma > USB_WALL_THRESHOLD_MA) + type = the_chip->usb_psy.type; + if (type == POWER_SUPPLY_TYPE_USB_DCP || + type == POWER_SUPPLY_TYPE_USB_ACA || + type == POWER_SUPPLY_TYPE_USB_CDP) val->intval = is_usb_chg_plugged_in(the_chip); - return 0; break; default: @@ -1252,7 +1446,7 @@ static int switch_usb_to_charge_mode(struct pm8921_chg_chip *chip) return 0; /* enable usbin valid comparator and remove force usb ovp fet off */ - rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0xB2); + rc = pm_chg_write(chip, USB_OVP_TEST, 0xB2); if (rc < 0) { pr_err("Failed to write 0xB2 to USB_OVP_TEST rc = %d\n", rc); return rc; @@ -1271,7 +1465,7 @@ static int switch_usb_to_host_mode(struct pm8921_chg_chip *chip) return 0; /* disable usbin valid comparator and force usb ovp fet off */ - rc = pm8xxx_writeb(chip->dev->parent, USB_OVP_TEST, 0xB3); + rc = pm_chg_write(chip, USB_OVP_TEST, 0xB3); if (rc < 0) { pr_err("Failed to write 0xB3 to USB_OVP_TEST rc = %d\n", rc); return rc; @@ -1299,6 +1493,8 @@ static int pm_power_set_property_usb(struct power_supply *psy, else return -EINVAL; break; + case POWER_SUPPLY_PROP_TYPE: + return pm8921_set_usb_power_supply_type(val->intval); default: return -EINVAL; } @@ -1327,21 +1523,10 @@ static int pm_power_get_property_usb(struct power_supply *psy, case POWER_SUPPLY_PROP_PRESENT: case POWER_SUPPLY_PROP_ONLINE: val->intval = 0; - if (charging_disabled) - return 0; - /* - * if drawing any current from usb is disabled behave - * as if no usb cable is connected - */ - if (pm_is_chg_charge_dis(the_chip)) - return 0; - - /* USB charging */ - if (usb_target_ma < USB_WALL_THRESHOLD_MA) + if (the_chip->usb_psy.type == POWER_SUPPLY_TYPE_USB) val->intval = is_usb_chg_plugged_in(the_chip); - else - return 0; + break; case POWER_SUPPLY_PROP_SCOPE: @@ -1366,9 +1551,11 @@ static enum power_supply_property msm_batt_power_props[] = { POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, POWER_SUPPLY_PROP_VOLTAGE_NOW, POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_NOW, POWER_SUPPLY_PROP_TEMP, - POWER_SUPPLY_PROP_ENERGY_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_NOW, }; static int get_prop_battery_uvolts(struct pm8921_chg_chip *chip) @@ -1408,10 +1595,41 @@ static int get_prop_batt_present(struct pm8921_chg_chip *chip) return pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ); } +static int get_prop_batt_status(struct pm8921_chg_chip *chip) +{ + int batt_state = POWER_SUPPLY_STATUS_DISCHARGING; + int fsm_state = pm_chg_get_fsm_state(chip); + int i; + + if (chip->ext_psy) { + if (chip->ext_charge_done) + return POWER_SUPPLY_STATUS_FULL; + if (chip->ext_charging) + return POWER_SUPPLY_STATUS_CHARGING; + } + + for (i = 0; i < ARRAY_SIZE(map); i++) + if (map[i].fsm_state == fsm_state) + batt_state = map[i].batt_state; + + if (fsm_state == FSM_STATE_ON_CHG_HIGHI_1) { + if (!pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ) + || !pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ) + || pm_chg_get_rt_status(chip, CHGHOT_IRQ) + || pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ)) + + batt_state = POWER_SUPPLY_STATUS_NOT_CHARGING; + } + return batt_state; +} + static int get_prop_batt_capacity(struct pm8921_chg_chip *chip) { int percent_soc; + if (chip->battery_less_hardware) + return 100; + if (!get_prop_batt_present(chip)) percent_soc = voltage_based_capacity(chip); else @@ -1421,12 +1639,34 @@ static int get_prop_batt_capacity(struct pm8921_chg_chip *chip) percent_soc = voltage_based_capacity(chip); if (percent_soc <= 10) - pr_warn("low battery charge = %d%%\n", percent_soc); + pr_warn_ratelimited("low battery charge = %d%%\n", + percent_soc); + + if (percent_soc <= chip->resume_charge_percent + && get_prop_batt_status(chip) == POWER_SUPPLY_STATUS_FULL) { + pr_debug("soc fell below %d. charging enabled.\n", + chip->resume_charge_percent); + if (chip->is_bat_warm) + pr_warn_ratelimited("battery is warm = %d, do not resume charging at %d%%.\n", + chip->is_bat_warm, + chip->resume_charge_percent); + else if (chip->is_bat_cool) + pr_warn_ratelimited("battery is cool = %d, do not resume charging at %d%%.\n", + chip->is_bat_cool, + chip->resume_charge_percent); + else + pm_chg_vbatdet_set(the_chip, PM8921_CHG_VBATDET_MAX); + } chip->recent_reported_soc = percent_soc; return percent_soc; } +static int get_prop_batt_current_max(struct pm8921_chg_chip *chip) +{ + return pm8921_bms_get_current_max(); +} + static int get_prop_batt_current(struct pm8921_chg_chip *chip) { int result_ua, rc; @@ -1454,6 +1694,20 @@ static int get_prop_batt_fcc(struct pm8921_chg_chip *chip) return rc; } +static int get_prop_batt_charge_now(struct pm8921_chg_chip *chip) +{ + int rc; + int cc_uah; + + rc = pm8921_bms_cc_uah(&cc_uah); + + if (rc == 0) + return cc_uah; + + pr_err("unable to get batt fcc rc = %d\n", rc); + return rc; +} + static int get_prop_batt_health(struct pm8921_chg_chip *chip) { int temp; @@ -1493,40 +1747,15 @@ static int get_prop_charge_type(struct pm8921_chg_chip *chip) return POWER_SUPPLY_CHARGE_TYPE_NONE; } -static int get_prop_batt_status(struct pm8921_chg_chip *chip) -{ - int batt_state = POWER_SUPPLY_STATUS_DISCHARGING; - int fsm_state = pm_chg_get_fsm_state(chip); - int i; - - if (chip->ext_psy) { - if (chip->ext_charge_done) - return POWER_SUPPLY_STATUS_FULL; - if (chip->ext_charging) - return POWER_SUPPLY_STATUS_CHARGING; - } - - for (i = 0; i < ARRAY_SIZE(map); i++) - if (map[i].fsm_state == fsm_state) - batt_state = map[i].batt_state; - - if (fsm_state == FSM_STATE_ON_CHG_HIGHI_1) { - if (!pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ) - || !pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ) - || pm_chg_get_rt_status(chip, CHGHOT_IRQ) - || pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ)) - - batt_state = POWER_SUPPLY_STATUS_NOT_CHARGING; - } - return batt_state; -} - #define MAX_TOLERABLE_BATT_TEMP_DDC 680 static int get_prop_batt_temp(struct pm8921_chg_chip *chip) { int rc; struct pm8xxx_adc_chan_result result; + if (chip->battery_less_hardware) + return 300; + rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); if (rc) { pr_err("error reading adc channel = %d, rc = %d\n", @@ -1580,11 +1809,17 @@ static int pm_batt_power_get_property(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_NOW: val->intval = get_prop_batt_current(chip); break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = get_prop_batt_current_max(chip); + break; case POWER_SUPPLY_PROP_TEMP: val->intval = get_prop_batt_temp(chip); break; - case POWER_SUPPLY_PROP_ENERGY_FULL: - val->intval = get_prop_batt_fcc(chip) * 1000; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = get_prop_batt_fcc(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = get_prop_batt_charge_now(chip); break; default: return -EINVAL; @@ -1633,7 +1868,13 @@ static void __pm8921_charger_vbus_draw(unsigned int mA) return; } - if (mA >= 0 && mA <= 2) { + if (usb_max_current && mA > usb_max_current) { + pr_debug("restricting usb current to %d instead of %d\n", + usb_max_current, mA); + mA = usb_max_current; + } + + if (mA <= 2) { usb_chg_current = 0; rc = pm_chg_iusbmax_set(the_chip, 0); if (rc) { @@ -1651,6 +1892,12 @@ static void __pm8921_charger_vbus_draw(unsigned int mA) break; } + if (i < 0) { + pr_err("can't find %dmA in usb_ma_table. Use min.\n", + mA); + i = 0; + } + /* Check if IUSB_FINE_RES is available */ while ((usb_ma_table[i].value & PM8917_IUSB_FINE_RES) && !the_chip->iusb_fine_res) @@ -1658,9 +1905,8 @@ static void __pm8921_charger_vbus_draw(unsigned int mA) if (i < 0) i = 0; rc = pm_chg_iusbmax_set(the_chip, i); - if (rc) { + if (rc) pr_err("unable to set iusb to %d rc = %d\n", i, rc); - } } } @@ -1721,22 +1967,6 @@ void pm8921_charger_vbus_draw(unsigned int mA) } EXPORT_SYMBOL_GPL(pm8921_charger_vbus_draw); -int pm8921_charger_enable(bool enable) -{ - int rc; - - if (!the_chip) { - pr_err("called before init\n"); - return -EINVAL; - } - enable = !!enable; - rc = pm_chg_auto_enable(the_chip, enable); - if (rc) - pr_err("Failed rc=%d\n", rc); - return rc; -} -EXPORT_SYMBOL(pm8921_charger_enable); - int pm8921_is_usb_chg_plugged_in(void) { if (!the_chip) { @@ -1791,8 +2021,7 @@ int pm8921_disable_input_current_limit(bool disable) if (disable) { pr_warn("Disabling input current limit!\n"); - return pm8xxx_writeb(the_chip->dev->parent, - CHG_BUCK_CTRL_TEST3, 0xF2); + return pm_chg_write(the_chip, CHG_BUCK_CTRL_TEST3, 0xF2); } return 0; } @@ -1958,7 +2187,7 @@ int pm8921_set_usb_power_supply_type(enum power_supply_type type) return -EINVAL; } - if (type < POWER_SUPPLY_TYPE_USB) + if (type < POWER_SUPPLY_TYPE_USB && type > POWER_SUPPLY_TYPE_BATTERY) return -EINVAL; the_chip->usb_psy.type = type; @@ -1977,78 +2206,86 @@ int pm8921_batt_temperature(void) return get_prop_batt_temp(the_chip); } -static int pm8921_charger_enable_batt_alarm(struct pm8921_chg_chip *chip) +static int pm8921_apply_19p2mhz_kickstart(struct pm8921_chg_chip *chip) { - int rc = 0; + int err; + u8 temp; + unsigned long flags = 0; - rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); - if (!rc) - rc = pm8xxx_batt_alarm_enable( - PM8XXX_BATT_ALARM_LOWER_COMPARATOR); - if (rc) { - pr_err("unable to set batt alarm state rc=%d\n", rc); - return rc; + spin_lock_irqsave(&lpm_lock, flags); + err = pm8921_chg_set_lpm(chip, 0); + if (err) { + pr_err("Error settig LPM rc=%d\n", err); + goto kick_err; } - return rc; -} -static int pm8921_charger_configure_batt_alarm(struct pm8921_chg_chip *chip) -{ - int rc = 0; - - rc = pm8xxx_batt_alarm_disable(PM8XXX_BATT_ALARM_UPPER_COMPARATOR); - if (!rc) - rc = pm8xxx_batt_alarm_disable( - PM8XXX_BATT_ALARM_LOWER_COMPARATOR); - if (rc) { - pr_err("unable to set batt alarm state rc=%d\n", rc); - return rc; + temp = 0xD1; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; } - /* - * The batt-alarm driver requires sane values for both min / max, - * regardless of whether they're both activated. - */ - rc = pm8xxx_batt_alarm_threshold_set( - PM8XXX_BATT_ALARM_LOWER_COMPARATOR, - chip->alarm_voltage_mv); - /* We only handle the lower limit of the battery alarm, thus - * set a high sane maximum. - */ - if (!rc) - rc = pm8xxx_batt_alarm_threshold_set( - PM8XXX_BATT_ALARM_UPPER_COMPARATOR, 5000); - if (rc) { - pr_err("unable to set batt alarm threshold rc=%d\n", rc); - return rc; + temp = 0xD3; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; } - rc = pm8xxx_batt_alarm_hold_time_set( - PM8XXX_BATT_ALARM_HOLD_TIME_16_MS); - if (rc) { - pr_err("unable to set batt alarm hold time rc=%d\n", rc); - return rc; + temp = 0xD1; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; } - /* PWM enabled at 2Hz */ - rc = pm8xxx_batt_alarm_pwm_rate_set(1, 7, 4); - if (rc) { - pr_err("unable to set batt alarm pwm rate rc=%d\n", rc); - return rc; + temp = 0xD5; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; } - rc = pm8xxx_batt_alarm_register_notifier(&alarm_notifier); - if (rc) { - pr_err("unable to register alarm notifier rc=%d\n", rc); - return rc; + /* Wait a few clock cycles before re-enabling hw clock switching */ + udelay(183); + + temp = 0xD1; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; } - return rc; + temp = 0xD0; + err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); + if (err) { + pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); + goto kick_err; + } + + /* Wait for few clock cycles before re-enabling LPM */ + udelay(32); + +kick_err: + err = pm8921_chg_set_lpm(chip, 1); + if (err) + pr_err("Error settig LPM rc=%d\n", err); + + spin_unlock_irqrestore(&lpm_lock, flags); + + return err; } static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip) { - int usb_present; + int usb_present, rc = 0; + + if (chip->lockup_lpm_wrkarnd) { + rc = pm8921_apply_19p2mhz_kickstart(chip); + if (rc) + pr_err("Failed to apply kickstart rc=%d\n", rc); + } pm_chg_failed_clear(chip, 1); usb_present = is_usb_chg_plugged_in(chip); @@ -2058,23 +2295,30 @@ static void handle_usb_insertion_removal(struct pm8921_chg_chip *chip) power_supply_changed(&chip->usb_psy); power_supply_changed(&chip->batt_psy); pm8921_bms_calibrate_hkadc(); + + /* Enable/disable bypass if charger is on battery */ + if (chip->lockup_lpm_wrkarnd) + pm8921_chg_bypass_bat_gone_debounce(chip, + is_chg_on_bat(chip)); } if (usb_present) { schedule_delayed_work(&chip->unplug_check_work, - round_jiffies_relative(msecs_to_jiffies - (UNPLUG_CHECK_WAIT_PERIOD_MS))); + msecs_to_jiffies(UNPLUG_CHECK_RAMP_MS)); pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); } else { /* USB unplugged reset target current */ usb_target_ma = 0; pm8921_chg_disable_irq(chip, CHG_GONE_IRQ); } - enable_input_voltage_regulation(chip); bms_notify_check(chip); } static void handle_stop_ext_chg(struct pm8921_chg_chip *chip) { + if (chip->lockup_lpm_wrkarnd) + /* Enable bypass if charger is on battery */ + pm8921_chg_bypass_bat_gone_debounce(chip, is_chg_on_bat(chip)); + if (!chip->ext_psy) { pr_debug("external charger not registered.\n"); return; @@ -2101,10 +2345,13 @@ static void handle_start_ext_chg(struct pm8921_chg_chip *chip) int dc_present; int batt_present; int batt_temp_ok; - int vbat_ov; unsigned long delay = round_jiffies_relative(msecs_to_jiffies(EOC_CHECK_PERIOD_MS)); + /* Disable bypass if charger connected and not running on bat */ + if (chip->lockup_lpm_wrkarnd) + pm8921_chg_bypass_bat_gone_debounce(chip, is_chg_on_bat(chip)); + if (!chip->ext_psy) { pr_debug("external charger not registered.\n"); return; @@ -2115,7 +2362,7 @@ static void handle_start_ext_chg(struct pm8921_chg_chip *chip) return; } - dc_present = is_dc_chg_plugged_in(the_chip); + dc_present = is_dc_chg_plugged_in(chip); batt_present = pm_chg_get_rt_status(chip, BATT_INSERTED_IRQ); batt_temp_ok = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ); @@ -2131,28 +2378,29 @@ static void handle_start_ext_chg(struct pm8921_chg_chip *chip) pr_warn("%s. battery temperature not ok.\n", __func__); return; } - pm8921_disable_source_current(true); /* Force BATFET=ON */ - vbat_ov = pm_chg_get_rt_status(chip, VBAT_OV_IRQ); - if (vbat_ov) { - pr_warn("%s. battery over voltage.\n", __func__); - return; - } + + /* Force BATFET=ON */ + pm8921_disable_source_current(true); schedule_delayed_work(&chip->unplug_check_work, - round_jiffies_relative(msecs_to_jiffies - (UNPLUG_CHECK_WAIT_PERIOD_MS))); - pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); + msecs_to_jiffies(UNPLUG_CHECK_RAMP_MS)); power_supply_set_online(chip->ext_psy, dc_present); power_supply_set_charge_type(chip->ext_psy, POWER_SUPPLY_CHARGE_TYPE_FAST); - power_supply_changed(&chip->dc_psy); chip->ext_charging = true; chip->ext_charge_done = false; bms_notify_check(chip); - /* Start BMS */ + /* + * since we wont get a fastchg irq from external charger + * use eoc worker to detect end of charging + */ schedule_delayed_work(&chip->eoc_work, delay); wake_lock(&chip->eoc_wake_lock); + if (chip->btc_override) + schedule_delayed_work(&chip->btc_override_work, + round_jiffies_relative(msecs_to_jiffies + (chip->btc_delay_ms))); /* Update battery charging LEDs and user space battery info */ power_supply_changed(&chip->batt_psy); } @@ -2162,72 +2410,31 @@ static void turn_off_ovp_fet(struct pm8921_chg_chip *chip, u16 ovptestreg) u8 temp; int rc; - rc = pm8xxx_writeb(chip->dev->parent, ovptestreg, 0x30); + rc = pm_chg_write(chip, ovptestreg, 0x30); if (rc) { - pr_err("Failed to write 0x30 to OVP_TEST rc = %d\n", rc); + pr_err("Failed to write 0x30 to ovptestreg rc = %d\n", rc); return; } rc = pm8xxx_readb(chip->dev->parent, ovptestreg, &temp); if (rc) { - pr_err("Failed to read from OVP_TEST rc = %d\n", rc); + pr_err("Failed to read from ovptestreg rc = %d\n", rc); return; } /* set ovp fet disable bit and the write bit */ temp |= 0x81; - rc = pm8xxx_writeb(chip->dev->parent, ovptestreg, temp); + rc = pm_chg_write(chip, ovptestreg, temp); if (rc) { - pr_err("Failed to write 0x%x OVP_TEST rc=%d\n", temp, rc); + pr_err("Failed to write 0x%x ovptestreg rc=%d\n", temp, rc); return; } } -static int pm8921_battery_gauge_alarm_notify(struct notifier_block *nb, - unsigned long status, void *unused) -{ - int rc, fsm_state; - - pr_info("status: %lu\n", status); - - /* Check if called before init */ - - switch (status) { - case 0: - pr_err("spurious interrupt\n"); - break; - /* expected case - trip of low threshold */ - case 1: - if (!the_chip) { - pr_err("not initialized\n"); - return -EINVAL; - } - - fsm_state = pm_chg_get_fsm_state(the_chip); - the_chip->disable_hw_clock_switching = 1; - - rc = pm8xxx_batt_alarm_disable( - PM8XXX_BATT_ALARM_UPPER_COMPARATOR); - if (!rc) - rc = pm8xxx_batt_alarm_disable( - PM8XXX_BATT_ALARM_LOWER_COMPARATOR); - if (rc) - pr_err("unable to set alarm state rc=%d\n", rc); - break; - case 2: - pr_err("trip of high threshold\n"); - break; - default: - pr_err("error received\n"); - }; - - return 0; -} - static void turn_on_ovp_fet(struct pm8921_chg_chip *chip, u16 ovptestreg) { u8 temp; int rc; - rc = pm8xxx_writeb(chip->dev->parent, ovptestreg, 0x30); + rc = pm_chg_write(chip, ovptestreg, 0x30); if (rc) { pr_err("Failed to write 0x30 to OVP_TEST rc = %d\n", rc); return; @@ -2240,7 +2447,7 @@ static void turn_on_ovp_fet(struct pm8921_chg_chip *chip, u16 ovptestreg) /* unset ovp fet disable bit and set the write bit */ temp &= 0xFE; temp |= 0x80; - rc = pm8xxx_writeb(chip->dev->parent, ovptestreg, temp); + rc = pm_chg_write(chip, ovptestreg, temp); if (rc) { pr_err("Failed to write 0x%x to OVP_TEST rc = %d\n", temp, rc); @@ -2308,7 +2515,11 @@ static void unplug_ovp_fet_open(struct pm8921_chg_chip *chip) break; } } - pm_chg_masked_write(chip, ovpreg, OVP_DEBOUNCE_TIME, 0x2); + if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917) + pm_chg_masked_write(chip, ovpreg, OVP_DEBOUNCE_TIME, 0x6); + else + pm_chg_masked_write(chip, ovpreg, OVP_DEBOUNCE_TIME, 0x2); + pr_debug("Exit count=%d chg_gone=%d, active_valid=%d\n", count, chg_gone, active_chg_plugged_in); return; @@ -2337,6 +2548,13 @@ static void decrease_usb_ma_value(int *value) while (!the_chip->iusb_fine_res && i > 0 && (usb_ma_table[i].value & PM8917_IUSB_FINE_RES)) i--; + + if (i < 0) { + pr_err("can't find %dmA in usb_ma_table. Use min.\n", + *value); + i = 0; + } + *value = usb_ma_table[i].usb_ma; } } @@ -2366,15 +2584,25 @@ static void vin_collapse_check_worker(struct work_struct *work) struct pm8921_chg_chip *chip = container_of(dwork, struct pm8921_chg_chip, vin_collapse_check_work); - /* AICL only for wall-chargers */ - if (is_usb_chg_plugged_in(chip) && - usb_target_ma > USB_WALL_THRESHOLD_MA) { + /* + * AICL only for wall-chargers. If the charger appears to be plugged + * back in now, the corresponding unplug must have been because of we + * were trying to draw more current than the charger can support. In + * such a case reset usb current to 500mA and decrease the target. + * The AICL algorithm will step up the current from 500mA to target + */ + if (is_usb_chg_plugged_in(chip) + && usb_target_ma > USB_WALL_THRESHOLD_MA) { /* decrease usb_target_ma */ decrease_usb_ma_value(&usb_target_ma); /* reset here, increase in unplug_check_worker */ __pm8921_charger_vbus_draw(USB_WALL_THRESHOLD_MA); pr_debug("usb_now=%d, usb_target = %d\n", USB_WALL_THRESHOLD_MA, usb_target_ma); + if (!delayed_work_pending(&chip->unplug_check_work)) + schedule_delayed_work(&chip->unplug_check_work, + msecs_to_jiffies + (UNPLUG_CHECK_WAIT_PERIOD_MS)); } else { handle_usb_insertion_removal(chip); } @@ -2392,12 +2620,6 @@ static irqreturn_t usbin_valid_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t usbin_ov_irq_handler(int irq, void *data) -{ - pr_err("USB OverVoltage\n"); - return IRQ_HANDLED; -} - static irqreturn_t batt_inserted_irq_handler(int irq, void *data) { struct pm8921_chg_chip *chip = data; @@ -2442,18 +2664,6 @@ static irqreturn_t vbatdet_low_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static irqreturn_t usbin_uv_irq_handler(int irq, void *data) -{ - pr_err("USB UnderVoltage\n"); - return IRQ_HANDLED; -} - -static irqreturn_t vbat_ov_irq_handler(int irq, void *data) -{ - pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); - return IRQ_HANDLED; -} - static irqreturn_t chgwdog_irq_handler(int irq, void *data) { pr_debug("fsm_state=%d\n", pm_chg_get_fsm_state(data)); @@ -2500,9 +2710,11 @@ static irqreturn_t chgfail_irq_handler(int irq, void *data) struct pm8921_chg_chip *chip = data; int ret; - ret = pm_chg_failed_clear(chip, 1); - if (ret) - pr_err("Failed to write CHG_FAILED_CLEAR bit\n"); + if (!chip->stop_chg_upon_expiry) { + ret = pm_chg_failed_clear(chip, 1); + if (ret) + pr_err("Failed to write CHG_FAILED_CLEAR bit\n"); + } pr_err("batt_present = %d, batt_temp_ok = %d, state_changed_to=%d\n", get_prop_batt_present(chip), @@ -2529,29 +2741,49 @@ static irqreturn_t chgstate_irq_handler(int irq, void *data) return IRQ_HANDLED; } -static int param_vin_disable_counter = 5; -module_param(param_vin_disable_counter, int, 0644); +enum { + PON_TIME_25NS = 0x04, + PON_TIME_50NS = 0x08, + PON_TIME_100NS = 0x0C, +}; -static void attempt_reverse_boost_fix(struct pm8921_chg_chip *chip, - int count, int usb_ma) +static void set_min_pon_time(struct pm8921_chg_chip *chip, int pon_time_ns) { - if (usb_ma) - __pm8921_charger_vbus_draw(500); - pr_debug("count = %d iusb=500mA\n", count); - disable_input_voltage_regulation(chip); - pr_debug("count = %d disable_input_regulation\n", count); + u8 temp; + int rc; - msleep(20); + rc = pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, 0x40); + if (rc) { + pr_err("Failed to write 0x70 to CTRL_TEST3 rc = %d\n", rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, &temp); + if (rc) { + pr_err("Failed to read CTRL_TEST3 rc = %d\n", rc); + return; + } + /* clear the min pon time select bit */ + temp &= 0xF3; + /* set the pon time */ + temp |= (u8)pon_time_ns; + /* write enable bank 4 */ + temp |= 0x80; + rc = pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, temp); + if (rc) { + pr_err("Failed to write 0x%x to CTRL_TEST3 rc=%d\n", temp, rc); + return; + } +} - pr_debug("count = %d end sleep 20ms chg_gone=%d, usb_valid = %d\n", - count, - pm_chg_get_rt_status(chip, CHG_GONE_IRQ), - is_usb_chg_plugged_in(chip)); - pr_debug("count = %d restoring input regulation and usb_ma = %d\n", - count, usb_ma); - enable_input_voltage_regulation(chip); - if (usb_ma) - __pm8921_charger_vbus_draw(usb_ma); +static void attempt_reverse_boost_fix(struct pm8921_chg_chip *chip) +{ + pr_debug("Start\n"); + set_min_pon_time(chip, PON_TIME_100NS); + pm_chg_vinmin_set(chip, chip->vin_min + 200); + msleep(250); + pm_chg_vinmin_set(chip, chip->vin_min); + set_min_pon_time(chip, PON_TIME_25NS); + pr_debug("End\n"); } #define VIN_ACTIVE_BIT BIT(0) @@ -2562,19 +2794,18 @@ static void unplug_check_worker(struct work_struct *work) struct delayed_work *dwork = to_delayed_work(work); struct pm8921_chg_chip *chip = container_of(dwork, struct pm8921_chg_chip, unplug_check_work); - u8 reg_loop, active_path; + u8 reg_loop = 0, active_path; int rc, ibat, active_chg_plugged_in, usb_ma; int chg_gone = 0; - - reg_loop = 0; + bool ramp = false; rc = pm8xxx_readb(chip->dev->parent, PBL_ACCESS1, &active_path); if (rc) { pr_err("Failed to read PBL_ACCESS1 rc=%d\n", rc); return; } - chip->active_path = active_path; + chip->active_path = active_path; active_chg_plugged_in = is_active_chg_plugged_in(chip, active_path); pr_debug("active_path = 0x%x, active_chg_plugged_in = %d\n", active_path, active_chg_plugged_in); @@ -2582,11 +2813,6 @@ static void unplug_check_worker(struct work_struct *work) pr_debug("USB charger active\n"); pm_chg_iusbmax_get(chip, &usb_ma); - if (usb_ma == 500 && !usb_target_ma) { - pr_debug("Stopping Unplug Check Worker USB == 500mA\n"); - disable_input_voltage_regulation(chip); - return; - } if (usb_ma <= 100) { pr_debug( @@ -2596,10 +2822,6 @@ static void unplug_check_worker(struct work_struct *work) } } else if (active_path & DC_ACTIVE_BIT) { pr_debug("DC charger active\n"); - /* Some board designs are not prone to reverse boost on DC - * charging path */ - if (!chip->dc_unplug_check) - return; } else { /* No charger active */ if (!(is_usb_chg_plugged_in(chip) @@ -2610,15 +2832,35 @@ static void unplug_check_worker(struct work_struct *work) pm_chg_get_fsm_state(chip), get_prop_batt_current(chip) ); + if (chip->lockup_lpm_wrkarnd) { + rc = pm8921_apply_19p2mhz_kickstart(chip); + if (rc) + pr_err("Failed kickstart rc=%d\n", rc); + + /* + * Make sure kickstart happens at least 200 ms + * after charger has been removed. + */ + if (chip->final_kickstart) { + chip->final_kickstart = false; + goto check_again_later; + } + } + return; + } else { + goto check_again_later; } - return; } - if (active_path & USB_ACTIVE_BIT) { + chip->final_kickstart = true; + + /* AICL only for usb wall charger */ + if ((active_path & USB_ACTIVE_BIT) && usb_target_ma > 0) { reg_loop = pm_chg_get_regulation_loop(chip); pr_debug("reg_loop=0x%x usb_ma = %d\n", reg_loop, usb_ma); if ((reg_loop & VIN_ACTIVE_BIT) && - (usb_ma > USB_WALL_THRESHOLD_MA)) { + (usb_ma > USB_WALL_THRESHOLD_MA) + && !charging_disabled) { decrease_usb_ma_value(&usb_ma); usb_target_ma = usb_ma; /* end AICL here */ @@ -2633,28 +2875,17 @@ static void unplug_check_worker(struct work_struct *work) ibat = get_prop_batt_current(chip); if (reg_loop & VIN_ACTIVE_BIT) { - - pr_debug("ibat = %d fsm = %d reg_loop = 0x%x\n", - ibat, pm_chg_get_fsm_state(chip), reg_loop); if (ibat > 0) { - int count = 0; - - while (count++ < param_vin_disable_counter - && active_chg_plugged_in == 1) { - if (active_path & USB_ACTIVE_BIT) - attempt_reverse_boost_fix(chip, - count, usb_ma); - else - attempt_reverse_boost_fix(chip, - count, 0); - /* after reverse boost fix check if the active - * charger was detected as removed */ - active_chg_plugged_in - = is_active_chg_plugged_in(chip, - active_path); - pr_debug("active_chg_plugged_in = %d\n", - active_chg_plugged_in); - } + pr_debug("revboost ibat = %d fsm = %d loop = 0x%x\n", + ibat, pm_chg_get_fsm_state(chip), reg_loop); + attempt_reverse_boost_fix(chip); + /* after reverse boost fix check if the active + * charger was detected as removed */ + active_chg_plugged_in + = is_active_chg_plugged_in(chip, + active_path); + pr_debug("revboost post: active_chg_plugged_in = %d\n", + active_chg_plugged_in); } } @@ -2669,22 +2900,30 @@ static void unplug_check_worker(struct work_struct *work) unplug_ovp_fet_open(chip); } - if (!(reg_loop & VIN_ACTIVE_BIT) && (active_path & USB_ACTIVE_BIT)) { + /* AICL only for usb wall charger */ + if (!(reg_loop & VIN_ACTIVE_BIT) && (active_path & USB_ACTIVE_BIT) + && usb_target_ma > 0 + && !charging_disabled) { /* only increase iusb_max if vin loop not active */ if (usb_ma < usb_target_ma) { increase_usb_ma_value(&usb_ma); __pm8921_charger_vbus_draw(usb_ma); pr_debug("usb_now=%d, usb_target = %d\n", usb_ma, usb_target_ma); + ramp = true; } else { usb_target_ma = usb_ma; } } check_again_later: + pr_debug("ramp: %d\n", ramp); /* schedule to check again later */ - schedule_delayed_work(&chip->unplug_check_work, - round_jiffies_relative(msecs_to_jiffies - (UNPLUG_CHECK_WAIT_PERIOD_MS))); + if (ramp) + schedule_delayed_work(&chip->unplug_check_work, + msecs_to_jiffies(UNPLUG_CHECK_RAMP_MS)); + else + schedule_delayed_work(&chip->unplug_check_work, + msecs_to_jiffies(UNPLUG_CHECK_WAIT_PERIOD_MS)); } static irqreturn_t loop_change_irq_handler(int irq, void *data) @@ -2698,6 +2937,30 @@ static irqreturn_t loop_change_irq_handler(int irq, void *data) return IRQ_HANDLED; } +struct ibatmax_max_adj_entry { + int ibat_max_ma; + int max_adj_ma; +}; + +static struct ibatmax_max_adj_entry ibatmax_adj_table[] = { + {975, 300}, + {1475, 150}, + {1975, 200}, + {2475, 250}, +}; + +static int find_ibat_max_adj_ma(int ibat_target_ma) +{ + int i = 0; + + for (i = ARRAY_SIZE(ibatmax_adj_table) - 1; i >= 0; i--) { + if (ibat_target_ma <= ibatmax_adj_table[i].ibat_max_ma) + break; + } + + return ibatmax_adj_table[i].max_adj_ma; +} + static irqreturn_t fastchg_irq_handler(int irq, void *data) { struct pm8921_chg_chip *chip = data; @@ -2710,6 +2973,13 @@ static irqreturn_t fastchg_irq_handler(int irq, void *data) round_jiffies_relative(msecs_to_jiffies (EOC_CHECK_PERIOD_MS))); } + if (high_transition + && chip->btc_override + && !delayed_work_pending(&chip->btc_override_work)) { + schedule_delayed_work(&chip->btc_override_work, + round_jiffies_relative(msecs_to_jiffies + (chip->btc_delay_ms))); + } power_supply_changed(&chip->batt_psy); bms_notify_check(chip); return IRQ_HANDLED; @@ -2853,14 +3123,37 @@ static irqreturn_t dcin_valid_irq_handler(int irq, void *data) struct pm8921_chg_chip *chip = data; int dc_present; + pm_chg_failed_clear(chip, 1); dc_present = pm_chg_get_rt_status(chip, DCIN_VALID_IRQ); - if (chip->ext_psy) - power_supply_set_online(chip->ext_psy, dc_present); - chip->dc_present = dc_present; + + if (chip->dc_present ^ dc_present) + pm8921_bms_calibrate_hkadc(); + if (dc_present) - handle_start_ext_chg(chip); + pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); else - handle_stop_ext_chg(chip); + pm8921_chg_disable_irq(chip, CHG_GONE_IRQ); + + chip->dc_present = dc_present; + + if (chip->ext_psy) { + if (dc_present) + handle_start_ext_chg(chip); + else + handle_stop_ext_chg(chip); + } else { + if (chip->lockup_lpm_wrkarnd) + /* if no external supply call bypass debounce here */ + pm8921_chg_bypass_bat_gone_debounce(chip, + is_chg_on_bat(chip)); + + if (dc_present) + schedule_delayed_work(&chip->unplug_check_work, + msecs_to_jiffies(UNPLUG_CHECK_WAIT_PERIOD_MS)); + power_supply_changed(&chip->dc_psy); + } + + power_supply_changed(&chip->batt_psy); return IRQ_HANDLED; } @@ -2923,7 +3216,6 @@ static void update_heartbeat(struct work_struct *work) struct pm8921_chg_chip *chip = container_of(dwork, struct pm8921_chg_chip, update_heartbeat_work); - pm_chg_failed_clear(chip, 1); power_supply_changed(&chip->batt_psy); if (chip->recent_reported_soc <= 20) schedule_delayed_work(&chip->update_heartbeat_work, @@ -2943,12 +3235,10 @@ static int ichg_threshold_ua = -400000; module_param(ichg_threshold_ua, int, 0644); #define PM8921_CHG_VDDMAX_RES_MV 10 -static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip) +static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip, + int vbat_batt_terminal_uv) { - int ichg_meas_ua, vbat_uv; - int ichg_meas_ma; int adj_vdd_max_mv, programmed_vdd_max; - int vbat_batt_terminal_uv; int vbat_batt_terminal_mv; int reg_loop; int delta_mv = 0; @@ -2970,18 +3260,6 @@ static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip) reg_loop); return; } - - pm8921_bms_get_simultaneous_battery_voltage_and_current(&ichg_meas_ua, - &vbat_uv); - if (ichg_meas_ua >= 0) { - pr_debug("Exiting ichg_meas_ua = %d > 0\n", ichg_meas_ua); - return; - } - - ichg_meas_ma = ichg_meas_ua / 1000; - - /* rconn_mohm is in milliOhms */ - vbat_batt_terminal_uv = vbat_uv + ichg_meas_ma * the_chip->rconn_mohm; vbat_batt_terminal_mv = vbat_batt_terminal_uv/1000; pm_chg_vddmax_get(the_chip, &programmed_vdd_max); @@ -3009,6 +3287,95 @@ static void adjust_vdd_max_for_fastchg(struct pm8921_chg_chip *chip) pm_chg_vddmax_set(chip, adj_vdd_max_mv); } +static void set_appropriate_vbatdet(struct pm8921_chg_chip *chip) +{ + if (chip->is_bat_cool) + pm_chg_vbatdet_set(the_chip, + the_chip->cool_bat_voltage + - the_chip->resume_voltage_delta); + else if (chip->is_bat_warm) + pm_chg_vbatdet_set(the_chip, + the_chip->warm_bat_voltage + - the_chip->resume_voltage_delta); + else + pm_chg_vbatdet_set(the_chip, + the_chip->max_voltage_mv + - the_chip->resume_voltage_delta); +} + +static void set_appropriate_battery_current(struct pm8921_chg_chip *chip) +{ + unsigned int chg_current = chip->max_bat_chg_current; + + if (chip->is_bat_cool) + chg_current = min(chg_current, chip->cool_bat_chg_current); + + if (chip->is_bat_warm) + chg_current = min(chg_current, chip->warm_bat_chg_current); + + if (thermal_mitigation != 0 && chip->thermal_mitigation) + chg_current = min(chg_current, + chip->thermal_mitigation[thermal_mitigation]); + + pm_chg_ibatmax_set(the_chip, chg_current); +} + +#define TEMP_HYSTERISIS_DECIDEGC 20 +static void battery_cool(bool enter) +{ + pr_debug("enter = %d\n", enter); + if (enter == the_chip->is_bat_cool) + return; + the_chip->is_bat_cool = enter; + if (enter) + pm_chg_vddmax_set(the_chip, the_chip->cool_bat_voltage); + else + pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); + set_appropriate_battery_current(the_chip); + set_appropriate_vbatdet(the_chip); +} + +static void battery_warm(bool enter) +{ + pr_debug("enter = %d\n", enter); + if (enter == the_chip->is_bat_warm) + return; + the_chip->is_bat_warm = enter; + if (enter) + pm_chg_vddmax_set(the_chip, the_chip->warm_bat_voltage); + else + pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); + + set_appropriate_battery_current(the_chip); + set_appropriate_vbatdet(the_chip); +} + +static void check_temp_thresholds(struct pm8921_chg_chip *chip) +{ + int temp = 0; + + temp = get_prop_batt_temp(chip); + pr_debug("temp = %d, warm_thr_temp = %d, cool_thr_temp = %d\n", + temp, chip->warm_temp_dc, + chip->cool_temp_dc); + + if (chip->warm_temp_dc != INT_MIN) { + if (chip->is_bat_warm + && temp < chip->warm_temp_dc - TEMP_HYSTERISIS_DECIDEGC) + battery_warm(false); + else if (!chip->is_bat_warm && temp >= chip->warm_temp_dc) + battery_warm(true); + } + + if (chip->cool_temp_dc != INT_MIN) { + if (chip->is_bat_cool + && temp > chip->cool_temp_dc + TEMP_HYSTERISIS_DECIDEGC) + battery_cool(false); + else if (!chip->is_bat_cool && temp <= chip->cool_temp_dc) + battery_cool(true); + } +} + enum { CHG_IN_PROGRESS, CHG_NOT_IN_PROGRESS, @@ -3017,10 +3384,10 @@ enum { #define VBAT_TOLERANCE_MV 70 #define CHG_DISABLE_MSLEEP 100 -static int is_charging_finished(struct pm8921_chg_chip *chip) +static int is_charging_finished(struct pm8921_chg_chip *chip, + int vbat_batt_terminal_uv, int ichg_meas_ma) { - int vbat_meas_uv, vbat_meas_mv, vbat_programmed, vbatdet_low; - int ichg_meas_ma, iterm_programmed; + int vbat_programmed, iterm_programmed, vbat_intended; int regulation_loop, fast_chg, vcp; int rc; static int last_vbat_programmed = -EINVAL; @@ -3037,30 +3404,19 @@ static int is_charging_finished(struct pm8921_chg_chip *chip) if (vcp == 1) return CHG_IN_PROGRESS; - vbatdet_low = pm_chg_get_rt_status(chip, VBATDET_LOW_IRQ); - pr_debug("vbatdet_low = %d\n", vbatdet_low); - if (vbatdet_low == 1) - return CHG_IN_PROGRESS; - /* reset count if battery is hot/cold */ rc = pm_chg_get_rt_status(chip, BAT_TEMP_OK_IRQ); pr_debug("batt_temp_ok = %d\n", rc); if (rc == 0) return CHG_IN_PROGRESS; - /* reset count if battery voltage is less than vddmax */ - vbat_meas_uv = get_prop_battery_uvolts(chip); - if (vbat_meas_uv < 0) - return CHG_IN_PROGRESS; - vbat_meas_mv = vbat_meas_uv / 1000; - rc = pm_chg_vddmax_get(chip, &vbat_programmed); if (rc) { pr_err("couldnt read vddmax rc = %d\n", rc); return CHG_IN_PROGRESS; } - pr_debug("vddmax = %d vbat_meas_mv=%d\n", - vbat_programmed, vbat_meas_mv); + pr_debug("vddmax = %d vbat_batt_terminal_uv=%d\n", + vbat_programmed, vbat_batt_terminal_uv); if (last_vbat_programmed == -EINVAL) last_vbat_programmed = vbat_programmed; @@ -3072,6 +3428,20 @@ static int is_charging_finished(struct pm8921_chg_chip *chip) return CHG_IN_PROGRESS; } + if (chip->is_bat_cool) + vbat_intended = chip->cool_bat_voltage; + else if (chip->is_bat_warm) + vbat_intended = chip->warm_bat_voltage; + else + vbat_intended = chip->max_voltage_mv; + + if (vbat_batt_terminal_uv / 1000 < vbat_intended) { + pr_debug("terminal_uv:%d < vbat_intended:%d.\n", + vbat_batt_terminal_uv, + vbat_intended); + return CHG_IN_PROGRESS; + } + regulation_loop = pm_chg_get_regulation_loop(chip); if (regulation_loop < 0) { pr_err("couldnt read the regulation loop err=%d\n", @@ -3091,7 +3461,6 @@ static int is_charging_finished(struct pm8921_chg_chip *chip) return CHG_IN_PROGRESS; } - ichg_meas_ma = (get_prop_batt_current(chip)) / 1000; pr_debug("iterm_programmed = %d ichg_meas_ma=%d\n", iterm_programmed, ichg_meas_ma); /* @@ -3107,6 +3476,155 @@ static int is_charging_finished(struct pm8921_chg_chip *chip) return CHG_FINISHED; } +#define COMP_OVERRIDE_HOT_BANK 6 +#define COMP_OVERRIDE_COLD_BANK 7 +#define COMP_OVERRIDE_BIT BIT(1) +static int pm_chg_override_cold(struct pm8921_chg_chip *chip, int flag) +{ + u8 val; + int rc = 0; + + val = 0x80 | COMP_OVERRIDE_COLD_BANK << 2 | COMP_OVERRIDE_BIT; + + if (flag) + val |= 0x01; + + rc = pm_chg_write(chip, COMPARATOR_OVERRIDE, val); + if (rc < 0) + pr_err("Could not write 0x%x to override rc = %d\n", val, rc); + + pr_debug("btc cold = %d val = 0x%x\n", flag, val); + return rc; +} + +static int pm_chg_override_hot(struct pm8921_chg_chip *chip, int flag) +{ + u8 val; + int rc = 0; + + val = 0x80 | COMP_OVERRIDE_HOT_BANK << 2 | COMP_OVERRIDE_BIT; + + if (flag) + val |= 0x01; + + rc = pm_chg_write(chip, COMPARATOR_OVERRIDE, val); + if (rc < 0) + pr_err("Could not write 0x%x to override rc = %d\n", val, rc); + + pr_debug("btc hot = %d val = 0x%x\n", flag, val); + return rc; +} + +static void __devinit pm8921_chg_btc_override_init(struct pm8921_chg_chip *chip) +{ + int rc = 0; + u8 reg; + u8 val; + + val = COMP_OVERRIDE_HOT_BANK << 2; + rc = pm_chg_write(chip, COMPARATOR_OVERRIDE, val); + if (rc < 0) { + pr_err("Could not write 0x%x to override rc = %d\n", val, rc); + goto cold_init; + } + rc = pm8xxx_readb(chip->dev->parent, COMPARATOR_OVERRIDE, ®); + if (rc < 0) { + pr_err("Could not read bank %d of override rc = %d\n", + COMP_OVERRIDE_HOT_BANK, rc); + goto cold_init; + } + if ((reg & COMP_OVERRIDE_BIT) != COMP_OVERRIDE_BIT) { + /* for now override it as not hot */ + rc = pm_chg_override_hot(chip, 0); + if (rc < 0) + pr_err("Could not override hot rc = %d\n", rc); + } + +cold_init: + val = COMP_OVERRIDE_COLD_BANK << 2; + rc = pm_chg_write(chip, COMPARATOR_OVERRIDE, val); + if (rc < 0) { + pr_err("Could not write 0x%x to override rc = %d\n", val, rc); + return; + } + rc = pm8xxx_readb(chip->dev->parent, COMPARATOR_OVERRIDE, ®); + if (rc < 0) { + pr_err("Could not read bank %d of override rc = %d\n", + COMP_OVERRIDE_COLD_BANK, rc); + return; + } + if ((reg & COMP_OVERRIDE_BIT) != COMP_OVERRIDE_BIT) { + /* for now override it as not cold */ + rc = pm_chg_override_cold(chip, 0); + if (rc < 0) + pr_err("Could not override cold rc = %d\n", rc); + } +} + +static void btc_override_worker(struct work_struct *work) +{ + int decidegc; + int temp; + int rc = 0; + struct delayed_work *dwork = to_delayed_work(work); + struct pm8921_chg_chip *chip = container_of(dwork, + struct pm8921_chg_chip, btc_override_work); + + if (!chip->btc_override) { + pr_err("called when not enabled\n"); + return; + } + + decidegc = get_prop_batt_temp(chip); + + pr_debug("temp=%d\n", decidegc); + + temp = pm_chg_get_rt_status(chip, BATTTEMP_HOT_IRQ); + if (temp) { + if (decidegc < chip->btc_override_hot_decidegc) + /* stop forcing batt hot */ + rc = pm_chg_override_hot(chip, 0); + if (rc) + pr_err("Couldnt write 0 to hot comp\n"); + } else { + if (decidegc >= chip->btc_override_hot_decidegc) + /* start forcing batt hot */ + rc = pm_chg_override_hot(chip, 1); + if (rc && chip->btc_panic_if_cant_stop_chg) + panic("Couldnt override comps to stop chg\n"); + } + + temp = pm_chg_get_rt_status(chip, BATTTEMP_COLD_IRQ); + if (temp) { + if (decidegc > chip->btc_override_cold_decidegc) + /* stop forcing batt cold */ + rc = pm_chg_override_cold(chip, 0); + if (rc) + pr_err("Couldnt write 0 to cold comp\n"); + } else { + if (decidegc <= chip->btc_override_cold_decidegc) + /* start forcing batt cold */ + rc = pm_chg_override_cold(chip, 1); + if (rc && chip->btc_panic_if_cant_stop_chg) + panic("Couldnt override comps to stop chg\n"); + } + + if ((is_dc_chg_plugged_in(the_chip) || is_usb_chg_plugged_in(the_chip)) + && get_prop_batt_status(chip) != POWER_SUPPLY_STATUS_FULL) { + schedule_delayed_work(&chip->btc_override_work, + round_jiffies_relative(msecs_to_jiffies + (chip->btc_delay_ms))); + return; + } + + rc = pm_chg_override_hot(chip, 0); + if (rc) + pr_err("Couldnt write 0 to hot comp\n"); + rc = pm_chg_override_cold(chip, 0); + if (rc) + pr_err("Couldnt write 0 to cold comp\n"); +} + /** * eoc_worker - internal function to check if battery EOC * has happened @@ -3126,26 +3644,24 @@ static void eoc_worker(struct work_struct *work) struct pm8921_chg_chip, eoc_work); static int count; int end; + int vbat_meas_uv, vbat_meas_mv; + int ichg_meas_ua, ichg_meas_ma; + int vbat_batt_terminal_uv; - pm_chg_failed_clear(chip, 1); - end = is_charging_finished(chip); + pm8921_bms_get_simultaneous_battery_voltage_and_current( + &ichg_meas_ua, &vbat_meas_uv); + vbat_meas_mv = vbat_meas_uv / 1000; + /* rconn_mohm is in milliOhms */ + ichg_meas_ma = ichg_meas_ua / 1000; + vbat_batt_terminal_uv = vbat_meas_uv + + ichg_meas_ma + * the_chip->rconn_mohm; + + end = is_charging_finished(chip, vbat_batt_terminal_uv, ichg_meas_ma); if (end == CHG_NOT_IN_PROGRESS) { count = 0; - wake_unlock(&chip->eoc_wake_lock); - return; - } - - /* If the disable hw clock switching - * flag was set it can now be unset. Also, re-enable - * the battery alarm to set the flag again when needed - */ - if (chip->disable_hw_clock_switching) { - /* Unset the hw clock switching flag */ - chip->disable_hw_clock_switching = 0; - - if (pm8921_charger_enable_batt_alarm(chip)) - pr_err("couldn't set up batt alarm!\n"); + goto eoc_worker_stop; } if (end == CHG_FINISHED) { @@ -3169,120 +3685,20 @@ static void eoc_worker(struct work_struct *work) chip->bms_notify.is_battery_full = 1; /* declare end of charging by invoking chgdone interrupt */ chgdone_irq_handler(chip->pmic_chg_irq[CHGDONE_IRQ], chip); - wake_unlock(&chip->eoc_wake_lock); } else { - adjust_vdd_max_for_fastchg(chip); + check_temp_thresholds(chip); + adjust_vdd_max_for_fastchg(chip, vbat_batt_terminal_uv); pr_debug("EOC count = %d\n", count); schedule_delayed_work(&chip->eoc_work, round_jiffies_relative(msecs_to_jiffies (EOC_CHECK_PERIOD_MS))); - } -} - -static void btm_configure_work(struct work_struct *work) -{ - int rc; - - rc = pm8xxx_adc_btm_configure(&btm_config); - if (rc) - pr_err("failed to configure btm rc=%d", rc); -} - -DECLARE_WORK(btm_config_work, btm_configure_work); - -static void set_appropriate_battery_current(struct pm8921_chg_chip *chip) -{ - unsigned int chg_current = chip->max_bat_chg_current; - - if (chip->is_bat_cool) - chg_current = min(chg_current, chip->cool_bat_chg_current); - - if (chip->is_bat_warm) - chg_current = min(chg_current, chip->warm_bat_chg_current); - - if (thermal_mitigation != 0 && chip->thermal_mitigation) - chg_current = min(chg_current, - chip->thermal_mitigation[thermal_mitigation]); - - pm_chg_ibatmax_set(the_chip, chg_current); -} - -#define TEMP_HYSTERISIS_DEGC 2 -static void battery_cool(bool enter) -{ - pr_debug("enter = %d\n", enter); - if (enter == the_chip->is_bat_cool) return; - the_chip->is_bat_cool = enter; - if (enter) { - btm_config.low_thr_temp = - the_chip->cool_temp_dc + TEMP_HYSTERISIS_DEGC; - set_appropriate_battery_current(the_chip); - pm_chg_vddmax_set(the_chip, the_chip->cool_bat_voltage); - pm_chg_vbatdet_set(the_chip, - the_chip->cool_bat_voltage - - the_chip->resume_voltage_delta); - } else { - btm_config.low_thr_temp = the_chip->cool_temp_dc; - set_appropriate_battery_current(the_chip); - pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); - pm_chg_vbatdet_set(the_chip, - the_chip->max_voltage_mv - - the_chip->resume_voltage_delta); } - schedule_work(&btm_config_work); -} -static void battery_warm(bool enter) -{ - pr_debug("enter = %d\n", enter); - if (enter == the_chip->is_bat_warm) - return; - the_chip->is_bat_warm = enter; - if (enter) { - btm_config.high_thr_temp = - the_chip->warm_temp_dc - TEMP_HYSTERISIS_DEGC; - set_appropriate_battery_current(the_chip); - pm_chg_vddmax_set(the_chip, the_chip->warm_bat_voltage); - pm_chg_vbatdet_set(the_chip, - the_chip->warm_bat_voltage - - the_chip->resume_voltage_delta); - } else { - btm_config.high_thr_temp = the_chip->warm_temp_dc; - set_appropriate_battery_current(the_chip); - pm_chg_vddmax_set(the_chip, the_chip->max_voltage_mv); - pm_chg_vbatdet_set(the_chip, - the_chip->max_voltage_mv - - the_chip->resume_voltage_delta); - } - schedule_work(&btm_config_work); -} - -static int configure_btm(struct pm8921_chg_chip *chip) -{ - int rc; - - if (chip->warm_temp_dc != INT_MIN) - btm_config.btm_warm_fn = battery_warm; - else - btm_config.btm_warm_fn = NULL; - - if (chip->cool_temp_dc != INT_MIN) - btm_config.btm_cool_fn = battery_cool; - else - btm_config.btm_cool_fn = NULL; - - btm_config.low_thr_temp = chip->cool_temp_dc; - btm_config.high_thr_temp = chip->warm_temp_dc; - btm_config.interval = chip->temp_check_period; - rc = pm8xxx_adc_btm_configure(&btm_config); - if (rc) - pr_err("failed to configure btm rc = %d\n", rc); - rc = pm8xxx_adc_btm_start(); - if (rc) - pr_err("failed to start btm rc = %d\n", rc); - - return rc; +eoc_worker_stop: + wake_unlock(&chip->eoc_wake_lock); + /* set the vbatdet back, in case it was changed to trigger charging */ + set_appropriate_vbatdet(chip); } /** @@ -3413,10 +3829,9 @@ static void __devinit determine_initial_state(struct pm8921_chg_chip *chip) chip->usb_present = !!is_usb_chg_plugged_in(chip); notify_usb_of_the_plugin_event(chip->usb_present); - if (chip->usb_present) { + if (chip->usb_present || chip->dc_present) { schedule_delayed_work(&chip->unplug_check_work, - round_jiffies_relative(msecs_to_jiffies - (UNPLUG_CHECK_WAIT_PERIOD_MS))); + msecs_to_jiffies(UNPLUG_CHECK_WAIT_PERIOD_MS)); pm8921_chg_enable_irq(chip, CHG_GONE_IRQ); } @@ -3424,8 +3839,6 @@ static void __devinit determine_initial_state(struct pm8921_chg_chip *chip) pm8921_chg_enable_irq(chip, USBIN_VALID_IRQ); pm8921_chg_enable_irq(chip, BATT_REMOVED_IRQ); pm8921_chg_enable_irq(chip, BATT_INSERTED_IRQ); - pm8921_chg_enable_irq(chip, USBIN_OV_IRQ); - pm8921_chg_enable_irq(chip, USBIN_UV_IRQ); pm8921_chg_enable_irq(chip, DCIN_OV_IRQ); pm8921_chg_enable_irq(chip, DCIN_UV_IRQ); pm8921_chg_enable_irq(chip, CHGFAIL_IRQ); @@ -3462,6 +3875,12 @@ static void __devinit determine_initial_state(struct pm8921_chg_chip *chip) chip->dc_present, get_prop_batt_present(chip), fsm_state); + + /* Determine which USB trim column to use */ + if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917) + chip->usb_trim_table = usb_trim_8917_table; + else if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8038) + chip->usb_trim_table = usb_trim_8038_table; } struct pm_chg_irq_init_data { @@ -3481,14 +3900,10 @@ struct pm_chg_irq_init_data { struct pm_chg_irq_init_data chg_irq_data[] = { CHG_IRQ(USBIN_VALID_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, usbin_valid_irq_handler), - CHG_IRQ(USBIN_OV_IRQ, IRQF_TRIGGER_RISING, usbin_ov_irq_handler), CHG_IRQ(BATT_INSERTED_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, batt_inserted_irq_handler), CHG_IRQ(VBATDET_LOW_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, vbatdet_low_irq_handler), - CHG_IRQ(USBIN_UV_IRQ, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, - usbin_uv_irq_handler), - CHG_IRQ(VBAT_OV_IRQ, IRQF_TRIGGER_RISING, vbat_ov_irq_handler), CHG_IRQ(CHGWDOG_IRQ, IRQF_TRIGGER_RISING, chgwdog_irq_handler), CHG_IRQ(VCP_IRQ, IRQF_TRIGGER_RISING, vcp_irq_handler), CHG_IRQ(ATCDONE_IRQ, IRQF_TRIGGER_RISING, atcdone_irq_handler), @@ -3560,91 +3975,6 @@ err_out: return -EINVAL; } -static void pm8921_chg_force_19p2mhz_clk(struct pm8921_chg_chip *chip) -{ - int err; - u8 temp; - - temp = 0xD1; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD3; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD1; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD5; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - udelay(183); - - temp = 0xD1; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD0; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - udelay(32); - - temp = 0xD1; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD3; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } -} - -static void pm8921_chg_set_hw_clk_switching(struct pm8921_chg_chip *chip) -{ - int err; - u8 temp; - - temp = 0xD1; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } - - temp = 0xD0; - err = pm8xxx_writeb(chip->dev->parent, CHG_TEST, temp); - if (err) { - pr_err("Error %d writing %d to addr %d\n", err, temp, CHG_TEST); - return; - } -} - #define VREF_BATT_THERM_FORCE_ON BIT(7) static void detect_battery_removal(struct pm8921_chg_chip *chip) { @@ -3668,13 +3998,20 @@ static void detect_battery_removal(struct pm8921_chg_chip *chip) #define CHG_VCP_EN BIT(0) #define CHG_BAT_TEMP_DIS_BIT BIT(2) #define SAFE_CURRENT_MA 1500 +#define PM_SUB_REV 0x001 +#define MIN_CHARGE_CURRENT_MA 350 +#define DEFAULT_SAFETY_MINUTES 500 static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) { - int rc; - int vdd_safe; + u8 subrev; + int rc, vdd_safe, fcc_uah, safety_time = DEFAULT_SAFETY_MINUTES; - /* forcing 19p2mhz before accessing any charger registers */ - pm8921_chg_force_19p2mhz_clk(chip); + spin_lock_init(&lpm_lock); + rc = pm8921_apply_19p2mhz_kickstart(chip); + if (rc) { + pr_err("Failed to apply kickstart rc=%d\n", rc); + return rc; + } detect_battery_removal(chip); @@ -3712,7 +4049,11 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) chip->max_voltage_mv, rc); return rc; } - rc = pm_chg_ibatsafe_set(chip, SAFE_CURRENT_MA); + + if (chip->safe_current_ma == 0) + chip->safe_current_ma = SAFE_CURRENT_MA; + + rc = pm_chg_ibatsafe_set(chip, chip->safe_current_ma); if (rc) { pr_err("Failed to set max voltage to %d rc=%d\n", SAFE_CURRENT_MA, rc); @@ -3740,20 +4081,30 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) return rc; } - if (chip->safety_time != 0) { - rc = pm_chg_tchg_max_set(chip, chip->safety_time); - if (rc) { - pr_err("Failed to set max time to %d minutes rc=%d\n", - chip->safety_time, rc); - return rc; - } + fcc_uah = pm8921_bms_get_fcc(); + if (fcc_uah > 0) { + safety_time = div_s64((s64)fcc_uah * 60, + 1000 * MIN_CHARGE_CURRENT_MA); + /* add 20 minutes of buffer time */ + safety_time += 20; + + /* make sure we do not exceed the maximum programmable time */ + if (safety_time > PM8921_CHG_TCHG_MAX) + safety_time = PM8921_CHG_TCHG_MAX; + } + + rc = pm_chg_tchg_max_set(chip, safety_time); + if (rc) { + pr_err("Failed to set max time to %d minutes rc=%d\n", + safety_time, rc); + return rc; } if (chip->ttrkl_time != 0) { rc = pm_chg_ttrkl_max_set(chip, chip->ttrkl_time); if (rc) { pr_err("Failed to set trkl time to %d minutes rc=%d\n", - chip->safety_time, rc); + chip->ttrkl_time, rc); return rc; } } @@ -3782,7 +4133,13 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) return rc; } /* switch to a 3.2Mhz for the buck */ - rc = pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CLOCK_CTRL, 0x15); + if (pm8xxx_get_revision(chip->dev->parent) >= PM8XXX_REVISION_8038_1p0) + rc = pm_chg_write(chip, + CHG_BUCK_CLOCK_CTRL_8038, 0x15); + else + rc = pm_chg_write(chip, + CHG_BUCK_CLOCK_CTRL, 0x15); + if (rc) { pr_err("Failed to switch buck clk rc=%d\n", rc); return rc; @@ -3842,43 +4199,48 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) chip->led_src_config, rc); } - /* Workarounds for die 1.1 and 1.0 */ - if (pm8xxx_get_revision(chip->dev->parent) < PM8XXX_REVISION_8921_2p0) { - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST2, 0xF1); - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xCE); - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD8); - - /* software workaround for correct battery_id detection */ - pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_0, 0xFF); - pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_1, 0xFF); - pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_2, 0xFF); - pm8xxx_writeb(chip->dev->parent, PSI_TXRX_SAMPLE_DATA_3, 0xFF); - pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0D); - udelay(100); - pm8xxx_writeb(chip->dev->parent, PSI_CONFIG_STATUS, 0x0C); + /* Workarounds for die 3.0 */ + if (pm8xxx_get_revision(chip->dev->parent) == PM8XXX_REVISION_8921_3p0 + && pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8921) { + rc = pm8xxx_readb(chip->dev->parent, PM_SUB_REV, &subrev); + if (rc) { + pr_err("read failed: addr=%03X, rc=%d\n", + PM_SUB_REV, rc); + return rc; + } + /* Check if die 3.0.1 is present */ + if (subrev & 0x1) + pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, 0xA4); + else + pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, 0xAC); } - /* Workarounds for die 3.0 */ - if (pm8xxx_get_revision(chip->dev->parent) == PM8XXX_REVISION_8921_3p0) - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xAC); - - /* Enable isub_fine resolution AICL for PM8917 */ if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8917) { + /* Set PM8917 USB_OVP debounce time to 15 ms */ + rc = pm_chg_masked_write(chip, USB_OVP_CONTROL, + OVP_DEBOUNCE_TIME, 0x6); + if (rc) { + pr_err("Failed to set USB OVP db rc=%d\n", rc); + return rc; + } + + /* Enable isub_fine resolution AICL for PM8917 */ chip->iusb_fine_res = true; - if (chip->uvd_voltage_mv) + if (chip->uvd_voltage_mv) { rc = pm_chg_uvd_threshold_set(chip, chip->uvd_voltage_mv); if (rc) { pr_err("Failed to set UVD threshold %drc=%d\n", chip->uvd_voltage_mv, rc); - return rc; + return rc; + } } } - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0xD9); + pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, 0xD9); /* Disable EOC FSM processing */ - pm8xxx_writeb(chip->dev->parent, CHG_BUCK_CTRL_TEST3, 0x91); + pm_chg_write(chip, CHG_BUCK_CTRL_TEST3, 0x91); rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, VREF_BATT_THERM_FORCE_ON); @@ -3897,6 +4259,45 @@ static int __devinit pm8921_chg_hw_init(struct pm8921_chg_chip *chip) return rc; } + if (pm8xxx_get_version(chip->dev->parent) == PM8XXX_VERSION_8921) { + /* Clear kickstart */ + rc = pm8xxx_writeb(chip->dev->parent, CHG_TEST, 0xD0); + if (rc) { + pr_err("Failed to clear kickstart rc=%d\n", rc); + return rc; + } + + /* From here the lpm_workaround will be active */ + chip->lockup_lpm_wrkarnd = true; + + /* Enable LPM */ + pm8921_chg_set_lpm(chip, 1); + } + + if (chip->lockup_lpm_wrkarnd) { + chip->vreg_xoadc = regulator_get(chip->dev, "vreg_xoadc"); + if (IS_ERR(chip->vreg_xoadc)) + return -ENODEV; + + rc = regulator_set_optimum_mode(chip->vreg_xoadc, 10000); + if (rc < 0) { + pr_err("Failed to set configure HPM rc=%d\n", rc); + return rc; + } + + rc = regulator_set_voltage(chip->vreg_xoadc, 1800000, 1800000); + if (rc) { + pr_err("Failed to set L14 voltage rc=%d\n", rc); + return rc; + } + + rc = regulator_enable(chip->vreg_xoadc); + if (rc) { + pr_err("Failed to enable L14 rc=%d\n", rc); + return rc; + } + } + return 0; } @@ -3959,9 +4360,9 @@ static int set_reg(void *data, u64 val) u8 temp; temp = (u8) val; - ret = pm8xxx_writeb(the_chip->dev->parent, addr, temp); + ret = pm_chg_write(the_chip, addr, temp); if (ret) { - pr_err("pm8xxx_writeb to %x value =%d errored = %d\n", + pr_err("pm_chg_write to %x value =%d errored = %d\n", addr, temp, ret); return -EAGAIN; } @@ -3969,6 +4370,81 @@ static int set_reg(void *data, u64 val) } DEFINE_SIMPLE_ATTRIBUTE(reg_fops, get_reg, set_reg, "0x%02llx\n"); +static int reg_loop; +#define MAX_REG_LOOP_CHAR 10 +static int get_reg_loop_param(char *buf, struct kernel_param *kp) +{ + u8 temp; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + temp = pm_chg_get_regulation_loop(the_chip); + return snprintf(buf, MAX_REG_LOOP_CHAR, "%d", temp); +} +module_param_call(reg_loop, NULL, get_reg_loop_param, + ®_loop, 0644); + +static int max_chg_ma; +#define MAX_MA_CHAR 10 +static int get_max_chg_ma_param(char *buf, struct kernel_param *kp) +{ + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + return snprintf(buf, MAX_MA_CHAR, "%d", the_chip->max_bat_chg_current); +} +module_param_call(max_chg_ma, NULL, get_max_chg_ma_param, + &max_chg_ma, 0644); +static int ibatmax_ma; +static int set_ibat_max(const char *val, struct kernel_param *kp) +{ + int rc; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + rc = param_set_int(val, kp); + if (rc) { + pr_err("error setting value %d\n", rc); + return rc; + } + + if (abs(ibatmax_ma - the_chip->max_bat_chg_current) + <= the_chip->ibatmax_max_adj_ma) { + rc = pm_chg_ibatmax_set(the_chip, ibatmax_ma); + if (rc) { + pr_err("Failed to set ibatmax rc = %d\n", rc); + return rc; + } + } + + return 0; +} +static int get_ibat_max(char *buf, struct kernel_param *kp) +{ + int ibat_ma; + int rc; + + if (!the_chip) { + pr_err("called before init\n"); + return -EINVAL; + } + + rc = pm_chg_ibatmax_get(the_chip, &ibat_ma); + if (rc) { + pr_err("ibatmax_get error = %d\n", rc); + return rc; + } + + return snprintf(buf, MAX_MA_CHAR, "%d", ibat_ma); +} +module_param_call(ibatmax_ma, set_ibat_max, get_ibat_max, + &ibatmax_ma, 0644); enum { BAT_WARM_ZONE, BAT_COOL_ZONE, @@ -4072,11 +4548,19 @@ static int pm8921_charger_suspend_noirq(struct device *dev) int rc; struct pm8921_chg_chip *chip = dev_get_drvdata(dev); + if (chip->lockup_lpm_wrkarnd) { + rc = regulator_disable(chip->vreg_xoadc); + if (rc) + pr_err("Failed to disable L14 rc=%d\n", rc); + + rc = pm8921_apply_19p2mhz_kickstart(chip); + if (rc) + pr_err("Failed to apply kickstart rc=%d\n", rc); + } + rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, 0); if (rc) pr_err("Failed to Force Vref therm off rc=%d\n", rc); - if (!(chip->disable_hw_clock_switching)) - pm8921_chg_set_hw_clk_switching(chip); return 0; } @@ -4085,7 +4569,15 @@ static int pm8921_charger_resume_noirq(struct device *dev) int rc; struct pm8921_chg_chip *chip = dev_get_drvdata(dev); - pm8921_chg_force_19p2mhz_clk(chip); + if (chip->lockup_lpm_wrkarnd) { + rc = regulator_enable(chip->vreg_xoadc); + if (rc) + pr_err("Failed to enable L14 rc=%d\n", rc); + + rc = pm8921_apply_19p2mhz_kickstart(chip); + if (rc) + pr_err("Failed to apply kickstart rc=%d\n", rc); + } rc = pm_chg_masked_write(chip, CHG_CNTRL, VREF_BATT_THERM_FORCE_ON, VREF_BATT_THERM_FORCE_ON); @@ -4096,37 +4588,26 @@ static int pm8921_charger_resume_noirq(struct device *dev) static int pm8921_charger_resume(struct device *dev) { - int rc; struct pm8921_chg_chip *chip = dev_get_drvdata(dev); - if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN) - && !(chip->keep_btm_on_suspend)) { - rc = pm8xxx_adc_btm_configure(&btm_config); - if (rc) - pr_err("couldn't reconfigure btm rc=%d\n", rc); - - rc = pm8xxx_adc_btm_start(); - if (rc) - pr_err("couldn't restart btm rc=%d\n", rc); - } if (pm8921_chg_is_enabled(chip, LOOP_CHANGE_IRQ)) { disable_irq_wake(chip->pmic_chg_irq[LOOP_CHANGE_IRQ]); pm8921_chg_disable_irq(chip, LOOP_CHANGE_IRQ); } + + if (chip->btc_override && (is_dc_chg_plugged_in(the_chip) || + is_usb_chg_plugged_in(the_chip))) + schedule_delayed_work(&chip->btc_override_work, 0); + return 0; } static int pm8921_charger_suspend(struct device *dev) { - int rc; struct pm8921_chg_chip *chip = dev_get_drvdata(dev); - if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN) - && !(chip->keep_btm_on_suspend)) { - rc = pm8xxx_adc_btm_end(); - if (rc) - pr_err("Failed to disable BTM on suspend rc=%d\n", rc); - } + if (chip->btc_override) + cancel_delayed_work_sync(&chip->btc_override_work); if (is_usb_chg_plugged_in(chip)) { pm8921_chg_enable_irq(chip, LOOP_CHANGE_IRQ); @@ -4155,14 +4636,16 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) } chip->dev = &pdev->dev; - chip->safety_time = pdata->safety_time; chip->ttrkl_time = pdata->ttrkl_time; chip->update_time = pdata->update_time; chip->max_voltage_mv = pdata->max_voltage; - chip->alarm_voltage_mv = pdata->alarm_voltage; + chip->alarm_low_mv = pdata->alarm_low_mv; + chip->alarm_high_mv = pdata->alarm_high_mv; chip->min_voltage_mv = pdata->min_voltage; + chip->safe_current_ma = pdata->safe_current_ma; chip->uvd_voltage_mv = pdata->uvd_thresh_voltage; chip->resume_voltage_delta = pdata->resume_voltage_delta; + chip->resume_charge_percent = pdata->resume_charge_percent; chip->term_current = pdata->term_current; chip->vbat_channel = pdata->charger_cdata.vbat_channel; chip->batt_temp_channel = pdata->charger_cdata.batt_temp_channel; @@ -4180,13 +4663,13 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) chip->warm_temp_dc = INT_MIN; chip->temp_check_period = pdata->temp_check_period; - chip->dc_unplug_check = pdata->dc_unplug_check; chip->max_bat_chg_current = pdata->max_bat_chg_current; + /* Assign to corresponding module parameter */ + usb_max_current = pdata->usb_max_current; chip->cool_bat_chg_current = pdata->cool_bat_chg_current; chip->warm_bat_chg_current = pdata->warm_bat_chg_current; chip->cool_bat_voltage = pdata->cool_bat_voltage; chip->warm_bat_voltage = pdata->warm_bat_voltage; - chip->keep_btm_on_suspend = pdata->keep_btm_on_suspend; chip->trkl_voltage = pdata->trkl_voltage; chip->weak_voltage = pdata->weak_voltage; chip->trkl_current = pdata->trkl_current; @@ -4200,6 +4683,23 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) chip->rconn_mohm = pdata->rconn_mohm; chip->led_src_config = pdata->led_src_config; chip->has_dc_supply = pdata->has_dc_supply; + chip->battery_less_hardware = pdata->battery_less_hardware; + chip->btc_override = pdata->btc_override; + if (chip->btc_override) { + chip->btc_delay_ms = pdata->btc_delay_ms; + chip->btc_override_cold_decidegc + = pdata->btc_override_cold_degc * 10; + chip->btc_override_hot_decidegc + = pdata->btc_override_hot_degc * 10; + chip->btc_panic_if_cant_stop_chg + = pdata->btc_panic_if_cant_stop_chg; + } + + if (chip->battery_less_hardware) + charging_disabled = 1; + + chip->ibatmax_max_adj_ma = find_ibat_max_adj_ma( + chip->max_bat_chg_current); rc = pm8921_chg_hw_init(chip); if (rc) { @@ -4207,6 +4707,11 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) goto free_chip; } + if (chip->btc_override) + pm8921_chg_btc_override_init(chip); + + chip->stop_chg_upon_expiry = pdata->stop_chg_upon_expiry; + chip->usb_psy.name = "usb", chip->usb_psy.type = POWER_SUPPLY_TYPE_USB, chip->usb_psy.supplied_to = pm_power_supplied_to, @@ -4251,12 +4756,21 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) platform_set_drvdata(pdev, chip); the_chip = chip; + /* set initial state of the USB charger type to UNKNOWN */ + power_supply_set_supply_type(&chip->usb_psy, POWER_SUPPLY_TYPE_UNKNOWN); + wake_lock_init(&chip->eoc_wake_lock, WAKE_LOCK_SUSPEND, "pm8921_eoc"); INIT_DELAYED_WORK(&chip->eoc_work, eoc_worker); INIT_DELAYED_WORK(&chip->vin_collapse_check_work, vin_collapse_check_worker); INIT_DELAYED_WORK(&chip->unplug_check_work, unplug_check_worker); + INIT_WORK(&chip->bms_notify.work, bms_notify); + INIT_WORK(&chip->battery_id_valid_work, battery_id_valid); + + INIT_DELAYED_WORK(&chip->update_heartbeat_work, update_heartbeat); + INIT_DELAYED_WORK(&chip->btc_override_work, btc_override_worker); + rc = request_irqs(chip, pdev); if (rc) { pr_err("couldn't register interrupts rc=%d\n", rc); @@ -4264,54 +4778,23 @@ static int __devinit pm8921_charger_probe(struct platform_device *pdev) } enable_irq_wake(chip->pmic_chg_irq[USBIN_VALID_IRQ]); - enable_irq_wake(chip->pmic_chg_irq[USBIN_OV_IRQ]); - enable_irq_wake(chip->pmic_chg_irq[USBIN_UV_IRQ]); - enable_irq_wake(chip->pmic_chg_irq[BAT_TEMP_OK_IRQ]); + enable_irq_wake(chip->pmic_chg_irq[DCIN_VALID_IRQ]); enable_irq_wake(chip->pmic_chg_irq[VBATDET_LOW_IRQ]); enable_irq_wake(chip->pmic_chg_irq[FASTCHG_IRQ]); - /* - * if both the cool_temp_dc and warm_temp_dc are invalid device doesnt - * care for jeita compliance - */ - if (!(chip->cool_temp_dc == INT_MIN && chip->warm_temp_dc == INT_MIN)) { - rc = configure_btm(chip); - if (rc) { - pr_err("couldn't register with btm rc=%d\n", rc); - goto free_irq; - } - } - rc = pm8921_charger_configure_batt_alarm(chip); - if (rc) { - pr_err("Couldn't configure battery alarm! rc=%d\n", rc); - goto free_irq; - } - - rc = pm8921_charger_enable_batt_alarm(chip); - if (rc) { - pr_err("Couldn't enable battery alarm! rc=%d\n", rc); - goto free_irq; - } create_debugfs_entries(chip); - INIT_WORK(&chip->bms_notify.work, bms_notify); - INIT_WORK(&chip->battery_id_valid_work, battery_id_valid); - /* determine what state the charger is in */ determine_initial_state(chip); - if (chip->update_time) { - INIT_DELAYED_WORK(&chip->update_heartbeat_work, - update_heartbeat); + if (chip->update_time) schedule_delayed_work(&chip->update_heartbeat_work, round_jiffies_relative(msecs_to_jiffies (chip->update_time))); - } return 0; -free_irq: - free_irqs(chip); unregister_batt: + wake_lock_destroy(&chip->eoc_wake_lock); power_supply_unregister(&chip->batt_psy); unregister_dc: power_supply_unregister(&chip->dc_psy); @@ -4326,6 +4809,7 @@ static int __devexit pm8921_charger_remove(struct platform_device *pdev) { struct pm8921_chg_chip *chip = platform_get_drvdata(pdev); + regulator_put(chip->vreg_xoadc); free_irqs(chip); platform_set_drvdata(pdev, NULL); the_chip = NULL; diff --git a/drivers/power/pm8xxx-ccadc.c b/drivers/power/pm8xxx-ccadc.c index e48257acbcb..7e37daa4ed1 100644 --- a/drivers/power/pm8xxx-ccadc.c +++ b/drivers/power/pm8xxx-ccadc.c @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011-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 @@ -17,12 +17,15 @@ #include #include #include +#include #include #include +#include #include #include #include #include +#include #define CCADC_ANA_PARAM 0x240 #define CCADC_DIG_PARAM 0x241 @@ -67,13 +70,17 @@ struct pm8xxx_ccadc_chip { struct device *dev; struct dentry *dent; + unsigned int batt_temp_channel; u16 ccadc_offset; int ccadc_gain_uv; unsigned int revision; unsigned int calib_delay_ms; + unsigned long last_calib_time; + int last_calib_temp; int eoc_irq; - int r_sense; + int r_sense_uohm; struct delayed_work calib_ccadc_work; + struct mutex calib_mutex; }; static struct pm8xxx_ccadc_chip *the_chip; @@ -312,6 +319,54 @@ static int calib_ccadc_program_trim(struct pm8xxx_ccadc_chip *chip, return 0; } +static int get_batt_temp(struct pm8xxx_ccadc_chip *chip, int *batt_temp) +{ + int rc; + struct pm8xxx_adc_chan_result result; + + rc = pm8xxx_adc_read(chip->batt_temp_channel, &result); + if (rc) { + pr_err("error reading batt_temp_channel = %d, rc = %d\n", + chip->batt_temp_channel, rc); + return rc; + } + *batt_temp = result.physical; + pr_debug("batt_temp phy = %lld meas = 0x%llx\n", result.physical, + result.measurement); + return 0; +} + +static int get_current_time(unsigned long *now_tm_sec) +{ + struct rtc_time tm; + struct rtc_device *rtc; + int rc; + + rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (rtc == NULL) { + pr_err("%s: unable to open rtc device (%s)\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); + return -EINVAL; + } + + rc = rtc_read_time(rtc, &tm); + if (rc) { + pr_err("Error reading rtc device (%s) : %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + return rc; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + return rc; + } + rtc_tm_to_time(&tm, now_tm_sec); + + return 0; +} + void pm8xxx_calib_ccadc(void) { u8 data_msb, data_lsb, sec_cntrl; @@ -324,11 +379,12 @@ void pm8xxx_calib_ccadc(void) return; } + mutex_lock(&the_chip->calib_mutex); rc = pm8xxx_readb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, &sec_cntrl); if (rc < 0) { pr_err("error = %d reading ADC_ARB_SECP_CNTRL\n", rc); - return; + goto calibration_unlock; } rc = calib_ccadc_enable_arbiter(the_chip); @@ -460,6 +516,8 @@ void pm8xxx_calib_ccadc(void) pr_debug("error = %d programming gain trim\n", rc); bail: pm8xxx_writeb(the_chip->dev->parent, ADC_ARB_SECP_CNTRL, sec_cntrl); +calibration_unlock: + mutex_unlock(&the_chip->calib_mutex); } EXPORT_SYMBOL(pm8xxx_calib_ccadc); @@ -480,6 +538,9 @@ static irqreturn_t pm8921_bms_ccadc_eoc_handler(int irq, void *data) struct pm8xxx_ccadc_chip *chip = data; int rc; + if (!the_chip) + goto out; + pr_debug("irq = %d triggered\n", irq); data_msb = chip->ccadc_offset >> 8; data_lsb = chip->ccadc_offset; @@ -488,6 +549,7 @@ static irqreturn_t pm8921_bms_ccadc_eoc_handler(int irq, void *data) data_msb, data_lsb, 0); disable_irq_nosync(chip->eoc_irq); +out: return IRQ_HANDLED; } @@ -558,7 +620,8 @@ int pm8xxx_ccadc_get_battery_current(int *bat_current_ua) return rc; } - *bat_current_ua = voltage_uv * 1000/the_chip->r_sense; + *bat_current_ua = div_s64((s64)voltage_uv * 1000000LL, + the_chip->r_sense_uohm); /* * ccadc reads +ve current when the battery is charging * We need to return -ve if the battery is charging @@ -671,12 +734,15 @@ static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev) chip->dev = &pdev->dev; chip->revision = pm8xxx_get_revision(chip->dev->parent); chip->eoc_irq = res->start; - chip->r_sense = pdata->r_sense; + chip->r_sense_uohm = pdata->r_sense_uohm; chip->calib_delay_ms = pdata->calib_delay_ms; + chip->batt_temp_channel = pdata->ccadc_cdata.batt_temp_channel; + mutex_init(&chip->calib_mutex); calib_ccadc_read_offset_and_gain(chip, &chip->ccadc_gain_uv, &chip->ccadc_offset); + irq_set_status_flags(chip->eoc_irq, IRQ_NOAUTOEN); rc = request_irq(chip->eoc_irq, pm8921_bms_ccadc_eoc_handler, IRQF_TRIGGER_RISING, "bms_eoc_ccadc", chip); @@ -685,9 +751,6 @@ static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev) goto free_chip; } - - disable_irq_nosync(chip->eoc_irq); - platform_set_drvdata(pdev, chip); the_chip = chip; INIT_DELAYED_WORK(&chip->calib_ccadc_work, calibrate_ccadc_work); @@ -698,6 +761,7 @@ static int __devinit pm8xxx_ccadc_probe(struct platform_device *pdev) return 0; free_chip: + mutex_destroy(&chip->calib_mutex); kfree(chip); return rc; } @@ -712,12 +776,50 @@ static int __devexit pm8xxx_ccadc_remove(struct platform_device *pdev) return 0; } +#define CCADC_CALIB_TEMP_THRESH 20 +static int pm8xxx_ccadc_resume(struct device *dev) +{ + int rc, batt_temp, delta_temp; + unsigned long current_time_sec; + unsigned long time_since_last_calib; + + rc = get_batt_temp(the_chip, &batt_temp); + if (rc) { + pr_err("unable to get batt_temp: %d\n", rc); + return 0; + } + rc = get_current_time(¤t_time_sec); + if (rc) { + pr_err("unable to get current time: %d\n", rc); + return 0; + } + if (current_time_sec > the_chip->last_calib_time) { + time_since_last_calib = current_time_sec - + the_chip->last_calib_time; + delta_temp = abs(batt_temp - the_chip->last_calib_temp); + pr_debug("time since last calib: %lu, delta_temp = %d\n", + time_since_last_calib, delta_temp); + if (time_since_last_calib >= the_chip->calib_delay_ms/1000 + || delta_temp > CCADC_CALIB_TEMP_THRESH) { + the_chip->last_calib_time = current_time_sec; + the_chip->last_calib_temp = batt_temp; + pm8xxx_calib_ccadc(); + } + } + return 0; +} + +static const struct dev_pm_ops pm8xxx_ccadc_pm_ops = { + .resume = pm8xxx_ccadc_resume, +}; + static struct platform_driver pm8xxx_ccadc_driver = { .probe = pm8xxx_ccadc_probe, .remove = __devexit_p(pm8xxx_ccadc_remove), .driver = { .name = PM8XXX_CCADC_DEV_NAME, .owner = THIS_MODULE, + .pm = &pm8xxx_ccadc_pm_ops, }, }; diff --git a/drivers/usb/otg/msm_otg.c b/drivers/usb/otg/msm_otg.c index 73ed93d4946..ada56e38312 100644 --- a/drivers/usb/otg/msm_otg.c +++ b/drivers/usb/otg/msm_otg.c @@ -1079,7 +1079,7 @@ static int msm_otg_notify_chg_type(struct msm_otg *motg) motg->chg_type == USB_ACA_C_CHARGER)) charger_type = POWER_SUPPLY_TYPE_USB_ACA; else - charger_type = POWER_SUPPLY_TYPE_BATTERY; + charger_type = POWER_SUPPLY_TYPE_UNKNOWN; return pm8921_set_usb_power_supply_type(charger_type); } diff --git a/include/linux/mfd/pm8xxx/batterydata-lib.h b/include/linux/mfd/pm8xxx/batterydata-lib.h new file mode 100644 index 00000000000..c55e47e0ce9 --- /dev/null +++ b/include/linux/mfd/pm8xxx/batterydata-lib.h @@ -0,0 +1,155 @@ +/* Copyright (c) 2012, 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. + */ + +#ifndef __PM8XXX_BMS_BATTERYDATA_H +#define __PM8XXX_BMS_BATTERYDATA_H + +#include + +#define FCC_CC_COLS 5 +#define FCC_TEMP_COLS 8 + +#define PC_CC_ROWS 29 +#define PC_CC_COLS 13 + +#define PC_TEMP_ROWS 29 +#define PC_TEMP_COLS 8 + +#define MAX_SINGLE_LUT_COLS 20 + +struct single_row_lut { + int x[MAX_SINGLE_LUT_COLS]; + int y[MAX_SINGLE_LUT_COLS]; + int cols; +}; + +/** + * struct sf_lut - + * @rows: number of percent charge entries should be <= PC_CC_ROWS + * @cols: number of charge cycle entries should be <= PC_CC_COLS + * @row_entries: the charge cycles/temperature at which sf data + * is available in the table. + * The charge cycles must be in increasing order from 0 to rows. + * @percent: the percent charge at which sf data is available in the table + * The percentcharge must be in decreasing order from 0 to cols. + * @sf: the scaling factor data + */ +struct sf_lut { + int rows; + int cols; + int row_entries[PC_CC_COLS]; + int percent[PC_CC_ROWS]; + int sf[PC_CC_ROWS][PC_CC_COLS]; +}; + +/** + * struct pc_temp_ocv_lut - + * @rows: number of percent charge entries should be <= PC_TEMP_ROWS + * @cols: number of temperature entries should be <= PC_TEMP_COLS + * @temp: the temperatures at which ocv data is available in the table + * The temperatures must be in increasing order from 0 to rows. + * @percent: the percent charge at which ocv data is available in the table + * The percentcharge must be in decreasing order from 0 to cols. + * @ocv: the open circuit voltage + */ +struct pc_temp_ocv_lut { + int rows; + int cols; + int temp[PC_TEMP_COLS]; + int percent[PC_TEMP_ROWS]; + int ocv[PC_TEMP_ROWS][PC_TEMP_COLS]; +}; + +enum battery_type { + BATT_UNKNOWN = 0, + BATT_PALLADIUM, + BATT_DESAY, +}; + +/** + * struct bms_battery_data - + * @fcc: full charge capacity (mAmpHour) + * @fcc_temp_lut: table to get fcc at a given temp + * @pc_temp_ocv_lut: table to get percent charge given batt temp and cycles + * @pc_sf_lut: table to get percent charge scaling factor given cycles + * and percent charge + * @rbatt_sf_lut: table to get battery resistance scaling factor given + * temperature and percent charge + * @default_rbatt_mohm: the default value of battery resistance to use when + * readings from bms are not available. + * @delta_rbatt_mohm: the resistance to be added towards lower soc to + * compensate for battery capacitance. + */ + +struct bms_battery_data { + unsigned int fcc; + struct single_row_lut *fcc_temp_lut; + struct single_row_lut *fcc_sf_lut; + struct pc_temp_ocv_lut *pc_temp_ocv_lut; + struct sf_lut *pc_sf_lut; + struct sf_lut *rbatt_sf_lut; + int default_rbatt_mohm; + int delta_rbatt_mohm; +}; + +#if defined(CONFIG_PM8921_BMS) || \ + defined(CONFIG_PM8921_BMS_MODULE) +extern struct bms_battery_data palladium_1500_data; +extern struct bms_battery_data desay_5200_data; + +int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp); +int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc); +int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut, + int cycles); +int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int ocv); +int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int pc); +int linear_interpolate(int y0, int x0, int y1, int x1, int x); +int is_between(int left, int right, int value); +#else +static inline int interpolate_fcc(struct single_row_lut *fcc_temp_lut, + int batt_temp) +{ + return -EINVAL; +} +static inline int interpolate_scalingfactor(struct sf_lut *sf_lut, + int row_entry, int pc) +{ + return -EINVAL; +} +static inline int interpolate_scalingfactor_fcc( + struct single_row_lut *fcc_sf_lut, int cycles) +{ + return -EINVAL; +} +static inline int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int ocv) +{ + return -EINVAL; +} +static inline int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp_degc, int pc) +{ + return -EINVAL; +} +static inline int linear_interpolate(int y0, int x0, int y1, int x1, int x) +{ + return -EINVAL; +} +static inline int is_between(int left, int right, int value) +{ + return -EINVAL; +} +#endif + +#endif diff --git a/include/linux/mfd/pm8xxx/ccadc.h b/include/linux/mfd/pm8xxx/ccadc.h index 29f7a62161c..e4d5a67f51c 100644 --- a/include/linux/mfd/pm8xxx/ccadc.h +++ b/include/linux/mfd/pm8xxx/ccadc.h @@ -17,14 +17,20 @@ #define PM8XXX_CCADC_DEV_NAME "pm8xxx-ccadc" +struct pm8xxx_ccadc_core_data { + unsigned int batt_temp_channel; +}; + /** * struct pm8xxx_ccadc_platform_data - - * @r_sense: sense resistor value in (mOhms) + * @ccadc_cdata: core data for the ccadc driver containing channel info + * @r_sense_uohm: sense resistor value in (micro Ohms) * @calib_delay_ms: how often should the adc calculate gain and offset */ struct pm8xxx_ccadc_platform_data { - int r_sense; - unsigned int calib_delay_ms; + struct pm8xxx_ccadc_core_data ccadc_cdata; + int r_sense_uohm; + unsigned int calib_delay_ms; }; #define CCADC_READING_RESOLUTION_N 542535 diff --git a/include/linux/mfd/pm8xxx/core.h b/include/linux/mfd/pm8xxx/core.h index 08e9014c7ab..38c589d80ed 100644 --- a/include/linux/mfd/pm8xxx/core.h +++ b/include/linux/mfd/pm8xxx/core.h @@ -75,6 +75,27 @@ enum pm8xxx_version { #define PM8XXX_REVISION_8917_TEST 0 #define PM8XXX_REVISION_8917_1p0 1 +#define PM8XXX_RESTART_UNKNOWN 0 +#define PM8XXX_RESTART_CBL 1 +#define PM8XXX_RESTART_KPD 2 +#define PM8XXX_RESTART_CHG 3 +#define PM8XXX_RESTART_SMPL 4 +#define PM8XXX_RESTART_RTC 5 +#define PM8XXX_RESTART_HARD_RESET 6 +#define PM8XXX_RESTART_GEN_PURPOSE 7 +#define PM8XXX_RESTART_REASON_MASK 0x07 + +static const char * const pm8xxx_restart_reason_str[] = { + [0] = "Unknown", + [1] = "Triggered from CBL (external charger)", + [2] = "Triggered from KPD (power key press)", + [3] = "Triggered from CHG (usb charger insertion)", + [4] = "Triggered from SMPL (sudden momentary power loss)", + [5] = "Triggered from RTC (real time clock)", + [6] = "Triggered by Hard Reset", + [7] = "Triggered by General Purpose Trigger", +}; + struct pm8xxx_drvdata { int (*pmic_readb) (const struct device *dev, u16 addr, u8 *val); @@ -88,6 +109,8 @@ struct pm8xxx_drvdata { int irq); enum pm8xxx_version (*pmic_get_version) (const struct device *dev); int (*pmic_get_revision) (const struct device *dev); + u8 (*pmic_restart_reason) + (const struct device *dev); void *pm_chip_data; }; @@ -156,4 +179,12 @@ static inline int pm8xxx_get_revision(const struct device *dev) return dd->pmic_get_revision(dev); } +static inline u8 pm8xxx_restart_reason(const struct device *dev) +{ + struct pm8xxx_drvdata *dd = dev_get_drvdata(dev); + + if (!dd) + return -EINVAL; + return dd->pmic_restart_reason(dev); +} #endif diff --git a/include/linux/mfd/pm8xxx/pm8921-bms.h b/include/linux/mfd/pm8xxx/pm8921-bms.h index a73a2849b6d..8ab3ac2578e 100644 --- a/include/linux/mfd/pm8xxx/pm8921-bms.h +++ b/include/linux/mfd/pm8xxx/pm8921-bms.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2011-2012, Code Aurora Forum. All rights reserved. +/* Copyright (c) 2011-2013, Code Aurora Forum. 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 @@ -14,87 +14,10 @@ #define __PM8XXX_BMS_H #include +#include #define PM8921_BMS_DEV_NAME "pm8921-bms" -#define FCC_CC_COLS 5 -#define FCC_TEMP_COLS 8 - -#define PC_CC_ROWS 29 -#define PC_CC_COLS 13 - -#define PC_TEMP_ROWS 29 -#define PC_TEMP_COLS 8 - -#define MAX_SINGLE_LUT_COLS 20 - -struct single_row_lut { - int x[MAX_SINGLE_LUT_COLS]; - int y[MAX_SINGLE_LUT_COLS]; - int cols; -}; - -/** - * struct sf_lut - - * @rows: number of percent charge entries should be <= PC_CC_ROWS - * @cols: number of charge cycle entries should be <= PC_CC_COLS - * @row_entries: the charge cycles/temperature at which sf data - * is available in the table. - * The charge cycles must be in increasing order from 0 to rows. - * @percent: the percent charge at which sf data is available in the table - * The percentcharge must be in decreasing order from 0 to cols. - * @sf: the scaling factor data - */ -struct sf_lut { - int rows; - int cols; - int row_entries[PC_CC_COLS]; - int percent[PC_CC_ROWS]; - int sf[PC_CC_ROWS][PC_CC_COLS]; -}; - -/** - * struct pc_temp_ocv_lut - - * @rows: number of percent charge entries should be <= PC_TEMP_ROWS - * @cols: number of temperature entries should be <= PC_TEMP_COLS - * @temp: the temperatures at which ocv data is available in the table - * The temperatures must be in increasing order from 0 to rows. - * @percent: the percent charge at which ocv data is available in the table - * The percentcharge must be in decreasing order from 0 to cols. - * @ocv: the open circuit voltage - */ -struct pc_temp_ocv_lut { - int rows; - int cols; - int temp[PC_TEMP_COLS]; - int percent[PC_TEMP_ROWS]; - int ocv[PC_TEMP_ROWS][PC_TEMP_COLS]; -}; - -/** - * struct pm8921_bms_battery_data - - * @fcc: full charge capacity (mAmpHour) - * @fcc_temp_lut: table to get fcc at a given temp - * @pc_temp_ocv_lut: table to get percent charge given batt temp and cycles - * @pc_sf_lut: table to get percent charge scaling factor given cycles - * and percent charge - * @rbatt_sf_lut: table to get battery resistance scaling factor given - * temperature and percent charge - * @default_rbatt_mohm: the default value of battery resistance to use when - * readings from bms are not available. - * @delta_rbatt_mohm: the resistance to be added towards lower soc to - * compensate for battery capacitance. - */ -struct pm8921_bms_battery_data { - unsigned int fcc; - struct single_row_lut *fcc_temp_lut; - struct single_row_lut *fcc_sf_lut; - struct pc_temp_ocv_lut *pc_temp_ocv_lut; - struct sf_lut *pc_sf_lut; - struct sf_lut *rbatt_sf_lut; - int default_rbatt_mohm; - int delta_rbatt_mohm; -}; struct pm8xxx_bms_core_data { unsigned int batt_temp_channel; @@ -104,27 +27,25 @@ struct pm8xxx_bms_core_data { unsigned int batt_id_channel; }; -enum battery_type { - BATT_UNKNOWN = 0, - BATT_PALLADIUM, - BATT_DESAY, -}; - /** * struct pm8921_bms_platform_data - * @batt_type: allows to force chose battery calibration data - * @r_sense: sense resistor value in (mOhms) + * @r_sense_uohm: sense resistor value in (micro Ohms) * @i_test: current at which the unusable charger cutoff is to be * calculated or the peak system current (mA) * @v_cutoff: the loaded voltage at which the battery * is considered empty(mV) * @enable_fcc_learning: if set the driver will learn full charge * capacity of the battery upon end of charge + * @normal_voltage_calc_ms: The period of soc calculation in ms when battery + * voltage higher than cutoff voltage + * @low_voltage_calc_ms: The period of soc calculation in ms when battery + * voltage is near cutoff voltage */ struct pm8921_bms_platform_data { struct pm8xxx_bms_core_data bms_cdata; enum battery_type battery_type; - unsigned int r_sense; + int r_sense_uohm; unsigned int i_test; unsigned int v_cutoff; unsigned int max_voltage_uv; @@ -134,11 +55,11 @@ struct pm8921_bms_platform_data { int ignore_shutdown_soc; int adjust_soc_low_threshold; int chg_term_ua; + int normal_voltage_calc_ms; + int low_voltage_calc_ms; }; #if defined(CONFIG_PM8921_BMS) || defined(CONFIG_PM8921_BMS_MODULE) -extern struct pm8921_bms_battery_data palladium_1500_data; -extern struct pm8921_bms_battery_data desay_5200_data; /** * pm8921_bms_get_vsense_avg - return the voltage across the sense * resitor in microvolts @@ -202,9 +123,11 @@ void pm8921_bms_calibrate_hkadc(void); int pm8921_bms_get_simultaneous_battery_voltage_and_current(int *ibat_ua, int *vbat_uv); /** - * pm8921_bms_get_rbatt - function to get the battery resistance in mOhm. + * pm8921_bms_get_current_max + * - function to get the max current that can be drawn from + * the battery before it dips below the min allowed voltage */ -int pm8921_bms_get_rbatt(void); +int pm8921_bms_get_current_max(void); /** * pm8921_bms_invalidate_shutdown_soc - function to notify the bms driver that * the battery was replaced between reboot @@ -212,6 +135,15 @@ int pm8921_bms_get_rbatt(void); * soc stored in a coincell backed register */ void pm8921_bms_invalidate_shutdown_soc(void); + +/** + * pm8921_bms_cc_uah - function to get the coulomb counter based charge. Note + * that the coulomb counter are reset when the current + * consumption is low (below 8mA for more than 5 minutes), + * This will lead in a very low coulomb counter charge + * value upon wakeup from sleep. + */ +int pm8921_bms_cc_uah(int *cc_uah); #else static inline int pm8921_bms_get_vsense_avg(int *result) { @@ -250,6 +182,14 @@ static inline int pm8921_bms_get_rbatt(void) static inline void pm8921_bms_invalidate_shutdown_soc(void) { } +static inline int pm8921_bms_cc_uah(int *cc_uah) +{ + return -ENXIO; +} +static inline int pm8921_bms_get_current_max(void) +{ + return -ENXIO; +} #endif #endif diff --git a/include/linux/mfd/pm8xxx/pm8921-charger.h b/include/linux/mfd/pm8xxx/pm8921-charger.h index 7dbe98cd2a5..e5065ea8cf7 100644 --- a/include/linux/mfd/pm8xxx/pm8921-charger.h +++ b/include/linux/mfd/pm8xxx/pm8921-charger.h @@ -58,19 +58,25 @@ enum pm8921_chg_led_src_config { /** * struct pm8921_charger_platform_data - - * @safety_time: max charging time in minutes incl. fast and trkl * valid range 4 to 512 min. PON default 120 min * @ttrkl_time: max trckl charging time in minutes * valid range 1 to 64 mins. PON default 15 min * @update_time: how often the userland be updated of the charging (msec) - * @alarm_voltage: the voltage (mV) when lower battery alarm is triggered + * @alarm_low_mv: the voltage (mV) when low battery alarm is triggered + * @alarm_high_mv: the voltage (mV) when high battery alarm is triggered * @max_voltage: the max voltage (mV) the battery should be charged up to * @min_voltage: the voltage (mV) where charging method switches from * trickle to fast. This is also the minimum voltage the * system operates at * @uvd_thresh_voltage: the USB falling UVD threshold (mV) (PM8917 only) + * @safe_current_ma: The upper limit of current allowed to be pushed in + * battery. This ends up writing in a one time + * programmable register. * @resume_voltage_delta: the (mV) drop to wait for before resume charging * after the battery has been fully charged + * @resume_charge_percent: the % SOC the charger will drop to after the + * battery is fully charged before resuming + * charging. * @term_current: the charger current (mA) at which EOC happens * @cool_temp: the temperature (degC) at which the battery is * considered cool charging current and voltage is reduced. @@ -83,6 +89,7 @@ enum pm8921_chg_led_src_config { * area * @max_bat_chg_current: Max charge current of the battery in mA * Usually 70% of full charge capacity + * @usb_max_current: Maximum USB current in mA * @cool_bat_chg_current: chg current (mA) when the battery is cool * @warm_bat_chg_current: chg current (mA) when the battery is warm * @cool_bat_voltage: chg voltage (mV) when the battery is cool @@ -90,10 +97,6 @@ enum pm8921_chg_led_src_config { * @get_batt_capacity_percent: * a board specific function to return battery * capacity. If null - a default one will be used - * @dc_unplug_check: enables the reverse boosting fix for the DC_IN line - * however, this should only be enabled for devices which - * control the DC OVP FETs otherwise this option should - * remain disabled * @has_dc_supply: report DC online if this bit is set in board file * @trkl_voltage: the trkl voltage in (mV) below which hw controlled * trkl charging happens with linear charger @@ -120,22 +123,44 @@ enum pm8921_chg_led_src_config { * resistance of the pads, connectors, battery terminals * and rsense. * @led_src_config: Power source for anode of charger indicator LED. + * @btc_override: disable the comparators for conifugrations where a + * suitable voltages don't appear on vbatt therm line + * for the charger to detect battery is either cold / hot. + * @btc_override_cold_degc: Temperature in degCelcius when the battery is + * deemed cold and charging never happens. Used + * only if btc_override = 1 + * @btc_override_hot_degc: Temperature in degCelcius when the battery is + * deemed hot and charging never happens. Used + * only if btc_override = 1 + * @btc_delay_ms: Delay in milliseconds to monitor the battery temperature + * while charging when btc_override = 1 + * @btc_panic_if_cant_stop_chg: flag to instruct the driver to panic if the + * driver couldn't stop charging when battery + * temperature is out of bounds. Used only if + * btc_override = 1 + * stop_chg_upon_expiry: flag to indicate that the charger driver should + * stop charging the battery when the safety timer + * expires. If not set the charger driver will + * restart charging upon expiry. */ struct pm8921_charger_platform_data { struct pm8xxx_charger_core_data charger_cdata; - unsigned int safety_time; unsigned int ttrkl_time; unsigned int update_time; unsigned int max_voltage; unsigned int min_voltage; unsigned int uvd_thresh_voltage; - unsigned int alarm_voltage; + unsigned int safe_current_ma; + unsigned int alarm_low_mv; + unsigned int alarm_high_mv; unsigned int resume_voltage_delta; + int resume_charge_percent; unsigned int term_current; int cool_temp; int warm_temp; unsigned int temp_check_period; unsigned int max_bat_chg_current; + unsigned int usb_max_current; unsigned int cool_bat_chg_current; unsigned int warm_bat_chg_current; unsigned int cool_bat_voltage; @@ -144,7 +169,6 @@ struct pm8921_charger_platform_data { int64_t batt_id_min; int64_t batt_id_max; bool keep_btm_on_suspend; - bool dc_unplug_check; bool has_dc_supply; int trkl_voltage; int weak_voltage; @@ -157,6 +181,13 @@ struct pm8921_charger_platform_data { enum pm8921_chg_hot_thr hot_thr; int rconn_mohm; enum pm8921_chg_led_src_config led_src_config; + int battery_less_hardware; + int btc_override; + int btc_override_cold_degc; + int btc_override_hot_degc; + int btc_delay_ms; + int btc_panic_if_cant_stop_chg; + int stop_chg_upon_expiry; }; enum pm8921_charger_source { @@ -169,15 +200,6 @@ enum pm8921_charger_source { void pm8921_charger_vbus_draw(unsigned int mA); int pm8921_charger_register_vbus_sn(void (*callback)(int)); void pm8921_charger_unregister_vbus_sn(void (*callback)(int)); -/** - * pm8921_charger_enable - - * - * @enable: 1 means enable charging, 0 means disable - * - * Enable/Disable battery charging current, the device will still draw current - * from the charging source - */ -int pm8921_charger_enable(bool enable); /** * pm8921_is_usb_chg_plugged_in - is usb plugged in @@ -309,10 +331,6 @@ static inline int pm8921_charger_register_vbus_sn(void (*callback)(int)) static inline void pm8921_charger_unregister_vbus_sn(void (*callback)(int)) { } -static inline int pm8921_charger_enable(bool enable) -{ - return -ENXIO; -} static inline int pm8921_is_usb_chg_plugged_in(void) { return -ENXIO;