diff options
author | Nicholas Troast <ntroast@codeaurora.org> | 2016-03-28 10:35:32 -0700 |
---|---|---|
committer | Jeevan Shriram <jshriram@codeaurora.org> | 2016-05-06 12:06:23 -0700 |
commit | 9f1ad26f6b1248c368e540a2f7f7aef2972d997f (patch) | |
tree | 381977f20b230d638fcc2a9b9c51ac1168d01374 /drivers/power/qcom-charger | |
parent | 4e468402df857c050dc5ebb287411d14fae5221d (diff) |
power: move QTI charger drivers to a new sub-directory again
The original commit was reverted due to conflicts in the kernel upgrade
process. Now that the upgrade is complete we can introduce this commit
again.
Original commit:
commit 8e10bff71301 ("power: move QTI charger drivers to a new
sub-directory qcom-charger")
QTI charger drivers have outgrown their home in power and deserve their
own sub-directory. Move all QTI charger drivers and their dependencies to
a new sub-directory of power called qcom-charger.
CRs-Fixed: 1001767
Change-Id: I5465a944a79f622ddf69534075b067db0fb10c95
Signed-off-by: Nicholas Troast <ntroast@codeaurora.org>
Diffstat (limited to 'drivers/power/qcom-charger')
-rw-r--r-- | drivers/power/qcom-charger/Kconfig | 64 | ||||
-rw-r--r-- | drivers/power/qcom-charger/Makefile | 6 | ||||
-rw-r--r-- | drivers/power/qcom-charger/batterydata-lib.c | 493 | ||||
-rw-r--r-- | drivers/power/qcom-charger/bcl_peripheral.c | 1153 | ||||
-rw-r--r-- | drivers/power/qcom-charger/msm_bcl.c | 374 | ||||
-rw-r--r-- | drivers/power/qcom-charger/pmic-voter.c | 267 | ||||
-rw-r--r-- | drivers/power/qcom-charger/pmic-voter.h | 46 | ||||
-rw-r--r-- | drivers/power/qcom-charger/qpnp-fg.c | 7020 | ||||
-rw-r--r-- | drivers/power/qcom-charger/qpnp-smbcharger.c | 8352 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb1351-charger.c | 3292 | ||||
-rw-r--r-- | drivers/power/qcom-charger/smb135x-charger.c | 4584 |
11 files changed, 25651 insertions, 0 deletions
diff --git a/drivers/power/qcom-charger/Kconfig b/drivers/power/qcom-charger/Kconfig new file mode 100644 index 000000000000..08932de855bb --- /dev/null +++ b/drivers/power/qcom-charger/Kconfig @@ -0,0 +1,64 @@ +menu "Qualcomm Technologies Inc Charger and FG Drivers" + +config QPNP_SMBCHARGER + tristate "QPNP SMB Charger driver" + depends on SPMI + select POWER_SUPPLY + help + Say Y here to enable the dual path switch mode battery charger which + supports USB detection and battery charging up to 3A. + The driver also offers relevant information to userspace via the + power supply framework. + +config QPNP_FG + tristate "QPNP fuel gauge driver" + depends on SPMI + select POWER_SUPPLY + help + Say Y here to enable the Fuel Gauge driver. This adds support for + battery fuel gauging and state of charge of battery connected to the + fuel gauge. The state of charge is reported through a BMS power + supply property and also sends uevents when the capacity is updated. + +config SMB135X_CHARGER + tristate "SMB135X Battery Charger" + depends on I2C + select POWER_SUPPLY + help + Say Y to include support for SMB135X Battery Charger. + SMB135X is a dual path switching mode charger capable of charging + the battery with 3Amps of current. + The driver supports charger enable/disable. + The driver reports the charger status via the power supply framework. + A charger status change triggers an IRQ via the device STAT pin. + +config SMB1351_USB_CHARGER + tristate "smb1351 usb charger (with VBUS detection)" + depends on I2C + select POWER_SUPPLY + help + Say Y to enable support for the SMB1351 switching mode based charger. + The driver supports charging control (enable/disable) and + charge-current limiting. It also provides USB VBUS detection and + notification support. The driver controls SMB1351 via I2C and + supports device-tree interface. + +config MSM_BCL_CTL + bool "BCL Framework driver" + help + Say Y here to enable this BCL Framework driver. This driver provides + interface, which can be used by the BCL h/w drivers to implement the + basic functionalities. This framework abstracts the underlying + hardware for the top level modules. + +config MSM_BCL_PERIPHERAL_CTL + bool "BCL driver to control the PMIC BCL peripheral" + depends on SPMI + depends on MSM_BCL_CTL + select POWER_SUPPLY + help + Say Y here to enable this BCL PMIC peripheral driver. This driver + provides routines to configure and monitor the BCL + PMIC peripheral. + +endmenu diff --git a/drivers/power/qcom-charger/Makefile b/drivers/power/qcom-charger/Makefile new file mode 100644 index 000000000000..e951851a9ef8 --- /dev/null +++ b/drivers/power/qcom-charger/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_QPNP_SMBCHARGER) += qpnp-smbcharger.o batterydata-lib.o pmic-voter.o +obj-$(CONFIG_QPNP_FG) += qpnp-fg.o +obj-$(CONFIG_SMB135X_CHARGER) += smb135x-charger.o pmic-voter.o +obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o +obj-$(CONFIG_MSM_BCL_CTL) += msm_bcl.o +obj-$(CONFIG_MSM_BCL_PERIPHERAL_CTL) += bcl_peripheral.o diff --git a/drivers/power/qcom-charger/batterydata-lib.c b/drivers/power/qcom-charger/batterydata-lib.c new file mode 100644 index 000000000000..226581468fda --- /dev/null +++ b/drivers/power/qcom-charger/batterydata-lib.c @@ -0,0 +1,493 @@ +/* Copyright (c) 2012-2014, 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 <linux/module.h> +#include <linux/batterydata-lib.h> + +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_scaled(struct single_row_lut *lut, + int x, int scale) +{ + int i, result; + + if (x < lut->x[0] * scale) { + 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] * scale) { + 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] * scale) + break; + if (x == lut->x[i] * scale) { + result = lut->y[i]; + } else { + result = linear_interpolate( + lut->y[i - 1], + lut->x[i - 1] * scale, + lut->y[i], + lut->x[i] * scale, + x); + } + return result; +} + +int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp) +{ + return interpolate_single_lut_scaled(fcc_temp_lut, + batt_temp, + DEGC_SCALE); +} + +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_scaled(fcc_sf_lut, cycles, 1); + 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; + } else 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; + } else { + 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] * DEGC_SCALE) + row_entry = sf_lut->row_entries[0] * DEGC_SCALE; + if (row_entry > sf_lut->row_entries[cols - 1] * DEGC_SCALE) + row_entry = sf_lut->row_entries[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) + if (row_entry <= sf_lut->row_entries[i] * DEGC_SCALE) + break; + if (row_entry == sf_lut->row_entries[i] * DEGC_SCALE) { + 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] * DEGC_SCALE, + sf_lut->sf[row1][i], + sf_lut->row_entries[i] * DEGC_SCALE, + row_entry); + + scalefactorrow2 = linear_interpolate( + sf_lut->sf[row2][i - 1], + sf_lut->row_entries[i - 1] * DEGC_SCALE, + sf_lut->sf[row2][i], + sf_lut->row_entries[i] * DEGC_SCALE, + 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, 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; + } else 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; + } else { + 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 < pc_temp_ocv->temp[0] * DEGC_SCALE) + batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE; + if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE) + batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) + if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE) + break; + if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) { + 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] * DEGC_SCALE, + pc_temp_ocv->ocv[row1][i], + pc_temp_ocv->temp[i] * DEGC_SCALE, + batt_temp); + + ocvrow2 = linear_interpolate( + pc_temp_ocv->ocv[row2][i - 1], + pc_temp_ocv->temp[i - 1] * DEGC_SCALE, + pc_temp_ocv->ocv[row2][i], + pc_temp_ocv->temp[i] * DEGC_SCALE, + batt_temp); + + 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, 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 < pc_temp_ocv->temp[0] * DEGC_SCALE) { + pr_debug("batt_temp %d < known temp range\n", batt_temp); + batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE; + } + + if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE) { + pr_debug("batt_temp %d > known temp range\n", batt_temp); + batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE; + } + + for (j = 0; j < cols; j++) + if (batt_temp <= pc_temp_ocv->temp[j] * DEGC_SCALE) + break; + if (batt_temp == pc_temp_ocv->temp[j] * DEGC_SCALE) { + /* 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 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] * DEGC_SCALE, + pcj, + pc_temp_ocv->temp[j] * DEGC_SCALE, + 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%%\n", + ocv, batt_temp); + return 100; +} + +int interpolate_slope(struct pc_temp_ocv_lut *pc_temp_ocv, + int batt_temp, int pc) +{ + int i, ocvrow1, ocvrow2, rows, cols; + int row1 = 0; + int row2 = 0; + int slope; + + rows = pc_temp_ocv->rows; + cols = pc_temp_ocv->cols; + if (pc >= pc_temp_ocv->percent[0]) { + pr_debug("pc %d >= max pc range - use the slope at pc=%d\n", + pc, pc_temp_ocv->percent[0]); + row1 = 0; + row2 = 1; + } else if (pc <= pc_temp_ocv->percent[rows - 1]) { + pr_debug("pc %d is <= min pc range - use the slope at pc=%d\n", + pc, pc_temp_ocv->percent[rows - 1]); + row1 = rows - 2; + row2 = rows - 1; + } else { + for (i = 0; i < rows; i++) { + if (pc == pc_temp_ocv->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + if (pc > pc_temp_ocv->percent[i]) { + row1 = i - 1; + row2 = i; + break; + } + } + } + + if (batt_temp < pc_temp_ocv->temp[0] * DEGC_SCALE) + batt_temp = pc_temp_ocv->temp[0] * DEGC_SCALE; + if (batt_temp > pc_temp_ocv->temp[cols - 1] * DEGC_SCALE) + batt_temp = pc_temp_ocv->temp[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) + if (batt_temp <= pc_temp_ocv->temp[i] * DEGC_SCALE) + break; + + if (batt_temp == pc_temp_ocv->temp[i] * DEGC_SCALE) { + slope = (pc_temp_ocv->ocv[row1][i] - + pc_temp_ocv->ocv[row2][i]); + if (slope <= 0) { + pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc); + slope = 1; + } + slope *= 1000; + slope /= (pc_temp_ocv->percent[row1] - + pc_temp_ocv->percent[row2]); + return slope; + } + ocvrow1 = linear_interpolate( + pc_temp_ocv->ocv[row1][i - 1], + pc_temp_ocv->temp[i - 1] * DEGC_SCALE, + pc_temp_ocv->ocv[row1][i], + pc_temp_ocv->temp[i] * DEGC_SCALE, + batt_temp); + + ocvrow2 = linear_interpolate( + pc_temp_ocv->ocv[row2][i - 1], + pc_temp_ocv->temp[i - 1] * DEGC_SCALE, + pc_temp_ocv->ocv[row2][i], + pc_temp_ocv->temp[i] * DEGC_SCALE, + batt_temp); + + slope = (ocvrow1 - ocvrow2); + if (slope <= 0) { + pr_warn("Slope=%d for pc=%d, using 1\n", slope, pc); + slope = 1; + } + slope *= 1000; + slope /= (pc_temp_ocv->percent[row1] - pc_temp_ocv->percent[row2]); + + return slope; +} + + +int interpolate_acc(struct ibat_temp_acc_lut *ibat_acc_lut, + int batt_temp, int ibat) +{ + int i, accrow1, accrow2, rows, cols; + int row1 = 0; + int row2 = 0; + int acc; + + rows = ibat_acc_lut->rows; + cols = ibat_acc_lut->cols; + + if (ibat > ibat_acc_lut->ibat[rows - 1]) { + pr_debug("ibatt(%d) > max range(%d)\n", ibat, + ibat_acc_lut->ibat[rows - 1]); + row1 = rows - 1; + row2 = rows - 2; + } else if (ibat < ibat_acc_lut->ibat[0]) { + pr_debug("ibatt(%d) < max range(%d)\n", ibat, + ibat_acc_lut->ibat[0]); + row1 = 0; + row2 = 0; + } else { + for (i = 0; i < rows; i++) { + if (ibat == ibat_acc_lut->ibat[i]) { + row1 = i; + row2 = i; + break; + } + if (ibat < ibat_acc_lut->ibat[i]) { + row1 = i; + row2 = i - 1; + break; + } + } + } + + if (batt_temp < ibat_acc_lut->temp[0] * DEGC_SCALE) + batt_temp = ibat_acc_lut->temp[0] * DEGC_SCALE; + if (batt_temp > ibat_acc_lut->temp[cols - 1] * DEGC_SCALE) + batt_temp = ibat_acc_lut->temp[cols - 1] * DEGC_SCALE; + + for (i = 0; i < cols; i++) + if (batt_temp <= ibat_acc_lut->temp[i] * DEGC_SCALE) + break; + + if (batt_temp == (ibat_acc_lut->temp[i] * DEGC_SCALE)) { + acc = linear_interpolate( + ibat_acc_lut->acc[row1][i], + ibat_acc_lut->ibat[row1], + ibat_acc_lut->acc[row2][i], + ibat_acc_lut->ibat[row2], + ibat); + return acc; + } + + accrow1 = linear_interpolate( + ibat_acc_lut->acc[row1][i - 1], + ibat_acc_lut->temp[i - 1] * DEGC_SCALE, + ibat_acc_lut->acc[row1][i], + ibat_acc_lut->temp[i] * DEGC_SCALE, + batt_temp); + + accrow2 = linear_interpolate( + ibat_acc_lut->acc[row2][i - 1], + ibat_acc_lut->temp[i - 1] * DEGC_SCALE, + ibat_acc_lut->acc[row2][i], + ibat_acc_lut->temp[i] * DEGC_SCALE, + batt_temp); + + acc = linear_interpolate(accrow1, + ibat_acc_lut->ibat[row1], + accrow2, + ibat_acc_lut->ibat[row2], + ibat); + + if (acc < 0) + acc = 0; + + return acc; +} diff --git a/drivers/power/qcom-charger/bcl_peripheral.c b/drivers/power/qcom-charger/bcl_peripheral.c new file mode 100644 index 000000000000..85d018a1a8b8 --- /dev/null +++ b/drivers/power/qcom-charger/bcl_peripheral.c @@ -0,0 +1,1153 @@ +/* + * Copyright (c) 2014-2015, 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:%s " fmt, KBUILD_MODNAME, __func__ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/of_address.h> +#include <linux/spmi.h> +#include <linux/platform_device.h> +#include <linux/mutex.h> +#include <linux/msm_bcl.h> +#include <linux/power_supply.h> + +#define CREATE_TRACE_POINTS +#define _BCL_HW_TRACE +#include <trace/trace_thermal.h> + +#define BCL_DRIVER_NAME "bcl_peripheral" +#define BCL_VBAT_INT_NAME "bcl-low-vbat-int" +#define BCL_IBAT_INT_NAME "bcl-high-ibat-int" +#define BCL_PARAM_MAX_ATTR 3 + +#define BCL_INT_EN 0x15 +#define BCL_MONITOR_EN 0x46 +#define BCL_VBAT_VALUE 0x54 +#define BCL_IBAT_VALUE 0x55 +#define BCL_VBAT_CP_VALUE 0x56 +#define BCL_IBAT_CP_VALUE 0x57 +#define BCL_VBAT_MIN 0x58 +#define BCL_IBAT_MAX 0x59 +#define BCL_VBAT_MIN_CP 0x5A +#define BCL_IBAT_MAX_CP 0x5B +#define BCL_V_GAIN_BAT 0x60 +#define BCL_I_GAIN_RSENSE 0x61 +#define BCL_I_OFFSET_RSENSE 0x62 +#define BCL_I_GAIN_BATFET 0x63 +#define BCL_I_OFFSET_BATFET 0x64 +#define BCL_I_SENSE_SRC 0x65 +#define BCL_VBAT_MIN_CLR 0x66 +#define BCL_IBAT_MAX_CLR 0x67 +#define BCL_VBAT_TRIP 0x68 +#define BCL_IBAT_TRIP 0x69 + +#define BCL_CONSTANT_NUM 32 +#define BCL_READ_RETRY_LIMIT 3 +#define VAL_CP_REG_BUF_LEN 3 +#define VAL_REG_BUF_OFFSET 0 +#define VAL_CP_REG_BUF_OFFSET 2 +#define PON_SPARE_FULL_CURRENT 0x0 +#define PON_SPARE_DERATED_CURRENT 0x1 + +#define READ_CONV_FACTOR(_node, _key, _val, _ret, _dest) do { \ + _ret = of_property_read_u32(_node, _key, &_val); \ + if (_ret) { \ + pr_err("Error reading key:%s. err:%d\n", _key, _ret); \ + goto bcl_dev_exit; \ + } \ + _dest = _val; \ + } while (0) + +#define READ_OPTIONAL_PROP(_node, _key, _val, _ret, _dest) do { \ + _ret = of_property_read_u32(_node, _key, &_val); \ + if (_ret && _ret != -EINVAL) { \ + pr_err("Error reading key:%s. err:%d\n", _key, _ret); \ + goto bcl_dev_exit; \ + } else if (!_ret) { \ + _dest = _val; \ + } \ + } while (0) + +enum bcl_monitor_state { + BCL_PARAM_INACTIVE, + BCL_PARAM_MONITOR, + BCL_PARAM_POLLING, +}; + +struct bcl_peripheral_data { + struct bcl_param_data *param_data; + struct bcl_driver_ops ops; + enum bcl_monitor_state state; + struct delayed_work poll_work; + int irq_num; + int high_trip; + int low_trip; + int trip_val; + int scaling_factor; + int offset_factor_num; + int offset_factor_den; + int offset; + int gain_factor_num; + int gain_factor_den; + int gain; + uint32_t polling_delay_ms; + int inhibit_derating_ua; + int (*read_max) (int *adc_value); + int (*clear_max) (void); + struct mutex state_trans_lock; +}; + +struct bcl_device { + bool enabled; + struct device *dev; + struct platform_device *pdev; + struct regmap *regmap; + uint16_t base_addr; + uint16_t pon_spare_addr; + int i_src; + struct bcl_peripheral_data param[BCL_PARAM_MAX]; +}; + +static struct bcl_device *bcl_perph; +static struct power_supply_desc bcl_psy_d; +static struct power_supply *bcl_psy; +static const char bcl_psy_name[] = "fg_adc"; +static bool calibration_done; +static DEFINE_MUTEX(bcl_access_mutex); +static DEFINE_MUTEX(bcl_enable_mutex); + +static int bcl_read_multi_register(int16_t reg_offset, uint8_t *data, int len) +{ + int ret = 0, trace_len = 0; + + if (!bcl_perph) { + pr_err("BCL device not initialized\n"); + return -EINVAL; + } + ret = regmap_bulk_read(bcl_perph->regmap, + (bcl_perph->base_addr + reg_offset), data, len); + if (ret < 0) { + pr_err("Error reading register %d. err:%d", reg_offset, ret); + return ret; + } + while (trace_len < len) { + trace_bcl_hw_reg_access("Read", + bcl_perph->base_addr + reg_offset + trace_len, + data[trace_len]); + trace_len++; + } + + return ret; +} + +static int bcl_read_register(int16_t reg_offset, uint8_t *data) +{ + return bcl_read_multi_register(reg_offset, data, 1); +} + +static int bcl_write_general_register(int16_t reg_offset, + uint16_t base, uint8_t data) +{ + int ret = 0; + uint8_t *write_buf = &data; + + if (!bcl_perph) { + pr_err("BCL device not initialized\n"); + return -EINVAL; + } + ret = regmap_write(bcl_perph->regmap, (base + reg_offset), *write_buf); + if (ret < 0) { + pr_err("Error reading register %d. err:%d", reg_offset, ret); + return ret; + } + pr_debug("wrote 0x%02x to 0x%04x\n", data, base + reg_offset); + trace_bcl_hw_reg_access("write", base + reg_offset, data); + + return ret; +} + +static int bcl_write_register(int16_t reg_offset, uint8_t data) +{ + return bcl_write_general_register(reg_offset, + bcl_perph->base_addr, data); +} + +static void convert_vbat_to_adc_val(int *val) +{ + struct bcl_peripheral_data *perph_data = NULL; + + if (!bcl_perph) + return; + perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE]; + *val = (*val * 100 + / (100 + (perph_data->gain_factor_num * perph_data->gain) + * BCL_CONSTANT_NUM + / perph_data->gain_factor_den)) + / perph_data->scaling_factor; + return; +} + +static void convert_adc_to_vbat_val(int *val) +{ + struct bcl_peripheral_data *perph_data = NULL; + + if (!bcl_perph) + return; + perph_data = &bcl_perph->param[BCL_PARAM_VOLTAGE]; + *val = ((*val + 2) * perph_data->scaling_factor) + * (100 + (perph_data->gain_factor_num * perph_data->gain) + * BCL_CONSTANT_NUM / perph_data->gain_factor_den) + / 100; + return; +} + +static void convert_ibat_to_adc_val(int *val) +{ + struct bcl_peripheral_data *perph_data = NULL; + + if (!bcl_perph) + return; + perph_data = &bcl_perph->param[BCL_PARAM_CURRENT]; + *val = (*val * 100 + / (100 + (perph_data->gain_factor_num * perph_data->gain) + * BCL_CONSTANT_NUM / perph_data->gain_factor_den) + - (perph_data->offset_factor_num * perph_data->offset) + / perph_data->offset_factor_den) + / perph_data->scaling_factor; + return; +} + +static void convert_adc_to_ibat_val(int *val) +{ + struct bcl_peripheral_data *perph_data = NULL; + + if (!bcl_perph) + return; + perph_data = &bcl_perph->param[BCL_PARAM_CURRENT]; + *val = (*val * perph_data->scaling_factor + + (perph_data->offset_factor_num * perph_data->offset) + / perph_data->offset_factor_den) + * (100 + (perph_data->gain_factor_num * perph_data->gain) + * BCL_CONSTANT_NUM / perph_data->gain_factor_den) / 100; + return; +} + +static int bcl_set_high_vbat(int thresh_value) +{ + bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip = thresh_value; + return 0; +} + +static int bcl_set_low_ibat(int thresh_value) +{ + bcl_perph->param[BCL_PARAM_CURRENT].low_trip = thresh_value; + return 0; +} + +static int bcl_set_high_ibat(int thresh_value) +{ + int ret = 0, ibat_ua; + int8_t val = 0; + + ibat_ua = thresh_value; + convert_ibat_to_adc_val(&thresh_value); + pr_debug("Setting Ibat high trip:%d. ADC_val:%d\n", ibat_ua, + thresh_value); + val = (int8_t)thresh_value; + ret = bcl_write_register(BCL_IBAT_TRIP, val); + if (ret) { + pr_err("Error accessing BCL peripheral. err:%d\n", ret); + return ret; + } + bcl_perph->param[BCL_PARAM_CURRENT].high_trip = thresh_value; + + if (bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua == 0 + || bcl_perph->pon_spare_addr == 0) + return ret; + + ret = bcl_write_general_register(bcl_perph->pon_spare_addr, + PON_SPARE_FULL_CURRENT, val); + if (ret) { + pr_debug("Error accessing PON register. err:%d\n", ret); + return ret; + } + thresh_value = ibat_ua + - bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua; + convert_ibat_to_adc_val(&thresh_value); + val = (int8_t)thresh_value; + ret = bcl_write_general_register(bcl_perph->pon_spare_addr, + PON_SPARE_DERATED_CURRENT, val); + if (ret) { + pr_debug("Error accessing PON register. err:%d\n", ret); + return ret; + } + + return ret; +} + +static int bcl_set_low_vbat(int thresh_value) +{ + int ret = 0, vbat_uv; + int8_t val = 0; + + vbat_uv = thresh_value; + convert_vbat_to_adc_val(&thresh_value); + pr_debug("Setting Vbat low trip:%d. ADC_val:%d\n", vbat_uv, + thresh_value); + val = (int8_t)thresh_value; + ret = bcl_write_register(BCL_VBAT_TRIP, val); + if (ret) { + pr_err("Error accessing BCL peripheral. err:%d\n", ret); + return ret; + } + bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip = thresh_value; + + return ret; +} + +static int bcl_access_monitor_enable(bool enable) +{ + int ret = 0, i = 0; + struct bcl_peripheral_data *perph_data = NULL; + + mutex_lock(&bcl_enable_mutex); + if (enable == bcl_perph->enabled) + goto access_exit; + + for (; i < BCL_PARAM_MAX; i++) { + perph_data = &bcl_perph->param[i]; + mutex_lock(&perph_data->state_trans_lock); + if (enable) { + switch (perph_data->state) { + case BCL_PARAM_INACTIVE: + trace_bcl_hw_state_event( + (i == BCL_PARAM_VOLTAGE) + ? "Voltage Inactive to Monitor" + : "Current Inactive to Monitor", + 0); + enable_irq(perph_data->irq_num); + break; + case BCL_PARAM_POLLING: + case BCL_PARAM_MONITOR: + default: + break; + } + perph_data->state = BCL_PARAM_MONITOR; + } else { + switch (perph_data->state) { + case BCL_PARAM_MONITOR: + trace_bcl_hw_state_event( + (i == BCL_PARAM_VOLTAGE) + ? "Voltage Monitor to Inactive" + : "Current Monitor to Inactive", + 0); + disable_irq_nosync(perph_data->irq_num); + /* Fall through to clear the poll work */ + case BCL_PARAM_INACTIVE: + case BCL_PARAM_POLLING: + cancel_delayed_work_sync( + &perph_data->poll_work); + break; + default: + break; + } + perph_data->state = BCL_PARAM_INACTIVE; + } + mutex_unlock(&perph_data->state_trans_lock); + } + bcl_perph->enabled = enable; + +access_exit: + mutex_unlock(&bcl_enable_mutex); + return ret; +} + +static int bcl_monitor_enable(void) +{ + trace_bcl_hw_event("BCL Enable"); + return bcl_access_monitor_enable(true); +} + +static int bcl_monitor_disable(void) +{ + trace_bcl_hw_event("BCL Disable"); + return bcl_access_monitor_enable(false); +} + +static int bcl_read_ibat_high_trip(int *thresh_value) +{ + int ret = 0; + int8_t val = 0; + + *thresh_value = (int)val; + ret = bcl_read_register(BCL_IBAT_TRIP, &val); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + ret = 0; + val = bcl_perph->param[BCL_PARAM_CURRENT].high_trip; + *thresh_value = (int)val; + } else { + *thresh_value = (int)val; + convert_adc_to_ibat_val(thresh_value); + pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n", + *thresh_value, val); + } + + return ret; +} + +static int bcl_read_ibat_low_trip(int *thresh_value) +{ + *thresh_value = bcl_perph->param[BCL_PARAM_CURRENT].low_trip; + return 0; +} + +static int bcl_read_vbat_low_trip(int *thresh_value) +{ + int ret = 0; + int8_t val = 0; + + *thresh_value = (int)val; + ret = bcl_read_register(BCL_VBAT_TRIP, &val); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + ret = 0; + *thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].low_trip; + } else { + *thresh_value = (int)val; + convert_adc_to_vbat_val(thresh_value); + pr_debug("Reading Ibat high trip:%d. ADC_val:%d\n", + *thresh_value, val); + } + + return ret; +} + +static int bcl_read_vbat_high_trip(int *thresh_value) +{ + *thresh_value = bcl_perph->param[BCL_PARAM_VOLTAGE].high_trip; + return 0; +} + +static int bcl_clear_vbat_min(void) +{ + int ret = 0; + + ret = bcl_write_register(BCL_VBAT_MIN_CLR, BIT(7)); + if (ret) + pr_err("Error in clearing vbat min reg. err:%d", ret); + + return ret; +} + +static int bcl_clear_ibat_max(void) +{ + int ret = 0; + + ret = bcl_write_register(BCL_IBAT_MAX_CLR, BIT(7)); + if (ret) + pr_err("Error in clearing ibat max reg. err:%d", ret); + + return ret; +} + +static int bcl_read_ibat_max(int *adc_value) +{ + int ret = 0, timeout = 0; + int8_t val[VAL_CP_REG_BUF_LEN] = {0}; + + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + do { + ret = bcl_read_multi_register(BCL_IBAT_MAX, val, + VAL_CP_REG_BUF_LEN); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + goto bcl_read_exit; + } + } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET] + && timeout++ < BCL_READ_RETRY_LIMIT); + if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) { + ret = -ENODEV; + goto bcl_read_exit; + } + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + convert_adc_to_ibat_val(adc_value); + pr_debug("Ibat Max:%d. ADC_val:%d\n", *adc_value, + val[VAL_REG_BUF_OFFSET]); + trace_bcl_hw_sensor_reading("Ibat Max[uA]", *adc_value); + +bcl_read_exit: + return ret; +} + +static int bcl_read_vbat_min(int *adc_value) +{ + int ret = 0, timeout = 0; + int8_t val[VAL_CP_REG_BUF_LEN] = {0}; + + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + do { + ret = bcl_read_multi_register(BCL_VBAT_MIN, val, + VAL_CP_REG_BUF_LEN); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + goto bcl_read_exit; + } + } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET] + && timeout++ < BCL_READ_RETRY_LIMIT); + if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) { + ret = -ENODEV; + goto bcl_read_exit; + } + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + convert_adc_to_vbat_val(adc_value); + pr_debug("Vbat Min:%d. ADC_val:%d\n", *adc_value, + val[VAL_REG_BUF_OFFSET]); + trace_bcl_hw_sensor_reading("vbat Min[uV]", *adc_value); + +bcl_read_exit: + return ret; +} + +static int bcl_read_ibat(int *adc_value) +{ + int ret = 0, timeout = 0; + int8_t val[VAL_CP_REG_BUF_LEN] = {0}; + + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + do { + ret = bcl_read_multi_register(BCL_IBAT_VALUE, val, + VAL_CP_REG_BUF_LEN); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + goto bcl_read_exit; + } + } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET] + && timeout++ < BCL_READ_RETRY_LIMIT); + if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) { + ret = -ENODEV; + goto bcl_read_exit; + } + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + convert_adc_to_ibat_val(adc_value); + pr_debug("Read Ibat:%d. ADC_val:%d\n", *adc_value, + val[VAL_REG_BUF_OFFSET]); + trace_bcl_hw_sensor_reading("ibat[uA]", *adc_value); + +bcl_read_exit: + return ret; +} + +static int bcl_read_vbat(int *adc_value) +{ + int ret = 0, timeout = 0; + int8_t val[VAL_CP_REG_BUF_LEN] = {0}; + + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + do { + ret = bcl_read_multi_register(BCL_VBAT_VALUE, val, + VAL_CP_REG_BUF_LEN); + if (ret) { + pr_err("BCL register read error. err:%d\n", ret); + goto bcl_read_exit; + } + } while (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET] + && timeout++ < BCL_READ_RETRY_LIMIT); + if (val[VAL_REG_BUF_OFFSET] != val[VAL_CP_REG_BUF_OFFSET]) { + ret = -ENODEV; + goto bcl_read_exit; + } + *adc_value = (int)val[VAL_REG_BUF_OFFSET]; + convert_adc_to_vbat_val(adc_value); + pr_debug("Read Vbat:%d. ADC_val:%d\n", *adc_value, + val[VAL_REG_BUF_OFFSET]); + trace_bcl_hw_sensor_reading("vbat[uV]", *adc_value); + +bcl_read_exit: + return ret; +} + +static void bcl_poll_ibat_low(struct work_struct *work) +{ + int ret = 0, val = 0; + struct bcl_peripheral_data *perph_data = + &bcl_perph->param[BCL_PARAM_CURRENT]; + + trace_bcl_hw_event("ibat poll low. Enter"); + mutex_lock(&perph_data->state_trans_lock); + if (perph_data->state != BCL_PARAM_POLLING) { + pr_err("Invalid ibat state %d\n", perph_data->state); + goto exit_ibat; + } + + ret = perph_data->read_max(&val); + if (ret) { + pr_err("Error in reading ibat. err:%d", ret); + goto reschedule_ibat; + } + ret = perph_data->clear_max(); + if (ret) + pr_err("Error clearing max ibat reg. err:%d\n", ret); + if (val <= perph_data->low_trip) { + pr_debug("Ibat reached low clear trip. ibat:%d\n", val); + trace_bcl_hw_state_event("Polling to Monitor. Ibat[uA]:", val); + trace_bcl_hw_mitigation("Ibat low trip. Ibat[uA]", val); + perph_data->ops.notify(perph_data->param_data, val, + BCL_LOW_TRIP); + perph_data->state = BCL_PARAM_MONITOR; + enable_irq(perph_data->irq_num); + } else { + goto reschedule_ibat; + } + +exit_ibat: + mutex_unlock(&perph_data->state_trans_lock); + trace_bcl_hw_event("ibat poll low. Exit"); + return; + +reschedule_ibat: + mutex_unlock(&perph_data->state_trans_lock); + schedule_delayed_work(&perph_data->poll_work, + msecs_to_jiffies(perph_data->polling_delay_ms)); + trace_bcl_hw_event("ibat poll low. Exit"); + return; +} + +static void bcl_poll_vbat_high(struct work_struct *work) +{ + int ret = 0, val = 0; + struct bcl_peripheral_data *perph_data = + &bcl_perph->param[BCL_PARAM_VOLTAGE]; + + trace_bcl_hw_event("vbat poll high. Enter"); + mutex_lock(&perph_data->state_trans_lock); + if (perph_data->state != BCL_PARAM_POLLING) { + pr_err("Invalid vbat state %d\n", perph_data->state); + goto exit_vbat; + } + + ret = perph_data->read_max(&val); + if (ret) { + pr_err("Error in reading vbat. err:%d", ret); + goto reschedule_vbat; + } + ret = perph_data->clear_max(); + if (ret) + pr_err("Error clearing min vbat reg. err:%d\n", ret); + if (val >= perph_data->high_trip) { + pr_debug("Vbat reached high clear trip. vbat:%d\n", val); + trace_bcl_hw_state_event("Polling to Monitor. vbat[uV]:", val); + trace_bcl_hw_mitigation("vbat high trip. vbat[uV]", val); + perph_data->ops.notify(perph_data->param_data, val, + BCL_HIGH_TRIP); + perph_data->state = BCL_PARAM_MONITOR; + enable_irq(perph_data->irq_num); + } else { + goto reschedule_vbat; + } + +exit_vbat: + mutex_unlock(&perph_data->state_trans_lock); + trace_bcl_hw_event("vbat poll high. Exit"); + return; + +reschedule_vbat: + mutex_unlock(&perph_data->state_trans_lock); + schedule_delayed_work(&perph_data->poll_work, + msecs_to_jiffies(perph_data->polling_delay_ms)); + trace_bcl_hw_event("vbat poll high. Exit"); + return; +} + +static irqreturn_t bcl_handle_ibat(int irq, void *data) +{ + int thresh_value = 0, ret = 0; + struct bcl_peripheral_data *perph_data = + (struct bcl_peripheral_data *)data; + + trace_bcl_hw_mitigation_event("Ibat interrupted"); + mutex_lock(&perph_data->state_trans_lock); + if (perph_data->state == BCL_PARAM_MONITOR) { + ret = perph_data->read_max(&perph_data->trip_val); + if (ret) { + pr_err("Error reading max/min reg. err:%d\n", ret); + goto exit_intr; + } + ret = perph_data->clear_max(); + if (ret) + pr_err("Error clearing max/min reg. err:%d\n", ret); + thresh_value = perph_data->high_trip; + convert_adc_to_ibat_val(&thresh_value); + /* Account threshold trip from PBS threshold for dead time */ + thresh_value -= perph_data->inhibit_derating_ua; + if (perph_data->trip_val < thresh_value) { + pr_debug("False Ibat high trip. ibat:%d ibat_thresh_val:%d\n", + perph_data->trip_val, thresh_value); + trace_bcl_hw_event("Ibat invalid interrupt"); + goto exit_intr; + } + pr_debug("Ibat reached high trip. ibat:%d\n", + perph_data->trip_val); + trace_bcl_hw_state_event("Monitor to Polling. ibat[uA]:", + perph_data->trip_val); + disable_irq_nosync(perph_data->irq_num); + perph_data->state = BCL_PARAM_POLLING; + trace_bcl_hw_mitigation("ibat high trip. ibat[uA]", + perph_data->trip_val); + perph_data->ops.notify(perph_data->param_data, + perph_data->trip_val, BCL_HIGH_TRIP); + schedule_delayed_work(&perph_data->poll_work, + msecs_to_jiffies(perph_data->polling_delay_ms)); + } else { + pr_debug("Ignoring interrupt\n"); + trace_bcl_hw_event("Ibat Ignoring interrupt"); + } + +exit_intr: + mutex_unlock(&perph_data->state_trans_lock); + return IRQ_HANDLED; +} + +static irqreturn_t bcl_handle_vbat(int irq, void *data) +{ + int thresh_value = 0, ret = 0; + struct bcl_peripheral_data *perph_data = + (struct bcl_peripheral_data *)data; + + trace_bcl_hw_mitigation_event("Vbat Interrupted"); + mutex_lock(&perph_data->state_trans_lock); + if (perph_data->state == BCL_PARAM_MONITOR) { + ret = perph_data->read_max(&perph_data->trip_val); + if (ret) { + pr_err("Error reading max/min reg. err:%d\n", ret); + goto exit_intr; + } + ret = perph_data->clear_max(); + if (ret) + pr_err("Error clearing max/min reg. err:%d\n", ret); + thresh_value = perph_data->low_trip; + convert_adc_to_vbat_val(&thresh_value); + if (perph_data->trip_val > thresh_value) { + pr_debug("False vbat min trip. vbat:%d vbat_thresh_val:%d\n", + perph_data->trip_val, thresh_value); + trace_bcl_hw_event("Vbat Invalid interrupt"); + goto exit_intr; + } + pr_debug("Vbat reached Low trip. vbat:%d\n", + perph_data->trip_val); + trace_bcl_hw_state_event("Monitor to Polling. vbat[uV]:", + perph_data->trip_val); + disable_irq_nosync(perph_data->irq_num); + perph_data->state = BCL_PARAM_POLLING; + trace_bcl_hw_mitigation("vbat low trip. vbat[uV]", + perph_data->trip_val); + perph_data->ops.notify(perph_data->param_data, + perph_data->trip_val, BCL_LOW_TRIP); + schedule_delayed_work(&perph_data->poll_work, + msecs_to_jiffies(perph_data->polling_delay_ms)); + } else { + pr_debug("Ignoring interrupt\n"); + trace_bcl_hw_event("Vbat Ignoring interrupt"); + } + +exit_intr: + mutex_unlock(&perph_data->state_trans_lock); + return IRQ_HANDLED; +} + +static int bcl_get_devicetree_data(struct platform_device *pdev) +{ + int ret = 0, irq_num = 0, temp_val = 0; + char *key = NULL; + const __be32 *prop = NULL; + struct device_node *dev_node = pdev->dev.of_node; + + prop = of_get_address_by_name(dev_node, "fg_user_adc", 0, 0); + if (prop) { + bcl_perph->base_addr = be32_to_cpu(*prop); + pr_debug("fg_user_adc@%04x\n", bcl_perph->base_addr); + } else { + dev_err(&pdev->dev, "No fg_user_adc registers found\n"); + return -EINVAL; + } + + prop = of_get_address_by_name(dev_node, + "pon_spare", 0, 0); + if (prop) { + bcl_perph->pon_spare_addr = be32_to_cpu(*prop); + pr_debug("pon_spare@%04x\n", bcl_perph->pon_spare_addr); + } + + /* Register SPMI peripheral interrupt */ + irq_num = platform_get_irq_byname(pdev, BCL_VBAT_INT_NAME); + if (irq_num < 0) { + pr_err("Invalid vbat IRQ\n"); + ret = -ENXIO; + goto bcl_dev_exit; + } + bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num = irq_num; + irq_num = platform_get_irq_byname(pdev, BCL_IBAT_INT_NAME); + if (irq_num < 0) { + pr_err("Invalid ibat IRQ\n"); + ret = -ENXIO; + goto bcl_dev_exit; + } + bcl_perph->param[BCL_PARAM_CURRENT].irq_num = irq_num; + + /* Get VADC and IADC scaling factor */ + key = "qcom,vbat-scaling-factor"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_VOLTAGE].scaling_factor); + key = "qcom,vbat-gain-numerator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_num); + key = "qcom,vbat-gain-denominator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_VOLTAGE].gain_factor_den); + key = "qcom,ibat-scaling-factor"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].scaling_factor); + key = "qcom,ibat-offset-numerator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_num); + key = "qcom,ibat-offset-denominator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].offset_factor_den); + key = "qcom,ibat-gain-numerator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_num); + key = "qcom,ibat-gain-denominator"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].gain_factor_den); + key = "qcom,vbat-polling-delay-ms"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_VOLTAGE].polling_delay_ms); + key = "qcom,ibat-polling-delay-ms"; + READ_CONV_FACTOR(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].polling_delay_ms); + key = "qcom,inhibit-derating-ua"; + READ_OPTIONAL_PROP(dev_node, key, temp_val, ret, + bcl_perph->param[BCL_PARAM_CURRENT].inhibit_derating_ua); + +bcl_dev_exit: + return ret; +} + +static int bcl_calibrate(void) +{ + int ret = 0; + int8_t i_src = 0, val = 0; + + ret = bcl_read_register(BCL_I_SENSE_SRC, &i_src); + if (ret) { + pr_err("Error reading current sense reg. err:%d\n", ret); + goto bcl_cal_exit; + } + + ret = bcl_read_register((i_src & 0x01) ? BCL_I_GAIN_RSENSE + : BCL_I_GAIN_BATFET, &val); + if (ret) { + pr_err("Error reading %s current gain. err:%d\n", + (i_src & 0x01) ? "rsense" : "batfet", ret); + goto bcl_cal_exit; + } + bcl_perph->param[BCL_PARAM_CURRENT].gain = val; + ret = bcl_read_register((i_src & 0x01) ? BCL_I_OFFSET_RSENSE + : BCL_I_OFFSET_BATFET, &val); + if (ret) { + pr_err("Error reading %s current offset. err:%d\n", + (i_src & 0x01) ? "rsense" : "batfet", ret); + goto bcl_cal_exit; + } + bcl_perph->param[BCL_PARAM_CURRENT].offset = val; + ret = bcl_read_register(BCL_V_GAIN_BAT, &val); + if (ret) { + pr_err("Error reading vbat offset. err:%d\n", ret); + goto bcl_cal_exit; + } + bcl_perph->param[BCL_PARAM_VOLTAGE].gain = val; + + if (((i_src & 0x01) != bcl_perph->i_src) + && (bcl_perph->enabled)) { + bcl_set_low_vbat(bcl_perph->param[BCL_PARAM_VOLTAGE] + .low_trip); + bcl_set_high_ibat(bcl_perph->param[BCL_PARAM_CURRENT] + .high_trip); + bcl_perph->i_src = i_src; + } + +bcl_cal_exit: + return ret; +} + +static void power_supply_callback(struct power_supply *psy) +{ + static struct power_supply *bms_psy; + int ret = 0; + + if (calibration_done) + return; + + if (!bms_psy) + bms_psy = power_supply_get_by_name("bms"); + if (bms_psy) { + calibration_done = true; + trace_bcl_hw_event("Recalibrate callback"); + ret = bcl_calibrate(); + if (ret) + pr_err("Could not read calibration values. err:%d", + ret); + } +} + +static int bcl_psy_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + return 0; +} +static int bcl_psy_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + return -EINVAL; +} + +static int bcl_update_data(void) +{ + int ret = 0; + + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.read = bcl_read_vbat; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_high_trip + = bcl_read_vbat_high_trip; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.get_low_trip + = bcl_read_vbat_low_trip; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_high_trip + = bcl_set_high_vbat; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.set_low_trip + = bcl_set_low_vbat; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.enable + = bcl_monitor_enable; + bcl_perph->param[BCL_PARAM_VOLTAGE].ops.disable + = bcl_monitor_disable; + bcl_perph->param[BCL_PARAM_VOLTAGE].read_max + = bcl_read_vbat_min; + bcl_perph->param[BCL_PARAM_VOLTAGE].clear_max + = bcl_clear_vbat_min; + + bcl_perph->param[BCL_PARAM_CURRENT].ops.read = bcl_read_ibat; + bcl_perph->param[BCL_PARAM_CURRENT].ops.get_high_trip + = bcl_read_ibat_high_trip; + bcl_perph->param[BCL_PARAM_CURRENT].ops.get_low_trip + = bcl_read_ibat_low_trip; + bcl_perph->param[BCL_PARAM_CURRENT].ops.set_high_trip + = bcl_set_high_ibat; + bcl_perph->param[BCL_PARAM_CURRENT].ops.set_low_trip + = bcl_set_low_ibat; + bcl_perph->param[BCL_PARAM_CURRENT].ops.enable + = bcl_monitor_enable; + bcl_perph->param[BCL_PARAM_CURRENT].ops.disable + = bcl_monitor_disable; + bcl_perph->param[BCL_PARAM_CURRENT].read_max + = bcl_read_ibat_max; + bcl_perph->param[BCL_PARAM_CURRENT].clear_max + = bcl_clear_ibat_max; + + bcl_perph->param[BCL_PARAM_VOLTAGE].param_data = msm_bcl_register_param( + BCL_PARAM_VOLTAGE, &bcl_perph->param[BCL_PARAM_VOLTAGE].ops, + "vbat"); + if (!bcl_perph->param[BCL_PARAM_VOLTAGE].param_data) { + pr_err("register Vbat failed.\n"); + ret = -ENODEV; + goto update_data_exit; + } + bcl_perph->param[BCL_PARAM_CURRENT].param_data = msm_bcl_register_param( + BCL_PARAM_CURRENT, &bcl_perph->param[BCL_PARAM_CURRENT].ops, + "ibat"); + if (!bcl_perph->param[BCL_PARAM_CURRENT].param_data) { + pr_err("register Ibat failed.\n"); + ret = -ENODEV; + goto update_data_exit; + } + INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_VOLTAGE].poll_work, + bcl_poll_vbat_high); + INIT_DELAYED_WORK(&bcl_perph->param[BCL_PARAM_CURRENT].poll_work, + bcl_poll_ibat_low); + mutex_init(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock); + mutex_init(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock); + +update_data_exit: + return ret; +} + +static int bcl_probe(struct platform_device *pdev) +{ + int ret = 0; + struct power_supply_config bcl_psy_cfg = {}; + + bcl_perph = devm_kzalloc(&pdev->dev, sizeof(struct bcl_device), + GFP_KERNEL); + if (!bcl_perph) { + pr_err("Memory alloc failed\n"); + return -ENOMEM; + } + bcl_perph->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!bcl_perph->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + bcl_perph->pdev = pdev; + bcl_perph->dev = &(pdev->dev); + + ret = bcl_get_devicetree_data(pdev); + if (ret) { + pr_err("Device tree data fetch error. err:%d", ret); + goto bcl_probe_exit; + } + ret = bcl_calibrate(); + if (ret) { + pr_debug("Could not read calibration values. err:%d", ret); + goto bcl_probe_exit; + } + bcl_psy_d.name = bcl_psy_name; + bcl_psy_d.type = POWER_SUPPLY_TYPE_BMS; + bcl_psy_d.get_property = bcl_psy_get_property; + bcl_psy_d.set_property = bcl_psy_set_property; + bcl_psy_d.num_properties = 0; + bcl_psy_d.external_power_changed = power_supply_callback; + + bcl_psy_cfg.num_supplicants = 0; + bcl_psy_cfg.drv_data = bcl_perph; + + bcl_psy = devm_power_supply_register(&pdev->dev, &bcl_psy_d, + &bcl_psy_cfg); + if (IS_ERR(bcl_psy)) { + pr_err("Unable to register bcl_psy rc = %ld\n", + PTR_ERR(bcl_psy)); + return ret; + } + + ret = bcl_update_data(); + if (ret) { + pr_err("Update data failed. err:%d", ret); + goto bcl_probe_exit; + } + mutex_lock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock); + ret = devm_request_threaded_irq(&pdev->dev, + bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num, + NULL, bcl_handle_vbat, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "bcl_vbat_interrupt", + &bcl_perph->param[BCL_PARAM_VOLTAGE]); + if (ret) { + dev_err(&pdev->dev, "Error requesting VBAT irq. err:%d", ret); + mutex_unlock( + &bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock); + goto bcl_probe_exit; + } + /* + * BCL is enabled by default in hardware. + * Disable BCL monitoring till a valid threshold is set by APPS + */ + disable_irq_nosync(bcl_perph->param[BCL_PARAM_VOLTAGE].irq_num); + mutex_unlock(&bcl_perph->param[BCL_PARAM_VOLTAGE].state_trans_lock); + + mutex_lock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock); + ret = devm_request_threaded_irq(&pdev->dev, + bcl_perph->param[BCL_PARAM_CURRENT].irq_num, + NULL, bcl_handle_ibat, + IRQF_TRIGGER_RISING | IRQF_ONESHOT, + "bcl_ibat_interrupt", + &bcl_perph->param[BCL_PARAM_CURRENT]); + if (ret) { + dev_err(&pdev->dev, "Error requesting IBAT irq. err:%d", ret); + mutex_unlock( + &bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock); + goto bcl_probe_exit; + } + disable_irq_nosync(bcl_perph->param[BCL_PARAM_CURRENT].irq_num); + mutex_unlock(&bcl_perph->param[BCL_PARAM_CURRENT].state_trans_lock); + + dev_set_drvdata(&pdev->dev, bcl_perph); + ret = bcl_write_register(BCL_MONITOR_EN, BIT(7)); + if (ret) { + pr_err("Error accessing BCL peripheral. err:%d\n", ret); + goto bcl_probe_exit; + } + + return 0; + +bcl_probe_exit: + bcl_perph = NULL; + return ret; +} + +static int bcl_remove(struct platform_device *pdev) +{ + int ret = 0, i = 0; + + ret = bcl_monitor_disable(); + if (ret) + pr_err("Error disabling BCL. err:%d\n", ret); + + for (; i < BCL_PARAM_MAX; i++) { + if (!bcl_perph->param[i].param_data) + continue; + + ret = msm_bcl_unregister_param(bcl_perph->param[i].param_data); + if (ret) + pr_err("Error unregistering with Framework. err:%d\n", + ret); + } + + return 0; +} + +static struct of_device_id bcl_match[] = { + { .compatible = "qcom,msm-bcl", + }, + {}, +}; + +static struct platform_driver bcl_driver = { + .probe = bcl_probe, + .remove = bcl_remove, + .driver = { + .name = BCL_DRIVER_NAME, + .owner = THIS_MODULE, + .of_match_table = bcl_match, + }, +}; + +static int __init bcl_perph_init(void) +{ + pr_info("BCL Initialized\n"); + return platform_driver_register(&bcl_driver); +} + +static void __exit bcl_perph_exit(void) +{ + platform_driver_unregister(&bcl_driver); +} +fs_initcall(bcl_perph_init); +module_exit(bcl_perph_exit); +MODULE_ALIAS("platform:" BCL_DRIVER_NAME); + diff --git a/drivers/power/qcom-charger/msm_bcl.c b/drivers/power/qcom-charger/msm_bcl.c new file mode 100644 index 000000000000..6b7cefdc0250 --- /dev/null +++ b/drivers/power/qcom-charger/msm_bcl.c @@ -0,0 +1,374 @@ +/* Copyright (c) 2014, 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:%s " fmt, KBUILD_MODNAME, __func__ + +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/interrupt.h> +#include <linux/workqueue.h> +#include <linux/kernel.h> +#include <linux/io.h> +#include <linux/err.h> +#include <linux/of.h> +#include <linux/sysfs.h> +#include <linux/mutex.h> +#include <linux/msm_bcl.h> +#include <linux/slab.h> + +#define BCL_PARAM_MAX_ATTR 3 + +#define BCL_DEFINE_RO_PARAM(_attr, _name, _attr_gp, _index) \ + _attr.attr.name = __stringify(_name); \ + _attr.attr.mode = 0444; \ + _attr.show = _name##_show; \ + _attr_gp.attrs[_index] = &_attr.attr; + +static struct bcl_param_data *bcl[BCL_PARAM_MAX]; + +static ssize_t high_trip_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int val = 0, ret = 0; + struct bcl_param_data *dev_param = container_of(attr, + struct bcl_param_data, high_trip_attr); + + if (!dev_param->registered) + return -ENODEV; + + ret = dev_param->ops->get_high_trip(&val); + if (ret) { + pr_err("High trip value read failed. err:%d\n", ret); + return ret; + } + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t low_trip_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int val = 0, ret = 0; + struct bcl_param_data *dev_param = container_of(attr, + struct bcl_param_data, low_trip_attr); + + if (!dev_param->registered) + return -ENODEV; + + ret = dev_param->ops->get_low_trip(&val); + if (ret) { + pr_err("Low trip value read failed. err:%d\n", ret); + return ret; + } + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t value_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + int32_t val = 0, ret = 0; + struct bcl_param_data *dev_param = container_of(attr, + struct bcl_param_data, val_attr); + + if (!dev_param->registered) + return -ENODEV; + + ret = dev_param->ops->read(&val); + if (ret) { + pr_err("Value read failed. err:%d\n", ret); + return ret; + } + dev_param->last_read_val = val; + + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +int msm_bcl_set_threshold(enum bcl_param param_type, + enum bcl_trip_type trip_type, struct bcl_threshold *inp_thresh) +{ + int ret = 0; + + if (!bcl[param_type] || !bcl[param_type]->registered) { + pr_err("BCL not initialized\n"); + return -EINVAL; + } + if ((!inp_thresh) + || (inp_thresh->trip_value < 0) + || (!inp_thresh->trip_notify) + || (param_type >= BCL_PARAM_MAX) + || (trip_type >= BCL_TRIP_MAX)) { + pr_err("Invalid Input\n"); + return -EINVAL; + } + + bcl[param_type]->thresh[trip_type] = inp_thresh; + if (trip_type == BCL_HIGH_TRIP) { + bcl[param_type]->high_trip = inp_thresh->trip_value; + ret = bcl[param_type]->ops->set_high_trip( + inp_thresh->trip_value); + } else { + bcl[param_type]->low_trip = inp_thresh->trip_value; + ret = bcl[param_type]->ops->set_low_trip( + inp_thresh->trip_value); + } + if (ret) { + pr_err("Error setting trip%d for param%d. err:%d\n", trip_type, + param_type, ret); + return ret; + } + + return ret; +} + +static int bcl_thresh_notify(struct bcl_param_data *param_data, int val, + enum bcl_trip_type trip_type) +{ + if (!param_data || trip_type >= BCL_TRIP_MAX + || !param_data->registered) { + pr_err("Invalid input\n"); + return -EINVAL; + } + + param_data->thresh[trip_type]->trip_notify(trip_type, val, + param_data->thresh[trip_type]->trip_data); + + return 0; +} + +static int bcl_add_sysfs_nodes(enum bcl_param param_type); +struct bcl_param_data *msm_bcl_register_param(enum bcl_param param_type, + struct bcl_driver_ops *param_ops, char *name) +{ + int ret = 0; + + if (!bcl[param_type] + || param_type >= BCL_PARAM_MAX || !param_ops || !name + || !param_ops->read || !param_ops->set_high_trip + || !param_ops->get_high_trip || !param_ops->set_low_trip + || !param_ops->get_low_trip || !param_ops->enable + || !param_ops->disable) { + pr_err("Invalid input\n"); + return NULL; + } + if (bcl[param_type]->registered) { + pr_err("param%d already initialized\n", param_type); + return NULL; + } + + ret = bcl_add_sysfs_nodes(param_type); + if (ret) { + pr_err("Error creating sysfs nodes. err:%d\n", ret); + return NULL; + } + bcl[param_type]->ops = param_ops; + bcl[param_type]->registered = true; + strlcpy(bcl[param_type]->name, name, BCL_NAME_MAX_LEN); + param_ops->notify = bcl_thresh_notify; + + return bcl[param_type]; +} + +int msm_bcl_unregister_param(struct bcl_param_data *param_data) +{ + int i = 0, ret = -EINVAL; + + if (!bcl[i] || !param_data) { + pr_err("Invalid input\n"); + return ret; + } + for (; i < BCL_PARAM_MAX; i++) { + if (param_data != bcl[i]) + continue; + bcl[i]->ops->disable(); + bcl[i]->registered = false; + ret = 0; + break; + } + + return ret; +} + +int msm_bcl_disable(void) +{ + int ret = 0, i = 0; + + if (!bcl[i]) { + pr_err("BCL not initialized\n"); + return -EINVAL; + } + + for (; i < BCL_PARAM_MAX; i++) { + if (!bcl[i]->registered) + continue; + ret = bcl[i]->ops->disable(); + if (ret) { + pr_err("Error in disabling interrupt. param:%d err%d\n", + i, ret); + return ret; + } + } + + return ret; +} + +int msm_bcl_enable(void) +{ + int ret = 0, i = 0; + struct bcl_param_data *param_data = NULL; + + if (!bcl[i] || !bcl[BCL_PARAM_VOLTAGE]->thresh + || !bcl[BCL_PARAM_CURRENT]->thresh) { + pr_err("BCL not initialized\n"); + return -EINVAL; + } + + for (; i < BCL_PARAM_MAX; i++) { + if (!bcl[i]->registered) + continue; + param_data = bcl[i]; + ret = param_data->ops->set_high_trip(param_data->high_trip); + if (ret) { + pr_err("Error setting high trip. param:%d. err:%d", + i, ret); + return ret; + } + ret = param_data->ops->set_low_trip(param_data->low_trip); + if (ret) { + pr_err("Error setting low trip. param:%d. err:%d", + i, ret); + return ret; + } + ret = param_data->ops->enable(); + if (ret) { + pr_err("Error enabling interrupt. param:%d. err:%d", + i, ret); + return ret; + } + } + + return ret; +} + +int msm_bcl_read(enum bcl_param param_type, int *value) +{ + int ret = 0; + + if (!value || param_type >= BCL_PARAM_MAX) { + pr_err("Invalid input\n"); + return -EINVAL; + } + if (!bcl[param_type] || !bcl[param_type]->registered) { + pr_err("BCL driver not initialized\n"); + return -ENOSYS; + } + + ret = bcl[param_type]->ops->read(value); + if (ret) { + pr_err("Error reading param%d. err:%d\n", param_type, ret); + return ret; + } + bcl[param_type]->last_read_val = *value; + + return ret; +} + +static struct class msm_bcl_class = { + .name = "msm_bcl", +}; + +static int bcl_add_sysfs_nodes(enum bcl_param param_type) +{ + char *param_name[BCL_PARAM_MAX] = {"voltage", "current"}; + int ret = 0; + + bcl[param_type]->device.class = &msm_bcl_class; + dev_set_name(&bcl[param_type]->device, "%s", param_name[param_type]); + ret = device_register(&bcl[param_type]->device); + if (ret) { + pr_err("Error registering device %s. err:%d\n", + param_name[param_type], ret); + return ret; + } + bcl[param_type]->bcl_attr_gp.attrs = kzalloc(sizeof(struct attribute *) + * (BCL_PARAM_MAX_ATTR + 1), GFP_KERNEL); + if (!bcl[param_type]->bcl_attr_gp.attrs) { + pr_err("Sysfs attribute create failed.\n"); + ret = -ENOMEM; + goto add_sysfs_exit; + } + BCL_DEFINE_RO_PARAM(bcl[param_type]->val_attr, value, + bcl[param_type]->bcl_attr_gp, 0); + BCL_DEFINE_RO_PARAM(bcl[param_type]->high_trip_attr, high_trip, + bcl[param_type]->bcl_attr_gp, 1); + BCL_DEFINE_RO_PARAM(bcl[param_type]->low_trip_attr, low_trip, + bcl[param_type]->bcl_attr_gp, 2); + bcl[param_type]->bcl_attr_gp.attrs[BCL_PARAM_MAX_ATTR] = NULL; + + ret = sysfs_create_group(&bcl[param_type]->device.kobj, + &bcl[param_type]->bcl_attr_gp); + if (ret) { + pr_err("Failure to create sysfs nodes. err:%d", ret); + goto add_sysfs_exit; + } + +add_sysfs_exit: + return ret; +} + +static int msm_bcl_init(void) +{ + int ret = 0, i = 0; + + for (; i < BCL_PARAM_MAX; i++) { + bcl[i] = kzalloc(sizeof(struct bcl_param_data), + GFP_KERNEL); + if (!bcl[i]) { + pr_err("kzalloc failed\n"); + while ((--i) >= 0) + kfree(bcl[i]); + return -ENOMEM; + } + } + + return ret; +} + + +static int __init msm_bcl_init_driver(void) +{ + int ret = 0; + + ret = msm_bcl_init(); + if (ret) { + pr_err("msm bcl init failed. err:%d\n", ret); + return ret; + } + return class_register(&msm_bcl_class); +} + +static void __exit bcl_exit(void) +{ + int i = 0; + + for (; i < BCL_PARAM_MAX; i++) { + sysfs_remove_group(&bcl[i]->device.kobj, + &bcl[i]->bcl_attr_gp); + kfree(bcl[i]->bcl_attr_gp.attrs); + kfree(bcl[i]); + } + class_unregister(&msm_bcl_class); +} + +fs_initcall(msm_bcl_init_driver); +module_exit(bcl_exit); diff --git a/drivers/power/qcom-charger/pmic-voter.c b/drivers/power/qcom-charger/pmic-voter.c new file mode 100644 index 000000000000..8723ea9938b0 --- /dev/null +++ b/drivers/power/qcom-charger/pmic-voter.c @@ -0,0 +1,267 @@ +/* Copyright (c) 2015 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/spinlock.h> +#include <linux/errno.h> +#include <linux/bitops.h> +#include <linux/printk.h> +#include <linux/device.h> + +#include "pmic-voter.h" + +#define NUM_MAX_CLIENTS 8 + +struct client_vote { + int state; + int value; +}; + +struct votable { + struct client_vote votes[NUM_MAX_CLIENTS]; + struct device *dev; + const char *name; + int num_clients; + int type; + int effective_client_id; + int effective_result; + int default_result; + struct mutex vote_lock; + int (*callback)(struct device *dev, + int effective_result, + int effective_client, + int last_result, + int last_client); +}; + +static int vote_set_any(struct votable *votable) +{ + int i; + + for (i = 0; i < votable->num_clients; i++) + if (votable->votes[i].state == 1) + return 1; + return 0; +} + +static int vote_min(struct votable *votable) +{ + int min_vote = INT_MAX; + int client_index = -EINVAL; + int i; + + for (i = 0; i < votable->num_clients; i++) { + if (votable->votes[i].state == 1 && + min_vote > votable->votes[i].value) { + min_vote = votable->votes[i].value; + client_index = i; + } + } + + return client_index; +} + +static int vote_max(struct votable *votable) +{ + int max_vote = INT_MIN; + int client_index = -EINVAL; + int i; + + for (i = 0; i < votable->num_clients; i++) { + if (votable->votes[i].state == 1 && + max_vote < votable->votes[i].value) { + max_vote = votable->votes[i].value; + client_index = i; + } + } + + return client_index; +} + +void lock_votable(struct votable *votable) +{ + mutex_lock(&votable->vote_lock); +} + +void unlock_votable(struct votable *votable) +{ + mutex_unlock(&votable->vote_lock); +} + +int get_client_vote(struct votable *votable, int client_id) +{ + int value; + + lock_votable(votable); + value = get_client_vote_locked(votable, client_id); + unlock_votable(votable); + return value; +} + +int get_client_vote_locked(struct votable *votable, int client_id) +{ + if (votable->votes[client_id].state < 0) + return votable->default_result; + + return votable->votes[client_id].value; +} + +int get_effective_result(struct votable *votable) +{ + int value; + + lock_votable(votable); + value = get_effective_result_locked(votable); + unlock_votable(votable); + return value; +} + +int get_effective_result_locked(struct votable *votable) +{ + if (votable->effective_result < 0) + return votable->default_result; + + return votable->effective_result; +} + +int get_effective_client_id(struct votable *votable) +{ + int id; + + lock_votable(votable); + id = get_effective_client_id_locked(votable); + unlock_votable(votable); + return id; +} + +int get_effective_client_id_locked(struct votable *votable) +{ + return votable->effective_client_id; +} + +int vote(struct votable *votable, int client_id, bool state, int val) +{ + int effective_id = - EINVAL; + int effective_result; + int rc = 0; + + lock_votable(votable); + + if (votable->votes[client_id].state == state && + votable->votes[client_id].value == val) { + pr_debug("%s: votes unchanged; skipping\n", votable->name); + goto out; + } + + votable->votes[client_id].state = state; + votable->votes[client_id].value = val; + + pr_debug("%s: %d voting for %d - %s\n", + votable->name, + client_id, val, state ? "on" : "off"); + switch (votable->type) { + case VOTE_MIN: + effective_id = vote_min(votable); + break; + case VOTE_MAX: + effective_id = vote_max(votable); + break; + case VOTE_SET_ANY: + votable->votes[client_id].value = state; + effective_result = vote_set_any(votable); + if (effective_result != votable->effective_result) { + votable->effective_client_id = client_id; + votable->effective_result = effective_result; + rc = votable->callback(votable->dev, + effective_result, client_id, + state, client_id); + } + goto out; + } + + /* + * If the votable does not have any votes it will maintain the last + * known effective_result and effective_client_id + */ + if (effective_id < 0) { + pr_debug("%s: no votes; skipping callback\n", votable->name); + goto out; + } + + effective_result = votable->votes[effective_id].value; + + if (effective_result != votable->effective_result) { + votable->effective_client_id = effective_id; + votable->effective_result = effective_result; + pr_debug("%s: effective vote is now %d voted by %d\n", + votable->name, effective_result, effective_id); + rc = votable->callback(votable->dev, effective_result, + effective_id, val, client_id); + } + +out: + unlock_votable(votable); + return rc; +} + +struct votable *create_votable(struct device *dev, const char *name, + int votable_type, + int num_clients, + int default_result, + int (*callback)(struct device *dev, + int effective_result, + int effective_client, + int last_result, + int last_client) + ) +{ + int i; + struct votable *votable; + + if (!callback) { + dev_err(dev, "Invalid callback specified for voter\n"); + return ERR_PTR(-EINVAL); + } + + if (votable_type >= NUM_VOTABLE_TYPES) { + dev_err(dev, "Invalid votable_type specified for voter\n"); + return ERR_PTR(-EINVAL); + } + + if (num_clients > NUM_MAX_CLIENTS) { + dev_err(dev, "Invalid num_clients specified for voter\n"); + return ERR_PTR(-EINVAL); + } + + votable = devm_kzalloc(dev, sizeof(struct votable), GFP_KERNEL); + if (!votable) + return ERR_PTR(-ENOMEM); + + votable->dev = dev; + votable->name = name; + votable->num_clients = num_clients; + votable->callback = callback; + votable->type = votable_type; + votable->default_result = default_result; + mutex_init(&votable->vote_lock); + + /* + * Because effective_result and client states are invalid + * before the first vote, initialize them to -EINVAL + */ + votable->effective_result = -EINVAL; + votable->effective_client_id = -EINVAL; + + for (i = 0; i < votable->num_clients; i++) + votable->votes[i].state = -EINVAL; + + return votable; +} diff --git a/drivers/power/qcom-charger/pmic-voter.h b/drivers/power/qcom-charger/pmic-voter.h new file mode 100644 index 000000000000..30cfecad4287 --- /dev/null +++ b/drivers/power/qcom-charger/pmic-voter.h @@ -0,0 +1,46 @@ +/* Copyright (c) 2015 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 __PMIC_VOTER_H +#define __PMIC_VOTER_H + +#include <linux/mutex.h> + +struct votable; + +enum votable_type { + VOTE_MIN, + VOTE_MAX, + VOTE_SET_ANY, + NUM_VOTABLE_TYPES, +}; + +int get_client_vote(struct votable *votable, int client_id); +int get_client_vote_locked(struct votable *votable, int client_id); +int get_effective_result(struct votable *votable); +int get_effective_result_locked(struct votable *votable); +int get_effective_client_id(struct votable *votable); +int get_effective_client_id_locked(struct votable *votable); +int vote(struct votable *votable, int client_id, bool state, int val); +struct votable *create_votable(struct device *dev, const char *name, + int votable_type, int num_clients, + int default_result, + int (*callback)(struct device *dev, + int effective_result, + int effective_client, + int last_result, + int last_client) + ); +void lock_votable(struct votable *votable); +void unlock_votable(struct votable *votable); + +#endif /* __PMIC_VOTER_H */ diff --git a/drivers/power/qcom-charger/qpnp-fg.c b/drivers/power/qcom-charger/qpnp-fg.c new file mode 100644 index 000000000000..8660c1f8c3f5 --- /dev/null +++ b/drivers/power/qcom-charger/qpnp-fg.c @@ -0,0 +1,7020 @@ +/* Copyright (c) 2014-2016, 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) "FG: %s: " fmt, __func__ + +#include <linux/atomic.h> +#include <linux/delay.h> +#include <linux/kernel.h> +#include <linux/regmap.h> +#include <linux/of.h> +#include <linux/rtc.h> +#include <linux/err.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/init.h> +#include <linux/spmi.h> +#include <linux/platform_device.h> +#include <linux/of_irq.h> +#include <linux/interrupt.h> +#include <linux/bitops.h> +#include <linux/types.h> +#include <linux/module.h> +#include <linux/ktime.h> +#include <linux/power_supply.h> +#include <linux/of_batterydata.h> +#include <linux/string_helpers.h> +#include <linux/alarmtimer.h> +#include <linux/qpnp/qpnp-revid.h> + +/* Register offsets */ + +/* Interrupt offsets */ +#define INT_RT_STS(base) (base + 0x10) +#define INT_EN_CLR(base) (base + 0x16) + +/* SPMI Register offsets */ +#define SOC_MONOTONIC_SOC 0x09 +#define SOC_BOOT_MOD 0x50 +#define SOC_RESTART 0x51 + +#define REG_OFFSET_PERP_SUBTYPE 0x05 + +/* RAM register offsets */ +#define RAM_OFFSET 0x400 + +/* Bit/Mask definitions */ +#define FULL_PERCENT 0xFF +#define MAX_TRIES_SOC 5 +#define MA_MV_BIT_RES 39 +#define MSB_SIGN BIT(7) +#define IBAT_VBAT_MASK 0x7F +#define NO_OTP_PROF_RELOAD BIT(6) +#define REDO_FIRST_ESTIMATE BIT(3) +#define RESTART_GO BIT(0) +#define THERM_DELAY_MASK 0xE0 + +/* SUBTYPE definitions */ +#define FG_SOC 0x9 +#define FG_BATT 0xA +#define FG_ADC 0xB +#define FG_MEMIF 0xC + +#define QPNP_FG_DEV_NAME "qcom,qpnp-fg" +#define MEM_IF_TIMEOUT_MS 5000 +#define BUCKET_COUNT 8 +#define BUCKET_SOC_PCT (256 / BUCKET_COUNT) + +#define BCL_MA_TO_ADC(_current, _adc_val) { \ + _adc_val = (u8)((_current) * 100 / 976); \ +} + +/* Debug Flag Definitions */ +enum { + FG_SPMI_DEBUG_WRITES = BIT(0), /* Show SPMI writes */ + FG_SPMI_DEBUG_READS = BIT(1), /* Show SPMI reads */ + FG_IRQS = BIT(2), /* Show interrupts */ + FG_MEM_DEBUG_WRITES = BIT(3), /* Show SRAM writes */ + FG_MEM_DEBUG_READS = BIT(4), /* Show SRAM reads */ + FG_POWER_SUPPLY = BIT(5), /* Show POWER_SUPPLY */ + FG_STATUS = BIT(6), /* Show FG status changes */ + FG_AGING = BIT(7), /* Show FG aging algorithm */ +}; + +/* PMIC REVISIONS */ +#define REVID_RESERVED 0 +#define REVID_VARIANT 1 +#define REVID_ANA_MAJOR 2 +#define REVID_DIG_MAJOR 3 + +enum dig_major { + DIG_REV_1 = 0x1, + DIG_REV_2 = 0x2, + DIG_REV_3 = 0x3, +}; + +enum pmic_subtype { + PMI8994 = 10, + PMI8950 = 17, + PMI8996 = 19, + PMI8937 = 55, +}; + +enum wa_flags { + IADC_GAIN_COMP_WA = BIT(0), + USE_CC_SOC_REG = BIT(1), + PULSE_REQUEST_WA = BIT(2), + BCL_HI_POWER_FOR_CHGLED_WA = BIT(3) +}; + +enum current_sense_type { + INTERNAL_CURRENT_SENSE, + EXTERNAL_CURRENT_SENSE, +}; + +struct fg_mem_setting { + u16 address; + u8 offset; + int value; +}; + +struct fg_mem_data { + u16 address; + u8 offset; + unsigned int len; + int value; +}; + +struct fg_learning_data { + int64_t cc_uah; + int64_t learned_cc_uah; + int init_cc_pc_val; + bool active; + bool feedback_on; + struct mutex learning_lock; + ktime_t time_stamp; + /* configuration properties */ + int max_start_soc; + int max_increment; + int max_decrement; + int min_temp; + int max_temp; + int vbat_est_thr_uv; +}; + +struct fg_rslow_data { + u8 rslow_cfg; + u8 rslow_thr; + u8 rs_to_rslow[2]; + u8 rslow_comp[4]; + uint32_t chg_rs_to_rslow; + uint32_t chg_rslow_comp_c1; + uint32_t chg_rslow_comp_c2; + uint32_t chg_rslow_comp_thr; + bool active; + struct mutex lock; +}; + +struct fg_cyc_ctr_data { + bool en; + bool started[BUCKET_COUNT]; + u16 count[BUCKET_COUNT]; + u8 last_soc[BUCKET_COUNT]; + int id; + struct mutex lock; +}; + +struct fg_iadc_comp_data { + u8 dfl_gain_reg[2]; + bool gain_active; + int64_t dfl_gain; +}; + +struct fg_cc_soc_data { + int init_sys_soc; + int init_cc_soc; + int full_capacity; + int delta_soc; +}; + +/* FG_MEMIF setting index */ +enum fg_mem_setting_index { + FG_MEM_SOFT_COLD = 0, + FG_MEM_SOFT_HOT, + FG_MEM_HARD_COLD, + FG_MEM_HARD_HOT, + FG_MEM_RESUME_SOC, + FG_MEM_BCL_LM_THRESHOLD, + FG_MEM_BCL_MH_THRESHOLD, + FG_MEM_TERM_CURRENT, + FG_MEM_CHG_TERM_CURRENT, + FG_MEM_IRQ_VOLT_EMPTY, + FG_MEM_CUTOFF_VOLTAGE, + FG_MEM_VBAT_EST_DIFF, + FG_MEM_DELTA_SOC, + FG_MEM_BATT_LOW, + FG_MEM_THERM_DELAY, + FG_MEM_SETTING_MAX, +}; + +/* FG_MEMIF data index */ +enum fg_mem_data_index { + FG_DATA_BATT_TEMP = 0, + FG_DATA_OCV, + FG_DATA_VOLTAGE, + FG_DATA_CURRENT, + FG_DATA_BATT_ESR, + FG_DATA_BATT_ESR_COUNT, + FG_DATA_BATT_SOC, + FG_DATA_CC_CHARGE, + FG_DATA_VINT_ERR, + FG_DATA_CPRED_VOLTAGE, + /* values below this only gets read once per profile reload */ + FG_DATA_BATT_ID, + FG_DATA_BATT_ID_INFO, + FG_DATA_MAX, +}; + +#define SETTING(_idx, _address, _offset, _value) \ + [FG_MEM_##_idx] = { \ + .address = _address, \ + .offset = _offset, \ + .value = _value, \ + } \ + +static struct fg_mem_setting settings[FG_MEM_SETTING_MAX] = { + /* ID Address, Offset, Value*/ + SETTING(SOFT_COLD, 0x454, 0, 100), + SETTING(SOFT_HOT, 0x454, 1, 400), + SETTING(HARD_COLD, 0x454, 2, 50), + SETTING(HARD_HOT, 0x454, 3, 450), + SETTING(RESUME_SOC, 0x45C, 1, 0), + SETTING(BCL_LM_THRESHOLD, 0x47C, 2, 50), + SETTING(BCL_MH_THRESHOLD, 0x47C, 3, 752), + SETTING(TERM_CURRENT, 0x40C, 2, 250), + SETTING(CHG_TERM_CURRENT, 0x4F8, 2, 250), + SETTING(IRQ_VOLT_EMPTY, 0x458, 3, 3100), + SETTING(CUTOFF_VOLTAGE, 0x40C, 0, 3200), + SETTING(VBAT_EST_DIFF, 0x000, 0, 30), + SETTING(DELTA_SOC, 0x450, 3, 1), + SETTING(BATT_LOW, 0x458, 0, 4200), + SETTING(THERM_DELAY, 0x4AC, 3, 0), +}; + +#define DATA(_idx, _address, _offset, _length, _value) \ + [FG_DATA_##_idx] = { \ + .address = _address, \ + .offset = _offset, \ + .len = _length, \ + .value = _value, \ + } \ + +static struct fg_mem_data fg_data[FG_DATA_MAX] = { + /* ID Address, Offset, Length, Value*/ + DATA(BATT_TEMP, 0x550, 2, 2, -EINVAL), + DATA(OCV, 0x588, 3, 2, -EINVAL), + DATA(VOLTAGE, 0x5CC, 1, 2, -EINVAL), + DATA(CURRENT, 0x5CC, 3, 2, -EINVAL), + DATA(BATT_ESR, 0x554, 2, 2, -EINVAL), + DATA(BATT_ESR_COUNT, 0x558, 2, 2, -EINVAL), + DATA(BATT_SOC, 0x56C, 1, 3, -EINVAL), + DATA(CC_CHARGE, 0x570, 0, 4, -EINVAL), + DATA(VINT_ERR, 0x560, 0, 4, -EINVAL), + DATA(CPRED_VOLTAGE, 0x540, 0, 2, -EINVAL), + DATA(BATT_ID, 0x594, 1, 1, -EINVAL), + DATA(BATT_ID_INFO, 0x594, 3, 1, -EINVAL), +}; + +static int fg_debug_mask; +module_param_named( + debug_mask, fg_debug_mask, int, S_IRUSR | S_IWUSR +); + +static int fg_sense_type = -EINVAL; +static int fg_restart; + +static int fg_est_dump; +module_param_named( + first_est_dump, fg_est_dump, int, S_IRUSR | S_IWUSR +); + +static char *fg_batt_type; +module_param_named( + battery_type, fg_batt_type, charp, S_IRUSR | S_IWUSR +); + +static int fg_sram_update_period_ms = 30000; +module_param_named( + sram_update_period_ms, fg_sram_update_period_ms, int, S_IRUSR | S_IWUSR +); + +struct fg_irq { + int irq; + unsigned long disabled; +}; + +enum fg_soc_irq { + HIGH_SOC, + LOW_SOC, + FULL_SOC, + EMPTY_SOC, + DELTA_SOC, + FIRST_EST_DONE, + SW_FALLBK_OCV, + SW_FALLBK_NEW_BATT, + FG_SOC_IRQ_COUNT, +}; + +enum fg_batt_irq { + JEITA_SOFT_COLD, + JEITA_SOFT_HOT, + VBATT_LOW, + BATT_IDENTIFIED, + BATT_ID_REQ, + BATTERY_UNKNOWN, + BATT_MISSING, + BATT_MATCH, + FG_BATT_IRQ_COUNT, +}; + +enum fg_mem_if_irq { + FG_MEM_AVAIL, + TA_RCVRY_SUG, + FG_MEM_IF_IRQ_COUNT, +}; + +enum fg_batt_aging_mode { + FG_AGING_NONE, + FG_AGING_ESR, + FG_AGING_CC, +}; + +enum register_type { + MEM_INTF_CFG, + MEM_INTF_CTL, + MEM_INTF_ADDR_LSB, + MEM_INTF_RD_DATA0, + MEM_INTF_WR_DATA0, + MAX_ADDRESS, +}; + +struct register_offset { + u16 address[MAX_ADDRESS]; +}; + +static struct register_offset offset[] = { + [0] = { + /* CFG CTL LSB RD0 WD0 */ + .address = {0x40, 0x41, 0x42, 0x4C, 0x48}, + }, + [1] = { + /* CFG CTL LSB RD0 WD0 */ + .address = {0x50, 0x51, 0x61, 0x67, 0x63}, + }, +}; + +#define MEM_INTF_CFG(chip) \ + ((chip)->mem_base + (chip)->offset[MEM_INTF_CFG]) +#define MEM_INTF_CTL(chip) \ + ((chip)->mem_base + (chip)->offset[MEM_INTF_CTL]) +#define MEM_INTF_ADDR_LSB(chip) \ + ((chip)->mem_base + (chip)->offset[MEM_INTF_ADDR_LSB]) +#define MEM_INTF_RD_DATA0(chip) \ + ((chip)->mem_base + (chip)->offset[MEM_INTF_RD_DATA0]) +#define MEM_INTF_WR_DATA0(chip) \ + ((chip)->mem_base + (chip)->offset[MEM_INTF_WR_DATA0]) + +struct fg_wakeup_source { + struct wakeup_source source; + unsigned long enabled; +}; + +static void fg_stay_awake(struct fg_wakeup_source *source) +{ + if (!__test_and_set_bit(0, &source->enabled)) { + __pm_stay_awake(&source->source); + pr_debug("enabled source %s\n", source->source.name); + } +} + +static void fg_relax(struct fg_wakeup_source *source) +{ + if (__test_and_clear_bit(0, &source->enabled)) { + __pm_relax(&source->source); + pr_debug("disabled source %s\n", source->source.name); + } +} + +#define THERMAL_COEFF_N_BYTES 6 +struct fg_chip { + struct device *dev; + struct platform_device *pdev; + struct regmap *regmap; + u8 pmic_subtype; + u8 pmic_revision[4]; + u8 revision[4]; + u16 soc_base; + u16 batt_base; + u16 mem_base; + u16 vbat_adc_addr; + u16 ibat_adc_addr; + u16 tp_rev_addr; + u32 wa_flag; + atomic_t memif_user_cnt; + struct fg_irq soc_irq[FG_SOC_IRQ_COUNT]; + struct fg_irq batt_irq[FG_BATT_IRQ_COUNT]; + struct fg_irq mem_irq[FG_MEM_IF_IRQ_COUNT]; + struct completion sram_access_granted; + struct completion sram_access_revoked; + struct completion batt_id_avail; + struct completion first_soc_done; + struct power_supply *bms_psy; + struct power_supply_desc bms_psy_d; + struct mutex rw_lock; + struct mutex sysfs_restart_lock; + struct delayed_work batt_profile_init; + struct work_struct dump_sram; + struct work_struct status_change_work; + struct work_struct cycle_count_work; + struct work_struct battery_age_work; + struct work_struct update_esr_work; + struct work_struct set_resume_soc_work; + struct work_struct rslow_comp_work; + struct work_struct sysfs_restart_work; + struct work_struct init_work; + struct work_struct charge_full_work; + struct work_struct gain_comp_work; + struct work_struct bcl_hi_power_work; + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct power_supply *dc_psy; + struct fg_wakeup_source memif_wakeup_source; + struct fg_wakeup_source profile_wakeup_source; + struct fg_wakeup_source empty_check_wakeup_source; + struct fg_wakeup_source resume_soc_wakeup_source; + struct fg_wakeup_source gain_comp_wakeup_source; + struct fg_wakeup_source capacity_learning_wakeup_source; + bool first_profile_loaded; + struct fg_wakeup_source update_temp_wakeup_source; + struct fg_wakeup_source update_sram_wakeup_source; + bool fg_restarting; + bool profile_loaded; + bool use_otp_profile; + bool battery_missing; + bool power_supply_registered; + bool sw_rbias_ctrl; + bool use_thermal_coefficients; + bool esr_strict_filter; + bool soc_empty; + bool charge_done; + bool resume_soc_lowered; + bool vbat_low_irq_enabled; + bool charge_full; + bool hold_soc_while_full; + bool input_present; + bool otg_present; + bool safety_timer_expired; + bool bad_batt_detection_en; + bool bcl_lpm_disabled; + bool charging_disabled; + struct delayed_work update_jeita_setting; + struct delayed_work update_sram_data; + struct delayed_work update_temp_work; + struct delayed_work check_empty_work; + char *batt_profile; + u8 thermal_coefficients[THERMAL_COEFF_N_BYTES]; + u32 cc_cv_threshold_mv; + unsigned int batt_profile_len; + unsigned int batt_max_voltage_uv; + const char *batt_type; + const char *batt_psy_name; + unsigned long last_sram_update_time; + unsigned long last_temp_update_time; + int64_t ocv_coeffs[12]; + int64_t cutoff_voltage; + int evaluation_current; + int ocv_junction_p1p2; + int ocv_junction_p2p3; + int nom_cap_uah; + int actual_cap_uah; + int status; + int prev_status; + int health; + enum fg_batt_aging_mode batt_aging_mode; + /* capacity learning */ + struct fg_learning_data learning_data; + struct alarm fg_cap_learning_alarm; + struct work_struct fg_cap_learning_work; + struct fg_cc_soc_data sw_cc_soc_data; + /* rslow compensation */ + struct fg_rslow_data rslow_comp; + /* cycle counter */ + struct fg_cyc_ctr_data cyc_ctr; + /* iadc compensation */ + struct fg_iadc_comp_data iadc_comp_data; + /* interleaved memory access */ + u16 *offset; + bool ima_supported; + bool init_done; + /* jeita hysteresis */ + bool jeita_hysteresis_support; + bool batt_hot; + bool batt_cold; + int cold_hysteresis; + int hot_hysteresis; + /* ESR pulse tuning */ + struct fg_wakeup_source esr_extract_wakeup_source; + struct work_struct esr_extract_config_work; + bool esr_extract_disabled; + bool imptr_pulse_slow_en; + bool esr_pulse_tune_en; +}; + +/* FG_MEMIF DEBUGFS structures */ +#define ADDR_LEN 4 /* 3 byte address + 1 space character */ +#define CHARS_PER_ITEM 3 /* Format is 'XX ' */ +#define ITEMS_PER_LINE 4 /* 4 data items per line */ +#define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * CHARS_PER_ITEM) + 1) +#define MAX_REG_PER_TRANSACTION (8) + +static const char *DFS_ROOT_NAME = "fg_memif"; +static const mode_t DFS_MODE = S_IRUSR | S_IWUSR; +static const char *default_batt_type = "Unknown Battery"; +static const char *loading_batt_type = "Loading Battery Data"; +static const char *missing_batt_type = "Disconnected Battery"; + +/* Log buffer */ +struct fg_log_buffer { + size_t rpos; /* Current 'read' position in buffer */ + size_t wpos; /* Current 'write' position in buffer */ + size_t len; /* Length of the buffer */ + char data[0]; /* Log buffer */ +}; + +/* transaction parameters */ +struct fg_trans { + u32 cnt; /* Number of bytes to read */ + u16 addr; /* 12-bit address in SRAM */ + u32 offset; /* Offset of last read data + byte offset */ + struct fg_chip *chip; + struct fg_log_buffer *log; /* log buffer */ + u8 *data; /* fg data that is read */ +}; + +struct fg_dbgfs { + u32 cnt; + u32 addr; + struct fg_chip *chip; + struct dentry *root; + struct mutex lock; + struct debugfs_blob_wrapper help_msg; +}; + +static struct fg_dbgfs dbgfs_data = { + .lock = __MUTEX_INITIALIZER(dbgfs_data.lock), + .help_msg = { + .data = +"FG Debug-FS support\n" +"\n" +"Hierarchy schema:\n" +"/sys/kernel/debug/fg_memif\n" +" /help -- Static help text\n" +" /address -- Starting register address for reads or writes\n" +" /count -- Number of registers to read (only used for reads)\n" +" /data -- Initiates the SRAM read (formatted output)\n" +"\n", + }, +}; + +static const struct of_device_id fg_match_table[] = { + { .compatible = QPNP_FG_DEV_NAME, }, + {} +}; + +static char *fg_supplicants[] = { + "battery", + "bcl", + "fg_adc" +}; + +#define DEBUG_PRINT_BUFFER_SIZE 64 +static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len) +{ + int pos = 0; + int i; + + for (i = 0; i < buf_len; i++) { + pos += scnprintf(str + pos, str_len - pos, "%02X", buf[i]); + if (i < buf_len - 1) + pos += scnprintf(str + pos, str_len - pos, " "); + } +} + +static int fg_write(struct fg_chip *chip, u8 *val, u16 addr, int len) +{ + int rc = 0; + struct platform_device *pdev = chip->pdev; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + if ((addr & 0xff00) == 0) { + pr_err("addr cannot be zero base=0x%02x sid=0x%02x rc=%d\n", + addr, to_spmi_device(pdev->dev.parent)->usid, rc); + return -EINVAL; + } + + rc = regmap_bulk_write(chip->regmap, addr, val, len); + if (rc) { + pr_err("write failed addr=0x%02x sid=0x%02x rc=%d\n", + addr, to_spmi_device(pdev->dev.parent)->usid, rc); + return rc; + } + + if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_WRITES)) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len); + pr_info("write(0x%04X), sid=%d, len=%d; %s\n", + addr, to_spmi_device(pdev->dev.parent)->usid, len, + str); + } + + return rc; +} + +static int fg_read(struct fg_chip *chip, u8 *val, u16 addr, int len) +{ + int rc = 0; + struct platform_device *pdev = chip->pdev; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + if ((addr & 0xff00) == 0) { + pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n", + addr, to_spmi_device(pdev->dev.parent)->usid, rc); + return -EINVAL; + } + + rc = regmap_bulk_read(chip->regmap, addr, val, len); + if (rc) { + pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n", addr, + to_spmi_device(pdev->dev.parent)->usid, rc); + return rc; + } + + if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_READS)) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len); + pr_info("read(0x%04x), sid=%d, len=%d; %s\n", + addr, to_spmi_device(pdev->dev.parent)->usid, len, + str); + } + + return rc; +} + +static int fg_masked_write(struct fg_chip *chip, u16 addr, + u8 mask, u8 val, int len) +{ + int rc; + + rc = regmap_update_bits(chip->regmap, addr, mask, val); + if (rc) { + pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc); + return rc; + } + + return rc; +} + +#define RIF_MEM_ACCESS_REQ BIT(7) +static int fg_check_rif_mem_access(struct fg_chip *chip, bool *status) +{ + int rc; + u8 mem_if_sts; + + rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1); + if (rc) { + pr_err("failed to read rif_mem status rc=%d\n", rc); + return rc; + } + + *status = mem_if_sts & RIF_MEM_ACCESS_REQ; + return 0; +} + +static bool fg_check_sram_access(struct fg_chip *chip) +{ + int rc; + u8 mem_if_sts; + bool rif_mem_sts = false; + + rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) { + pr_err("failed to read mem status rc=%d\n", rc); + return false; + } + + if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0) + return false; + + rc = fg_check_rif_mem_access(chip, &rif_mem_sts); + if (rc) + return false; + + return rif_mem_sts; +} + +static inline int fg_assert_sram_access(struct fg_chip *chip) +{ + int rc; + u8 mem_if_sts; + + rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) { + pr_err("failed to read mem status rc=%d\n", rc); + return rc; + } + + if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0) { + pr_err("mem_avail not high: %02x\n", mem_if_sts); + return -EINVAL; + } + + rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1); + if (rc) { + pr_err("failed to read mem status rc=%d\n", rc); + return rc; + } + + if ((mem_if_sts & RIF_MEM_ACCESS_REQ) == 0) { + pr_err("mem_avail not high: %02x\n", mem_if_sts); + return -EINVAL; + } + + return 0; +} + +#define INTF_CTL_BURST BIT(7) +#define INTF_CTL_WR_EN BIT(6) +static int fg_config_access(struct fg_chip *chip, bool write, + bool burst) +{ + int rc; + u8 intf_ctl = 0; + + intf_ctl = (write ? INTF_CTL_WR_EN : 0) | (burst ? INTF_CTL_BURST : 0); + + rc = fg_write(chip, &intf_ctl, MEM_INTF_CTL(chip), 1); + if (rc) { + pr_err("failed to set mem access bit\n"); + return -EIO; + } + + return rc; +} + +static int fg_req_and_wait_access(struct fg_chip *chip, int timeout) +{ + int rc = 0, ret = 0; + bool tried_again = false; + + if (!fg_check_sram_access(chip)) { + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), + RIF_MEM_ACCESS_REQ, RIF_MEM_ACCESS_REQ, 1); + if (rc) { + pr_err("failed to set mem access bit\n"); + return -EIO; + } + fg_stay_awake(&chip->memif_wakeup_source); + } + +wait: + /* Wait for MEM_AVAIL IRQ. */ + ret = wait_for_completion_interruptible_timeout( + &chip->sram_access_granted, + msecs_to_jiffies(timeout)); + /* If we were interrupted wait again one more time. */ + if (ret == -ERESTARTSYS && !tried_again) { + tried_again = true; + goto wait; + } else if (ret <= 0) { + rc = -ETIMEDOUT; + pr_err("transaction timed out rc=%d\n", rc); + return rc; + } + + return rc; +} + +static int fg_release_access(struct fg_chip *chip) +{ + int rc; + + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), + RIF_MEM_ACCESS_REQ, 0, 1); + fg_relax(&chip->memif_wakeup_source); + reinit_completion(&chip->sram_access_granted); + + return rc; +} + +static void fg_release_access_if_necessary(struct fg_chip *chip) +{ + mutex_lock(&chip->rw_lock); + if (atomic_sub_return(1, &chip->memif_user_cnt) <= 0) { + fg_release_access(chip); + } + mutex_unlock(&chip->rw_lock); +} + +/* + * fg_mem_lock disallows the fuel gauge to release access until it has been + * released. + * + * an equal number of calls must be made to fg_mem_release for the fuel gauge + * driver to release the sram access. + */ +static void fg_mem_lock(struct fg_chip *chip) +{ + mutex_lock(&chip->rw_lock); + atomic_add_return(1, &chip->memif_user_cnt); + mutex_unlock(&chip->rw_lock); +} + +static void fg_mem_release(struct fg_chip *chip) +{ + fg_release_access_if_necessary(chip); +} + +static int fg_set_ram_addr(struct fg_chip *chip, u16 *address) +{ + int rc; + + rc = fg_write(chip, (u8 *) address, + chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], 2); + if (rc) { + pr_err("spmi write failed: addr=%03X, rc=%d\n", + chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], rc); + return rc; + } + + return rc; +} + +#define BUF_LEN 4 +static int fg_sub_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len, + int offset) +{ + int rc, total_len; + u8 *rd_data = val; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + rc = fg_config_access(chip, 0, (len > 4)); + if (rc) + return rc; + + rc = fg_set_ram_addr(chip, &address); + if (rc) + return rc; + + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("length %d addr=%02X\n", len, address); + + total_len = len; + while (len > 0) { + if (!offset) { + rc = fg_read(chip, rd_data, MEM_INTF_RD_DATA0(chip), + min(len, BUF_LEN)); + } else { + rc = fg_read(chip, rd_data, + MEM_INTF_RD_DATA0(chip) + offset, + min(len, BUF_LEN - offset)); + + /* manually set address to allow continous reads */ + address += BUF_LEN; + + rc = fg_set_ram_addr(chip, &address); + if (rc) + return rc; + } + if (rc) { + pr_err("spmi read failed: addr=%03x, rc=%d\n", + MEM_INTF_RD_DATA0(chip) + offset, rc); + return rc; + } + rd_data += (BUF_LEN - offset); + len -= (BUF_LEN - offset); + offset = 0; + } + + if (fg_debug_mask & FG_MEM_DEBUG_READS) { + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len); + pr_info("data: %s\n", str); + } + return rc; +} + +static int fg_conventional_mem_read(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset, bool keep_access) +{ + int rc = 0, user_cnt = 0, orig_address = address; + + if (offset > 3) { + pr_err("offset too large %d\n", offset); + return -EINVAL; + } + + address = ((orig_address + offset) / 4) * 4; + offset = (orig_address + offset) % 4; + + user_cnt = atomic_add_return(1, &chip->memif_user_cnt); + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("user_cnt %d\n", user_cnt); + mutex_lock(&chip->rw_lock); + if (!fg_check_sram_access(chip)) { + rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS); + if (rc) + goto out; + } + + rc = fg_sub_mem_read(chip, val, address, len, offset); + +out: + user_cnt = atomic_sub_return(1, &chip->memif_user_cnt); + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("user_cnt %d\n", user_cnt); + + fg_assert_sram_access(chip); + + if (!keep_access && (user_cnt == 0) && !rc) { + rc = fg_release_access(chip); + if (rc) { + pr_err("failed to set mem access bit\n"); + rc = -EIO; + } + } + + mutex_unlock(&chip->rw_lock); + return rc; +} + +static int fg_conventional_mem_write(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset, bool keep_access) +{ + int rc = 0, user_cnt = 0, sublen; + bool access_configured = false; + u8 *wr_data = val, word[4]; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + if (address < RAM_OFFSET) + return -EINVAL; + + if (offset > 3) + return -EINVAL; + + address = ((address + offset) / 4) * 4; + offset = (address + offset) % 4; + + user_cnt = atomic_add_return(1, &chip->memif_user_cnt); + if (fg_debug_mask & FG_MEM_DEBUG_WRITES) + pr_info("user_cnt %d\n", user_cnt); + mutex_lock(&chip->rw_lock); + if (!fg_check_sram_access(chip)) { + rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS); + if (rc) + goto out; + } + + if (fg_debug_mask & FG_MEM_DEBUG_WRITES) { + pr_info("length %d addr=%02X offset=%d\n", + len, address, offset); + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, wr_data, len); + pr_info("writing: %s\n", str); + } + + while (len > 0) { + if (offset != 0) { + sublen = min(4 - offset, len); + rc = fg_sub_mem_read(chip, word, address, 4, 0); + if (rc) + goto out; + memcpy(word + offset, wr_data, sublen); + /* configure access as burst if more to write */ + rc = fg_config_access(chip, 1, (len - sublen) > 0); + if (rc) + goto out; + rc = fg_set_ram_addr(chip, &address); + if (rc) + goto out; + offset = 0; + access_configured = true; + } else if (len >= 4) { + if (!access_configured) { + rc = fg_config_access(chip, 1, len > 4); + if (rc) + goto out; + rc = fg_set_ram_addr(chip, &address); + if (rc) + goto out; + access_configured = true; + } + sublen = 4; + memcpy(word, wr_data, 4); + } else if (len > 0 && len < 4) { + sublen = len; + rc = fg_sub_mem_read(chip, word, address, 4, 0); + if (rc) + goto out; + memcpy(word, wr_data, sublen); + rc = fg_config_access(chip, 1, 0); + if (rc) + goto out; + rc = fg_set_ram_addr(chip, &address); + if (rc) + goto out; + access_configured = true; + } else { + pr_err("Invalid length: %d\n", len); + break; + } + rc = fg_write(chip, word, MEM_INTF_WR_DATA0(chip), 4); + if (rc) { + pr_err("spmi write failed: addr=%03x, rc=%d\n", + MEM_INTF_WR_DATA0(chip), rc); + goto out; + } + len -= sublen; + wr_data += sublen; + address += 4; + } + +out: + user_cnt = atomic_sub_return(1, &chip->memif_user_cnt); + if (fg_debug_mask & FG_MEM_DEBUG_WRITES) + pr_info("user_cnt %d\n", user_cnt); + + fg_assert_sram_access(chip); + + if (!keep_access && (user_cnt == 0) && !rc) { + rc = fg_release_access(chip); + if (rc) { + pr_err("failed to set mem access bit\n"); + rc = -EIO; + } + } + + mutex_unlock(&chip->rw_lock); + return rc; +} + +#define MEM_INTF_IMA_CFG 0x52 +#define MEM_INTF_IMA_OPR_STS 0x54 +#define MEM_INTF_IMA_ERR_STS 0x5F +#define MEM_INTF_IMA_EXP_STS 0x55 +#define MEM_INTF_IMA_HW_STS 0x56 +#define MEM_INTF_IMA_BYTE_EN 0x60 +#define IMA_ADDR_STBL_ERR BIT(7) +#define IMA_WR_ACS_ERR BIT(6) +#define IMA_RD_ACS_ERR BIT(5) +#define IMA_IACS_CLR BIT(2) +#define IMA_IACS_RDY BIT(1) +static int fg_check_ima_exception(struct fg_chip *chip) +{ + int rc = 0, ret = 0; + u8 err_sts, exp_sts = 0, hw_sts = 0; + + rc = fg_read(chip, &err_sts, + chip->mem_base + MEM_INTF_IMA_ERR_STS, 1); + if (rc) { + pr_err("failed to read beat count rc=%d\n", rc); + return rc; + } + + if (err_sts & (IMA_ADDR_STBL_ERR | IMA_WR_ACS_ERR | IMA_RD_ACS_ERR)) { + u8 temp; + + fg_read(chip, &exp_sts, + chip->mem_base + MEM_INTF_IMA_EXP_STS, 1); + fg_read(chip, &hw_sts, + chip->mem_base + MEM_INTF_IMA_HW_STS, 1); + pr_err("IMA access failed ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n", + err_sts, exp_sts, hw_sts); + rc = err_sts; + + /* clear the error */ + ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + IMA_IACS_CLR, IMA_IACS_CLR, 1); + temp = 0x4; + ret |= fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1); + temp = 0x0; + ret |= fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1); + ret |= fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1); + ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG, + IMA_IACS_CLR, 0, 1); + if (!ret) + return -EAGAIN; + else + pr_err("Error clearing IMA exception ret=%d\n", ret); + } + + return rc; +} + +static int fg_check_iacs_ready(struct fg_chip *chip) +{ + int rc = 0, timeout = 250; + u8 ima_opr_sts = 0; + + /* + * Additional delay to make sure IACS ready bit is set after + * Read/Write operation. + */ + + usleep_range(30, 35); + while (1) { + rc = fg_read(chip, &ima_opr_sts, + chip->mem_base + MEM_INTF_IMA_OPR_STS, 1); + if (!rc && (ima_opr_sts & IMA_IACS_RDY)) { + break; + } else { + if (!(--timeout) || rc) + break; + /* delay for iacs_ready to be asserted */ + usleep_range(5000, 7000); + } + } + + if (!timeout || rc) { + pr_err("IACS_RDY not set\n"); + /* perform IACS_CLR sequence */ + fg_check_ima_exception(chip); + return -EBUSY; + } + + return 0; +} + +#define IACS_SLCT BIT(5) +static int __fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, + u16 address, int offset, int len) +{ + int rc = 0, i; + u8 *word = val, byte_enable = 0, num_bytes = 0; + + if (fg_debug_mask & FG_MEM_DEBUG_WRITES) + pr_info("length %d addr=%02X offset=%d\n", + len, address, offset); + + while (len > 0) { + num_bytes = (offset + len) > BUF_LEN ? + (BUF_LEN - offset) : len; + /* write to byte_enable */ + for (i = offset; i < (offset + num_bytes); i++) + byte_enable |= BIT(i); + + rc = fg_write(chip, &byte_enable, + chip->mem_base + MEM_INTF_IMA_BYTE_EN, 1); + if (rc) { + pr_err("Unable to write to byte_en_reg rc=%d\n", + rc); + return rc; + } + /* write data */ + rc = fg_write(chip, word, MEM_INTF_WR_DATA0(chip) + offset, + num_bytes); + if (rc) { + pr_err("spmi write failed: addr=%03x, rc=%d\n", + MEM_INTF_WR_DATA0(chip) + offset, rc); + return rc; + } + /* + * The last-byte WR_DATA3 starts the write transaction. + * Write a dummy value to WR_DATA3 if it does not have + * valid data. This dummy data is not written to the + * SRAM as byte_en for WR_DATA3 is not set. + */ + if (!(byte_enable & BIT(3))) { + u8 dummy_byte = 0x0; + rc = fg_write(chip, &dummy_byte, + MEM_INTF_WR_DATA0(chip) + 3, 1); + if (rc) { + pr_err("Unable to write dummy-data to WR_DATA3 rc=%d\n", + rc); + return rc; + } + } + + rc = fg_check_iacs_ready(chip); + if (rc) { + pr_debug("IACS_RDY failed rc=%d\n", rc); + return rc; + } + + /* check for error condition */ + rc = fg_check_ima_exception(chip); + if (rc) { + pr_err("IMA transaction failed rc=%d", rc); + return rc; + } + + word += num_bytes; + len -= num_bytes; + offset = byte_enable = 0; + } + + return rc; +} + +static int __fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address, + int offset, int len) +{ + int rc = 0, total_len; + u8 *rd_data = val, num_bytes; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("length %d addr=%02X\n", len, address); + + total_len = len; + while (len > 0) { + num_bytes = (offset + len) > BUF_LEN ? (BUF_LEN - offset) : len; + rc = fg_read(chip, rd_data, MEM_INTF_RD_DATA0(chip) + offset, + num_bytes); + if (rc) { + pr_err("spmi read failed: addr=%03x, rc=%d\n", + MEM_INTF_RD_DATA0(chip) + offset, rc); + return rc; + } + + rd_data += num_bytes; + len -= num_bytes; + offset = 0; + + rc = fg_check_iacs_ready(chip); + if (rc) { + pr_debug("IACS_RDY failed rc=%d\n", rc); + return rc; + } + + /* check for error condition */ + rc = fg_check_ima_exception(chip); + if (rc) { + pr_err("IMA transaction failed rc=%d", rc); + return rc; + } + + if (len && (len + offset) < BUF_LEN) { + /* move to single mode */ + u8 intr_ctl = 0; + + rc = fg_write(chip, &intr_ctl, MEM_INTF_CTL(chip), 1); + if (rc) { + pr_err("failed to move to single mode rc=%d\n", + rc); + return -EIO; + } + } + } + + if (fg_debug_mask & FG_MEM_DEBUG_READS) { + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len); + pr_info("data: %s\n", str); + } + + return rc; +} + +#define IMA_REQ_ACCESS (IACS_SLCT | RIF_MEM_ACCESS_REQ) +static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val, + u16 address, int len, int offset, int op) +{ + int rc = 0; + bool rif_mem_sts = true; + int time_count = 0; + + while (1) { + rc = fg_check_rif_mem_access(chip, &rif_mem_sts); + if (rc) + return rc; + + if (!rif_mem_sts) + break; + + if (fg_debug_mask & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("RIF_MEM_ACCESS_REQ is not clear yet for IMA_%s\n", + op ? "write" : "read"); + + /* + * Try this no more than 4 times. If RIF_MEM_ACCESS_REQ is not + * clear, then return an error instead of waiting for it again. + */ + if (time_count > 4) { + pr_err("Waited for 1.5 seconds polling RIF_MEM_ACCESS_REQ\n"); + return -ETIMEDOUT; + } + + /* Wait for 4ms before reading RIF_MEM_ACCESS_REQ again */ + usleep_range(4000, 4100); + time_count++; + } + + /* configure for IMA access */ + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), + IMA_REQ_ACCESS, IMA_REQ_ACCESS, 1); + if (rc) { + pr_err("failed to set mem access bit rc = %d\n", rc); + return rc; + } + + /* configure for the read/write single/burst mode */ + rc = fg_config_access(chip, op, (offset + len) > 4); + if (rc) { + pr_err("failed to set configure memory access rc = %d\n", rc); + return rc; + } + + rc = fg_check_iacs_ready(chip); + if (rc) { + pr_debug("IACS_RDY failed rc=%d\n", rc); + return rc; + } + + /* write addresses to the register */ + rc = fg_set_ram_addr(chip, &address); + if (rc) { + pr_err("failed to set SRAM address rc = %d\n", rc); + return rc; + } + + rc = fg_check_iacs_ready(chip); + if (rc) + pr_debug("IACS_RDY failed rc=%d\n", rc); + + return rc; +} + +#define MEM_INTF_FG_BEAT_COUNT 0x57 +#define BEAT_COUNT_MASK 0x0F +#define RETRY_COUNT 3 +static int fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset) +{ + int rc = 0, orig_address = address; + u8 start_beat_count, end_beat_count, count = 0; + bool retry = false; + + if (offset > 3) { + pr_err("offset too large %d\n", offset); + return -EINVAL; + } + + fg_stay_awake(&chip->memif_wakeup_source); + address = ((orig_address + offset) / 4) * 4; + offset = (orig_address + offset) % 4; + + if (address < RAM_OFFSET) { + /* + * OTP memory reads need a conventional memory access, do a + * conventional read when SRAM offset < RAM_OFFSET. + */ + rc = fg_conventional_mem_read(chip, val, address, len, offset, + 0); + if (rc) + pr_err("Failed to read OTP memory %d\n", rc); + goto exit; + } + + mutex_lock(&chip->rw_lock); + +retry: + rc = fg_interleaved_mem_config(chip, val, address, offset, len, 0); + if (rc) { + pr_err("failed to configure SRAM for IMA rc = %d\n", rc); + goto out; + } + + /* read the start beat count */ + rc = fg_read(chip, &start_beat_count, + chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1); + if (rc) { + pr_err("failed to read beat count rc=%d\n", rc); + goto out; + } + + /* read data */ + rc = __fg_interleaved_mem_read(chip, val, address, offset, len); + if (rc) { + if ((rc == -EAGAIN) && (count < RETRY_COUNT)) { + count++; + pr_err("IMA access failed retry_count = %d\n", count); + goto retry; + } else { + pr_err("failed to read SRAM address rc = %d\n", rc); + goto out; + } + } + + /* read the end beat count */ + rc = fg_read(chip, &end_beat_count, + chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1); + if (rc) { + pr_err("failed to read beat count rc=%d\n", rc); + goto out; + } + + start_beat_count &= BEAT_COUNT_MASK; + end_beat_count &= BEAT_COUNT_MASK; + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("Start beat_count = %x End beat_count = %x\n", + start_beat_count, end_beat_count); + if (start_beat_count != end_beat_count) { + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("Beat count do not match - retry transaction\n"); + retry = true; + } +out: + /* Release IMA access */ + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); + if (rc) + pr_err("failed to reset IMA access bit rc = %d\n", rc); + + if (retry) { + retry = false; + goto retry; + } + mutex_unlock(&chip->rw_lock); + +exit: + fg_relax(&chip->memif_wakeup_source); + return rc; +} + +static int fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset) +{ + int rc = 0, orig_address = address; + u8 count = 0; + + if (address < RAM_OFFSET) + return -EINVAL; + + if (offset > 3) { + pr_err("offset too large %d\n", offset); + return -EINVAL; + } + + fg_stay_awake(&chip->memif_wakeup_source); + address = ((orig_address + offset) / 4) * 4; + offset = (orig_address + offset) % 4; + + mutex_lock(&chip->rw_lock); + +retry: + rc = fg_interleaved_mem_config(chip, val, address, offset, len, 1); + if (rc) { + pr_err("failed to xonfigure SRAM for IMA rc = %d\n", rc); + goto out; + } + + /* write data */ + rc = __fg_interleaved_mem_write(chip, val, address, offset, len); + if (rc) { + if ((rc == -EAGAIN) && (count < RETRY_COUNT)) { + count++; + pr_err("IMA access failed retry_count = %d\n", count); + goto retry; + } else { + pr_err("failed to write SRAM address rc = %d\n", rc); + goto out; + } + } + +out: + /* Release IMA access */ + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1); + if (rc) + pr_err("failed to reset IMA access bit rc = %d\n", rc); + + mutex_unlock(&chip->rw_lock); + fg_relax(&chip->memif_wakeup_source); + return rc; +} + +static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset, bool keep_access) +{ + if (chip->ima_supported) + return fg_interleaved_mem_read(chip, val, address, + len, offset); + else + return fg_conventional_mem_read(chip, val, address, + len, offset, keep_access); +} + +static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address, + int len, int offset, bool keep_access) +{ + if (chip->ima_supported) + return fg_interleaved_mem_write(chip, val, address, + len, offset); + else + return fg_conventional_mem_write(chip, val, address, + len, offset, keep_access); +} + +static int fg_mem_masked_write(struct fg_chip *chip, u16 addr, + u8 mask, u8 val, u8 offset) +{ + int rc = 0; + u8 reg[4]; + char str[DEBUG_PRINT_BUFFER_SIZE]; + + rc = fg_mem_read(chip, reg, addr, 4, 0, 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", addr, rc); + return rc; + } + + reg[offset] &= ~mask; + reg[offset] |= val & mask; + + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, reg, 4); + pr_debug("Writing %s address %03x, offset %d\n", str, addr, offset); + + rc = fg_mem_write(chip, reg, addr, 4, 0, 0); + if (rc) { + pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc); + return rc; + } + + return rc; +} + +static int soc_to_setpoint(int soc) +{ + return DIV_ROUND_CLOSEST(soc * 255, 100); +} + +static void batt_to_setpoint_adc(int vbatt_mv, u8 *data) +{ + int val; + /* Battery voltage is an offset from 0 V and LSB is 1/2^15. */ + val = DIV_ROUND_CLOSEST(vbatt_mv * 32768, 5000); + data[0] = val & 0xFF; + data[1] = val >> 8; + return; +} + +static u8 batt_to_setpoint_8b(int vbatt_mv) +{ + int val; + /* Battery voltage is an offset from 2.5 V and LSB is 5/2^9. */ + val = (vbatt_mv - 2500) * 512 / 1000; + return DIV_ROUND_CLOSEST(val, 5); +} + +static u8 therm_delay_to_setpoint(u32 delay_us) +{ + u8 val; + + if (delay_us < 2560) + val = 0; + else if (delay_us > 163840) + val = 7; + else + val = ilog2(delay_us / 10) - 7; + return val << 5; +} + +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); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + rtc_tm_to_time(&tm, now_tm_sec); + +close_time: + rtc_class_close(rtc); + return rc; +} + +#define BATTERY_SOC_REG 0x56C +#define BATTERY_SOC_OFFSET 1 +#define FULL_PERCENT_3B 0xFFFFFF +static int get_battery_soc_raw(struct fg_chip *chip) +{ + int rc; + u8 buffer[3]; + + rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0); + if (rc) { + pr_err("Unable to read battery soc: %d\n", rc); + return 0; + } + return (int)(buffer[2] << 16 | buffer[1] << 8 | buffer[0]); +} + +#define COUNTER_IMPTR_REG 0X558 +#define COUNTER_PULSE_REG 0X55C +#define SOC_FULL_REG 0x564 +#define COUNTER_IMPTR_OFFSET 2 +#define COUNTER_PULSE_OFFSET 0 +#define SOC_FULL_OFFSET 3 +#define ESR_PULSE_RECONFIG_SOC 0xFFF971 +static int fg_configure_soc(struct fg_chip *chip) +{ + u32 batt_soc; + u8 cntr[2] = {0, 0}; + int rc = 0; + + mutex_lock(&chip->rw_lock); + atomic_add_return(1, &chip->memif_user_cnt); + mutex_unlock(&chip->rw_lock); + + /* Read Battery SOC */ + batt_soc = get_battery_soc_raw(chip); + + if (batt_soc > ESR_PULSE_RECONFIG_SOC) { + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info("Configuring soc registers batt_soc: %x\n", + batt_soc); + batt_soc = ESR_PULSE_RECONFIG_SOC; + rc = fg_mem_write(chip, (u8 *)&batt_soc, BATTERY_SOC_REG, 3, + BATTERY_SOC_OFFSET, 1); + if (rc) { + pr_err("failed to write BATT_SOC rc=%d\n", rc); + goto out; + } + + rc = fg_mem_write(chip, (u8 *)&batt_soc, SOC_FULL_REG, 3, + SOC_FULL_OFFSET, 1); + if (rc) { + pr_err("failed to write SOC_FULL rc=%d\n", rc); + goto out; + } + + rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 2, + COUNTER_IMPTR_OFFSET, 1); + if (rc) { + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + goto out; + } + + rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2, + COUNTER_PULSE_OFFSET, 0); + if (rc) + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + } +out: + fg_release_access_if_necessary(chip); + return rc; +} + +#define SOC_EMPTY BIT(3) +static bool fg_is_batt_empty(struct fg_chip *chip) +{ + u8 fg_soc_sts; + int rc; + + rc = fg_read(chip, &fg_soc_sts, + INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + return false; + } + + return (fg_soc_sts & SOC_EMPTY) != 0; +} + +static int get_monotonic_soc_raw(struct fg_chip *chip) +{ + u8 cap[2]; + int rc, tries = 0; + + while (tries < MAX_TRIES_SOC) { + rc = fg_read(chip, cap, + chip->soc_base + SOC_MONOTONIC_SOC, 2); + if (rc) { + pr_err("spmi read failed: addr=%03x, rc=%d\n", + chip->soc_base + SOC_MONOTONIC_SOC, rc); + return rc; + } + + if (cap[0] == cap[1]) + break; + + tries++; + } + + if (tries == MAX_TRIES_SOC) { + pr_err("shadow registers do not match\n"); + return -EINVAL; + } + + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info_ratelimited("raw: 0x%02x\n", cap[0]); + return cap[0]; +} + +#define EMPTY_CAPACITY 0 +#define DEFAULT_CAPACITY 50 +#define MISSING_CAPACITY 100 +#define FULL_CAPACITY 100 +#define FULL_SOC_RAW 0xFF +static int get_prop_capacity(struct fg_chip *chip) +{ + int msoc; + + if (chip->battery_missing) + return MISSING_CAPACITY; + if (!chip->profile_loaded && !chip->use_otp_profile) + return DEFAULT_CAPACITY; + if (chip->charge_full) + return FULL_CAPACITY; + if (chip->soc_empty) { + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info_ratelimited("capacity: %d, EMPTY\n", + EMPTY_CAPACITY); + return EMPTY_CAPACITY; + } + msoc = get_monotonic_soc_raw(chip); + if (msoc == 0) + return EMPTY_CAPACITY; + else if (msoc == FULL_SOC_RAW) + return FULL_CAPACITY; + return DIV_ROUND_CLOSEST((msoc - 1) * (FULL_CAPACITY - 2), + FULL_SOC_RAW - 2) + 1; +} + +#define HIGH_BIAS 3 +#define MED_BIAS BIT(1) +#define LOW_BIAS BIT(0) +static u8 bias_ua[] = { + [HIGH_BIAS] = 150, + [MED_BIAS] = 15, + [LOW_BIAS] = 5, +}; + +static int64_t get_batt_id(unsigned int battery_id_uv, u8 bid_info) +{ + u64 battery_id_ohm; + + if ((bid_info & 0x3) == 0) { + pr_err("can't determine battery id 0x%02x\n", bid_info); + return -EINVAL; + } + + battery_id_ohm = div_u64(battery_id_uv, bias_ua[bid_info & 0x3]); + + return battery_id_ohm; +} + +#define DEFAULT_TEMP_DEGC 250 +static int get_sram_prop_now(struct fg_chip *chip, unsigned int type) +{ + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info("addr 0x%02X, offset %d value %d\n", + fg_data[type].address, fg_data[type].offset, + fg_data[type].value); + + if (type == FG_DATA_BATT_ID) + return get_batt_id(fg_data[type].value, + fg_data[FG_DATA_BATT_ID_INFO].value); + + return fg_data[type].value; +} + +#define MIN_TEMP_DEGC -300 +#define MAX_TEMP_DEGC 970 +static int get_prop_jeita_temp(struct fg_chip *chip, unsigned int type) +{ + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info("addr 0x%02X, offset %d\n", settings[type].address, + settings[type].offset); + + return settings[type].value; +} + +static int set_prop_jeita_temp(struct fg_chip *chip, + unsigned int type, int decidegc) +{ + int rc = 0; + + if (fg_debug_mask & FG_POWER_SUPPLY) + pr_info("addr 0x%02X, offset %d temp%d\n", + settings[type].address, + settings[type].offset, decidegc); + + settings[type].value = decidegc; + + cancel_delayed_work_sync( + &chip->update_jeita_setting); + schedule_delayed_work( + &chip->update_jeita_setting, 0); + + return rc; +} + +#define EXTERNAL_SENSE_SELECT 0x4AC +#define EXTERNAL_SENSE_OFFSET 0x2 +#define EXTERNAL_SENSE_BIT BIT(2) +static int set_prop_sense_type(struct fg_chip *chip, int ext_sense_type) +{ + int rc; + + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + EXTERNAL_SENSE_BIT, + ext_sense_type ? EXTERNAL_SENSE_BIT : 0, + EXTERNAL_SENSE_OFFSET); + if (rc) { + pr_err("failed to write profile rc=%d\n", rc); + return rc; + } + + return 0; +} + +#define EXPONENT_MASK 0xF800 +#define MANTISSA_MASK 0x3FF +#define SIGN BIT(10) +#define EXPONENT_SHIFT 11 +#define MICRO_UNIT 1000000ULL +static int64_t float_decode(u16 reg) +{ + int64_t final_val, exponent_val, mantissa_val; + int exponent, mantissa, n; + bool sign; + + exponent = (reg & EXPONENT_MASK) >> EXPONENT_SHIFT; + mantissa = (reg & MANTISSA_MASK); + sign = !!(reg & SIGN); + + pr_debug("exponent=%d mantissa=%d sign=%d\n", exponent, mantissa, sign); + + mantissa_val = mantissa * MICRO_UNIT; + + n = exponent - 15; + if (n < 0) + exponent_val = MICRO_UNIT >> -n; + else + exponent_val = MICRO_UNIT << n; + + n = n - 10; + if (n < 0) + mantissa_val >>= -n; + else + mantissa_val <<= n; + + final_val = exponent_val + mantissa_val; + + if (sign) + final_val *= -1; + + return final_val; +} + +#define MIN_HALFFLOAT_EXP_N -15 +#define MAX_HALFFLOAT_EXP_N 16 +static int log2_floor(int64_t uval) +{ + int n = 0; + int64_t i = MICRO_UNIT; + + if (uval > i) { + while (uval > i && n > MIN_HALFFLOAT_EXP_N) { + i <<= 1; + n += 1; + } + if (uval < i) + n -= 1; + } else if (uval < i) { + while (uval < i && n < MAX_HALFFLOAT_EXP_N) { + i >>= 1; + n -= 1; + } + } + + return n; +} + +static int64_t exp2_int(int64_t n) +{ + int p = n - 1; + + if (p > 0) + return (2 * MICRO_UNIT) << p; + else + return (2 * MICRO_UNIT) >> abs(p); +} + +static u16 float_encode(int64_t uval) +{ + int sign = 0, n, exp, mantissa; + u16 half = 0; + + if (uval < 0) { + sign = 1; + uval = abs(uval); + } + n = log2_floor(uval); + exp = n + 15; + mantissa = div_s64(div_s64((uval - exp2_int(n)) * exp2_int(10 - n), + MICRO_UNIT) + MICRO_UNIT / 2, MICRO_UNIT); + + half = (mantissa & MANTISSA_MASK) | ((sign << 10) & SIGN) + | ((exp << 11) & EXPONENT_MASK); + + if (fg_debug_mask & FG_STATUS) + pr_info("uval = %lld, m = 0x%02x, sign = 0x%02x, exp = 0x%02x, half = 0x%04x\n", + uval, mantissa, sign, exp, half); + return half; +} + +#define BATT_IDED BIT(3) +static int fg_is_batt_id_valid(struct fg_chip *chip) +{ + u8 fg_batt_sts; + int rc; + + rc = fg_read(chip, &fg_batt_sts, + INT_RT_STS(chip->batt_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + return rc; + } + + if (fg_debug_mask & FG_IRQS) + pr_info("fg batt sts 0x%x\n", fg_batt_sts); + + return (fg_batt_sts & BATT_IDED) ? 1 : 0; +} + +static int64_t twos_compliment_extend(int64_t val, int nbytes) +{ + int i; + int64_t mask; + + mask = 0x80LL << ((nbytes - 1) * 8); + if (val & mask) { + for (i = 8; i > nbytes; i--) { + mask = 0xFFLL << ((i - 1) * 8); + val |= mask; + } + } + + return val; +} + +#define LSB_24B_NUMRTR 596046 +#define LSB_24B_DENMTR 1000000 +#define LSB_16B_NUMRTR 152587 +#define LSB_16B_DENMTR 1000 +#define LSB_8B 9800 +#define TEMP_LSB_16B 625 +#define DECIKELVIN 2730 +#define SRAM_PERIOD_NO_ID_UPDATE_MS 100 +#define FULL_PERCENT_28BIT 0xFFFFFFF +static void update_sram_data(struct fg_chip *chip, int *resched_ms) +{ + int i, j, rc = 0; + u8 reg[4]; + int64_t temp; + int battid_valid = fg_is_batt_id_valid(chip); + + fg_stay_awake(&chip->update_sram_wakeup_source); + if (chip->fg_restarting) + goto resched; + + fg_mem_lock(chip); + for (i = 1; i < FG_DATA_MAX; i++) { + if (chip->profile_loaded && i >= FG_DATA_BATT_ID) + continue; + rc = fg_mem_read(chip, reg, fg_data[i].address, + fg_data[i].len, fg_data[i].offset, 0); + if (rc) { + pr_err("Failed to update sram data\n"); + break; + } + + temp = 0; + for (j = 0; j < fg_data[i].len; j++) + temp |= reg[j] << (8 * j); + + switch (i) { + case FG_DATA_OCV: + case FG_DATA_VOLTAGE: + case FG_DATA_CPRED_VOLTAGE: + fg_data[i].value = div_u64( + (u64)(u16)temp * LSB_16B_NUMRTR, + LSB_16B_DENMTR); + break; + case FG_DATA_CURRENT: + temp = twos_compliment_extend(temp, fg_data[i].len); + fg_data[i].value = div_s64( + (s64)temp * LSB_16B_NUMRTR, + LSB_16B_DENMTR); + break; + case FG_DATA_BATT_ESR: + fg_data[i].value = float_decode((u16) temp); + break; + case FG_DATA_BATT_ESR_COUNT: + fg_data[i].value = (u16)temp; + break; + case FG_DATA_BATT_ID: + if (battid_valid) + fg_data[i].value = reg[0] * LSB_8B; + break; + case FG_DATA_BATT_ID_INFO: + if (battid_valid) + fg_data[i].value = reg[0]; + break; + case FG_DATA_BATT_SOC: + fg_data[i].value = div64_s64((temp * 10000), + FULL_PERCENT_3B); + break; + case FG_DATA_CC_CHARGE: + temp = twos_compliment_extend(temp, fg_data[i].len); + fg_data[i].value = div64_s64( + temp * (int64_t)chip->nom_cap_uah, + FULL_PERCENT_28BIT); + break; + case FG_DATA_VINT_ERR: + temp = twos_compliment_extend(temp, fg_data[i].len); + fg_data[i].value = div64_s64(temp * chip->nom_cap_uah, + FULL_PERCENT_3B); + break; + }; + + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("%d %lld %d\n", i, temp, fg_data[i].value); + } + fg_mem_release(chip); + + if (!rc) + get_current_time(&chip->last_sram_update_time); + +resched: + if (battid_valid) { + complete_all(&chip->batt_id_avail); + *resched_ms = fg_sram_update_period_ms; + } else { + *resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS; + } + fg_relax(&chip->update_sram_wakeup_source); +} + +#define SRAM_TIMEOUT_MS 3000 +static void update_sram_data_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + update_sram_data.work); + int resched_ms, ret; + bool tried_again = false; + +wait: + /* Wait for MEMIF access revoked */ + ret = wait_for_completion_interruptible_timeout( + &chip->sram_access_revoked, + msecs_to_jiffies(SRAM_TIMEOUT_MS)); + + /* If we were interrupted wait again one more time. */ + if (ret == -ERESTARTSYS && !tried_again) { + tried_again = true; + goto wait; + } else if (ret <= 0) { + pr_err("transaction timed out ret=%d\n", ret); + goto out; + } + update_sram_data(chip, &resched_ms); + +out: + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(resched_ms)); +} + +#define BATT_TEMP_OFFSET 3 +#define BATT_TEMP_CNTRL_MASK 0x17 +#define DISABLE_THERM_BIT BIT(0) +#define TEMP_SENSE_ALWAYS_BIT BIT(1) +#define TEMP_SENSE_CHARGE_BIT BIT(2) +#define FORCE_RBIAS_ON_BIT BIT(4) +#define BATT_TEMP_OFF DISABLE_THERM_BIT +#define BATT_TEMP_ON (FORCE_RBIAS_ON_BIT | TEMP_SENSE_ALWAYS_BIT | \ + TEMP_SENSE_CHARGE_BIT) +#define TEMP_PERIOD_UPDATE_MS 10000 +#define TEMP_PERIOD_TIMEOUT_MS 3000 +static void update_temp_data(struct work_struct *work) +{ + s16 temp; + u8 reg[2]; + bool tried_again = false; + int rc, ret, timeout = TEMP_PERIOD_TIMEOUT_MS; + struct fg_chip *chip = container_of(work, + struct fg_chip, + update_temp_work.work); + + if (chip->fg_restarting) + goto resched; + + fg_stay_awake(&chip->update_temp_wakeup_source); + if (chip->sw_rbias_ctrl) { + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + BATT_TEMP_CNTRL_MASK, + BATT_TEMP_ON, + BATT_TEMP_OFFSET); + if (rc) { + pr_err("failed to write BATT_TEMP_ON rc=%d\n", rc); + goto out; + } + +wait: + /* Wait for MEMIF access revoked */ + ret = wait_for_completion_interruptible_timeout( + &chip->sram_access_revoked, + msecs_to_jiffies(timeout)); + + /* If we were interrupted wait again one more time. */ + if (ret == -ERESTARTSYS && !tried_again) { + tried_again = true; + goto wait; + } else if (ret <= 0) { + rc = -ETIMEDOUT; + pr_err("transaction timed out ret=%d\n", ret); + goto out; + } + } + + /* Read FG_DATA_BATT_TEMP now */ + rc = fg_mem_read(chip, reg, fg_data[0].address, + fg_data[0].len, fg_data[0].offset, + chip->sw_rbias_ctrl ? 1 : 0); + if (rc) { + pr_err("Failed to update temp data\n"); + goto out; + } + + temp = reg[0] | (reg[1] << 8); + fg_data[0].value = (temp * TEMP_LSB_16B / 1000) + - DECIKELVIN; + + if (fg_debug_mask & FG_MEM_DEBUG_READS) + pr_info("BATT_TEMP %d %d\n", temp, fg_data[0].value); + + get_current_time(&chip->last_temp_update_time); + +out: + if (chip->sw_rbias_ctrl) { + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + BATT_TEMP_CNTRL_MASK, + BATT_TEMP_OFF, + BATT_TEMP_OFFSET); + if (rc) + pr_err("failed to write BATT_TEMP_OFF rc=%d\n", rc); + } + fg_relax(&chip->update_temp_wakeup_source); + +resched: + schedule_delayed_work( + &chip->update_temp_work, + msecs_to_jiffies(TEMP_PERIOD_UPDATE_MS)); +} + +static void update_jeita_setting(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + update_jeita_setting.work); + u8 reg[4]; + int i, rc; + + for (i = 0; i < 4; i++) + reg[i] = (settings[FG_MEM_SOFT_COLD + i].value / 10) + 30; + + rc = fg_mem_write(chip, reg, settings[FG_MEM_SOFT_COLD].address, + 4, settings[FG_MEM_SOFT_COLD].offset, 0); + if (rc) + pr_err("failed to update JEITA setting rc=%d\n", rc); +} + +static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold) +{ + u16 address; + int offset, rc; + + address = settings[FG_MEM_RESUME_SOC].address; + offset = settings[FG_MEM_RESUME_SOC].offset; + + rc = fg_mem_masked_write(chip, address, 0xFF, threshold, offset); + + if (rc) + pr_err("write failed rc=%d\n", rc); + else + pr_debug("setting resume-soc to %x\n", threshold); + + return rc; +} + +#define VBATT_LOW_STS_BIT BIT(2) +static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts) +{ + int rc = 0; + u8 fg_batt_sts; + + rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1); + if (!rc) + *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT); + return rc; +} + +#define BATT_CYCLE_NUMBER_REG 0x5E8 +#define BATT_CYCLE_OFFSET 0 +static void restore_cycle_counter(struct fg_chip *chip) +{ + int rc = 0, i, address; + u8 data[2]; + + fg_mem_lock(chip); + for (i = 0; i < BUCKET_COUNT; i++) { + address = BATT_CYCLE_NUMBER_REG + i * 2; + rc = fg_mem_read(chip, (u8 *)&data, address, 2, + BATT_CYCLE_OFFSET, 0); + if (rc) + pr_err("Failed to read BATT_CYCLE_NUMBER[%d] rc: %d\n", + i, rc); + else + chip->cyc_ctr.count[i] = data[0] | data[1] << 8; + } + fg_mem_release(chip); +} + +static void clear_cycle_counter(struct fg_chip *chip) +{ + int rc = 0, len, i; + + if (!chip->cyc_ctr.en) + return; + + len = sizeof(chip->cyc_ctr.count); + memset(chip->cyc_ctr.count, 0, len); + for (i = 0; i < BUCKET_COUNT; i++) { + chip->cyc_ctr.started[i] = false; + chip->cyc_ctr.last_soc[i] = 0; + } + rc = fg_mem_write(chip, (u8 *)&chip->cyc_ctr.count, + BATT_CYCLE_NUMBER_REG, len, + BATT_CYCLE_OFFSET, 0); + if (rc) + pr_err("failed to write BATT_CYCLE_NUMBER rc=%d\n", rc); +} + +static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket) +{ + int rc = 0, address; + u16 cyc_count; + u8 data[2]; + + if (bucket < 0 || (bucket > BUCKET_COUNT - 1)) + return 0; + + cyc_count = chip->cyc_ctr.count[bucket]; + cyc_count++; + data[0] = cyc_count & 0xFF; + data[1] = cyc_count >> 8; + + address = BATT_CYCLE_NUMBER_REG + bucket * 2; + + rc = fg_mem_write(chip, data, address, 2, BATT_CYCLE_OFFSET, 0); + if (rc) + pr_err("failed to write BATT_CYCLE_NUMBER[%d] rc=%d\n", + bucket, rc); + else + chip->cyc_ctr.count[bucket] = cyc_count; + return rc; +} + +static void update_cycle_count(struct work_struct *work) +{ + int rc = 0, bucket, i; + u8 reg[3], batt_soc; + struct fg_chip *chip = container_of(work, + struct fg_chip, + cycle_count_work); + + mutex_lock(&chip->cyc_ctr.lock); + rc = fg_mem_read(chip, reg, BATTERY_SOC_REG, 3, + BATTERY_SOC_OFFSET, 0); + if (rc) { + pr_err("Failed to read battery soc rc: %d\n", rc); + goto out; + } + batt_soc = reg[2]; + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + /* Find out which bucket the SOC falls in */ + bucket = batt_soc / BUCKET_SOC_PCT; + + if (fg_debug_mask & FG_STATUS) + pr_info("batt_soc: %x bucket: %d\n", reg[2], bucket); + + /* + * If we've started counting for the previous bucket, + * then store the counter for that bucket if the + * counter for current bucket is getting started. + */ + if (bucket > 0 && chip->cyc_ctr.started[bucket - 1] && + !chip->cyc_ctr.started[bucket]) { + rc = fg_inc_store_cycle_ctr(chip, bucket - 1); + if (rc) { + pr_err("Error in storing cycle_ctr rc: %d\n", + rc); + goto out; + } else { + chip->cyc_ctr.started[bucket - 1] = false; + chip->cyc_ctr.last_soc[bucket - 1] = 0; + } + } + if (!chip->cyc_ctr.started[bucket]) { + chip->cyc_ctr.started[bucket] = true; + chip->cyc_ctr.last_soc[bucket] = batt_soc; + } + } else { + for (i = 0; i < BUCKET_COUNT; i++) { + if (chip->cyc_ctr.started[i] && + batt_soc > chip->cyc_ctr.last_soc[i]) { + rc = fg_inc_store_cycle_ctr(chip, i); + if (rc) + pr_err("Error in storing cycle_ctr rc: %d\n", + rc); + chip->cyc_ctr.last_soc[i] = 0; + } + chip->cyc_ctr.started[i] = false; + } + } +out: + mutex_unlock(&chip->cyc_ctr.lock); +} + +static int fg_get_cycle_count(struct fg_chip *chip) +{ + int count; + + if (!chip->cyc_ctr.en) + return 0; + + if ((chip->cyc_ctr.id <= 0) || (chip->cyc_ctr.id > BUCKET_COUNT)) + return -EINVAL; + + mutex_lock(&chip->cyc_ctr.lock); + count = chip->cyc_ctr.count[chip->cyc_ctr.id - 1]; + mutex_unlock(&chip->cyc_ctr.lock); + return count; +} + +static void half_float_to_buffer(int64_t uval, u8 *buffer) +{ + u16 raw; + + raw = float_encode(uval); + buffer[0] = (u8)(raw & 0xFF); + buffer[1] = (u8)((raw >> 8) & 0xFF); +} + +static int64_t half_float(u8 *buffer) +{ + u16 val; + + val = buffer[1] << 8 | buffer[0]; + return float_decode(val); +} + +static int voltage_2b(u8 *buffer) +{ + u16 val; + + val = buffer[1] << 8 | buffer[0]; + /* the range of voltage 2b is [-5V, 5V], so it will fit in an int */ + return (int)div_u64(((u64)val) * LSB_16B_NUMRTR, LSB_16B_DENMTR); +} + +static int bcap_uah_2b(u8 *buffer) +{ + u16 val; + + val = buffer[1] << 8 | buffer[0]; + return ((int)val) * 1000; +} + +static int lookup_ocv_for_soc(struct fg_chip *chip, int soc) +{ + int64_t *coeffs; + + if (soc > chip->ocv_junction_p1p2 * 10) + coeffs = chip->ocv_coeffs; + else if (soc > chip->ocv_junction_p2p3 * 10) + coeffs = chip->ocv_coeffs + 4; + else + coeffs = chip->ocv_coeffs + 8; + /* the range of ocv will fit in a 32 bit int */ + return (int)(coeffs[0] + + div_s64(coeffs[1] * soc, 1000LL) + + div_s64(coeffs[2] * soc * soc, 1000000LL) + + div_s64(coeffs[3] * soc * soc * soc, 1000000000LL)); +} + +static int lookup_soc_for_ocv(struct fg_chip *chip, int ocv) +{ + int64_t val; + int soc = -EINVAL; + /* + * binary search variables representing the valid start and end + * percentages to search + */ + int start = 0, end = 1000, mid; + + if (fg_debug_mask & FG_AGING) + pr_info("target_ocv = %d\n", ocv); + /* do a binary search for the closest soc to match the ocv */ + while (end - start > 1) { + mid = (start + end) / 2; + val = lookup_ocv_for_soc(chip, mid); + if (fg_debug_mask & FG_AGING) + pr_info("start = %d, mid = %d, end = %d, ocv = %lld\n", + start, mid, end, val); + if (ocv < val) { + end = mid; + } else if (ocv > val) { + start = mid; + } else { + soc = mid; + break; + } + } + /* + * if the exact soc was not found and there are two or less values + * remaining, just compare them and see which one is closest to the ocv + */ + if (soc == -EINVAL) { + if (abs(ocv - lookup_ocv_for_soc(chip, start)) + > abs(ocv - lookup_ocv_for_soc(chip, end))) + soc = end; + else + soc = start; + } + if (fg_debug_mask & FG_AGING) + pr_info("closest = %d, target_ocv = %d, ocv_found = %d\n", + soc, ocv, lookup_ocv_for_soc(chip, soc)); + return soc; +} + +#define ESR_ACTUAL_REG 0x554 +#define BATTERY_ESR_REG 0x4F4 +#define TEMP_RS_TO_RSLOW_REG 0x514 +static int estimate_battery_age(struct fg_chip *chip, int *actual_capacity) +{ + int64_t ocv_cutoff_new, ocv_cutoff_aged, temp_rs_to_rslow; + int64_t esr_actual, battery_esr, val; + int soc_cutoff_aged, soc_cutoff_new, rc; + int battery_soc, unusable_soc, batt_temp; + u8 buffer[3]; + + if (chip->batt_aging_mode != FG_AGING_ESR) + return 0; + + if (chip->nom_cap_uah == 0) { + if (fg_debug_mask & FG_AGING) + pr_info("ocv coefficients not loaded, aborting\n"); + return 0; + } + fg_mem_lock(chip); + + batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + if (batt_temp < 150 || batt_temp > 400) { + if (fg_debug_mask & FG_AGING) + pr_info("Battery temp (%d) out of range, aborting\n", + (int)batt_temp); + rc = 0; + goto done; + } + + battery_soc = get_battery_soc_raw(chip) * 100 / FULL_PERCENT_3B; + if (battery_soc < 25 || battery_soc > 75) { + if (fg_debug_mask & FG_AGING) + pr_info("Battery SoC (%d) out of range, aborting\n", + (int)battery_soc); + rc = 0; + goto done; + } + + rc = fg_mem_read(chip, buffer, ESR_ACTUAL_REG, 2, 2, 0); + esr_actual = half_float(buffer); + rc |= fg_mem_read(chip, buffer, BATTERY_ESR_REG, 2, 2, 0); + battery_esr = half_float(buffer); + + if (rc) { + goto error_done; + } else if (esr_actual < battery_esr) { + if (fg_debug_mask & FG_AGING) + pr_info("Batt ESR lower than ESR actual, aborting\n"); + rc = 0; + goto done; + } + rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, 0, 0); + temp_rs_to_rslow = half_float(buffer); + + if (rc) + goto error_done; + + fg_mem_release(chip); + + if (fg_debug_mask & FG_AGING) { + pr_info("batt_soc = %d, cutoff_voltage = %lld, eval current = %d\n", + battery_soc, chip->cutoff_voltage, + chip->evaluation_current); + pr_info("temp_rs_to_rslow = %lld, batt_esr = %lld, esr_actual = %lld\n", + temp_rs_to_rslow, battery_esr, esr_actual); + } + + /* calculate soc_cutoff_new */ + val = (1000000LL + temp_rs_to_rslow) * battery_esr; + do_div(val, 1000000); + ocv_cutoff_new = div64_s64(chip->evaluation_current * val, 1000) + + chip->cutoff_voltage; + + /* calculate soc_cutoff_aged */ + val = (1000000LL + temp_rs_to_rslow) * esr_actual; + do_div(val, 1000000); + ocv_cutoff_aged = div64_s64(chip->evaluation_current * val, 1000) + + chip->cutoff_voltage; + + if (fg_debug_mask & FG_AGING) + pr_info("ocv_cutoff_new = %lld, ocv_cutoff_aged = %lld\n", + ocv_cutoff_new, ocv_cutoff_aged); + + soc_cutoff_new = lookup_soc_for_ocv(chip, ocv_cutoff_new); + soc_cutoff_aged = lookup_soc_for_ocv(chip, ocv_cutoff_aged); + + if (fg_debug_mask & FG_AGING) + pr_info("aged soc = %d, new soc = %d\n", + soc_cutoff_aged, soc_cutoff_new); + unusable_soc = soc_cutoff_aged - soc_cutoff_new; + + *actual_capacity = div64_s64(((int64_t)chip->nom_cap_uah) + * (1000 - unusable_soc), 1000); + if (fg_debug_mask & FG_AGING) + pr_info("nom cap = %d, actual cap = %d\n", + chip->nom_cap_uah, *actual_capacity); + + return rc; + +error_done: + pr_err("some register reads failed: %d\n", rc); +done: + fg_mem_release(chip); + return rc; +} + +static void battery_age_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + battery_age_work); + + estimate_battery_age(chip, &chip->actual_cap_uah); +} + +static enum power_supply_property fg_power_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CAPACITY_RAW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CHARGE_NOW, + POWER_SUPPLY_PROP_CHARGE_NOW_RAW, + POWER_SUPPLY_PROP_CHARGE_NOW_ERROR, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_COOL_TEMP, + POWER_SUPPLY_PROP_WARM_TEMP, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_RESISTANCE_ID, + POWER_SUPPLY_PROP_BATTERY_TYPE, + POWER_SUPPLY_PROP_UPDATE_NOW, + POWER_SUPPLY_PROP_ESR_COUNT, + POWER_SUPPLY_PROP_VOLTAGE_MIN, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CYCLE_COUNT_ID, + POWER_SUPPLY_PROP_HI_POWER, +}; + +static int fg_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + bool vbatt_low_sts; + + switch (psp) { + case POWER_SUPPLY_PROP_BATTERY_TYPE: + if (chip->battery_missing) + val->strval = missing_batt_type; + else if (chip->fg_restarting) + val->strval = loading_batt_type; + else + val->strval = chip->batt_type; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_capacity(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY_RAW: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_SOC); + break; + case POWER_SUPPLY_PROP_CHARGE_NOW_ERROR: + val->intval = get_sram_prop_now(chip, FG_DATA_VINT_ERR); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_sram_prop_now(chip, FG_DATA_CURRENT); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_sram_prop_now(chip, FG_DATA_VOLTAGE); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = get_sram_prop_now(chip, FG_DATA_OCV); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chip->batt_max_voltage_uv; + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_COLD); + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_HOT); + break; + case POWER_SUPPLY_PROP_RESISTANCE: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR); + break; + case POWER_SUPPLY_PROP_ESR_COUNT: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR_COUNT); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = fg_get_cycle_count(chip); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + val->intval = chip->cyc_ctr.id; + break; + case POWER_SUPPLY_PROP_RESISTANCE_ID: + val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ID); + break; + case POWER_SUPPLY_PROP_UPDATE_NOW: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + if (!fg_get_vbatt_status(chip, &vbatt_low_sts)) + val->intval = (int)vbatt_low_sts; + else + val->intval = 1; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = chip->nom_cap_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = chip->learning_data.learned_cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW: + val->intval = chip->learning_data.cc_uah; + break; + case POWER_SUPPLY_PROP_CHARGE_NOW_RAW: + val->intval = get_sram_prop_now(chip, FG_DATA_CC_CHARGE); + break; + case POWER_SUPPLY_PROP_HI_POWER: + val->intval = !!chip->bcl_lpm_disabled; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int correction_times[] = { + 1470, + 2940, + 4410, + 5880, + 7350, + 8820, + 10290, + 11760, + 13230, + 14700, + 16170, + 17640, + 19110, + 20580, + 22050, + 23520, + 24990, + 26460, + 27930, + 29400, + 30870, + 32340, + 33810, + 35280, + 36750, + 38220, + 39690, + 41160, + 42630, + 44100, + 45570, + 47040, +}; + +static int correction_factors[] = { + 1000000, + 1007874, + 1015789, + 1023745, + 1031742, + 1039780, + 1047859, + 1055979, + 1064140, + 1072342, + 1080584, + 1088868, + 1097193, + 1105558, + 1113964, + 1122411, + 1130899, + 1139427, + 1147996, + 1156606, + 1165256, + 1173947, + 1182678, + 1191450, + 1200263, + 1209115, + 1218008, + 1226942, + 1235915, + 1244929, + 1253983, + 1263076, +}; + +#define FG_CONVERSION_FACTOR (64198531LL) +static int iavg_3b_to_uah(u8 *buffer, int delta_ms) +{ + int64_t val, i_filtered; + int i, correction_factor; + + for (i = 0; i < ARRAY_SIZE(correction_times); i++) { + if (correction_times[i] > delta_ms) + break; + } + if (i >= ARRAY_SIZE(correction_times)) { + if (fg_debug_mask & FG_STATUS) + pr_info("fuel gauge took more than 32 cycles\n"); + i = ARRAY_SIZE(correction_times) - 1; + } + correction_factor = correction_factors[i]; + if (fg_debug_mask & FG_STATUS) + pr_info("delta_ms = %d, cycles = %d, correction = %d\n", + delta_ms, i, correction_factor); + val = buffer[2] << 16 | buffer[1] << 8 | buffer[0]; + /* convert val from signed 24b to signed 64b */ + i_filtered = (val << 40) >> 40; + val = i_filtered * correction_factor; + val = div64_s64(val + FG_CONVERSION_FACTOR / 2, FG_CONVERSION_FACTOR); + if (fg_debug_mask & FG_STATUS) + pr_info("i_filtered = 0x%llx/%lld, cc_uah = %lld\n", + i_filtered, i_filtered, val); + + return val; +} + +static bool fg_is_temperature_ok_for_learning(struct fg_chip *chip) +{ + int batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP); + + if (batt_temp > chip->learning_data.max_temp + || batt_temp < chip->learning_data.min_temp) { + if (fg_debug_mask & FG_AGING) + pr_info("temp (%d) out of range [%d, %d], aborting\n", + batt_temp, + chip->learning_data.min_temp, + chip->learning_data.max_temp); + return false; + } + return true; +} + +static void fg_cap_learning_stop(struct fg_chip *chip) +{ + chip->learning_data.cc_uah = 0; + chip->learning_data.active = false; +} + +#define I_FILTERED_REG 0x584 +static void fg_cap_learning_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + fg_cap_learning_work); + u8 i_filtered[3], data[3]; + int rc, cc_uah, delta_ms; + ktime_t now_kt, delta_kt; + + mutex_lock(&chip->learning_data.learning_lock); + if (!chip->learning_data.active) + goto fail; + if (!fg_is_temperature_ok_for_learning(chip)) { + fg_cap_learning_stop(chip); + goto fail; + } + + if (chip->wa_flag & USE_CC_SOC_REG) { + mutex_unlock(&chip->learning_data.learning_lock); + fg_relax(&chip->capacity_learning_wakeup_source); + return; + } + + fg_mem_lock(chip); + + rc = fg_mem_read(chip, i_filtered, I_FILTERED_REG, 3, 0, 0); + if (rc) { + pr_err("Failed to read i_filtered: %d\n", rc); + fg_mem_release(chip); + goto fail; + } + memset(data, 0, 3); + rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0); + if (rc) { + pr_err("Failed to clear i_filtered: %d\n", rc); + fg_mem_release(chip); + goto fail; + } + fg_mem_release(chip); + + now_kt = ktime_get_boottime(); + delta_kt = ktime_sub(now_kt, chip->learning_data.time_stamp); + chip->learning_data.time_stamp = now_kt; + + delta_ms = (int)div64_s64(ktime_to_ns(delta_kt), 1000000); + + cc_uah = iavg_3b_to_uah(i_filtered, delta_ms); + chip->learning_data.cc_uah -= cc_uah; + if (fg_debug_mask & FG_AGING) + pr_info("total_cc_uah = %lld\n", chip->learning_data.cc_uah); + +fail: + mutex_unlock(&chip->learning_data.learning_lock); + return; + +} + +#define CC_SOC_BASE_REG 0x5BC +#define CC_SOC_OFFSET 3 +#define CC_SOC_MAGNITUDE_MASK 0x1FFFFFFF +#define CC_SOC_NEGATIVE_BIT BIT(29) +static int fg_get_cc_soc(struct fg_chip *chip, int *cc_soc) +{ + int rc; + u8 reg[4]; + unsigned int temp, magnitude; + + rc = fg_mem_read(chip, reg, CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0); + if (rc) { + pr_err("Failed to read CC_SOC_REG rc=%d\n", rc); + return rc; + } + + temp = reg[3] << 24 | reg[2] << 16 | reg[1] << 8 | reg[0]; + magnitude = temp & CC_SOC_MAGNITUDE_MASK; + if (temp & CC_SOC_NEGATIVE_BIT) + *cc_soc = -1 * (~magnitude + 1); + else + *cc_soc = magnitude; + + return 0; +} + +static int fg_cap_learning_process_full_data(struct fg_chip *chip) +{ + int cc_pc_val, rc = -EINVAL; + unsigned int cc_soc_delta_pc; + int64_t delta_cc_uah; + + if (!chip->learning_data.active) + goto fail; + + if (!fg_is_temperature_ok_for_learning(chip)) { + fg_cap_learning_stop(chip); + goto fail; + } + + rc = fg_get_cc_soc(chip, &cc_pc_val); + if (rc) { + pr_err("failed to get CC_SOC, stopping capacity learning\n"); + fg_cap_learning_stop(chip); + goto fail; + } + + cc_soc_delta_pc = DIV_ROUND_CLOSEST( + abs(cc_pc_val - chip->learning_data.init_cc_pc_val) + * 100, FULL_PERCENT_28BIT); + + delta_cc_uah = div64_s64( + chip->learning_data.learned_cc_uah * cc_soc_delta_pc, + 100); + chip->learning_data.cc_uah = delta_cc_uah + chip->learning_data.cc_uah; + + if (fg_debug_mask & FG_AGING) + pr_info("current cc_soc=%d cc_soc_pc=%d total_cc_uah = %lld\n", + cc_pc_val, cc_soc_delta_pc, + chip->learning_data.cc_uah); + + return 0; + +fail: + return rc; +} + +#define FG_CAP_LEARNING_INTERVAL_NS 30000000000 +static enum alarmtimer_restart fg_cap_learning_alarm_cb(struct alarm *alarm, + ktime_t now) +{ + struct fg_chip *chip = container_of(alarm, struct fg_chip, + fg_cap_learning_alarm); + + if (chip->learning_data.active) { + if (fg_debug_mask & FG_AGING) + pr_info("alarm fired\n"); + schedule_work(&chip->fg_cap_learning_work); + alarm_forward_now(alarm, + ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS)); + return ALARMTIMER_RESTART; + } + if (fg_debug_mask & FG_AGING) + pr_info("alarm misfired\n"); + return ALARMTIMER_NORESTART; +} + +#define FG_AGING_STORAGE_REG 0x5E4 +#define ACTUAL_CAPACITY_REG 0x578 +#define MAH_TO_SOC_CONV_REG 0x4A0 +#define CC_SOC_COEFF_OFFSET 0 +#define ACTUAL_CAPACITY_OFFSET 2 +#define MAH_TO_SOC_CONV_CS_OFFSET 0 +static int fg_calc_and_store_cc_soc_coeff(struct fg_chip *chip, int16_t cc_mah) +{ + int rc; + int64_t cc_to_soc_coeff, mah_to_soc; + u8 data[2]; + + rc = fg_mem_write(chip, (u8 *)&cc_mah, ACTUAL_CAPACITY_REG, 2, + ACTUAL_CAPACITY_OFFSET, 0); + if (rc) { + pr_err("Failed to store actual capacity: %d\n", rc); + return rc; + } + + rc = fg_mem_read(chip, (u8 *)&data, MAH_TO_SOC_CONV_REG, 2, + MAH_TO_SOC_CONV_CS_OFFSET, 0); + if (rc) { + pr_err("Failed to read mah_to_soc_conv_cs: %d\n", rc); + } else { + mah_to_soc = data[1] << 8 | data[0]; + mah_to_soc *= MICRO_UNIT; + cc_to_soc_coeff = div64_s64(mah_to_soc, cc_mah); + half_float_to_buffer(cc_to_soc_coeff, data); + rc = fg_mem_write(chip, (u8 *)data, + ACTUAL_CAPACITY_REG, 2, + CC_SOC_COEFF_OFFSET, 0); + if (rc) + pr_err("Failed to write cc_soc_coeff_offset: %d\n", + rc); + else if (fg_debug_mask & FG_AGING) + pr_info("new cc_soc_coeff %lld [%x %x] saved to sram\n", + cc_to_soc_coeff, data[0], data[1]); + } + return rc; +} + +static void fg_cap_learning_load_data(struct fg_chip *chip) +{ + int16_t cc_mah; + int64_t old_cap = chip->learning_data.learned_cc_uah; + int rc; + + rc = fg_mem_read(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to load aged capacity: %d\n", rc); + } else { + chip->learning_data.learned_cc_uah = cc_mah * 1000; + if (fg_debug_mask & FG_AGING) + pr_info("learned capacity %lld-> %lld/%x uah\n", + old_cap, + chip->learning_data.learned_cc_uah, + cc_mah); + } +} + +static void fg_cap_learning_save_data(struct fg_chip *chip) +{ + int16_t cc_mah; + int rc; + + cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); + + rc = fg_mem_write(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0); + if (rc) + pr_err("Failed to store aged capacity: %d\n", rc); + else if (fg_debug_mask & FG_AGING) + pr_info("learned capacity %lld uah (%d/0x%x uah) saved to sram\n", + chip->learning_data.learned_cc_uah, + cc_mah, cc_mah); + + if (chip->learning_data.feedback_on) { + rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah); + if (rc) + pr_err("Error in storing cc_soc_coeff, rc:%d\n", rc); + } +} + +static void fg_cap_learning_post_process(struct fg_chip *chip) +{ + int64_t max_inc_val, min_dec_val, old_cap; + + max_inc_val = chip->learning_data.learned_cc_uah + * (1000 + chip->learning_data.max_increment); + do_div(max_inc_val, 1000); + + min_dec_val = chip->learning_data.learned_cc_uah + * (1000 - chip->learning_data.max_decrement); + do_div(min_dec_val, 1000); + + old_cap = chip->learning_data.learned_cc_uah; + if (chip->learning_data.cc_uah > max_inc_val) + chip->learning_data.learned_cc_uah = max_inc_val; + else if (chip->learning_data.cc_uah < min_dec_val) + chip->learning_data.learned_cc_uah = min_dec_val; + else + chip->learning_data.learned_cc_uah = + chip->learning_data.cc_uah; + + fg_cap_learning_save_data(chip); + if (fg_debug_mask & FG_AGING) + pr_info("final cc_uah = %lld, learned capacity %lld -> %lld uah\n", + chip->learning_data.cc_uah, + old_cap, chip->learning_data.learned_cc_uah); +} + +static int get_vbat_est_diff(struct fg_chip *chip) +{ + return abs(fg_data[FG_DATA_VOLTAGE].value + - fg_data[FG_DATA_CPRED_VOLTAGE].value); +} + +#define CBITS_INPUT_FILTER_REG 0x4B4 +#define IBATTF_TAU_MASK 0x38 +#define IBATTF_TAU_99_S 0x30 +static int fg_cap_learning_check(struct fg_chip *chip) +{ + u8 data[4]; + int rc = 0, battery_soc, cc_pc_val; + int vbat_est_diff, vbat_est_thr_uv; + unsigned int cc_pc_100 = FULL_PERCENT_28BIT; + + mutex_lock(&chip->learning_data.learning_lock); + if (chip->status == POWER_SUPPLY_STATUS_CHARGING + && !chip->learning_data.active + && chip->batt_aging_mode == FG_AGING_CC) { + if (chip->learning_data.learned_cc_uah == 0) { + if (fg_debug_mask & FG_AGING) + pr_info("no capacity, aborting\n"); + goto fail; + } + + if (!fg_is_temperature_ok_for_learning(chip)) + goto fail; + + fg_mem_lock(chip); + if (!chip->learning_data.feedback_on) { + vbat_est_diff = get_vbat_est_diff(chip); + vbat_est_thr_uv = chip->learning_data.vbat_est_thr_uv; + if (vbat_est_diff >= vbat_est_thr_uv && + vbat_est_thr_uv > 0) { + if (fg_debug_mask & FG_AGING) + pr_info("vbat_est_diff (%d) < threshold (%d)\n", + vbat_est_diff, vbat_est_thr_uv); + fg_mem_release(chip); + fg_cap_learning_stop(chip); + goto fail; + } + } + battery_soc = get_battery_soc_raw(chip); + if (fg_debug_mask & FG_AGING) + pr_info("checking battery soc (%d vs %d)\n", + battery_soc * 100 / FULL_PERCENT_3B, + chip->learning_data.max_start_soc); + /* check if the battery is low enough to start soc learning */ + if (battery_soc * 100 / FULL_PERCENT_3B + > chip->learning_data.max_start_soc) { + if (fg_debug_mask & FG_AGING) + pr_info("battery soc too low (%d < %d), aborting\n", + battery_soc * 100 / FULL_PERCENT_3B, + chip->learning_data.max_start_soc); + fg_mem_release(chip); + fg_cap_learning_stop(chip); + goto fail; + } + + /* set the coulomb counter to a percentage of the capacity */ + chip->learning_data.cc_uah = div64_s64( + (chip->learning_data.learned_cc_uah * battery_soc), + FULL_PERCENT_3B); + + /* Use CC_SOC_REG based capacity learning */ + if (chip->wa_flag & USE_CC_SOC_REG) { + fg_mem_release(chip); + /* SW_CC_SOC based capacity learning */ + if (fg_get_cc_soc(chip, &cc_pc_val)) { + pr_err("failed to get CC_SOC, stop capacity learning\n"); + fg_cap_learning_stop(chip); + goto fail; + } + + chip->learning_data.init_cc_pc_val = cc_pc_val; + chip->learning_data.active = true; + if (fg_debug_mask & FG_AGING) + pr_info("SW_CC_SOC based learning init_CC_SOC=%d\n", + chip->learning_data.init_cc_pc_val); + } else { + rc = fg_mem_masked_write(chip, CBITS_INPUT_FILTER_REG, + IBATTF_TAU_MASK, IBATTF_TAU_99_S, 0); + if (rc) { + pr_err("Failed to write IF IBAT Tau: %d\n", + rc); + fg_mem_release(chip); + fg_cap_learning_stop(chip); + goto fail; + } + + /* clear the i_filtered register */ + memset(data, 0, 4); + rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0); + if (rc) { + pr_err("Failed to clear i_filtered: %d\n", rc); + fg_mem_release(chip); + fg_cap_learning_stop(chip); + goto fail; + } + fg_mem_release(chip); + chip->learning_data.time_stamp = ktime_get_boottime(); + chip->learning_data.active = true; + + if (fg_debug_mask & FG_AGING) + pr_info("cap learning started, soc = %d cc_uah = %lld\n", + battery_soc * 100 / FULL_PERCENT_3B, + chip->learning_data.cc_uah); + alarm_start_relative(&chip->fg_cap_learning_alarm, + ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS)); + } + } else if ((chip->status != POWER_SUPPLY_STATUS_CHARGING) + && chip->learning_data.active) { + if (fg_debug_mask & FG_AGING) + pr_info("capacity learning stopped\n"); + if (!(chip->wa_flag & USE_CC_SOC_REG)) + alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + + if (chip->status == POWER_SUPPLY_STATUS_FULL) { + if (chip->wa_flag & USE_CC_SOC_REG) { + rc = fg_cap_learning_process_full_data(chip); + if (rc) { + fg_cap_learning_stop(chip); + goto fail; + } + /* reset SW_CC_SOC register to 100% */ + rc = fg_mem_write(chip, (u8 *)&cc_pc_100, + CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0); + if (rc) + pr_err("Failed to reset CC_SOC_REG rc=%d\n", + rc); + } + fg_cap_learning_post_process(chip); + } + + fg_cap_learning_stop(chip); + } + +fail: + mutex_unlock(&chip->learning_data.learning_lock); + return rc; +} + +static bool is_usb_present(struct fg_chip *chip) +{ + union power_supply_propval prop = {0,}; + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (chip->usb_psy) + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &prop); + return prop.intval != 0; +} + +static bool is_dc_present(struct fg_chip *chip) +{ + union power_supply_propval prop = {0,}; + if (!chip->dc_psy) + chip->dc_psy = power_supply_get_by_name("dc"); + + if (chip->dc_psy) + power_supply_get_property(chip->dc_psy, + POWER_SUPPLY_PROP_PRESENT, &prop); + return prop.intval != 0; +} + +static bool is_input_present(struct fg_chip *chip) +{ + return is_usb_present(chip) || is_dc_present(chip); +} + +static bool is_otg_present(struct fg_chip *chip) +{ + union power_supply_propval prop = {0,}; + + if (!chip->usb_psy) + chip->usb_psy = power_supply_get_by_name("usb"); + + if (chip->usb_psy) + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_USB_OTG, &prop); + return prop.intval != 0; +} + +static bool is_charger_available(struct fg_chip *chip) +{ + if (!chip->batt_psy_name) + return false; + + if (!chip->batt_psy) + chip->batt_psy = power_supply_get_by_name(chip->batt_psy_name); + + if (!chip->batt_psy) + return false; + + return true; +} + +static int set_prop_enable_charging(struct fg_chip *chip, bool enable) +{ + int rc = 0; + union power_supply_propval ret = {enable, }; + + if (!is_charger_available(chip)) { + pr_err("Charger not available yet!\n"); + return -EINVAL; + } + + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, + &ret); + if (rc) { + pr_err("couldn't configure batt chg %d\n", rc); + return rc; + } + + chip->charging_disabled = !enable; + if (fg_debug_mask & FG_STATUS) + pr_info("%sabling charging\n", enable ? "en" : "dis"); + + return rc; +} + +#define MAX_BATTERY_CC_SOC_CAPACITY 150 +static void status_change_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + status_change_work); + unsigned long current_time = 0; + int cc_soc, rc, capacity = get_prop_capacity(chip); + + if (chip->esr_pulse_tune_en) { + fg_stay_awake(&chip->esr_extract_wakeup_source); + schedule_work(&chip->esr_extract_config_work); + } + + if (chip->status == POWER_SUPPLY_STATUS_FULL) { + if (capacity >= 99 && chip->hold_soc_while_full + && chip->health == POWER_SUPPLY_HEALTH_GOOD) { + if (fg_debug_mask & FG_STATUS) + pr_info("holding soc at 100\n"); + chip->charge_full = true; + } else if (fg_debug_mask & FG_STATUS) { + pr_info("terminated charging at %d/0x%02x\n", + capacity, get_monotonic_soc_raw(chip)); + } + } + if (chip->status == POWER_SUPPLY_STATUS_FULL || + chip->status == POWER_SUPPLY_STATUS_CHARGING) { + if (!chip->vbat_low_irq_enabled) { + enable_irq(chip->batt_irq[VBATT_LOW].irq); + enable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = true; + } + if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100) + fg_configure_soc(chip); + } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) { + if (chip->vbat_low_irq_enabled) { + disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + } + } + fg_cap_learning_check(chip); + schedule_work(&chip->update_esr_work); + + if (chip->wa_flag & USE_CC_SOC_REG) { + if (fg_get_cc_soc(chip, &cc_soc)) { + pr_err("failed to get CC_SOC\n"); + return; + } + } + + if (chip->prev_status != chip->status && chip->last_sram_update_time) { + get_current_time(¤t_time); + /* + * When charging status changes, update SRAM parameters if it + * was not updated before 5 seconds from now. + */ + if (chip->last_sram_update_time + 5 < current_time) { + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work(&chip->update_sram_data, + msecs_to_jiffies(0)); + } + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + if ((chip->wa_flag & USE_CC_SOC_REG) && + chip->bad_batt_detection_en && + chip->status == POWER_SUPPLY_STATUS_CHARGING) { + chip->sw_cc_soc_data.init_sys_soc = capacity; + chip->sw_cc_soc_data.init_cc_soc = cc_soc; + if (fg_debug_mask & FG_STATUS) + pr_info(" Init_sys_soc %d init_cc_soc %d\n", + chip->sw_cc_soc_data.init_sys_soc, + chip->sw_cc_soc_data.init_cc_soc); + } + } + if ((chip->wa_flag & USE_CC_SOC_REG) && chip->bad_batt_detection_en + && chip->safety_timer_expired) { + chip->sw_cc_soc_data.delta_soc = + DIV_ROUND_CLOSEST(abs(cc_soc - + chip->sw_cc_soc_data.init_cc_soc) + * 100, FULL_PERCENT_28BIT); + chip->sw_cc_soc_data.full_capacity = + chip->sw_cc_soc_data.delta_soc + + chip->sw_cc_soc_data.init_sys_soc; + pr_info("Init_sys_soc %d init_cc_soc %d cc_soc %d delta_soc %d full_capacity %d\n", + chip->sw_cc_soc_data.init_sys_soc, + chip->sw_cc_soc_data.init_cc_soc, cc_soc, + chip->sw_cc_soc_data.delta_soc, + chip->sw_cc_soc_data.full_capacity); + /* + * If sw_cc_soc capacity greater than 150, then it's a bad + * battery. else, reset timer and restart charging. + */ + if (chip->sw_cc_soc_data.full_capacity > + MAX_BATTERY_CC_SOC_CAPACITY) { + pr_info("Battery possibly damaged, do not restart charging\n"); + } else { + pr_info("Reset safety-timer and restart charging\n"); + rc = set_prop_enable_charging(chip, false); + if (rc) { + pr_err("failed to disable charging %d\n", rc); + return; + } + + chip->safety_timer_expired = false; + msleep(200); + + rc = set_prop_enable_charging(chip, true); + if (rc) { + pr_err("failed to enable charging %d\n", rc); + return; + } + } + } +} + +/* + * Check for change in the status of input or OTG and schedule + * IADC gain compensation work. + */ +static void check_gain_compensation(struct fg_chip *chip) +{ + bool input_present = is_input_present(chip); + bool otg_present = is_otg_present(chip); + + if ((chip->wa_flag & IADC_GAIN_COMP_WA) + && ((chip->input_present ^ input_present) + || (chip->otg_present ^ otg_present))) { + fg_stay_awake(&chip->gain_comp_wakeup_source); + chip->input_present = input_present; + chip->otg_present = otg_present; + cancel_work_sync(&chip->gain_comp_work); + schedule_work(&chip->gain_comp_work); + } +} + +static void fg_hysteresis_config(struct fg_chip *chip) +{ + int hard_hot = 0, hard_cold = 0; + + hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT); + hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD); + if (chip->health == POWER_SUPPLY_HEALTH_OVERHEAT && !chip->batt_hot) { + /* turn down the hard hot threshold */ + chip->batt_hot = true; + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot - chip->hot_hysteresis); + if (fg_debug_mask & FG_STATUS) + pr_info("hard hot hysteresis: old hot=%d, new hot=%d\n", + hard_hot, hard_hot - chip->hot_hysteresis); + } else if (chip->health == POWER_SUPPLY_HEALTH_COLD && + !chip->batt_cold) { + /* turn up the hard cold threshold */ + chip->batt_cold = true; + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold + chip->cold_hysteresis); + if (fg_debug_mask & FG_STATUS) + pr_info("hard cold hysteresis: old cold=%d, new cold=%d\n", + hard_cold, hard_cold + chip->hot_hysteresis); + } else if (chip->health != POWER_SUPPLY_HEALTH_OVERHEAT && + chip->batt_hot) { + /* restore the hard hot threshold */ + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot + chip->hot_hysteresis); + chip->batt_hot = !chip->batt_hot; + if (fg_debug_mask & FG_STATUS) + pr_info("restore hard hot threshold: old hot=%d, new hot=%d\n", + hard_hot, + hard_hot + chip->hot_hysteresis); + } else if (chip->health != POWER_SUPPLY_HEALTH_COLD && + chip->batt_cold) { + /* restore the hard cold threshold */ + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold - chip->cold_hysteresis); + chip->batt_cold = !chip->batt_cold; + if (fg_debug_mask & FG_STATUS) + pr_info("restore hard cold threshold: old cold=%d, new cold=%d\n", + hard_cold, + hard_cold - chip->cold_hysteresis); + } +} + +#define BATT_INFO_STS(base) (base + 0x09) +#define JEITA_HARD_HOT_RT_STS BIT(6) +#define JEITA_HARD_COLD_RT_STS BIT(5) +static int fg_init_batt_temp_state(struct fg_chip *chip) +{ + int rc = 0; + u8 batt_info_sts; + int hard_hot = 0, hard_cold = 0; + + /* + * read the batt_info_sts register to parse battery's + * initial status and do hysteresis config accordingly. + */ + rc = fg_read(chip, &batt_info_sts, + BATT_INFO_STS(chip->batt_base), 1); + if (rc) { + pr_err("failed to read batt info sts, rc=%d\n", rc); + return rc; + } + + hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT); + hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD); + chip->batt_hot = + (batt_info_sts & JEITA_HARD_HOT_RT_STS) ? true : false; + chip->batt_cold = + (batt_info_sts & JEITA_HARD_COLD_RT_STS) ? true : false; + if (chip->batt_hot || chip->batt_cold) { + if (chip->batt_hot) { + chip->health = POWER_SUPPLY_HEALTH_OVERHEAT; + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot - chip->hot_hysteresis); + } else { + chip->health = POWER_SUPPLY_HEALTH_COLD; + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold + chip->cold_hysteresis); + } + } + + return rc; +} + +static int fg_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + int rc = 0, unused; + + switch (psp) { + case POWER_SUPPLY_PROP_COOL_TEMP: + rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_COLD, val->intval); + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_HOT, val->intval); + break; + case POWER_SUPPLY_PROP_UPDATE_NOW: + if (val->intval) + update_sram_data(chip, &unused); + break; + case POWER_SUPPLY_PROP_STATUS: + chip->prev_status = chip->status; + chip->status = val->intval; + schedule_work(&chip->status_change_work); + check_gain_compensation(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + chip->health = val->intval; + if (chip->health == POWER_SUPPLY_HEALTH_GOOD) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + + if (chip->jeita_hysteresis_support) + fg_hysteresis_config(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_DONE: + chip->charge_done = val->intval; + if (!chip->resume_soc_lowered) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + if ((val->intval > 0) && (val->intval <= BUCKET_COUNT)) { + chip->cyc_ctr.id = val->intval; + } else { + pr_err("rejecting invalid cycle_count_id = %d\n", + val->intval); + rc = -EINVAL; + } + break; + case POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED: + chip->safety_timer_expired = val->intval; + schedule_work(&chip->status_change_work); + break; + case POWER_SUPPLY_PROP_HI_POWER: + if (chip->wa_flag & BCL_HI_POWER_FOR_CHGLED_WA) { + chip->bcl_lpm_disabled = !!val->intval; + schedule_work(&chip->bcl_hi_power_work); + } + break; + default: + return -EINVAL; + }; + + return rc; +}; + +static int fg_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_COOL_TEMP: + case POWER_SUPPLY_PROP_WARM_TEMP: + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + return 1; + default: + break; + } + + return 0; +} + +#define SRAM_DUMP_START 0x400 +#define SRAM_DUMP_LEN 0x200 +static void dump_sram(struct work_struct *work) +{ + int i, rc; + u8 *buffer, rt_sts; + char str[16]; + struct fg_chip *chip = container_of(work, + struct fg_chip, + dump_sram); + + buffer = devm_kzalloc(chip->dev, SRAM_DUMP_LEN, GFP_KERNEL); + if (buffer == NULL) { + pr_err("Can't allocate buffer\n"); + return; + } + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + else + pr_info("soc rt_sts: 0x%x\n", rt_sts); + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->batt_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + else + pr_info("batt rt_sts: 0x%x\n", rt_sts); + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->mem_base), rc); + else + pr_info("memif rt_sts: 0x%x\n", rt_sts); + + rc = fg_mem_read(chip, buffer, SRAM_DUMP_START, SRAM_DUMP_LEN, 0, 0); + if (rc) { + pr_err("dump failed: rc = %d\n", rc); + return; + } + + for (i = 0; i < SRAM_DUMP_LEN; i += 4) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buffer + i, 4); + pr_info("%03X %s\n", SRAM_DUMP_START + i, str); + } + devm_kfree(chip->dev, buffer); +} + +#define MAXRSCHANGE_REG 0x434 +#define ESR_VALUE_OFFSET 1 +#define ESR_STRICT_VALUE 0x4120391F391F3019 +#define ESR_DEFAULT_VALUE 0x58CD4A6761C34A67 +static void update_esr_value(struct work_struct *work) +{ + union power_supply_propval prop = {0, }; + u64 esr_value; + int rc = 0; + struct fg_chip *chip = container_of(work, + struct fg_chip, + update_esr_work); + + if (!is_charger_available(chip)) + return; + + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); + + if (!chip->esr_strict_filter) { + if ((prop.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER && + chip->status == POWER_SUPPLY_STATUS_CHARGING) || + (chip->status == POWER_SUPPLY_STATUS_FULL)) { + esr_value = ESR_STRICT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, + MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write strict ESR value rc=%d\n", + rc); + else + chip->esr_strict_filter = true; + } + } else if ((prop.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER && + chip->status == POWER_SUPPLY_STATUS_CHARGING) || + (chip->status == POWER_SUPPLY_STATUS_DISCHARGING)) { + esr_value = ESR_DEFAULT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write default ESR value rc=%d\n", rc); + else + chip->esr_strict_filter = false; + } +} + +#define TEMP_COUNTER_REG 0x580 +#define VBAT_FILTERED_OFFSET 1 +#define GAIN_REG 0x424 +#define GAIN_OFFSET 1 +#define K_VCOR_REG 0x484 +#define DEF_GAIN_OFFSET 2 +#define PICO_UNIT 0xE8D4A51000LL +#define ATTO_UNIT 0xDE0B6B3A7640000LL +#define VBAT_REF 3800000 + +/* + * IADC Gain compensation steps: + * If Input/OTG absent: + * - read VBAT_FILTERED, KVCOR, GAIN + * - calculate the gain compensation using following formula: + * gain = (1 + gain) * (1 + kvcor * (vbat_filtered - 3800000)) - 1; + * else + * - reset to the default gain compensation + */ +static void iadc_gain_comp_work(struct work_struct *work) +{ + u8 reg[4]; + int rc; + uint64_t vbat_filtered; + int64_t gain, kvcor, temp, numerator; + struct fg_chip *chip = container_of(work, struct fg_chip, + gain_comp_work); + bool input_present = is_input_present(chip); + bool otg_present = is_otg_present(chip); + + if (!chip->init_done) + goto done; + + if (!input_present && !otg_present) { + /* read VBAT_FILTERED */ + rc = fg_mem_read(chip, reg, TEMP_COUNTER_REG, 3, + VBAT_FILTERED_OFFSET, 0); + if (rc) { + pr_err("Failed to read VBAT: rc=%d\n", rc); + goto done; + } + temp = (reg[2] << 16) | (reg[1] << 8) | reg[0]; + vbat_filtered = div_u64((u64)temp * LSB_24B_NUMRTR, + LSB_24B_DENMTR); + + /* read K_VCOR */ + rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to KVCOR rc=%d\n", rc); + goto done; + } + kvcor = half_float(reg); + + /* calculate gain */ + numerator = (MICRO_UNIT + chip->iadc_comp_data.dfl_gain) + * (PICO_UNIT + kvcor * (vbat_filtered - VBAT_REF)) + - ATTO_UNIT; + gain = div64_s64(numerator, PICO_UNIT); + + /* write back gain */ + half_float_to_buffer(gain, reg); + rc = fg_mem_write(chip, reg, GAIN_REG, 2, GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write gain reg rc=%d\n", rc); + goto done; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("IADC gain update [%x %x]\n", reg[1], reg[0]); + chip->iadc_comp_data.gain_active = true; + } else { + /* reset gain register */ + rc = fg_mem_write(chip, chip->iadc_comp_data.dfl_gain_reg, + GAIN_REG, 2, GAIN_OFFSET, 0); + if (rc) { + pr_err("unable to write gain comp: %d\n", rc); + goto done; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("IADC gain reset [%x %x]\n", + chip->iadc_comp_data.dfl_gain_reg[1], + chip->iadc_comp_data.dfl_gain_reg[0]); + chip->iadc_comp_data.gain_active = false; + } + +done: + fg_relax(&chip->gain_comp_wakeup_source); +} + +#define BATT_MISSING_STS BIT(6) +static bool is_battery_missing(struct fg_chip *chip) +{ + int rc; + u8 fg_batt_sts; + + rc = fg_read(chip, &fg_batt_sts, + INT_RT_STS(chip->batt_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + return false; + } + + return (fg_batt_sts & BATT_MISSING_STS) ? true : false; +} + +#define SOC_FIRST_EST_DONE BIT(5) +static bool is_first_est_done(struct fg_chip *chip) +{ + int rc; + u8 fg_soc_sts; + + rc = fg_read(chip, &fg_soc_sts, + INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + return false; + } + + return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false; +} + +static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + int rc; + bool vbatt_low_sts; + + if (fg_debug_mask & FG_IRQS) + pr_info("vbatt-low triggered\n"); + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + rc = fg_get_vbatt_status(chip, &vbatt_low_sts); + if (rc) { + pr_err("error in reading vbatt_status, rc:%d\n", rc); + goto out; + } + if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { + if (fg_debug_mask & FG_IRQS) + pr_info("disabling vbatt_low irq\n"); + disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + } + } + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); +out: + return IRQ_HANDLED; +} + +static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + chip->battery_missing = true; + chip->profile_loaded = false; + chip->batt_type = default_batt_type; + mutex_lock(&chip->cyc_ctr.lock); + if (fg_debug_mask & FG_IRQS) + pr_info("battery missing, clearing cycle counters\n"); + clear_cycle_counter(chip); + mutex_unlock(&chip->cyc_ctr.lock); + } else { + if (!chip->use_otp_profile) { + reinit_completion(&chip->batt_id_avail); + reinit_completion(&chip->first_soc_done); + schedule_delayed_work(&chip->batt_profile_init, 0); + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(0)); + } else { + chip->battery_missing = false; + } + } + + if (fg_debug_mask & FG_IRQS) + pr_info("batt-missing triggered: %s\n", + batt_missing ? "missing" : "present"); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + return IRQ_HANDLED; +} + +static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 mem_if_sts; + int rc; + + rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) { + pr_err("failed to read mem status rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (fg_check_sram_access(chip)) { + if ((fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("sram access granted\n"); + reinit_completion(&chip->sram_access_revoked); + complete_all(&chip->sram_access_granted); + } else { + if ((fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("sram access revoked\n"); + complete_all(&chip->sram_access_revoked); + } + + if (!rc && (fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("mem_if sts 0x%02x\n", mem_if_sts); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 soc_rt_sts; + int rc; + + rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + } + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered 0x%x\n", soc_rt_sts); + + schedule_work(&chip->battery_age_work); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + if (chip->rslow_comp.chg_rs_to_rslow > 0 && + chip->rslow_comp.chg_rslow_comp_c1 > 0 && + chip->rslow_comp.chg_rslow_comp_c2 > 0) + schedule_work(&chip->rslow_comp_work); + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + schedule_work(&chip->update_esr_work); + if (chip->charge_full) + schedule_work(&chip->charge_full_work); + if (chip->wa_flag & IADC_GAIN_COMP_WA + && chip->iadc_comp_data.gain_active) { + fg_stay_awake(&chip->gain_comp_wakeup_source); + schedule_work(&chip->gain_comp_work); + } + + if (chip->wa_flag & USE_CC_SOC_REG + && chip->learning_data.active) { + fg_stay_awake(&chip->capacity_learning_wakeup_source); + schedule_work(&chip->fg_cap_learning_work); + } + + if (chip->esr_pulse_tune_en) { + fg_stay_awake(&chip->esr_extract_wakeup_source); + schedule_work(&chip->esr_extract_config_work); + } + + return IRQ_HANDLED; +} + +#define FG_EMPTY_DEBOUNCE_MS 1500 +static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 soc_rt_sts; + int rc; + + rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + goto done; + } + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered 0x%x\n", soc_rt_sts); + if (fg_is_batt_empty(chip)) { + fg_stay_awake(&chip->empty_check_wakeup_source); + schedule_delayed_work(&chip->check_empty_work, + msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); + } else { + chip->soc_empty = false; + } + +done: + return IRQ_HANDLED; +} + +static irqreturn_t fg_first_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered\n"); + + if (fg_est_dump) + schedule_work(&chip->dump_sram); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + complete_all(&chip->first_soc_done); + + return IRQ_HANDLED; +} + +static void fg_external_power_changed(struct power_supply *psy) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + + if (is_input_present(chip) && chip->rslow_comp.active && + chip->rslow_comp.chg_rs_to_rslow > 0 && + chip->rslow_comp.chg_rslow_comp_c1 > 0 && + chip->rslow_comp.chg_rslow_comp_c2 > 0) + schedule_work(&chip->rslow_comp_work); + if (!is_input_present(chip) && chip->resume_soc_lowered) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + if (!is_input_present(chip) && chip->charge_full) + schedule_work(&chip->charge_full_work); +} + +static void set_resume_soc_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + set_resume_soc_work); + int rc, resume_soc_raw; + + if (is_input_present(chip) && !chip->resume_soc_lowered) { + if (!chip->charge_done) + goto done; + resume_soc_raw = get_monotonic_soc_raw(chip) + - (0xFF - settings[FG_MEM_RESUME_SOC].value); + if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + goto done; + } + if (fg_debug_mask & FG_STATUS) { + pr_info("resume soc lowered to 0x%02x\n", + resume_soc_raw); + } + } else if (settings[FG_MEM_RESUME_SOC].value > 0) { + pr_err("bad resume soc 0x%02x\n", resume_soc_raw); + } + chip->charge_done = false; + chip->resume_soc_lowered = true; + } else if (chip->resume_soc_lowered && (!is_input_present(chip) + || chip->health == POWER_SUPPLY_HEALTH_GOOD)) { + resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; + if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + goto done; + } + if (fg_debug_mask & FG_STATUS) { + pr_info("resume soc set to 0x%02x\n", + resume_soc_raw); + } + } else if (settings[FG_MEM_RESUME_SOC].value > 0) { + pr_err("bad resume soc 0x%02x\n", resume_soc_raw); + } + chip->resume_soc_lowered = false; + } +done: + fg_relax(&chip->resume_soc_wakeup_source); +} + + +#define OCV_COEFFS_START_REG 0x4C0 +#define OCV_JUNCTION_REG 0x4D8 +#define NOM_CAP_REG 0x4F4 +#define CUTOFF_VOLTAGE_REG 0x40C +#define RSLOW_CFG_REG 0x538 +#define RSLOW_CFG_OFFSET 2 +#define RSLOW_THRESH_REG 0x52C +#define RSLOW_THRESH_OFFSET 0 +#define TEMP_RS_TO_RSLOW_OFFSET 2 +#define RSLOW_COMP_REG 0x528 +#define RSLOW_COMP_C1_OFFSET 0 +#define RSLOW_COMP_C2_OFFSET 2 +static int populate_system_data(struct fg_chip *chip) +{ + u8 buffer[24]; + int rc, i; + int16_t cc_mah; + + fg_mem_lock(chip); + rc = fg_mem_read(chip, buffer, OCV_COEFFS_START_REG, 24, 0, 0); + if (rc) { + pr_err("Failed to read ocv coefficients: %d\n", rc); + goto done; + } + for (i = 0; i < 12; i += 1) + chip->ocv_coeffs[i] = half_float(buffer + (i * 2)); + if (fg_debug_mask & FG_AGING) { + pr_info("coeffs1 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[0], chip->ocv_coeffs[1], + chip->ocv_coeffs[2], chip->ocv_coeffs[3]); + pr_info("coeffs2 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[4], chip->ocv_coeffs[5], + chip->ocv_coeffs[6], chip->ocv_coeffs[7]); + pr_info("coeffs3 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[8], chip->ocv_coeffs[9], + chip->ocv_coeffs[10], chip->ocv_coeffs[11]); + } + rc = fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 0, 0); + chip->ocv_junction_p1p2 = buffer[0] * 100 / 255; + rc |= fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 1, 0); + chip->ocv_junction_p2p3 = buffer[0] * 100 / 255; + if (rc) { + pr_err("Failed to read ocv junctions: %d\n", rc); + goto done; + } + rc = fg_mem_read(chip, buffer, NOM_CAP_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to read nominal capacitance: %d\n", rc); + goto done; + } + chip->nom_cap_uah = bcap_uah_2b(buffer); + chip->actual_cap_uah = chip->nom_cap_uah; + if (chip->learning_data.learned_cc_uah == 0) { + chip->learning_data.learned_cc_uah = chip->nom_cap_uah; + fg_cap_learning_save_data(chip); + } else if (chip->learning_data.feedback_on) { + cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); + rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah); + if (rc) + pr_err("Error in restoring cc_soc_coeff, rc:%d\n", rc); + } + rc = fg_mem_read(chip, buffer, CUTOFF_VOLTAGE_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to read cutoff voltage: %d\n", rc); + goto done; + } + chip->cutoff_voltage = voltage_2b(buffer); + if (fg_debug_mask & FG_AGING) + pr_info("cutoff_voltage = %lld, nom_cap_uah = %d p1p2 = %d, p2p3 = %d\n", + chip->cutoff_voltage, chip->nom_cap_uah, + chip->ocv_junction_p1p2, + chip->ocv_junction_p2p3); + + rc = fg_mem_read(chip, buffer, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow cfg: %d\n", rc); + goto done; + } + chip->rslow_comp.rslow_cfg = buffer[0]; + rc = fg_mem_read(chip, buffer, RSLOW_THRESH_REG, 1, + RSLOW_THRESH_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow thresh: %d\n", rc); + goto done; + } + chip->rslow_comp.rslow_thr = buffer[0]; + rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RSLOW_THRESH_OFFSET, 0); + if (rc) { + pr_err("unable to read rs to rslow: %d\n", rc); + goto done; + } + memcpy(chip->rslow_comp.rs_to_rslow, buffer, 2); + rc = fg_mem_read(chip, buffer, RSLOW_COMP_REG, 4, + RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow comp: %d\n", rc); + goto done; + } + memcpy(chip->rslow_comp.rslow_comp, buffer, 4); + +done: + fg_mem_release(chip); + return rc; +} + +#define RSLOW_CFG_MASK (BIT(2) | BIT(3) | BIT(4) | BIT(5)) +#define RSLOW_CFG_ON_VAL (BIT(2) | BIT(3)) +#define RSLOW_THRESH_FULL_VAL 0xFF +static int fg_rslow_charge_comp_set(struct fg_chip *chip) +{ + int rc; + u8 buffer[2]; + + mutex_lock(&chip->rslow_comp.lock); + fg_mem_lock(chip); + + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_MASK, RSLOW_CFG_ON_VAL, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg: %d\n", rc); + goto done; + } + rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG, + 0xFF, RSLOW_THRESH_FULL_VAL, RSLOW_THRESH_OFFSET); + if (rc) { + pr_err("unable to write rslow thresh: %d\n", rc); + goto done; + } + + half_float_to_buffer(chip->rslow_comp.chg_rs_to_rslow, buffer); + rc = fg_mem_write(chip, buffer, + TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + if (rc) { + pr_err("unable to write rs to rslow: %d\n", rc); + goto done; + } + half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c1, buffer); + rc = fg_mem_write(chip, buffer, + RSLOW_COMP_REG, 2, RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c2, buffer); + rc = fg_mem_write(chip, buffer, + RSLOW_COMP_REG, 2, RSLOW_COMP_C2_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + chip->rslow_comp.active = true; + if (fg_debug_mask & FG_STATUS) + pr_info("Activated rslow charge comp values\n"); + +done: + fg_mem_release(chip); + mutex_unlock(&chip->rslow_comp.lock); + return rc; +} + +#define RSLOW_CFG_ORIG_MASK (BIT(4) | BIT(5)) +static int fg_rslow_charge_comp_clear(struct fg_chip *chip) +{ + u8 reg; + int rc; + + mutex_lock(&chip->rslow_comp.lock); + fg_mem_lock(chip); + + reg = chip->rslow_comp.rslow_cfg & RSLOW_CFG_ORIG_MASK; + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_MASK, reg, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg: %d\n", rc); + goto done; + } + rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG, + 0xFF, chip->rslow_comp.rslow_thr, RSLOW_THRESH_OFFSET); + if (rc) { + pr_err("unable to write rslow thresh: %d\n", rc); + goto done; + } + + rc = fg_mem_write(chip, chip->rslow_comp.rs_to_rslow, + TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + if (rc) { + pr_err("unable to write rs to rslow: %d\n", rc); + goto done; + } + rc = fg_mem_write(chip, chip->rslow_comp.rslow_comp, + RSLOW_COMP_REG, 4, RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + chip->rslow_comp.active = false; + if (fg_debug_mask & FG_STATUS) + pr_info("Cleared rslow charge comp values\n"); + +done: + fg_mem_release(chip); + mutex_unlock(&chip->rslow_comp.lock); + return rc; +} + +static void rslow_comp_work(struct work_struct *work) +{ + int battery_soc_1b; + struct fg_chip *chip = container_of(work, + struct fg_chip, + rslow_comp_work); + + battery_soc_1b = get_battery_soc_raw(chip) >> 16; + if (battery_soc_1b > chip->rslow_comp.chg_rslow_comp_thr + && chip->status == POWER_SUPPLY_STATUS_CHARGING) { + if (!chip->rslow_comp.active) + fg_rslow_charge_comp_set(chip); + } else { + if (chip->rslow_comp.active) + fg_rslow_charge_comp_clear(chip); + } +} + +#define MICROUNITS_TO_ADC_RAW(units) \ + div64_s64(units * LSB_16B_DENMTR, LSB_16B_NUMRTR) +static int update_chg_iterm(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_current_raw; + s64 current_ma = -settings[FG_MEM_CHG_TERM_CURRENT].value; + + converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000); + data[0] = cpu_to_le16(converted_current_raw) & 0xFF; + data[1] = cpu_to_le16(converted_current_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n", + current_ma, converted_current_raw, data[0], data[1]); + return fg_mem_write(chip, data, + settings[FG_MEM_CHG_TERM_CURRENT].address, + 2, settings[FG_MEM_CHG_TERM_CURRENT].offset, 0); +} + +#define CC_CV_SETPOINT_REG 0x4F8 +#define CC_CV_SETPOINT_OFFSET 0 +static void update_cc_cv_setpoint(struct fg_chip *chip) +{ + int rc; + u8 tmp[2]; + + if (!chip->cc_cv_threshold_mv) + return; + batt_to_setpoint_adc(chip->cc_cv_threshold_mv, tmp); + rc = fg_mem_write(chip, tmp, CC_CV_SETPOINT_REG, 2, + CC_CV_SETPOINT_OFFSET, 0); + if (rc) { + pr_err("failed to write CC_CV_VOLT rc=%d\n", rc); + return; + } + if (fg_debug_mask & FG_STATUS) + pr_info("Wrote %x %x to address %x for CC_CV setpoint\n", + tmp[0], tmp[1], CC_CV_SETPOINT_REG); +} + +#define CBITS_INPUT_FILTER_REG 0x4B4 +#define CBITS_RMEAS1_OFFSET 1 +#define CBITS_RMEAS2_OFFSET 2 +#define CBITS_RMEAS1_DEFAULT_VAL 0x65 +#define CBITS_RMEAS2_DEFAULT_VAL 0x65 +#define IMPTR_FAST_TIME_SHIFT 1 +#define IMPTR_LONG_TIME_SHIFT (1 << 4) +#define IMPTR_PULSE_CTR_CHG 1 +#define IMPTR_PULSE_CTR_DISCHG (1 << 4) +static int fg_config_imptr_pulse(struct fg_chip *chip, bool slow) +{ + int rc; + u8 cntr[2] = {0, 0}; + u8 val; + + if (slow == chip->imptr_pulse_slow_en) { + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled already\n", + slow ? "en" : "dis"); + return 0; + } + + fg_mem_lock(chip); + + val = slow ? (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT) : + CBITS_RMEAS1_DEFAULT_VAL; + rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS1_OFFSET, 0); + if (rc) { + pr_err("unable to write cbits_rmeas1_offset rc=%d\n", rc); + goto done; + } + + val = slow ? (IMPTR_PULSE_CTR_CHG | IMPTR_PULSE_CTR_DISCHG) : + CBITS_RMEAS2_DEFAULT_VAL; + rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS2_OFFSET, 0); + if (rc) { + pr_err("unable to write cbits_rmeas2_offset rc=%d\n", rc); + goto done; + } + + if (slow) { + rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 4, + COUNTER_IMPTR_OFFSET, 0); + if (rc) { + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + goto done; + } + + rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2, + COUNTER_PULSE_OFFSET, 0); + if (rc) { + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + goto done; + } + } + + chip->imptr_pulse_slow_en = slow; + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled\n", slow ? "en" : "dis"); +done: + fg_mem_release(chip); + return rc; +} + +#define CURRENT_DELTA_MIN_REG 0x42C +#define CURRENT_DELTA_MIN_OFFSET 1 +#define SYS_CFG_1_REG 0x4AC +#define SYS_CFG_1_OFFSET 0 +#define CURRENT_DELTA_MIN_DEFAULT 0x16 +#define CURRENT_DELTA_MIN_500MA 0xCD +#define RSLOW_CFG_USE_FIX_RSER_VAL BIT(7) +#define ENABLE_ESR_PULSE_VAL BIT(3) +static int fg_config_esr_extract(struct fg_chip *chip, bool disable) +{ + int rc; + u8 val; + + if (disable == chip->esr_extract_disabled) { + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract already %sabled\n", + disable ? "dis" : "en"); + return 0; + } + + fg_mem_lock(chip); + + val = disable ? CURRENT_DELTA_MIN_500MA : + CURRENT_DELTA_MIN_DEFAULT; + rc = fg_mem_write(chip, &val, CURRENT_DELTA_MIN_REG, 1, + CURRENT_DELTA_MIN_OFFSET, 0); + if (rc) { + pr_err("unable to write curr_delta_min rc=%d\n", rc); + goto done; + } + + val = disable ? RSLOW_CFG_USE_FIX_RSER_VAL : 0; + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_USE_FIX_RSER_VAL, val, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg rc= %d\n", rc); + goto done; + } + + val = disable ? 0 : ENABLE_ESR_PULSE_VAL; + rc = fg_mem_masked_write(chip, SYS_CFG_1_REG, + ENABLE_ESR_PULSE_VAL, val, SYS_CFG_1_OFFSET); + if (rc) { + pr_err("unable to write sys_cfg_1 rc= %d\n", rc); + goto done; + } + + chip->esr_extract_disabled = disable; + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract is %sabled\n", disable ? "dis" : "en"); +done: + fg_mem_release(chip); + return rc; +} + +#define ESR_EXTRACT_STOP_SOC 2 +#define IMPTR_PULSE_CONFIG_SOC 5 +static void esr_extract_config_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, struct fg_chip, + esr_extract_config_work); + bool input_present = is_input_present(chip); + int capacity = get_prop_capacity(chip); + + if (input_present && capacity <= ESR_EXTRACT_STOP_SOC) { + fg_config_esr_extract(chip, true); + } else if (capacity > ESR_EXTRACT_STOP_SOC) { + fg_config_esr_extract(chip, false); + + if (capacity <= IMPTR_PULSE_CONFIG_SOC) + fg_config_imptr_pulse(chip, true); + else + fg_config_imptr_pulse(chip, false); + } + + fg_relax(&chip->esr_extract_wakeup_source); +} + +#define LOW_LATENCY BIT(6) +#define BATT_PROFILE_OFFSET 0x4C0 +#define PROFILE_INTEGRITY_REG 0x53C +#define PROFILE_INTEGRITY_BIT BIT(0) +#define FIRST_EST_DONE_BIT BIT(5) +#define MAX_TRIES_FIRST_EST 3 +#define FIRST_EST_WAIT_MS 2000 +#define PROFILE_LOAD_TIMEOUT_MS 5000 +static int fg_do_restart(struct fg_chip *chip, bool write_profile) +{ + int rc, ibat_ua; + u8 reg = 0; + u8 buf[2]; + bool tried_once = false; + + if (fg_debug_mask & FG_STATUS) + pr_info("restarting fuel gauge...\n"); + +try_again: + if (write_profile) { + if (!chip->charging_disabled) { + pr_err("Charging not yet disabled!\n"); + return -EINVAL; + } + + ibat_ua = get_sram_prop_now(chip, FG_DATA_CURRENT); + if (ibat_ua == -EINVAL) { + pr_err("SRAM not updated yet!\n"); + return ibat_ua; + } + + if (ibat_ua < 0) { + pr_warn("Charging enabled?, ibat_ua: %d\n", ibat_ua); + + if (!tried_once) { + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work(&chip->update_sram_data, + msecs_to_jiffies(0)); + msleep(1000); + tried_once = true; + goto try_again; + } + } + } + + chip->fg_restarting = true; + /* + * save the temperature if the sw rbias control is active so that there + * is no gap of time when there is no valid temperature read after the + * restart + */ + if (chip->sw_rbias_ctrl) { + rc = fg_mem_read(chip, buf, + fg_data[FG_DATA_BATT_TEMP].address, + fg_data[FG_DATA_BATT_TEMP].len, + fg_data[FG_DATA_BATT_TEMP].offset, 0); + if (rc) { + pr_err("failed to read batt temp rc=%d\n", rc); + goto sub_and_fail; + } + } + /* + * release the sram access and configure the correct settings + * before re-requesting access. + */ + mutex_lock(&chip->rw_lock); + fg_release_access(chip); + + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, 0, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto unlock_and_fail; + } + + /* unset the restart bits so the fg doesn't continuously restart */ + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, 0, 1); + if (rc) { + pr_err("failed to unset fg restart: %d\n", rc); + goto unlock_and_fail; + } + + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), + LOW_LATENCY, LOW_LATENCY, 1); + if (rc) { + pr_err("failed to set low latency access bit\n"); + goto unlock_and_fail; + } + mutex_unlock(&chip->rw_lock); + + /* read once to get a fg cycle in */ + rc = fg_mem_read(chip, ®, PROFILE_INTEGRITY_REG, 1, 0, 0); + if (rc) { + pr_err("failed to read profile integrity rc=%d\n", rc); + goto fail; + } + + /* + * If this is not the first time a profile has been loaded, sleep for + * 3 seconds to make sure the NO_OTP_RELOAD is cleared in memory + */ + if (chip->first_profile_loaded) + msleep(3000); + + mutex_lock(&chip->rw_lock); + fg_release_access(chip); + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), LOW_LATENCY, 0, 1); + if (rc) { + pr_err("failed to set low latency access bit\n"); + goto unlock_and_fail; + } + + atomic_add_return(1, &chip->memif_user_cnt); + mutex_unlock(&chip->rw_lock); + + if (write_profile) { + /* write the battery profile */ + rc = fg_mem_write(chip, chip->batt_profile, BATT_PROFILE_OFFSET, + chip->batt_profile_len, 0, 1); + if (rc) { + pr_err("failed to write profile rc=%d\n", rc); + goto sub_and_fail; + } + /* write the integrity bits and release access */ + rc = fg_mem_masked_write(chip, PROFILE_INTEGRITY_REG, + PROFILE_INTEGRITY_BIT, + PROFILE_INTEGRITY_BIT, 0); + if (rc) { + pr_err("failed to write profile rc=%d\n", rc); + goto sub_and_fail; + } + } + + /* decrement the user count so that memory access can be released */ + fg_release_access_if_necessary(chip); + + /* + * make sure that the first estimate has completed + * in case of a hotswap + */ + rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done, + msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS)); + if (rc <= 0) { + pr_err("transaction timed out rc=%d\n", rc); + rc = -ETIMEDOUT; + goto fail; + } + + /* + * reinitialize the completion so that the driver knows when the restart + * finishes + */ + reinit_completion(&chip->first_soc_done); + + if (chip->esr_pulse_tune_en) { + fg_stay_awake(&chip->esr_extract_wakeup_source); + schedule_work(&chip->esr_extract_config_work); + } + + /* + * set the restart bits so that the next fg cycle will not reload + * the profile + */ + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, NO_OTP_PROF_RELOAD, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto fail; + } + + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, reg, 1); + if (rc) { + pr_err("failed to set fg restart: %d\n", rc); + goto fail; + } + + /* wait for the first estimate to complete */ + rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done, + msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS)); + if (rc <= 0) { + pr_err("transaction timed out rc=%d\n", rc); + rc = -ETIMEDOUT; + goto fail; + } + rc = fg_read(chip, ®, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + goto fail; + } + if ((reg & FIRST_EST_DONE_BIT) == 0) + pr_err("Battery profile reloading failed, no first estimate\n"); + + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, 0, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto fail; + } + /* unset the restart bits so the fg doesn't continuously restart */ + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, 0, 1); + if (rc) { + pr_err("failed to unset fg restart: %d\n", rc); + goto fail; + } + + /* restore the battery temperature reading here */ + if (chip->sw_rbias_ctrl) { + if (fg_debug_mask & FG_STATUS) + pr_info("reloaded 0x%02x%02x into batt temp", + buf[0], buf[1]); + rc = fg_mem_write(chip, buf, + fg_data[FG_DATA_BATT_TEMP].address, + fg_data[FG_DATA_BATT_TEMP].len, + fg_data[FG_DATA_BATT_TEMP].offset, 0); + if (rc) { + pr_err("failed to write batt temp rc=%d\n", rc); + goto fail; + } + } + + /* Enable charging now as the first estimate is done now */ + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + chip->fg_restarting = false; + + if (fg_debug_mask & FG_STATUS) + pr_info("done!\n"); + return 0; + +unlock_and_fail: + mutex_unlock(&chip->rw_lock); + goto fail; +sub_and_fail: + fg_release_access_if_necessary(chip); + goto fail; +fail: + chip->fg_restarting = false; + return -EINVAL; +} + +#define FG_PROFILE_LEN 128 +#define PROFILE_COMPARE_LEN 32 +#define THERMAL_COEFF_ADDR 0x444 +#define THERMAL_COEFF_OFFSET 0x2 +#define BATTERY_PSY_WAIT_MS 2000 +static int fg_batt_profile_init(struct fg_chip *chip) +{ + int rc = 0, ret; + int len; + struct device_node *node = chip->pdev->dev.of_node; + struct device_node *batt_node, *profile_node; + const char *data, *batt_type_str; + bool tried_again = false, vbat_in_range, profiles_same; + u8 reg = 0; + +wait: + fg_stay_awake(&chip->profile_wakeup_source); + ret = wait_for_completion_interruptible_timeout(&chip->batt_id_avail, + msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS)); + /* If we were interrupted wait again one more time. */ + if (ret == -ERESTARTSYS && !tried_again) { + tried_again = true; + pr_debug("interrupted, waiting again\n"); + goto wait; + } else if (ret <= 0) { + rc = -ETIMEDOUT; + pr_err("profile loading timed out rc=%d\n", rc); + goto no_profile; + } + + batt_node = of_find_node_by_name(node, "qcom,battery-data"); + if (!batt_node) { + pr_warn("No available batterydata, using OTP defaults\n"); + rc = 0; + goto no_profile; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("battery id = %d\n", + get_sram_prop_now(chip, FG_DATA_BATT_ID)); + profile_node = of_batterydata_get_best_profile(batt_node, "bms", + fg_batt_type); + if (!profile_node) { + pr_err("couldn't find profile handle\n"); + rc = -ENODATA; + goto no_profile; + } + + /* read rslow compensation values if they're available */ + rc = of_property_read_u32(profile_node, "qcom,chg-rs-to-rslow", + &chip->rslow_comp.chg_rs_to_rslow); + if (rc) { + chip->rslow_comp.chg_rs_to_rslow = -EINVAL; + if (rc != -EINVAL) + pr_err("Could not read rs to rslow: %d\n", rc); + } + rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-c1", + &chip->rslow_comp.chg_rslow_comp_c1); + if (rc) { + chip->rslow_comp.chg_rslow_comp_c1 = -EINVAL; + if (rc != -EINVAL) + pr_err("Could not read rslow comp c1: %d\n", rc); + } + rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-c2", + &chip->rslow_comp.chg_rslow_comp_c2); + if (rc) { + chip->rslow_comp.chg_rslow_comp_c2 = -EINVAL; + if (rc != -EINVAL) + pr_err("Could not read rslow comp c2: %d\n", rc); + } + rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-thr", + &chip->rslow_comp.chg_rslow_comp_thr); + if (rc) { + chip->rslow_comp.chg_rslow_comp_thr = -EINVAL; + if (rc != -EINVAL) + pr_err("Could not read rslow comp thr: %d\n", rc); + } + + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &chip->batt_max_voltage_uv); + + if (rc) + pr_warn("couldn't find battery max voltage\n"); + + /* + * Only configure from profile if fg-cc-cv-threshold-mv is not + * defined in the charger device node. + */ + if (!of_find_property(chip->pdev->dev.of_node, + "qcom,fg-cc-cv-threshold-mv", NULL)) { + of_property_read_u32(profile_node, + "qcom,fg-cc-cv-threshold-mv", + &chip->cc_cv_threshold_mv); + } + + data = of_get_property(profile_node, "qcom,fg-profile-data", &len); + if (!data) { + pr_err("no battery profile loaded\n"); + rc = 0; + goto no_profile; + } + + if (len != FG_PROFILE_LEN) { + pr_err("battery profile incorrect size: %d\n", len); + rc = -EINVAL; + goto no_profile; + } + + rc = of_property_read_string(profile_node, "qcom,battery-type", + &batt_type_str); + if (rc) { + pr_err("Could not find battery data type: %d\n", rc); + rc = 0; + goto no_profile; + } + + if (!chip->batt_profile) + chip->batt_profile = devm_kzalloc(chip->dev, + sizeof(char) * len, GFP_KERNEL); + + if (!chip->batt_profile) { + pr_err("out of memory\n"); + rc = -ENOMEM; + goto no_profile; + } + + rc = fg_mem_read(chip, ®, PROFILE_INTEGRITY_REG, 1, 0, 1); + if (rc) { + pr_err("failed to read profile integrity rc=%d\n", rc); + goto no_profile; + } + + rc = fg_mem_read(chip, chip->batt_profile, BATT_PROFILE_OFFSET, + len, 0, 1); + if (rc) { + pr_err("failed to read profile rc=%d\n", rc); + goto no_profile; + } + + /* Check whether the charger is ready */ + if (!is_charger_available(chip)) + goto reschedule; + + /* Disable charging for a FG cycle before calculating vbat_in_range */ + if (!chip->charging_disabled) { + rc = set_prop_enable_charging(chip, false); + if (rc) + pr_err("Failed to disable charging, rc=%d\n", rc); + + goto reschedule; + } + + vbat_in_range = get_vbat_est_diff(chip) + < settings[FG_MEM_VBAT_EST_DIFF].value * 1000; + profiles_same = memcmp(chip->batt_profile, data, + PROFILE_COMPARE_LEN) == 0; + if (reg & PROFILE_INTEGRITY_BIT) { + fg_cap_learning_load_data(chip); + if (vbat_in_range && !fg_is_batt_empty(chip) && profiles_same) { + if (fg_debug_mask & FG_STATUS) + pr_info("Battery profiles same, using default\n"); + if (fg_est_dump) + schedule_work(&chip->dump_sram); + goto done; + } + } else { + pr_info("Battery profile not same, clearing data\n"); + clear_cycle_counter(chip); + chip->learning_data.learned_cc_uah = 0; + } + + if (fg_est_dump) + dump_sram(&chip->dump_sram); + + if ((fg_debug_mask & FG_STATUS) && !vbat_in_range) + pr_info("Vbat out of range: v_current_pred: %d, v:%d\n", + fg_data[FG_DATA_CPRED_VOLTAGE].value, + fg_data[FG_DATA_VOLTAGE].value); + + if ((fg_debug_mask & FG_STATUS) && fg_is_batt_empty(chip)) + pr_info("battery empty\n"); + + if ((fg_debug_mask & FG_STATUS) && !profiles_same) + pr_info("profiles differ\n"); + + if (fg_debug_mask & FG_STATUS) { + pr_info("Using new profile\n"); + print_hex_dump(KERN_INFO, "FG: loaded profile: ", + DUMP_PREFIX_NONE, 16, 1, + chip->batt_profile, len, false); + } + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + memcpy(chip->batt_profile, data, len); + + chip->batt_profile_len = len; + + if (fg_debug_mask & FG_STATUS) + print_hex_dump(KERN_INFO, "FG: new profile: ", + DUMP_PREFIX_NONE, 16, 1, chip->batt_profile, + chip->batt_profile_len, false); + + rc = fg_do_restart(chip, true); + if (rc) { + pr_err("restart failed: %d\n", rc); + goto no_profile; + } + + /* + * Only configure from profile if thermal-coefficients is not + * defined in the FG device node. + */ + if (!of_find_property(chip->pdev->dev.of_node, + "qcom,thermal-coefficients", NULL)) { + data = of_get_property(profile_node, + "qcom,thermal-coefficients", &len); + if (data && len == THERMAL_COEFF_N_BYTES) { + memcpy(chip->thermal_coefficients, data, len); + rc = fg_mem_write(chip, chip->thermal_coefficients, + THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES, + THERMAL_COEFF_OFFSET, 0); + if (rc) + pr_err("spmi write failed addr:%03x, ret:%d\n", + THERMAL_COEFF_ADDR, rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Battery thermal coefficients changed\n"); + } + } + +done: + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + if (fg_batt_type) + chip->batt_type = fg_batt_type; + else + chip->batt_type = batt_type_str; + chip->first_profile_loaded = true; + chip->profile_loaded = true; + chip->battery_missing = is_battery_missing(chip); + update_chg_iterm(chip); + update_cc_cv_setpoint(chip); + rc = populate_system_data(chip); + if (rc) { + pr_err("failed to read ocv properties=%d\n", rc); + return rc; + } + estimate_battery_age(chip, &chip->actual_cap_uah); + schedule_work(&chip->status_change_work); + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + fg_relax(&chip->profile_wakeup_source); + pr_info("Battery SOC: %d, V: %duV\n", get_prop_capacity(chip), + fg_data[FG_DATA_VOLTAGE].value); + return rc; +no_profile: + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + fg_relax(&chip->profile_wakeup_source); + return rc; +reschedule: + schedule_delayed_work( + &chip->batt_profile_init, + msecs_to_jiffies(BATTERY_PSY_WAIT_MS)); + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(0)); + fg_relax(&chip->profile_wakeup_source); + return 0; +} + +static void check_empty_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + check_empty_work.work); + + if (fg_is_batt_empty(chip)) { + if (fg_debug_mask & FG_STATUS) + pr_info("EMPTY SOC high\n"); + chip->soc_empty = true; + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + } + fg_relax(&chip->empty_check_wakeup_source); +} + +static void batt_profile_init(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + batt_profile_init.work); + + if (fg_batt_profile_init(chip)) + pr_err("failed to initialize profile\n"); +} + +static void sysfs_restart_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + sysfs_restart_work); + int rc; + + rc = fg_do_restart(chip, false); + if (rc) + pr_err("fg restart failed: %d\n", rc); + mutex_lock(&chip->sysfs_restart_lock); + fg_restart = 0; + mutex_unlock(&chip->sysfs_restart_lock); +} + +#define SRAM_MONOTONIC_SOC_REG 0x574 +#define SRAM_MONOTONIC_SOC_OFFSET 2 +#define SRAM_RELEASE_TIMEOUT_MS 500 +static void charge_full_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + charge_full_work); + int rc; + u8 buffer[3]; + int bsoc; + int resume_soc_raw = FULL_SOC_RAW - settings[FG_MEM_RESUME_SOC].value; + bool disable = false; + u8 reg; + + if (chip->status != POWER_SUPPLY_STATUS_FULL) { + if (fg_debug_mask & FG_STATUS) + pr_info("battery not full: %d\n", chip->status); + disable = true; + } + + fg_mem_lock(chip); + rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0); + if (rc) { + pr_err("Unable to read battery soc: %d\n", rc); + goto out; + } + if (buffer[2] <= resume_soc_raw) { + if (fg_debug_mask & FG_STATUS) + pr_info("bsoc = 0x%02x <= resume = 0x%02x\n", + buffer[2], resume_soc_raw); + disable = true; + } + if (!disable) + goto out; + + rc = fg_mem_write(chip, buffer, SOC_FULL_REG, 3, + SOC_FULL_OFFSET, 0); + if (rc) { + pr_err("failed to write SOC_FULL rc=%d\n", rc); + goto out; + } + /* force a full soc value into the monotonic in order to display 100 */ + buffer[0] = 0xFF; + buffer[1] = 0xFF; + rc = fg_mem_write(chip, buffer, SRAM_MONOTONIC_SOC_REG, 2, + SRAM_MONOTONIC_SOC_OFFSET, 0); + if (rc) { + pr_err("failed to write SOC_FULL rc=%d\n", rc); + goto out; + } + if (fg_debug_mask & FG_STATUS) { + bsoc = buffer[0] | buffer[1] << 8 | buffer[2] << 16; + pr_info("wrote %06x into soc full\n", bsoc); + } + fg_mem_release(chip); + /* + * wait one cycle to make sure the soc is updated before clearing + * the soc mask bit + */ + fg_mem_lock(chip); + fg_mem_read(chip, ®, PROFILE_INTEGRITY_REG, 1, 0, 0); +out: + fg_mem_release(chip); + if (disable) + chip->charge_full = false; +} + +static void update_bcl_thresholds(struct fg_chip *chip) +{ + u8 data[4]; + u8 mh_offset = 0, lm_offset = 0; + u16 address = 0; + int ret = 0; + + address = settings[FG_MEM_BCL_MH_THRESHOLD].address; + mh_offset = settings[FG_MEM_BCL_MH_THRESHOLD].offset; + lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset; + ret = fg_mem_read(chip, data, address, 4, 0, 1); + if (ret) + pr_err("Error reading BCL LM & MH threshold rc:%d\n", ret); + else + pr_debug("Old BCL LM threshold:%x MH threshold:%x\n", + data[lm_offset], data[mh_offset]); + BCL_MA_TO_ADC(settings[FG_MEM_BCL_MH_THRESHOLD].value, data[mh_offset]); + BCL_MA_TO_ADC(settings[FG_MEM_BCL_LM_THRESHOLD].value, data[lm_offset]); + + ret = fg_mem_write(chip, data, address, 4, 0, 0); + if (ret) + pr_err("spmi write failed. addr:%03x, ret:%d\n", + address, ret); + else + pr_debug("New BCL LM threshold:%x MH threshold:%x\n", + data[lm_offset], data[mh_offset]); +} + +static int disable_bcl_lpm(struct fg_chip *chip) +{ + u8 data[4]; + u8 lm_offset = 0; + u16 address = 0; + int rc = 0; + + address = settings[FG_MEM_BCL_LM_THRESHOLD].address; + lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset; + rc = fg_mem_read(chip, data, address, 4, 0, 1); + if (rc) { + pr_err("Error reading BCL LM & MH threshold rc:%d\n", rc); + return rc; + } + pr_debug("Old BCL LM threshold:%x\n", data[lm_offset]); + + /* Put BCL always above LPM */ + BCL_MA_TO_ADC(0, data[lm_offset]); + + rc = fg_mem_write(chip, data, address, 4, 0, 0); + if (rc) + pr_err("spmi write failed. addr:%03x, rc:%d\n", + address, rc); + else + pr_debug("New BCL LM threshold:%x\n", data[lm_offset]); + + return rc; +} + +static void bcl_hi_power_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + bcl_hi_power_work); + int rc; + + if (chip->bcl_lpm_disabled) { + rc = disable_bcl_lpm(chip); + if (rc) + pr_err("failed to disable bcl low mode %d\n", + rc); + } else { + update_bcl_thresholds(chip); + } +} + +#define VOLT_UV_TO_VOLTCMP8(volt_uv) \ + ((volt_uv - 2500000) / 9766) +static int update_irq_volt_empty(struct fg_chip *chip) +{ + u8 data; + int volt_mv = settings[FG_MEM_IRQ_VOLT_EMPTY].value; + + data = (u8)VOLT_UV_TO_VOLTCMP8(volt_mv * 1000); + + if (fg_debug_mask & FG_STATUS) + pr_info("voltage = %d, converted_raw = %04x\n", volt_mv, data); + return fg_mem_write(chip, &data, + settings[FG_MEM_IRQ_VOLT_EMPTY].address, 1, + settings[FG_MEM_IRQ_VOLT_EMPTY].offset, 0); +} + +static int update_cutoff_voltage(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_voltage_raw; + s64 voltage_mv = settings[FG_MEM_CUTOFF_VOLTAGE].value; + + converted_voltage_raw = (s16)MICROUNITS_TO_ADC_RAW(voltage_mv * 1000); + data[0] = cpu_to_le16(converted_voltage_raw) & 0xFF; + data[1] = cpu_to_le16(converted_voltage_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("voltage = %lld, converted_raw = %04x, data = %02x %02x\n", + voltage_mv, converted_voltage_raw, data[0], data[1]); + return fg_mem_write(chip, data, settings[FG_MEM_CUTOFF_VOLTAGE].address, + 2, settings[FG_MEM_CUTOFF_VOLTAGE].offset, 0); +} + +static int update_iterm(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_current_raw; + s64 current_ma = -settings[FG_MEM_TERM_CURRENT].value; + + converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000); + data[0] = cpu_to_le16(converted_current_raw) & 0xFF; + data[1] = cpu_to_le16(converted_current_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n", + current_ma, converted_current_raw, data[0], data[1]); + return fg_mem_write(chip, data, settings[FG_MEM_TERM_CURRENT].address, + 2, settings[FG_MEM_TERM_CURRENT].offset, 0); +} + +#define OF_READ_SETTING(type, qpnp_dt_property, retval, optional) \ +do { \ + if (retval) \ + break; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_dt_property, \ + &settings[type].value); \ + \ + if ((retval == -EINVAL) && optional) \ + retval = 0; \ + else if (retval) \ + pr_err("Error reading " #qpnp_dt_property \ + " property rc = %d\n", rc); \ +} while (0) + +#define OF_READ_PROPERTY(store, qpnp_dt_property, retval, default_val) \ +do { \ + if (retval) \ + break; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_dt_property, \ + &store); \ + \ + if (retval == -EINVAL) { \ + retval = 0; \ + store = default_val; \ + } else if (retval) { \ + pr_err("Error reading " #qpnp_dt_property \ + " property rc = %d\n", rc); \ + } \ +} while (0) + +#define DEFAULT_EVALUATION_CURRENT_MA 1000 +static int fg_of_init(struct fg_chip *chip) +{ + int rc = 0, sense_type, len = 0; + const char *data; + struct device_node *node = chip->pdev->dev.of_node; + u32 temp[2] = {0}; + + OF_READ_SETTING(FG_MEM_SOFT_HOT, "warm-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_SOFT_COLD, "cool-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_HARD_HOT, "hot-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_HARD_COLD, "cold-bat-decidegc", rc, 1); + + if (of_find_property(node, "qcom,cold-hot-jeita-hysteresis", NULL)) { + int hard_hot = 0, soft_hot = 0, hard_cold = 0, soft_cold = 0; + + rc = of_property_read_u32_array(node, + "qcom,cold-hot-jeita-hysteresis", temp, 2); + if (rc) { + pr_err("Error reading cold-hot-jeita-hysteresis rc=%d\n", + rc); + return rc; + } + + chip->jeita_hysteresis_support = true; + chip->cold_hysteresis = temp[0]; + chip->hot_hysteresis = temp[1]; + hard_hot = settings[FG_MEM_HARD_HOT].value; + soft_hot = settings[FG_MEM_SOFT_HOT].value; + hard_cold = settings[FG_MEM_HARD_COLD].value; + soft_cold = settings[FG_MEM_SOFT_COLD].value; + if (((hard_hot - chip->hot_hysteresis) < soft_hot) || + ((hard_cold + chip->cold_hysteresis) > soft_cold)) { + chip->jeita_hysteresis_support = false; + pr_err("invalid hysteresis: hot_hysterresis = %d cold_hysteresis = %d\n", + chip->hot_hysteresis, chip->cold_hysteresis); + } else { + pr_debug("cold_hysteresis = %d, hot_hysteresis = %d\n", + chip->cold_hysteresis, chip->hot_hysteresis); + } + } + + OF_READ_SETTING(FG_MEM_BCL_LM_THRESHOLD, "bcl-lm-threshold-ma", + rc, 1); + OF_READ_SETTING(FG_MEM_BCL_MH_THRESHOLD, "bcl-mh-threshold-ma", + rc, 1); + OF_READ_SETTING(FG_MEM_TERM_CURRENT, "fg-iterm-ma", rc, 1); + OF_READ_SETTING(FG_MEM_CHG_TERM_CURRENT, "fg-chg-iterm-ma", rc, 1); + OF_READ_SETTING(FG_MEM_CUTOFF_VOLTAGE, "fg-cutoff-voltage-mv", rc, 1); + data = of_get_property(chip->pdev->dev.of_node, + "qcom,thermal-coefficients", &len); + if (data && len == THERMAL_COEFF_N_BYTES) { + memcpy(chip->thermal_coefficients, data, len); + chip->use_thermal_coefficients = true; + } + OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc", rc, 1); + settings[FG_MEM_RESUME_SOC].value = + DIV_ROUND_CLOSEST(settings[FG_MEM_RESUME_SOC].value + * FULL_SOC_RAW, FULL_CAPACITY); + OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc-raw", rc, 1); + OF_READ_SETTING(FG_MEM_IRQ_VOLT_EMPTY, "irq-volt-empty-mv", rc, 1); + OF_READ_SETTING(FG_MEM_VBAT_EST_DIFF, "vbat-estimate-diff-mv", rc, 1); + OF_READ_SETTING(FG_MEM_DELTA_SOC, "fg-delta-soc", rc, 1); + OF_READ_SETTING(FG_MEM_BATT_LOW, "fg-vbatt-low-threshold", rc, 1); + OF_READ_SETTING(FG_MEM_THERM_DELAY, "fg-therm-delay-us", rc, 1); + OF_READ_PROPERTY(chip->learning_data.max_increment, + "cl-max-increment-deciperc", rc, 5); + OF_READ_PROPERTY(chip->learning_data.max_decrement, + "cl-max-decrement-deciperc", rc, 100); + OF_READ_PROPERTY(chip->learning_data.max_temp, + "cl-max-temp-decidegc", rc, 450); + OF_READ_PROPERTY(chip->learning_data.min_temp, + "cl-min-temp-decidegc", rc, 150); + OF_READ_PROPERTY(chip->learning_data.max_start_soc, + "cl-max-start-capacity", rc, 15); + OF_READ_PROPERTY(chip->learning_data.vbat_est_thr_uv, + "cl-vbat-est-thr-uv", rc, 40000); + OF_READ_PROPERTY(chip->evaluation_current, + "aging-eval-current-ma", rc, + DEFAULT_EVALUATION_CURRENT_MA); + OF_READ_PROPERTY(chip->cc_cv_threshold_mv, + "fg-cc-cv-threshold-mv", rc, 0); + if (of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-learning-on")) + chip->batt_aging_mode = FG_AGING_CC; + else if (of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-estimation-on")) + chip->batt_aging_mode = FG_AGING_ESR; + else + chip->batt_aging_mode = FG_AGING_NONE; + if (chip->batt_aging_mode == FG_AGING_CC) { + chip->learning_data.feedback_on + = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-learning-feedback"); + } + if (fg_debug_mask & FG_AGING) + pr_info("battery aging mode: %d\n", chip->batt_aging_mode); + + /* Get the use-otp-profile property */ + chip->use_otp_profile = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,use-otp-profile"); + chip->hold_soc_while_full + = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,hold-soc-while-full"); + + sense_type = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,ext-sense-type"); + if (rc == 0) { + if (fg_sense_type < 0) + fg_sense_type = sense_type; + + if (fg_debug_mask & FG_STATUS) { + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + pr_info("Using internal sense\n"); + else if (fg_sense_type == EXTERNAL_CURRENT_SENSE) + pr_info("Using external sense\n"); + else + pr_info("Using default sense\n"); + } + } else { + rc = 0; + } + + chip->bad_batt_detection_en = of_property_read_bool(node, + "qcom,bad-battery-detection-enable"); + + chip->sw_rbias_ctrl = of_property_read_bool(node, + "qcom,sw-rbias-control"); + + chip->cyc_ctr.en = of_property_read_bool(node, + "qcom,cycle-counter-en"); + if (chip->cyc_ctr.en) + chip->cyc_ctr.id = 1; + + chip->esr_pulse_tune_en = of_property_read_bool(node, + "qcom,esr-pulse-tuning-en"); + + return rc; +} + +static int fg_init_irqs(struct fg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + struct platform_device *pdev = chip->pdev; + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + child->full_name, rc); + return rc; + } + + if ((base == chip->vbat_adc_addr) || + (base == chip->ibat_adc_addr) || + (base == chip->tp_rev_addr)) + continue; + + rc = fg_read(chip, &subtype, + base + REG_OFFSET_PERP_SUBTYPE, 1); + if (rc) { + pr_err("Peripheral subtype read failed rc=%d\n", rc); + return rc; + } + + switch (subtype) { + case FG_SOC: + chip->soc_irq[FULL_SOC].irq = of_irq_get_byname(child, + "full-soc"); + if (chip->soc_irq[FULL_SOC].irq < 0) { + pr_err("Unable to get full-soc irq\n"); + return rc; + } + chip->soc_irq[EMPTY_SOC].irq = of_irq_get_byname(child, + "empty-soc"); + if (chip->soc_irq[EMPTY_SOC].irq < 0) { + pr_err("Unable to get low-soc irq\n"); + return rc; + } + chip->soc_irq[DELTA_SOC].irq = of_irq_get_byname(child, + "delta-soc"); + if (chip->soc_irq[DELTA_SOC].irq < 0) { + pr_err("Unable to get delta-soc irq\n"); + return rc; + } + chip->soc_irq[FIRST_EST_DONE].irq + = of_irq_get_byname(child, "first-est-done"); + if (chip->soc_irq[FIRST_EST_DONE].irq < 0) { + pr_err("Unable to get first-est-done irq\n"); + return rc; + } + + rc = devm_request_irq(chip->dev, + chip->soc_irq[FULL_SOC].irq, + fg_soc_irq_handler, IRQF_TRIGGER_RISING, + "full-soc", chip); + if (rc < 0) { + pr_err("Can't request %d full-soc: %d\n", + chip->soc_irq[FULL_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[EMPTY_SOC].irq, + fg_empty_soc_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "empty-soc", chip); + if (rc < 0) { + pr_err("Can't request %d empty-soc: %d\n", + chip->soc_irq[EMPTY_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[DELTA_SOC].irq, + fg_soc_irq_handler, IRQF_TRIGGER_RISING, + "delta-soc", chip); + if (rc < 0) { + pr_err("Can't request %d delta-soc: %d\n", + chip->soc_irq[DELTA_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[FIRST_EST_DONE].irq, + fg_first_soc_irq_handler, IRQF_TRIGGER_RISING, + "first-est-done", chip); + if (rc < 0) { + pr_err("Can't request %d delta-soc: %d\n", + chip->soc_irq[FIRST_EST_DONE].irq, rc); + return rc; + } + + enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); + enable_irq_wake(chip->soc_irq[FULL_SOC].irq); + enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); + break; + case FG_MEMIF: + chip->mem_irq[FG_MEM_AVAIL].irq + = of_irq_get_byname(child, "mem-avail"); + if (chip->mem_irq[FG_MEM_AVAIL].irq < 0) { + pr_err("Unable to get mem-avail irq\n"); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->mem_irq[FG_MEM_AVAIL].irq, + fg_mem_avail_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "mem-avail", chip); + if (rc < 0) { + pr_err("Can't request %d mem-avail: %d\n", + chip->mem_irq[FG_MEM_AVAIL].irq, rc); + return rc; + } + break; + case FG_BATT: + chip->batt_irq[BATT_MISSING].irq + = of_irq_get_byname(child, "batt-missing"); + if (chip->batt_irq[BATT_MISSING].irq < 0) { + pr_err("Unable to get batt-missing irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_threaded_irq(chip->dev, + chip->batt_irq[BATT_MISSING].irq, + NULL, + fg_batt_missing_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "batt-missing", chip); + if (rc < 0) { + pr_err("Can't request %d batt-missing: %d\n", + chip->batt_irq[BATT_MISSING].irq, rc); + return rc; + } + chip->batt_irq[VBATT_LOW].irq + = of_irq_get_byname(child, "vbatt-low"); + if (chip->batt_irq[VBATT_LOW].irq < 0) { + pr_err("Unable to get vbatt-low irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_irq(chip->dev, + chip->batt_irq[VBATT_LOW].irq, + fg_vbatt_low_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "vbatt-low", chip); + if (rc < 0) { + pr_err("Can't request %d vbatt-low: %d\n", + chip->batt_irq[VBATT_LOW].irq, rc); + return rc; + } + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + break; + case FG_ADC: + break; + default: + pr_err("subtype %d\n", subtype); + return -EINVAL; + } + } + + return rc; +} + +static void fg_cleanup(struct fg_chip *chip) +{ + cancel_delayed_work_sync(&chip->update_sram_data); + cancel_delayed_work_sync(&chip->update_temp_work); + cancel_delayed_work_sync(&chip->update_jeita_setting); + cancel_delayed_work_sync(&chip->check_empty_work); + cancel_delayed_work_sync(&chip->batt_profile_init); + alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + cancel_work_sync(&chip->rslow_comp_work); + cancel_work_sync(&chip->set_resume_soc_work); + cancel_work_sync(&chip->fg_cap_learning_work); + cancel_work_sync(&chip->dump_sram); + cancel_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->cycle_count_work); + cancel_work_sync(&chip->update_esr_work); + cancel_work_sync(&chip->sysfs_restart_work); + cancel_work_sync(&chip->gain_comp_work); + cancel_work_sync(&chip->init_work); + cancel_work_sync(&chip->charge_full_work); + cancel_work_sync(&chip->esr_extract_config_work); + mutex_destroy(&chip->rslow_comp.lock); + mutex_destroy(&chip->rw_lock); + mutex_destroy(&chip->cyc_ctr.lock); + mutex_destroy(&chip->learning_data.learning_lock); + mutex_destroy(&chip->sysfs_restart_lock); + wakeup_source_trash(&chip->resume_soc_wakeup_source.source); + wakeup_source_trash(&chip->empty_check_wakeup_source.source); + wakeup_source_trash(&chip->memif_wakeup_source.source); + wakeup_source_trash(&chip->profile_wakeup_source.source); + wakeup_source_trash(&chip->update_temp_wakeup_source.source); + wakeup_source_trash(&chip->update_sram_wakeup_source.source); + wakeup_source_trash(&chip->gain_comp_wakeup_source.source); + wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); + wakeup_source_trash(&chip->esr_extract_wakeup_source.source); +} + +static int fg_remove(struct platform_device *pdev) +{ + struct fg_chip *chip = dev_get_drvdata(&pdev->dev); + + fg_cleanup(chip); + dev_set_drvdata(&pdev->dev, NULL); + return 0; +} + +static int fg_memif_data_open(struct inode *inode, struct file *file) +{ + struct fg_log_buffer *log; + struct fg_trans *trans; + u8 *data_buf; + + size_t logbufsize = SZ_4K; + size_t databufsize = SZ_4K; + + if (!dbgfs_data.chip) { + pr_err("Not initialized data\n"); + return -EINVAL; + } + + /* Per file "transaction" data */ + trans = kzalloc(sizeof(*trans), GFP_KERNEL); + if (!trans) { + pr_err("Unable to allocate memory for transaction data\n"); + return -ENOMEM; + } + + /* Allocate log buffer */ + log = kzalloc(logbufsize, GFP_KERNEL); + + if (!log) { + kfree(trans); + pr_err("Unable to allocate memory for log buffer\n"); + return -ENOMEM; + } + + log->rpos = 0; + log->wpos = 0; + log->len = logbufsize - sizeof(*log); + + /* Allocate data buffer */ + data_buf = kzalloc(databufsize, GFP_KERNEL); + + if (!data_buf) { + kfree(trans); + kfree(log); + pr_err("Unable to allocate memory for data buffer\n"); + return -ENOMEM; + } + + trans->log = log; + trans->data = data_buf; + trans->cnt = dbgfs_data.cnt; + trans->addr = dbgfs_data.addr; + trans->chip = dbgfs_data.chip; + trans->offset = trans->addr; + + file->private_data = trans; + return 0; +} + +static int fg_memif_dfs_close(struct inode *inode, struct file *file) +{ + struct fg_trans *trans = file->private_data; + + if (trans && trans->log && trans->data) { + file->private_data = NULL; + kfree(trans->log); + kfree(trans->data); + kfree(trans); + } + + return 0; +} + +/** + * print_to_log: format a string and place into the log buffer + * @log: The log buffer to place the result into. + * @fmt: The format string to use. + * @...: The arguments for the format string. + * + * The return value is the number of characters written to @log buffer + * not including the trailing '\0'. + */ +static int print_to_log(struct fg_log_buffer *log, const char *fmt, ...) +{ + va_list args; + int cnt; + char *buf = &log->data[log->wpos]; + size_t size = log->len - log->wpos; + + va_start(args, fmt); + cnt = vscnprintf(buf, size, fmt, args); + va_end(args); + + log->wpos += cnt; + return cnt; +} + +/** + * write_next_line_to_log: Writes a single "line" of data into the log buffer + * @trans: Pointer to SRAM transaction data. + * @offset: SRAM address offset to start reading from. + * @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read. + * + * The 'offset' is a 12-bit SRAM address. + * + * On a successful read, the pcnt is decremented by the number of data + * bytes read from the SRAM. When the cnt reaches 0, all requested bytes have + * been read. + */ +static int +write_next_line_to_log(struct fg_trans *trans, int offset, size_t *pcnt) +{ + int i, j; + u8 data[ITEMS_PER_LINE]; + struct fg_log_buffer *log = trans->log; + + int cnt = 0; + int padding = offset % ITEMS_PER_LINE; + int items_to_read = min(ARRAY_SIZE(data) - padding, *pcnt); + int items_to_log = min(ITEMS_PER_LINE, padding + items_to_read); + + /* Buffer needs enough space for an entire line */ + if ((log->len - log->wpos) < MAX_LINE_LENGTH) + goto done; + + memcpy(data, trans->data + (offset - trans->addr), items_to_read); + + *pcnt -= items_to_read; + + /* Each line starts with the aligned offset (12-bit address) */ + cnt = print_to_log(log, "%3.3X ", offset & 0xfff); + if (cnt == 0) + goto done; + + /* If the offset is unaligned, add padding to right justify items */ + for (i = 0; i < padding; ++i) { + cnt = print_to_log(log, "-- "); + if (cnt == 0) + goto done; + } + + /* Log the data items */ + for (j = 0; i < items_to_log; ++i, ++j) { + cnt = print_to_log(log, "%2.2X ", data[j]); + if (cnt == 0) + goto done; + } + + /* If the last character was a space, then replace it with a newline */ + if (log->wpos > 0 && log->data[log->wpos - 1] == ' ') + log->data[log->wpos - 1] = '\n'; + +done: + return cnt; +} + +/** + * get_log_data - reads data from SRAM and saves to the log buffer + * @trans: Pointer to SRAM transaction data. + * + * Returns the number of "items" read or SPMI error code for read failures. + */ +static int get_log_data(struct fg_trans *trans) +{ + int cnt, rc; + int last_cnt; + int items_read; + int total_items_read = 0; + u32 offset = trans->offset; + size_t item_cnt = trans->cnt; + struct fg_log_buffer *log = trans->log; + + if (item_cnt == 0) + return 0; + + if (item_cnt > SZ_4K) { + pr_err("Reading too many bytes\n"); + return -EINVAL; + } + + rc = fg_mem_read(trans->chip, trans->data, + trans->addr, trans->cnt, 0, 0); + if (rc) { + pr_err("dump failed: rc = %d\n", rc); + return rc; + } + /* Reset the log buffer 'pointers' */ + log->wpos = log->rpos = 0; + + /* Keep reading data until the log is full */ + do { + last_cnt = item_cnt; + cnt = write_next_line_to_log(trans, offset, &item_cnt); + items_read = last_cnt - item_cnt; + offset += items_read; + total_items_read += items_read; + } while (cnt && item_cnt > 0); + + /* Adjust the transaction offset and count */ + trans->cnt = item_cnt; + trans->offset += total_items_read; + + return total_items_read; +} + +/** + * fg_memif_dfs_reg_read: reads value(s) from SRAM and fills user's buffer a + * byte array (coded as string) + * @file: file pointer + * @buf: where to put the result + * @count: maximum space available in @buf + * @ppos: starting position + * @return number of user bytes read, or negative error value + */ +static ssize_t fg_memif_dfs_reg_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) +{ + struct fg_trans *trans = file->private_data; + struct fg_log_buffer *log = trans->log; + size_t ret; + size_t len; + + /* Is the the log buffer empty */ + if (log->rpos >= log->wpos) { + if (get_log_data(trans) <= 0) + return 0; + } + + len = min(count, log->wpos - log->rpos); + + ret = copy_to_user(buf, &log->data[log->rpos], len); + if (ret == len) { + pr_err("error copy sram register values to user\n"); + return -EFAULT; + } + + /* 'ret' is the number of bytes not copied */ + len -= ret; + + *ppos += len; + log->rpos += len; + return len; +} + +/** + * fg_memif_dfs_reg_write: write user's byte array (coded as string) to SRAM. + * @file: file pointer + * @buf: user data to be written. + * @count: maximum space available in @buf + * @ppos: starting position + * @return number of user byte written, or negative error value + */ +static ssize_t fg_memif_dfs_reg_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + int bytes_read; + int data; + int pos = 0; + int cnt = 0; + u8 *values; + size_t ret = 0; + + struct fg_trans *trans = file->private_data; + u32 offset = trans->offset; + + /* Make a copy of the user data */ + char *kbuf = kmalloc(count + 1, GFP_KERNEL); + if (!kbuf) + return -ENOMEM; + + ret = copy_from_user(kbuf, buf, count); + if (ret == count) { + pr_err("failed to copy data from user\n"); + ret = -EFAULT; + goto free_buf; + } + + count -= ret; + *ppos += count; + kbuf[count] = '\0'; + + /* Override the text buffer with the raw data */ + values = kbuf; + + /* Parse the data in the buffer. It should be a string of numbers */ + while (sscanf(kbuf + pos, "%i%n", &data, &bytes_read) == 1) { + pos += bytes_read; + values[cnt++] = data & 0xff; + } + + if (!cnt) + goto free_buf; + + pr_info("address %x, count %d\n", offset, cnt); + /* Perform the write(s) */ + + ret = fg_mem_write(trans->chip, values, offset, + cnt, 0, 0); + if (ret) { + pr_err("SPMI write failed, err = %zu\n", ret); + } else { + ret = count; + trans->offset += cnt > 4 ? 4 : cnt; + } + +free_buf: + kfree(kbuf); + return ret; +} + +static const struct file_operations fg_memif_dfs_reg_fops = { + .open = fg_memif_data_open, + .release = fg_memif_dfs_close, + .read = fg_memif_dfs_reg_read, + .write = fg_memif_dfs_reg_write, +}; + +/** + * fg_dfs_create_fs: create debugfs file system. + * @return pointer to root directory or NULL if failed to create fs + */ +static struct dentry *fg_dfs_create_fs(void) +{ + struct dentry *root, *file; + + pr_debug("Creating FG_MEM debugfs file-system\n"); + root = debugfs_create_dir(DFS_ROOT_NAME, NULL); + if (IS_ERR_OR_NULL(root)) { + pr_err("Error creating top level directory err:%ld", + (long)root); + if (PTR_ERR(root) == -ENODEV) + pr_err("debugfs is not enabled in the kernel"); + return NULL; + } + + dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data); + + file = debugfs_create_blob("help", S_IRUGO, root, &dbgfs_data.help_msg); + if (!file) { + pr_err("error creating help entry\n"); + goto err_remove_fs; + } + return root; + +err_remove_fs: + debugfs_remove_recursive(root); + return NULL; +} + +/** + * fg_dfs_get_root: return a pointer to FG debugfs root directory. + * @return a pointer to the existing directory, or if no root + * directory exists then create one. Directory is created with file that + * configures SRAM transaction, namely: address, and count. + * @returns valid pointer on success or NULL + */ +struct dentry *fg_dfs_get_root(void) +{ + if (dbgfs_data.root) + return dbgfs_data.root; + + if (mutex_lock_interruptible(&dbgfs_data.lock) < 0) + return NULL; + /* critical section */ + if (!dbgfs_data.root) { /* double checking idiom */ + dbgfs_data.root = fg_dfs_create_fs(); + } + mutex_unlock(&dbgfs_data.lock); + return dbgfs_data.root; +} + +/* + * fg_dfs_create: adds new fg_mem if debugfs entry + * @return zero on success + */ +int fg_dfs_create(struct fg_chip *chip) +{ + struct dentry *root; + struct dentry *file; + + root = fg_dfs_get_root(); + if (!root) + return -ENOENT; + + dbgfs_data.chip = chip; + + file = debugfs_create_u32("count", DFS_MODE, root, &(dbgfs_data.cnt)); + if (!file) { + pr_err("error creating 'count' entry\n"); + goto err_remove_fs; + } + + file = debugfs_create_x32("address", DFS_MODE, + root, &(dbgfs_data.addr)); + if (!file) { + pr_err("error creating 'address' entry\n"); + goto err_remove_fs; + } + + file = debugfs_create_file("data", DFS_MODE, root, &dbgfs_data, + &fg_memif_dfs_reg_fops); + if (!file) { + pr_err("error creating 'data' entry\n"); + goto err_remove_fs; + } + + return 0; + +err_remove_fs: + debugfs_remove_recursive(root); + return -ENOMEM; +} + +#define EXTERNAL_SENSE_OFFSET_REG 0x41C +#define EXT_OFFSET_TRIM_REG 0xF8 +#define SEC_ACCESS_REG 0xD0 +#define SEC_ACCESS_UNLOCK 0xA5 +#define BCL_TRIM_REV_FIXED 12 +static int bcl_trim_workaround(struct fg_chip *chip) +{ + u8 reg, rc; + + if (chip->tp_rev_addr == 0) + return 0; + + rc = fg_read(chip, ®, chip->tp_rev_addr, 1); + if (rc) { + pr_err("Failed to read tp reg, rc = %d\n", rc); + return rc; + } + if (reg >= BCL_TRIM_REV_FIXED) { + if (fg_debug_mask & FG_STATUS) + pr_info("workaround not applied, tp_rev = %d\n", reg); + return 0; + } + + rc = fg_mem_read(chip, ®, EXTERNAL_SENSE_OFFSET_REG, 1, 2, 0); + if (rc) { + pr_err("Failed to read ext sense offset trim, rc = %d\n", rc); + return rc; + } + rc = fg_masked_write(chip, chip->soc_base + SEC_ACCESS_REG, + SEC_ACCESS_UNLOCK, SEC_ACCESS_UNLOCK, 1); + + rc |= fg_masked_write(chip, chip->soc_base + EXT_OFFSET_TRIM_REG, + 0xFF, reg, 1); + if (rc) { + pr_err("Failed to write ext sense offset trim, rc = %d\n", rc); + return rc; + } + + return 0; +} + +#define FG_ALG_SYSCTL_1 0x4B0 +#define SOC_CNFG 0x450 +#define SOC_DELTA_OFFSET 3 +#define DELTA_SOC_PERCENT 1 +#define I_TERM_QUAL_BIT BIT(1) +#define PATCH_NEG_CURRENT_BIT BIT(3) +#define KI_COEFF_PRED_FULL_ADDR 0x408 +#define KI_COEFF_PRED_FULL_4_0_MSB 0x88 +#define KI_COEFF_PRED_FULL_4_0_LSB 0x00 +#define TEMP_FRAC_SHIFT_REG 0x4A4 +#define FG_ADC_CONFIG_REG 0x4B8 +#define FG_BCL_CONFIG_OFFSET 0x3 +#define BCL_FORCED_HPM_IN_CHARGE BIT(2) +static int fg_common_hw_init(struct fg_chip *chip) +{ + int rc; + int resume_soc_raw; + u8 val; + + update_iterm(chip); + update_cutoff_voltage(chip); + update_irq_volt_empty(chip); + update_bcl_thresholds(chip); + + resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; + if (resume_soc_raw > 0) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + return rc; + } + } else { + pr_info("FG auto recharge threshold not specified in DT\n"); + } + + if (fg_sense_type >= 0) { + rc = set_prop_sense_type(chip, fg_sense_type); + if (rc) { + pr_err("failed to config sense type %d rc=%d\n", + fg_sense_type, rc); + return rc; + } + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_DELTA_SOC].address, 0xFF, + soc_to_setpoint(settings[FG_MEM_DELTA_SOC].value), + settings[FG_MEM_DELTA_SOC].offset); + if (rc) { + pr_err("failed to write delta soc rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF, + batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value), + settings[FG_MEM_BATT_LOW].offset); + if (rc) { + pr_err("failed to write Vbatt_low rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_THERM_DELAY].address, + THERM_DELAY_MASK, + therm_delay_to_setpoint(settings[FG_MEM_THERM_DELAY].value), + settings[FG_MEM_THERM_DELAY].offset); + if (rc) { + pr_err("failed to write therm_delay rc=%d\n", rc); + return rc; + } + + if (chip->use_thermal_coefficients) { + fg_mem_write(chip, chip->thermal_coefficients, + THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES, + THERMAL_COEFF_OFFSET, 0); + } + + if (!chip->sw_rbias_ctrl) { + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + BATT_TEMP_CNTRL_MASK, + TEMP_SENSE_ALWAYS_BIT, + BATT_TEMP_OFFSET); + if (rc) { + pr_err("failed to write BATT_TEMP_OFFSET rc=%d\n", rc); + return rc; + } + } + + /* Read the cycle counter back from FG SRAM */ + if (chip->cyc_ctr.en) + restore_cycle_counter(chip); + + if (chip->esr_pulse_tune_en) { + rc = fg_mem_read(chip, &val, SYS_CFG_1_REG, 1, SYS_CFG_1_OFFSET, + 0); + if (rc) { + pr_err("unable to read sys_cfg_1: %d\n", rc); + return rc; + } + + if (!(val & ENABLE_ESR_PULSE_VAL)) + chip->esr_extract_disabled = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract is %sabled\n", + chip->esr_extract_disabled ? "dis" : "en"); + + rc = fg_mem_read(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS1_OFFSET, 0); + if (rc) { + pr_err("unable to read cbits_input_filter_reg: %d\n", + rc); + return rc; + } + + if (val & (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT)) + chip->imptr_pulse_slow_en = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled\n", + chip->imptr_pulse_slow_en ? "en" : "dis"); + + rc = fg_mem_read(chip, &val, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, + 0); + if (rc) { + pr_err("unable to read rslow cfg: %d\n", rc); + return rc; + } + + if (val & RSLOW_CFG_ON_VAL) + chip->rslow_comp.active = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("rslow_comp active is %sabled\n", + chip->rslow_comp.active ? "en" : "dis"); + } + + return 0; +} + +static int fg_8994_hw_init(struct fg_chip *chip) +{ + int rc = 0; + u8 data[4]; + u64 esr_value; + + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + PATCH_NEG_CURRENT_BIT, + PATCH_NEG_CURRENT_BIT, + EXTERNAL_SENSE_OFFSET); + if (rc) { + pr_err("failed to write patch current bit rc=%d\n", rc); + return rc; + } + + rc = bcl_trim_workaround(chip); + if (rc) { + pr_err("failed to redo bcl trim rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) { + pr_err("failed to force hpm in charge rc=%d\n", rc); + return rc; + } + + fg_mem_masked_write(chip, FG_ALG_SYSCTL_1, I_TERM_QUAL_BIT, 0, 0); + + data[0] = 0xA2; + data[1] = 0x12; + + rc = fg_mem_write(chip, data, TEMP_FRAC_SHIFT_REG, 2, 2, 0); + if (rc) { + pr_err("failed to write temp ocv constants rc=%d\n", rc); + return rc; + } + + data[0] = KI_COEFF_PRED_FULL_4_0_LSB; + data[1] = KI_COEFF_PRED_FULL_4_0_MSB; + fg_mem_write(chip, data, KI_COEFF_PRED_FULL_ADDR, 2, 2, 0); + + esr_value = ESR_DEFAULT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write default ESR value rc=%d\n", rc); + else + pr_debug("set default value to esr filter\n"); + + return 0; +} + +#define FG_USBID_CONFIG_OFFSET 0x2 +#define DISABLE_USBID_DETECT_BIT BIT(0) +static int fg_8996_hw_init(struct fg_chip *chip) +{ + int rc; + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) { + pr_err("failed to force hpm in charge rc=%d\n", rc); + return rc; + } + + /* enable usbid conversions for PMi8996 V1.0 */ + if (chip->pmic_revision[REVID_DIG_MAJOR] == 1 + && chip->pmic_revision[REVID_ANA_MAJOR] == 0) { + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + DISABLE_USBID_DETECT_BIT, + 0, FG_USBID_CONFIG_OFFSET); + if (rc) { + pr_err("failed to enable usbid conversions: %d\n", rc); + return rc; + } + } + + return rc; +} + +static int fg_8950_hw_init(struct fg_chip *chip) +{ + int rc; + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) + pr_err("failed to force hpm in charge rc=%d\n", rc); + + return rc; +} + +static int fg_hw_init(struct fg_chip *chip) +{ + int rc = 0; + + rc = fg_common_hw_init(chip); + if (rc) { + pr_err("Unable to initialize FG HW rc=%d\n", rc); + return rc; + } + + /* add PMIC specific hw init */ + switch (chip->pmic_subtype) { + case PMI8994: + rc = fg_8994_hw_init(chip); + chip->wa_flag |= PULSE_REQUEST_WA; + break; + case PMI8996: + rc = fg_8996_hw_init(chip); + /* Setup workaround flag based on PMIC type */ + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + chip->wa_flag |= IADC_GAIN_COMP_WA; + if (chip->pmic_revision[REVID_DIG_MAJOR] > 1) + chip->wa_flag |= USE_CC_SOC_REG; + + break; + case PMI8950: + case PMI8937: + rc = fg_8950_hw_init(chip); + /* Setup workaround flag based on PMIC type */ + chip->wa_flag |= BCL_HI_POWER_FOR_CHGLED_WA; + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + chip->wa_flag |= IADC_GAIN_COMP_WA; + if (chip->pmic_revision[REVID_DIG_MAJOR] > 1) + chip->wa_flag |= USE_CC_SOC_REG; + + break; + } + if (rc) + pr_err("Unable to initialize PMIC specific FG HW rc=%d\n", rc); + + pr_debug("wa_flag=0x%x\n", chip->wa_flag); + + return rc; +} + +#define DIG_MINOR 0x0 +#define DIG_MAJOR 0x1 +#define ANA_MINOR 0x2 +#define ANA_MAJOR 0x3 +#define IACS_INTR_SRC_SLCT BIT(3) +static int fg_setup_memif_offset(struct fg_chip *chip) +{ + int rc; + + rc = fg_read(chip, chip->revision, chip->mem_base + DIG_MINOR, 4); + if (rc) { + pr_err("Unable to read FG revision rc=%d\n", rc); + return rc; + } + + switch (chip->revision[DIG_MAJOR]) { + case DIG_REV_1: + case DIG_REV_2: + chip->offset = offset[0].address; + break; + case DIG_REV_3: + chip->offset = offset[1].address; + chip->ima_supported = true; + break; + default: + pr_err("Digital Major rev=%d not supported\n", + chip->revision[DIG_MAJOR]); + return -EINVAL; + } + + if (chip->ima_supported) { + /* + * Change the FG_MEM_INT interrupt to track IACS_READY + * condition instead of end-of-transaction. This makes sure + * that the next transaction starts only after the hw is ready. + */ + rc = fg_masked_write(chip, + chip->mem_base + MEM_INTF_IMA_CFG, IACS_INTR_SRC_SLCT, + IACS_INTR_SRC_SLCT, 1); + if (rc) { + pr_err("failed to configure interrupt source %d\n", rc); + return rc; + } + } + + return 0; +} + +static int fg_detect_pmic_type(struct fg_chip *chip) +{ + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + + revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR_OR_NULL(pmic_rev_id)) { + pr_err("Unable to get pmic_revid rc=%ld\n", + PTR_ERR(pmic_rev_id)); + /* + * the revid peripheral must be registered, any failure + * here only indicates that the rev-id module has not + * probed yet. + */ + return -EPROBE_DEFER; + } + + switch (pmic_rev_id->pmic_subtype) { + case PMI8994: + case PMI8950: + case PMI8937: + case PMI8996: + chip->pmic_subtype = pmic_rev_id->pmic_subtype; + chip->pmic_revision[REVID_RESERVED] = pmic_rev_id->rev1; + chip->pmic_revision[REVID_VARIANT] = pmic_rev_id->rev2; + chip->pmic_revision[REVID_ANA_MAJOR] = pmic_rev_id->rev3; + chip->pmic_revision[REVID_DIG_MAJOR] = pmic_rev_id->rev4; + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + return 0; +} + +#define INIT_JEITA_DELAY_MS 1000 + +static void delayed_init_work(struct work_struct *work) +{ + u8 reg[2]; + int rc; + struct fg_chip *chip = container_of(work, + struct fg_chip, + init_work); + + /* hold memory access until initialization finishes */ + fg_mem_lock(chip); + + rc = fg_hw_init(chip); + if (rc) { + pr_err("failed to hw init rc = %d\n", rc); + fg_mem_release(chip); + fg_cleanup(chip); + return; + } + /* release memory access before update_sram_data is called */ + fg_mem_release(chip); + + schedule_delayed_work( + &chip->update_jeita_setting, + msecs_to_jiffies(INIT_JEITA_DELAY_MS)); + + if (chip->last_sram_update_time == 0) + update_sram_data_work(&chip->update_sram_data.work); + + if (chip->last_temp_update_time == 0) + update_temp_data(&chip->update_temp_work.work); + + if (!chip->use_otp_profile) + schedule_delayed_work(&chip->batt_profile_init, 0); + + if (chip->wa_flag & IADC_GAIN_COMP_WA) { + /* read default gain config */ + rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read default gain rc=%d\n", rc); + goto done; + } + + if (reg[1] || reg[0]) { + /* + * Default gain register has valid value: + * - write to gain register. + */ + rc = fg_mem_write(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write gain rc=%d\n", rc); + goto done; + } + } else { + /* + * Default gain register is invalid: + * - read gain register for default gain value + * - write to default gain register. + */ + rc = fg_mem_read(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read gain rc=%d\n", rc); + goto done; + } + rc = fg_mem_write(chip, reg, K_VCOR_REG, 2, + DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write default gain rc=%d\n", + rc); + goto done; + } + } + + chip->iadc_comp_data.dfl_gain_reg[0] = reg[0]; + chip->iadc_comp_data.dfl_gain_reg[1] = reg[1]; + chip->iadc_comp_data.dfl_gain = half_float(reg); + chip->input_present = is_input_present(chip); + chip->otg_present = is_otg_present(chip); + chip->init_done = true; + + pr_debug("IADC gain initial config reg_val 0x%x%x gain %lld\n", + reg[1], reg[0], chip->iadc_comp_data.dfl_gain); + } + + pr_debug("FG: HW_init success\n"); + + return; +done: + fg_cleanup(chip); +} + +static int fg_probe(struct platform_device *pdev) +{ + struct device *dev = &(pdev->dev); + struct fg_chip *chip; + struct device_node *child; + unsigned int base; + u8 subtype, reg; + int rc = 0; + struct power_supply_config bms_psy_cfg; + + if (!pdev) { + pr_err("no valid spmi pointer\n"); + return -ENODEV; + } + + if (!pdev->dev.of_node) { + pr_err("device node missing\n"); + return -ENODEV; + } + + chip = devm_kzalloc(dev, sizeof(struct fg_chip), GFP_KERNEL); + if (chip == NULL) { + pr_err("Can't allocate fg_chip\n"); + return -ENOMEM; + } + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->pdev = pdev; + chip->dev = &(pdev->dev); + + wakeup_source_init(&chip->empty_check_wakeup_source.source, + "qpnp_fg_empty_check"); + wakeup_source_init(&chip->memif_wakeup_source.source, + "qpnp_fg_memaccess"); + wakeup_source_init(&chip->profile_wakeup_source.source, + "qpnp_fg_profile"); + wakeup_source_init(&chip->update_temp_wakeup_source.source, + "qpnp_fg_update_temp"); + wakeup_source_init(&chip->update_sram_wakeup_source.source, + "qpnp_fg_update_sram"); + wakeup_source_init(&chip->resume_soc_wakeup_source.source, + "qpnp_fg_set_resume_soc"); + wakeup_source_init(&chip->gain_comp_wakeup_source.source, + "qpnp_fg_gain_comp"); + wakeup_source_init(&chip->capacity_learning_wakeup_source.source, + "qpnp_fg_cap_learning"); + wakeup_source_init(&chip->esr_extract_wakeup_source.source, + "qpnp_fg_esr_extract"); + mutex_init(&chip->rw_lock); + mutex_init(&chip->cyc_ctr.lock); + mutex_init(&chip->learning_data.learning_lock); + mutex_init(&chip->rslow_comp.lock); + mutex_init(&chip->sysfs_restart_lock); + INIT_DELAYED_WORK(&chip->update_jeita_setting, update_jeita_setting); + INIT_DELAYED_WORK(&chip->update_sram_data, update_sram_data_work); + INIT_DELAYED_WORK(&chip->update_temp_work, update_temp_data); + INIT_DELAYED_WORK(&chip->check_empty_work, check_empty_work); + INIT_DELAYED_WORK(&chip->batt_profile_init, batt_profile_init); + INIT_WORK(&chip->rslow_comp_work, rslow_comp_work); + INIT_WORK(&chip->fg_cap_learning_work, fg_cap_learning_work); + INIT_WORK(&chip->dump_sram, dump_sram); + INIT_WORK(&chip->status_change_work, status_change_work); + INIT_WORK(&chip->cycle_count_work, update_cycle_count); + INIT_WORK(&chip->battery_age_work, battery_age_work); + INIT_WORK(&chip->update_esr_work, update_esr_value); + INIT_WORK(&chip->set_resume_soc_work, set_resume_soc_work); + INIT_WORK(&chip->sysfs_restart_work, sysfs_restart_work); + INIT_WORK(&chip->init_work, delayed_init_work); + INIT_WORK(&chip->charge_full_work, charge_full_work); + INIT_WORK(&chip->gain_comp_work, iadc_gain_comp_work); + INIT_WORK(&chip->bcl_hi_power_work, bcl_hi_power_work); + INIT_WORK(&chip->esr_extract_config_work, esr_extract_config_work); + alarm_init(&chip->fg_cap_learning_alarm, ALARM_BOOTTIME, + fg_cap_learning_alarm_cb); + init_completion(&chip->sram_access_granted); + init_completion(&chip->sram_access_revoked); + complete_all(&chip->sram_access_revoked); + init_completion(&chip->batt_id_avail); + init_completion(&chip->first_soc_done); + dev_set_drvdata(&pdev->dev, chip); + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + rc = -ENXIO; + goto of_init_fail; + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + child->full_name, rc); + goto of_init_fail; + } + + if (strcmp("qcom,fg-adc-vbat", child->name) == 0) { + chip->vbat_adc_addr = base; + continue; + } else if (strcmp("qcom,fg-adc-ibat", child->name) == 0) { + chip->ibat_adc_addr = base; + continue; + } else if (strcmp("qcom,revid-tp-rev", child->name) == 0) { + chip->tp_rev_addr = base; + continue; + } + + rc = fg_read(chip, &subtype, + base + REG_OFFSET_PERP_SUBTYPE, 1); + if (rc) { + pr_err("Peripheral subtype read failed rc=%d\n", rc); + goto of_init_fail; + } + + switch (subtype) { + case FG_SOC: + chip->soc_base = base; + break; + case FG_MEMIF: + chip->mem_base = base; + break; + case FG_BATT: + chip->batt_base = base; + break; + default: + pr_err("Invalid peripheral subtype=0x%x\n", subtype); + rc = -EINVAL; + } + } + + rc = fg_detect_pmic_type(chip); + if (rc) { + pr_err("Unable to detect PMIC type rc=%d\n", rc); + return rc; + } + + rc = fg_setup_memif_offset(chip); + if (rc) { + pr_err("Unable to setup mem_if offsets rc=%d\n", rc); + goto of_init_fail; + } + + rc = fg_of_init(chip); + if (rc) { + pr_err("failed to parse devicetree rc%d\n", rc); + goto of_init_fail; + } + + if (chip->jeita_hysteresis_support) { + rc = fg_init_batt_temp_state(chip); + if (rc) { + pr_err("failed to get battery status rc%d\n", rc); + goto of_init_fail; + } + } + + /* check if the first estimate is already finished at this time */ + if (is_first_est_done(chip)) + complete_all(&chip->first_soc_done); + + reg = 0xFF; + rc = fg_write(chip, ®, INT_EN_CLR(chip->mem_base), 1); + if (rc) { + pr_err("failed to clear interrupts %d\n", rc); + goto of_init_fail; + } + + rc = fg_init_irqs(chip); + if (rc) { + pr_err("failed to request interrupts %d\n", rc); + goto cancel_work; + } + + chip->batt_type = default_batt_type; + + chip->bms_psy_d.name = "bms"; + chip->bms_psy_d.type = POWER_SUPPLY_TYPE_BMS; + chip->bms_psy_d.properties = fg_power_props; + chip->bms_psy_d.num_properties = ARRAY_SIZE(fg_power_props); + chip->bms_psy_d.get_property = fg_power_get_property; + chip->bms_psy_d.set_property = fg_power_set_property; + chip->bms_psy_d.external_power_changed = fg_external_power_changed; + chip->bms_psy_d.property_is_writeable = fg_property_is_writeable; + + bms_psy_cfg.drv_data = chip; + bms_psy_cfg.supplied_to = fg_supplicants; + bms_psy_cfg.num_supplicants = ARRAY_SIZE(fg_supplicants); + bms_psy_cfg.of_node = NULL; + chip->bms_psy = devm_power_supply_register(chip->dev, + &chip->bms_psy_d, + &bms_psy_cfg); + if (IS_ERR(chip->bms_psy)) { + pr_err("batt failed to register rc = %ld\n", + PTR_ERR(chip->bms_psy)); + goto of_init_fail; + } + chip->power_supply_registered = true; + /* + * Just initialize the batt_psy_name here. Power supply + * will be obtained later. + */ + chip->batt_psy_name = "battery"; + + if (chip->mem_base) { + rc = fg_dfs_create(chip); + if (rc < 0) { + pr_err("failed to create debugfs rc = %d\n", rc); + goto cancel_work; + } + } + + schedule_work(&chip->init_work); + + pr_info("FG Probe success - FG Revision DIG:%d.%d ANA:%d.%d PMIC subtype=%d\n", + chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR], + chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR], + chip->pmic_subtype); + + return rc; + +cancel_work: + cancel_delayed_work_sync(&chip->update_jeita_setting); + cancel_delayed_work_sync(&chip->update_sram_data); + cancel_delayed_work_sync(&chip->update_temp_work); + cancel_delayed_work_sync(&chip->check_empty_work); + cancel_delayed_work_sync(&chip->batt_profile_init); + alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + cancel_work_sync(&chip->set_resume_soc_work); + cancel_work_sync(&chip->fg_cap_learning_work); + cancel_work_sync(&chip->dump_sram); + cancel_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->cycle_count_work); + cancel_work_sync(&chip->update_esr_work); + cancel_work_sync(&chip->rslow_comp_work); + cancel_work_sync(&chip->sysfs_restart_work); + cancel_work_sync(&chip->gain_comp_work); + cancel_work_sync(&chip->init_work); + cancel_work_sync(&chip->charge_full_work); + cancel_work_sync(&chip->bcl_hi_power_work); + cancel_work_sync(&chip->esr_extract_config_work); +of_init_fail: + mutex_destroy(&chip->rslow_comp.lock); + mutex_destroy(&chip->rw_lock); + mutex_destroy(&chip->cyc_ctr.lock); + mutex_destroy(&chip->learning_data.learning_lock); + mutex_destroy(&chip->sysfs_restart_lock); + wakeup_source_trash(&chip->resume_soc_wakeup_source.source); + wakeup_source_trash(&chip->empty_check_wakeup_source.source); + wakeup_source_trash(&chip->memif_wakeup_source.source); + wakeup_source_trash(&chip->profile_wakeup_source.source); + wakeup_source_trash(&chip->update_temp_wakeup_source.source); + wakeup_source_trash(&chip->update_sram_wakeup_source.source); + wakeup_source_trash(&chip->gain_comp_wakeup_source.source); + wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); + wakeup_source_trash(&chip->esr_extract_wakeup_source.source); + return rc; +} + +static void check_and_update_sram_data(struct fg_chip *chip) +{ + unsigned long current_time = 0, next_update_time, time_left; + + get_current_time(¤t_time); + + next_update_time = chip->last_temp_update_time + + (TEMP_PERIOD_UPDATE_MS / 1000); + + if (next_update_time > current_time) + time_left = next_update_time - current_time; + else + time_left = 0; + + schedule_delayed_work( + &chip->update_temp_work, msecs_to_jiffies(time_left * 1000)); + + next_update_time = chip->last_sram_update_time + + (fg_sram_update_period_ms / 1000); + + if (next_update_time > current_time) + time_left = next_update_time - current_time; + else + time_left = 0; + + schedule_delayed_work( + &chip->update_sram_data, msecs_to_jiffies(time_left * 1000)); +} + +static int fg_suspend(struct device *dev) +{ + struct fg_chip *chip = dev_get_drvdata(dev); + + if (!chip->sw_rbias_ctrl) + return 0; + + cancel_delayed_work(&chip->update_temp_work); + cancel_delayed_work(&chip->update_sram_data); + + return 0; +} + +static int fg_resume(struct device *dev) +{ + struct fg_chip *chip = dev_get_drvdata(dev); + + if (!chip->sw_rbias_ctrl) + return 0; + + check_and_update_sram_data(chip); + return 0; +} + +static const struct dev_pm_ops qpnp_fg_pm_ops = { + .suspend = fg_suspend, + .resume = fg_resume, +}; + +static int fg_sense_type_set(const char *val, const struct kernel_param *kp) +{ + int rc; + struct power_supply *bms_psy; + struct fg_chip *chip; + int old_fg_sense_type = fg_sense_type; + + rc = param_set_int(val, kp); + if (rc) { + pr_err("Unable to set fg_sense_type: %d\n", rc); + return rc; + } + + if (fg_sense_type != 0 && fg_sense_type != 1) { + pr_err("Bad value %d\n", fg_sense_type); + fg_sense_type = old_fg_sense_type; + return -EINVAL; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("fg_sense_type set to %d\n", fg_sense_type); + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return 0; + } + + chip = power_supply_get_drvdata(bms_psy); + rc = set_prop_sense_type(chip, fg_sense_type); + return rc; +} + +static struct kernel_param_ops fg_sense_type_ops = { + .set = fg_sense_type_set, + .get = param_get_int, +}; + +module_param_cb(sense_type, &fg_sense_type_ops, &fg_sense_type, 0644); + +static int fg_restart_set(const char *val, const struct kernel_param *kp) +{ + struct power_supply *bms_psy; + struct fg_chip *chip; + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return 0; + } + chip = power_supply_get_drvdata(bms_psy); + + mutex_lock(&chip->sysfs_restart_lock); + if (fg_restart != 0) { + mutex_unlock(&chip->sysfs_restart_lock); + return 0; + } + fg_restart = 1; + mutex_unlock(&chip->sysfs_restart_lock); + + if (fg_debug_mask & FG_STATUS) + pr_info("fuel gauge restart initiated from sysfs...\n"); + + schedule_work(&chip->sysfs_restart_work); + return 0; +} + +static struct kernel_param_ops fg_restart_ops = { + .set = fg_restart_set, + .get = param_get_int, +}; + +module_param_cb(restart, &fg_restart_ops, &fg_restart, 0644); + +static struct platform_driver fg_driver = { + .driver = { + .name = QPNP_FG_DEV_NAME, + .of_match_table = fg_match_table, + .pm = &qpnp_fg_pm_ops, + }, + .probe = fg_probe, + .remove = fg_remove, +}; + +static int __init fg_init(void) +{ + return platform_driver_register(&fg_driver); +} + +static void __exit fg_exit(void) +{ + return platform_driver_unregister(&fg_driver); +} + +module_init(fg_init); +module_exit(fg_exit); + +MODULE_DESCRIPTION("QPNP Fuel Gauge Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" QPNP_FG_DEV_NAME); diff --git a/drivers/power/qcom-charger/qpnp-smbcharger.c b/drivers/power/qcom-charger/qpnp-smbcharger.c new file mode 100644 index 000000000000..fbb163908c03 --- /dev/null +++ b/drivers/power/qcom-charger/qpnp-smbcharger.c @@ -0,0 +1,8352 @@ +/* Copyright (c) 2014-2016 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) "SMBCHG: %s: " fmt, __func__ + +#include <linux/regmap.h> +#include <linux/spinlock.h> +#include <linux/gpio.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/sched.h> +#include <linux/power_supply.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/of_irq.h> +#include <linux/bitops.h> +#include <linux/regulator/consumer.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/machine.h> +#include <linux/spmi.h> +#include <linux/platform_device.h> +#include <linux/printk.h> +#include <linux/ratelimit.h> +#include <linux/debugfs.h> +#include <linux/leds.h> +#include <linux/rtc.h> +#include <linux/qpnp/qpnp-adc.h> +#include <linux/batterydata-lib.h> +#include <linux/of_batterydata.h> +#include <linux/msm_bcl.h> +#include <linux/ktime.h> +#include <linux/extcon.h> +#include "pmic-voter.h" + +/* Mask/Bit helpers */ +#define _SMB_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) +/* Config registers */ +struct smbchg_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +struct parallel_usb_cfg { + struct power_supply *psy; + int min_current_thr_ma; + int min_9v_current_thr_ma; + int allowed_lowering_ma; + int current_max_ma; + bool avail; + struct mutex lock; + int initial_aicl_ma; + ktime_t last_disabled; + bool enabled_once; +}; + +struct ilim_entry { + int vmin_uv; + int vmax_uv; + int icl_pt_ma; + int icl_lv_ma; + int icl_hv_ma; +}; + +struct ilim_map { + int num; + struct ilim_entry *entries; +}; + +struct smbchg_version_tables { + const int *dc_ilim_ma_table; + int dc_ilim_ma_len; + const int *usb_ilim_ma_table; + int usb_ilim_ma_len; + const int *iterm_ma_table; + int iterm_ma_len; + const int *fcc_comp_table; + int fcc_comp_len; + const int *aicl_rerun_period_table; + int aicl_rerun_period_len; + int rchg_thr_mv; +}; + +struct smbchg_chip { + struct device *dev; + struct platform_device *pdev; + struct regmap *regmap; + int schg_version; + + /* peripheral register address bases */ + u16 chgr_base; + u16 bat_if_base; + u16 usb_chgpth_base; + u16 dc_chgpth_base; + u16 otg_base; + u16 misc_base; + + int fake_battery_soc; + u8 revision[4]; + + /* configuration parameters */ + int iterm_ma; + int usb_max_current_ma; + int typec_current_ma; + int dc_max_current_ma; + int dc_target_current_ma; + int cfg_fastchg_current_ma; + int fastchg_current_ma; + int vfloat_mv; + int fastchg_current_comp; + int float_voltage_comp; + int resume_delta_mv; + int safety_time; + int prechg_safety_time; + int bmd_pin_src; + int jeita_temp_hard_limit; + int aicl_rerun_period_s; + bool use_vfloat_adjustments; + bool iterm_disabled; + bool bmd_algo_disabled; + bool soft_vfloat_comp_disabled; + bool chg_enabled; + bool charge_unknown_battery; + bool chg_inhibit_en; + bool chg_inhibit_source_fg; + bool low_volt_dcin; + bool cfg_chg_led_support; + bool cfg_chg_led_sw_ctrl; + bool vbat_above_headroom; + bool force_aicl_rerun; + bool hvdcp3_supported; + bool restricted_charging; + bool skip_usb_suspend_for_fake_battery; + bool hvdcp_not_supported; + bool otg_pinctrl; + u8 original_usbin_allowance; + struct parallel_usb_cfg parallel; + struct delayed_work parallel_en_work; + struct dentry *debug_root; + struct smbchg_version_tables tables; + + /* wipower params */ + struct ilim_map wipower_default; + struct ilim_map wipower_pt; + struct ilim_map wipower_div2; + struct qpnp_vadc_chip *vadc_dev; + bool wipower_dyn_icl_avail; + struct ilim_entry current_ilim; + struct mutex wipower_config; + bool wipower_configured; + struct qpnp_adc_tm_btm_param param; + + /* flash current prediction */ + int rpara_uohm; + int rslow_uohm; + int vled_max_uv; + + /* vfloat adjustment */ + int max_vbat_sample; + int n_vbat_samples; + + /* status variables */ + int wake_reasons; + int previous_soc; + int usb_online; + bool dc_present; + bool usb_present; + bool batt_present; + int otg_retries; + ktime_t otg_enable_time; + bool aicl_deglitch_short; + bool safety_timer_en; + bool aicl_complete; + bool usb_ov_det; + bool otg_pulse_skip_dis; + const char *battery_type; + enum power_supply_type usb_supply_type; + bool very_weak_charger; + bool parallel_charger_detected; + bool chg_otg_enabled; + bool flash_triggered; + bool flash_active; + bool icl_disabled; + u32 wa_flags; + int usb_icl_delta; + bool typec_dfp; + unsigned int usb_current_max; + unsigned int usb_health; + + /* jeita and temperature */ + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + unsigned int thermal_levels; + unsigned int therm_lvl_sel; + unsigned int *thermal_mitigation; + + /* irqs */ + int batt_hot_irq; + int batt_warm_irq; + int batt_cool_irq; + int batt_cold_irq; + int batt_missing_irq; + int vbat_low_irq; + int chg_hot_irq; + int chg_term_irq; + int taper_irq; + bool taper_irq_enabled; + struct mutex taper_irq_lock; + int recharge_irq; + int fastchg_irq; + int wdog_timeout_irq; + int power_ok_irq; + int dcin_uv_irq; + int usbin_uv_irq; + int usbin_ov_irq; + int src_detect_irq; + int otg_fail_irq; + int otg_oc_irq; + int aicl_done_irq; + int usbid_change_irq; + int chg_error_irq; + bool enable_aicl_wake; + + /* psy */ + struct power_supply_desc usb_psy_d; + struct power_supply *usb_psy; + struct power_supply_desc batt_psy_d; + struct power_supply *batt_psy; + struct power_supply_desc dc_psy_d; + struct power_supply *dc_psy; + struct power_supply *bms_psy; + struct power_supply *typec_psy; + int dc_psy_type; + const char *bms_psy_name; + const char *battery_psy_name; + + struct regulator *dpdm_reg; + struct smbchg_regulator otg_vreg; + struct smbchg_regulator ext_otg_vreg; + struct work_struct usb_set_online_work; + struct delayed_work vfloat_adjust_work; + struct delayed_work hvdcp_det_work; + spinlock_t sec_access_lock; + struct mutex therm_lvl_lock; + struct mutex usb_set_online_lock; + struct mutex pm_lock; + /* aicl deglitch workaround */ + unsigned long first_aicl_seconds; + int aicl_irq_count; + struct mutex usb_status_lock; + bool hvdcp_3_det_ignore_uv; + struct completion src_det_lowered; + struct completion src_det_raised; + struct completion usbin_uv_lowered; + struct completion usbin_uv_raised; + int pulse_cnt; + struct led_classdev led_cdev; + bool skip_usb_notification; + u32 vchg_adc_channel; + struct qpnp_vadc_chip *vchg_vadc_dev; + + /* voters */ + struct votable *fcc_votable; + struct votable *usb_icl_votable; + struct votable *dc_icl_votable; + struct votable *usb_suspend_votable; + struct votable *dc_suspend_votable; + struct votable *battchg_suspend_votable; + struct votable *hw_aicl_rerun_disable_votable; + struct votable *hw_aicl_rerun_enable_indirect_votable; + struct votable *aicl_deglitch_short_votable; + + /* extcon for VBUS / ID notification to USB */ + struct extcon_dev *extcon; +}; + +enum qpnp_schg { + QPNP_SCHG, + QPNP_SCHG_LITE, +}; + +static char *version_str[] = { + [QPNP_SCHG] = "SCHG", + [QPNP_SCHG_LITE] = "SCHG_LITE", +}; + +enum pmic_subtype { + PMI8994 = 10, + PMI8950 = 17, + PMI8996 = 19, + PMI8937 = 55, +}; + +enum smbchg_wa { + SMBCHG_AICL_DEGLITCH_WA = BIT(0), + SMBCHG_HVDCP_9V_EN_WA = BIT(1), + SMBCHG_USB100_WA = BIT(2), + SMBCHG_BATT_OV_WA = BIT(3), + SMBCHG_CC_ESR_WA = BIT(4), + SMBCHG_FLASH_ICL_DISABLE_WA = BIT(5), + SMBCHG_RESTART_WA = BIT(6), + SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA = BIT(7), +}; + +enum print_reason { + PR_REGISTER = BIT(0), + PR_INTERRUPT = BIT(1), + PR_STATUS = BIT(2), + PR_DUMP = BIT(3), + PR_PM = BIT(4), + PR_MISC = BIT(5), + PR_WIPOWER = BIT(6), + PR_TYPEC = BIT(7), +}; + +enum wake_reason { + PM_PARALLEL_CHECK = BIT(0), + PM_REASON_VFLOAT_ADJUST = BIT(1), + PM_ESR_PULSE = BIT(2), + PM_PARALLEL_TAPER = BIT(3), + PM_DETECT_HVDCP = BIT(4), +}; + +enum fcc_voters { + ESR_PULSE_FCC_VOTER, + BATT_TYPE_FCC_VOTER, + RESTRICTED_CHG_FCC_VOTER, + NUM_FCC_VOTER, +}; + +enum icl_voters { + PSY_ICL_VOTER, + THERMAL_ICL_VOTER, + HVDCP_ICL_VOTER, + USER_ICL_VOTER, + WEAK_CHARGER_ICL_VOTER, + SW_AICL_ICL_VOTER, + CHG_SUSPEND_WORKAROUND_ICL_VOTER, + NUM_ICL_VOTER, +}; + +enum enable_voters { + /* userspace has suspended charging altogether */ + USER_EN_VOTER, + /* + * this specific path has been suspended through the power supply + * framework + */ + POWER_SUPPLY_EN_VOTER, + /* + * the usb driver has suspended this path by setting a current limit + * of < 2MA + */ + USB_EN_VOTER, + /* + * when a wireless charger comes online, + * the dc path is suspended for a second + */ + WIRELESS_EN_VOTER, + /* + * the thermal daemon can suspend a charge path when the system + * temperature levels rise + */ + THERMAL_EN_VOTER, + /* + * an external OTG supply is being used, suspend charge path so the + * charger does not accidentally try to charge from the external supply. + */ + OTG_EN_VOTER, + /* + * the charger is very weak, do not draw any current from it + */ + WEAK_CHARGER_EN_VOTER, + /* + * fake battery voter, if battery id-resistance around 7.5 Kohm + */ + FAKE_BATTERY_EN_VOTER, + NUM_EN_VOTERS, +}; + +enum battchg_enable_voters { + /* userspace has disabled battery charging */ + BATTCHG_USER_EN_VOTER, + /* battery charging disabled while loading battery profiles */ + BATTCHG_UNKNOWN_BATTERY_EN_VOTER, + NUM_BATTCHG_EN_VOTERS, +}; + +enum hw_aicl_rerun_enable_indirect_voters { + /* enabled via device tree */ + DEFAULT_CONFIG_HW_AICL_VOTER, + /* Varb workaround voter */ + VARB_WORKAROUND_VOTER, + /* SHUTDOWN workaround voter */ + SHUTDOWN_WORKAROUND_VOTER, + NUM_HW_AICL_RERUN_ENABLE_INDIRECT_VOTERS, +}; + +enum hw_aicl_rerun_disable_voters { + /* the results from enabling clients */ + HW_AICL_RERUN_ENABLE_INDIRECT_VOTER, + /* Weak charger voter */ + WEAK_CHARGER_HW_AICL_VOTER, + NUM_HW_AICL_DISABLE_VOTERS, +}; + +enum aicl_short_deglitch_voters { + /* Varb workaround voter */ + VARB_WORKAROUND_SHORT_DEGLITCH_VOTER, + /* QC 2.0 */ + HVDCP_SHORT_DEGLITCH_VOTER, + NUM_HW_SHORT_DEGLITCH_VOTERS, +}; + +static const unsigned int smbchg_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +static int smbchg_debug_mask; +module_param_named( + debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR +); + +static int smbchg_parallel_en = 1; +module_param_named( + parallel_en, smbchg_parallel_en, int, S_IRUSR | S_IWUSR +); + +static int smbchg_main_chg_fcc_percent = 50; +module_param_named( + main_chg_fcc_percent, smbchg_main_chg_fcc_percent, + int, S_IRUSR | S_IWUSR +); + +static int smbchg_main_chg_icl_percent = 60; +module_param_named( + main_chg_icl_percent, smbchg_main_chg_icl_percent, + int, S_IRUSR | S_IWUSR +); + +static int smbchg_default_hvdcp_icl_ma = 1800; +module_param_named( + default_hvdcp_icl_ma, smbchg_default_hvdcp_icl_ma, + int, S_IRUSR | S_IWUSR +); + +static int smbchg_default_hvdcp3_icl_ma = 3000; +module_param_named( + default_hvdcp3_icl_ma, smbchg_default_hvdcp3_icl_ma, + int, S_IRUSR | S_IWUSR +); + +static int smbchg_default_dcp_icl_ma = 1800; +module_param_named( + default_dcp_icl_ma, smbchg_default_dcp_icl_ma, + int, S_IRUSR | S_IWUSR +); + +static int wipower_dyn_icl_en; +module_param_named( + dynamic_icl_wipower_en, wipower_dyn_icl_en, + int, S_IRUSR | S_IWUSR +); + +static int wipower_dcin_interval = ADC_MEAS1_INTERVAL_2P0MS; +module_param_named( + wipower_dcin_interval, wipower_dcin_interval, + int, S_IRUSR | S_IWUSR +); + +#define WIPOWER_DEFAULT_HYSTERISIS_UV 250000 +static int wipower_dcin_hyst_uv = WIPOWER_DEFAULT_HYSTERISIS_UV; +module_param_named( + wipower_dcin_hyst_uv, wipower_dcin_hyst_uv, + int, S_IRUSR | S_IWUSR +); + +#define pr_smb(reason, fmt, ...) \ + do { \ + if (smbchg_debug_mask & (reason)) \ + pr_info(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +#define pr_smb_rt(reason, fmt, ...) \ + do { \ + if (smbchg_debug_mask & (reason)) \ + pr_info_ratelimited(fmt, ##__VA_ARGS__); \ + else \ + pr_debug(fmt, ##__VA_ARGS__); \ + } while (0) + +static int smbchg_read(struct smbchg_chip *chip, u8 *val, + u16 addr, int count) +{ + int rc = 0; + struct platform_device *pdev = chip->pdev; + + if (addr == 0) { + dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n", + addr, to_spmi_device(pdev->dev.parent)->usid, rc); + return -EINVAL; + } + + rc = regmap_bulk_read(chip->regmap, addr, val, count); + if (rc) { + dev_err(chip->dev, "spmi read failed addr=0x%02x sid=0x%02x rc=%d\n", + addr, to_spmi_device(pdev->dev.parent)->usid, + rc); + return rc; + } + return 0; +} + +/* + * Writes a register to the specified by the base and limited by the bit mask + * + * Do not use this function for register writes if possible. Instead use the + * smbchg_masked_write function. + * + * The sec_access_lock must be held for all register writes and this function + * does not do that. If this function is used, please hold the spinlock or + * random secure access writes may fail. + */ +static int smbchg_masked_write_raw(struct smbchg_chip *chip, u16 base, u8 mask, + u8 val) +{ + int rc; + + rc = regmap_update_bits(chip->regmap, base, mask, val); + if (rc) { + dev_err(chip->dev, "spmi write failed: addr=%03X, rc=%d\n", + base, rc); + return rc; + } + + return 0; +} + +/* + * Writes a register to the specified by the base and limited by the bit mask + * + * This function holds a spin lock to ensure secure access register writes goes + * through. If the secure access unlock register is armed, any old register + * write can unarm the secure access unlock, causing the next write to fail. + * + * Note: do not use this for sec_access registers. Instead use the function + * below: smbchg_sec_masked_write + */ +static int smbchg_masked_write(struct smbchg_chip *chip, u16 base, u8 mask, + u8 val) +{ + unsigned long flags; + int rc; + + spin_lock_irqsave(&chip->sec_access_lock, flags); + rc = smbchg_masked_write_raw(chip, base, mask, val); + spin_unlock_irqrestore(&chip->sec_access_lock, flags); + + return rc; +} + +/* + * Unlocks sec access and writes to the register specified. + * + * This function holds a spin lock to exclude other register writes while + * the two writes are taking place. + */ +#define SEC_ACCESS_OFFSET 0xD0 +#define SEC_ACCESS_VALUE 0xA5 +#define PERIPHERAL_MASK 0xFF +static int smbchg_sec_masked_write(struct smbchg_chip *chip, u16 base, u8 mask, + u8 val) +{ + unsigned long flags; + int rc; + u16 peripheral_base = base & (~PERIPHERAL_MASK); + + spin_lock_irqsave(&chip->sec_access_lock, flags); + + rc = smbchg_masked_write_raw(chip, peripheral_base + SEC_ACCESS_OFFSET, + SEC_ACCESS_VALUE, SEC_ACCESS_VALUE); + if (rc) { + dev_err(chip->dev, "Unable to unlock sec_access: %d", rc); + goto out; + } + + rc = smbchg_masked_write_raw(chip, base, mask, val); + +out: + spin_unlock_irqrestore(&chip->sec_access_lock, flags); + return rc; +} + +static void smbchg_stay_awake(struct smbchg_chip *chip, int reason) +{ + int reasons; + + mutex_lock(&chip->pm_lock); + reasons = chip->wake_reasons | reason; + if (reasons != 0 && chip->wake_reasons == 0) { + pr_smb(PR_PM, "staying awake: 0x%02x (bit %d)\n", + reasons, reason); + pm_stay_awake(chip->dev); + } + chip->wake_reasons = reasons; + mutex_unlock(&chip->pm_lock); +} + +static void smbchg_relax(struct smbchg_chip *chip, int reason) +{ + int reasons; + + mutex_lock(&chip->pm_lock); + reasons = chip->wake_reasons & (~reason); + if (reasons == 0 && chip->wake_reasons != 0) { + pr_smb(PR_PM, "relaxing: 0x%02x (bit %d)\n", + reasons, reason); + pm_relax(chip->dev); + } + chip->wake_reasons = reasons; + mutex_unlock(&chip->pm_lock); +}; + +enum pwr_path_type { + UNKNOWN = 0, + PWR_PATH_BATTERY = 1, + PWR_PATH_USB = 2, + PWR_PATH_DC = 3, +}; + +#define PWR_PATH 0x08 +#define PWR_PATH_MASK 0x03 +static enum pwr_path_type smbchg_get_pwr_path(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + PWR_PATH, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read PWR_PATH rc = %d\n", rc); + return PWR_PATH_BATTERY; + } + + return reg & PWR_PATH_MASK; +} + +#define RID_STS 0xB +#define RID_MASK 0xF +#define IDEV_STS 0x8 +#define RT_STS 0x10 +#define USBID_MSB 0xE +#define USBIN_UV_BIT BIT(0) +#define USBIN_OV_BIT BIT(1) +#define USBIN_SRC_DET_BIT BIT(2) +#define FMB_STS_MASK SMB_MASK(3, 0) +#define USBID_GND_THRESHOLD 0x495 +static bool is_otg_present_schg(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + u8 usbid_reg[2]; + u16 usbid_val; + /* + * After the falling edge of the usbid change interrupt occurs, + * there may still be some time before the ADC conversion for USB RID + * finishes in the fuel gauge. In the worst case, this could be up to + * 15 ms. + * + * Sleep for 20 ms (minimum msleep time) to wait for the conversion to + * finish and the USB RID status register to be updated before trying + * to detect OTG insertions. + */ + + msleep(20); + + /* + * There is a problem with USBID conversions on PMI8994 revisions + * 2.0.0. As a workaround, check that the cable is not + * detected as factory test before enabling OTG. + */ + rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read IDEV_STS rc = %d\n", rc); + return false; + } + + if ((reg & FMB_STS_MASK) != 0) { + pr_smb(PR_STATUS, "IDEV_STS = %02x, not ground\n", reg); + return false; + } + + rc = smbchg_read(chip, usbid_reg, chip->usb_chgpth_base + USBID_MSB, 2); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read USBID rc = %d\n", rc); + return false; + } + usbid_val = (usbid_reg[0] << 8) | usbid_reg[1]; + + if (usbid_val > USBID_GND_THRESHOLD) { + pr_smb(PR_STATUS, "USBID = 0x%04x, too high to be ground\n", + usbid_val); + return false; + } + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RID_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read usb rid status rc = %d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "RID_STS = %02x\n", reg); + + return (reg & RID_MASK) == 0; +} + +#define RID_GND_DET_STS BIT(2) +static bool is_otg_present_schg_lite(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->otg_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read otg RT status rc = %d\n", rc); + return false; + } + + return !!(reg & RID_GND_DET_STS); +} + +static bool is_otg_present(struct smbchg_chip *chip) +{ + if (chip->schg_version == QPNP_SCHG_LITE) + return is_otg_present_schg_lite(chip); + + return is_otg_present_schg(chip); +} + +#define USBIN_9V BIT(5) +#define USBIN_UNREG BIT(4) +#define USBIN_LV BIT(3) +#define DCIN_9V BIT(2) +#define DCIN_UNREG BIT(1) +#define DCIN_LV BIT(0) +#define INPUT_STS 0x0D +#define DCIN_UV_BIT BIT(0) +#define DCIN_OV_BIT BIT(1) +static bool is_dc_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->dc_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read dc status rc = %d\n", rc); + return false; + } + + if ((reg & DCIN_UV_BIT) || (reg & DCIN_OV_BIT)) + return false; + + return true; +} + +static bool is_usb_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + if (!(reg & USBIN_SRC_DET_BIT) || (reg & USBIN_OV_BIT)) + return false; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + INPUT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); + return false; + } + + return !!(reg & (USBIN_9V | USBIN_UNREG | USBIN_LV)); +} + +static char *usb_type_str[] = { + "SDP", /* bit 0 */ + "OTHER", /* bit 1 */ + "DCP", /* bit 2 */ + "CDP", /* bit 3 */ + "NONE", /* bit 4 error case */ +}; + +#define N_TYPE_BITS 4 +#define TYPE_BITS_OFFSET 4 + +static int get_type(u8 type_reg) +{ + unsigned long type = type_reg; + type >>= TYPE_BITS_OFFSET; + return find_first_bit(&type, N_TYPE_BITS); +} + +/* helper to return the string of USB type */ +static inline char *get_usb_type_name(int type) +{ + return usb_type_str[type]; +} + +static enum power_supply_type usb_type_enum[] = { + POWER_SUPPLY_TYPE_USB, /* bit 0 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 1 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 2 */ + POWER_SUPPLY_TYPE_USB_CDP, /* bit 3 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 4 error case, report DCP */ +}; + +/* helper to return enum power_supply_type of USB type */ +static inline enum power_supply_type get_usb_supply_type(int type) +{ + return usb_type_enum[type]; +} + +static bool is_src_detect_high(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + return reg &= USBIN_SRC_DET_BIT; +} + +static void read_usb_type(struct smbchg_chip *chip, char **usb_type_name, + enum power_supply_type *usb_supply_type) +{ + int rc, type; + u8 reg; + + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low\n"); + *usb_type_name = "Absent"; + *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + return; + } + + rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); + *usb_type_name = "Other"; + *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + return; + } + type = get_type(reg); + *usb_type_name = get_usb_type_name(type); + *usb_supply_type = get_usb_supply_type(type); +} + +#define CHGR_STS 0x0E +#define BATT_LESS_THAN_2V BIT(4) +#define CHG_HOLD_OFF_BIT BIT(3) +#define CHG_TYPE_MASK SMB_MASK(2, 1) +#define CHG_TYPE_SHIFT 1 +#define BATT_NOT_CHG_VAL 0x0 +#define BATT_PRE_CHG_VAL 0x1 +#define BATT_FAST_CHG_VAL 0x2 +#define BATT_TAPER_CHG_VAL 0x3 +#define CHG_INHIBIT_BIT BIT(1) +#define BAT_TCC_REACHED_BIT BIT(7) +static int get_prop_batt_status(struct smbchg_chip *chip) +{ + int rc, status = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg = 0, chg_type; + bool charger_present, chg_inhibit; + + charger_present = is_usb_present(chip) | is_dc_present(chip) | + chip->hvdcp_3_det_ignore_uv; + if (!charger_present) + return POWER_SUPPLY_STATUS_DISCHARGING; + + rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + if (reg & BAT_TCC_REACHED_BIT) + return POWER_SUPPLY_STATUS_FULL; + + chg_inhibit = reg & CHG_INHIBIT_BIT; + if (chg_inhibit) + return POWER_SUPPLY_STATUS_FULL; + + rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + if (reg & CHG_HOLD_OFF_BIT) { + /* + * when chg hold off happens the battery is + * not charging + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto out; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + + if (chg_type == BATT_NOT_CHG_VAL && !chip->hvdcp_3_det_ignore_uv) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; +out: + pr_smb_rt(PR_MISC, "CHGR_STS = 0x%02x\n", reg); + return status; +} + +#define BAT_PRES_STATUS 0x08 +#define BAT_PRES_BIT BIT(7) +static int get_prop_batt_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->bat_if_base + BAT_PRES_STATUS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return 0; + } + + return !!(reg & BAT_PRES_BIT); +} + +static int get_prop_charge_type(struct smbchg_chip *chip) +{ + int rc; + u8 reg, chg_type; + + rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return 0; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + if (chg_type == BATT_NOT_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + else if (chg_type == BATT_TAPER_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TAPER; + else if (chg_type == BATT_FAST_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (chg_type == BATT_PRE_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int set_property_on_fg(struct smbchg_chip *chip, + enum power_supply_property prop, int val) +{ + int rc; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + if (!chip->bms_psy) { + pr_smb(PR_STATUS, "no bms psy found\n"); + return -EINVAL; + } + + ret.intval = val; + rc = power_supply_set_property(chip->bms_psy, prop, &ret); + if (rc) + pr_smb(PR_STATUS, + "bms psy does not allow updating prop %d rc = %d\n", + prop, rc); + + return rc; +} + +static int get_property_from_fg(struct smbchg_chip *chip, + enum power_supply_property prop, int *val) +{ + int rc; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + if (!chip->bms_psy) { + pr_smb(PR_STATUS, "no bms psy found\n"); + return -EINVAL; + } + + rc = power_supply_get_property(chip->bms_psy, prop, &ret); + if (rc) { + pr_smb(PR_STATUS, + "bms psy doesn't support reading prop %d rc = %d\n", + prop, rc); + return rc; + } + + *val = ret.intval; + return rc; +} + +#define DEFAULT_BATT_CAPACITY 50 +static int get_prop_batt_capacity(struct smbchg_chip *chip) +{ + int capacity, rc; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CAPACITY, &capacity); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get capacity rc = %d\n", rc); + capacity = DEFAULT_BATT_CAPACITY; + } + return capacity; +} + +#define DEFAULT_BATT_TEMP 200 +static int get_prop_batt_temp(struct smbchg_chip *chip) +{ + int temp, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_TEMP, &temp); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get temperature rc = %d\n", rc); + temp = DEFAULT_BATT_TEMP; + } + return temp; +} + +#define DEFAULT_BATT_CURRENT_NOW 0 +static int get_prop_batt_current_now(struct smbchg_chip *chip) +{ + int ua, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, &ua); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get current rc = %d\n", rc); + ua = DEFAULT_BATT_CURRENT_NOW; + } + return ua; +} + +#define DEFAULT_BATT_VOLTAGE_NOW 0 +static int get_prop_batt_voltage_now(struct smbchg_chip *chip) +{ + int uv, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_NOW, &uv); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); + uv = DEFAULT_BATT_VOLTAGE_NOW; + } + return uv; +} + +#define DEFAULT_BATT_VOLTAGE_MAX_DESIGN 4200000 +static int get_prop_batt_voltage_max_design(struct smbchg_chip *chip) +{ + int uv, rc; + + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &uv); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); + uv = DEFAULT_BATT_VOLTAGE_MAX_DESIGN; + } + return uv; +} + +static int get_prop_batt_health(struct smbchg_chip *chip) +{ + if (chip->batt_hot) + return POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + return POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + return POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + return POWER_SUPPLY_HEALTH_COOL; + else + return POWER_SUPPLY_HEALTH_GOOD; +} + +static void get_property_from_typec(struct smbchg_chip *chip, + enum power_supply_property property, + union power_supply_propval *prop) +{ + int rc; + + rc = power_supply_get_property(chip->typec_psy, + property, prop); + if (rc) + pr_smb(PR_TYPEC, + "typec psy doesn't support reading prop %d rc = %d\n", + property, rc); +} + +static void update_typec_status(struct smbchg_chip *chip) +{ + union power_supply_propval type = {0, }; + union power_supply_propval capability = {0, }; + + get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type); + if (type.intval != POWER_SUPPLY_TYPE_UNKNOWN) { + get_property_from_typec(chip, + POWER_SUPPLY_PROP_CURRENT_CAPABILITY, + &capability); + chip->typec_current_ma = capability.intval; + pr_smb(PR_TYPEC, "SMB Type-C mode = %d, current=%d\n", + type.intval, capability.intval); + } else { + pr_smb(PR_TYPEC, + "typec detection not completed continuing with USB update\n"); + } +} + +/* + * finds the index of the closest value in the array. If there are two that + * are equally close, the lower index will be returned + */ +static int find_closest_in_array(const int *arr, int len, int val) +{ + int i, closest = 0; + + if (len == 0) + return closest; + for (i = 0; i < len; i++) + if (abs(val - arr[i]) < abs(val - arr[closest])) + closest = i; + + return closest; +} + +/* finds the index of the closest smaller value in the array. */ +static int find_smaller_in_array(const int *table, int val, int len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (val >= table[i]) + break; + } + + return i; +} + +static const int iterm_ma_table_8994[] = { + 300, + 50, + 100, + 150, + 200, + 250, + 500, + 600 +}; + +static const int iterm_ma_table_8996[] = { + 300, + 50, + 100, + 150, + 200, + 250, + 400, + 500 +}; + +static const int usb_ilim_ma_table_8994[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500, + 3000 +}; + +static const int usb_ilim_ma_table_8996[] = { + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1100, + 1200, + 1300, + 1400, + 1450, + 1500, + 1550, + 1600, + 1700, + 1800, + 1900, + 1950, + 2000, + 2050, + 2100, + 2200, + 2300, + 2400, + 2500, + 2600, + 2700, + 2800, + 2900, + 3000 +}; + +static int dc_ilim_ma_table_8994[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, +}; + +static int dc_ilim_ma_table_8996[] = { + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1100, + 1200, + 1300, + 1400, + 1450, + 1500, + 1550, + 1600, + 1700, + 1800, + 1900, + 1950, + 2000, + 2050, + 2100, + 2200, + 2300, + 2400, +}; + +static const int fcc_comp_table_8994[] = { + 250, + 700, + 900, + 1200, +}; + +static const int fcc_comp_table_8996[] = { + 250, + 1100, + 1200, + 1500, +}; + +static const int aicl_rerun_period[] = { + 45, + 90, + 180, + 360, +}; + +static const int aicl_rerun_period_schg_lite[] = { + 3, /* 2.8s */ + 6, /* 5.6s */ + 11, /* 11.3s */ + 23, /* 22.5s */ + 45, + 90, + 180, + 360, +}; + +static void use_pmi8994_tables(struct smbchg_chip *chip) +{ + chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8994; + chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8994); + chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8994; + chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8994); + chip->tables.iterm_ma_table = iterm_ma_table_8994; + chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8994); + chip->tables.fcc_comp_table = fcc_comp_table_8994; + chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8994); + chip->tables.rchg_thr_mv = 200; + chip->tables.aicl_rerun_period_table = aicl_rerun_period; + chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period); +} + +static void use_pmi8996_tables(struct smbchg_chip *chip) +{ + chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8996; + chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8996); + chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8996; + chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8996); + chip->tables.iterm_ma_table = iterm_ma_table_8996; + chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8996); + chip->tables.fcc_comp_table = fcc_comp_table_8996; + chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8996); + chip->tables.rchg_thr_mv = 150; + chip->tables.aicl_rerun_period_table = aicl_rerun_period; + chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period); +} + +#define CMD_CHG_REG 0x42 +#define EN_BAT_CHG_BIT BIT(1) +static int smbchg_charging_en(struct smbchg_chip *chip, bool en) +{ + /* The en bit is configured active low */ + return smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + EN_BAT_CHG_BIT, en ? 0 : EN_BAT_CHG_BIT); +} + +#define CMD_IL 0x40 +#define USBIN_SUSPEND_BIT BIT(4) +#define CURRENT_100_MA 100 +#define CURRENT_150_MA 150 +#define CURRENT_500_MA 500 +#define CURRENT_900_MA 900 +#define CURRENT_1500_MA 1500 +#define SUSPEND_CURRENT_MA 2 +#define ICL_OVERRIDE_BIT BIT(2) +static int smbchg_usb_suspend(struct smbchg_chip *chip, bool suspend) +{ + int rc; + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set usb suspend rc = %d\n", rc); + return rc; +} + +#define DCIN_SUSPEND_BIT BIT(3) +static int smbchg_dc_suspend(struct smbchg_chip *chip, bool suspend) +{ + int rc = 0; + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set dc suspend rc = %d\n", rc); + return rc; +} + +#define IL_CFG 0xF2 +#define DCIN_INPUT_MASK SMB_MASK(4, 0) +static int smbchg_set_dc_current_max(struct smbchg_chip *chip, int current_ma) +{ + int i; + u8 dc_cur_val; + + i = find_smaller_in_array(chip->tables.dc_ilim_ma_table, + current_ma, chip->tables.dc_ilim_ma_len); + + if (i < 0) { + dev_err(chip->dev, "Cannot find %dma current_table\n", + current_ma); + return -EINVAL; + } + + chip->dc_max_current_ma = chip->tables.dc_ilim_ma_table[i]; + dc_cur_val = i & DCIN_INPUT_MASK; + + pr_smb(PR_STATUS, "dc current set to %d mA\n", + chip->dc_max_current_ma); + return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG, + DCIN_INPUT_MASK, dc_cur_val); +} + +#define AICL_WL_SEL_CFG 0xF5 +#define AICL_WL_SEL_MASK SMB_MASK(1, 0) +#define AICL_WL_SEL_SCHG_LITE_MASK SMB_MASK(2, 0) +static int smbchg_set_aicl_rerun_period_s(struct smbchg_chip *chip, + int period_s) +{ + int i; + u8 reg, mask; + + i = find_smaller_in_array(chip->tables.aicl_rerun_period_table, + period_s, chip->tables.aicl_rerun_period_len); + + if (i < 0) { + dev_err(chip->dev, "Cannot find %ds in aicl rerun period\n", + period_s); + return -EINVAL; + } + + if (chip->schg_version == QPNP_SCHG_LITE) + mask = AICL_WL_SEL_SCHG_LITE_MASK; + else + mask = AICL_WL_SEL_MASK; + + reg = i & mask; + + pr_smb(PR_STATUS, "aicl rerun period set to %ds\n", + chip->tables.aicl_rerun_period_table[i]); + return smbchg_sec_masked_write(chip, + chip->dc_chgpth_base + AICL_WL_SEL_CFG, + mask, reg); +} + +static struct power_supply *get_parallel_psy(struct smbchg_chip *chip) +{ + if (!chip->parallel.avail) + return NULL; + if (chip->parallel.psy) + return chip->parallel.psy; + chip->parallel.psy = power_supply_get_by_name("usb-parallel"); + if (!chip->parallel.psy) + pr_smb(PR_STATUS, "parallel charger not found\n"); + return chip->parallel.psy; +} + +static void smbchg_usb_update_online_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + usb_set_online_work); + bool user_enabled = !get_client_vote(chip->usb_suspend_votable, + USER_EN_VOTER); + int online; + + online = user_enabled && chip->usb_present && !chip->very_weak_charger; + + mutex_lock(&chip->usb_set_online_lock); + if (chip->usb_online != online) { + pr_smb(PR_MISC, "setting usb psy online = %d\n", online); + chip->usb_online = online; + power_supply_changed(chip->usb_psy); + } + mutex_unlock(&chip->usb_set_online_lock); +} + +#define CHGPTH_CFG 0xF4 +#define CFG_USB_2_3_SEL_BIT BIT(7) +#define CFG_USB_2 0 +#define CFG_USB_3 BIT(7) +#define USBIN_INPUT_MASK SMB_MASK(4, 0) +#define USBIN_MODE_CHG_BIT BIT(0) +#define USBIN_LIMITED_MODE 0 +#define USBIN_HC_MODE BIT(0) +#define USB51_MODE_BIT BIT(1) +#define USB51_100MA 0 +#define USB51_500MA BIT(1) +static int smbchg_set_high_usb_chg_current(struct smbchg_chip *chip, + int current_ma) +{ + int i, rc; + u8 usb_cur_val; + + if (current_ma == CURRENT_100_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CFG_USB_2 rc=%d\n", rc); + return rc; + } + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT | ICL_OVERRIDE_BIT, + USBIN_LIMITED_MODE | USB51_100MA | ICL_OVERRIDE_BIT); + if (rc < 0) { + pr_err("Couldn't set ICL_OVERRIDE rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, + "Forcing 100mA current limit\n"); + chip->usb_max_current_ma = CURRENT_100_MA; + return rc; + } + + i = find_smaller_in_array(chip->tables.usb_ilim_ma_table, + current_ma, chip->tables.usb_ilim_ma_len); + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_150_MA); + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_150_MA, rc); + else + chip->usb_max_current_ma = 150; + return rc; + } + + usb_cur_val = i & USBIN_INPUT_MASK; + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + IL_CFG, + USBIN_INPUT_MASK, usb_cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); + return rc; + } + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT, USBIN_HC_MODE); + if (rc < 0) + dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc); + chip->usb_max_current_ma = chip->tables.usb_ilim_ma_table[i]; + return rc; +} + +/* if APSD results are used + * if SDP is detected it will look at 500mA setting + * if set it will draw 500mA + * if unset it will draw 100mA + * if CDP/DCP it will look at 0x0C setting + * i.e. values in 0x41[1, 0] does not matter + */ +static int smbchg_set_usb_current_max(struct smbchg_chip *chip, + int current_ma) +{ + int rc = 0; + + /* + * if the battery is not present, do not allow the usb ICL to lower in + * order to avoid browning out the device during a hotswap. + */ + if (!chip->batt_present && current_ma < chip->usb_max_current_ma) { + pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n", + current_ma); + return 0; + } + pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma); + + if (current_ma <= SUSPEND_CURRENT_MA) { + /* suspend the usb if current <= 2mA */ + rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, true, 0); + chip->usb_max_current_ma = 0; + goto out; + } else { + rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, false, 0); + } + + switch (chip->usb_supply_type) { + case POWER_SUPPLY_TYPE_USB: + if ((current_ma < CURRENT_150_MA) && + (chip->wa_flags & SMBCHG_USB100_WA)) + current_ma = CURRENT_150_MA; + + if (current_ma < CURRENT_150_MA) { + /* force 100mA */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 100; + } + /* specific current values */ + if (current_ma == CURRENT_150_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 150; + } + if (current_ma == CURRENT_500_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_500MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 500; + } + if (current_ma == CURRENT_900_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_500MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 900; + } + break; + case POWER_SUPPLY_TYPE_USB_CDP: + if (current_ma < CURRENT_1500_MA) { + /* use override for CDP */ + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT); + if (rc < 0) + pr_err("Couldn't set override rc = %d\n", rc); + } + /* fall through */ + default: + rc = smbchg_set_high_usb_chg_current(chip, current_ma); + if (rc < 0) + pr_err("Couldn't set %dmA rc = %d\n", current_ma, rc); + break; + } + +out: + pr_smb(PR_STATUS, "usb type = %d current set to %d mA\n", + chip->usb_supply_type, chip->usb_max_current_ma); + return rc; +} + +#define USBIN_HVDCP_STS 0x0C +#define USBIN_HVDCP_SEL_BIT BIT(4) +#define USBIN_HVDCP_SEL_9V_BIT BIT(1) +#define SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT BIT(2) +#define SCHG_LITE_USBIN_HVDCP_SEL_BIT BIT(0) +static int smbchg_get_min_parallel_current_ma(struct smbchg_chip *chip) +{ + int rc; + u8 reg, hvdcp_sel, hvdcp_sel_9v; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); + return 0; + } + if (chip->schg_version == QPNP_SCHG_LITE) { + hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT; + hvdcp_sel_9v = SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT; + } else { + hvdcp_sel = USBIN_HVDCP_SEL_BIT; + hvdcp_sel_9v = USBIN_HVDCP_SEL_9V_BIT; + } + + if ((reg & hvdcp_sel) && (reg & hvdcp_sel_9v)) + return chip->parallel.min_9v_current_thr_ma; + return chip->parallel.min_current_thr_ma; +} + +static bool is_hvdcp_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg, hvdcp_sel; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc < 0) { + pr_err("Couldn't read hvdcp status rc = %d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "HVDCP_STS = 0x%02x\n", reg); + /* + * If a valid HVDCP is detected, notify it to the usb_psy only + * if USB is still present. + */ + if (chip->schg_version == QPNP_SCHG_LITE) + hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT; + else + hvdcp_sel = USBIN_HVDCP_SEL_BIT; + + if ((reg & hvdcp_sel) && is_usb_present(chip)) + return true; + + return false; +} + +#define FCC_CFG 0xF2 +#define FCC_500MA_VAL 0x4 +#define FCC_MASK SMB_MASK(4, 0) +static int smbchg_set_fastchg_current_raw(struct smbchg_chip *chip, + int current_ma) +{ + int i, rc; + u8 cur_val; + + /* the fcc enumerations are the same as the usb currents */ + i = find_smaller_in_array(chip->tables.usb_ilim_ma_table, + current_ma, chip->tables.usb_ilim_ma_len); + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_500_MA); + + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, + FCC_MASK, + FCC_500MA_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_500_MA, rc); + else + chip->fastchg_current_ma = 500; + return rc; + } + + if (chip->tables.usb_ilim_ma_table[i] == chip->fastchg_current_ma) { + pr_smb(PR_STATUS, "skipping fastchg current request: %d\n", + chip->fastchg_current_ma); + return 0; + } + + cur_val = i & FCC_MASK; + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, + FCC_MASK, cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to fcc cfg rc = %d\n", rc); + return rc; + } + pr_smb(PR_STATUS, "fastcharge current requested %d, set to %d\n", + current_ma, chip->tables.usb_ilim_ma_table[cur_val]); + + chip->fastchg_current_ma = chip->tables.usb_ilim_ma_table[cur_val]; + return rc; +} + +#define ICL_STS_1_REG 0x7 +#define ICL_STS_2_REG 0x9 +#define ICL_STS_MASK 0x1F +#define AICL_SUSP_BIT BIT(6) +#define AICL_STS_BIT BIT(5) +#define USBIN_SUSPEND_STS_BIT BIT(3) +#define USBIN_ACTIVE_PWR_SRC_BIT BIT(1) +#define DCIN_ACTIVE_PWR_SRC_BIT BIT(0) +#define PARALLEL_REENABLE_TIMER_MS 1000 +#define PARALLEL_CHG_THRESHOLD_CURRENT 1800 +static bool smbchg_is_usbin_active_pwr_src(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_2_REG, 1); + if (rc < 0) { + dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc); + return false; + } + + return !(reg & USBIN_SUSPEND_STS_BIT) + && (reg & USBIN_ACTIVE_PWR_SRC_BIT); +} + +static int smbchg_parallel_usb_charging_en(struct smbchg_chip *chip, bool en) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + + if (!parallel_psy || !chip->parallel_charger_detected) + return 0; + + pval.intval = en; + return power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval); +} + +#define ESR_PULSE_CURRENT_DELTA_MA 200 +static int smbchg_sw_esr_pulse_en(struct smbchg_chip *chip, bool en) +{ + int rc, fg_current_now, icl_ma; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, + &fg_current_now); + if (rc) { + pr_smb(PR_STATUS, "bms psy does not support OCV\n"); + return 0; + } + + icl_ma = max(chip->iterm_ma + ESR_PULSE_CURRENT_DELTA_MA, + fg_current_now - ESR_PULSE_CURRENT_DELTA_MA); + rc = vote(chip->fcc_votable, ESR_PULSE_FCC_VOTER, en, icl_ma); + if (rc < 0) { + pr_err("Couldn't Vote FCC en = %d rc = %d\n", en, rc); + return rc; + } + rc = smbchg_parallel_usb_charging_en(chip, !en); + return rc; +} + +#define USB_AICL_CFG 0xF3 +#define AICL_EN_BIT BIT(2) +static void smbchg_rerun_aicl(struct smbchg_chip *chip) +{ + pr_smb(PR_STATUS, "Rerunning AICL...\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + /* Add a delay so that AICL successfully clears */ + msleep(50); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); +} + +static void taper_irq_en(struct smbchg_chip *chip, bool en) +{ + mutex_lock(&chip->taper_irq_lock); + if (en != chip->taper_irq_enabled) { + if (en) { + enable_irq(chip->taper_irq); + enable_irq_wake(chip->taper_irq); + } else { + disable_irq_wake(chip->taper_irq); + disable_irq_nosync(chip->taper_irq); + } + chip->taper_irq_enabled = en; + } + mutex_unlock(&chip->taper_irq_lock); +} + +static int smbchg_get_aicl_level_ma(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_1_REG, 1); + if (rc < 0) { + dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc); + return 0; + } + if (reg & AICL_SUSP_BIT) { + pr_warn("AICL suspended: %02x\n", reg); + return 0; + } + reg &= ICL_STS_MASK; + if (reg >= chip->tables.usb_ilim_ma_len) { + pr_warn("invalid AICL value: %02x\n", reg); + return 0; + } + return chip->tables.usb_ilim_ma_table[reg]; +} + +static void smbchg_parallel_usb_disable(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + + if (!parallel_psy || !chip->parallel_charger_detected) + return; + pr_smb(PR_STATUS, "disabling parallel charger\n"); + chip->parallel.last_disabled = ktime_get_boottime(); + taper_irq_en(chip, false); + chip->parallel.initial_aicl_ma = 0; + chip->parallel.current_max_ma = 0; + pval.intval = SUSPEND_CURRENT_MA * 1000; + power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_CURRENT_MAX, + &pval); + + pval.intval = false; + power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_PRESENT, + &pval); + smbchg_set_fastchg_current_raw(chip, + get_effective_result_locked(chip->fcc_votable)); + smbchg_set_usb_current_max(chip, + get_effective_result_locked(chip->usb_icl_votable)); + smbchg_rerun_aicl(chip); +} + +#define PARALLEL_TAPER_MAX_TRIES 3 +#define PARALLEL_FCC_PERCENT_REDUCTION 75 +#define MINIMUM_PARALLEL_FCC_MA 500 +#define CHG_ERROR_BIT BIT(0) +#define BAT_TAPER_MODE_BIT BIT(6) +static void smbchg_parallel_usb_taper(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + int parallel_fcc_ma, tries = 0; + u8 reg = 0; + + if (!parallel_psy || !chip->parallel_charger_detected) + return; + + smbchg_stay_awake(chip, PM_PARALLEL_TAPER); +try_again: + mutex_lock(&chip->parallel.lock); + if (chip->parallel.current_max_ma == 0) { + pr_smb(PR_STATUS, "Not parallel charging, skipping\n"); + goto done; + } + power_supply_get_property(parallel_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + tries += 1; + parallel_fcc_ma = pval.intval / 1000; + pr_smb(PR_STATUS, "try #%d parallel charger fcc = %d\n", + tries, parallel_fcc_ma); + if (parallel_fcc_ma < MINIMUM_PARALLEL_FCC_MA + || tries > PARALLEL_TAPER_MAX_TRIES) { + smbchg_parallel_usb_disable(chip); + goto done; + } + pval.intval = ((parallel_fcc_ma + * PARALLEL_FCC_PERCENT_REDUCTION) / 100); + pr_smb(PR_STATUS, "reducing FCC of parallel charger to %d\n", + pval.intval); + /* Change it to uA */ + pval.intval *= 1000; + power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + /* + * sleep here for 100 ms in order to make sure the charger has a chance + * to go back into constant current charging + */ + mutex_unlock(&chip->parallel.lock); + msleep(100); + + mutex_lock(&chip->parallel.lock); + if (chip->parallel.current_max_ma == 0) { + pr_smb(PR_STATUS, "Not parallel charging, skipping\n"); + goto done; + } + smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + if (reg & BAT_TAPER_MODE_BIT) { + mutex_unlock(&chip->parallel.lock); + goto try_again; + } + taper_irq_en(chip, true); +done: + mutex_unlock(&chip->parallel.lock); + smbchg_relax(chip, PM_PARALLEL_TAPER); +} + +static void smbchg_parallel_usb_enable(struct smbchg_chip *chip, + int total_current_ma) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + int new_parallel_cl_ma, set_parallel_cl_ma, new_pmi_cl_ma, rc; + int current_table_index, target_icl_ma; + int fcc_ma, main_fastchg_current_ma; + int target_parallel_fcc_ma, supplied_parallel_fcc_ma; + int parallel_chg_fcc_percent; + + if (!parallel_psy || !chip->parallel_charger_detected) + return; + + pr_smb(PR_STATUS, "Attempting to enable parallel charger\n"); + pval.intval = chip->vfloat_mv + 50; + rc = power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set Vflt on parallel psy rc: %d\n", rc); + return; + } + /* Set USB ICL */ + target_icl_ma = get_effective_result_locked(chip->usb_icl_votable); + new_parallel_cl_ma = total_current_ma + * (100 - smbchg_main_chg_icl_percent) / 100; + taper_irq_en(chip, true); + + pval.intval = true; + power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_PRESENT, + &pval); + + pval.intval = new_parallel_cl_ma * 1000; + power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_CURRENT_MAX, + &pval); + + /* read back the real amount of current we are getting */ + power_supply_get_property(parallel_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + set_parallel_cl_ma = pval.intval / 1000; + chip->parallel.current_max_ma = new_parallel_cl_ma; + pr_smb(PR_MISC, "Requested ICL = %d from parallel, got %d\n", + new_parallel_cl_ma, set_parallel_cl_ma); + new_pmi_cl_ma = max(0, target_icl_ma - set_parallel_cl_ma); + pr_smb(PR_STATUS, "New Total USB current = %d[%d, %d]\n", + total_current_ma, new_pmi_cl_ma, + set_parallel_cl_ma); + smbchg_set_usb_current_max(chip, new_pmi_cl_ma); + + /* begin splitting the fast charge current */ + fcc_ma = get_effective_result_locked(chip->fcc_votable); + parallel_chg_fcc_percent = + 100 - smbchg_main_chg_fcc_percent; + target_parallel_fcc_ma = + (fcc_ma * parallel_chg_fcc_percent) / 100; + pval.intval = target_parallel_fcc_ma * 1000; + power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + /* check how much actual current is supplied by the parallel charger */ + power_supply_get_property(parallel_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + supplied_parallel_fcc_ma = pval.intval / 1000; + pr_smb(PR_MISC, "Requested FCC = %d from parallel, got %d\n", + target_parallel_fcc_ma, supplied_parallel_fcc_ma); + + /* then for the main charger, use the left over FCC */ + current_table_index = find_smaller_in_array( + chip->tables.usb_ilim_ma_table, + fcc_ma - supplied_parallel_fcc_ma, + chip->tables.usb_ilim_ma_len); + main_fastchg_current_ma = + chip->tables.usb_ilim_ma_table[current_table_index]; + smbchg_set_fastchg_current_raw(chip, main_fastchg_current_ma); + pr_smb(PR_STATUS, "FCC = %d[%d, %d]\n", fcc_ma, main_fastchg_current_ma, + supplied_parallel_fcc_ma); + + chip->parallel.enabled_once = true; + + return; +} + +static bool smbchg_is_parallel_usb_ok(struct smbchg_chip *chip, + int *ret_total_current_ma) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + int min_current_thr_ma, rc, type; + int total_current_ma, current_limit_ma, parallel_cl_ma; + ktime_t kt_since_last_disable; + u8 reg; + int fcc_ma = get_effective_result_locked(chip->fcc_votable); + int fcc_voter_id = get_effective_client_id_locked(chip->fcc_votable); + int usb_icl_ma = get_effective_result_locked(chip->usb_icl_votable); + + if (!parallel_psy || !smbchg_parallel_en + || !chip->parallel_charger_detected) { + pr_smb(PR_STATUS, "Parallel charging not enabled\n"); + return false; + } + + kt_since_last_disable = ktime_sub(ktime_get_boottime(), + chip->parallel.last_disabled); + if (chip->parallel.current_max_ma == 0 + && chip->parallel.enabled_once + && ktime_to_ms(kt_since_last_disable) + < PARALLEL_REENABLE_TIMER_MS) { + pr_smb(PR_STATUS, "Only been %lld since disable, skipping\n", + ktime_to_ms(kt_since_last_disable)); + return false; + } + + /* + * If the battery is not present, try not to change parallel charging + * from OFF to ON or from ON to OFF, as it could cause the device to + * brown out in the instant that the USB settings are changed. + * + * Only allow parallel charging check to report false (thereby turnin + * off parallel charging) if the battery is still there, or if parallel + * charging is disabled in the first place. + */ + if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST + && (get_prop_batt_present(chip) + || chip->parallel.current_max_ma == 0)) { + pr_smb(PR_STATUS, "Not in fast charge, skipping\n"); + return false; + } + + if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) { + pr_smb(PR_STATUS, "JEITA active, skipping\n"); + return false; + } + + rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); + return false; + } + + type = get_type(reg); + if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB_CDP) { + pr_smb(PR_STATUS, "CDP adapter, skipping\n"); + return false; + } + + if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB) { + pr_smb(PR_STATUS, "SDP adapter, skipping\n"); + return false; + } + + /* + * If USBIN is suspended or not the active power source, do not enable + * parallel charging. The device may be charging off of DCIN. + */ + if (!smbchg_is_usbin_active_pwr_src(chip)) { + pr_smb(PR_STATUS, "USB not active power source: %02x\n", reg); + return false; + } + + min_current_thr_ma = smbchg_get_min_parallel_current_ma(chip); + if (min_current_thr_ma <= 0) { + pr_smb(PR_STATUS, "parallel charger unavailable for thr: %d\n", + min_current_thr_ma); + return false; + } + + if (usb_icl_ma < min_current_thr_ma) { + pr_smb(PR_STATUS, "Weak USB chg skip enable: %d < %d\n", + usb_icl_ma, min_current_thr_ma); + return false; + } + + /* + * Suspend the parallel charger if the charging current is < 1800 mA + * and is not because of an ESR pulse. + */ + if (fcc_voter_id != ESR_PULSE_FCC_VOTER + && fcc_ma < PARALLEL_CHG_THRESHOLD_CURRENT) { + pr_smb(PR_STATUS, "FCC %d lower than %d\n", + fcc_ma, + PARALLEL_CHG_THRESHOLD_CURRENT); + return false; + } + + current_limit_ma = smbchg_get_aicl_level_ma(chip); + if (current_limit_ma <= 0) + return false; + + if (chip->parallel.initial_aicl_ma == 0) { + if (current_limit_ma < min_current_thr_ma) { + pr_smb(PR_STATUS, "Initial AICL very low: %d < %d\n", + current_limit_ma, min_current_thr_ma); + return false; + } + chip->parallel.initial_aicl_ma = current_limit_ma; + } + + power_supply_get_property(parallel_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &pval); + parallel_cl_ma = pval.intval / 1000; + /* + * Read back the real amount of current we are getting + * Treat 2mA as 0 because that is the suspend current setting + */ + if (parallel_cl_ma <= SUSPEND_CURRENT_MA) + parallel_cl_ma = 0; + + /* + * Set the parallel charge path's input current limit (ICL) + * to the total current / 2 + */ + total_current_ma = min(current_limit_ma + parallel_cl_ma, usb_icl_ma); + + if (total_current_ma < chip->parallel.initial_aicl_ma + - chip->parallel.allowed_lowering_ma) { + pr_smb(PR_STATUS, + "Total current reduced a lot: %d (%d + %d) < %d - %d\n", + total_current_ma, + current_limit_ma, parallel_cl_ma, + chip->parallel.initial_aicl_ma, + chip->parallel.allowed_lowering_ma); + return false; + } + + *ret_total_current_ma = total_current_ma; + return true; +} + +#define PARALLEL_CHARGER_EN_DELAY_MS 500 +static void smbchg_parallel_usb_en_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + parallel_en_work.work); + int previous_aicl_ma, total_current_ma, aicl_ma; + bool in_progress; + + /* do a check to see if the aicl is stable */ + previous_aicl_ma = smbchg_get_aicl_level_ma(chip); + msleep(PARALLEL_CHARGER_EN_DELAY_MS); + aicl_ma = smbchg_get_aicl_level_ma(chip); + if (previous_aicl_ma == aicl_ma) { + pr_smb(PR_STATUS, "AICL at %d\n", aicl_ma); + } else { + pr_smb(PR_STATUS, + "AICL changed [%d -> %d], recheck %d ms\n", + previous_aicl_ma, aicl_ma, + PARALLEL_CHARGER_EN_DELAY_MS); + goto recheck; + } + + mutex_lock(&chip->parallel.lock); + in_progress = (chip->parallel.current_max_ma != 0); + if (smbchg_is_parallel_usb_ok(chip, &total_current_ma)) { + smbchg_parallel_usb_enable(chip, total_current_ma); + } else { + if (in_progress) { + pr_smb(PR_STATUS, "parallel charging unavailable\n"); + smbchg_parallel_usb_disable(chip); + } + } + mutex_unlock(&chip->parallel.lock); + smbchg_relax(chip, PM_PARALLEL_CHECK); + return; + +recheck: + schedule_delayed_work(&chip->parallel_en_work, 0); +} + +static void smbchg_parallel_usb_check_ok(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + + if (!parallel_psy || !chip->parallel_charger_detected) + return; + + smbchg_stay_awake(chip, PM_PARALLEL_CHECK); + schedule_delayed_work(&chip->parallel_en_work, 0); +} + +static int charging_suspend_vote_cb(struct device *dev, int suspend, + int client, int last_suspend, + int last_client) +{ + int rc; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + rc = smbchg_charging_en(chip, !suspend); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't configure batt chg: 0x%x rc = %d\n", + !suspend, rc); + } + + return rc; +} + +static int usb_suspend_vote_cb(struct device *dev, int suspend, + int client, int last_suspend, + int last_client) +{ + int rc; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + rc = smbchg_usb_suspend(chip, suspend); + if (rc < 0) + return rc; + + if (client == THERMAL_EN_VOTER || client == POWER_SUPPLY_EN_VOTER || + client == USER_EN_VOTER || + client == FAKE_BATTERY_EN_VOTER) + smbchg_parallel_usb_check_ok(chip); + + return rc; +} + +static int dc_suspend_vote_cb(struct device *dev, int suspend, + int client, int last_suspend, + int last_client) +{ + int rc; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + rc = smbchg_dc_suspend(chip, suspend); + if (rc < 0) + return rc; + + if (chip->dc_psy_type != -EINVAL && chip->dc_psy) + power_supply_changed(chip->dc_psy); + + return rc; +} + +static int set_fastchg_current_vote_cb(struct device *dev, + int fcc_ma, + int client, + int last_fcc_ma, + int last_client) +{ + struct smbchg_chip *chip = dev_get_drvdata(dev); + int rc; + + if (chip->parallel.current_max_ma == 0) { + rc = smbchg_set_fastchg_current_raw(chip, fcc_ma); + if (rc < 0) { + pr_err("Can't set FCC fcc_ma=%d rc=%d\n", fcc_ma, rc); + return rc; + } + } + /* + * check if parallel charging can be enabled, and if enabled, + * distribute the fcc + */ + smbchg_parallel_usb_check_ok(chip); + return 0; +} + +static int smbchg_set_fastchg_current_user(struct smbchg_chip *chip, + int current_ma) +{ + int rc = 0; + + pr_smb(PR_STATUS, "User setting FCC to %d\n", current_ma); + + rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, current_ma); + if (rc < 0) + pr_err("Couldn't vote en rc %d\n", rc); + return rc; +} + +static struct ilim_entry *smbchg_wipower_find_entry(struct smbchg_chip *chip, + struct ilim_map *map, int uv) +{ + int i; + struct ilim_entry *ret = &(chip->wipower_default.entries[0]); + + for (i = 0; i < map->num; i++) { + if (is_between(map->entries[i].vmin_uv, map->entries[i].vmax_uv, + uv)) + ret = &map->entries[i]; + } + return ret; +} + +#define ZIN_ICL_PT 0xFC +#define ZIN_ICL_LV 0xFD +#define ZIN_ICL_HV 0xFE +#define ZIN_ICL_MASK SMB_MASK(4, 0) +static int smbchg_dcin_ilim_config(struct smbchg_chip *chip, int offset, int ma) +{ + int i, rc; + + i = find_smaller_in_array(chip->tables.dc_ilim_ma_table, + ma, chip->tables.dc_ilim_ma_len); + + if (i < 0) + i = 0; + + rc = smbchg_sec_masked_write(chip, chip->bat_if_base + offset, + ZIN_ICL_MASK, i); + if (rc) + dev_err(chip->dev, "Couldn't write bat if offset %d value = %d rc = %d\n", + offset, i, rc); + return rc; +} + +static int smbchg_wipower_ilim_config(struct smbchg_chip *chip, + struct ilim_entry *ilim) +{ + int rc = 0; + + if (chip->current_ilim.icl_pt_ma != ilim->icl_pt_ma) { + rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_PT, ilim->icl_pt_ma); + if (rc) + dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", + ZIN_ICL_PT, ilim->icl_pt_ma, rc); + else + chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma; + } + + if (chip->current_ilim.icl_lv_ma != ilim->icl_lv_ma) { + rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_LV, ilim->icl_lv_ma); + if (rc) + dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", + ZIN_ICL_LV, ilim->icl_lv_ma, rc); + else + chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma; + } + + if (chip->current_ilim.icl_hv_ma != ilim->icl_hv_ma) { + rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_HV, ilim->icl_hv_ma); + if (rc) + dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n", + ZIN_ICL_HV, ilim->icl_hv_ma, rc); + else + chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma; + } + return rc; +} + +static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx); +static int smbchg_wipower_dcin_btm_configure(struct smbchg_chip *chip, + struct ilim_entry *ilim) +{ + int rc; + + if (ilim->vmin_uv == chip->current_ilim.vmin_uv + && ilim->vmax_uv == chip->current_ilim.vmax_uv) + return 0; + + chip->param.channel = DCIN; + chip->param.btm_ctx = chip; + if (wipower_dcin_interval < ADC_MEAS1_INTERVAL_0MS) + wipower_dcin_interval = ADC_MEAS1_INTERVAL_0MS; + + if (wipower_dcin_interval > ADC_MEAS1_INTERVAL_16S) + wipower_dcin_interval = ADC_MEAS1_INTERVAL_16S; + + chip->param.timer_interval = wipower_dcin_interval; + chip->param.threshold_notification = &btm_notify_dcin; + chip->param.high_thr = ilim->vmax_uv + wipower_dcin_hyst_uv; + chip->param.low_thr = ilim->vmin_uv - wipower_dcin_hyst_uv; + chip->param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + rc = qpnp_vadc_channel_monitor(chip->vadc_dev, &chip->param); + if (rc) { + dev_err(chip->dev, "Couldn't configure btm for dcin rc = %d\n", + rc); + } else { + chip->current_ilim.vmin_uv = ilim->vmin_uv; + chip->current_ilim.vmax_uv = ilim->vmax_uv; + pr_smb(PR_STATUS, "btm ilim = (%duV %duV %dmA %dmA %dmA)\n", + ilim->vmin_uv, ilim->vmax_uv, + ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); + } + return rc; +} + +static int smbchg_wipower_icl_configure(struct smbchg_chip *chip, + int dcin_uv, bool div2) +{ + int rc = 0; + struct ilim_map *map = div2 ? &chip->wipower_div2 : &chip->wipower_pt; + struct ilim_entry *ilim = smbchg_wipower_find_entry(chip, map, dcin_uv); + + rc = smbchg_wipower_ilim_config(chip, ilim); + if (rc) { + dev_err(chip->dev, "failed to config ilim rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n", + rc, dcin_uv, div2, + ilim->vmin_uv, ilim->vmax_uv, + ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); + return rc; + } + + rc = smbchg_wipower_dcin_btm_configure(chip, ilim); + if (rc) { + dev_err(chip->dev, "failed to config btm rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n", + rc, dcin_uv, div2, + ilim->vmin_uv, ilim->vmax_uv, + ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma); + return rc; + } + chip->wipower_configured = true; + return 0; +} + +static void smbchg_wipower_icl_deconfigure(struct smbchg_chip *chip) +{ + int rc; + struct ilim_entry *ilim = &(chip->wipower_default.entries[0]); + + if (!chip->wipower_configured) + return; + + rc = smbchg_wipower_ilim_config(chip, ilim); + if (rc) + dev_err(chip->dev, "Couldn't config default ilim rc = %d\n", + rc); + + rc = qpnp_vadc_end_channel_monitor(chip->vadc_dev); + if (rc) + dev_err(chip->dev, "Couldn't de configure btm for dcin rc = %d\n", + rc); + + chip->wipower_configured = false; + chip->current_ilim.vmin_uv = 0; + chip->current_ilim.vmax_uv = 0; + chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma; + chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma; + chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma; + pr_smb(PR_WIPOWER, "De config btm\n"); +} + +#define FV_STS 0x0C +#define DIV2_ACTIVE BIT(7) +static void __smbchg_wipower_check(struct smbchg_chip *chip) +{ + int chg_type; + bool usb_present, dc_present; + int rc; + int dcin_uv; + bool div2; + struct qpnp_vadc_result adc_result; + u8 reg; + + if (!wipower_dyn_icl_en) { + smbchg_wipower_icl_deconfigure(chip); + return; + } + + chg_type = get_prop_charge_type(chip); + usb_present = is_usb_present(chip); + dc_present = is_dc_present(chip); + if (chg_type != POWER_SUPPLY_CHARGE_TYPE_NONE + && !usb_present + && dc_present + && chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) { + rc = qpnp_vadc_read(chip->vadc_dev, DCIN, &adc_result); + if (rc) { + pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc); + return; + } + dcin_uv = adc_result.physical; + + /* check div_by_2 */ + rc = smbchg_read(chip, ®, chip->chgr_base + FV_STS, 1); + if (rc) { + pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc); + return; + } + div2 = !!(reg & DIV2_ACTIVE); + + pr_smb(PR_WIPOWER, + "config ICL chg_type = %d usb = %d dc = %d dcin_uv(adc_code) = %d (0x%x) div2 = %d\n", + chg_type, usb_present, dc_present, dcin_uv, + adc_result.adc_code, div2); + smbchg_wipower_icl_configure(chip, dcin_uv, div2); + } else { + pr_smb(PR_WIPOWER, + "deconfig ICL chg_type = %d usb = %d dc = %d\n", + chg_type, usb_present, dc_present); + smbchg_wipower_icl_deconfigure(chip); + } +} + +static void smbchg_wipower_check(struct smbchg_chip *chip) +{ + if (!chip->wipower_dyn_icl_avail) + return; + + mutex_lock(&chip->wipower_config); + __smbchg_wipower_check(chip); + mutex_unlock(&chip->wipower_config); +} + +static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx) +{ + struct smbchg_chip *chip = ctx; + + mutex_lock(&chip->wipower_config); + pr_smb(PR_WIPOWER, "%s state\n", + state == ADC_TM_LOW_STATE ? "low" : "high"); + chip->current_ilim.vmin_uv = 0; + chip->current_ilim.vmax_uv = 0; + __smbchg_wipower_check(chip); + mutex_unlock(&chip->wipower_config); +} + +static int force_dcin_icl_write(void *data, u64 val) +{ + struct smbchg_chip *chip = data; + + smbchg_wipower_check(chip); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_dcin_icl_ops, NULL, + force_dcin_icl_write, "0x%02llx\n"); + +/* + * set the dc charge path's maximum allowed current draw + * that may be limited by the system's thermal level + */ +static int set_dc_current_limit_vote_cb(struct device *dev, + int icl_ma, + int client, + int last_icl_ma, + int last_client) +{ + struct smbchg_chip *chip = dev_get_drvdata(dev); + + return smbchg_set_dc_current_max(chip, icl_ma); +} + +/* + * set the usb charge path's maximum allowed current draw + * that may be limited by the system's thermal level + */ +static int set_usb_current_limit_vote_cb(struct device *dev, + int icl_ma, + int client, + int last_icl_ma, + int last_client) +{ + struct smbchg_chip *chip = dev_get_drvdata(dev); + int rc, aicl_ma, effective_id; + + effective_id = get_effective_client_id_locked(chip->usb_icl_votable); + + /* disable parallel charging if HVDCP is voting for 300mA */ + if (effective_id == HVDCP_ICL_VOTER) + smbchg_parallel_usb_disable(chip); + + if (chip->parallel.current_max_ma == 0) { + rc = smbchg_set_usb_current_max(chip, icl_ma); + if (rc) { + pr_err("Failed to set usb current max: %d\n", rc); + return rc; + } + } + + /* skip the aicl rerun if hvdcp icl voter is active */ + if (effective_id == HVDCP_ICL_VOTER) + return 0; + + aicl_ma = smbchg_get_aicl_level_ma(chip); + if (icl_ma > aicl_ma) + smbchg_rerun_aicl(chip); + smbchg_parallel_usb_check_ok(chip); + return 0; +} + +static int smbchg_system_temp_level_set(struct smbchg_chip *chip, + int lvl_sel) +{ + int rc = 0; + int prev_therm_lvl; + int thermal_icl_ma; + + if (!chip->thermal_mitigation) { + dev_err(chip->dev, "Thermal mitigation not supported\n"); + return -EINVAL; + } + + if (lvl_sel < 0) { + dev_err(chip->dev, "Unsupported level selected %d\n", lvl_sel); + return -EINVAL; + } + + if (lvl_sel >= chip->thermal_levels) { + dev_err(chip->dev, "Unsupported level selected %d forcing %d\n", + lvl_sel, chip->thermal_levels - 1); + lvl_sel = chip->thermal_levels - 1; + } + + if (lvl_sel == chip->therm_lvl_sel) + return 0; + + mutex_lock(&chip->therm_lvl_lock); + prev_therm_lvl = chip->therm_lvl_sel; + chip->therm_lvl_sel = lvl_sel; + if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) { + /* + * Disable charging if highest value selected by + * setting the DC and USB path in suspend + */ + rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, true, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + goto out; + } + rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER, true, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + goto out; + } + goto out; + } + + if (chip->therm_lvl_sel == 0) { + rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't disable USB thermal ICL vote rc=%d\n", + rc); + + rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't disable DC thermal ICL vote rc=%d\n", + rc); + } else { + thermal_icl_ma = + (int)chip->thermal_mitigation[chip->therm_lvl_sel]; + rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, true, + thermal_icl_ma); + if (rc < 0) + pr_err("Couldn't vote for USB thermal ICL rc=%d\n", rc); + + rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, true, + thermal_icl_ma); + if (rc < 0) + pr_err("Couldn't vote for DC thermal ICL rc=%d\n", rc); + } + + if (prev_therm_lvl == chip->thermal_levels - 1) { + /* + * If previously highest value was selected charging must have + * been disabed. Enable charging by taking the DC and USB path + * out of suspend. + */ + rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, false, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + goto out; + } + rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER, + false, 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + goto out; + } + } +out: + mutex_unlock(&chip->therm_lvl_lock); + return rc; +} + +static int smbchg_ibat_ocp_threshold_ua = 4500000; +module_param(smbchg_ibat_ocp_threshold_ua, int, 0644); + +#define UCONV 1000000LL +#define MCONV 1000LL +#define FLASH_V_THRESHOLD 3000000 +#define FLASH_VDIP_MARGIN 100000 +#define VPH_FLASH_VDIP (FLASH_V_THRESHOLD + FLASH_VDIP_MARGIN) +#define BUCK_EFFICIENCY 800LL +static int smbchg_calc_max_flash_current(struct smbchg_chip *chip) +{ + int ocv_uv, esr_uohm, rbatt_uohm, ibat_now, rc; + int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw; + int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv); + if (rc) { + pr_smb(PR_STATUS, "bms psy does not support OCV\n"); + return 0; + } + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_RESISTANCE, + &esr_uohm); + if (rc) { + pr_smb(PR_STATUS, "bms psy does not support resistance\n"); + return 0; + } + + rc = msm_bcl_read(BCL_PARAM_CURRENT, &ibat_now); + if (rc) { + pr_smb(PR_STATUS, "BCL current read failed: %d\n", rc); + return 0; + } + + rbatt_uohm = esr_uohm + chip->rpara_uohm + chip->rslow_uohm; + /* + * Calculate the maximum current that can pulled out of the battery + * before the battery voltage dips below a safe threshold. + */ + ibat_safe_ua = div_s64((ocv_uv - VPH_FLASH_VDIP) * UCONV, + rbatt_uohm); + + if (ibat_safe_ua <= smbchg_ibat_ocp_threshold_ua) { + /* + * If the calculated current is below the OCP threshold, then + * use it as the possible flash current. + */ + ibat_flash_ua = ibat_safe_ua - ibat_now; + vph_flash_uv = VPH_FLASH_VDIP; + } else { + /* + * If the calculated current is above the OCP threshold, then + * use the ocp threshold instead. + * + * Any higher current will be tripping the battery OCP. + */ + ibat_flash_ua = smbchg_ibat_ocp_threshold_ua - ibat_now; + vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm + * smbchg_ibat_ocp_threshold_ua, UCONV); + } + /* Calculate the input voltage of the flash module. */ + vin_flash_uv = max((chip->vled_max_uv + 500000LL), + div64_s64((vph_flash_uv * 1200), 1000)); + /* Calculate the available power for the flash module. */ + avail_flash_power_fw = BUCK_EFFICIENCY * vph_flash_uv * ibat_flash_ua; + /* + * Calculate the available amount of current the flash module can draw + * before collapsing the battery. (available power/ flash input voltage) + */ + avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV); + pr_smb(PR_MISC, + "avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d\n", + avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm); + return (int)avail_flash_ua; +} + +#define FCC_CMP_CFG 0xF3 +#define FCC_COMP_MASK SMB_MASK(1, 0) +static int smbchg_fastchg_current_comp_set(struct smbchg_chip *chip, + int comp_current) +{ + int rc; + u8 i; + + for (i = 0; i < chip->tables.fcc_comp_len; i++) + if (comp_current == chip->tables.fcc_comp_table[i]) + break; + + if (i >= chip->tables.fcc_comp_len) + return -EINVAL; + + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CMP_CFG, + FCC_COMP_MASK, i); + + if (rc) + dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n", + rc); + + return rc; +} + +#define CFG_TCC_REG 0xF9 +#define CHG_ITERM_MASK SMB_MASK(2, 0) +static int smbchg_iterm_set(struct smbchg_chip *chip, int iterm_ma) +{ + int rc; + u8 reg; + + reg = find_closest_in_array( + chip->tables.iterm_ma_table, + chip->tables.iterm_ma_len, + iterm_ma); + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CFG_TCC_REG, + CHG_ITERM_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set iterm rc = %d\n", rc); + return rc; + } + pr_smb(PR_STATUS, "set tcc (%d) to 0x%02x\n", + iterm_ma, reg); + chip->iterm_ma = iterm_ma; + + return 0; +} + +#define FV_CMP_CFG 0xF5 +#define FV_COMP_MASK SMB_MASK(5, 0) +static int smbchg_float_voltage_comp_set(struct smbchg_chip *chip, int code) +{ + int rc; + u8 val; + + val = code & FV_COMP_MASK; + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FV_CMP_CFG, + FV_COMP_MASK, val); + + if (rc) + dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n", + rc); + + return rc; +} + +#define VFLOAT_CFG_REG 0xF4 +#define MIN_FLOAT_MV 3600 +#define MAX_FLOAT_MV 4500 +#define VFLOAT_MASK SMB_MASK(5, 0) + +#define MID_RANGE_FLOAT_MV_MIN 3600 +#define MID_RANGE_FLOAT_MIN_VAL 0x05 +#define MID_RANGE_FLOAT_STEP_MV 20 + +#define HIGH_RANGE_FLOAT_MIN_MV 4340 +#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A +#define HIGH_RANGE_FLOAT_STEP_MV 10 + +#define VHIGH_RANGE_FLOAT_MIN_MV 4360 +#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2C +#define VHIGH_RANGE_FLOAT_STEP_MV 20 +static int smbchg_float_voltage_set(struct smbchg_chip *chip, int vfloat_mv) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval prop; + int rc, delta; + u8 temp; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", + vfloat_mv); + return -EINVAL; + } + + if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) { + /* mid range */ + delta = vfloat_mv - MID_RANGE_FLOAT_MV_MIN; + temp = MID_RANGE_FLOAT_MIN_VAL + delta + / MID_RANGE_FLOAT_STEP_MV; + vfloat_mv -= delta % MID_RANGE_FLOAT_STEP_MV; + } else if (vfloat_mv <= VHIGH_RANGE_FLOAT_MIN_MV) { + /* high range */ + delta = vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV; + temp = HIGH_RANGE_FLOAT_MIN_VAL + delta + / HIGH_RANGE_FLOAT_STEP_MV; + vfloat_mv -= delta % HIGH_RANGE_FLOAT_STEP_MV; + } else { + /* very high range */ + delta = vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV; + temp = VHIGH_RANGE_FLOAT_MIN_VAL + delta + / VHIGH_RANGE_FLOAT_STEP_MV; + vfloat_mv -= delta % VHIGH_RANGE_FLOAT_STEP_MV; + } + + if (parallel_psy) { + prop.intval = vfloat_mv + 50; + rc = power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop); + if (rc) + dev_err(chip->dev, "Couldn't set float voltage on parallel psy rc: %d\n", + rc); + } + + rc = smbchg_sec_masked_write(chip, chip->chgr_base + VFLOAT_CFG_REG, + VFLOAT_MASK, temp); + + if (rc) + dev_err(chip->dev, "Couldn't set float voltage rc = %d\n", rc); + else + chip->vfloat_mv = vfloat_mv; + + return rc; +} + +static int smbchg_float_voltage_get(struct smbchg_chip *chip) +{ + return chip->vfloat_mv; +} + +#define SFT_CFG 0xFD +#define SFT_EN_MASK SMB_MASK(5, 4) +#define SFT_TO_MASK SMB_MASK(3, 2) +#define PRECHG_SFT_TO_MASK SMB_MASK(1, 0) +#define SFT_TIMER_DISABLE_BIT BIT(5) +#define PRECHG_SFT_TIMER_DISABLE_BIT BIT(4) +#define SAFETY_TIME_MINUTES_SHIFT 2 +static int smbchg_safety_timer_enable(struct smbchg_chip *chip, bool enable) +{ + int rc; + u8 reg; + + if (enable == chip->safety_timer_en) + return 0; + + if (enable) + reg = 0; + else + reg = SFT_TIMER_DISABLE_BIT | PRECHG_SFT_TIMER_DISABLE_BIT; + + rc = smbchg_sec_masked_write(chip, chip->chgr_base + SFT_CFG, + SFT_EN_MASK, reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't %s safety timer rc = %d\n", + enable ? "enable" : "disable", rc); + return rc; + } + chip->safety_timer_en = enable; + return 0; +} + +enum skip_reason { + REASON_OTG_ENABLED = BIT(0), + REASON_FLASH_ENABLED = BIT(1) +}; + +#define BAT_IF_TRIM7_REG 0xF7 +#define CFG_750KHZ_BIT BIT(1) +#define MISC_CFG_NTC_VOUT_REG 0xF3 +#define CFG_NTC_VOUT_FSW_BIT BIT(0) +static int smbchg_switch_buck_frequency(struct smbchg_chip *chip, + bool flash_active) +{ + int rc; + + if (!(chip->wa_flags & SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA)) + return 0; + + if (chip->flash_active == flash_active) { + pr_smb(PR_STATUS, "Fsw not changed, flash_active: %d\n", + flash_active); + return 0; + } + + /* + * As per the systems team recommendation, before the flash fires, + * buck switching frequency(Fsw) needs to be increased to 1MHz. Once the + * flash is disabled, Fsw needs to be set back to 750KHz. + */ + rc = smbchg_sec_masked_write(chip, chip->misc_base + + MISC_CFG_NTC_VOUT_REG, CFG_NTC_VOUT_FSW_BIT, + flash_active ? CFG_NTC_VOUT_FSW_BIT : 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set switching frequency multiplier rc=%d\n", + rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BAT_IF_TRIM7_REG, + CFG_750KHZ_BIT, flash_active ? 0 : CFG_750KHZ_BIT); + if (rc < 0) { + dev_err(chip->dev, "Cannot set switching freq: %d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, "Fsw @ %sHz\n", flash_active ? "1M" : "750K"); + chip->flash_active = flash_active; + return 0; +} + +#define OTG_TRIM6 0xF6 +#define TR_ENB_SKIP_BIT BIT(2) +#define OTG_EN_BIT BIT(0) +static int smbchg_otg_pulse_skip_disable(struct smbchg_chip *chip, + enum skip_reason reason, bool disable) +{ + int rc; + bool disabled; + + disabled = !!chip->otg_pulse_skip_dis; + pr_smb(PR_STATUS, "%s pulse skip, reason %d\n", + disable ? "disabling" : "enabling", reason); + if (disable) + chip->otg_pulse_skip_dis |= reason; + else + chip->otg_pulse_skip_dis &= ~reason; + if (disabled == !!chip->otg_pulse_skip_dis) + return 0; + disabled = !!chip->otg_pulse_skip_dis; + + rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_TRIM6, + TR_ENB_SKIP_BIT, disabled ? TR_ENB_SKIP_BIT : 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't %s otg pulse skip rc = %d\n", + disabled ? "disable" : "enable", rc); + return rc; + } + pr_smb(PR_STATUS, "%s pulse skip\n", disabled ? "disabled" : "enabled"); + return 0; +} + +#define LOW_PWR_OPTIONS_REG 0xFF +#define FORCE_TLIM_BIT BIT(4) +static int smbchg_force_tlim_en(struct smbchg_chip *chip, bool enable) +{ + int rc; + + rc = smbchg_sec_masked_write(chip, chip->otg_base + LOW_PWR_OPTIONS_REG, + FORCE_TLIM_BIT, enable ? FORCE_TLIM_BIT : 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't %s otg force tlim rc = %d\n", + enable ? "enable" : "disable", rc); + return rc; + } + return rc; +} + +static void smbchg_vfloat_adjust_check(struct smbchg_chip *chip) +{ + if (!chip->use_vfloat_adjustments) + return; + + smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST); + pr_smb(PR_STATUS, "Starting vfloat adjustments\n"); + schedule_delayed_work(&chip->vfloat_adjust_work, 0); +} + +#define FV_STS_REG 0xC +#define AICL_INPUT_STS_BIT BIT(6) +static bool smbchg_is_input_current_limited(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->chgr_base + FV_STS_REG, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read FV_STS rc=%d\n", rc); + return false; + } + + return !!(reg & AICL_INPUT_STS_BIT); +} + +#define SW_ESR_PULSE_MS 1500 +static void smbchg_cc_esr_wa_check(struct smbchg_chip *chip) +{ + int rc, esr_count; + + if (!(chip->wa_flags & SMBCHG_CC_ESR_WA)) + return; + + if (!is_usb_present(chip) && !is_dc_present(chip)) { + pr_smb(PR_STATUS, "No inputs present, skipping\n"); + return; + } + + if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST) { + pr_smb(PR_STATUS, "Not in fast charge, skipping\n"); + return; + } + + if (!smbchg_is_input_current_limited(chip)) { + pr_smb(PR_STATUS, "Not input current limited, skipping\n"); + return; + } + + set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1); + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_ESR_COUNT, &esr_count); + if (rc) { + pr_smb(PR_STATUS, + "could not read ESR counter rc = %d\n", rc); + return; + } + + /* + * The esr_count is counting down the number of fuel gauge cycles + * before a ESR pulse is needed. + * + * After a successful ESR pulse, this count is reset to some + * high number like 28. If this reaches 0, then the fuel gauge + * hardware should force a ESR pulse. + * + * However, if the device is in constant current charge mode while + * being input current limited, the ESR pulse will not affect the + * battery current, so the measurement will fail. + * + * As a failsafe, force a manual ESR pulse if this value is read as + * 0. + */ + if (esr_count != 0) { + pr_smb(PR_STATUS, "ESR count is not zero, skipping\n"); + return; + } + + pr_smb(PR_STATUS, "Lowering charge current for ESR pulse\n"); + smbchg_stay_awake(chip, PM_ESR_PULSE); + smbchg_sw_esr_pulse_en(chip, true); + msleep(SW_ESR_PULSE_MS); + pr_smb(PR_STATUS, "Raising charge current for ESR pulse\n"); + smbchg_relax(chip, PM_ESR_PULSE); + smbchg_sw_esr_pulse_en(chip, false); +} + +static void smbchg_soc_changed(struct smbchg_chip *chip) +{ + smbchg_cc_esr_wa_check(chip); +} + +#define DC_AICL_CFG 0xF3 +#define MISC_TRIM_OPT_15_8 0xF5 +#define USB_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3)) +#define USB_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3)) +#define USB_AICL_DEGLITCH_LONG 0 +#define DC_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3)) +#define DC_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3)) +#define DC_AICL_DEGLITCH_LONG 0 +#define AICL_RERUN_MASK (BIT(5) | BIT(4)) +#define AICL_RERUN_ON (BIT(5) | BIT(4)) +#define AICL_RERUN_OFF 0 + +static int smbchg_hw_aicl_rerun_enable_indirect_cb(struct device *dev, + int enable, + int client, int last_enable, + int last_client) +{ + int rc = 0; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + /* + * If the indirect voting result of all the clients is to enable hw aicl + * rerun, then remove our vote to disable hw aicl rerun + */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + HW_AICL_RERUN_ENABLE_INDIRECT_VOTER, !enable, 0); + if (rc < 0) { + pr_err("Couldn't vote for hw rerun rc= %d\n", rc); + return rc; + } + + return rc; +} + +static int smbchg_hw_aicl_rerun_disable_cb(struct device *dev, int disable, + int client, int last_disable, + int last_client) +{ + int rc = 0; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + rc = smbchg_sec_masked_write(chip, + chip->misc_base + MISC_TRIM_OPT_15_8, + AICL_RERUN_MASK, disable ? AICL_RERUN_OFF : AICL_RERUN_ON); + if (rc < 0) + pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n", rc); + + return rc; +} + +static int smbchg_aicl_deglitch_config_cb(struct device *dev, int shorter, + int client, int last_result, + int last_client) +{ + int rc = 0; + struct smbchg_chip *chip = dev_get_drvdata(dev); + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USB_AICL_CFG, + USB_AICL_DEGLITCH_MASK, + shorter ? USB_AICL_DEGLITCH_SHORT : USB_AICL_DEGLITCH_LONG); + if (rc < 0) { + pr_err("Couldn't write to USB_AICL_CFG rc=%d\n", rc); + return rc; + } + rc = smbchg_sec_masked_write(chip, + chip->dc_chgpth_base + DC_AICL_CFG, + DC_AICL_DEGLITCH_MASK, + shorter ? DC_AICL_DEGLITCH_SHORT : DC_AICL_DEGLITCH_LONG); + if (rc < 0) { + pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc); + return rc; + } + return rc; +} + +static void smbchg_aicl_deglitch_wa_en(struct smbchg_chip *chip, bool en) +{ + int rc; + + rc = vote(chip->aicl_deglitch_short_votable, + VARB_WORKAROUND_VOTER, en, 0); + if (rc < 0) { + pr_err("Couldn't vote %s deglitch rc=%d\n", + en ? "short" : "long", rc); + return; + } + pr_smb(PR_STATUS, "AICL deglitch set to %s\n", en ? "short" : "long"); + + rc = vote(chip->hw_aicl_rerun_enable_indirect_votable, + VARB_WORKAROUND_VOTER, en, 0); + if (rc < 0) { + pr_err("Couldn't vote hw aicl rerun rc= %d\n", rc); + return; + } + chip->aicl_deglitch_short = en; +} + +static void smbchg_aicl_deglitch_wa_check(struct smbchg_chip *chip) +{ + union power_supply_propval prop = {0,}; + int rc; + bool low_volt_chgr = true; + + if (!(chip->wa_flags & SMBCHG_AICL_DEGLITCH_WA)) + return; + + if (!is_usb_present(chip) && !is_dc_present(chip)) { + pr_smb(PR_STATUS, "Charger removed\n"); + smbchg_aicl_deglitch_wa_en(chip, false); + return; + } + + if (!chip->bms_psy) + return; + + if (is_usb_present(chip)) { + if (is_hvdcp_present(chip)) + low_volt_chgr = false; + } else if (is_dc_present(chip)) { + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) + low_volt_chgr = false; + else + low_volt_chgr = chip->low_volt_dcin; + } + + if (!low_volt_chgr) { + pr_smb(PR_STATUS, "High volt charger! Don't set deglitch\n"); + smbchg_aicl_deglitch_wa_en(chip, false); + return; + } + + /* It is possible that battery voltage went high above threshold + * when the charger is inserted and can go low because of system + * load. We shouldn't be reconfiguring AICL deglitch when this + * happens as it will lead to oscillation again which is being + * fixed here. Do it once when the battery voltage crosses the + * threshold (e.g. 4.2 V) and clear it only when the charger + * is removed. + */ + if (!chip->vbat_above_headroom) { + rc = power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_VOLTAGE_MIN, &prop); + if (rc < 0) { + pr_err("could not read voltage_min, rc=%d\n", rc); + return; + } + chip->vbat_above_headroom = !prop.intval; + } + smbchg_aicl_deglitch_wa_en(chip, chip->vbat_above_headroom); +} + +#define MISC_TEST_REG 0xE2 +#define BB_LOOP_DISABLE_ICL BIT(2) +static int smbchg_icl_loop_disable_check(struct smbchg_chip *chip) +{ + bool icl_disabled = !chip->chg_otg_enabled && chip->flash_triggered; + int rc = 0; + + if ((chip->wa_flags & SMBCHG_FLASH_ICL_DISABLE_WA) + && icl_disabled != chip->icl_disabled) { + rc = smbchg_sec_masked_write(chip, + chip->misc_base + MISC_TEST_REG, + BB_LOOP_DISABLE_ICL, + icl_disabled ? BB_LOOP_DISABLE_ICL : 0); + chip->icl_disabled = icl_disabled; + } + + return rc; +} + +#define UNKNOWN_BATT_TYPE "Unknown Battery" +#define LOADING_BATT_TYPE "Loading Battery Data" +static int smbchg_config_chg_battery_type(struct smbchg_chip *chip) +{ + int rc = 0, max_voltage_uv = 0, fastchg_ma = 0, ret = 0, iterm_ua = 0; + struct device_node *batt_node, *profile_node; + struct device_node *node = chip->pdev->dev.of_node; + union power_supply_propval prop = {0,}; + + rc = power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_BATTERY_TYPE, &prop); + if (rc) { + pr_smb(PR_STATUS, "Unable to read battery-type rc=%d\n", rc); + return 0; + } + if (!strcmp(prop.strval, UNKNOWN_BATT_TYPE) || + !strcmp(prop.strval, LOADING_BATT_TYPE)) { + pr_smb(PR_MISC, "Battery-type not identified\n"); + return 0; + } + /* quit if there is no change in the battery-type from previous */ + if (chip->battery_type && !strcmp(prop.strval, chip->battery_type)) + return 0; + + batt_node = of_parse_phandle(node, "qcom,battery-data", 0); + if (!batt_node) { + pr_smb(PR_MISC, "No batterydata available\n"); + return 0; + } + + profile_node = of_batterydata_get_best_profile(batt_node, + "bms", NULL); + if (!profile_node) { + pr_err("couldn't find profile handle\n"); + return -EINVAL; + } + chip->battery_type = prop.strval; + + /* change vfloat */ + rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv", + &max_voltage_uv); + if (rc) { + pr_warn("couldn't find battery max voltage rc=%d\n", rc); + ret = rc; + } else { + if (chip->vfloat_mv != (max_voltage_uv / 1000)) { + pr_info("Vfloat changed from %dmV to %dmV for battery-type %s\n", + chip->vfloat_mv, (max_voltage_uv / 1000), + chip->battery_type); + rc = smbchg_float_voltage_set(chip, + (max_voltage_uv / 1000)); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + return rc; + } + } + } + + /* change chg term */ + rc = of_property_read_u32(profile_node, "qcom,chg-term-ua", + &iterm_ua); + if (rc && rc != -EINVAL) { + pr_warn("couldn't read battery term current=%d\n", rc); + ret = rc; + } else if (!rc) { + if (chip->iterm_ma != (iterm_ua / 1000) + && !chip->iterm_disabled) { + pr_info("Term current changed from %dmA to %dmA for battery-type %s\n", + chip->iterm_ma, (iterm_ua / 1000), + chip->battery_type); + rc = smbchg_iterm_set(chip, + (iterm_ua / 1000)); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set iterm rc = %d\n", rc); + return rc; + } + } + chip->iterm_ma = iterm_ua / 1000; + } + + /* + * Only configure from profile if fastchg-ma is not defined in the + * charger device node. + */ + if (!of_find_property(chip->pdev->dev.of_node, + "qcom,fastchg-current-ma", NULL)) { + rc = of_property_read_u32(profile_node, + "qcom,fastchg-current-ma", &fastchg_ma); + if (rc) { + ret = rc; + } else { + pr_smb(PR_MISC, + "fastchg-ma changed from to %dma for battery-type %s\n", + fastchg_ma, chip->battery_type); + rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, + fastchg_ma); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't vote for fastchg current rc=%d\n", + rc); + return rc; + } + } + } + + return ret; +} + +#define MAX_INV_BATT_ID 7700 +#define MIN_INV_BATT_ID 7300 +static void check_battery_type(struct smbchg_chip *chip) +{ + union power_supply_propval prop = {0,}; + bool en; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_BATTERY_TYPE, &prop); + en = (strcmp(prop.strval, UNKNOWN_BATT_TYPE) != 0 + || chip->charge_unknown_battery) + && (strcmp(prop.strval, LOADING_BATT_TYPE) != 0); + vote(chip->battchg_suspend_votable, + BATTCHG_UNKNOWN_BATTERY_EN_VOTER, !en, 0); + + if (!chip->skip_usb_suspend_for_fake_battery) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_RESISTANCE_ID, &prop); + /* suspend USB path for invalid battery-id */ + en = (prop.intval <= MAX_INV_BATT_ID && + prop.intval >= MIN_INV_BATT_ID) ? 1 : 0; + vote(chip->usb_suspend_votable, FAKE_BATTERY_EN_VOTER, + en, 0); + } + } +} + +static void smbchg_external_power_changed(struct power_supply *psy) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + union power_supply_propval prop = {0,}; + int rc, current_limit = 0, soc; + enum power_supply_type usb_supply_type; + char *usb_type_name = "null"; + + if (chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + smbchg_aicl_deglitch_wa_check(chip); + if (chip->bms_psy) { + check_battery_type(chip); + soc = get_prop_batt_capacity(chip); + if (chip->previous_soc != soc) { + chip->previous_soc = soc; + smbchg_soc_changed(chip); + } + + rc = smbchg_config_chg_battery_type(chip); + if (rc) + pr_smb(PR_MISC, + "Couldn't update charger configuration rc=%d\n", + rc); + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop); + if (rc == 0) + vote(chip->usb_suspend_votable, POWER_SUPPLY_EN_VOTER, + !prop.intval, 0); + + current_limit = chip->usb_current_max / 1000; + + /* Override if type-c charger used */ + if (chip->typec_current_ma > 500 && + current_limit < chip->typec_current_ma) + current_limit = chip->typec_current_ma; + + read_usb_type(chip, &usb_type_name, &usb_supply_type); + + if (usb_supply_type != POWER_SUPPLY_TYPE_USB) + goto skip_current_for_non_sdp; + + pr_smb(PR_MISC, "usb type = %s current_limit = %d\n", + usb_type_name, current_limit); + + rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true, + current_limit); + if (rc < 0) + pr_err("Couldn't update USB PSY ICL vote rc=%d\n", rc); + +skip_current_for_non_sdp: + smbchg_vfloat_adjust_check(chip); + + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); +} + +static int smbchg_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + chip->otg_retries = 0; + chip->chg_otg_enabled = true; + smbchg_icl_loop_disable_check(chip); + smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, true); + + /* If pin control mode then return from here */ + if (chip->otg_pinctrl) + return rc; + + /* sleep to make sure the pulse skip is actually disabled */ + msleep(20); + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, OTG_EN_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc); + else + chip->otg_enable_time = ktime_get(); + pr_smb(PR_STATUS, "Enabling OTG Boost\n"); + return rc; +} + +static int smbchg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + if (!chip->otg_pinctrl) { + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", + rc); + } + + chip->chg_otg_enabled = false; + smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, false); + smbchg_icl_loop_disable_check(chip); + pr_smb(PR_STATUS, "Disabling OTG Boost\n"); + return rc; +} + +static int smbchg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + rc = smbchg_read(chip, ®, chip->bat_if_base + CMD_CHG_REG, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & OTG_EN_BIT) ? 1 : 0; +} + +struct regulator_ops smbchg_otg_reg_ops = { + .enable = smbchg_otg_regulator_enable, + .disable = smbchg_otg_regulator_disable, + .is_enabled = smbchg_otg_regulator_is_enable, +}; + +#define USBIN_CHGR_CFG 0xF1 +#define ADAPTER_ALLOWANCE_MASK 0x7 +#define USBIN_ADAPTER_9V 0x3 +#define USBIN_ADAPTER_5V_9V_CONT 0x2 +#define USBIN_ADAPTER_5V_UNREGULATED_9V 0x5 +#define HVDCP_EN_BIT BIT(3) +static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, true, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't suspend charger rc=%d\n", rc); + return rc; + } + + rc = smbchg_read(chip, &chip->original_usbin_allowance, + chip->usb_chgpth_base + USBIN_CHGR_CFG, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc); + return rc; + } + + /* + * To disallow source detect and usbin_uv interrupts, set the adapter + * allowance to 9V, so that the audio boost operating in reverse never + * gets detected as a valid input + */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n", rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + 0xFF, USBIN_ADAPTER_9V); + if (rc < 0) { + dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, "Enabling OTG Boost\n"); + return rc; +} + +static int smbchg_external_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, false, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't unsuspend charger rc=%d\n", rc); + return rc; + } + + /* + * Reenable HVDCP and set the adapter allowance back to the original + * value in order to allow normal USBs to be recognized as a valid + * input. + */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable HVDCP rc=%d\n", rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + 0xFF, chip->original_usbin_allowance); + if (rc < 0) { + dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, "Disabling OTG Boost\n"); + return rc; +} + +static int smbchg_external_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + return get_client_vote(chip->usb_suspend_votable, OTG_EN_VOTER); +} + +struct regulator_ops smbchg_external_otg_reg_ops = { + .enable = smbchg_external_otg_regulator_enable, + .disable = smbchg_external_otg_regulator_disable, + .is_enabled = smbchg_external_otg_regulator_is_enable, +}; + +static int smbchg_regulator_init(struct smbchg_chip *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + struct device_node *regulator_node; + + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smbchg_otg_reg_ops; + chip->otg_vreg.rdesc.of_match = "qcom,smbcharger-boost-otg"; + chip->otg_vreg.rdesc.name = "qcom,smbcharger-boost-otg"; + + chip->otg_vreg.rdev = devm_regulator_register(chip->dev, + &chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "OTG reg failed, rc=%d\n", rc); + } + if (rc) + return rc; + + regulator_node = of_get_child_by_name(chip->dev->of_node, + "qcom,smbcharger-external-otg"); + if (!regulator_node) { + dev_dbg(chip->dev, "external-otg node absent\n"); + return 0; + } + + chip->ext_otg_vreg.rdesc.owner = THIS_MODULE; + chip->ext_otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->ext_otg_vreg.rdesc.ops = &smbchg_external_otg_reg_ops; + chip->ext_otg_vreg.rdesc.of_match = "qcom,smbcharger-external-otg"; + chip->ext_otg_vreg.rdesc.name = "qcom,smbcharger-external-otg"; + if (of_get_property(chip->dev->of_node, "otg-parent-supply", NULL)) + chip->ext_otg_vreg.rdesc.supply_name = "otg-parent"; + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->ext_otg_vreg.rdev = devm_regulator_register(chip->dev, + &chip->ext_otg_vreg.rdesc, + &cfg); + if (IS_ERR(chip->ext_otg_vreg.rdev)) { + rc = PTR_ERR(chip->ext_otg_vreg.rdev); + chip->ext_otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "external OTG reg failed, rc=%d\n", rc); + } + + return rc; +} + +#define CMD_CHG_LED_REG 0x43 +#define CHG_LED_CTRL_BIT BIT(0) +#define LED_SW_CTRL_BIT 0x1 +#define LED_CHG_CTRL_BIT 0x0 +#define CHG_LED_ON 0x03 +#define CHG_LED_OFF 0x00 +#define LED_BLINKING_PATTERN1 0x01 +#define LED_BLINKING_PATTERN2 0x02 +#define LED_BLINKING_CFG_MASK SMB_MASK(2, 1) +#define CHG_LED_SHIFT 1 +static int smbchg_chg_led_controls(struct smbchg_chip *chip) +{ + u8 reg, mask; + int rc; + + if (chip->cfg_chg_led_sw_ctrl) { + /* turn-off LED by default for software control */ + mask = CHG_LED_CTRL_BIT | LED_BLINKING_CFG_MASK; + reg = LED_SW_CTRL_BIT; + } else { + mask = CHG_LED_CTRL_BIT; + reg = LED_CHG_CTRL_BIT; + } + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_LED_REG, + mask, reg); + if (rc < 0) + dev_err(chip->dev, + "Couldn't write LED_CTRL_BIT rc=%d\n", rc); + return rc; +} + +static void smbchg_chg_led_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct smbchg_chip *chip = container_of(cdev, + struct smbchg_chip, led_cdev); + union power_supply_propval pval = {0, }; + u8 reg; + int rc; + + reg = (value > LED_OFF) ? CHG_LED_ON << CHG_LED_SHIFT : + CHG_LED_OFF << CHG_LED_SHIFT; + pval.intval = value > LED_OFF ? 1 : 0; + power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER, + &pval); + pr_smb(PR_STATUS, + "set the charger led brightness to value=%d\n", + value); + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + CMD_CHG_LED_REG, + LED_BLINKING_CFG_MASK, reg); + if (rc) + dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n", + rc); +} + +static enum +led_brightness smbchg_chg_led_brightness_get(struct led_classdev *cdev) +{ + struct smbchg_chip *chip = container_of(cdev, + struct smbchg_chip, led_cdev); + u8 reg_val, chg_led_sts; + int rc; + + rc = smbchg_read(chip, ®_val, chip->bat_if_base + CMD_CHG_LED_REG, + 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read CHG_LED_REG sts rc=%d\n", + rc); + return rc; + } + + chg_led_sts = (reg_val & LED_BLINKING_CFG_MASK) >> CHG_LED_SHIFT; + + pr_smb(PR_STATUS, "chg_led_sts = %02x\n", chg_led_sts); + + return (chg_led_sts == CHG_LED_OFF) ? LED_OFF : LED_FULL; +} + +static void smbchg_chg_led_blink_set(struct smbchg_chip *chip, + unsigned long blinking) +{ + union power_supply_propval pval = {0, }; + u8 reg; + int rc; + + pval.intval = (blinking == 0) ? 0 : 1; + power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER, + &pval); + + if (blinking == 0) { + reg = CHG_LED_OFF << CHG_LED_SHIFT; + } else { + if (blinking == 1) + reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT; + else if (blinking == 2) + reg = LED_BLINKING_PATTERN2 << CHG_LED_SHIFT; + else + reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT; + } + + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + CMD_CHG_LED_REG, + LED_BLINKING_CFG_MASK, reg); + if (rc) + dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n", + rc); +} + +static ssize_t smbchg_chg_led_blink_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct smbchg_chip *chip = container_of(cdev, struct smbchg_chip, + led_cdev); + unsigned long blinking; + ssize_t rc = -EINVAL; + + rc = kstrtoul(buf, 10, &blinking); + if (rc) + return rc; + + smbchg_chg_led_blink_set(chip, blinking); + + return len; +} + +static DEVICE_ATTR(blink, 0664, NULL, smbchg_chg_led_blink_store); + +static struct attribute *led_blink_attributes[] = { + &dev_attr_blink.attr, + NULL, +}; + +static struct attribute_group smbchg_led_attr_group = { + .attrs = led_blink_attributes +}; + +static int smbchg_register_chg_led(struct smbchg_chip *chip) +{ + int rc; + + chip->led_cdev.name = "red"; + chip->led_cdev.brightness_set = smbchg_chg_led_brightness_set; + chip->led_cdev.brightness_get = smbchg_chg_led_brightness_get; + + rc = led_classdev_register(chip->dev, &chip->led_cdev); + if (rc) { + dev_err(chip->dev, "unable to register charger led, rc=%d\n", + rc); + return rc; + } + + rc = sysfs_create_group(&chip->led_cdev.dev->kobj, + &smbchg_led_attr_group); + if (rc) { + dev_err(chip->dev, "led sysfs rc: %d\n", rc); + return rc; + } + + return rc; +} + +static int vf_adjust_low_threshold = 5; +module_param(vf_adjust_low_threshold, int, 0644); + +static int vf_adjust_high_threshold = 7; +module_param(vf_adjust_high_threshold, int, 0644); + +static int vf_adjust_n_samples = 10; +module_param(vf_adjust_n_samples, int, 0644); + +static int vf_adjust_max_delta_mv = 40; +module_param(vf_adjust_max_delta_mv, int, 0644); + +static int vf_adjust_trim_steps_per_adjust = 1; +module_param(vf_adjust_trim_steps_per_adjust, int, 0644); + +#define CENTER_TRIM_CODE 7 +#define MAX_LIN_CODE 14 +#define MAX_TRIM_CODE 15 +#define SCALE_SHIFT 4 +#define VF_TRIM_OFFSET_MASK SMB_MASK(3, 0) +#define VF_STEP_SIZE_MV 10 +#define SCALE_LSB_MV 17 +static int smbchg_trim_add_steps(int prev_trim, int delta_steps) +{ + int scale_steps; + int linear_offset, linear_scale; + int offset_code = prev_trim & VF_TRIM_OFFSET_MASK; + int scale_code = (prev_trim & ~VF_TRIM_OFFSET_MASK) >> SCALE_SHIFT; + + if (abs(delta_steps) > 1) { + pr_smb(PR_STATUS, + "Cant trim multiple steps delta_steps = %d\n", + delta_steps); + return prev_trim; + } + if (offset_code <= CENTER_TRIM_CODE) + linear_offset = offset_code + CENTER_TRIM_CODE; + else if (offset_code > CENTER_TRIM_CODE) + linear_offset = MAX_TRIM_CODE - offset_code; + + if (scale_code <= CENTER_TRIM_CODE) + linear_scale = scale_code + CENTER_TRIM_CODE; + else if (scale_code > CENTER_TRIM_CODE) + linear_scale = scale_code - (CENTER_TRIM_CODE + 1); + + /* check if we can accomodate delta steps with just the offset */ + if (linear_offset + delta_steps >= 0 + && linear_offset + delta_steps <= MAX_LIN_CODE) { + linear_offset += delta_steps; + + if (linear_offset > CENTER_TRIM_CODE) + offset_code = linear_offset - CENTER_TRIM_CODE; + else + offset_code = MAX_TRIM_CODE - linear_offset; + + return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code; + } + + /* changing offset cannot satisfy delta steps, change the scale bits */ + scale_steps = delta_steps > 0 ? 1 : -1; + + if (linear_scale + scale_steps < 0 + || linear_scale + scale_steps > MAX_LIN_CODE) { + pr_smb(PR_STATUS, + "Cant trim scale_steps = %d delta_steps = %d\n", + scale_steps, delta_steps); + return prev_trim; + } + + linear_scale += scale_steps; + + if (linear_scale > CENTER_TRIM_CODE) + scale_code = linear_scale - CENTER_TRIM_CODE; + else + scale_code = linear_scale + (CENTER_TRIM_CODE + 1); + prev_trim = (prev_trim & VF_TRIM_OFFSET_MASK) + | scale_code << SCALE_SHIFT; + + /* + * now that we have changed scale which is a 17mV jump, change the + * offset bits (10mV) too so the effective change is just 7mV + */ + delta_steps = -1 * delta_steps; + + linear_offset = clamp(linear_offset + delta_steps, 0, MAX_LIN_CODE); + if (linear_offset > CENTER_TRIM_CODE) + offset_code = linear_offset - CENTER_TRIM_CODE; + else + offset_code = MAX_TRIM_CODE - linear_offset; + + return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code; +} + +#define TRIM_14 0xFE +#define VF_TRIM_MASK 0xFF +static int smbchg_adjust_vfloat_mv_trim(struct smbchg_chip *chip, + int delta_mv) +{ + int sign, delta_steps, rc = 0; + u8 prev_trim, new_trim; + int i; + + sign = delta_mv > 0 ? 1 : -1; + delta_steps = (delta_mv + sign * VF_STEP_SIZE_MV / 2) + / VF_STEP_SIZE_MV; + + rc = smbchg_read(chip, &prev_trim, chip->misc_base + TRIM_14, 1); + if (rc) { + dev_err(chip->dev, "Unable to read trim 14: %d\n", rc); + return rc; + } + + for (i = 1; i <= abs(delta_steps) + && i <= vf_adjust_trim_steps_per_adjust; i++) { + new_trim = (u8)smbchg_trim_add_steps(prev_trim, + delta_steps > 0 ? 1 : -1); + if (new_trim == prev_trim) { + pr_smb(PR_STATUS, + "VFloat trim unchanged from %02x\n", prev_trim); + /* treat no trim change as an error */ + return -EINVAL; + } + + rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_14, + VF_TRIM_MASK, new_trim); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't change vfloat trim rc=%d\n", rc); + } + pr_smb(PR_STATUS, + "VFlt trim %02x to %02x, delta steps: %d\n", + prev_trim, new_trim, delta_steps); + prev_trim = new_trim; + } + + return rc; +} + +#define VFLOAT_RESAMPLE_DELAY_MS 10000 +static void smbchg_vfloat_adjust_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + vfloat_adjust_work.work); + int vbat_uv, vbat_mv, ibat_ua, rc, delta_vfloat_mv; + bool taper, enable; + + smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST); + taper = (get_prop_charge_type(chip) + == POWER_SUPPLY_CHARGE_TYPE_TAPER); + enable = taper && (chip->parallel.current_max_ma == 0); + + if (!enable) { + pr_smb(PR_MISC, + "Stopping vfloat adj taper=%d parallel_ma = %d\n", + taper, chip->parallel.current_max_ma); + goto stop; + } + + if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) { + pr_smb(PR_STATUS, "JEITA active, skipping\n"); + goto stop; + } + + set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1); + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &vbat_uv); + if (rc) { + pr_smb(PR_STATUS, + "bms psy does not support voltage rc = %d\n", rc); + goto stop; + } + vbat_mv = vbat_uv / 1000; + + if ((vbat_mv - chip->vfloat_mv) < -1 * vf_adjust_max_delta_mv) { + pr_smb(PR_STATUS, "Skip vbat out of range: %d\n", vbat_mv); + goto reschedule; + } + + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_CURRENT_NOW, &ibat_ua); + if (rc) { + pr_smb(PR_STATUS, + "bms psy does not support current_now rc = %d\n", rc); + goto stop; + } + + if (ibat_ua / 1000 > -chip->iterm_ma) { + pr_smb(PR_STATUS, "Skip ibat too high: %d\n", ibat_ua); + goto reschedule; + } + + pr_smb(PR_STATUS, "sample number = %d vbat_mv = %d ibat_ua = %d\n", + chip->n_vbat_samples, + vbat_mv, + ibat_ua); + + chip->max_vbat_sample = max(chip->max_vbat_sample, vbat_mv); + chip->n_vbat_samples += 1; + if (chip->n_vbat_samples < vf_adjust_n_samples) { + pr_smb(PR_STATUS, "Skip %d samples; max = %d\n", + chip->n_vbat_samples, chip->max_vbat_sample); + goto reschedule; + } + /* if max vbat > target vfloat, delta_vfloat_mv could be negative */ + delta_vfloat_mv = chip->vfloat_mv - chip->max_vbat_sample; + pr_smb(PR_STATUS, "delta_vfloat_mv = %d, samples = %d, mvbat = %d\n", + delta_vfloat_mv, chip->n_vbat_samples, chip->max_vbat_sample); + /* + * enough valid samples has been collected, adjust trim codes + * based on maximum of collected vbat samples if necessary + */ + if (delta_vfloat_mv > vf_adjust_high_threshold + || delta_vfloat_mv < -1 * vf_adjust_low_threshold) { + rc = smbchg_adjust_vfloat_mv_trim(chip, delta_vfloat_mv); + if (rc) { + pr_smb(PR_STATUS, + "Stopping vfloat adj after trim adj rc = %d\n", + rc); + goto stop; + } + chip->max_vbat_sample = 0; + chip->n_vbat_samples = 0; + goto reschedule; + } + +stop: + chip->max_vbat_sample = 0; + chip->n_vbat_samples = 0; + smbchg_relax(chip, PM_REASON_VFLOAT_ADJUST); + return; + +reschedule: + schedule_delayed_work(&chip->vfloat_adjust_work, + msecs_to_jiffies(VFLOAT_RESAMPLE_DELAY_MS)); + return; +} + +static int smbchg_charging_status_change(struct smbchg_chip *chip) +{ + smbchg_vfloat_adjust_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + return 0; +} + +#define BB_CLMP_SEL 0xF8 +#define BB_CLMP_MASK SMB_MASK(1, 0) +#define BB_CLMP_VFIX_3338MV 0x1 +#define BB_CLMP_VFIX_3512MV 0x2 +static int smbchg_set_optimal_charging_mode(struct smbchg_chip *chip, int type) +{ + int rc; + bool hvdcp2 = (type == POWER_SUPPLY_TYPE_USB_HVDCP + && smbchg_is_usbin_active_pwr_src(chip)); + + /* + * Set the charger switching freq to 1MHZ if HVDCP 2.0, + * or 750KHZ otherwise + */ + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + BAT_IF_TRIM7_REG, + CFG_750KHZ_BIT, hvdcp2 ? 0 : CFG_750KHZ_BIT); + if (rc) { + dev_err(chip->dev, "Cannot set switching freq: %d\n", rc); + return rc; + } + + /* + * Set the charger switch frequency clamp voltage threshold to 3.338V + * if HVDCP 2.0, or 3.512V otherwise. + */ + rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BB_CLMP_SEL, + BB_CLMP_MASK, + hvdcp2 ? BB_CLMP_VFIX_3338MV : BB_CLMP_VFIX_3512MV); + if (rc) { + dev_err(chip->dev, "Cannot set switching freq: %d\n", rc); + return rc; + } + + return 0; +} + +#define DEFAULT_SDP_MA 100 +#define DEFAULT_CDP_MA 1500 +static int smbchg_change_usb_supply_type(struct smbchg_chip *chip, + enum power_supply_type type) +{ + int rc, current_limit_ma; + + /* + * if the type is not unknown, set the type before changing ICL vote + * in order to ensure that the correct current limit registers are + * used + */ + if (type != POWER_SUPPLY_TYPE_UNKNOWN) + chip->usb_supply_type = type; + + /* + * Type-C only supports STD(900), MEDIUM(1500) and HIGH(3000) current + * modes, skip all BC 1.2 current if external typec is supported. + * Note: for SDP supporting current based on USB notifications. + */ + if (chip->typec_psy && (type != POWER_SUPPLY_TYPE_USB)) + current_limit_ma = chip->typec_current_ma; + else if (type == POWER_SUPPLY_TYPE_USB) + current_limit_ma = DEFAULT_SDP_MA; + else if (type == POWER_SUPPLY_TYPE_USB_CDP) + current_limit_ma = DEFAULT_CDP_MA; + else if (type == POWER_SUPPLY_TYPE_USB_HVDCP) + current_limit_ma = smbchg_default_hvdcp_icl_ma; + else if (type == POWER_SUPPLY_TYPE_USB_HVDCP_3) + current_limit_ma = smbchg_default_hvdcp3_icl_ma; + else + current_limit_ma = smbchg_default_dcp_icl_ma; + + pr_smb(PR_STATUS, "Type %d: setting mA = %d\n", + type, current_limit_ma); + rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true, + current_limit_ma); + if (rc < 0) { + pr_err("Couldn't vote for new USB ICL rc=%d\n", rc); + goto out; + } + + /* otherwise if it is unknown, set type after the vote */ + if (type == POWER_SUPPLY_TYPE_UNKNOWN) + chip->usb_supply_type = type; + + if (!chip->skip_usb_notification) + power_supply_changed(chip->usb_psy); + + /* set the correct buck switching frequency */ + rc = smbchg_set_optimal_charging_mode(chip, type); + if (rc < 0) + pr_err("Couldn't set charger optimal mode rc=%d\n", rc); + +out: + return rc; +} + +#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4) +#define HVDCP_5V 0x00 +#define HVDCP_9V 0x10 +#define USB_CMD_HVDCP_1 0x42 +#define FORCE_HVDCP_2p0 BIT(3) + +static int force_9v_hvdcp(struct smbchg_chip *chip) +{ + int rc; + + /* Force 5V HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc) { + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc); + return rc; + } + + /* Force QC2.0 */ + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + USB_CMD_HVDCP_1, + FORCE_HVDCP_2p0, FORCE_HVDCP_2p0); + rc |= smbchg_masked_write(chip, + chip->usb_chgpth_base + USB_CMD_HVDCP_1, + FORCE_HVDCP_2p0, 0); + if (rc < 0) { + pr_err("Couldn't force QC2.0 rc=%d\n", rc); + return rc; + } + + /* Delay to switch into HVDCP 2.0 and avoid UV */ + msleep(500); + + /* Force 9V HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc) + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc); + + return rc; +} + +static void smbchg_hvdcp_det_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + hvdcp_det_work.work); + int rc; + + if (is_hvdcp_present(chip)) { + if (!chip->hvdcp3_supported && + (chip->wa_flags & SMBCHG_HVDCP_9V_EN_WA)) { + /* force HVDCP 2.0 */ + rc = force_9v_hvdcp(chip); + if (rc) + pr_err("could not force 9V HVDCP continuing rc=%d\n", + rc); + } + smbchg_change_usb_supply_type(chip, + POWER_SUPPLY_TYPE_USB_HVDCP); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_aicl_deglitch_wa_check(chip); + } + smbchg_relax(chip, PM_DETECT_HVDCP); +} + +static int set_usb_psy_dp_dm(struct smbchg_chip *chip, int state) +{ + int rc; + u8 reg; + union power_supply_propval pval = {0, }; + + /* + * ensure that we are not in the middle of an insertion where usbin_uv + * is low and src_detect hasnt gone high. If so force dp=F dm=F + * which guarantees proper type detection + */ + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (!rc && !(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_MISC, "overwriting state = %d with %d\n", + state, POWER_SUPPLY_DP_DM_DPF_DMF); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + return regulator_enable(chip->dpdm_reg); + } + pr_smb(PR_MISC, "setting usb psy dp dm = %d\n", state); + pval.intval = state; + return power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, &pval); +} + +#define APSD_CFG 0xF5 +#define AUTO_SRC_DETECT_EN_BIT BIT(0) +#define APSD_TIMEOUT_MS 1500 +static void restore_from_hvdcp_detection(struct smbchg_chip *chip) +{ + int rc; + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + /* switch to 9V HVDCP */ + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) + pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc); + + /* enable HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable HVDCP rc=%d\n", rc); + + /* enable APSD */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable APSD rc=%d\n", rc); + + /* Reset back to 5V unregulated */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + ADAPTER_ALLOWANCE_MASK, USBIN_ADAPTER_5V_UNREGULATED_9V); + if (rc < 0) + pr_err("Couldn't write usb allowance rc=%d\n", rc); + + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable AICL rc=%d\n", rc); + + chip->hvdcp_3_det_ignore_uv = false; + chip->pulse_cnt = 0; +} + +#define RESTRICTED_CHG_FCC_PERCENT 50 +static int smbchg_restricted_charging(struct smbchg_chip *chip, bool enable) +{ + int current_table_index, fastchg_current; + int rc = 0; + + /* If enable, set the fcc to the set point closest + * to 50% of the configured fcc while remaining below it + */ + current_table_index = find_smaller_in_array( + chip->tables.usb_ilim_ma_table, + chip->cfg_fastchg_current_ma + * RESTRICTED_CHG_FCC_PERCENT / 100, + chip->tables.usb_ilim_ma_len); + fastchg_current = + chip->tables.usb_ilim_ma_table[current_table_index]; + rc = vote(chip->fcc_votable, RESTRICTED_CHG_FCC_VOTER, enable, + fastchg_current); + + pr_smb(PR_STATUS, "restricted_charging set to %d\n", enable); + chip->restricted_charging = enable; + + return rc; +} + +static void handle_usb_removal(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + int rc; + + pr_smb(PR_STATUS, "triggered\n"); + smbchg_aicl_deglitch_wa_check(chip); + /* Clear the OV detected status set before */ + if (chip->usb_ov_det) + chip->usb_ov_det = false; + /* Clear typec current status */ + if (chip->typec_psy) + chip->typec_current_ma = 0; + smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_UNKNOWN); + extcon_set_cable_state_(chip->extcon, EXTCON_USB, chip->usb_present); + if (chip->dpdm_reg) + regulator_disable(chip->dpdm_reg); + schedule_work(&chip->usb_set_online_work); + + pr_smb(PR_MISC, "setting usb psy health UNKNOWN\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + power_supply_changed(chip->usb_psy); + + if (parallel_psy && chip->parallel_charger_detected) { + pval.intval = false; + power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + } + if (chip->parallel.avail && chip->aicl_done_irq + && chip->enable_aicl_wake) { + disable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = false; + } + chip->parallel.enabled_once = false; + chip->vbat_above_headroom = false; + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + ICL_OVERRIDE_BIT, 0); + if (rc < 0) + pr_err("Couldn't set override rc = %d\n", rc); + + vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, false, 0); + chip->usb_icl_delta = 0; + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, false, 0); + vote(chip->aicl_deglitch_short_votable, + HVDCP_SHORT_DEGLITCH_VOTER, false, 0); + if (!chip->hvdcp_not_supported) + restore_from_hvdcp_detection(chip); +} + +static bool is_usbin_uv_high(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + return reg &= USBIN_UV_BIT; +} + +#define HVDCP_NOTIFY_MS 2500 +static void handle_usb_insertion(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + enum power_supply_type usb_supply_type; + int rc; + char *usb_type_name = "null"; + + pr_smb(PR_STATUS, "triggered\n"); + /* usb inserted */ + read_usb_type(chip, &usb_type_name, &usb_supply_type); + pr_smb(PR_STATUS, + "inserted type = %d (%s)", usb_supply_type, usb_type_name); + + smbchg_aicl_deglitch_wa_check(chip); + if (chip->typec_psy) + update_typec_status(chip); + smbchg_change_usb_supply_type(chip, usb_supply_type); + + /* Only notify USB if it's not a charger */ + if (usb_supply_type == POWER_SUPPLY_TYPE_USB || + usb_supply_type == POWER_SUPPLY_TYPE_USB_CDP) + extcon_set_cable_state_(chip->extcon, EXTCON_USB, + chip->usb_present); + + /* Notify the USB psy if OV condition is not present */ + if (!chip->usb_ov_det) { + /* + * Note that this could still be a very weak charger + * if the handle_usb_insertion was triggered from + * the falling edge of an USBIN_OV interrupt + */ + pr_smb(PR_MISC, "setting usb psy health %s\n", + chip->very_weak_charger + ? "UNSPEC_FAILURE" : "GOOD"); + chip->usb_health = chip->very_weak_charger + ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + : POWER_SUPPLY_HEALTH_GOOD; + power_supply_changed(chip->usb_psy); + } + schedule_work(&chip->usb_set_online_work); + + if (!chip->hvdcp_not_supported && + (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP)) { + cancel_delayed_work_sync(&chip->hvdcp_det_work); + smbchg_stay_awake(chip, PM_DETECT_HVDCP); + schedule_delayed_work(&chip->hvdcp_det_work, + msecs_to_jiffies(HVDCP_NOTIFY_MS)); + } + + if (parallel_psy) { + pval.intval = true; + rc = power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + chip->parallel_charger_detected = rc ? false : true; + if (rc) + pr_debug("parallel-charger absent rc=%d\n", rc); + } + + if (chip->parallel.avail && chip->aicl_done_irq + && !chip->enable_aicl_wake) { + rc = enable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = true; + } +} + +void update_usb_status(struct smbchg_chip *chip, bool usb_present, bool force) +{ + mutex_lock(&chip->usb_status_lock); + if (force) { + chip->usb_present = usb_present; + chip->usb_present ? handle_usb_insertion(chip) + : handle_usb_removal(chip); + goto unlock; + } + if (!chip->usb_present && usb_present) { + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } else if (chip->usb_present && !usb_present) { + chip->usb_present = usb_present; + handle_usb_removal(chip); + } + + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); +unlock: + mutex_unlock(&chip->usb_status_lock); +} + +static int otg_oc_reset(struct smbchg_chip *chip) +{ + int rc; + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, 0); + if (rc) + pr_err("Failed to disable OTG rc=%d\n", rc); + + msleep(20); + + /* + * There is a possibility that an USBID interrupt might have + * occurred notifying USB power supply to disable OTG. We + * should not enable OTG in such cases. + */ + if (!is_otg_present(chip)) { + pr_smb(PR_STATUS, + "OTG is not present, not enabling OTG_EN_BIT\n"); + goto out; + } + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, OTG_EN_BIT); + if (rc) + pr_err("Failed to re-enable OTG rc=%d\n", rc); + +out: + return rc; +} + +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); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + rtc_tm_to_time(&tm, now_tm_sec); + +close_time: + rtc_class_close(rtc); + return rc; +} + +#define AICL_IRQ_LIMIT_SECONDS 60 +#define AICL_IRQ_LIMIT_COUNT 25 +static void increment_aicl_count(struct smbchg_chip *chip) +{ + bool bad_charger = false; + int max_aicl_count, rc; + u8 reg; + long elapsed_seconds; + unsigned long now_seconds; + + pr_smb(PR_INTERRUPT, "aicl count c:%d dgltch:%d first:%ld\n", + chip->aicl_irq_count, chip->aicl_deglitch_short, + chip->first_aicl_seconds); + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_1_REG, 1); + if (!rc) + chip->aicl_complete = reg & AICL_STS_BIT; + else + chip->aicl_complete = false; + + if (chip->aicl_deglitch_short || chip->force_aicl_rerun) { + if (!chip->aicl_irq_count) + get_current_time(&chip->first_aicl_seconds); + get_current_time(&now_seconds); + elapsed_seconds = now_seconds + - chip->first_aicl_seconds; + + if (elapsed_seconds > AICL_IRQ_LIMIT_SECONDS) { + pr_smb(PR_INTERRUPT, + "resetting: elp:%ld first:%ld now:%ld c=%d\n", + elapsed_seconds, chip->first_aicl_seconds, + now_seconds, chip->aicl_irq_count); + chip->aicl_irq_count = 1; + get_current_time(&chip->first_aicl_seconds); + return; + } + /* + * Double the amount of AICLs allowed if parallel charging is + * enabled. + */ + max_aicl_count = AICL_IRQ_LIMIT_COUNT + * (chip->parallel.avail ? 2 : 1); + chip->aicl_irq_count++; + + if (chip->aicl_irq_count > max_aicl_count) { + pr_smb(PR_INTERRUPT, "elp:%ld first:%ld now:%ld c=%d\n", + elapsed_seconds, chip->first_aicl_seconds, + now_seconds, chip->aicl_irq_count); + pr_smb(PR_INTERRUPT, "Disable AICL rerun\n"); + chip->very_weak_charger = true; + bad_charger = true; + + /* + * Disable AICL rerun since many interrupts were + * triggered in a short time + */ + /* disable hw aicl */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, true, 0); + if (rc < 0) { + pr_err("Couldn't disable hw aicl rerun rc=%d\n", + rc); + return; + } + + /* Vote 100mA current limit */ + rc = vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, + true, CURRENT_100_MA); + if (rc < 0) { + pr_err("Can't vote %d current limit rc=%d\n", + CURRENT_100_MA, rc); + } + + chip->aicl_irq_count = 0; + } else if ((get_prop_charge_type(chip) == + POWER_SUPPLY_CHARGE_TYPE_FAST) && + (reg & AICL_SUSP_BIT)) { + /* + * If the AICL_SUSP_BIT is on, then AICL reruns have + * already been disabled. Set the very weak charger + * flag so that the driver reports a bad charger + * and does not reenable AICL reruns. + */ + chip->very_weak_charger = true; + bad_charger = true; + } + if (bad_charger) { + pr_smb(PR_MISC, + "setting usb psy health UNSPEC_FAILURE\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + power_supply_changed(chip->usb_psy); + schedule_work(&chip->usb_set_online_work); + } + } +} + +static int wait_for_usbin_uv(struct smbchg_chip *chip, bool high) +{ + int rc; + int tries = 3; + struct completion *completion = &chip->usbin_uv_lowered; + bool usbin_uv; + + if (high) + completion = &chip->usbin_uv_raised; + + while (tries--) { + rc = wait_for_completion_interruptible_timeout( + completion, + msecs_to_jiffies(APSD_TIMEOUT_MS)); + if (rc >= 0) + break; + } + + usbin_uv = is_usbin_uv_high(chip); + + if (high == usbin_uv) + return 0; + + pr_err("usbin uv didnt go to a %s state, still at %s, tries = %d, rc = %d\n", + high ? "risen" : "lowered", + usbin_uv ? "high" : "low", + tries, rc); + return -EINVAL; +} + +static int wait_for_src_detect(struct smbchg_chip *chip, bool high) +{ + int rc; + int tries = 3; + struct completion *completion = &chip->src_det_lowered; + bool src_detect; + + if (high) + completion = &chip->src_det_raised; + + while (tries--) { + rc = wait_for_completion_interruptible_timeout( + completion, + msecs_to_jiffies(APSD_TIMEOUT_MS)); + if (rc >= 0) + break; + } + + src_detect = is_src_detect_high(chip); + + if (high == src_detect) + return 0; + + pr_err("src detect didnt go to a %s state, still at %s, tries = %d, rc = %d\n", + high ? "risen" : "lowered", + src_detect ? "high" : "low", + tries, rc); + return -EINVAL; +} + +static int fake_insertion_removal(struct smbchg_chip *chip, bool insertion) +{ + int rc; + bool src_detect; + bool usbin_uv; + + if (insertion) { + reinit_completion(&chip->src_det_raised); + reinit_completion(&chip->usbin_uv_lowered); + } else { + reinit_completion(&chip->src_det_lowered); + reinit_completion(&chip->usbin_uv_raised); + } + + /* ensure that usbin uv real time status is in the right state */ + usbin_uv = is_usbin_uv_high(chip); + if (usbin_uv != insertion) { + pr_err("Skip faking, usbin uv is already %d\n", usbin_uv); + return -EINVAL; + } + + /* ensure that src_detect real time status is in the right state */ + src_detect = is_src_detect_high(chip); + if (src_detect == insertion) { + pr_err("Skip faking, src detect is already %d\n", src_detect); + return -EINVAL; + } + + pr_smb(PR_MISC, "Allow only %s charger\n", + insertion ? "5-9V" : "9V only"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + ADAPTER_ALLOWANCE_MASK, + insertion ? + USBIN_ADAPTER_5V_9V_CONT : USBIN_ADAPTER_9V); + if (rc < 0) { + pr_err("Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on %s usbin uv\n", + insertion ? "falling" : "rising"); + rc = wait_for_usbin_uv(chip, !insertion); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on %s src det\n", + insertion ? "rising" : "falling"); + rc = wait_for_src_detect(chip, insertion); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int smbchg_prepare_for_pulsing(struct smbchg_chip *chip) +{ + int rc = 0; + u8 reg; + + /* switch to 5V HVDCP */ + pr_smb(PR_MISC, "Switch to 5V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc); + goto out; + } + + /* wait for HVDCP to lower to 5V */ + msleep(500); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and that we are still in 5V hvdcp + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 500mS sleep\n"); + goto out; + } + + /* disable HVDCP */ + pr_smb(PR_MISC, "Disable HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable HVDCP rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300); + if (rc < 0) { + pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "Disable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + + chip->hvdcp_3_det_ignore_uv = true; + /* fake a removal */ + pr_smb(PR_MISC, "Faking Removal\n"); + rc = fake_insertion_removal(chip, false); + if (rc < 0) { + pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc); + goto handle_removal; + } + + /* disable APSD */ + pr_smb(PR_MISC, "Disabling APSD\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable APSD rc=%d\n", rc); + goto out; + } + + /* fake an insertion */ + pr_smb(PR_MISC, "Faking Insertion\n"); + rc = fake_insertion_removal(chip, true); + if (rc < 0) { + pr_err("Couldn't fake insertion rc=%d\n", rc); + goto handle_removal; + } + chip->hvdcp_3_det_ignore_uv = false; + + pr_smb(PR_MISC, "Enable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + + set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DMF); + /* + * DCP will switch to HVDCP in this time by removing the short + * between DP DM + */ + msleep(HVDCP_NOTIFY_MS); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and the usb type should be none since APSD was disabled + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 2s sleep\n"); + rc = -EINVAL; + goto out; + } + + smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if ((reg >> TYPE_BITS_OFFSET) != 0) { + pr_smb(PR_MISC, "type bits set after 2s sleep - abort\n"); + rc = -EINVAL; + goto out; + } + + set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DM3P3); + /* Wait 60mS after entering continuous mode */ + msleep(60); + + return 0; +out: + chip->hvdcp_3_det_ignore_uv = false; + restore_from_hvdcp_detection(chip); + return rc; +handle_removal: + chip->hvdcp_3_det_ignore_uv = false; + update_usb_status(chip, 0, 0); + return rc; +} + +static int smbchg_unprepare_for_pulsing(struct smbchg_chip *chip) +{ + int rc = 0; + + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + + /* switch to 9V HVDCP */ + pr_smb(PR_MISC, "Switch to 9V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc); + return rc; + } + + /* enable HVDCP */ + pr_smb(PR_MISC, "Enable HVDCP\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) { + pr_err("Couldn't enable HVDCP rc=%d\n", rc); + return rc; + } + + /* enable APSD */ + pr_smb(PR_MISC, "Enabling APSD\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT); + if (rc < 0) { + pr_err("Couldn't enable APSD rc=%d\n", rc); + return rc; + } + + /* Disable AICL */ + pr_smb(PR_MISC, "Disable AICL\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable AICL rc=%d\n", rc); + return rc; + } + + /* fake a removal */ + chip->hvdcp_3_det_ignore_uv = true; + pr_smb(PR_MISC, "Faking Removal\n"); + rc = fake_insertion_removal(chip, false); + if (rc < 0) { + pr_err("Couldn't fake removal rc=%d\n", rc); + goto out; + } + + /* + * reset the enabled once flag for parallel charging so + * parallel charging can immediately restart after the HVDCP pulsing + * is complete + */ + chip->parallel.enabled_once = false; + + /* fake an insertion */ + pr_smb(PR_MISC, "Faking Insertion\n"); + rc = fake_insertion_removal(chip, true); + if (rc < 0) { + pr_err("Couldn't fake insertion rc=%d\n", rc); + goto out; + } + chip->hvdcp_3_det_ignore_uv = false; + + /* Enable AICL */ + pr_smb(PR_MISC, "Enable AICL\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't enable AICL rc=%d\n", rc); + return rc; + } + +out: + /* + * There are many QC 2.0 chargers that collapse before the aicl deglitch + * timer can mitigate. Hence set the aicl deglitch time to a shorter + * period. + */ + + rc = vote(chip->aicl_deglitch_short_votable, + HVDCP_SHORT_DEGLITCH_VOTER, true, 0); + if (rc < 0) + pr_err("Couldn't reduce aicl deglitch rc=%d\n", rc); + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + chip->hvdcp_3_det_ignore_uv = false; + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "HVDCP removed\n"); + update_usb_status(chip, 0, 0); + } + return rc; +} + +#define USB_CMD_APSD 0x41 +#define APSD_RERUN BIT(0) +static int rerun_apsd(struct smbchg_chip *chip) +{ + int rc; + + reinit_completion(&chip->src_det_raised); + reinit_completion(&chip->usbin_uv_lowered); + reinit_completion(&chip->src_det_lowered); + reinit_completion(&chip->usbin_uv_raised); + + /* re-run APSD */ + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + USB_CMD_APSD, + APSD_RERUN, APSD_RERUN); + if (rc) { + pr_err("Couldn't re-run APSD rc=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on rising usbin uv\n"); + rc = wait_for_usbin_uv(chip, true); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on falling src det\n"); + rc = wait_for_src_detect(chip, false); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on falling usbin uv\n"); + rc = wait_for_usbin_uv(chip, false); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on rising src det\n"); + rc = wait_for_src_detect(chip, true); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + return rc; +} + +#define SCHG_LITE_USBIN_HVDCP_5_9V 0x8 +#define SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK 0x38 +#define SCHG_LITE_USBIN_HVDCP_SEL_IDLE BIT(3) +static bool is_hvdcp_5v_cont_mode(struct smbchg_chip *chip) +{ + int rc; + u8 reg = 0; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc) { + pr_err("Unable to read HVDCP status rc=%d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "HVDCP status = %x\n", reg); + + if (reg & SCHG_LITE_USBIN_HVDCP_SEL_IDLE) { + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + INPUT_STS, 1); + if (rc) { + pr_err("Unable to read INPUT status rc=%d\n", rc); + return false; + } + pr_smb(PR_STATUS, "INPUT status = %x\n", reg); + if ((reg & SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK) == + SCHG_LITE_USBIN_HVDCP_5_9V) + return true; + } + return false; +} + +static int smbchg_prepare_for_pulsing_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + /* check if HVDCP is already in 5V continuous mode */ + if (is_hvdcp_5v_cont_mode(chip)) { + pr_smb(PR_MISC, "HVDCP by default is in 5V continuous mode\n"); + return 0; + } + + /* switch to 5V HVDCP */ + pr_smb(PR_MISC, "Switch to 5V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc); + goto out; + } + + /* wait for HVDCP to lower to 5V */ + msleep(500); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and that we are still in 5V hvdcp + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 500mS sleep\n"); + goto out; + } + + pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300); + if (rc < 0) { + pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "Disable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + + chip->hvdcp_3_det_ignore_uv = true; + + /* re-run APSD */ + rc = rerun_apsd(chip); + if (rc) { + pr_err("APSD rerun failed\n"); + goto out; + } + + chip->hvdcp_3_det_ignore_uv = false; + + pr_smb(PR_MISC, "Enable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + /* + * DCP will switch to HVDCP in this time by removing the short + * between DP DM + */ + msleep(HVDCP_NOTIFY_MS); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and the usb type should be none since APSD was disabled + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 2s sleep\n"); + rc = -EINVAL; + goto out; + } + + /* We are set if HVDCP in 5V continuous mode */ + if (!is_hvdcp_5v_cont_mode(chip)) { + pr_err("HVDCP could not be set in 5V continuous mode\n"); + goto out; + } + + return 0; +out: + chip->hvdcp_3_det_ignore_uv = false; + restore_from_hvdcp_detection(chip); + return rc; +} + +static int smbchg_unprepare_for_pulsing_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Forcing 9V HVDCP 2.0\n"); + rc = force_9v_hvdcp(chip); + if (rc) { + pr_err("Failed to force 9V HVDCP=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + return rc; +} + +#define CMD_HVDCP_2 0x43 +#define SINGLE_INCREMENT BIT(0) +#define SINGLE_DECREMENT BIT(1) +static int smbchg_dp_pulse_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Increment DP\n"); + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2, + SINGLE_INCREMENT, SINGLE_INCREMENT); + if (rc) + pr_err("Single-increment failed rc=%d\n", rc); + + return rc; +} + +static int smbchg_dm_pulse_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Decrement DM\n"); + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2, + SINGLE_DECREMENT, SINGLE_DECREMENT); + if (rc) + pr_err("Single-decrement failed rc=%d\n", rc); + + return rc; +} + +static int smbchg_hvdcp3_confirmed(struct smbchg_chip *chip) +{ + int rc = 0; + + /* + * reset the enabled once flag for parallel charging because this is + * effectively a new insertion. + */ + chip->parallel.enabled_once = false; + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_USB_HVDCP_3); + + return rc; +} + +static int smbchg_dp_dm(struct smbchg_chip *chip, int val) +{ + int rc = 0; + int target_icl_vote_ma; + + switch (val) { + case POWER_SUPPLY_DP_DM_PREPARE: + if (!is_hvdcp_present(chip)) { + pr_err("No pulsing unless HVDCP\n"); + return -ENODEV; + } + if (chip->schg_version == QPNP_SCHG_LITE) + rc = smbchg_prepare_for_pulsing_lite(chip); + else + rc = smbchg_prepare_for_pulsing(chip); + break; + case POWER_SUPPLY_DP_DM_UNPREPARE: + if (chip->schg_version == QPNP_SCHG_LITE) + rc = smbchg_unprepare_for_pulsing_lite(chip); + else + rc = smbchg_unprepare_for_pulsing(chip); + break; + case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3: + rc = smbchg_hvdcp3_confirmed(chip); + break; + case POWER_SUPPLY_DP_DM_DP_PULSE: + if (chip->schg_version == QPNP_SCHG) + rc = set_usb_psy_dp_dm(chip, + POWER_SUPPLY_DP_DM_DP_PULSE); + else + rc = smbchg_dp_pulse_lite(chip); + if (!rc) + chip->pulse_cnt++; + pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_DM_PULSE: + if (chip->schg_version == QPNP_SCHG) + rc = set_usb_psy_dp_dm(chip, + POWER_SUPPLY_DP_DM_DM_PULSE); + else + rc = smbchg_dm_pulse_lite(chip); + if (!rc && chip->pulse_cnt) + chip->pulse_cnt--; + pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_HVDCP3_SUPPORTED: + chip->hvdcp3_supported = true; + pr_smb(PR_MISC, "HVDCP3 supported\n"); + break; + case POWER_SUPPLY_DP_DM_ICL_DOWN: + chip->usb_icl_delta -= 100; + target_icl_vote_ma = get_client_vote(chip->usb_icl_votable, + PSY_ICL_VOTER); + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true, + target_icl_vote_ma + chip->usb_icl_delta); + break; + case POWER_SUPPLY_DP_DM_ICL_UP: + chip->usb_icl_delta += 100; + target_icl_vote_ma = get_client_vote(chip->usb_icl_votable, + PSY_ICL_VOTER); + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true, + target_icl_vote_ma + chip->usb_icl_delta); + break; + default: + break; + } + + return rc; +} + +static void update_typec_capability_status(struct smbchg_chip *chip, + const union power_supply_propval *val) +{ + pr_smb(PR_TYPEC, "typec capability = %dma\n", val->intval); + + pr_debug("changing ICL from %dma to %dma\n", chip->typec_current_ma, + val->intval); + chip->typec_current_ma = val->intval; + smbchg_change_usb_supply_type(chip, chip->usb_supply_type); +} + +static void update_typec_otg_status(struct smbchg_chip *chip, int mode, + bool force) +{ + union power_supply_propval pval = {0, }; + pr_smb(PR_TYPEC, "typec mode = %d\n", mode); + + if (mode == POWER_SUPPLY_TYPE_DFP) { + chip->typec_dfp = true; + pval.intval = 1; + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, + chip->typec_dfp); + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + } else if (force || chip->typec_dfp) { + chip->typec_dfp = false; + pval.intval = 0; + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, + chip->typec_dfp); + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + } +} + +static int smbchg_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->usb_current_max; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->usb_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->usb_online; + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = chip->usb_supply_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->usb_health; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smbchg_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + chip->usb_current_max = val->intval; + break; + case POWER_SUPPLY_PROP_ONLINE: + chip->usb_online = val->intval; + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} + +static int +smbchg_usb_is_writeable(struct power_supply *psy, enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + + +static char *smbchg_usb_supplicants[] = { + "battery", + "bms", +}; + +static enum power_supply_property smbchg_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_HEALTH, +}; + +#define CHARGE_OUTPUT_VTG_RATIO 840 +static int smbchg_get_iusb(struct smbchg_chip *chip) +{ + int rc, iusb_ua = -EINVAL; + struct qpnp_vadc_result adc_result; + + if (!is_usb_present(chip) && !is_dc_present(chip)) + return 0; + + if (chip->vchg_vadc_dev && chip->vchg_adc_channel != -EINVAL) { + rc = qpnp_vadc_read(chip->vchg_vadc_dev, + chip->vchg_adc_channel, &adc_result); + if (rc) { + pr_smb(PR_STATUS, + "error in VCHG (channel-%d) read rc = %d\n", + chip->vchg_adc_channel, rc); + return 0; + } + iusb_ua = div_s64(adc_result.physical * 1000, + CHARGE_OUTPUT_VTG_RATIO); + } + + return iusb_ua; +} + +static enum power_supply_property smbchg_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, + POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE, + POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_CURRENT_NOW, + POWER_SUPPLY_PROP_FLASH_ACTIVE, + POWER_SUPPLY_PROP_FLASH_TRIGGER, + POWER_SUPPLY_PROP_DP_DM, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_RERUN_AICL, + POWER_SUPPLY_PROP_RESTRICTED_CHARGING, +}; + +static int smbchg_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + vote(chip->battchg_suspend_votable, BATTCHG_USER_EN_VOTER, + !val->intval, 0); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + rc = vote(chip->usb_suspend_votable, USER_EN_VOTER, + !val->intval, 0); + rc = vote(chip->dc_suspend_votable, USER_EN_VOTER, + !val->intval, 0); + chip->chg_enabled = val->intval; + schedule_work(&chip->usb_set_online_work); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smbchg_system_temp_level_set(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smbchg_set_fastchg_current_user(chip, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smbchg_float_voltage_set(chip, val->intval); + break; + case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: + rc = smbchg_safety_timer_enable(chip, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + rc = smbchg_switch_buck_frequency(chip, val->intval); + if (rc) { + pr_err("Couldn't switch buck frequency, rc=%d\n", rc); + /* + * Trigger a panic if there is an error while switching + * buck frequency. This will prevent LS FET damage. + */ + BUG_ON(1); + } + + rc = smbchg_otg_pulse_skip_disable(chip, + REASON_FLASH_ENABLED, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_TRIGGER: + chip->flash_triggered = !!val->intval; + smbchg_icl_loop_disable_check(chip); + break; + case POWER_SUPPLY_PROP_FORCE_TLIM: + rc = smbchg_force_tlim_en(chip, val->intval); + break; + case POWER_SUPPLY_PROP_DP_DM: + rc = smbchg_dp_dm(chip, val->intval); + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + smbchg_rerun_aicl(chip); + break; + case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: + rc = smbchg_restricted_charging(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_CAPABILITY: + if (chip->typec_psy) + update_typec_capability_status(chip, val); + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + if (chip->typec_psy) + update_typec_otg_status(chip, val->intval, false); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int smbchg_battery_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: + case POWER_SUPPLY_PROP_DP_DM: + case POWER_SUPPLY_PROP_RERUN_AICL: + case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int smbchg_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + val->intval = + !get_effective_result(chip->battchg_suspend_votable); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = chip->chg_enabled; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = smbchg_float_voltage_get(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_FLASH_CURRENT_MAX: + val->intval = smbchg_calc_max_flash_current(chip); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = chip->fastchg_current_ma * 1000; + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + val->intval = chip->therm_lvl_sel; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX: + val->intval = smbchg_get_aicl_level_ma(chip) * 1000; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED: + val->intval = (int)chip->aicl_complete; + break; + case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: + val->intval = (int)chip->restricted_charging; + break; + /* properties from fg */ + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_prop_batt_current_now(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_prop_batt_voltage_now(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = get_prop_batt_voltage_max_design(chip); + break; + case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: + val->intval = chip->safety_timer_en; + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + val->intval = chip->otg_pulse_skip_dis; + break; + case POWER_SUPPLY_PROP_FLASH_TRIGGER: + val->intval = chip->flash_triggered; + break; + case POWER_SUPPLY_PROP_DP_DM: + val->intval = chip->pulse_cnt; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + val->intval = smbchg_is_input_current_limited(chip); + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + val->intval = 0; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW: + val->intval = smbchg_get_iusb(chip); + break; + default: + return -EINVAL; + } + return 0; +} + +static char *smbchg_dc_supplicants[] = { + "bms", +}; + +static enum power_supply_property smbchg_dc_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CURRENT_MAX, +}; + +static int smbchg_dc_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + rc = vote(chip->dc_suspend_votable, POWER_SUPPLY_EN_VOTER, + !val->intval, 0); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = vote(chip->dc_icl_votable, USER_ICL_VOTER, true, + val->intval / 1000); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int smbchg_dc_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = is_dc_present(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !get_effective_result(chip->dc_suspend_votable); + break; + case POWER_SUPPLY_PROP_ONLINE: + /* return if dc is charging the battery */ + val->intval = (smbchg_get_pwr_path(chip) == PWR_PATH_DC) + && (get_prop_batt_status(chip) + == POWER_SUPPLY_STATUS_CHARGING); + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->dc_max_current_ma * 1000; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smbchg_dc_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CURRENT_MAX: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +#define HOT_BAT_HARD_BIT BIT(0) +#define HOT_BAT_SOFT_BIT BIT(1) +#define COLD_BAT_HARD_BIT BIT(2) +#define COLD_BAT_SOFT_BIT BIT(3) +#define BAT_OV_BIT BIT(4) +#define BAT_LOW_BIT BIT(5) +#define BAT_MISSING_BIT BIT(6) +#define BAT_TERM_MISSING_BIT BIT(7) +static irqreturn_t batt_hot_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_cold_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_warm_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_warm = !!(reg & HOT_BAT_SOFT_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_cool_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_cool = !!(reg & COLD_BAT_SOFT_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_pres_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_present = !(reg & BAT_MISSING_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t vbat_low_handler(int irq, void *_chip) +{ + pr_warn_ratelimited("vbat low\n"); + return IRQ_HANDLED; +} + +#define CHG_COMP_SFT_BIT BIT(3) +static irqreturn_t chg_error_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int rc = 0; + u8 reg; + + pr_smb(PR_INTERRUPT, "chg-error triggered\n"); + + rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc); + } else { + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + if (reg & CHG_COMP_SFT_BIT) + set_property_on_fg(chip, + POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED, + 1); + } + + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t fastchg_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + + pr_smb(PR_INTERRUPT, "p2f triggered\n"); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t chg_hot_handler(int irq, void *_chip) +{ + pr_warn_ratelimited("chg hot\n"); + smbchg_wipower_check(_chip); + return IRQ_HANDLED; +} + +static irqreturn_t chg_term_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + + pr_smb(PR_INTERRUPT, "tcc triggered\n"); + /* + * Charge termination is a pulse and not level triggered. That means, + * TCC bit in RT_STS can get cleared by the time this interrupt is + * handled. Instead of relying on that to determine whether the + * charge termination had happened, we've to simply notify the FG + * about this as long as the interrupt is handled. + */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_CHARGE_DONE, 1); + + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + + return IRQ_HANDLED; +} + +static irqreturn_t taper_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + taper_irq_en(chip, false); + smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_taper(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t recharge_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + return IRQ_HANDLED; +} + +static irqreturn_t wdog_timeout_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->misc_base + RT_STS, 1); + pr_warn_ratelimited("wdog timeout rt_stat = 0x%02x\n", reg); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + return IRQ_HANDLED; +} + +/** + * power_ok_handler() - called when the switcher turns on or turns off + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating switcher turning on or off + */ +static irqreturn_t power_ok_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->misc_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + return IRQ_HANDLED; +} + +/** + * dcin_uv_handler() - called when the dc voltage crosses the uv threshold + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating whether dc voltage is uv + */ +#define DCIN_UNSUSPEND_DELAY_MS 1000 +static irqreturn_t dcin_uv_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool dc_present = is_dc_present(chip); + + pr_smb(PR_STATUS, "chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + if (chip->dc_present != dc_present) { + /* dc changed */ + chip->dc_present = dc_present; + if (chip->dc_psy_type != -EINVAL && chip->batt_psy) + power_supply_changed(chip->dc_psy); + smbchg_charging_status_change(chip); + smbchg_aicl_deglitch_wa_check(chip); + chip->vbat_above_headroom = false; + } + + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +/** + * usbin_ov_handler() - this is called when an overvoltage condition occurs + * @chip: pointer to smbchg_chip chip + */ +static irqreturn_t usbin_ov_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int rc; + u8 reg; + bool usb_present; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + goto out; + } + + /* OV condition is detected. Notify it to USB psy */ + if (reg & USBIN_OV_BIT) { + chip->usb_ov_det = true; + pr_smb(PR_MISC, "setting usb psy health OV\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + power_supply_changed(chip->usb_psy); + } else { + chip->usb_ov_det = false; + /* If USB is present, then handle the USB insertion */ + usb_present = is_usb_present(chip); + if (usb_present) + update_usb_status(chip, usb_present, false); + } +out: + return IRQ_HANDLED; +} + +/** + * usbin_uv_handler() - this is called when USB charger is removed + * @chip: pointer to smbchg_chip chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +#define ICL_MODE_MASK SMB_MASK(5, 4) +#define ICL_MODE_HIGH_CURRENT 0 +static irqreturn_t usbin_uv_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int aicl_level = smbchg_get_aicl_level_ma(chip); + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc) { + pr_err("could not read rt sts: %d", rc); + goto out; + } + + pr_smb(PR_STATUS, + "%s chip->usb_present = %d rt_sts = 0x%02x hvdcp_3_det_ignore_uv = %d aicl = %d\n", + chip->hvdcp_3_det_ignore_uv ? "Ignoring":"", + chip->usb_present, reg, chip->hvdcp_3_det_ignore_uv, + aicl_level); + + /* + * set usb_psy's dp=f dm=f if this is a new insertion, i.e. it is + * not already src_detected and usbin_uv is seen falling + */ + if (!(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_MISC, "setting usb dp=f dm=f\n"); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + } + + if (reg & USBIN_UV_BIT) + complete_all(&chip->usbin_uv_raised); + else + complete_all(&chip->usbin_uv_lowered); + + if (chip->hvdcp_3_det_ignore_uv) + goto out; + + if ((reg & USBIN_UV_BIT) && (reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_STATUS, "Very weak charger detected\n"); + chip->very_weak_charger = true; + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_2_REG, 1); + if (rc) { + dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", + rc); + goto out; + } + if ((reg & ICL_MODE_MASK) != ICL_MODE_HIGH_CURRENT) { + /* + * If AICL is not even enabled, this is either an + * SDP or a grossly out of spec charger. Do not + * draw any current from it. + */ + rc = vote(chip->usb_suspend_votable, + WEAK_CHARGER_EN_VOTER, true, 0); + if (rc < 0) + pr_err("could not disable charger: %d", rc); + } else if (aicl_level == chip->tables.usb_ilim_ma_table[0]) { + /* + * we are in a situation where the adapter is not able + * to supply even 300mA. Disable hw aicl reruns else it + * is only a matter of time when we get back here again + */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, true, 0); + if (rc < 0) + pr_err("Couldn't disable hw aicl rerun rc=%d\n", + rc); + } + pr_smb(PR_MISC, "setting usb psy health UNSPEC_FAILURE\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + power_supply_changed(chip->usb_psy); + schedule_work(&chip->usb_set_online_work); + } + + smbchg_wipower_check(chip); +out: + return IRQ_HANDLED; +} + +/** + * src_detect_handler() - this is called on rising edge when USB charger type + * is detected and on falling edge when USB voltage falls + * below the coarse detect voltage(1V), use it for + * handling USB charger insertion and removal. + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static irqreturn_t src_detect_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool usb_present = is_usb_present(chip); + bool src_detect = is_src_detect_high(chip); + int rc; + + pr_smb(PR_STATUS, + "%s chip->usb_present = %d usb_present = %d src_detect = %d hvdcp_3_det_ignore_uv=%d\n", + chip->hvdcp_3_det_ignore_uv ? "Ignoring":"", + chip->usb_present, usb_present, src_detect, + chip->hvdcp_3_det_ignore_uv); + + if (src_detect) + complete_all(&chip->src_det_raised); + else + complete_all(&chip->src_det_lowered); + + if (chip->hvdcp_3_det_ignore_uv) + goto out; + + /* + * When VBAT is above the AICL threshold (4.25V) - 180mV (4.07V), + * an input collapse due to AICL will actually cause an USBIN_UV + * interrupt to fire as well. + * + * Handle USB insertions and removals in the source detect handler + * instead of the USBIN_UV handler since the latter is untrustworthy + * when the battery voltage is high. + */ + chip->very_weak_charger = false; + /* + * a src detect marks a new insertion or a real removal, + * vote for enable aicl hw reruns + */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't enable hw aicl rerun rc=%d\n", rc); + + rc = vote(chip->usb_suspend_votable, WEAK_CHARGER_EN_VOTER, false, 0); + if (rc < 0) + pr_err("could not enable charger: %d\n", rc); + + if (src_detect) { + update_usb_status(chip, usb_present, 0); + } else { + update_usb_status(chip, 0, false); + chip->aicl_irq_count = 0; + } +out: + return IRQ_HANDLED; +} + +/** + * otg_oc_handler() - called when the usb otg goes over current + */ +#define NUM_OTG_RETRIES 5 +#define OTG_OC_RETRY_DELAY_US 50000 +static irqreturn_t otg_oc_handler(int irq, void *_chip) +{ + int rc; + struct smbchg_chip *chip = _chip; + s64 elapsed_us = ktime_us_delta(ktime_get(), chip->otg_enable_time); + + pr_smb(PR_INTERRUPT, "triggered\n"); + + if (chip->schg_version == QPNP_SCHG_LITE) { + pr_warn("OTG OC triggered - OTG disabled\n"); + return IRQ_HANDLED; + } + + if (elapsed_us > OTG_OC_RETRY_DELAY_US) + chip->otg_retries = 0; + + /* + * Due to a HW bug in the PMI8994 charger, the current inrush that + * occurs when connecting certain OTG devices can cause the OTG + * overcurrent protection to trip. + * + * The work around is to try reenabling the OTG when getting an + * overcurrent interrupt once. + */ + if (chip->otg_retries < NUM_OTG_RETRIES) { + chip->otg_retries += 1; + pr_smb(PR_STATUS, + "Retrying OTG enable. Try #%d, elapsed_us %lld\n", + chip->otg_retries, elapsed_us); + rc = otg_oc_reset(chip); + if (rc) + pr_err("Failed to reset OTG OC state rc=%d\n", rc); + chip->otg_enable_time = ktime_get(); + } + return IRQ_HANDLED; +} + +/** + * otg_fail_handler() - called when the usb otg fails + * (when vbat < OTG UVLO threshold) + */ +static irqreturn_t otg_fail_handler(int irq, void *_chip) +{ + pr_smb(PR_INTERRUPT, "triggered\n"); + return IRQ_HANDLED; +} + +/** + * aicl_done_handler() - called when the usb AICL algorithm is finished + * and a current is set. + */ +static irqreturn_t aicl_done_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool usb_present = is_usb_present(chip); + int aicl_level = smbchg_get_aicl_level_ma(chip); + + pr_smb(PR_INTERRUPT, "triggered, aicl: %d\n", aicl_level); + + increment_aicl_count(chip); + + if (usb_present) + smbchg_parallel_usb_check_ok(chip); + + if (chip->aicl_complete && chip->batt_psy) + power_supply_changed(chip->batt_psy); + + return IRQ_HANDLED; +} + +/** + * usbid_change_handler() - called when the usb RID changes. + * This is used mostly for detecting OTG + */ +static irqreturn_t usbid_change_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool otg_present; + + pr_smb(PR_INTERRUPT, "triggered\n"); + + otg_present = is_otg_present(chip); + pr_smb(PR_MISC, "setting usb psy OTG = %d\n", + otg_present ? 1 : 0); + + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, otg_present); + + if (otg_present) + pr_smb(PR_STATUS, "OTG detected\n"); + + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + + return IRQ_HANDLED; +} + +static int determine_initial_status(struct smbchg_chip *chip) +{ + union power_supply_propval type = {0, }; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + batt_pres_handler(0, chip); + batt_hot_handler(0, chip); + batt_warm_handler(0, chip); + batt_cool_handler(0, chip); + batt_cold_handler(0, chip); + if (chip->typec_psy) { + get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type); + update_typec_otg_status(chip, type.intval, true); + } else { + usbid_change_handler(0, chip); + } + src_detect_handler(0, chip); + + chip->usb_present = is_usb_present(chip); + chip->dc_present = is_dc_present(chip); + + if (chip->usb_present) { + int rc = 0; + pr_smb(PR_MISC, "setting usb dp=f dm=f\n"); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + handle_usb_insertion(chip); + } else { + handle_usb_removal(chip); + } + + return 0; +} + +static int prechg_time[] = { + 24, + 48, + 96, + 192, +}; +static int chg_time[] = { + 192, + 384, + 768, + 1536, +}; + +enum bpd_type { + BPD_TYPE_BAT_NONE, + BPD_TYPE_BAT_ID, + BPD_TYPE_BAT_THM, + BPD_TYPE_BAT_THM_BAT_ID, + BPD_TYPE_DEFAULT, +}; + +static const char * const bpd_label[] = { + [BPD_TYPE_BAT_NONE] = "bpd_none", + [BPD_TYPE_BAT_ID] = "bpd_id", + [BPD_TYPE_BAT_THM] = "bpd_thm", + [BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id", +}; + +static inline int get_bpd(const char *name) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(bpd_label); i++) { + if (strcmp(bpd_label[i], name) == 0) + return i; + } + return -EINVAL; +} + +#define REVISION1_REG 0x0 +#define DIG_MINOR 0 +#define DIG_MAJOR 1 +#define ANA_MINOR 2 +#define ANA_MAJOR 3 +#define CHGR_CFG1 0xFB +#define RECHG_THRESHOLD_SRC_BIT BIT(1) +#define TERM_I_SRC_BIT BIT(2) +#define TERM_SRC_FG BIT(2) +#define CHG_INHIB_CFG_REG 0xF7 +#define CHG_INHIBIT_50MV_VAL 0x00 +#define CHG_INHIBIT_100MV_VAL 0x01 +#define CHG_INHIBIT_200MV_VAL 0x02 +#define CHG_INHIBIT_300MV_VAL 0x03 +#define CHG_INHIBIT_MASK 0x03 +#define USE_REGISTER_FOR_CURRENT BIT(2) +#define CHGR_CFG2 0xFC +#define CHG_EN_SRC_BIT BIT(7) +#define CHG_EN_POLARITY_BIT BIT(6) +#define P2F_CHG_TRAN BIT(5) +#define CHG_BAT_OV_ECC BIT(4) +#define I_TERM_BIT BIT(3) +#define AUTO_RECHG_BIT BIT(2) +#define CHARGER_INHIBIT_BIT BIT(0) +#define USB51_COMMAND_POL BIT(2) +#define USB51AC_CTRL BIT(1) +#define TR_8OR32B 0xFE +#define BUCK_8_16_FREQ_BIT BIT(0) +#define BM_CFG 0xF3 +#define BATT_MISSING_ALGO_BIT BIT(2) +#define BMD_PIN_SRC_MASK SMB_MASK(1, 0) +#define PIN_SRC_SHIFT 0 +#define CHGR_CFG 0xFF +#define RCHG_LVL_BIT BIT(0) +#define VCHG_EN_BIT BIT(1) +#define VCHG_INPUT_CURRENT_BIT BIT(3) +#define CFG_AFVC 0xF6 +#define VFLOAT_COMP_ENABLE_MASK SMB_MASK(2, 0) +#define TR_RID_REG 0xFA +#define FG_INPUT_FET_DELAY_BIT BIT(3) +#define TRIM_OPTIONS_7_0 0xF6 +#define INPUT_MISSING_POLLER_EN_BIT BIT(3) +#define CHGR_CCMP_CFG 0xFA +#define JEITA_TEMP_HARD_LIMIT_BIT BIT(5) +#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4) +#define HVDCP_ADAPTER_SEL_9V_BIT BIT(4) +#define HVDCP_AUTH_ALG_EN_BIT BIT(6) +#define CMD_APSD 0x41 +#define APSD_RERUN_BIT BIT(0) +#define OTG_CFG 0xF1 +#define HICCUP_ENABLED_BIT BIT(6) +#define OTG_PIN_POLARITY_BIT BIT(4) +#define OTG_PIN_ACTIVE_LOW BIT(4) +#define OTG_EN_CTRL_MASK SMB_MASK(3, 2) +#define OTG_PIN_CTRL_RID_DIS 0x04 +#define OTG_CMD_CTRL_RID_EN 0x08 +#define AICL_ADC_BIT BIT(6) +static void batt_ov_wa_check(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + /* disable-'battery OV disables charging' feature */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2, + CHG_BAT_OV_ECC, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return; + } + + /* + * if battery OV is set: + * restart charging by disable/enable charging + */ + rc = smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read Battery RT status rc = %d\n", rc); + return; + } + + if (reg & BAT_OV_BIT) { + rc = smbchg_charging_en(chip, false); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't disable charging: rc = %d\n", rc); + return; + } + + /* delay for charging-disable to take affect */ + msleep(200); + + rc = smbchg_charging_en(chip, true); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't enable charging: rc = %d\n", rc); + return; + } + } +} + +static int smbchg_hw_init(struct smbchg_chip *chip) +{ + int rc, i; + u8 reg, mask; + + rc = smbchg_read(chip, chip->revision, + chip->misc_base + REVISION1_REG, 4); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read revision rc=%d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "Charger Revision DIG: %d.%d; ANA: %d.%d\n", + chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR], + chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]); + + /* Setup 9V HVDCP */ + if (!chip->hvdcp_not_supported) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) { + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", + rc); + return rc; + } + } + + if (chip->aicl_rerun_period_s > 0) { + rc = smbchg_set_aicl_rerun_period_s(chip, + chip->aicl_rerun_period_s); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set AICL rerun timer rc=%d\n", + rc); + return rc; + } + } + + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG, + FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable fg input fet delay rc=%d\n", + rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0, + INPUT_MISSING_POLLER_EN_BIT, + INPUT_MISSING_POLLER_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable input missing poller rc=%d\n", + rc); + return rc; + } + + /* + * Do not force using current from the register i.e. use auto + * power source detect (APSD) mA ratings for the initial current values. + * + * If this is set, AICL will not rerun at 9V for HVDCPs + */ + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USE_REGISTER_FOR_CURRENT, 0); + + if (rc < 0) { + dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc); + return rc; + } + + /* + * set chg en by cmd register, set chg en by writing bit 1, + * enable auto pre to fast, enable auto recharge by default. + * enable current termination and charge inhibition based on + * the device tree configuration. + */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2, + CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT | P2F_CHG_TRAN + | I_TERM_BIT | AUTO_RECHG_BIT | CHARGER_INHIBIT_BIT, + CHG_EN_POLARITY_BIT + | (chip->chg_inhibit_en ? CHARGER_INHIBIT_BIT : 0) + | (chip->iterm_disabled ? I_TERM_BIT : 0)); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return rc; + } + + /* + * enable battery charging to make sure it hasn't been changed earlier + * by the bootloader. + */ + rc = smbchg_charging_en(chip, true); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable battery charging=%d\n", rc); + return rc; + } + + /* + * Based on the configuration, use the analog sensors or the fuelgauge + * adc for recharge threshold source. + */ + + if (chip->chg_inhibit_source_fg) + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1, + TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, + TERM_SRC_FG | RECHG_THRESHOLD_SRC_BIT); + else + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1, + TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, 0); + + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return rc; + } + + /* + * control USB suspend via command bits and set correct 100/500mA + * polarity on the usb current + */ + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + USB51_COMMAND_POL | USB51AC_CTRL, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set usb_chgpth cfg rc=%d\n", rc); + return rc; + } + + check_battery_type(chip); + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smbchg_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + return rc; + } + pr_smb(PR_STATUS, "set vfloat to %d\n", chip->vfloat_mv); + } + + /* set the fast charge current compensation */ + if (chip->fastchg_current_comp != -EINVAL) { + rc = smbchg_fastchg_current_comp_set(chip, + chip->fastchg_current_comp); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "set fastchg current comp to %d\n", + chip->fastchg_current_comp); + } + + /* set the float voltage compensation */ + if (chip->float_voltage_comp != -EINVAL) { + rc = smbchg_float_voltage_comp_set(chip, + chip->float_voltage_comp); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "set float voltage comp to %d\n", + chip->float_voltage_comp); + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); + return -EINVAL; + } else { + smbchg_iterm_set(chip, chip->iterm_ma); + } + } + + /* set the safety time voltage */ + if (chip->safety_time != -EINVAL) { + reg = (chip->safety_time > 0 ? 0 : SFT_TIMER_DISABLE_BIT) | + (chip->prechg_safety_time > 0 + ? 0 : PRECHG_SFT_TIMER_DISABLE_BIT); + + for (i = 0; i < ARRAY_SIZE(chg_time); i++) { + if (chip->safety_time <= chg_time[i]) { + reg |= i << SAFETY_TIME_MINUTES_SHIFT; + break; + } + } + for (i = 0; i < ARRAY_SIZE(prechg_time); i++) { + if (chip->prechg_safety_time <= prechg_time[i]) { + reg |= i; + break; + } + } + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + SFT_CFG, + SFT_EN_MASK | SFT_TO_MASK | + (chip->prechg_safety_time > 0 + ? PRECHG_SFT_TO_MASK : 0), reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set safety timer rc = %d\n", + rc); + return rc; + } + chip->safety_timer_en = true; + } else { + rc = smbchg_read(chip, ®, chip->chgr_base + SFT_CFG, 1); + if (rc < 0) + dev_err(chip->dev, "Unable to read SFT_CFG rc = %d\n", + rc); + else if (!(reg & SFT_EN_MASK)) + chip->safety_timer_en = true; + } + + /* configure jeita temperature hard limit */ + if (chip->jeita_temp_hard_limit >= 0) { + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHGR_CCMP_CFG, + JEITA_TEMP_HARD_LIMIT_BIT, + chip->jeita_temp_hard_limit + ? 0 : JEITA_TEMP_HARD_LIMIT_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set jeita temp hard limit rc = %d\n", + rc); + return rc; + } + } + + /* make the buck switch faster to prevent some vbus oscillation */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + TR_8OR32B, + BUCK_8_16_FREQ_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set buck frequency rc = %d\n", rc); + return rc; + } + + /* battery missing detection */ + mask = BATT_MISSING_ALGO_BIT; + reg = chip->bmd_algo_disabled ? 0 : BATT_MISSING_ALGO_BIT; + if (chip->bmd_pin_src < BPD_TYPE_DEFAULT) { + mask |= BMD_PIN_SRC_MASK; + reg |= chip->bmd_pin_src << PIN_SRC_SHIFT; + } + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + BM_CFG, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_missing config = %d\n", + rc); + return rc; + } + + if (chip->vchg_adc_channel != -EINVAL) { + /* configure and enable VCHG */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG, + VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT, + VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", + rc); + return rc; + } + } + + smbchg_charging_status_change(chip); + + vote(chip->usb_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0); + vote(chip->dc_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0); + /* resume threshold */ + if (chip->resume_delta_mv != -EINVAL) { + + /* + * Configure only if the recharge threshold source is not + * fuel gauge ADC. + */ + if (!chip->chg_inhibit_source_fg) { + if (chip->resume_delta_mv < 100) + reg = CHG_INHIBIT_50MV_VAL; + else if (chip->resume_delta_mv < 200) + reg = CHG_INHIBIT_100MV_VAL; + else if (chip->resume_delta_mv < 300) + reg = CHG_INHIBIT_200MV_VAL; + else + reg = CHG_INHIBIT_300MV_VAL; + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHG_INHIB_CFG_REG, + CHG_INHIBIT_MASK, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n", + rc); + return rc; + } + } + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHGR_CFG, + RCHG_LVL_BIT, + (chip->resume_delta_mv + < chip->tables.rchg_thr_mv) + ? 0 : RCHG_LVL_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", + rc); + return rc; + } + } + + /* DC path current settings */ + if (chip->dc_psy_type != -EINVAL) { + rc = vote(chip->dc_icl_votable, PSY_ICL_VOTER, true, + chip->dc_target_current_ma); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't vote for initial DC ICL rc=%d\n", rc); + return rc; + } + } + + + /* + * on some devices the battery is powered via external sources which + * could raise its voltage above the float voltage. smbchargers go + * in to reverse boost in such a situation and the workaround is to + * disable float voltage compensation (note that the battery will appear + * hot/cold when powered via external source). + */ + if (chip->soft_vfloat_comp_disabled) { + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CFG_AFVC, + VFLOAT_COMP_ENABLE_MASK, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n", + rc); + return rc; + } + } + + rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, + chip->cfg_fastchg_current_ma); + if (rc < 0) { + dev_err(chip->dev, "Couldn't vote fastchg ma rc = %d\n", rc); + return rc; + } + + rc = smbchg_read(chip, &chip->original_usbin_allowance, + chip->usb_chgpth_base + USBIN_CHGR_CFG, 1); + if (rc < 0) + dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc); + + if (chip->wipower_dyn_icl_avail) { + rc = smbchg_wipower_ilim_config(chip, + &(chip->wipower_default.entries[0])); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set default wipower ilim = %d\n", + rc); + return rc; + } + } + /* unsuspend dc path, it could be suspended by the bootloader */ + rc = smbchg_dc_suspend(chip, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't unsuspend dc path= %d\n", rc); + return rc; + } + + if (chip->force_aicl_rerun) { + /* vote to enable hw aicl */ + rc = vote(chip->hw_aicl_rerun_enable_indirect_votable, + DEFAULT_CONFIG_HW_AICL_VOTER, true, 0); + if (rc < 0) { + pr_err("Couldn't vote enable hw aicl rerun rc=%d\n", + rc); + return rc; + } + } + + if (chip->schg_version == QPNP_SCHG_LITE) { + /* enable OTG hiccup mode */ + rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG, + HICCUP_ENABLED_BIT, HICCUP_ENABLED_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set OTG OC config rc = %d\n", + rc); + } + + if (chip->otg_pinctrl) { + /* configure OTG enable to pin control active low */ + rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG, + OTG_PIN_POLARITY_BIT | OTG_EN_CTRL_MASK, + OTG_PIN_ACTIVE_LOW | OTG_PIN_CTRL_RID_DIS); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set OTG EN config rc = %d\n", + rc); + return rc; + } + } + + if (chip->wa_flags & SMBCHG_BATT_OV_WA) + batt_ov_wa_check(chip); + + /* turn off AICL adc for improved accuracy */ + rc = smbchg_sec_masked_write(chip, + chip->misc_base + MISC_TRIM_OPT_15_8, AICL_ADC_BIT, 0); + if (rc) + pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n", + rc); + + return rc; +} + +static struct of_device_id smbchg_match_table[] = { + { + .compatible = "qcom,qpnp-smbcharger", + }, + { }, +}; + +#define DC_MA_MIN 300 +#define DC_MA_MAX 2000 +#define OF_PROP_READ(chip, prop, dt_property, retval, optional) \ +do { \ + if (retval) \ + break; \ + if (optional) \ + prop = -EINVAL; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," dt_property , \ + &prop); \ + \ + if ((retval == -EINVAL) && optional) \ + retval = 0; \ + else if (retval) \ + dev_err(chip->dev, "Error reading " #dt_property \ + " property rc = %d\n", rc); \ +} while (0) + +#define ILIM_ENTRIES 3 +#define VOLTAGE_RANGE_ENTRIES 2 +#define RANGE_ENTRY (ILIM_ENTRIES + VOLTAGE_RANGE_ENTRIES) +static int smb_parse_wipower_map_dt(struct smbchg_chip *chip, + struct ilim_map *map, char *property) +{ + struct device_node *node = chip->dev->of_node; + int total_elements, size; + struct property *prop; + const __be32 *data; + int num, i; + + prop = of_find_property(node, property, &size); + if (!prop) { + dev_err(chip->dev, "%s missing\n", property); + return -EINVAL; + } + + total_elements = size / sizeof(int); + if (total_elements % RANGE_ENTRY) { + dev_err(chip->dev, "%s table not in multiple of %d, total elements = %d\n", + property, RANGE_ENTRY, total_elements); + return -EINVAL; + } + + data = prop->value; + num = total_elements / RANGE_ENTRY; + map->entries = devm_kzalloc(chip->dev, + num * sizeof(struct ilim_entry), GFP_KERNEL); + if (!map->entries) { + dev_err(chip->dev, "kzalloc failed for default ilim\n"); + return -ENOMEM; + } + for (i = 0; i < num; i++) { + map->entries[i].vmin_uv = be32_to_cpup(data++); + map->entries[i].vmax_uv = be32_to_cpup(data++); + map->entries[i].icl_pt_ma = be32_to_cpup(data++); + map->entries[i].icl_lv_ma = be32_to_cpup(data++); + map->entries[i].icl_hv_ma = be32_to_cpup(data++); + } + map->num = num; + return 0; +} + +static int smb_parse_wipower_dt(struct smbchg_chip *chip) +{ + int rc = 0; + + chip->wipower_dyn_icl_avail = false; + + if (!chip->vadc_dev) + goto err; + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_default, + "qcom,wipower-default-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n", + rc); + goto err; + } + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_pt, + "qcom,wipower-pt-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n", + rc); + goto err; + } + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_div2, + "qcom,wipower-div2-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-div2-ilim-map rc = %d\n", + rc); + goto err; + } + chip->wipower_dyn_icl_avail = true; + return 0; +err: + chip->wipower_default.num = 0; + chip->wipower_pt.num = 0; + chip->wipower_default.num = 0; + if (chip->wipower_default.entries) + devm_kfree(chip->dev, chip->wipower_default.entries); + if (chip->wipower_pt.entries) + devm_kfree(chip->dev, chip->wipower_pt.entries); + if (chip->wipower_div2.entries) + devm_kfree(chip->dev, chip->wipower_div2.entries); + chip->wipower_default.entries = NULL; + chip->wipower_pt.entries = NULL; + chip->wipower_div2.entries = NULL; + chip->vadc_dev = NULL; + return rc; +} + +#define DEFAULT_VLED_MAX_UV 3500000 +#define DEFAULT_FCC_MA 2000 +static int smb_parse_dt(struct smbchg_chip *chip) +{ + int rc = 0, ocp_thresh = -EINVAL; + struct device_node *node = chip->dev->of_node; + const char *dc_psy_type, *bpd; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + /* read optional u32 properties */ + OF_PROP_READ(chip, ocp_thresh, + "ibat-ocp-threshold-ua", rc, 1); + if (ocp_thresh >= 0) + smbchg_ibat_ocp_threshold_ua = ocp_thresh; + OF_PROP_READ(chip, chip->iterm_ma, "iterm-ma", rc, 1); + OF_PROP_READ(chip, chip->cfg_fastchg_current_ma, + "fastchg-current-ma", rc, 1); + if (chip->cfg_fastchg_current_ma == -EINVAL) + chip->cfg_fastchg_current_ma = DEFAULT_FCC_MA; + OF_PROP_READ(chip, chip->vfloat_mv, "float-voltage-mv", rc, 1); + OF_PROP_READ(chip, chip->safety_time, "charging-timeout-mins", rc, 1); + OF_PROP_READ(chip, chip->vled_max_uv, "vled-max-uv", rc, 1); + if (chip->vled_max_uv < 0) + chip->vled_max_uv = DEFAULT_VLED_MAX_UV; + OF_PROP_READ(chip, chip->rpara_uohm, "rparasitic-uohm", rc, 1); + if (chip->rpara_uohm < 0) + chip->rpara_uohm = 0; + OF_PROP_READ(chip, chip->prechg_safety_time, "precharging-timeout-mins", + rc, 1); + OF_PROP_READ(chip, chip->fastchg_current_comp, "fastchg-current-comp", + rc, 1); + OF_PROP_READ(chip, chip->float_voltage_comp, "float-voltage-comp", + rc, 1); + if (chip->safety_time != -EINVAL && + (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) { + dev_err(chip->dev, "Bad charging-timeout-mins %d\n", + chip->safety_time); + return -EINVAL; + } + if (chip->prechg_safety_time != -EINVAL && + (chip->prechg_safety_time > + prechg_time[ARRAY_SIZE(prechg_time) - 1])) { + dev_err(chip->dev, "Bad precharging-timeout-mins %d\n", + chip->prechg_safety_time); + return -EINVAL; + } + OF_PROP_READ(chip, chip->resume_delta_mv, "resume-delta-mv", rc, 1); + OF_PROP_READ(chip, chip->parallel.min_current_thr_ma, + "parallel-usb-min-current-ma", rc, 1); + OF_PROP_READ(chip, chip->parallel.min_9v_current_thr_ma, + "parallel-usb-9v-min-current-ma", rc, 1); + OF_PROP_READ(chip, chip->parallel.allowed_lowering_ma, + "parallel-allowed-lowering-ma", rc, 1); + if (chip->parallel.min_current_thr_ma != -EINVAL + && chip->parallel.min_9v_current_thr_ma != -EINVAL) + chip->parallel.avail = true; + /* + * use the dt values if they exist, otherwise do not touch the params + */ + of_property_read_u32(node, "qcom,parallel-main-chg-fcc-percent", + &smbchg_main_chg_fcc_percent); + of_property_read_u32(node, "qcom,parallel-main-chg-icl-percent", + &smbchg_main_chg_icl_percent); + pr_smb(PR_STATUS, "parallel usb thr: %d, 9v thr: %d\n", + chip->parallel.min_current_thr_ma, + chip->parallel.min_9v_current_thr_ma); + OF_PROP_READ(chip, chip->jeita_temp_hard_limit, + "jeita-temp-hard-limit", rc, 1); + OF_PROP_READ(chip, chip->aicl_rerun_period_s, + "aicl-rerun-period-s", rc, 1); + OF_PROP_READ(chip, chip->vchg_adc_channel, + "vchg-adc-channel-id", rc, 1); + + /* read boolean configuration properties */ + chip->use_vfloat_adjustments = of_property_read_bool(node, + "qcom,autoadjust-vfloat"); + chip->bmd_algo_disabled = of_property_read_bool(node, + "qcom,bmd-algo-disabled"); + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + chip->soft_vfloat_comp_disabled = of_property_read_bool(node, + "qcom,soft-vfloat-comp-disabled"); + chip->chg_enabled = !(of_property_read_bool(node, + "qcom,charging-disabled")); + chip->charge_unknown_battery = of_property_read_bool(node, + "qcom,charge-unknown-battery"); + chip->chg_inhibit_en = of_property_read_bool(node, + "qcom,chg-inhibit-en"); + chip->chg_inhibit_source_fg = of_property_read_bool(node, + "qcom,chg-inhibit-fg"); + chip->low_volt_dcin = of_property_read_bool(node, + "qcom,low-volt-dcin"); + chip->force_aicl_rerun = of_property_read_bool(node, + "qcom,force-aicl-rerun"); + chip->skip_usb_suspend_for_fake_battery = of_property_read_bool(node, + "qcom,skip-usb-suspend-for-fake-battery"); + + /* parse the battery missing detection pin source */ + rc = of_property_read_string(chip->pdev->dev.of_node, + "qcom,bmd-pin-src", &bpd); + if (rc) { + /* Select BAT_THM as default BPD scheme */ + chip->bmd_pin_src = BPD_TYPE_DEFAULT; + rc = 0; + } else { + chip->bmd_pin_src = get_bpd(bpd); + if (chip->bmd_pin_src < 0) { + dev_err(chip->dev, + "failed to determine bpd schema %d\n", rc); + return rc; + } + } + + /* parse the dc power supply configuration */ + rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type); + if (rc) { + chip->dc_psy_type = -EINVAL; + rc = 0; + } else { + if (strcmp(dc_psy_type, "Mains") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS; + else if (strcmp(dc_psy_type, "Wireless") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS; + else if (strcmp(dc_psy_type, "Wipower") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIPOWER; + } + if (chip->dc_psy_type != -EINVAL) { + OF_PROP_READ(chip, chip->dc_target_current_ma, + "dc-psy-ma", rc, 0); + if (rc) + return rc; + if (chip->dc_target_current_ma < DC_MA_MIN + || chip->dc_target_current_ma > DC_MA_MAX) { + dev_err(chip->dev, "Bad dc mA %d\n", + chip->dc_target_current_ma); + return -EINVAL; + } + } + + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) + smb_parse_wipower_dt(chip); + + /* read the bms power supply name */ + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + /* read the battery power supply name */ + rc = of_property_read_string(node, "qcom,battery-psy-name", + &chip->battery_psy_name); + if (rc) + chip->battery_psy_name = "battery"; + + /* Get the charger led support property */ + chip->cfg_chg_led_sw_ctrl = + of_property_read_bool(node, "qcom,chg-led-sw-controls"); + chip->cfg_chg_led_support = + of_property_read_bool(node, "qcom,chg-led-support"); + + if (of_find_property(node, "qcom,thermal-mitigation", + &chip->thermal_levels)) { + chip->thermal_mitigation = devm_kzalloc(chip->dev, + chip->thermal_levels, + GFP_KERNEL); + + if (chip->thermal_mitigation == NULL) { + dev_err(chip->dev, "thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chip->thermal_mitigation, chip->thermal_levels); + if (rc) { + dev_err(chip->dev, + "Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + chip->skip_usb_notification + = of_property_read_bool(node, + "qcom,skip-usb-notification"); + + chip->otg_pinctrl = of_property_read_bool(node, "qcom,otg-pinctrl"); + + return 0; +} + +#define SUBTYPE_REG 0x5 +#define SMBCHG_CHGR_SUBTYPE 0x1 +#define SMBCHG_OTG_SUBTYPE 0x8 +#define SMBCHG_BAT_IF_SUBTYPE 0x3 +#define SMBCHG_USB_CHGPTH_SUBTYPE 0x4 +#define SMBCHG_DC_CHGPTH_SUBTYPE 0x5 +#define SMBCHG_MISC_SUBTYPE 0x7 +#define SMBCHG_LITE_CHGR_SUBTYPE 0x51 +#define SMBCHG_LITE_OTG_SUBTYPE 0x58 +#define SMBCHG_LITE_BAT_IF_SUBTYPE 0x53 +#define SMBCHG_LITE_USB_CHGPTH_SUBTYPE 0x54 +#define SMBCHG_LITE_DC_CHGPTH_SUBTYPE 0x55 +#define SMBCHG_LITE_MISC_SUBTYPE 0x57 +static int smbchg_request_irq(struct smbchg_chip *chip, + struct device_node *child, + int irq_num, char *irq_name, + irqreturn_t (irq_handler)(int irq, void *_chip), + int flags) +{ + int rc; + + irq_num = of_irq_get_byname(child, irq_name); + if (irq_num < 0) { + dev_err(chip->dev, "Unable to get %s irqn", irq_name); + rc = -ENXIO; + } + rc = devm_request_threaded_irq(chip->dev, + irq_num, NULL, irq_handler, flags, irq_name, + chip); + if (rc < 0) { + dev_err(chip->dev, "Unable to request %s irq: %dn", + irq_name, rc); + rc = -ENXIO; + } + return 0; +} + +static int smbchg_request_irqs(struct smbchg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT; + + if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(chip->pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + rc = 0; + continue; + } + + rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1); + if (rc) { + dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n", + rc); + return rc; + } + + switch (subtype) { + case SMBCHG_CHGR_SUBTYPE: + case SMBCHG_LITE_CHGR_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->chg_error_irq, "chg-error", + chg_error_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->taper_irq, + "chg-taper-thr", taper_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + disable_irq_nosync(chip->taper_irq); + rc = smbchg_request_irq(chip, child, chip->chg_term_irq, + "chg-tcc-thr", chg_term_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->recharge_irq, + "chg-rechg-thr", recharge_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->fastchg_irq, + "chg-p2f-thr", fastchg_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->chg_term_irq); + enable_irq_wake(chip->chg_error_irq); + enable_irq_wake(chip->fastchg_irq); + break; + case SMBCHG_BAT_IF_SUBTYPE: + case SMBCHG_LITE_BAT_IF_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->batt_hot_irq, + "batt-hot", batt_hot_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_warm_irq, + "batt-warm", batt_warm_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_cool_irq, + "batt-cool", batt_cool_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_cold_irq, + "batt-cold", batt_cold_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_missing_irq, + "batt-missing", batt_pres_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->vbat_low_irq, + "batt-low", vbat_low_handler, flags); + if (rc < 0) + return rc; + + enable_irq_wake(chip->batt_hot_irq); + enable_irq_wake(chip->batt_warm_irq); + enable_irq_wake(chip->batt_cool_irq); + enable_irq_wake(chip->batt_cold_irq); + enable_irq_wake(chip->batt_missing_irq); + enable_irq_wake(chip->vbat_low_irq); + break; + case SMBCHG_USB_CHGPTH_SUBTYPE: + case SMBCHG_LITE_USB_CHGPTH_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->usbin_uv_irq, + "usbin-uv", usbin_uv_handler, + flags | IRQF_EARLY_RESUME); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->usbin_ov_irq, + "usbin-ov", usbin_ov_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->src_detect_irq, + "usbin-src-det", + src_detect_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->aicl_done_irq, + "aicl-done", + aicl_done_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + + if (chip->schg_version != QPNP_SCHG_LITE) { + rc = smbchg_request_irq(chip, child, + chip->otg_fail_irq, "otg-fail", + otg_fail_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_oc_irq, "otg-oc", + otg_oc_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->usbid_change_irq, "usbid-change", + usbid_change_handler, + (IRQF_TRIGGER_FALLING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + enable_irq_wake(chip->otg_oc_irq); + enable_irq_wake(chip->usbid_change_irq); + enable_irq_wake(chip->otg_fail_irq); + } + enable_irq_wake(chip->usbin_uv_irq); + enable_irq_wake(chip->usbin_ov_irq); + enable_irq_wake(chip->src_detect_irq); + if (chip->parallel.avail && chip->usb_present) { + rc = enable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = true; + } + break; + case SMBCHG_DC_CHGPTH_SUBTYPE: + case SMBCHG_LITE_DC_CHGPTH_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->dcin_uv_irq, + "dcin-uv", dcin_uv_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->dcin_uv_irq); + break; + case SMBCHG_MISC_SUBTYPE: + case SMBCHG_LITE_MISC_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->power_ok_irq, + "power-ok", power_ok_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->chg_hot_irq, + "temp-shutdown", chg_hot_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->wdog_timeout_irq, + "wdog-timeout", + wdog_timeout_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->chg_hot_irq); + enable_irq_wake(chip->wdog_timeout_irq); + break; + case SMBCHG_OTG_SUBTYPE: + break; + case SMBCHG_LITE_OTG_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->usbid_change_irq, "usbid-change", + usbid_change_handler, + (IRQF_TRIGGER_FALLING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_oc_irq, "otg-oc", + otg_oc_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_fail_irq, "otg-fail", + otg_fail_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->usbid_change_irq); + enable_irq_wake(chip->otg_oc_irq); + enable_irq_wake(chip->otg_fail_irq); + break; + } + } + + return rc; +} + +#define REQUIRE_BASE(chip, base, rc) \ +do { \ + if (!rc && !chip->base) { \ + dev_err(chip->dev, "Missing " #base "\n"); \ + rc = -EINVAL; \ + } \ +} while (0) + +static int smbchg_parse_peripherals(struct smbchg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + + if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(chip->pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + rc = 0; + continue; + } + + rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1); + if (rc) { + dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n", + rc); + return rc; + } + + switch (subtype) { + case SMBCHG_CHGR_SUBTYPE: + case SMBCHG_LITE_CHGR_SUBTYPE: + chip->chgr_base = base; + break; + case SMBCHG_BAT_IF_SUBTYPE: + case SMBCHG_LITE_BAT_IF_SUBTYPE: + chip->bat_if_base = base; + break; + case SMBCHG_USB_CHGPTH_SUBTYPE: + case SMBCHG_LITE_USB_CHGPTH_SUBTYPE: + chip->usb_chgpth_base = base; + break; + case SMBCHG_DC_CHGPTH_SUBTYPE: + case SMBCHG_LITE_DC_CHGPTH_SUBTYPE: + chip->dc_chgpth_base = base; + break; + case SMBCHG_MISC_SUBTYPE: + case SMBCHG_LITE_MISC_SUBTYPE: + chip->misc_base = base; + break; + case SMBCHG_OTG_SUBTYPE: + case SMBCHG_LITE_OTG_SUBTYPE: + chip->otg_base = base; + break; + } + } + + REQUIRE_BASE(chip, chgr_base, rc); + REQUIRE_BASE(chip, bat_if_base, rc); + REQUIRE_BASE(chip, usb_chgpth_base, rc); + REQUIRE_BASE(chip, dc_chgpth_base, rc); + REQUIRE_BASE(chip, misc_base, rc); + + return rc; +} + +static inline void dump_reg(struct smbchg_chip *chip, u16 addr, + const char *name) +{ + u8 reg; + + smbchg_read(chip, ®, addr, 1); + pr_smb(PR_DUMP, "%s - %04X = %02X\n", name, addr, reg); +} + +/* dumps useful registers for debug */ +static void dump_regs(struct smbchg_chip *chip) +{ + u16 addr; + + /* charger peripheral */ + for (addr = 0xB; addr <= 0x10; addr++) + dump_reg(chip, chip->chgr_base + addr, "CHGR Status"); + for (addr = 0xF0; addr <= 0xFF; addr++) + dump_reg(chip, chip->chgr_base + addr, "CHGR Config"); + /* battery interface peripheral */ + dump_reg(chip, chip->bat_if_base + RT_STS, "BAT_IF Status"); + dump_reg(chip, chip->bat_if_base + CMD_CHG_REG, "BAT_IF Command"); + for (addr = 0xF0; addr <= 0xFB; addr++) + dump_reg(chip, chip->bat_if_base + addr, "BAT_IF Config"); + /* usb charge path peripheral */ + for (addr = 0x7; addr <= 0x10; addr++) + dump_reg(chip, chip->usb_chgpth_base + addr, "USB Status"); + dump_reg(chip, chip->usb_chgpth_base + CMD_IL, "USB Command"); + for (addr = 0xF0; addr <= 0xF5; addr++) + dump_reg(chip, chip->usb_chgpth_base + addr, "USB Config"); + /* dc charge path peripheral */ + dump_reg(chip, chip->dc_chgpth_base + RT_STS, "DC Status"); + for (addr = 0xF0; addr <= 0xF6; addr++) + dump_reg(chip, chip->dc_chgpth_base + addr, "DC Config"); + /* misc peripheral */ + dump_reg(chip, chip->misc_base + IDEV_STS, "MISC Status"); + dump_reg(chip, chip->misc_base + RT_STS, "MISC Status"); + for (addr = 0xF0; addr <= 0xF3; addr++) + dump_reg(chip, chip->misc_base + addr, "MISC CFG"); +} + +static int create_debugfs_entries(struct smbchg_chip *chip) +{ + struct dentry *ent; + + chip->debug_root = debugfs_create_dir("qpnp-smbcharger", NULL); + if (!chip->debug_root) { + dev_err(chip->dev, "Couldn't create debug dir\n"); + return -EINVAL; + } + + ent = debugfs_create_file("force_dcin_icl_check", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_dcin_icl_ops); + if (!ent) { + dev_err(chip->dev, + "Couldn't create force dcin icl check file\n"); + return -EINVAL; + } + return 0; +} + +static int smbchg_check_chg_version(struct smbchg_chip *chip) +{ + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + int rc; + + revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR(pmic_rev_id)) { + rc = PTR_ERR(revid_dev_node); + if (rc != -EPROBE_DEFER) + pr_err("Unable to get pmic_revid rc=%d\n", rc); + return rc; + } + + switch (pmic_rev_id->pmic_subtype) { + case PMI8994: + chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA + | SMBCHG_BATT_OV_WA + | SMBCHG_CC_ESR_WA + | SMBCHG_RESTART_WA; + use_pmi8994_tables(chip); + chip->schg_version = QPNP_SCHG; + break; + case PMI8950: + case PMI8937: + chip->wa_flags |= SMBCHG_BATT_OV_WA; + if (pmic_rev_id->rev4 < 2) /* PMI8950 1.0 */ { + chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA; + } else { /* rev > PMI8950 v1.0 */ + chip->wa_flags |= SMBCHG_HVDCP_9V_EN_WA + | SMBCHG_USB100_WA; + } + use_pmi8994_tables(chip); + chip->tables.aicl_rerun_period_table = + aicl_rerun_period_schg_lite; + chip->tables.aicl_rerun_period_len = + ARRAY_SIZE(aicl_rerun_period_schg_lite); + + chip->schg_version = QPNP_SCHG_LITE; + if (pmic_rev_id->pmic_subtype == PMI8937) + chip->hvdcp_not_supported = true; + break; + case PMI8996: + chip->wa_flags |= SMBCHG_CC_ESR_WA + | SMBCHG_FLASH_ICL_DISABLE_WA + | SMBCHG_RESTART_WA + | SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA; + use_pmi8996_tables(chip); + chip->schg_version = QPNP_SCHG; + break; + default: + pr_err("PMIC subtype %d not supported, WA flags not set\n", + pmic_rev_id->pmic_subtype); + } + + pr_smb(PR_STATUS, "pmic=%s, wa_flags=0x%x, hvdcp_supported=%s\n", + pmic_rev_id->pmic_name, chip->wa_flags, + chip->hvdcp_not_supported ? "false" : "true"); + + return 0; +} + +static void rerun_hvdcp_det_if_necessary(struct smbchg_chip *chip) +{ + enum power_supply_type usb_supply_type; + char *usb_type_name; + int rc; + + if (!(chip->wa_flags & SMBCHG_RESTART_WA)) + return; + + read_usb_type(chip, &usb_type_name, &usb_supply_type); + if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP + && !is_hvdcp_present(chip)) { + pr_smb(PR_STATUS, "DCP found rerunning APSD\n"); + rc = vote(chip->usb_icl_votable, + CHG_SUSPEND_WORKAROUND_ICL_VOTER, true, 300); + if (rc < 0) + pr_err("Couldn't vote for 300mA for suspend wa, going ahead rc=%d\n", + rc); + + pr_smb(PR_STATUS, "Faking Removal\n"); + fake_insertion_removal(chip, false); + msleep(500); + pr_smb(PR_STATUS, "Faking Insertion\n"); + fake_insertion_removal(chip, true); + + read_usb_type(chip, &usb_type_name, &usb_supply_type); + if (usb_supply_type != POWER_SUPPLY_TYPE_USB_DCP) { + msleep(500); + pr_smb(PR_STATUS, "Fake Removal again as type!=DCP\n"); + fake_insertion_removal(chip, false); + msleep(500); + pr_smb(PR_STATUS, "Fake Insert again as type!=DCP\n"); + fake_insertion_removal(chip, true); + } + + rc = vote(chip->usb_icl_votable, + CHG_SUSPEND_WORKAROUND_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't vote for 0 for suspend wa, going ahead rc=%d\n", + rc); + } +} + +static int smbchg_probe(struct platform_device *pdev) +{ + int rc; + struct smbchg_chip *chip; + struct power_supply *typec_psy = NULL; + struct qpnp_vadc_chip *vadc_dev, *vchg_vadc_dev; + const char *typec_psy_name; + struct power_supply_config usb_psy_cfg = {}; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config dc_psy_cfg = {}; + + if (of_property_read_bool(pdev->dev.of_node, "qcom,external-typec")) { + /* read the type power supply name */ + rc = of_property_read_string(pdev->dev.of_node, + "qcom,typec-psy-name", &typec_psy_name); + if (rc) { + pr_err("failed to get prop typec-psy-name rc=%d\n", + rc); + return rc; + } + + typec_psy = power_supply_get_by_name(typec_psy_name); + if (!typec_psy) { + pr_smb(PR_STATUS, + "Type-C supply not found, deferring probe\n"); + return -EPROBE_DEFER; + } + } + + vadc_dev = NULL; + if (of_find_property(pdev->dev.of_node, "qcom,dcin-vadc", NULL)) { + vadc_dev = qpnp_get_vadc(&pdev->dev, "dcin"); + if (IS_ERR(vadc_dev)) { + rc = PTR_ERR(vadc_dev); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, + "Couldn't get vadc rc=%d\n", + rc); + return rc; + } + } + + vchg_vadc_dev = NULL; + if (of_find_property(pdev->dev.of_node, "qcom,vchg_sns-vadc", NULL)) { + vchg_vadc_dev = qpnp_get_vadc(&pdev->dev, "vchg_sns"); + if (IS_ERR(vchg_vadc_dev)) { + rc = PTR_ERR(vchg_vadc_dev); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Couldn't get vadc 'vchg' rc=%d\n", + rc); + return rc; + } + } + + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->fcc_votable = create_votable(&pdev->dev, + "SMBCHG: fcc", + VOTE_MIN, NUM_FCC_VOTER, 2000, + set_fastchg_current_vote_cb); + if (IS_ERR(chip->fcc_votable)) + return PTR_ERR(chip->fcc_votable); + + chip->usb_icl_votable = create_votable(&pdev->dev, + "SMBCHG: usb_icl", + VOTE_MIN, NUM_ICL_VOTER, 3000, + set_usb_current_limit_vote_cb); + if (IS_ERR(chip->usb_icl_votable)) + return PTR_ERR(chip->usb_icl_votable); + + chip->dc_icl_votable = create_votable(&pdev->dev, + "SMBCHG: dcl_icl", + VOTE_MIN, NUM_ICL_VOTER, 3000, + set_dc_current_limit_vote_cb); + if (IS_ERR(chip->dc_icl_votable)) + return PTR_ERR(chip->dc_icl_votable); + + chip->usb_suspend_votable = create_votable(&pdev->dev, + "SMBCHG: usb_suspend", + VOTE_SET_ANY, NUM_EN_VOTERS, 0, + usb_suspend_vote_cb); + if (IS_ERR(chip->usb_suspend_votable)) + return PTR_ERR(chip->usb_suspend_votable); + + chip->dc_suspend_votable = create_votable(&pdev->dev, + "SMBCHG: dc_suspend", + VOTE_SET_ANY, NUM_EN_VOTERS, 0, + dc_suspend_vote_cb); + if (IS_ERR(chip->dc_suspend_votable)) + return PTR_ERR(chip->dc_suspend_votable); + + chip->battchg_suspend_votable = create_votable(&pdev->dev, + "SMBCHG: battchg_suspend", + VOTE_SET_ANY, NUM_BATTCHG_EN_VOTERS, 0, + charging_suspend_vote_cb); + if (IS_ERR(chip->battchg_suspend_votable)) + return PTR_ERR(chip->battchg_suspend_votable); + + chip->hw_aicl_rerun_disable_votable = create_votable(&pdev->dev, + "SMBCHG: hwaicl_disable", + VOTE_SET_ANY, NUM_HW_AICL_DISABLE_VOTERS, 0, + smbchg_hw_aicl_rerun_disable_cb); + if (IS_ERR(chip->hw_aicl_rerun_disable_votable)) + return PTR_ERR(chip->hw_aicl_rerun_disable_votable); + + chip->hw_aicl_rerun_enable_indirect_votable = create_votable(&pdev->dev, + "SMBCHG: hwaicl_enable_indirect", + VOTE_SET_ANY, NUM_HW_AICL_RERUN_ENABLE_INDIRECT_VOTERS, + 0, smbchg_hw_aicl_rerun_enable_indirect_cb); + if (IS_ERR(chip->hw_aicl_rerun_enable_indirect_votable)) + return PTR_ERR(chip->hw_aicl_rerun_enable_indirect_votable); + + chip->aicl_deglitch_short_votable = create_votable(&pdev->dev, + "SMBCHG: hwaicl_short_deglitch", + VOTE_SET_ANY, NUM_HW_SHORT_DEGLITCH_VOTERS, 0, + smbchg_aicl_deglitch_config_cb); + if (IS_ERR(chip->aicl_deglitch_short_votable)) + return PTR_ERR(chip->aicl_deglitch_short_votable); + + INIT_WORK(&chip->usb_set_online_work, smbchg_usb_update_online_work); + INIT_DELAYED_WORK(&chip->parallel_en_work, + smbchg_parallel_usb_en_work); + INIT_DELAYED_WORK(&chip->vfloat_adjust_work, smbchg_vfloat_adjust_work); + INIT_DELAYED_WORK(&chip->hvdcp_det_work, smbchg_hvdcp_det_work); + init_completion(&chip->src_det_lowered); + init_completion(&chip->src_det_raised); + init_completion(&chip->usbin_uv_lowered); + init_completion(&chip->usbin_uv_raised); + chip->vadc_dev = vadc_dev; + chip->vchg_vadc_dev = vchg_vadc_dev; + chip->pdev = pdev; + chip->dev = &pdev->dev; + + chip->typec_psy = typec_psy; + chip->fake_battery_soc = -EINVAL; + chip->usb_online = -EINVAL; + dev_set_drvdata(&pdev->dev, chip); + + spin_lock_init(&chip->sec_access_lock); + mutex_init(&chip->therm_lvl_lock); + mutex_init(&chip->usb_set_online_lock); + mutex_init(&chip->parallel.lock); + mutex_init(&chip->taper_irq_lock); + mutex_init(&chip->pm_lock); + mutex_init(&chip->wipower_config); + mutex_init(&chip->usb_status_lock); + device_init_wakeup(chip->dev, true); + + rc = smbchg_parse_peripherals(chip); + if (rc) { + dev_err(chip->dev, "Error parsing DT peripherals: %d\n", rc); + return rc; + } + + rc = smbchg_check_chg_version(chip); + if (rc) { + pr_err("Unable to check schg version rc=%d\n", rc); + return rc; + } + + rc = smb_parse_dt(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Unable to parse DT nodes: %d\n", rc); + return rc; + } + + rc = smbchg_regulator_init(chip); + if (rc) { + dev_err(&pdev->dev, + "Couldn't initialize regulator rc=%d\n", rc); + return rc; + } + + chip->extcon = devm_extcon_dev_allocate(chip->dev, smbchg_extcon_cable); + if (IS_ERR(chip->extcon)) { + dev_err(chip->dev, "failed to allocate extcon device\n"); + return PTR_ERR(chip->extcon); + } + + rc = devm_extcon_dev_register(chip->dev, chip->extcon); + if (rc) { + dev_err(chip->dev, "failed to register extcon device\n"); + return rc; + } + + chip->usb_psy_d.name = "usb"; + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + chip->usb_psy_d.get_property = smbchg_usb_get_property; + chip->usb_psy_d.set_property = smbchg_usb_set_property; + chip->usb_psy_d.properties = smbchg_usb_properties; + chip->usb_psy_d.num_properties = ARRAY_SIZE(smbchg_usb_properties); + chip->usb_psy_d.property_is_writeable = smbchg_usb_is_writeable; + + usb_psy_cfg.drv_data = chip; + usb_psy_cfg.supplied_to = smbchg_usb_supplicants; + usb_psy_cfg.num_supplicants = ARRAY_SIZE(smbchg_usb_supplicants); + + chip->usb_psy = devm_power_supply_register(chip->dev, + &chip->usb_psy_d, &usb_psy_cfg); + if (IS_ERR(chip->usb_psy)) { + dev_err(&pdev->dev, "Unable to register usb_psy rc = %ld\n", + PTR_ERR(chip->usb_psy)); + return PTR_ERR(chip->usb_psy); + } + + if (of_find_property(chip->dev->of_node, "dpdm-supply", NULL)) { + chip->dpdm_reg = devm_regulator_get(chip->dev, "dpdm"); + if (IS_ERR(chip->dpdm_reg)) + return PTR_ERR(chip->dpdm_reg); + } + + rc = smbchg_hw_init(chip); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to intialize hardware rc = %d\n", rc); + goto out; + } + + rc = determine_initial_status(chip); + if (rc < 0) { + dev_err(&pdev->dev, + "Unable to determine init status rc = %d\n", rc); + goto out; + } + + chip->previous_soc = -EINVAL; + chip->batt_psy_d.name = chip->battery_psy_name; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smbchg_battery_get_property; + chip->batt_psy_d.set_property = smbchg_battery_set_property; + chip->batt_psy_d.properties = smbchg_battery_properties; + chip->batt_psy_d.num_properties = ARRAY_SIZE(smbchg_battery_properties); + chip->batt_psy_d.external_power_changed = smbchg_external_power_changed; + chip->batt_psy_d.property_is_writeable = smbchg_battery_is_writeable; + + batt_psy_cfg.drv_data = chip; + batt_psy_cfg.num_supplicants = 0; + chip->batt_psy = devm_power_supply_register(chip->dev, + &chip->batt_psy_d, + &batt_psy_cfg); + if (IS_ERR(chip->batt_psy)) { + dev_err(&pdev->dev, + "Unable to register batt_psy rc = %ld\n", + PTR_ERR(chip->batt_psy)); + goto out; + } + + if (chip->dc_psy_type != -EINVAL) { + chip->dc_psy_d.name = "dc"; + chip->dc_psy_d.type = chip->dc_psy_type; + chip->dc_psy_d.get_property = smbchg_dc_get_property; + chip->dc_psy_d.set_property = smbchg_dc_set_property; + chip->dc_psy_d.property_is_writeable = smbchg_dc_is_writeable; + chip->dc_psy_d.properties = smbchg_dc_properties; + chip->dc_psy_d.num_properties + = ARRAY_SIZE(smbchg_dc_properties); + + dc_psy_cfg.drv_data = chip; + dc_psy_cfg.num_supplicants + = ARRAY_SIZE(smbchg_dc_supplicants); + dc_psy_cfg.supplied_to = smbchg_dc_supplicants; + + chip->dc_psy = devm_power_supply_register(chip->dev, + &chip->dc_psy_d, + &dc_psy_cfg); + if (IS_ERR(chip->dc_psy)) { + dev_err(&pdev->dev, + "Unable to register dc_psy rc = %ld\n", + PTR_ERR(chip->dc_psy)); + goto out; + } + } + + if (chip->cfg_chg_led_support && + chip->schg_version == QPNP_SCHG_LITE) { + rc = smbchg_register_chg_led(chip); + if (rc) { + dev_err(chip->dev, + "Unable to register charger led: %d\n", + rc); + goto out; + } + + rc = smbchg_chg_led_controls(chip); + if (rc) { + dev_err(chip->dev, + "Failed to set charger led controld bit: %d\n", + rc); + goto unregister_led_class; + } + } + + rc = smbchg_request_irqs(chip); + if (rc < 0) { + dev_err(&pdev->dev, "Unable to request irqs rc = %d\n", rc); + goto unregister_led_class; + } + + rerun_hvdcp_det_if_necessary(chip); + + dump_regs(chip); + create_debugfs_entries(chip); + dev_info(chip->dev, + "SMBCHG successfully probe Charger version=%s Revision DIG:%d.%d ANA:%d.%d batt=%d dc=%d usb=%d\n", + version_str[chip->schg_version], + chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR], + chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR], + get_prop_batt_present(chip), + chip->dc_present, chip->usb_present); + return 0; + +unregister_led_class: + if (chip->cfg_chg_led_support && chip->schg_version == QPNP_SCHG_LITE) + led_classdev_unregister(&chip->led_cdev); +out: + handle_usb_removal(chip); + return rc; +} + +static int smbchg_remove(struct platform_device *pdev) +{ + struct smbchg_chip *chip = dev_get_drvdata(&pdev->dev); + + debugfs_remove_recursive(chip->debug_root); + + return 0; +} + +static void smbchg_shutdown(struct platform_device *pdev) +{ + struct smbchg_chip *chip = dev_get_drvdata(&pdev->dev); + int i, rc; + + if (!(chip->wa_flags & SMBCHG_RESTART_WA)) + return; + + if (!is_hvdcp_present(chip)) + return; + + pr_smb(PR_MISC, "Disable Parallel\n"); + mutex_lock(&chip->parallel.lock); + smbchg_parallel_en = 0; + smbchg_parallel_usb_disable(chip); + mutex_unlock(&chip->parallel.lock); + + pr_smb(PR_MISC, "Disable all interrupts\n"); + disable_irq(chip->aicl_done_irq); + disable_irq(chip->batt_cold_irq); + disable_irq(chip->batt_cool_irq); + disable_irq(chip->batt_hot_irq); + disable_irq(chip->batt_missing_irq); + disable_irq(chip->batt_warm_irq); + disable_irq(chip->chg_error_irq); + disable_irq(chip->chg_hot_irq); + disable_irq(chip->chg_term_irq); + disable_irq(chip->dcin_uv_irq); + disable_irq(chip->fastchg_irq); + disable_irq(chip->otg_fail_irq); + disable_irq(chip->otg_oc_irq); + disable_irq(chip->power_ok_irq); + disable_irq(chip->recharge_irq); + disable_irq(chip->src_detect_irq); + disable_irq(chip->taper_irq); + disable_irq(chip->usbid_change_irq); + disable_irq(chip->usbin_ov_irq); + disable_irq(chip->usbin_uv_irq); + disable_irq(chip->vbat_low_irq); + disable_irq(chip->wdog_timeout_irq); + + /* remove all votes for short deglitch */ + for (i = 0; i < NUM_HW_SHORT_DEGLITCH_VOTERS; i++) + vote(chip->aicl_deglitch_short_votable, i, false, 0); + + /* vote to ensure AICL rerun is enabled */ + rc = vote(chip->hw_aicl_rerun_enable_indirect_votable, + SHUTDOWN_WORKAROUND_VOTER, true, 0); + if (rc < 0) + pr_err("Couldn't vote to enable indirect AICL rerun\n"); + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't vote to enable AICL rerun\n"); + + /* switch to 5V HVDCP */ + pr_smb(PR_MISC, "Switch to 5V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc); + return; + } + + pr_smb(PR_MISC, "Wait 500mS to lower to 5V\n"); + /* wait for HVDCP to lower to 5V */ + msleep(500); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and that we are still in 5V hvdcp + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 500mS sleep\n"); + return; + } + + /* disable HVDCP */ + pr_smb(PR_MISC, "Disable HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, 0); + if (rc < 0) + pr_err("Couldn't disable HVDCP rc=%d\n", rc); + + chip->hvdcp_3_det_ignore_uv = true; + /* fake a removal */ + pr_smb(PR_MISC, "Faking Removal\n"); + rc = fake_insertion_removal(chip, false); + if (rc < 0) + pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc); + + /* fake an insertion */ + pr_smb(PR_MISC, "Faking Insertion\n"); + rc = fake_insertion_removal(chip, true); + if (rc < 0) + pr_err("Couldn't fake insertion rc=%d\n", rc); + + pr_smb(PR_MISC, "Wait 1S to settle\n"); + msleep(1000); + chip->hvdcp_3_det_ignore_uv = false; + + pr_smb(PR_STATUS, "wrote power off configurations\n"); +} + +static const struct dev_pm_ops smbchg_pm_ops = { +}; + +MODULE_DEVICE_TABLE(spmi, smbchg_id); + +static struct platform_driver smbchg_driver = { + .driver = { + .name = "qpnp-smbcharger", + .owner = THIS_MODULE, + .of_match_table = smbchg_match_table, + .pm = &smbchg_pm_ops, + }, + .probe = smbchg_probe, + .remove = smbchg_remove, + .shutdown = smbchg_shutdown, +}; + +static int __init smbchg_init(void) +{ + return platform_driver_register(&smbchg_driver); +} + +static void __exit smbchg_exit(void) +{ + return platform_driver_unregister(&smbchg_driver); +} + +module_init(smbchg_init); +module_exit(smbchg_exit); + +MODULE_DESCRIPTION("QPNP SMB Charger"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:qpnp-smbcharger"); diff --git a/drivers/power/qcom-charger/smb1351-charger.c b/drivers/power/qcom-charger/smb1351-charger.c new file mode 100644 index 000000000000..0f18844b9afa --- /dev/null +++ b/drivers/power/qcom-charger/smb1351-charger.c @@ -0,0 +1,3292 @@ +/* Copyright (c) 2015 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 <linux/i2c.h> +#include <linux/debugfs.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/machine.h> +#include <linux/of.h> +#include <linux/mutex.h> +#include <linux/delay.h> +#include <linux/qpnp/qpnp-adc.h> +#include <linux/pinctrl/consumer.h> + +/* Mask/Bit helpers */ +#define _SMB1351_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB1351_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB1351_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Configuration registers */ +#define CHG_CURRENT_CTRL_REG 0x0 +#define FAST_CHG_CURRENT_MASK SMB1351_MASK(7, 4) +#define AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(3, 0) + +#define CHG_OTH_CURRENT_CTRL_REG 0x1 +#define PRECHG_CURRENT_MASK SMB1351_MASK(7, 5) +#define ITERM_MASK SMB1351_MASK(4, 2) +#define USB_2_3_MODE_SEL_BIT BIT(1) +#define USB_2_3_MODE_SEL_BY_I2C 0 +#define USB_2_3_MODE_SEL_BY_PIN 0x2 +#define USB_5_1_CMD_POLARITY_BIT BIT(0) +#define USB_CMD_POLARITY_500_1_100_0 0 +#define USB_CMD_POLARITY_500_0_100_1 0x1 + +#define VARIOUS_FUNC_REG 0x2 +#define SUSPEND_MODE_CTRL_BIT BIT(7) +#define SUSPEND_MODE_CTRL_BY_PIN 0 +#define SUSPEND_MODE_CTRL_BY_I2C 0x80 +#define BATT_TO_SYS_POWER_CTRL_BIT BIT(6) +#define MAX_SYS_VOLTAGE BIT(5) +#define AICL_EN_BIT BIT(4) +#define AICL_DET_TH_BIT BIT(3) +#define APSD_EN_BIT BIT(2) +#define BATT_OV_BIT BIT(1) +#define VCHG_FUNC_BIT BIT(0) + +#define VFLOAT_REG 0x3 +#define PRECHG_TO_FAST_VOLTAGE_CFG_MASK SMB1351_MASK(7, 6) +#define VFLOAT_MASK SMB1351_MASK(5, 0) + +#define CHG_CTRL_REG 0x4 +#define AUTO_RECHG_BIT BIT(7) +#define AUTO_RECHG_ENABLE 0 +#define AUTO_RECHG_DISABLE 0x80 +#define ITERM_EN_BIT BIT(6) +#define ITERM_ENABLE 0 +#define ITERM_DISABLE 0x40 +#define MAPPED_AC_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(5, 4) +#define AUTO_RECHG_TH_BIT BIT(3) +#define AUTO_RECHG_TH_50MV 0 +#define AUTO_RECHG_TH_100MV 0x8 +#define AFCV_MASK SMB1351_MASK(2, 0) + +#define CHG_STAT_TIMERS_CTRL_REG 0x5 +#define STAT_OUTPUT_POLARITY_BIT BIT(7) +#define STAT_OUTPUT_MODE_BIT BIT(6) +#define STAT_OUTPUT_CTRL_BIT BIT(5) +#define OTH_CHG_IL_BIT BIT(4) +#define COMPLETE_CHG_TIMEOUT_MASK SMB1351_MASK(3, 2) +#define PRECHG_TIMEOUT_MASK SMB1351_MASK(1, 0) + +#define CHG_PIN_EN_CTRL_REG 0x6 +#define LED_BLINK_FUNC_BIT BIT(7) +#define EN_PIN_CTRL_MASK SMB1351_MASK(6, 5) +#define EN_BY_I2C_0_DISABLE 0 +#define EN_BY_I2C_0_ENABLE 0x20 +#define EN_BY_PIN_HIGH_ENABLE 0x40 +#define EN_BY_PIN_LOW_ENABLE 0x60 +#define USBCS_CTRL_BIT BIT(4) +#define USBCS_CTRL_BY_I2C 0 +#define USBCS_CTRL_BY_PIN 0x10 +#define USBCS_INPUT_STATE_BIT BIT(3) +#define CHG_ERR_BIT BIT(2) +#define APSD_DONE_BIT BIT(1) +#define USB_FAIL_BIT BIT(0) + +#define THERM_A_CTRL_REG 0x7 +#define MIN_SYS_VOLTAGE_MASK SMB1351_MASK(7, 6) +#define LOAD_BATT_10MA_FVC_BIT BIT(5) +#define THERM_MONITOR_BIT BIT(4) +#define THERM_MONITOR_EN 0 +#define SOFT_COLD_TEMP_LIMIT_MASK SMB1351_MASK(3, 2) +#define SOFT_HOT_TEMP_LIMIT_MASK SMB1351_MASK(1, 0) + +#define WDOG_SAFETY_TIMER_CTRL_REG 0x8 +#define AICL_FAIL_OPTION_BIT BIT(7) +#define AICL_FAIL_TO_SUSPEND 0 +#define AICL_FAIL_TO_150_MA 0x80 +#define WDOG_TIMEOUT_MASK SMB1351_MASK(6, 5) +#define WDOG_IRQ_SAFETY_TIMER_MASK SMB1351_MASK(4, 3) +#define WDOG_IRQ_SAFETY_TIMER_EN_BIT BIT(2) +#define WDOG_OPTION_BIT BIT(1) +#define WDOG_TIMER_EN_BIT BIT(0) + +#define OTG_USBIN_AICL_CTRL_REG 0x9 +#define OTG_ID_PIN_CTRL_MASK SMB1351_MASK(7, 6) +#define OTG_PIN_POLARITY_BIT BIT(5) +#define DCIN_IC_GLITCH_FILTER_HV_ADAPTER_MASK SMB1351_MASK(4, 3) +#define DCIN_IC_GLITCH_FILTER_LV_ADAPTER_BIT BIT(2) +#define USBIN_AICL_CFG1_BIT BIT(1) +#define USBIN_AICL_CFG0_BIT BIT(0) + +#define OTG_TLIM_CTRL_REG 0xA +#define SWITCH_FREQ_MASK SMB1351_MASK(7, 6) +#define THERM_LOOP_TEMP_SEL_MASK SMB1351_MASK(5, 4) +#define OTG_OC_LIMIT_MASK SMB1351_MASK(3, 2) +#define OTG_BATT_UVLO_TH_MASK SMB1351_MASK(1, 0) + +#define HARD_SOFT_LIMIT_CELL_TEMP_REG 0xB +#define HARD_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(7, 6) +#define HARD_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(5, 4) +#define SOFT_LIMIT_COLD_TEMP_ALARM_TRIP_MASK SMB1351_MASK(3, 2) +#define SOFT_LIMIT_HOT_TEMP_ALARM_TRIP_MASK SMB1351_MASK(1, 0) + +#define FAULT_INT_REG 0xC +#define HOT_COLD_HARD_LIMIT_BIT BIT(7) +#define HOT_COLD_SOFT_LIMIT_BIT BIT(6) +#define BATT_UVLO_IN_OTG_BIT BIT(5) +#define OTG_OC_BIT BIT(4) +#define INPUT_OVLO_BIT BIT(3) +#define INPUT_UVLO_BIT BIT(2) +#define AICL_DONE_FAIL_BIT BIT(1) +#define INTERNAL_OVER_TEMP_BIT BIT(0) + +#define STATUS_INT_REG 0xD +#define CHG_OR_PRECHG_TIMEOUT_BIT BIT(7) +#define RID_CHANGE_BIT BIT(6) +#define BATT_OVP_BIT BIT(5) +#define FAST_TERM_TAPER_RECHG_INHIBIT_BIT BIT(4) +#define WDOG_TIMER_BIT BIT(3) +#define POK_BIT BIT(2) +#define BATT_MISSING_BIT BIT(1) +#define BATT_LOW_BIT BIT(0) + +#define VARIOUS_FUNC_2_REG 0xE +#define CHG_HOLD_OFF_TIMER_AFTER_PLUGIN_BIT BIT(7) +#define CHG_INHIBIT_BIT BIT(6) +#define FAST_CHG_CC_IN_BATT_SOFT_LIMIT_MODE_BIT BIT(5) +#define FVCL_IN_BATT_SOFT_LIMIT_MODE_MASK SMB1351_MASK(4, 3) +#define HARD_TEMP_LIMIT_BEHAVIOR_BIT BIT(2) +#define PRECHG_TO_FASTCHG_BIT BIT(1) +#define STAT_PIN_CONFIG_BIT BIT(0) + +#define FLEXCHARGER_REG 0x10 +#define AFVC_IRQ_BIT BIT(7) +#define CHG_CONFIG_MASK SMB1351_MASK(6, 4) +#define LOW_BATT_VOLTAGE_DET_TH_MASK SMB1351_MASK(3, 0) + +#define VARIOUS_FUNC_3_REG 0x11 +#define SAFETY_TIMER_EN_MASK SMB1351_MASK(7, 6) +#define BLOCK_SUSPEND_DURING_VBATT_LOW_BIT BIT(5) +#define TIMEOUT_SEL_FOR_APSD_BIT BIT(4) +#define SDP_SUSPEND_BIT BIT(3) +#define QC_2P1_AUTO_INCREMENT_MODE_BIT BIT(2) +#define QC_2P1_AUTH_ALGO_BIT BIT(1) +#define DCD_EN_BIT BIT(0) + +#define HVDCP_BATT_MISSING_CTRL_REG 0x12 +#define HVDCP_ADAPTER_SEL_MASK SMB1351_MASK(7, 6) +#define HVDCP_EN_BIT BIT(5) +#define HVDCP_AUTO_INCREMENT_LIMIT_BIT BIT(4) +#define BATT_MISSING_ON_INPUT_PLUGIN_BIT BIT(3) +#define BATT_MISSING_2P6S_POLLER_BIT BIT(2) +#define BATT_MISSING_ALGO_BIT BIT(1) +#define BATT_MISSING_THERM_PIN_SOURCE_BIT BIT(0) + +#define PON_OPTIONS_REG 0x13 +#define SYSOK_INOK_POLARITY_BIT BIT(7) +#define SYSOK_OPTIONS_MASK SMB1351_MASK(6, 4) +#define INPUT_MISSING_POLLER_CONFIG_BIT BIT(3) +#define VBATT_LOW_DISABLED_OR_RESET_STATE_BIT BIT(2) +#define QC_2P1_AUTH_ALGO_IRQ_EN_BIT BIT(0) + +#define OTG_MODE_POWER_OPTIONS_REG 0x14 +#define ADAPTER_CONFIG_MASK SMB1351_MASK(7, 6) +#define MAP_HVDCP_BIT BIT(5) +#define SDP_LOW_BATT_FORCE_USB5_OVER_USB1_BIT BIT(4) +#define OTG_HICCUP_MODE_BIT BIT(2) +#define INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(1, 0) + +#define CHARGER_I2C_CTRL_REG 0x15 +#define FULLON_MODE_EN_BIT BIT(7) +#define I2C_HS_MODE_EN_BIT BIT(6) +#define SYSON_LDO_OUTPUT_SEL_BIT BIT(5) +#define VBATT_TRACKING_VOLTAGE_DIFF_BIT BIT(4) +#define DISABLE_AFVC_WHEN_ENTER_TAPER_BIT BIT(3) +#define VCHG_IINV_BIT BIT(2) +#define AFVC_OVERRIDE_BIT BIT(1) +#define SYSOK_PIN_CONFIG_BIT BIT(0) + +#define VERSION_REG 0x2E +#define VERSION_MASK BIT(1) + +/* Command registers */ +#define CMD_I2C_REG 0x30 +#define CMD_RELOAD_BIT BIT(7) +#define CMD_BQ_CFG_ACCESS_BIT BIT(6) + +#define CMD_INPUT_LIMIT_REG 0x31 +#define CMD_OVERRIDE_BIT BIT(7) +#define CMD_SUSPEND_MODE_BIT BIT(6) +#define CMD_INPUT_CURRENT_MODE_BIT BIT(3) +#define CMD_INPUT_CURRENT_MODE_APSD 0 +#define CMD_INPUT_CURRENT_MODE_CMD 0x08 +#define CMD_USB_2_3_SEL_BIT BIT(2) +#define CMD_USB_2_MODE 0 +#define CMD_USB_3_MODE 0x4 +#define CMD_USB_1_5_AC_CTRL_MASK SMB1351_MASK(1, 0) +#define CMD_USB_100_MODE 0 +#define CMD_USB_500_MODE 0x2 +#define CMD_USB_AC_MODE 0x1 + +#define CMD_CHG_REG 0x32 +#define CMD_DISABLE_THERM_MONITOR_BIT BIT(4) +#define CMD_TURN_OFF_STAT_PIN_BIT BIT(3) +#define CMD_PRE_TO_FAST_EN_BIT BIT(2) +#define CMD_CHG_EN_BIT BIT(1) +#define CMD_CHG_DISABLE 0 +#define CMD_CHG_ENABLE 0x2 +#define CMD_OTG_EN_BIT BIT(0) + +#define CMD_DEAD_BATT_REG 0x33 +#define CMD_STOP_DEAD_BATT_TIMER_MASK SMB1351_MASK(7, 0) + +#define CMD_HVDCP_REG 0x34 +#define CMD_APSD_RE_RUN_BIT BIT(7) +#define CMD_FORCE_HVDCP_2P0_BIT BIT(5) +#define CMD_HVDCP_MODE_MASK SMB1351_MASK(5, 0) + +/* Status registers */ +#define STATUS_0_REG 0x36 +#define STATUS_AICL_BIT BIT(7) +#define STATUS_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(6, 5) +#define STATUS_DCIN_INPUT_CURRENT_LIMIT_MASK SMB1351_MASK(4, 0) + +#define STATUS_1_REG 0x37 +#define STATUS_INPUT_RANGE_MASK SMB1351_MASK(7, 4) +#define STATUS_INPUT_USB_BIT BIT(0) + +#define STATUS_2_REG 0x38 +#define STATUS_FAST_CHG_BIT BIT(7) +#define STATUS_HARD_LIMIT_BIT BIT(6) +#define STATUS_FLOAT_VOLTAGE_MASK SMB1351_MASK(5, 0) + +#define STATUS_3_REG 0x39 +#define STATUS_CHG_BIT BIT(7) +#define STATUS_PRECHG_CURRENT_MASK SMB1351_MASK(6, 4) +#define STATUS_FAST_CHG_CURRENT_MASK SMB1351_MASK(3, 0) + +#define STATUS_4_REG 0x3A +#define STATUS_OTG_BIT BIT(7) +#define STATUS_AFVC_BIT BIT(6) +#define STATUS_DONE_BIT BIT(5) +#define STATUS_BATT_LESS_THAN_2V_BIT BIT(4) +#define STATUS_HOLD_OFF_BIT BIT(3) +#define STATUS_CHG_MASK SMB1351_MASK(2, 1) +#define STATUS_NO_CHARGING 0 +#define STATUS_FAST_CHARGING 0x4 +#define STATUS_PRE_CHARGING 0x2 +#define STATUS_TAPER_CHARGING 0x6 +#define STATUS_CHG_EN_STATUS_BIT BIT(0) + +#define STATUS_5_REG 0x3B +#define STATUS_SOURCE_DETECTED_MASK SMB1351_MASK(7, 0) +#define STATUS_PORT_CDP 0x80 +#define STATUS_PORT_DCP 0x40 +#define STATUS_PORT_OTHER 0x20 +#define STATUS_PORT_SDP 0x10 +#define STATUS_PORT_ACA_A 0x8 +#define STATUS_PORT_ACA_B 0x4 +#define STATUS_PORT_ACA_C 0x2 +#define STATUS_PORT_ACA_DOCK 0x1 + +#define STATUS_6_REG 0x3C +#define STATUS_DCD_TIMEOUT_BIT BIT(7) +#define STATUS_DCD_GOOD_DG_BIT BIT(6) +#define STATUS_OCD_GOOD_DG_BIT BIT(5) +#define STATUS_RID_ABD_DG_BIT BIT(4) +#define STATUS_RID_FLOAT_STATE_MACHINE_BIT BIT(3) +#define STATUS_RID_A_STATE_MACHINE_BIT BIT(2) +#define STATUS_RID_B_STATE_MACHINE_BIT BIT(1) +#define STATUS_RID_C_STATE_MACHINE_BIT BIT(0) + +#define STATUS_7_REG 0x3D +#define STATUS_HVDCP_MASK SMB1351_MASK(7, 0) + +#define STATUS_8_REG 0x3E +#define STATUS_USNIN_HV_INPUT_SEL_BIT BIT(5) +#define STATUS_USBIN_LV_UNDER_INPUT_SEL_BIT BIT(4) +#define STATUS_USBIN_LV_INPUT_SEL_BIT BIT(3) + +/* Revision register */ +#define CHG_REVISION_REG 0x3F +#define GUI_REVISION_MASK SMB1351_MASK(7, 4) +#define DEVICE_REVISION_MASK SMB1351_MASK(3, 0) + +/* IRQ status registers */ +#define IRQ_A_REG 0x40 +#define IRQ_HOT_HARD_BIT BIT(6) +#define IRQ_COLD_HARD_BIT BIT(4) +#define IRQ_HOT_SOFT_BIT BIT(2) +#define IRQ_COLD_SOFT_BIT BIT(0) + +#define IRQ_B_REG 0x41 +#define IRQ_BATT_TERMINAL_REMOVED_BIT BIT(6) +#define IRQ_BATT_MISSING_BIT BIT(4) +#define IRQ_LOW_BATT_VOLTAGE_BIT BIT(2) +#define IRQ_INTERNAL_TEMP_LIMIT_BIT BIT(0) + +#define IRQ_C_REG 0x42 +#define IRQ_PRE_TO_FAST_VOLTAGE_BIT BIT(6) +#define IRQ_RECHG_BIT BIT(4) +#define IRQ_TAPER_BIT BIT(2) +#define IRQ_TERM_BIT BIT(0) + +#define IRQ_D_REG 0x43 +#define IRQ_BATT_OV_BIT BIT(6) +#define IRQ_CHG_ERROR_BIT BIT(4) +#define IRQ_CHG_TIMEOUT_BIT BIT(2) +#define IRQ_PRECHG_TIMEOUT_BIT BIT(0) + +#define IRQ_E_REG 0x44 +#define IRQ_USBIN_OV_BIT BIT(6) +#define IRQ_USBIN_UV_BIT BIT(4) +#define IRQ_AFVC_BIT BIT(2) +#define IRQ_POWER_OK_BIT BIT(0) + +#define IRQ_F_REG 0x45 +#define IRQ_OTG_OVER_CURRENT_BIT BIT(6) +#define IRQ_OTG_FAIL_BIT BIT(4) +#define IRQ_RID_BIT BIT(2) +#define IRQ_OTG_OC_RETRY_BIT BIT(0) + +#define IRQ_G_REG 0x46 +#define IRQ_SOURCE_DET_BIT BIT(6) +#define IRQ_AICL_DONE_BIT BIT(4) +#define IRQ_AICL_FAIL_BIT BIT(2) +#define IRQ_CHG_INHIBIT_BIT BIT(0) + +#define IRQ_H_REG 0x47 +#define IRQ_IC_LIMIT_STATUS_BIT BIT(5) +#define IRQ_HVDCP_2P1_STATUS_BIT BIT(4) +#define IRQ_HVDCP_AUTH_DONE_BIT BIT(2) +#define IRQ_WDOG_TIMEOUT_BIT BIT(0) + +/* constants */ +#define USB2_MIN_CURRENT_MA 100 +#define USB2_MAX_CURRENT_MA 500 +#define USB3_MIN_CURRENT_MA 150 +#define USB3_MAX_CURRENT_MA 900 +#define SMB1351_IRQ_REG_COUNT 8 +#define SMB1351_CHG_PRE_MIN_MA 100 +#define SMB1351_CHG_FAST_MIN_MA 1000 +#define SMB1351_CHG_FAST_MAX_MA 4500 +#define SMB1351_CHG_PRE_SHIFT 5 +#define SMB1351_CHG_FAST_SHIFT 4 +#define DEFAULT_BATT_CAPACITY 50 +#define DEFAULT_BATT_TEMP 250 +#define SUSPEND_CURRENT_MA 2 + +#define CHG_ITERM_200MA 0x0 +#define CHG_ITERM_300MA 0x04 +#define CHG_ITERM_400MA 0x08 +#define CHG_ITERM_500MA 0x0C +#define CHG_ITERM_600MA 0x10 +#define CHG_ITERM_700MA 0x14 + +#define ADC_TM_WARM_COOL_THR_ENABLE ADC_TM_HIGH_LOW_THR_ENABLE + +enum reason { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), + SOC = BIT(3), +}; + +static char *pm_batt_supplied_to[] = { + "bms", +}; + +struct smb1351_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +enum chip_version { + SMB_UNKNOWN = 0, + SMB1350, + SMB1351, + SMB_MAX_TYPE, +}; + +static const char *smb1351_version_str[SMB_MAX_TYPE] = { + [SMB_UNKNOWN] = "Unknown", + [SMB1350] = "SMB1350", + [SMB1351] = "SMB1351", +}; + +struct smb1351_charger { + struct i2c_client *client; + struct device *dev; + + bool recharge_disabled; + int recharge_mv; + bool iterm_disabled; + int iterm_ma; + int vfloat_mv; + int chg_present; + int fake_battery_soc; + bool chg_autonomous_mode; + bool disable_apsd; + bool using_pmic_therm; + bool jeita_supported; + bool battery_missing; + const char *bms_psy_name; + bool resume_completed; + bool irq_waiting; + struct delayed_work chg_remove_work; + struct delayed_work hvdcp_det_work; + + /* status tracking */ + bool batt_full; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + + int battchg_disabled_status; + int usb_suspended_status; + int target_fastchg_current_max_ma; + int fastchg_current_max_ma; + int workaround_flags; + + int parallel_pin_polarity_setting; + bool parallel_charger; + bool parallel_charger_present; + bool bms_controlled_charging; + bool apsd_rerun; + bool usbin_ov; + bool chg_remove_work_scheduled; + bool force_hvdcp_2p0; + enum chip_version version; + + /* psy */ + struct power_supply *usb_psy; + int usb_psy_ma; + struct power_supply *bms_psy; + struct power_supply_desc batt_psy_d; + struct power_supply *batt_psy; + struct power_supply *parallel_psy; + struct power_supply_desc parallel_psy_d; + + struct smb1351_regulator otg_vreg; + struct mutex irq_complete; + + struct dentry *debug_root; + u32 peek_poke_address; + + /* adc_tm parameters */ + struct qpnp_vadc_chip *vadc_dev; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct qpnp_adc_tm_btm_param adc_param; + + /* jeita parameters */ + int batt_hot_decidegc; + int batt_cold_decidegc; + int batt_warm_decidegc; + int batt_cool_decidegc; + int batt_missing_decidegc; + unsigned int batt_warm_ma; + unsigned int batt_warm_mv; + unsigned int batt_cool_ma; + unsigned int batt_cool_mv; + + /* pinctrl parameters */ + const char *pinctrl_state_name; + struct pinctrl *smb_pinctrl; +}; + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb1351_charger *chip, u8 rt_stat); + int high; + int low; +}; + +struct irq_handler_info { + u8 stat_reg; + u8 val; + u8 prev_val; + struct smb_irq_info irq_info[4]; +}; + +/* USB input charge current */ +static int usb_chg_current[] = { + 500, 685, 1000, 1100, 1200, 1300, 1500, 1600, + 1700, 1800, 2000, 2200, 2500, 3000, +}; + +static int fast_chg_current[] = { + 1000, 1200, 1400, 1600, 1800, 2000, 2200, + 2400, 2600, 2800, 3000, 3400, 3600, 3800, + 4000, 4640, +}; + +static int pre_chg_current[] = { + 200, 300, 400, 500, 600, 700, +}; + +struct battery_status { + bool batt_hot; + bool batt_warm; + bool batt_cool; + bool batt_cold; + bool batt_present; +}; + +enum { + BATT_HOT = 0, + BATT_WARM, + BATT_NORMAL, + BATT_COOL, + BATT_COLD, + BATT_MISSING, + BATT_STATUS_MAX, +}; + +static struct battery_status batt_s[] = { + [BATT_HOT] = {1, 0, 0, 0, 1}, + [BATT_WARM] = {0, 1, 0, 0, 1}, + [BATT_NORMAL] = {0, 0, 0, 0, 1}, + [BATT_COOL] = {0, 0, 1, 0, 1}, + [BATT_COLD] = {0, 0, 0, 1, 1}, + [BATT_MISSING] = {0, 0, 0, 1, 0}, +}; + +static int smb1351_read_reg(struct smb1351_charger *chip, int reg, u8 *val) +{ + s32 ret; + + pm_stay_awake(chip->dev); + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + pr_err("i2c read fail: can't read from %02x: %d\n", reg, ret); + pm_relax(chip->dev); + return ret; + } else { + *val = ret; + } + pm_relax(chip->dev); + pr_debug("Reading 0x%02x=0x%02x\n", reg, *val); + return 0; +} + +static int smb1351_write_reg(struct smb1351_charger *chip, int reg, u8 val) +{ + s32 ret; + + pm_stay_awake(chip->dev); + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0) { + pr_err("i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + pm_relax(chip->dev); + return ret; + } + pm_relax(chip->dev); + pr_debug("Writing 0x%02x=0x%02x\n", reg, val); + return 0; +} + +static int smb1351_masked_write(struct smb1351_charger *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + rc = smb1351_read_reg(chip, reg, &temp); + if (rc) { + pr_err("read failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + temp &= ~mask; + temp |= val & mask; + rc = smb1351_write_reg(chip, reg, temp); + if (rc) { + pr_err("write failed: reg=%03X, rc=%d\n", reg, rc); + return rc; + } + return 0; +} + +static int smb1351_enable_volatile_writes(struct smb1351_charger *chip) +{ + int rc; + + rc = smb1351_masked_write(chip, CMD_I2C_REG, CMD_BQ_CFG_ACCESS_BIT, + CMD_BQ_CFG_ACCESS_BIT); + if (rc) + pr_err("Couldn't write CMD_BQ_CFG_ACCESS_BIT rc=%d\n", rc); + + return rc; +} + +static int smb1351_usb_suspend(struct smb1351_charger *chip, int reason, + bool suspend) +{ + int rc = 0; + int suspended; + + suspended = chip->usb_suspended_status; + + pr_debug("reason = %d requested_suspend = %d suspended_status = %d\n", + reason, suspend, suspended); + + if (suspend == false) + suspended &= ~reason; + else + suspended |= reason; + + pr_debug("new suspended_status = %d\n", suspended); + + rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, + CMD_SUSPEND_MODE_BIT, + suspended ? CMD_SUSPEND_MODE_BIT : 0); + if (rc) + pr_err("Couldn't suspend rc = %d\n", rc); + else + chip->usb_suspended_status = suspended; + + return rc; +} + +static int smb1351_battchg_disable(struct smb1351_charger *chip, + int reason, int disable) +{ + int rc = 0; + int disabled; + + if (chip->chg_autonomous_mode) { + pr_debug("Charger in autonomous mode\n"); + return 0; + } + + disabled = chip->battchg_disabled_status; + + pr_debug("reason = %d requested_disable = %d disabled_status = %d\n", + reason, disable, disabled); + if (disable == true) + disabled |= reason; + else + disabled &= ~reason; + + pr_debug("new disabled_status = %d\n", disabled); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_CHG_EN_BIT, + disabled ? 0 : CMD_CHG_ENABLE); + if (rc) + pr_err("Couldn't %s charging rc=%d\n", + disable ? "disable" : "enable", rc); + else + chip->battchg_disabled_status = disabled; + + return rc; +} + +static int smb1351_fastchg_current_set(struct smb1351_charger *chip, + unsigned int fastchg_current) +{ + int i, rc; + bool is_pre_chg = false; + + + if ((fastchg_current < SMB1351_CHG_PRE_MIN_MA) || + (fastchg_current > SMB1351_CHG_FAST_MAX_MA)) { + pr_err("bad pre_fastchg current mA=%d asked to set\n", + fastchg_current); + return -EINVAL; + } + + /* + * fast chg current could not support less than 1000mA + * use pre chg to instead for the parallel charging + */ + if (fastchg_current < SMB1351_CHG_FAST_MIN_MA) { + is_pre_chg = true; + pr_debug("is_pre_chg true, current is %d\n", fastchg_current); + } + + if (is_pre_chg) { + /* set prechg current */ + for (i = ARRAY_SIZE(pre_chg_current) - 1; i >= 0; i--) { + if (pre_chg_current[i] <= fastchg_current) + break; + } + if (i < 0) + i = 0; + chip->fastchg_current_max_ma = pre_chg_current[i]; + pr_debug("prechg setting %02x\n", i); + + i = i << SMB1351_CHG_PRE_SHIFT; + + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + PRECHG_CURRENT_MASK, i); + if (rc) + pr_err("Couldn't write CHG_OTH_CURRENT_CTRL_REG rc=%d\n", + rc); + + return smb1351_masked_write(chip, VARIOUS_FUNC_2_REG, + PRECHG_TO_FASTCHG_BIT, PRECHG_TO_FASTCHG_BIT); + } else { + if (chip->version == SMB_UNKNOWN) + return -EINVAL; + + /* SMB1350 supports FCC upto 2600 mA */ + if (chip->version == SMB1350 && fastchg_current > 2600) + fastchg_current = 2600; + + /* set fastchg current */ + for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) { + if (fast_chg_current[i] <= fastchg_current) + break; + } + if (i < 0) + i = 0; + chip->fastchg_current_max_ma = fast_chg_current[i]; + + i = i << SMB1351_CHG_FAST_SHIFT; + pr_debug("fastchg limit=%d setting %02x\n", + chip->fastchg_current_max_ma, i); + + /* make sure pre chg mode is disabled */ + rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG, + PRECHG_TO_FASTCHG_BIT, 0); + if (rc) + pr_err("Couldn't write VARIOUS_FUNC_2_REG rc=%d\n", rc); + + return smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG, + FAST_CHG_CURRENT_MASK, i); + } +} + +#define MIN_FLOAT_MV 3500 +#define MAX_FLOAT_MV 4500 +#define VFLOAT_STEP_MV 20 + +static int smb1351_float_voltage_set(struct smb1351_charger *chip, + int vfloat_mv) +{ + u8 temp; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + pr_err("bad float voltage mv =%d asked to set\n", vfloat_mv); + return -EINVAL; + } + + temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV; + + return smb1351_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp); +} + +static int smb1351_iterm_set(struct smb1351_charger *chip, int iterm_ma) +{ + int rc; + u8 reg; + + if (iterm_ma <= 200) + reg = CHG_ITERM_200MA; + else if (iterm_ma <= 300) + reg = CHG_ITERM_300MA; + else if (iterm_ma <= 400) + reg = CHG_ITERM_400MA; + else if (iterm_ma <= 500) + reg = CHG_ITERM_500MA; + else if (iterm_ma <= 600) + reg = CHG_ITERM_600MA; + else + reg = CHG_ITERM_700MA; + + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, + ITERM_MASK, reg); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + /* enable the iterm */ + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + ITERM_EN_BIT, ITERM_ENABLE); + if (rc) { + pr_err("Couldn't enable iterm rc = %d\n", rc); + return rc; + } + return 0; +} + +static int smb1351_chg_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, + CMD_OTG_EN_BIT); + if (rc) + pr_err("Couldn't enable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb1351_chg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0); + if (rc) + pr_err("Couldn't disable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb1351_chg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smb1351_charger *chip = rdev_get_drvdata(rdev); + + rc = smb1351_read_reg(chip, CMD_CHG_REG, ®); + if (rc) { + pr_err("Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & CMD_OTG_EN_BIT) ? 1 : 0; +} + +struct regulator_ops smb1351_chg_otg_reg_ops = { + .enable = smb1351_chg_otg_regulator_enable, + .disable = smb1351_chg_otg_regulator_disable, + .is_enabled = smb1351_chg_otg_regulator_is_enable, +}; + +static int smb1351_regulator_init(struct smb1351_charger *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb1351_chg_otg_reg_ops; + chip->otg_vreg.rdesc.name = + chip->dev->of_node->name; + chip->otg_vreg.rdesc.of_match = + chip->dev->of_node->name; + + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->otg_vreg.rdev = regulator_register( + &chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + pr_err("OTG reg failed, rc=%d\n", rc); + } + return rc; +} + +static int smb_chip_get_version(struct smb1351_charger *chip) +{ + u8 ver; + int rc = 0; + + if (chip->version == SMB_UNKNOWN) { + rc = smb1351_read_reg(chip, VERSION_REG, &ver); + if (rc) { + pr_err("Couldn't read version rc=%d\n", rc); + return rc; + } + + /* If bit 1 is set, it is SMB1350 */ + if (ver & VERSION_MASK) + chip->version = SMB1350; + else + chip->version = SMB1351; + } + + return rc; +} + +static int smb1351_hw_init(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0, mask = 0; + + /* configure smb_pinctrl to enable irqs */ + if (chip->pinctrl_state_name) { + chip->smb_pinctrl = pinctrl_get_select(chip->dev, + chip->pinctrl_state_name); + if (IS_ERR(chip->smb_pinctrl)) { + pr_err("Could not get/set %s pinctrl state rc = %ld\n", + chip->pinctrl_state_name, + PTR_ERR(chip->smb_pinctrl)); + return PTR_ERR(chip->smb_pinctrl); + } + } + + /* + * If the charger is pre-configured for autonomous operation, + * do not apply additional settings + */ + if (chip->chg_autonomous_mode) { + pr_debug("Charger configured for autonomous mode\n"); + return 0; + } + + rc = smb_chip_get_version(chip); + if (rc) { + pr_err("Couldn't get version rc = %d\n", rc); + return rc; + } + + rc = smb1351_enable_volatile_writes(chip); + if (rc) { + pr_err("Couldn't configure volatile writes rc=%d\n", rc); + return rc; + } + + /* setup battery missing source */ + reg = BATT_MISSING_THERM_PIN_SOURCE_BIT; + mask = BATT_MISSING_THERM_PIN_SOURCE_BIT; + rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG, + mask, reg); + if (rc) { + pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup defaults for CHG_PIN_EN_CTRL_REG */ + reg = EN_BY_I2C_0_DISABLE | USBCS_CTRL_BY_I2C | CHG_ERR_BIT | + APSD_DONE_BIT | LED_BLINK_FUNC_BIT; + mask = EN_PIN_CTRL_MASK | USBCS_CTRL_BIT | CHG_ERR_BIT | + APSD_DONE_BIT | LED_BLINK_FUNC_BIT; + rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup USB 2.0/3.0 detection and USB 500/100 command polarity */ + reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0; + mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT; + rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", rc); + return rc; + } + /* setup USB suspend, AICL and APSD */ + reg = SUSPEND_MODE_CTRL_BY_I2C | AICL_EN_BIT; + if (!chip->disable_apsd) + reg |= APSD_EN_BIT; + mask = SUSPEND_MODE_CTRL_BIT | AICL_EN_BIT | APSD_EN_BIT; + rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, mask, reg); + if (rc) { + pr_err("Couldn't set VARIOUS_FUNC_REG rc=%d\n", rc); + return rc; + } + /* Fault and Status IRQ configuration */ + reg = HOT_COLD_HARD_LIMIT_BIT | HOT_COLD_SOFT_LIMIT_BIT + | INPUT_OVLO_BIT | INPUT_UVLO_BIT | AICL_DONE_FAIL_BIT; + rc = smb1351_write_reg(chip, FAULT_INT_REG, reg); + if (rc) { + pr_err("Couldn't set FAULT_INT_REG rc=%d\n", rc); + return rc; + } + reg = CHG_OR_PRECHG_TIMEOUT_BIT | BATT_OVP_BIT | + FAST_TERM_TAPER_RECHG_INHIBIT_BIT | + BATT_MISSING_BIT | BATT_LOW_BIT; + rc = smb1351_write_reg(chip, STATUS_INT_REG, reg); + if (rc) { + pr_err("Couldn't set STATUS_INT_REG rc=%d\n", rc); + return rc; + } + /* setup THERM Monitor */ + if (!chip->using_pmic_therm) { + rc = smb1351_masked_write(chip, THERM_A_CTRL_REG, + THERM_MONITOR_BIT, THERM_MONITOR_EN); + if (rc) { + pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n", rc); + return rc; + } + } + /* set the fast charge current limit */ + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + if (rc) { + pr_err("Couldn't set fastchg current rc=%d\n", rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb1351_float_voltage_set(chip, chip->vfloat_mv); + if (rc) { + pr_err("Couldn't set float voltage rc = %d\n", rc); + return rc; + } + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + pr_err("Error: Both iterm_disabled and iterm_ma set\n"); + return -EINVAL; + } else { + rc = smb1351_iterm_set(chip, chip->iterm_ma); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + } + } else if (chip->iterm_disabled) { + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + ITERM_EN_BIT, ITERM_DISABLE); + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return rc; + } + } + + /* set recharge-threshold */ + if (chip->recharge_mv != -EINVAL) { + if (chip->recharge_disabled) { + pr_err("Error: Both recharge_disabled and recharge_mv set\n"); + return -EINVAL; + } else { + reg = AUTO_RECHG_ENABLE; + if (chip->recharge_mv > 50) + reg |= AUTO_RECHG_TH_100MV; + else + reg |= AUTO_RECHG_TH_50MV; + + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT | + AUTO_RECHG_TH_BIT, reg); + if (rc) { + pr_err("Couldn't set rechg-cfg rc = %d\n", rc); + return rc; + } + } + } else if (chip->recharge_disabled) { + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT, + AUTO_RECHG_DISABLE); + if (rc) { + pr_err("Couldn't disable auto-rechg rc = %d\n", rc); + return rc; + } + } + + /* enable/disable charging by suspending usb */ + rc = smb1351_usb_suspend(chip, USER, chip->usb_suspended_status); + if (rc) { + pr_err("Unable to %s battery charging. rc=%d\n", + chip->usb_suspended_status ? "disable" : "enable", + rc); + } + + return rc; +} + +static enum power_supply_property smb1351_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_MODEL_NAME, +}; + +static int smb1351_get_prop_batt_status(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + if (chip->batt_full) + return POWER_SUPPLY_STATUS_FULL; + + rc = smb1351_read_reg(chip, STATUS_4_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_4 rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + pr_debug("STATUS_4_REG(0x3A)=%x\n", reg); + + if (reg & STATUS_HOLD_OFF_BIT) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + if (reg & STATUS_CHG_MASK) + return POWER_SUPPLY_STATUS_CHARGING; + + return POWER_SUPPLY_STATUS_DISCHARGING; +} + +static int smb1351_get_prop_batt_present(struct smb1351_charger *chip) +{ + return !chip->battery_missing; +} + +static int smb1351_get_prop_batt_capacity(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + return ret.intval; + } + pr_debug("return DEFAULT_BATT_CAPACITY\n"); + return DEFAULT_BATT_CAPACITY; +} + +static int smb1351_get_prop_batt_temp(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + int rc = 0; + struct qpnp_vadc_result results; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_TEMP, &ret); + return ret.intval; + } + if (chip->vadc_dev) { + rc = qpnp_vadc_read(chip->vadc_dev, + LR_MUX1_BATT_THERM, &results); + if (rc) + pr_debug("Unable to read adc batt temp rc=%d\n", rc); + else + return (int)results.physical; + } + + pr_debug("return default temperature\n"); + return DEFAULT_BATT_TEMP; +} + +static int smb1351_get_prop_charge_type(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + rc = smb1351_read_reg(chip, STATUS_4_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_4 rc = %d\n", rc); + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + } + + pr_debug("STATUS_4_REG(0x3A)=%x\n", reg); + + reg &= STATUS_CHG_MASK; + + if (reg == STATUS_FAST_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (reg == STATUS_TAPER_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_TAPER; + else if (reg == STATUS_PRE_CHARGING) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int smb1351_get_prop_batt_health(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->batt_hot) + ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + ret.intval = POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + ret.intval = POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + ret.intval = POWER_SUPPLY_HEALTH_COOL; + else + ret.intval = POWER_SUPPLY_HEALTH_GOOD; + + return ret.intval; +} + +static int smb1351_set_usb_chg_current(struct smb1351_charger *chip, + int current_ma) +{ + int i, rc = 0; + u8 reg = 0, mask = 0; + + pr_debug("USB current_ma = %d\n", current_ma); + + if (chip->chg_autonomous_mode) { + pr_debug("Charger in autonomous mode\n"); + return 0; + } + + /* set suspend bit when urrent_ma <= 2 */ + if (current_ma <= SUSPEND_CURRENT_MA) { + smb1351_usb_suspend(chip, CURRENT, true); + pr_debug("USB suspend\n"); + return 0; + } + + if (current_ma > SUSPEND_CURRENT_MA && + current_ma < USB2_MIN_CURRENT_MA) + current_ma = USB2_MIN_CURRENT_MA; + + if (current_ma == USB2_MIN_CURRENT_MA) { + /* USB 2.0 - 100mA */ + reg = CMD_USB_2_MODE | CMD_USB_100_MODE; + } else if (current_ma == USB3_MIN_CURRENT_MA) { + /* USB 3.0 - 150mA */ + reg = CMD_USB_3_MODE | CMD_USB_100_MODE; + } else if (current_ma == USB2_MAX_CURRENT_MA) { + /* USB 2.0 - 500mA */ + reg = CMD_USB_2_MODE | CMD_USB_500_MODE; + } else if (current_ma == USB3_MAX_CURRENT_MA) { + /* USB 3.0 - 900mA */ + reg = CMD_USB_3_MODE | CMD_USB_500_MODE; + } else if (current_ma > USB2_MAX_CURRENT_MA) { + /* HC mode - if none of the above */ + reg = CMD_USB_AC_MODE; + + for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) { + if (usb_chg_current[i] <= current_ma) + break; + } + if (i < 0) + i = 0; + rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG, + AC_INPUT_CURRENT_LIMIT_MASK, i); + if (rc) { + pr_err("Couldn't set input mA rc=%d\n", rc); + return rc; + } + } + /* control input current mode by command */ + reg |= CMD_INPUT_CURRENT_MODE_CMD; + mask = CMD_INPUT_CURRENT_MODE_BIT | CMD_USB_2_3_SEL_BIT | + CMD_USB_1_5_AC_CTRL_MASK; + rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg); + if (rc) { + pr_err("Couldn't set charging mode rc = %d\n", rc); + return rc; + } + + /* unset the suspend bit here */ + smb1351_usb_suspend(chip, CURRENT, false); + + return rc; +} + +static int smb1351_batt_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + return 1; + default: + break; + } + return 0; +} + +static int smb1351_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc; + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + if (!chip->bms_controlled_charging) + return -EINVAL; + switch (val->intval) { + case POWER_SUPPLY_STATUS_FULL: + rc = smb1351_battchg_disable(chip, SOC, true); + if (rc) { + pr_err("Couldn't disable charging rc = %d\n", + rc); + } else { + chip->batt_full = true; + pr_debug("status = FULL, batt_full = %d\n", + chip->batt_full); + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + chip->batt_full = false; + power_supply_changed(chip->batt_psy); + pr_debug("status = DISCHARGING, batt_full = %d\n", + chip->batt_full); + break; + case POWER_SUPPLY_STATUS_CHARGING: + rc = smb1351_battchg_disable(chip, SOC, false); + if (rc) { + pr_err("Couldn't enable charging rc = %d\n", + rc); + } else { + chip->batt_full = false; + pr_debug("status = CHARGING, batt_full = %d\n", + chip->batt_full); + } + break; + default: + return -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + smb1351_usb_suspend(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + smb1351_battchg_disable(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + power_supply_changed(chip->batt_psy); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb1351_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb1351_get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb1351_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = smb1351_get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !chip->usb_suspended_status; + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + val->intval = !chip->battchg_disabled_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = smb1351_get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb1351_get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = smb1351_get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_MODEL_NAME: + val->strval = "smb1351"; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property smb1351_parallel_properties[] = { + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, +}; + +static int smb1351_parallel_set_chg_present(struct smb1351_charger *chip, + int present) +{ + int rc; + u8 reg, mask = 0; + + if (present == chip->parallel_charger_present) { + pr_debug("present %d -> %d, skipping\n", + chip->parallel_charger_present, present); + return 0; + } + + if (present) { + /* Check if SMB1351 is present */ + rc = smb1351_read_reg(chip, CHG_REVISION_REG, ®); + if (rc) { + pr_debug("Failed to detect smb1351-parallel-charger, may be absent\n"); + return -ENODEV; + } + + rc = smb_chip_get_version(chip); + if (rc) { + pr_err("Couldn't get version rc = %d\n", rc); + return rc; + } + + rc = smb1351_enable_volatile_writes(chip); + if (rc) { + pr_err("Couldn't configure for volatile rc = %d\n", rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb1351_float_voltage_set(chip, chip->vfloat_mv); + if (rc) { + pr_err("Couldn't set float voltage rc = %d\n", + rc); + return rc; + } + } + + /* set recharge-threshold and enable auto recharge */ + if (chip->recharge_mv != -EINVAL) { + reg = AUTO_RECHG_ENABLE; + if (chip->recharge_mv > 50) + reg |= AUTO_RECHG_TH_100MV; + else + reg |= AUTO_RECHG_TH_50MV; + + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + AUTO_RECHG_BIT | + AUTO_RECHG_TH_BIT, reg); + if (rc) { + pr_err("Couldn't set rechg-cfg rc = %d\n", rc); + return rc; + } + } + + /* set chg en by pin active low */ + reg = chip->parallel_pin_polarity_setting | USBCS_CTRL_BY_I2C; + rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, + EN_PIN_CTRL_MASK | USBCS_CTRL_BIT, reg); + if (rc) { + pr_err("Couldn't set en pin rc=%d\n", rc); + return rc; + } + + /* control USB suspend via command bits */ + rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, + SUSPEND_MODE_CTRL_BIT, + SUSPEND_MODE_CTRL_BY_I2C); + if (rc) { + pr_err("Couldn't set USB suspend rc=%d\n", rc); + return rc; + } + + /* + * setup USB 2.0/3.0 detection and USB 500/100 + * command polarity + */ + reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0; + mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT; + rc = smb1351_masked_write(chip, + CHG_OTH_CURRENT_CTRL_REG, mask, reg); + if (rc) { + pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", + rc); + return rc; + } + + /* set fast charging current limit */ + chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MIN_MA; + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + if (rc) { + pr_err("Couldn't set fastchg current rc=%d\n", rc); + return rc; + } + } + + chip->parallel_charger_present = present; + /* + * When present is being set force USB suspend, start charging + * only when POWER_SUPPLY_PROP_CURRENT_MAX is set. + */ + chip->usb_psy_ma = SUSPEND_CURRENT_MA; + smb1351_usb_suspend(chip, CURRENT, true); + + return 0; +} + +static int smb1351_get_closest_usb_setpoint(int val) +{ + int i; + + for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) { + if (usb_chg_current[i] <= val) + break; + } + if (i < 0) + i = 0; + + if (i >= ARRAY_SIZE(usb_chg_current) - 1) + return ARRAY_SIZE(usb_chg_current) - 1; + + /* check what is closer, i or i + 1 */ + if (abs(usb_chg_current[i] - val) < abs(usb_chg_current[i + 1] - val)) + return i; + else + return i + 1; +} + +static bool smb1351_is_input_current_limited(struct smb1351_charger *chip) +{ + int rc; + u8 reg; + + rc = smb1351_read_reg(chip, IRQ_H_REG, ®); + if (rc) { + pr_err("Failed to read IRQ_H_REG for ICL status: %d\n", rc); + return false; + } + + return !!(reg & IRQ_IC_LIMIT_STATUS_BIT); +} + +static int smb1351_parallel_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0, index; + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + /* + *CHG EN is controlled by pin in the parallel charging. + *Use suspend if disable charging by command. + */ + if (chip->parallel_charger_present) + rc = smb1351_usb_suspend(chip, USER, !val->intval); + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smb1351_parallel_set_chg_present(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + if (chip->parallel_charger_present) { + chip->target_fastchg_current_max_ma = + val->intval / 1000; + rc = smb1351_fastchg_current_set(chip, + chip->target_fastchg_current_max_ma); + } + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (chip->parallel_charger_present) { + index = smb1351_get_closest_usb_setpoint( + val->intval / 1000); + chip->usb_psy_ma = usb_chg_current[index]; + rc = smb1351_set_usb_chg_current(chip, + chip->usb_psy_ma); + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (chip->parallel_charger_present && + (chip->vfloat_mv != val->intval)) { + rc = smb1351_float_voltage_set(chip, val->intval); + if (!rc) + chip->vfloat_mv = val->intval; + } else { + chip->vfloat_mv = val->intval; + } + break; + default: + return -EINVAL; + } + return rc; +} + +static int smb1351_parallel_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + return 1; + default: + return 0; + } +} + +static int smb1351_parallel_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !chip->usb_suspended_status; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (chip->parallel_charger_present) + val->intval = chip->usb_psy_ma * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = chip->vfloat_mv; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->parallel_charger_present; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + if (chip->parallel_charger_present) + val->intval = chip->fastchg_current_max_ma * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (chip->parallel_charger_present) + val->intval = smb1351_get_prop_batt_status(chip); + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + if (chip->parallel_charger_present) + val->intval = + smb1351_is_input_current_limited(chip) ? 1 : 0; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static void smb1351_chg_set_appropriate_battery_current( + struct smb1351_charger *chip) +{ + int rc; + unsigned int current_max = chip->target_fastchg_current_max_ma; + + if (chip->batt_cool) + current_max = min(current_max, chip->batt_cool_ma); + if (chip->batt_warm) + current_max = min(current_max, chip->batt_warm_ma); + + pr_debug("setting %dmA", current_max); + + rc = smb1351_fastchg_current_set(chip, current_max); + if (rc) + pr_err("Couldn't set charging current rc = %d\n", rc); +} + +static void smb1351_chg_set_appropriate_vddmax(struct smb1351_charger *chip) +{ + int rc; + unsigned int vddmax = chip->vfloat_mv; + + if (chip->batt_cool) + vddmax = min(vddmax, chip->batt_cool_mv); + if (chip->batt_warm) + vddmax = min(vddmax, chip->batt_warm_mv); + + pr_debug("setting %dmV\n", vddmax); + + rc = smb1351_float_voltage_set(chip, vddmax); + if (rc) + pr_err("Couldn't set float voltage rc = %d\n", rc); +} + +static void smb1351_chg_ctrl_in_jeita(struct smb1351_charger *chip) +{ + union power_supply_propval ret = {0, }; + int rc; + + /* enable the iterm to prevent the reverse boost */ + if (chip->iterm_disabled) { + if (chip->batt_cool || chip->batt_warm) { + rc = smb1351_iterm_set(chip, 100); + pr_debug("set the iterm due to JEITA\n"); + } else { + rc = smb1351_masked_write(chip, CHG_CTRL_REG, + ITERM_EN_BIT, ITERM_DISABLE); + pr_debug("disable the iterm when exits warm/cool\n"); + } + if (rc) { + pr_err("Couldn't set iterm rc = %d\n", rc); + return; + } + } + /* + * When JEITA back to normal, the charging maybe disabled due to + * the current termination. So re-enable the charging if the soc + * is less than 100 in the normal mode. A 200ms delay is requred + * before the disabe and enable operation. + */ + if (chip->bms_psy) { + rc = power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + if (rc) { + pr_err("Couldn't read the bms capacity rc = %d\n", + rc); + return; + } + if (!chip->batt_cool && !chip->batt_warm + && !chip->batt_cold && !chip->batt_hot + && ret.intval < 100) { + rc = smb1351_battchg_disable(chip, THERMAL, true); + if (rc) { + pr_err("Couldn't disable charging rc = %d\n", + rc); + return; + } + /* delay for resetting the charging */ + msleep(200); + rc = smb1351_battchg_disable(chip, THERMAL, false); + if (rc) { + pr_err("Couldn't enable charging rc = %d\n", + rc); + return; + } else { + chip->batt_full = false; + pr_debug("re-enable charging, batt_full = %d\n", + chip->batt_full); + } + pr_debug("batt psy changed\n"); + power_supply_changed(chip->batt_psy); + } + } +} + +#define HYSTERESIS_DECIDEGC 20 +static void smb1351_chg_adc_notification(enum qpnp_tm_state state, void *ctx) +{ + struct smb1351_charger *chip = ctx; + struct battery_status *cur = NULL; + int temp; + + if (state >= ADC_TM_STATE_NUM) { + pr_err("invalid state parameter %d\n", state); + return; + } + + temp = smb1351_get_prop_batt_temp(chip); + + pr_debug("temp = %d state = %s\n", temp, + state == ADC_TM_WARM_STATE ? "hot" : "cold"); + + /* reset the adc status request */ + chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE; + + /* temp from low to high */ + if (state == ADC_TM_WARM_STATE) { + /* WARM -> HOT */ + if (temp >= chip->batt_hot_decidegc) { + cur = &batt_s[BATT_HOT]; + chip->adc_param.low_temp = + chip->batt_hot_decidegc - HYSTERESIS_DECIDEGC; + chip->adc_param.state_request = ADC_TM_COOL_THR_ENABLE; + /* NORMAL -> WARM */ + } else if (temp >= chip->batt_warm_decidegc && + chip->jeita_supported) { + cur = &batt_s[BATT_WARM]; + chip->adc_param.low_temp = + chip->batt_warm_decidegc - HYSTERESIS_DECIDEGC; + chip->adc_param.high_temp = chip->batt_hot_decidegc; + /* COOL -> NORMAL */ + } else if (temp >= chip->batt_cool_decidegc && + chip->jeita_supported) { + cur = &batt_s[BATT_NORMAL]; + chip->adc_param.low_temp = + chip->batt_cool_decidegc - HYSTERESIS_DECIDEGC; + chip->adc_param.high_temp = chip->batt_warm_decidegc; + /* COLD -> COOL */ + } else if (temp >= chip->batt_cold_decidegc) { + cur = &batt_s[BATT_COOL]; + chip->adc_param.low_temp = + chip->batt_cold_decidegc - HYSTERESIS_DECIDEGC; + if (chip->jeita_supported) + chip->adc_param.high_temp = + chip->batt_cool_decidegc; + else + chip->adc_param.high_temp = + chip->batt_hot_decidegc; + /* MISSING -> COLD */ + } else if (temp >= chip->batt_missing_decidegc) { + cur = &batt_s[BATT_COLD]; + chip->adc_param.high_temp = chip->batt_cold_decidegc; + chip->adc_param.low_temp = chip->batt_missing_decidegc + - HYSTERESIS_DECIDEGC; + } + /* temp from high to low */ + } else { + /* COLD -> MISSING */ + if (temp <= chip->batt_missing_decidegc) { + cur = &batt_s[BATT_MISSING]; + chip->adc_param.high_temp = chip->batt_missing_decidegc + + HYSTERESIS_DECIDEGC; + chip->adc_param.state_request = ADC_TM_WARM_THR_ENABLE; + /* COOL -> COLD */ + } else if (temp <= chip->batt_cold_decidegc) { + cur = &batt_s[BATT_COLD]; + chip->adc_param.high_temp = + chip->batt_cold_decidegc + HYSTERESIS_DECIDEGC; + /* add low_temp to enable batt present check */ + chip->adc_param.low_temp = chip->batt_missing_decidegc; + /* NORMAL -> COOL */ + } else if (temp <= chip->batt_cool_decidegc && + chip->jeita_supported) { + cur = &batt_s[BATT_COOL]; + chip->adc_param.high_temp = + chip->batt_cool_decidegc + HYSTERESIS_DECIDEGC; + chip->adc_param.low_temp = chip->batt_cold_decidegc; + /* WARM -> NORMAL */ + } else if (temp <= chip->batt_warm_decidegc && + chip->jeita_supported) { + cur = &batt_s[BATT_NORMAL]; + chip->adc_param.high_temp = + chip->batt_warm_decidegc + HYSTERESIS_DECIDEGC; + chip->adc_param.low_temp = chip->batt_cool_decidegc; + /* HOT -> WARM */ + } else if (temp <= chip->batt_hot_decidegc) { + cur = &batt_s[BATT_WARM]; + if (chip->jeita_supported) + chip->adc_param.low_temp = + chip->batt_warm_decidegc; + else + chip->adc_param.low_temp = + chip->batt_cold_decidegc; + chip->adc_param.high_temp = + chip->batt_hot_decidegc + HYSTERESIS_DECIDEGC; + } + } + + if (cur->batt_present) + chip->battery_missing = false; + else + chip->battery_missing = true; + + if (cur->batt_hot ^ chip->batt_hot || + cur->batt_cold ^ chip->batt_cold) { + chip->batt_hot = cur->batt_hot; + chip->batt_cold = cur->batt_cold; + /* stop charging explicitly since we use PMIC thermal pin*/ + if (cur->batt_hot || cur->batt_cold || + chip->battery_missing) + smb1351_battchg_disable(chip, THERMAL, 1); + else + smb1351_battchg_disable(chip, THERMAL, 0); + } + + if ((chip->batt_warm ^ cur->batt_warm || + chip->batt_cool ^ cur->batt_cool) + && chip->jeita_supported) { + chip->batt_warm = cur->batt_warm; + chip->batt_cool = cur->batt_cool; + smb1351_chg_set_appropriate_battery_current(chip); + smb1351_chg_set_appropriate_vddmax(chip); + smb1351_chg_ctrl_in_jeita(chip); + } + + pr_debug("hot %d, cold %d, warm %d, cool %d, soft jeita supported %d, missing %d, low = %d deciDegC, high = %d deciDegC\n", + chip->batt_hot, chip->batt_cold, chip->batt_warm, + chip->batt_cool, chip->jeita_supported, + chip->battery_missing, chip->adc_param.low_temp, + chip->adc_param.high_temp); + if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param)) + pr_err("request ADC error\n"); +} + +static int rerun_apsd(struct smb1351_charger *chip) +{ + int rc; + + pr_debug("Reruning APSD\nDisabling APSD\n"); + + rc = smb1351_masked_write(chip, CMD_HVDCP_REG, CMD_APSD_RE_RUN_BIT, + CMD_APSD_RE_RUN_BIT); + if (rc) + pr_err("Couldn't re-run APSD algo\n"); + + return 0; +} + +static void smb1351_hvdcp_det_work(struct work_struct *work) +{ + int rc; + u8 reg; + union power_supply_propval pval = {0, }; + struct smb1351_charger *chip = container_of(work, + struct smb1351_charger, + hvdcp_det_work.work); + + rc = smb1351_read_reg(chip, STATUS_7_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc); + goto end; + } + pr_debug("STATUS_7_REG = 0x%02X\n", reg); + + if (reg) { + pr_debug("HVDCP detected; notifying USB PSY\n"); + pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + } +end: + pm_relax(chip->dev); +} + +#define HVDCP_NOTIFY_MS 2500 +static int smb1351_apsd_complete_handler(struct smb1351_charger *chip, + u8 status) +{ + int rc; + u8 reg = 0; + union power_supply_propval prop = {0, }; + enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN; + + /* + * If apsd is disabled, charger detection is done by + * USB phy driver. + */ + if (chip->disable_apsd || chip->usbin_ov) { + pr_debug("APSD %s, status = %d\n", + chip->disable_apsd ? "disabled" : "enabled", !!status); + pr_debug("USBIN ov, status = %d\n", chip->usbin_ov); + return 0; + } + + rc = smb1351_read_reg(chip, STATUS_5_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_5 rc = %d\n", rc); + return rc; + } + + pr_debug("STATUS_5_REG(0x3B)=%x\n", reg); + + switch (reg) { + case STATUS_PORT_ACA_DOCK: + case STATUS_PORT_ACA_C: + case STATUS_PORT_ACA_B: + case STATUS_PORT_ACA_A: + type = POWER_SUPPLY_TYPE_USB_ACA; + break; + case STATUS_PORT_CDP: + type = POWER_SUPPLY_TYPE_USB_CDP; + break; + case STATUS_PORT_DCP: + type = POWER_SUPPLY_TYPE_USB_DCP; + break; + case STATUS_PORT_SDP: + type = POWER_SUPPLY_TYPE_USB; + break; + case STATUS_PORT_OTHER: + type = POWER_SUPPLY_TYPE_USB_DCP; + break; + default: + type = POWER_SUPPLY_TYPE_USB; + break; + } + + if (status) { + chip->chg_present = true; + pr_debug("APSD complete. USB type detected=%d chg_present=%d\n", + type, chip->chg_present); + if (!chip->battery_missing && !chip->apsd_rerun) { + if (type == POWER_SUPPLY_TYPE_USB) { + pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n"); + prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, &prop); + chip->apsd_rerun = true; + rerun_apsd(chip); + return 0; + } + pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n"); + prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, &prop); + } + /* + * If defined force hvdcp 2p0 property, + * we force to hvdcp 2p0 in the APSD handler. + */ + if (chip->force_hvdcp_2p0) { + pr_debug("Force set to HVDCP 2.0 mode\n"); + smb1351_masked_write(chip, VARIOUS_FUNC_3_REG, + QC_2P1_AUTH_ALGO_BIT, 0); + smb1351_masked_write(chip, CMD_HVDCP_REG, + CMD_FORCE_HVDCP_2P0_BIT, + CMD_FORCE_HVDCP_2P0_BIT); + type = POWER_SUPPLY_TYPE_USB_HVDCP; + } else if (type == POWER_SUPPLY_TYPE_USB_DCP) { + pr_debug("schedule hvdcp detection worker\n"); + pm_stay_awake(chip->dev); + schedule_delayed_work(&chip->hvdcp_det_work, + msecs_to_jiffies(HVDCP_NOTIFY_MS)); + } + + prop.intval = type; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &prop); + /* + * SMB is now done sampling the D+/D- lines, + * indicate USB driver + */ + pr_debug("updating usb_psy present=%d\n", chip->chg_present); + prop.intval = chip->chg_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &prop); + chip->apsd_rerun = false; + } else if (!chip->apsd_rerun) { + /* Handle Charger removal */ + prop.intval = POWER_SUPPLY_TYPE_UNKNOWN; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &prop); + + chip->chg_present = false; + prop.intval = chip->chg_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &prop); + + pr_debug("Set usb psy dm=r df=r\n"); + prop.intval = POWER_SUPPLY_DP_DM_DPR_DMR; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, &prop); + } + + return 0; +} + +/* + * As source detect interrupt is not triggered on the falling edge, + * we need to schedule a work for checking source detect status after + * charger UV interrupt fired. + */ +#define FIRST_CHECK_DELAY 100 +#define SECOND_CHECK_DELAY 1000 +static void smb1351_chg_remove_work(struct work_struct *work) +{ + int rc; + u8 reg; + struct smb1351_charger *chip = container_of(work, + struct smb1351_charger, chg_remove_work.work); + + rc = smb1351_read_reg(chip, IRQ_G_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_G_REG rc = %d\n", rc); + goto end; + } + + if (!(reg & IRQ_SOURCE_DET_BIT)) { + pr_debug("chg removed\n"); + smb1351_apsd_complete_handler(chip, 0); + } else if (!chip->chg_remove_work_scheduled) { + chip->chg_remove_work_scheduled = true; + goto reschedule; + } else { + pr_debug("charger is present\n"); + } +end: + chip->chg_remove_work_scheduled = false; + pm_relax(chip->dev); + return; + +reschedule: + pr_debug("reschedule after 1s\n"); + schedule_delayed_work(&chip->chg_remove_work, + msecs_to_jiffies(SECOND_CHECK_DELAY)); +} + +static int smb1351_usbin_uv_handler(struct smb1351_charger *chip, u8 status) +{ + union power_supply_propval pval = {0, }; + + /* use this to detect USB insertion only if !apsd */ + if (chip->disable_apsd) { + /* + * If APSD is disabled, src det interrupt won't trigger. + * Hence use usbin_uv for removal and insertion notification + */ + if (status == 0) { + chip->chg_present = true; + pr_debug("updating usb_psy present=%d\n", + chip->chg_present); + pval.intval = POWER_SUPPLY_TYPE_USB; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + + pval.intval = chip->chg_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &pval); + } else { + chip->chg_present = false; + + pval.intval = POWER_SUPPLY_TYPE_UNKNOWN; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + + pr_debug("updating usb_psy present=%d\n", + chip->chg_present); + pval.intval = chip->chg_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &pval); + } + return 0; + } + + if (status) { + cancel_delayed_work_sync(&chip->hvdcp_det_work); + pm_relax(chip->dev); + pr_debug("schedule charger remove worker\n"); + schedule_delayed_work(&chip->chg_remove_work, + msecs_to_jiffies(FIRST_CHECK_DELAY)); + pm_stay_awake(chip->dev); + } + + pr_debug("chip->chg_present = %d\n", chip->chg_present); + + return 0; +} + +static int smb1351_usbin_ov_handler(struct smb1351_charger *chip, u8 status) +{ + int rc; + u8 reg; + union power_supply_propval pval = {0, }; + + rc = smb1351_read_reg(chip, IRQ_E_REG, ®); + if (rc) + pr_err("Couldn't read IRQ_E rc = %d\n", rc); + + if (status != 0) { + chip->chg_present = false; + chip->usbin_ov = true; + + pval.intval = POWER_SUPPLY_TYPE_UNKNOWN; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + + pval.intval = chip->chg_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &pval); + } else { + chip->usbin_ov = false; + if (reg & IRQ_USBIN_UV_BIT) + pr_debug("Charger unplugged from OV\n"); + else + smb1351_apsd_complete_handler(chip, 1); + } + + if (chip->usb_psy) { + pval.intval = status ? POWER_SUPPLY_HEALTH_OVERVOLTAGE + : POWER_SUPPLY_HEALTH_GOOD; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_HEALTH, &pval); + pr_debug("chip ov status is %d\n", pval.intval); + } + pr_debug("chip->chg_present = %d\n", chip->chg_present); + + return 0; +} + +static int smb1351_fast_chg_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("enter\n"); + return 0; +} + +static int smb1351_chg_term_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("enter\n"); + if (!chip->bms_controlled_charging) + chip->batt_full = !!status; + return 0; +} + +static int smb1351_safety_timeout_handler(struct smb1351_charger *chip, + u8 status) +{ + pr_debug("safety_timeout triggered\n"); + return 0; +} + +static int smb1351_aicl_done_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("aicl_done triggered\n"); + return 0; +} + +static int smb1351_hot_hard_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_hot = !!status; + return 0; +} +static int smb1351_cold_hard_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cold = !!status; + return 0; +} +static int smb1351_hot_soft_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_warm = !!status; + return 0; +} +static int smb1351_cold_soft_handler(struct smb1351_charger *chip, u8 status) +{ + pr_debug("status = 0x%02x\n", status); + chip->batt_cool = !!status; + return 0; +} + +static int smb1351_battery_missing_handler(struct smb1351_charger *chip, + u8 status) +{ + if (status) + chip->battery_missing = true; + else + chip->battery_missing = false; + + return 0; +} + +static struct irq_handler_info handlers[] = { + [0] = { + .stat_reg = IRQ_A_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "cold_soft", + .smb_irq = smb1351_cold_soft_handler, + }, + { .name = "hot_soft", + .smb_irq = smb1351_hot_soft_handler, + }, + { .name = "cold_hard", + .smb_irq = smb1351_cold_hard_handler, + }, + { .name = "hot_hard", + .smb_irq = smb1351_hot_hard_handler, + }, + }, + }, + [1] = { + .stat_reg = IRQ_B_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "internal_temp_limit", + }, + { .name = "vbatt_low", + }, + { .name = "battery_missing", + .smb_irq = smb1351_battery_missing_handler, + }, + { .name = "batt_therm_removed", + }, + }, + }, + [2] = { + .stat_reg = IRQ_C_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "chg_term", + .smb_irq = smb1351_chg_term_handler, + }, + { .name = "taper", + }, + { .name = "recharge", + }, + { .name = "fast_chg", + .smb_irq = smb1351_fast_chg_handler, + }, + }, + }, + [3] = { + .stat_reg = IRQ_D_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "prechg_timeout", + }, + { .name = "safety_timeout", + .smb_irq = smb1351_safety_timeout_handler, + }, + { .name = "chg_error", + }, + { .name = "batt_ov", + }, + }, + }, + [4] = { + .stat_reg = IRQ_E_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "power_ok", + }, + { .name = "afvc", + }, + { .name = "usbin_uv", + .smb_irq = smb1351_usbin_uv_handler, + }, + { .name = "usbin_ov", + .smb_irq = smb1351_usbin_ov_handler, + }, + }, + }, + [5] = { + .stat_reg = IRQ_F_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "otg_oc_retry", + }, + { .name = "rid", + }, + { .name = "otg_fail", + }, + { .name = "otg_oc", + }, + }, + }, + [6] = { + .stat_reg = IRQ_G_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "chg_inhibit", + }, + { .name = "aicl_fail", + }, + { .name = "aicl_done", + .smb_irq = smb1351_aicl_done_handler, + }, + { .name = "apsd_complete", + .smb_irq = smb1351_apsd_complete_handler, + }, + }, + }, + [7] = { + .stat_reg = IRQ_H_REG, + .val = 0, + .prev_val = 0, + .irq_info = { + { .name = "wdog_timeout", + }, + { .name = "hvdcp_auth_done", + }, + }, + }, +}; + +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BITS_PER_IRQ 2 +static irqreturn_t smb1351_chg_stat_handler(int irq, void *dev_id) +{ + struct smb1351_charger *chip = dev_id; + int i, j; + u8 triggered; + u8 changed; + u8 rt_stat, prev_rt_stat; + int rc; + int handler_count = 0; + + mutex_lock(&chip->irq_complete); + + chip->irq_waiting = true; + if (!chip->resume_completed) { + pr_debug("IRQ triggered before device-resume\n"); + disable_irq_nosync(irq); + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + rc = smb1351_read_reg(chip, handlers[i].stat_reg, + &handlers[i].val); + if (rc) { + pr_err("Couldn't read %d rc = %d\n", + handlers[i].stat_reg, rc); + continue; + } + + for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { + triggered = handlers[i].val + & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ)); + rt_stat = handlers[i].val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + prev_rt_stat = handlers[i].prev_val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + changed = prev_rt_stat ^ rt_stat; + + if (triggered || changed) + rt_stat ? handlers[i].irq_info[j].high++ : + handlers[i].irq_info[j].low++; + + if ((triggered || changed) + && handlers[i].irq_info[j].smb_irq != NULL) { + handler_count++; + rc = handlers[i].irq_info[j].smb_irq(chip, + rt_stat); + if (rc) + pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n", + j, handlers[i].stat_reg, rc); + } + } + handlers[i].prev_val = handlers[i].val; + } + + pr_debug("handler count = %d\n", handler_count); + if (handler_count) { + pr_debug("batt psy changed\n"); + power_supply_changed(chip->batt_psy); + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +static void smb1351_external_power_changed(struct power_supply *psy) +{ + struct smb1351_charger *chip = power_supply_get_drvdata(psy); + union power_supply_propval prop = {0,}; + int rc, current_limit = 0, online = 0; + + if (chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + if (rc) + pr_err("Couldn't read USB online property, rc=%d\n", rc); + else + online = prop.intval; + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (rc) + pr_err("Couldn't read USB current_max property, rc=%d\n", rc); + else + current_limit = prop.intval / 1000; + + pr_debug("online = %d, current_limit = %d\n", online, current_limit); + + smb1351_enable_volatile_writes(chip); + smb1351_set_usb_chg_current(chip, current_limit); + + pr_debug("updating batt psy\n"); +} + +#define LAST_CNFG_REG 0x16 +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cnfg_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_cnfg_regs, chip); +} + +static const struct file_operations cnfg_debugfs_ops = { + .owner = THIS_MODULE, + .open = cnfg_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_CMD_REG 0x30 +#define LAST_CMD_REG 0x34 +static int show_cmd_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cmd_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_cmd_regs, chip); +} + +static const struct file_operations cmd_debugfs_ops = { + .owner = THIS_MODULE, + .open = cmd_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_STATUS_REG 0x36 +#define LAST_STATUS_REG 0x3F +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb1351_charger *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int status_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_status_regs, chip); +} + +static const struct file_operations status_debugfs_ops = { + .owner = THIS_MODULE, + .open = status_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_irq_count(struct seq_file *m, void *data) +{ + int i, j, total = 0; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + for (j = 0; j < 4; j++) { + seq_printf(m, "%s=%d\t(high=%d low=%d)\n", + handlers[i].irq_info[j].name, + handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low, + handlers[i].irq_info[j].high, + handlers[i].irq_info[j].low); + total += (handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low); + } + + seq_printf(m, "\n\tTotal = %d\n", total); + + return 0; +} + +static int irq_count_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1351_charger *chip = inode->i_private; + + return single_open(file, show_irq_count, chip); +} + +static const struct file_operations irq_count_debugfs_ops = { + .owner = THIS_MODULE, + .open = irq_count_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int get_reg(void *data, u64 *val) +{ + struct smb1351_charger *chip = data; + int rc; + u8 temp; + + rc = smb1351_read_reg(chip, chip->peek_poke_address, &temp); + if (rc) { + pr_err("Couldn't read reg %x rc = %d\n", + chip->peek_poke_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + struct smb1351_charger *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = smb1351_write_reg(chip, chip->peek_poke_address, temp); + if (rc) { + pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n", + temp, chip->peek_poke_address, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); + +static int force_irq_set(void *data, u64 val) +{ + struct smb1351_charger *chip = data; + + smb1351_chg_stat_handler(chip->client->irq, data); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); + +#ifdef DEBUG +static void dump_regs(struct smb1351_charger *chip) +{ + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb1351_read_reg(chip, addr, ®); + if (rc) + pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } +} +#else +static void dump_regs(struct smb1351_charger *chip) +{ +} +#endif + +static int smb1351_parse_dt(struct smb1351_charger *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + + if (!node) { + pr_err("device tree info. missing\n"); + return -EINVAL; + } + + chip->usb_suspended_status = of_property_read_bool(node, + "qcom,charging-disabled"); + + chip->chg_autonomous_mode = of_property_read_bool(node, + "qcom,chg-autonomous-mode"); + + chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd"); + + chip->using_pmic_therm = of_property_read_bool(node, + "qcom,using-pmic-therm"); + chip->bms_controlled_charging = of_property_read_bool(node, + "qcom,bms-controlled-charging"); + chip->force_hvdcp_2p0 = of_property_read_bool(node, + "qcom,force-hvdcp-2p0"); + + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma", + &chip->target_fastchg_current_max_ma); + if (rc) + chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MAX_MA; + + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + + rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma); + if (rc) + chip->iterm_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc) + chip->vfloat_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,recharge-mv", + &chip->recharge_mv); + if (rc) + chip->recharge_mv = -EINVAL; + + chip->recharge_disabled = of_property_read_bool(node, + "qcom,recharge-disabled"); + + /* thermal and jeita support */ + rc = of_property_read_u32(node, "qcom,batt-cold-decidegc", + &chip->batt_cold_decidegc); + if (rc < 0) + chip->batt_cold_decidegc = -EINVAL; + + rc = of_property_read_u32(node, "qcom,batt-hot-decidegc", + &chip->batt_hot_decidegc); + if (rc < 0) + chip->batt_hot_decidegc = -EINVAL; + + rc = of_property_read_u32(node, "qcom,batt-warm-decidegc", + &chip->batt_warm_decidegc); + + rc |= of_property_read_u32(node, "qcom,batt-cool-decidegc", + &chip->batt_cool_decidegc); + + if (!rc) { + rc = of_property_read_u32(node, "qcom,batt-cool-mv", + &chip->batt_cool_mv); + + rc |= of_property_read_u32(node, "qcom,batt-warm-mv", + &chip->batt_warm_mv); + + rc |= of_property_read_u32(node, "qcom,batt-cool-ma", + &chip->batt_cool_ma); + + rc |= of_property_read_u32(node, "qcom,batt-warm-ma", + &chip->batt_warm_ma); + if (rc) + chip->jeita_supported = false; + else + chip->jeita_supported = true; + } + + pr_debug("jeita_supported = %d\n", chip->jeita_supported); + + rc = of_property_read_u32(node, "qcom,batt-missing-decidegc", + &chip->batt_missing_decidegc); + + chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL); + + return 0; +} + +static int smb1351_determine_initial_state(struct smb1351_charger *chip) +{ + int rc; + u8 reg = 0; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. Reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + rc = smb1351_read_reg(chip, IRQ_B_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_B rc = %d\n", rc); + goto fail_init_status; + } + + chip->battery_missing = (reg & IRQ_BATT_MISSING_BIT) ? true : false; + + rc = smb1351_read_reg(chip, IRQ_C_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_C rc = %d\n", rc); + goto fail_init_status; + } + chip->batt_full = (reg & IRQ_TERM_BIT) ? true : false; + + rc = smb1351_read_reg(chip, IRQ_A_REG, ®); + if (rc) { + pr_err("Couldn't read irq A rc = %d\n", rc); + return rc; + } + + if (reg & IRQ_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_COLD_HARD_BIT) + chip->batt_cold = true; + if (reg & IRQ_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_COLD_SOFT_BIT) + chip->batt_cool = true; + + rc = smb1351_read_reg(chip, IRQ_E_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_E rc = %d\n", rc); + goto fail_init_status; + } + + if (reg & IRQ_USBIN_UV_BIT) { + smb1351_usbin_uv_handler(chip, 1); + } else { + smb1351_usbin_uv_handler(chip, 0); + smb1351_apsd_complete_handler(chip, 1); + } + + rc = smb1351_read_reg(chip, IRQ_G_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_G rc = %d\n", rc); + goto fail_init_status; + } + + if (reg & IRQ_SOURCE_DET_BIT) + smb1351_apsd_complete_handler(chip, 1); + + return 0; + +fail_init_status: + pr_err("Couldn't determine initial status\n"); + return rc; +} + +static int is_parallel_charger(struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + + return of_property_read_bool(node, "qcom,parallel-charger"); +} + +static int create_debugfs_entries(struct smb1351_charger *chip) +{ + struct dentry *ent; + + chip->debug_root = debugfs_create_dir("smb1351", NULL); + if (!chip->debug_root) { + pr_err("Couldn't create debug dir\n"); + } else { + ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent) + pr_err("Couldn't create cnfg debug file\n"); + + ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent) + pr_err("Couldn't create status debug file\n"); + + ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent) + pr_err("Couldn't create cmd debug file\n"); + + ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->peek_poke_address)); + if (!ent) + pr_err("Couldn't create address debug file\n"); + + ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent) + pr_err("Couldn't create data debug file\n"); + + ent = debugfs_create_file("force_irq", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_irq_ops); + if (!ent) + pr_err("Couldn't create data debug file\n"); + + ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent) + pr_err("Couldn't create count debug file\n"); + } + return 0; +} + +static int smb1351_main_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb1351_charger *chip; + struct power_supply *usb_psy; + struct power_supply_config batt_psy_cfg = {}; + u8 reg = 0; + + usb_psy = power_supply_get_by_name("usb"); + if (!usb_psy) { + pr_debug("USB psy not found; deferring probe\n"); + return -EPROBE_DEFER; + } + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + pr_err("Couldn't allocate memory\n"); + return -ENOMEM; + } + + chip->client = client; + chip->dev = &client->dev; + chip->usb_psy = usb_psy; + chip->fake_battery_soc = -EINVAL; + INIT_DELAYED_WORK(&chip->chg_remove_work, smb1351_chg_remove_work); + INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb1351_hvdcp_det_work); + device_init_wakeup(chip->dev, true); + + /* probe the device to check if its actually connected */ + rc = smb1351_read_reg(chip, CHG_REVISION_REG, ®); + if (rc) { + pr_err("Failed to detect smb1351, device may be absent\n"); + return -ENODEV; + } + pr_debug("smb1351 chip revision is %d\n", reg); + + rc = smb1351_parse_dt(chip); + if (rc) { + pr_err("Couldn't parse DT nodes rc=%d\n", rc); + return rc; + } + + /* using vadc and adc_tm for implementing pmic therm */ + if (chip->using_pmic_therm) { + chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg"); + if (IS_ERR(chip->vadc_dev)) { + rc = PTR_ERR(chip->vadc_dev); + if (rc != -EPROBE_DEFER) + pr_err("vadc property missing\n"); + return rc; + } + chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg"); + if (IS_ERR(chip->adc_tm_dev)) { + rc = PTR_ERR(chip->adc_tm_dev); + if (rc != -EPROBE_DEFER) + pr_err("adc_tm property missing\n"); + return rc; + } + } + + i2c_set_clientdata(client, chip); + + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smb1351_battery_get_property; + chip->batt_psy_d.set_property = smb1351_battery_set_property; + chip->batt_psy_d.property_is_writeable = + smb1351_batt_property_is_writeable; + chip->batt_psy_d.properties = smb1351_battery_properties; + chip->batt_psy_d.num_properties = + ARRAY_SIZE(smb1351_battery_properties); + chip->batt_psy_d.external_power_changed = + smb1351_external_power_changed; + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + batt_psy_cfg.drv_data = chip; + batt_psy_cfg.supplied_to = pm_batt_supplied_to; + batt_psy_cfg.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to); + chip->batt_psy = devm_power_supply_register(chip->dev, + &chip->batt_psy_d, + &batt_psy_cfg); + if (IS_ERR(chip->batt_psy)) { + pr_err("Couldn't register batt psy rc=%ld\n", + PTR_ERR(chip->batt_psy)); + return rc; + } + + dump_regs(chip); + + rc = smb1351_regulator_init(chip); + if (rc) { + pr_err("Couldn't initialize smb1351 ragulator rc=%d\n", rc); + goto fail_smb1351_regulator_init; + } + + rc = smb1351_hw_init(chip); + if (rc) { + pr_err("Couldn't intialize hardware rc=%d\n", rc); + goto fail_smb1351_hw_init; + } + + rc = smb1351_determine_initial_state(chip); + if (rc) { + pr_err("Couldn't determine initial state rc=%d\n", rc); + goto fail_smb1351_hw_init; + } + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + smb1351_chg_stat_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "smb1351_chg_stat_irq", chip); + if (rc) { + pr_err("Failed STAT irq=%d request rc = %d\n", + client->irq, rc); + goto fail_smb1351_hw_init; + } + enable_irq_wake(client->irq); + } + + if (chip->using_pmic_therm) { + if (!chip->jeita_supported) { + /* add hot/cold temperature monitor */ + chip->adc_param.low_temp = chip->batt_cold_decidegc; + chip->adc_param.high_temp = chip->batt_hot_decidegc; + } else { + chip->adc_param.low_temp = chip->batt_cool_decidegc; + chip->adc_param.high_temp = chip->batt_warm_decidegc; + } + chip->adc_param.timer_interval = ADC_MEAS2_INTERVAL_1S; + chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE; + chip->adc_param.btm_ctx = chip; + chip->adc_param.threshold_notification = + smb1351_chg_adc_notification; + chip->adc_param.channel = LR_MUX1_BATT_THERM; + + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->adc_param); + if (rc) { + pr_err("requesting ADC error %d\n", rc); + goto fail_smb1351_hw_init; + } + } + + create_debugfs_entries(chip); + + dump_regs(chip); + + pr_info("smb1351 successfully probed. charger=%d, batt=%d version=%s\n", + chip->chg_present, + smb1351_get_prop_batt_present(chip), + smb1351_version_str[chip->version]); + return 0; + +fail_smb1351_hw_init: + regulator_unregister(chip->otg_vreg.rdev); +fail_smb1351_regulator_init: + return rc; +} + +static int smb1351_parallel_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb1351_charger *chip; + struct device_node *node = client->dev.of_node; + struct power_supply_config parallel_psy_cfg = {}; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + pr_err("Couldn't allocate memory\n"); + return -ENOMEM; + } + + chip->client = client; + chip->dev = &client->dev; + chip->parallel_charger = true; + + chip->usb_suspended_status = of_property_read_bool(node, + "qcom,charging-disabled"); + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc) + chip->vfloat_mv = -EINVAL; + rc = of_property_read_u32(node, "qcom,recharge-mv", + &chip->recharge_mv); + if (rc) + chip->recharge_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity", + &chip->parallel_pin_polarity_setting); + if (rc) + chip->parallel_pin_polarity_setting = EN_BY_PIN_LOW_ENABLE; + else + chip->parallel_pin_polarity_setting = + chip->parallel_pin_polarity_setting ? + EN_BY_PIN_HIGH_ENABLE : EN_BY_PIN_LOW_ENABLE; + + i2c_set_clientdata(client, chip); + + chip->parallel_psy_d.name = "usb-parallel"; + chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_USB_PARALLEL; + chip->parallel_psy_d.get_property = smb1351_parallel_get_property; + chip->parallel_psy_d.set_property = smb1351_parallel_set_property; + chip->parallel_psy_d.properties = smb1351_parallel_properties; + chip->parallel_psy_d.property_is_writeable + = smb1351_parallel_is_writeable; + chip->parallel_psy_d.num_properties + = ARRAY_SIZE(smb1351_parallel_properties); + + parallel_psy_cfg.drv_data = chip; + parallel_psy_cfg.num_supplicants = 0; + chip->parallel_psy = devm_power_supply_register(chip->dev, + &chip->parallel_psy_d, + ¶llel_psy_cfg); + if (IS_ERR(chip->parallel_psy)) { + pr_err("Couldn't register parallel psy rc=%ld\n", + PTR_ERR(chip->parallel_psy)); + return rc; + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + create_debugfs_entries(chip); + + pr_info("smb1351 parallel successfully probed.\n"); + + return 0; +} + +static int smb1351_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (is_parallel_charger(client)) + return smb1351_parallel_charger_probe(client, id); + else + return smb1351_main_charger_probe(client, id); +} + +static int smb1351_charger_remove(struct i2c_client *client) +{ + struct smb1351_charger *chip = i2c_get_clientdata(client); + + cancel_delayed_work_sync(&chip->chg_remove_work); + + mutex_destroy(&chip->irq_complete); + debugfs_remove_recursive(chip->debug_root); + return 0; +} + +static int smb1351_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + mutex_lock(&chip->irq_complete); + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + + return 0; +} + +static int smb1351_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int smb1351_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1351_charger *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + mutex_unlock(&chip->irq_complete); + smb1351_chg_stat_handler(client->irq, chip); + enable_irq(client->irq); + } else { + mutex_unlock(&chip->irq_complete); + } + return 0; +} + +static const struct dev_pm_ops smb1351_pm_ops = { + .suspend = smb1351_suspend, + .suspend_noirq = smb1351_suspend_noirq, + .resume = smb1351_resume, +}; + +static struct of_device_id smb1351_match_table[] = { + { .compatible = "qcom,smb1351-charger",}, + { }, +}; + +static const struct i2c_device_id smb1351_charger_id[] = { + {"smb1351-charger", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb1351_charger_id); + +static struct i2c_driver smb1351_charger_driver = { + .driver = { + .name = "smb1351-charger", + .owner = THIS_MODULE, + .of_match_table = smb1351_match_table, + .pm = &smb1351_pm_ops, + }, + .probe = smb1351_charger_probe, + .remove = smb1351_charger_remove, + .id_table = smb1351_charger_id, +}; + +module_i2c_driver(smb1351_charger_driver); + +MODULE_DESCRIPTION("smb1351 Charger"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb1351-charger"); diff --git a/drivers/power/qcom-charger/smb135x-charger.c b/drivers/power/qcom-charger/smb135x-charger.c new file mode 100644 index 000000000000..65d4ae56ff83 --- /dev/null +++ b/drivers/power/qcom-charger/smb135x-charger.c @@ -0,0 +1,4584 @@ +/* Copyright (c) 2013-2016 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 <linux/i2c.h> +#include <linux/debugfs.h> +#include <linux/gpio.h> +#include <linux/errno.h> +#include <linux/delay.h> +#include <linux/module.h> +#include <linux/interrupt.h> +#include <linux/slab.h> +#include <linux/power_supply.h> +#include <linux/of.h> +#include <linux/of_gpio.h> +#include <linux/bitops.h> +#include <linux/mutex.h> +#include <linux/regulator/driver.h> +#include <linux/regulator/of_regulator.h> +#include <linux/regulator/machine.h> +#include <linux/pinctrl/consumer.h> + +#define SMB135X_BITS_PER_REG 8 + +/* Mask/Bit helpers */ +#define _SMB135X_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB135X_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB135X_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Config registers */ +#define CFG_3_REG 0x03 +#define CHG_ITERM_50MA 0x08 +#define CHG_ITERM_100MA 0x10 +#define CHG_ITERM_150MA 0x18 +#define CHG_ITERM_200MA 0x20 +#define CHG_ITERM_250MA 0x28 +#define CHG_ITERM_300MA 0x00 +#define CHG_ITERM_500MA 0x30 +#define CHG_ITERM_600MA 0x38 +#define CHG_ITERM_MASK SMB135X_MASK(5, 3) + +#define CFG_4_REG 0x04 +#define CHG_INHIBIT_MASK SMB135X_MASK(7, 6) +#define CHG_INHIBIT_50MV_VAL 0x00 +#define CHG_INHIBIT_100MV_VAL 0x40 +#define CHG_INHIBIT_200MV_VAL 0x80 +#define CHG_INHIBIT_300MV_VAL 0xC0 + +#define CFG_5_REG 0x05 +#define RECHARGE_200MV_BIT BIT(2) +#define USB_2_3_BIT BIT(5) + +#define CFG_A_REG 0x0A +#define DCIN_INPUT_MASK SMB135X_MASK(4, 0) + +#define CFG_C_REG 0x0C +#define USBIN_INPUT_MASK SMB135X_MASK(4, 0) +#define USBIN_ADAPTER_ALLOWANCE_MASK SMB135X_MASK(7, 5) +#define ALLOW_5V_ONLY 0x00 +#define ALLOW_5V_OR_9V 0x20 +#define ALLOW_5V_TO_9V 0x40 +#define ALLOW_9V_ONLY 0x60 + +#define CFG_D_REG 0x0D + +#define CFG_E_REG 0x0E +#define POLARITY_100_500_BIT BIT(2) +#define USB_CTRL_BY_PIN_BIT BIT(1) +#define HVDCP_5_9_BIT BIT(4) + +#define CFG_11_REG 0x11 +#define PRIORITY_BIT BIT(7) +#define AUTO_SRC_DET_EN_BIT BIT(0) + +#define USBIN_DCIN_CFG_REG 0x12 +#define USBIN_SUSPEND_VIA_COMMAND_BIT BIT(6) + +#define CFG_14_REG 0x14 +#define CHG_EN_BY_PIN_BIT BIT(7) +#define CHG_EN_ACTIVE_LOW_BIT BIT(6) +#define CHG_EN_ACTIVE_HIGH_BIT 0x0 +#define PRE_TO_FAST_REQ_CMD_BIT BIT(5) +#define DISABLE_CURRENT_TERM_BIT BIT(3) +#define DISABLE_AUTO_RECHARGE_BIT BIT(2) +#define EN_CHG_INHIBIT_BIT BIT(0) + +#define CFG_16_REG 0x16 +#define SAFETY_TIME_EN_BIT BIT(5) +#define SAFETY_TIME_EN_SHIFT 5 +#define SAFETY_TIME_MINUTES_MASK SMB135X_MASK(3, 2) +#define SAFETY_TIME_MINUTES_SHIFT 2 + +#define CFG_17_REG 0x17 +#define CHG_STAT_DISABLE_BIT BIT(0) +#define CHG_STAT_ACTIVE_HIGH_BIT BIT(1) +#define CHG_STAT_IRQ_ONLY_BIT BIT(4) + +#define CFG_19_REG 0x19 +#define BATT_MISSING_ALGO_BIT BIT(2) +#define BATT_MISSING_THERM_BIT BIT(1) + +#define CFG_1A_REG 0x1A +#define HOT_SOFT_VFLOAT_COMP_EN_BIT BIT(3) +#define COLD_SOFT_VFLOAT_COMP_EN_BIT BIT(2) +#define HOT_SOFT_CURRENT_COMP_EN_BIT BIT(1) +#define COLD_SOFT_CURRENT_COMP_EN_BIT BIT(0) + +#define CFG_1B_REG 0x1B +#define COLD_HARD_MASK SMB135X_MASK(7, 6) +#define COLD_HARD_SHIFT 6 +#define HOT_HARD_MASK SMB135X_MASK(5, 4) +#define HOT_HARD_SHIFT 4 +#define COLD_SOFT_MASK SMB135X_MASK(3, 2) +#define COLD_SOFT_SHIFT 2 +#define HOT_SOFT_MASK SMB135X_MASK(1, 0) +#define HOT_SOFT_SHIFT 0 + +#define VFLOAT_REG 0x1E + +#define VERSION1_REG 0x2A +#define VERSION1_MASK SMB135X_MASK(7, 6) +#define VERSION1_SHIFT 6 +#define VERSION2_REG 0x32 +#define VERSION2_MASK SMB135X_MASK(1, 0) +#define VERSION3_REG 0x34 + +/* Irq Config registers */ +#define IRQ_CFG_REG 0x07 +#define IRQ_BAT_HOT_COLD_HARD_BIT BIT(7) +#define IRQ_BAT_HOT_COLD_SOFT_BIT BIT(6) +#define IRQ_OTG_OVER_CURRENT_BIT BIT(4) +#define IRQ_USBIN_UV_BIT BIT(2) +#define IRQ_INTERNAL_TEMPERATURE_BIT BIT(0) + +#define IRQ2_CFG_REG 0x08 +#define IRQ2_SAFETY_TIMER_BIT BIT(7) +#define IRQ2_CHG_ERR_BIT BIT(6) +#define IRQ2_CHG_PHASE_CHANGE_BIT BIT(4) +#define IRQ2_CHG_INHIBIT_BIT BIT(3) +#define IRQ2_POWER_OK_BIT BIT(2) +#define IRQ2_BATT_MISSING_BIT BIT(1) +#define IRQ2_VBAT_LOW_BIT BIT(0) + +#define IRQ3_CFG_REG 0x09 +#define IRQ3_RID_DETECT_BIT BIT(4) +#define IRQ3_SRC_DETECT_BIT BIT(2) +#define IRQ3_DCIN_UV_BIT BIT(0) + +#define USBIN_OTG_REG 0x0F +#define OTG_CNFG_MASK SMB135X_MASK(3, 2) +#define OTG_CNFG_PIN_CTRL 0x04 +#define OTG_CNFG_COMMAND_CTRL 0x08 +#define OTG_CNFG_AUTO_CTRL 0x0C + +/* Command Registers */ +#define CMD_I2C_REG 0x40 +#define ALLOW_VOLATILE_BIT BIT(6) + +#define CMD_INPUT_LIMIT 0x41 +#define USB_SHUTDOWN_BIT BIT(6) +#define DC_SHUTDOWN_BIT BIT(5) +#define USE_REGISTER_FOR_CURRENT BIT(2) +#define USB_100_500_AC_MASK SMB135X_MASK(1, 0) +#define USB_100_VAL 0x02 +#define USB_500_VAL 0x00 +#define USB_AC_VAL 0x01 + +#define CMD_CHG_REG 0x42 +#define CMD_CHG_EN BIT(1) +#define OTG_EN BIT(0) + +/* Status registers */ +#define STATUS_1_REG 0x47 +#define USING_USB_BIT BIT(1) +#define USING_DC_BIT BIT(0) + +#define STATUS_2_REG 0x48 +#define HARD_LIMIT_STS_BIT BIT(6) + +#define STATUS_4_REG 0x4A +#define BATT_NET_CHG_CURRENT_BIT BIT(7) +#define BATT_LESS_THAN_2V BIT(4) +#define CHG_HOLD_OFF_BIT BIT(3) +#define CHG_TYPE_MASK SMB135X_MASK(2, 1) +#define CHG_TYPE_SHIFT 1 +#define BATT_NOT_CHG_VAL 0x0 +#define BATT_PRE_CHG_VAL 0x1 +#define BATT_FAST_CHG_VAL 0x2 +#define BATT_TAPER_CHG_VAL 0x3 +#define CHG_EN_BIT BIT(0) + +#define STATUS_5_REG 0x4B +#define CDP_BIT BIT(7) +#define DCP_BIT BIT(6) +#define OTHER_BIT BIT(5) +#define SDP_BIT BIT(4) +#define ACA_A_BIT BIT(3) +#define ACA_B_BIT BIT(2) +#define ACA_C_BIT BIT(1) +#define ACA_DOCK_BIT BIT(0) + +#define STATUS_6_REG 0x4C +#define RID_FLOAT_BIT BIT(3) +#define RID_A_BIT BIT(2) +#define RID_B_BIT BIT(1) +#define RID_C_BIT BIT(0) + +#define STATUS_7_REG 0x4D + +#define STATUS_8_REG 0x4E +#define USBIN_9V BIT(5) +#define USBIN_UNREG BIT(4) +#define USBIN_LV BIT(3) +#define DCIN_9V BIT(2) +#define DCIN_UNREG BIT(1) +#define DCIN_LV BIT(0) + +#define STATUS_9_REG 0x4F +#define REV_MASK SMB135X_MASK(3, 0) + +/* Irq Status registers */ +#define IRQ_A_REG 0x50 +#define IRQ_A_HOT_HARD_BIT BIT(6) +#define IRQ_A_COLD_HARD_BIT BIT(4) +#define IRQ_A_HOT_SOFT_BIT BIT(2) +#define IRQ_A_COLD_SOFT_BIT BIT(0) + +#define IRQ_B_REG 0x51 +#define IRQ_B_BATT_TERMINAL_BIT BIT(6) +#define IRQ_B_BATT_MISSING_BIT BIT(4) +#define IRQ_B_VBAT_LOW_BIT BIT(2) +#define IRQ_B_TEMPERATURE_BIT BIT(0) + +#define IRQ_C_REG 0x52 +#define IRQ_C_TERM_BIT BIT(0) +#define IRQ_C_FASTCHG_BIT BIT(6) + +#define IRQ_D_REG 0x53 +#define IRQ_D_TIMEOUT_BIT BIT(2) + +#define IRQ_E_REG 0x54 +#define IRQ_E_DC_OV_BIT BIT(6) +#define IRQ_E_DC_UV_BIT BIT(4) +#define IRQ_E_USB_OV_BIT BIT(2) +#define IRQ_E_USB_UV_BIT BIT(0) + +#define IRQ_F_REG 0x55 +#define IRQ_F_POWER_OK_BIT BIT(0) + +#define IRQ_G_REG 0x56 +#define IRQ_G_SRC_DETECT_BIT BIT(6) + +enum { + WRKARND_USB100_BIT = BIT(0), + WRKARND_APSD_FAIL = BIT(1), +}; + +enum { + REV_1 = 1, /* Rev 1.0 */ + REV_1_1 = 2, /* Rev 1.1 */ + REV_2 = 3, /* Rev 2 */ + REV_2_1 = 5, /* Rev 2.1 */ + REV_MAX, +}; + +static char *revision_str[] = { + [REV_1] = "rev1", + [REV_1_1] = "rev1.1", + [REV_2] = "rev2", + [REV_2_1] = "rev2.1", +}; + +enum { + V_SMB1356, + V_SMB1357, + V_SMB1358, + V_SMB1359, + V_MAX, +}; + +static int version_data[] = { + [V_SMB1356] = V_SMB1356, + [V_SMB1357] = V_SMB1357, + [V_SMB1358] = V_SMB1358, + [V_SMB1359] = V_SMB1359, +}; + +static char *version_str[] = { + [V_SMB1356] = "smb1356", + [V_SMB1357] = "smb1357", + [V_SMB1358] = "smb1358", + [V_SMB1359] = "smb1359", +}; + +enum { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), +}; + +enum path_type { + USB, + DC, +}; + +static int chg_time[] = { + 192, + 384, + 768, + 1536, +}; + +static char *pm_batt_supplied_to[] = { + "bms", +}; + +struct smb135x_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +struct smb135x_chg { + struct i2c_client *client; + struct device *dev; + struct mutex read_write_lock; + + u8 revision; + int version; + + bool chg_enabled; + bool chg_disabled_permanently; + + bool usb_present; + bool dc_present; + bool usb_slave_present; + bool dc_ov; + + bool bmd_algo_disabled; + bool iterm_disabled; + int iterm_ma; + int vfloat_mv; + int safety_time; + int resume_delta_mv; + int fake_battery_soc; + struct dentry *debug_root; + int usb_current_arr_size; + int *usb_current_table; + int dc_current_arr_size; + int *dc_current_table; + bool inhibit_disabled; + int fastchg_current_arr_size; + int *fastchg_current_table; + int fastchg_ma; + u8 irq_cfg_mask[3]; + int otg_oc_count; + struct delayed_work reset_otg_oc_count_work; + struct mutex otg_oc_count_lock; + struct delayed_work hvdcp_det_work; + + bool parallel_charger; + bool parallel_charger_present; + bool bms_controlled_charging; + u32 parallel_pin_polarity_setting; + + /* psy */ + struct power_supply *usb_psy; + int usb_psy_ma; + int real_usb_psy_ma; + struct power_supply_desc batt_psy_d; + struct power_supply *batt_psy; + struct power_supply_desc dc_psy_d; + struct power_supply *dc_psy; + struct power_supply_desc parallel_psy_d; + struct power_supply *parallel_psy; + struct power_supply *bms_psy; + int dc_psy_type; + int dc_psy_ma; + const char *bms_psy_name; + + /* status tracking */ + bool chg_done_batt_full; + bool batt_present; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + + bool resume_completed; + bool irq_waiting; + u32 usb_suspended; + u32 dc_suspended; + struct mutex path_suspend_lock; + + u32 peek_poke_address; + struct smb135x_regulator otg_vreg; + int skip_writes; + int skip_reads; + u32 workaround_flags; + bool soft_vfloat_comp_disabled; + bool soft_current_comp_disabled; + struct mutex irq_complete; + struct regulator *therm_bias_vreg; + struct regulator *usb_pullup_vreg; + struct delayed_work wireless_insertion_work; + + unsigned int thermal_levels; + unsigned int therm_lvl_sel; + unsigned int *thermal_mitigation; + unsigned int gamma_setting_num; + unsigned int *gamma_setting; + struct mutex current_change_lock; + + const char *pinctrl_state_name; + struct pinctrl *smb_pinctrl; + + bool apsd_rerun; + bool id_line_not_connected; +}; + +#define RETRY_COUNT 5 +int retry_sleep_ms[RETRY_COUNT] = { + 10, 20, 30, 40, 50 +}; + +static int __smb135x_read(struct smb135x_chg *chip, int reg, + u8 *val) +{ + s32 ret; + int retry_count = 0; + +retry: + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0 && retry_count < RETRY_COUNT) { + /* sleep for few ms before retrying */ + msleep(retry_sleep_ms[retry_count++]); + goto retry; + } + if (ret < 0) { + dev_err(chip->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } else { + *val = ret; + } + + return 0; +} + +static int __smb135x_write(struct smb135x_chg *chip, int reg, + u8 val) +{ + s32 ret; + int retry_count = 0; + +retry: + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + if (ret < 0 && retry_count < RETRY_COUNT) { + /* sleep for few ms before retrying */ + msleep(retry_sleep_ms[retry_count++]); + goto retry; + } + if (ret < 0) { + dev_err(chip->dev, + "i2c write fail: can't write %02x to %02x: %d\n", + val, reg, ret); + return ret; + } + pr_debug("Writing 0x%02x=0x%02x\n", reg, val); + return 0; +} + +static int smb135x_read(struct smb135x_chg *chip, int reg, + u8 *val) +{ + int rc; + + if (chip->skip_reads) { + *val = 0; + return 0; + } + mutex_lock(&chip->read_write_lock); + pm_stay_awake(chip->dev); + rc = __smb135x_read(chip, reg, val); + pm_relax(chip->dev); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb135x_write(struct smb135x_chg *chip, int reg, + u8 val) +{ + int rc; + + if (chip->skip_writes) + return 0; + + mutex_lock(&chip->read_write_lock); + pm_stay_awake(chip->dev); + rc = __smb135x_write(chip, reg, val); + pm_relax(chip->dev); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb135x_masked_write(struct smb135x_chg *chip, int reg, + u8 mask, u8 val) +{ + s32 rc; + u8 temp; + + if (chip->skip_writes || chip->skip_reads) + return 0; + + mutex_lock(&chip->read_write_lock); + rc = __smb135x_read(chip, reg, &temp); + if (rc < 0) { + dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc); + goto out; + } + temp &= ~mask; + temp |= val & mask; + rc = __smb135x_write(chip, reg, temp); + if (rc < 0) { + dev_err(chip->dev, + "write failed: reg=%03X, rc=%d\n", reg, rc); + } +out: + mutex_unlock(&chip->read_write_lock); + return rc; +} + +static int read_revision(struct smb135x_chg *chip, u8 *revision) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, STATUS_9_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc); + return rc; + } + *revision = (reg & REV_MASK); + return 0; +} + +static int read_version1(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION1_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 1 rc = %d\n", rc); + return rc; + } + *version = (reg & VERSION1_MASK) >> VERSION1_SHIFT; + return 0; +} + +static int read_version2(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION2_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 2 rc = %d\n", rc); + return rc; + } + *version = (reg & VERSION2_MASK); + return 0; +} + +static int read_version3(struct smb135x_chg *chip, u8 *version) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, VERSION3_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version 3 rc = %d\n", rc); + return rc; + } + *version = reg; + return 0; +} + +#define TRIM_23_REG 0x23 +#define CHECK_USB100_GOOD_BIT BIT(1) +static bool is_usb100_broken(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, TRIM_23_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc); + return rc; + } + return !!(reg & CHECK_USB100_GOOD_BIT); +} + +static bool is_usb_slave_present(struct smb135x_chg *chip) +{ + bool usb_slave_present; + u8 reg; + int rc; + + if (chip->id_line_not_connected) + return false; + + rc = smb135x_read(chip, STATUS_6_REG, ®); + if (rc < 0) { + pr_err("Couldn't read stat 6 rc = %d\n", rc); + return false; + } + + if ((reg & (RID_FLOAT_BIT | RID_A_BIT | RID_B_BIT | RID_C_BIT)) == 0) + usb_slave_present = 1; + else + usb_slave_present = 0; + + pr_debug("stat6= 0x%02x slave_present = %d\n", reg, usb_slave_present); + return usb_slave_present; +} + +static char *usb_type_str[] = { + "ACA_DOCK", /* bit 0 */ + "ACA_C", /* bit 1 */ + "ACA_B", /* bit 2 */ + "ACA_A", /* bit 3 */ + "SDP", /* bit 4 */ + "OTHER", /* bit 5 */ + "DCP", /* bit 6 */ + "CDP", /* bit 7 */ + "NONE", /* bit 8 error case */ +}; + +/* helper to return the string of USB type */ +static char *get_usb_type_name(u8 stat_5) +{ + unsigned long stat = stat_5; + + return usb_type_str[find_first_bit(&stat, SMB135X_BITS_PER_REG)]; +} + +static enum power_supply_type usb_type_enum[] = { + POWER_SUPPLY_TYPE_USB_ACA, /* bit 0 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 1 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 2 */ + POWER_SUPPLY_TYPE_USB_ACA, /* bit 3 */ + POWER_SUPPLY_TYPE_USB, /* bit 4 */ + POWER_SUPPLY_TYPE_UNKNOWN, /* bit 5 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 6 */ + POWER_SUPPLY_TYPE_USB_CDP, /* bit 7 */ + POWER_SUPPLY_TYPE_UNKNOWN, /* bit 8 error case, report UNKNWON */ +}; + +/* helper to return enum power_supply_type of USB type */ +static enum power_supply_type get_usb_supply_type(u8 stat_5) +{ + unsigned long stat = stat_5; + + return usb_type_enum[find_first_bit(&stat, SMB135X_BITS_PER_REG)]; +} + +static enum power_supply_property smb135x_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, +}; + +static int smb135x_get_prop_batt_status(struct smb135x_chg *chip) +{ + int rc; + int status = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg = 0; + u8 chg_type; + + if (chip->chg_done_batt_full) + return POWER_SUPPLY_STATUS_FULL; + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Unable to read STATUS_4_REG rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + if (reg & CHG_HOLD_OFF_BIT) { + /* + * when chg hold off happens the battery is + * not charging + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto out; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + + if (chg_type == BATT_NOT_CHG_VAL) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; +out: + pr_debug("STATUS_4_REG=%x\n", reg); + return status; +} + +static int smb135x_get_prop_batt_present(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) + return 0; + + /* treat battery gone if less than 2V */ + if (reg & BATT_LESS_THAN_2V) + return 0; + + return chip->batt_present; +} + +static int smb135x_get_prop_charge_type(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + u8 chg_type; + + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + if (chg_type == BATT_NOT_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + else if (chg_type == BATT_FAST_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (chg_type == BATT_PRE_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + else if (chg_type == BATT_TAPER_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TAPER; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +#define DEFAULT_BATT_CAPACITY 50 +static int smb135x_get_prop_batt_capacity(struct smb135x_chg *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + return ret.intval; + } + + return DEFAULT_BATT_CAPACITY; +} + +static int smb135x_get_prop_batt_health(struct smb135x_chg *chip) +{ + union power_supply_propval ret = {0, }; + + if (chip->batt_hot) + ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + ret.intval = POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + ret.intval = POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + ret.intval = POWER_SUPPLY_HEALTH_COOL; + else + ret.intval = POWER_SUPPLY_HEALTH_GOOD; + + return ret.intval; +} + +static int smb135x_enable_volatile_writes(struct smb135x_chg *chip) +{ + int rc; + + rc = smb135x_masked_write(chip, CMD_I2C_REG, + ALLOW_VOLATILE_BIT, ALLOW_VOLATILE_BIT); + if (rc < 0) + dev_err(chip->dev, + "Couldn't set VOLATILE_W_PERM_BIT rc=%d\n", rc); + + return rc; +} + +static int usb_current_table_smb1356[] = { + 180, + 240, + 270, + 285, + 300, + 330, + 360, + 390, + 420, + 540, + 570, + 600, + 660, + 720, + 840, + 900, + 960, + 1080, + 1110, + 1128, + 1146, + 1170, + 1182, + 1200, + 1230, + 1260, + 1380, + 1440, + 1560, + 1620, + 1680, + 1800 +}; + +static int fastchg_current_table[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 2700, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 2800, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500, + 3000 +}; + +static int usb_current_table_smb1357_smb1358[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500, + 3000 +}; + +static int usb_current_table_smb1359[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500 +}; + +static int dc_current_table_smb1356[] = { + 180, + 240, + 270, + 285, + 300, + 330, + 360, + 390, + 420, + 540, + 570, + 600, + 660, + 720, + 840, + 870, + 900, + 960, + 1080, + 1110, + 1128, + 1146, + 1158, + 1170, + 1182, + 1200, +}; + +static int dc_current_table[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, +}; + +#define CURRENT_100_MA 100 +#define CURRENT_150_MA 150 +#define CURRENT_500_MA 500 +#define CURRENT_900_MA 900 +#define SUSPEND_CURRENT_MA 2 + +static int __smb135x_usb_suspend(struct smb135x_chg *chip, bool suspend) +{ + int rc; + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_SHUTDOWN_BIT, suspend ? USB_SHUTDOWN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + return rc; +} + +static int __smb135x_dc_suspend(struct smb135x_chg *chip, bool suspend) +{ + int rc = 0; + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + DC_SHUTDOWN_BIT, suspend ? DC_SHUTDOWN_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc); + return rc; +} + +static int smb135x_path_suspend(struct smb135x_chg *chip, enum path_type path, + int reason, bool suspend) +{ + int rc = 0; + int suspended; + int *path_suspended; + int (*func)(struct smb135x_chg *chip, bool suspend); + + mutex_lock(&chip->path_suspend_lock); + if (path == USB) { + suspended = chip->usb_suspended; + path_suspended = &chip->usb_suspended; + func = __smb135x_usb_suspend; + } else { + suspended = chip->dc_suspended; + path_suspended = &chip->dc_suspended; + func = __smb135x_dc_suspend; + } + + if (suspend == false) + suspended &= ~reason; + else + suspended |= reason; + + if (*path_suspended && !suspended) + rc = func(chip, 0); + if (!(*path_suspended) && suspended) + rc = func(chip, 1); + + if (rc) + dev_err(chip->dev, "Couldn't set/unset suspend for %s path rc = %d\n", + path == USB ? "usb" : "dc", + rc); + else + *path_suspended = suspended; + + mutex_unlock(&chip->path_suspend_lock); + return rc; +} + +static int smb135x_get_usb_chg_current(struct smb135x_chg *chip) +{ + if (chip->usb_suspended) + return SUSPEND_CURRENT_MA; + else + return chip->real_usb_psy_ma; +} +#define FCC_MASK SMB135X_MASK(4, 0) +#define CFG_1C_REG 0x1C +static int smb135x_get_fastchg_current(struct smb135x_chg *chip) +{ + u8 reg; + int rc; + + rc = smb135x_read(chip, CFG_1C_REG, ®); + if (rc < 0) { + pr_debug("cannot read 1c rc = %d\n", rc); + return 0; + } + reg &= FCC_MASK; + if (reg < 0 || chip->fastchg_current_arr_size == 0 + || reg > chip->fastchg_current_table[ + chip->fastchg_current_arr_size - 1]) { + dev_err(chip->dev, "Current table out of range\n"); + return -EINVAL; + } + return chip->fastchg_current_table[reg]; +} + +static int smb135x_set_fastchg_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc, diff, best, best_diff; + u8 reg; + + /* + * if there is no array loaded or if the smallest current limit is + * above the requested current, then do nothing + */ + if (chip->fastchg_current_arr_size == 0) { + dev_err(chip->dev, "no table loaded\n"); + return -EINVAL; + } else if ((current_ma - chip->fastchg_current_table[0]) < 0) { + dev_err(chip->dev, "invalid current requested\n"); + return -EINVAL; + } + + /* use the closest setting under the requested current */ + best = 0; + best_diff = current_ma - chip->fastchg_current_table[best]; + + for (i = 1; i < chip->fastchg_current_arr_size; i++) { + diff = current_ma - chip->fastchg_current_table[i]; + if (diff >= 0 && diff < best_diff) { + best_diff = diff; + best = i; + } + } + i = best; + + reg = i & FCC_MASK; + rc = smb135x_masked_write(chip, CFG_1C_REG, FCC_MASK, reg); + if (rc < 0) + dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); + pr_debug("fastchg current set to %dma\n", + chip->fastchg_current_table[i]); + return rc; +} + +static int smb135x_set_high_usb_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc; + u8 usb_cur_val; + + for (i = chip->usb_current_arr_size - 1; i >= 0; i--) { + if (current_ma >= chip->usb_current_table[i]) + break; + } + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_150_MA); + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_150_MA, rc); + else + chip->real_usb_psy_ma = CURRENT_150_MA; + return rc; + } + + usb_cur_val = i & USBIN_INPUT_MASK; + rc = smb135x_masked_write(chip, CFG_C_REG, + USBIN_INPUT_MASK, usb_cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); + return rc; + } + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_AC_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc); + else + chip->real_usb_psy_ma = chip->usb_current_table[i]; + return rc; +} + +#define MAX_VERSION 0xF +#define USB_100_PROBLEM_VERSION 0x2 +/* if APSD results are used + * if SDP is detected it will look at 500mA setting + * if set it will draw 500mA + * if unset it will draw 100mA + * if CDP/DCP it will look at 0x0C setting + * i.e. values in 0x41[1, 0] does not matter + */ +static int smb135x_set_usb_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int rc; + + pr_debug("USB current_ma = %d\n", current_ma); + + if (chip->workaround_flags & WRKARND_USB100_BIT) { + pr_info("USB requested = %dmA using %dmA\n", current_ma, + CURRENT_500_MA); + current_ma = CURRENT_500_MA; + } + + if (current_ma == 0) + /* choose the lowest available value of 100mA */ + current_ma = CURRENT_100_MA; + + if (current_ma == SUSPEND_CURRENT_MA) { + /* force suspend bit */ + rc = smb135x_path_suspend(chip, USB, CURRENT, true); + chip->real_usb_psy_ma = SUSPEND_CURRENT_MA; + goto out; + } + if (current_ma < CURRENT_150_MA) { + /* force 100mA */ + rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + chip->real_usb_psy_ma = CURRENT_100_MA; + goto out; + } + /* specific current values */ + if (current_ma == CURRENT_150_MA) { + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_100_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + chip->real_usb_psy_ma = CURRENT_150_MA; + goto out; + } + if (current_ma == CURRENT_500_MA) { + rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_500_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + chip->real_usb_psy_ma = CURRENT_500_MA; + goto out; + } + if (current_ma == CURRENT_900_MA) { + rc = smb135x_masked_write(chip, CFG_5_REG, + USB_2_3_BIT, USB_2_3_BIT); + rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USB_100_500_AC_MASK, USB_500_VAL); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); + chip->real_usb_psy_ma = CURRENT_900_MA; + goto out; + } + + rc = smb135x_set_high_usb_chg_current(chip, current_ma); + rc |= smb135x_path_suspend(chip, USB, CURRENT, false); +out: + if (rc < 0) + dev_err(chip->dev, + "Couldn't set %dmA rc = %d\n", current_ma, rc); + return rc; +} + +static int smb135x_set_dc_chg_current(struct smb135x_chg *chip, + int current_ma) +{ + int i, rc; + u8 dc_cur_val; + + for (i = chip->dc_current_arr_size - 1; i >= 0; i--) { + if (chip->dc_psy_ma >= chip->dc_current_table[i]) + break; + } + dc_cur_val = i & DCIN_INPUT_MASK; + rc = smb135x_masked_write(chip, CFG_A_REG, + DCIN_INPUT_MASK, dc_cur_val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n", + rc); + return rc; + } + return 0; +} + +static int smb135x_set_appropriate_current(struct smb135x_chg *chip, + enum path_type path) +{ + int therm_ma, current_ma; + int path_current = (path == USB) ? chip->usb_psy_ma : chip->dc_psy_ma; + int (*func)(struct smb135x_chg *chip, int current_ma); + int rc = 0; + + if (!chip->usb_psy && path == USB) + return 0; + + /* + * If battery is absent do not modify the current at all, these + * would be some appropriate values set by the bootloader or default + * configuration and since it is the only source of power we should + * not change it + */ + if (!chip->batt_present) { + pr_debug("ignoring current request since battery is absent\n"); + return 0; + } + + if (path == USB) { + path_current = chip->usb_psy_ma; + func = smb135x_set_usb_chg_current; + } else { + path_current = chip->dc_psy_ma; + func = smb135x_set_dc_chg_current; + if (chip->dc_psy_type == -EINVAL) + func = NULL; + } + + if (chip->therm_lvl_sel > 0 + && chip->therm_lvl_sel < (chip->thermal_levels - 1)) + /* + * consider thermal limit only when it is active and not at + * the highest level + */ + therm_ma = chip->thermal_mitigation[chip->therm_lvl_sel]; + else + therm_ma = path_current; + + current_ma = min(therm_ma, path_current); + if (func != NULL) + rc = func(chip, current_ma); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %s current to min(%d, %d)rc = %d\n", + path == USB ? "usb" : "dc", + therm_ma, path_current, + rc); + return rc; +} + +static int smb135x_charging_enable(struct smb135x_chg *chip, int enable) +{ + int rc; + + rc = smb135x_masked_write(chip, CMD_CHG_REG, + CMD_CHG_EN, enable ? CMD_CHG_EN : 0); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n", + enable, rc); + return rc; + } + + return 0; +} + +static int __smb135x_charging(struct smb135x_chg *chip, int enable) +{ + int rc = 0; + + pr_debug("charging enable = %d\n", enable); + + if (chip->chg_disabled_permanently) { + pr_debug("charging is disabled permanetly\n"); + return -EINVAL; + } + + rc = smb135x_charging_enable(chip, enable); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't %s charging rc = %d\n", + enable ? "enable" : "disable", rc); + return rc; + } + chip->chg_enabled = enable; + + /* set the suspended status */ + rc = smb135x_path_suspend(chip, DC, USER, !enable); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend to %d rc = %d\n", + enable, rc); + return rc; + } + rc = smb135x_path_suspend(chip, USB, USER, !enable); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend to %d rc = %d\n", + enable, rc); + return rc; + } + + pr_debug("charging %s\n", + enable ? "enabled" : "disabled running from batt"); + return rc; +} + +static int smb135x_charging(struct smb135x_chg *chip, int enable) +{ + int rc = 0; + + pr_debug("charging enable = %d\n", enable); + + __smb135x_charging(chip, enable); + + if (chip->usb_psy) { + pr_debug("usb psy changed\n"); + power_supply_changed(chip->usb_psy); + } + if (chip->dc_psy_type != -EINVAL) { + pr_debug("dc psy changed\n"); + power_supply_changed(chip->dc_psy); + } + pr_debug("charging %s\n", + enable ? "enabled" : "disabled running from batt"); + return rc; +} + +static int smb135x_system_temp_level_set(struct smb135x_chg *chip, + int lvl_sel) +{ + int rc = 0; + int prev_therm_lvl; + + if (!chip->thermal_mitigation) { + pr_err("Thermal mitigation not supported\n"); + return -EINVAL; + } + + if (lvl_sel < 0) { + pr_err("Unsupported level selected %d\n", lvl_sel); + return -EINVAL; + } + + if (lvl_sel >= chip->thermal_levels) { + pr_err("Unsupported level selected %d forcing %d\n", lvl_sel, + chip->thermal_levels - 1); + lvl_sel = chip->thermal_levels - 1; + } + + if (lvl_sel == chip->therm_lvl_sel) + return 0; + + mutex_lock(&chip->current_change_lock); + prev_therm_lvl = chip->therm_lvl_sel; + chip->therm_lvl_sel = lvl_sel; + if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) { + /* + * Disable charging if highest value selected by + * setting the DC and USB path in suspend + */ + rc = smb135x_path_suspend(chip, DC, THERMAL, true); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + goto out; + } + rc = smb135x_path_suspend(chip, USB, THERMAL, true); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + goto out; + } + goto out; + } + + smb135x_set_appropriate_current(chip, USB); + smb135x_set_appropriate_current(chip, DC); + + if (prev_therm_lvl == chip->thermal_levels - 1) { + /* + * If previously highest value was selected charging must have + * been disabed. Enable charging by taking the DC and USB path + * out of suspend. + */ + rc = smb135x_path_suspend(chip, DC, THERMAL, false); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set dc suspend rc %d\n", rc); + goto out; + } + rc = smb135x_path_suspend(chip, USB, THERMAL, false); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usb suspend rc %d\n", rc); + goto out; + } + } +out: + mutex_unlock(&chip->current_change_lock); + return rc; +} + +static int smb135x_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0, update_psy = 0; + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + if (!chip->bms_controlled_charging) { + rc = -EINVAL; + break; + } + switch (val->intval) { + case POWER_SUPPLY_STATUS_FULL: + rc = smb135x_charging_enable(chip, false); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable charging rc = %d\n", + rc); + } else { + chip->chg_done_batt_full = true; + update_psy = 1; + dev_dbg(chip->dev, "status = FULL chg_done_batt_full = %d", + chip->chg_done_batt_full); + } + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + chip->chg_done_batt_full = false; + update_psy = 1; + dev_dbg(chip->dev, "status = DISCHARGING chg_done_batt_full = %d", + chip->chg_done_batt_full); + break; + case POWER_SUPPLY_STATUS_CHARGING: + rc = smb135x_charging_enable(chip, true); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable charging rc = %d\n", + rc); + } else { + chip->chg_done_batt_full = false; + dev_dbg(chip->dev, "status = CHARGING chg_done_batt_full = %d", + chip->chg_done_batt_full); + } + break; + default: + update_psy = 0; + rc = -EINVAL; + } + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + smb135x_charging(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + update_psy = 1; + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smb135x_system_temp_level_set(chip, val->intval); + break; + default: + rc = -EINVAL; + } + + if (!rc && update_psy) + power_supply_changed(chip->batt_psy); + return rc; +} + +static int smb135x_battery_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int smb135x_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb135x_get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb135x_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = chip->chg_enabled; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = smb135x_get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = smb135x_get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb135x_get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_TECHNOLOGY: + val->intval = POWER_SUPPLY_TECHNOLOGY_LION; + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + val->intval = chip->therm_lvl_sel; + break; + default: + return -EINVAL; + } + return 0; +} + +static enum power_supply_property smb135x_dc_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_HEALTH, +}; + +static int smb135x_dc_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->dc_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->chg_enabled ? chip->dc_present : 0; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->dc_present; + break; + default: + return -EINVAL; + } + return 0; +} + +#define MIN_FLOAT_MV 3600 +#define MAX_FLOAT_MV 4500 + +#define MID_RANGE_FLOAT_MV_MIN 3600 +#define MID_RANGE_FLOAT_MIN_VAL 0x05 +#define MID_RANGE_FLOAT_STEP_MV 20 + +#define HIGH_RANGE_FLOAT_MIN_MV 4340 +#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A +#define HIGH_RANGE_FLOAT_STEP_MV 10 + +#define VHIGH_RANGE_FLOAT_MIN_MV 4400 +#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2E +#define VHIGH_RANGE_FLOAT_STEP_MV 20 +static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv) +{ + u8 temp; + + if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) { + dev_err(chip->dev, "bad float voltage mv =%d asked to set\n", + vfloat_mv); + return -EINVAL; + } + + if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) { + /* mid range */ + temp = MID_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - MID_RANGE_FLOAT_MV_MIN) + / MID_RANGE_FLOAT_STEP_MV; + } else if (vfloat_mv < VHIGH_RANGE_FLOAT_MIN_MV) { + /* high range */ + temp = HIGH_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV) + / HIGH_RANGE_FLOAT_STEP_MV; + } else { + /* very high range */ + temp = VHIGH_RANGE_FLOAT_MIN_VAL + + (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV) + / VHIGH_RANGE_FLOAT_STEP_MV; + } + + return smb135x_write(chip, VFLOAT_REG, temp); +} + +static int smb135x_set_resume_threshold(struct smb135x_chg *chip, + int resume_delta_mv) +{ + int rc; + u8 reg; + + if (!chip->inhibit_disabled) { + if (resume_delta_mv < 100) + reg = CHG_INHIBIT_50MV_VAL; + else if (resume_delta_mv < 200) + reg = CHG_INHIBIT_100MV_VAL; + else if (resume_delta_mv < 300) + reg = CHG_INHIBIT_200MV_VAL; + else + reg = CHG_INHIBIT_300MV_VAL; + + rc = smb135x_masked_write(chip, CFG_4_REG, CHG_INHIBIT_MASK, + reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n", + rc); + return rc; + } + } + + if (resume_delta_mv < 200) + reg = 0; + else + reg = RECHARGE_200MV_BIT; + + rc = smb135x_masked_write(chip, CFG_5_REG, RECHARGE_200MV_BIT, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", rc); + return rc; + } + return 0; +} + +static enum power_supply_property smb135x_parallel_properties[] = { + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, +}; + +static bool smb135x_is_input_current_limited(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + + rc = smb135x_read(chip, STATUS_2_REG, ®); + if (rc) { + pr_debug("Couldn't read _REG for ICL status rc = %d\n", rc); + return false; + } + + return !!(reg & HARD_LIMIT_STS_BIT); +} + +static int smb135x_parallel_set_chg_present(struct smb135x_chg *chip, + int present) +{ + u8 val; + int rc; + + if (present == chip->parallel_charger_present) { + pr_debug("present %d -> %d, skipping\n", + chip->parallel_charger_present, present); + return 0; + } + + if (present) { + /* Check if SMB135x is present */ + rc = smb135x_read(chip, VERSION1_REG, &val); + if (rc) { + pr_debug("Failed to detect smb135x-parallel charger may be absent\n"); + return -ENODEV; + } + + rc = smb135x_enable_volatile_writes(chip); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't configure for volatile rc = %d\n", + rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb135x_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", + rc); + return rc; + } + } + + /* resume threshold */ + if (chip->resume_delta_mv != -EINVAL) { + smb135x_set_resume_threshold(chip, + chip->resume_delta_mv); + } + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, + USE_REGISTER_FOR_CURRENT, + USE_REGISTER_FOR_CURRENT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set input limit cmd rc=%d\n", + rc); + return rc; + } + + /* set chg en by pin active low and enable auto recharge */ + rc = smb135x_masked_write(chip, CFG_14_REG, + CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT + | DISABLE_AUTO_RECHARGE_BIT, + CHG_EN_BY_PIN_BIT | + chip->parallel_pin_polarity_setting); + + /* set bit 0 = 100mA bit 1 = 500mA and set register control */ + rc = smb135x_masked_write(chip, CFG_E_REG, + POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT, + POLARITY_100_500_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set usbin cfg rc=%d\n", + rc); + return rc; + } + + /* control USB suspend via command bits */ + rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG, + USBIN_SUSPEND_VIA_COMMAND_BIT, + USBIN_SUSPEND_VIA_COMMAND_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg rc=%d\n", rc); + return rc; + } + + /* set the fastchg_current to the lowest setting */ + if (chip->fastchg_current_arr_size > 0) + rc = smb135x_set_fastchg_current(chip, + chip->fastchg_current_table[0]); + + /* + * enforce chip->chg_enabled since this could be the first + * time we have i2c access to the charger after + * chip->chg_enabled has been modified + */ + smb135x_charging(chip, chip->chg_enabled); + } + + chip->parallel_charger_present = present; + /* + * When present is being set force USB suspend, start charging + * only when CURRENT_MAX is set. + * + * Usually the chip will be shutdown (no i2c access to the chip) + * when USB is removed, however there could be situations when + * it is not. To cover for USB reinsetions in such situations + * force USB suspend when present is being unset. + * It is likely that i2c access could fail here - do not return error. + * (It is not possible to detect whether the chip is in shutdown state + * or not except for the i2c error). + */ + chip->usb_psy_ma = SUSPEND_CURRENT_MA; + rc = smb135x_path_suspend(chip, USB, CURRENT, true); + + if (present) { + if (rc) { + dev_err(chip->dev, + "Couldn't set usb suspend to true rc = %d\n", + rc); + return rc; + } + /* Check if the USB is configured for suspend. If not, do it */ + mutex_lock(&chip->path_suspend_lock); + rc = smb135x_read(chip, CMD_INPUT_LIMIT, &val); + if (rc) { + dev_err(chip->dev, + "Couldn't read 0x%02x rc:%d\n", CMD_INPUT_LIMIT, + rc); + mutex_unlock(&chip->path_suspend_lock); + return rc; + } else if (!(val & BIT(6))) { + rc = __smb135x_usb_suspend(chip, 1); + } + mutex_unlock(&chip->path_suspend_lock); + if (rc) { + dev_err(chip->dev, + "Couldn't set usb to suspend rc:%d\n", rc); + return rc; + } + } else { + chip->real_usb_psy_ma = SUSPEND_CURRENT_MA; + } + return 0; +} + +static int smb135x_parallel_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + if (chip->parallel_charger_present) + smb135x_charging(chip, val->intval); + else + chip->chg_enabled = val->intval; + break; + case POWER_SUPPLY_PROP_PRESENT: + rc = smb135x_parallel_set_chg_present(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + if (chip->parallel_charger_present) { + rc = smb135x_set_fastchg_current(chip, + val->intval / 1000); + } + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (chip->parallel_charger_present) { + chip->usb_psy_ma = val->intval / 1000; + rc = smb135x_set_usb_chg_current(chip, + chip->usb_psy_ma); + } + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (chip->parallel_charger_present && + (chip->vfloat_mv != val->intval)) { + rc = smb135x_float_voltage_set(chip, val->intval); + if (!rc) + chip->vfloat_mv = val->intval; + } else { + chip->vfloat_mv = val->intval; + } + break; + default: + return -EINVAL; + } + return rc; +} + +static int smb135x_parallel_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} +static int smb135x_parallel_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = chip->chg_enabled; + break; + case POWER_SUPPLY_PROP_CURRENT_MAX: + if (chip->parallel_charger_present) + val->intval = smb135x_get_usb_chg_current(chip) * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = chip->vfloat_mv; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->parallel_charger_present; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + if (chip->parallel_charger_present) + val->intval = smb135x_get_fastchg_current(chip) * 1000; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_STATUS: + if (chip->parallel_charger_present) + val->intval = smb135x_get_prop_batt_status(chip); + else + val->intval = POWER_SUPPLY_STATUS_DISCHARGING; + break; + case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED: + if (chip->parallel_charger_present) + val->intval = smb135x_is_input_current_limited(chip); + else + val->intval = 0; + break; + default: + return -EINVAL; + } + return 0; +} + +static void smb135x_external_power_changed(struct power_supply *psy) +{ + struct smb135x_chg *chip = power_supply_get_drvdata(psy); + union power_supply_propval prop = {0,}; + int rc, current_limit = 0; + + if (!chip->usb_psy) + return; + + if (chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_CURRENT_MAX, &prop); + if (rc < 0) + dev_err(chip->dev, + "could not read USB current_max property, rc=%d\n", rc); + else + current_limit = prop.intval / 1000; + + pr_debug("current_limit = %d\n", current_limit); + + if (chip->usb_psy_ma != current_limit) { + mutex_lock(&chip->current_change_lock); + chip->usb_psy_ma = current_limit; + rc = smb135x_set_appropriate_current(chip, USB); + mutex_unlock(&chip->current_change_lock); + if (rc < 0) + dev_err(chip->dev, "Couldn't set usb current rc = %d\n", + rc); + } + + rc = power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + if (rc < 0) + dev_err(chip->dev, + "could not read USB ONLINE property, rc=%d\n", rc); + + /* update online property */ + rc = 0; + if (chip->usb_present && chip->chg_enabled && chip->usb_psy_ma != 0) { + if (prop.intval == 0) { + prop.intval = 1; + rc = power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + } + } else { + if (prop.intval == 1) { + prop.intval = 0; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + } + } + if (rc < 0) + dev_err(chip->dev, "could not set usb online, rc=%d\n", rc); +} + +static bool elapsed_msec_greater(struct timeval *start_time, + struct timeval *end_time, int ms) +{ + int msec_elapsed; + + msec_elapsed = (end_time->tv_sec - start_time->tv_sec) * 1000 + + DIV_ROUND_UP(end_time->tv_usec - start_time->tv_usec, 1000); + + return (msec_elapsed > ms); +} + +#define MAX_STEP_MS 10 +static int smb135x_chg_otg_enable(struct smb135x_chg *chip) +{ + int rc = 0; + int restart_count = 0; + struct timeval time_a, time_b, time_c, time_d; + u8 reg; + + if (chip->revision == REV_2) { + /* + * Workaround for a hardware bug where the OTG needs to be + * enabled disabled and enabled for it to be actually enabled. + * The time between each step should be atmost MAX_STEP_MS + * + * Note that if enable-disable executes within the timeframe + * but the final enable takes more than MAX_STEP_ME, we treat + * it as the first enable and try disabling again. We don't + * want to issue enable back to back. + * + * Notice the instances when time is captured and the + * successive steps. + * timeA-enable-timeC-disable-timeB-enable-timeD. + * When + * (timeB - timeA) < MAX_STEP_MS AND + * (timeC - timeD) < MAX_STEP_MS + * then it is guaranteed that the successive steps + * must have executed within MAX_STEP_MS + */ + do_gettimeofday(&time_a); +restart_from_enable: + /* first step - enable otg */ + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", + rc); + return rc; + } + +restart_from_disable: + /* second step - disable otg */ + do_gettimeofday(&time_c); + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", + rc); + return rc; + } + do_gettimeofday(&time_b); + + if (elapsed_msec_greater(&time_a, &time_b, MAX_STEP_MS)) { + restart_count++; + if (restart_count > 10) { + dev_err(chip->dev, + "Couldn't enable OTG restart_count=%d\n", + restart_count); + return -EAGAIN; + } + time_a = time_b; + pr_debug("restarting from first enable\n"); + goto restart_from_enable; + } + + /* third step (first step in case of a failure) - enable otg */ + time_a = time_b; + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", + rc); + return rc; + } + do_gettimeofday(&time_d); + + if (elapsed_msec_greater(&time_c, &time_d, MAX_STEP_MS)) { + restart_count++; + if (restart_count > 10) { + dev_err(chip->dev, + "Couldn't enable OTG restart_count=%d\n", + restart_count); + return -EAGAIN; + } + pr_debug("restarting from disable\n"); + goto restart_from_disable; + } + } else { + rc = smb135x_read(chip, CMD_CHG_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read cmd reg rc=%d\n", + rc); + return rc; + } + if (reg & OTG_EN) { + /* if it is set, disable it before re-enabling it */ + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", + rc); + return rc; + } + } + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", + rc); + return rc; + } + } + + return rc; +} + +static int smb135x_chg_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + + chip->otg_oc_count = 0; + rc = smb135x_chg_otg_enable(chip); + if (rc) + dev_err(chip->dev, "Couldn't enable otg regulator rc=%d\n", rc); + + return rc; +} + +static int smb135x_chg_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + + mutex_lock(&chip->otg_oc_count_lock); + cancel_delayed_work_sync(&chip->reset_otg_oc_count_work); + mutex_unlock(&chip->otg_oc_count_lock); + rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc); + return rc; +} + +static int smb135x_chg_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + int rc = 0; + u8 reg = 0; + struct smb135x_chg *chip = rdev_get_drvdata(rdev); + + rc = smb135x_read(chip, CMD_CHG_REG, ®); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & OTG_EN) ? 1 : 0; +} + +struct regulator_ops smb135x_chg_otg_reg_ops = { + .enable = smb135x_chg_otg_regulator_enable, + .disable = smb135x_chg_otg_regulator_disable, + .is_enabled = smb135x_chg_otg_regulator_is_enable, +}; + +static int smb135x_set_current_tables(struct smb135x_chg *chip) +{ + switch (chip->version) { + case V_SMB1356: + chip->usb_current_table = usb_current_table_smb1356; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1356); + chip->dc_current_table = dc_current_table_smb1356; + chip->dc_current_arr_size + = ARRAY_SIZE(dc_current_table_smb1356); + chip->fastchg_current_table = NULL; + chip->fastchg_current_arr_size = 0; + break; + case V_SMB1357: + chip->usb_current_table = usb_current_table_smb1357_smb1358; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1357_smb1358); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + chip->fastchg_current_table = fastchg_current_table; + chip->fastchg_current_arr_size + = ARRAY_SIZE(fastchg_current_table); + break; + case V_SMB1358: + chip->usb_current_table = usb_current_table_smb1357_smb1358; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1357_smb1358); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + chip->fastchg_current_table = fastchg_current_table; + chip->fastchg_current_arr_size + = ARRAY_SIZE(fastchg_current_table); + break; + case V_SMB1359: + chip->usb_current_table = usb_current_table_smb1359; + chip->usb_current_arr_size + = ARRAY_SIZE(usb_current_table_smb1359); + chip->dc_current_table = dc_current_table; + chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table); + chip->fastchg_current_table = NULL; + chip->fastchg_current_arr_size = 0; + break; + } + return 0; +} + +#define SMB1356_VERSION3_BIT BIT(7) +#define SMB1357_VERSION1_VAL 0x01 +#define SMB1358_VERSION1_VAL 0x02 +#define SMB1359_VERSION1_VAL 0x00 +#define SMB1357_VERSION2_VAL 0x01 +#define SMB1358_VERSION2_VAL 0x02 +#define SMB1359_VERSION2_VAL 0x00 +static int smb135x_chip_version_and_revision(struct smb135x_chg *chip) +{ + int rc; + u8 version1, version2, version3; + + /* read the revision */ + rc = read_revision(chip, &chip->revision); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc); + return rc; + } + + if (chip->revision >= REV_MAX || revision_str[chip->revision] == NULL) { + dev_err(chip->dev, "Bad revision found = %d\n", chip->revision); + return -EINVAL; + } + + /* check if it is smb1356 */ + rc = read_version3(chip, &version3); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read version3 rc = %d\n", rc); + return rc; + } + + if (version3 & SMB1356_VERSION3_BIT) { + chip->version = V_SMB1356; + goto wrkarnd_and_input_current_values; + } + + /* check if it is smb1357, smb1358 or smb1359 based on revision */ + if (chip->revision <= REV_1_1) { + rc = read_version1(chip, &version1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read version 1 rc = %d\n", rc); + return rc; + } + switch (version1) { + case SMB1357_VERSION1_VAL: + chip->version = V_SMB1357; + break; + case SMB1358_VERSION1_VAL: + chip->version = V_SMB1358; + break; + case SMB1359_VERSION1_VAL: + chip->version = V_SMB1359; + break; + default: + dev_err(chip->dev, + "Unknown version 1 = 0x%02x rc = %d\n", + version1, rc); + return rc; + } + } else { + rc = read_version2(chip, &version2); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read version 2 rc = %d\n", rc); + return rc; + } + switch (version2) { + case SMB1357_VERSION2_VAL: + chip->version = V_SMB1357; + break; + case SMB1358_VERSION2_VAL: + chip->version = V_SMB1358; + break; + case SMB1359_VERSION2_VAL: + chip->version = V_SMB1359; + break; + default: + dev_err(chip->dev, + "Unknown version 2 = 0x%02x rc = %d\n", + version2, rc); + return rc; + } + } + +wrkarnd_and_input_current_values: + if (is_usb100_broken(chip)) + chip->workaround_flags |= WRKARND_USB100_BIT; + /* + * Rev v1.0 and v1.1 of SMB135x fails charger type detection + * (apsd) due to interference on the D+/- lines by the USB phy. + * Set the workaround flag to disable charger type reporting + * for this revision. + */ + if (chip->revision <= REV_1_1) + chip->workaround_flags |= WRKARND_APSD_FAIL; + + pr_debug("workaround_flags = %x\n", chip->workaround_flags); + + return smb135x_set_current_tables(chip); +} + +static int smb135x_regulator_init(struct smb135x_chg *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb135x_chg_otg_reg_ops; + chip->otg_vreg.rdesc.name = chip->dev->of_node->name; + chip->otg_vreg.rdesc.of_match = chip->dev->of_node->name; + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->otg_vreg.rdev = regulator_register(&chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "OTG reg failed, rc=%d\n", rc); + } + + return rc; +} + +static void smb135x_regulator_deinit(struct smb135x_chg *chip) +{ + if (chip->otg_vreg.rdev) + regulator_unregister(chip->otg_vreg.rdev); +} + +static void wireless_insertion_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + wireless_insertion_work.work); + + /* unsuspend dc */ + smb135x_path_suspend(chip, DC, CURRENT, false); +} + +static int hot_hard_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_hot = !!rt_stat; + return 0; +} +static int cold_hard_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_cold = !!rt_stat; + return 0; +} +static int hot_soft_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_warm = !!rt_stat; + return 0; +} +static int cold_soft_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_cool = !!rt_stat; + return 0; +} +static int battery_missing_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_present = !rt_stat; + return 0; +} +static int vbat_low_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("vbat low\n"); + return 0; +} +static int chg_hot_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("chg hot\n"); + return 0; +} +static int chg_term_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + + /* + * This handler gets called even when the charger based termination + * is disabled (due to change in RT status). However, in a bms + * controlled design the battery status should not be updated. + */ + if (!chip->iterm_disabled) + chip->chg_done_batt_full = !!rt_stat; + return 0; +} + +static int taper_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int fast_chg_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + + if (rt_stat & IRQ_C_FASTCHG_BIT) + chip->chg_done_batt_full = false; + + return 0; +} + +static int recharge_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + int rc; + + pr_debug("rt_stat = 0x%02x\n", rt_stat); + + if (chip->bms_controlled_charging) { + rc = smb135x_charging_enable(chip, true); + if (rc < 0) + dev_err(chip->dev, "Couldn't enable charging rc = %d\n", + rc); + } + + return 0; +} + +static int safety_timeout_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_warn("safety timeout rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +/** + * power_ok_handler() - called when the switcher turns on or turns off + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating switcher turning on or off + */ +static int power_ok_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + return 0; +} + +static int rid_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + bool usb_slave_present; + union power_supply_propval pval = {0, }; + + usb_slave_present = is_usb_slave_present(chip); + + if (chip->usb_slave_present ^ usb_slave_present) { + chip->usb_slave_present = usb_slave_present; + if (chip->usb_psy) { + pr_debug("setting usb psy usb_otg = %d\n", + chip->usb_slave_present); + pval.intval = chip->usb_slave_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_USB_OTG, &pval); + } + } + return 0; +} + +#define RESET_OTG_OC_COUNT_MS 100 +static void reset_otg_oc_count_work(struct work_struct *work) +{ + struct smb135x_chg *chip = + container_of(work, struct smb135x_chg, + reset_otg_oc_count_work.work); + + mutex_lock(&chip->otg_oc_count_lock); + pr_debug("It has been %dmS since OverCurrent interrupt resetting the count\n", + RESET_OTG_OC_COUNT_MS); + chip->otg_oc_count = 0; + mutex_unlock(&chip->otg_oc_count_lock); +} + +#define MAX_OTG_RETRY 3 +static int otg_oc_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + int rc; + + mutex_lock(&chip->otg_oc_count_lock); + cancel_delayed_work_sync(&chip->reset_otg_oc_count_work); + ++chip->otg_oc_count; + if (chip->otg_oc_count < MAX_OTG_RETRY) { + rc = smb135x_chg_otg_enable(chip); + if (rc < 0) + dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", + rc); + } else { + pr_warn_ratelimited("Tried enabling OTG %d times, the USB slave is nonconformant.\n", + chip->otg_oc_count); + } + + pr_debug("rt_stat = 0x%02x\n", rt_stat); + schedule_delayed_work(&chip->reset_otg_oc_count_work, + msecs_to_jiffies(RESET_OTG_OC_COUNT_MS)); + mutex_unlock(&chip->otg_oc_count_lock); + return 0; +} + +static int handle_dc_removal(struct smb135x_chg *chip) +{ + union power_supply_propval prop; + + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) { + cancel_delayed_work_sync(&chip->wireless_insertion_work); + smb135x_path_suspend(chip, DC, CURRENT, true); + } + if (chip->dc_psy_type != -EINVAL) { + prop.intval = chip->dc_present; + power_supply_set_property(chip->dc_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + } + return 0; +} + +#define DCIN_UNSUSPEND_DELAY_MS 1000 +static int handle_dc_insertion(struct smb135x_chg *chip) +{ + union power_supply_propval prop; + + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) + schedule_delayed_work(&chip->wireless_insertion_work, + msecs_to_jiffies(DCIN_UNSUSPEND_DELAY_MS)); + if (chip->dc_psy_type != -EINVAL) { + prop.intval = chip->dc_present; + power_supply_set_property(chip->dc_psy, + POWER_SUPPLY_PROP_ONLINE, &prop); + } + return 0; +} +/** + * dcin_uv_handler() - called when the dc voltage crosses the uv threshold + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating whether dc voltage is uv + */ +static int dcin_uv_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if dc is undervolted. If so dc_present + * should be marked removed + */ + bool dc_present = !rt_stat; + + pr_debug("chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + if (chip->dc_present && !dc_present) { + /* dc removed */ + chip->dc_present = dc_present; + handle_dc_removal(chip); + } + + if (!chip->dc_present && dc_present) { + /* dc inserted */ + chip->dc_present = dc_present; + handle_dc_insertion(chip); + } + + return 0; +} + +static int dcin_ov_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if dc is overvolted. If so dc_present + * should be marked removed + */ + bool dc_present = !rt_stat; + + pr_debug("chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + chip->dc_ov = !!rt_stat; + + if (chip->dc_present && !dc_present) { + /* dc removed */ + chip->dc_present = dc_present; + handle_dc_removal(chip); + } + + if (!chip->dc_present && dc_present) { + /* dc inserted */ + chip->dc_present = dc_present; + handle_dc_insertion(chip); + } + return 0; +} + +static int handle_usb_removal(struct smb135x_chg *chip) +{ + union power_supply_propval pval = {0,}; + + if (chip->usb_psy) { + cancel_delayed_work_sync(&chip->hvdcp_det_work); + pm_relax(chip->dev); + pr_debug("setting usb psy type = %d\n", + POWER_SUPPLY_TYPE_UNKNOWN); + pval.intval = POWER_SUPPLY_TYPE_UNKNOWN; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + + pr_debug("setting usb psy present = %d\n", chip->usb_present); + pval.intval = chip->usb_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &pval); + + pr_debug("Setting usb psy dp=r dm=r\n"); + pval.intval = POWER_SUPPLY_DP_DM_DPR_DMR; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, + &pval); + } + return 0; +} + +static int rerun_apsd(struct smb135x_chg *chip) +{ + int rc; + + pr_debug("Reruning APSD\nDisabling APSD\n"); + rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 0); + if (rc) { + dev_err(chip->dev, "Couldn't Disable APSD rc=%d\n", rc); + return rc; + } + pr_debug("Allow only 9V chargers\n"); + rc = smb135x_masked_write(chip, CFG_C_REG, + USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_9V_ONLY); + if (rc) + dev_err(chip->dev, "Couldn't Allow 9V rc=%d\n", rc); + pr_debug("Enabling APSD\n"); + rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 1); + if (rc) + dev_err(chip->dev, "Couldn't Enable APSD rc=%d\n", rc); + pr_debug("Allow 5V-9V\n"); + rc = smb135x_masked_write(chip, CFG_C_REG, + USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_5V_TO_9V); + if (rc) + dev_err(chip->dev, "Couldn't Allow 5V-9V rc=%d\n", rc); + return rc; +} + +static void smb135x_hvdcp_det_work(struct work_struct *work) +{ + int rc; + u8 reg; + struct smb135x_chg *chip = container_of(work, struct smb135x_chg, + hvdcp_det_work.work); + union power_supply_propval pval = {0,}; + + rc = smb135x_read(chip, STATUS_7_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc); + goto end; + } + pr_debug("STATUS_7_REG = 0x%02X\n", reg); + + if (reg) { + pr_debug("HVDCP detected; notifying USB PSY\n"); + pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + } +end: + pm_relax(chip->dev); +} + +#define HVDCP_NOTIFY_MS 2500 +static int handle_usb_insertion(struct smb135x_chg *chip) +{ + u8 reg; + int rc; + char *usb_type_name = "null"; + enum power_supply_type usb_supply_type; + union power_supply_propval pval = {0,}; + + /* usb inserted */ + rc = smb135x_read(chip, STATUS_5_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); + return rc; + } + /* + * Report the charger type as UNKNOWN if the + * apsd-fail flag is set. This nofifies the USB driver + * to initiate a s/w based charger type detection. + */ + if (chip->workaround_flags & WRKARND_APSD_FAIL) + reg = 0; + + usb_type_name = get_usb_type_name(reg); + usb_supply_type = get_usb_supply_type(reg); + pr_debug("inserted %s, usb psy type = %d stat_5 = 0x%02x apsd_rerun = %d\n", + usb_type_name, usb_supply_type, reg, chip->apsd_rerun); + + if (chip->batt_present && !chip->apsd_rerun && chip->usb_psy) { + if (usb_supply_type == POWER_SUPPLY_TYPE_USB) { + pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n"); + pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, + &pval); + chip->apsd_rerun = true; + rerun_apsd(chip); + /* rising edge of src detect will happen in few mS */ + return 0; + } else { + pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n"); + pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, + &pval); + } + } + + if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP) { + pr_debug("schedule hvdcp detection worker\n"); + pm_stay_awake(chip->dev); + schedule_delayed_work(&chip->hvdcp_det_work, + msecs_to_jiffies(HVDCP_NOTIFY_MS)); + } + + if (chip->usb_psy) { + if (chip->bms_controlled_charging) { + /* enable charging on USB insertion */ + rc = smb135x_charging_enable(chip, true); + if (rc < 0) + dev_err(chip->dev, "Couldn't enable charging rc = %d\n", + rc); + } + pr_debug("setting usb psy type = %d\n", usb_supply_type); + pval.intval = usb_supply_type; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_TYPE, &pval); + + pr_debug("setting usb psy present = %d\n", chip->usb_present); + pval.intval = chip->usb_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, + &pval); + } + chip->apsd_rerun = false; + return 0; +} + +/** + * usbin_uv_handler() + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static int usbin_uv_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * rt_stat indicates if usb is undervolted + */ + bool usb_present = !rt_stat; + + pr_debug("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + + return 0; +} + +static int usbin_ov_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + union power_supply_propval pval = {0, }; + /* + * rt_stat indicates if usb is overvolted. If so usb_present + * should be marked removed + */ + bool usb_present = !rt_stat; + + pr_debug("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + if (chip->usb_present && !usb_present) { + /* USB removed */ + chip->usb_present = usb_present; + handle_usb_removal(chip); + } else if (!chip->usb_present && usb_present) { + /* USB inserted */ + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } + + if (chip->usb_psy) { + pval.intval = rt_stat ? POWER_SUPPLY_HEALTH_OVERVOLTAGE + : POWER_SUPPLY_HEALTH_GOOD; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_HEALTH, &pval); + } + + return 0; +} + +/** + * src_detect_handler() - this is called on rising edge when USB + * charger type is detected and on falling edge when + * USB voltage falls below the coarse detect voltage + * (1V), use it for handling USB charger insertion + * and removal. + * @chip: pointer to smb135x_chg chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static int src_detect_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + bool usb_present = !!rt_stat; + + pr_debug("chip->usb_present = %d usb_present = %d\n", + chip->usb_present, usb_present); + + if (!chip->usb_present && usb_present) { + /* USB inserted */ + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } else if (usb_present && chip->apsd_rerun) { + handle_usb_insertion(chip); + } else if (chip->usb_present && !usb_present) { + chip->usb_present = !chip->usb_present; + handle_usb_removal(chip); + } + + return 0; +} + +static int chg_inhibit_handler(struct smb135x_chg *chip, u8 rt_stat) +{ + /* + * charger is inserted when the battery voltage is high + * so h/w won't start charging just yet. Treat this as + * battery full + */ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + + if (!chip->inhibit_disabled) + chip->chg_done_batt_full = !!rt_stat; + return 0; +} + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb135x_chg *chip, + u8 rt_stat); + int high; + int low; +}; + +struct irq_handler_info { + u8 stat_reg; + u8 val; + u8 prev_val; + struct smb_irq_info irq_info[4]; +}; + +static struct irq_handler_info handlers[] = { + {IRQ_A_REG, 0, 0, + { + { + .name = "cold_soft", + .smb_irq = cold_soft_handler, + }, + { + .name = "hot_soft", + .smb_irq = hot_soft_handler, + }, + { + .name = "cold_hard", + .smb_irq = cold_hard_handler, + }, + { + .name = "hot_hard", + .smb_irq = hot_hard_handler, + }, + }, + }, + {IRQ_B_REG, 0, 0, + { + { + .name = "chg_hot", + .smb_irq = chg_hot_handler, + }, + { + .name = "vbat_low", + .smb_irq = vbat_low_handler, + }, + { + .name = "battery_missing", + .smb_irq = battery_missing_handler, + }, + { + .name = "battery_missing", + .smb_irq = battery_missing_handler, + }, + }, + }, + {IRQ_C_REG, 0, 0, + { + { + .name = "chg_term", + .smb_irq = chg_term_handler, + }, + { + .name = "taper", + .smb_irq = taper_handler, + }, + { + .name = "recharge", + .smb_irq = recharge_handler, + }, + { + .name = "fast_chg", + .smb_irq = fast_chg_handler, + }, + }, + }, + {IRQ_D_REG, 0, 0, + { + { + .name = "prechg_timeout", + }, + { + .name = "safety_timeout", + .smb_irq = safety_timeout_handler, + }, + { + .name = "aicl_done", + }, + { + .name = "battery_ov", + }, + }, + }, + {IRQ_E_REG, 0, 0, + { + { + .name = "usbin_uv", + .smb_irq = usbin_uv_handler, + }, + { + .name = "usbin_ov", + .smb_irq = usbin_ov_handler, + }, + { + .name = "dcin_uv", + .smb_irq = dcin_uv_handler, + }, + { + .name = "dcin_ov", + .smb_irq = dcin_ov_handler, + }, + }, + }, + {IRQ_F_REG, 0, 0, + { + { + .name = "power_ok", + .smb_irq = power_ok_handler, + }, + { + .name = "rid", + .smb_irq = rid_handler, + }, + { + .name = "otg_fail", + }, + { + .name = "otg_oc", + .smb_irq = otg_oc_handler, + }, + }, + }, + {IRQ_G_REG, 0, 0, + { + { + .name = "chg_inhibit", + .smb_irq = chg_inhibit_handler, + }, + { + .name = "chg_error", + }, + { + .name = "wd_timeout", + }, + { + .name = "src_detect", + .smb_irq = src_detect_handler, + }, + }, + }, +}; + +static int smb135x_irq_read(struct smb135x_chg *chip) +{ + int rc, i; + + /* + * When dcin path is suspended the irq triggered status is not cleared + * causing a storm. To prevent this situation unsuspend dcin path while + * reading interrupts and restore its status back. + */ + mutex_lock(&chip->path_suspend_lock); + + if (chip->dc_suspended) + __smb135x_dc_suspend(chip, false); + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + rc = smb135x_read(chip, handlers[i].stat_reg, + &handlers[i].val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read %d rc = %d\n", + handlers[i].stat_reg, rc); + handlers[i].val = 0; + continue; + } + } + + if (chip->dc_suspended) + __smb135x_dc_suspend(chip, true); + + mutex_unlock(&chip->path_suspend_lock); + + return rc; +} +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BITS_PER_IRQ 2 +static irqreturn_t smb135x_chg_stat_handler(int irq, void *dev_id) +{ + struct smb135x_chg *chip = dev_id; + int i, j; + u8 triggered; + u8 changed; + u8 rt_stat, prev_rt_stat; + int rc; + int handler_count = 0; + + mutex_lock(&chip->irq_complete); + chip->irq_waiting = true; + if (!chip->resume_completed) { + dev_dbg(chip->dev, "IRQ triggered before device-resume\n"); + disable_irq_nosync(irq); + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + smb135x_irq_read(chip); + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { + triggered = handlers[i].val + & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ)); + rt_stat = handlers[i].val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + prev_rt_stat = handlers[i].prev_val + & (IRQ_STATUS_MASK << (j * BITS_PER_IRQ)); + changed = prev_rt_stat ^ rt_stat; + + if (triggered || changed) + rt_stat ? handlers[i].irq_info[j].high++ : + handlers[i].irq_info[j].low++; + + if ((triggered || changed) + && handlers[i].irq_info[j].smb_irq != NULL) { + handler_count++; + rc = handlers[i].irq_info[j].smb_irq(chip, + rt_stat); + if (rc < 0) + dev_err(chip->dev, + "Couldn't handle %d irq for reg 0x%02x rc = %d\n", + j, handlers[i].stat_reg, rc); + } + } + handlers[i].prev_val = handlers[i].val; + } + + pr_debug("handler count = %d\n", handler_count); + if (handler_count) { + pr_debug("batt psy changed\n"); + power_supply_changed(chip->batt_psy); + if (chip->usb_psy) { + pr_debug("usb psy changed\n"); + power_supply_changed(chip->usb_psy); + } + if (chip->dc_psy_type != -EINVAL) { + pr_debug("dc psy changed\n"); + power_supply_changed(chip->dc_psy); + } + } + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +#define LAST_CNFG_REG 0x1F +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cnfg_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_cnfg_regs, chip); +} + +static const struct file_operations cnfg_debugfs_ops = { + .owner = THIS_MODULE, + .open = cnfg_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_CMD_REG 0x40 +#define LAST_CMD_REG 0x42 +static int show_cmd_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int cmd_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_cmd_regs, chip); +} + +static const struct file_operations cmd_debugfs_ops = { + .owner = THIS_MODULE, + .open = cmd_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define FIRST_STATUS_REG 0x46 +#define LAST_STATUS_REG 0x56 +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb135x_chg *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int status_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_status_regs, chip); +} + +static const struct file_operations status_debugfs_ops = { + .owner = THIS_MODULE, + .open = status_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_irq_count(struct seq_file *m, void *data) +{ + int i, j, total = 0; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) + for (j = 0; j < 4; j++) { + seq_printf(m, "%s=%d\t(high=%d low=%d)\n", + handlers[i].irq_info[j].name, + handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low, + handlers[i].irq_info[j].high, + handlers[i].irq_info[j].low); + total += (handlers[i].irq_info[j].high + + handlers[i].irq_info[j].low); + } + + seq_printf(m, "\n\tTotal = %d\n", total); + + return 0; +} + +static int irq_count_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb135x_chg *chip = inode->i_private; + + return single_open(file, show_irq_count, chip); +} + +static const struct file_operations irq_count_debugfs_ops = { + .owner = THIS_MODULE, + .open = irq_count_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int get_reg(void *data, u64 *val) +{ + struct smb135x_chg *chip = data; + int rc; + u8 temp; + + rc = smb135x_read(chip, chip->peek_poke_address, &temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read reg %x rc = %d\n", + chip->peek_poke_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int set_reg(void *data, u64 val) +{ + struct smb135x_chg *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = smb135x_write(chip, chip->peek_poke_address, temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't write 0x%02x to 0x%02x rc= %d\n", + chip->peek_poke_address, temp, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); + +static int force_irq_set(void *data, u64 val) +{ + struct smb135x_chg *chip = data; + + smb135x_chg_stat_handler(chip->client->irq, data); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n"); + +static int force_rechg_set(void *data, u64 val) +{ + int rc = 0; + struct smb135x_chg *chip = data; + + if (!chip->chg_enabled) { + pr_debug("Charging Disabled force recharge not allowed\n"); + return -EINVAL; + } + + if (!chip->inhibit_disabled) { + rc = smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT, + 0); + if (rc) + dev_err(chip->dev, + "Couldn't disable charge-inhibit rc=%d\n", rc); + + /* delay for charge-inhibit to take affect */ + msleep(500); + } + + rc |= smb135x_charging(chip, false); + rc |= smb135x_charging(chip, true); + + if (!chip->inhibit_disabled) { + rc |= smb135x_masked_write(chip, CFG_14_REG, + EN_CHG_INHIBIT_BIT, EN_CHG_INHIBIT_BIT); + if (rc) + dev_err(chip->dev, + "Couldn't enable charge-inhibit rc=%d\n", rc); + } + + return rc; +} +DEFINE_SIMPLE_ATTRIBUTE(force_rechg_ops, NULL, force_rechg_set, "0x%02llx\n"); + +#ifdef DEBUG +static void dump_regs(struct smb135x_chg *chip) +{ + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb135x_read(chip, addr, ®); + if (rc < 0) + dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n", + addr, rc); + else + pr_debug("0x%02x = 0x%02x\n", addr, reg); + } +} +#else +static void dump_regs(struct smb135x_chg *chip) +{ +} +#endif +static int determine_initial_status(struct smb135x_chg *chip) +{ + union power_supply_propval pval = {0, }; + int rc; + u8 reg; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + chip->batt_present = true; + rc = smb135x_read(chip, IRQ_B_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq b rc = %d\n", rc); + return rc; + } + if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT) + chip->batt_present = false; + rc = smb135x_read(chip, STATUS_4_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 4 rc = %d\n", rc); + return rc; + } + /* treat battery gone if less than 2V */ + if (reg & BATT_LESS_THAN_2V) + chip->batt_present = false; + + rc = smb135x_read(chip, IRQ_A_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + + if (reg & IRQ_A_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_A_COLD_HARD_BIT) + chip->batt_cold = true; + if (reg & IRQ_A_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_A_COLD_SOFT_BIT) + chip->batt_cool = true; + + rc = smb135x_read(chip, IRQ_C_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + if (reg & IRQ_C_TERM_BIT) + chip->chg_done_batt_full = true; + + rc = smb135x_read(chip, IRQ_E_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc); + return rc; + } + chip->usb_present = !(reg & IRQ_E_USB_OV_BIT) + && !(reg & IRQ_E_USB_UV_BIT); + chip->dc_present = !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT); + + if (chip->usb_present) + handle_usb_insertion(chip); + else + handle_usb_removal(chip); + + if (chip->dc_psy_type != -EINVAL) { + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) { + /* + * put the dc path in suspend state if it is powered + * by wireless charger + */ + if (chip->dc_present) + smb135x_path_suspend(chip, DC, CURRENT, false); + else + smb135x_path_suspend(chip, DC, CURRENT, true); + } + } + + chip->usb_slave_present = is_usb_slave_present(chip); + if (chip->usb_psy && !chip->id_line_not_connected) { + pr_debug("setting usb psy usb_otg = %d\n", + chip->usb_slave_present); + pval.intval = chip->usb_slave_present; + power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_USB_OTG, &pval); + } + return 0; +} + +static int smb135x_hw_init(struct smb135x_chg *chip) +{ + int rc; + int i; + u8 reg, mask; + + if (chip->pinctrl_state_name) { + chip->smb_pinctrl = pinctrl_get_select(chip->dev, + chip->pinctrl_state_name); + if (IS_ERR(chip->smb_pinctrl)) { + pr_err("Could not get/set %s pinctrl state rc = %ld\n", + chip->pinctrl_state_name, + PTR_ERR(chip->smb_pinctrl)); + return PTR_ERR(chip->smb_pinctrl); + } + } + + if (chip->therm_bias_vreg) { + rc = regulator_enable(chip->therm_bias_vreg); + if (rc) { + pr_err("Couldn't enable therm-bias rc = %d\n", rc); + return rc; + } + } + + /* + * Enable USB data line pullup regulator this is needed for the D+ + * line to be at proper voltage for HVDCP charger detection. + */ + if (chip->usb_pullup_vreg) { + rc = regulator_enable(chip->usb_pullup_vreg); + if (rc) { + pr_err("Unable to enable data line pull-up regulator rc=%d\n", + rc); + if (chip->therm_bias_vreg) + regulator_disable(chip->therm_bias_vreg); + return rc; + } + } + + rc = smb135x_enable_volatile_writes(chip); + if (rc < 0) { + dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n", + rc); + goto free_regulator; + } + + /* + * force using current from the register i.e. ignore auto + * power source detect (APSD) mA ratings + */ + mask = USE_REGISTER_FOR_CURRENT; + + if (chip->workaround_flags & WRKARND_USB100_BIT) + reg = 0; + else + /* this ignores APSD results */ + reg = USE_REGISTER_FOR_CURRENT; + + rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc); + goto free_regulator; + } + + /* set bit 0 = 100mA bit 1 = 500mA and set register control */ + rc = smb135x_masked_write(chip, CFG_E_REG, + POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT, + POLARITY_100_500_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set usbin cfg rc=%d\n", rc); + goto free_regulator; + } + + /* + * set chg en by cmd register, set chg en by writing bit 1, + * enable auto pre to fast, enable current termination, enable + * auto recharge, enable chg inhibition based on the dt flag + */ + if (chip->inhibit_disabled) + reg = 0; + else + reg = EN_CHG_INHIBIT_BIT; + + rc = smb135x_masked_write(chip, CFG_14_REG, + CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT + | PRE_TO_FAST_REQ_CMD_BIT | DISABLE_AUTO_RECHARGE_BIT + | EN_CHG_INHIBIT_BIT, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set cfg 14 rc=%d\n", rc); + goto free_regulator; + } + + /* control USB suspend via command bits */ + rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG, + USBIN_SUSPEND_VIA_COMMAND_BIT, USBIN_SUSPEND_VIA_COMMAND_BIT); + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb135x_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + goto free_regulator; + } + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); + rc = -EINVAL; + goto free_regulator; + } else { + if (chip->iterm_ma <= 50) + reg = CHG_ITERM_50MA; + else if (chip->iterm_ma <= 100) + reg = CHG_ITERM_100MA; + else if (chip->iterm_ma <= 150) + reg = CHG_ITERM_150MA; + else if (chip->iterm_ma <= 200) + reg = CHG_ITERM_200MA; + else if (chip->iterm_ma <= 250) + reg = CHG_ITERM_250MA; + else if (chip->iterm_ma <= 300) + reg = CHG_ITERM_300MA; + else if (chip->iterm_ma <= 500) + reg = CHG_ITERM_500MA; + else + reg = CHG_ITERM_600MA; + + rc = smb135x_masked_write(chip, CFG_3_REG, + CHG_ITERM_MASK, reg); + if (rc) { + dev_err(chip->dev, + "Couldn't set iterm rc = %d\n", rc); + goto free_regulator; + } + + rc = smb135x_masked_write(chip, CFG_14_REG, + DISABLE_CURRENT_TERM_BIT, 0); + if (rc) { + dev_err(chip->dev, + "Couldn't enable iterm rc = %d\n", rc); + goto free_regulator; + } + } + } else if (chip->iterm_disabled) { + rc = smb135x_masked_write(chip, CFG_14_REG, + DISABLE_CURRENT_TERM_BIT, + DISABLE_CURRENT_TERM_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set iterm rc = %d\n", + rc); + goto free_regulator; + } + } + + /* set the safety time voltage */ + if (chip->safety_time != -EINVAL) { + if (chip->safety_time == 0) { + /* safety timer disabled */ + reg = 1 << SAFETY_TIME_EN_SHIFT; + rc = smb135x_masked_write(chip, CFG_16_REG, + SAFETY_TIME_EN_BIT, reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't disable safety timer rc = %d\n", + rc); + goto free_regulator; + } + } else { + for (i = 0; i < ARRAY_SIZE(chg_time); i++) { + if (chip->safety_time <= chg_time[i]) { + reg = i << SAFETY_TIME_MINUTES_SHIFT; + break; + } + } + rc = smb135x_masked_write(chip, CFG_16_REG, + SAFETY_TIME_EN_BIT | SAFETY_TIME_MINUTES_MASK, + reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set safety timer rc = %d\n", + rc); + goto free_regulator; + } + } + } + + /* battery missing detection */ + rc = smb135x_masked_write(chip, CFG_19_REG, + BATT_MISSING_ALGO_BIT | BATT_MISSING_THERM_BIT, + chip->bmd_algo_disabled ? BATT_MISSING_THERM_BIT : + BATT_MISSING_ALGO_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_missing config = %d\n", + rc); + goto free_regulator; + } + + /* set maximum fastchg current */ + if (chip->fastchg_ma != -EINVAL) { + rc = smb135x_set_fastchg_current(chip, chip->fastchg_ma); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set fastchg current = %d\n", + rc); + goto free_regulator; + } + } + + if (chip->usb_pullup_vreg) { + /* enable 9V HVDCP adapter support */ + rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT, + HVDCP_5_9_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't request for 5 or 9V rc=%d\n", rc); + goto free_regulator; + } + } + + if (chip->gamma_setting) { + rc = smb135x_masked_write(chip, CFG_1B_REG, COLD_HARD_MASK, + chip->gamma_setting[0] << COLD_HARD_SHIFT); + + rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_HARD_MASK, + chip->gamma_setting[1] << HOT_HARD_SHIFT); + + rc |= smb135x_masked_write(chip, CFG_1B_REG, COLD_SOFT_MASK, + chip->gamma_setting[2] << COLD_SOFT_SHIFT); + + rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_SOFT_MASK, + chip->gamma_setting[3] << HOT_SOFT_SHIFT); + if (rc < 0) + goto free_regulator; + } + + __smb135x_charging(chip, chip->chg_enabled); + + /* interrupt enabling - active low */ + if (chip->client->irq) { + mask = CHG_STAT_IRQ_ONLY_BIT | CHG_STAT_ACTIVE_HIGH_BIT + | CHG_STAT_DISABLE_BIT; + reg = CHG_STAT_IRQ_ONLY_BIT; + rc = smb135x_masked_write(chip, CFG_17_REG, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq config rc = %d\n", + rc); + goto free_regulator; + } + + /* enabling only interesting interrupts */ + rc = smb135x_write(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT + | IRQ_BAT_HOT_COLD_SOFT_BIT + | IRQ_OTG_OVER_CURRENT_BIT + | IRQ_INTERNAL_TEMPERATURE_BIT + | IRQ_USBIN_UV_BIT); + + rc |= smb135x_write(chip, IRQ2_CFG_REG, + IRQ2_SAFETY_TIMER_BIT + | IRQ2_CHG_ERR_BIT + | IRQ2_CHG_PHASE_CHANGE_BIT + | IRQ2_POWER_OK_BIT + | IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT); + + rc |= smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT + | IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq enable rc = %d\n", + rc); + goto free_regulator; + } + } + + /* resume threshold */ + if (chip->resume_delta_mv != -EINVAL) { + smb135x_set_resume_threshold(chip, chip->resume_delta_mv); + } + + /* DC path current settings */ + if (chip->dc_psy_type != -EINVAL) { + rc = smb135x_set_dc_chg_current(chip, chip->dc_psy_ma); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n", + rc); + goto free_regulator; + } + } + + /* + * on some devices the battery is powered via external sources which + * could raise its voltage above the float voltage. smb135x chips go + * in to reverse boost in such a situation and the workaround is to + * disable float voltage compensation (note that the battery will appear + * hot/cold when powered via external source). + */ + + if (chip->soft_vfloat_comp_disabled) { + mask = HOT_SOFT_VFLOAT_COMP_EN_BIT + | COLD_SOFT_VFLOAT_COMP_EN_BIT; + rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n", + rc); + goto free_regulator; + } + } + + if (chip->soft_current_comp_disabled) { + mask = HOT_SOFT_CURRENT_COMP_EN_BIT + | COLD_SOFT_CURRENT_COMP_EN_BIT; + rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable soft current rc = %d\n", + rc); + goto free_regulator; + } + } + + /* + * Command mode for OTG control. This gives us RID interrupts but keeps + * enabling the 5V OTG via i2c register control + */ + rc = smb135x_masked_write(chip, USBIN_OTG_REG, OTG_CNFG_MASK, + OTG_CNFG_COMMAND_CTRL); + if (rc < 0) { + dev_err(chip->dev, "Couldn't write to otg cfg reg rc = %d\n", + rc); + goto free_regulator; + } + return 0; + +free_regulator: + if (chip->therm_bias_vreg) + regulator_disable(chip->therm_bias_vreg); + if (chip->usb_pullup_vreg) + regulator_disable(chip->usb_pullup_vreg); + return rc; +} + +static struct of_device_id smb135x_match_table[] = { + { + .compatible = "qcom,smb1356-charger", + .data = &version_data[V_SMB1356], + }, + { + .compatible = "qcom,smb1357-charger", + .data = &version_data[V_SMB1357], + }, + { + .compatible = "qcom,smb1358-charger", + .data = &version_data[V_SMB1358], + }, + { + .compatible = "qcom,smb1359-charger", + .data = &version_data[V_SMB1359], + }, + { }, +}; + +#define DC_MA_MIN 300 +#define DC_MA_MAX 2000 +#define NUM_GAMMA_VALUES 4 +static int smb_parse_dt(struct smb135x_chg *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + const char *dc_psy_type; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc < 0) + chip->vfloat_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,charging-timeout", + &chip->safety_time); + if (rc < 0) + chip->safety_time = -EINVAL; + + if (!rc && + (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) { + dev_err(chip->dev, "Bad charging-timeout %d\n", + chip->safety_time); + return -EINVAL; + } + + chip->bmd_algo_disabled = of_property_read_bool(node, + "qcom,bmd-algo-disabled"); + + chip->dc_psy_type = -EINVAL; + dc_psy_type = of_get_property(node, "qcom,dc-psy-type", NULL); + if (dc_psy_type) { + if (strcmp(dc_psy_type, "Mains") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS; + else if (strcmp(dc_psy_type, "Wireless") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS; + } + + if (chip->dc_psy_type != -EINVAL) { + rc = of_property_read_u32(node, "qcom,dc-psy-ma", + &chip->dc_psy_ma); + if (rc < 0) { + dev_err(chip->dev, + "no mA current for dc rc = %d\n", rc); + return rc; + } + + if (chip->dc_psy_ma < DC_MA_MIN + || chip->dc_psy_ma > DC_MA_MAX) { + dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma); + return -EINVAL; + } + } + + rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", + &chip->resume_delta_mv); + if (rc < 0) + chip->resume_delta_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma); + if (rc < 0) + chip->iterm_ma = -EINVAL; + + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + + chip->chg_disabled_permanently = (of_property_read_bool(node, + "qcom,charging-disabled")); + chip->chg_enabled = !chip->chg_disabled_permanently; + + chip->inhibit_disabled = of_property_read_bool(node, + "qcom,inhibit-disabled"); + + chip->bms_controlled_charging = of_property_read_bool(node, + "qcom,bms-controlled-charging"); + + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + rc = of_property_read_u32(node, "qcom,fastchg-ma", &chip->fastchg_ma); + if (rc < 0) + chip->fastchg_ma = -EINVAL; + + chip->soft_vfloat_comp_disabled = of_property_read_bool(node, + "qcom,soft-vfloat-comp-disabled"); + + chip->soft_current_comp_disabled = of_property_read_bool(node, + "qcom,soft-current-comp-disabled"); + + if (of_find_property(node, "therm-bias-supply", NULL)) { + /* get the thermistor bias regulator */ + chip->therm_bias_vreg = devm_regulator_get(chip->dev, + "therm-bias"); + if (IS_ERR(chip->therm_bias_vreg)) + return PTR_ERR(chip->therm_bias_vreg); + } + + /* + * Gamma value indicates the ratio of the pull up resistors and NTC + * resistor in battery pack. There are 4 options, refer to the graphic + * user interface and choose the right one. + */ + if (of_find_property(node, "qcom,gamma-setting", + &chip->gamma_setting_num)) { + chip->gamma_setting_num = chip->gamma_setting_num / + sizeof(chip->gamma_setting_num); + if (NUM_GAMMA_VALUES != chip->gamma_setting_num) { + pr_err("Gamma setting not correct!\n"); + return -EINVAL; + } + + chip->gamma_setting = devm_kzalloc(chip->dev, + chip->gamma_setting_num * + sizeof(chip->gamma_setting_num), GFP_KERNEL); + if (!chip->gamma_setting) { + pr_err("gamma setting kzalloc failed!\n"); + return -ENOMEM; + } + + rc = of_property_read_u32_array(node, + "qcom,gamma-setting", + chip->gamma_setting, chip->gamma_setting_num); + if (rc) { + pr_err("Couldn't read gamma setting, rc = %d\n", rc); + return rc; + } + } + + if (of_find_property(node, "qcom,thermal-mitigation", + &chip->thermal_levels)) { + chip->thermal_mitigation = devm_kzalloc(chip->dev, + chip->thermal_levels, + GFP_KERNEL); + + if (chip->thermal_mitigation == NULL) { + pr_err("thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chip->thermal_mitigation, chip->thermal_levels); + if (rc) { + pr_err("Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + if (of_find_property(node, "usb-pullup-supply", NULL)) { + /* get the data line pull-up regulator */ + chip->usb_pullup_vreg = devm_regulator_get(chip->dev, + "usb-pullup"); + if (IS_ERR(chip->usb_pullup_vreg)) + return PTR_ERR(chip->usb_pullup_vreg); + } + + chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL); + + chip->id_line_not_connected = of_property_read_bool(node, + "qcom,id-line-not-connected"); + return 0; +} + +static int create_debugfs_entries(struct smb135x_chg *chip) +{ + chip->debug_root = debugfs_create_dir("smb135x", NULL); + if (!chip->debug_root) + dev_err(chip->dev, "Couldn't create debug dir\n"); + + if (chip->debug_root) { + struct dentry *ent; + + ent = debugfs_create_file("config_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cnfg debug file\n"); + + ent = debugfs_create_file("status_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create status debug file\n"); + + ent = debugfs_create_file("cmd_registers", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cmd debug file\n"); + + ent = debugfs_create_x32("address", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->peek_poke_address)); + if (!ent) + dev_err(chip->dev, + "Couldn't create address debug file\n"); + + ent = debugfs_create_file("data", S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file\n"); + + ent = debugfs_create_file("force_irq", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_irq_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create force_irq debug file\n"); + + ent = debugfs_create_x32("skip_writes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->skip_writes)); + if (!ent) + dev_err(chip->dev, + "Couldn't create skip writes debug file\n"); + + ent = debugfs_create_x32("skip_reads", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->skip_reads)); + if (!ent) + dev_err(chip->dev, + "Couldn't create skip reads debug file\n"); + + ent = debugfs_create_file("irq_count", S_IFREG | S_IRUGO, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create irq_count debug file\n"); + + ent = debugfs_create_file("force_recharge", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_rechg_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create force recharge debug file\n"); + + ent = debugfs_create_x32("usb_suspend_votes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->usb_suspended)); + if (!ent) + dev_err(chip->dev, + "Couldn't create usb_suspend_votes file\n"); + + ent = debugfs_create_x32("dc_suspend_votes", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, + &(chip->dc_suspended)); + if (!ent) + dev_err(chip->dev, + "Couldn't create dc_suspend_votes file\n"); + } + return 0; +} + +static int is_parallel_charger(struct i2c_client *client) +{ + struct device_node *node = client->dev.of_node; + + return of_property_read_bool(node, "qcom,parallel-charger"); +} + +static int smb135x_main_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb135x_chg *chip; + struct power_supply *usb_psy; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config dc_psy_cfg = {}; + u8 reg = 0; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + dev_err(&client->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + chip->client = client; + chip->dev = &client->dev; + + rc = smb_parse_dt(chip); + if (rc < 0) { + dev_err(&client->dev, "Unable to parse DT nodes\n"); + return rc; + } + + usb_psy = power_supply_get_by_name("usb"); + if (!usb_psy && chip->chg_enabled) { + dev_dbg(&client->dev, "USB supply not found; defer probe\n"); + return -EPROBE_DEFER; + } + chip->usb_psy = usb_psy; + + chip->fake_battery_soc = -EINVAL; + + INIT_DELAYED_WORK(&chip->wireless_insertion_work, + wireless_insertion_work); + + INIT_DELAYED_WORK(&chip->reset_otg_oc_count_work, + reset_otg_oc_count_work); + INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb135x_hvdcp_det_work); + mutex_init(&chip->path_suspend_lock); + mutex_init(&chip->current_change_lock); + mutex_init(&chip->read_write_lock); + mutex_init(&chip->otg_oc_count_lock); + device_init_wakeup(chip->dev, true); + /* probe the device to check if its actually connected */ + rc = smb135x_read(chip, CFG_4_REG, ®); + if (rc) { + pr_err("Failed to detect SMB135x, device may be absent\n"); + return -ENODEV; + } + + i2c_set_clientdata(client, chip); + + rc = smb135x_chip_version_and_revision(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't detect version/revision rc=%d\n", rc); + return rc; + } + + dump_regs(chip); + + rc = smb135x_regulator_init(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't initialize regulator rc=%d\n", rc); + return rc; + } + + rc = smb135x_hw_init(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to intialize hardware rc = %d\n", rc); + goto free_regulator; + } + + rc = determine_initial_status(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to determine init status rc = %d\n", rc); + goto free_regulator; + } + + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smb135x_battery_get_property; + chip->batt_psy_d.set_property = smb135x_battery_set_property; + chip->batt_psy_d.properties = smb135x_battery_properties; + chip->batt_psy_d.num_properties + = ARRAY_SIZE(smb135x_battery_properties); + chip->batt_psy_d.external_power_changed + = smb135x_external_power_changed; + chip->batt_psy_d.property_is_writeable = smb135x_battery_is_writeable; + + batt_psy_cfg.drv_data = chip; + batt_psy_cfg.num_supplicants = 0; + if (chip->bms_controlled_charging) { + batt_psy_cfg.supplied_to = pm_batt_supplied_to; + batt_psy_cfg.num_supplicants + = ARRAY_SIZE(pm_batt_supplied_to); + } + chip->batt_psy = devm_power_supply_register(chip->dev, + &chip->batt_psy_d, &batt_psy_cfg); + if (IS_ERR(chip->batt_psy)) { + dev_err(&client->dev, "Unable to register batt_psy rc = %ld\n", + PTR_ERR(chip->batt_psy)); + goto free_regulator; + } + + if (chip->dc_psy_type != -EINVAL) { + chip->dc_psy_d.name = "dc"; + chip->dc_psy_d.type = chip->dc_psy_type; + chip->dc_psy_d.get_property = smb135x_dc_get_property; + chip->dc_psy_d.properties = smb135x_dc_properties; + chip->dc_psy_d.num_properties + = ARRAY_SIZE(smb135x_dc_properties); + + dc_psy_cfg.drv_data = chip; + dc_psy_cfg.num_supplicants = 0; + chip->dc_psy = devm_power_supply_register(chip->dev, + &chip->dc_psy_d, + &dc_psy_cfg); + + if (IS_ERR(chip->dc_psy)) { + dev_err(&client->dev, + "Unable to register dc_psy rc = %ld\n", + PTR_ERR(chip->dc_psy)); + goto free_regulator; + } + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + smb135x_chg_stat_handler, + IRQF_TRIGGER_LOW | IRQF_ONESHOT, + "smb135x_chg_stat_irq", chip); + if (rc < 0) { + dev_err(&client->dev, + "request_irq for irq=%d failed rc = %d\n", + client->irq, rc); + goto free_regulator; + } + enable_irq_wake(client->irq); + } + + create_debugfs_entries(chip); + dev_info(chip->dev, "SMB135X version = %s revision = %s successfully probed batt=%d dc = %d usb = %d\n", + version_str[chip->version], + revision_str[chip->revision], + smb135x_get_prop_batt_present(chip), + chip->dc_present, chip->usb_present); + return 0; + +free_regulator: + smb135x_regulator_deinit(chip); + return rc; +} + +static int smb135x_parallel_charger_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + int rc; + struct smb135x_chg *chip; + const struct of_device_id *match; + struct device_node *node = client->dev.of_node; + struct power_supply_config parallel_psy_cfg = {}; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) { + dev_err(&client->dev, "Unable to allocate memory\n"); + return -ENOMEM; + } + + chip->client = client; + chip->dev = &client->dev; + chip->parallel_charger = true; + chip->dc_psy_type = -EINVAL; + + chip->chg_enabled = !(of_property_read_bool(node, + "qcom,charging-disabled")); + + rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", + &chip->resume_delta_mv); + if (rc < 0) + chip->resume_delta_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,float-voltage-mv", + &chip->vfloat_mv); + if (rc < 0) + chip->vfloat_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity", + &chip->parallel_pin_polarity_setting); + if (rc) + chip->parallel_pin_polarity_setting = CHG_EN_ACTIVE_LOW_BIT; + else + chip->parallel_pin_polarity_setting = + chip->parallel_pin_polarity_setting ? + CHG_EN_ACTIVE_HIGH_BIT : CHG_EN_ACTIVE_LOW_BIT; + + mutex_init(&chip->path_suspend_lock); + mutex_init(&chip->current_change_lock); + mutex_init(&chip->read_write_lock); + + match = of_match_node(smb135x_match_table, node); + if (match == NULL) { + dev_err(chip->dev, "device tree match not found\n"); + return -EINVAL; + } + + chip->version = *(int *)match->data; + smb135x_set_current_tables(chip); + + i2c_set_clientdata(client, chip); + + chip->parallel_psy_d.name = "usb-parallel"; + chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_USB_PARALLEL; + chip->parallel_psy_d.get_property = smb135x_parallel_get_property; + chip->parallel_psy_d.set_property = smb135x_parallel_set_property; + chip->parallel_psy_d.properties = smb135x_parallel_properties; + chip->parallel_psy_d.property_is_writeable + = smb135x_parallel_is_writeable; + chip->parallel_psy_d.num_properties + = ARRAY_SIZE(smb135x_parallel_properties); + + parallel_psy_cfg.drv_data = chip; + parallel_psy_cfg.num_supplicants = 0; + chip->parallel_psy = devm_power_supply_register(chip->dev, + &chip->parallel_psy_d, + ¶llel_psy_cfg); + if (IS_ERR(chip->parallel_psy)) { + dev_err(&client->dev, + "Unable to register parallel_psy rc = %ld\n", + PTR_ERR(chip->parallel_psy)); + return rc; + } + + chip->resume_completed = true; + mutex_init(&chip->irq_complete); + + create_debugfs_entries(chip); + + dev_info(chip->dev, "SMB135X USB PARALLEL CHARGER version = %s successfully probed\n", + version_str[chip->version]); + return 0; +} + +static int smb135x_chg_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + if (is_parallel_charger(client)) + return smb135x_parallel_charger_probe(client, id); + else + return smb135x_main_charger_probe(client, id); +} + +static int smb135x_chg_remove(struct i2c_client *client) +{ + int rc; + struct smb135x_chg *chip = i2c_get_clientdata(client); + + debugfs_remove_recursive(chip->debug_root); + + if (chip->parallel_charger) + goto mutex_destroy; + + if (chip->therm_bias_vreg) { + rc = regulator_disable(chip->therm_bias_vreg); + if (rc) + pr_err("Couldn't disable therm-bias rc = %d\n", rc); + } + + if (chip->usb_pullup_vreg) { + rc = regulator_disable(chip->usb_pullup_vreg); + if (rc) + pr_err("Couldn't disable data-pullup rc = %d\n", rc); + } + + smb135x_regulator_deinit(chip); + +mutex_destroy: + mutex_destroy(&chip->irq_complete); + return 0; +} + +static int smb135x_suspend(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + int i, rc; + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + /* Save the current IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb135x_read(chip, IRQ_CFG_REG + i, + &chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't save irq cfg regs rc=%d\n", rc); + } + + /* enable only important IRQs */ + rc = smb135x_write(chip, IRQ_CFG_REG, IRQ_USBIN_UV_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq_cfg rc = %d\n", rc); + + rc = smb135x_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT + | IRQ2_POWER_OK_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq2_cfg rc = %d\n", rc); + + rc = smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT + | IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set irq3_cfg rc = %d\n", rc); + + mutex_lock(&chip->irq_complete); + chip->resume_completed = false; + mutex_unlock(&chip->irq_complete); + + return 0; +} + +static int smb135x_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int smb135x_resume(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb135x_chg *chip = i2c_get_clientdata(client); + int i, rc; + + /* no suspend resume activities for parallel charger */ + if (chip->parallel_charger) + return 0; + /* Restore the IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb135x_write(chip, IRQ_CFG_REG + i, + chip->irq_cfg_mask[i]); + if (rc) + dev_err(chip->dev, + "Couldn't restore irq cfg regs rc=%d\n", rc); + } + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + mutex_unlock(&chip->irq_complete); + smb135x_chg_stat_handler(client->irq, chip); + enable_irq(client->irq); + } else { + mutex_unlock(&chip->irq_complete); + } + return 0; +} + +static const struct dev_pm_ops smb135x_pm_ops = { + .resume = smb135x_resume, + .suspend_noirq = smb135x_suspend_noirq, + .suspend = smb135x_suspend, +}; + +static const struct i2c_device_id smb135x_chg_id[] = { + {"smb135x-charger", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb135x_chg_id); + +static void smb135x_shutdown(struct i2c_client *client) +{ + int rc; + struct smb135x_chg *chip = i2c_get_clientdata(client); + + if (chip->usb_pullup_vreg) { + /* + * switch to 5V adapter to prevent any errorneous request of 12V + * when USB D+ line pull-up regulator turns off. + */ + rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT, 0); + if (rc < 0) + dev_err(chip->dev, + "Couldn't request for 5V rc=%d\n", rc); + } +} + +static struct i2c_driver smb135x_chg_driver = { + .driver = { + .name = "smb135x-charger", + .owner = THIS_MODULE, + .of_match_table = smb135x_match_table, + .pm = &smb135x_pm_ops, + }, + .probe = smb135x_chg_probe, + .remove = smb135x_chg_remove, + .id_table = smb135x_chg_id, + .shutdown = smb135x_shutdown, +}; + +module_i2c_driver(smb135x_chg_driver); + +MODULE_DESCRIPTION("SMB135x Charger"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb135x-charger"); |