diff --git a/arch/arm/configs/vendor/msm8937-perf_defconfig b/arch/arm/configs/vendor/msm8937-perf_defconfig index e256dfd1c13d..68c85e7327e0 100644 --- a/arch/arm/configs/vendor/msm8937-perf_defconfig +++ b/arch/arm/configs/vendor/msm8937-perf_defconfig @@ -370,7 +370,10 @@ CONFIG_POWER_RESET=y CONFIG_POWER_RESET_QCOM=y CONFIG_POWER_RESET_SYSCON=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -379,6 +382,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -515,9 +519,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -670,9 +676,12 @@ CONFIG_LKDTM=m CONFIG_BUG_ON_DATA_CORRUPTION=y CONFIG_CORESIGHT=y CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y CONFIG_CORESIGHT_STM=y CONFIG_CORESIGHT_CTI=y CONFIG_CORESIGHT_TPDA=y CONFIG_CORESIGHT_TPDM=y CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/arch/arm/configs/vendor/msm8937_32go-perf_defconfig b/arch/arm/configs/vendor/msm8937_32go-perf_defconfig index b5cae63c78dd..d322972ec0d2 100644 --- a/arch/arm/configs/vendor/msm8937_32go-perf_defconfig +++ b/arch/arm/configs/vendor/msm8937_32go-perf_defconfig @@ -362,7 +362,10 @@ CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_QCOM=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -371,6 +374,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -508,9 +512,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -652,3 +658,13 @@ CONFIG_SCHED_STACK_END_CHECK=y CONFIG_IPC_LOGGING=y # CONFIG_FTRACE is not set CONFIG_BUG_ON_DATA_CORRUPTION=y +CONFIG_CORESIGHT=y +CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y +CONFIG_CORESIGHT_STM=y +CONFIG_CORESIGHT_TPDA=y +CONFIG_CORESIGHT_TPDM=y +CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y +CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/arch/arm/configs/vendor/msm8937_32go_defconfig b/arch/arm/configs/vendor/msm8937_32go_defconfig index e2ce60fafc24..2b026a14b8c9 100644 --- a/arch/arm/configs/vendor/msm8937_32go_defconfig +++ b/arch/arm/configs/vendor/msm8937_32go_defconfig @@ -369,7 +369,10 @@ CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_QCOM=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -378,6 +381,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -517,9 +521,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -610,7 +616,6 @@ CONFIG_ANDROID=y CONFIG_ANDROID_BINDER_IPC=y CONFIG_ANDROID_BINDERFS=y CONFIG_QCOM_QFPROM=y -CONFIG_STM=y CONFIG_SLIMBUS_MSM_NGD=y CONFIG_SENSORS_SSC=y CONFIG_QCOM_KGSL=y @@ -716,4 +721,15 @@ CONFIG_BUG_ON_DATA_CORRUPTION=y CONFIG_PANIC_ON_DATA_CORRUPTION=y CONFIG_DEBUG_USER=y CONFIG_FORCE_PAGES=y -CONFIG_PID_IN_CONTEXTIDR=y +CONFIG_CORESIGHT=y +CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_SOURCE_ETM4X=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y +CONFIG_CORESIGHT_STM=y +CONFIG_CORESIGHT_CTI=y +CONFIG_CORESIGHT_TPDA=y +CONFIG_CORESIGHT_TPDM=y +CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y +CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/arch/arm/configs/vendor/msm8937_defconfig b/arch/arm/configs/vendor/msm8937_defconfig index 882e8ee61cbb..a3bc5274eaa4 100644 --- a/arch/arm/configs/vendor/msm8937_defconfig +++ b/arch/arm/configs/vendor/msm8937_defconfig @@ -376,7 +376,10 @@ CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET=y CONFIG_POWER_RESET_QCOM=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -385,6 +388,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -524,9 +528,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -727,9 +733,13 @@ CONFIG_DEBUG_USER=y CONFIG_FORCE_PAGES=y CONFIG_CORESIGHT=y CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_SOURCE_ETM4X=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y CONFIG_CORESIGHT_STM=y CONFIG_CORESIGHT_CTI=y CONFIG_CORESIGHT_TPDA=y CONFIG_CORESIGHT_TPDM=y CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/arch/arm/include/asm/etmv4x.h b/arch/arm/include/asm/etmv4x.h new file mode 100644 index 000000000000..1b46b82aba36 --- /dev/null +++ b/arch/arm/include/asm/etmv4x.h @@ -0,0 +1,388 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016, 2018, 2021, 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 __ASM_ETMV4X_H +#define __ASM_ETMV4X_H + +#include + + +/* 32 bit register read for AArch32 */ +#define trc_readl(reg) RSYSL_##reg() +#define trc_readq(reg) RSYSL_##reg() + +/* 32 bit register write for AArch32 */ +#define trc_write(val, reg) WSYS_##reg(val) + +#define MRC(op0, op1, crn, crm, op2) \ +({ \ +uint32_t val; \ +asm volatile("mrc p"#op0", "#op1", %0, "#crn", "#crm", "#op2 : "=r" (val)); \ +val; \ +}) + +#define MCR(val, op0, op1, crn, crm, op2) \ +({ \ +asm volatile("mcr p"#op0", "#op1", %0, "#crn", "#crm", "#op2 : : "r" (val));\ +}) + +/* Clock and Power Management Register */ +#define RSYSL_CPMR_EL1() MRC(15, 7, c15, c0, 5) +#define WSYS_CPMR_EL1(val) MCR(val, 15, 7, c15, c0, 5) + +/* + * ETMv4 Registers + * + * Read only + * ETMAUTHSTATUS, ETMDEVARCH, ETMDEVID, ETMIDRn[0-13], ETMOSLSR, ETMSTATR + * + * Write only + * ETMOSLAR + */ +/* 32 bit registers */ +#define RSYSL_ETMAUTHSTATUS() MRC(14, 1, c7, c14, 6) +#define RSYSL_ETMAUXCTLR() MRC(14, 1, c0, c6, 0) +#define RSYSL_ETMCCCTLR() MRC(14, 1, c0, c14, 0) +#define RSYSL_ETMCIDCCTLR0() MRC(14, 1, c3, c0, 2) +#define RSYSL_ETMCNTCTLR0() MRC(14, 1, c0, c4, 5) +#define RSYSL_ETMCNTCTLR1() MRC(14, 1, c0, c5, 5) +#define RSYSL_ETMCNTCTLR2() MRC(14, 1, c0, c6, 5) +#define RSYSL_ETMCNTCTLR3() MRC(14, 1, c0, c7, 5) +#define RSYSL_ETMCNTRLDVR0() MRC(14, 1, c0, c0, 5) +#define RSYSL_ETMCNTRLDVR1() MRC(14, 1, c0, c1, 5) +#define RSYSL_ETMCNTRLDVR2() MRC(14, 1, c0, c2, 5) +#define RSYSL_ETMCNTRLDVR3() MRC(14, 1, c0, c3, 5) +#define RSYSL_ETMCNTVR0() MRC(14, 1, c0, c8, 5) +#define RSYSL_ETMCNTVR1() MRC(14, 1, c0, c9, 5) +#define RSYSL_ETMCNTVR2() MRC(14, 1, c0, c10, 5) +#define RSYSL_ETMCNTVR3() MRC(14, 1, c0, c11, 5) +#define RSYSL_ETMCONFIGR() MRC(14, 1, c0, c4, 0) +#define RSYSL_ETMDEVARCH() MRC(14, 1, c7, c15, 6) +#define RSYSL_ETMDEVID() MRC(14, 1, c7, c2, 7) +#define RSYSL_ETMEVENTCTL0R() MRC(14, 1, c0, c8, 0) +#define RSYSL_ETMEVENTCTL1R() MRC(14, 1, c0, c9, 0) +#define RSYSL_ETMEXTINSELR() MRC(14, 1, c0, c8, 4) +#define RSYSL_ETMIDR0() MRC(14, 1, c0, c8, 7) +#define RSYSL_ETMIDR1() MRC(14, 1, c0, c9, 7) +#define RSYSL_ETMIDR10() MRC(14, 1, c0, c2, 6) +#define RSYSL_ETMIDR11() MRC(14, 1, c0, c3, 6) +#define RSYSL_ETMIDR12() MRC(14, 1, c0, c4, 6) +#define RSYSL_ETMIDR13() MRC(14, 1, c0, c5, 6) +#define RSYSL_ETMIDR2() MRC(14, 1, c0, c10, 7) +#define RSYSL_ETMIDR3() MRC(14, 1, c0, c11, 7) +#define RSYSL_ETMIDR4() MRC(14, 1, c0, c12, 7) +#define RSYSL_ETMIDR5() MRC(14, 1, c0, c13, 7) +#define RSYSL_ETMIDR6() MRC(14, 1, c0, c14, 7) +#define RSYSL_ETMIDR7() MRC(14, 1, c0, c15, 7) +#define RSYSL_ETMIDR8() MRC(14, 1, c0, c0, 6) +#define RSYSL_ETMIDR9() MRC(14, 1, c0, c1, 6) +#define RSYSL_ETMIMSPEC0() MRC(14, 1, c0, c0, 7) +#define RSYSL_ETMOSLSR() MRC(14, 1, c1, c1, 4) +#define RSYSL_ETMPRGCTLR() MRC(14, 1, c0, c1, 0) +#define RSYSL_ETMRSCTLR10() MRC(14, 1, c1, c10, 0) +#define RSYSL_ETMRSCTLR11() MRC(14, 1, c1, c11, 0) +#define RSYSL_ETMRSCTLR12() MRC(14, 1, c1, c12, 0) +#define RSYSL_ETMRSCTLR13() MRC(14, 1, c1, c13, 0) +#define RSYSL_ETMRSCTLR14() MRC(14, 1, c1, c14, 0) +#define RSYSL_ETMRSCTLR15() MRC(14, 1, c1, c15, 0) +#define RSYSL_ETMRSCTLR2() MRC(14, 1, c1, c2, 0) +#define RSYSL_ETMRSCTLR3() MRC(14, 1, c1, c3, 0) +#define RSYSL_ETMRSCTLR4() MRC(14, 1, c1, c4, 0) +#define RSYSL_ETMRSCTLR5() MRC(14, 1, c1, c5, 0) +#define RSYSL_ETMRSCTLR6() MRC(14, 1, c1, c6, 0) +#define RSYSL_ETMRSCTLR7() MRC(14, 1, c1, c7, 0) +#define RSYSL_ETMRSCTLR8() MRC(14, 1, c1, c8, 0) +#define RSYSL_ETMRSCTLR9() MRC(14, 1, c1, c9, 0) +#define RSYSL_ETMRSCTLR16() MRC(14, 1, c1, c0, 1) +#define RSYSL_ETMRSCTLR17() MRC(14, 1, c1, c1, 1) +#define RSYSL_ETMRSCTLR18() MRC(14, 1, c1, c2, 1) +#define RSYSL_ETMRSCTLR19() MRC(14, 1, c1, c3, 1) +#define RSYSL_ETMRSCTLR20() MRC(14, 1, c1, c4, 1) +#define RSYSL_ETMRSCTLR21() MRC(14, 1, c1, c5, 1) +#define RSYSL_ETMRSCTLR22() MRC(14, 1, c1, c6, 1) +#define RSYSL_ETMRSCTLR23() MRC(14, 1, c1, c7, 1) +#define RSYSL_ETMRSCTLR24() MRC(14, 1, c1, c8, 1) +#define RSYSL_ETMRSCTLR25() MRC(14, 1, c1, c9, 1) +#define RSYSL_ETMRSCTLR26() MRC(14, 1, c1, c10, 1) +#define RSYSL_ETMRSCTLR27() MRC(14, 1, c1, c11, 1) +#define RSYSL_ETMRSCTLR28() MRC(14, 1, c1, c12, 1) +#define RSYSL_ETMRSCTLR29() MRC(14, 1, c1, c13, 1) +#define RSYSL_ETMRSCTLR30() MRC(14, 1, c1, c14, 1) +#define RSYSL_ETMRSCTLR31() MRC(14, 1, c1, c15, 1) +#define RSYSL_ETMSEQEVR0() MRC(14, 1, c0, c0, 4) +#define RSYSL_ETMSEQEVR1() MRC(14, 1, c0, c1, 4) +#define RSYSL_ETMSEQEVR2() MRC(14, 1, c0, c2, 4) +#define RSYSL_ETMSEQRSTEVR() MRC(14, 1, c0, c6, 4) +#define RSYSL_ETMSEQSTR() MRC(14, 1, c0, c7, 4) +#define RSYSL_ETMSTALLCTLR() MRC(14, 1, c0, c11, 0) +#define RSYSL_ETMSTATR() MRC(14, 1, c0, c3, 0) +#define RSYSL_ETMSYNCPR() MRC(14, 1, c0, c13, 0) +#define RSYSL_ETMTRACEIDR() MRC(14, 1, c0, c0, 1) +#define RSYSL_ETMTSCTLR() MRC(14, 1, c0, c12, 0) +#define RSYSL_ETMVICTLR() MRC(14, 1, c0, c0, 2) +#define RSYSL_ETMVIIECTLR() MRC(14, 1, c0, c1, 2) +#define RSYSL_ETMVISSCTLR() MRC(14, 1, c0, c2, 2) +#define RSYSL_ETMSSCCR0() MRC(14, 1, c1, c0, 2) +#define RSYSL_ETMSSCCR1() MRC(14, 1, c1, c1, 2) +#define RSYSL_ETMSSCCR2() MRC(14, 1, c1, c2, 2) +#define RSYSL_ETMSSCCR3() MRC(14, 1, c1, c3, 2) +#define RSYSL_ETMSSCCR4() MRC(14, 1, c1, c4, 2) +#define RSYSL_ETMSSCCR5() MRC(14, 1, c1, c5, 2) +#define RSYSL_ETMSSCCR6() MRC(14, 1, c1, c6, 2) +#define RSYSL_ETMSSCCR7() MRC(14, 1, c1, c7, 2) +#define RSYSL_ETMSSCSR0() MRC(14, 1, c1, c8, 2) +#define RSYSL_ETMSSCSR1() MRC(14, 1, c1, c9, 2) +#define RSYSL_ETMSSCSR2() MRC(14, 1, c1, c10, 2) +#define RSYSL_ETMSSCSR3() MRC(14, 1, c1, c11, 2) +#define RSYSL_ETMSSCSR4() MRC(14, 1, c1, c12, 2) +#define RSYSL_ETMSSCSR5() MRC(14, 1, c1, c13, 2) +#define RSYSL_ETMSSCSR6() MRC(14, 1, c1, c14, 2) +#define RSYSL_ETMSSCSR7() MRC(14, 1, c1, c15, 2) +#define RSYSL_ETMSSPCICR0() MRC(14, 1, c1, c0, 3) +#define RSYSL_ETMSSPCICR1() MRC(14, 1, c1, c1, 3) +#define RSYSL_ETMSSPCICR2() MRC(14, 1, c1, c2, 3) +#define RSYSL_ETMSSPCICR3() MRC(14, 1, c1, c3, 3) +#define RSYSL_ETMSSPCICR4() MRC(14, 1, c1, c4, 3) +#define RSYSL_ETMSSPCICR5() MRC(14, 1, c1, c5, 3) +#define RSYSL_ETMSSPCICR6() MRC(14, 1, c1, c6, 3) +#define RSYSL_ETMSSPCICR7() MRC(14, 1, c1, c7, 3) + +/* + * 64 bit registers, ignore the upper 32bit + * A read from a 32-bit register location using a 64-bit access result + * in the upper 32bits being return as RES0. + */ +#define RSYSL_ETMACATR0() MRC(14, 1, c2, c0, 2) +#define RSYSL_ETMACATR1() MRC(14, 1, c2, c2, 2) +#define RSYSL_ETMACATR2() MRC(14, 1, c2, c4, 2) +#define RSYSL_ETMACATR3() MRC(14, 1, c2, c6, 2) +#define RSYSL_ETMACATR4() MRC(14, 1, c2, c8, 2) +#define RSYSL_ETMACATR5() MRC(14, 1, c2, c10, 2) +#define RSYSL_ETMACATR6() MRC(14, 1, c2, c12, 2) +#define RSYSL_ETMACATR7() MRC(14, 1, c2, c14, 2) +#define RSYSL_ETMACATR8() MRC(14, 1, c2, c0, 3) +#define RSYSL_ETMACATR9() MRC(14, 1, c2, c2, 3) +#define RSYSL_ETMACATR10() MRC(14, 1, c2, c4, 3) +#define RSYSL_ETMACATR11() MRC(14, 1, c2, c6, 3) +#define RSYSL_ETMACATR12() MRC(14, 1, c2, c8, 3) +#define RSYSL_ETMACATR13() MRC(14, 1, c2, c10, 3) +#define RSYSL_ETMACATR14() MRC(14, 1, c2, c12, 3) +#define RSYSL_ETMACATR15() MRC(14, 1, c2, c14, 3) +#define RSYSL_ETMCIDCVR0() MRC(14, 1, c3, c0, 0) +#define RSYSL_ETMCIDCVR1() MRC(14, 1, c3, c2, 0) +#define RSYSL_ETMCIDCVR2() MRC(14, 1, c3, c4, 0) +#define RSYSL_ETMCIDCVR3() MRC(14, 1, c3, c6, 0) +#define RSYSL_ETMCIDCVR4() MRC(14, 1, c3, c8, 0) +#define RSYSL_ETMCIDCVR5() MRC(14, 1, c3, c10, 0) +#define RSYSL_ETMCIDCVR6() MRC(14, 1, c3, c12, 0) +#define RSYSL_ETMCIDCVR7() MRC(14, 1, c3, c14, 0) +#define RSYSL_ETMACVR0() MRC(14, 1, c2, c0, 0) +#define RSYSL_ETMACVR1() MRC(14, 1, c2, c2, 0) +#define RSYSL_ETMACVR2() MRC(14, 1, c2, c4, 0) +#define RSYSL_ETMACVR3() MRC(14, 1, c2, c6, 0) +#define RSYSL_ETMACVR4() MRC(14, 1, c2, c8, 0) +#define RSYSL_ETMACVR5() MRC(14, 1, c2, c10, 0) +#define RSYSL_ETMACVR6() MRC(14, 1, c2, c12, 0) +#define RSYSL_ETMACVR7() MRC(14, 1, c2, c14, 0) +#define RSYSL_ETMACVR8() MRC(14, 1, c2, c0, 1) +#define RSYSL_ETMACVR9() MRC(14, 1, c2, c2, 1) +#define RSYSL_ETMACVR10() MRC(14, 1, c2, c4, 1) +#define RSYSL_ETMACVR11() MRC(14, 1, c2, c6, 1) +#define RSYSL_ETMACVR12() MRC(14, 1, c2, c8, 1) +#define RSYSL_ETMACVR13() MRC(14, 1, c2, c10, 1) +#define RSYSL_ETMACVR14() MRC(14, 1, c2, c12, 1) +#define RSYSL_ETMACVR15() MRC(14, 1, c2, c14, 1) +#define RSYSL_ETMVMIDCVR0() MRC(14, 1, c3, c0, 1) +#define RSYSL_ETMVMIDCVR1() MRC(14, 1, c3, c2, 1) +#define RSYSL_ETMVMIDCVR2() MRC(14, 1, c3, c4, 1) +#define RSYSL_ETMVMIDCVR3() MRC(14, 1, c3, c6, 1) +#define RSYSL_ETMVMIDCVR4() MRC(14, 1, c3, c8, 1) +#define RSYSL_ETMVMIDCVR5() MRC(14, 1, c3, c10, 1) +#define RSYSL_ETMVMIDCVR6() MRC(14, 1, c3, c12, 1) +#define RSYSL_ETMVMIDCVR7() MRC(14, 1, c3, c14, 1) +#define RSYSL_ETMDVCVR0() MRC(14, 1, c2, c0, 4) +#define RSYSL_ETMDVCVR1() MRC(14, 1, c2, c4, 4) +#define RSYSL_ETMDVCVR2() MRC(14, 1, c2, c8, 4) +#define RSYSL_ETMDVCVR3() MRC(14, 1, c2, c12, 4) +#define RSYSL_ETMDVCVR4() MRC(14, 1, c2, c0, 5) +#define RSYSL_ETMDVCVR5() MRC(14, 1, c2, c4, 5) +#define RSYSL_ETMDVCVR6() MRC(14, 1, c2, c8, 5) +#define RSYSL_ETMDVCVR7() MRC(14, 1, c2, c12, 5) +#define RSYSL_ETMDVCMR0() MRC(14, 1, c2, c0, 6) +#define RSYSL_ETMDVCMR1() MRC(14, 1, c2, c4, 6) +#define RSYSL_ETMDVCMR2() MRC(14, 1, c2, c8, 6) +#define RSYSL_ETMDVCMR3() MRC(14, 1, c2, c12, 6) +#define RSYSL_ETMDVCMR4() MRC(14, 1, c2, c0, 7) +#define RSYSL_ETMDVCMR5() MRC(14, 1, c2, c4, 7) +#define RSYSL_ETMDVCMR6() MRC(14, 1, c2, c8, 7) +#define RSYSL_ETMDVCMR7() MRC(14, 1, c2, c12, 7) + +/* + * 32 and 64 bit registers + * A write to a 32-bit register location using a 64-bit access result + * in the upper 32bit of access + */ +#define WSYS_ETMAUXCTLR(val) MCR(val, 14, 1, c0, c6, 0) +#define WSYS_ETMACATR0(val) MCR(val, 14, 1, c2, c0, 2) +#define WSYS_ETMACATR1(val) MCR(val, 14, 1, c2, c2, 2) +#define WSYS_ETMACATR2(val) MCR(val, 14, 1, c2, c4, 2) +#define WSYS_ETMACATR3(val) MCR(val, 14, 1, c2, c6, 2) +#define WSYS_ETMACATR4(val) MCR(val, 14, 1, c2, c8, 2) +#define WSYS_ETMACATR5(val) MCR(val, 14, 1, c2, c10, 2) +#define WSYS_ETMACATR6(val) MCR(val, 14, 1, c2, c12, 2) +#define WSYS_ETMACATR7(val) MCR(val, 14, 1, c2, c14, 2) +#define WSYS_ETMACATR8(val) MCR(val, 14, 1, c2, c0, 3) +#define WSYS_ETMACATR9(val) MCR(val, 14, 1, c2, c2, 3) +#define WSYS_ETMACATR10(val) MCR(val, 14, 1, c2, c4, 3) +#define WSYS_ETMACATR11(val) MCR(val, 14, 1, c2, c6, 3) +#define WSYS_ETMACATR12(val) MCR(val, 14, 1, c2, c8, 3) +#define WSYS_ETMACATR13(val) MCR(val, 14, 1, c2, c10, 3) +#define WSYS_ETMACATR14(val) MCR(val, 14, 1, c2, c12, 3) +#define WSYS_ETMACATR15(val) MCR(val, 14, 1, c2, c14, 3) +#define WSYS_ETMACVR0(val) MCR(val, 14, 1, c2, c0, 0) +#define WSYS_ETMACVR1(val) MCR(val, 14, 1, c2, c2, 0) +#define WSYS_ETMACVR2(val) MCR(val, 14, 1, c2, c4, 0) +#define WSYS_ETMACVR3(val) MCR(val, 14, 1, c2, c6, 0) +#define WSYS_ETMACVR4(val) MCR(val, 14, 1, c2, c8, 0) +#define WSYS_ETMACVR5(val) MCR(val, 14, 1, c2, c10, 0) +#define WSYS_ETMACVR6(val) MCR(val, 14, 1, c2, c12, 0) +#define WSYS_ETMACVR7(val) MCR(val, 14, 1, c2, c14, 0) +#define WSYS_ETMACVR8(val) MCR(val, 14, 1, c2, c0, 1) +#define WSYS_ETMACVR9(val) MCR(val, 14, 1, c2, c2, 1) +#define WSYS_ETMACVR10(val) MCR(val, 14, 1, c2, c4, 1) +#define WSYS_ETMACVR11(val) MCR(val, 14, 1, c2, c6, 1) +#define WSYS_ETMACVR12(val) MCR(val, 14, 1, c2, c8, 1) +#define WSYS_ETMACVR13(val) MCR(val, 14, 1, c2, c10, 1) +#define WSYS_ETMACVR14(val) MCR(val, 14, 1, c2, c12, 1) +#define WSYS_ETMACVR15(val) MCR(val, 14, 1, c2, c14, 1) +#define WSYS_ETMCCCTLR(val) MCR(val, 14, 1, c0, c14, 0) +#define WSYS_ETMCIDCCTLR0(val) MCR(val, 14, 1, c3, c0, 2) +#define WSYS_ETMCIDCVR0(val) MCR(val, 14, 1, c3, c0, 0) +#define WSYS_ETMCIDCVR1(val) MCR(val, 14, 1, c3, c2, 0) +#define WSYS_ETMCIDCVR2(val) MCR(val, 14, 1, c3, c4, 0) +#define WSYS_ETMCIDCVR3(val) MCR(val, 14, 1, c3, c6, 0) +#define WSYS_ETMCIDCVR4(val) MCR(val, 14, 1, c3, c8, 0) +#define WSYS_ETMCIDCVR5(val) MCR(val, 14, 1, c3, c10, 0) +#define WSYS_ETMCIDCVR6(val) MCR(val, 14, 1, c3, c12, 0) +#define WSYS_ETMCIDCVR7(val) MCR(val, 14, 1, c3, c14, 0) +#define WSYS_ETMCNTCTLR0(val) MCR(val, 14, 1, c0, c4, 5) +#define WSYS_ETMCNTCTLR1(val) MCR(val, 14, 1, c0, c5, 5) +#define WSYS_ETMCNTCTLR2(val) MCR(val, 14, 1, c0, c6, 5) +#define WSYS_ETMCNTCTLR3(val) MCR(val, 14, 1, c0, c7, 5) +#define WSYS_ETMCNTRLDVR0(val) MCR(val, 14, 1, c0, c0, 5) +#define WSYS_ETMCNTRLDVR1(val) MCR(val, 14, 1, c0, c1, 5) +#define WSYS_ETMCNTRLDVR2(val) MCR(val, 14, 1, c0, c2, 5) +#define WSYS_ETMCNTRLDVR3(val) MCR(val, 14, 1, c0, c3, 5) +#define WSYS_ETMCNTVR0(val) MCR(val, 14, 1, c0, c8, 5) +#define WSYS_ETMCNTVR1(val) MCR(val, 14, 1, c0, c9, 5) +#define WSYS_ETMCNTVR2(val) MCR(val, 14, 1, c0, c10, 5) +#define WSYS_ETMCNTVR3(val) MCR(val, 14, 1, c0, c11, 5) +#define WSYS_ETMCONFIGR(val) MCR(val, 14, 1, c0, c4, 0) +#define WSYS_ETMEVENTCTL0R(val) MCR(val, 14, 1, c0, c8, 0) +#define WSYS_ETMEVENTCTL1R(val) MCR(val, 14, 1, c0, c9, 0) +#define WSYS_ETMEXTINSELR(val) MCR(val, 14, 1, c0, c8, 4) +#define WSYS_ETMIMSPEC0(val) MCR(val, 14, 1, c0, c0, 7) +#define WSYS_ETMOSLAR(val) MCR(val, 14, 1, c1, c0, 4) +#define WSYS_ETMPRGCTLR(val) MCR(val, 14, 1, c0, c1, 0) +#define WSYS_ETMRSCTLR10(val) MCR(val, 14, 1, c1, c10, 0) +#define WSYS_ETMRSCTLR11(val) MCR(val, 14, 1, c1, c11, 0) +#define WSYS_ETMRSCTLR12(val) MCR(val, 14, 1, c1, c12, 0) +#define WSYS_ETMRSCTLR13(val) MCR(val, 14, 1, c1, c13, 0) +#define WSYS_ETMRSCTLR14(val) MCR(val, 14, 1, c1, c14, 0) +#define WSYS_ETMRSCTLR15(val) MCR(val, 14, 1, c1, c15, 0) +#define WSYS_ETMRSCTLR2(val) MCR(val, 14, 1, c1, c2, 0) +#define WSYS_ETMRSCTLR3(val) MCR(val, 14, 1, c1, c3, 0) +#define WSYS_ETMRSCTLR4(val) MCR(val, 14, 1, c1, c4, 0) +#define WSYS_ETMRSCTLR5(val) MCR(val, 14, 1, c1, c5, 0) +#define WSYS_ETMRSCTLR6(val) MCR(val, 14, 1, c1, c6, 0) +#define WSYS_ETMRSCTLR7(val) MCR(val, 14, 1, c1, c7, 0) +#define WSYS_ETMRSCTLR8(val) MCR(val, 14, 1, c1, c8, 0) +#define WSYS_ETMRSCTLR9(val) MCR(val, 14, 1, c1, c9, 0) +#define WSYS_ETMRSCTLR16(val) MCR(val, 14, 1, c1, c0, 1) +#define WSYS_ETMRSCTLR17(val) MCR(val, 14, 1, c1, c1, 1) +#define WSYS_ETMRSCTLR18(val) MCR(val, 14, 1, c1, c2, 1) +#define WSYS_ETMRSCTLR19(val) MCR(val, 14, 1, c1, c3, 1) +#define WSYS_ETMRSCTLR20(val) MCR(val, 14, 1, c1, c4, 1) +#define WSYS_ETMRSCTLR21(val) MCR(val, 14, 1, c1, c5, 1) +#define WSYS_ETMRSCTLR22(val) MCR(val, 14, 1, c1, c6, 1) +#define WSYS_ETMRSCTLR23(val) MCR(val, 14, 1, c1, c7, 1) +#define WSYS_ETMRSCTLR24(val) MCR(val, 14, 1, c1, c8, 1) +#define WSYS_ETMRSCTLR25(val) MCR(val, 14, 1, c1, c9, 1) +#define WSYS_ETMRSCTLR26(val) MCR(val, 14, 1, c1, c10, 1) +#define WSYS_ETMRSCTLR27(val) MCR(val, 14, 1, c1, c11, 1) +#define WSYS_ETMRSCTLR28(val) MCR(val, 14, 1, c1, c12, 1) +#define WSYS_ETMRSCTLR29(val) MCR(val, 14, 1, c1, c13, 1) +#define WSYS_ETMRSCTLR30(val) MCR(val, 14, 1, c1, c14, 1) +#define WSYS_ETMRSCTLR31(val) MCR(val, 14, 1, c1, c15, 1) +#define WSYS_ETMSEQEVR0(val) MCR(val, 14, 1, c0, c0, 4) +#define WSYS_ETMSEQEVR1(val) MCR(val, 14, 1, c0, c1, 4) +#define WSYS_ETMSEQEVR2(val) MCR(val, 14, 1, c0, c2, 4) +#define WSYS_ETMSEQRSTEVR(val) MCR(val, 14, 1, c0, c6, 4) +#define WSYS_ETMSEQSTR(val) MCR(val, 14, 1, c0, c7, 4) +#define WSYS_ETMSTALLCTLR(val) MCR(val, 14, 1, c0, c11, 0) +#define WSYS_ETMSYNCPR(val) MCR(val, 14, 1, c0, c13, 0) +#define WSYS_ETMTRACEIDR(val) MCR(val, 14, 1, c0, c0, 1) +#define WSYS_ETMTSCTLR(val) MCR(val, 14, 1, c0, c12, 0) +#define WSYS_ETMVICTLR(val) MCR(val, 14, 1, c0, c0, 2) +#define WSYS_ETMVIIECTLR(val) MCR(val, 14, 1, c0, c1, 2) +#define WSYS_ETMVISSCTLR(val) MCR(val, 14, 1, c0, c2, 2) +#define WSYS_ETMVMIDCVR0(val) MCR(val, 14, 1, c3, c0, 1) +#define WSYS_ETMVMIDCVR1(val) MCR(val, 14, 1, c3, c2, 1) +#define WSYS_ETMVMIDCVR2(val) MCR(val, 14, 1, c3, c4, 1) +#define WSYS_ETMVMIDCVR3(val) MCR(val, 14, 1, c3, c6, 1) +#define WSYS_ETMVMIDCVR4(val) MCR(val, 14, 1, c3, c8, 1) +#define WSYS_ETMVMIDCVR5(val) MCR(val, 14, 1, c3, c10, 1) +#define WSYS_ETMVMIDCVR6(val) MCR(val, 14, 1, c3, c12, 1) +#define WSYS_ETMVMIDCVR7(val) MCR(val, 14, 1, c3, c14, 1) +#define WSYS_ETMDVCVR0(val) MCR(val, 14, 1, c2, c0, 4) +#define WSYS_ETMDVCVR1(val) MCR(val, 14, 1, c2, c4, 4) +#define WSYS_ETMDVCVR2(val) MCR(val, 14, 1, c2, c8, 4) +#define WSYS_ETMDVCVR3(val) MCR(val, 14, 1, c2, c12, 4) +#define WSYS_ETMDVCVR4(val) MCR(val, 14, 1, c2, c0, 5) +#define WSYS_ETMDVCVR5(val) MCR(val, 14, 1, c2, c4, 5) +#define WSYS_ETMDVCVR6(val) MCR(val, 14, 1, c2, c8, 5) +#define WSYS_ETMDVCVR7(val) MCR(val, 14, 1, c2, c12, 5) +#define WSYS_ETMDVCMR0(val) MCR(val, 14, 1, c2, c0, 6) +#define WSYS_ETMDVCMR1(val) MCR(val, 14, 1, c2, c4, 6) +#define WSYS_ETMDVCMR2(val) MCR(val, 14, 1, c2, c8, 6) +#define WSYS_ETMDVCMR3(val) MCR(val, 14, 1, c2, c12, 6) +#define WSYS_ETMDVCMR4(val) MCR(val, 14, 1, c2, c0, 7) +#define WSYS_ETMDVCMR5(val) MCR(val, 14, 1, c2, c4, 7) +#define WSYS_ETMDVCMR6(val) MCR(val, 14, 1, c2, c8, 7) +#define WSYS_ETMDVCMR7(val) MCR(val, 14, 1, c2, c12, 7) +#define WSYS_ETMSSCCR0(val) MCR(val, 14, 1, c1, c0, 2) +#define WSYS_ETMSSCCR1(val) MCR(val, 14, 1, c1, c1, 2) +#define WSYS_ETMSSCCR2(val) MCR(val, 14, 1, c1, c2, 2) +#define WSYS_ETMSSCCR3(val) MCR(val, 14, 1, c1, c3, 2) +#define WSYS_ETMSSCCR4(val) MCR(val, 14, 1, c1, c4, 2) +#define WSYS_ETMSSCCR5(val) MCR(val, 14, 1, c1, c5, 2) +#define WSYS_ETMSSCCR6(val) MCR(val, 14, 1, c1, c6, 2) +#define WSYS_ETMSSCCR7(val) MCR(val, 14, 1, c1, c7, 2) +#define WSYS_ETMSSCSR0(val) MCR(val, 14, 1, c1, c8, 2) +#define WSYS_ETMSSCSR1(val) MCR(val, 14, 1, c1, c9, 2) +#define WSYS_ETMSSCSR2(val) MCR(val, 14, 1, c1, c10, 2) +#define WSYS_ETMSSCSR3(val) MCR(val, 14, 1, c1, c11, 2) +#define WSYS_ETMSSCSR4(val) MCR(val, 14, 1, c1, c12, 2) +#define WSYS_ETMSSCSR5(val) MCR(val, 14, 1, c1, c13, 2) +#define WSYS_ETMSSCSR6(val) MCR(val, 14, 1, c1, c14, 2) +#define WSYS_ETMSSCSR7(val) MCR(val, 14, 1, c1, c15, 2) +#define WSYS_ETMSSPCICR0(val) MCR(val, 14, 1, c1, c0, 3) +#define WSYS_ETMSSPCICR1(val) MCR(val, 14, 1, c1, c1, 3) +#define WSYS_ETMSSPCICR2(val) MCR(val, 14, 1, c1, c2, 3) +#define WSYS_ETMSSPCICR3(val) MCR(val, 14, 1, c1, c3, 3) +#define WSYS_ETMSSPCICR4(val) MCR(val, 14, 1, c1, c4, 3) +#define WSYS_ETMSSPCICR5(val) MCR(val, 14, 1, c1, c5, 3) +#define WSYS_ETMSSPCICR6(val) MCR(val, 14, 1, c1, c6, 3) +#define WSYS_ETMSSPCICR7(val) MCR(val, 14, 1, c1, c7, 3) + +#endif diff --git a/arch/arm/include/asm/hardware/debugv8.h b/arch/arm/include/asm/hardware/debugv8.h new file mode 100644 index 000000000000..6ba0026c0946 --- /dev/null +++ b/arch/arm/include/asm/hardware/debugv8.h @@ -0,0 +1,248 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* Copyright (c) 2016, 2018, 2021, 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 __ASM_HARDWARE_DEBUGV8_H +#define __ASM_HARDWARE_DEBUGV8_H + +#include + +/* Accessors for CP14 registers */ +#define dbg_read(reg) RCP14_##reg() +#define dbg_write(val, reg) WCP14_##reg(val) + +/* MRC14 registers */ +#define MRC14(op1, crn, crm, op2) \ +({ \ +uint32_t val; \ +asm volatile("mrc p14, "#op1", %0, "#crn", "#crm", "#op2 : "=r" (val)); \ +val; \ +}) + +/* MCR14 registers */ +#define MCR14(val, op1, crn, crm, op2) \ +({ \ +asm volatile("mcr p14, "#op1", %0, "#crn", "#crm", "#op2 : : "r" (val));\ +}) + +/* + * Debug Registers + * + * Read only + * DBGDIDR, DBGDSCRint, DBGDTRRXint, DBGDRAR, DBGOSLSR, DBGOSSRR, DBGDSAR, + * DBGAUTHSTATUS, DBGDEVID2, DBGDEVID1, DBGDEVID + * + * Write only + * DBGDTRTXint, DBGOSLAR + */ +#define RCP14_DBGDIDR() MRC14(0, c0, c0, 0) +#define RCP14_DBGDSCRint() MRC14(0, c0, c1, 0) +#define RCP14_DBGDCCINT() MRC14(0, c0, c2, 0) +#define RCP14_DBGDTRRXint() MRC14(0, c0, c5, 0) +#define RCP14_DBGWFAR() MRC14(0, c0, c6, 0) +#define RCP14_DBGVCR() MRC14(0, c0, c7, 0) +#define RCP14_DBGDTRRXext() MRC14(0, c0, c0, 2) +#define RCP14_DBGDSCRext() MRC14(0, c0, c2, 2) +#define RCP14_DBGDTRTXext() MRC14(0, c0, c3, 2) +#define RCP14_DBGOSECCR() MRC14(0, c0, c6, 2) +#define RCP14_DBGBVR0() MRC14(0, c0, c0, 4) +#define RCP14_DBGBVR1() MRC14(0, c0, c1, 4) +#define RCP14_DBGBVR2() MRC14(0, c0, c2, 4) +#define RCP14_DBGBVR3() MRC14(0, c0, c3, 4) +#define RCP14_DBGBVR4() MRC14(0, c0, c4, 4) +#define RCP14_DBGBVR5() MRC14(0, c0, c5, 4) +#define RCP14_DBGBVR6() MRC14(0, c0, c6, 4) +#define RCP14_DBGBVR7() MRC14(0, c0, c7, 4) +#define RCP14_DBGBVR8() MRC14(0, c0, c8, 4) +#define RCP14_DBGBVR9() MRC14(0, c0, c9, 4) +#define RCP14_DBGBVR10() MRC14(0, c0, c10, 4) +#define RCP14_DBGBVR11() MRC14(0, c0, c11, 4) +#define RCP14_DBGBVR12() MRC14(0, c0, c12, 4) +#define RCP14_DBGBVR13() MRC14(0, c0, c13, 4) +#define RCP14_DBGBVR14() MRC14(0, c0, c14, 4) +#define RCP14_DBGBVR15() MRC14(0, c0, c15, 4) +#define RCP14_DBGBCR0() MRC14(0, c0, c0, 5) +#define RCP14_DBGBCR1() MRC14(0, c0, c1, 5) +#define RCP14_DBGBCR2() MRC14(0, c0, c2, 5) +#define RCP14_DBGBCR3() MRC14(0, c0, c3, 5) +#define RCP14_DBGBCR4() MRC14(0, c0, c4, 5) +#define RCP14_DBGBCR5() MRC14(0, c0, c5, 5) +#define RCP14_DBGBCR6() MRC14(0, c0, c6, 5) +#define RCP14_DBGBCR7() MRC14(0, c0, c7, 5) +#define RCP14_DBGBCR8() MRC14(0, c0, c8, 5) +#define RCP14_DBGBCR9() MRC14(0, c0, c9, 5) +#define RCP14_DBGBCR10() MRC14(0, c0, c10, 5) +#define RCP14_DBGBCR11() MRC14(0, c0, c11, 5) +#define RCP14_DBGBCR12() MRC14(0, c0, c12, 5) +#define RCP14_DBGBCR13() MRC14(0, c0, c13, 5) +#define RCP14_DBGBCR14() MRC14(0, c0, c14, 5) +#define RCP14_DBGBCR15() MRC14(0, c0, c15, 5) +#define RCP14_DBGWVR0() MRC14(0, c0, c0, 6) +#define RCP14_DBGWVR1() MRC14(0, c0, c1, 6) +#define RCP14_DBGWVR2() MRC14(0, c0, c2, 6) +#define RCP14_DBGWVR3() MRC14(0, c0, c3, 6) +#define RCP14_DBGWVR4() MRC14(0, c0, c4, 6) +#define RCP14_DBGWVR5() MRC14(0, c0, c5, 6) +#define RCP14_DBGWVR6() MRC14(0, c0, c6, 6) +#define RCP14_DBGWVR7() MRC14(0, c0, c7, 6) +#define RCP14_DBGWVR8() MRC14(0, c0, c8, 6) +#define RCP14_DBGWVR9() MRC14(0, c0, c9, 6) +#define RCP14_DBGWVR10() MRC14(0, c0, c10, 6) +#define RCP14_DBGWVR11() MRC14(0, c0, c11, 6) +#define RCP14_DBGWVR12() MRC14(0, c0, c12, 6) +#define RCP14_DBGWVR13() MRC14(0, c0, c13, 6) +#define RCP14_DBGWVR14() MRC14(0, c0, c14, 6) +#define RCP14_DBGWVR15() MRC14(0, c0, c15, 6) +#define RCP14_DBGWCR0() MRC14(0, c0, c0, 7) +#define RCP14_DBGWCR1() MRC14(0, c0, c1, 7) +#define RCP14_DBGWCR2() MRC14(0, c0, c2, 7) +#define RCP14_DBGWCR3() MRC14(0, c0, c3, 7) +#define RCP14_DBGWCR4() MRC14(0, c0, c4, 7) +#define RCP14_DBGWCR5() MRC14(0, c0, c5, 7) +#define RCP14_DBGWCR6() MRC14(0, c0, c6, 7) +#define RCP14_DBGWCR7() MRC14(0, c0, c7, 7) +#define RCP14_DBGWCR8() MRC14(0, c0, c8, 7) +#define RCP14_DBGWCR9() MRC14(0, c0, c9, 7) +#define RCP14_DBGWCR10() MRC14(0, c0, c10, 7) +#define RCP14_DBGWCR11() MRC14(0, c0, c11, 7) +#define RCP14_DBGWCR12() MRC14(0, c0, c12, 7) +#define RCP14_DBGWCR13() MRC14(0, c0, c13, 7) +#define RCP14_DBGWCR14() MRC14(0, c0, c14, 7) +#define RCP14_DBGWCR15() MRC14(0, c0, c15, 7) +#define RCP14_DBGDRAR() MRC14(0, c1, c0, 0) +#define RCP14_DBGBXVR0() MRC14(0, c1, c0, 1) +#define RCP14_DBGBXVR1() MRC14(0, c1, c1, 1) +#define RCP14_DBGBXVR2() MRC14(0, c1, c2, 1) +#define RCP14_DBGBXVR3() MRC14(0, c1, c3, 1) +#define RCP14_DBGBXVR4() MRC14(0, c1, c4, 1) +#define RCP14_DBGBXVR5() MRC14(0, c1, c5, 1) +#define RCP14_DBGBXVR6() MRC14(0, c1, c6, 1) +#define RCP14_DBGBXVR7() MRC14(0, c1, c7, 1) +#define RCP14_DBGBXVR8() MRC14(0, c1, c8, 1) +#define RCP14_DBGBXVR9() MRC14(0, c1, c9, 1) +#define RCP14_DBGBXVR10() MRC14(0, c1, c10, 1) +#define RCP14_DBGBXVR11() MRC14(0, c1, c11, 1) +#define RCP14_DBGBXVR12() MRC14(0, c1, c12, 1) +#define RCP14_DBGBXVR13() MRC14(0, c1, c13, 1) +#define RCP14_DBGBXVR14() MRC14(0, c1, c14, 1) +#define RCP14_DBGBXVR15() MRC14(0, c1, c15, 1) +#define RCP14_DBGOSLSR() MRC14(0, c1, c1, 4) +#define RCP14_DBGOSSRR() MRC14(0, c1, c2, 4) +#define RCP14_DBGOSDLR() MRC14(0, c1, c3, 4) +#define RCP14_DBGPRCR() MRC14(0, c1, c4, 4) +#define RCP14_DBGPRSR() MRC14(0, c1, c5, 4) +#define RCP14_DBGDSAR() MRC14(0, c2, c0, 0) +#define RCP14_DBGITCTRL() MRC14(0, c7, c0, 4) +#define RCP14_DBGCLAIMSET() MRC14(0, c7, c8, 6) +#define RCP14_DBGCLAIMCLR() MRC14(0, c7, c9, 6) +#define RCP14_DBGAUTHSTATUS() MRC14(0, c7, c14, 6) +#define RCP14_DBGDEVID2() MRC14(0, c7, c0, 7) +#define RCP14_DBGDEVID1() MRC14(0, c7, c1, 7) +#define RCP14_DBGDEVID() MRC14(0, c7, c2, 7) + +#define WCP14_DBGDCCINT(val) MCR14(val, 0, c0, c2, 0) +#define WCP14_DBGDTRTXint(val) MCR14(val, 0, c0, c5, 0) +#define WCP14_DBGWFAR(val) MCR14(val, 0, c0, c6, 0) +#define WCP14_DBGVCR(val) MCR14(val, 0, c0, c7, 0) +#define WCP14_DBGDTRRXext(val) MCR14(val, 0, c0, c0, 2) +#define WCP14_DBGDSCRext(val) MCR14(val, 0, c0, c2, 2) +#define WCP14_DBGDTRTXext(val) MCR14(val, 0, c0, c3, 2) +#define WCP14_DBGOSECCR(val) MCR14(val, 0, c0, c6, 2) +#define WCP14_DBGBVR0(val) MCR14(val, 0, c0, c0, 4) +#define WCP14_DBGBVR1(val) MCR14(val, 0, c0, c1, 4) +#define WCP14_DBGBVR2(val) MCR14(val, 0, c0, c2, 4) +#define WCP14_DBGBVR3(val) MCR14(val, 0, c0, c3, 4) +#define WCP14_DBGBVR4(val) MCR14(val, 0, c0, c4, 4) +#define WCP14_DBGBVR5(val) MCR14(val, 0, c0, c5, 4) +#define WCP14_DBGBVR6(val) MCR14(val, 0, c0, c6, 4) +#define WCP14_DBGBVR7(val) MCR14(val, 0, c0, c7, 4) +#define WCP14_DBGBVR8(val) MCR14(val, 0, c0, c8, 4) +#define WCP14_DBGBVR9(val) MCR14(val, 0, c0, c9, 4) +#define WCP14_DBGBVR10(val) MCR14(val, 0, c0, c10, 4) +#define WCP14_DBGBVR11(val) MCR14(val, 0, c0, c11, 4) +#define WCP14_DBGBVR12(val) MCR14(val, 0, c0, c12, 4) +#define WCP14_DBGBVR13(val) MCR14(val, 0, c0, c13, 4) +#define WCP14_DBGBVR14(val) MCR14(val, 0, c0, c14, 4) +#define WCP14_DBGBVR15(val) MCR14(val, 0, c0, c15, 4) +#define WCP14_DBGBCR0(val) MCR14(val, 0, c0, c0, 5) +#define WCP14_DBGBCR1(val) MCR14(val, 0, c0, c1, 5) +#define WCP14_DBGBCR2(val) MCR14(val, 0, c0, c2, 5) +#define WCP14_DBGBCR3(val) MCR14(val, 0, c0, c3, 5) +#define WCP14_DBGBCR4(val) MCR14(val, 0, c0, c4, 5) +#define WCP14_DBGBCR5(val) MCR14(val, 0, c0, c5, 5) +#define WCP14_DBGBCR6(val) MCR14(val, 0, c0, c6, 5) +#define WCP14_DBGBCR7(val) MCR14(val, 0, c0, c7, 5) +#define WCP14_DBGBCR8(val) MCR14(val, 0, c0, c8, 5) +#define WCP14_DBGBCR9(val) MCR14(val, 0, c0, c9, 5) +#define WCP14_DBGBCR10(val) MCR14(val, 0, c0, c10, 5) +#define WCP14_DBGBCR11(val) MCR14(val, 0, c0, c11, 5) +#define WCP14_DBGBCR12(val) MCR14(val, 0, c0, c12, 5) +#define WCP14_DBGBCR13(val) MCR14(val, 0, c0, c13, 5) +#define WCP14_DBGBCR14(val) MCR14(val, 0, c0, c14, 5) +#define WCP14_DBGBCR15(val) MCR14(val, 0, c0, c15, 5) +#define WCP14_DBGWVR0(val) MCR14(val, 0, c0, c0, 6) +#define WCP14_DBGWVR1(val) MCR14(val, 0, c0, c1, 6) +#define WCP14_DBGWVR2(val) MCR14(val, 0, c0, c2, 6) +#define WCP14_DBGWVR3(val) MCR14(val, 0, c0, c3, 6) +#define WCP14_DBGWVR4(val) MCR14(val, 0, c0, c4, 6) +#define WCP14_DBGWVR5(val) MCR14(val, 0, c0, c5, 6) +#define WCP14_DBGWVR6(val) MCR14(val, 0, c0, c6, 6) +#define WCP14_DBGWVR7(val) MCR14(val, 0, c0, c7, 6) +#define WCP14_DBGWVR8(val) MCR14(val, 0, c0, c8, 6) +#define WCP14_DBGWVR9(val) MCR14(val, 0, c0, c9, 6) +#define WCP14_DBGWVR10(val) MCR14(val, 0, c0, c10, 6) +#define WCP14_DBGWVR11(val) MCR14(val, 0, c0, c11, 6) +#define WCP14_DBGWVR12(val) MCR14(val, 0, c0, c12, 6) +#define WCP14_DBGWVR13(val) MCR14(val, 0, c0, c13, 6) +#define WCP14_DBGWVR14(val) MCR14(val, 0, c0, c14, 6) +#define WCP14_DBGWVR15(val) MCR14(val, 0, c0, c15, 6) +#define WCP14_DBGWCR0(val) MCR14(val, 0, c0, c0, 7) +#define WCP14_DBGWCR1(val) MCR14(val, 0, c0, c1, 7) +#define WCP14_DBGWCR2(val) MCR14(val, 0, c0, c2, 7) +#define WCP14_DBGWCR3(val) MCR14(val, 0, c0, c3, 7) +#define WCP14_DBGWCR4(val) MCR14(val, 0, c0, c4, 7) +#define WCP14_DBGWCR5(val) MCR14(val, 0, c0, c5, 7) +#define WCP14_DBGWCR6(val) MCR14(val, 0, c0, c6, 7) +#define WCP14_DBGWCR7(val) MCR14(val, 0, c0, c7, 7) +#define WCP14_DBGWCR8(val) MCR14(val, 0, c0, c8, 7) +#define WCP14_DBGWCR9(val) MCR14(val, 0, c0, c9, 7) +#define WCP14_DBGWCR10(val) MCR14(val, 0, c0, c10, 7) +#define WCP14_DBGWCR11(val) MCR14(val, 0, c0, c11, 7) +#define WCP14_DBGWCR12(val) MCR14(val, 0, c0, c12, 7) +#define WCP14_DBGWCR13(val) MCR14(val, 0, c0, c13, 7) +#define WCP14_DBGWCR14(val) MCR14(val, 0, c0, c14, 7) +#define WCP14_DBGWCR15(val) MCR14(val, 0, c0, c15, 7) +#define WCP14_DBGBXVR0(val) MCR14(val, 0, c1, c0, 1) +#define WCP14_DBGBXVR1(val) MCR14(val, 0, c1, c1, 1) +#define WCP14_DBGBXVR2(val) MCR14(val, 0, c1, c2, 1) +#define WCP14_DBGBXVR3(val) MCR14(val, 0, c1, c3, 1) +#define WCP14_DBGBXVR4(val) MCR14(val, 0, c1, c4, 1) +#define WCP14_DBGBXVR5(val) MCR14(val, 0, c1, c5, 1) +#define WCP14_DBGBXVR6(val) MCR14(val, 0, c1, c6, 1) +#define WCP14_DBGBXVR7(val) MCR14(val, 0, c1, c7, 1) +#define WCP14_DBGBXVR8(val) MCR14(val, 0, c1, c8, 1) +#define WCP14_DBGBXVR9(val) MCR14(val, 0, c1, c9, 1) +#define WCP14_DBGBXVR10(val) MCR14(val, 0, c1, c10, 1) +#define WCP14_DBGBXVR11(val) MCR14(val, 0, c1, c11, 1) +#define WCP14_DBGBXVR12(val) MCR14(val, 0, c1, c12, 1) +#define WCP14_DBGBXVR13(val) MCR14(val, 0, c1, c13, 1) +#define WCP14_DBGBXVR14(val) MCR14(val, 0, c1, c14, 1) +#define WCP14_DBGBXVR15(val) MCR14(val, 0, c1, c15, 1) +#define WCP14_DBGOSLAR(val) MCR14(val, 0, c1, c0, 4) +#define WCP14_DBGOSSRR(val) MCR14(val, 0, c1, c2, 4) +#define WCP14_DBGOSDLR(val) MCR14(val, 0, c1, c3, 4) +#define WCP14_DBGPRCR(val) MCR14(val, 0, c1, c4, 4) +#define WCP14_DBGITCTRL(val) MCR14(val, 0, c7, c0, 4) +#define WCP14_DBGCLAIMSET(val) MCR14(val, 0, c7, c8, 6) +#define WCP14_DBGCLAIMCLR(val) MCR14(val, 0, c7, c9, 6) + +#endif diff --git a/arch/arm/include/asm/hw_breakpoint.h b/arch/arm/include/asm/hw_breakpoint.h index ac54c06764e6..c67df04a2c9d 100644 --- a/arch/arm/include/asm/hw_breakpoint.h +++ b/arch/arm/include/asm/hw_breakpoint.h @@ -53,6 +53,7 @@ static inline void decode_ctrl_reg(u32 reg, #define ARM_DEBUG_ARCH_V7_MM 4 #define ARM_DEBUG_ARCH_V7_1 5 #define ARM_DEBUG_ARCH_V8 6 +#define ARM_DEBUG_ARCH_V8_8 8 /* Breakpoint */ #define ARM_BREAKPOINT_EXECUTE 0 diff --git a/arch/arm64/configs/vendor/kona_defconfig b/arch/arm64/configs/vendor/kona_defconfig index 1e43017ca2ed..ed1204b60381 100644 --- a/arch/arm64/configs/vendor/kona_defconfig +++ b/arch/arm64/configs/vendor/kona_defconfig @@ -370,6 +370,7 @@ CONFIG_TABLET_USB_HANWANG=y CONFIG_TABLET_USB_KBTAB=y CONFIG_INPUT_TOUCHSCREEN=y CONFIG_TOUCHSCREEN_FTS=y +CONFIG_TOUCHSCREEN_NT36XXX=y CONFIG_INPUT_MISC=y CONFIG_INPUT_QPNP_POWER_ON=y CONFIG_INPUT_QTI_HAPTICS=y diff --git a/arch/arm64/configs/vendor/msm8937-perf_defconfig b/arch/arm64/configs/vendor/msm8937-perf_defconfig index f490bfb1d592..46b5b4561041 100644 --- a/arch/arm64/configs/vendor/msm8937-perf_defconfig +++ b/arch/arm64/configs/vendor/msm8937-perf_defconfig @@ -365,7 +365,10 @@ CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET_QCOM=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -374,6 +377,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -508,9 +512,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -651,7 +657,11 @@ CONFIG_BUG_ON_DATA_CORRUPTION=y CONFIG_DEBUG_ALIGN_RODATA=y CONFIG_CORESIGHT=y CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y CONFIG_CORESIGHT_STM=y CONFIG_CORESIGHT_TPDA=y CONFIG_CORESIGHT_TPDM=y CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y +CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/arch/arm64/configs/vendor/msm8937_defconfig b/arch/arm64/configs/vendor/msm8937_defconfig index 584e5dbec44e..dfa5df35c1a7 100644 --- a/arch/arm64/configs/vendor/msm8937_defconfig +++ b/arch/arm64/configs/vendor/msm8937_defconfig @@ -374,7 +374,10 @@ CONFIG_PINCTRL_QCOM_SPMI_PMIC=y CONFIG_GPIO_SYSFS=y CONFIG_POWER_RESET_QCOM=y CONFIG_QPNP_SMB5=y +CONFIG_QPNP_VM_BMS=y +CONFIG_QPNP_LINEAR_CHARGER=y CONFIG_SMB1351_USB_CHARGER=y +CONFIG_SMB1360_CHARGER_FG=y CONFIG_SMB1355_SLAVE_CHARGER=y CONFIG_QPNP_QG=y CONFIG_THERMAL=y @@ -383,6 +386,7 @@ CONFIG_THERMAL_GOV_USER_SPACE=y CONFIG_THERMAL_GOV_LOW_LIMITS=y CONFIG_CPU_THERMAL=y CONFIG_DEVFREQ_THERMAL=y +CONFIG_QCOM_SPMI_TEMP_ALARM=y CONFIG_THERMAL_QPNP_ADC_TM=y CONFIG_THERMAL_TSENS=y CONFIG_QTI_ADC_TM=y @@ -521,9 +525,11 @@ CONFIG_MMC_CQHCI_CRYPTO_QTI=y CONFIG_LEDS_QTI_TRI_LED=y CONFIG_LEDS_QPNP_FLASH_V2=y CONFIG_LEDS_QPNP_VIBRATOR_LDO=y +CONFIG_LEDS_QPNP_VIBRATOR=y CONFIG_LEDS_TRIGGER_TIMER=y CONFIG_EDAC=y CONFIG_RTC_CLASS=y +CONFIG_RTC_DRV_PM8XXX=y CONFIG_DMADEVICES=y CONFIG_QCOM_SPS_DMA=y CONFIG_UIO=y @@ -716,12 +722,15 @@ CONFIG_ATOMIC64_SELFTEST=m CONFIG_MEMTEST=y CONFIG_BUG_ON_DATA_CORRUPTION=y CONFIG_PANIC_ON_DATA_CORRUPTION=y -CONFIG_PID_IN_CONTEXTIDR=y CONFIG_CORESIGHT=y CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y +CONFIG_CORESIGHT_SOURCE_ETM4X=y +CONFIG_CORESIGHT_DYNAMIC_REPLICATOR=y CONFIG_CORESIGHT_STM=y CONFIG_CORESIGHT_CTI=y CONFIG_CORESIGHT_TPDA=y CONFIG_CORESIGHT_TPDM=y CONFIG_CORESIGHT_HWEVENT=y +CONFIG_CORESIGHT_DUMMY=y CONFIG_CORESIGHT_REMOTE_ETM=y +CONFIG_CORESIGHT_TGU=y diff --git a/drivers/dma-buf/dma-buf.c b/drivers/dma-buf/dma-buf.c index 6ddd1356fd3e..be0597f58d47 100644 --- a/drivers/dma-buf/dma-buf.c +++ b/drivers/dma-buf/dma-buf.c @@ -102,35 +102,12 @@ static char *dmabuffs_dname(struct dentry *dentry, char *buffer, int buflen) dentry->d_name.name, ret > 0 ? name : ""); } -static const struct dentry_operations dma_buf_dentry_ops = { - .d_dname = dmabuffs_dname, -}; - -static struct vfsmount *dma_buf_mnt; - -static struct dentry *dma_buf_fs_mount(struct file_system_type *fs_type, - int flags, const char *name, void *data) -{ - return mount_pseudo(fs_type, "dmabuf:", NULL, &dma_buf_dentry_ops, - DMA_BUF_MAGIC); -} - -static struct file_system_type dma_buf_fs_type = { - .name = "dmabuf", - .mount = dma_buf_fs_mount, - .kill_sb = kill_anon_super, -}; - -static int dma_buf_release(struct inode *inode, struct file *file) +static void dma_buf_release(struct dentry *dentry) { struct dma_buf *dmabuf; - struct dentry *dentry = file->f_path.dentry; int dtor_ret = 0; - if (!is_dma_buf_file(file)) - return -EINVAL; - - dmabuf = file->private_data; + dmabuf = dentry->d_fsdata; spin_lock(&dentry->d_lock); dentry->d_fsdata = NULL; @@ -167,9 +144,28 @@ static int dma_buf_release(struct inode *inode, struct file *file) module_put(dmabuf->owner); dmabuf_dent_put(dmabuf); - return 0; } +static const struct dentry_operations dma_buf_dentry_ops = { + .d_dname = dmabuffs_dname, + .d_release = dma_buf_release, +}; + +static struct vfsmount *dma_buf_mnt; + +static struct dentry *dma_buf_fs_mount(struct file_system_type *fs_type, + int flags, const char *name, void *data) +{ + return mount_pseudo(fs_type, "dmabuf:", NULL, &dma_buf_dentry_ops, + DMA_BUF_MAGIC); +} + +static struct file_system_type dma_buf_fs_type = { + .name = "dmabuf", + .mount = dma_buf_fs_mount, + .kill_sb = kill_anon_super, +}; + static int dma_buf_mmap_internal(struct file *file, struct vm_area_struct *vma) { struct dma_buf *dmabuf; @@ -488,7 +484,6 @@ static void dma_buf_show_fdinfo(struct seq_file *m, struct file *file) } static const struct file_operations dma_buf_fops = { - .release = dma_buf_release, .mmap = dma_buf_mmap_internal, .llseek = dma_buf_llseek, .poll = dma_buf_poll, diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c index 6d18580125a8..5d0dd684788c 100644 --- a/drivers/gpu/msm/adreno_a6xx.c +++ b/drivers/gpu/msm/adreno_a6xx.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #include @@ -89,9 +89,27 @@ static u32 a6xx_ifpc_pwrup_reglist[] = { }; /* Applicable to a620 and a650 */ +static u32 a650_ifpc_pwrup_reglist[] = { + A6XX_CP_PROTECT_REG+32, + A6XX_CP_PROTECT_REG+33, + A6XX_CP_PROTECT_REG+34, + A6XX_CP_PROTECT_REG+35, + A6XX_CP_PROTECT_REG+36, + A6XX_CP_PROTECT_REG+37, + A6XX_CP_PROTECT_REG+38, + A6XX_CP_PROTECT_REG+39, + A6XX_CP_PROTECT_REG+40, + A6XX_CP_PROTECT_REG+41, + A6XX_CP_PROTECT_REG+42, + A6XX_CP_PROTECT_REG+43, + A6XX_CP_PROTECT_REG+44, + A6XX_CP_PROTECT_REG+45, + A6XX_CP_PROTECT_REG+46, + A6XX_CP_PROTECT_REG+47, +}; + static u32 a650_pwrup_reglist[] = { A6XX_RBBM_GBIF_CLIENT_QOS_CNTL, - A6XX_CP_PROTECT_REG + 47, /* Programmed for infinite span */ A6XX_TPL1_BICUBIC_WEIGHTS_TABLE_0, A6XX_TPL1_BICUBIC_WEIGHTS_TABLE_1, A6XX_TPL1_BICUBIC_WEIGHTS_TABLE_2, @@ -351,14 +369,21 @@ struct a6xx_reglist_list { static void a6xx_patch_pwrup_reglist(struct adreno_device *adreno_dev) { - struct a6xx_reglist_list reglist[3]; + struct a6xx_reglist_list reglist[4]; void *ptr = adreno_dev->pwrup_reglist.hostptr; struct cpu_gpu_lock *lock = ptr; int items = 0, i, j; u32 *dest = ptr + sizeof(*lock); + u16 list_offset = 0; /* Static IFPC-only registers */ - reglist[items++] = REGLIST(a6xx_ifpc_pwrup_reglist); + reglist[items] = REGLIST(a6xx_ifpc_pwrup_reglist); + list_offset += reglist[items++].count * 2; + + if (adreno_is_a650_family(adreno_dev)) { + reglist[items] = REGLIST(a650_ifpc_pwrup_reglist); + list_offset += reglist[items++].count * 2; + } /* Static IFPC + preemption registers */ reglist[items++] = REGLIST(a6xx_pwrup_reglist); @@ -401,7 +426,7 @@ static void a6xx_patch_pwrup_reglist(struct adreno_device *adreno_dev) * all the lists and list_offset should be specified as the size in * dwords of the first entry in the list. */ - lock->list_offset = reglist[0].count * 2; + lock->list_offset = list_offset; } /* diff --git a/drivers/hwtracing/coresight/coresight-tmc.c b/drivers/hwtracing/coresight/coresight-tmc.c index de9577449593..1571a3f978a5 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.c +++ b/drivers/hwtracing/coresight/coresight-tmc.c @@ -1,5 +1,5 @@ // SPDX-License-Identifier: GPL-2.0 -/* Copyright (c) 2012,2017-2019, The Linux Foundation. All rights reserved. +/* Copyright (c) 2012,2017-2019,2021, The Linux Foundation. All rights reserved. * * Description: CoreSight Trace Memory Controller driver */ @@ -23,10 +23,13 @@ #include #include #include +#include #include "coresight-priv.h" #include "coresight-tmc.h" +#define TMC_REG_DUMP_MAGIC 0x42445953 + void tmc_wait_for_tmcready(struct tmc_drvdata *drvdata) { /* Ensure formatter, unformatter and hardware fifo are empty */ @@ -56,10 +59,84 @@ void tmc_flush_and_stop(struct tmc_drvdata *drvdata) tmc_wait_for_tmcready(drvdata); } +static void __tmc_reg_dump(struct tmc_drvdata *drvdata) +{ + struct dump_vaddr_entry *dump_entry; + struct msm_dump_data *dump_data; + uint32_t *reg_buf; + + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + dump_entry = get_msm_dump_ptr(MSM_DUMP_DATA_TMC_ETR_REG); + dev_dbg(drvdata->dev, "%s: TMC ETR dump entry ptr is %pK\n", + __func__, dump_entry); + } else if (drvdata->config_type == TMC_CONFIG_TYPE_ETB || + drvdata->config_type == TMC_CONFIG_TYPE_ETF) { + dump_entry = get_msm_dump_ptr(MSM_DUMP_DATA_TMC_ETF_REG); + dev_dbg(drvdata->dev, "%s: TMC ETF dump entry ptr is %pK\n", + __func__, dump_entry); + } else + return; + + if (dump_entry == NULL) + return; + + reg_buf = (uint32_t *)(dump_entry->dump_vaddr); + dump_data = dump_entry->dump_data_vaddr; + + if (reg_buf == NULL || dump_data == NULL) + return; + + dev_dbg(drvdata->dev, "%s: TMC dump reg ptr is %pK, dump_data is %pK\n", + __func__, reg_buf, dump_data); + + reg_buf[1] = readl_relaxed(drvdata->base + TMC_RSZ); + reg_buf[3] = readl_relaxed(drvdata->base + TMC_STS); + reg_buf[5] = readl_relaxed(drvdata->base + TMC_RRP); + reg_buf[6] = readl_relaxed(drvdata->base + TMC_RWP); + reg_buf[7] = readl_relaxed(drvdata->base + TMC_TRG); + reg_buf[8] = readl_relaxed(drvdata->base + TMC_CTL); + reg_buf[10] = readl_relaxed(drvdata->base + TMC_MODE); + reg_buf[11] = readl_relaxed(drvdata->base + TMC_LBUFLEVEL); + reg_buf[12] = readl_relaxed(drvdata->base + TMC_CBUFLEVEL); + reg_buf[13] = readl_relaxed(drvdata->base + TMC_BUFWM); + if (drvdata->config_type == TMC_CONFIG_TYPE_ETR) { + reg_buf[14] = readl_relaxed(drvdata->base + TMC_RRPHI); + reg_buf[15] = readl_relaxed(drvdata->base + TMC_RWPHI); + reg_buf[68] = readl_relaxed(drvdata->base + TMC_AXICTL); + reg_buf[70] = readl_relaxed(drvdata->base + TMC_DBALO); + reg_buf[71] = readl_relaxed(drvdata->base + TMC_DBAHI); + } + reg_buf[192] = readl_relaxed(drvdata->base + TMC_FFSR); + reg_buf[193] = readl_relaxed(drvdata->base + TMC_FFCR); + reg_buf[194] = readl_relaxed(drvdata->base + TMC_PSCR); + reg_buf[1000] = readl_relaxed(drvdata->base + CORESIGHT_CLAIMSET); + reg_buf[1001] = readl_relaxed(drvdata->base + CORESIGHT_CLAIMCLR); + reg_buf[1005] = readl_relaxed(drvdata->base + CORESIGHT_LSR); + reg_buf[1006] = readl_relaxed(drvdata->base + CORESIGHT_AUTHSTATUS); + reg_buf[1010] = readl_relaxed(drvdata->base + CORESIGHT_DEVID); + reg_buf[1011] = readl_relaxed(drvdata->base + CORESIGHT_DEVTYPE); + reg_buf[1012] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR4); + reg_buf[1013] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR5); + reg_buf[1014] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR6); + reg_buf[1015] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR7); + reg_buf[1016] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR0); + reg_buf[1017] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR1); + reg_buf[1018] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR2); + reg_buf[1019] = readl_relaxed(drvdata->base + CORESIGHT_PERIPHIDR3); + reg_buf[1020] = readl_relaxed(drvdata->base + CORESIGHT_COMPIDR0); + reg_buf[1021] = readl_relaxed(drvdata->base + CORESIGHT_COMPIDR1); + reg_buf[1022] = readl_relaxed(drvdata->base + CORESIGHT_COMPIDR2); + reg_buf[1023] = readl_relaxed(drvdata->base + CORESIGHT_COMPIDR3); + + dump_data->magic = TMC_REG_DUMP_MAGIC; +} + void tmc_enable_hw(struct tmc_drvdata *drvdata) { drvdata->enable = true; writel_relaxed(TMC_CTL_CAPT_EN, drvdata->base + TMC_CTL); + if (drvdata->force_reg_dump) + __tmc_reg_dump(drvdata); } void tmc_disable_hw(struct tmc_drvdata *drvdata) @@ -650,6 +727,8 @@ static int tmc_probe(struct amba_device *adev, const struct amba_id *id) return -EPROBE_DEFER; } } + if (of_property_read_bool(drvdata->dev->of_node, "qcom,force-reg-dump")) + drvdata->force_reg_dump = true; desc.pdata = pdata; desc.dev = dev; diff --git a/drivers/hwtracing/coresight/coresight-tmc.h b/drivers/hwtracing/coresight/coresight-tmc.h index 1ca22dd77b50..9583a8bf8cad 100644 --- a/drivers/hwtracing/coresight/coresight-tmc.h +++ b/drivers/hwtracing/coresight/coresight-tmc.h @@ -268,6 +268,7 @@ struct tmc_drvdata { struct idr idr; struct mutex idr_mutex; struct etr_buf *perf_buf; + bool force_reg_dump; }; struct etr_buf_operations { diff --git a/drivers/media/platform/msm/npu/npu_mgr.c b/drivers/media/platform/msm/npu/npu_mgr.c index 32adf1babe58..8e93a71aa0e1 100644 --- a/drivers/media/platform/msm/npu/npu_mgr.c +++ b/drivers/media/platform/msm/npu/npu_mgr.c @@ -15,6 +15,7 @@ #include "npu_common.h" #include #include +#include /* ------------------------------------------------------------------------- * Defines @@ -279,6 +280,50 @@ int load_fw(struct npu_device *npu_dev) return 0; } +static void complete_pending_commands(struct npu_host_ctx *host_ctx) +{ + struct npu_network *network = NULL; + struct npu_kevent kevt; + struct npu_network_cmd *cmd; + struct npu_misc_cmd *misc_cmd; + int i; + + /* flush all pending npu cmds */ + for (i = 0; i < MAX_LOADED_NETWORK; i++) { + network = &host_ctx->networks[i]; + if (!network->is_valid || !network->fw_error) + continue; + + if (network->is_async) { + NPU_DBG("async cmd, queue ssr event\n"); + kevt.evt.type = MSM_NPU_EVENT_TYPE_SSR; + kevt.evt.u.ssr.network_hdl = + network->network_hdl; + if (npu_queue_event(network->client, &kevt)) + NPU_ERR("queue npu event failed\n"); + + while (!list_empty(&network->cmd_list)) { + cmd = list_first_entry(&network->cmd_list, + struct npu_network_cmd, list); + npu_dequeue_network_cmd(network, cmd); + npu_free_network_cmd(host_ctx, cmd); + } + } else { + list_for_each_entry(cmd, &network->cmd_list, list) { + NPU_INFO("complete network %llx trans_id %d\n", + network->id, cmd->trans_id); + complete(&cmd->cmd_done); + } + } + } + + list_for_each_entry(misc_cmd, &host_ctx->misc_cmd_list, list) { + NPU_INFO("complete misc cmd trans_id %d\n", + misc_cmd->trans_id); + complete(&misc_cmd->cmd_done); + } +} + int unload_fw(struct npu_device *npu_dev) { struct npu_host_ctx *host_ctx = &npu_dev->host_ctx; @@ -294,7 +339,9 @@ int unload_fw(struct npu_device *npu_dev) mutex_unlock(&host_ctx->lock); return 0; } else if (host_ctx->fw_state == FW_ENABLED) { - NPU_ERR("fw is enabled now, can't be unloaded\n"); + NPU_ERR("fw is enabled now, device is shutting down?\n"); + host_ctx->dev_shuttingdown = true; + complete_pending_commands(host_ctx); mutex_unlock(&host_ctx->lock); return -EBUSY; } @@ -315,6 +362,11 @@ static int enable_fw_nolock(struct npu_device *npu_dev) int ret = 0; uint32_t reg_val; + if (host_ctx->dev_shuttingdown) { + NPU_ERR("device is shutting down, ignore enable request\n"); + return -EIO; + } + if (host_ctx->fw_state == FW_UNLOADED) { ret = load_fw_nolock(npu_dev, host_ctx->auto_pil_disable ? true : false); @@ -470,6 +522,11 @@ static int disable_fw_nolock(struct npu_device *npu_dev) if (host_ctx->fw_ref_cnt > 0) return ret; + if (host_ctx->dev_shuttingdown) { + NPU_ERR("device is shutting down, ignore disable request\n"); + return -EIO; + } + /* turn on auto ACK for warm shuts down */ npu_cc_reg_write(npu_dev, NPU_CC_NPU_CPC_RSC_CTRL, 3); reinit_completion(&host_ctx->fw_shutdown_done); @@ -712,6 +769,24 @@ static int npu_panic_handler(struct notifier_block *this, return NOTIFY_DONE; } +static int npu_reboot_handler(struct notifier_block *this, + unsigned long code, void *unused) +{ + struct npu_host_ctx *host_ctx = + container_of(this, struct npu_host_ctx, reboot_nb); + + NPU_INFO("Device is rebooting with code %d\n", code); + + if ((code == NOTIFY_DONE) || (code == SYS_POWER_OFF)) { + mutex_lock(&host_ctx->lock); + host_ctx->dev_shuttingdown = true; + complete_pending_commands(host_ctx); + mutex_unlock(&host_ctx->lock); + } + + return NOTIFY_DONE; +} + static void npu_update_pwr_work(struct work_struct *work) { int ret; @@ -764,6 +839,13 @@ int npu_host_init(struct npu_device *npu_dev) goto fail; } + host_ctx->reboot_nb.notifier_call = npu_reboot_handler; + ret = register_reboot_notifier(&host_ctx->reboot_nb); + if (ret) { + NPU_ERR("register reboot notifier failed\n"); + goto fail; + } + host_ctx->panic_nb.notifier_call = npu_panic_handler; ret = atomic_notifier_chain_register(&panic_notifier_list, &host_ctx->panic_nb); @@ -839,6 +921,7 @@ int npu_host_init(struct npu_device *npu_dev) if (host_ctx->notif_hdle) subsys_notif_unregister_notifier(host_ctx->notif_hdle, &host_ctx->nb); + unregister_reboot_notifier(&host_ctx->reboot_nb); mutex_destroy(&host_ctx->lock); return ret; } @@ -854,6 +937,7 @@ void npu_host_deinit(struct npu_device *npu_dev) destroy_workqueue(host_ctx->wq); destroy_workqueue(host_ctx->wq_pri); subsys_notif_unregister_notifier(host_ctx->notif_hdle, &host_ctx->nb); + unregister_reboot_notifier(&host_ctx->reboot_nb); mutex_destroy(&host_ctx->lock); } @@ -947,9 +1031,6 @@ static int host_error_hdlr(struct npu_device *npu_dev, bool force) { struct npu_host_ctx *host_ctx = &npu_dev->host_ctx; struct npu_network *network = NULL; - struct npu_kevent kevt; - struct npu_network_cmd *cmd; - struct npu_misc_cmd *misc_cmd; bool fw_alive = true; int i, ret = 0; @@ -961,6 +1042,12 @@ static int host_error_hdlr(struct npu_device *npu_dev, bool force) return 0; } + if (host_ctx->dev_shuttingdown) { + NPU_INFO("device is shutting down, igonre error handler\n"); + mutex_unlock(&host_ctx->lock); + return -EIO; + } + if (host_ctx->wdg_irq_sts) { NPU_INFO("watchdog irq triggered\n"); fw_alive = false; @@ -1070,41 +1157,8 @@ static int host_error_hdlr(struct npu_device *npu_dev, bool force) } complete(&host_ctx->fw_deinit_done); + complete_pending_commands(host_ctx); - /* flush all pending npu cmds */ - for (i = 0; i < MAX_LOADED_NETWORK; i++) { - network = &host_ctx->networks[i]; - if (!network->is_valid || !network->fw_error) - continue; - - if (network->is_async) { - NPU_DBG("async cmd, queue ssr event\n"); - kevt.evt.type = MSM_NPU_EVENT_TYPE_SSR; - kevt.evt.u.ssr.network_hdl = - network->network_hdl; - if (npu_queue_event(network->client, &kevt)) - NPU_ERR("queue npu event failed\n"); - - while (!list_empty(&network->cmd_list)) { - cmd = list_first_entry(&network->cmd_list, - struct npu_network_cmd, list); - npu_dequeue_network_cmd(network, cmd); - npu_free_network_cmd(host_ctx, cmd); - } - } else { - list_for_each_entry(cmd, &network->cmd_list, list) { - NPU_DBG("complete network %llx trans_id %d\n", - network->id, cmd->trans_id); - complete(&cmd->cmd_done); - } - } - } - - list_for_each_entry(misc_cmd, &host_ctx->misc_cmd_list, list) { - NPU_DBG("complete misc cmd trans_id %d\n", - misc_cmd->trans_id); - complete(&misc_cmd->cmd_done); - } mutex_unlock(&host_ctx->lock); return ret; @@ -2086,6 +2140,7 @@ static int npu_send_network_cmd(struct npu_device *npu_dev, WARN_ON(!mutex_is_locked(&host_ctx->lock)); if (network->fw_error || host_ctx->fw_error || + host_ctx->dev_shuttingdown || (host_ctx->fw_state != FW_ENABLED)) { NPU_ERR("fw is in error state or disabled\n"); ret = -EIO; @@ -2111,7 +2166,8 @@ static int npu_send_misc_cmd(struct npu_device *npu_dev, uint32_t q_idx, WARN_ON(!mutex_is_locked(&host_ctx->lock)); - if (host_ctx->fw_error || (host_ctx->fw_state != FW_ENABLED)) { + if (host_ctx->fw_error || host_ctx->dev_shuttingdown || + (host_ctx->fw_state != FW_ENABLED)) { NPU_ERR("fw is in error state or disabled\n"); ret = -EIO; } else { @@ -2548,6 +2604,12 @@ int32_t npu_host_load_network_v2(struct npu_client *client, goto free_load_cmd; } + if (host_ctx->dev_shuttingdown) { + ret = -EIO; + NPU_ERR("device is shutting down\n"); + goto free_load_cmd; + } + if (!ret) { NPU_ERR("npu: NPU_IPC_CMD_LOAD time out %lld:%d\n", network->id, load_cmd->trans_id); @@ -2633,6 +2695,11 @@ int32_t npu_host_unload_network(struct npu_client *client, goto free_network; } + if (host_ctx->dev_shuttingdown) { + NPU_ERR("device is shutting down, skip unload network in fw\n"); + goto free_network; + } + NPU_DBG("Unload network %lld\n", network->id); /* prepare IPC packet for UNLOAD */ unload_packet.header.cmd_type = NPU_IPC_CMD_UNLOAD; @@ -2686,7 +2753,7 @@ int32_t npu_host_unload_network(struct npu_client *client, mutex_lock(&host_ctx->lock); - if (network->fw_error) { + if (network->fw_error || host_ctx->dev_shuttingdown) { ret = -EIO; NPU_ERR("fw is in error state during unload network\n"); goto free_network; @@ -2779,6 +2846,12 @@ int32_t npu_host_exec_network_v2(struct npu_client *client, goto exec_v2_done; } + if (host_ctx->dev_shuttingdown) { + NPU_ERR("device is shutting down\n"); + ret = -EIO; + goto exec_v2_done; + } + if (network->is_async && !async_ioctl) { NPU_ERR("network is in async mode\n"); ret = -EINVAL; @@ -2869,6 +2942,12 @@ int32_t npu_host_exec_network_v2(struct npu_client *client, goto free_exec_cmd; } + if (host_ctx->dev_shuttingdown) { + ret = -EIO; + NPU_ERR("device is shutting down during execute_v2 network\n"); + goto free_exec_cmd; + } + if (!ret) { NPU_ERR("npu: %llx:%d NPU_IPC_CMD_EXECUTE_V2 time out\n", network->id, exec_cmd->trans_id); diff --git a/drivers/media/platform/msm/npu/npu_mgr.h b/drivers/media/platform/msm/npu/npu_mgr.h index e44fb38d827b..f22e721379c2 100644 --- a/drivers/media/platform/msm/npu/npu_mgr.h +++ b/drivers/media/platform/msm/npu/npu_mgr.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #ifndef _NPU_MGR_H @@ -131,10 +131,12 @@ struct npu_host_ctx { uint32_t err_irq_sts; uint32_t wdg_irq_sts; bool fw_error; + bool dev_shuttingdown; bool cancel_work; bool app_crashed; struct notifier_block nb; struct notifier_block panic_nb; + struct notifier_block reboot_nb; void *notif_hdle; spinlock_t bridge_mbox_lock; bool bridge_mbox_pwr_on; diff --git a/drivers/media/usb/uvc/uvc_video.c b/drivers/media/usb/uvc/uvc_video.c index ffffb66d51a0..ce64c58aceb3 100644 --- a/drivers/media/usb/uvc/uvc_video.c +++ b/drivers/media/usb/uvc/uvc_video.c @@ -26,6 +26,8 @@ #include "uvcvideo.h" +#define CONFIG_DMA_NONCOHERENT 1 + /* ------------------------------------------------------------------------ * UVC Controls */ diff --git a/drivers/net/wireless/cnss2/pci.c b/drivers/net/wireless/cnss2/pci.c index d83f87a63ba0..87e96623345b 100644 --- a/drivers/net/wireless/cnss2/pci.c +++ b/drivers/net/wireless/cnss2/pci.c @@ -130,6 +130,13 @@ static struct cnss_pci_reg qdss_csr[] = { { NULL }, }; +static struct cnss_pci_reg pci_scratch[] = { + { "PCIE_SCRATCH_0", PCIE_SCRATCH_0_SOC_PCIE_REG }, + { "PCIE_SCRATCH_1", PCIE_SCRATCH_1_SOC_PCIE_REG }, + { "PCIE_SCRATCH_2", PCIE_SCRATCH_2_SOC_PCIE_REG }, + { NULL }, +}; + static struct cnss_misc_reg wcss_reg_access_seq[] = { {0, QCA6390_GCC_DEBUG_CLK_CTL, 0}, {1, QCA6390_GCC_DEBUG_CLK_CTL, 0x802}, @@ -722,6 +729,35 @@ static int cnss_set_pci_link(struct cnss_pci_data *pci_priv, bool link_up) return ret; } +static void cnss_pci_soc_scratch_reg_dump(struct cnss_pci_data *pci_priv) +{ + u32 reg_offset, val; + int i; + + switch (pci_priv->device_id) { + case QCA6490_DEVICE_ID: + break; + default: + return; + } + + if (in_interrupt() || irqs_disabled()) + return; + + if (cnss_pci_check_link_status(pci_priv)) + return; + + cnss_pr_dbg("Start to dump SOC Scratch registers\n"); + + for (i = 0; pci_scratch[i].name; i++) { + reg_offset = pci_scratch[i].offset; + if (cnss_pci_reg_read(pci_priv, reg_offset, &val)) + return; + cnss_pr_dbg("PCIE_SOC_REG_%s = 0x%x\n", + pci_scratch[i].name, val); + } +} + int cnss_suspend_pci_link(struct cnss_pci_data *pci_priv) { int ret = 0; @@ -841,6 +877,7 @@ int cnss_pci_recover_link_down(struct cnss_pci_data *pci_priv) jiffies + msecs_to_jiffies(DEV_RDDM_TIMEOUT)); mhi_debug_reg_dump(pci_priv->mhi_ctrl); + cnss_pci_soc_scratch_reg_dump(pci_priv); return 0; } @@ -1758,6 +1795,7 @@ static void cnss_pci_dump_misc_reg(struct cnss_pci_data *pci_priv) return; mhi_debug_reg_dump(pci_priv->mhi_ctrl); + cnss_pci_soc_scratch_reg_dump(pci_priv); cnss_pci_misc_reg_dump(pci_priv, pci_priv->wcss_reg, pci_priv->wcss_reg_size, "wcss"); cnss_pci_misc_reg_dump(pci_priv, pci_priv->pcie_reg, @@ -1775,6 +1813,7 @@ static void cnss_pci_dump_mhi_reg(struct cnss_pci_data *pci_priv) return; mhi_debug_reg_dump(pci_priv->mhi_ctrl); + cnss_pci_soc_scratch_reg_dump(pci_priv); } static void cnss_pci_dump_shadow_reg(struct cnss_pci_data *pci_priv) @@ -1885,9 +1924,9 @@ static void cnss_pci_dump_qca6390_sram_mem(struct cnss_pci_data *pci_priv) sbl_log_size = (sbl_log_size > QCA6390_DEBUG_SBL_LOG_SRAM_MAX_SIZE ? QCA6390_DEBUG_SBL_LOG_SRAM_MAX_SIZE : sbl_log_size); - if (sbl_log_start < QCA6390_V2_SBL_DATA_START || - sbl_log_start > QCA6390_V2_SBL_DATA_END || - (sbl_log_start + sbl_log_size) > QCA6390_V2_SBL_DATA_END) + if (sbl_log_start < SRAM_START || + sbl_log_start > SRAM_END || + (sbl_log_start + sbl_log_size) > SRAM_END) goto out; cnss_pr_dbg("Dumping SBL log data\n"); @@ -1955,17 +1994,11 @@ static void cnss_pci_dump_bl_sram_mem(struct cnss_pci_data *pci_priv) sbl_log_size = (sbl_log_size > QCA6490_DEBUG_SBL_LOG_SRAM_MAX_SIZE ? QCA6490_DEBUG_SBL_LOG_SRAM_MAX_SIZE : sbl_log_size); - if (plat_priv->device_version.major_version == FW_V2_NUMBER) { - if (sbl_log_start < QCA6490_V2_SBL_DATA_START || - sbl_log_start > QCA6490_V2_SBL_DATA_END || - (sbl_log_start + sbl_log_size) > QCA6490_V2_SBL_DATA_END) - goto out; - } else { - if (sbl_log_start < QCA6490_V1_SBL_DATA_START || - sbl_log_start > QCA6490_V1_SBL_DATA_END || - (sbl_log_start + sbl_log_size) > QCA6490_V1_SBL_DATA_END) - goto out; - } + + if (sbl_log_start < SRAM_START || + sbl_log_start > SRAM_END || + (sbl_log_start + sbl_log_size) > SRAM_END) + goto out; cnss_pr_dbg("Dumping SBL log data"); for (i = 0; i < sbl_log_size; i += sizeof(val)) { @@ -4178,6 +4211,7 @@ static void cnss_pci_dump_registers(struct cnss_pci_data *pci_priv) return; mhi_debug_reg_dump(pci_priv->mhi_ctrl); + cnss_pci_soc_scratch_reg_dump(pci_priv); cnss_pci_dump_ce_reg(pci_priv, CNSS_CE_COMMON); cnss_pci_dump_ce_reg(pci_priv, CNSS_CE_09); cnss_pci_dump_ce_reg(pci_priv, CNSS_CE_10); @@ -4621,6 +4655,7 @@ static void cnss_dev_rddm_timeout_hdlr(struct timer_list *t) cnss_pr_err("Unable to collect ramdumps due to abrupt reset\n"); mhi_debug_reg_dump(mhi_ctrl); + cnss_pci_soc_scratch_reg_dump(pci_priv); cnss_schedule_recovery(&pci_priv->pci_dev->dev, CNSS_REASON_TIMEOUT); } diff --git a/drivers/net/wireless/cnss2/reg.h b/drivers/net/wireless/cnss2/reg.h index 1c8c12e72a15..3bfbd7a652ff 100644 --- a/drivers/net/wireless/cnss2/reg.h +++ b/drivers/net/wireless/cnss2/reg.h @@ -1,5 +1,5 @@ /* SPDX-License-Identifier: GPL-2.0-only */ -/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */ +/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */ #ifndef _CNSS_REG_H #define _CNSS_REG_H @@ -267,12 +267,8 @@ #define QCA6390_SYSPM_DBG_BUS_SEL_REG 0x1F82008 #define QCA6390_SYSPM_WCSSAON_SR_STATUS 0x1F8200C -#define QCA6490_DEBUG_PBL_LOG_SRAM_START 0x1403D58 +#define QCA6490_DEBUG_PBL_LOG_SRAM_START 0x01403DA0 #define QCA6490_DEBUG_PBL_LOG_SRAM_MAX_SIZE 40 -#define QCA6490_V1_SBL_DATA_START 0x143b000 -#define QCA6490_V1_SBL_DATA_END (0x143b000 + 0x00011000) -#define QCA6490_V2_SBL_DATA_START 0x1435000 -#define QCA6490_V2_SBL_DATA_END (0x1435000 + 0x00011000) #define QCA6490_DEBUG_SBL_LOG_SRAM_MAX_SIZE 48 #define QCA6490_TCSR_PBL_LOGGING_REG 0x01B000F8 #define QCA6490_PCIE_BHI_ERRDBG2_REG 0x01E0E238 @@ -282,12 +278,18 @@ #define QCA6390_DEBUG_PBL_LOG_SRAM_START 0x01403D58 #define QCA6390_DEBUG_PBL_LOG_SRAM_MAX_SIZE 80 -#define QCA6390_V2_SBL_DATA_START 0x016c8580 -#define QCA6390_V2_SBL_DATA_END (0x016c8580 + 0x00011000) #define QCA6390_DEBUG_SBL_LOG_SRAM_MAX_SIZE 44 #define QCA6390_TCSR_PBL_LOGGING_REG 0x01B000F8 #define QCA6390_PCIE_BHI_ERRDBG2_REG 0x01E0E238 #define QCA6390_PCIE_BHI_ERRDBG3_REG 0x01E0E23C #define QCA6390_PBL_WLAN_BOOT_CFG 0x01E22B34 #define QCA6390_PBL_BOOTSTRAP_STATUS 0x01910008 + +#define SRAM_START 0x01400000 +#define SRAM_END 0x01800000 + +/* PCIE SOC scratch registers, address same for QCA6390 & QCA6490*/ +#define PCIE_SCRATCH_0_SOC_PCIE_REG 0x1E04040 +#define PCIE_SCRATCH_1_SOC_PCIE_REG 0x1E04044 +#define PCIE_SCRATCH_2_SOC_PCIE_REG 0x1E0405C #endif diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_hdr.c b/drivers/platform/msm/ipa/ipa_v3/ipa_hdr.c index d64a4bff4504..79527916e5e7 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_hdr.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_hdr.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012-2019,2021 The Linux Foundation. All rights reserved. */ #include "ipa_i.h" @@ -90,6 +90,15 @@ static int ipa3_hdr_proc_ctx_to_hw_format(struct ipa_mem_buffer *mem, 0 : 1; } } + /* Check the pointer and header length to avoid + * dangerous overflow in HW + */ + if (unlikely(!entry->hdr || !entry->hdr->offset_entry || + !entry->offset_entry || + entry->hdr->hdr_len == 0 || + entry->hdr->hdr_len > + ipa_hdr_bin_sz[IPA_HDR_BIN_MAX - 1])) + return -EINVAL; ret = ipahal_cp_proc_ctx_to_hw_buff(entry->type, mem->base, entry->offset_entry->offset, @@ -747,7 +756,7 @@ int __ipa3_del_hdr(u32 hdr_hdl, bool by_user) return 0; } - if (entry->is_hdr_proc_ctx) { + if (entry->is_hdr_proc_ctx || entry->proc_ctx) { dma_unmap_single(ipa3_ctx->pdev, entry->phys_base, entry->hdr_len, @@ -1076,6 +1085,7 @@ int ipa3_reset_hdr(bool user_only) if (ipa3_id_find(entry->id) == NULL) { mutex_unlock(&ipa3_ctx->lock); + IPAERR_RL("Invalid header ID\n"); WARN_ON_RATELIMIT_IPA(1); return -EFAULT; } @@ -1086,6 +1096,7 @@ int ipa3_reset_hdr(bool user_only) entry->phys_base, entry->hdr_len, DMA_TO_DEVICE); + entry->proc_ctx->hdr = NULL; entry->proc_ctx = NULL; } else { /* move the offset entry to free list */ @@ -1143,6 +1154,7 @@ int ipa3_reset_hdr(bool user_only) if (ipa3_id_find(ctx_entry->id) == NULL) { mutex_unlock(&ipa3_ctx->lock); + IPAERR_RL("Invalid proc header ID\n"); WARN_ON_RATELIMIT_IPA(1); return -EFAULT; } diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_odl.c b/drivers/platform/msm/ipa/ipa_v3/ipa_odl.c index 938bb36c191c..9bc47e5228a8 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_odl.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_odl.c @@ -250,6 +250,7 @@ int ipa3_send_adpl_msg(unsigned long skb_data) list_add_tail(&msg->link, &ipa3_odl_ctx->adpl_msg_list); atomic_inc(&ipa3_odl_ctx->stats.numer_in_queue); mutex_unlock(&ipa3_odl_ctx->adpl_msg_lock); + wake_up(&ipa3_odl_ctx->adpl_msg_waitq); IPA_STATS_INC_CNT(ipa3_odl_ctx->stats.odl_rx_pkt); return 0; @@ -534,7 +535,9 @@ static ssize_t ipa_adpl_read(struct file *filp, char __user *buf, size_t count, int ret = 0; char __user *start = buf; struct ipa3_push_msg_odl *msg; + DEFINE_WAIT_FUNC(wait, woken_wake_function); + add_wait_queue(&ipa3_odl_ctx->adpl_msg_waitq, &wait); while (1) { IPADBG_LOW("Writing message to adpl pipe\n"); if (!ipa3_odl_ctx->odl_state.odl_open) @@ -579,9 +582,6 @@ static ssize_t ipa_adpl_read(struct file *filp, char __user *buf, size_t count, IPA_STATS_INC_CNT(ipa3_odl_ctx->stats.odl_tx_diag_pkt); kfree(msg); msg = NULL; - } else { - ret = -EAGAIN; - break; } ret = -EAGAIN; @@ -594,9 +594,9 @@ static ssize_t ipa_adpl_read(struct file *filp, char __user *buf, size_t count, if (start != buf) break; - + wait_woken(&wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT); } - + remove_wait_queue(&ipa3_odl_ctx->adpl_msg_waitq, &wait); if (start != buf && ret != -EFAULT) ret = buf - start; @@ -672,6 +672,7 @@ int ipa_odl_init(void) odl_cdev = ipa3_odl_ctx->odl_cdev; INIT_LIST_HEAD(&ipa3_odl_ctx->adpl_msg_list); + init_waitqueue_head(&ipa3_odl_ctx->adpl_msg_waitq); mutex_init(&ipa3_odl_ctx->adpl_msg_lock); mutex_init(&ipa3_odl_ctx->pipe_lock); diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_odl.h b/drivers/platform/msm/ipa/ipa_v3/ipa_odl.h index 4e876d167246..0976d8c8b3f7 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_odl.h +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_odl.h @@ -58,6 +58,7 @@ struct ipa_odl_context { bool odl_ctl_msg_wq_flag; struct ipa3_odlstats stats; u32 odl_pm_hdl; + wait_queue_head_t adpl_msg_waitq; }; struct ipa3_push_msg_odl { diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c index 174f2bfd6d5e..40254a4b89fb 100644 --- a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c +++ b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c @@ -484,8 +484,11 @@ static int ipa3_qmi_send_req_wait(struct qmi_handle *client_handle, req_desc->ei_array, req); - if (unlikely(!ipa_q6_clnt)) + if (unlikely(!ipa_q6_clnt)) { + mutex_unlock(&ipa3_qmi_lock); return -EINVAL; + } + mutex_unlock(&ipa3_qmi_lock); if (ret < 0) { diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig index c52014ece27c..9581455fc4d2 100644 --- a/drivers/power/supply/qcom/Kconfig +++ b/drivers/power/supply/qcom/Kconfig @@ -40,6 +40,25 @@ config QPNP_SMBLITE as fuel gauge and USB. VBUS regulator is registered for supporting OTG. +config QPNP_VM_BMS + tristate "QPNP Voltage-Mode Battery Monitoring System driver" + depends on MFD_SPMI_PMIC + help + Say Y here to enable support for QPNP chip vm-bms device. + The voltage-mode (vm) BMS driver uses periodic VBATT + readings from the battery to calculate the State of + Charge. + +config QPNP_LINEAR_CHARGER + tristate "QPNP Linear Charger driver" + depends on MFD_SPMI_PMIC + help + Say Y here to enable the Linear battery charger which supports USB + detection and charging. The driver also offers relevant information + to userspace via the power supply framework. + The power supply framework is used to communicate battery and + usb properties to userspace and other driver consumers like USB. + config SMB138X_CHARGER tristate "SMB138X Battery Charger" depends on MFD_I2C_PMIC @@ -73,6 +92,17 @@ config SMB1351_USB_CHARGER notification support. The driver controls SMB1351 via I2C and supports device-tree interface. +config SMB1360_CHARGER_FG + tristate "SMB1360 Charger and Fuel Gauge" + depends on I2C + help + Say Y to include support for SMB1360 Charger and Fuel Gauge. + SMB1360 is a single path switching mode charger capable of charging + the battery with 1.5Amps of current. It supports a fuel gauge which + uses voltage and coloumb counting for state of charge reporting. + The driver reports the status via the power supply framework. + A status change triggers an IRQ via the device STAT pin. + config SMB1355_SLAVE_CHARGER tristate "SMB1355 Slave Battery Charger" depends on MFD_I2C_PMIC diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile index 397decf8753a..b9cc5e6b00f2 100644 --- a/drivers/power/supply/qcom/Makefile +++ b/drivers/power/supply/qcom/Makefile @@ -1,5 +1,5 @@ # SPDX-License-Identifier: GPL-2.0-only - +obj-$(CONFIG_SMB1360_CHARGER_FG) += smb1360-charger-fg.o obj-$(CONFIG_QPNP_SMB5) += step-chg-jeita.o battery.o qpnp-smb5.o smb5-lib.o pmic-voter.o storm-watch.o schgm-flash.o obj-$(CONFIG_SMB1390_CHARGE_PUMP_PSY) += smb1390-charger-psy.o pmic-voter.o obj-$(CONFIG_SMB1355_SLAVE_CHARGER) += smb1355-charger.o pmic-voter.o @@ -13,3 +13,5 @@ obj-$(CONFIG_QPNP_QG) += qpnp-qg.o pmic-voter.o qg-util.o qg-soc.o qg-sdam.o q obj-$(CONFIG_HL6111R) += hl6111r.o obj-$(CONFIG_SMB1398_CHARGER) += smb1398-charger.o pmic-voter.o obj-$(CONFIG_QPNP_SMBLITE) += step-chg-jeita.o battery.o qpnp-smblite.o smblite-lib.o pmic-voter.o storm-watch.o schgm-flashlite.o +obj-$(CONFIG_QPNP_VM_BMS) += qpnp-vm-bms.o batterydata-lib.o batterydata-interface.o +obj-$(CONFIG_QPNP_LINEAR_CHARGER) += qpnp-linear-charger.o diff --git a/drivers/power/supply/qcom/batterydata-interface.c b/drivers/power/supply/qcom/batterydata-interface.c new file mode 100644 index 000000000000..a6c78805db50 --- /dev/null +++ b/drivers/power/supply/qcom/batterydata-interface.c @@ -0,0 +1,211 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014, 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "BATTERY: %s: " fmt, __func__ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct battery_data { + dev_t dev_no; + struct class *battery_class; + struct device *battery_device; + struct cdev battery_cdev; + struct bms_battery_data *profile; +}; +static struct battery_data *the_battery; + +static int battery_data_open(struct inode *inode, struct file *file) +{ + struct battery_data *battery = container_of(inode->i_cdev, + struct battery_data, battery_cdev); + + pr_debug("battery_data device opened\n"); + + file->private_data = battery; + + return 0; +} + +static long battery_data_ioctl(struct file *file, unsigned int cmd, + unsigned long arg) +{ + struct battery_data *battery = file->private_data; + struct battery_params __user *bp_user = + (struct battery_params __user *)arg; + struct battery_params bp; + int soc, rbatt_sf, slope, fcc_mah; + int rc = 0; + + if (!battery->profile) { + pr_err("Battery data not set!\n"); + return -EINVAL; + } + if (copy_from_user(&bp, bp_user, sizeof(bp))) { + pr_err("copy_from_user failed\n"); + return -EFAULT; + } + + switch (cmd) { + case BPIOCXSOC: + soc = interpolate_pc(battery->profile->pc_temp_ocv_lut, + bp.batt_temp, bp.ocv_uv / 1000); + rc = put_user(soc, &bp_user->soc); + if (rc) { + pr_err("BPIOCXSOC: Failed to 'put_user' rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXSOC: ocv=%d batt_temp=%d soc=%d\n", + bp.ocv_uv / 1000, bp.batt_temp, soc); + break; + case BPIOCXRBATT: + rbatt_sf = interpolate_scalingfactor( + battery->profile->rbatt_sf_lut, + bp.batt_temp, bp.soc); + rc = put_user(rbatt_sf, &bp_user->rbatt_sf); + if (rc) { + pr_err("BPIOCXRBATT: Failed to 'put_user' rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXRBATT: soc=%d batt_temp=%d rbatt_sf=%d\n", + bp.soc, bp.batt_temp, rbatt_sf); + break; + case BPIOCXSLOPE: + slope = interpolate_slope(battery->profile->pc_temp_ocv_lut, + bp.batt_temp, bp.soc); + rc = put_user(slope, &bp_user->slope); + if (rc) { + pr_err("BPIOCXSLOPE: Failed to 'put_user' rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXSLOPE: soc=%d batt_temp=%d slope=%d\n", + bp.soc, bp.batt_temp, slope); + break; + case BPIOCXFCC: + fcc_mah = interpolate_fcc(battery->profile->fcc_temp_lut, + bp.batt_temp); + rc = put_user(fcc_mah, &bp_user->fcc_mah); + if (rc) { + pr_err("BPIOCXFCC: Failed to 'put_user' rc=%d\n", rc); + goto ret_err; + } + pr_debug("BPIOCXFCC: batt_temp=%d fcc_mah=%d\n", + bp.batt_temp, fcc_mah); + break; + default: + pr_err("IOCTL %d not supported\n", cmd); + rc = -EINVAL; + + } +ret_err: + return rc; +} + +static int battery_data_release(struct inode *inode, struct file *file) +{ + pr_debug("battery_data device closed\n"); + + return 0; +} + +static const struct file_operations battery_data_fops = { + .owner = THIS_MODULE, + .open = battery_data_open, + .unlocked_ioctl = battery_data_ioctl, + .compat_ioctl = battery_data_ioctl, + .release = battery_data_release, +}; + +int config_battery_data(struct bms_battery_data *profile) +{ + if (!the_battery) { + pr_err("Battery data not initialized\n"); + return -ENODEV; + } + + the_battery->profile = profile; + + pr_debug("Battery profile set - %s\n", + the_battery->profile->battery_type); + + return 0; +} + +static int batterydata_init(void) +{ + int rc; + struct battery_data *battery; + + battery = kzalloc(sizeof(*battery), GFP_KERNEL); + if (!battery) + return -ENOMEM; + + /* character device to access the battery-data from userspace */ + rc = alloc_chrdev_region(&battery->dev_no, 0, 1, "battery_data"); + if (rc) { + pr_err("Unable to allocate chrdev rc=%d\n", rc); + return rc; + } + cdev_init(&battery->battery_cdev, &battery_data_fops); + rc = cdev_add(&battery->battery_cdev, battery->dev_no, 1); + if (rc) { + pr_err("Unable to add battery_cdev rc=%d\n", rc); + goto unregister_chrdev; + } + + battery->battery_class = class_create(THIS_MODULE, "battery_data"); + if (IS_ERR_OR_NULL(battery->battery_class)) { + pr_err("Fail to create battery class\n"); + rc = -ENODEV; + goto delete_cdev; + } + + battery->battery_device = device_create(battery->battery_class, + NULL, battery->dev_no, + NULL, "battery_data"); + if (IS_ERR(battery->battery_device)) { + pr_err("Fail to create battery_device device\n"); + rc = -ENODEV; + goto delete_cdev; + } + + the_battery = battery; + + pr_info("Battery-data device created!\n"); + + return 0; + +delete_cdev: + cdev_del(&battery->battery_cdev); +unregister_chrdev: + unregister_chrdev_region(battery->dev_no, 1); + the_battery = NULL; + return rc; +} +subsys_initcall(batterydata_init); + +static void batterydata_exit(void) +{ + if (the_battery) { + device_destroy(the_battery->battery_class, the_battery->dev_no); + cdev_del(&the_battery->battery_cdev); + unregister_chrdev_region(the_battery->dev_no, 1); + } + kfree(the_battery); + the_battery = NULL; +} +module_exit(batterydata_exit); + +MODULE_DESCRIPTION("Battery-data Interface driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/power/supply/qcom/batterydata-lib.c b/drivers/power/supply/qcom/batterydata-lib.c new file mode 100644 index 000000000000..2f68c04a8f02 --- /dev/null +++ b/drivers/power/supply/qcom/batterydata-lib.c @@ -0,0 +1,485 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2012-2014, 2018, 2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include + +int linear_interpolate(int y0, int x0, int y1, int x1, int x) +{ + if (y0 == y1 || x == x0) + return y0; + if (x1 == x0 || x == x1) + return y1; + + return y0 + ((y1 - y0) * (x - x0) / (x1 - x0)); +} + +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/supply/qcom/qpnp-linear-charger.c b/drivers/power/supply/qcom/qpnp-linear-charger.c new file mode 100644 index 000000000000..a87d6d27ce59 --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-linear-charger.c @@ -0,0 +1,3683 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2015, 2017-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "CHG: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define CREATE_MASK(NUM_BITS, POS) \ + ((unsigned char) (((1 << (NUM_BITS)) - 1) << (POS))) +#define LBC_MASK(MSB_BIT, LSB_BIT) \ + CREATE_MASK(MSB_BIT - LSB_BIT + 1, LSB_BIT) + +/* Interrupt offsets */ +#define INT_RT_STS_REG 0x10 +#define FAST_CHG_ON_IRQ BIT(5) +#define OVERTEMP_ON_IRQ BIT(4) +#define BAT_TEMP_OK_IRQ BIT(1) +#define BATT_PRES_IRQ BIT(0) + +/* USB CHARGER PATH peripheral register offsets */ +#define USB_IN_VALID_MASK BIT(1) +#define CHG_GONE_BIT BIT(2) +#define USB_SUSP_REG 0x47 +#define USB_SUSPEND_BIT BIT(0) +#define USB_COMP_OVR1_REG 0xEA +#define USBIN_LLIMIT_OK_MASK LBC_MASK(1, 0) +#define USBIN_LLIMIT_OK_NO_OVERRIDE 0x00 +#define USBIN_LLIMIT_OK_OVERRIDE_1 0x03 +#define USB_OVP_TST5_REG 0xE7 +#define CHG_GONE_OK_EN_BIT BIT(2) + +/* CHARGER peripheral register offset */ +#define CHG_OPTION_REG 0x08 +#define CHG_OPTION_MASK BIT(7) +#define CHG_STATUS_REG 0x09 +#define CHG_ON_BIT BIT(0) +#define CHG_VDD_LOOP_BIT BIT(1) +#define VINMIN_LOOP_BIT BIT(3) +#define CHG_VDD_MAX_REG 0x40 +#define CHG_VDD_SAFE_REG 0x41 +#define CHG_IBAT_MAX_REG 0x44 +#define CHG_IBAT_SAFE_REG 0x45 +#define CHG_VIN_MIN_REG 0x47 +#define CHG_CTRL_REG 0x49 +#define CHG_ENABLE BIT(7) +#define CHG_FORCE_BATT_ON BIT(0) +#define CHG_EN_MASK (BIT(7) | BIT(0)) +#define CHG_FAILED_REG 0x4A +#define CHG_FAILED_BIT BIT(7) +#define CHG_VBAT_WEAK_REG 0x52 +#define CHG_IBATTERM_EN_REG 0x5B +#define CHG_USB_ENUM_T_STOP_REG 0x4E +#define CHG_TCHG_MAX_EN_REG 0x60 +#define CHG_TCHG_MAX_EN_BIT BIT(7) +#define CHG_TCHG_MAX_MASK LBC_MASK(6, 0) +#define CHG_TCHG_MAX_REG 0x61 +#define CHG_WDOG_EN_REG 0x65 +#define CHG_PERPH_RESET_CTRL3_REG 0xDA +#define CHG_COMP_OVR1 0xEE +#define CHG_VBAT_DET_OVR_MASK LBC_MASK(1, 0) +#define CHG_TEST_LOOP_REG 0xE5 +#define VIN_MIN_LOOP_DISABLE_BIT BIT(0) +#define OVERRIDE_0 0x2 +#define OVERRIDE_NONE 0x0 + +/* BATTIF peripheral register offset */ +#define BAT_IF_PRES_STATUS_REG 0x08 +#define BATT_PRES_MASK BIT(7) +#define BAT_IF_TEMP_STATUS_REG 0x09 +#define BATT_TEMP_HOT_MASK BIT(6) +#define BATT_TEMP_COLD_MASK LBC_MASK(7, 6) +#define BATT_TEMP_OK_MASK BIT(7) +#define BAT_IF_VREF_BAT_THM_CTRL_REG 0x4A +#define VREF_BATT_THERM_FORCE_ON LBC_MASK(7, 6) +#define VREF_BAT_THM_ENABLED_FSM BIT(7) +#define BAT_IF_BPD_CTRL_REG 0x48 +#define BATT_BPD_CTRL_SEL_MASK LBC_MASK(1, 0) +#define BATT_BPD_OFFMODE_EN BIT(3) +#define BATT_THM_EN BIT(1) +#define BATT_ID_EN BIT(0) +#define BAT_IF_BTC_CTRL 0x49 +#define BTC_COMP_EN_MASK BIT(7) +#define BTC_COLD_MASK BIT(1) +#define BTC_HOT_MASK BIT(0) +#define BTC_COMP_OVERRIDE_REG 0xE5 + +/* MISC peripheral register offset */ +#define MISC_REV2_REG 0x01 +#define MISC_BOOT_DONE_REG 0x42 +#define MISC_BOOT_DONE BIT(7) +#define MISC_TRIM3_REG 0xF3 +#define MISC_TRIM3_VDD_MASK LBC_MASK(5, 4) +#define MISC_TRIM4_REG 0xF4 +#define MISC_TRIM4_VDD_MASK BIT(4) + +#define PERP_SUBTYPE_REG 0x05 +#define SEC_ACCESS 0xD0 + +/* Linear peripheral subtype values */ +#define LBC_CHGR_SUBTYPE 0x15 +#define LBC_BAT_IF_SUBTYPE 0x16 +#define LBC_USB_PTH_SUBTYPE 0x17 +#define LBC_MISC_SUBTYPE 0x18 + +#define QPNP_CHG_I_MAX_MIN_90 90 + +/* Feature flags */ +#define VDD_TRIM_SUPPORTED BIT(0) + +#define QPNP_CHARGER_DEV_NAME "qcom,qpnp-linear-charger" + +/* usb_interrupts */ + +struct qpnp_lbc_irq { + int irq; + unsigned long disabled; + bool is_wake; +}; + +enum { + USBIN_VALID = 0, + USB_OVER_TEMP, + USB_CHG_GONE, + BATT_PRES, + BATT_TEMPOK, + CHG_DONE, + CHG_FAILED, + CHG_FAST_CHG, + CHG_VBAT_DET_LO, + MAX_IRQS, +}; + +enum { + USER = BIT(0), + THERMAL = BIT(1), + CURRENT = BIT(2), + SOC = BIT(3), + PARALLEL = BIT(4), + COLLAPSE = BIT(5), + DEBUG_BOARD = BIT(6), +}; + +enum bpd_type { + BPD_TYPE_BAT_ID, + BPD_TYPE_BAT_THM, + BPD_TYPE_BAT_THM_BAT_ID, +}; + +static const char * const bpd_label[] = { + [BPD_TYPE_BAT_ID] = "bpd_id", + [BPD_TYPE_BAT_THM] = "bpd_thm", + [BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id", +}; + +enum btc_type { + HOT_THD_25_PCT = 25, + HOT_THD_35_PCT = 35, + COLD_THD_70_PCT = 70, + COLD_THD_80_PCT = 80, +}; + +static u8 btc_value[] = { + [HOT_THD_25_PCT] = 0x0, + [HOT_THD_35_PCT] = BIT(0), + [COLD_THD_70_PCT] = 0x0, + [COLD_THD_80_PCT] = BIT(1), +}; + +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; +} + +static enum power_supply_property msm_batt_power_props[] = { + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_DEBUG_BATTERY, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_COOL_TEMP, + POWER_SUPPLY_PROP_WARM_TEMP, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT, + POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX, +}; + +static char *pm_batt_supplied_to[] = { + "bms", +}; + +struct vddtrim_map { + int trim_uv; + int trim_val; +}; + +/* + * VDDTRIM is a 3 bit value which is split across two + * register TRIM3(bit 5:4) -> VDDTRIM bit(2:1) + * register TRIM4(bit 4) -> VDDTRIM bit(0) + */ +#define TRIM_CENTER 4 +#define MAX_VDD_EA_TRIM_CFG 8 +#define VDD_TRIM3_MASK LBC_MASK(2, 1) +#define VDD_TRIM3_SHIFT 3 +#define VDD_TRIM4_MASK BIT(0) +#define VDD_TRIM4_SHIFT 4 +#define AVG(VAL1, VAL2) ((VAL1 + VAL2) / 2) + +/* + * VDDTRIM table containing map of trim voltage and + * corresponding trim value. + */ +static struct vddtrim_map vddtrim_map[] = { + {36700, 0x00}, + {28000, 0x01}, + {19800, 0x02}, + {10760, 0x03}, + {0, 0x04}, + {-8500, 0x05}, + {-16800, 0x06}, + {-25440, 0x07}, +}; + +static const unsigned int qpnp_lbc_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +/* + * struct qpnp_lbc_chip - device information + * @dev: device pointer to access the parent + * @pdev: pdev pointer to access platform information + * @chgr_base: charger peripheral base address + * @bat_if_base: battery interface peripheral base address + * @usb_chgpth_base: USB charge path peripheral base address + * @misc_base: misc peripheral base address + * @bat_is_cool: indicates that battery is cool + * @bat_is_warm: indicates that battery is warm + * @chg_done: indicates that charging is completed + * @usb_present: present status of USB + * @batt_present: present status of battery + * @cfg_charging_disabled: disable drawing current from USB. + * @cfg_use_fake_battery: flag to report default battery properties + * @fastchg_on: indicate charger in fast charge mode + * @cfg_btc_disabled: flag to disable btc (disables hot and cold + * irqs) + * @cfg_max_voltage_mv: the max volts the batt should be charged up to + * @cfg_min_voltage_mv: VIN_MIN configuration + * @cfg_batt_weak_voltage_uv: weak battery voltage threshold + * @cfg_warm_bat_chg_ma: warm battery maximum charge current in mA + * @cfg_cool_bat_chg_ma: cool battery maximum charge current in mA + * @cfg_safe_voltage_mv: safe voltage to which battery can charge + * @cfg_warm_bat_mv: warm temperature battery target voltage + * @cfg_warm_bat_mv: warm temperature battery target voltage + * @cfg_cool_bat_mv: cool temperature battery target voltage + * @cfg_soc_resume_limit: SOC at which battery resumes charging + * @cfg_float_charge: enable float charging + * @charger_disabled: maintain USB path state. + * @cfg_charger_detect_eoc: charger can detect end of charging + * @cfg_disable_vbatdet_based_recharge: keep VBATDET comparator overridden to + * low and VBATDET irq disabled. + * @cfg_collapsible_chgr_support: support collapsible charger + * @cfg_chgr_led_support: support charger led work. + * @cfg_safe_current: battery safety current setting + * @cfg_hot_batt_p: hot battery threshold setting + * @cfg_cold_batt_p: eold battery threshold setting + * @cfg_warm_bat_decidegc: warm battery temperature in degree Celsius + * @cfg_cool_bat_decidegc: cool battery temperature in degree Celsius + * @fake_battery_soc: SOC value to be reported to userspace + * @cfg_tchg_mins: maximum allowed software initiated charge time + * @chg_failed_count: counter to maintained number of times charging + * failed + * @cfg_bpd_detection: battery present detection mechanism selection + * @cfg_thermal_levels: amount of thermal mitigation levels + * @cfg_thermal_mitigation: thermal mitigation level values + * @therm_lvl_sel: thermal mitigation level selection + * @jeita_configure_lock: lock to serialize jeita configuration request + * @hw_access_lock: lock to serialize access to charger registers + * @ibat_change_lock: lock to serialize ibat change requests from + * USB and thermal. + * @irq_lock lock to serialize enabling/disabling of irq + * @supported_feature_flag bitmask for all supported features + * @vddtrim_alarm alarm to schedule trim work at regular + * interval + * @vddtrim_work work to perform actual vddmax trimming + * @init_trim_uv initial trim voltage at bootup + * @delta_vddmax_uv current vddmax trim voltage + * @chg_enable_lock: lock to serialize charging enable/disable for + * SOC based resume charging + * @usb_psy: power supply to export information to + * userspace + * @bms_psy: power supply to export information to + * userspace + * @batt_psy: power supply to export information to + * userspace + */ +struct qpnp_lbc_chip { + struct device *dev; + struct platform_device *pdev; + struct regmap *regmap; + u16 chgr_base; + u16 bat_if_base; + u16 usb_chgpth_base; + u16 misc_base; + bool bat_is_cool; + bool bat_is_warm; + bool chg_done; + bool usb_present; + bool batt_present; + bool cfg_charging_disabled; + bool cfg_btc_disabled; + bool cfg_use_fake_battery; + bool fastchg_on; + bool cfg_use_external_charger; + bool cfg_chgr_led_support; + bool non_collapsible_chgr_detected; + bool debug_board; + unsigned int cfg_warm_bat_chg_ma; + unsigned int cfg_cool_bat_chg_ma; + unsigned int cfg_safe_voltage_mv; + unsigned int cfg_max_voltage_mv; + unsigned int cfg_min_voltage_mv; + unsigned int cfg_charger_detect_eoc; + unsigned int cfg_disable_vbatdet_based_recharge; + unsigned int cfg_batt_weak_voltage_uv; + unsigned int cfg_collapsible_chgr_support; + unsigned int cfg_warm_bat_mv; + unsigned int cfg_cool_bat_mv; + unsigned int cfg_hot_batt_p; + unsigned int cfg_cold_batt_p; + unsigned int cfg_thermal_levels; + unsigned int therm_lvl_sel; + unsigned int *thermal_mitigation; + unsigned int cfg_safe_current; + unsigned int cfg_volt_cutoff_mv; + unsigned int cutoff_threshold_uv; + unsigned int cfg_tchg_mins; + unsigned int chg_failed_count; + unsigned int supported_feature_flag; + int usb_online; + int cfg_bpd_detection; + int cfg_warm_bat_decidegc; + int cfg_cool_bat_decidegc; + int fake_battery_soc; + int cfg_soc_resume_limit; + int cfg_float_charge; + int charger_disabled; + int prev_max_ma; + int usb_psy_ma; + int delta_vddmax_uv; + int init_trim_uv; + enum power_supply_type usb_supply_type; + struct delayed_work collapsible_detection_work; + + /* parallel-chg params */ + int parallel_charging_enabled; + int lbc_max_chg_current; + int ichg_now; + int current_soc; + int cutoff_count; + + struct alarm vddtrim_alarm; + struct work_struct vddtrim_work; + struct qpnp_lbc_irq irqs[MAX_IRQS]; + struct mutex jeita_configure_lock; + struct mutex chg_enable_lock; + spinlock_t ibat_change_lock; + spinlock_t hw_access_lock; + spinlock_t irq_lock; + struct power_supply *usb_psy; + struct power_supply_desc usb_psy_d; + struct power_supply *bms_psy; + struct power_supply *batt_psy; + struct power_supply_desc batt_psy_d; + struct qpnp_adc_tm_btm_param adc_param; + struct iio_channel *vbat_sns; + struct iio_channel *lr_mux1_batt_therm; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct led_classdev led_cdev; + struct dentry *debug_root; + struct work_struct debug_board_work; + + /* parallel-chg params */ + struct power_supply *parallel_psy; + struct power_supply_desc parallel_psy_d; + struct delayed_work parallel_work; + struct extcon_dev *extcon; +}; + +static void qpnp_lbc_enable_irq(struct qpnp_lbc_chip *chip, + struct qpnp_lbc_irq *irq) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->irq_lock, flags); + if (__test_and_clear_bit(0, &irq->disabled)) { + pr_debug("number = %d\n", irq->irq); + enable_irq(irq->irq); + if (irq->is_wake) + enable_irq_wake(irq->irq); + } + spin_unlock_irqrestore(&chip->irq_lock, flags); +} + +static void qpnp_lbc_disable_irq(struct qpnp_lbc_chip *chip, + struct qpnp_lbc_irq *irq) +{ + unsigned long flags; + + spin_lock_irqsave(&chip->irq_lock, flags); + if (!__test_and_set_bit(0, &irq->disabled)) { + pr_debug("number = %d\n", irq->irq); + disable_irq_nosync(irq->irq); + if (irq->is_wake) + disable_irq_wake(irq->irq); + } + spin_unlock_irqrestore(&chip->irq_lock, flags); +} + +static int __qpnp_lbc_read(struct qpnp_lbc_chip *chip, u16 base, + u8 *val, int count) +{ + int rc = 0; + + if (base == 0) { + pr_err("base addr cannot be zero\n"); + return -EINVAL; + } + + rc = regmap_bulk_read(chip->regmap, base, val, count); + if (rc) + pr_err("SPMI read failed base=0x%02x rc=%d\n", base, rc); + + return rc; +} + +static int __qpnp_lbc_write(struct qpnp_lbc_chip *chip, u16 base, + u8 *val, int count) +{ + int rc; + + if (base == 0) { + pr_err("base addr cannot be zero\n"); + return -EINVAL; + } + + rc = regmap_bulk_write(chip->regmap, base, val, count); + if (rc) + pr_err("SPMI write failed base=0x%02x rc=%d\n", base, rc); + + return rc; +} + +static int __qpnp_lbc_secure_write(struct qpnp_lbc_chip *chip, u16 base, + u16 offset, u8 *val, int count) +{ + int rc; + u8 reg_val; + + reg_val = 0xA5; + rc = __qpnp_lbc_write(chip, base + SEC_ACCESS, ®_val, 1); + if (rc) + return rc; + + return __qpnp_lbc_write(chip, base + offset, val, 1); +} + +static int qpnp_lbc_read(struct qpnp_lbc_chip *chip, u16 base, + u8 *val, int count) +{ + int rc = 0; + unsigned long flags; + + if (base == 0) { + pr_err("base addr cannot be zero\n"); + return -EINVAL; + } + + spin_lock_irqsave(&chip->hw_access_lock, flags); + rc = __qpnp_lbc_read(chip, base, val, count); + spin_unlock_irqrestore(&chip->hw_access_lock, flags); + + return rc; +} + +static int qpnp_lbc_write(struct qpnp_lbc_chip *chip, u16 base, + u8 *val, int count) +{ + int rc = 0; + unsigned long flags; + + if (base == 0) { + pr_err("base addr cannot be zero\n"); + return -EINVAL; + } + + spin_lock_irqsave(&chip->hw_access_lock, flags); + rc = __qpnp_lbc_write(chip, base, val, count); + spin_unlock_irqrestore(&chip->hw_access_lock, flags); + + return rc; +} + +static int qpnp_lbc_masked_write(struct qpnp_lbc_chip *chip, u16 base, + u8 mask, u8 val) +{ + int rc; + u8 reg_val; + unsigned long flags; + + spin_lock_irqsave(&chip->hw_access_lock, flags); + rc = __qpnp_lbc_read(chip, base, ®_val, 1); + if (rc) + goto out; + + pr_debug("addr = 0x%x read 0x%x\n", base, reg_val); + + reg_val &= ~mask; + reg_val |= val & mask; + + pr_debug("writing to base=%x val=%x\n", base, reg_val); + + rc = __qpnp_lbc_write(chip, base, ®_val, 1); + +out: + spin_unlock_irqrestore(&chip->hw_access_lock, flags); + return rc; +} + +static int __qpnp_lbc_secure_masked_write(struct qpnp_lbc_chip *chip, u16 base, + u16 offset, u8 mask, u8 val) +{ + int rc; + u8 reg_val, reg_val1; + + rc = __qpnp_lbc_read(chip, base + offset, ®_val, 1); + if (rc) + return rc; + + pr_debug("addr = 0x%x read 0x%x\n", base, reg_val); + + reg_val &= ~mask; + reg_val |= val & mask; + pr_debug("writing to base=%x val=%x\n", base, reg_val); + + reg_val1 = 0xA5; + rc = __qpnp_lbc_write(chip, base + SEC_ACCESS, ®_val1, 1); + if (rc) + return rc; + + rc = __qpnp_lbc_write(chip, base + offset, ®_val, 1); + + return rc; +} + +static int qpnp_lbc_get_trim_voltage(u8 trim_reg) +{ + int i; + + for (i = 0; i < MAX_VDD_EA_TRIM_CFG; i++) + if (trim_reg == vddtrim_map[i].trim_val) + return vddtrim_map[i].trim_uv; + + pr_err("Invalid trim reg reg_val=%x\n", trim_reg); + return -EINVAL; +} + +static u8 qpnp_lbc_get_trim_val(struct qpnp_lbc_chip *chip) +{ + int i, sign; + int delta_uv; + + sign = (chip->delta_vddmax_uv >= 0) ? -1 : 1; + + switch (sign) { + case -1: + for (i = TRIM_CENTER; i >= 0; i--) { + if (vddtrim_map[i].trim_uv > chip->delta_vddmax_uv) { + delta_uv = AVG(vddtrim_map[i].trim_uv, + vddtrim_map[i + 1].trim_uv); + if (chip->delta_vddmax_uv >= delta_uv) + return vddtrim_map[i].trim_val; + else + return vddtrim_map[i + 1].trim_val; + } + } + i = 0; + break; + case 1: + for (i = TRIM_CENTER; i < ARRAY_SIZE(vddtrim_map); i++) { + if (vddtrim_map[i].trim_uv < chip->delta_vddmax_uv) { + delta_uv = AVG(vddtrim_map[i].trim_uv, + vddtrim_map[i - 1].trim_uv); + if (chip->delta_vddmax_uv >= delta_uv) + return vddtrim_map[i - 1].trim_val; + else + return vddtrim_map[i].trim_val; + } + } + i = ARRAY_SIZE(vddtrim_map) - 1; + break; + } + + return vddtrim_map[i].trim_val; +} + +static int qpnp_lbc_is_usb_chg_plugged_in(struct qpnp_lbc_chip *chip) +{ + u8 usbin_valid_rt_sts; + int rc; + + rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG, + &usbin_valid_rt_sts, 1); + if (rc) + return rc; + + pr_debug("rt_sts 0x%x\n", usbin_valid_rt_sts); + + return (usbin_valid_rt_sts & USB_IN_VALID_MASK) ? 1 : 0; +} + +static int qpnp_lbc_is_chg_gone(struct qpnp_lbc_chip *chip) +{ + u8 rt_sts; + int rc; + + rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG, + &rt_sts, 1); + if (rc) + return rc; + + pr_debug("rt_sts 0x%x\n", rt_sts); + + return (rt_sts & CHG_GONE_BIT) ? 1 : 0; +} + +static int qpnp_lbc_charger_enable(struct qpnp_lbc_chip *chip, int reason, + int enable) +{ + int disabled = chip->charger_disabled; + u8 reg_val; + int rc = 0; + + pr_debug("reason=%d requested_enable=%d disabled_status=%d\n", + reason, enable, disabled); + if (enable) + disabled &= ~reason; + else + disabled |= reason; + + if (!!chip->charger_disabled == !!disabled) + goto skip; + + reg_val = !!disabled ? CHG_FORCE_BATT_ON : CHG_ENABLE; + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG, + CHG_EN_MASK, reg_val); + if (rc) { + pr_err("Failed to %s charger\n", + reg_val ? "enable" : "disable"); + return rc; + } +skip: + chip->charger_disabled = disabled; + return rc; +} + +static int qpnp_lbc_is_batt_present(struct qpnp_lbc_chip *chip) +{ + u8 batt_pres_rt_sts; + int rc; + + rc = qpnp_lbc_read(chip, chip->bat_if_base + INT_RT_STS_REG, + &batt_pres_rt_sts, 1); + if (rc) + return rc; + + return (batt_pres_rt_sts & BATT_PRES_IRQ) ? 1 : 0; +} + +static int qpnp_lbc_bat_if_configure_btc(struct qpnp_lbc_chip *chip) +{ + u8 btc_cfg = 0, mask = 0, rc; + + /* Do nothing if battery peripheral not present */ + if (!chip->bat_if_base) + return 0; + + if ((chip->cfg_hot_batt_p == HOT_THD_25_PCT) + || (chip->cfg_hot_batt_p == HOT_THD_35_PCT)) { + btc_cfg |= btc_value[chip->cfg_hot_batt_p]; + mask |= BTC_HOT_MASK; + } + + if ((chip->cfg_cold_batt_p == COLD_THD_70_PCT) || + (chip->cfg_cold_batt_p == COLD_THD_80_PCT)) { + btc_cfg |= btc_value[chip->cfg_cold_batt_p]; + mask |= BTC_COLD_MASK; + } + + mask |= BTC_COMP_EN_MASK; + if (!chip->cfg_btc_disabled) + btc_cfg |= BTC_COMP_EN_MASK; + + pr_debug("BTC configuration mask=%x\n", btc_cfg); + + rc = qpnp_lbc_masked_write(chip, + chip->bat_if_base + BAT_IF_BTC_CTRL, + mask, btc_cfg); + if (rc) + pr_err("Failed to configure BTC\n"); + + return rc; +} + +static int qpnp_chg_collapsible_chgr_config(struct qpnp_lbc_chip *chip, + bool enable) +{ + u8 reg_val; + int rc; + + pr_debug("Configure for %scollapsible charger\n", + enable ? "" : "non-"); + /* + * The flow to enable/disable the collapsible charger configuration: + * Enable: Override USBIN_LLIMIT_OK --> + * Disable VIN_MIN comparator --> + * Enable CHG_GONE comparator + * Disable: Enable VIN_MIN comparator --> + * Enable USBIN_LLIMIT_OK --> + * Disable CHG_GONE comparator + */ + if (enable) { + /* Override USBIN_LLIMIT_OK */ + reg_val = USBIN_LLIMIT_OK_OVERRIDE_1; + rc = __qpnp_lbc_secure_masked_write(chip, + chip->usb_chgpth_base, + USB_COMP_OVR1_REG, + USBIN_LLIMIT_OK_MASK, reg_val); + if (rc) { + pr_err("Failed to override USB_LLIMIT_OK\n"); + return rc; + } + } + + /* Configure VIN_MIN comparator */ + rc = __qpnp_lbc_secure_masked_write(chip, + chip->chgr_base, CHG_TEST_LOOP_REG, + VIN_MIN_LOOP_DISABLE_BIT, + enable ? VIN_MIN_LOOP_DISABLE_BIT : 0); + if (rc) { + pr_err("Failed to %s VIN_MIN comparator\n", + enable ? "disable" : "enable"); + return rc; + } + + if (!enable) { + /* Enable USBIN_LLIMIT_OK */ + reg_val = USBIN_LLIMIT_OK_NO_OVERRIDE; + rc = __qpnp_lbc_secure_masked_write(chip, + chip->usb_chgpth_base, + USB_COMP_OVR1_REG, + USBIN_LLIMIT_OK_MASK, reg_val); + if (rc) { + pr_err("Failed to override USB_LLIMIT_OK\n"); + return rc; + } + } + + /* Configure CHG_GONE comparator */ + reg_val = enable ? CHG_GONE_OK_EN_BIT : 0; + rc = __qpnp_lbc_secure_masked_write(chip, + chip->usb_chgpth_base, USB_OVP_TST5_REG, + CHG_GONE_OK_EN_BIT, reg_val); + if (rc) { + pr_err("Failed to write CHG_GONE comparator\n"); + return rc; + } + + return 0; +} + +#define QPNP_LBC_VBATWEAK_MIN_UV 3000000 +#define QPNP_LBC_VBATWEAK_MAX_UV 3581250 +#define QPNP_LBC_VBATWEAK_STEP_UV 18750 +static int qpnp_lbc_vbatweak_set(struct qpnp_lbc_chip *chip, int voltage) +{ + u8 reg_val; + int rc; + + if (voltage < QPNP_LBC_VBATWEAK_MIN_UV || + voltage > QPNP_LBC_VBATWEAK_MAX_UV) { + rc = -EINVAL; + } else { + reg_val = (voltage - QPNP_LBC_VBATWEAK_MIN_UV) / + QPNP_LBC_VBATWEAK_STEP_UV; + pr_debug("VBAT_WEAK=%d setting %02x\n", + chip->cfg_batt_weak_voltage_uv, reg_val); + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VBAT_WEAK_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set VBAT_WEAK\n"); + } + + return rc; +} + +#define QPNP_LBC_VBAT_MIN_MV 4000 +#define QPNP_LBC_VBAT_MAX_MV 4775 +#define QPNP_LBC_VBAT_STEP_MV 25 +static int qpnp_lbc_vddsafe_set(struct qpnp_lbc_chip *chip, int voltage) +{ + u8 reg_val; + int rc; + + if (voltage < QPNP_LBC_VBAT_MIN_MV + || voltage > QPNP_LBC_VBAT_MAX_MV) { + pr_err("Invalid vddsafe voltage mV=%d min=%d max=%d\n", + voltage, QPNP_LBC_VBAT_MIN_MV, + QPNP_LBC_VBAT_MAX_MV); + return -EINVAL; + } + reg_val = (voltage - QPNP_LBC_VBAT_MIN_MV) / QPNP_LBC_VBAT_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, reg_val); + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VDD_SAFE_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set VDD_SAFE\n"); + + return rc; +} + +static int qpnp_lbc_vddmax_set(struct qpnp_lbc_chip *chip, int voltage) +{ + u8 reg_val; + int rc, trim_val; + unsigned long flags; + + if (voltage < QPNP_LBC_VBAT_MIN_MV + || voltage > QPNP_LBC_VBAT_MAX_MV) { + pr_err("Invalid vddmax voltage mV=%d min=%d max=%d\n", + voltage, QPNP_LBC_VBAT_MIN_MV, + QPNP_LBC_VBAT_MAX_MV); + return -EINVAL; + } + + spin_lock_irqsave(&chip->hw_access_lock, flags); + reg_val = (voltage - QPNP_LBC_VBAT_MIN_MV) / QPNP_LBC_VBAT_STEP_MV; + pr_debug("voltage=%d setting %02x\n", voltage, reg_val); + rc = __qpnp_lbc_write(chip, chip->chgr_base + CHG_VDD_MAX_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to set VDD_MAX\n"); + goto out; + } + + /* Update trim value */ + if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED) { + trim_val = qpnp_lbc_get_trim_val(chip); + reg_val = (trim_val & VDD_TRIM3_MASK) << VDD_TRIM3_SHIFT; + rc = __qpnp_lbc_secure_masked_write(chip, + chip->misc_base, MISC_TRIM3_REG, + MISC_TRIM3_VDD_MASK, reg_val); + if (rc) { + pr_err("Failed to set MISC_TRIM3_REG\n"); + goto out; + } + + reg_val = (trim_val & VDD_TRIM4_MASK) << VDD_TRIM4_SHIFT; + rc = __qpnp_lbc_secure_masked_write(chip, + chip->misc_base, MISC_TRIM4_REG, + MISC_TRIM4_VDD_MASK, reg_val); + if (rc) { + pr_err("Failed to set MISC_TRIM4_REG\n"); + goto out; + } + + chip->delta_vddmax_uv = qpnp_lbc_get_trim_voltage(trim_val); + if (chip->delta_vddmax_uv == -EINVAL) { + pr_err("Invalid trim voltage=%d\n", + chip->delta_vddmax_uv); + rc = -EINVAL; + goto out; + } + + pr_debug("VDD_MAX delta=%d trim value=%x\n", + chip->delta_vddmax_uv, trim_val); + } + +out: + spin_unlock_irqrestore(&chip->hw_access_lock, flags); + return rc; +} + +static int qpnp_lbc_set_appropriate_vddmax(struct qpnp_lbc_chip *chip) +{ + int rc; + + if (chip->bat_is_cool) + rc = qpnp_lbc_vddmax_set(chip, chip->cfg_cool_bat_mv); + else if (chip->bat_is_warm) + rc = qpnp_lbc_vddmax_set(chip, chip->cfg_warm_bat_mv); + else + rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv); + if (rc) + pr_err("Failed to set appropriate vddmax\n"); + + return rc; +} + +#define QPNP_LBC_MIN_DELTA_UV 13000 +static void qpnp_lbc_adjust_vddmax(struct qpnp_lbc_chip *chip, int vbat_uv) +{ + int delta_uv, prev_delta_uv, rc; + + prev_delta_uv = chip->delta_vddmax_uv; + delta_uv = (int)(chip->cfg_max_voltage_mv * 1000) - vbat_uv; + + /* + * If delta_uv is positive, apply trim if delta_uv > 13mv + * If delta_uv is negative always apply trim. + */ + if (delta_uv > 0 && delta_uv < QPNP_LBC_MIN_DELTA_UV) { + pr_debug("vbat is not low enough to increase vdd\n"); + return; + } + + pr_debug("vbat=%d current delta_uv=%d prev delta_vddmax_uv=%d\n", + vbat_uv, delta_uv, chip->delta_vddmax_uv); + chip->delta_vddmax_uv = delta_uv + chip->delta_vddmax_uv; + pr_debug("new delta_vddmax_uv %d\n", chip->delta_vddmax_uv); + rc = qpnp_lbc_set_appropriate_vddmax(chip); + if (rc) + chip->delta_vddmax_uv = prev_delta_uv; +} + +#define QPNP_LBC_VINMIN_MIN_MV 4200 +#define QPNP_LBC_VINMIN_MAX_MV 5037 +#define QPNP_LBC_VINMIN_STEP_MV 27 +static int qpnp_lbc_vinmin_set(struct qpnp_lbc_chip *chip, int voltage) +{ + u8 reg_val; + int rc; + + if ((voltage < QPNP_LBC_VINMIN_MIN_MV) + || (voltage > QPNP_LBC_VINMIN_MAX_MV)) { + pr_err("Invalid vinmin voltage mV=%d min=%d max=%d\n", + voltage, QPNP_LBC_VINMIN_MIN_MV, + QPNP_LBC_VINMIN_MAX_MV); + return -EINVAL; + } + + reg_val = (voltage - QPNP_LBC_VINMIN_MIN_MV) / QPNP_LBC_VINMIN_STEP_MV; + pr_debug("VIN_MIN=%d setting %02x\n", voltage, reg_val); + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_VIN_MIN_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set VIN_MIN\n"); + + return rc; +} + +#define QPNP_LBC_IBATSAFE_MIN_MA 90 +#define QPNP_LBC_IBATSAFE_MAX_MA 1440 +#define QPNP_LBC_I_STEP_MA 90 +static int qpnp_lbc_ibatsafe_set(struct qpnp_lbc_chip *chip, int safe_current) +{ + u8 reg_val; + int rc; + + if (safe_current < QPNP_LBC_IBATSAFE_MIN_MA + || safe_current > QPNP_LBC_IBATSAFE_MAX_MA) { + pr_err("Invalid safecurrent mA=%d min=%d max=%d\n", + safe_current, QPNP_LBC_IBATSAFE_MIN_MA, + QPNP_LBC_IBATSAFE_MAX_MA); + return -EINVAL; + } + + reg_val = (safe_current - QPNP_LBC_IBATSAFE_MIN_MA) + / QPNP_LBC_I_STEP_MA; + pr_debug("Ibate_safe=%d setting %02x\n", safe_current, reg_val); + + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_IBAT_SAFE_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set IBAT_SAFE\n"); + + return rc; +} + +#define QPNP_LBC_IBATMAX_MIN 90 +#define QPNP_LBC_IBATMAX_MAX 1440 +/* + * Set maximum current limit from charger + * ibat = System current + charging current + */ +static int qpnp_lbc_ibatmax_set(struct qpnp_lbc_chip *chip, int chg_current) +{ + u8 reg_val; + int rc; + + if (chg_current > QPNP_LBC_IBATMAX_MAX) + pr_debug("Invalid max charge current mA=%d max=%d\n", + chg_current, + QPNP_LBC_IBATMAX_MAX); + + chg_current = clamp(chg_current, QPNP_LBC_IBATMAX_MIN, + QPNP_LBC_IBATMAX_MAX); + reg_val = (chg_current - QPNP_LBC_IBATMAX_MIN) / QPNP_LBC_I_STEP_MA; + + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_IBAT_MAX_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set IBAT_MAX\n"); + else + chip->prev_max_ma = chg_current; + + return rc; +} + +#define QPNP_LBC_TCHG_MIN 4 +#define QPNP_LBC_TCHG_MAX 512 +#define QPNP_LBC_TCHG_STEP 4 +static int qpnp_lbc_tchg_max_set(struct qpnp_lbc_chip *chip, int minutes) +{ + u8 reg_val = 0; + int rc; + + /* Disable timer */ + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_EN_REG, + CHG_TCHG_MAX_EN_BIT, 0); + if (rc) { + pr_err("Failed to write tchg_max_en\n"); + return rc; + } + + /* If minutes is 0, just disable timer */ + if (!minutes) { + pr_debug("Charger safety timer disabled\n"); + return rc; + } + + minutes = clamp(minutes, QPNP_LBC_TCHG_MIN, QPNP_LBC_TCHG_MAX); + + reg_val = (minutes / QPNP_LBC_TCHG_STEP) - 1; + + pr_debug("TCHG_MAX=%d mins setting %x\n", minutes, reg_val); + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_REG, + CHG_TCHG_MAX_MASK, reg_val); + if (rc) { + pr_err("Failed to write tchg_max_reg\n"); + return rc; + } + + /* Enable timer */ + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_TCHG_MAX_EN_REG, + CHG_TCHG_MAX_EN_BIT, CHG_TCHG_MAX_EN_BIT); + if (rc) + pr_err("Failed to write tchg_max_en\n"); + + return rc; +} + +#define LBC_CHGR_LED 0x4D +#define CHGR_LED_ON BIT(0) +#define CHGR_LED_OFF 0x0 +#define CHGR_LED_STAT_MASK LBC_MASK(1, 0) +static void qpnp_lbc_chgr_led_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct qpnp_lbc_chip *chip = container_of(cdev, struct qpnp_lbc_chip, + led_cdev); + u8 reg; + int rc; + + if (value > LED_FULL) + value = LED_FULL; + + pr_debug("set the charger led brightness to value=%d\n", value); + reg = (value > LED_OFF) ? CHGR_LED_ON : CHGR_LED_OFF; + + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + LBC_CHGR_LED, + CHGR_LED_STAT_MASK, reg); + if (rc) + pr_err("Failed to write charger led\n"); +} + +static enum +led_brightness qpnp_lbc_chgr_led_brightness_get(struct led_classdev *cdev) +{ + + struct qpnp_lbc_chip *chip = container_of(cdev, struct qpnp_lbc_chip, + led_cdev); + u8 reg_val, chgr_led_sts; + int rc; + + rc = qpnp_lbc_read(chip, chip->chgr_base + LBC_CHGR_LED, + ®_val, 1); + if (rc) { + pr_err("Failed to read charger led\n"); + return rc; + } + + chgr_led_sts = reg_val & CHGR_LED_STAT_MASK; + pr_debug("charger led brightness chgr_led_sts=%d\n", chgr_led_sts); + + return (chgr_led_sts == CHGR_LED_ON) ? LED_FULL : LED_OFF; +} + +static int qpnp_lbc_register_chgr_led(struct qpnp_lbc_chip *chip) +{ + int rc; + + chip->led_cdev.name = "red"; + chip->led_cdev.brightness_set = qpnp_lbc_chgr_led_brightness_set; + chip->led_cdev.brightness_get = qpnp_lbc_chgr_led_brightness_get; + + rc = led_classdev_register(chip->dev, &chip->led_cdev); + if (rc) + pr_err("unable to register charger led, rc=%d\n", rc); + + return rc; +}; + +static int is_vinmin_set(struct qpnp_lbc_chip *chip) +{ + u8 reg; + int rc; + + rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, ®, 1); + if (rc) { + pr_err("Unable to read charger status\n"); + return false; + } + pr_debug("chg_status=0x%x\n", reg); + + return !!(reg & VINMIN_LOOP_BIT); + +} + +static int is_battery_charging(struct qpnp_lbc_chip *chip) +{ + u8 reg; + int rc; + + rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, ®, 1); + if (rc) { + pr_err("Unable to read charger status\n"); + return false; + } + pr_debug("chg_status=0x%x\n", reg); + + return !!(reg & CHG_ON_BIT); +} + +static int qpnp_lbc_vbatdet_override(struct qpnp_lbc_chip *chip, int ovr_val) +{ + int rc; + u8 reg_val; + unsigned long flags; + + spin_lock_irqsave(&chip->hw_access_lock, flags); + + rc = __qpnp_lbc_read(chip, chip->chgr_base + CHG_COMP_OVR1, + ®_val, 1); + if (rc) + goto out; + + pr_debug("addr = 0x%x read 0x%x\n", chip->chgr_base, reg_val); + + reg_val &= ~CHG_VBAT_DET_OVR_MASK; + reg_val |= ovr_val & CHG_VBAT_DET_OVR_MASK; + + pr_debug("writing to base=%x val=%x\n", chip->chgr_base, reg_val); + + rc = __qpnp_lbc_secure_write(chip, chip->chgr_base, CHG_COMP_OVR1, + ®_val, 1); + +out: + spin_unlock_irqrestore(&chip->hw_access_lock, flags); + return rc; +} + +static int get_prop_battery_voltage_now(struct qpnp_lbc_chip *chip) +{ + int rc = 0, batt_volt; + + rc = iio_read_channel_processed(chip->vbat_sns, &batt_volt); + if (rc < 0) { + pr_err("Unable to read vbat rc=%d\n", rc); + return 0; + } + + return batt_volt; +} + +static int get_prop_batt_present(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_read(chip, chip->bat_if_base + BAT_IF_PRES_STATUS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read battery status read failed\n"); + return 0; + } + + return (reg_val & BATT_PRES_MASK) ? 1 : 0; +} + +static int get_prop_batt_health(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_read(chip, chip->bat_if_base + BAT_IF_TEMP_STATUS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read battery health\n"); + return POWER_SUPPLY_HEALTH_UNKNOWN; + } + + if (BATT_TEMP_HOT_MASK & reg_val) + return POWER_SUPPLY_HEALTH_OVERHEAT; + if (!(BATT_TEMP_COLD_MASK & reg_val)) + return POWER_SUPPLY_HEALTH_COLD; + if (chip->bat_is_cool) + return POWER_SUPPLY_HEALTH_COOL; + if (chip->bat_is_warm) + return POWER_SUPPLY_HEALTH_WARM; + + return POWER_SUPPLY_HEALTH_GOOD; +} + +static int get_prop_charge_type(struct qpnp_lbc_chip *chip) +{ + int rc; + u8 reg_val; + + if (!get_prop_batt_present(chip)) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + + rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read interrupt sts\n"); + return POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + if (reg_val & FAST_CHG_ON_IRQ) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int get_prop_current_now(struct qpnp_lbc_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CURRENT_NOW, &ret); + return ret.intval; + } + + pr_debug("No BMS supply registered return 0\n"); + return 0; +} + +#define DEFAULT_CAPACITY 50 +static int get_prop_capacity(struct qpnp_lbc_chip *chip) +{ + union power_supply_propval ret = {0,}; + int soc; + + if (!chip->bms_psy) + chip->bms_psy = power_supply_get_by_name("bms"); + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + if (chip->cfg_use_fake_battery || !get_prop_batt_present(chip)) + return DEFAULT_CAPACITY; + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CAPACITY, &ret); + soc = ret.intval; + if (soc == 0) { + if (!qpnp_lbc_is_usb_chg_plugged_in(chip)) + pr_warn_ratelimited("Batt 0, CHG absent\n"); + } + return soc; + } + pr_debug("No BMS supply registered return %d\n", DEFAULT_CAPACITY); + + /* + * Return default capacity to avoid userspace + * from shutting down unecessarily + */ + return DEFAULT_CAPACITY; +} + +#define CUTOFF_COUNT 3 +static int get_prop_batt_status(struct qpnp_lbc_chip *chip) +{ + int rc, curr_now, soc_now, curr_volt; + u8 reg_val; + + /* + * If SOC = 0 and we are discharging with input connected, report + * the battery status as DISCHARGING. + */ + soc_now = chip->current_soc; + curr_now = get_prop_current_now(chip); + curr_volt = get_prop_battery_voltage_now(chip); + if (qpnp_lbc_is_usb_chg_plugged_in(chip) && soc_now == 0) { + if ((curr_now > 0) && (curr_volt < chip->cutoff_threshold_uv)) { + if (chip->cutoff_count > CUTOFF_COUNT) + return POWER_SUPPLY_STATUS_DISCHARGING; + chip->cutoff_count++; + } else { + chip->cutoff_count = 0; + } + } else { + chip->cutoff_count = 0; + } + + if (qpnp_lbc_is_usb_chg_plugged_in(chip) && chip->chg_done) + return POWER_SUPPLY_STATUS_FULL; + + rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read interrupt sts\n"); + return POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + if (reg_val & FAST_CHG_ON_IRQ) + return POWER_SUPPLY_STATUS_CHARGING; + + return POWER_SUPPLY_STATUS_DISCHARGING; +} + +static int get_bms_property(struct qpnp_lbc_chip *chip, + enum power_supply_property psy_prop) +{ + union power_supply_propval ret = {0,}; + + if (!chip->bms_psy) + chip->bms_psy = power_supply_get_by_name("bms"); + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, psy_prop, &ret); + return ret.intval; + } + pr_debug("No BMS supply registered\n"); + + return -EINVAL; +} + +static int get_prop_charge_count(struct qpnp_lbc_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (!chip->bms_psy) + chip->bms_psy = power_supply_get_by_name("bms"); + + if (chip->bms_psy) { + power_supply_get_property(chip->bms_psy, + POWER_SUPPLY_PROP_CHARGE_COUNTER, &ret); + } else { + pr_debug("No BMS supply registered return 0\n"); + } + + return ret.intval; +} + +#define DEFAULT_TEMP 250 +static int get_prop_batt_temp(struct qpnp_lbc_chip *chip) +{ + int rc = 0, batt_temp; + + if (chip->cfg_use_fake_battery || !get_prop_batt_present(chip)) + return DEFAULT_TEMP; + + rc = iio_read_channel_processed(chip->lr_mux1_batt_therm, &batt_temp); + if (rc < 0) { + pr_debug("Unable to read batt temperature rc=%d\n", rc); + return DEFAULT_TEMP; + } + pr_debug("get_bat_temp %d\n", batt_temp); + + return batt_temp; +} + +static void qpnp_lbc_set_appropriate_current(struct qpnp_lbc_chip *chip) +{ + unsigned int chg_current = chip->usb_psy_ma; + + if (chip->bat_is_cool && chip->cfg_cool_bat_chg_ma) + chg_current = min(chg_current, chip->cfg_cool_bat_chg_ma); + if (chip->bat_is_warm && chip->cfg_warm_bat_chg_ma) + chg_current = min(chg_current, chip->cfg_warm_bat_chg_ma); + if (chip->therm_lvl_sel != 0 && chip->thermal_mitigation) + chg_current = min(chg_current, + chip->thermal_mitigation[chip->therm_lvl_sel]); + + pr_debug("setting charger current %d mA\n", chg_current); + qpnp_lbc_ibatmax_set(chip, chg_current); +} + +static void qpnp_batt_external_power_changed(struct power_supply *psy) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + if (chip->bat_if_base && chip->batt_psy) { + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + } +} + +static int qpnp_lbc_system_temp_level_set(struct qpnp_lbc_chip *chip, + int lvl_sel) +{ + int rc = 0; + int prev_therm_lvl; + unsigned long flags; + + 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->cfg_thermal_levels) { + pr_err("Unsupported level selected %d forcing %d\n", lvl_sel, + chip->cfg_thermal_levels - 1); + lvl_sel = chip->cfg_thermal_levels - 1; + } + + if (lvl_sel == chip->therm_lvl_sel) + return 0; + + spin_lock_irqsave(&chip->ibat_change_lock, flags); + prev_therm_lvl = chip->therm_lvl_sel; + chip->therm_lvl_sel = lvl_sel; + if (chip->therm_lvl_sel == (chip->cfg_thermal_levels - 1)) { + /* Disable charging if highest value selected by */ + rc = qpnp_lbc_charger_enable(chip, THERMAL, 0); + if (rc < 0) + pr_err("Failed to set disable charging\n"); + goto out; + } + + qpnp_lbc_set_appropriate_current(chip); + + if (prev_therm_lvl == chip->cfg_thermal_levels - 1) { + /* + * If previously highest value was selected charging must have + * been disabed. Enable charging. + */ + rc = qpnp_lbc_charger_enable(chip, THERMAL, 1); + if (rc < 0) + pr_err("Failed to enable charging\n"); + } +out: + spin_unlock_irqrestore(&chip->ibat_change_lock, flags); + return rc; +} + +#define MIN_COOL_TEMP -300 +#define MAX_WARM_TEMP 1000 +#define HYSTERISIS_DECIDEGC 20 + +static int qpnp_lbc_configure_jeita(struct qpnp_lbc_chip *chip, + enum power_supply_property psp, int temp_degc) +{ + int rc = 0; + + if ((temp_degc < MIN_COOL_TEMP) || (temp_degc > MAX_WARM_TEMP)) { + pr_err("Invalid temp range=%d min=%d max=%d\n", + temp_degc, MIN_COOL_TEMP, MAX_WARM_TEMP); + return -EINVAL; + } + + if (chip->cfg_use_fake_battery || chip->debug_board) + return 0; + + mutex_lock(&chip->jeita_configure_lock); + switch (psp) { + case POWER_SUPPLY_PROP_COOL_TEMP: + if (temp_degc >= + (chip->cfg_warm_bat_decidegc - HYSTERISIS_DECIDEGC)) { + pr_err("Can't set cool %d higher than warm %d - hysterisis %d\n", + temp_degc, + chip->cfg_warm_bat_decidegc, + HYSTERISIS_DECIDEGC); + rc = -EINVAL; + goto mutex_unlock; + } + if (chip->bat_is_cool) + chip->adc_param.high_temp = + temp_degc + HYSTERISIS_DECIDEGC; + else if (!chip->bat_is_warm) + chip->adc_param.low_temp = temp_degc; + + chip->cfg_cool_bat_decidegc = temp_degc; + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + if (temp_degc <= + (chip->cfg_cool_bat_decidegc + HYSTERISIS_DECIDEGC)) { + pr_err("Can't set warm %d higher than cool %d + hysterisis %d\n", + temp_degc, + chip->cfg_warm_bat_decidegc, + HYSTERISIS_DECIDEGC); + rc = -EINVAL; + goto mutex_unlock; + } + if (chip->bat_is_warm) + chip->adc_param.low_temp = + temp_degc - HYSTERISIS_DECIDEGC; + else if (!chip->bat_is_cool) + chip->adc_param.high_temp = temp_degc; + + chip->cfg_warm_bat_decidegc = temp_degc; + break; + default: + rc = -EINVAL; + goto mutex_unlock; + } + + if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param)) + pr_err("request ADC error\n"); + +mutex_unlock: + mutex_unlock(&chip->jeita_configure_lock); + return rc; +} + +static void qpnp_lbc_debug_board_work_fn(struct work_struct *work) +{ + struct qpnp_lbc_chip *chip = container_of(work, struct qpnp_lbc_chip, + debug_board_work); + int rc = 0; + + if (chip->adc_param.channel == LR_MUX1_BATT_THERM + && chip->debug_board) { + pr_debug("Disable adc-tm notifications for debug board\n"); + rc = qpnp_adc_tm_disable_chan_meas(chip->adc_tm_dev, + &chip->adc_param); + if (rc < 0) + pr_err("failed to disable tm %d\n", rc); + } +} + +static int qpnp_batt_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_COOL_TEMP: + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + case POWER_SUPPLY_PROP_WARM_TEMP: + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + return 1; + default: + break; + } + + return 0; +} + +/* + * End of charge happens only when BMS reports the battery status as full. For + * charging to end the s/w must put the usb path in suspend. Note that there + * is no battery fet and usb path suspend is the only control to prevent any + * current going in to the battery (and the system) + * Charging can begin only when VBATDET comparator outputs 0. This indicates + * that the battery is a at a lower voltage than 4% of the vddmax value. + * S/W can override this comparator to output a favourable value - this is + * used while resuming charging when the battery hasnt fallen below 4% but + * the SOC has fallen below the resume threshold. + * + * In short, when SOC resume happens: + * a. override the comparator to output 0 + * b. enable charging + * + * When vbatdet based resume happens: + * a. enable charging + * + * When end of charge happens: + * a. disable the overrides in the comparator + * (may be from a previous soc resume) + * b. disable charging + */ +static int qpnp_batt_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + mutex_lock(&chip->chg_enable_lock); + switch (val->intval) { + case POWER_SUPPLY_STATUS_FULL: + if (chip->cfg_float_charge) + break; + /* Disable charging */ + rc = qpnp_lbc_charger_enable(chip, SOC, 0); + if (!rc) + chip->chg_done = true; + + /* + * Enable VBAT_DET based charging: + * To enable charging when VBAT falls below VBAT_DET + * and device stays suspended after EOC. + */ + if (!chip->cfg_disable_vbatdet_based_recharge) { + /* No override for VBAT_DET_LO comp */ + rc = qpnp_lbc_vbatdet_override(chip, + OVERRIDE_NONE); + if (rc) + pr_err("Failed to override VBAT_DET rc=%d\n", + rc); + else + qpnp_lbc_enable_irq(chip, + &chip->irqs[CHG_VBAT_DET_LO]); + } + break; + case POWER_SUPPLY_STATUS_CHARGING: + chip->chg_done = false; + pr_debug("resuming charging by bms\n"); + if (!chip->cfg_disable_vbatdet_based_recharge) + qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); + + qpnp_lbc_charger_enable(chip, SOC, 1); + break; + case POWER_SUPPLY_STATUS_DISCHARGING: + chip->chg_done = false; + pr_debug("status = DISCHARGING chg_done = %d\n", + chip->chg_done); + break; + default: + break; + } + mutex_unlock(&chip->chg_enable_lock); + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + rc = qpnp_lbc_configure_jeita(chip, psp, val->intval); + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + rc = qpnp_lbc_configure_jeita(chip, psp, val->intval); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + pr_debug("power supply changed batt_psy\n"); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + chip->cfg_charging_disabled = !(val->intval); + rc = qpnp_lbc_charger_enable(chip, USER, + !chip->cfg_charging_disabled); + break; + case POWER_SUPPLY_PROP_DEBUG_BATTERY: + chip->debug_board = val->intval; + schedule_work(&chip->debug_board_work); + rc = qpnp_lbc_charger_enable(chip, DEBUG_BOARD, + !(val->intval)); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN: + qpnp_lbc_vinmin_set(chip, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + qpnp_lbc_system_temp_level_set(chip, val->intval); + break; + default: + return -EINVAL; + } + + if (chip->bat_if_base && chip->batt_psy) + power_supply_changed(chip->batt_psy); + + return rc; +} + +static int qpnp_batt_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN: + val->intval = chip->cfg_max_voltage_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MIN_DESIGN: + val->intval = chip->cfg_min_voltage_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + val->intval = chip->cfg_max_voltage_mv * 1000; + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = get_prop_battery_voltage_now(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_COOL_TEMP: + val->intval = chip->cfg_cool_bat_decidegc; + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + val->intval = chip->cfg_warm_bat_decidegc; + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_capacity(chip); + chip->current_soc = val->intval; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_prop_current_now(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = get_prop_charge_count(chip); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + val->intval = get_bms_property(chip, psp); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = get_bms_property(chip, psp); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !(chip->cfg_charging_disabled); + break; + case POWER_SUPPLY_PROP_DEBUG_BATTERY: + val->intval = chip->debug_board; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT: + val->intval = chip->therm_lvl_sel; + break; + case POWER_SUPPLY_PROP_CHARGE_CONTROL_LIMIT_MAX: + val->intval = chip->cfg_thermal_levels; + break; + default: + return -EINVAL; + } + + return 0; +} + +#define VINMIN_DELAY msecs_to_jiffies(500) +static void qpnp_lbc_parallel_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct qpnp_lbc_chip *chip = container_of(dwork, + struct qpnp_lbc_chip, parallel_work); + + if (is_vinmin_set(chip)) { + /* vinmin-loop triggered - stop ibat increase */ + pr_debug("vinmin_loop triggered ichg_now=%d\n", chip->ichg_now); + goto exit_work; + } else { + int temp = chip->ichg_now + QPNP_LBC_I_STEP_MA; + + if (temp > chip->lbc_max_chg_current) { + pr_debug("ichg_now=%d beyond max_chg_limit=%d - stopping\n", + temp, chip->lbc_max_chg_current); + goto exit_work; + } + chip->ichg_now = temp; + qpnp_lbc_ibatmax_set(chip, chip->ichg_now); + pr_debug("ichg_now increased to %d\n", chip->ichg_now); + } + + schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); + + return; + +exit_work: + pm_relax(chip->dev); +} + +static int qpnp_lbc_parallel_charging_config(struct qpnp_lbc_chip *chip, + int enable) +{ + chip->parallel_charging_enabled = !!enable; + + if (enable) { + /* Prevent sleep until charger is configured */ + chip->ichg_now = QPNP_LBC_IBATMAX_MIN; + qpnp_lbc_ibatmax_set(chip, chip->ichg_now); + qpnp_lbc_charger_enable(chip, PARALLEL, 1); + pm_stay_awake(chip->dev); + schedule_delayed_work(&chip->parallel_work, VINMIN_DELAY); + } else { + cancel_delayed_work_sync(&chip->parallel_work); + pm_relax(chip->dev); + /* set minimum charging current and disable charging */ + chip->ichg_now = 0; + chip->lbc_max_chg_current = 0; + qpnp_lbc_ibatmax_set(chip, 0); + qpnp_lbc_charger_enable(chip, PARALLEL, 0); + } + + pr_debug("charging=%d ichg_now=%d max_chg_current=%d\n", + enable, chip->ichg_now, chip->lbc_max_chg_current); + + return 0; +} + +static void qpnp_lbc_set_current(struct qpnp_lbc_chip *chip, int current_ma) +{ + pr_debug("USB present=%d current_ma=%dmA\n", chip->usb_present, + current_ma); + + if (current_ma <= 2 && get_prop_batt_present(chip)) { + qpnp_lbc_charger_enable(chip, CURRENT, 0); + chip->usb_psy_ma = QPNP_CHG_I_MAX_MIN_90; + qpnp_lbc_set_appropriate_current(chip); + } else { + chip->usb_psy_ma = current_ma; + qpnp_lbc_set_appropriate_current(chip); + qpnp_lbc_charger_enable(chip, CURRENT, 1); + } +} + +static enum power_supply_property qpnp_lbc_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, +}; +#define MICRO_5V 5000000 +static int qpnp_lbc_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->usb_psy_ma * 1000; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->usb_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + if (is_battery_charging(chip)) + val->intval = 1; + else + val->intval = 0; + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB; + if (chip->usb_present && + (chip->usb_supply_type != POWER_SUPPLY_TYPE_UNKNOWN)) + val->intval = chip->usb_supply_type; + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = POWER_SUPPLY_TYPE_UNKNOWN; + if (chip->usb_present && + (chip->usb_supply_type != POWER_SUPPLY_TYPE_UNKNOWN)) + val->intval = chip->usb_supply_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + if (chip->usb_present) + val->intval = MICRO_5V; + else + val->intval = 0; + break; + default: + return -EINVAL; + } + + return 0; +} + +static int qpnp_lbc_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + qpnp_lbc_set_current(chip, (val->intval / 1000)); + break; + case POWER_SUPPLY_PROP_TYPE: + case POWER_SUPPLY_PROP_REAL_TYPE: + chip->usb_supply_type = val->intval; + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} +static int qpnp_lbc_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 enum power_supply_property qpnp_lbc_parallel_properties[] = { + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION, +}; + +static int qpnp_lbc_parallel_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + qpnp_lbc_parallel_charging_config(chip, !!val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + chip->lbc_max_chg_current = val->intval / 1000; + pr_debug("lbc_max_current=%d\n", chip->lbc_max_chg_current); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int qpnp_lbc_parallel_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_CONSTANT_CHARGE_CURRENT_MAX: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int qpnp_lbc_parallel_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct qpnp_lbc_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = chip->parallel_charging_enabled; + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + val->intval = chip->lbc_max_chg_current * 1000; + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = chip->ichg_now * 1000; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION: + val->intval = is_vinmin_set(chip); + break; + default: + return -EINVAL; + } + return 0; +} + + +static void qpnp_lbc_jeita_adc_notification(enum qpnp_tm_state state, void *ctx) +{ + struct qpnp_lbc_chip *chip = ctx; + bool bat_warm = false, bat_cool = false; + int temp; + unsigned long flags; + + if (state >= ADC_TM_STATE_NUM) { + pr_err("invalid notification %d\n", state); + return; + } + + temp = get_prop_batt_temp(chip); + + pr_debug("temp = %d state = %s\n", temp, + state == ADC_TM_WARM_STATE ? "warm" : "cool"); + + if (state == ADC_TM_WARM_STATE) { + if (temp >= chip->cfg_warm_bat_decidegc) { + /* Normal to warm */ + bat_warm = true; + bat_cool = false; + chip->adc_param.low_temp = + chip->cfg_warm_bat_decidegc + - HYSTERISIS_DECIDEGC; + chip->adc_param.state_request = + ADC_TM_COOL_THR_ENABLE; + } else if (temp >= + chip->cfg_cool_bat_decidegc + HYSTERISIS_DECIDEGC) { + /* Cool to normal */ + bat_warm = false; + bat_cool = false; + + chip->adc_param.low_temp = + chip->cfg_cool_bat_decidegc; + chip->adc_param.high_temp = + chip->cfg_warm_bat_decidegc; + chip->adc_param.state_request = + ADC_TM_HIGH_LOW_THR_ENABLE; + } + } else { + if (temp <= chip->cfg_cool_bat_decidegc) { + /* Normal to cool */ + bat_warm = false; + bat_cool = true; + chip->adc_param.high_temp = + chip->cfg_cool_bat_decidegc + + HYSTERISIS_DECIDEGC; + chip->adc_param.state_request = + ADC_TM_WARM_THR_ENABLE; + } else if (temp <= (chip->cfg_warm_bat_decidegc - + HYSTERISIS_DECIDEGC)){ + /* Warm to normal */ + bat_warm = false; + bat_cool = false; + + chip->adc_param.low_temp = + chip->cfg_cool_bat_decidegc; + chip->adc_param.high_temp = + chip->cfg_warm_bat_decidegc; + chip->adc_param.state_request = + ADC_TM_HIGH_LOW_THR_ENABLE; + } + } + + if (chip->bat_is_cool ^ bat_cool || chip->bat_is_warm ^ bat_warm) { + spin_lock_irqsave(&chip->ibat_change_lock, flags); + chip->bat_is_cool = bat_cool; + chip->bat_is_warm = bat_warm; + qpnp_lbc_set_appropriate_vddmax(chip); + qpnp_lbc_set_appropriate_current(chip); + spin_unlock_irqrestore(&chip->ibat_change_lock, flags); + } + + pr_debug("warm %d, cool %d, low = %d deciDegC, high = %d deciDegC\n", + chip->bat_is_warm, chip->bat_is_cool, + 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"); +} + +#define IBAT_TERM_EN_MASK BIT(3) +static int qpnp_lbc_chg_init(struct qpnp_lbc_chip *chip) +{ + int rc; + u8 reg_val; + + qpnp_lbc_vbatweak_set(chip, chip->cfg_batt_weak_voltage_uv); + rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv); + if (rc) { + pr_err("Failed to set vin_min rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_safe_voltage_mv); + if (rc) { + pr_err("Failed to set vdd_safe rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv); + if (rc) { + pr_err("Failed to set vdd_safe rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_ibatsafe_set(chip, chip->cfg_safe_current); + if (rc) { + pr_err("Failed to set ibat_safe rc=%d\n", rc); + return rc; + } + + if (of_find_property(chip->dev->of_node, "qcom,tchg-mins", NULL)) { + rc = qpnp_lbc_tchg_max_set(chip, chip->cfg_tchg_mins); + if (rc) { + pr_err("Failed to set tchg_mins rc=%d\n", rc); + return rc; + } + } + + /* + * Override VBAT_DET comparator to enable charging + * irrespective of VBAT above VBAT_DET. + */ + rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); + if (rc) { + pr_err("Failed to override comp rc=%d\n", rc); + return rc; + } + + /* + * Disable iterm comparator of linear charger to disable charger + * detecting end of charge condition based on DT configuration + * and float charge configuration. + */ + if (!chip->cfg_charger_detect_eoc || chip->cfg_float_charge) { + rc = qpnp_lbc_masked_write(chip, + chip->chgr_base + CHG_IBATTERM_EN_REG, + IBAT_TERM_EN_MASK, 0); + if (rc) { + pr_err("Failed to disable EOC comp rc=%d\n", rc); + return rc; + } + } + + /* Disable charger watchdog */ + reg_val = 0; + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_WDOG_EN_REG, + ®_val, 1); + + return rc; +} + +static int qpnp_lbc_bat_if_init(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + /* Select battery presence detection */ + switch (chip->cfg_bpd_detection) { + case BPD_TYPE_BAT_THM: + reg_val = BATT_THM_EN; + break; + case BPD_TYPE_BAT_ID: + reg_val = BATT_ID_EN; + break; + case BPD_TYPE_BAT_THM_BAT_ID: + reg_val = BATT_THM_EN | BATT_ID_EN; + break; + default: + reg_val = BATT_THM_EN; + break; + } + + rc = qpnp_lbc_masked_write(chip, + chip->bat_if_base + BAT_IF_BPD_CTRL_REG, + BATT_BPD_CTRL_SEL_MASK, reg_val); + if (rc) { + pr_err("Failed to choose BPD rc=%d\n", rc); + return rc; + } + + /* Force on VREF_BAT_THM */ + reg_val = VREF_BATT_THERM_FORCE_ON; + rc = qpnp_lbc_write(chip, + chip->bat_if_base + BAT_IF_VREF_BAT_THM_CTRL_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to force on VREF_BAT_THM rc=%d\n", rc); + return rc; + } + + return 0; +} + +static int qpnp_lbc_usb_path_init(struct qpnp_lbc_chip *chip) +{ + int rc; + u8 reg_val; + + if (qpnp_lbc_is_usb_chg_plugged_in(chip)) { + reg_val = 0; + rc = qpnp_lbc_write(chip, + chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to write enum stop rc=%d\n", rc); + return -ENXIO; + } + } + + if (chip->cfg_charging_disabled) { + rc = qpnp_lbc_charger_enable(chip, USER, 0); + if (rc) + pr_err("Failed to disable charging rc=%d\n", rc); + + /* + * Disable follow-on-reset if charging is explicitly disabled, + * this forces the charging to be disabled across reset. + * Note: Explicitly disabling charging is only a debug/test + * configuration + */ + reg_val = 0x0; + rc = __qpnp_lbc_secure_write(chip, chip->chgr_base, + CHG_PERPH_RESET_CTRL3_REG, ®_val, 1); + if (rc) + pr_err("Failed to configure PERPH_CTRL3 rc=%d\n", rc); + else + pr_debug("Charger is not following PMIC reset\n"); + } else { + /* + * Enable charging explicitly, + * because not sure the default behavior. + */ + reg_val = CHG_ENABLE; + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG, + CHG_EN_MASK, reg_val); + if (rc) + pr_err("Failed to enable charger rc=%d\n", rc); + } + + return rc; +} + +#define LBC_MISC_DIG_VERSION_1 0x01 +static int qpnp_lbc_misc_init(struct qpnp_lbc_chip *chip) +{ + int rc; + u8 reg_val, reg_val1, trim_center; + + /* Check if this LBC MISC version supports VDD trimming */ + rc = qpnp_lbc_read(chip, chip->misc_base + MISC_REV2_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc); + return rc; + } + + if (reg_val >= LBC_MISC_DIG_VERSION_1) { + chip->supported_feature_flag |= VDD_TRIM_SUPPORTED; + /* Read initial VDD trim value */ + rc = qpnp_lbc_read(chip, chip->misc_base + MISC_TRIM3_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc); + return rc; + } + + rc = qpnp_lbc_read(chip, chip->misc_base + MISC_TRIM4_REG, + ®_val1, 1); + if (rc) { + pr_err("Failed to read VDD_EA TRIM3 reg rc=%d\n", rc); + return rc; + } + + trim_center = ((reg_val & MISC_TRIM3_VDD_MASK) + >> VDD_TRIM3_SHIFT) + | ((reg_val1 & MISC_TRIM4_VDD_MASK) + >> VDD_TRIM4_SHIFT); + chip->init_trim_uv = qpnp_lbc_get_trim_voltage(trim_center); + chip->delta_vddmax_uv = chip->init_trim_uv; + pr_debug("Initial trim center %x trim_uv %d\n", + trim_center, chip->init_trim_uv); + } + + pr_debug("Setting BOOT_DONE\n"); + reg_val = MISC_BOOT_DONE; + rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG, + ®_val, 1); + + return rc; +} + +static int show_lbc_config(struct seq_file *m, void *data) +{ + struct qpnp_lbc_chip *chip = m->private; + + seq_printf(m, "cfg_charging_disabled\t=\t%d\n" + "cfg_btc_disabled\t=\t%d\n" + "cfg_use_fake_battery\t=\t%d\n" + "cfg_use_external_charger\t=\t%d\n" + "cfg_chgr_led_support\t=\t%d\n" + "cfg_warm_bat_chg_ma\t=\t%d\n" + "cfg_cool_bat_chg_ma\t=\t%d\n" + "cfg_safe_voltage_mv\t=\t%d\n" + "cfg_max_voltage_mv\t=\t%d\n" + "cfg_min_voltage_mv\t=\t%d\n" + "cfg_charger_detect_eoc\t=\t%d\n" + "cfg_disable_vbatdet_based_recharge\t=\t%d\n" + "cfg_collapsible_chgr_support\t=\t%d\n" + "cfg_batt_weak_voltage_uv\t=\t%d\n" + "cfg_warm_bat_mv\t=\t%d\n" + "cfg_cool_bat_mv\t=\t%d\n" + "cfg_hot_batt_p\t=\t%d\n" + "cfg_cold_batt_p\t=\t%d\n" + "cfg_thermal_levels\t=\t%d\n" + "cfg_safe_current\t=\t%d\n" + "cfg_tchg_mins\t=\t%d\n" + "cfg_bpd_detection\t=\t%d\n" + "cfg_warm_bat_decidegc\t=\t%d\n" + "cfg_cool_bat_decidegc\t=\t%d\n" + "cfg_soc_resume_limit\t=\t%d\n" + "cfg_float_charge\t=\t%d\n", + chip->cfg_charging_disabled, + chip->cfg_btc_disabled, + chip->cfg_use_fake_battery, + chip->cfg_use_external_charger, + chip->cfg_chgr_led_support, + chip->cfg_warm_bat_chg_ma, + chip->cfg_cool_bat_chg_ma, + chip->cfg_safe_voltage_mv, + chip->cfg_max_voltage_mv, + chip->cfg_min_voltage_mv, + chip->cfg_charger_detect_eoc, + chip->cfg_disable_vbatdet_based_recharge, + chip->cfg_collapsible_chgr_support, + chip->cfg_batt_weak_voltage_uv, + chip->cfg_warm_bat_mv, + chip->cfg_cool_bat_mv, + chip->cfg_hot_batt_p, + chip->cfg_cold_batt_p, + chip->cfg_thermal_levels, + chip->cfg_safe_current, + chip->cfg_tchg_mins, + chip->cfg_bpd_detection, + chip->cfg_warm_bat_decidegc, + chip->cfg_cool_bat_decidegc, + chip->cfg_soc_resume_limit, + chip->cfg_float_charge); + + return 0; +} + +static int qpnp_lbc_config_open(struct inode *inode, struct file *file) +{ + struct qpnp_lbc_chip *chip = inode->i_private; + + return single_open(file, show_lbc_config, chip); +} + +static const struct file_operations qpnp_lbc_config_debugfs_ops = { + .owner = THIS_MODULE, + .open = qpnp_lbc_config_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define OF_PROP_READ(chip, prop, qpnp_dt_property, retval, optional) \ +do { \ + if (retval) \ + break; \ + \ + retval = of_property_read_u32(chip->dev->of_node, \ + "qcom," qpnp_dt_property, \ + &chip->prop); \ + \ + if ((retval == -EINVAL) && optional) \ + retval = 0; \ + else if (retval) \ + pr_err("Error reading " #qpnp_dt_property \ + " property rc = %d\n", rc); \ +} while (0) + +#define DEFAULT_CUTOFF_MV 3400 +static int qpnp_charger_read_dt_props(struct qpnp_lbc_chip *chip) +{ + int rc = 0; + const char *bpd; + + OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0); + OF_PROP_READ(chip, cfg_safe_voltage_mv, "vddsafe-mv", rc, 0); + OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0); + OF_PROP_READ(chip, cfg_safe_current, "ibatsafe-ma", rc, 0); + OF_PROP_READ(chip, cfg_volt_cutoff_mv, "v-cutoff-mv", rc, 0); + if (rc) + pr_err("Error reading required property rc=%d\n", rc); + + if (!chip->cfg_volt_cutoff_mv) + chip->cfg_volt_cutoff_mv = DEFAULT_CUTOFF_MV; + + chip->cutoff_threshold_uv = (chip->cfg_volt_cutoff_mv - 100) * 1000; + + OF_PROP_READ(chip, cfg_tchg_mins, "tchg-mins", rc, 1); + OF_PROP_READ(chip, cfg_warm_bat_decidegc, "warm-bat-decidegc", rc, 1); + OF_PROP_READ(chip, cfg_cool_bat_decidegc, "cool-bat-decidegc", rc, 1); + OF_PROP_READ(chip, cfg_hot_batt_p, "batt-hot-percentage", rc, 1); + OF_PROP_READ(chip, cfg_cold_batt_p, "batt-cold-percentage", rc, 1); + OF_PROP_READ(chip, cfg_batt_weak_voltage_uv, "vbatweak-uv", rc, 1); + OF_PROP_READ(chip, cfg_soc_resume_limit, "resume-soc", rc, 1); + if (rc) { + pr_err("Error reading optional property rc=%d\n", rc); + return rc; + } + + rc = of_property_read_string(chip->dev->of_node, + "qcom,bpd-detection", &bpd); + if (rc) { + + chip->cfg_bpd_detection = BPD_TYPE_BAT_THM; + rc = 0; + } else { + chip->cfg_bpd_detection = get_bpd(bpd); + if (chip->cfg_bpd_detection < 0) { + pr_err("Failed to determine bpd schema rc=%d\n", rc); + return -EINVAL; + } + } + + /* + * Look up JEITA compliance parameters if cool and warm temp + * provided + */ + if (chip->cfg_cool_bat_decidegc || chip->cfg_warm_bat_decidegc) { + 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("Failed to get adc-tm rc=%d\n", rc); + return rc; + } + + OF_PROP_READ(chip, cfg_warm_bat_chg_ma, "ibatmax-warm-ma", + rc, 1); + OF_PROP_READ(chip, cfg_cool_bat_chg_ma, "ibatmax-cool-ma", + rc, 1); + OF_PROP_READ(chip, cfg_warm_bat_mv, "warm-bat-mv", rc, 1); + OF_PROP_READ(chip, cfg_cool_bat_mv, "cool-bat-mv", rc, 1); + if (rc) { + pr_err("Error reading battery temp prop rc=%d\n", rc); + return rc; + } + } + + /* Get the btc-disabled property */ + chip->cfg_btc_disabled = of_property_read_bool( + chip->dev->of_node, "qcom,btc-disabled"); + + /* Get the charging-disabled property */ + chip->cfg_charging_disabled = + of_property_read_bool(chip->dev->of_node, + "qcom,charging-disabled"); + + /* Get the fake-batt-values property */ + chip->cfg_use_fake_battery = + of_property_read_bool(chip->dev->of_node, + "qcom,use-default-batt-values"); + + /* Get the float charging property */ + chip->cfg_float_charge = + of_property_read_bool(chip->dev->of_node, + "qcom,float-charge"); + + /* Get the charger EOC detect property */ + chip->cfg_charger_detect_eoc = + of_property_read_bool(chip->dev->of_node, + "qcom,charger-detect-eoc"); + + /* Get the vbatdet disable property */ + chip->cfg_disable_vbatdet_based_recharge = + of_property_read_bool(chip->dev->of_node, + "qcom,disable-vbatdet-based-recharge"); + + /* Get the charger led support property */ + chip->cfg_chgr_led_support = + of_property_read_bool(chip->dev->of_node, + "qcom,chgr-led-support"); + + /* Get the collapsible charger support property */ + chip->cfg_collapsible_chgr_support = + of_property_read_bool(chip->dev->of_node, + "qcom,collapsible-chgr-support"); + + /* Disable charging when faking battery values */ + if (chip->cfg_use_fake_battery) + chip->cfg_charging_disabled = true; + + chip->cfg_use_external_charger = of_property_read_bool( + chip->dev->of_node, "qcom,use-external-charger"); + + if (of_find_property(chip->dev->of_node, + "qcom,thermal-mitigation", + &chip->cfg_thermal_levels)) { + chip->thermal_mitigation = devm_kzalloc(chip->dev, + chip->cfg_thermal_levels, + GFP_KERNEL); + + if (chip->thermal_mitigation == NULL) { + pr_err("thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->cfg_thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(chip->dev->of_node, + "qcom,thermal-mitigation", + chip->thermal_mitigation, + chip->cfg_thermal_levels); + if (rc) { + pr_err("Failed to read threm limits rc = %d\n", rc); + return rc; + } + } + + pr_debug("vddmax-mv=%d, vddsafe-mv=%d, vinmin-mv=%d, v-cutoff-mv=%d, ibatsafe-ma=$=%d\n", + chip->cfg_max_voltage_mv, + chip->cfg_safe_voltage_mv, + chip->cfg_min_voltage_mv, + chip->cfg_volt_cutoff_mv, + chip->cfg_safe_current); + pr_debug("warm-bat-decidegc=%d, cool-bat-decidegc=%d, batt-hot-percentage=%d, batt-cold-percentage=%d\n", + chip->cfg_warm_bat_decidegc, + chip->cfg_cool_bat_decidegc, + chip->cfg_hot_batt_p, + chip->cfg_cold_batt_p); + pr_debug("tchg-mins=%d, vbatweak-uv=%d, resume-soc=%d\n", + chip->cfg_tchg_mins, + chip->cfg_batt_weak_voltage_uv, + chip->cfg_soc_resume_limit); + pr_debug("bpd-detection=%d, ibatmax-warm-ma=%d, ibatmax-cool-ma=%d, warm-bat-mv=%d, cool-bat-mv=%d\n", + chip->cfg_bpd_detection, + chip->cfg_warm_bat_chg_ma, + chip->cfg_cool_bat_chg_ma, + chip->cfg_warm_bat_mv, + chip->cfg_cool_bat_mv); + pr_debug("btc-disabled=%d, charging-disabled=%d, use-default-batt-values=%d, float-charge=%d\n", + chip->cfg_btc_disabled, + chip->cfg_charging_disabled, + chip->cfg_use_fake_battery, + chip->cfg_float_charge); + pr_debug("charger-detect-eoc=%d, disable-vbatdet-based-recharge=%d, chgr-led-support=%d\n", + chip->cfg_charger_detect_eoc, + chip->cfg_disable_vbatdet_based_recharge, + chip->cfg_chgr_led_support); + pr_debug("collapsible-chg-support=%d, use-external-charger=%d, thermal_levels=%d\n", + chip->cfg_collapsible_chgr_support, + chip->cfg_use_external_charger, + chip->cfg_thermal_levels); + return rc; +} + +#define CHG_REMOVAL_DETECT_DLY_MS 300 +static irqreturn_t qpnp_lbc_chg_gone_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int chg_gone; + + if (chip->cfg_collapsible_chgr_support) { + chg_gone = qpnp_lbc_is_chg_gone(chip); + pr_debug("chg-gone triggered, rt_sts: %d\n", chg_gone); + if (chg_gone) { + /* + * Disable charger to prevent fastchg irq storming + * if a non-collapsible charger is being used. + */ + pr_debug("disable charging for non-collapsbile charger\n"); + qpnp_lbc_charger_enable(chip, COLLAPSE, 0); + qpnp_lbc_disable_irq(chip, &chip->irqs[USBIN_VALID]); + qpnp_lbc_disable_irq(chip, &chip->irqs[USB_CHG_GONE]); + qpnp_chg_collapsible_chgr_config(chip, 0); + /* + * Check after a delay if the charger is still + * inserted. It decides if a non-collapsible + * charger is being used, or charger has been + * removed. + */ + schedule_delayed_work(&chip->collapsible_detection_work, + msecs_to_jiffies(CHG_REMOVAL_DETECT_DLY_MS)); + } + } + + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_lbc_usbin_valid_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int usb_present; + unsigned long flags; + + usb_present = qpnp_lbc_is_usb_chg_plugged_in(chip); + pr_debug("usbin-valid triggered: %d\n", usb_present); + + if (chip->usb_present ^ usb_present) { + chip->usb_present = usb_present; + if (!usb_present) { + chip->usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + qpnp_lbc_charger_enable(chip, CURRENT, 0); + spin_lock_irqsave(&chip->ibat_change_lock, flags); + chip->usb_psy_ma = QPNP_CHG_I_MAX_MIN_90; + qpnp_lbc_set_appropriate_current(chip); + spin_unlock_irqrestore(&chip->ibat_change_lock, + flags); + if (chip->cfg_collapsible_chgr_support) + chip->non_collapsible_chgr_detected = false; + + if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED) + alarm_try_to_cancel(&chip->vddtrim_alarm); + } else { + /* + * Override VBAT_DET comparator to start charging + * even if VBAT > VBAT_DET. + */ + if (!chip->cfg_disable_vbatdet_based_recharge) + qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); + + /* + * If collapsible charger supported, enable chgr_gone + * irq, and configure for collapsible charger. + */ + if (chip->cfg_collapsible_chgr_support && + !chip->non_collapsible_chgr_detected) { + qpnp_lbc_enable_irq(chip, + &chip->irqs[USB_CHG_GONE]); + qpnp_chg_collapsible_chgr_config(chip, 1); + } + /* + * Enable SOC based charging to make sure + * charging gets enabled on USB insertion + * irrespective of battery SOC above resume_soc. + */ + qpnp_lbc_charger_enable(chip, SOC, 1); + } + + pr_debug("Updating usb_psy PRESENT property\n"); + if (chip->usb_present) + extcon_set_state_sync(chip->extcon, + EXTCON_USB, true); + else + extcon_set_state_sync(chip->extcon, + EXTCON_USB, false); + } + + power_supply_changed(chip->usb_psy); + if (chip->bat_if_base) { + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + } + + return IRQ_HANDLED; +} + +static int qpnp_lbc_is_batt_temp_ok(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_read(chip, chip->bat_if_base + INT_RT_STS_REG, + ®_val, 1); + if (rc) { + pr_err("reg read failed: addr=%03X, rc=%d\n", + chip->bat_if_base + INT_RT_STS_REG, rc); + return rc; + } + + return (reg_val & BAT_TEMP_OK_IRQ) ? 1 : 0; +} + +static irqreturn_t qpnp_lbc_batt_temp_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int batt_temp_good; + + batt_temp_good = qpnp_lbc_is_batt_temp_ok(chip); + pr_debug("batt-temp triggered: %d\n", batt_temp_good); + + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_lbc_batt_pres_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int batt_present; + + if (chip->debug_board) + return IRQ_HANDLED; + + batt_present = qpnp_lbc_is_batt_present(chip); + pr_debug("batt-pres triggered: %d\n", batt_present); + + if (chip->batt_present ^ batt_present) { + chip->batt_present = batt_present; + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + + if ((chip->cfg_cool_bat_decidegc + || chip->cfg_warm_bat_decidegc) + && batt_present && !chip->cfg_use_fake_battery) { + pr_debug("enabling vadc notifications\n"); + if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->adc_param)) + pr_err("request ADC error\n"); + } else if ((chip->cfg_cool_bat_decidegc + || chip->cfg_warm_bat_decidegc) + && !batt_present && !chip->cfg_use_fake_battery) { + qpnp_adc_tm_disable_chan_meas(chip->adc_tm_dev, + &chip->adc_param); + pr_debug("disabling vadc notifications\n"); + } + } + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_lbc_chg_failed_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int rc; + u8 reg_val = CHG_FAILED_BIT; + + pr_debug("chg_failed triggered count=%u\n", ++chip->chg_failed_count); + rc = qpnp_lbc_write(chip, chip->chgr_base + CHG_FAILED_REG, + ®_val, 1); + if (rc) + pr_err("Failed to write chg_fail clear bit rc=%d\n", rc); + + if (chip->bat_if_base) { + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + } + + return IRQ_HANDLED; +} + +static int qpnp_lbc_is_fastchg_on(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_read(chip, chip->chgr_base + INT_RT_STS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read interrupt status rc=%d\n", rc); + return rc; + } + pr_debug("charger status %x\n", reg_val); + return (reg_val & FAST_CHG_ON_IRQ) ? 1 : 0; +} + +#define TRIM_PERIOD_NS (50LL * NSEC_PER_SEC) +static irqreturn_t qpnp_lbc_fastchg_irq_handler(int irq, void *_chip) +{ + ktime_t kt; + struct qpnp_lbc_chip *chip = _chip; + bool fastchg_on = false; + + fastchg_on = qpnp_lbc_is_fastchg_on(chip); + + pr_debug("FAST_CHG IRQ triggered, fastchg_on: %d\n", fastchg_on); + + if (chip->fastchg_on ^ fastchg_on) { + chip->fastchg_on = fastchg_on; + if (fastchg_on) { + mutex_lock(&chip->chg_enable_lock); + chip->chg_done = false; + mutex_unlock(&chip->chg_enable_lock); + /* + * Start alarm timer to periodically calculate + * and update VDD_MAX trim value. + */ + if (chip->supported_feature_flag & + VDD_TRIM_SUPPORTED) { + kt = ns_to_ktime(TRIM_PERIOD_NS); + alarm_start_relative(&chip->vddtrim_alarm, + kt); + } + } + } + + if (chip->bat_if_base) { + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + } + + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_lbc_chg_done_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + + pr_debug("charging done triggered\n"); + + chip->chg_done = true; + pr_debug("power supply changed batt_psy\n"); + power_supply_changed(chip->batt_psy); + + return IRQ_HANDLED; +} + +static irqreturn_t qpnp_lbc_vbatdet_lo_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int rc; + + pr_debug("vbatdet-lo triggered\n"); + + /* + * Disable vbatdet irq to prevent interrupt storm when VBAT is + * close to VBAT_DET. + */ + qpnp_lbc_disable_irq(chip, &chip->irqs[CHG_VBAT_DET_LO]); + + /* + * Override VBAT_DET comparator to 0 to fix comparator toggling + * near VBAT_DET threshold. + */ + qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); + + /* + * Battery has fallen below the vbatdet threshold and it is + * time to resume charging. + */ + rc = qpnp_lbc_charger_enable(chip, SOC, 1); + if (rc) + pr_err("Failed to enable charging\n"); + + return IRQ_HANDLED; +} + +static int qpnp_lbc_is_overtemp(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_read(chip, chip->usb_chgpth_base + INT_RT_STS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read interrupt status rc=%d\n", rc); + return rc; + } + + pr_debug("OVERTEMP rt status %x\n", reg_val); + return (reg_val & OVERTEMP_ON_IRQ) ? 1 : 0; +} + +static irqreturn_t qpnp_lbc_usb_overtemp_irq_handler(int irq, void *_chip) +{ + struct qpnp_lbc_chip *chip = _chip; + int overtemp = qpnp_lbc_is_overtemp(chip); + + pr_warn_ratelimited("charger %s temperature limit\n", + overtemp ? "exceeds" : "within"); + + return IRQ_HANDLED; +} + +static int qpnp_disable_lbc_charger(struct qpnp_lbc_chip *chip) +{ + int rc; + u8 reg; + + reg = CHG_FORCE_BATT_ON; + rc = qpnp_lbc_masked_write(chip, chip->chgr_base + CHG_CTRL_REG, + CHG_EN_MASK, reg); + /* disable BTC */ + rc |= qpnp_lbc_masked_write(chip, chip->bat_if_base + BAT_IF_BTC_CTRL, + BTC_COMP_EN_MASK, 0); + /* Enable BID and disable THM based BPD */ + reg = BATT_ID_EN | BATT_BPD_OFFMODE_EN; + rc |= qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG, + ®, 1); + return rc; +} + +#define REQUEST_IRQ(chip, idx, rc, irq_name, threaded, flags, wake)\ +do { \ + if (rc) \ + break; \ + if (chip->irqs[idx].irq) { \ + if (threaded) \ + rc = devm_request_threaded_irq(chip->dev, \ + chip->irqs[idx].irq, NULL, \ + qpnp_lbc_##irq_name##_irq_handler, \ + flags, #irq_name, chip); \ + else \ + rc = devm_request_irq(chip->dev, \ + chip->irqs[idx].irq, \ + qpnp_lbc_##irq_name##_irq_handler, \ + flags, #irq_name, chip); \ + if (rc < 0) { \ + pr_err("Unable to request " #irq_name " %d\n", \ + rc); \ + } else { \ + rc = 0; \ + if (wake) { \ + enable_irq_wake(chip->irqs[idx].irq); \ + chip->irqs[idx].is_wake = true; \ + } \ + } \ + } \ +} while (0) + +static inline void get_irq_resource(struct qpnp_lbc_chip *chip, int idx, + const char *name, struct device_node *child) +{ + int rc = 0; + + rc = of_irq_get_byname(child, name); + if (rc < 0) + pr_err("Unable to get irq resource for %s - %d\n", name, rc); + else + chip->irqs[idx].irq = rc; +} + +static int qpnp_lbc_request_irqs(struct qpnp_lbc_chip *chip) +{ + int rc = 0; + + REQUEST_IRQ(chip, CHG_FAILED, rc, chg_failed, 0, + IRQF_TRIGGER_RISING, 1); + + REQUEST_IRQ(chip, CHG_FAST_CHG, rc, fastchg, 1, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, 1); + + REQUEST_IRQ(chip, CHG_DONE, rc, chg_done, 0, + IRQF_TRIGGER_RISING, 0); + + REQUEST_IRQ(chip, CHG_VBAT_DET_LO, rc, vbatdet_lo, 0, + IRQF_TRIGGER_FALLING, 1); + + REQUEST_IRQ(chip, BATT_PRES, rc, batt_pres, 1, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, 1); + + REQUEST_IRQ(chip, BATT_TEMPOK, rc, batt_temp, 0, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 1); + + REQUEST_IRQ(chip, USBIN_VALID, rc, usbin_valid, 1, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT, 1); + + REQUEST_IRQ(chip, USB_CHG_GONE, rc, chg_gone, 0, + IRQF_TRIGGER_RISING, 1); + + REQUEST_IRQ(chip, USB_OVER_TEMP, rc, usb_overtemp, 0, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, 0); + + return 0; +} + +static int qpnp_lbc_get_irqs(struct qpnp_lbc_chip *chip, u8 subtype, + struct device_node *child) +{ + switch (subtype) { + case LBC_CHGR_SUBTYPE: + get_irq_resource(chip, CHG_FAST_CHG, "fast-chg-on", child); + get_irq_resource(chip, CHG_FAILED, "chg-failed", child); + + if (!chip->cfg_disable_vbatdet_based_recharge) + get_irq_resource(chip, CHG_VBAT_DET_LO, + "vbat-det-lo", child); + if (chip->cfg_charger_detect_eoc) + get_irq_resource(chip, CHG_DONE, "chg-done", child); + break; + + case LBC_BAT_IF_SUBTYPE: + get_irq_resource(chip, BATT_PRES, "batt-pres", child); + get_irq_resource(chip, BATT_TEMPOK, "bat-temp-ok", child); + break; + + case LBC_USB_PTH_SUBTYPE: + get_irq_resource(chip, USBIN_VALID, "usbin-valid", child); + get_irq_resource(chip, USB_OVER_TEMP, "usb-over-temp", child); + get_irq_resource(chip, USB_CHG_GONE, "chg-gone", child); + break; + + default: + return -EINVAL; + } + + return 0; +} + +/* Get/Set initial state of charger */ +static void determine_initial_status(struct qpnp_lbc_chip *chip) +{ + chip->usb_present = qpnp_lbc_is_usb_chg_plugged_in(chip); + power_supply_changed(chip->usb_psy); + /* + * Set USB psy online to avoid userspace from shutting down if battery + * capacity is at zero and no chargers online. + */ + if (chip->usb_present) { + if (chip->cfg_collapsible_chgr_support && + !chip->non_collapsible_chgr_detected) { + qpnp_lbc_enable_irq(chip, + &chip->irqs[USB_CHG_GONE]); + qpnp_chg_collapsible_chgr_config(chip, 1); + } + extcon_set_state_sync(chip->extcon, EXTCON_USB, true); + } else { + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + } + power_supply_changed(chip->usb_psy); +} + +static void qpnp_lbc_collapsible_detection_work(struct work_struct *work) +{ + struct delayed_work *dwork = to_delayed_work(work); + struct qpnp_lbc_chip *chip = container_of(dwork, + struct qpnp_lbc_chip, + collapsible_detection_work); + + if (qpnp_lbc_is_usb_chg_plugged_in(chip)) { + chip->non_collapsible_chgr_detected = true; + pr_debug("Non-collapsible charger detected\n"); + } else { + chip->non_collapsible_chgr_detected = false; + pr_debug("Charger removal detected\n"); + } + qpnp_lbc_charger_enable(chip, COLLAPSE, 1); + qpnp_lbc_enable_irq(chip, &chip->irqs[USBIN_VALID]); +} + +#define IBAT_TRIM -300 +static void qpnp_lbc_vddtrim_work_fn(struct work_struct *work) +{ + int rc, vbat_now_uv, ibat_now; + u8 reg_val; + ktime_t kt; + struct qpnp_lbc_chip *chip = container_of(work, struct qpnp_lbc_chip, + vddtrim_work); + + vbat_now_uv = get_prop_battery_voltage_now(chip); + ibat_now = get_prop_current_now(chip) / 1000; + pr_debug("vbat %d ibat %d capacity %d\n", + vbat_now_uv, ibat_now, get_prop_capacity(chip)); + + /* + * Stop trimming under following condition: + * USB removed + * Charging Stopped + */ + if (!qpnp_lbc_is_fastchg_on(chip) || + !qpnp_lbc_is_usb_chg_plugged_in(chip)) { + pr_debug("stop trim charging stopped\n"); + goto exit; + } else { + rc = qpnp_lbc_read(chip, chip->chgr_base + CHG_STATUS_REG, + ®_val, 1); + if (rc) { + pr_err("Failed to read chg status rc=%d\n", rc); + goto out; + } + + /* + * Update VDD trim voltage only if following conditions are + * met: + * If charger is in VDD loop AND + * If ibat is between 0 ma and -300 ma + */ + if ((reg_val & CHG_VDD_LOOP_BIT) && + ((ibat_now < 0) && (ibat_now > IBAT_TRIM))) + qpnp_lbc_adjust_vddmax(chip, vbat_now_uv); + } + +out: + kt = ns_to_ktime(TRIM_PERIOD_NS); + alarm_start_relative(&chip->vddtrim_alarm, kt); +exit: + pm_relax(chip->dev); +} + +static enum alarmtimer_restart vddtrim_callback(struct alarm *alarm, + ktime_t now) +{ + struct qpnp_lbc_chip *chip = container_of(alarm, struct qpnp_lbc_chip, + vddtrim_alarm); + + pm_stay_awake(chip->dev); + schedule_work(&chip->vddtrim_work); + + return ALARMTIMER_NORESTART; +} + +static int qpnp_lbc_parallel_charger_init(struct qpnp_lbc_chip *chip) +{ + u8 reg_val; + int rc; + + rc = qpnp_lbc_vinmin_set(chip, chip->cfg_min_voltage_mv); + if (rc) { + pr_err("Failed to set vin_min rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_vddsafe_set(chip, chip->cfg_max_voltage_mv); + if (rc) { + pr_err("Failed to set vdd_safe rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_vddmax_set(chip, chip->cfg_max_voltage_mv); + if (rc) { + pr_err("Failed to set vdd_max rc=%d\n", rc); + return rc; + } + + /* set the minimum charging current */ + rc = qpnp_lbc_ibatmax_set(chip, 0); + if (rc) { + pr_err("Failed to set IBAT_MAX to 0 rc=%d\n", rc); + return rc; + } + + /* disable charging */ + rc = qpnp_lbc_charger_enable(chip, PARALLEL, 0); + if (rc) { + pr_err("Unable to disable charging rc=%d\n", rc); + return 0; + } + + /* Enable BID and disable THM based BPD */ + reg_val = BATT_ID_EN | BATT_BPD_OFFMODE_EN; + rc = qpnp_lbc_write(chip, chip->bat_if_base + BAT_IF_BPD_CTRL_REG, + ®_val, 1); + if (rc) + pr_err("Failed to override BPD configuration rc=%d\n", rc); + + /* Disable and override BTC */ + reg_val = 0x2A; + rc = __qpnp_lbc_secure_write(chip, chip->bat_if_base, + BTC_COMP_OVERRIDE_REG, ®_val, 1); + if (rc) + pr_err("Failed to disable BTC override rc=%d\n", rc); + + reg_val = 0; + rc = qpnp_lbc_write(chip, + chip->bat_if_base + BAT_IF_BTC_CTRL, ®_val, 1); + if (rc) + pr_err("Failed to disable BTC rc=%d\n", rc); + + /* override VBAT_DET */ + rc = qpnp_lbc_vbatdet_override(chip, OVERRIDE_0); + if (rc) + pr_err("Failed to override VBAT_DET rc=%d\n", rc); + + /* Set BOOT_DONE and ENUM complete */ + reg_val = 0; + rc = qpnp_lbc_write(chip, + chip->usb_chgpth_base + CHG_USB_ENUM_T_STOP_REG, + ®_val, 1); + if (rc) + pr_err("Failed to stop enum-timer rc=%d\n", rc); + + reg_val = MISC_BOOT_DONE; + rc = qpnp_lbc_write(chip, chip->misc_base + MISC_BOOT_DONE_REG, + ®_val, 1); + if (rc) + pr_err("Failed to set boot-done rc=%d\n", rc); + + return rc; +} + +static int qpnp_lbc_parse_resources(struct qpnp_lbc_chip *chip) +{ + u8 subtype; + int rc = 0; + struct platform_device *pdev = chip->pdev; + struct device_node *child; + unsigned int base; + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + goto fail_charger_enable; + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + pr_debug("register address = %#X rc = %d\n", base, rc); + if (rc < 0) { + pr_err("Couldn`t find reg in node = %s rc = %d\n", + child->full_name, rc); + goto fail_charger_enable; + } + + rc = qpnp_lbc_read(chip, base + PERP_SUBTYPE_REG, &subtype, 1); + if (rc) { + pr_err("Peripheral subtype read failed rc=%d\n", rc); + return rc; + } + + switch (subtype) { + case LBC_CHGR_SUBTYPE: + chip->chgr_base = base; + rc = qpnp_lbc_get_irqs(chip, subtype, child); + if (rc) { + pr_err("Failed to get CHGR irqs rc=%d\n", rc); + return rc; + } + break; + case LBC_USB_PTH_SUBTYPE: + chip->usb_chgpth_base = base; + rc = qpnp_lbc_get_irqs(chip, subtype, child); + if (rc) { + pr_err("Failed to get USB_PTH irqs rc=%d\n", + rc); + return rc; + } + break; + case LBC_BAT_IF_SUBTYPE: + chip->bat_if_base = base; + rc = qpnp_lbc_get_irqs(chip, subtype, child); + if (rc) { + pr_err("Failed to get BAT_IF irqs rc=%d\n", rc); + return rc; + } + break; + case LBC_MISC_SUBTYPE: + chip->misc_base = base; + break; + default: + pr_err("Invalid peripheral subtype=0x%x\n", subtype); + rc = -EINVAL; + } + } + + pr_debug("chgr_base=%x usb_chgpth_base=%x bat_if_base=%x misc_base=%x\n", + chip->chgr_base, chip->usb_chgpth_base, + chip->bat_if_base, chip->misc_base); + + return rc; + +fail_charger_enable: + dev_set_drvdata(&pdev->dev, NULL); + return -ENXIO; +} + +static int qpnp_lbc_parallel_probe(struct platform_device *pdev) +{ + int rc = 0; + struct qpnp_lbc_chip *chip; + struct power_supply_config parallel_psy_cfg = {}; + + chip = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_lbc_chip), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + pr_err("Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->dev = &pdev->dev; + chip->pdev = pdev; + dev_set_drvdata(&pdev->dev, chip); + device_init_wakeup(&pdev->dev, 1); + spin_lock_init(&chip->hw_access_lock); + spin_lock_init(&chip->ibat_change_lock); + INIT_DELAYED_WORK(&chip->parallel_work, qpnp_lbc_parallel_work); + + OF_PROP_READ(chip, cfg_max_voltage_mv, "vddmax-mv", rc, 0); + if (rc) + return rc; + OF_PROP_READ(chip, cfg_min_voltage_mv, "vinmin-mv", rc, 0); + if (rc) + return rc; + + rc = qpnp_lbc_parse_resources(chip); + if (rc) { + pr_err("Unable to parse LBC(parallel) resources rc=%d\n", rc); + return rc; + } + + rc = qpnp_lbc_parallel_charger_init(chip); + if (rc) { + pr_err("Unable to initialize LBC(parallel) rc=%d\n", rc); + return rc; + } + + chip->parallel_psy_d.name = "parallel"; + chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL; + chip->parallel_psy_d.get_property = qpnp_lbc_parallel_get_property; + chip->parallel_psy_d.set_property = qpnp_lbc_parallel_set_property; + chip->parallel_psy_d.properties = qpnp_lbc_parallel_properties; + chip->parallel_psy_d.property_is_writeable = + qpnp_lbc_parallel_is_writeable; + chip->parallel_psy_d.num_properties = + ARRAY_SIZE(qpnp_lbc_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("Unable to register LBC parallel_psy rc = %ld\n", + PTR_ERR(chip->parallel_psy)); + return PTR_ERR(chip->parallel_psy); + } + + pr_debug("LBC (parallel) registered successfully!\n"); + + return 0; +} + +static int qpnp_lbc_main_probe(struct platform_device *pdev) +{ + ktime_t kt; + struct qpnp_lbc_chip *chip; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config usb_psy_cfg = {}; + int rc = 0; + + chip = devm_kzalloc(&pdev->dev, sizeof(struct qpnp_lbc_chip), + GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) + return -EINVAL; + + chip->dev = &pdev->dev; + chip->pdev = pdev; + dev_set_drvdata(&pdev->dev, chip); + device_init_wakeup(&pdev->dev, 1); + chip->fake_battery_soc = -EINVAL; + chip->current_soc = DEFAULT_CAPACITY; + chip->usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + + chip->extcon = devm_extcon_dev_allocate(chip->dev, + qpnp_lbc_extcon_cable); + if (IS_ERR(chip->extcon)) { + pr_err("failed to allocate extcon device\n"); + rc = PTR_ERR(chip->extcon); + return rc; + } + + rc = devm_extcon_dev_register(chip->dev, chip->extcon); + if (rc) { + pr_err("failed to register extcon device\n"); + return rc; + } + + mutex_init(&chip->jeita_configure_lock); + mutex_init(&chip->chg_enable_lock); + spin_lock_init(&chip->hw_access_lock); + spin_lock_init(&chip->ibat_change_lock); + spin_lock_init(&chip->irq_lock); + INIT_WORK(&chip->vddtrim_work, qpnp_lbc_vddtrim_work_fn); + alarm_init(&chip->vddtrim_alarm, ALARM_REALTIME, vddtrim_callback); + INIT_DELAYED_WORK(&chip->collapsible_detection_work, + qpnp_lbc_collapsible_detection_work); + INIT_WORK(&chip->debug_board_work, qpnp_lbc_debug_board_work_fn); + /* Get all device-tree properties */ + rc = qpnp_charger_read_dt_props(chip); + if (rc) { + pr_err("Failed to read DT properties rc=%d\n", rc); + return rc; + } + + rc = qpnp_lbc_parse_resources(chip); + if (rc) { + pr_err("Unable to parse LBC resources rc=%d\n", rc); + goto fail_chg_enable; + } + + if (chip->cfg_use_external_charger) { + pr_warn("Disabling Linear Charger (e-external-charger = 1)\n"); + rc = qpnp_disable_lbc_charger(chip); + if (rc) + pr_err("Unable to disable charger rc=%d\n", rc); + return -ENODEV; + } + + chip->usb_psy_d.name = "usb"; + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + chip->usb_psy_d.properties = qpnp_lbc_usb_properties; + chip->usb_psy_d.num_properties = ARRAY_SIZE(qpnp_lbc_usb_properties); + chip->usb_psy_d.get_property = qpnp_lbc_usb_get_property; + chip->usb_psy_d.set_property = qpnp_lbc_usb_set_property; + chip->usb_psy_d.property_is_writeable = qpnp_lbc_usb_is_writeable; + + usb_psy_cfg.drv_data = chip; + usb_psy_cfg.num_supplicants = 0; + + chip->usb_psy = devm_power_supply_register(chip->dev, + &chip->usb_psy_d, &usb_psy_cfg); + if (IS_ERR(chip->usb_psy)) { + pr_err("Unable to register usb_psy rc = %ld\n", + PTR_ERR(chip->usb_psy)); + rc = PTR_ERR(chip->usb_psy); + goto fail_chg_enable; + } + + + chip->vbat_sns = iio_channel_get(&pdev->dev, "vbat_sns"); + if (IS_ERR(chip->vbat_sns)) { + if (PTR_ERR(chip->vbat_sns) != -EPROBE_DEFER) + pr_err("vbat_sns unavailable %ld\n", + PTR_ERR(chip->vbat_sns)); + rc = PTR_ERR(chip->vbat_sns); + chip->vbat_sns = NULL; + goto fail_chg_enable; + } + + chip->lr_mux1_batt_therm = iio_channel_get(&pdev->dev, "batt_therm"); + if (IS_ERR(chip->lr_mux1_batt_therm)) { + if (PTR_ERR(chip->lr_mux1_batt_therm) != -EPROBE_DEFER) + pr_err("lr_mux1_batt_therm unavailable %ld\n", + PTR_ERR(chip->lr_mux1_batt_therm)); + rc = PTR_ERR(chip->lr_mux1_batt_therm); + chip->lr_mux1_batt_therm = NULL; + goto fail_chg_enable; + } + + /* Initialize h/w */ + rc = qpnp_lbc_misc_init(chip); + if (rc) { + pr_err("unable to initialize LBC MISC rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_chg_init(chip); + if (rc) { + pr_err("unable to initialize LBC charger rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_bat_if_init(chip); + if (rc) { + pr_err("unable to initialize LBC BAT_IF rc=%d\n", rc); + return rc; + } + rc = qpnp_lbc_usb_path_init(chip); + if (rc) { + pr_err("unable to initialize LBC USB path rc=%d\n", rc); + return rc; + } + + if (chip->cfg_chgr_led_support) { + rc = qpnp_lbc_register_chgr_led(chip); + if (rc) { + pr_err("unable to register charger led rc=%d\n", rc); + return rc; + } + } + + if (chip->bat_if_base) { + chip->batt_present = qpnp_lbc_is_batt_present(chip); + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.properties = msm_batt_power_props; + chip->batt_psy_d.num_properties = + ARRAY_SIZE(msm_batt_power_props); + chip->batt_psy_d.get_property = qpnp_batt_power_get_property; + chip->batt_psy_d.set_property = qpnp_batt_power_set_property; + chip->batt_psy_d.external_power_changed = + qpnp_batt_external_power_changed; + chip->batt_psy_d.property_is_writeable = + qpnp_batt_property_is_writeable; + + 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("Unable to register LBC batt_psy rc = %ld\n", + PTR_ERR(chip->batt_psy)); + goto fail_chg_enable; + } + } + + if ((chip->cfg_cool_bat_decidegc || chip->cfg_warm_bat_decidegc) + && chip->bat_if_base && !chip->cfg_use_fake_battery) { + chip->adc_param.low_temp = chip->cfg_cool_bat_decidegc; + chip->adc_param.high_temp = chip->cfg_warm_bat_decidegc; + chip->adc_param.timer_interval = ADC_MEAS1_INTERVAL_1S; + chip->adc_param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE; + chip->adc_param.btm_ctx = chip; + chip->adc_param.threshold_notification = + qpnp_lbc_jeita_adc_notification; + chip->adc_param.channel = LR_MUX1_BATT_THERM; + + if (get_prop_batt_present(chip)) { + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->adc_param); + if (rc) { + pr_err("request ADC error rc=%d\n", rc); + goto unregister_batt; + } + } + } + + rc = qpnp_lbc_bat_if_configure_btc(chip); + if (rc) { + pr_err("Failed to configure btc rc=%d\n", rc); + goto unregister_batt; + } + + /* Get/Set charger's initial status */ + determine_initial_status(chip); + + rc = qpnp_lbc_request_irqs(chip); + if (rc) { + pr_err("unable to initialize LBC MISC rc=%d\n", rc); + goto unregister_batt; + } + + if (chip->cfg_charging_disabled && !get_prop_batt_present(chip)) + pr_info("Battery absent and charging disabled\n"); + + /* Configure initial alarm for VDD trim */ + if ((chip->supported_feature_flag & VDD_TRIM_SUPPORTED) && + qpnp_lbc_is_fastchg_on(chip)) { + kt = ns_to_ktime(TRIM_PERIOD_NS); + alarm_start_relative(&chip->vddtrim_alarm, kt); + } + + chip->debug_root = debugfs_create_dir("qpnp_lbc", NULL); + if (!chip->debug_root) + pr_err("Couldn't create debug dir\n"); + + if (chip->debug_root) { + struct dentry *ent; + + ent = debugfs_create_file("lbc_config", S_IFREG | 0444, + chip->debug_root, chip, + &qpnp_lbc_config_debugfs_ops); + if (!ent) + pr_err("Couldn't create lbc_config debug file\n"); + } + + pr_debug("Probe chg_dis=%d bpd=%d usb=%d batt_pres=%d batt_volt=%d soc=%d\n", + chip->cfg_charging_disabled, + chip->cfg_bpd_detection, + qpnp_lbc_is_usb_chg_plugged_in(chip), + get_prop_batt_present(chip), + get_prop_battery_voltage_now(chip), + get_prop_capacity(chip)); + + return 0; + +unregister_batt: + if (chip->bat_if_base) + power_supply_unregister(chip->batt_psy); +fail_chg_enable: + power_supply_unregister(chip->usb_psy); + dev_set_drvdata(&pdev->dev, NULL); + return rc; +} + +static int is_parallel_charger(struct platform_device *pdev) +{ + return of_property_read_bool(pdev->dev.of_node, + "qcom,parallel-charger"); +} + +static int qpnp_lbc_probe(struct platform_device *pdev) +{ + if (is_parallel_charger(pdev)) + return qpnp_lbc_parallel_probe(pdev); + else + return qpnp_lbc_main_probe(pdev); +} + + +static int qpnp_lbc_remove(struct platform_device *pdev) +{ + struct qpnp_lbc_chip *chip = dev_get_drvdata(&pdev->dev); + + if (chip->supported_feature_flag & VDD_TRIM_SUPPORTED) { + alarm_cancel(&chip->vddtrim_alarm); + cancel_work_sync(&chip->vddtrim_work); + } + cancel_work_sync(&chip->debug_board_work); + cancel_delayed_work_sync(&chip->collapsible_detection_work); + debugfs_remove_recursive(chip->debug_root); + if (chip->bat_if_base) + power_supply_unregister(chip->batt_psy); + power_supply_unregister(chip->usb_psy); + mutex_destroy(&chip->jeita_configure_lock); + mutex_destroy(&chip->chg_enable_lock); + dev_set_drvdata(&pdev->dev, NULL); + return 0; +} + +static const struct of_device_id qpnp_lbc_match_table[] = { + { .compatible = QPNP_CHARGER_DEV_NAME, }, + {} +}; + +static struct platform_driver qpnp_lbc_driver = { + .probe = qpnp_lbc_probe, + .remove = qpnp_lbc_remove, + .driver = { + .name = QPNP_CHARGER_DEV_NAME, + .of_match_table = qpnp_lbc_match_table, + }, +}; + +/* + * qpnp_lbc_init() - register platform driver for qpnp-chg + */ +static int __init qpnp_lbc_init(void) +{ + return platform_driver_register(&qpnp_lbc_driver); +} +module_init(qpnp_lbc_init); + +static void __exit qpnp_lbc_exit(void) +{ + platform_driver_unregister(&qpnp_lbc_driver); +} +module_exit(qpnp_lbc_exit); + +MODULE_DESCRIPTION("QPNP Linear charger driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" QPNP_CHARGER_DEV_NAME); diff --git a/drivers/power/supply/qcom/qpnp-vm-bms.c b/drivers/power/supply/qcom/qpnp-vm-bms.c new file mode 100644 index 000000000000..2b30c05fd9cd --- /dev/null +++ b/drivers/power/supply/qcom/qpnp-vm-bms.c @@ -0,0 +1,4654 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2014-2016, 2018-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "VBMS: %s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _BMS_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define BMS_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _BMS_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Config / Data registers */ +#define REVISION1_REG 0x0 +#define STATUS1_REG 0x8 +#define FSM_STATE_MASK BMS_MASK(5, 3) +#define FSM_STATE_SHIFT 3 + +#define STATUS2_REG 0x9 +#define FIFO_CNT_SD_MASK BMS_MASK(7, 4) +#define FIFO_CNT_SD_SHIFT 4 + +#define MODE_CTL_REG 0x40 +#define FORCE_S3_MODE BIT(0) +#define ENABLE_S3_MODE BIT(1) +#define FORCE_S2_MODE BIT(2) +#define ENABLE_S2_MODE BIT(3) +#define S2_MODE_MASK BMS_MASK(3, 2) +#define S3_MODE_MASK BMS_MASK(1, 0) + +#define DATA_CTL1_REG 0x42 +#define MASTER_HOLD_BIT BIT(0) + +#define DATA_CTL2_REG 0x43 +#define FIFO_CNT_SD_CLR_BIT BIT(2) +#define ACC_DATA_SD_CLR_BIT BIT(1) +#define ACC_CNT_SD_CLR_BIT BIT(0) + +#define S3_OCV_TOL_CTL_REG 0x44 + +#define EN_CTL_REG 0x46 +#define BMS_EN_BIT BIT(7) + +#define FIFO_LENGTH_REG 0x47 +#define S1_FIFO_LENGTH_MASK BMS_MASK(3, 0) +#define S2_FIFO_LENGTH_MASK BMS_MASK(7, 4) +#define S2_FIFO_LENGTH_SHIFT 4 + +#define S1_SAMPLE_INTVL_REG 0x55 +#define S2_SAMPLE_INTVL_REG 0x56 +#define S3_SAMPLE_INTVL_REG 0x57 + +#define S1_ACC_CNT_REG 0x5E +#define S2_ACC_CNT_REG 0x5F +#define ACC_CNT_MASK BMS_MASK(2, 0) + +#define ACC_DATA0_SD_REG 0x63 +#define ACC_CNT_SD_REG 0x67 +#define OCV_DATA0_REG 0x6A +#define FIFO_0_LSB_REG 0xC0 + +#define BMS_SOC_REG 0xB0 +#define BMS_OCV_REG 0xB1 /* B1 & B2 */ +#define SOC_STORAGE_MASK 0xFE + +#define CHARGE_INCREASE_STORAGE 0xB3 +#define CHARGE_CYCLE_STORAGE_LSB 0xB4 /* B4 & B5 */ + +#define SEC_ACCESS 0xD0 + +#define QPNP_CHARGER_PRESENT BIT(7) + +/* Constants */ +#define OCV_TOL_LSB_UV 300 +#define MAX_OCV_TOL_THRESHOLD (OCV_TOL_LSB_UV * 0xFF) +#define MAX_SAMPLE_COUNT 256 +#define MAX_SAMPLE_INTERVAL 2550 +#define BMS_READ_TIMEOUT 500 +#define BMS_DEFAULT_TEMP 250 +#define OCV_INVALID 0xFFFF +#define SOC_INVALID 0xFF +#define OCV_UNINITIALIZED 0xFFFF +#define VBATT_ERROR_MARGIN 20000 +#define CV_DROP_MARGIN 10000 +#define MIN_OCV_UV 2000000 +#define TIME_PER_PERCENT_UUC 60 +#define IAVG_SAMPLES 16 +#define MIN_SOC_UUC 3 + +#define QPNP_VM_BMS_DEV_NAME "qcom,qpnp-vm-bms" + +#define DEBUG_BATT_ID_LOW 6500 +#define DEBUG_BATT_ID_HIGH 8500 + +#define VADC1_LC_USR_BASE 0x3100 +#define INT_TEST_VAL_OFFSET 0xE1 +#define QPNP_VBAT_COEFF_1 3000 +#define QPNP_VBAT_COEFF_2 45810000 +#define QPNP_VBAT_COEFF_3 100000 +#define QPNP_VBAT_COEFF_4 3500 +#define QPNP_VBAT_COEFF_5 80000000 +#define QPNP_VBAT_COEFF_6 4400 +#define QPNP_VBAT_COEFF_7 32200000 +#define QPNP_VBAT_COEFF_8 3880 +#define QPNP_VBAT_COEFF_9 5770 +#define QPNP_VBAT_COEFF_10 3660 +#define QPNP_VBAT_COEFF_11 5320 +#define QPNP_VBAT_COEFF_12 8060000 +#define QPNP_VBAT_COEFF_13 102640000 +#define QPNP_VBAT_COEFF_14 22220000 +#define QPNP_VBAT_COEFF_15 83060000 +#define QPNP_VBAT_COEFF_16 2810 +#define QPNP_VBAT_COEFF_17 5260 +#define QPNP_VBAT_COEFF_18 8027 +#define QPNP_VBAT_COEFF_19 2347 +#define QPNP_VBAT_COEFF_20 6043 +#define QPNP_VBAT_COEFF_21 1914 +#define QPNP_VBAT_OFFSET_SMIC 9446 +#define QPNP_VBAT_OFFSET_GF 9441 +#define QPNP_OCV_OFFSET_SMIC 4596 +#define QPNP_OCV_OFFSET_GF 5896 +#define QPNP_VBAT_COEFF_22 6800 +#define QPNP_VBAT_COEFF_23 3500 +#define QPNP_VBAT_COEFF_24 4360 +#define QPNP_VBAT_COEFF_25 8060 +#define QPNP_VBAT_COEFF_26 7895 +#define QPNP_VBAT_COEFF_27 5658 +#define QPNP_VBAT_COEFF_28 5760 +#define QPNP_VBAT_COEFF_29 7900 +#define QPNP_VBAT_COEFF_30 5660 +#define QPNP_VBAT_COEFF_31 3620 +#define QPNP_VBAT_COEFF_32 1230 +#define QPNP_VBAT_COEFF_33 5760 +#define QPNP_VBAT_COEFF_34 4080 +#define QPNP_VBAT_COEFF_35 7000 +#define QPNP_VBAT_COEFF_36 3040 +#define QPNP_VBAT_COEFF_37 3850 +#define QPNP_VBAT_COEFF_38 5000 +#define QPNP_VBAT_COEFF_39 2610 +#define QPNP_VBAT_COEFF_40 4190 +#define QPNP_VBAT_COEFF_41 5800 +#define QPNP_VBAT_COEFF_42 2620 +#define QPNP_VBAT_COEFF_43 4030 +#define QPNP_VBAT_COEFF_44 3230 +#define QPNP_VBAT_COEFF_45 3450 +#define QPNP_VBAT_COEFF_46 2120 +#define QPNP_VBAT_COEFF_47 3560 +#define QPNP_VBAT_COEFF_48 2190 +#define QPNP_VBAT_COEFF_49 4180 +#define QPNP_VBAT_COEFF_50 27800000 +#define QPNP_VBAT_COEFF_51 5110 +#define QPNP_VBAT_COEFF_52 34444000 + +#define COMP_ID_GF 0 +#define COMP_ID_SMIC 1 +#define COMP_ID_TSMC 2 +#define COMP_ID_NUM 3 + +/* indicates the state of BMS */ +enum { + IDLE_STATE, + S1_STATE, + S2_STATE, + S3_STATE, + S7_STATE, +}; + +enum { + WRKARND_PON_OCV_COMP = BIT(0), +}; + +struct bms_irq { + int irq; + unsigned long disabled; +}; + +struct bms_wakeup_source { + struct wakeup_source *source; + unsigned long disabled; +}; + +struct temp_curr_comp_map { + int temp_decideg; + int current_ma; +}; + +struct bms_dt_cfg { + bool cfg_report_charger_eoc; + bool cfg_force_bms_active_on_charger; + bool cfg_force_s3_on_suspend; + bool cfg_ignore_shutdown_soc; + bool cfg_use_voltage_soc; + int cfg_v_cutoff_uv; + int cfg_max_voltage_uv; + int cfg_r_conn_mohm; + int cfg_shutdown_soc_valid_limit; + int cfg_low_soc_calc_threshold; + int cfg_low_soc_calculate_soc_ms; + int cfg_low_voltage_threshold; + int cfg_low_voltage_calculate_soc_ms; + int cfg_low_soc_fifo_length; + int cfg_calculate_soc_ms; + int cfg_s1_sample_interval_ms; + int cfg_s2_sample_interval_ms; + int cfg_s1_sample_count; + int cfg_s2_sample_count; + int cfg_s1_fifo_length; + int cfg_s2_fifo_length; + int cfg_disable_bms; + int cfg_s3_ocv_tol_uv; + int cfg_soc_resume_limit; + int cfg_low_temp_threshold; + int cfg_ibat_avg_samples; + int cfg_battery_aging_comp; + bool cfg_use_reported_soc; +}; + +struct qpnp_bms_chip { + struct device *dev; + struct platform_device *pdev; + struct regmap *regmap; + dev_t dev_no; + u16 base; + u8 revision[2]; + u32 batt_pres_addr; + u32 chg_pres_addr; + + /* status variables */ + u8 current_fsm_state; + bool last_soc_invalid; + bool warm_reset; + bool bms_psy_registered; + bool battery_full; + bool bms_dev_open; + bool data_ready; + bool apply_suspend_config; + bool in_cv_state; + bool low_soc_fifo_set; + int battery_status; + int calculated_soc; + int current_now; + int prev_current_now; + int prev_voltage_based_soc; + int calculate_soc_ms; + int voltage_soc_uv; + int battery_present; + int last_soc; + int last_soc_unbound; + int last_soc_change_sec; + int charge_start_tm_sec; + int catch_up_time_sec; + int delta_time_s; + int uuc_delta_time_s; + int ocv_at_100; + int last_ocv_uv; + int s2_fifo_length; + int last_acc; + int hi_power_state; + unsigned int vadc_v0625; + unsigned int vadc_v1250; + unsigned long tm_sec; + unsigned long workaround_flag; + unsigned long uuc_tm_sec; + u32 seq_num; + u8 shutdown_soc; + bool shutdown_soc_invalid; + u16 last_ocv_raw; + u32 shutdown_ocv; + bool suspend_data_valid; + int iavg_num_samples; + unsigned int iavg_index; + int iavg_samples_ma[IAVG_SAMPLES]; + int iavg_ma; + int prev_soc_uuc; + int eoc_reported; + u8 charge_increase; + u16 charge_cycles; + unsigned int start_soc; + unsigned int end_soc; + unsigned int chg_start_soc; + + struct bms_battery_data *batt_data; + struct bms_dt_cfg dt; + + struct dentry *debug_root; + struct bms_wakeup_source vbms_lv_wake_source; + struct bms_wakeup_source vbms_cv_wake_source; + struct bms_wakeup_source vbms_soc_wake_source; + wait_queue_head_t bms_wait_q; + struct delayed_work monitor_soc_work; + struct mutex bms_data_mutex; + struct mutex bms_device_mutex; + struct mutex last_soc_mutex; + struct mutex state_change_mutex; + struct class *bms_class; + struct device *bms_device; + struct cdev bms_cdev; + struct qpnp_vm_bms_data bms_data; + struct iio_channel *ref_625mv; + struct iio_channel *ref_125v; + struct iio_channel *vbat_sns; + struct iio_channel *lr_mux1_batt_therm; + struct iio_channel *die_temp; + struct iio_channel *lr_mux2_batt_id; + struct qpnp_adc_tm_chip *adc_tm_dev; + struct pmic_revid_data *revid_data; + struct qpnp_adc_tm_btm_param vbat_monitor_params; + struct bms_irq fifo_update_done_irq; + struct bms_irq fsm_state_change_irq; + struct power_supply_desc bms_psy_d; + struct power_supply *bms_psy; + struct power_supply *batt_psy; + struct power_supply *usb_psy; + struct notifier_block nb; + bool reported_soc_in_use; + bool charger_removed_since_full; + bool charger_reinserted; + bool reported_soc_high_current; + int reported_soc; + int reported_soc_change_sec; + int reported_soc_delta; + int batt_id_ohm; + int fab_id; +}; + +static struct qpnp_bms_chip *the_chip; + +static struct temp_curr_comp_map temp_curr_comp_lut[] = { + {-300, 15}, + {250, 17}, + {850, 28}, +}; + +static void disable_bms_irq(struct bms_irq *irq) +{ + if (!__test_and_set_bit(0, &irq->disabled)) { + disable_irq(irq->irq); + pr_debug("disabled irq %d\n", irq->irq); + } +} + +static void enable_bms_irq(struct bms_irq *irq) +{ + if (__test_and_clear_bit(0, &irq->disabled)) { + enable_irq(irq->irq); + pr_debug("enable irq %d\n", irq->irq); + } +} + +static void bms_stay_awake(struct bms_wakeup_source *source) +{ + if (__test_and_clear_bit(0, &source->disabled)) { + __pm_stay_awake(source->source); + pr_debug("enabled source %s\n", source->source->name); + } +} + +static void bms_relax(struct bms_wakeup_source *source) +{ + if (!__test_and_set_bit(0, &source->disabled)) { + __pm_relax(source->source); + pr_debug("disabled source %s\n", source->source->name); + } +} + +static bool bms_wake_active(struct bms_wakeup_source *source) +{ + return !source->disabled; +} + +static int bound_soc(int soc) +{ + soc = max(0, soc); + soc = min(100, soc); + + return soc; +} + +static char *qpnp_vm_bms_supplicants[] = { + "battery", +}; + +static int qpnp_read_wrapper(struct qpnp_bms_chip *chip, u8 *val, + u16 base, int count) +{ + int rc; + + rc = regmap_bulk_read(chip->regmap, base, val, count); + if (rc) + pr_err("Regmap read failed rc=%d\n", rc); + + return rc; +} + +static int qpnp_write_wrapper(struct qpnp_bms_chip *chip, u8 *val, + u16 base, int count) +{ + int rc; + + rc = regmap_bulk_write(chip->regmap, base, val, count); + if (rc) + pr_err("Regmap write failed rc=%d\n", rc); + + return rc; +} + +static int qpnp_masked_write_base(struct qpnp_bms_chip *chip, u16 addr, + u8 mask, u8 val) +{ + int rc; + u8 reg; + + rc = qpnp_read_wrapper(chip, ®, addr, 1); + if (rc) { + pr_err("read failed addr = %03X, rc = %d\n", addr, rc); + return rc; + } + reg &= ~mask; + reg |= val & mask; + rc = qpnp_write_wrapper(chip, ®, addr, 1); + if (rc) + pr_err("write failed addr = %03X, val = %02x, mask = %02x, reg = %02x, rc = %d\n", + addr, val, mask, reg, rc); + + return rc; +} + +static int qpnp_secure_write_wrapper(struct qpnp_bms_chip *chip, u8 *val, + u16 base) +{ + int rc; + u8 reg; + + reg = 0xA5; + rc = qpnp_write_wrapper(chip, ®, chip->base + SEC_ACCESS, 1); + if (rc) { + pr_err("Error %d writing 0xA5 to 0x%x reg\n", + rc, SEC_ACCESS); + return rc; + } + rc = qpnp_write_wrapper(chip, val, base, 1); + if (rc) + pr_err("Error %d writing %d to 0x%x reg\n", rc, *val, base); + + return rc; +} + +static int backup_ocv_soc(struct qpnp_bms_chip *chip, int ocv_uv, int soc) +{ + int rc; + u16 ocv_mv = ocv_uv / 1000; + + rc = qpnp_write_wrapper(chip, (u8 *)&ocv_mv, + chip->base + BMS_OCV_REG, 2); + if (rc) + pr_err("Unable to backup OCV rc=%d\n", rc); + + rc = qpnp_masked_write_base(chip, chip->base + BMS_SOC_REG, + SOC_STORAGE_MASK, (soc + 1) << 1); + if (rc) + pr_err("Unable to backup SOC rc=%d\n", rc); + + pr_debug("ocv_mv=%d soc=%d\n", ocv_mv, soc); + + 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; +} + +static int calculate_delta_time(unsigned long *time_stamp, int *delta_time_s) +{ + unsigned long now_tm_sec = 0; + + /* default to delta time = 0 if anything fails */ + *delta_time_s = 0; + + if (get_current_time(&now_tm_sec)) { + pr_err("RTC read failed\n"); + return 0; + } + + *delta_time_s = (now_tm_sec - *time_stamp); + + /* remember this time */ + *time_stamp = now_tm_sec; + return 0; +} + +static bool is_debug_batt_id(struct qpnp_bms_chip *chip) +{ + if (is_between(DEBUG_BATT_ID_LOW, DEBUG_BATT_ID_HIGH, + chip->batt_id_ohm)) + return true; + + return false; +} + +static int bms_notifier_cb(struct notifier_block *nb, + unsigned long event, void *data) +{ + union power_supply_propval ret = {0,}; + struct power_supply *psy = data; + struct qpnp_bms_chip *chip = container_of(nb, struct qpnp_bms_chip, nb); + + if (event != PSY_EVENT_PROP_CHANGED) + return NOTIFY_OK; + + if ((strcmp(psy->desc->name, "battery") == 0)) { + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + if (is_debug_batt_id(chip)) { + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_DEBUG_BATTERY, &ret); + if (!ret.intval) { + ret.intval = 1; + power_supply_set_property( + chip->batt_psy, + POWER_SUPPLY_PROP_DEBUG_BATTERY, + &ret); + } + } + } + } + + return NOTIFY_OK; +} +static bool is_charger_present(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->usb_psy == NULL) + chip->usb_psy = power_supply_get_by_name("usb"); + if (chip->usb_psy) { + power_supply_get_property(chip->usb_psy, + POWER_SUPPLY_PROP_PRESENT, &ret); + return ret.intval; + } + + return false; +} + +static bool is_battery_charging(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + /* if battery has been registered, use the type property */ + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &ret); + return ret.intval != POWER_SUPPLY_CHARGE_TYPE_NONE; + } + + /* Default to false if the battery power supply is not registered. */ + pr_debug("battery power supply is not registered\n"); + return false; +} + +#define BAT_PRES_BIT BIT(7) +static bool is_battery_present(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + int rc; + u8 batt_pres; + + /* first try to use the batt_pres register if given */ + if (chip->batt_pres_addr) { + rc = qpnp_read_wrapper(chip, &batt_pres, + chip->batt_pres_addr, 1); + if (!rc && (batt_pres & BAT_PRES_BIT)) + return true; + else + return false; + } + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + /* if battery has been registered, use the present property */ + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_PRESENT, &ret); + return ret.intval; + } + + /* Default to false if the battery power supply is not registered. */ + pr_debug("battery power supply is not registered\n"); + return false; +} + +#define BAT_REMOVED_OFFMODE_BIT BIT(6) +static bool is_battery_replaced_in_offmode(struct qpnp_bms_chip *chip) +{ + u8 batt_pres; + int rc; + + if (chip->batt_pres_addr) { + rc = qpnp_read_wrapper(chip, &batt_pres, + chip->batt_pres_addr, 1); + pr_debug("offmode removed: %02x\n", batt_pres); + if (!rc && (batt_pres & BAT_REMOVED_OFFMODE_BIT)) + return true; + } + + return false; +} + +static bool is_battery_taper_charging(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + + if (chip->batt_psy) { + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &ret); + return ret.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER; + } + + return false; +} + +static int master_hold_control(struct qpnp_bms_chip *chip, bool enable) +{ + u8 reg = 0; + int rc; + + reg = enable ? MASTER_HOLD_BIT : 0; + + rc = qpnp_secure_write_wrapper(chip, ®, + chip->base + DATA_CTL1_REG); + if (rc) + pr_err("Unable to write reg=%x rc=%d\n", DATA_CTL1_REG, rc); + + return rc; +} + +static int force_fsm_state(struct qpnp_bms_chip *chip, u8 state) +{ + int rc; + u8 mode_ctl = 0; + + switch (state) { + case S2_STATE: + mode_ctl = (FORCE_S2_MODE | ENABLE_S2_MODE); + break; + case S3_STATE: + mode_ctl = (FORCE_S3_MODE | ENABLE_S3_MODE); + break; + default: + pr_debug("Invalid state %d\n", state); + return -EINVAL; + } + + rc = qpnp_secure_write_wrapper(chip, &mode_ctl, + chip->base + MODE_CTL_REG); + if (rc) { + pr_err("Unable to write reg=%x rc=%d\n", MODE_CTL_REG, rc); + return rc; + } + /* delay for the FSM state to take affect in hardware */ + usleep_range(500, 600); + + pr_debug("force_mode=%d mode_cntl_reg=%x\n", state, mode_ctl); + + return 0; +} + +static int get_sample_interval(struct qpnp_bms_chip *chip, + u8 fsm_state, u32 *interval) +{ + int rc; + u8 val = 0, reg; + + *interval = 0; + + switch (fsm_state) { + case S1_STATE: + reg = S1_SAMPLE_INTVL_REG; + break; + case S2_STATE: + reg = S2_SAMPLE_INTVL_REG; + break; + case S3_STATE: + reg = S3_SAMPLE_INTVL_REG; + break; + default: + pr_err("Invalid state %d\n", fsm_state); + return -EINVAL; + } + + rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); + if (rc) { + pr_err("Failed to get state(%d) sample_interval, rc=%d\n", + fsm_state, rc); + return rc; + } + + *interval = val * 10; + + return 0; +} + +static int get_sample_count(struct qpnp_bms_chip *chip, + u8 fsm_state, u32 *count) +{ + int rc; + u8 val = 0, reg; + + *count = 0; + + switch (fsm_state) { + case S1_STATE: + reg = S1_ACC_CNT_REG; + break; + case S2_STATE: + reg = S2_ACC_CNT_REG; + break; + default: + pr_err("Invalid state %d\n", fsm_state); + return -EINVAL; + } + + rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); + if (rc) { + pr_err("Failed to get state(%d) sample_count, rc=%d\n", + fsm_state, rc); + return rc; + } + val &= ACC_CNT_MASK; + + *count = val ? (1 << (val + 1)) : 1; + + return 0; +} + +static int get_fifo_length(struct qpnp_bms_chip *chip, + u8 fsm_state, u32 *fifo_length) +{ + int rc; + u8 val = 0, reg, mask = 0, shift = 0; + + *fifo_length = 0; + + switch (fsm_state) { + case S1_STATE: + reg = FIFO_LENGTH_REG; + mask = S1_FIFO_LENGTH_MASK; + shift = 0; + break; + case S2_STATE: + reg = FIFO_LENGTH_REG; + mask = S2_FIFO_LENGTH_MASK; + shift = S2_FIFO_LENGTH_SHIFT; + break; + default: + pr_err("Invalid state %d\n", fsm_state); + return -EINVAL; + } + + rc = qpnp_read_wrapper(chip, &val, chip->base + reg, 1); + if (rc) { + pr_err("Failed to get state(%d) fifo_length, rc=%d\n", + fsm_state, rc); + return rc; + } + + val &= mask; + val >>= shift; + + *fifo_length = val; + + return 0; +} + +static int set_fifo_length(struct qpnp_bms_chip *chip, + u8 fsm_state, u32 fifo_length) +{ + int rc; + u8 reg, mask = 0, shift = 0; + + /* fifo_length of 1 is not supported due to a hardware issue */ + if ((fifo_length <= 1) || (fifo_length > MAX_FIFO_REGS)) { + pr_err("Invalid FIFO length = %d\n", fifo_length); + return -EINVAL; + } + + switch (fsm_state) { + case S1_STATE: + reg = FIFO_LENGTH_REG; + mask = S1_FIFO_LENGTH_MASK; + shift = 0; + break; + case S2_STATE: + reg = FIFO_LENGTH_REG; + mask = S2_FIFO_LENGTH_MASK; + shift = S2_FIFO_LENGTH_SHIFT; + break; + default: + pr_err("Invalid state %d\n", fsm_state); + return -EINVAL; + } + + rc = master_hold_control(chip, true); + if (rc) + pr_err("Unable to apply master_hold rc=%d\n", rc); + + rc = qpnp_masked_write_base(chip, chip->base + reg, mask, + fifo_length << shift); + if (rc) + pr_err("Unable to set fifo length rc=%d\n", rc); + + rc = master_hold_control(chip, false); + if (rc) + pr_err("Unable to apply master_hold rc=%d\n", rc); + + return rc; +} + +static int get_fsm_state(struct qpnp_bms_chip *chip, u8 *state) +{ + int rc; + + /* + * To read the STATUS1 register, write a value(any) to this register, + * wait for 10ms and then read the register. + */ + *state = 0; + rc = qpnp_write_wrapper(chip, state, chip->base + STATUS1_REG, 1); + if (rc) { + pr_err("Unable to write STATUS1_REG rc=%d\n", rc); + return rc; + } + usleep_range(10000, 11000); + + /* read the current FSM state */ + rc = qpnp_read_wrapper(chip, state, chip->base + STATUS1_REG, 1); + if (rc) { + pr_err("Unable to read STATUS1_REG rc=%d\n", rc); + return rc; + } + *state = (*state & FSM_STATE_MASK) >> FSM_STATE_SHIFT; + + return rc; +} + +static int update_fsm_state(struct qpnp_bms_chip *chip) +{ + u8 state = 0; + int rc; + + mutex_lock(&chip->state_change_mutex); + rc = get_fsm_state(chip, &state); + if (rc) { + pr_err("Unable to get fsm_state rc=%d\n", rc); + goto fail_fsm; + } + + chip->current_fsm_state = state; + +fail_fsm: + mutex_unlock(&chip->state_change_mutex); + return rc; +} + +static int backup_charge_cycle(struct qpnp_bms_chip *chip) +{ + int rc = 0; + + if (chip->charge_increase >= 0) { + rc = qpnp_write_wrapper(chip, &chip->charge_increase, + chip->base + CHARGE_INCREASE_STORAGE, 1); + if (rc) + pr_err("Unable to backup charge_increase rc=%d\n", rc); + } + + if (chip->charge_cycles >= 0) { + rc = qpnp_write_wrapper(chip, (u8 *)&chip->charge_cycles, + chip->base + CHARGE_CYCLE_STORAGE_LSB, 2); + if (rc) + pr_err("Unable to backup charge_cycles rc=%d\n", rc); + } + + pr_debug("%s storing charge_increase=%u charge_cycle=%u\n", + rc ? "Unable to" : "Successfully", + chip->charge_increase, chip->charge_cycles); + + return rc; +} + +static int read_chgcycle_data_from_backup(struct qpnp_bms_chip *chip) +{ + int rc; + uint16_t temp_u16 = 0; + u8 temp_u8 = 0; + + rc = qpnp_read_wrapper(chip, &temp_u8, + chip->base + CHARGE_INCREASE_STORAGE, 1); + if (rc) { + pr_err("Unable to read charge_increase rc=%d\n", rc); + return rc; + } + + rc = qpnp_read_wrapper(chip, (u8 *)&temp_u16, + chip->base + CHARGE_CYCLE_STORAGE_LSB, 2); + if (rc) { + pr_err("Unable to read charge_cycle rc=%d\n", rc); + return rc; + } + + if ((temp_u8 == 0xFF) || (temp_u16 == 0xFFFF)) { + chip->charge_cycles = 0; + chip->charge_increase = 0; + pr_info("rejecting aging data charge_increase=%u charge_cycle=%u\n", + temp_u8, temp_u16); + rc = backup_charge_cycle(chip); + if (rc) + pr_err("Unable to reset charge cycles rc=%d\n", rc); + } else { + chip->charge_increase = temp_u8; + chip->charge_cycles = temp_u16; + } + + pr_debug("charge_increase=%u charge_cycle=%u\n", + chip->charge_increase, chip->charge_cycles); + return rc; +} + +static int calculate_uuc_iavg(struct qpnp_bms_chip *chip) +{ + int i; + int iavg_ma = chip->current_now / 1000; + + /* only continue if ibat has changed */ + if (chip->current_now == chip->prev_current_now) + goto ibat_unchanged; + else + chip->prev_current_now = chip->current_now; + + chip->iavg_samples_ma[chip->iavg_index] = iavg_ma; + chip->iavg_index = (chip->iavg_index + 1) % + chip->dt.cfg_ibat_avg_samples; + chip->iavg_num_samples++; + if (chip->iavg_num_samples >= chip->dt.cfg_ibat_avg_samples) + chip->iavg_num_samples = chip->dt.cfg_ibat_avg_samples; + + if (chip->iavg_num_samples) { + iavg_ma = 0; + /* maintain a 16 sample average of ibat */ + for (i = 0; i < chip->iavg_num_samples; i++) { + pr_debug("iavg_samples_ma[%d] = %d\n", i, + chip->iavg_samples_ma[i]); + iavg_ma += chip->iavg_samples_ma[i]; + } + + chip->iavg_ma = DIV_ROUND_CLOSEST(iavg_ma, + chip->iavg_num_samples); + } + +ibat_unchanged: + pr_debug("current_now_ma=%d averaged_iavg_ma=%d\n", + chip->current_now / 1000, chip->iavg_ma); + + return chip->iavg_ma; +} + +static int adjust_uuc(struct qpnp_bms_chip *chip, int soc_uuc) +{ + int max_percent_change; + + calculate_delta_time(&chip->uuc_tm_sec, &chip->uuc_delta_time_s); + + /* make sure that the UUC changes 1% at a time */ + max_percent_change = max(chip->uuc_delta_time_s + / TIME_PER_PERCENT_UUC, 1); + + if (chip->prev_soc_uuc == -EINVAL) { + /* start with a minimum UUC if the initial UUC is high */ + if (soc_uuc > MIN_SOC_UUC) + chip->prev_soc_uuc = MIN_SOC_UUC; + else + chip->prev_soc_uuc = soc_uuc; + } else { + if (abs(chip->prev_soc_uuc - soc_uuc) <= max_percent_change) + chip->prev_soc_uuc = soc_uuc; + else if (soc_uuc > chip->prev_soc_uuc) + chip->prev_soc_uuc += max_percent_change; + else + chip->prev_soc_uuc -= max_percent_change; + } + + pr_debug("soc_uuc=%d new_soc_uuc=%d\n", soc_uuc, chip->prev_soc_uuc); + + return chip->prev_soc_uuc; +} + +static int lookup_soc_ocv(struct qpnp_bms_chip *chip, int ocv_uv, int batt_temp) +{ + int soc_ocv = 0, soc_cutoff = 0, soc_final = 0; + int fcc, acc, soc_uuc = 0, soc_acc = 0, iavg_ma = 0; + + soc_ocv = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, + batt_temp, ocv_uv / 1000); + soc_cutoff = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, + batt_temp, chip->dt.cfg_v_cutoff_uv / 1000); + + soc_final = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_cutoff), + (100 - soc_cutoff)); + + if (chip->batt_data->ibat_acc_lut) { + /* Apply ACC logic only if we discharging */ + if (chip->current_now > 0) { + + /* + * IBAT averaging is disabled at low temp. + * allowing the SOC to catcup quickly. + */ + if (batt_temp > chip->dt.cfg_low_temp_threshold) + iavg_ma = calculate_uuc_iavg(chip); + else + iavg_ma = chip->current_now / 1000; + + fcc = interpolate_fcc(chip->batt_data->fcc_temp_lut, + batt_temp); + acc = interpolate_acc(chip->batt_data->ibat_acc_lut, + batt_temp, iavg_ma); + if (acc <= 0) { + if (chip->last_acc) + acc = chip->last_acc; + else + acc = fcc; + } + soc_uuc = ((fcc - acc) * 100) / fcc; + + if (batt_temp > chip->dt.cfg_low_temp_threshold) + soc_uuc = adjust_uuc(chip, soc_uuc); + + soc_acc = DIV_ROUND_CLOSEST(100 * (soc_ocv - soc_uuc), + (100 - soc_uuc)); + + pr_debug("fcc=%d acc=%d soc_final=%d soc_uuc=%d soc_acc=%d current_now=%d iavg_ma=%d\n", + fcc, acc, soc_final, soc_uuc, + soc_acc, chip->current_now / 1000, iavg_ma); + + soc_final = soc_acc; + chip->last_acc = acc; + } else { + /* charging - reset all the counters */ + chip->last_acc = 0; + chip->iavg_num_samples = 0; + chip->iavg_index = 0; + chip->iavg_ma = 0; + chip->prev_current_now = 0; + chip->prev_soc_uuc = -EINVAL; + } + } + + soc_final = bound_soc(soc_final); + + pr_debug("soc_final=%d soc_ocv=%d soc_cutoff=%d ocv_uv=%u batt_temp=%d\n", + soc_final, soc_ocv, soc_cutoff, ocv_uv, batt_temp); + + return soc_final; +} + +#define V_PER_BIT_MUL_FACTOR 97656 +#define V_PER_BIT_DIV_FACTOR 1000 +#define VADC_INTRINSIC_OFFSET 0x6000 +static int vadc_reading_to_uv(int reading, bool vadc_bms) +{ + int64_t value; + + if (!vadc_bms) { + /* + * All the BMS H/W VADC values are pre-compensated + * for VADC_INTRINSIC_OFFSET, subtract this offset + * only if this reading is not obtained from BMS + */ + + if (reading <= VADC_INTRINSIC_OFFSET) + return 0; + + reading -= VADC_INTRINSIC_OFFSET; + } + + value = (reading * V_PER_BIT_MUL_FACTOR); + + return div_u64(value, (u32)V_PER_BIT_DIV_FACTOR); +} + +static int get_calculation_delay_ms(struct qpnp_bms_chip *chip) +{ + if (bms_wake_active(&chip->vbms_lv_wake_source)) + return chip->dt.cfg_low_voltage_calculate_soc_ms; + if (chip->calculated_soc < chip->dt.cfg_low_soc_calc_threshold) + return chip->dt.cfg_low_soc_calculate_soc_ms; + else + return chip->dt.cfg_calculate_soc_ms; +} + +#define VADC_CALIB_UV 625000 +#define VBATT_MUL_FACTOR 3 +static int adjust_vbatt_reading(struct qpnp_bms_chip *chip, int reading_uv) +{ + s64 numerator, denominator; + + if (reading_uv == 0) + return 0; + + /* don't adjust if not calibrated */ + if (chip->vadc_v0625 == 0 || chip->vadc_v1250 == 0) { + pr_debug("No cal yet return %d\n", + VBATT_MUL_FACTOR * reading_uv); + return VBATT_MUL_FACTOR * reading_uv; + } + + numerator = ((s64)reading_uv - chip->vadc_v0625) * VADC_CALIB_UV; + denominator = (s64)chip->vadc_v1250 - chip->vadc_v0625; + + if (denominator == 0) + return reading_uv * VBATT_MUL_FACTOR; + + return (VADC_CALIB_UV + div_s64(numerator, denominator)) + * VBATT_MUL_FACTOR; +} + +static int calib_vadc(struct qpnp_bms_chip *chip) +{ + int rc, raw_0625, raw_1250; + + rc = iio_read_channel_processed(chip->ref_625mv, &raw_0625); + if (rc < 0) { + pr_debug("raw_0625 channel read failed with rc = %d\n", rc); + return rc; + } + + rc = iio_read_channel_processed(chip->ref_125v, &raw_1250); + if (rc < 0) { + pr_debug("raw_1250 channel read failed with rc = %d\n", rc); + return rc; + } + + chip->vadc_v0625 = raw_0625; + chip->vadc_v1250 = raw_1250; + + pr_debug("vadc calib: 0625=%d raw (%d uv), 1250=%d raw (%d uv)\n", + raw_0625, chip->vadc_v0625, raw_1250, chip->vadc_v1250); + + return 0; +} + +static int32_t get_ocv_comp(int64_t *result, + struct qpnp_bms_chip *chip, int64_t die_temp) +{ + int64_t temp_var = 0, offset = 0; + int64_t old = *result; + int version; + + version = qpnp_adc_get_revid_version(chip->dev); + if (version == -EINVAL) + return 0; + switch (version) { + case QPNP_REV_ID_8916_1_0: + switch (chip->fab_id) { + case COMP_ID_SMIC: + if (die_temp < 25000) + temp_var = QPNP_VBAT_COEFF_26; + else + temp_var = QPNP_VBAT_COEFF_27; + temp_var = (die_temp - 25000) * temp_var; + break; + default: + case COMP_ID_GF: + offset = QPNP_OCV_OFFSET_GF; + if (die_temp < 25000) + temp_var = QPNP_VBAT_COEFF_26; + else + temp_var = QPNP_VBAT_COEFF_27; + temp_var = (die_temp - 25000) * temp_var; + break; + } + break; + case QPNP_REV_ID_8916_1_1: + switch (chip->fab_id) { + /* FAB_ID is zero */ + case COMP_ID_GF: + if (die_temp < 25000) + temp_var = QPNP_VBAT_COEFF_29; + else + temp_var = QPNP_VBAT_COEFF_30; + temp_var = (die_temp - 25000) * temp_var; + break; + /* FAB_ID is non-zero */ + default: + if (die_temp < 25000) + temp_var = QPNP_VBAT_COEFF_31; + else + temp_var = (-QPNP_VBAT_COEFF_32); + temp_var = (die_temp - 25000) * temp_var; + break; + } + break; + case QPNP_REV_ID_8916_2_0: + switch (chip->fab_id) { + case COMP_ID_SMIC: + offset = (-QPNP_VBAT_COEFF_38); + if (die_temp < 0) + temp_var = die_temp * QPNP_VBAT_COEFF_36; + else if (die_temp > 40000) + temp_var = ((die_temp - 40000) * + (-QPNP_VBAT_COEFF_37)); + break; + case COMP_ID_TSMC: + if (die_temp < 10000) + temp_var = ((die_temp - 10000) * + QPNP_VBAT_COEFF_41); + else if (die_temp > 50000) + temp_var = ((die_temp - 50000) * + (-QPNP_VBAT_COEFF_42)); + break; + default: + case COMP_ID_GF: + if (die_temp < 20000) + temp_var = ((die_temp - 20000) * + QPNP_VBAT_COEFF_45); + else if (die_temp > 40000) + temp_var = ((die_temp - 40000) * + (-QPNP_VBAT_COEFF_46)); + break; + } + break; + default: + temp_var = 0; + break; + } + temp_var = div64_s64(temp_var, QPNP_VBAT_COEFF_3); + + temp_var = 1000000 + temp_var; + + *result = *result * temp_var; + + if (offset) + *result -= offset; + + *result = div64_s64(*result, 1000000); + pr_debug("%lld compensated into %lld\n", old, *result); + + return 0; +} + +static int32_t get_vbat_sns_comp(int64_t *result, + struct qpnp_bms_chip *chip, int64_t die_temp) +{ + int64_t temp_var = 0, offset = 0; + int64_t old = *result; + int version; + + version = qpnp_adc_get_revid_version(chip->dev); + if (version == -EINVAL) + return 0; + + switch (version) { + case QPNP_REV_ID_8916_1_0: + switch (chip->fab_id) { + case COMP_ID_SMIC: + temp_var = ((die_temp - 25000) * + (QPNP_VBAT_COEFF_28)); + break; + default: + case COMP_ID_GF: + temp_var = ((die_temp - 25000) * + (QPNP_VBAT_COEFF_28)); + break; + } + break; + case QPNP_REV_ID_8916_1_1: + switch (chip->fab_id) { + /* FAB_ID is zero */ + case COMP_ID_GF: + temp_var = ((die_temp - 25000) * + (QPNP_VBAT_COEFF_33)); + break; + /* FAB_ID is non-zero */ + default: + offset = QPNP_VBAT_COEFF_35; + if (die_temp > 50000) { + temp_var = ((die_temp - 25000) * + (QPNP_VBAT_COEFF_34)); + } + break; + } + break; + case QPNP_REV_ID_8916_2_0: + switch (chip->fab_id) { + case COMP_ID_SMIC: + if (die_temp < 0) { + temp_var = (die_temp * + QPNP_VBAT_COEFF_39); + } else if (die_temp > 40000) { + temp_var = ((die_temp - 40000) * + (-QPNP_VBAT_COEFF_40)); + } + break; + case COMP_ID_TSMC: + if (die_temp < 10000) + temp_var = ((die_temp - 10000) * + QPNP_VBAT_COEFF_43); + else if (die_temp > 50000) + temp_var = ((die_temp - 50000) * + (-QPNP_VBAT_COEFF_44)); + break; + default: + case COMP_ID_GF: + if (die_temp < 20000) + temp_var = ((die_temp - 20000) * + QPNP_VBAT_COEFF_47); + else if (die_temp > 40000) + temp_var = ((die_temp - 40000) * + (-QPNP_VBAT_COEFF_48)); + break; + } + break; + default: + temp_var = 0; + break; + } + + temp_var = div64_s64(temp_var, QPNP_VBAT_COEFF_3); + + temp_var = 1000000 + temp_var; + + *result = *result * temp_var; + + if (offset) + *result -= offset; + + *result = div64_s64(*result, 1000000); + pr_debug("%lld compensated into %lld\n", old, *result); + + return 0; +} + +int32_t get_vbat_sns_comp_result(struct qpnp_bms_chip *chip, + int64_t *result, bool is_pon_ocv) +{ + int rc, die_temp_result; + + rc = iio_read_channel_processed(chip->die_temp, &die_temp_result); + if (rc < 0) { + pr_err("error reading die temperature rc=%d\n", rc); + return rc; + } + + pr_debug("die-temp = %d\n", die_temp_result); + + if (is_pon_ocv) + rc = get_ocv_comp(result, chip, die_temp_result); + else + rc = get_vbat_sns_comp(result, chip, + die_temp_result); + + if (rc < 0) + pr_err("Error with vbat compensation\n"); + + return rc; +} + + +static int convert_vbatt_raw_to_uv(struct qpnp_bms_chip *chip, + u16 reading, bool is_pon_ocv) +{ + int64_t uv, vbatt; + int rc; + + uv = vadc_reading_to_uv(reading, true); + pr_debug("%u raw converted into %lld uv\n", reading, uv); + + uv = adjust_vbatt_reading(chip, uv); + pr_debug("adjusted into %lld uv\n", uv); + + vbatt = uv; + rc = get_vbat_sns_comp_result(chip, &uv, is_pon_ocv); + if (rc) { + pr_debug("Vbatt compensation failed rc = %d\n", rc); + uv = vbatt; + } else { + pr_debug("temp-compensated %lld into %lld uv\n", vbatt, uv); + } + + return uv; +} + +static void convert_and_store_ocv(struct qpnp_bms_chip *chip, + int batt_temp, bool is_pon_ocv) +{ + int rc; + + rc = calib_vadc(chip); + if (rc) + pr_err("Vadc reference voltage read failed, rc = %d\n", rc); + + chip->last_ocv_uv = convert_vbatt_raw_to_uv(chip, + chip->last_ocv_raw, is_pon_ocv); + + pr_debug("last_ocv_uv = %d\n", chip->last_ocv_uv); +} + +static int read_and_update_ocv(struct qpnp_bms_chip *chip, int batt_temp, + bool is_pon_ocv) +{ + int rc, ocv_uv; + u16 ocv_data = 0; + + /* read the BMS h/w OCV */ + rc = qpnp_read_wrapper(chip, (u8 *)&ocv_data, + chip->base + OCV_DATA0_REG, 2); + if (rc) { + pr_err("Error reading ocv: rc = %d\n", rc); + return -ENXIO; + } + + /* check if OCV is within limits */ + ocv_uv = convert_vbatt_raw_to_uv(chip, ocv_data, is_pon_ocv); + if (ocv_uv < MIN_OCV_UV) { + pr_err("OCV too low or invalid (%d)- rejecting it\n", ocv_uv); + return 0; + } + + if ((chip->last_ocv_raw == OCV_UNINITIALIZED) || + (chip->last_ocv_raw != ocv_data)) { + pr_debug("new OCV!\n"); + chip->last_ocv_raw = ocv_data; + convert_and_store_ocv(chip, batt_temp, is_pon_ocv); + } + + pr_debug("ocv_raw=0x%x last_ocv_raw=0x%x last_ocv_uv=%d\n", + ocv_data, chip->last_ocv_raw, chip->last_ocv_uv); + + return 0; +} + +static int get_battery_voltage(struct qpnp_bms_chip *chip, int *result_uv) +{ + int rc; + + rc = iio_read_channel_processed(chip->vbat_sns, result_uv); + if (rc < 0) { + pr_err("error reading vbat_sns channel rc = %d\n", rc); + return rc; + } + pr_debug("mvolts phy=%d\n", result_uv); + return 0; +} + +static int get_battery_status(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + /* if battery has been registered, use the status property */ + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + return ret.intval; + } + + /* Default to false if the battery power supply is not registered. */ + pr_debug("battery power supply is not registered\n"); + return POWER_SUPPLY_STATUS_UNKNOWN; +} + +static int get_batt_therm(struct qpnp_bms_chip *chip, int *batt_temp) +{ + int rc; + + rc = iio_read_channel_processed(chip->lr_mux1_batt_therm, batt_temp); + if (rc < 0) { + pr_err("lr_mux1_batt_therm channel read error : rc = %d\n", rc); + return rc; + } + return 0; +} + +static int get_prop_bms_rbatt(struct qpnp_bms_chip *chip) +{ + return chip->batt_data->default_rbatt_mohm; +} + +static int get_rbatt(struct qpnp_bms_chip *chip, int soc, int batt_temp) +{ + int rbatt_mohm, scalefactor; + + rbatt_mohm = chip->batt_data->default_rbatt_mohm; + if (chip->batt_data->rbatt_sf_lut == NULL) { + pr_debug("RBATT = %d\n", rbatt_mohm); + return rbatt_mohm; + } + + scalefactor = interpolate_scalingfactor( + chip->batt_data->rbatt_sf_lut, + batt_temp, soc); + rbatt_mohm = (rbatt_mohm * scalefactor) / 100; + + if (chip->dt.cfg_r_conn_mohm > 0) + rbatt_mohm += chip->dt.cfg_r_conn_mohm; + + return rbatt_mohm; +} + +static void charging_began(struct qpnp_bms_chip *chip) +{ + int rc; + u8 state; + + mutex_lock(&chip->last_soc_mutex); + + chip->charge_start_tm_sec = 0; + chip->catch_up_time_sec = 0; + chip->start_soc = chip->last_soc; + + /* + * reset ocv_at_100 to -EINVAL to indicate + * start of charging. + */ + chip->ocv_at_100 = -EINVAL; + + mutex_unlock(&chip->last_soc_mutex); + + /* + * If the BMS state is not in S2, force it in S2. Such + * a condition can only occur if we are coming out of + * suspend. + */ + mutex_lock(&chip->state_change_mutex); + rc = get_fsm_state(chip, &state); + if (rc) + pr_err("Unable to get FSM state rc=%d\n", rc); + if (rc || (state != S2_STATE)) { + pr_debug("Forcing S2 state\n"); + rc = force_fsm_state(chip, S2_STATE); + if (rc) + pr_err("Unable to set FSM state rc=%d\n", rc); + } + mutex_unlock(&chip->state_change_mutex); +} + +static void charging_ended(struct qpnp_bms_chip *chip) +{ + u8 state; + int rc, status = get_battery_status(chip); + + mutex_lock(&chip->last_soc_mutex); + + chip->charge_start_tm_sec = 0; + chip->catch_up_time_sec = 0; + chip->end_soc = chip->last_soc; + + if (status == POWER_SUPPLY_STATUS_FULL) + chip->last_soc_invalid = true; + + mutex_unlock(&chip->last_soc_mutex); + + /* + * If the BMS state is not in S2, force it in S2. Such + * a condition can only occur if we are coming out of + * suspend. + */ + mutex_lock(&chip->state_change_mutex); + rc = get_fsm_state(chip, &state); + if (rc) + pr_err("Unable to get FSM state rc=%d\n", rc); + if (rc || (state != S2_STATE)) { + pr_debug("Forcing S2 state\n"); + rc = force_fsm_state(chip, S2_STATE); + if (rc) + pr_err("Unable to set FSM state rc=%d\n", rc); + } + mutex_unlock(&chip->state_change_mutex); + + /* Calculate charge accumulated and update charge cycle */ + if (chip->dt.cfg_battery_aging_comp && + (chip->end_soc > chip->start_soc)) { + chip->charge_increase += (chip->end_soc - chip->start_soc); + if (chip->charge_increase > 100) { + chip->charge_cycles++; + chip->charge_increase %= 100; + } + pr_debug("start_soc=%u end_soc=%u charge_cycles=%u charge_increase=%u\n", + chip->start_soc, chip->end_soc, + chip->charge_cycles, chip->charge_increase); + rc = backup_charge_cycle(chip); + if (rc) + pr_err("Unable to store charge cycles rc=%d\n", rc); + } +} + +static int estimate_ocv(struct qpnp_bms_chip *chip) +{ + int i, rc, vbatt = 0, vbatt_final = 0; + + for (i = 0; i < 5; i++) { + rc = get_battery_voltage(chip, &vbatt); + if (rc) { + pr_err("Unable to read battery-voltage rc=%d\n", rc); + return rc; + } + /* + * Conservatively select the lowest vbatt to avoid reporting + * a higher ocv due to variations in bootup current. + */ + + if (i == 0) + vbatt_final = vbatt; + else if (vbatt < vbatt_final) + vbatt_final = vbatt; + + msleep(20); + } + + /* + * TODO: Revisit the OCV calcuations to use approximate ibatt + * and rbatt. + */ + return vbatt_final; +} + +static int scale_soc_while_chg(struct qpnp_bms_chip *chip, int chg_time_sec, + int catch_up_sec, int new_soc, int prev_soc) +{ + int scaled_soc; + int numerator; + + /* + * Don't report a high value immediately slowly scale the + * value from prev_soc to the new soc based on a charge time + * weighted average + */ + pr_debug("cts=%d catch_up_sec=%d\n", chg_time_sec, catch_up_sec); + if (catch_up_sec == 0) + return new_soc; + + if (chg_time_sec > catch_up_sec) + return new_soc; + + numerator = (catch_up_sec - chg_time_sec) * prev_soc + + chg_time_sec * new_soc; + scaled_soc = numerator / catch_up_sec; + + pr_debug("cts=%d new_soc=%d prev_soc=%d scaled_soc=%d\n", + chg_time_sec, new_soc, prev_soc, scaled_soc); + + return scaled_soc; +} + +static int report_eoc(struct qpnp_bms_chip *chip) +{ + int rc = -EINVAL; + union power_supply_propval ret = {0,}; + + if (chip->batt_psy == NULL) + chip->batt_psy = power_supply_get_by_name("battery"); + if (chip->batt_psy) { + rc = power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + if (rc) { + pr_err("Unable to get battery 'STATUS' rc=%d\n", rc); + } else if (ret.intval != POWER_SUPPLY_STATUS_FULL) { + pr_debug("Report EOC to charger\n"); + ret.intval = POWER_SUPPLY_STATUS_FULL; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + if (rc) { + pr_err("Unable to set 'STATUS' rc=%d\n", rc); + return rc; + } + chip->eoc_reported = true; + } + } else { + pr_err("battery psy not registered\n"); + } + + return rc; +} + +static void check_recharge_condition(struct qpnp_bms_chip *chip) +{ + int rc; + union power_supply_propval ret = {0,}; + int status = get_battery_status(chip); + + if (chip->last_soc > chip->dt.cfg_soc_resume_limit) + return; + + if (status == POWER_SUPPLY_STATUS_UNKNOWN) { + pr_debug("Unable to read battery status\n"); + return; + } + + /* Report recharge to charger for SOC based resume of charging */ + if ((status != POWER_SUPPLY_STATUS_CHARGING) && chip->eoc_reported) { + ret.intval = POWER_SUPPLY_STATUS_CHARGING; + rc = power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + if (rc < 0) { + pr_err("Unable to set battery property rc=%d\n", rc); + } else { + pr_info("soc dropped below resume_soc soc=%d resume_soc=%d, restart charging\n", + chip->last_soc, + chip->dt.cfg_soc_resume_limit); + chip->eoc_reported = false; + } + } +} + +static void check_eoc_condition(struct qpnp_bms_chip *chip) +{ + int rc; + int status = get_battery_status(chip); + union power_supply_propval ret = {0,}; + + if (status == POWER_SUPPLY_STATUS_UNKNOWN) { + pr_err("Unable to read battery status\n"); + return; + } + + /* + * Check battery status: + * if last_soc is 100 and battery status is still charging + * reset ocv_at_100 and force reporting of eoc to charger. + */ + if ((chip->last_soc == 100) && + (status == POWER_SUPPLY_STATUS_CHARGING)) + chip->ocv_at_100 = -EINVAL; + + /* + * Store the OCV value at 100. If the new ocv is greater than + * ocv_at_100 (battery settles), update ocv_at_100. Else + * if the SOC drops, reset ocv_at_100. + */ + if (chip->ocv_at_100 == -EINVAL) { + if (chip->last_soc == 100) { + if (chip->dt.cfg_report_charger_eoc) { + rc = report_eoc(chip); + if (!rc) { + /* + * update ocv_at_100 only if EOC is + * reported successfully. + */ + chip->ocv_at_100 = chip->last_ocv_uv; + pr_debug("Battery FULL\n"); + } else { + pr_err("Unable to report eoc rc=%d\n", + rc); + chip->ocv_at_100 = -EINVAL; + } + } + if (chip->dt.cfg_use_reported_soc) { + /* begin reported_soc process */ + chip->reported_soc_in_use = true; + chip->charger_removed_since_full = false; + chip->charger_reinserted = false; + chip->reported_soc = 100; + pr_debug("Begin reported_soc process\n"); + } + } + } else { + if (chip->last_ocv_uv >= chip->ocv_at_100) { + pr_debug("new_ocv(%d) > ocv_at_100(%d) maintaining SOC to 100\n", + chip->last_ocv_uv, chip->ocv_at_100); + chip->ocv_at_100 = chip->last_ocv_uv; + chip->last_soc = 100; + } else if (chip->last_soc != 100) { + /* + * Report that the battery is discharging. + * This gets called once when the SOC falls + * below 100. + */ + if (chip->reported_soc_in_use + && chip->reported_soc == 100) { + pr_debug("reported_soc=100, last_soc=%d, do not send DISCHARING status\n", + chip->last_soc); + } else { + ret.intval = POWER_SUPPLY_STATUS_DISCHARGING; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + } + pr_debug("SOC dropped (%d) discarding ocv_at_100\n", + chip->last_soc); + chip->ocv_at_100 = -EINVAL; + } + } +} + +static int report_voltage_based_soc(struct qpnp_bms_chip *chip) +{ + pr_debug("Reported voltage based soc = %d\n", + chip->prev_voltage_based_soc); + return chip->prev_voltage_based_soc; +} + +static int prepare_reported_soc(struct qpnp_bms_chip *chip) +{ + if (!chip->charger_removed_since_full) { + /* + * charger is not removed since full, + * keep reported_soc as 100 and calculate the delta soc + * between reported_soc and last_soc + */ + chip->reported_soc = 100; + chip->reported_soc_delta = 100 - chip->last_soc; + pr_debug("Keep at reported_soc 100, reported_soc_delta=%d, last_soc=%d\n", + chip->reported_soc_delta, + chip->last_soc); + } else { + /* charger is removed since full */ + if (chip->charger_reinserted) { + /* + * charger reinserted, keep the reported_soc + * until it equals to last_soc. + */ + if (chip->reported_soc == chip->last_soc) { + chip->reported_soc_in_use = false; + chip->reported_soc_high_current = false; + pr_debug("reported_soc equals to last_soc, stop reported_soc process\n"); + } + chip->reported_soc_change_sec = 0; + } + } + pr_debug("Reporting reported_soc=%d, last_soc=%d\n", + chip->reported_soc, chip->last_soc); + return chip->reported_soc; +} + +#define SOC_CATCHUP_SEC_MAX 600 +#define SOC_CATCHUP_SEC_PER_PERCENT 60 +#define MAX_CATCHUP_SOC (SOC_CATCHUP_SEC_MAX / SOC_CATCHUP_SEC_PER_PERCENT) +#define SOC_CHANGE_PER_SEC 5 +static int report_vm_bms_soc(struct qpnp_bms_chip *chip) +{ + int soc, soc_change, batt_temp, rc; + int time_since_last_change_sec = 0, charge_time_sec = 0; + unsigned long last_change_sec; + bool charging; + + soc = chip->calculated_soc; + + last_change_sec = chip->last_soc_change_sec; + calculate_delta_time(&last_change_sec, &time_since_last_change_sec); + + charging = is_battery_charging(chip); + + pr_debug("charging=%d last_soc=%d last_soc_unbound=%d\n", + charging, chip->last_soc, chip->last_soc_unbound); + /* + * account for charge time - limit it to SOC_CATCHUP_SEC to + * avoid overflows when charging continues for extended periods + */ + if (charging && chip->last_soc != -EINVAL) { + if (chip->charge_start_tm_sec == 0 || + (chip->catch_up_time_sec == 0 && + (abs(soc - chip->last_soc) >= MIN_SOC_UUC))) { + /* + * calculating soc for the first time + * after start of chg. Initialize catchup time + */ + if (abs(soc - chip->last_soc) < MAX_CATCHUP_SOC) + chip->catch_up_time_sec = + (soc - chip->last_soc) + * SOC_CATCHUP_SEC_PER_PERCENT; + else + chip->catch_up_time_sec = SOC_CATCHUP_SEC_MAX; + + chip->chg_start_soc = chip->last_soc; + + if (chip->catch_up_time_sec < 0) + chip->catch_up_time_sec = 0; + chip->charge_start_tm_sec = last_change_sec; + + pr_debug("chg_start_soc=%d charge_start_tm_sec=%d catch_up_time_sec=%d\n", + chip->chg_start_soc, chip->charge_start_tm_sec, + chip->catch_up_time_sec); + } + + charge_time_sec = min(SOC_CATCHUP_SEC_MAX, (int)last_change_sec + - chip->charge_start_tm_sec); + + /* end catchup if calculated soc and last soc are same */ + if (chip->last_soc == soc) { + chip->catch_up_time_sec = 0; + chip->chg_start_soc = chip->last_soc; + } + } + + if (chip->last_soc != -EINVAL) { + /* + * last_soc < soc ... if we have not been charging at all + * since the last time this was called, report previous SoC. + * Otherwise, scale and catch up. + */ + rc = get_batt_therm(chip, &batt_temp); + if (rc) + batt_temp = BMS_DEFAULT_TEMP; + + if (chip->last_soc < soc && !charging) + soc = chip->last_soc; + else if (chip->last_soc < soc && soc != 100) + soc = scale_soc_while_chg(chip, charge_time_sec, + chip->catch_up_time_sec, + soc, chip->chg_start_soc); + + /* + * if the battery is close to cutoff or if the batt_temp + * is under the low-temp threshold allow bigger change + */ + if (bms_wake_active(&chip->vbms_lv_wake_source) || + (batt_temp <= chip->dt.cfg_low_temp_threshold)) + soc_change = min((int)abs(chip->last_soc - soc), + time_since_last_change_sec); + else + soc_change = min((int)abs(chip->last_soc - soc), + time_since_last_change_sec + / SOC_CHANGE_PER_SEC); + + if (chip->last_soc_unbound) { + chip->last_soc_unbound = false; + } else { + /* + * if soc have not been unbound by resume, + * only change reported SoC by 1. + */ + soc_change = min(1, soc_change); + } + + if (soc < chip->last_soc && soc != 0) + soc = chip->last_soc - soc_change; + if (soc > chip->last_soc && soc != 100) + soc = chip->last_soc + soc_change; + } + + if (chip->last_soc != soc && !chip->last_soc_unbound) + chip->last_soc_change_sec = last_change_sec; + + /* + * Check/update eoc under following condition: + * if there is change in soc: + * soc != chip->last_soc + * during bootup if soc is 100: + */ + soc = bound_soc(soc); + if ((soc != chip->last_soc) || (soc == 100)) { + chip->last_soc = soc; + check_eoc_condition(chip); + if ((chip->dt.cfg_soc_resume_limit > 0) && !charging) + check_recharge_condition(chip); + } + + pr_debug("last_soc=%d calculated_soc=%d soc=%d time_since_last_change=%d\n", + chip->last_soc, chip->calculated_soc, + soc, time_since_last_change_sec); + + /* + * Backup the actual ocv (last_ocv_uv) and not the + * last_soc-interpolated ocv. This makes sure that + * the BMS algorithm always uses the correct ocv and + * can catch up on the last_soc (across reboots). + * We do not want the algorithm to be based of a wrong + * initial OCV. + */ + + backup_ocv_soc(chip, chip->last_ocv_uv, chip->last_soc); + + if (chip->reported_soc_in_use) + return prepare_reported_soc(chip); + + pr_debug("Reported SOC=%d\n", chip->last_soc); + + return chip->last_soc; +} + +static int report_state_of_charge(struct qpnp_bms_chip *chip) +{ + int soc; + + mutex_lock(&chip->last_soc_mutex); + + if (chip->dt.cfg_use_voltage_soc) + soc = report_voltage_based_soc(chip); + else + soc = report_vm_bms_soc(chip); + + mutex_unlock(&chip->last_soc_mutex); + + return soc; +} + +static void btm_notify_vbat(enum qpnp_tm_state state, void *ctx) +{ + struct qpnp_bms_chip *chip = ctx; + int vbat_uv; + int rc; + + rc = get_battery_voltage(chip, &vbat_uv); + if (rc) { + pr_err("error reading vbat_sns adc channel rc=%d\n", rc); + goto out; + } + + pr_debug("vbat is at %d, state is at %d\n", vbat_uv, state); + + if (state == ADC_TM_LOW_STATE) { + pr_debug("low voltage btm notification triggered\n"); + if (vbat_uv <= (chip->vbat_monitor_params.low_thr + + VBATT_ERROR_MARGIN)) { + if (!bms_wake_active(&chip->vbms_lv_wake_source)) + bms_stay_awake(&chip->vbms_lv_wake_source); + + chip->vbat_monitor_params.state_request = + ADC_TM_HIGH_THR_ENABLE; + } else { + pr_debug("faulty btm trigger, discarding\n"); + goto out; + } + } else if (state == ADC_TM_HIGH_STATE) { + pr_debug("high voltage btm notification triggered\n"); + if (vbat_uv > chip->vbat_monitor_params.high_thr) { + chip->vbat_monitor_params.state_request = + ADC_TM_LOW_THR_ENABLE; + if (bms_wake_active(&chip->vbms_lv_wake_source)) + bms_relax(&chip->vbms_lv_wake_source); + } else { + pr_debug("faulty btm trigger, discarding\n"); + goto out; + } + } else { + pr_debug("unknown voltage notification state: %d\n", state); + goto out; + } + + if (chip->bms_psy_registered) + power_supply_changed(chip->bms_psy); + +out: + qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); +} + +static int reset_vbat_monitoring(struct qpnp_bms_chip *chip) +{ + int rc; + + chip->vbat_monitor_params.state_request = ADC_TM_HIGH_LOW_THR_DISABLE; + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + if (rc) { + pr_err("tm disable failed: %d\n", rc); + return rc; + } + + if (bms_wake_active(&chip->vbms_lv_wake_source)) + bms_relax(&chip->vbms_lv_wake_source); + + return 0; +} + +static int setup_vbat_monitoring(struct qpnp_bms_chip *chip) +{ + int rc; + + if (is_debug_batt_id(chip)) { + pr_debug("skip configuring vbat monitoring for debug_board\n"); + return 0; + } + chip->vbat_monitor_params.low_thr = + chip->dt.cfg_low_voltage_threshold; + chip->vbat_monitor_params.high_thr = + chip->dt.cfg_low_voltage_threshold + + VBATT_ERROR_MARGIN; + chip->vbat_monitor_params.state_request = ADC_TM_LOW_THR_ENABLE; + chip->vbat_monitor_params.channel = VBAT_SNS; + chip->vbat_monitor_params.btm_ctx = chip; + chip->vbat_monitor_params.timer_interval = ADC_MEAS1_INTERVAL_1S; + chip->vbat_monitor_params.threshold_notification = &btm_notify_vbat; + pr_debug("set low thr to %d and high to %d\n", + chip->vbat_monitor_params.low_thr, + chip->vbat_monitor_params.high_thr); + + rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev, + &chip->vbat_monitor_params); + if (rc) { + pr_err("adc-tm setup failed: %d\n", rc); + return rc; + } + + pr_debug("vbat monitoring setup complete\n"); + return 0; +} + +static void very_low_voltage_check(struct qpnp_bms_chip *chip, int vbat_uv) +{ + if (!bms_wake_active(&chip->vbms_lv_wake_source) + && (vbat_uv <= chip->dt.cfg_low_voltage_threshold)) { + pr_debug("voltage=%d holding low voltage ws\n", vbat_uv); + bms_stay_awake(&chip->vbms_lv_wake_source); + } else if (bms_wake_active(&chip->vbms_lv_wake_source) + && (vbat_uv > chip->dt.cfg_low_voltage_threshold)) { + pr_debug("voltage=%d releasing low voltage ws\n", vbat_uv); + bms_relax(&chip->vbms_lv_wake_source); + } +} + +static void cv_voltage_check(struct qpnp_bms_chip *chip, int vbat_uv) +{ + if (bms_wake_active(&chip->vbms_cv_wake_source)) { + if ((vbat_uv < (chip->dt.cfg_max_voltage_uv - + VBATT_ERROR_MARGIN + CV_DROP_MARGIN)) + && !is_battery_taper_charging(chip)) { + pr_debug("Fell below CV, releasing cv ws\n"); + chip->in_cv_state = false; + bms_relax(&chip->vbms_cv_wake_source); + } else if (!is_battery_charging(chip)) { + pr_debug("charging stopped, releasing cv ws\n"); + chip->in_cv_state = false; + bms_relax(&chip->vbms_cv_wake_source); + } + } else if (!bms_wake_active(&chip->vbms_cv_wake_source) + && is_battery_charging(chip) + && ((vbat_uv > (chip->dt.cfg_max_voltage_uv - + VBATT_ERROR_MARGIN)) + || is_battery_taper_charging(chip))) { + pr_debug("CC_TO_CV voltage=%d holding cv ws\n", vbat_uv); + chip->in_cv_state = true; + bms_stay_awake(&chip->vbms_cv_wake_source); + } +} + +static void low_soc_check(struct qpnp_bms_chip *chip) +{ + int rc; + + if (chip->dt.cfg_low_soc_fifo_length < 1) + return; + + mutex_lock(&chip->state_change_mutex); + + if (chip->calculated_soc <= chip->dt.cfg_low_soc_calc_threshold) { + if (!chip->low_soc_fifo_set) { + pr_debug("soc=%d (low-soc) setting fifo_length to %d\n", + chip->calculated_soc, + chip->dt.cfg_low_soc_fifo_length); + rc = get_fifo_length(chip, S2_STATE, + &chip->s2_fifo_length); + if (rc) { + pr_err("Unable to get_fifo_length rc=%d\n", rc); + goto low_soc_exit; + } + rc = set_fifo_length(chip, S2_STATE, + chip->dt.cfg_low_soc_fifo_length); + if (rc) { + pr_err("Unable to set_fifo_length rc=%d\n", rc); + goto low_soc_exit; + } + chip->low_soc_fifo_set = true; + } + } else { + if (chip->low_soc_fifo_set) { + pr_debug("soc=%d setting back fifo_length to %d\n", + chip->calculated_soc, + chip->s2_fifo_length); + rc = set_fifo_length(chip, S2_STATE, + chip->s2_fifo_length); + if (rc) { + pr_err("Unable to set_fifo_length rc=%d\n", rc); + goto low_soc_exit; + } + chip->low_soc_fifo_set = false; + } + } + +low_soc_exit: + mutex_unlock(&chip->state_change_mutex); +} + +static int calculate_soc_from_voltage(struct qpnp_bms_chip *chip) +{ + int voltage_range_uv, voltage_remaining_uv, voltage_based_soc; + int rc, vbat_uv; + + /* check if we have the averaged fifo data */ + if (chip->voltage_soc_uv) { + vbat_uv = chip->voltage_soc_uv; + } else { + rc = get_battery_voltage(chip, &vbat_uv); + if (rc < 0) { + pr_err("adc vbat failed err = %d\n", rc); + return rc; + } + pr_debug("instant-voltage based voltage-soc\n"); + } + + voltage_range_uv = chip->dt.cfg_max_voltage_uv - + chip->dt.cfg_v_cutoff_uv; + voltage_remaining_uv = vbat_uv - chip->dt.cfg_v_cutoff_uv; + voltage_based_soc = voltage_remaining_uv * 100 / voltage_range_uv; + + voltage_based_soc = clamp(voltage_based_soc, 0, 100); + + if (chip->prev_voltage_based_soc != voltage_based_soc + && chip->bms_psy_registered) { + pr_debug("update bms_psy\n"); + power_supply_changed(chip->bms_psy); + } + chip->prev_voltage_based_soc = voltage_based_soc; + + pr_debug("vbat used = %duv\n", vbat_uv); + pr_debug("Calculated voltage based soc=%d\n", voltage_based_soc); + + if (voltage_based_soc == 100) + if (chip->dt.cfg_report_charger_eoc) + report_eoc(chip); + + return 0; +} + +static void calculate_reported_soc(struct qpnp_bms_chip *chip) +{ + union power_supply_propval ret = {0,}; + + if (chip->last_soc < 0) { + pr_debug("last_soc is not ready, return\n"); + return; + } + + if (chip->reported_soc > chip->last_soc) { + /*send DISCHARGING status if the reported_soc drops from 100 */ + if (chip->reported_soc == 100) { + ret.intval = POWER_SUPPLY_STATUS_DISCHARGING; + power_supply_set_property(chip->batt_psy, + POWER_SUPPLY_PROP_STATUS, &ret); + pr_debug("Report discharging status, reported_soc=%d, last_soc=%d\n", + chip->reported_soc, chip->last_soc); + } + /* + * reported_soc_delta is used to prevent + * the big change in last_soc, + * this is not used in high current mode + */ + if (chip->reported_soc_delta > 0) + chip->reported_soc_delta--; + + if (chip->reported_soc_high_current) + chip->reported_soc--; + else + chip->reported_soc = chip->last_soc + + chip->reported_soc_delta; + + pr_debug("New reported_soc=%d, last_soc is=%d\n", + chip->reported_soc, chip->last_soc); + } else { + chip->reported_soc_in_use = false; + chip->reported_soc_high_current = false; + pr_debug("reported_soc equals last_soc,stop reported_soc process\n"); + } + pr_debug("bms power_supply_changed\n"); + power_supply_changed(chip->bms_psy); +} + +static int clamp_soc_based_on_voltage(struct qpnp_bms_chip *chip, int soc) +{ + int rc, vbat_uv; + + rc = get_battery_voltage(chip, &vbat_uv); + if (rc < 0) { + pr_err("adc vbat failed err = %d\n", rc); + return soc; + } + + /* only clamp when discharging */ + if (is_battery_charging(chip)) + return soc; + + if (soc <= 0 && vbat_uv > chip->dt.cfg_v_cutoff_uv) { + pr_debug("clamping soc to 1, vbat (%d) > cutoff (%d)\n", + vbat_uv, chip->dt.cfg_v_cutoff_uv); + return 1; + } + pr_debug("not clamping, using soc = %d, vbat = %d and cutoff = %d\n", + soc, vbat_uv, chip->dt.cfg_v_cutoff_uv); + return soc; +} + +static void battery_voltage_check(struct qpnp_bms_chip *chip) +{ + int rc, vbat_uv = 0; + + rc = get_battery_voltage(chip, &vbat_uv); + if (rc < 0) { + pr_err("Failed to read battery-voltage rc=%d\n", rc); + } else { + very_low_voltage_check(chip, vbat_uv); + cv_voltage_check(chip, vbat_uv); + } +} + +#define UI_SOC_CATCHUP_TIME (60) +static void monitor_soc_work(struct work_struct *work) +{ + struct qpnp_bms_chip *chip = container_of(work, + struct qpnp_bms_chip, + monitor_soc_work.work); + int rc, new_soc = 0, batt_temp; + + /*skip if its a debug-board */ + if (is_debug_batt_id(chip)) + return; + + bms_stay_awake(&chip->vbms_soc_wake_source); + + calculate_delta_time(&chip->tm_sec, &chip->delta_time_s); + pr_debug("elapsed_time=%d\n", chip->delta_time_s); + + mutex_lock(&chip->last_soc_mutex); + + if (!is_battery_present(chip)) { + /* if battery is not preset report 100% SOC */ + pr_debug("battery gone, reporting 100\n"); + chip->last_soc_invalid = true; + chip->last_soc = -EINVAL; + new_soc = 100; + } else { + battery_voltage_check(chip); + + if (chip->dt.cfg_use_voltage_soc) { + calculate_soc_from_voltage(chip); + } else { + rc = get_batt_therm(chip, &batt_temp); + if (rc < 0) { + pr_err("Unable to read batt temp rc=%d, using default=%d\n", + rc, BMS_DEFAULT_TEMP); + batt_temp = BMS_DEFAULT_TEMP; + } + + if (chip->last_soc_invalid) { + chip->last_soc_invalid = false; + chip->last_soc = -EINVAL; + } + new_soc = lookup_soc_ocv(chip, chip->last_ocv_uv, + batt_temp); + /* clamp soc due to BMS hw/sw immaturities */ + new_soc = clamp_soc_based_on_voltage(chip, new_soc); + + if (chip->calculated_soc != new_soc) { + pr_debug("SOC changed! new_soc=%d prev_soc=%d\n", + new_soc, chip->calculated_soc); + chip->calculated_soc = new_soc; + /* + * To recalculate the catch-up time, clear it + * when SOC changes. + */ + chip->catch_up_time_sec = 0; + + if (chip->calculated_soc == 100) + /* update last_soc immediately */ + report_vm_bms_soc(chip); + + pr_debug("update bms_psy\n"); + power_supply_changed(chip->bms_psy); + } else if (chip->last_soc != chip->calculated_soc) { + pr_debug("update bms_psy\n"); + power_supply_changed(chip->bms_psy); + } else { + report_vm_bms_soc(chip); + } + } + /* low SOC configuration */ + low_soc_check(chip); + } + /* + * schedule the work only if last_soc has not caught up with + * the calculated soc or if we are using voltage based soc + */ + if ((chip->last_soc != chip->calculated_soc) || + chip->dt.cfg_use_voltage_soc) + schedule_delayed_work(&chip->monitor_soc_work, + msecs_to_jiffies(get_calculation_delay_ms(chip))); + + if (chip->reported_soc_in_use && chip->charger_removed_since_full + && !chip->charger_reinserted) { + /* record the elapsed time after last reported_soc change */ + chip->reported_soc_change_sec += chip->delta_time_s; + pr_debug("reported_soc_change_sec=%d\n", + chip->reported_soc_change_sec); + + /* above the catch up time, calculate new reported_soc */ + if (chip->reported_soc_change_sec > UI_SOC_CATCHUP_TIME) { + calculate_reported_soc(chip); + chip->reported_soc_change_sec = 0; + } + } + + mutex_unlock(&chip->last_soc_mutex); + + bms_relax(&chip->vbms_soc_wake_source); +} + +#define DEBUG_BOARD_SOC 67 +#define BATT_MISSING_SOC 50 +static int get_prop_bms_capacity(struct qpnp_bms_chip *chip) +{ + if (is_debug_batt_id(chip)) + return DEBUG_BOARD_SOC; + + if (!chip->battery_present) + return BATT_MISSING_SOC; + + return report_state_of_charge(chip); +} + +static bool is_hi_power_state_requested(struct qpnp_bms_chip *chip) +{ + + pr_debug("hi_power_state=0x%x\n", chip->hi_power_state); + + if (chip->hi_power_state & VMBMS_IGNORE_ALL_BIT) + return false; + else + return !!chip->hi_power_state; + +} + +static int qpnp_vm_bms_config_power_state(struct qpnp_bms_chip *chip, + int usecase, bool hi_power_enable) +{ + if (usecase < 0) { + pr_err("Invalid power-usecase %x\n", usecase); + return -EINVAL; + } + + if (hi_power_enable) + chip->hi_power_state |= usecase; + else + chip->hi_power_state &= ~usecase; + + pr_debug("hi_power_state=%x usecase=%x hi_power_enable=%d\n", + chip->hi_power_state, usecase, hi_power_enable); + + return 0; +} + +static int get_prop_bms_current_now(struct qpnp_bms_chip *chip) +{ + return chip->current_now; +} + +static int get_current_cc(struct qpnp_bms_chip *chip) +{ + int soc, cc_full; + int64_t current_charge; + + if (chip->batt_data == NULL) + return -EINVAL; + + cc_full = chip->batt_data->fcc; + if (chip->dt.cfg_use_voltage_soc) + soc = chip->prev_voltage_based_soc; + else + soc = chip->last_soc; + + /* + * Full charge capacity is in mAh and soc is in % + * current_charge capacity is defined in uAh + * Hence conversion ((mAh * pct * 1000) / 100) => (mAh * pct * 10) + */ + current_charge = cc_full * soc * 10; + + return current_charge; +} + +static int get_charge_full(struct qpnp_bms_chip *chip) +{ + + if (chip->batt_data) + return chip->batt_data->fcc * 1000; + + return -EINVAL; +} + +static enum power_supply_property bms_power_props[] = { + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE, + POWER_SUPPLY_PROP_RESISTANCE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_VOLTAGE_OCV, + POWER_SUPPLY_PROP_HI_POWER, + POWER_SUPPLY_PROP_LOW_POWER, + POWER_SUPPLY_PROP_BATTERY_TYPE, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_CYCLE_COUNT, + POWER_SUPPLY_PROP_CHARGE_COUNTER, + POWER_SUPPLY_PROP_CHARGE_FULL, + POWER_SUPPLY_PROP_RESISTANCE_ID, +}; + +static int +qpnp_vm_bms_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + case POWER_SUPPLY_PROP_HI_POWER: + case POWER_SUPPLY_PROP_LOW_POWER: + return 1; + default: + break; + } + + return 0; +} + +static int qpnp_vm_bms_power_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct qpnp_bms_chip *chip = power_supply_get_drvdata(psy); + int value = 0, rc; + + val->intval = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = get_prop_bms_capacity(chip); + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = chip->battery_status; + break; + case POWER_SUPPLY_PROP_RESISTANCE: + val->intval = get_prop_bms_rbatt(chip); + break; + case POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE: + if (chip->batt_data->rbatt_capacitive_mohm > 0) + val->intval = chip->batt_data->rbatt_capacitive_mohm; + if (chip->dt.cfg_r_conn_mohm > 0) + val->intval += chip->dt.cfg_r_conn_mohm; + break; + case POWER_SUPPLY_PROP_RESISTANCE_NOW: + rc = get_batt_therm(chip, &value); + if (rc < 0) + value = BMS_DEFAULT_TEMP; + val->intval = get_rbatt(chip, chip->calculated_soc, value); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = get_prop_bms_current_now(chip); + break; + case POWER_SUPPLY_PROP_BATTERY_TYPE: + val->strval = chip->batt_data->battery_type; + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + val->intval = chip->last_ocv_uv; + break; + case POWER_SUPPLY_PROP_TEMP: + rc = get_batt_therm(chip, &value); + if (rc < 0) + value = BMS_DEFAULT_TEMP; + val->intval = value; + break; + case POWER_SUPPLY_PROP_HI_POWER: + val->intval = is_hi_power_state_requested(chip); + break; + case POWER_SUPPLY_PROP_LOW_POWER: + val->intval = !is_hi_power_state_requested(chip); + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT: + if (chip->dt.cfg_battery_aging_comp) + val->intval = chip->charge_cycles; + else + val->intval = -EINVAL; + break; + case POWER_SUPPLY_PROP_CHARGE_COUNTER: + val->intval = get_current_cc(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL: + val->intval = get_charge_full(chip); + break; + case POWER_SUPPLY_PROP_RESISTANCE_ID: + val->intval = chip->batt_id_ohm; + break; + default: + return -EINVAL; + } + return 0; +} + +static int qpnp_vm_bms_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + int rc = 0; + struct qpnp_bms_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_NOW: + chip->current_now = val->intval; + pr_debug("IBATT = %d\n", val->intval); + break; + case POWER_SUPPLY_PROP_VOLTAGE_OCV: + pm_relax(chip->dev); + cancel_delayed_work_sync(&chip->monitor_soc_work); + chip->last_ocv_uv = val->intval; + pr_debug("OCV = %d\n", val->intval); + schedule_delayed_work(&chip->monitor_soc_work, 0); + break; + case POWER_SUPPLY_PROP_HI_POWER: + rc = qpnp_vm_bms_config_power_state(chip, val->intval, true); + if (rc) + pr_err("Unable to set power-state rc=%d\n", rc); + break; + case POWER_SUPPLY_PROP_LOW_POWER: + rc = qpnp_vm_bms_config_power_state(chip, val->intval, false); + if (rc) + pr_err("Unable to set power-state rc=%d\n", rc); + break; + default: + return -EINVAL; + } + return rc; +} + +static void bms_new_battery_setup(struct qpnp_bms_chip *chip) +{ + int rc; + + mutex_lock(&chip->bms_data_mutex); + + chip->last_soc_invalid = true; + /* + * disable and re-enable the BMS hardware to reset + * the realtime-FIFO data and restart accumulation + */ + rc = qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, + BMS_EN_BIT, 0); + /* delay for the BMS hardware to reset its state */ + msleep(200); + rc |= qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, + BMS_EN_BIT, BMS_EN_BIT); + /* delay for the BMS hardware to re-start */ + msleep(200); + if (rc) + pr_err("Unable to reset BMS rc=%d\n", rc); + + chip->last_ocv_uv = estimate_ocv(chip); + + memset(&chip->bms_data, 0, sizeof(chip->bms_data)); + + /* update the sequence number */ + chip->bms_data.seq_num = chip->seq_num++; + + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->bms_wait_q); + + /* hold a wake lock until the read thread is scheduled */ + if (chip->bms_dev_open) + pm_stay_awake(chip->dev); + + mutex_unlock(&chip->bms_data_mutex); + + /* reset aging variables */ + if (chip->dt.cfg_battery_aging_comp) { + chip->charge_cycles = 0; + chip->charge_increase = 0; + rc = backup_charge_cycle(chip); + if (rc) + pr_err("Unable to reset aging data rc=%d\n", rc); + } +} + +static void battery_insertion_check(struct qpnp_bms_chip *chip) +{ + int present = (int)is_battery_present(chip); + + if (chip->battery_present != present) { + pr_debug("shadow_sts=%d status=%d\n", + chip->battery_present, present); + if (chip->battery_present != -EINVAL) { + if (present) { + /* new battery inserted */ + bms_new_battery_setup(chip); + setup_vbat_monitoring(chip); + pr_debug("New battery inserted!\n"); + } else { + /* battery removed */ + reset_vbat_monitoring(chip); + pr_debug("Battery removed\n"); + } + } + chip->battery_present = present; + } +} + +static void battery_status_check(struct qpnp_bms_chip *chip) +{ + int status = get_battery_status(chip); + + if (chip->battery_status != status) { + if (status == POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("charging started\n"); + charging_began(chip); + } else if (chip->battery_status == + POWER_SUPPLY_STATUS_CHARGING) { + pr_debug("charging stopped\n"); + charging_ended(chip); + } + + if (status == POWER_SUPPLY_STATUS_FULL) { + pr_debug("battery full\n"); + chip->battery_full = true; + } else if (chip->battery_status == POWER_SUPPLY_STATUS_FULL) { + pr_debug("battery not-full anymore\n"); + chip->battery_full = false; + } + chip->battery_status = status; + } +} + +#define HIGH_CURRENT_TH 2 +static void reported_soc_check_status(struct qpnp_bms_chip *chip) +{ + u8 present; + + present = is_charger_present(chip); + pr_debug("usb_present=%d\n", present); + + if (!present && !chip->charger_removed_since_full) { + chip->charger_removed_since_full = true; + pr_debug("reported_soc: charger removed since full\n"); + return; + } + if (chip->reported_soc_high_current) { + pr_debug("reported_soc in high current mode, return\n"); + return; + } + if ((chip->reported_soc - chip->last_soc) > + (100 - chip->dt.cfg_soc_resume_limit + + HIGH_CURRENT_TH)) { + chip->reported_soc_high_current = true; + chip->charger_removed_since_full = true; + chip->charger_reinserted = false; + pr_debug("reported_soc enters high current mode\n"); + return; + } + if (present && chip->charger_removed_since_full) { + chip->charger_reinserted = true; + pr_debug("reported_soc: charger reinserted\n"); + } + if (!present && chip->charger_removed_since_full) { + chip->charger_reinserted = false; + pr_debug("reported_soc: charger removed again\n"); + } +} + +static void qpnp_vm_bms_ext_power_changed(struct power_supply *psy) +{ + struct qpnp_bms_chip *chip = power_supply_get_drvdata(psy); + + pr_debug("Triggered!\n"); + battery_status_check(chip); + battery_insertion_check(chip); + + mutex_lock(&chip->last_soc_mutex); + battery_voltage_check(chip); + mutex_unlock(&chip->last_soc_mutex); + + if (chip->reported_soc_in_use) + reported_soc_check_status(chip); +} + + +static void dump_bms_data(const char *func, struct qpnp_bms_chip *chip) +{ + int i; + + pr_debug("%s: fifo_count=%d acc_count=%d seq_num=%d\n", + func, chip->bms_data.num_fifo, + chip->bms_data.acc_count, + chip->bms_data.seq_num); + + for (i = 0; i < chip->bms_data.num_fifo; i++) + pr_debug("fifo=%d fifo_uv=%d sample_interval=%d sample_count=%d\n", + i, chip->bms_data.fifo_uv[i], + chip->bms_data.sample_interval_ms, + chip->bms_data.sample_count); + pr_debug("avg_acc_data=%d\n", chip->bms_data.acc_uv); +} + +static int read_and_populate_fifo_data(struct qpnp_bms_chip *chip) +{ + u8 fifo_count = 0, val = 0; + u8 fifo_data_raw[MAX_FIFO_REGS * 2]; + u16 fifo_data; + int rc, i, j; + int64_t voltage_soc_avg = 0; + + /* read the completed FIFO count */ + rc = qpnp_read_wrapper(chip, &val, chip->base + STATUS2_REG, 1); + if (rc) { + pr_err("Unable to read STATUS2 register rc=%d\n", rc); + return rc; + } + fifo_count = (val & FIFO_CNT_SD_MASK) >> FIFO_CNT_SD_SHIFT; + pr_debug("fifo_count=%d\n", fifo_count); + if (!fifo_count) { + pr_debug("No data in FIFO\n"); + return 0; + } else if (fifo_count > MAX_FIFO_REGS) { + pr_err("Invalid fifo-length %d rejecting data\n", fifo_count); + chip->bms_data.num_fifo = 0; + return 0; + } + + /* read the FIFO data */ + for (i = 0; i < fifo_count * 2; i++) { + rc = qpnp_read_wrapper(chip, &fifo_data_raw[i], + chip->base + FIFO_0_LSB_REG + i, 1); + if (rc) { + pr_err("Unable to read FIFO register(%d) rc=%d\n", + i, rc); + return rc; + } + } + + /* populate the structure */ + chip->bms_data.num_fifo = fifo_count; + + rc = get_sample_interval(chip, chip->current_fsm_state, + &chip->bms_data.sample_interval_ms); + if (rc) { + pr_err("Unable to read state=%d sample_interval rc=%d\n", + chip->current_fsm_state, rc); + return rc; + } + + rc = get_sample_count(chip, chip->current_fsm_state, + &chip->bms_data.sample_count); + if (rc) { + pr_err("Unable to read state=%d sample_count rc=%d\n", + chip->current_fsm_state, rc); + return rc; + } + + for (i = 0, j = 0; i < fifo_count * 2; i = i + 2, j++) { + fifo_data = fifo_data_raw[i] | (fifo_data_raw[i + 1] << 8); + chip->bms_data.fifo_uv[j] = convert_vbatt_raw_to_uv(chip, + fifo_data, 0); + voltage_soc_avg += chip->bms_data.fifo_uv[j]; + } + /* store the fifo average for voltage-based-soc */ + chip->voltage_soc_uv = div_u64(voltage_soc_avg, fifo_count); + + return 0; +} + +static int read_and_populate_acc_data(struct qpnp_bms_chip *chip) +{ + int rc; + u32 acc_data_sd = 0, acc_count_sd = 0, avg_acc_data = 0; + + /* read ACC SD count */ + rc = qpnp_read_wrapper(chip, (u8 *)&acc_count_sd, + chip->base + ACC_CNT_SD_REG, 1); + if (rc) { + pr_err("Unable to read ACC_CNT_SD_REG rc=%d\n", rc); + return rc; + } + if (!acc_count_sd) { + pr_debug("No data in accumulator\n"); + return 0; + } + /* read ACC SD data */ + rc = qpnp_read_wrapper(chip, (u8 *)&acc_data_sd, + chip->base + ACC_DATA0_SD_REG, 3); + if (rc) { + pr_err("Unable to read ACC_DATA0_SD_REG rc=%d\n", rc); + return rc; + } + avg_acc_data = div_u64(acc_data_sd, acc_count_sd); + + chip->bms_data.acc_uv = convert_vbatt_raw_to_uv(chip, + avg_acc_data, 0); + chip->bms_data.acc_count = acc_count_sd; + + rc = get_sample_interval(chip, chip->current_fsm_state, + &chip->bms_data.sample_interval_ms); + if (rc) { + pr_err("Unable to read state=%d sample_interval rc=%d\n", + chip->current_fsm_state, rc); + return rc; + } + + rc = get_sample_count(chip, chip->current_fsm_state, + &chip->bms_data.sample_count); + if (rc) { + pr_err("Unable to read state=%d sample_count rc=%d\n", + chip->current_fsm_state, rc); + return rc; + } + + return 0; +} + +static int clear_fifo_acc_data(struct qpnp_bms_chip *chip) +{ + int rc; + u8 reg = 0; + + reg = FIFO_CNT_SD_CLR_BIT | ACC_DATA_SD_CLR_BIT | ACC_CNT_SD_CLR_BIT; + rc = qpnp_masked_write_base(chip, chip->base + DATA_CTL2_REG, reg, reg); + if (rc) + pr_err("Unable to write DATA_CTL2_REG rc=%d\n", rc); + + return rc; +} + +static irqreturn_t bms_fifo_update_done_irq_handler(int irq, void *_chip) +{ + int rc; + struct qpnp_bms_chip *chip = _chip; + + pr_debug("fifo_update_done triggered\n"); + + mutex_lock(&chip->bms_data_mutex); + + if (chip->suspend_data_valid) { + pr_debug("Suspend data not processed yet\n"); + goto fail_fifo; + } + + rc = calib_vadc(chip); + if (rc) + pr_err("Unable to calibrate vadc rc=%d\n", rc); + + /* clear old data */ + memset(&chip->bms_data, 0, sizeof(chip->bms_data)); + /* + * 1. Read FIFO and populate the bms_data + * 2. Clear FIFO data + * 3. Notify userspace + */ + rc = update_fsm_state(chip); + if (rc) { + pr_err("Unable to read FSM state rc=%d\n", rc); + goto fail_fifo; + } + pr_debug("fsm_state=%d\n", chip->current_fsm_state); + + rc = read_and_populate_fifo_data(chip); + if (rc) { + pr_err("Unable to read FIFO data rc=%d\n", rc); + goto fail_fifo; + } + + rc = clear_fifo_acc_data(chip); + if (rc) + pr_err("Unable to clear FIFO/ACC data rc=%d\n", rc); + + /* update the sequence number */ + chip->bms_data.seq_num = chip->seq_num++; + + dump_bms_data(__func__, chip); + + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->bms_wait_q); + + /* hold a wake lock until the read thread is scheduled */ + if (chip->bms_dev_open) + pm_stay_awake(chip->dev); +fail_fifo: + mutex_unlock(&chip->bms_data_mutex); + return IRQ_HANDLED; +} + +static irqreturn_t bms_fsm_state_change_irq_handler(int irq, void *_chip) +{ + int rc; + struct qpnp_bms_chip *chip = _chip; + + pr_debug("fsm_state_changed triggered\n"); + + mutex_lock(&chip->bms_data_mutex); + + if (chip->suspend_data_valid) { + pr_debug("Suspend data not processed yet\n"); + goto fail_state; + } + + rc = calib_vadc(chip); + if (rc) + pr_err("Unable to calibrate vadc rc=%d\n", rc); + + /* clear old data */ + memset(&chip->bms_data, 0, sizeof(chip->bms_data)); + /* + * 1. Read FIFO and ACC_DATA and populate the bms_data + * 2. Clear FIFO & ACC data + * 3. Notify userspace + */ + pr_debug("prev_fsm_state=%d\n", chip->current_fsm_state); + + rc = read_and_populate_fifo_data(chip); + if (rc) { + pr_err("Unable to read FIFO data rc=%d\n", rc); + goto fail_state; + } + + /* read accumulator data */ + rc = read_and_populate_acc_data(chip); + if (rc) { + pr_err("Unable to read ACC_SD data rc=%d\n", rc); + goto fail_state; + } + + rc = update_fsm_state(chip); + if (rc) { + pr_err("Unable to read FSM state rc=%d\n", rc); + goto fail_state; + } + + rc = clear_fifo_acc_data(chip); + if (rc) + pr_err("Unable to clear FIFO/ACC data rc=%d\n", rc); + + /* update the sequence number */ + chip->bms_data.seq_num = chip->seq_num++; + + dump_bms_data(__func__, chip); + + /* signal the read thread */ + chip->data_ready = true; + wake_up_interruptible(&chip->bms_wait_q); + + /* hold a wake lock until the read thread is scheduled */ + if (chip->bms_dev_open) + pm_stay_awake(chip->dev); +fail_state: + mutex_unlock(&chip->bms_data_mutex); + return IRQ_HANDLED; +} + +static int read_shutdown_ocv_soc(struct qpnp_bms_chip *chip) +{ + u8 stored_soc = 0; + u16 stored_ocv = 0; + int rc; + + rc = qpnp_read_wrapper(chip, (u8 *)&stored_ocv, + chip->base + BMS_OCV_REG, 2); + if (rc) { + pr_err("failed to read addr = %d %d\n", + chip->base + BMS_OCV_REG, rc); + return -EINVAL; + } + + /* if shutdwon ocv is invalid, reject shutdown soc too */ + if (!stored_ocv || (stored_ocv == OCV_INVALID)) { + pr_debug("shutdown OCV %d - invalid\n", stored_ocv); + chip->shutdown_ocv = OCV_INVALID; + chip->shutdown_soc = SOC_INVALID; + return -EINVAL; + } + chip->shutdown_ocv = stored_ocv * 1000; + + /* + * The previous SOC is stored in the first 7 bits of the register as + * (Shutdown SOC + 1). This allows for register reset values of both + * 0x00 and 0xFF. + */ + rc = qpnp_read_wrapper(chip, &stored_soc, chip->base + BMS_SOC_REG, 1); + if (rc) { + pr_err("failed to read addr = %d %d\n", + chip->base + BMS_SOC_REG, rc); + return -EINVAL; + } + + if (!stored_soc || stored_soc == SOC_INVALID) { + chip->shutdown_soc = SOC_INVALID; + chip->shutdown_ocv = OCV_INVALID; + return -EINVAL; + } + chip->shutdown_soc = (stored_soc >> 1) - 1; + + pr_debug("shutdown_ocv=%d shutdown_soc=%d\n", + chip->shutdown_ocv, chip->shutdown_soc); + + return 0; +} + +static int interpolate_current_comp(int die_temp) +{ + int i; + int num_rows = ARRAY_SIZE(temp_curr_comp_lut); + + if (die_temp <= (temp_curr_comp_lut[0].temp_decideg)) + return temp_curr_comp_lut[0].current_ma; + + if (die_temp >= (temp_curr_comp_lut[num_rows - 1].temp_decideg)) + return temp_curr_comp_lut[num_rows - 1].current_ma; + + for (i = 0; i < num_rows - 1; i++) + if (die_temp <= (temp_curr_comp_lut[i].temp_decideg)) + break; + + if (die_temp == (temp_curr_comp_lut[i].temp_decideg)) + return temp_curr_comp_lut[i].current_ma; + + return linear_interpolate( + temp_curr_comp_lut[i - 1].current_ma, + temp_curr_comp_lut[i - 1].temp_decideg, + temp_curr_comp_lut[i].current_ma, + temp_curr_comp_lut[i].temp_decideg, + die_temp); +} + +static void adjust_pon_ocv(struct qpnp_bms_chip *chip, int batt_temp) +{ + int rc, current_ma, rbatt_mohm, die_temp, delta_uv, pc, result; + + rc = iio_read_channel_processed(chip->die_temp, &result); + if (rc < 0) { + pr_err("error reading die_temp channel rc=%d\n", rc); + } else { + pc = interpolate_pc(chip->batt_data->pc_temp_ocv_lut, + batt_temp, chip->last_ocv_uv / 1000); + /* + * For pc < 2, use the rbatt of pc = 2. This is to avoid + * the huge rbatt values at pc < 2 which can disrupt the pon_ocv + * calculations. + */ + if (pc < 2) + pc = 2; + rbatt_mohm = get_rbatt(chip, pc, batt_temp); + /* convert die_temp to DECIDEGC */ + die_temp = result / 100; + current_ma = interpolate_current_comp(die_temp); + delta_uv = rbatt_mohm * current_ma; + pr_debug("PON OCV changed from %d to %d pc=%d rbatt=%d current_ma=%d die_temp=%d batt_temp=%d delta_uv=%d\n", + chip->last_ocv_uv, chip->last_ocv_uv + delta_uv, pc, + rbatt_mohm, current_ma, die_temp, batt_temp, delta_uv); + + chip->last_ocv_uv += delta_uv; + } +} + +static int calculate_initial_soc(struct qpnp_bms_chip *chip) +{ + int rc, batt_temp = 0, est_ocv = 0; + + rc = get_batt_therm(chip, &batt_temp); + if (rc < 0) { + pr_err("Unable to read batt temp, using default=%d\n", + BMS_DEFAULT_TEMP); + batt_temp = BMS_DEFAULT_TEMP; + } + + rc = read_and_update_ocv(chip, batt_temp, true); + if (rc) { + pr_err("Unable to read PON OCV rc=%d\n", rc); + return rc; + } + + rc = read_shutdown_ocv_soc(chip); + if (rc < 0 || chip->dt.cfg_ignore_shutdown_soc) + chip->shutdown_soc_invalid = true; + + if (chip->warm_reset) { + /* + * if we have powered on from warm reset - + * Always use shutdown SOC. If shudown SOC is invalid then + * estimate OCV + */ + if (chip->shutdown_soc_invalid) { + pr_debug("Estimate OCV\n"); + est_ocv = estimate_ocv(chip); + if (est_ocv <= 0) { + pr_err("Unable to estimate OCV rc=%d\n", + est_ocv); + return -EINVAL; + } + chip->last_ocv_uv = est_ocv; + chip->calculated_soc = lookup_soc_ocv(chip, est_ocv, + batt_temp); + } else { + chip->last_ocv_uv = chip->shutdown_ocv; + chip->last_soc = chip->shutdown_soc; + chip->calculated_soc = lookup_soc_ocv(chip, + chip->shutdown_ocv, batt_temp); + pr_debug("Using shutdown SOC\n"); + } + } else { + /* + * In PM8916 2.0 PON OCV calculation is delayed due to + * change in the ordering of power-on sequence of LDO6. + * Adjust PON OCV to include current during PON. + */ + if (chip->workaround_flag & WRKARND_PON_OCV_COMP) + adjust_pon_ocv(chip, batt_temp); + + /* !warm_reset use PON OCV only if shutdown SOC is invalid */ + chip->calculated_soc = lookup_soc_ocv(chip, + chip->last_ocv_uv, batt_temp); + if (!chip->shutdown_soc_invalid && + (abs(chip->shutdown_soc - chip->calculated_soc) < + chip->dt.cfg_shutdown_soc_valid_limit)) { + chip->last_ocv_uv = chip->shutdown_ocv; + chip->last_soc = chip->shutdown_soc; + chip->calculated_soc = lookup_soc_ocv(chip, + chip->shutdown_ocv, batt_temp); + pr_debug("Using shutdown SOC\n"); + } else { + chip->shutdown_soc_invalid = true; + pr_debug("Using PON SOC\n"); + } + } + /* store the start-up OCV for voltage-based-soc */ + chip->voltage_soc_uv = chip->last_ocv_uv; + + pr_info("warm_reset=%d est_ocv=%d shutdown_soc_invalid=%d shutdown_ocv=%d shutdown_soc=%d last_soc=%d calculated_soc=%d last_ocv_uv=%d\n", + chip->warm_reset, est_ocv, chip->shutdown_soc_invalid, + chip->shutdown_ocv, chip->shutdown_soc, chip->last_soc, + chip->calculated_soc, chip->last_ocv_uv); + + return 0; +} + +static int calculate_initial_aging_comp(struct qpnp_bms_chip *chip) +{ + int rc; + bool battery_removed = is_battery_replaced_in_offmode(chip); + + if (battery_removed || chip->shutdown_soc_invalid) { + pr_info("Clearing aging data battery_removed=%d shutdown_soc_invalid=%d\n", + battery_removed, chip->shutdown_soc_invalid); + chip->charge_cycles = 0; + chip->charge_increase = 0; + rc = backup_charge_cycle(chip); + if (rc) + pr_err("Unable to reset aging data rc=%d\n", rc); + } else { + rc = read_chgcycle_data_from_backup(chip); + if (rc) + pr_err("Unable to read aging data rc=%d\n", rc); + } + + pr_debug("Initial aging data charge_cycles=%u charge_increase=%u\n", + chip->charge_cycles, chip->charge_increase); + return rc; +} + +static int bms_load_hw_defaults(struct qpnp_bms_chip *chip) +{ + u8 val, bms_en = 0; + u32 interval[2], count[2], fifo[2]; + int rc; + + /* S3 OCV tolerence threshold */ + if (chip->dt.cfg_s3_ocv_tol_uv >= 0 && + chip->dt.cfg_s3_ocv_tol_uv <= MAX_OCV_TOL_THRESHOLD) { + val = chip->dt.cfg_s3_ocv_tol_uv / OCV_TOL_LSB_UV; + rc = qpnp_masked_write_base(chip, + chip->base + S3_OCV_TOL_CTL_REG, 0xFF, val); + if (rc) { + pr_err("Unable to write s3_ocv_tol_threshold rc=%d\n", + rc); + return rc; + } + } + + /* S1 accumulator threshold */ + if (chip->dt.cfg_s1_sample_count >= 1 && + chip->dt.cfg_s1_sample_count <= MAX_SAMPLE_COUNT) { + val = (chip->dt.cfg_s1_sample_count > 1) ? + (ilog2(chip->dt.cfg_s1_sample_count) - 1) : 0; + rc = qpnp_masked_write_base(chip, + chip->base + S1_ACC_CNT_REG, + ACC_CNT_MASK, val); + if (rc) { + pr_err("Unable to write s1 sample count rc=%d\n", rc); + return rc; + } + } + + /* S2 accumulator threshold */ + if (chip->dt.cfg_s2_sample_count >= 1 && + chip->dt.cfg_s2_sample_count <= MAX_SAMPLE_COUNT) { + val = (chip->dt.cfg_s2_sample_count > 1) ? + (ilog2(chip->dt.cfg_s2_sample_count) - 1) : 0; + rc = qpnp_masked_write_base(chip, + chip->base + S2_ACC_CNT_REG, + ACC_CNT_MASK, val); + if (rc) { + pr_err("Unable to write s2 sample count rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.cfg_s1_sample_interval_ms >= 0 && + chip->dt.cfg_s1_sample_interval_ms <= MAX_SAMPLE_INTERVAL) { + val = chip->dt.cfg_s1_sample_interval_ms / 10; + rc = qpnp_write_wrapper(chip, &val, + chip->base + S1_SAMPLE_INTVL_REG, 1); + if (rc) { + pr_err("Unable to write s1 sample inteval rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.cfg_s2_sample_interval_ms >= 0 && + chip->dt.cfg_s2_sample_interval_ms <= MAX_SAMPLE_INTERVAL) { + val = chip->dt.cfg_s2_sample_interval_ms / 10; + rc = qpnp_write_wrapper(chip, &val, + chip->base + S2_SAMPLE_INTVL_REG, 1); + if (rc) { + pr_err("Unable to write s2 sample inteval rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.cfg_s1_fifo_length >= 0 && + chip->dt.cfg_s1_fifo_length <= MAX_FIFO_REGS) { + rc = qpnp_masked_write_base(chip, chip->base + FIFO_LENGTH_REG, + S1_FIFO_LENGTH_MASK, + chip->dt.cfg_s1_fifo_length); + if (rc) { + pr_err("Unable to write s1 fifo length rc=%d\n", rc); + return rc; + } + } + + if (chip->dt.cfg_s2_fifo_length >= 0 && + chip->dt.cfg_s2_fifo_length <= MAX_FIFO_REGS) { + rc = qpnp_masked_write_base(chip, chip->base + + FIFO_LENGTH_REG, S2_FIFO_LENGTH_MASK, + chip->dt.cfg_s2_fifo_length + << S2_FIFO_LENGTH_SHIFT); + if (rc) { + pr_err("Unable to write s2 fifo length rc=%d\n", rc); + return rc; + } + } + + get_sample_interval(chip, S1_STATE, &interval[0]); + get_sample_interval(chip, S2_STATE, &interval[1]); + get_sample_count(chip, S1_STATE, &count[0]); + get_sample_count(chip, S2_STATE, &count[1]); + get_fifo_length(chip, S1_STATE, &fifo[0]); + get_fifo_length(chip, S2_STATE, &fifo[1]); + + /* Force the BMS state to S2 at boot-up */ + rc = force_fsm_state(chip, S2_STATE); + if (rc) { + pr_err("Unable to force S2 state rc=%d\n", rc); + return rc; + } + + rc = qpnp_read_wrapper(chip, &bms_en, chip->base + EN_CTL_REG, 1); + if (rc) { + pr_err("Unable to read BMS_EN state rc=%d\n", rc); + return rc; + } + + rc = update_fsm_state(chip); + if (rc) { + pr_err("Unable to read FSM state rc=%d\n", rc); + return rc; + } + + pr_info("BMS_EN=%d Sample_Interval-S1=[%d]S2=[%d] Sample_Count-S1=[%d]S2=[%d] Fifo_Length-S1=[%d]S2=[%d] FSM_state=%d\n", + !!bms_en, interval[0], interval[1], count[0], + count[1], fifo[0], fifo[1], + chip->current_fsm_state); + + return 0; +} + +static ssize_t vm_bms_read(struct file *file, char __user *buf, size_t count, + loff_t *ppos) +{ + int rc; + struct qpnp_bms_chip *chip = file->private_data; + + if (!chip->data_ready && (file->f_flags & O_NONBLOCK)) { + rc = -EAGAIN; + goto fail_read; + } + + rc = wait_event_interruptible(chip->bms_wait_q, chip->data_ready); + if (rc) { + pr_debug("wait failed! rc=%d\n", rc); + goto fail_read; + } + + if (!chip->data_ready) { + pr_debug("No Data, false wakeup\n"); + rc = -EFAULT; + goto fail_read; + } + + mutex_lock(&chip->bms_data_mutex); + + if (copy_to_user(buf, &chip->bms_data, sizeof(chip->bms_data))) { + pr_err("Failed in copy_to_user\n"); + mutex_unlock(&chip->bms_data_mutex); + rc = -EFAULT; + goto fail_read; + } + pr_debug("Data copied!!\n"); + chip->data_ready = false; + + mutex_unlock(&chip->bms_data_mutex); + /* wakelock-timeout for userspace to pick up */ + pm_wakeup_event(chip->dev, BMS_READ_TIMEOUT); + + return sizeof(chip->bms_data); + +fail_read: + pm_relax(chip->dev); + return rc; +} + +static int vm_bms_open(struct inode *inode, struct file *file) +{ + struct qpnp_bms_chip *chip = container_of(inode->i_cdev, + struct qpnp_bms_chip, bms_cdev); + + mutex_lock(&chip->bms_device_mutex); + + if (chip->bms_dev_open) { + pr_debug("BMS device already open\n"); + mutex_unlock(&chip->bms_device_mutex); + return -EBUSY; + } + + chip->bms_dev_open = true; + file->private_data = chip; + pr_debug("BMS device opened\n"); + + mutex_unlock(&chip->bms_device_mutex); + + return 0; +} + +static int vm_bms_release(struct inode *inode, struct file *file) +{ + struct qpnp_bms_chip *chip = container_of(inode->i_cdev, + struct qpnp_bms_chip, bms_cdev); + + mutex_lock(&chip->bms_device_mutex); + + chip->bms_dev_open = false; + pm_relax(chip->dev); + pr_debug("BMS device closed\n"); + + mutex_unlock(&chip->bms_device_mutex); + + return 0; +} + +static const struct file_operations bms_fops = { + .owner = THIS_MODULE, + .open = vm_bms_open, + .read = vm_bms_read, + .release = vm_bms_release, +}; + +static void bms_init_defaults(struct qpnp_bms_chip *chip) +{ + chip->data_ready = false; + chip->last_ocv_raw = OCV_UNINITIALIZED; + chip->battery_status = POWER_SUPPLY_STATUS_UNKNOWN; + chip->battery_present = -EINVAL; + chip->calculated_soc = -EINVAL; + chip->last_soc = -EINVAL; + chip->vbms_lv_wake_source.disabled = 1; + chip->vbms_cv_wake_source.disabled = 1; + chip->vbms_soc_wake_source.disabled = 1; + chip->ocv_at_100 = -EINVAL; + chip->prev_soc_uuc = -EINVAL; + chip->charge_cycles = 0; + chip->start_soc = 0; + chip->end_soc = 0; + chip->charge_increase = 0; +} + +#define REQUEST_IRQ(chip, rc, irq_name) \ +do { \ + rc = devm_request_threaded_irq(chip->dev, \ + chip->irq_name##_irq.irq, NULL, \ + bms_##irq_name##_irq_handler, \ + IRQF_TRIGGER_RISING | IRQF_ONESHOT, \ + #irq_name, chip); \ + if (rc < 0) \ + pr_err("Unable to request " #irq_name " irq: %d\n", rc);\ +} while (0) + +#define FIND_IRQ(chip, pdev, irq_name, rc) \ +do { \ + chip->irq_name##_irq.irq = of_irq_get_byname(child, \ + #irq_name); \ + if (chip->irq_name##_irq.irq < 0) { \ + rc = chip->irq_name##_irq.irq; \ + pr_err("Unable to get " #irq_name " irq rc=%d\n", rc); \ + } \ +} while (0) + +static int bms_request_irqs(struct qpnp_bms_chip *chip) +{ + int rc; + + REQUEST_IRQ(chip, rc, fifo_update_done); + if (rc < 0) + return rc; + + REQUEST_IRQ(chip, rc, fsm_state_change); + if (rc < 0) + return rc; + + /* Disable the state change IRQ */ + disable_bms_irq(&chip->fsm_state_change_irq); + enable_irq_wake(chip->fifo_update_done_irq.irq); + + return 0; +} + +static int bms_find_irqs(struct qpnp_bms_chip *chip, struct device_node *child) +{ + int rc = 0; + + FIND_IRQ(chip, child, fifo_update_done, rc); + if (rc < 0) + return rc; + FIND_IRQ(chip, child, fsm_state_change, rc); + if (rc < 0) + return rc; + + return 0; +} + + +static int64_t read_battery_id_uv(struct qpnp_bms_chip *chip) +{ + int rc, result; + + rc = iio_read_channel_processed(chip->lr_mux2_batt_id, &result); + if (rc < 0) { + pr_err("error reading batt id channel rc = %d\n", rc); + return rc; + } + + return result; +} + +static int show_bms_config(struct seq_file *m, void *data) +{ + struct qpnp_bms_chip *chip = m->private; + int s1_sample_interval, s2_sample_interval; + int s1_sample_count, s2_sample_count; + int s1_fifo_length, s2_fifo_length; + + get_sample_interval(chip, S1_STATE, &s1_sample_interval); + get_sample_interval(chip, S2_STATE, &s2_sample_interval); + get_sample_count(chip, S1_STATE, &s1_sample_count); + get_sample_count(chip, S2_STATE, &s2_sample_count); + get_fifo_length(chip, S1_STATE, &s1_fifo_length); + get_fifo_length(chip, S2_STATE, &s2_fifo_length); + + seq_printf(m, "r_conn_mohm\t=\t%d\n" + "v_cutoff_uv\t=\t%d\n" + "max_voltage_uv\t=\t%d\n" + "use_voltage_soc\t=\t%d\n" + "low_soc_calc_threshold\t=\t%d\n" + "low_soc_calculate_soc_ms\t=\t%d\n" + "low_voltage_threshold\t=\t%d\n" + "low_voltage_calculate_soc_ms\t=\t%d\n" + "calculate_soc_ms\t=\t%d\n" + "ignore_shutdown_soc\t=\t%d\n" + "shutdown_soc_valid_limit\t=\t%d\n" + "force_s3_on_suspend\t=\t%d\n" + "report_charger_eoc\t=\t%d\n" + "aging_compensation\t=\t%d\n" + "use_reported_soc\t=\t%d\n" + "s1_sample_interval_ms\t=\t%d\n" + "s2_sample_interval_ms\t=\t%d\n" + "s1_sample_count\t=\t%d\n" + "s2_sample_count\t=\t%d\n" + "s1_fifo_length\t=\t%d\n" + "s2_fifo_length\t=\t%d\n", + chip->dt.cfg_r_conn_mohm, + chip->dt.cfg_v_cutoff_uv, + chip->dt.cfg_max_voltage_uv, + chip->dt.cfg_use_voltage_soc, + chip->dt.cfg_low_soc_calc_threshold, + chip->dt.cfg_low_soc_calculate_soc_ms, + chip->dt.cfg_low_voltage_threshold, + chip->dt.cfg_low_voltage_calculate_soc_ms, + chip->dt.cfg_calculate_soc_ms, + chip->dt.cfg_ignore_shutdown_soc, + chip->dt.cfg_shutdown_soc_valid_limit, + chip->dt.cfg_force_s3_on_suspend, + chip->dt.cfg_report_charger_eoc, + chip->dt.cfg_battery_aging_comp, + chip->dt.cfg_use_reported_soc, + s1_sample_interval, + s2_sample_interval, + s1_sample_count, + s2_sample_count, + s1_fifo_length, + s2_fifo_length); + + return 0; +} + +static int bms_config_open(struct inode *inode, struct file *file) +{ + struct qpnp_bms_chip *chip = inode->i_private; + + return single_open(file, show_bms_config, chip); +} + +static const struct file_operations bms_config_debugfs_ops = { + .owner = THIS_MODULE, + .open = bms_config_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_bms_status(struct seq_file *m, void *data) +{ + struct qpnp_bms_chip *chip = m->private; + + seq_printf(m, "bms_psy_registered\t=\t%d\n" + "bms_dev_open\t=\t%d\n" + "warm_reset\t=\t%d\n" + "battery_status\t=\t%d\n" + "battery_present\t=\t%d\n" + "in_cv_state\t=\t%d\n" + "calculated_soc\t=\t%d\n" + "last_soc\t=\t%d\n" + "last_ocv_uv\t=\t%d\n" + "last_ocv_raw\t=\t%d\n" + "last_soc_unbound\t=\t%d\n" + "current_fsm_state\t=\t%d\n" + "current_now\t=\t%d\n" + "ocv_at_100\t=\t%d\n" + "low_voltage_ws_active\t=\t%d\n" + "cv_ws_active\t=\t%d\n", + chip->bms_psy_registered, + chip->bms_dev_open, + chip->warm_reset, + chip->battery_status, + chip->battery_present, + chip->in_cv_state, + chip->calculated_soc, + chip->last_soc, + chip->last_ocv_uv, + chip->last_ocv_raw, + chip->last_soc_unbound, + chip->current_fsm_state, + chip->current_now, + chip->ocv_at_100, + bms_wake_active(&chip->vbms_lv_wake_source), + bms_wake_active(&chip->vbms_cv_wake_source)); + return 0; +} + +static int bms_status_open(struct inode *inode, struct file *file) +{ + struct qpnp_bms_chip *chip = inode->i_private; + + return single_open(file, show_bms_status, chip); +} + +static const struct file_operations bms_status_debugfs_ops = { + .owner = THIS_MODULE, + .open = bms_status_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int show_bms_data(struct seq_file *m, void *data) +{ + struct qpnp_bms_chip *chip = m->private; + int i; + + mutex_lock(&chip->bms_data_mutex); + + seq_printf(m, "seq_num=%d\n", chip->bms_data.seq_num); + for (i = 0; i < chip->bms_data.num_fifo; i++) + seq_printf(m, "fifo_uv[%d]=%d sample_count=%d interval_ms=%d\n", + i, chip->bms_data.fifo_uv[i], + chip->bms_data.sample_count, + chip->bms_data.sample_interval_ms); + seq_printf(m, "acc_uv=%d sample_count=%d sample_interval=%d\n", + chip->bms_data.acc_uv, chip->bms_data.acc_count, + chip->bms_data.sample_interval_ms); + + mutex_unlock(&chip->bms_data_mutex); + + return 0; +} + +static int bms_data_open(struct inode *inode, struct file *file) +{ + struct qpnp_bms_chip *chip = inode->i_private; + + return single_open(file, show_bms_data, chip); +} + +static const struct file_operations bms_data_debugfs_ops = { + .owner = THIS_MODULE, + .open = bms_data_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +#define BID_RPULL_OHM 100000 +#define VREF_BAT_THERM 1800 +static int get_battery_id(struct qpnp_bms_chip *chip, + int64_t battery_id_uv) +{ + int batt_id_mv; + int64_t denom; + + batt_id_mv = div_s64(battery_id_uv, 1000); + if (batt_id_mv == 0) { + pr_debug("batt_id_mv = 0 from ADC\n"); + return 0; + } + + denom = div64_s64(VREF_BAT_THERM * 1000, batt_id_mv) - 1000; + if (denom <= 0) { + /* batt id connector might be open, return 0 kohms */ + return 0; + } + + chip->batt_id_ohm = div64_u64(BID_RPULL_OHM * 1000 + denom / 2, denom); + + return 0; +} + +static int set_battery_data(struct qpnp_bms_chip *chip) +{ + int64_t battery_id_uv; + int rc = 0; + struct bms_battery_data *batt_data; + struct device_node *node; + + battery_id_uv = read_battery_id_uv(chip); + if (battery_id_uv < 0) { + pr_err("cannot read battery id_uv err = %lld\n", battery_id_uv); + return battery_id_uv; + } + + rc = get_battery_id(chip, battery_id_uv); + if (rc < 0) { + pr_err("Failed to calcualte battery-id rc=%d\n", rc); + return rc; + } + + node = of_parse_phandle(chip->pdev->dev.of_node, + "qcom,battery-data", 0); + pr_debug(" battery-id-uV=%lld batt_id=%d ohm\n", + battery_id_uv, chip->batt_id_ohm); + + if (!node) { + pr_err("No available batterydata\n"); + return -EINVAL; + } + + batt_data = devm_kzalloc(chip->dev, + sizeof(struct bms_battery_data), GFP_KERNEL); + if (!batt_data) + return -EINVAL; + + batt_data->fcc_temp_lut = devm_kzalloc(chip->dev, + sizeof(struct single_row_lut), GFP_KERNEL); + batt_data->pc_temp_ocv_lut = devm_kzalloc(chip->dev, + sizeof(struct pc_temp_ocv_lut), GFP_KERNEL); + batt_data->rbatt_sf_lut = devm_kzalloc(chip->dev, + sizeof(struct sf_lut), GFP_KERNEL); + batt_data->ibat_acc_lut = devm_kzalloc(chip->dev, + sizeof(struct ibat_temp_acc_lut), GFP_KERNEL); + + batt_data->max_voltage_uv = -1; + batt_data->cutoff_uv = -1; + batt_data->iterm_ua = -1; + + /* + * if the alloced luts are 0s, of_batterydata_read_data ignores + * them. + */ + rc = of_batterydata_read_data(node, batt_data, battery_id_uv); + if (rc || !batt_data->pc_temp_ocv_lut + || !batt_data->fcc_temp_lut + || !batt_data->rbatt_sf_lut + || !batt_data->ibat_acc_lut) { + pr_err("battery data load failed\n"); + return rc; + } + + if (batt_data->pc_temp_ocv_lut == NULL) { + pr_err("temp ocv lut table has not been loaded\n"); + + return -EINVAL; + } + + /* check if ibat_acc_lut is valid */ + if (!batt_data->ibat_acc_lut->rows) { + pr_info("ibat_acc_lut not present\n"); + batt_data->ibat_acc_lut = NULL; + } + + /* Override battery properties if specified in the battery profile */ + if (batt_data->max_voltage_uv >= 0) + chip->dt.cfg_max_voltage_uv = batt_data->max_voltage_uv; + if (batt_data->cutoff_uv >= 0) + chip->dt.cfg_v_cutoff_uv = batt_data->cutoff_uv; + + chip->batt_data = batt_data; + + return 0; +} + +static int parse_pdev_dt_properties(struct qpnp_bms_chip *chip, + struct platform_device *pdev) +{ + struct device_node *child; + int rc; + unsigned int base; + + chip->dev = &(pdev->dev); + chip->pdev = pdev; + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes found\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 -ENXIO; + } + + pr_debug("Node name = %s\n", child->name); + + if (strcmp("qcom,batt-pres-status", + child->name) == 0) { + chip->batt_pres_addr = base; + continue; + } + + if (strcmp("qcom,qpnp-chg-pres", + child->name) == 0) { + chip->chg_pres_addr = base; + continue; + } + + chip->base = base; + rc = bms_find_irqs(chip, child); + if (rc) { + pr_err("Could not find irqs rc=%d\n", rc); + return rc; + } + } + + if (chip->base == 0) { + dev_err(&pdev->dev, "BMS peripheral was not registered\n"); + return -EINVAL; + } + + pr_debug("bms-base=0x%04x bat-pres-reg=0x%04x qpnp-chg-pres=0x%04x\n", + chip->base, chip->batt_pres_addr, chip->chg_pres_addr); + + return 0; +} + +#define PROP_READ(chip_prop, qpnp_pdev_property, retval) \ +do { \ + if (retval) \ + break; \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_pdev_property, \ + &chip->dt.chip_prop); \ + if (retval) { \ + pr_err("Error reading " #qpnp_pdev_property \ + " property %d\n", retval); \ + } \ +} while (0) + +#define PROP_READ_OPTIONAL(chip_prop, qpnp_pdev_property, retval) \ +do { \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_pdev_property, \ + &chip->dt.chip_prop); \ + if (retval) \ + chip->dt.chip_prop = -EINVAL; \ +} while (0) + +static int parse_bms_dt_properties(struct qpnp_bms_chip *chip) +{ + int rc = 0; + + PROP_READ(cfg_v_cutoff_uv, "v-cutoff-uv", rc); + PROP_READ(cfg_max_voltage_uv, "max-voltage-uv", rc); + PROP_READ(cfg_r_conn_mohm, "r-conn-mohm", rc); + PROP_READ(cfg_shutdown_soc_valid_limit, + "shutdown-soc-valid-limit", rc); + PROP_READ(cfg_low_soc_calc_threshold, + "low-soc-calculate-soc-threshold", rc); + PROP_READ(cfg_low_soc_calculate_soc_ms, + "low-soc-calculate-soc-ms", rc); + PROP_READ(cfg_low_voltage_calculate_soc_ms, + "low-voltage-calculate-soc-ms", rc); + PROP_READ(cfg_calculate_soc_ms, "calculate-soc-ms", rc); + PROP_READ(cfg_low_voltage_threshold, "low-voltage-threshold", rc); + + if (rc) { + pr_err("Missing required properties rc=%d\n", rc); + return rc; + } + + PROP_READ_OPTIONAL(cfg_s1_sample_interval_ms, + "s1-sample-interval-ms", rc); + PROP_READ_OPTIONAL(cfg_s2_sample_interval_ms, + "s2-sample-interval-ms", rc); + PROP_READ_OPTIONAL(cfg_s1_sample_count, "s1-sample-count", rc); + PROP_READ_OPTIONAL(cfg_s2_sample_count, "s2-sample-count", rc); + PROP_READ_OPTIONAL(cfg_s1_fifo_length, "s1-fifo-length", rc); + PROP_READ_OPTIONAL(cfg_s2_fifo_length, "s2-fifo-length", rc); + PROP_READ_OPTIONAL(cfg_s3_ocv_tol_uv, "s3-ocv-tolerence-uv", rc); + PROP_READ_OPTIONAL(cfg_low_soc_fifo_length, + "low-soc-fifo-length", rc); + PROP_READ_OPTIONAL(cfg_soc_resume_limit, "resume-soc", rc); + PROP_READ_OPTIONAL(cfg_low_temp_threshold, + "low-temp-threshold", rc); + if (rc) + chip->dt.cfg_low_temp_threshold = 0; + + PROP_READ_OPTIONAL(cfg_ibat_avg_samples, + "ibat-avg-samples", rc); + if (rc || (chip->dt.cfg_ibat_avg_samples <= 0) || + (chip->dt.cfg_ibat_avg_samples > IAVG_SAMPLES)) + chip->dt.cfg_ibat_avg_samples = IAVG_SAMPLES; + + chip->dt.cfg_ignore_shutdown_soc = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,ignore-shutdown-soc"); + chip->dt.cfg_use_voltage_soc = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,use-voltage-soc"); + chip->dt.cfg_force_s3_on_suspend = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,force-s3-on-suspend"); + chip->dt.cfg_report_charger_eoc = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,report-charger-eoc"); + chip->dt.cfg_disable_bms = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,disable-bms"); + chip->dt.cfg_force_bms_active_on_charger = of_property_read_bool( + chip->pdev->dev.of_node, + "qcom,force-bms-active-on-charger"); + chip->dt.cfg_battery_aging_comp = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,batt-aging-comp"); + chip->dt.cfg_use_reported_soc = of_property_read_bool( + chip->pdev->dev.of_node, "qcom,use-reported-soc"); + pr_debug("v_cutoff_uv=%d, max_v=%d\n", chip->dt.cfg_v_cutoff_uv, + chip->dt.cfg_max_voltage_uv); + pr_debug("r_conn=%d shutdown_soc_valid_limit=%d low_temp_threshold=%d ibat_avg_samples=%d\n", + chip->dt.cfg_r_conn_mohm, + chip->dt.cfg_shutdown_soc_valid_limit, + chip->dt.cfg_low_temp_threshold, + chip->dt.cfg_ibat_avg_samples); + pr_debug("ignore_shutdown_soc=%d, use_voltage_soc=%d low_soc_fifo_length=%d\n", + chip->dt.cfg_ignore_shutdown_soc, + chip->dt.cfg_use_voltage_soc, + chip->dt.cfg_low_soc_fifo_length); + pr_debug("force-s3-on-suspend=%d report-charger-eoc=%d disable-bms=%d disable-suspend-on-usb=%d aging_compensation=%d\n", + chip->dt.cfg_force_s3_on_suspend, + chip->dt.cfg_report_charger_eoc, + chip->dt.cfg_disable_bms, + chip->dt.cfg_force_bms_active_on_charger, + chip->dt.cfg_battery_aging_comp); + pr_debug("use-reported-soc is %d\n", + chip->dt.cfg_use_reported_soc); + + return 0; +} + +static int bms_get_adc(struct qpnp_bms_chip *chip, + struct platform_device *pdev) +{ + int rc = 0; + + chip->ref_625mv = iio_channel_get(&pdev->dev, "ref_625mv"); + if (IS_ERR(chip->ref_625mv)) { + if (PTR_ERR(chip->ref_625mv) != -EPROBE_DEFER) + pr_err("ref_625mv unavailable %ld\n", + PTR_ERR(chip->ref_625mv)); + rc = PTR_ERR(chip->ref_625mv); + chip->ref_625mv = NULL; + return rc; + } + + chip->ref_125v = iio_channel_get(&pdev->dev, "ref_1250v"); + if (IS_ERR(chip->ref_125v)) { + if (PTR_ERR(chip->ref_125v) != -EPROBE_DEFER) + pr_err("ref_125v unavailable %ld\n", + PTR_ERR(chip->ref_125v)); + rc = PTR_ERR(chip->ref_125v); + chip->ref_125v = NULL; + return rc; + } + + chip->vbat_sns = iio_channel_get(&pdev->dev, "vbat_sns"); + if (IS_ERR(chip->vbat_sns)) { + if (PTR_ERR(chip->vbat_sns) != -EPROBE_DEFER) + pr_err("vbat_sns unavailable %ld\n", + PTR_ERR(chip->vbat_sns)); + rc = PTR_ERR(chip->vbat_sns); + chip->vbat_sns = NULL; + return rc; + } + + chip->lr_mux1_batt_therm = iio_channel_get(&pdev->dev, "batt_therm"); + if (IS_ERR(chip->lr_mux1_batt_therm)) { + if (PTR_ERR(chip->lr_mux1_batt_therm) != -EPROBE_DEFER) + pr_err("lr_mux1_batt_therm unavailable %ld\n", + PTR_ERR(chip->lr_mux1_batt_therm)); + rc = PTR_ERR(chip->lr_mux1_batt_therm); + chip->lr_mux1_batt_therm = NULL; + return rc; + } + + chip->die_temp = iio_channel_get(&pdev->dev, "die_temp"); + if (IS_ERR(chip->die_temp)) { + if (PTR_ERR(chip->die_temp) != -EPROBE_DEFER) + pr_err("die_temp unavailable %ld\n", + PTR_ERR(chip->die_temp)); + rc = PTR_ERR(chip->die_temp); + chip->die_temp = NULL; + return rc; + } + + chip->lr_mux2_batt_id = iio_channel_get(&pdev->dev, "batt_id"); + if (IS_ERR(chip->lr_mux2_batt_id)) { + if (PTR_ERR(chip->lr_mux2_batt_id) != -EPROBE_DEFER) + pr_err("lr_mux2_batt_id unavailable %ld\n", + PTR_ERR(chip->lr_mux2_batt_id)); + rc = PTR_ERR(chip->lr_mux2_batt_id); + chip->lr_mux2_batt_id = NULL; + return rc; + } + + chip->adc_tm_dev = qpnp_get_adc_tm(&pdev->dev, "bms"); + if (IS_ERR(chip->adc_tm_dev)) { + rc = PTR_ERR(chip->adc_tm_dev); + if (rc == -EPROBE_DEFER) + pr_err("adc-tm not found - defer probe rc=%d\n", rc); + else + pr_err("adc-tm property missing, rc=%d\n", rc); + } + + return rc; +} + +static int register_bms_char_device(struct qpnp_bms_chip *chip) +{ + int rc; + + rc = alloc_chrdev_region(&chip->dev_no, 0, 1, "vm_bms"); + if (rc) { + pr_err("Unable to allocate chrdev rc=%d\n", rc); + return rc; + } + cdev_init(&chip->bms_cdev, &bms_fops); + rc = cdev_add(&chip->bms_cdev, chip->dev_no, 1); + if (rc) { + pr_err("Unable to add bms_cdev rc=%d\n", rc); + goto unregister_chrdev; + } + + chip->bms_class = class_create(THIS_MODULE, "vm_bms"); + if (IS_ERR_OR_NULL(chip->bms_class)) { + pr_err("Fail to create bms class\n"); + rc = -EINVAL; + goto delete_cdev; + } + chip->bms_device = device_create(chip->bms_class, + NULL, chip->dev_no, + NULL, "vm_bms"); + if (IS_ERR(chip->bms_device)) { + pr_err("Fail to create bms_device device\n"); + rc = -EINVAL; + goto delete_cdev; + } + + return 0; + +delete_cdev: + cdev_del(&chip->bms_cdev); +unregister_chrdev: + unregister_chrdev_region(chip->dev_no, 1); + return rc; +} + +static int qpnp_vm_bms_probe(struct platform_device *pdev) +{ + struct qpnp_bms_chip *chip; + struct device_node *revid_dev_node; + struct power_supply_config bms_psy_cfg; + int rc, vbatt = 0; + + 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; + } + + rc = regmap_read(chip->regmap, + VADC1_LC_USR_BASE + INT_TEST_VAL_OFFSET, &chip->fab_id); + if (rc) { + pr_err("read failed rc=%d\n", rc); + return rc; + } + + rc = bms_get_adc(chip, pdev); + if (rc < 0) { + pr_err("Failed to get adc rc=%d\n", rc); + return rc; + } + + revid_dev_node = of_parse_phandle(pdev->dev.of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property\n"); + return -EINVAL; + } + + chip->revid_data = get_revid_data(revid_dev_node); + if (IS_ERR(chip->revid_data)) { + pr_err("revid error rc = %ld\n", PTR_ERR(chip->revid_data)); + return -EINVAL; + } + if ((chip->revid_data->pmic_subtype == PM8916_SUBTYPE) && + chip->revid_data->rev4 == PM8916_V2P0_REV4) + chip->workaround_flag |= WRKARND_PON_OCV_COMP; + + rc = qpnp_pon_is_warm_reset(); + if (rc < 0) { + pr_err("Error reading warm reset status rc=%d\n", rc); + return rc; + } + chip->warm_reset = !!rc; + + rc = parse_pdev_dt_properties(chip, pdev); + if (rc) { + pr_err("Error registering pdev resource rc=%d\n", rc); + return rc; + } + + rc = parse_bms_dt_properties(chip); + if (rc) { + pr_err("Unable to read all bms properties, rc = %d\n", rc); + return rc; + } + + if (chip->dt.cfg_disable_bms) { + pr_info("VMBMS disabled (disable-bms = 1)\n"); + rc = qpnp_masked_write_base(chip, chip->base + EN_CTL_REG, + BMS_EN_BIT, 0); + if (rc) + pr_err("Unable to disable VMBMS rc=%d\n", rc); + return -ENODEV; + } + + rc = qpnp_read_wrapper(chip, chip->revision, + chip->base + REVISION1_REG, 2); + if (rc) { + pr_err("Error reading version register rc=%d\n", rc); + return rc; + } + + pr_debug("BMS version: %hhu.%hhu\n", + chip->revision[1], chip->revision[0]); + + dev_set_drvdata(&pdev->dev, chip); + device_init_wakeup(&pdev->dev, 1); + mutex_init(&chip->bms_data_mutex); + mutex_init(&chip->bms_device_mutex); + mutex_init(&chip->last_soc_mutex); + mutex_init(&chip->state_change_mutex); + init_waitqueue_head(&chip->bms_wait_q); + + /* read battery-id and select the battery profile */ + rc = set_battery_data(chip); + if (rc) { + pr_err("Unable to read battery data %d\n", rc); + goto fail_init; + } + + /* set the battery profile */ + rc = config_battery_data(chip->batt_data); + if (rc) { + pr_err("Unable to config battery data %d\n", rc); + goto fail_init; + } + + chip->vbms_lv_wake_source.source = wakeup_source_register(NULL, + "vbms_lv_wake"); + chip->vbms_cv_wake_source.source = wakeup_source_register(NULL, + "vbms_cv_wake"); + chip->vbms_soc_wake_source.source = wakeup_source_register(NULL, + "vbms_soc_wake"); + INIT_DELAYED_WORK(&chip->monitor_soc_work, monitor_soc_work); + + bms_init_defaults(chip); + bms_load_hw_defaults(chip); + + if (is_battery_present(chip)) { + rc = setup_vbat_monitoring(chip); + if (rc) { + pr_err("fail to configure vbat monitoring rc=%d\n", + rc); + goto fail_setup; + } + } + + rc = bms_request_irqs(chip); + if (rc) { + pr_err("error requesting bms irqs, rc = %d\n", rc); + goto fail_irq; + } + + battery_insertion_check(chip); + battery_status_check(chip); + + /* character device to pass data to the userspace */ + rc = register_bms_char_device(chip); + if (rc) { + pr_err("Unable to regiter '/dev/vm_bms' rc=%d\n", rc); + goto fail_bms_device; + } + + the_chip = chip; + calculate_initial_soc(chip); + if (chip->dt.cfg_battery_aging_comp) { + rc = calculate_initial_aging_comp(chip); + if (rc) + pr_err("Unable to calculate initial aging data rc=%d\n", + rc); + } + + /* setup & register the battery power supply */ + chip->bms_psy_d.name = "bms"; + chip->bms_psy_d.type = POWER_SUPPLY_TYPE_BMS; + chip->bms_psy_d.properties = bms_power_props; + chip->bms_psy_d.num_properties = ARRAY_SIZE(bms_power_props); + chip->bms_psy_d.get_property = qpnp_vm_bms_power_get_property; + chip->bms_psy_d.set_property = qpnp_vm_bms_power_set_property; + chip->bms_psy_d.external_power_changed = qpnp_vm_bms_ext_power_changed; + chip->bms_psy_d.property_is_writeable = + qpnp_vm_bms_property_is_writeable; + + bms_psy_cfg.supplied_to = qpnp_vm_bms_supplicants; + bms_psy_cfg.num_supplicants = ARRAY_SIZE(qpnp_vm_bms_supplicants); + bms_psy_cfg.drv_data = chip; + bms_psy_cfg.of_node = NULL; + bms_psy_cfg.fwnode = 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("power_supply_register bms failed rc = %ld\n", + PTR_ERR(chip->bms_psy)); + goto fail_psy; + } + chip->bms_psy_registered = true; + + chip->nb.notifier_call = bms_notifier_cb; + rc = power_supply_reg_notifier(&chip->nb); + if (rc < 0) + pr_err("Failed register psy notifier rc = %d\n", rc); + + rc = get_battery_voltage(chip, &vbatt); + if (rc) { + pr_err("error reading vbat_sns adc channel, rc=%d\n", rc); + goto fail_get_vtg; + } + + chip->debug_root = debugfs_create_dir("qpnp_vmbms", NULL); + if (!chip->debug_root) + pr_err("Couldn't create debug dir\n"); + + if (chip->debug_root) { + struct dentry *ent; + + ent = debugfs_create_file("bms_data", S_IFREG | 0444, + chip->debug_root, chip, + &bms_data_debugfs_ops); + if (!ent) + pr_err("Couldn't create bms_data debug file\n"); + + ent = debugfs_create_file("bms_config", S_IFREG | 0444, + chip->debug_root, chip, + &bms_config_debugfs_ops); + if (!ent) + pr_err("Couldn't create bms_config debug file\n"); + + ent = debugfs_create_file("bms_status", S_IFREG | 0444, + chip->debug_root, chip, + &bms_status_debugfs_ops); + if (!ent) + pr_err("Couldn't create bms_status debug file\n"); + } + + schedule_delayed_work(&chip->monitor_soc_work, 0); + pr_info("probe success: soc=%d vbatt=%d ocv=%d warm_reset=%d\n", + get_prop_bms_capacity(chip), vbatt, + chip->last_ocv_uv, chip->warm_reset); + + return rc; + +fail_get_vtg: + power_supply_unregister(chip->bms_psy); +fail_psy: + device_destroy(chip->bms_class, chip->dev_no); + cdev_del(&chip->bms_cdev); + unregister_chrdev_region(chip->dev_no, 1); +fail_bms_device: + chip->bms_psy_registered = false; +fail_irq: + reset_vbat_monitoring(chip); +fail_setup: + wakeup_source_unregister(chip->vbms_lv_wake_source.source); + wakeup_source_unregister(chip->vbms_cv_wake_source.source); + wakeup_source_unregister(chip->vbms_soc_wake_source.source); +fail_init: + mutex_destroy(&chip->bms_data_mutex); + mutex_destroy(&chip->last_soc_mutex); + mutex_destroy(&chip->state_change_mutex); + mutex_destroy(&chip->bms_device_mutex); + the_chip = NULL; + + return rc; +} + +static int qpnp_vm_bms_remove(struct platform_device *pdev) +{ + struct qpnp_bms_chip *chip = dev_get_drvdata(&pdev->dev); + + cancel_delayed_work_sync(&chip->monitor_soc_work); + debugfs_remove_recursive(chip->debug_root); + device_destroy(chip->bms_class, chip->dev_no); + cdev_del(&chip->bms_cdev); + unregister_chrdev_region(chip->dev_no, 1); + reset_vbat_monitoring(chip); + wakeup_source_unregister(chip->vbms_lv_wake_source.source); + wakeup_source_unregister(chip->vbms_cv_wake_source.source); + wakeup_source_unregister(chip->vbms_soc_wake_source.source); + mutex_destroy(&chip->bms_data_mutex); + mutex_destroy(&chip->last_soc_mutex); + mutex_destroy(&chip->state_change_mutex); + mutex_destroy(&chip->bms_device_mutex); + power_supply_unreg_notifier(&chip->nb); + power_supply_unregister(chip->bms_psy); + dev_set_drvdata(&pdev->dev, NULL); + the_chip = NULL; + + return 0; +} + +static void process_suspend_data(struct qpnp_bms_chip *chip) +{ + int rc; + + mutex_lock(&chip->bms_data_mutex); + + chip->suspend_data_valid = false; + + memset(&chip->bms_data, 0, sizeof(chip->bms_data)); + + rc = read_and_populate_fifo_data(chip); + if (rc) + pr_err("Unable to read FIFO data rc=%d\n", rc); + + rc = read_and_populate_acc_data(chip); + if (rc) + pr_err("Unable to read ACC_SD data rc=%d\n", rc); + + rc = clear_fifo_acc_data(chip); + if (rc) + pr_err("Unable to clear FIFO/ACC data rc=%d\n", rc); + + if (chip->bms_data.num_fifo || chip->bms_data.acc_count) { + pr_debug("suspend data valid\n"); + chip->suspend_data_valid = true; + } + + mutex_unlock(&chip->bms_data_mutex); +} + +static void process_resume_data(struct qpnp_bms_chip *chip) +{ + int rc, batt_temp = 0; + int old_ocv = 0; + bool ocv_updated = false; + + rc = get_batt_therm(chip, &batt_temp); + if (rc < 0) { + pr_err("Unable to read batt temp, using default=%d\n", + BMS_DEFAULT_TEMP); + batt_temp = BMS_DEFAULT_TEMP; + } + + mutex_lock(&chip->bms_data_mutex); + /* + * We can get a h/w OCV update when the sleep_b + * is low, which is possible when APPS is suspended. + * So check for an OCV update only in bms_resume + */ + old_ocv = chip->last_ocv_uv; + rc = read_and_update_ocv(chip, batt_temp, false); + if (rc) + pr_err("Unable to read/upadate OCV rc=%d\n", rc); + + if (old_ocv != chip->last_ocv_uv) { + ocv_updated = true; + /* new OCV, clear suspended data */ + chip->suspend_data_valid = false; + memset(&chip->bms_data, 0, sizeof(chip->bms_data)); + chip->calculated_soc = lookup_soc_ocv(chip, + chip->last_ocv_uv, batt_temp); + pr_debug("OCV in sleep SOC=%d\n", chip->calculated_soc); + chip->last_soc_unbound = true; + chip->voltage_soc_uv = chip->last_ocv_uv; + pr_debug("update bms_psy\n"); + power_supply_changed(chip->bms_psy); + } + + if (ocv_updated || chip->suspend_data_valid) { + /* there is data to be sent */ + pr_debug("ocv_updated=%d suspend_data_valid=%d\n", + ocv_updated, chip->suspend_data_valid); + chip->bms_data.seq_num = chip->seq_num++; + dump_bms_data(__func__, chip); + + chip->data_ready = true; + wake_up_interruptible(&chip->bms_wait_q); + if (chip->bms_dev_open) + pm_stay_awake(chip->dev); + + } + chip->suspend_data_valid = false; + mutex_unlock(&chip->bms_data_mutex); +} + +static int bms_suspend(struct device *dev) +{ + struct qpnp_bms_chip *chip = dev_get_drvdata(dev); + bool battery_charging = is_battery_charging(chip); + bool hi_power_state = is_hi_power_state_requested(chip); + bool charger_present = is_charger_present(chip); + bool bms_suspend_config; + + /* + * Keep BMS FSM active if 'cfg_force_bms_active_on_charger' property + * is present and charger inserted. This ensures that recharge + * starts once battery SOC falls below resume_soc. + */ + bms_suspend_config = chip->dt.cfg_force_bms_active_on_charger + && charger_present; + + chip->apply_suspend_config = false; + if (!battery_charging && !hi_power_state && !bms_suspend_config) + chip->apply_suspend_config = true; + + pr_debug("battery_charging=%d power_state=%s hi_power_state=0x%x apply_suspend_config=%d bms_suspend_config=%d usb_present=%d\n", + battery_charging, hi_power_state ? "hi" : "low", + chip->hi_power_state, + chip->apply_suspend_config, bms_suspend_config, + charger_present); + + if (chip->apply_suspend_config) { + if (chip->dt.cfg_force_s3_on_suspend) { + disable_bms_irq(&chip->fifo_update_done_irq); + pr_debug("Forcing S3 state\n"); + mutex_lock(&chip->state_change_mutex); + force_fsm_state(chip, S3_STATE); + mutex_unlock(&chip->state_change_mutex); + /* Store accumulated data if any */ + process_suspend_data(chip); + } + } + + cancel_delayed_work_sync(&chip->monitor_soc_work); + + return 0; +} + +static int bms_resume(struct device *dev) +{ + u8 state = 0; + int rc, monitor_soc_delay = 0; + unsigned long tm_now_sec; + struct qpnp_bms_chip *chip = dev_get_drvdata(dev); + + if (chip->apply_suspend_config) { + if (chip->dt.cfg_force_s3_on_suspend) { + /* + * Update the state to S2 only if we are in S3. There is + * a possibility of being in S2 if we resumed on + * a charger insertion + */ + mutex_lock(&chip->state_change_mutex); + rc = get_fsm_state(chip, &state); + if (rc) + pr_err("Unable to get FSM state rc=%d\n", rc); + if (rc || (state == S3_STATE)) { + pr_debug("Unforcing S3 state, setting S2 state\n"); + force_fsm_state(chip, S2_STATE); + } + mutex_unlock(&chip->state_change_mutex); + enable_bms_irq(&chip->fifo_update_done_irq); + /* + * if we were charging while suspended, we will + * be woken up by the fifo done interrupt and no + * additional processing is needed. + */ + process_resume_data(chip); + } + } + + /* Start monitor_soc_work based on when it last executed */ + rc = get_current_time(&tm_now_sec); + if (rc) { + pr_err("Could not read current time: %d\n", rc); + } else { + monitor_soc_delay = get_calculation_delay_ms(chip) - + ((tm_now_sec - chip->tm_sec) * 1000); + monitor_soc_delay = max(0, monitor_soc_delay); + } + pr_debug("monitor_soc_delay_sec=%d tm_now_sec=%ld chip->tm_sec=%ld\n", + monitor_soc_delay / 1000, tm_now_sec, chip->tm_sec); + schedule_delayed_work(&chip->monitor_soc_work, + msecs_to_jiffies(monitor_soc_delay)); + + return 0; +} + +static const struct dev_pm_ops qpnp_vm_bms_pm_ops = { + .suspend = bms_suspend, + .resume = bms_resume, +}; + +static const struct of_device_id qpnp_vm_bms_match_table[] = { + { .compatible = QPNP_VM_BMS_DEV_NAME }, + {} +}; + +static struct platform_driver qpnp_vm_bms_driver = { + .probe = qpnp_vm_bms_probe, + .remove = qpnp_vm_bms_remove, + .driver = { + .name = QPNP_VM_BMS_DEV_NAME, + .of_match_table = qpnp_vm_bms_match_table, + .pm = &qpnp_vm_bms_pm_ops, + }, +}; +module_platform_driver(qpnp_vm_bms_driver); + +MODULE_DESCRIPTION("QPNP VM-BMS Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" QPNP_VM_BMS_DEV_NAME); diff --git a/drivers/power/supply/qcom/smb1360-charger-fg.c b/drivers/power/supply/qcom/smb1360-charger-fg.c new file mode 100644 index 000000000000..afbe825003ce --- /dev/null +++ b/drivers/power/supply/qcom/smb1360-charger-fg.c @@ -0,0 +1,5444 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2013-2015, 2018-2021, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "SMB:%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define _SMB1360_MASK(BITS, POS) \ + ((unsigned char)(((1 << (BITS)) - 1) << (POS))) +#define SMB1360_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \ + _SMB1360_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \ + (RIGHT_BIT_POS)) + +/* Charger Registers */ +#define CFG_BATT_CHG_REG 0x00 +#define CHG_ITERM_MASK SMB1360_MASK(2, 0) +#define CHG_ITERM_25MA 0x0 +#define CHG_ITERM_200MA 0x7 +#define RECHG_MV_MASK SMB1360_MASK(6, 5) +#define RECHG_MV_SHIFT 5 +#define OTG_CURRENT_MASK SMB1360_MASK(4, 3) +#define OTG_CURRENT_SHIFT 3 + +#define CFG_BATT_CHG_ICL_REG 0x05 +#define AC_INPUT_ICL_PIN_BIT BIT(7) +#define AC_INPUT_PIN_HIGH_BIT BIT(6) +#define RESET_STATE_USB_500 BIT(5) +#define INPUT_CURR_LIM_MASK SMB1360_MASK(3, 0) +#define INPUT_CURR_LIM_300MA 0x0 + +#define CFG_GLITCH_FLT_REG 0x06 +#define AICL_ENABLED_BIT BIT(0) +#define INPUT_UV_GLITCH_FLT_20MS_BIT BIT(7) + +#define CFG_CHG_MISC_REG 0x7 +#define CHG_EN_BY_PIN_BIT BIT(7) +#define CHG_EN_ACTIVE_LOW_BIT BIT(6) +#define PRE_TO_FAST_REQ_CMD_BIT BIT(5) +#define CFG_BAT_OV_ENDS_CHG_CYC BIT(4) +#define CHG_CURR_TERM_DIS_BIT BIT(3) +#define CFG_AUTO_RECHG_DIS_BIT BIT(2) +#define CFG_CHG_INHIBIT_EN_BIT BIT(0) + +#define CFG_CHG_FUNC_CTRL_REG 0x08 +#define CHG_RECHG_THRESH_FG_SRC_BIT BIT(1) + +#define CFG_STAT_CTRL_REG 0x09 +#define CHG_STAT_IRQ_ONLY_BIT BIT(4) +#define CHG_TEMP_CHG_ERR_BLINK_BIT BIT(3) +#define CHG_STAT_ACTIVE_HIGH_BIT BIT(1) +#define CHG_STAT_DISABLE_BIT BIT(0) + +#define CFG_SFY_TIMER_CTRL_REG 0x0A +#define SAFETY_TIME_DISABLE_BIT BIT(5) +#define SAFETY_TIME_MINUTES_SHIFT 2 +#define SAFETY_TIME_MINUTES_MASK SMB1360_MASK(3, 2) + +#define CFG_BATT_MISSING_REG 0x0D +#define BATT_MISSING_SRC_THERM_BIT BIT(1) + +#define CFG_FG_BATT_CTRL_REG 0x0E +#define CFG_FG_OTP_BACK_UP_ENABLE BIT(7) +#define BATT_ID_ENABLED_BIT BIT(5) +#define CHG_BATT_ID_FAIL BIT(4) +#define BATT_ID_FAIL_SELECT_PROFILE BIT(3) +#define BATT_PROFILE_SELECT_MASK SMB1360_MASK(3, 0) +#define BATT_PROFILEA_MASK 0x0 +#define BATT_PROFILEB_MASK 0xF + +#define IRQ_CFG_REG 0x0F +#define IRQ_BAT_HOT_COLD_HARD_BIT BIT(7) +#define IRQ_BAT_HOT_COLD_SOFT_BIT BIT(6) +#define IRQ_DCIN_UV_BIT BIT(2) +#define IRQ_AICL_DONE_BIT BIT(1) +#define IRQ_INTERNAL_TEMPERATURE_BIT BIT(0) + +#define IRQ2_CFG_REG 0x10 +#define IRQ2_SAFETY_TIMER_BIT BIT(7) +#define IRQ2_CHG_ERR_BIT BIT(6) +#define IRQ2_CHG_PHASE_CHANGE_BIT BIT(4) +#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 0x11 +#define IRQ3_FG_ACCESS_OK_BIT BIT(6) +#define IRQ3_SOC_CHANGE_BIT BIT(4) +#define IRQ3_SOC_MIN_BIT BIT(3) +#define IRQ3_SOC_MAX_BIT BIT(2) +#define IRQ3_SOC_EMPTY_BIT BIT(1) +#define IRQ3_SOC_FULL_BIT BIT(0) + +#define CHG_CURRENT_REG 0x13 +#define FASTCHG_CURR_MASK SMB1360_MASK(4, 2) +#define FASTCHG_CURR_SHIFT 2 + +#define CHG_CMP_CFG 0x14 +#define JEITA_COMP_CURR_MASK SMB1360_MASK(3, 0) +#define JEITA_COMP_EN_MASK SMB1360_MASK(7, 4) +#define JEITA_COMP_EN_SHIFT 4 +#define JEITA_COMP_EN_BIT SMB1360_MASK(7, 4) +#define BATT_CHG_FLT_VTG_REG 0x15 +#define VFLOAT_MASK SMB1360_MASK(6, 0) +#define CFG_FVC_REG 0x16 +#define FLT_VTG_COMP_MASK SMB1360_MASK(6, 0) + +#define SHDN_CTRL_REG 0x1A +#define SHDN_CMD_USE_BIT BIT(1) +#define SHDN_CMD_POLARITY_BIT BIT(2) + +#define CURRENT_GAIN_LSB_REG 0x1D +#define CURRENT_GAIN_MSB_REG 0x1E + +/* Command Registers */ +#define CMD_I2C_REG 0x40 +#define ALLOW_VOLATILE_BIT BIT(6) +#define FG_ACCESS_ENABLED_BIT BIT(5) +#define FG_RESET_BIT BIT(4) +#define CYCLE_STRETCH_CLEAR_BIT BIT(3) + +#define CMD_IL_REG 0x41 +#define USB_CTRL_MASK SMB1360_MASK(1, 0) +#define USB_100_BIT 0x01 +#define USB_500_BIT 0x00 +#define USB_AC_BIT 0x02 +#define SHDN_CMD_BIT BIT(7) + +#define CMD_CHG_REG 0x42 +#define CMD_CHG_EN BIT(1) +#define CMD_OTG_EN_BIT BIT(0) + +/* Status Registers */ +#define STATUS_1_REG 0x48 +#define AICL_CURRENT_STATUS_MASK SMB1360_MASK(6, 0) +#define AICL_LIMIT_1500MA 0xF + +#define STATUS_3_REG 0x4B +#define CHG_HOLD_OFF_BIT BIT(3) +#define CHG_TYPE_MASK SMB1360_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_4_REG 0x4C +#define CYCLE_STRETCH_ACTIVE_BIT BIT(5) + +#define REVISION_CTRL_REG 0x4F +#define DEVICE_REV_MASK SMB1360_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_C_REG 0x52 +#define IRQ_C_CHG_TERM BIT(0) + +#define IRQ_D_REG 0x53 +#define IRQ_E_REG 0x54 +#define IRQ_E_USBIN_UV_BIT BIT(0) + +#define IRQ_F_REG 0x55 + +#define IRQ_G_REG 0x56 + +#define IRQ_H_REG 0x57 +#define IRQ_I_REG 0x58 +#define FG_ACCESS_ALLOWED_BIT BIT(0) +#define BATT_ID_RESULT_BIT SMB1360_MASK(6, 4) +#define BATT_ID_SHIFT 4 + +/* FG registers - IRQ config register */ +#define SOC_MAX_REG 0x24 +#define SOC_MIN_REG 0x25 +#define VTG_EMPTY_REG 0x26 +#define SOC_DELTA_REG 0x28 +#define JEITA_SOFT_COLD_REG 0x29 +#define JEITA_SOFT_HOT_REG 0x2A +#define VTG_MIN_REG 0x2B + +/* FG SHADOW registers */ +#define SHDW_FG_ESR_ACTUAL 0x20 +#define SHDW_FG_BATT_STATUS 0x60 +#define BATTERY_PROFILE_BIT BIT(0) + +#define SHDW_FG_MSYS_SOC 0x61 +#define SHDW_FG_CAPACITY 0x62 +#define SHDW_FG_VTG_NOW 0x69 +#define SHDW_FG_CURR_NOW 0x6B +#define SHDW_FG_BATT_TEMP 0x6D + +#define VOLTAGE_PREDICTED_REG 0x80 +#define CC_TO_SOC_COEFF 0xBA +#define NOMINAL_CAPACITY_REG 0xBC +#define ACTUAL_CAPACITY_REG 0xBE +#define FG_AUTO_RECHARGE_SOC 0xD2 +#define FG_SYS_CUTOFF_V_REG 0xD3 +#define FG_CC_TO_CV_V_REG 0xD5 +#define FG_ITERM_REG 0xD9 +#define FG_THERM_C1_COEFF_REG 0xDB +#define FG_IBATT_STANDBY_REG 0xCF + +#define FG_I2C_CFG_MASK SMB1360_MASK(2, 1) +#define FG_CFG_I2C_ADDR 0x2 +#define FG_PROFILE_A_ADDR 0x4 +#define FG_PROFILE_B_ADDR 0x6 + +/* Constants */ +#define CURRENT_100_MA 100 +#define CURRENT_500_MA 500 +#define MAX_8_BITS 255 +#define JEITA_WORK_MS 3000 + +#define FG_RESET_THRESHOLD_MV 15 +#define SMB1360_REV_1 0x01 + +#define SMB1360_POWERON_DELAY_MS 2000 +#define SMB1360_FG_RESET_DELAY_MS 1500 + +enum { + WRKRND_FG_CONFIG_FAIL = BIT(0), + WRKRND_BATT_DET_FAIL = BIT(1), + WRKRND_USB100_FAIL = BIT(2), + WRKRND_HARD_JEITA = BIT(3), +}; + +enum { + USER = BIT(0), +}; + +enum { + PARALLEL_USER = BIT(0), + PARALLEL_CURRENT = BIT(1), + PARALLEL_JEITA_SOFT = BIT(2), + PARALLEL_JEITA_HARD = BIT(3), + PARALLEL_EOC = BIT(4), +}; + +enum fg_i2c_access_type { + FG_ACCESS_CFG = 0x1, + FG_ACCESS_PROFILE_A = 0x2, + FG_ACCESS_PROFILE_B = 0x3 +}; + +enum { + BATTERY_PROFILE_A, + BATTERY_PROFILE_B, + BATTERY_PROFILE_MAX, +}; + +static int otg_curr_ma[] = {350, 550, 950, 1500}; + +struct otp_backup_pool { + u8 reg_start; + u8 reg_end; + u8 start_now; + u16 alg_bitmap; + bool initialized; + struct mutex lock; +}; + +enum otp_backup_alg { + OTP_BACKUP_NOT_USE = 0, + OTP_BACKUP_FG_USE, + OTP_BACKUP_PROF_A_USE, + OTP_BACKUP_PROF_B_USE, +}; + +struct smb1360_otg_regulator { + struct regulator_desc rdesc; + struct regulator_dev *rdev; +}; + +enum wakeup_src { + WAKEUP_SRC_FG_ACCESS = 0, + WAKEUP_SRC_JEITA_SOFT, + WAKEUP_SRC_PARALLEL, + WAKEUP_SRC_MIN_SOC, + WAKEUP_SRC_EMPTY_SOC, + WAKEUP_SRC_JEITA_HYSTERSIS, + WAKEUP_SRC_MAX, +}; +#define WAKEUP_SRC_MASK (~(~0 << WAKEUP_SRC_MAX)) + +struct smb1360_wakeup_source { + struct wakeup_source *source; + unsigned long enabled_bitmap; + spinlock_t ws_lock; +}; + +static const unsigned int smb1360_extcon_cable[] = { + EXTCON_USB, + EXTCON_USB_HOST, + EXTCON_NONE, +}; + +struct smb1360_chip { + struct i2c_client *client; + struct device *dev; + u8 revision; + u8 soft_hot_rt_stat; + u8 soft_cold_rt_stat; + struct delayed_work jeita_work; + struct delayed_work delayed_init_work; + unsigned short default_i2c_addr; + unsigned short fg_i2c_addr; + bool pulsed_irq; + struct completion fg_mem_access_granted; + + /* wakeup source */ + struct smb1360_wakeup_source smb1360_ws; + + /* configuration data - charger */ + int fake_battery_soc; + bool batt_id_disabled; + bool charging_disabled; + bool recharge_disabled; + bool chg_inhibit_disabled; + bool iterm_disabled; + bool shdn_after_pwroff; + bool config_hard_thresholds; + bool soft_jeita_supported; + bool ov_ends_chg_cycle_disabled; + int iterm_ma; + int vfloat_mv; + int safety_time; + int resume_delta_mv; + u32 default_batt_profile; + unsigned int thermal_levels; + unsigned int therm_lvl_sel; + unsigned int *thermal_mitigation; + int otg_batt_curr_limit; + bool min_icl_usb100; + int cold_bat_decidegc; + int hot_bat_decidegc; + int cool_bat_decidegc; + int warm_bat_decidegc; + int cool_bat_mv; + int warm_bat_mv; + int cool_bat_ma; + int warm_bat_ma; + int soft_cold_thresh; + int soft_hot_thresh; + + /* parallel-chg params */ + int fastchg_current; + int parallel_chg_disable_status; + int max_parallel_chg_current; + bool parallel_charging; + + /* configuration data - fg */ + int soc_max; + int soc_min; + int delta_soc; + int voltage_min_mv; + int voltage_empty_mv; + int batt_capacity_mah; + int cc_soc_coeff; + int v_cutoff_mv; + int fg_iterm_ma; + int fg_ibatt_standby_ma; + int fg_thermistor_c1_coeff; + int fg_cc_to_cv_mv; + int fg_auto_recharge_soc; + bool empty_soc_disabled; + int fg_reset_threshold_mv; + bool fg_reset_at_pon; + bool rsense_10mohm; + bool otg_fet_present; + bool fet_gain_enabled; + int otg_fet_enable_gpio; + int usb_id_gpio; + + /* status tracking */ + int voltage_now; + int current_now; + int resistance_now; + int temp_now; + int soc_now; + int fcc_mah; + bool usb_present; + bool batt_present; + bool batt_hot; + bool batt_cold; + bool batt_warm; + bool batt_cool; + bool batt_full; + bool resume_completed; + bool irq_waiting; + bool irq_disabled; + bool empty_soc; + bool awake_min_soc; + int workaround_flags; + u8 irq_cfg_mask[3]; + int usb_psy_ma; + int charging_disabled_status; + u32 connected_rid; + u32 profile_rid[BATTERY_PROFILE_MAX]; + + u32 peek_poke_address; + u32 fg_access_type; + u32 fg_peek_poke_address; + int skip_writes; + int skip_reads; + enum power_supply_type usb_supply_type; + struct dentry *debug_root; + + struct iio_channel *lr_mux2_batt_id; + struct power_supply *parallel_psy; + struct power_supply_desc parallel_psy_d; + struct power_supply *usb_psy; + struct power_supply_desc usb_psy_d; + struct power_supply *batt_psy; + struct power_supply_desc batt_psy_d; + struct smb1360_otg_regulator otg_vreg; + struct mutex irq_complete; + struct mutex charging_disable_lock; + struct mutex current_change_lock; + struct mutex read_write_lock; + struct mutex parallel_chg_lock; + struct work_struct parallel_work; + struct mutex otp_gain_lock; + struct mutex fg_access_request_lock; + struct otp_backup_pool otp_backup; + u8 current_gain_otp_reg; + bool otp_hard_jeita_config; + int otp_cold_bat_decidegc; + int otp_hot_bat_decidegc; + u8 hard_jeita_otp_reg; + struct work_struct jeita_hysteresis_work; + int cold_hysteresis; + int hot_hysteresis; + struct extcon_dev *extcon; + int usb_id_irq; +}; + +static int chg_time[] = { + 192, + 384, + 768, + 1536, +}; + +static int input_current_limit[] = { + 300, 400, 450, 500, 600, 700, 800, 850, 900, + 950, 1000, 1100, 1200, 1300, 1400, 1500, +}; + +static int fastchg_current[] = { + 450, 600, 750, 900, 1050, 1200, 1350, 1500, +}; + +static void smb1360_stay_awake(struct smb1360_wakeup_source *source, + enum wakeup_src wk_src) +{ + unsigned long flags; + + spin_lock_irqsave(&source->ws_lock, flags); + + if (!__test_and_set_bit(wk_src, &source->enabled_bitmap)) { + __pm_stay_awake(source->source); + pr_debug("enabled source %s, wakeup_src %d\n", + source->source->name, wk_src); + } + spin_unlock_irqrestore(&source->ws_lock, flags); +} + +static void smb1360_relax(struct smb1360_wakeup_source *source, + enum wakeup_src wk_src) +{ + unsigned long flags; + + spin_lock_irqsave(&source->ws_lock, flags); + if (__test_and_clear_bit(wk_src, &source->enabled_bitmap) && + !(source->enabled_bitmap & WAKEUP_SRC_MASK)) { + __pm_relax(source->source); + pr_debug("disabled source %s\n", source->source->name); + } + spin_unlock_irqrestore(&source->ws_lock, flags); + + pr_debug("relax source %s, wakeup_src %d\n", + source->source->name, wk_src); +} + +static void smb1360_wakeup_src_init(struct smb1360_chip *chip) +{ + spin_lock_init(&chip->smb1360_ws.ws_lock); + chip->smb1360_ws.source = wakeup_source_register(NULL, "smb1360"); +} + +static int is_between(int value, int left, int right) +{ + if (left >= right && left >= value && value >= right) + return 1; + if (left <= right && left <= value && value <= right) + return 1; + + return 0; +} + +static int bound(int val, int min, int max) +{ + if (val < min) + return min; + if (val > max) + return max; + + return val; +} + +static int __smb1360_read(struct smb1360_chip *chip, int reg, + u8 *val) +{ + s32 ret; + + ret = i2c_smbus_read_byte_data(chip->client, reg); + if (ret < 0) { + dev_err(chip->dev, + "i2c read fail: can't read from %02x: %d\n", reg, ret); + return ret; + } + *val = ret; + pr_debug("Reading 0x%02x=0x%02x\n", reg, *val); + + return 0; +} + +static int __smb1360_write(struct smb1360_chip *chip, int reg, + u8 val) +{ + s32 ret; + + ret = i2c_smbus_write_byte_data(chip->client, reg, val); + 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 smb1360_read(struct smb1360_chip *chip, int reg, + u8 *val) +{ + int rc; + + if (chip->skip_reads) { + *val = 0; + return 0; + } + mutex_lock(&chip->read_write_lock); + rc = __smb1360_read(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb1360_write(struct smb1360_chip *chip, int reg, + u8 val) +{ + int rc; + + if (chip->skip_writes) + return 0; + + mutex_lock(&chip->read_write_lock); + rc = __smb1360_write(chip, reg, val); + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb1360_fg_read(struct smb1360_chip *chip, int reg, + u8 *val) +{ + int rc; + + if (chip->skip_reads) { + *val = 0; + return 0; + } + + mutex_lock(&chip->read_write_lock); + chip->client->addr = chip->fg_i2c_addr; + rc = __smb1360_read(chip, reg, val); + chip->client->addr = chip->default_i2c_addr; + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb1360_fg_write(struct smb1360_chip *chip, int reg, + u8 val) +{ + int rc; + + if (chip->skip_writes) + return 0; + + mutex_lock(&chip->read_write_lock); + chip->client->addr = chip->fg_i2c_addr; + rc = __smb1360_write(chip, reg, val); + chip->client->addr = chip->default_i2c_addr; + mutex_unlock(&chip->read_write_lock); + + return rc; +} + +static int smb1360_read_bytes(struct smb1360_chip *chip, int reg, + u8 *val, u8 bytes) +{ + s32 rc; + + if (chip->skip_reads) { + *val = 0; + return 0; + } + + mutex_lock(&chip->read_write_lock); + rc = i2c_smbus_read_i2c_block_data(chip->client, reg, bytes, val); + if (rc < 0) + dev_err(chip->dev, + "i2c read fail: can't read %d bytes from %02x: %d\n", + bytes, reg, rc); + mutex_unlock(&chip->read_write_lock); + + return (rc < 0) ? rc : 0; +} + +static int smb1360_write_bytes(struct smb1360_chip *chip, int reg, + u8 *val, u8 bytes) +{ + s32 rc; + + if (chip->skip_writes) { + *val = 0; + return 0; + } + + mutex_lock(&chip->read_write_lock); + rc = i2c_smbus_write_i2c_block_data(chip->client, reg, bytes, val); + if (rc < 0) + dev_err(chip->dev, + "i2c write fail: can't read %d bytes from %02x: %d\n", + bytes, reg, rc); + mutex_unlock(&chip->read_write_lock); + + return (rc < 0) ? rc : 0; +} + +static int smb1360_masked_write(struct smb1360_chip *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 = __smb1360_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 = __smb1360_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 smb1360_select_fg_i2c_address(struct smb1360_chip *chip) +{ + unsigned short addr = chip->default_i2c_addr << 0x1; + + switch (chip->fg_access_type) { + case FG_ACCESS_CFG: + addr = (addr & ~FG_I2C_CFG_MASK) | FG_CFG_I2C_ADDR; + break; + case FG_ACCESS_PROFILE_A: + addr = (addr & ~FG_I2C_CFG_MASK) | FG_PROFILE_A_ADDR; + break; + case FG_ACCESS_PROFILE_B: + addr = (addr & ~FG_I2C_CFG_MASK) | FG_PROFILE_B_ADDR; + break; + default: + pr_err("Invalid FG access type=%d\n", chip->fg_access_type); + return -EINVAL; + } + + chip->fg_i2c_addr = addr >> 0x1; + pr_debug("FG_access_type=%d fg_i2c_addr=%x\n", chip->fg_access_type, + chip->fg_i2c_addr); + + return 0; +} + +#define EXPONENT_MASK 0xF800 +#define MANTISSA_MASK 0x3FF +#define SIGN_MASK 0x400 +#define EXPONENT_SHIFT 11 +#define SIGN_SHIFT 10 +#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_MASK); + + 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 MAX_MANTISSA (1023 * 1000000ULL) +static unsigned int float_encode(int64_t float_val) +{ + int exponent = 0, sign = 0; + unsigned int final_val = 0; + + if (float_val == 0) + return 0; + + if (float_val < 0) { + sign = 1; + float_val = -float_val; + } + + /* Reduce large mantissa until it fits into 10 bit */ + while (float_val >= MAX_MANTISSA) { + exponent++; + float_val >>= 1; + } + + /* Increase small mantissa to improve precision */ + while (float_val < MAX_MANTISSA && exponent > -25) { + exponent--; + float_val <<= 1; + } + + exponent = exponent + 25; + + /* Convert mantissa from micro-units to units */ + float_val = div_s64((float_val + MICRO_UNIT), (int)MICRO_UNIT); + + if (float_val == 1024) { + exponent--; + float_val <<= 1; + } + + float_val -= 1024; + + /* Ensure that resulting number is within range */ + if (float_val > MANTISSA_MASK) + float_val = MANTISSA_MASK; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + final_val = (float_val & MANTISSA_MASK) | (sign << SIGN_SHIFT) | + ((exponent << EXPONENT_SHIFT) & EXPONENT_MASK); + + return final_val; +} + +/* FG reset could only be done after FG access being granted */ +static int smb1360_force_fg_reset(struct smb1360_chip *chip) +{ + int rc; + + rc = smb1360_masked_write(chip, CMD_I2C_REG, FG_RESET_BIT, + FG_RESET_BIT); + if (rc) { + pr_err("Couldn't reset FG rc=%d\n", rc); + return rc; + } + + msleep(SMB1360_FG_RESET_DELAY_MS); + + rc = smb1360_masked_write(chip, CMD_I2C_REG, FG_RESET_BIT, 0); + if (rc) + pr_err("Couldn't un-reset FG rc=%d\n", rc); + + return rc; +} + +/* + * Requesting FG access relys on the FG_ACCESS_ALLOWED IRQ. + * This function can only be called after interrupt handler + * being installed successfully. + */ +#define SMB1360_FG_ACCESS_TIMEOUT_MS 5000 +#define SMB1360_FG_ACCESS_RETRY_COUNT 3 +static int smb1360_enable_fg_access(struct smb1360_chip *chip) +{ + int rc = 0; + u8 reg, retry = SMB1360_FG_ACCESS_RETRY_COUNT; + + pr_debug("request FG memory access\n"); + /* + * read the ACCESS_ALLOW status bit firstly to + * check if the access was granted before + */ + mutex_lock(&chip->fg_access_request_lock); + smb1360_stay_awake(&chip->smb1360_ws, WAKEUP_SRC_FG_ACCESS); + rc = smb1360_read(chip, IRQ_I_REG, ®); + if (rc) { + pr_err("Couldn't read IRQ_I_REG, rc=%d\n", rc); + goto bail_i2c; + } else if (reg & FG_ACCESS_ALLOWED_BIT) { + pr_debug("FG access was granted\n"); + goto bail_i2c; + } + + /* request FG access */ + rc = smb1360_masked_write(chip, CMD_I2C_REG, FG_ACCESS_ENABLED_BIT, + FG_ACCESS_ENABLED_BIT); + if (rc) { + pr_err("Couldn't enable FG access rc=%d\n", rc); + goto bail_i2c; + } + + while (retry--) { + rc = wait_for_completion_interruptible_timeout( + &chip->fg_mem_access_granted, + msecs_to_jiffies(SMB1360_FG_ACCESS_TIMEOUT_MS)); + if (rc <= 0) + pr_debug("FG access timeout, retry: %d\n", retry); + else + break; + } + if (rc == 0) /* timed out */ + rc = -ETIMEDOUT; + else if (rc > 0) /* completed */ + rc = 0; + + /* Clear the FG access bit if request failed */ + if (rc < 0) { + rc = smb1360_masked_write(chip, CMD_I2C_REG, + FG_ACCESS_ENABLED_BIT, 0); + if (rc) + pr_err("Couldn't disable FG access rc=%d\n", rc); + } + +bail_i2c: + smb1360_relax(&chip->smb1360_ws, WAKEUP_SRC_FG_ACCESS); + mutex_unlock(&chip->fg_access_request_lock); + return rc; +} + +static inline bool is_device_suspended(struct smb1360_chip *chip) +{ + return !chip->resume_completed; +} + +static int smb1360_disable_fg_access(struct smb1360_chip *chip) +{ + int rc; + + rc = smb1360_masked_write(chip, CMD_I2C_REG, FG_ACCESS_ENABLED_BIT, 0); + if (rc) + pr_err("Couldn't disable FG access rc=%d\n", rc); + + init_completion(&chip->fg_mem_access_granted); + + return rc; +} + +static int smb1360_enable_volatile_writes(struct smb1360_chip *chip) +{ + int rc; + + rc = smb1360_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 void smb1360_otp_backup_pool_init(struct smb1360_chip *chip) +{ + struct otp_backup_pool *pool = &chip->otp_backup; + + pool->reg_start = 0xE0; + pool->reg_end = 0xEF; + pool->start_now = pool->reg_start; + mutex_init(&pool->lock); +} + +static int smb1360_alloc_otp_backup_register(struct smb1360_chip *chip, + u8 size, int usage) +{ + int rc = 0, i; + u8 inv_pos; + struct otp_backup_pool *pool = &chip->otp_backup; + + if (size % 2) { + pr_err("Must be allocated with pairs\n"); + return -EINVAL; + } + + mutex_lock(&pool->lock); + if (pool->start_now + size > pool->reg_end) { + pr_err("Allocation fail: start = 0x%x, size = %d\n", + pool->start_now, size); + mutex_unlock(&pool->lock); + return -EBUSY; + } + rc = pool->start_now; + inv_pos = pool->reg_end - pool->start_now + 1; + for (i = 0; i < size; i = i + 2) { + inv_pos -= (i ? 2 : 0); + pool->alg_bitmap |= usage << (inv_pos - 2); + } + pr_debug("Allocation success, start = 0x%x, size = %d, alg_bitmap = 0x%x\n", + rc, size, pool->alg_bitmap); + pool->start_now += size; + mutex_unlock(&pool->lock); + + return rc; +} + +#define OTP_BACKUP_WA_ALG_1 0xF0 +#define OTP_BACKUP_WA_ALG_2 0xF1 +static int smb1360_otp_backup_alg_update(struct smb1360_chip *chip) +{ + int rc = 0; + struct otp_backup_pool *pool = &chip->otp_backup; + + mutex_lock(&pool->lock); + rc = smb1360_fg_write(chip, OTP_BACKUP_WA_ALG_1, + (u8)(pool->alg_bitmap >> 8)); + rc |= smb1360_fg_write(chip, OTP_BACKUP_WA_ALG_2, + (u8)(pool->alg_bitmap)); + if (rc) + pr_err("Write FG address F0/F1 failed, rc = %d\n", rc); + mutex_unlock(&pool->lock); + + return rc; +} + +#define TRIM_1C_REG 0x1C +#define CHECK_USB100_GOOD_BIT BIT(6) +static bool is_usb100_broken(struct smb1360_chip *chip) +{ + int rc; + u8 reg; + + rc = smb1360_read(chip, TRIM_1C_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read trim 1C reg rc = %d\n", rc); + return rc; + } + return !!(reg & CHECK_USB100_GOOD_BIT); +} + +static int read_revision(struct smb1360_chip *chip, u8 *revision) +{ + int rc; + + *revision = 0; + rc = smb1360_read(chip, REVISION_CTRL_REG, revision); + if (rc) + dev_err(chip->dev, "Couldn't read REVISION_CTRL_REG rc=%d\n", + rc); + + *revision &= DEVICE_REV_MASK; + + return rc; +} + +#define MIN_FLOAT_MV 3460 +#define MAX_FLOAT_MV 4730 +#define VFLOAT_STEP_MV 10 +static int smb1360_float_voltage_set(struct smb1360_chip *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; + } + + temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV; + + return smb1360_masked_write(chip, BATT_CHG_FLT_VTG_REG, + VFLOAT_MASK, temp); +} + +#define MIN_RECHG_MV 50 +#define MAX_RECHG_MV 300 +static int smb1360_recharge_threshold_set(struct smb1360_chip *chip, + int resume_mv) +{ + u8 temp; + + if ((resume_mv < MIN_RECHG_MV) || (resume_mv > MAX_RECHG_MV)) { + dev_err(chip->dev, "bad rechg_thrsh =%d asked to set\n", + resume_mv); + return -EINVAL; + } + + temp = resume_mv / 100; + + return smb1360_masked_write(chip, CFG_BATT_CHG_REG, + RECHG_MV_MASK, temp << RECHG_MV_SHIFT); +} + +static int __smb1360_charging_disable(struct smb1360_chip *chip, bool disable) +{ + int rc; + + rc = smb1360_masked_write(chip, CMD_CHG_REG, + CMD_CHG_EN, disable ? 0 : CMD_CHG_EN); + if (rc < 0) + pr_err("Couldn't set CHG_ENABLE_BIT disable=%d rc = %d\n", + disable, rc); + else + pr_debug("CHG_EN status=%d\n", !disable); + + return rc; +} + +static int smb1360_charging_disable(struct smb1360_chip *chip, int reason, + int disable) +{ + int rc = 0; + int disabled; + + mutex_lock(&chip->charging_disable_lock); + + disabled = chip->charging_disabled_status; + + pr_debug("reason=%d requested_disable=%d disabled_status=%d\n", + reason, disable, disabled); + + if (disable == true) + disabled |= reason; + else + disabled &= ~reason; + + if (disabled) + rc = __smb1360_charging_disable(chip, true); + else + rc = __smb1360_charging_disable(chip, false); + + if (rc) + pr_err("Couldn't disable charging for reason=%d rc=%d\n", + rc, reason); + else + chip->charging_disabled_status = disabled; + + mutex_unlock(&chip->charging_disable_lock); + + return rc; +} + +static int smb1360_soft_jeita_comp_enable(struct smb1360_chip *chip, + bool enable) +{ + int rc = 0; + + rc = smb1360_masked_write(chip, CHG_CMP_CFG, JEITA_COMP_EN_MASK, + enable ? JEITA_COMP_EN_BIT : 0); + if (rc) + pr_err("Couldn't %s JEITA compensation\n", enable ? + "enable" : "disable"); + + return rc; +} + +static enum power_supply_property smb1360_battery_properties[] = { + POWER_SUPPLY_PROP_HEALTH, + 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_CHARGE_FULL_DESIGN, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_RESISTANCE, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, +}; + +static int smb1360_get_prop_batt_present(struct smb1360_chip *chip) +{ + return chip->batt_present; +} + +static int smb1360_get_prop_batt_status(struct smb1360_chip *chip) +{ + int rc; + u8 reg = 0, chg_type; + + if (is_device_suspended(chip)) + return POWER_SUPPLY_STATUS_UNKNOWN; + + if (chip->batt_full) + return POWER_SUPPLY_STATUS_FULL; + + rc = smb1360_read(chip, STATUS_3_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_3_REG rc=%d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + pr_debug("STATUS_3_REG = %x\n", reg); + + if (reg & CHG_HOLD_OFF_BIT) + return POWER_SUPPLY_STATUS_NOT_CHARGING; + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + + if (chg_type == BATT_NOT_CHG_VAL) + return POWER_SUPPLY_STATUS_DISCHARGING; + else + return POWER_SUPPLY_STATUS_CHARGING; +} + +static int smb1360_get_prop_charge_type(struct smb1360_chip *chip) +{ + int rc; + u8 reg = 0; + u8 chg_type; + + if (is_device_suspended(chip)) + return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN; + + rc = smb1360_read(chip, STATUS_3_REG, ®); + if (rc) { + pr_err("Couldn't read STATUS_3_REG rc=%d\n", rc); + 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) || + (chg_type == BATT_TAPER_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 smb1360_get_prop_batt_health(struct smb1360_chip *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 smb1360_get_prop_batt_capacity(struct smb1360_chip *chip) +{ + u8 reg; + u32 temp = 0; + int rc, soc = 0; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + if (chip->empty_soc) { + pr_debug("empty_soc\n"); + return 0; + } + + if (is_device_suspended(chip)) + return chip->soc_now; + + rc = smb1360_read(chip, SHDW_FG_MSYS_SOC, ®); + if (rc) { + pr_err("Failed to read FG_MSYS_SOC rc=%d\n", rc); + return rc; + } + soc = (100 * reg) / MAX_8_BITS; + + temp = (100 * reg) % MAX_8_BITS; + if (temp > (MAX_8_BITS / 2)) + soc += 1; + + pr_debug("msys_soc_reg=0x%02x, fg_soc=%d batt_full = %d\n", reg, + soc, chip->batt_full); + + chip->soc_now = (chip->batt_full ? 100 : bound(soc, 0, 100)); + + return chip->soc_now; +} + +static int smb1360_get_prop_chg_full_design(struct smb1360_chip *chip) +{ + u8 reg[2]; + int rc, fcc_mah = 0; + + if (is_device_suspended(chip)) + return chip->fcc_mah; + + rc = smb1360_read_bytes(chip, SHDW_FG_CAPACITY, reg, 2); + if (rc) { + pr_err("Failed to read SHDW_FG_CAPACITY rc=%d\n", rc); + return rc; + } + fcc_mah = (reg[1] << 8) | reg[0]; + + pr_debug("reg[0]=0x%02x reg[1]=0x%02x fcc_mah=%d\n", + reg[0], reg[1], fcc_mah); + + chip->fcc_mah = fcc_mah * 1000; + + return chip->fcc_mah; +} + +static int smb1360_get_prop_batt_temp(struct smb1360_chip *chip) +{ + u8 reg[2]; + int rc, temp = 0; + + if (is_device_suspended(chip)) + return chip->temp_now; + + rc = smb1360_read_bytes(chip, SHDW_FG_BATT_TEMP, reg, 2); + if (rc) { + pr_err("Failed to read SHDW_FG_BATT_TEMP rc=%d\n", rc); + return rc; + } + + temp = (reg[1] << 8) | reg[0]; + temp = div_u64(temp * 625, 10000UL); /* temperature in kelvin */ + temp = (temp - 273) * 10; /* temperature in decideg */ + + pr_debug("reg[0]=0x%02x reg[1]=0x%02x temperature=%d\n", + reg[0], reg[1], temp); + + chip->temp_now = temp; + + return chip->temp_now; +} + +static int smb1360_get_prop_voltage_now(struct smb1360_chip *chip) +{ + u8 reg[2]; + int rc, temp = 0; + + if (is_device_suspended(chip)) + return chip->voltage_now; + + rc = smb1360_read_bytes(chip, SHDW_FG_VTG_NOW, reg, 2); + if (rc) { + pr_err("Failed to read SHDW_FG_VTG_NOW rc=%d\n", rc); + return rc; + } + + temp = (reg[1] << 8) | reg[0]; + temp = div_u64(temp * 5000, 0x7FFF); + + pr_debug("reg[0]=0x%02x reg[1]=0x%02x voltage=%d\n", + reg[0], reg[1], temp * 1000); + + chip->voltage_now = temp * 1000; + + return chip->voltage_now; +} + +static int smb1360_get_prop_batt_resistance(struct smb1360_chip *chip) +{ + u8 reg[2]; + u16 temp; + int rc; + int64_t resistance; + + if (is_device_suspended(chip)) + return chip->resistance_now; + + rc = smb1360_read_bytes(chip, SHDW_FG_ESR_ACTUAL, reg, 2); + if (rc) { + pr_err("Failed to read FG_ESR_ACTUAL rc=%d\n", rc); + return rc; + } + temp = (reg[1] << 8) | reg[0]; + + resistance = float_decode(temp) * 2; + + pr_debug("reg=0x%02x resistance=%lld\n", temp, resistance); + + /* resistance in uohms */ + chip->resistance_now = resistance; + + return chip->resistance_now; +} + +static int smb1360_get_prop_current_now(struct smb1360_chip *chip) +{ + u8 reg[2]; + int rc, temp = 0; + + if (is_device_suspended(chip)) + return chip->current_now; + + rc = smb1360_read_bytes(chip, SHDW_FG_CURR_NOW, reg, 2); + if (rc) { + pr_err("Failed to read SHDW_FG_CURR_NOW rc=%d\n", rc); + return rc; + } + + temp = ((s8)reg[1] << 8) | reg[0]; + temp = div_s64(temp * 2500, 0x7FFF); + + pr_debug("reg[0]=0x%02x reg[1]=0x%02x current=%d\n", + reg[0], reg[1], temp * 1000); + + chip->current_now = temp * 1000; + + return chip->current_now; +} + +static int smb1360_set_minimum_usb_current(struct smb1360_chip *chip) +{ + int rc = 0; + + if (chip->min_icl_usb100) { + pr_debug("USB min current set to 100mA\n"); + /* set input current limit to minimum (300mA) */ + rc = smb1360_masked_write(chip, CFG_BATT_CHG_ICL_REG, + INPUT_CURR_LIM_MASK, + INPUT_CURR_LIM_300MA); + if (rc) + pr_err("Couldn't set ICL mA rc=%d\n", rc); + + if (!(chip->workaround_flags & WRKRND_USB100_FAIL)) { + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_100_BIT); + if (rc) + pr_err("Couldn't configure for USB100 rc=%d\n", + rc); + } + } else { + pr_debug("USB min current set to 500mA\n"); + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_500_BIT); + if (rc) + pr_err("Couldn't configure for USB100 rc=%d\n", + rc); + } + + return rc; +} + +static struct power_supply *get_parallel_psy(struct smb1360_chip *chip) +{ + if (chip->parallel_psy) + return chip->parallel_psy; + chip->parallel_psy = power_supply_get_by_name("usb-parallel"); + if (!chip->parallel_psy) + pr_debug("parallel charger not found\n"); + return chip->parallel_psy; +} + +static int __smb1360_parallel_charger_enable(struct smb1360_chip *chip, + bool enable) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + + if (!parallel_psy) + return 0; + + pval.intval = (enable ? (chip->max_parallel_chg_current * 1000) : 0); + chip->parallel_psy_d.set_property(parallel_psy, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval); + pval.intval = (enable ? 1 : 0); + chip->parallel_psy_d.set_property(parallel_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval); + + pr_debug("Parallel-charger %s max_chg_current=%d\n", + enable ? "enabled" : "disabled", + enable ? (chip->max_parallel_chg_current * 1000) : 0); + + return 0; +} + +static int smb1360_parallel_charger_enable(struct smb1360_chip *chip, + int reason, bool enable) +{ + int disabled, *disabled_status; + + mutex_lock(&chip->parallel_chg_lock); + + disabled = chip->parallel_chg_disable_status; + disabled_status = &chip->parallel_chg_disable_status; + + pr_debug("reason=0x%x requested=%s disabled_status=0x%x\n", + reason, enable ? "enable" : "disable", disabled); + + if (enable) + disabled &= ~reason; + else + disabled |= reason; + + if (*disabled_status && !disabled) + __smb1360_parallel_charger_enable(chip, true); + + if (!(*disabled_status) && disabled) + __smb1360_parallel_charger_enable(chip, false); + + *disabled_status = disabled; + + pr_debug("disabled_status = %x\n", *disabled_status); + + mutex_unlock(&chip->parallel_chg_lock); + + return 0; +} + +static void smb1360_parallel_work(struct work_struct *work) +{ + u8 reg; + int rc, i; + struct smb1360_chip *chip = container_of(work, + struct smb1360_chip, parallel_work); + + /* check the AICL settled value */ + rc = smb1360_read(chip, STATUS_1_REG, ®); + if (rc) { + pr_debug("Unable to read AICL status rc=%d\n", rc); + goto exit_work; + } + pr_debug("STATUS_1 (aicl status)=0x%x\n", reg); + if ((reg & AICL_CURRENT_STATUS_MASK) == AICL_LIMIT_1500MA) { + /* Strong Charger - Enable parallel path */ + /* find the new fastchg current */ + chip->fastchg_current += (chip->max_parallel_chg_current / 2); + for (i = 0; i < ARRAY_SIZE(fastchg_current) - 1; i++) { + if (fastchg_current[i] >= chip->fastchg_current) + break; + } + if (i == ARRAY_SIZE(fastchg_current)) + i--; + + rc = smb1360_masked_write(chip, CHG_CURRENT_REG, + FASTCHG_CURR_MASK, i << FASTCHG_CURR_SHIFT); + if (rc) + pr_err("Couldn't set fastchg mA rc=%d\n", rc); + + pr_debug("fast-chg (parallel-mode) current set to = %d\n", + fastchg_current[i]); + + smb1360_parallel_charger_enable(chip, PARALLEL_CURRENT, true); + } else { + /* Weak-charger - Disable parallel path */ + smb1360_parallel_charger_enable(chip, PARALLEL_CURRENT, false); + } + +exit_work: + smb1360_relax(&chip->smb1360_ws, WAKEUP_SRC_PARALLEL); +} + +static int smb1360_set_appropriate_usb_current(struct smb1360_chip *chip) +{ + int rc = 0, i, therm_ma, current_ma; + int path_current = chip->usb_psy_ma; + + /* + * 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 (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 (chip->workaround_flags & WRKRND_HARD_JEITA) { + if (chip->batt_warm) + current_ma = min(current_ma, chip->warm_bat_ma); + else if (chip->batt_cool) + current_ma = min(current_ma, chip->cool_bat_ma); + } + + if (current_ma <= 2) { + /* + * SMB1360 does not support USB suspend - + * so set the current-limit to minimum in suspend. + */ + pr_debug("current_ma=%d <= 2 set USB current to minimum\n", + current_ma); + rc = smb1360_set_minimum_usb_current(chip); + if (rc < 0) + pr_err("Couldn't to set minimum USB current rc = %d\n", + rc); + /* disable parallel charger */ + if (chip->parallel_charging) + smb1360_parallel_charger_enable(chip, + PARALLEL_CURRENT, false); + + return rc; + } + + for (i = ARRAY_SIZE(input_current_limit) - 1; i >= 0; i--) { + if (input_current_limit[i] <= current_ma) + break; + } + if (i < 0) { + pr_debug("Couldn't find ICL mA rc=%d\n", rc); + i = 0; + } + /* set input current limit */ + rc = smb1360_masked_write(chip, CFG_BATT_CHG_ICL_REG, + INPUT_CURR_LIM_MASK, i); + if (rc) + pr_err("Couldn't set ICL mA rc=%d\n", rc); + + pr_debug("ICL set to = %d\n", input_current_limit[i]); + + if ((current_ma <= CURRENT_100_MA) && + ((chip->workaround_flags & WRKRND_USB100_FAIL) || + !chip->min_icl_usb100)) { + pr_debug("usb100 not supported: usb100_wrkrnd=%d min_icl_100=%d\n", + !!(chip->workaround_flags & WRKRND_USB100_FAIL), + chip->min_icl_usb100); + current_ma = CURRENT_500_MA; + } + + if (current_ma <= CURRENT_100_MA) { + /* USB 100 */ + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_100_BIT); + if (rc) + pr_err("Couldn't configure for USB100 rc=%d\n", rc); + pr_debug("Setting USB 100\n"); + } else if (current_ma <= CURRENT_500_MA) { + /* USB 500 */ + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_500_BIT); + if (rc) + pr_err("Couldn't configure for USB500 rc=%d\n", rc); + pr_debug("Setting USB 500\n"); + } else { + /* USB AC */ + if (chip->rsense_10mohm) + current_ma /= 2; + + for (i = ARRAY_SIZE(fastchg_current) - 1; i >= 0; i--) { + if (fastchg_current[i] <= current_ma) + break; + } + if (i < 0) { + pr_debug("Couldn't find fastchg mA rc=%d\n", rc); + i = 0; + } + + chip->fastchg_current = fastchg_current[i]; + + /* set fastchg limit */ + rc = smb1360_masked_write(chip, CHG_CURRENT_REG, + FASTCHG_CURR_MASK, i << FASTCHG_CURR_SHIFT); + if (rc) + pr_err("Couldn't set fastchg mA rc=%d\n", rc); + + /* + * To move to a new (higher) input-current setting, + * first set USB500 and then USBAC. This makes sure + * that the new ICL setting takes affect. + */ + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_500_BIT); + if (rc) + pr_err("Couldn't configure for USB500 rc=%d\n", rc); + + rc = smb1360_masked_write(chip, CMD_IL_REG, + USB_CTRL_MASK, USB_AC_BIT); + if (rc) + pr_err("Couldn't configure for USB AC rc=%d\n", rc); + + pr_debug("fast-chg current set to = %d\n", fastchg_current[i]); + } + + return rc; +} + +static int smb1360_set_jeita_comp_curr(struct smb1360_chip *chip, + int current_ma) +{ + int i; + int rc = 0; + + for (i = ARRAY_SIZE(fastchg_current) - 1; i >= 0; i--) { + if (fastchg_current[i] <= current_ma) + break; + } + if (i < 0) { + pr_debug("Couldn't find fastchg_current %dmA\n", current_ma); + i = 0; + } + + rc = smb1360_masked_write(chip, CHG_CMP_CFG, + JEITA_COMP_CURR_MASK, i); + if (rc) + pr_err("Couldn't configure for Icomp, rc = %d\n", rc); + + return rc; +} + +#define TEMP_THRE_SET(x) ((x + 300) / 10) +#define TEMP_THRE_GET(x) ((x * 10) - 300) +static int smb1360_set_soft_jeita_threshold(struct smb1360_chip *chip, + int cold_threshold, int hot_threshold) +{ + int rc = 0; + + rc = smb1360_write(chip, JEITA_SOFT_COLD_REG, + TEMP_THRE_SET(cold_threshold)); + if (rc) { + pr_err("Couldn't set soft cold threshold, rc = %d\n", rc); + return rc; + } + chip->soft_cold_thresh = cold_threshold; + + rc = smb1360_write(chip, JEITA_SOFT_HOT_REG, + TEMP_THRE_SET(hot_threshold)); + if (rc) { + pr_err("Couldn't set soft hot threshold, rc = %d\n", rc); + return rc; + } + chip->soft_hot_thresh = hot_threshold; + + return rc; +} + +static int smb1360_get_soft_jeita_threshold(struct smb1360_chip *chip, + int *cold_threshold, int *hot_threshold) +{ + int rc = 0; + u8 value; + + rc = smb1360_read(chip, JEITA_SOFT_COLD_REG, &value); + if (rc) { + pr_err("Couldn't get soft cold threshold, rc = %d\n", rc); + return rc; + } + *cold_threshold = TEMP_THRE_GET(value); + + rc = smb1360_read(chip, JEITA_SOFT_HOT_REG, &value); + if (rc) { + pr_err("Couldn't get soft hot threshold, rc = %d\n", rc); + return rc; + } + *hot_threshold = TEMP_THRE_GET(value); + + return rc; +} + +#define OTP_HARD_COLD_REG_ADDR 0x12 +#define OTP_HARD_HOT_REG_ADDR 0x13 +static int smb1360_set_otp_hard_jeita_threshold(struct smb1360_chip *chip, + int cold_threshold, int hot_threshold) +{ + int rc = 0, i; + u8 reg[4] = { 0 }; + int otp_reg = 0; + int temp_code; + + if (cold_threshold > chip->cool_bat_decidegc || + chip->cool_bat_decidegc >= chip->warm_bat_decidegc || + chip->warm_bat_decidegc > hot_threshold) { + pr_err("cold:%d, cool:%d, warm:%d, hot:%d should be ordered in size\n", + cold_threshold, chip->cool_bat_decidegc, + chip->warm_bat_decidegc, hot_threshold); + return -EINVAL; + } + pr_debug("cold:%d, cool:%d, warm:%d, hot:%d\n", + cold_threshold, chip->cool_bat_decidegc, + chip->warm_bat_decidegc, hot_threshold); + if (!chip->hard_jeita_otp_reg) { + otp_reg = smb1360_alloc_otp_backup_register(chip, + ARRAY_SIZE(reg), OTP_BACKUP_FG_USE); + if (otp_reg <= 0) { + pr_err("OTP reg allocation failed for hard JEITA\n"); + return otp_reg; + } + + chip->hard_jeita_otp_reg = otp_reg; + } else { + otp_reg = chip->hard_jeita_otp_reg; + } + pr_debug("hard_jeita_otp_reg = 0x%x\n", chip->hard_jeita_otp_reg); + + reg[0] = (u8)OTP_HARD_HOT_REG_ADDR; + temp_code = TEMP_THRE_SET(hot_threshold); + if (temp_code < 0) { + pr_err("hard hot temp encode failed\n"); + return temp_code; + } + reg[1] = (u8)temp_code; + reg[2] = (u8)OTP_HARD_COLD_REG_ADDR; + temp_code = TEMP_THRE_SET(cold_threshold); + if (temp_code < 0) { + pr_err("hard cold temp encode failed\n"); + return temp_code; + } + reg[3] = (u8)temp_code; + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("Couldn't request FG access rc = %d\n", rc); + return rc; + } + chip->fg_access_type = FG_ACCESS_CFG; + + rc = smb1360_select_fg_i2c_address(chip); + if (rc) { + pr_err("Unable to set FG access I2C address\n"); + goto restore_fg; + } + + for (i = 0; i < ARRAY_SIZE(reg); i++) { + rc = smb1360_fg_write(chip, (otp_reg + i), reg[i]); + if (rc) { + pr_err("Write FG address 0x%x: 0x%x failed, rc = %d\n", + otp_reg + i, reg[i], rc); + goto restore_fg; + } + pr_debug("Write FG addr=0x%x, value=0x%x\n", + otp_reg + i, reg[i]); + } + rc = smb1360_otp_backup_alg_update(chip); + if (rc) { + pr_err("Update OTP backup algorithm failed\n"); + goto restore_fg; + } + + rc = smb1360_masked_write(chip, CFG_FG_BATT_CTRL_REG, + CFG_FG_OTP_BACK_UP_ENABLE, CFG_FG_OTP_BACK_UP_ENABLE); + if (rc) { + pr_err("Write reg 0x0E failed, rc = %d\n", rc); + goto restore_fg; + } + +restore_fg: + rc = smb1360_disable_fg_access(chip); + if (rc) { + pr_err("Couldn't disable FG access rc = %d\n", rc); + return rc; + } + + return rc; +} + +static int smb1360_hard_jeita_otp_init(struct smb1360_chip *chip) +{ + int rc = 0; + + if (!chip->otp_hard_jeita_config) + return rc; + + rc = smb1360_set_otp_hard_jeita_threshold(chip, + chip->otp_cold_bat_decidegc, chip->otp_hot_bat_decidegc); + if (rc) { + dev_err(chip->dev, + "Couldn't set OTP hard jeita threshold,rc = %d\n", rc); + return rc; + } + + return rc; +} + +static int smb1360_system_temp_level_set(struct smb1360_chip *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)) { + rc = smb1360_set_minimum_usb_current(chip); + if (rc) + pr_err("Couldn't set USB current to minimum rc = %d\n", + rc); + } else { + rc = smb1360_set_appropriate_usb_current(chip); + if (rc) + pr_err("Couldn't set USB current rc = %d\n", rc); + } + + mutex_unlock(&chip->current_change_lock); + return rc; +} + +static enum power_supply_property smb1360_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_REAL_TYPE, + POWER_SUPPLY_PROP_SDP_CURRENT_MAX, +}; + +static int smb1360_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + int is_battery_charging = 0; + struct smb1360_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->usb_psy_ma * 1000; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->usb_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + is_battery_charging = smb1360_get_prop_batt_status(chip); + val->intval = chip->usb_present && + (is_battery_charging == POWER_SUPPLY_STATUS_CHARGING); + break; + case POWER_SUPPLY_PROP_REAL_TYPE: + val->intval = POWER_SUPPLY_TYPE_UNKNOWN; + if (chip->usb_present && + (chip->usb_supply_type != POWER_SUPPLY_TYPE_UNKNOWN)) + val->intval = chip->usb_supply_type; + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = POWER_SUPPLY_TYPE_USB; + if (chip->usb_present && + (chip->usb_supply_type != POWER_SUPPLY_TYPE_UNKNOWN)) + val->intval = chip->usb_supply_type; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smb1360_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smb1360_chip *chip = power_supply_get_drvdata(psy); + int rc = 0; + + switch (psp) { + case POWER_SUPPLY_PROP_SDP_CURRENT_MAX: + case POWER_SUPPLY_PROP_CURRENT_MAX: + chip->usb_psy_ma = val->intval / 1000; + rc = smb1360_set_appropriate_usb_current(chip); + break; + case POWER_SUPPLY_PROP_TYPE: + case POWER_SUPPLY_PROP_REAL_TYPE: + chip->usb_supply_type = val->intval; + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} + +static int smb1360_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 int smb1360_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + struct smb1360_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + smb1360_charging_disable(chip, USER, !val->intval); + if (chip->parallel_charging) + smb1360_parallel_charger_enable(chip, + PARALLEL_USER, val->intval); + power_supply_changed(chip->batt_psy); + power_supply_changed(chip->usb_psy); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + pr_info("fake_soc set to %d\n", chip->fake_battery_soc); + power_supply_changed(chip->batt_psy); + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smb1360_system_temp_level_set(chip, val->intval); + break; + default: + return -EINVAL; + } + + return 0; +} + +static int smb1360_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 smb1360_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smb1360_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_HEALTH: + val->intval = smb1360_get_prop_batt_health(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = smb1360_get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_STATUS: + val->intval = smb1360_get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + val->intval = !chip->charging_disabled_status; + break; + case POWER_SUPPLY_PROP_CHARGE_TYPE: + val->intval = smb1360_get_prop_charge_type(chip); + break; + case POWER_SUPPLY_PROP_CAPACITY: + val->intval = smb1360_get_prop_batt_capacity(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN: + val->intval = smb1360_get_prop_chg_full_design(chip); + break; + case POWER_SUPPLY_PROP_VOLTAGE_NOW: + val->intval = smb1360_get_prop_voltage_now(chip); + break; + case POWER_SUPPLY_PROP_CURRENT_NOW: + val->intval = smb1360_get_prop_current_now(chip); + break; + case POWER_SUPPLY_PROP_RESISTANCE: + val->intval = smb1360_get_prop_batt_resistance(chip); + break; + case POWER_SUPPLY_PROP_TEMP: + val->intval = smb1360_get_prop_batt_temp(chip); + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + val->intval = chip->therm_lvl_sel; + break; + default: + return -EINVAL; + } + return 0; +} + +static int hot_hard_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_hot = !!rt_stat; + + if (chip->parallel_charging) { + pr_debug("%s parallel-charging\n", chip->batt_hot ? + "Disable" : "Enable"); + smb1360_parallel_charger_enable(chip, + PARALLEL_JEITA_HARD, !chip->batt_hot); + } + if (chip->hot_hysteresis) { + smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_JEITA_HYSTERSIS); + schedule_work(&chip->jeita_hysteresis_work); + } + + return 0; +} + +static int cold_hard_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_cold = !!rt_stat; + + if (chip->parallel_charging) { + pr_debug("%s parallel-charging\n", chip->batt_cold ? + "Disable" : "Enable"); + smb1360_parallel_charger_enable(chip, + PARALLEL_JEITA_HARD, !chip->batt_cold); + } + if (chip->cold_hysteresis) { + smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_JEITA_HYSTERSIS); + schedule_work(&chip->jeita_hysteresis_work); + } + + return 0; +} + +static void smb1360_jeita_hysteresis_work(struct work_struct *work) +{ + int rc = 0; + int hard_hot, hard_cold; + struct smb1360_chip *chip = container_of(work, + struct smb1360_chip, jeita_hysteresis_work); + + /* disable hard JEITA IRQ first */ + rc = smb1360_masked_write(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT, 0); + if (rc) { + pr_err("disable hard JEITA IRQ failed, rc = %d\n", rc); + goto exit_worker; + } + hard_hot = chip->otp_hot_bat_decidegc; + hard_cold = chip->otp_cold_bat_decidegc; + if (chip->batt_hot) + hard_hot -= chip->hot_hysteresis; + else if (chip->batt_cold) + hard_cold += chip->cold_hysteresis; + + rc = smb1360_set_otp_hard_jeita_threshold(chip, hard_cold, hard_hot); + if (rc) { + pr_err("set hard JEITA threshold failed\n"); + goto exit_worker; + } + pr_debug("hard cold: %d, hard hot: %d reprogramed\n", + hard_cold, hard_hot); + /* enable hard JEITA IRQ at the end */ + rc = smb1360_masked_write(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT, IRQ_BAT_HOT_COLD_HARD_BIT); + if (rc) + pr_err("enable hard JEITA IRQ failed\n"); +exit_worker: + smb1360_relax(&chip->smb1360_ws, WAKEUP_SRC_JEITA_HYSTERSIS); +} + +/* + * This worker thread should only be called when WRKRND_HARD_JEITA + * is set. + * It is needed to re-program JEITA soft thresholds, compensate + * target voltage and charging current manually. + * The function is required as JEITA hard thresholds can't be programmed. + */ +static void smb1360_jeita_work_fn(struct work_struct *work) +{ + int temp; + int rc = 0; + struct smb1360_chip *chip = container_of(work, + struct smb1360_chip, jeita_work.work); + + temp = smb1360_get_prop_batt_temp(chip); + + if (temp > chip->hot_bat_decidegc) { + /* battery status is hot, only config thresholds */ + rc = smb1360_set_soft_jeita_threshold(chip, + chip->warm_bat_decidegc, chip->hot_bat_decidegc); + if (rc) { + dev_err(chip->dev, "Couldn't set jeita threshold\n"); + goto end; + } + } else if (temp > chip->warm_bat_decidegc || + (temp == chip->warm_bat_decidegc && !!chip->soft_hot_rt_stat)) { + /* battery status is warm, do compensation manually */ + chip->batt_warm = true; + chip->batt_cool = false; + rc = smb1360_float_voltage_set(chip, chip->warm_bat_mv); + if (rc) { + dev_err(chip->dev, "Couldn't set float voltage\n"); + goto end; + } + rc = smb1360_set_appropriate_usb_current(chip); + if (rc) + pr_err("Couldn't set USB current\n"); + rc = smb1360_set_soft_jeita_threshold(chip, + chip->warm_bat_decidegc, chip->hot_bat_decidegc); + if (rc) { + dev_err(chip->dev, "Couldn't set jeita threshold\n"); + goto end; + } + } else if (temp > chip->cool_bat_decidegc || + (temp == chip->cool_bat_decidegc && !chip->soft_cold_rt_stat)) { + /* battery status is good, do the normal charging */ + chip->batt_warm = false; + chip->batt_cool = false; + rc = smb1360_float_voltage_set(chip, chip->vfloat_mv); + if (rc) { + dev_err(chip->dev, "Couldn't set float voltage\n"); + goto end; + } + rc = smb1360_set_appropriate_usb_current(chip); + if (rc) + pr_err("Couldn't set USB current\n"); + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cool_bat_decidegc, chip->warm_bat_decidegc); + if (rc) { + dev_err(chip->dev, "Couldn't set jeita threshold\n"); + goto end; + } + } else if (temp > chip->cold_bat_decidegc) { + /* battery status is cool, do compensation manually */ + chip->batt_cool = true; + chip->batt_warm = false; + rc = smb1360_float_voltage_set(chip, chip->cool_bat_mv); + if (rc) { + dev_err(chip->dev, "Couldn't set float voltage\n"); + goto end; + } + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cold_bat_decidegc, chip->cool_bat_decidegc); + if (rc) { + dev_err(chip->dev, "Couldn't set jeita threshold\n"); + goto end; + } + } else { + /* battery status is cold, only config thresholds */ + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cold_bat_decidegc, chip->cool_bat_decidegc); + if (rc) { + dev_err(chip->dev, "Couldn't set jeita threshold\n"); + goto end; + } + } + + pr_debug("warm %d, cool %d, soft_cold_rt_sts %d, soft_hot_rt_sts %d, jeita supported %d, threshold_now %d %d\n", + chip->batt_warm, chip->batt_cool, !!chip->soft_cold_rt_stat, + !!chip->soft_hot_rt_stat, chip->soft_jeita_supported, + chip->soft_cold_thresh, chip->soft_hot_thresh); +end: + smb1360_relax(&chip->smb1360_ws, WAKEUP_SRC_JEITA_SOFT); +} + +static int hot_soft_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + chip->soft_hot_rt_stat = rt_stat; + pr_debug("rt_stat = 0x%02x\n", rt_stat); + if (!chip->config_hard_thresholds) + chip->batt_warm = !!rt_stat; + + if (chip->workaround_flags & WRKRND_HARD_JEITA) { + cancel_delayed_work_sync(&chip->jeita_work); + schedule_delayed_work(&chip->jeita_work, + msecs_to_jiffies(JEITA_WORK_MS)); + smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_JEITA_SOFT); + } + + if (chip->parallel_charging) { + pr_debug("%s parallel-charging\n", chip->batt_warm ? + "Disable" : "Enable"); + smb1360_parallel_charger_enable(chip, + PARALLEL_JEITA_SOFT, !chip->batt_warm); + } + return 0; +} + +static int cold_soft_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + chip->soft_cold_rt_stat = rt_stat; + pr_debug("rt_stat = 0x%02x\n", rt_stat); + if (!chip->config_hard_thresholds) + chip->batt_cool = !!rt_stat; + + if (chip->workaround_flags & WRKRND_HARD_JEITA) { + cancel_delayed_work_sync(&chip->jeita_work); + schedule_delayed_work(&chip->jeita_work, + msecs_to_jiffies(JEITA_WORK_MS)); + smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_JEITA_SOFT); + } + + if (chip->parallel_charging) { + pr_debug("%s parallel-charging\n", chip->batt_cool ? + "Disable" : "Enable"); + smb1360_parallel_charger_enable(chip, + PARALLEL_JEITA_SOFT, !chip->batt_cool); + } + + return 0; +} + +static int battery_missing_handler(struct smb1360_chip *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 smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("vbat low\n"); + + return 0; +} + +static int chg_hot_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_warn_ratelimited("chg hot\n"); + return 0; +} + +static int chg_term_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + chip->batt_full = !!rt_stat; + + if (chip->parallel_charging) { + pr_debug("%s parallel-charging\n", chip->batt_full ? + "Disable" : "Enable"); + smb1360_parallel_charger_enable(chip, + PARALLEL_EOC, !chip->batt_full); + } + + return 0; +} + +static int chg_fastchg_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("rt_stat = 0x%02x\n", rt_stat); + + return 0; +} + +static int usbin_uv_handler(struct smb1360_chip *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 removed */ + chip->usb_present = usb_present; + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + chip->usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + } + + if (!chip->usb_present && usb_present) { + /* USB inserted */ + chip->usb_present = usb_present; + extcon_set_state_sync(chip->extcon, EXTCON_USB, true); + } + power_supply_changed(chip->usb_psy); + + return 0; +} + +static int aicl_done_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + bool aicl_done = !!rt_stat; + + pr_debug("AICL done=%d\n", aicl_done); + + if (chip->parallel_charging && aicl_done) { + cancel_work_sync(&chip->parallel_work); + smb1360_stay_awake(&chip->smb1360_ws, WAKEUP_SRC_PARALLEL); + schedule_work(&chip->parallel_work); + } + + return 0; +} + +static int chg_inhibit_handler(struct smb1360_chip *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); + chip->batt_full = !!rt_stat; + return 0; +} + +static int delta_soc_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("SOC changed! - rt_stat = 0x%02x\n", rt_stat); + + return 0; +} + +static int min_soc_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("SOC dropped below min SOC, rt_stat = 0x%02x\n", rt_stat); + + if (chip->awake_min_soc) + rt_stat ? smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_MIN_SOC) : + smb1360_relax(&chip->smb1360_ws, + WAKEUP_SRC_MIN_SOC); + + return 0; +} + +static int empty_soc_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("SOC empty! rt_stat = 0x%02x\n", rt_stat); + + if (!chip->empty_soc_disabled) { + if (rt_stat) { + chip->empty_soc = true; + smb1360_stay_awake(&chip->smb1360_ws, + WAKEUP_SRC_EMPTY_SOC); + pr_warn_ratelimited("SOC is 0\n"); + } else { + chip->empty_soc = false; + smb1360_relax(&chip->smb1360_ws, + WAKEUP_SRC_EMPTY_SOC); + } + } + + return 0; +} + +static int full_soc_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + if (rt_stat) + pr_debug("SOC is 100\n"); + + return 0; +} + +static int fg_access_allowed_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("stat=%d\n", !!rt_stat); + + if (rt_stat & FG_ACCESS_ALLOWED_BIT) { + pr_debug("FG access granted\n"); + complete_all(&chip->fg_mem_access_granted); + } + + return 0; +} + +static int batt_id_complete_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + pr_debug("batt_id = %x\n", (rt_stat & BATT_ID_RESULT_BIT) + >> BATT_ID_SHIFT); + + return 0; +} + +static int smb1360_adjust_current_gain(struct smb1360_chip *chip, + int gain_factor) +{ + int i, rc; + int64_t current_gain, new_current_gain; + u16 reg_value1 = 0, reg_value2 = 0; + u8 reg[4] = {0x1D, 0x00, 0x1E, 0x00}; + int otp_reg = 0; + + if (!chip->current_gain_otp_reg) { + otp_reg = smb1360_alloc_otp_backup_register(chip, + ARRAY_SIZE(reg), OTP_BACKUP_FG_USE); + if (otp_reg <= 0) { + pr_err("OTP reg allocation fail for adjusting current gain\n"); + return otp_reg; + } + chip->current_gain_otp_reg = otp_reg; + } else { + otp_reg = chip->current_gain_otp_reg; + } + pr_debug("current_gain_otp_reg = 0x%x\n", chip->current_gain_otp_reg); + + if (gain_factor) { + rc = smb1360_fg_read(chip, CURRENT_GAIN_LSB_REG, ®[1]); + if (rc) { + pr_err("Unable to set FG access I2C address rc=%d\n", + rc); + return rc; + } + + rc = smb1360_fg_read(chip, CURRENT_GAIN_MSB_REG, ®[3]); + if (rc) { + pr_err("Unable to set FG access I2C address rc=%d\n", + rc); + return rc; + } + + reg_value1 = (reg[3] << 8) | reg[1]; + current_gain = float_decode(reg_value1); + new_current_gain = MICRO_UNIT + (gain_factor * current_gain); + reg_value2 = float_encode(new_current_gain); + reg[1] = reg_value2 & 0xFF; + reg[3] = (reg_value2 & 0xFF00) >> 8; + pr_debug("current_gain_reg=0x%x current_gain_decoded=%lld new_current_gain_decoded=%lld new_current_gain_reg=0x%x\n", + reg_value1, current_gain, new_current_gain, reg_value2); + + for (i = 0; i < ARRAY_SIZE(reg); i++) { + pr_debug("Writing reg_add=%x value=%x\n", + otp_reg + i, reg[i]); + + rc = smb1360_fg_write(chip, (otp_reg + i), reg[i]); + if (rc) { + pr_err("Write FG address 0x%x failed, rc = %d\n", + otp_reg + i, rc); + return rc; + } + } + rc = smb1360_otp_backup_alg_update(chip); + if (rc) { + pr_err("Update OTP backup algorithm failed\n"); + return rc; + } + } else { + pr_debug("Disabling gain correction\n"); + rc = smb1360_fg_write(chip, 0xF0, 0x00); + if (rc) { + pr_err("Write fg address 0x%x failed, rc = %d\n", + 0xF0, rc); + return rc; + } + } + + return 0; +} + +static int smb1360_otp_gain_config(struct smb1360_chip *chip, int gain_factor) +{ + int rc = 0; + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("Couldn't request FG access rc = %d\n", rc); + return rc; + } + chip->fg_access_type = FG_ACCESS_CFG; + + rc = smb1360_select_fg_i2c_address(chip); + if (rc) { + pr_err("Unable to set FG access I2C address\n"); + goto restore_fg; + } + + rc = smb1360_adjust_current_gain(chip, gain_factor); + if (rc) { + pr_err("Unable to modify current gain rc=%d\n", rc); + goto restore_fg; + } + + rc = smb1360_masked_write(chip, CFG_FG_BATT_CTRL_REG, + CFG_FG_OTP_BACK_UP_ENABLE, CFG_FG_OTP_BACK_UP_ENABLE); + if (rc) { + pr_err("Write reg 0x0E failed, rc = %d\n", rc); + goto restore_fg; + } + +restore_fg: + rc = smb1360_disable_fg_access(chip); + if (rc) { + pr_err("Couldn't disable FG access rc = %d\n", rc); + return rc; + } + + return rc; +} + +static int smb1360_otg_disable(struct smb1360_chip *chip) +{ + int rc; + + rc = smb1360_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; + } + + mutex_lock(&chip->otp_gain_lock); + /* Disable current gain configuration */ + if (chip->otg_fet_present && chip->fet_gain_enabled) { + /* Disable FET */ + gpio_set_value(chip->otg_fet_enable_gpio, 1); + rc = smb1360_otp_gain_config(chip, 0); + if (rc < 0) + pr_err("Couldn't config OTP gain config rc=%d\n", rc); + else + chip->fet_gain_enabled = false; + } + mutex_unlock(&chip->otp_gain_lock); + + return rc; +} + +static int otg_fail_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + int rc; + + pr_debug("OTG Failed stat=%d\n", rt_stat); + rc = smb1360_otg_disable(chip); + if (rc) + pr_err("Couldn't disable OTG mode rc=%d\n", rc); + + return 0; +} + +static int otg_oc_handler(struct smb1360_chip *chip, u8 rt_stat) +{ + int rc; + + pr_debug("OTG over-current stat=%d\n", rt_stat); + rc = smb1360_otg_disable(chip); + if (rc) + pr_err("Couldn't disable OTG mode rc=%d\n", rc); + + return 0; +} + +struct smb_irq_info { + const char *name; + int (*smb_irq)(struct smb1360_chip *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", + }, + { + .name = "recharge", + }, + { + .name = "fast_chg", + .smb_irq = chg_fastchg_handler, + }, + }, + }, + {IRQ_D_REG, 0, 0, + { + { + .name = "prechg_timeout", + }, + { + .name = "safety_timeout", + }, + { + .name = "aicl_done", + .smb_irq = aicl_done_handler, + }, + { + .name = "battery_ov", + }, + }, + }, + {IRQ_E_REG, 0, 0, + { + { + .name = "usbin_uv", + .smb_irq = usbin_uv_handler, + }, + { + .name = "usbin_ov", + }, + { + .name = "unused", + }, + { + .name = "chg_inhibit", + .smb_irq = chg_inhibit_handler, + }, + }, + }, + {IRQ_F_REG, 0, 0, + { + { + .name = "power_ok", + }, + { + .name = "unused", + }, + { + .name = "otg_fail", + .smb_irq = otg_fail_handler, + }, + { + .name = "otg_oc", + .smb_irq = otg_oc_handler, + }, + }, + }, + {IRQ_G_REG, 0, 0, + { + { + .name = "delta_soc", + .smb_irq = delta_soc_handler, + }, + { + .name = "chg_error", + }, + { + .name = "wd_timeout", + }, + { + .name = "unused", + }, + }, + }, + {IRQ_H_REG, 0, 0, + { + { + .name = "min_soc", + .smb_irq = min_soc_handler, + }, + { + .name = "max_soc", + }, + { + .name = "empty_soc", + .smb_irq = empty_soc_handler, + }, + { + .name = "full_soc", + .smb_irq = full_soc_handler, + }, + }, + }, + {IRQ_I_REG, 0, 0, + { + { + .name = "fg_access_allowed", + .smb_irq = fg_access_allowed_handler, + }, + { + .name = "fg_data_recovery", + }, + { + .name = "batt_id_complete", + .smb_irq = batt_id_complete_handler, + }, + }, + }, +}; + +#define IRQ_LATCHED_MASK 0x02 +#define IRQ_STATUS_MASK 0x01 +#define BATT_ID_LATCHED_MASK 0x08 +#define BATT_ID_STATUS_MASK 0x07 +#define BITS_PER_IRQ 2 +static irqreturn_t smb1360_stat_handler(int irq, void *dev_id) +{ + struct smb1360_chip *chip = dev_id; + int i, j; + u8 triggered; + u8 changed; + u8 rt_stat, prev_rt_stat, irq_latched_mask, irq_status_mask; + 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"); + if (!chip->irq_disabled) { + disable_irq_nosync(irq); + chip->irq_disabled = true; + } + mutex_unlock(&chip->irq_complete); + return IRQ_HANDLED; + } + chip->irq_waiting = false; + + for (i = 0; i < ARRAY_SIZE(handlers); i++) { + rc = smb1360_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); + continue; + } + + for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) { + if (handlers[i].stat_reg == IRQ_I_REG && j == 2) { + irq_latched_mask = BATT_ID_LATCHED_MASK; + irq_status_mask = BATT_ID_STATUS_MASK; + } else { + irq_latched_mask = IRQ_LATCHED_MASK; + irq_status_mask = IRQ_STATUS_MASK; + } + 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) + power_supply_changed(chip->batt_psy); + + mutex_unlock(&chip->irq_complete); + + return IRQ_HANDLED; +} + +static irqreturn_t smb1360_usb_id_irq_handler(int irq, void *dev_id) +{ + struct smb1360_chip *chip = dev_id; + int rc = 0; + bool id_state; + + id_state = gpio_get_value(chip->usb_id_gpio); + + rc = smb1360_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, + !id_state ? CMD_OTG_EN_BIT : 0); + if (rc) { + pr_err("Couldn't enable OTG mode rc=%d\n", rc); + return IRQ_HANDLED; + } + extcon_set_state_sync(chip->extcon, EXTCON_USB_HOST, + !id_state ? true : false); + + pr_debug("usb_id_irq triggered, id_state = %d\n", id_state); + + return IRQ_HANDLED; +} + +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++) { + if (!handlers[i].irq_info[j].name) + continue; + 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 smb1360_chip *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 smb1360_chip *chip = data; + int rc; + u8 temp; + + rc = smb1360_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 smb1360_chip *chip = data; + int rc; + u8 temp; + + temp = (u8) val; + rc = smb1360_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_DEBUGFS_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n"); + +static int fg_get_reg(void *data, u64 *val) +{ + struct smb1360_chip *chip = data; + int rc; + u8 temp; + + rc = smb1360_select_fg_i2c_address(chip); + if (rc) { + pr_err("Unable to set FG access I2C address\n"); + return -EINVAL; + } + + rc = smb1360_fg_read(chip, chip->fg_peek_poke_address, &temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read reg %x rc = %d\n", + chip->fg_peek_poke_address, rc); + return -EAGAIN; + } + *val = temp; + return 0; +} + +static int fg_set_reg(void *data, u64 val) +{ + struct smb1360_chip *chip = data; + int rc; + u8 temp; + + rc = smb1360_select_fg_i2c_address(chip); + if (rc) { + pr_err("Unable to set FG access I2C address\n"); + return -EINVAL; + } + + temp = (u8) val; + rc = smb1360_fg_write(chip, chip->fg_peek_poke_address, temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't write 0x%02x to 0x%02x rc= %d\n", + chip->fg_peek_poke_address, temp, rc); + return -EAGAIN; + } + return 0; +} +DEFINE_DEBUGFS_ATTRIBUTE(fg_poke_poke_debug_ops, fg_get_reg, + fg_set_reg, "0x%02llx\n"); + +#define LAST_CNFG_REG 0x17 +static int show_cnfg_regs(struct seq_file *m, void *data) +{ + struct smb1360_chip *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = 0; addr <= LAST_CNFG_REG; addr++) { + rc = smb1360_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 smb1360_chip *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 smb1360_chip *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) { + rc = smb1360_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 smb1360_chip *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 0x48 +#define LAST_STATUS_REG 0x4B +static int show_status_regs(struct seq_file *m, void *data) +{ + struct smb1360_chip *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) { + rc = smb1360_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 smb1360_chip *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, +}; + +#define FIRST_IRQ_REG 0x50 +#define LAST_IRQ_REG 0x58 +static int show_irq_stat_regs(struct seq_file *m, void *data) +{ + struct smb1360_chip *chip = m->private; + int rc; + u8 reg; + u8 addr; + + for (addr = FIRST_IRQ_REG; addr <= LAST_IRQ_REG; addr++) { + rc = smb1360_read(chip, addr, ®); + if (!rc) + seq_printf(m, "0x%02x = 0x%02x\n", addr, reg); + } + + return 0; +} + +static int irq_stat_debugfs_open(struct inode *inode, struct file *file) +{ + struct smb1360_chip *chip = inode->i_private; + + return single_open(file, show_irq_stat_regs, chip); +} + +static const struct file_operations irq_stat_debugfs_ops = { + .owner = THIS_MODULE, + .open = irq_stat_debugfs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int data_8(u8 *reg) +{ + return reg[0]; +} +static int data_16(u8 *reg) +{ + return (reg[1] << 8) | reg[0]; +} +static int data_24(u8 *reg) +{ + return (reg[2] << 16) | (reg[1] << 8) | reg[0]; +} +static int data_28(u8 *reg) +{ + return ((reg[3] & 0xF) << 24) | (reg[2] << 16) | + (reg[1] << 8) | reg[0]; +} +static int data_32(u8 *reg) +{ + return (reg[3] << 24) | (reg[2] << 16) | + (reg[1] << 8) | reg[0]; +} + +struct fg_regs { + int index; + int length; + char *param_name; + int (*calc_func)(u8 *index); +}; + +static struct fg_regs fg_scratch_pad[] = { + {0, 2, "v_current_predicted", data_16}, + {2, 2, "v_cutoff_predicted", data_16}, + {4, 2, "v_full_predicted", data_16}, + {6, 2, "ocv_estimate", data_16}, + {8, 2, "rslow_drop", data_16}, + {10, 2, "voltage_old", data_16}, + {12, 2, "current_old", data_16}, + {14, 4, "current_average_full", data_32}, + {18, 2, "temperature", data_16}, + {20, 2, "temp_last_track", data_16}, + {22, 2, "ESR_nominal", data_16}, + {26, 2, "Rslow", data_16}, + {28, 2, "counter_imptr", data_16}, + {30, 2, "counter_pulse", data_16}, + {32, 1, "IRQ_delta_prev", data_8}, + {33, 1, "cap_learning_counter", data_8}, + {34, 4, "Vact_int_error", data_32}, + {38, 3, "SOC_cutoff", data_24}, + {41, 3, "SOC_full", data_24}, + {44, 3, "SOC_auto_rechrge_temp", data_24}, + {47, 3, "Battery_SOC", data_24}, + {50, 4, "CC_SOC", data_28}, + {54, 2, "SOC_filtered", data_16}, + {56, 2, "SOC_Monotonic", data_16}, + {58, 2, "CC_SOC_coeff", data_16}, + {60, 2, "nominal_capacity", data_16}, + {62, 2, "actual_capacity", data_16}, + {68, 1, "temperature_counter", data_8}, + {69, 3, "Vbatt_filtered", data_24}, + {72, 3, "Ibatt_filtered", data_24}, + {75, 2, "Current_CC_shadow", data_16}, + {79, 2, "Ibatt_standby", data_16}, + {82, 1, "Auto_recharge_SOC_threshold", data_8}, + {83, 2, "System_cutoff_voltage", data_16}, + {85, 2, "System_CC_to_CV_voltage", data_16}, + {87, 2, "System_term_current", data_16}, + {89, 2, "System_fake_term_current", data_16}, + {91, 2, "thermistor_c1_coeff", data_16}, +}; + +static struct fg_regs fg_cfg[] = { + {0, 2, "ESR_actual", data_16}, + {4, 1, "IRQ_SOC_max", data_8}, + {5, 1, "IRQ_SOC_min", data_8}, + {6, 1, "IRQ_volt_empty", data_8}, + {7, 1, "Temp_external", data_8}, + {8, 1, "IRQ_delta_threshold", data_8}, + {9, 1, "JIETA_soft_cold", data_8}, + {10, 1, "JIETA_soft_hot", data_8}, + {11, 1, "IRQ_volt_min", data_8}, + {14, 2, "ESR_sys_replace", data_16}, +}; + +static struct fg_regs fg_shdw[] = { + {0, 1, "Latest_battery_info", data_8}, + {1, 1, "Latest_Msys_SOC", data_8}, + {2, 2, "Battery_capacity", data_16}, + {4, 2, "Rslow_drop", data_16}, + {6, 1, "Latest_SOC", data_8}, + {7, 1, "Latest_Cutoff_SOC", data_8}, + {8, 1, "Latest_full_SOC", data_8}, + {9, 2, "Voltage_shadow", data_16}, + {11, 2, "Current_shadow", data_16}, + {13, 2, "Latest_temperature", data_16}, + {15, 1, "Latest_system_sbits", data_8}, +}; + +#define FIRST_FG_CFG_REG 0x20 +#define LAST_FG_CFG_REG 0x2F +#define FIRST_FG_SHDW_REG 0x60 +#define LAST_FG_SHDW_REG 0x6F +#define FG_SCRATCH_PAD_MAX 93 +#define FG_SCRATCH_PAD_BASE_REG 0x80 +#define SMB1360_I2C_READ_LENGTH 32 + +static int smb1360_check_cycle_stretch(struct smb1360_chip *chip) +{ + int rc = 0; + u8 reg; + + rc = smb1360_read(chip, STATUS_4_REG, ®); + if (rc) { + pr_err("Unable to read status regiseter\n"); + } else if (reg & CYCLE_STRETCH_ACTIVE_BIT) { + /* clear cycle stretch */ + rc = smb1360_masked_write(chip, CMD_I2C_REG, + CYCLE_STRETCH_CLEAR_BIT, CYCLE_STRETCH_CLEAR_BIT); + if (rc) + pr_err("Unable to clear cycle stretch\n"); + } + + return rc; +} + +static int show_fg_regs(struct seq_file *m, void *data) +{ + struct smb1360_chip *chip = m->private; + int rc, i, j, rem_length; + u8 reg[FG_SCRATCH_PAD_MAX]; + + rc = smb1360_check_cycle_stretch(chip); + if (rc) + pr_err("Unable to check cycle-stretch\n"); + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("Couldn't request FG access rc=%d\n", rc); + return rc; + } + + for (i = 0; i < (FG_SCRATCH_PAD_MAX / SMB1360_I2C_READ_LENGTH); i++) { + j = i * SMB1360_I2C_READ_LENGTH; + rc = smb1360_read_bytes(chip, FG_SCRATCH_PAD_BASE_REG + j, + ®[j], SMB1360_I2C_READ_LENGTH); + if (rc) { + pr_err("Couldn't read scratch registers rc=%d\n", rc); + break; + } + } + + j = i * SMB1360_I2C_READ_LENGTH; + rem_length = (FG_SCRATCH_PAD_MAX % SMB1360_I2C_READ_LENGTH); + if (rem_length) { + rc = smb1360_read_bytes(chip, FG_SCRATCH_PAD_BASE_REG + j, + ®[j], rem_length); + if (rc) + pr_err("Couldn't read scratch registers rc=%d\n", rc); + } + + rc = smb1360_disable_fg_access(chip); + if (rc) { + pr_err("Couldn't disable FG access rc=%d\n", rc); + return rc; + } + + rc = smb1360_check_cycle_stretch(chip); + if (rc) + pr_err("Unable to check cycle-stretch\n"); + + + seq_puts(m, "FG scratch-pad registers\n"); + for (i = 0; i < ARRAY_SIZE(fg_scratch_pad); i++) + seq_printf(m, "\t%s = %x\n", fg_scratch_pad[i].param_name, + fg_scratch_pad[i].calc_func(®[fg_scratch_pad[i].index])); + + rem_length = LAST_FG_CFG_REG - FIRST_FG_CFG_REG + 1; + rc = smb1360_read_bytes(chip, FIRST_FG_CFG_REG, + ®[0], rem_length); + if (rc) + pr_err("Couldn't read config registers rc=%d\n", rc); + + seq_puts(m, "FG config registers\n"); + for (i = 0; i < ARRAY_SIZE(fg_cfg); i++) + seq_printf(m, "\t%s = %x\n", fg_cfg[i].param_name, + fg_cfg[i].calc_func(®[fg_cfg[i].index])); + + rem_length = LAST_FG_SHDW_REG - FIRST_FG_SHDW_REG + 1; + rc = smb1360_read_bytes(chip, FIRST_FG_SHDW_REG, + ®[0], rem_length); + if (rc) + pr_err("Couldn't read shadow registers rc=%d\n", rc); + + seq_puts(m, "FG shadow registers\n"); + for (i = 0; i < ARRAY_SIZE(fg_shdw); i++) + seq_printf(m, "\t%s = %x\n", fg_shdw[i].param_name, + fg_shdw[i].calc_func(®[fg_shdw[i].index])); + + return rc; +} + +static int fg_regs_open(struct inode *inode, struct file *file) +{ + struct smb1360_chip *chip = inode->i_private; + + return single_open(file, show_fg_regs, chip); +} + +static const struct file_operations fg_regs_debugfs_ops = { + .owner = THIS_MODULE, + .open = fg_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static int smb1360_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1360_chip *chip = rdev_get_drvdata(rdev); + + rc = smb1360_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; + } + + pr_debug("OTG mode enabled\n"); + /* Enable current gain configuration */ + mutex_lock(&chip->otp_gain_lock); + if (chip->otg_fet_present) { + /* Enable FET */ + gpio_set_value(chip->otg_fet_enable_gpio, 0); + rc = smb1360_otp_gain_config(chip, 3); + if (rc < 0) + pr_err("Couldn't config OTP gain config rc=%d\n", rc); + else + chip->fet_gain_enabled = true; + } + mutex_unlock(&chip->otp_gain_lock); + + return rc; +} + +static int smb1360_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smb1360_chip *chip = rdev_get_drvdata(rdev); + + rc = smb1360_otg_disable(chip); + if (rc) + pr_err("Couldn't disable OTG regulator rc=%d\n", rc); + + pr_debug("OTG mode disabled\n"); + return rc; +} + +static int smb1360_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + u8 reg = 0; + int rc = 0; + struct smb1360_chip *chip = rdev_get_drvdata(rdev); + + rc = smb1360_read(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; +} + +static struct regulator_ops smb1360_otg_reg_ops = { + .enable = smb1360_otg_regulator_enable, + .disable = smb1360_otg_regulator_disable, + .is_enabled = smb1360_otg_regulator_is_enable, +}; + +static int smb1360_regulator_init(struct smb1360_chip *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + + /* OTG is enabled by SMB1360 if usb-id config is defined */ + if (chip->usb_id_gpio > 0 && chip->usb_id_irq > 0) + return 0; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smb1360_otg_reg_ops; + chip->otg_vreg.rdesc.of_match = "qcom,smb1360-vbus"; + chip->otg_vreg.rdesc.name = "qcom,smb1360-vbus"; + + cfg.dev = chip->dev; + cfg.driver_data = chip; + cfg.of_node = chip->dev->of_node; + + 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 int smb1360_check_batt_profile(struct smb1360_chip *chip) +{ + int rc, i, timeout = 50; + u8 reg = 0, loaded_profile, new_profile = 0, bid_mask; + + if (!chip->connected_rid) { + pr_debug("Skip batt-profile loading connected_rid=%d\n", + chip->connected_rid); + return 0; + } + + rc = smb1360_read(chip, SHDW_FG_BATT_STATUS, ®); + if (rc) { + pr_err("Couldn't read FG_BATT_STATUS rc=%d\n", rc); + return rc; + } + + loaded_profile = !!(reg & BATTERY_PROFILE_BIT) ? + BATTERY_PROFILE_B : BATTERY_PROFILE_A; + + pr_debug("fg_batt_status=%x loaded_profile=%d\n", reg, loaded_profile); + + for (i = 0; i < BATTERY_PROFILE_MAX; i++) { + pr_debug("profile=%d profile_rid=%d connected_rid=%d\n", i, + chip->profile_rid[i], + chip->connected_rid); + if (abs(chip->profile_rid[i] - chip->connected_rid) < + (div_u64(chip->connected_rid, 10))) + break; + } + + if (i == BATTERY_PROFILE_MAX) { + pr_err("None of the battery-profiles match the connected-RID\n"); + return 0; + } + + if (i == loaded_profile) { + pr_debug("Loaded Profile-RID == connected-RID\n"); + return 0; + } + + new_profile = (loaded_profile == BATTERY_PROFILE_A) ? + BATTERY_PROFILE_B : BATTERY_PROFILE_A; + bid_mask = (new_profile == BATTERY_PROFILE_A) ? + BATT_PROFILEA_MASK : BATT_PROFILEB_MASK; + pr_info("Loaded Profile-RID != connected-RID, switch-profile old_profile=%d new_profile=%d\n", + loaded_profile, new_profile); + + /* set the BID mask */ + rc = smb1360_masked_write(chip, CFG_FG_BATT_CTRL_REG, + BATT_PROFILE_SELECT_MASK, bid_mask); + if (rc) { + pr_err("Couldn't reset battery-profile rc=%d\n", rc); + return rc; + } + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("FG access timed-out, rc = %d\n", rc); + return rc; + } + /* delay after handshaking for profile-switch to continue */ + msleep(1500); + + rc = smb1360_force_fg_reset(chip); + if (rc) { + pr_err("Couldn't reset FG rc=%d\n", rc); + goto restore_fg; + } + + rc = smb1360_disable_fg_access(chip); + if (rc) { + pr_err("disable FG access failed, rc = %d\n", rc); + return rc; + } + + timeout = 10; + while (timeout) { + /* delay for profile to change */ + msleep(500); + rc = smb1360_read(chip, SHDW_FG_BATT_STATUS, ®); + if (rc) { + pr_err("Could't read FG_BATT_STATUS rc=%d\n", rc); + return rc; + } + + reg = !!(reg & BATTERY_PROFILE_BIT); + if (reg == new_profile) { + pr_info("New profile=%d loaded\n", new_profile); + break; + } + timeout--; + } + + if (!timeout) { + pr_err("New profile could not be loaded\n"); + return -EBUSY; + } + + return 0; + +restore_fg: + smb1360_disable_fg_access(chip); + return rc; +} + +#define UPDATE_IRQ_STAT(irq_reg, value) \ + handlers[irq_reg - IRQ_A_REG].prev_val = value + +static int determine_initial_status(struct smb1360_chip *chip) +{ + int rc; + u8 reg = 0; + bool id_state; + + /* + * It is okay to read the IRQ status as the irq's are + * not registered yet. + */ + chip->batt_present = true; + rc = smb1360_read(chip, IRQ_B_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read IRQ_B_REG rc = %d\n", rc); + return rc; + } + UPDATE_IRQ_STAT(IRQ_B_REG, reg); + + if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT) + chip->batt_present = false; + + rc = smb1360_read(chip, IRQ_C_REG, ®); + if (rc) { + dev_err(chip->dev, "Couldn't read IRQ_C_REG rc = %d\n", rc); + return rc; + } + UPDATE_IRQ_STAT(IRQ_C_REG, reg); + + if (reg & IRQ_C_CHG_TERM) + chip->batt_full = true; + + rc = smb1360_read(chip, IRQ_A_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc); + return rc; + } + UPDATE_IRQ_STAT(IRQ_A_REG, reg); + + if (chip->workaround_flags & WRKRND_HARD_JEITA) { + schedule_delayed_work(&chip->jeita_work, 0); + } else { + if (reg & IRQ_A_HOT_HARD_BIT) + chip->batt_hot = true; + if (reg & IRQ_A_COLD_HARD_BIT) + chip->batt_cold = true; + if (!chip->config_hard_thresholds) { + if (reg & IRQ_A_HOT_SOFT_BIT) + chip->batt_warm = true; + if (reg & IRQ_A_COLD_SOFT_BIT) + chip->batt_cool = true; + } + } + + rc = smb1360_read(chip, IRQ_E_REG, ®); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc); + return rc; + } + UPDATE_IRQ_STAT(IRQ_E_REG, reg); + + /* Check usb charger presence and notify */ + chip->usb_present = (reg & IRQ_E_USBIN_UV_BIT) ? false : true; + /* USB removed */ + if (!chip->usb_present) + extcon_set_state_sync(chip->extcon, EXTCON_USB, false); + /* USB inserted */ + else + extcon_set_state_sync(chip->extcon, EXTCON_USB, true); + + pr_debug("usb %s at boot\n", chip->usb_present ? "present" : "absent"); + + /*check otg presence and notify*/ + if (chip->usb_id_gpio != -EINVAL) { + id_state = gpio_get_value(chip->usb_id_gpio); + /* usb-id is low, enable OTG */ + if (!id_state) { + rc = smb1360_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; + } + extcon_set_state_sync(chip->extcon, EXTCON_USB_HOST, + true); + pr_debug("OTG enabled at boot\n"); + } + } + + power_supply_changed(chip->usb_psy); + return 0; +} + +static int smb1360_fg_config(struct smb1360_chip *chip) +{ + int rc = 0, temp, fcc_mah; + u8 reg = 0, reg2[2]; + + if (chip->fg_reset_at_pon) { + int v_predicted, v_now; + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("Couldn't enable FG access rc=%d\n", rc); + return rc; + } + + rc = smb1360_read_bytes(chip, VOLTAGE_PREDICTED_REG, reg2, 2); + if (rc) { + pr_err("Failed to read VOLTAGE_PREDICTED rc=%d\n", rc); + goto disable_fg_reset; + } + v_predicted = (reg2[1] << 8) | reg2[0]; + v_predicted = div_u64(v_predicted * 5000, 0x7FFF); + + rc = smb1360_read_bytes(chip, SHDW_FG_VTG_NOW, reg2, 2); + if (rc) { + pr_err("Failed to read SHDW_FG_VTG_NOW rc=%d\n", rc); + goto disable_fg_reset; + } + v_now = (reg2[1] << 8) | reg2[0]; + v_now = div_u64(v_now * 5000, 0x7FFF); + + pr_debug("v_predicted=%d v_now=%d reset_threshold=%d\n", + v_predicted, v_now, chip->fg_reset_threshold_mv); + + /* + * Reset FG if the predicted voltage is off wrt + * the real-time voltage. + */ + temp = abs(v_predicted - v_now); + if (temp >= chip->fg_reset_threshold_mv) { + pr_info("Resetting FG - v_delta=%d threshold=%d\n", + temp, chip->fg_reset_threshold_mv); + /* delay for the FG access to settle */ + msleep(1500); + rc = smb1360_force_fg_reset(chip); + if (rc) { + pr_err("Couldn't reset FG rc=%d\n", rc); + goto disable_fg_reset; + } + } +disable_fg_reset: + smb1360_disable_fg_access(chip); + } + + /* + * The below IRQ thresholds are not accessible in REV_1 + * of SMB1360. + */ + if (!(chip->workaround_flags & WRKRND_FG_CONFIG_FAIL)) { + if (chip->delta_soc != -EINVAL) { + reg = abs(((chip->delta_soc * MAX_8_BITS) / 100) - 1); + pr_debug("delta_soc=%d reg=%x\n", chip->delta_soc, reg); + rc = smb1360_write(chip, SOC_DELTA_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to SOC_DELTA_REG rc=%d\n", + rc); + return rc; + } + } + + if (chip->soc_min != -EINVAL) { + if (is_between(chip->soc_min, 0, 100)) { + reg = DIV_ROUND_UP(chip->soc_min * MAX_8_BITS, + 100); + pr_debug("soc_min=%d reg=%x\n", + chip->soc_min, reg); + rc = smb1360_write(chip, SOC_MIN_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to SOC_MIN_REG rc=%d\n", + rc); + return rc; + } + } + } + + if (chip->soc_max != -EINVAL) { + if (is_between(chip->soc_max, 0, 100)) { + reg = DIV_ROUND_UP(chip->soc_max * MAX_8_BITS, + 100); + pr_debug("soc_max=%d reg=%x\n", + chip->soc_max, reg); + rc = smb1360_write(chip, SOC_MAX_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to SOC_MAX_REG rc=%d\n", + rc); + return rc; + } + } + } + + if (chip->voltage_min_mv != -EINVAL) { + temp = (chip->voltage_min_mv - 2500) * MAX_8_BITS; + reg = DIV_ROUND_UP(temp, 2500); + pr_debug("voltage_min=%d reg=%x\n", + chip->voltage_min_mv, reg); + rc = smb1360_write(chip, VTG_MIN_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to VTG_MIN_REG rc=%d\n", + rc); + return rc; + } + } + + if (chip->voltage_empty_mv != -EINVAL) { + temp = (chip->voltage_empty_mv - 2500) * MAX_8_BITS; + reg = DIV_ROUND_UP(temp, 2500); + pr_debug("voltage_empty=%d reg=%x\n", + chip->voltage_empty_mv, reg); + rc = smb1360_write(chip, VTG_EMPTY_REG, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to VTG_EMPTY_REG rc=%d\n", + rc); + return rc; + } + } + } + + /* scratch-pad register config */ + if (chip->batt_capacity_mah != -EINVAL + || chip->v_cutoff_mv != -EINVAL + || chip->fg_iterm_ma != -EINVAL + || chip->fg_ibatt_standby_ma != -EINVAL + || chip->fg_thermistor_c1_coeff != -EINVAL + || chip->fg_cc_to_cv_mv != -EINVAL + || chip->fg_auto_recharge_soc != -EINVAL) { + + rc = smb1360_enable_fg_access(chip); + if (rc) { + pr_err("Couldn't enable FG access rc=%d\n", rc); + return rc; + } + + /* Update battery capacity */ + if (chip->batt_capacity_mah != -EINVAL) { + rc = smb1360_read_bytes(chip, ACTUAL_CAPACITY_REG, + reg2, 2); + if (rc) { + pr_err("Failed to read ACTUAL CAPACITY rc=%d\n", + rc); + goto disable_fg; + } + fcc_mah = (reg2[1] << 8) | reg2[0]; + if (fcc_mah == chip->batt_capacity_mah) { + pr_debug("battery capacity correct\n"); + } else { + /* Update the battery capacity */ + reg2[1] = + (chip->batt_capacity_mah & 0xFF00) >> 8; + reg2[0] = (chip->batt_capacity_mah & 0xFF); + rc = smb1360_write_bytes(chip, + ACTUAL_CAPACITY_REG, reg2, 2); + if (rc) { + pr_err("Couldn't write batt-capacity rc=%d\n", + rc); + goto disable_fg; + } + rc = smb1360_write_bytes(chip, + NOMINAL_CAPACITY_REG, reg2, 2); + if (rc) { + pr_err("Couldn't write batt-capacity rc=%d\n", + rc); + goto disable_fg; + } + + /* Update CC to SOC COEFF */ + if (chip->cc_soc_coeff != -EINVAL) { + reg2[1] = + (chip->cc_soc_coeff & 0xFF00) >> 8; + reg2[0] = (chip->cc_soc_coeff & 0xFF); + rc = smb1360_write_bytes(chip, + CC_TO_SOC_COEFF, reg2, 2); + if (rc) { + pr_err("Couldn't write cc_soc_coeff rc=%d\n", + rc); + goto disable_fg; + } + } + } + } + + /* Update cutoff voltage for SOC = 0 */ + if (chip->v_cutoff_mv != -EINVAL) { + temp = (u16) div_u64(chip->v_cutoff_mv * 0x7FFF, 5000); + reg2[1] = (temp & 0xFF00) >> 8; + reg2[0] = temp & 0xFF; + rc = smb1360_write_bytes(chip, FG_SYS_CUTOFF_V_REG, + reg2, 2); + if (rc) { + pr_err("Couldn't write cutoff_mv rc=%d\n", rc); + goto disable_fg; + } + } + + /* + * Update FG iterm for SOC = 100, this value is always assumed + * to be -ve + */ + if (chip->fg_iterm_ma != -EINVAL) { + int iterm = chip->fg_iterm_ma * -1; + + temp = (s16) div_s64(iterm * 0x7FFF, 2500); + reg2[1] = (temp & 0xFF00) >> 8; + reg2[0] = temp & 0xFF; + rc = smb1360_write_bytes(chip, FG_ITERM_REG, + reg2, 2); + if (rc) { + pr_err("Couldn't write fg_iterm rc=%d\n", rc); + goto disable_fg; + } + } + + /* + * Update FG iterm standby for SOC = 0, this value is always + * assumed to be +ve + */ + if (chip->fg_ibatt_standby_ma != -EINVAL) { + int iterm = chip->fg_ibatt_standby_ma; + + temp = (u16) div_u64(iterm * 0x7FFF, 2500); + reg2[1] = (temp & 0xFF00) >> 8; + reg2[0] = temp & 0xFF; + rc = smb1360_write_bytes(chip, FG_IBATT_STANDBY_REG, + reg2, 2); + if (rc) { + pr_err("Couldn't write fg_iterm rc=%d\n", rc); + goto disable_fg; + } + } + + /* Update CC_to_CV voltage threshold */ + if (chip->fg_cc_to_cv_mv != -EINVAL) { + temp = (u16) div_u64(chip->fg_cc_to_cv_mv * 0x7FFF, + 5000); + reg2[1] = (temp & 0xFF00) >> 8; + reg2[0] = temp & 0xFF; + rc = smb1360_write_bytes(chip, FG_CC_TO_CV_V_REG, + reg2, 2); + if (rc) { + pr_err("Couldn't write cc_to_cv_mv rc=%d\n", + rc); + goto disable_fg; + } + } + + /* Update the thermistor c1 coefficient */ + if (chip->fg_thermistor_c1_coeff != -EINVAL) { + reg2[1] = (chip->fg_thermistor_c1_coeff & 0xFF00) >> 8; + reg2[0] = (chip->fg_thermistor_c1_coeff & 0xFF); + rc = smb1360_write_bytes(chip, FG_THERM_C1_COEFF_REG, + reg2, 2); + if (rc) { + pr_err("Couldn't write thermistor_c1_coeff rc=%d\n", + rc); + goto disable_fg; + } + } + + /* Update SoC based resume charging threshold */ + if (chip->fg_auto_recharge_soc != -EINVAL) { + rc = smb1360_masked_write(chip, CFG_CHG_FUNC_CTRL_REG, + CHG_RECHG_THRESH_FG_SRC_BIT, + CHG_RECHG_THRESH_FG_SRC_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't write to CFG_CHG_FUNC_CTRL_REG rc=%d\n", + rc); + goto disable_fg; + } + + reg = DIV_ROUND_UP(chip->fg_auto_recharge_soc * + MAX_8_BITS, 100); + pr_debug("fg_auto_recharge_soc=%d reg=%x\n", + chip->fg_auto_recharge_soc, reg); + rc = smb1360_write(chip, FG_AUTO_RECHARGE_SOC, reg); + if (rc) { + dev_err(chip->dev, "Couldn't write to FG_AUTO_RECHARGE_SOC rc=%d\n", + rc); + goto disable_fg; + } + } + +disable_fg: + /* disable FG access */ + smb1360_disable_fg_access(chip); + } + + return rc; +} + +static void smb1360_check_feature_support(struct smb1360_chip *chip) +{ + + if (is_usb100_broken(chip)) { + pr_debug("USB100 is not supported\n"); + chip->workaround_flags |= WRKRND_USB100_FAIL; + } + + /* + * FG Configuration + * + * The REV_1 of the chip does not allow access to + * FG config registers (20-2FH). Set the workaround flag. + * Also, the battery detection does not work when the DCIN is absent, + * add a workaround flag for it. + */ + if (chip->revision == SMB1360_REV_1) { + pr_debug("FG config and Battery detection is not supported\n"); + chip->workaround_flags |= + WRKRND_FG_CONFIG_FAIL | WRKRND_BATT_DET_FAIL; + } +} + +static int smb1360_enable(struct smb1360_chip *chip, bool enable) +{ + int rc = 0; + u8 val = 0, shdn_cmd_polar; + + rc = smb1360_read(chip, SHDN_CTRL_REG, &val); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read 0x1A reg rc = %d\n", rc); + return rc; + } + + /* Ignore if a CMD based shutdown is not enabled */ + if (!(val & SHDN_CMD_USE_BIT)) { + pr_debug("SMB not configured for CMD based shutdown\n"); + return 0; + } + + shdn_cmd_polar = !!(val & SHDN_CMD_POLARITY_BIT); + val = (shdn_cmd_polar ^ enable) ? SHDN_CMD_BIT : 0; + + pr_debug("enable=%d shdn_polarity=%d value=%d\n", enable, + shdn_cmd_polar, val); + + rc = smb1360_masked_write(chip, CMD_IL_REG, SHDN_CMD_BIT, val); + if (rc < 0) + pr_err("Couldn't shutdown smb1360 rc = %d\n", rc); + + return rc; +} + +static inline int smb1360_poweroff(struct smb1360_chip *chip) +{ + pr_debug("power off smb1360\n"); + return smb1360_enable(chip, false); +} + +static inline int smb1360_poweron(struct smb1360_chip *chip) +{ + pr_debug("power on smb1360\n"); + return smb1360_enable(chip, true); +} + +static int smb1360_jeita_init(struct smb1360_chip *chip) +{ + int rc = 0; + int temp; + + if (chip->config_hard_thresholds) { + if (chip->soft_jeita_supported) { + chip->workaround_flags |= WRKRND_HARD_JEITA; + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cool_bat_decidegc, chip->warm_bat_decidegc); + if (rc) { + dev_err(chip->dev, + "Couldn't set jeita threshold\n"); + return rc; + } + } else { + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cold_bat_decidegc, chip->hot_bat_decidegc); + if (rc) { + dev_err(chip->dev, + "Couldn't set jeita threshold\n"); + return rc; + } + } + } else { + if (chip->soft_jeita_supported) { + temp = min(chip->warm_bat_ma, chip->cool_bat_ma); + rc = smb1360_set_jeita_comp_curr(chip, temp); + if (rc) { + dev_err(chip->dev, "Couldn't set comp current\n"); + return rc; + } + + temp = (chip->vfloat_mv - chip->warm_bat_mv) / 10; + rc = smb1360_masked_write(chip, CFG_FVC_REG, + FLT_VTG_COMP_MASK, temp); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set VFLT compensation = %d\n", rc); + return rc; + } + + rc = smb1360_set_soft_jeita_threshold(chip, + chip->cool_bat_decidegc, chip->warm_bat_decidegc); + if (rc) { + dev_err(chip->dev, + "Couldn't set jeita threshold\n"); + return rc; + } + + rc = smb1360_soft_jeita_comp_enable(chip, true); + if (rc) { + dev_err(chip->dev, "Couldn't enable jeita\n"); + return rc; + } + } + } + + return rc; +} + +static int smb1360_otp_gain_init(struct smb1360_chip *chip) +{ + int rc = 0, gain_factor; + bool otp_gain_config = false; + + if (chip->rsense_10mohm) { + gain_factor = 2; + otp_gain_config = true; + } + + mutex_lock(&chip->otp_gain_lock); + if (chip->otg_fet_present) { + /* + * Reset current gain to the default value if OTG + * is not enabled + */ + if (!chip->fet_gain_enabled) { + otp_gain_config = true; + gain_factor = 0; + } + } + + if (otp_gain_config) { + rc = smb1360_otp_gain_config(chip, gain_factor); + if (rc < 0) + pr_err("Couldn't config OTP gain rc=%d\n", rc); + } + mutex_unlock(&chip->otp_gain_lock); + + return rc; +} + +static int smb1360_hw_init(struct smb1360_chip *chip) +{ + int rc; + int i; + u8 reg, mask; + + smb1360_check_feature_support(chip); + + rc = smb1360_enable_volatile_writes(chip); + if (rc < 0) { + dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n", + rc); + return rc; + } + + /* Bring SMB1360 out of shutdown, if it was enabled by default */ + rc = smb1360_poweron(chip); + if (rc < 0) { + pr_err("smb1360 power on failed\n"); + return rc; + } + + /* + * A 2 seconds delay is mandatory after bringing the chip out + * of shutdown. This guarantees that FG is in a proper state. + */ + schedule_delayed_work(&chip->delayed_init_work, + msecs_to_jiffies(SMB1360_POWERON_DELAY_MS)); + + /* + * set chg en by cmd register, set chg en by writing bit 1, + * enable auto pre to fast + */ + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CHG_EN_BY_PIN_BIT + | CHG_EN_ACTIVE_LOW_BIT + | PRE_TO_FAST_REQ_CMD_BIT, + 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set CFG_CHG_MISC_REG rc=%d\n", rc); + return rc; + } + + /* USB/AC pin settings */ + rc = smb1360_masked_write(chip, CFG_BATT_CHG_ICL_REG, + AC_INPUT_ICL_PIN_BIT + | AC_INPUT_PIN_HIGH_BIT + | RESET_STATE_USB_500, + AC_INPUT_PIN_HIGH_BIT + | RESET_STATE_USB_500); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set CFG_BATT_CHG_ICL_REG rc=%d\n", + rc); + return rc; + } + + /* AICL enable and set input-uv glitch flt to 20ms*/ + reg = AICL_ENABLED_BIT | INPUT_UV_GLITCH_FLT_20MS_BIT; + rc = smb1360_masked_write(chip, CFG_GLITCH_FLT_REG, reg, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set CFG_GLITCH_FLT_REG rc=%d\n", + rc); + return rc; + } + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smb1360_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; + } + } + + /* 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; + } + + if (chip->rsense_10mohm) + chip->iterm_ma /= 2; + + if (chip->iterm_ma < 25) + reg = CHG_ITERM_25MA; + else if (chip->iterm_ma > 200) + reg = CHG_ITERM_200MA; + else + reg = DIV_ROUND_UP(chip->iterm_ma, 25) - 1; + + rc = smb1360_masked_write(chip, CFG_BATT_CHG_REG, + CHG_ITERM_MASK, reg); + if (rc) { + dev_err(chip->dev, "Couldn't set iterm rc = %d\n", rc); + return rc; + } + + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CHG_CURR_TERM_DIS_BIT, 0); + if (rc) { + dev_err(chip->dev, + "Couldn't enable iterm rc = %d\n", rc); + return rc; + } + } else if (chip->iterm_disabled) { + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CHG_CURR_TERM_DIS_BIT, + CHG_CURR_TERM_DIS_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set iterm rc = %d\n", + rc); + return rc; + } + } + + /* set the safety time voltage */ + if (chip->safety_time != -EINVAL) { + if (chip->safety_time == 0) { + /* safety timer disabled */ + rc = smb1360_masked_write(chip, CFG_SFY_TIMER_CTRL_REG, + SAFETY_TIME_DISABLE_BIT, SAFETY_TIME_DISABLE_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't disable safety timer rc = %d\n", + rc); + return rc; + } + } 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 = smb1360_masked_write(chip, CFG_SFY_TIMER_CTRL_REG, + SAFETY_TIME_DISABLE_BIT | SAFETY_TIME_MINUTES_MASK, + reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set safety timer rc = %d\n", rc); + return rc; + } + } + } + + /* configure resume threshold, auto recharge and charge inhibit */ + if (chip->resume_delta_mv != -EINVAL) { + if (chip->recharge_disabled && chip->chg_inhibit_disabled) { + dev_err(chip->dev, + "Error: Both recharge_disabled and recharge_mv set\n"); + return -EINVAL; + } + rc = smb1360_recharge_threshold_set(chip, + chip->resume_delta_mv); + if (rc) { + dev_err(chip->dev, + "Couldn't set rechg thresh rc = %d\n", rc); + return rc; + } + } + + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CFG_AUTO_RECHG_DIS_BIT, + chip->recharge_disabled ? + CFG_AUTO_RECHG_DIS_BIT : 0); + if (rc) { + dev_err(chip->dev, "Couldn't set rechg-cfg rc = %d\n", rc); + return rc; + } + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CFG_CHG_INHIBIT_EN_BIT, + chip->chg_inhibit_disabled ? + 0 : CFG_CHG_INHIBIT_EN_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set chg_inhibit rc = %d\n", rc); + return rc; + } + + rc = smb1360_masked_write(chip, CFG_CHG_MISC_REG, + CFG_BAT_OV_ENDS_CHG_CYC, + chip->ov_ends_chg_cycle_disabled ? + 0 : CFG_BAT_OV_ENDS_CHG_CYC); + if (rc) { + dev_err(chip->dev, "Couldn't set bat_ov_ends_charge rc = %d\n" + , rc); + return rc; + } + + /* battery missing detection */ + rc = smb1360_masked_write(chip, CFG_BATT_MISSING_REG, + BATT_MISSING_SRC_THERM_BIT, + BATT_MISSING_SRC_THERM_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_missing config = %d\n", + rc); + return rc; + } + + rc = smb1360_jeita_init(chip); + if (rc < 0) { + dev_err(chip->dev, "Couldn't init jeita, rc = %d\n", rc); + return rc; + } + + /* interrupt enabling - active low */ + if (chip->client->irq) { + mask = CHG_STAT_IRQ_ONLY_BIT + | CHG_STAT_ACTIVE_HIGH_BIT + | CHG_STAT_DISABLE_BIT + | CHG_TEMP_CHG_ERR_BLINK_BIT; + + if (!chip->pulsed_irq) + reg = CHG_STAT_IRQ_ONLY_BIT; + else + reg = CHG_TEMP_CHG_ERR_BLINK_BIT; + rc = smb1360_masked_write(chip, CFG_STAT_CTRL_REG, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq config rc = %d\n", + rc); + return rc; + } + + /* enabling only interesting interrupts */ + rc = smb1360_write(chip, IRQ_CFG_REG, + IRQ_BAT_HOT_COLD_HARD_BIT + | IRQ_BAT_HOT_COLD_SOFT_BIT + | IRQ_INTERNAL_TEMPERATURE_BIT + | IRQ_DCIN_UV_BIT + | IRQ_AICL_DONE_BIT); + if (rc) { + dev_err(chip->dev, "Couldn't set irq1 config rc = %d\n", + rc); + return rc; + } + + rc = smb1360_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); + if (rc) { + dev_err(chip->dev, "Couldn't set irq2 config rc = %d\n", + rc); + return rc; + } + + rc = smb1360_write(chip, IRQ3_CFG_REG, + IRQ3_FG_ACCESS_OK_BIT + | IRQ3_SOC_CHANGE_BIT + | IRQ3_SOC_MIN_BIT + | IRQ3_SOC_MAX_BIT + | IRQ3_SOC_EMPTY_BIT + | IRQ3_SOC_FULL_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set irq3 enable rc = %d\n", + rc); + return rc; + } + } + + /* batt-id configuration */ + if (chip->batt_id_disabled) { + mask = BATT_ID_ENABLED_BIT | CHG_BATT_ID_FAIL; + reg = CHG_BATT_ID_FAIL; + rc = smb1360_masked_write(chip, CFG_FG_BATT_CTRL_REG, + mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_id_reg rc = %d\n", + rc); + return rc; + } + } + + /* USB OTG current limit configuration */ + if (chip->otg_batt_curr_limit != -EINVAL) { + for (i = 0; i < ARRAY_SIZE(otg_curr_ma); i++) { + if (otg_curr_ma[i] >= chip->otg_batt_curr_limit) + break; + } + + if (i == ARRAY_SIZE(otg_curr_ma)) + i = i - 1; + + rc = smb1360_masked_write(chip, CFG_BATT_CHG_REG, + OTG_CURRENT_MASK, + i << OTG_CURRENT_SHIFT); + if (rc) + pr_err("Couldn't set OTG current limit, rc = %d\n", rc); + } + + rc = smb1360_charging_disable(chip, USER, !!chip->charging_disabled); + if (rc) + dev_err(chip->dev, "Couldn't '%s' charging rc = %d\n", + chip->charging_disabled ? "disable" : "enable", rc); + + if (chip->parallel_charging) { + rc = smb1360_parallel_charger_enable(chip, PARALLEL_USER, + !chip->charging_disabled); + if (rc) + dev_err(chip->dev, "Couldn't '%s' parallel-charging rc = %d\n", + chip->charging_disabled ? "disable" : "enable", rc); + } + + return rc; +} + +static int smb1360_delayed_hw_init(struct smb1360_chip *chip) +{ + int rc; + + pr_debug("delayed hw init start!\n"); + + if (chip->otp_hard_jeita_config) { + rc = smb1360_hard_jeita_otp_init(chip); + if (rc) { + pr_err("Unable to change the OTP hard jeita, rc=%d\n", + rc); + return rc; + } + } + rc = smb1360_check_batt_profile(chip); + if (rc) { + pr_err("Unable to modify battery profile, rc=%d\n", rc); + return rc; + } + + rc = smb1360_otp_gain_init(chip); + if (rc) { + pr_err("Unable to config otp gain, rc=%d\n", rc); + return rc; + } + + rc = smb1360_fg_config(chip); + if (rc) { + pr_err("Couldn't configure FG rc=%d\n", rc); + return rc; + } + + rc = smb1360_check_cycle_stretch(chip); + if (rc) { + pr_err("Unable to check cycle-stretch\n"); + return rc; + } + + pr_debug("delayed hw init complete!\n"); + return rc; +} + +static void smb1360_delayed_init_work_fn(struct work_struct *work) +{ + int rc = 0; + struct smb1360_chip *chip = container_of(work, struct smb1360_chip, + delayed_init_work.work); + + rc = smb1360_delayed_hw_init(chip); + + if (!rc) { + /* + * If the delayed hw init successfully, update battery + * power_supply to make sure the correct SoC reported + * timely. + */ + power_supply_changed(chip->batt_psy); + } else if (rc == -ETIMEDOUT) { + /* + * If the delayed hw init failed causing by waiting for + * FG access timed-out, force a FG reset and queue the + * worker again to retry the initialization. + */ + pr_debug("delayed hw init timed-out, retry!\n"); + rc = smb1360_force_fg_reset(chip); + if (rc) { + pr_err("couldn't reset FG, rc = %d\n", rc); + return; + } + schedule_delayed_work(&chip->delayed_init_work, 0); + } else { + pr_err("delayed hw init failed, rc=%d\n", rc); + } +} + +static int smb_parse_batt_id(struct smb1360_chip *chip) +{ + int rc = 0, rpull = 0, vref = 0, batt_id_uv; + int64_t denom; + struct device_node *node = chip->dev->of_node; + + chip->lr_mux2_batt_id = iio_channel_get(chip->dev, "batt_id"); + if (IS_ERR(chip->lr_mux2_batt_id)) { + if (PTR_ERR(chip->lr_mux2_batt_id) != -EPROBE_DEFER) + pr_err("batt_id unavailable %ld\n", + PTR_ERR(chip->lr_mux2_batt_id)); + rc = PTR_ERR(chip->lr_mux2_batt_id); + chip->lr_mux2_batt_id = NULL; + return rc; + } + + rc = of_property_read_u32(node, "qcom,profile-a-rid-kohm", + &chip->profile_rid[0]); + if (rc < 0) { + pr_err("Couldn't read profile-a-rid-kohm rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, "qcom,profile-b-rid-kohm", + &chip->profile_rid[1]); + if (rc < 0) { + pr_err("Couldn't read profile-b-rid-kohm rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, "qcom,batt-id-vref-uv", &vref); + if (rc < 0) { + pr_err("Couldn't read batt-id-vref-uv rc=%d\n", rc); + return rc; + } + + rc = of_property_read_u32(node, "qcom,batt-id-rpullup-kohm", &rpull); + if (rc < 0) { + pr_err("Couldn't read batt-id-rpullup-kohm rc=%d\n", rc); + return rc; + } + + /* read battery ID */ + rc = iio_read_channel_processed(chip->lr_mux2_batt_id, &batt_id_uv); + if (rc < 0) { + pr_err("error reading batt id channel : rc = %d\n", rc); + return rc; + } + + if (batt_id_uv == 0) { + /* vadc not correct or batt id line grounded, report 0 kohms */ + pr_err("batt_id_uv = 0, batt-id grounded using same profile\n"); + return 0; + } + + denom = div64_s64(vref * 1000000LL, batt_id_uv) - 1000000LL; + if (denom == 0) { + /* batt id connector might be open, return 0 kohms */ + return 0; + } + chip->connected_rid = div64_s64(rpull * 1000000LL + denom/2, denom); + + pr_debug("batt_id_voltage = %lld, connected_rid = %d\n", + batt_id_uv, chip->connected_rid); + + return 0; +} + +/* + * Note the below: + * 1. if both qcom,soft-jeita-supported and qcom,config-hard-thresholds + * are not defined, SMB continues with default OTP configuration. + * 2. if both are enabled, the hard thresholds are modified. + * 3. if only qcom,config-hard-thresholds is defined, the soft JEITA is disabled + * 4. if only qcom,soft-jeita-supported is defined, the soft JEITA thresholds + * are modified. + */ +static int smb1360_parse_jeita_params(struct smb1360_chip *chip) +{ + int rc = 0; + struct device_node *node = chip->dev->of_node; + int temp[2]; + + if (of_property_read_bool(node, "qcom,config-hard-thresholds")) { + rc = of_property_read_u32(node, + "qcom,cold-bat-decidegc", &chip->cold_bat_decidegc); + if (rc) { + pr_err("cold_bat_decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, + "qcom,hot-bat-decidegc", &chip->hot_bat_decidegc); + if (rc) { + pr_err("hot_bat_decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + + chip->config_hard_thresholds = true; + pr_debug("config_hard_thresholds = %d, cold_bat_decidegc = %d, hot_bat_decidegc = %d\n", + chip->config_hard_thresholds, chip->cold_bat_decidegc, + chip->hot_bat_decidegc); + } else if (of_property_read_bool(node, "qcom,otp-hard-jeita-config")) { + rc = of_property_read_u32(node, "qcom,otp-cold-bat-decidegc", + &chip->otp_cold_bat_decidegc); + if (rc) { + pr_err("otp-cold-bat-decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,otp-hot-bat-decidegc", + &chip->otp_hot_bat_decidegc); + + if (rc) { + pr_err("otp-hot-bat-decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + + chip->otp_hard_jeita_config = true; + rc = of_property_read_u32_array(node, + "qcom,otp-hard-jeita-hysteresis", temp, 2); + if (rc) { + if (rc != -EINVAL) { + pr_err("read otp-hard-jeita-hysteresis failed, rc = %d\n", + rc); + return rc; + } + } else { + chip->cold_hysteresis = temp[0]; + chip->hot_hysteresis = temp[1]; + } + + pr_debug("otp_hard_jeita_config = %d, otp_cold_bat_decidegc = %d\n" + "otp_hot_bat_decidegc = %d, cold_hysteresis = %d\n" + "hot_hysteresis = %d\n", + chip->otp_hard_jeita_config, + chip->otp_cold_bat_decidegc, + chip->otp_hot_bat_decidegc, chip->cold_hysteresis, + chip->hot_hysteresis); + } + + if (of_property_read_bool(node, "qcom,soft-jeita-supported")) { + rc = of_property_read_u32(node, "qcom,warm-bat-decidegc", + &chip->warm_bat_decidegc); + if (rc) { + pr_err("warm_bat_decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,cool-bat-decidegc", + &chip->cool_bat_decidegc); + if (rc) { + pr_err("cool_bat_decidegc property error, rc = %d\n", + rc); + return -EINVAL; + } + rc = of_property_read_u32(node, "qcom,cool-bat-mv", + &chip->cool_bat_mv); + if (rc) { + pr_err("cool_bat_mv property error, rc = %d\n", rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,warm-bat-mv", + &chip->warm_bat_mv); + if (rc) { + pr_err("warm_bat_mv property error, rc = %d\n", rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,cool-bat-ma", + &chip->cool_bat_ma); + if (rc) { + pr_err("cool_bat_ma property error, rc = %d\n", rc); + return -EINVAL; + } + + rc = of_property_read_u32(node, "qcom,warm-bat-ma", + &chip->warm_bat_ma); + + if (rc) { + pr_err("warm_bat_ma property error, rc = %d\n", rc); + return -EINVAL; + } + + chip->soft_jeita_supported = true; + } else { + /* + * If no soft JEITA configuration required from devicetree, + * read the default soft JEITA setting for hard JEITA + * configuration sanity check. + */ + rc = smb1360_get_soft_jeita_threshold(chip, + &chip->cool_bat_decidegc, + &chip->warm_bat_decidegc); + if (rc) { + pr_err("get default soft JEITA threshold failed, rc=%d\n", + rc); + return rc; + } + } + + pr_debug("soft-jeita-enabled = %d, warm-bat-decidegc = %d, cool-bat-decidegc = %d, cool-bat-mv = %d, warm-bat-mv = %d, cool-bat-ma = %d, warm-bat-ma = %d\n", + chip->soft_jeita_supported, chip->warm_bat_decidegc, + chip->cool_bat_decidegc, chip->cool_bat_mv, chip->warm_bat_mv, + chip->cool_bat_ma, chip->warm_bat_ma); + + return rc; +} + +#define MAX_PARALLEL_CURRENT 540 +static int smb1360_parse_parallel_charging_params(struct smb1360_chip *chip) +{ + struct device_node *node = chip->dev->of_node; + + if (of_property_read_bool(node, "qcom,parallel-charging-enabled")) { + + if (!chip->rsense_10mohm) { + pr_err("10mohm-rsense configuration not enabled - parallel-charging disabled\n"); + return 0; + } + chip->parallel_charging = true; + chip->max_parallel_chg_current = MAX_PARALLEL_CURRENT; + of_property_read_u32(node, "qcom,max-parallel-current-ma", + &chip->max_parallel_chg_current); + + pr_debug("Max parallel charger current = %dma\n", + chip->max_parallel_chg_current); + + /* mark the parallel-charger as disabled */ + chip->parallel_chg_disable_status |= PARALLEL_CURRENT; + } + + return 0; +} + +static int smb_parse_dt(struct smb1360_chip *chip) +{ + int rc; + struct device_node *node = chip->dev->of_node; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + chip->rsense_10mohm = of_property_read_bool(node, "qcom,rsense-10mhom"); + + if (of_property_read_bool(node, "qcom,batt-profile-select")) { + rc = smb_parse_batt_id(chip); + if (rc < 0) { + if (rc != -EPROBE_DEFER) + pr_err("Unable to parse batt-id rc=%d\n", rc); + return rc; + } + } + + chip->otg_fet_present = of_property_read_bool(node, + "qcom,otg-fet-present"); + if (chip->otg_fet_present) { + chip->otg_fet_enable_gpio = of_get_named_gpio(node, + "qcom,otg-fet-enable-gpio", 0); + if (!gpio_is_valid(chip->otg_fet_enable_gpio)) { + if (chip->otg_fet_enable_gpio != -EPROBE_DEFER) + pr_err("Unable to get OTG FET enable gpio=%d\n", + chip->otg_fet_enable_gpio); + return chip->otg_fet_enable_gpio; + } + /* Configure OTG FET control gpio */ + rc = devm_gpio_request_one(chip->dev, + chip->otg_fet_enable_gpio, + GPIOF_OPEN_DRAIN | GPIOF_INIT_HIGH, + "smb1360_otg_fet_gpio"); + if (rc) { + pr_err("Unable to request gpio rc=%d\n", rc); + return rc; + } + } + chip->usb_id_gpio = -EINVAL; + if (of_find_property(node, "qcom,usb-id-gpio", NULL)) { + chip->usb_id_gpio = of_get_named_gpio(node, + "qcom,usb-id-gpio", 0); + } + + chip->pulsed_irq = of_property_read_bool(node, "qcom,stat-pulsed-irq"); + + 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; + } + + rc = of_property_read_u32(node, "qcom,recharge-thresh-mv", + &chip->resume_delta_mv); + if (rc < 0) + chip->resume_delta_mv = -EINVAL; + + chip->recharge_disabled = of_property_read_bool(node, + "qcom,recharge-disabled"); + + 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_inhibit_disabled = of_property_read_bool(node, + "qcom,chg-inhibit-disabled"); + + chip->charging_disabled = of_property_read_bool(node, + "qcom,charging-disabled"); + + chip->batt_id_disabled = of_property_read_bool(node, + "qcom,batt-id-disabled"); + + chip->shdn_after_pwroff = of_property_read_bool(node, + "qcom,shdn-after-pwroff"); + + chip->min_icl_usb100 = of_property_read_bool(node, + "qcom,min-icl-100ma"); + + chip->ov_ends_chg_cycle_disabled = of_property_read_bool(node, + "qcom,disable-ov-ends-chg-cycle"); + + rc = smb1360_parse_parallel_charging_params(chip); + if (rc) { + pr_err("Couldn't parse parallel charginng params 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; + } + } + + rc = smb1360_parse_jeita_params(chip); + if (rc < 0) { + pr_err("Couldn't parse jeita params, rc = %d\n", rc); + return rc; + } + + /* fg params */ + chip->empty_soc_disabled = of_property_read_bool(node, + "qcom,empty-soc-disabled"); + + rc = of_property_read_u32(node, "qcom,fg-delta-soc", &chip->delta_soc); + if (rc < 0) + chip->delta_soc = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-soc-max", &chip->soc_max); + if (rc < 0) + chip->soc_max = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-soc-min", &chip->soc_min); + if (rc < 0) + chip->soc_min = -EINVAL; + + chip->awake_min_soc = of_property_read_bool(node, + "qcom,awake-min-soc"); + + rc = of_property_read_u32(node, "qcom,fg-voltage-min-mv", + &chip->voltage_min_mv); + if (rc < 0) + chip->voltage_min_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-voltage-empty-mv", + &chip->voltage_empty_mv); + if (rc < 0) + chip->voltage_empty_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-batt-capacity-mah", + &chip->batt_capacity_mah); + if (rc < 0) + chip->batt_capacity_mah = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-cc-soc-coeff", + &chip->cc_soc_coeff); + if (rc < 0) + chip->cc_soc_coeff = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-cutoff-voltage-mv", + &chip->v_cutoff_mv); + if (rc < 0) + chip->v_cutoff_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-iterm-ma", + &chip->fg_iterm_ma); + if (rc < 0) + chip->fg_iterm_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-ibatt-standby-ma", + &chip->fg_ibatt_standby_ma); + if (rc < 0) + chip->fg_ibatt_standby_ma = -EINVAL; + + rc = of_property_read_u32(node, "qcom,thermistor-c1-coeff", + &chip->fg_thermistor_c1_coeff); + if (rc < 0) + chip->fg_thermistor_c1_coeff = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-cc-to-cv-mv", + &chip->fg_cc_to_cv_mv); + if (rc < 0) + chip->fg_cc_to_cv_mv = -EINVAL; + + rc = of_property_read_u32(node, "qcom,otg-batt-curr-limit", + &chip->otg_batt_curr_limit); + if (rc < 0) + chip->otg_batt_curr_limit = -EINVAL; + + rc = of_property_read_u32(node, "qcom,fg-auto-recharge-soc", + &chip->fg_auto_recharge_soc); + if (rc < 0) + chip->fg_auto_recharge_soc = -EINVAL; + + if (of_property_read_bool(node, "qcom,fg-reset-at-pon")) { + chip->fg_reset_at_pon = true; + rc = of_property_read_u32(node, "qcom,fg-reset-threshold-mv", + &chip->fg_reset_threshold_mv); + if (rc) { + pr_debug("FG reset voltage threshold not specified using 50mV\n"); + chip->fg_reset_threshold_mv = FG_RESET_THRESHOLD_MV; + } + } + + return 0; +} + +static int smb1360_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + u8 reg; + int rc; + struct smb1360_chip *chip; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config usb_psy_cfg = {}; + + chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->resume_completed = true; + chip->client = client; + chip->dev = &client->dev; + chip->fake_battery_soc = -EINVAL; + chip->usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + + chip->extcon = devm_extcon_dev_allocate(chip->dev, + smb1360_extcon_cable); + if (IS_ERR(chip->extcon)) { + pr_err("failed to allocate extcon device\n"); + rc = PTR_ERR(chip->extcon); + return rc; + } + + rc = devm_extcon_dev_register(chip->dev, chip->extcon); + if (rc) { + pr_err("failed to register extcon device\n"); + return rc; + } + + mutex_init(&chip->read_write_lock); + mutex_init(&chip->parallel_chg_lock); + mutex_init(&chip->otp_gain_lock); + mutex_init(&chip->fg_access_request_lock); + mutex_init(&chip->irq_complete); + mutex_init(&chip->charging_disable_lock); + mutex_init(&chip->current_change_lock); + + INIT_DELAYED_WORK(&chip->jeita_work, smb1360_jeita_work_fn); + INIT_DELAYED_WORK(&chip->delayed_init_work, + smb1360_delayed_init_work_fn); + init_completion(&chip->fg_mem_access_granted); + smb1360_wakeup_src_init(chip); + + chip->usb_psy_d.name = "usb"; + chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB; + chip->usb_psy_d.get_property = smb1360_usb_get_property; + chip->usb_psy_d.set_property = smb1360_usb_set_property; + chip->usb_psy_d.properties = smb1360_usb_properties; + chip->usb_psy_d.num_properties = ARRAY_SIZE(smb1360_usb_properties); + chip->usb_psy_d.property_is_writeable = smb1360_usb_is_writeable; + + usb_psy_cfg.drv_data = chip; + usb_psy_cfg.num_supplicants = 0; + + chip->usb_psy = devm_power_supply_register(chip->dev, + &chip->usb_psy_d, &usb_psy_cfg); + if (IS_ERR(chip->usb_psy)) { + dev_err(chip->dev, "Unable to register usb_psy rc = %ld\n", + PTR_ERR(chip->usb_psy)); + rc = PTR_ERR(chip->usb_psy); + return rc; + } + + /* probe the device to check if its actually connected */ + rc = smb1360_read(chip, CFG_BATT_CHG_REG, ®); + if (rc) { + pr_err("Failed to detect SMB1360, device may be absent\n"); + goto destroy_mutex; + } + + rc = read_revision(chip, &chip->revision); + if (rc) + dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc); + + rc = smb_parse_dt(chip); + if (rc < 0) { + dev_err(&client->dev, "Unable to parse DT nodes\n"); + goto destroy_mutex; + } + + device_init_wakeup(chip->dev, 1); + i2c_set_clientdata(client, chip); + chip->default_i2c_addr = client->addr; + INIT_WORK(&chip->parallel_work, smb1360_parallel_work); + if (chip->cold_hysteresis || chip->hot_hysteresis) + INIT_WORK(&chip->jeita_hysteresis_work, + smb1360_jeita_hysteresis_work); + + pr_debug("default_i2c_addr=%x\n", chip->default_i2c_addr); + smb1360_otp_backup_pool_init(chip); + rc = smb1360_hw_init(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to initialize hardware rc = %d\n", rc); + goto destroy_mutex; + } + + rc = smb1360_regulator_init(chip); + if (rc) { + dev_err(&client->dev, + "Couldn't initialize smb1360 ragulator rc=%d\n", rc); + goto fail_hw_init; + } + + rc = determine_initial_status(chip); + if (rc < 0) { + dev_err(&client->dev, + "Unable to determine init status rc = %d\n", rc); + goto fail_hw_init; + } + + chip->batt_psy_d.name = "battery"; + chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY; + chip->batt_psy_d.get_property = smb1360_battery_get_property; + chip->batt_psy_d.set_property = smb1360_battery_set_property; + chip->batt_psy_d.properties = smb1360_battery_properties; + chip->batt_psy_d.num_properties = + ARRAY_SIZE(smb1360_battery_properties); + chip->batt_psy_d.property_is_writeable = smb1360_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(&client->dev, "Unable to register batt_psy rc = %ld\n", + PTR_ERR(chip->batt_psy)); + goto unregister_batt_psy; + } + + /* STAT irq configuration */ + if (client->irq) { + rc = devm_request_threaded_irq(&client->dev, client->irq, NULL, + smb1360_stat_handler, IRQF_ONESHOT, + "smb1360_stat_irq", chip); + if (rc < 0) { + dev_err(&client->dev, + "request_irq for irq=%d failed rc = %d\n", + client->irq, rc); + goto unregister_batt_psy; + } + enable_irq_wake(client->irq); + } + + chip->usb_id_irq = of_irq_get_byname(chip->dev->of_node, + "smb1360_usb_id_irq"); + if (chip->usb_id_irq > 0) { + if (chip->usb_id_gpio == -EINVAL) { + pr_err("usb-id gpio not defined\n"); + } else { + rc = devm_request_threaded_irq(&client->dev, + chip->usb_id_irq, NULL, + smb1360_usb_id_irq_handler, + IRQF_ONESHOT + | IRQF_TRIGGER_FALLING + | IRQF_TRIGGER_RISING, + "smb1360_usb_id_irq", chip); + if (rc < 0) { + dev_err(&client->dev, + "usb-id request_irq for irq=%d failed rc = %d\n", + chip->usb_id_irq, rc); + goto unregister_batt_psy; + } + enable_irq_wake(chip->usb_id_irq); + } + } + chip->debug_root = debugfs_create_dir("smb1360", 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 | 0444, + chip->debug_root, chip, + &cnfg_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cnfg debug file rc = %d\n", + rc); + + ent = debugfs_create_file("status_registers", S_IFREG | 0444, + chip->debug_root, chip, + &status_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create status debug file rc = %d\n", + rc); + + ent = debugfs_create_file("irq_status", S_IFREG | 0444, + chip->debug_root, chip, + &irq_stat_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create irq_stat debug file rc = %d\n", + rc); + + ent = debugfs_create_file("cmd_registers", S_IFREG | 0444, + chip->debug_root, chip, + &cmd_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create cmd debug file rc = %d\n", + rc); + + ent = debugfs_create_file("fg_regs", + S_IFREG | 0444, chip->debug_root, chip, + &fg_regs_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create fg_scratch_pad debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("address", S_IFREG | 0644, + chip->debug_root, + &(chip->peek_poke_address)); + if (!ent) + dev_err(chip->dev, + "Couldn't create address debug file rc = %d\n", + rc); + + ent = debugfs_create_file("data", S_IFREG | 0644, + chip->debug_root, chip, + &poke_poke_debug_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("fg_address", + S_IFREG | 0644, + chip->debug_root, + &(chip->fg_peek_poke_address)); + if (!ent) + dev_err(chip->dev, + "Couldn't create address debug file rc = %d\n", + rc); + + ent = debugfs_create_file("fg_data", + S_IFREG | 0644, + chip->debug_root, chip, + &fg_poke_poke_debug_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("fg_access_type", + S_IFREG | 0644, + chip->debug_root, + &(chip->fg_access_type)); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("skip_writes", + S_IFREG | 0644, + chip->debug_root, + &(chip->skip_writes)); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_x32("skip_reads", + S_IFREG | 0644, + chip->debug_root, + &(chip->skip_reads)); + if (!ent) + dev_err(chip->dev, + "Couldn't create data debug file rc = %d\n", + rc); + + ent = debugfs_create_file("irq_count", S_IFREG | 0444, + chip->debug_root, chip, + &irq_count_debugfs_ops); + if (!ent) + dev_err(chip->dev, + "Couldn't create count debug file rc = %d\n", + rc); + } + + dev_info(chip->dev, "SMB1360 revision=0x%x probe success! batt=%d usb=%d soc=%d\n", + chip->revision, + smb1360_get_prop_batt_present(chip), + chip->usb_present, + smb1360_get_prop_batt_capacity(chip)); + + return 0; + +unregister_batt_psy: + power_supply_unregister(chip->batt_psy); +fail_hw_init: + if (chip->otg_vreg.rdev) + regulator_unregister(chip->otg_vreg.rdev); +destroy_mutex: + power_supply_unregister(chip->usb_psy); + wakeup_source_unregister(chip->smb1360_ws.source); + mutex_destroy(&chip->read_write_lock); + mutex_destroy(&chip->parallel_chg_lock); + mutex_destroy(&chip->otp_gain_lock); + mutex_destroy(&chip->fg_access_request_lock); + mutex_destroy(&chip->irq_complete); + mutex_destroy(&chip->charging_disable_lock); + mutex_destroy(&chip->current_change_lock); + return rc; +} + +static int smb1360_remove(struct i2c_client *client) +{ + struct smb1360_chip *chip = i2c_get_clientdata(client); + + if (chip->otg_vreg.rdev) + regulator_unregister(chip->otg_vreg.rdev); + + power_supply_unregister(chip->usb_psy); + power_supply_unregister(chip->batt_psy); + wakeup_source_unregister(chip->smb1360_ws.source); + mutex_destroy(&chip->charging_disable_lock); + mutex_destroy(&chip->current_change_lock); + mutex_destroy(&chip->read_write_lock); + mutex_destroy(&chip->parallel_chg_lock); + mutex_destroy(&chip->irq_complete); + mutex_destroy(&chip->otp_gain_lock); + mutex_destroy(&chip->fg_access_request_lock); + debugfs_remove_recursive(chip->debug_root); + + return 0; +} + +static int smb1360_suspend(struct device *dev) +{ + int i, rc; + struct i2c_client *client = to_i2c_client(dev); + struct smb1360_chip *chip = i2c_get_clientdata(client); + + /* Save the current IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb1360_read(chip, IRQ_CFG_REG + i, + &chip->irq_cfg_mask[i]); + if (rc) + pr_err("Couldn't save irq cfg regs rc=%d\n", rc); + } + + /* enable only important IRQs */ + rc = smb1360_write(chip, IRQ_CFG_REG, IRQ_DCIN_UV_BIT + | IRQ_AICL_DONE_BIT + | IRQ_BAT_HOT_COLD_SOFT_BIT + | IRQ_BAT_HOT_COLD_HARD_BIT); + if (rc < 0) + pr_err("Couldn't set irq_cfg rc=%d\n", rc); + + rc = smb1360_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT + | IRQ2_VBAT_LOW_BIT + | IRQ2_POWER_OK_BIT); + if (rc < 0) + pr_err("Couldn't set irq2_cfg rc=%d\n", rc); + + rc = smb1360_write(chip, IRQ3_CFG_REG, IRQ3_SOC_FULL_BIT + | IRQ3_SOC_MIN_BIT + | IRQ3_SOC_EMPTY_BIT); + if (rc < 0) + pr_err("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 smb1360_suspend_noirq(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct smb1360_chip *chip = i2c_get_clientdata(client); + + if (chip->irq_waiting) { + pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n"); + return -EBUSY; + } + return 0; +} + +static int smb1360_resume(struct device *dev) +{ + int i, rc; + struct i2c_client *client = to_i2c_client(dev); + struct smb1360_chip *chip = i2c_get_clientdata(client); + + /* Restore the IRQ config */ + for (i = 0; i < 3; i++) { + rc = smb1360_write(chip, IRQ_CFG_REG + i, + chip->irq_cfg_mask[i]); + if (rc) + pr_err("Couldn't restore irq cfg regs rc=%d\n", rc); + } + + mutex_lock(&chip->irq_complete); + chip->resume_completed = true; + if (chip->irq_waiting) { + chip->irq_disabled = false; + enable_irq(client->irq); + mutex_unlock(&chip->irq_complete); + smb1360_stat_handler(client->irq, chip); + } else { + mutex_unlock(&chip->irq_complete); + } + + power_supply_changed(chip->batt_psy); + + return 0; +} + +static void smb1360_shutdown(struct i2c_client *client) +{ + int rc; + struct smb1360_chip *chip = i2c_get_clientdata(client); + + rc = smb1360_otg_disable(chip); + if (rc) + pr_err("Couldn't disable OTG mode rc=%d\n", rc); + + if (chip->shdn_after_pwroff) { + rc = smb1360_poweroff(chip); + if (rc) + pr_err("Couldn't shutdown smb1360, rc = %d\n", rc); + pr_info("smb1360 power off\n"); + } +} + +static const struct dev_pm_ops smb1360_pm_ops = { + .resume = smb1360_resume, + .suspend_noirq = smb1360_suspend_noirq, + .suspend = smb1360_suspend, +}; + +static const struct of_device_id smb1360_match_table[] = { + { .compatible = "qcom,smb1360-chg-fg",}, + { }, +}; + +static const struct i2c_device_id smb1360_id[] = { + {"smb1360-chg-fg", 0}, + {}, +}; +MODULE_DEVICE_TABLE(i2c, smb1360_id); + +static struct i2c_driver smb1360_driver = { + .driver = { + .name = "smb1360-chg-fg", + .of_match_table = smb1360_match_table, + .pm = &smb1360_pm_ops, + }, + .probe = smb1360_probe, + .remove = smb1360_remove, + .shutdown = smb1360_shutdown, + .id_table = smb1360_id, +}; + +module_i2c_driver(smb1360_driver); + +MODULE_DESCRIPTION("SMB1360 Charger and Fuel Gauge"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("i2c:smb1360-chg-fg"); diff --git a/drivers/power/supply/qcom/smb1398-charger.c b/drivers/power/supply/qcom/smb1398-charger.c index c0d98d60edd7..c0be685a1925 100644 --- a/drivers/power/supply/qcom/smb1398-charger.c +++ b/drivers/power/supply/qcom/smb1398-charger.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2020 The Linux Foundation. All rights reserved. + * Copyright (c) 2020-2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "SMB1398: %s: " fmt, __func__ @@ -1715,6 +1715,10 @@ static void smb1398_status_change_work(struct work_struct *work) chip->usb_present = !!pval.intval; if (!chip->usb_present) /* USB has been removed */ smb1398_toggle_uvlo(chip); + pval.intval = 1; + if (is_cps_available(chip)) + power_supply_set_property(chip->div2_cp_slave_psy, + POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER, &pval); } rc = power_supply_get_property(chip->usb_psy, @@ -2274,6 +2278,7 @@ static enum power_supply_property div2_cp_slave_props[] = { POWER_SUPPLY_PROP_CP_ENABLE, POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, POWER_SUPPLY_PROP_CURRENT_CAPABILITY, + POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER, }; static int div2_cp_slave_get_prop(struct power_supply *psy, @@ -2297,6 +2302,9 @@ static int div2_cp_slave_get_prop(struct power_supply *psy, case POWER_SUPPLY_PROP_CURRENT_CAPABILITY: pval->intval = (int)chip->current_capability; break; + case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER: + pval->intval = 0; + break; default: dev_err(chip->dev, "read div2_cp_slave property %d is not supported\n", prop); @@ -2329,6 +2337,10 @@ static int div2_cp_slave_set_prop(struct power_supply *psy, return rc; chip->current_capability = mode; break; + case POWER_SUPPLY_PROP_CP_TOGGLE_SWITCHER: + /* use this case to toggle UVLO */ + rc = smb1398_toggle_uvlo(chip); + break; default: dev_err(chip->dev, "write div2_cp_slave property %d is not supported\n", prop); diff --git a/drivers/soc/qcom/memory_dump_v2.c b/drivers/soc/qcom/memory_dump_v2.c index 9ebced6d0113..7db2816e9434 100644 --- a/drivers/soc/qcom/memory_dump_v2.c +++ b/drivers/soc/qcom/memory_dump_v2.c @@ -82,6 +82,7 @@ struct msm_memory_dump { }; static struct msm_memory_dump memdump; +static struct msm_mem_dump_vaddr_tbl vaddr_tbl; /** * update_reg_dump_table - update the register dump table @@ -699,6 +700,28 @@ int msm_dump_data_register_nominidump(enum msm_dump_table_ids id, } EXPORT_SYMBOL(msm_dump_data_register_nominidump); +struct dump_vaddr_entry *get_msm_dump_ptr(enum msm_dump_data_ids id) +{ + int i; + + if (!vaddr_tbl.entries) + return NULL; + + if (id > MSM_DUMP_DATA_MAX) + return NULL; + + for (i = 0; i < vaddr_tbl.num_node; i++) { + if (vaddr_tbl.entries[i].id == id) + break; + } + + if (i == vaddr_tbl.num_node) + return NULL; + + return &vaddr_tbl.entries[i]; +} +EXPORT_SYMBOL(get_msm_dump_ptr); + #define MSM_DUMP_TOTAL_SIZE_OFFSET 0x724 static int init_memory_dump(void *dump_vaddr, phys_addr_t phys_addr, size_t size) @@ -787,6 +810,14 @@ static int mem_dump_alloc(struct platform_device *pdev) uint32_t ns_vmids[] = {VMID_HLOS}; uint32_t ns_vm_perms[] = {PERM_READ | PERM_WRITE}; u64 shm_bridge_handle; + int i = 0; + + vaddr_tbl.num_node = of_get_child_count(node); + vaddr_tbl.entries = devm_kcalloc(&pdev->dev, vaddr_tbl.num_node, + sizeof(struct dump_vaddr_entry), + GFP_KERNEL); + if (!vaddr_tbl.entries) + dev_err(&pdev->dev, "Unable to allocate mem for ptr addr\n"); total_size = size = ret = no_of_nodes = 0; /* For dump table registration with IMEM */ @@ -862,9 +893,16 @@ static int mem_dump_alloc(struct platform_device *pdev) dump_entry.addr = phys_addr; ret = msm_dump_data_register_nominidump(MSM_DUMP_TABLE_APPS, &dump_entry); - if (ret) + if (ret) { dev_err(&pdev->dev, "Data dump setup failed, id = %d\n", id); + } else if (vaddr_tbl.entries) { + vaddr_tbl.entries[i].id = id; + vaddr_tbl.entries[i].dump_vaddr = + dump_vaddr + MSM_DUMP_DATA_SIZE; + vaddr_tbl.entries[i].dump_data_vaddr = dump_data; + i++; + } md_entry.phys_addr = dump_data->addr; md_entry.virt_addr = (uintptr_t)dump_vaddr + MSM_DUMP_DATA_SIZE; diff --git a/drivers/soc/qcom/qdss_bridge.c b/drivers/soc/qcom/qdss_bridge.c index d48500b84ea4..7162149a76ef 100644 --- a/drivers/soc/qcom/qdss_bridge.c +++ b/drivers/soc/qcom/qdss_bridge.c @@ -151,7 +151,8 @@ static int qdss_check_entry(struct qdss_bridge_drvdata *drvdata) int ret = 0; list_for_each_entry(entry, &drvdata->buf_tbl, link) { - if (atomic_read(&entry->available) == 0) { + if (atomic_read(&entry->available) == 0 + && atomic_read(&entry->used) == 1) { ret = 1; return ret; } @@ -199,6 +200,7 @@ static void qdss_buf_tbl_remove(struct qdss_bridge_drvdata *drvdata, if (entry->buf != buf) continue; atomic_set(&entry->available, 1); + atomic_set(&entry->used, 0); spin_unlock_bh(&drvdata->lock); return; } @@ -382,6 +384,7 @@ static int usb_write(struct qdss_bridge_drvdata *drvdata, entry->usb_req->buf = buf; entry->usb_req->length = len; + atomic_set(&entry->used, 1); ret = usb_qdss_write(drvdata->usb_ch, entry->usb_req); return ret; diff --git a/drivers/soc/qcom/qdss_bridge.h b/drivers/soc/qcom/qdss_bridge.h index 3bf63a6c2d67..8125ccc38f6c 100644 --- a/drivers/soc/qcom/qdss_bridge.h +++ b/drivers/soc/qcom/qdss_bridge.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2017-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved. */ #ifndef _QDSS_BRIDGE_H @@ -11,6 +11,7 @@ struct qdss_buf_tbl_lst { unsigned char *buf; struct qdss_request *usb_req; atomic_t available; + atomic_t used; }; struct qdss_mhi_buf_tbl_t { diff --git a/drivers/thermal/qcom/qmi_sensors.c b/drivers/thermal/qcom/qmi_sensors.c index 97c1467c65fa..a145040ff5cc 100644 --- a/drivers/thermal/qcom/qmi_sensors.c +++ b/drivers/thermal/qcom/qmi_sensors.c @@ -1,6 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-only /* - * Copyright (c) 2018-2020, The Linux Foundation. All rights reserved. + * Copyright (c) 2018-2020,2021 The Linux Foundation. All rights reserved. */ #define pr_fmt(fmt) "%s:%s " fmt, KBUILD_MODNAME, __func__ @@ -124,7 +124,7 @@ static char sensor_clients[QMI_TS_MAX_NR][QMI_CLIENT_NAME_LENGTH] = { static int32_t encode_qmi(int32_t val) { uint32_t shift = 0, local_val = 0; - int32_t temp_val = 0; + unsigned long temp_val = 0; if (val == INT_MAX || val == INT_MIN) return 0; @@ -134,8 +134,7 @@ static int32_t encode_qmi(int32_t val) temp_val *= -1; local_val |= 1 << QMI_FL_SIGN_BIT; } - shift = find_last_bit((const unsigned long *)&temp_val, - sizeof(temp_val) * 8); + shift = find_last_bit(&temp_val, sizeof(temp_val) * 8); local_val |= ((shift + 127) << QMI_MANTISSA_MSB); temp_val &= ~(1 << shift); @@ -279,6 +278,13 @@ static int qmi_ts_request(struct qmi_sensor *qmi_sens, qmi_sens->low_thresh != INT_MIN; req.temp_threshold_low = encode_qmi(qmi_sens->low_thresh); + + pr_debug("Sensor:%s set high_trip:%d, low_trip:%d, high_valid:%d, low_valid:%d\n", + qmi_sens->qmi_name, + qmi_sens->high_thresh, + qmi_sens->low_thresh, + req.temp_threshold_high_valid, + req.temp_threshold_low_valid); } mutex_lock(&ts->mutex); diff --git a/drivers/thermal/qpnp-adc-tm.c b/drivers/thermal/qpnp-adc-tm.c index 0f81d59022ff..199c74f34726 100644 --- a/drivers/thermal/qpnp-adc-tm.c +++ b/drivers/thermal/qpnp-adc-tm.c @@ -2330,11 +2330,11 @@ static int qpnp_adc_tm_measure_ref_points(struct qpnp_adc_tm_chip *chip) int ret; struct qpnp_adc_drv *adc = chip->adc; - ret = iio_read_channel_processed(chip->ref_1250v, &read_1); + ret = iio_read_channel_raw(chip->ref_1250v, &read_1); if (ret < 0) goto err; - ret = iio_read_channel_processed(chip->ref_625mv, &read_2); + ret = iio_read_channel_raw(chip->ref_625mv, &read_2); if (ret < 0) goto err; @@ -2356,11 +2356,11 @@ static int qpnp_adc_tm_measure_ref_points(struct qpnp_adc_tm_chip *chip) read_1 = 0; read_2 = 0; - ret = iio_read_channel_processed(chip->ref_vdd, &read_1); + ret = iio_read_channel_raw(chip->ref_vdd, &read_1); if (ret < 0) goto err; - ret = iio_read_channel_processed(chip->ref_gnd, &read_2); + ret = iio_read_channel_raw(chip->ref_gnd, &read_2); if (ret < 0) goto err; diff --git a/drivers/tty/tty_io.c b/drivers/tty/tty_io.c index b4838ab8ba95..0e8b5f5b19f3 100644 --- a/drivers/tty/tty_io.c +++ b/drivers/tty/tty_io.c @@ -2751,10 +2751,14 @@ void __do_SAK(struct tty_struct *tty) struct task_struct *g, *p; struct pid *session; int i; + unsigned long flags; if (!tty) return; - session = tty->session; + + spin_lock_irqsave(&tty->ctrl_lock, flags); + session = get_pid(tty->session); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); tty_ldisc_flush(tty); @@ -2786,6 +2790,7 @@ void __do_SAK(struct tty_struct *tty) task_unlock(p); } while_each_thread(g, p); read_unlock(&tasklist_lock); + put_pid(session); #endif } diff --git a/drivers/tty/tty_jobctrl.c b/drivers/tty/tty_jobctrl.c index a42dec3c95d0..ffcab80ba77d 100644 --- a/drivers/tty/tty_jobctrl.c +++ b/drivers/tty/tty_jobctrl.c @@ -103,8 +103,8 @@ static void __proc_set_tty(struct tty_struct *tty) put_pid(tty->session); put_pid(tty->pgrp); tty->pgrp = get_pid(task_pgrp(current)); - spin_unlock_irqrestore(&tty->ctrl_lock, flags); tty->session = get_pid(task_session(current)); + spin_unlock_irqrestore(&tty->ctrl_lock, flags); if (current->signal->tty) { tty_debug(tty, "current tty %s not NULL!!\n", current->signal->tty->name); @@ -293,20 +293,23 @@ void disassociate_ctty(int on_exit) spin_lock_irq(¤t->sighand->siglock); put_pid(current->signal->tty_old_pgrp); current->signal->tty_old_pgrp = NULL; - tty = tty_kref_get(current->signal->tty); + spin_unlock_irq(¤t->sighand->siglock); + if (tty) { unsigned long flags; + + tty_lock(tty); spin_lock_irqsave(&tty->ctrl_lock, flags); put_pid(tty->session); put_pid(tty->pgrp); tty->session = NULL; tty->pgrp = NULL; spin_unlock_irqrestore(&tty->ctrl_lock, flags); + tty_unlock(tty); tty_kref_put(tty); } - spin_unlock_irq(¤t->sighand->siglock); /* Now clear signal->tty under the lock */ read_lock(&tasklist_lock); session_clear_tty(task_session(current)); @@ -477,14 +480,19 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t return -ENOTTY; if (retval) return retval; - if (!current->signal->tty || - (current->signal->tty != real_tty) || - (real_tty->session != task_session(current))) - return -ENOTTY; + if (get_user(pgrp_nr, p)) return -EFAULT; if (pgrp_nr < 0) return -EINVAL; + + spin_lock_irq(&real_tty->ctrl_lock); + if (!current->signal->tty || + (current->signal->tty != real_tty) || + (real_tty->session != task_session(current))) { + retval = -ENOTTY; + goto out_unlock_ctrl; + } rcu_read_lock(); pgrp = find_vpid(pgrp_nr); retval = -ESRCH; @@ -494,12 +502,12 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t if (session_of_pgrp(pgrp) != task_session(current)) goto out_unlock; retval = 0; - spin_lock_irq(&real_tty->ctrl_lock); put_pid(real_tty->pgrp); real_tty->pgrp = get_pid(pgrp); - spin_unlock_irq(&real_tty->ctrl_lock); out_unlock: rcu_read_unlock(); +out_unlock_ctrl: + spin_unlock_irq(&real_tty->ctrl_lock); return retval; } @@ -511,20 +519,30 @@ static int tiocspgrp(struct tty_struct *tty, struct tty_struct *real_tty, pid_t * * Obtain the session id of the tty. If there is no session * return an error. - * - * Locking: none. Reference to current->signal->tty is safe. */ static int tiocgsid(struct tty_struct *tty, struct tty_struct *real_tty, pid_t __user *p) { + unsigned long flags; + pid_t sid; + /* * (tty == real_tty) is a cheap way of * testing if the tty is NOT a master pty. */ if (tty == real_tty && current->signal->tty != real_tty) return -ENOTTY; + + spin_lock_irqsave(&real_tty->ctrl_lock, flags); if (!real_tty->session) - return -ENOTTY; - return put_user(pid_vnr(real_tty->session), p); + goto err; + sid = pid_vnr(real_tty->session); + spin_unlock_irqrestore(&real_tty->ctrl_lock, flags); + + return put_user(sid, p); + +err: + spin_unlock_irqrestore(&real_tty->ctrl_lock, flags); + return -ENOTTY; } /* diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c index 6076d580edba..9dfe4b32fb32 100644 --- a/drivers/usb/dwc3/dwc3-msm.c +++ b/drivers/usb/dwc3/dwc3-msm.c @@ -2579,7 +2579,7 @@ static int dwc3_msm_suspend(struct dwc3_msm *mdwc, bool enable_wakeup) dbg_event(0xFF, "pend evt", 0); /* disable power event irq, hs and ss phy irq is used as wake up src */ - disable_irq(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); + disable_irq_nosync(mdwc->wakeup_irq[PWR_EVNT_IRQ].irq); dwc3_set_phy_speed_flags(mdwc); /* Suspend HS PHY */ diff --git a/drivers/usb/gadget/function/f_uvc.c b/drivers/usb/gadget/function/f_uvc.c index bc6d481beaa4..0c739142d5d2 100644 --- a/drivers/usb/gadget/function/f_uvc.c +++ b/drivers/usb/gadget/function/f_uvc.c @@ -821,8 +821,8 @@ static struct usb_function_instance *uvc_alloc_inst(void) cd->wObjectiveFocalLengthMax = cpu_to_le16(0); cd->wOcularFocalLength = cpu_to_le16(0); cd->bControlSize = 3; - cd->bmControls[0] = 46; - cd->bmControls[1] = 2; + cd->bmControls[0] = 62; + cd->bmControls[1] = 126; cd->bmControls[2] = 10; pd = &opts->uvc_processing; @@ -833,8 +833,8 @@ static struct usb_function_instance *uvc_alloc_inst(void) pd->bSourceID = 1; pd->wMaxMultiplier = cpu_to_le16(16*1024); pd->bControlSize = 3; - pd->bmControls[0] = 90; - pd->bmControls[1] = 20; + pd->bmControls[0] = 91; + pd->bmControls[1] = 23; pd->bmControls[2] = 4; pd->iProcessing = 0; pd->bmVideoStandards = 0; diff --git a/include/linux/diagchar.h b/include/linux/diagchar.h index b5db75f52b5c..39f2b9f7f39e 100644 --- a/include/linux/diagchar.h +++ b/include/linux/diagchar.h @@ -143,7 +143,7 @@ * a new RANGE of SSIDs to the msg_mask_tbl. */ #define MSG_MASK_TBL_CNT 27 -#define APPS_EVENT_LAST_ID 0xCFA +#define APPS_EVENT_LAST_ID 0xCFE #define MSG_SSID_0 0 #define MSG_SSID_0_LAST 134 @@ -950,7 +950,7 @@ static const uint32_t msg_bld_masks_26[] = { /* LOG CODES */ static const uint32_t log_code_last_tbl[] = { 0x0, /* EQUIP ID 0 */ - 0x1D5E, /* EQUIP ID 1 */ + 0x1D86, /* EQUIP ID 1 */ 0x0, /* EQUIP ID 2 */ 0x0, /* EQUIP ID 3 */ 0x4910, /* EQUIP ID 4 */ diff --git a/include/linux/mm.h b/include/linux/mm.h index d7cca734feb5..e1eded55e23d 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -1498,22 +1498,13 @@ int generic_access_phys(struct vm_area_struct *vma, unsigned long addr, #ifdef CONFIG_SPECULATIVE_PAGE_FAULT static inline void vm_write_begin(struct vm_area_struct *vma) { - write_seqcount_begin(&vma->vm_sequence); -} -static inline void vm_write_begin_nested(struct vm_area_struct *vma, - int subclass) -{ - write_seqcount_begin_nested(&vma->vm_sequence, subclass); -} -static inline void vm_write_end(struct vm_area_struct *vma) -{ - write_seqcount_end(&vma->vm_sequence); -} -static inline void vm_raw_write_begin(struct vm_area_struct *vma) -{ + /* + * The reads never spins and preemption + * disablement is not required. + */ raw_write_seqcount_begin(&vma->vm_sequence); } -static inline void vm_raw_write_end(struct vm_area_struct *vma) +static inline void vm_write_end(struct vm_area_struct *vma) { raw_write_seqcount_end(&vma->vm_sequence); } @@ -1521,19 +1512,9 @@ static inline void vm_raw_write_end(struct vm_area_struct *vma) static inline void vm_write_begin(struct vm_area_struct *vma) { } -static inline void vm_write_begin_nested(struct vm_area_struct *vma, - int subclass) -{ -} static inline void vm_write_end(struct vm_area_struct *vma) { } -static inline void vm_raw_write_begin(struct vm_area_struct *vma) -{ -} -static inline void vm_raw_write_end(struct vm_area_struct *vma) -{ -} #endif /* CONFIG_SPECULATIVE_PAGE_FAULT */ extern void truncate_pagecache(struct inode *inode, loff_t new); diff --git a/include/linux/tty.h b/include/linux/tty.h index 83b2e68778c9..a75926ab316e 100644 --- a/include/linux/tty.h +++ b/include/linux/tty.h @@ -306,6 +306,10 @@ struct tty_struct { struct termiox *termiox; /* May be NULL for unsupported */ char name[64]; struct pid *pgrp; /* Protected by ctrl lock */ + /* + * Writes protected by both ctrl lock and legacy mutex, readers must use + * at least one of them. + */ struct pid *session; unsigned long flags; int count; diff --git a/include/soc/qcom/memory_dump.h b/include/soc/qcom/memory_dump.h index bc84bc8d8ef8..d3f514add392 100644 --- a/include/soc/qcom/memory_dump.h +++ b/include/soc/qcom/memory_dump.h @@ -1,6 +1,6 @@ /* SPDX-License-Identifier: GPL-2.0-only */ /* - * Copyright (c) 2012, 2014-2017, 2019, The Linux Foundation. All rights reserved. + * Copyright (c) 2012, 2014-2017, 2019, 2021, The Linux Foundation. All rights reserved. */ #ifndef __MSM_MEMORY_DUMP_H @@ -79,6 +79,8 @@ enum msm_dump_data_ids { MSM_DUMP_DATA_TMC_ETF = 0xF0, MSM_DUMP_DATA_TMC_ETF_SWAO = 0xF1, MSM_DUMP_DATA_TMC_REG = 0x100, + MSM_DUMP_DATA_TMC_ETR_REG = 0x100, + MSM_DUMP_DATA_TMC_ETF_REG = 0x101, MSM_DUMP_DATA_TMC_ETF_SWAO_REG = 0x102, MSM_DUMP_DATA_LOG_BUF = 0x110, MSM_DUMP_DATA_LOG_BUF_FIRST_IDX = 0x111, @@ -113,11 +115,23 @@ struct msm_dump_entry { uint64_t addr; }; +struct dump_vaddr_entry { + uint32_t id; + void *dump_vaddr; + struct msm_dump_data *dump_data_vaddr; +}; + +struct msm_mem_dump_vaddr_tbl { + uint8_t num_node; + struct dump_vaddr_entry *entries; +}; + #ifdef CONFIG_QCOM_MEMORY_DUMP_V2 extern int msm_dump_data_register(enum msm_dump_table_ids id, struct msm_dump_entry *entry); extern int msm_dump_data_register_nominidump(enum msm_dump_table_ids id, struct msm_dump_entry *entry); +extern struct dump_vaddr_entry *get_msm_dump_ptr(enum msm_dump_data_ids id); #else static inline int msm_dump_data_register(enum msm_dump_table_ids id, struct msm_dump_entry *entry) diff --git a/mm/mmap.c b/mm/mmap.c index 9f4e340e5632..b5c3692e7193 100644 --- a/mm/mmap.c +++ b/mm/mmap.c @@ -735,29 +735,9 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, long adjust_next = 0; int remove_next = 0; - /* - * Why using vm_raw_write*() functions here to avoid lockdep's warning ? - * - * Locked is complaining about a theoretical lock dependency, involving - * 3 locks: - * mapping->i_mmap_rwsem --> vma->vm_sequence --> fs_reclaim - * - * Here are the major path leading to this dependency : - * 1. __vma_adjust() mmap_sem -> vm_sequence -> i_mmap_rwsem - * 2. move_vmap() mmap_sem -> vm_sequence -> fs_reclaim - * 3. __alloc_pages_nodemask() fs_reclaim -> i_mmap_rwsem - * 4. unmap_mapping_range() i_mmap_rwsem -> vm_sequence - * - * So there is no way to solve this easily, especially because in - * unmap_mapping_range() the i_mmap_rwsem is grab while the impacted - * VMAs are not yet known. - * However, the way the vm_seq is used is guarantying that we will - * never block on it since we just check for its value and never wait - * for it to move, see vma_has_changed() and handle_speculative_fault(). - */ - vm_raw_write_begin(vma); + vm_write_begin(vma); if (next) - vm_raw_write_begin(next); + vm_write_begin(next); if (next && !insert) { struct vm_area_struct *exporter = NULL, *importer = NULL; @@ -841,8 +821,8 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, error = anon_vma_clone(importer, exporter); if (error) { if (next && next != vma) - vm_raw_write_end(next); - vm_raw_write_end(vma); + vm_write_end(next); + vm_write_end(vma); return error; } } @@ -971,7 +951,7 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, if (next->anon_vma) anon_vma_merge(vma, next); mm->map_count--; - vm_raw_write_end(next); + vm_write_end(next); put_vma(next); /* * In mprotect's case 6 (see comments on vma_merge), @@ -987,7 +967,7 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, */ next = vma->vm_next; if (next) - vm_raw_write_begin(next); + vm_write_begin(next); } else { /* * For the scope of the comment "next" and @@ -1035,9 +1015,9 @@ int __vma_adjust(struct vm_area_struct *vma, unsigned long start, uprobe_mmap(insert); if (next && next != vma) - vm_raw_write_end(next); + vm_write_end(next); if (!keep_locked) - vm_raw_write_end(vma); + vm_write_end(vma); validate_mm(mm); @@ -3348,7 +3328,7 @@ struct vm_area_struct *copy_vma(struct vm_area_struct **vmap, * that we protect it right now, and let the caller unprotect * it once the move is done. */ - vm_raw_write_begin(new_vma); + vm_write_begin(new_vma); vma_link(mm, new_vma, prev, rb_link, rb_parent); *need_rmap_locks = false; } diff --git a/mm/mremap.c b/mm/mremap.c index f1247602c12a..90dbf98c2404 100644 --- a/mm/mremap.c +++ b/mm/mremap.c @@ -300,7 +300,7 @@ static unsigned long move_vma(struct vm_area_struct *vma, * to be mapped in our back while we are copying the PTEs. */ if (vma != new_vma) - vm_raw_write_begin(vma); + vm_write_begin(vma); moved_len = move_page_tables(vma, old_addr, new_vma, new_addr, old_len, need_rmap_locks); @@ -319,7 +319,7 @@ static unsigned long move_vma(struct vm_area_struct *vma, move_page_tables(new_vma, new_addr, vma, old_addr, moved_len, true); if (vma != new_vma) - vm_raw_write_end(vma); + vm_write_end(vma); vma = new_vma; old_len = new_len; old_addr = new_addr; @@ -329,9 +329,9 @@ static unsigned long move_vma(struct vm_area_struct *vma, arch_remap(mm, old_addr, old_addr + old_len, new_addr, new_addr + new_len); if (vma != new_vma) - vm_raw_write_end(vma); + vm_write_end(vma); } - vm_raw_write_end(new_vma); + vm_write_end(new_vma); /* Conceal VM_ACCOUNT so old reservation is not undone */ if (vm_flags & VM_ACCOUNT) { diff --git a/net/qrtr/qrtr.c b/net/qrtr/qrtr.c index e602addc959e..e437aed324ad 100644 --- a/net/qrtr/qrtr.c +++ b/net/qrtr/qrtr.c @@ -1731,6 +1731,11 @@ static int qrtr_recvmsg(struct socket *sock, struct msghdr *msg, rc = copied; if (addr) { + /* There is an anonymous 2-byte hole after sq_family, + * make sure to clear it. + */ + memset(addr, 0, sizeof(*addr)); + addr->sq_family = AF_QIPCRTR; addr->sq_node = cb->src_node; addr->sq_port = cb->src_port; diff --git a/net/qrtr/smd.c b/net/qrtr/smd.c index 4b08f4e79a6a..e7590bce019c 100644 --- a/net/qrtr/smd.c +++ b/net/qrtr/smd.c @@ -24,7 +24,7 @@ static int qcom_smd_qrtr_callback(struct rpmsg_device *rpdev, int rc; if (!qdev) { - pr_err("%s:Not ready\n", __func__); + pr_err_ratelimited("%s:Not ready\n", __func__); return -EAGAIN; }