Merge "ASoC: wcd-mbhc: check HPH PA status before impedance detection" into msm-4.9
diff --git a/Documentation/devicetree/bindings/arm/msm/qcom,osm.txt b/Documentation/devicetree/bindings/arm/msm/qcom,osm.txt
new file mode 100644
index 0000000..964fea6
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/qcom,osm.txt
@@ -0,0 +1,476 @@
+Qualcomm Technologies, Inc. OSM Bindings
+
+Operating State Manager (OSM) is a hardware engine used by some Qualcomm
+Technologies, Inc. (QTI) SoCs to manage frequency and voltage scaling
+in hardware. OSM is capable of controlling frequency and voltage requests
+for multiple clusters via the existence of multiple OSM domains.
+
+Properties:
+- compatible
+	Usage:      required
+	Value type: <string>
+	Definition: must be "qcom,clk-cpu-osm".
+
+- reg
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Addresses and sizes for the memory of the OSM controller,
+		    cluster PLL management, and APCS common register regions.
+		    Optionally, the address of the efuse registers used to
+		    determine the pwrcl or perfcl speed-bins and/or the ACD
+		    register space to initialize prior to enabling OSM.
+
+- reg-names
+	Usage:      required
+	Value type: <stringlist>
+	Definition: Address names. Must be "osm_l3_base", "osm_pwrcl_base",
+		    "osm_perfcl_base", "l3_pll", "pwrcl_pll", "perfcl_pll",
+		    "l3_sequencer", "pwrcl_sequencer", "perfcl_sequencer" or
+		    "apps_itm_ctl". Optionally, "l3_efuse", "pwrcl_efuse"
+		    "perfcl_efuse".
+		    Must be specified in the same order as the corresponding
+		    addresses are specified in the reg property.
+
+- vdd-l3-supply
+	Usage:      required
+	Value type: <phandle>
+	Definition: phandle of the underlying regulator device that manages
+		    the voltage supply of the L3 cluster.
+
+- vdd-pwrcl-supply
+	Usage:      required
+	Value type: <phandle>
+	Definition: phandle of the underlying regulator device that manages
+		    the voltage supply of the Power cluster.
+
+- vdd-perfcl-supply
+	Usage:      required
+	Value type: <phandle>
+	Definition: phandle of the underlying regulator device that manages
+		    the voltage supply of the Performance cluster.
+
+- interrupts
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: OSM interrupt specifier.
+
+- interrupt-names
+	Usage:      required
+	Value type: <stringlist>
+	Definition: Interrupt names. this list must match up 1-to-1 with the
+		    interrupts specified in the 'interrupts' property.
+		    "pwrcl-irq" and "perfcl-irq" must be specified.
+
+- qcom,l3-speedbinX-v0
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the frequency in Hertz, frequency,
+		    PLL override data, ACC level, and virtual corner used
+		    by the OSM hardware for each supported DCVS setpoint
+		    of the L3 cluster.
+
+- qcom,pwrcl-speedbinX-v0
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the frequency in Hertz, frequency,
+		    PLL override data, ACC level, and virtual corner used
+		    by the OSM hardware for each supported DCVS setpoint
+		    of the Power cluster.
+
+- qcom,perfcl-speedbinX-v0
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the frequency in Hertz, frequency,
+		    PLL override data, ACC level and virtual corner used
+		    by the OSM hardware for each supported DCVS setpoint
+		    of the Performance cluster.
+
+- qcom,l3-min-cpr-vc-binX
+	Usage:	    required
+	Value type: <u32>
+	Definition: First virtual corner which does not use PLL post-divider
+		    for the L3 clock domain.
+
+- qcom,pwrcl-min-cpr-vc-binX
+	Usage:      required
+	Value type: <u32>
+	Definition: First virtual corner which does not use PLL post-divider
+		    for the power cluster.
+
+- qcom,perfcl-min-cpr-vc-binX
+	Usage:      required
+	Value type: <u32>
+	Definition: First virtual corner which does not use PLL post-divider
+		    for the performance cluster.
+
+- qcom,osm-no-tz
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates that there is no programming
+		    of the OSM hardware performed by the secure world.
+
+- qcom,osm-pll-setup
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates that the PLL setup sequence
+		    must be executed for each clock domain managed by the OSM
+		    controller.
+
+- qcom,up-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the DCVS up timer value in nanoseconds
+		    for each of the three clock domains managed by the OSM
+		    controller.
+
+- qcom,down-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the DCVS down timer value in nanoseconds
+		    for each of the three clock domains managed by the OSM
+		    controller.
+
+- qcom,pc-override-index
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the OSM performance index to be used
+		    when each cluster enters certain low power modes.
+
+- qcom,set-ret-inactive
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if domains in retention must
+		    be treated as inactive.
+
+- qcom,enable-llm-freq-vote
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if Limits hardware frequency
+		    votes must be honored by OSM.
+
+- qcom,llm-freq-up-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the LLM frequency up timer value in
+		    nanoseconds for each of the three clock domains managed by
+		    the OSM controller.
+
+- qcom,llm-freq-down-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the LLM frequency down timer value in
+		    nanoseconds for each of the three clock domains managed by
+		    the OSM controller.
+
+- qcom,enable-llm-volt-vote
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if Limits hardware voltage
+		    votes must be honored by OSM.
+
+- qcom,llm-volt-up-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the LLM voltage up timer value in
+		    nanoseconds for each of the three clock domains managed by
+		    the OSM controller.
+
+- qcom,llm-volt-down-timer
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the LLM voltage down timer value in
+		    nanoseconds for each of the three clock domains managed by
+		    the OSM controller.
+
+- qcom,cc-reads
+	Usage:      optional
+	Value type: <integer>
+	Definition: Defines the number of times the cycle counters must be
+		    read to determine the performance level of each clock
+		    domain.
+
+- qcom,l-val-base
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the register addresses of the L_VAL
+		    control register for each of the three clock domains
+		    managed by the OSM controller.
+
+- qcom,apcs-pll-user-ctl
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the register addresses of the PLL
+		    user control register for each of the three clock domains
+		    managed by the OSM controller.
+
+- qcom,perfcl-apcs-apm-threshold-voltage
+	Usage:      required
+	Value type: <u32>
+	Definition: Specifies the APM threshold voltage in microvolts.  If the
+		    VDD_APCC supply voltage is above or at this level, then the
+		    APM is switched to use VDD_APCC.  If VDD_APCC is below
+		    this level, then the APM is switched to use VDD_MX.
+
+- qcom,apm-mode-ctl
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the register addresses of the APM
+		    control register for each of the two clusters managed
+		    by the OSM controller.
+
+- qcom,apm-status-ctrl
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the register addresses of the APM
+		    controller status register for each of the three clock
+		    domains managed by the OSM controller.
+
+- qcom,perfcl-isense-addr
+	Usage:      required
+	Value type: <u32>
+	Definition: Contains the ISENSE register address.
+
+- qcom,l3-mem-acc-addr
+	Usage:      required if qcom,osm-no-tz is specified
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the addresses of the mem-acc
+		    configuration registers for the L3 cluster.
+		    The array must contain exactly three elements.
+
+- qcom,pwrcl-mem-acc-addr
+	Usage:      required if qcom,osm-no-tz is specified
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the addresses of the mem-acc
+		    configuration registers for the Power cluster.
+		    The array must contain exactly three elements.
+
+- qcom,perfcl-mem-acc-addr
+	Usage:      required if qcom,osm-no-tz is specified
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the addresses of the mem-acc
+		    configuration registers for the Performance cluster.
+		    The array must contain exactly three elements.
+
+		    corresponding CPRh device.
+
+- qcom,perfcl-apcs-mem-acc-threshold-voltage
+	Usage:      optional
+	Value type: <u32>
+	Definition: Specifies the highest MEM ACC threshold voltage in
+		    microvolts for the Performance cluster.  This voltage is
+		    used to determine which MEM ACC setting is used for the
+		    highest frequencies.  If specified, the voltage must match
+		    the MEM ACC threshold voltage specified for the
+		    corresponding CPRh device.
+
+- qcom,apcs-cbc-addr
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the addresses of the APCS_CBC_ADDR
+		    registers for all three clock domains.
+
+- qcom,apcs-ramp-ctl-addr
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Array which defines the addresses of the APCS_RAMP_CTL_ADDR
+		    registers for all three clock domains.
+
+- qcom,red-fsm-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the reduction FSM
+		    should be enabled.
+
+- qcom,boost-fsm-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the boost FSM should
+		    be enabled.
+
+- qcom,safe-fsm-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the safe FSM should
+		    be enabled.
+
+- qcom,ps-fsm-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the PS FSM should be
+		    enabled.
+
+- qcom,droop-fsm-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the droop FSM should
+		    be enabled.
+
+- qcom,set-c3-active
+	Usage:	    optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the cores in C3 are to
+		    be treated as active for core count calculations.
+
+- qcom,set-c2-active
+	Usage:      optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if the cores in C2 are to
+		    be treated as active for core count calculations.
+
+- qcom,disable-cc-dvcs
+	Usage:	    optional
+	Value type: <empty>
+	Definition: Boolean flag which indicates if core count based DCVS is
+		    to be disabled.
+
+- qcom,apcs-pll-min-freq
+	Usage:	    required
+	Value type: <u32>
+	Definition: Contains the addresses of the RAILx_CLKDOMy_PLL_MIN_FREQ
+		    registers for the three clock domains.
+
+- clock-names
+	Usage:      required
+	Value type: <string>
+	Definition: Must be "aux_clk".
+
+- clocks
+	Usage:      required
+	Value type: <phandle>
+	Definition: Phandle to the aux clock device.
+
+Example:
+	clock_cpucc: qcom,cpucc@0x17d41000 {
+		compatible = "qcom,clk-cpu-osm";
+		reg = <0x17d41000 0x1400>,
+			<0x17d43000 0x1400>,
+			<0x17d45800 0x1400>,
+			<0x178d0000 0x1000>,
+			<0x178c0000 0x1000>,
+			<0x178b0000 0x1000>,
+			<0x17d42400 0x0c00>,
+			<0x17d44400 0x0c00>,
+			<0x17d46c00 0x0c00>,
+			<0x17810090 0x8>;
+		reg-names = "osm_l3_base", "osm_pwrcl_base", "osm_perfcl_base",
+			"l3_pll", "pwrcl_pll", "perfcl_pll",
+			"l3_sequencer", "pwrcl_sequencer",
+			"perfcl_sequencer", "apps_itm_ctl";
+
+		vdd-l3-supply = <&apc0_l3_vreg>;
+		vdd-pwrcl-supply = <&apc0_pwrcl_vreg>;
+		vdd-perfcl-supply = <&apc1_perfcl_vreg>;
+
+		qcom,l3-speedbin0-v0 =
+			<   300000000 0x000c000f 0x00002020 0x1 1 >,
+			<   422400000 0x50140116 0x00002020 0x1 2 >,
+			<   499200000 0x5014021a 0x00002020 0x1 3 >,
+			<   576000000 0x5014031e 0x00002020 0x1 4 >,
+			<   652800000 0x501c0422 0x00002020 0x1 5 >,
+			<   729600000 0x501c0526 0x00002020 0x1 6 >,
+			<   806400000 0x501c062a 0x00002222 0x1 7 >,
+			<   883200000 0x4024072b 0x00002525 0x1 8 >,
+			<   960000000 0x40240832 0x00002828 0x2 9 >;
+
+		qcom,pwrcl-speedbin0-v0 =
+			<   300000000 0x000c000f 0x00002020 0x1 1 >,
+			<   422400000 0x50140116 0x00002020 0x1 2 >,
+			<   499200000 0x5014021a 0x00002020 0x1 3 >,
+			<   576000000 0x5014031e 0x00002020 0x1 4 >,
+			<   652800000 0x501c0422 0x00002020 0x1 5 >,
+			<   748800000 0x501c0527 0x00002020 0x1 6 >,
+			<   825600000 0x401c062b 0x00002222 0x1 7 >,
+			<   902400000 0x4024072f 0x00002626 0x1 8 >,
+			<   979200000 0x40240833 0x00002929 0x1 9 >,
+			<  1056000000 0x402c0937 0x00002c2c 0x1 10 >,
+			<  1132800000 0x402c0a3b 0x00002f2f 0x1 11 >,
+			<  1209600000 0x402c0b3f 0x00003333 0x1 12 >,
+			<  1286400000 0x40340c43 0x00003636 0x1 13 >,
+			<  1363200000 0x40340d47 0x00003939 0x1 14 >,
+			<  1440000000 0x403c0e4b 0x00003c3c 0x1 15 >,
+			<  1516800000 0x403c0f4f 0x00004040 0x2 16 >,
+			<  1593600000 0x403c1053 0x00004343 0x2 17 >;
+
+		qcom,perfcl-speedbin0-v0 =
+			<   300000000 0x000c000f 0x00002020 0x1 1 >,
+			<   422400000 0x50140116 0x00002020 0x1 2 >,
+			<   499200000 0x5014021a 0x00002020 0x1 3 >,
+			<   576000000 0x5014031e 0x00002020 0x1 4 >,
+			<   652800000 0x501c0422 0x00002020 0x1 5 >,
+			<   729600000 0x501c0526 0x00002020 0x1 6 >,
+			<   806400000 0x501c062a 0x00002222 0x1 7 >,
+			<   883200000 0x4024072b 0x00002525 0x1 8 >,
+			<   960000000 0x40240832 0x00002828 0x1 9 >,
+			<  1036800000 0x40240936 0x00002b2b 0x1 10 >,
+			<  1113600000 0x402c0a3a 0x00002e2e 0x1 11 >,
+			<  1190400000 0x402c0b3e 0x00003232 0x1 12 >,
+			<  1267200000 0x40340c42 0x00003535 0x1 13 >,
+			<  1344000000 0x40340d46 0x00003838 0x1 14 >,
+			<  1420800000 0x40340e4a 0x00003b3b 0x1 15 >,
+			<  1497600000 0x403c0f4e 0x00003e3e 0x1 16 >,
+			<  1574400000 0x403c1052 0x00004242 0x2 17 >,
+			<  1651200000 0x403c1156 0x00004545 0x2 18 >,
+			<  1728000000 0x4044125a 0x00004848 0x2 19 >,
+			<  1804800000 0x4044135e 0x00004b4b 0x2 20 >,
+			<  1881600000 0x404c1462 0x00004e4e 0x2 21 >,
+			<  1958400000 0x404c1566 0x00005252 0x3 22 >;
+
+		qcom,l3-min-cpr-vc-bin0 = <7>;
+		qcom,pwrcl-min-cpr-vc-bin0 = <6>;
+		qcom,perfcl-min-cpr-vc-bin0 = <7>;
+
+		qcom,up-timer =
+			<1000 1000 1000>;
+		qcom,down-timer =
+			<100000 100000 100000>;
+		qcom,pc-override-index =
+			<0 0 0>;
+		qcom,set-ret-inactive;
+		qcom,enable-llm-freq-vote;
+		qcom,llm-freq-up-timer =
+			<1000 1000 1000>;
+		qcom,llm-freq-down-timer =
+			<327675 327675 327675>;
+		qcom,enable-llm-volt-vote;
+		qcom,llm-volt-up-timer =
+			<1000 1000 1000>;
+		qcom,llm-volt-down-timer =
+			<327675 327675 327675>;
+		qcom,cc-reads = <10>;
+		qcom,cc-delay = <5>;
+		qcom,cc-factor = <100>;
+		qcom,osm-clk-rate = <100000000>;
+		qcom,xo-clk-rate = <19200000>;
+
+		qcom,l-val-base =
+			<0x178d0004 0x178c0004 0x178b0004>;
+		qcom,apcs-pll-user-ctl =
+			<0x178d000c 0x178c000c 0x178b000c>;
+		qcom,apcs-pll-min-freq =
+			<0x17d41094 0x17d43094 0x17d45894>;
+		qcom,apm-mode-ctl =
+			<0x0 0x0 0x17d20010>;
+		qcom,apm-status-ctrl =
+			<0x0 0x0 0x17d20000>;
+		qcom,perfcl-isense-addr = <0x17871480>;
+		qcom,l3-mem-acc-addr = <0x17990170 0x17990170 0x17990170>;
+		qcom,pwrcl-mem-acc-addr = <0x17990160 0x17990164 0x17990164>;
+		qcom,perfcl-mem-acc-addr = <0x17990168 0x1799016c 0x1799016c>;
+		qcom,cfg-gfmux-addr =<0x178d0084 0x178c0084 0x178b0084>;
+		qcom,apcs-cbc-addr = <0x178d008c 0x178c008c 0x178b008c>;
+		qcom,apcs-ramp-ctl-addr = <0x17840904 0x17840904 0x17830904>;
+
+		qcom,perfcl-apcs-apm-threshold-voltage = <800000>;
+		qcom,perfcl-apcs-mem-acc-threshold-voltage = <852000>;
+		qcom,boost-fsm-en;
+		qcom,safe-fsm-en;
+		qcom,ps-fsm-en;
+		qcom,droop-fsm-en;
+		qcom,osm-no-tz;
+		qcom,osm-pll-setup;
+
+		clock-names = "xo_ao";
+		clocks = <&clock_rpmh RPMH_CXO_CLK_A>;
+		#clock-cells = <1>;
+		#reset-cells = <1>;
+	};
diff --git a/Documentation/devicetree/bindings/cpufreq/msm-cpufreq.txt b/Documentation/devicetree/bindings/cpufreq/msm-cpufreq.txt
new file mode 100644
index 0000000..9427123
--- /dev/null
+++ b/Documentation/devicetree/bindings/cpufreq/msm-cpufreq.txt
@@ -0,0 +1,47 @@
+Qualcomm MSM CPUfreq device
+
+msm-cpufreq is a device that represents the list of usable CPU frequencies
+and provides a device handle for the CPUfreq driver to get the CPU and cache
+clocks.
+
+Required properties:
+- compatible:		Must be "qcom,msm-cpufreq"
+- qcom,cpufreq-table, or qcom,cpufreq-table-<X>:
+			A list of usable CPU frequencies (KHz).
+			Use "qcom,cpufreq-table" if all CPUs in the system
+			should share same list of frequencies.
+			Use "qcom,cpufreq-table-<cpuid>" to describe
+			different CPU freq tables for different CPUs.
+			The table should be listed only for the first CPU
+			if multiple CPUs are synchronous.
+
+Optional properties:
+- clock-names:		When DT based binding of clock is available, this
+			provides a list of CPU subsystem clocks.
+			"cpuX_clk" for every CPU that's present.
+			"l2_clk" when an async cache/CCI is present.
+
+Optional properties:
+- qcom,governor-per-policy:	This property denotes that governor tunables
+				should be associated with each cpufreq policy
+				group instead of being global.
+
+Example:
+	qcom,msm-cpufreq {
+		compatible = "qcom,msm-cpufreq";
+		qcom,cpufreq-table =
+			<  300000 >,
+			<  422400 >,
+			<  652800 >,
+			<  729600 >,
+			<  883200 >,
+			<  960000 >,
+			< 1036800 >,
+			< 1190400 >,
+			< 1267200 >,
+			< 1497600 >,
+			< 1574400 >,
+			< 1728000 >,
+			< 1958400 >,
+			< 2265600 >;
+	};
diff --git a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
index b6bc475..058dab1 100644
--- a/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
+++ b/Documentation/devicetree/bindings/media/video/msm-sde-rotator.txt
@@ -89,6 +89,10 @@
 				entries by name.
 - cache-slices:			The tuple has phandle to llcc device as the first argument and the
 				second argument is the usecase id of the client.
+- qcom,sde-ubwc-malsize:	A u32 property to specify the default UBWC
+				minimum allowable length configuration value.
+- qcom,sde-ubwc-swizzle:	A u32 property to specify the default UBWC
+				swizzle configuration value.
 
 Subnode properties:
 - compatible:		Compatible name used in smmu v2.
@@ -126,6 +130,8 @@
 		clock-names = "iface_clk", "rot_core_clk";
 
 		qcom,mdss-highest-bank-bit = <0x2>;
+		qcom,sde-ubwc-malsize = <0>;
+		qcom,sde-ubwc-swizzle = <1>;
 
 		/* Bus Scale Settings */
 		qcom,msm-bus,name = "mdss_rotator";
@@ -144,7 +150,7 @@
 
 		qcom,mdss-sbuf-headroom = <20>;
 		cache-slice-names = "rotator";
-		cache-slices = <&llcc 3>;
+		cache-slices = <&llcc 4>;
 
 		smmu_rot_unsec: qcom,smmu_rot_unsec_cb {
 			compatible = "qcom,smmu_sde_rot_unsec";
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg-gen3.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg-gen3.txt
new file mode 100644
index 0000000..9638888
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg-gen3.txt
@@ -0,0 +1,422 @@
+Qualcomm Techonologies, Inc. QPNP PMIC Fuel Gauge Gen3 Device
+
+QPNP PMIC FG Gen3 device provides interface to the clients to read properties
+related to the battery. Its main function is to retrieve the State of Charge
+(SOC), in percentage scale representing the amount of charge left in the
+battery.
+
+=======================
+Required Node Structure
+=======================
+
+FG Gen3 device must be described in two levels of device nodes.  The first
+level describes the FG Gen3 device.  The second level describes one or more
+peripherals managed by FG Gen3 driver. All the peripheral specific parameters
+such as base address, interrupts etc., should be under second level node.
+
+====================================
+First Level Node - FG Gen3 device
+====================================
+
+- compatible
+	Usage:      required
+	Value type: <string>
+	Definition: Should be "qcom,fg-gen3".
+
+- qcom,pmic-revid
+	Usage:      required
+	Value type: <phandle>
+	Definition: Should specify the phandle of PMIC revid module. This is
+		    used to identify the PMIC subtype.
+
+- io-channels
+- io-channel-names
+	Usage:      required
+	Value type: <phandle>
+	Definition: For details about IIO bindings see:
+		    Documentation/devicetree/bindings/iio/iio-bindings.txt
+
+- qcom,rradc-base
+	Usage:      required
+	Value type: <u32>
+	Definition: Should specify the base address of RR_ADC peripheral. This
+		    is used for reading certain peripheral registers under it.
+
+- qcom,fg-cutoff-voltage
+	Usage:      optional
+	Value type: <u32>
+	Definition: The voltage (in mV) where the fuel gauge will steer the SOC
+		    to be zero. For example, if the cutoff voltage is set to
+		    3400mv, the fuel gauge will try to count SoC so that the
+		    battery SOC will be 0 when it is 3400mV. If this property
+		    is not specified, then the default value used will be
+		    3200mV.
+
+- qcom,fg-empty-voltage
+	Usage:      optional
+	Value type: <u32>
+	Definition: The voltage threshold (in mV) based on which the empty soc
+		    interrupt will be triggered. When the empty soc interrupt
+		    fires, battery soc will be set to 0 and the userspace will
+		    be notified via the power supply framework. The userspace
+		    will read 0% soc and immediately shutdown. If this property
+		    is not specified, then the default value used will be
+		    2800mV.
+
+- qcom,fg-vbatt-low-thr
+	Usage:      optional
+	Value type: <u32>
+	Definition: The voltage threshold (in mV) which upon set will be used
+		    for configuring the low battery voltage threshold.
+
+- qcom,fg-recharge-voltage
+	Usage:      optional
+	Value type: <u32>
+	Definition: The voltage threshold (in mV) based on which the charging
+		    will be resumed once the charging is complete. If this
+		    property is not specified, then the default value will be
+		    4250mV.
+
+- qcom,fg-chg-term-current
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery current (in mA) at which the fuel gauge will issue
+		    an end of charge if the charger is configured to use the
+		    fuel gauge ADC for end of charge detection. If this
+		    property is not specified, then the default value used
+		    will be 100mA.
+
+- qcom,fg-sys-term-current
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery current (in mA) at which the fuel gauge will try to
+		    scale towards 100%. When the charge current goes above this
+		    the SOC should be at 100%. If this property is not
+		    specified, then the default value used will be -125mA.
+		    This value has to be specified in negative values for
+		    the charging current.
+
+- qcom,fg-delta-soc-thr
+	Usage:      optional
+	Value type: <u32>
+	Definition: Percentage of SOC increase upon which the delta monotonic &
+		    battery SOC interrupts will be triggered. If this property
+		    is not specified, then the default value will be 1.
+		    Possible values are in the range of 0 to 12.
+
+- qcom,fg-recharge-soc-thr
+	Usage:      optional
+	Value type: <u32>
+	Definition: Percentage of monotonic SOC upon which the charging will
+		    will be resumed once the charging is complete. If this
+		    property is not specified, then the default value will be
+		    95.
+
+- qcom,fg-rsense-sel
+	Usage:      optional
+	Value type: <u32>
+	Definition: Specifies the source of sense resistor.
+		    Allowed values are:
+		    0 - Rsense is from Battery FET
+		    2 - Rsense is Battery FET and SMB
+		    Option 2 can be used only when a parallel charger is
+		    present. If this property is not specified, then the
+		    default value will be 2.
+
+- qcom,fg-jeita-thresholds
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: A list of integers which holds the jeita thresholds (degC)
+		    in the following order. Allowed size is 4.
+		    Element 0 - JEITA cold threshold
+		    Element 1 - JEITA cool threshold
+		    Element 2 - JEITA warm threshold
+		    Element 3 - JEITA hot threshold
+		    If these parameters are not specified, then the default
+		    values used will be 0, 5, 45, 50.
+
+- qcom,fg-esr-timer-charging
+	Usage:      optional
+	Value type: <u32>
+	Definition: Number of cycles between ESR pulses while the battery is
+		    charging.
+
+- qcom,fg-esr-timer-awake
+	Usage:      optional
+	Value type: <u32>
+	Definition: Number of cycles between ESR pulses while the system is
+		    awake and the battery is discharging.
+
+- qcom,fg-esr-timer-asleep
+	Usage:      optional
+	Value type: <u32>
+	Definition: Number of cycles between ESR pulses while the system is
+		    asleep and the battery is discharging. This option requires
+		    qcom,fg-esr-timer-awake to be defined.
+
+- qcom,cycle-counter-en
+	Usage:      optional
+	Value type: <empty>
+	Definition: Enables the cycle counter feature.
+
+- qcom,fg-force-load-profile
+	Usage:      optional
+	Value type: <empty>
+	Definition: If set, battery profile will be force loaded if the profile
+		    loaded earlier by bootloader doesn't match with the profile
+		    available in the device tree.
+
+- qcom,cl-start-capacity
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery SOC threshold to start the capacity learning.
+		    If this is not specified, then the default value used
+		    will be 15.
+
+- qcom,cl-min-temp
+	Usage:      optional
+	Value type: <u32>
+	Definition: Lower limit of battery temperature to start the capacity
+		    learning. If this is not specified, then the default value
+		    used will be 150. Unit is in decidegC.
+
+- qcom,cl-max-temp
+	Usage:      optional
+	Value type: <u32>
+	Definition: Upper limit of battery temperature to start the capacity
+		    learning. If this is not specified, then the default value
+		    used will be 450 (45C). Unit is in decidegC.
+
+- qcom,cl-max-increment
+	Usage:      optional
+	Value type: <u32>
+	Definition: Maximum capacity increment allowed per capacity learning
+		    cycle. If this is not specified, then the default value
+		    used will be 5 (0.5%). Unit is in decipercentage.
+
+- qcom,cl-max-decrement
+	Usage:      optional
+	Value type: <u32>
+	Definition: Maximum capacity decrement allowed per capacity learning
+		    cycle. If this is not specified, then the default value
+		    used will be 100 (10%). Unit is in decipercentage.
+
+- qcom,cl-min-limit
+	Usage:      optional
+	Value type: <u32>
+	Definition: Minimum limit that the capacity cannot go below in a
+		    capacity learning cycle. If this is not specified, then
+		    the default value is 0. Unit is in decipercentage.
+
+- qcom,cl-max-limit
+	Usage:      optional
+	Value type: <u32>
+	Definition: Maximum limit that the capacity cannot go above in a
+		    capacity learning cycle. If this is not specified, then
+		    the default value is 0. Unit is in decipercentage.
+
+- qcom,battery-thermal-coefficients
+	Usage:      optional
+	Value type: <u8>
+	Definition: Byte array of battery thermal coefficients.
+		    This should be exactly 3 bytes in length.
+
+- qcom,fg-jeita-hyst-temp
+	Usage:      optional
+	Value type: <u32>
+	Definition: Hysteresis applied to Jeita temperature comparison.
+		    Possible values are:
+			0 - No hysteresis
+			1,2,3 - Value in Celsius.
+
+- qcom,fg-batt-temp-delta
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery temperature delta interrupt threshold. Possible
+		    values are: 2, 4, 6 and 10. Unit is in Kelvin.
+
+- qcom,hold-soc-while-full
+	Usage:      optional
+	Value type: <empty>
+	Definition: A boolean property that when defined holds SOC at 100% when
+		    the battery is full.
+
+- qcom,ki-coeff-soc-dischg
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array of monotonic SOC threshold values to change the ki
+		    coefficient for medium discharge current during discharge.
+		    This should be defined in the ascending order and in the
+		    range of 0-100. Array limit is set to 3.
+
+- qcom,ki-coeff-med-dischg
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array of ki coefficient values for medium discharge current
+		    during discharge. These values will be applied when the
+		    monotonic SOC goes below the SOC threshold specified under
+		    qcom,ki-coeff-soc-dischg. Array limit is set to 3. This
+		    property should be specified if qcom,ki-coeff-soc-dischg
+		    is specified to make it fully functional. Value has no
+		    unit. Allowed range is 0 to 62200 in micro units.
+
+- qcom,ki-coeff-hi-dischg
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Array of ki coefficient values for high discharge current
+		    during discharge. These values will be applied when the
+		    monotonic SOC goes below the SOC threshold specified under
+		    qcom,ki-coeff-soc-dischg. Array limit is set to 3. This
+		    property should be specified if qcom,ki-coeff-soc-dischg
+		    is specified to make it fully functional. Value has no
+		    unit. Allowed range is 0 to 62200 in micro units.
+
+- qcom,fg-rconn-mohms
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery connector resistance (Rconn) in milliohms. If Rconn
+		    is specified, then ESR to Rslow scaling factors will be
+		    updated to account it for an accurate ESR.
+
+- qcom,fg-esr-clamp-mohms
+	Usage:      optional
+	Value type: <u32>
+	Definition: Equivalent series resistance (ESR) in milliohms. If this
+		    is specified, then ESR will be clamped to this value when
+		    ESR is found to be dropping below this. Default value is
+		    20.
+
+- qcom,fg-esr-filter-switch-temp
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery temperature threshold below which low temperature
+		    ESR filter coefficients will be switched to normal
+		    temperature ESR filter coefficients. If this is not
+		    specified, then the default value used will be 100. Unit is
+		    in decidegC.
+
+- qcom,fg-esr-tight-filter-micro-pct
+	Usage:      optional
+	Value type: <u32>
+	Definition: Value in micro percentage for ESR tight filter. If this is
+		    not specified, then a default value of 3907 (0.39 %) will
+		    be used. Lowest possible value is 1954 (0.19 %).
+
+- qcom,fg-esr-broad-filter-micro-pct
+	Usage:      optional
+	Value type: <u32>
+	Definition: Value in micro percentage for ESR broad filter. If this is
+		    not specified, then a default value of 99610 (9.96 %) will
+		    be used. Lowest possible value is 1954 (0.19 %).
+
+- qcom,fg-esr-tight-lt-filter-micro-pct
+	Usage:      optional
+	Value type: <u32>
+	Definition: Value in micro percentage for low temperature ESR tight
+		    filter. If this is not specified, then a default value of
+		    48829 (4.88 %) will be used. Lowest possible value is 1954
+		    (0.19 %).
+
+- qcom,fg-esr-broad-lt-filter-micro-pct
+	Usage:      optional
+	Value type: <u32>
+	Definition: Value in micro percentage for low temperature ESR broad
+		    filter. If this is not specified, then a default value of
+		    148438 (14.84 %) will be used. Lowest possible value is
+		    1954 (0.19 %).
+
+- qcom,fg-auto-recharge-soc
+	Usage:      optional
+	Value type: <empty>
+	Definition: A boolean property when defined will configure automatic
+		    recharge SOC threshold. If not specified, automatic
+		    recharge voltage threshold will be configured. This has
+		    to be configured in conjunction with the charger side
+		    configuration for proper functionality.
+
+- qcom,slope-limit-temp-threshold
+	Usage:      optional
+	Value type: <u32>
+	Definition: Battery temperature threshold to decide when slope limit
+		    coefficients should be applied along with charging status.
+		    Unit is in decidegC.
+
+- qcom,slope-limit-coeffs
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: A list of integers which holds the slope limit coefficients
+		    in the following order. Allowed size is 4. Possible values
+		    are from 0 to 31. Unit is in decipercentage.
+		    Element 0 - Low temperature discharging
+		    Element 1 - Low temperature charging
+		    Element 2 - High temperature discharging
+		    Element 3 - High temperature charging
+		    These coefficients have to be specified along with the
+		    property "qcom,slope-limit-temp-threshold" to make dynamic
+		    slope limit adjustment functional.
+
+==========================================================
+Second Level Nodes - Peripherals managed by FG Gen3 driver
+==========================================================
+- reg
+	Usage:      required
+	Value type: <prop-encoded-array>
+	Definition: Addresses and sizes for the specified peripheral
+
+- interrupts
+	Usage:      optional
+	Value type: <prop-encoded-array>
+	Definition: Interrupt mapping as per the interrupt encoding
+
+- interrupt-names
+	Usage:      optional
+	Value type: <stringlist>
+	Definition: Interrupt names.  This list must match up 1-to-1 with the
+		    interrupts specified in the 'interrupts' property.
+
+========
+Example
+========
+
+pmi8998_fg: qpnp,fg {
+	compatible = "qcom,fg-gen3";
+	#address-cells = <1>;
+	#size-cells = <1>;
+	qcom,pmic-revid = <&pmi8998_revid>;
+	io-channels = <&pmi8998_rradc 3>;
+	io-channel-names = "rradc_batt_id";
+	qcom,rradc-base = <0x4500>;
+	qcom,ki-coeff-soc-dischg = <30 60 90>;
+	qcom,ki-coeff-med-dischg = <800 1000 1400>;
+	qcom,ki-coeff-hi-dischg = <1200 1500 2100>;
+	qcom,slope-limit-temp-threshold = <100>;
+	qcom,slope-limit-coeffs = <10 11 12 13>;
+	qcom,battery-thermal-coefficients = [9d 50 ff];
+	status = "okay";
+
+	qcom,fg-batt-soc@4000 {
+		status = "okay";
+		reg = <0x4000 0x100>;
+		interrupts = <0x2 0x40 0x0 IRQ_TYPE_EDGE_BOTH>,
+			     <0x2 0x40 0x1 IRQ_TYPE_EDGE_BOTH>,
+			     <0x2 0x40 0x2 IRQ_TYPE_EDGE_BOTH>,
+			     <0x2 0x40 0x3 IRQ_TYPE_EDGE_BOTH>;
+		interrupt-names = "soc-update",
+				  "soc-ready",
+				  "bsoc-delta",
+				  "msoc-delta";
+
+	};
+
+	qcom,fg-batt-info@4100 {
+		status = "okay";
+		reg = <0x4100 0x100>;
+		interrupts = <0x2 0x41 0x3 IRQ_TYPE_EDGE_BOTH>;
+		interrupt-names = "batt-missing";
+	};
+
+	qcom,fg-memif@4400 {
+		status = "okay";
+		reg = <0x4400 0x100>;
+	};
+};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qnovo.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qnovo.txt
new file mode 100644
index 0000000..96b7dd5
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-qnovo.txt
@@ -0,0 +1,32 @@
+QPNP Qnovo pulse engine
+
+QPNP Qnovo is a pulse charging engine which works in tandem with the QPNP SMB2
+Charger device. It configures the QPNP SMB2 charger to charge/discharge as per
+pulse characteristics.
+
+The QPNP Qnovo pulse engine has a single peripheral assigned to it.
+
+Required properties:
+- compatible:		Must be "qcom,qpnp-qnovo"
+- qcom,pmic-revid:	Should specify the phandle of PMIC
+			revid module. This is used to identify
+			the PMIC subtype.
+
+- reg:			The address for this peripheral
+- interrupts:		Specifies the interrupt associated with the peripheral.
+- interrupt-names:	Specifies the interrupt name for the peripheral. Qnovo
+			peripheral has only one interrupt "ptrain-done".
+
+Optional Properties:
+- qcom,external-rsense:		To indicate whether the platform uses external or
+				internal rsense for measuring battery current.
+
+Example:
+
+	qcom,qpnp-qnovo@1500 {
+		compatible = "qcom,qpnp-qnovo";
+		reg = <0x1500 0x100>;
+		interrupts = <0x2 0x15 0x0 IRQ_TYPE_NONE>;
+		interrupt-names = "ptrain-done";
+		qcom,pmic-revid = <&pmi8998_revid>;
+	};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt
new file mode 100644
index 0000000..f4a22e0
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smb2.txt
@@ -0,0 +1,315 @@
+Qualcomm Technologies, Inc. SMB2 Charger Specific Bindings
+
+SMB2 Charger is an efficient programmable battery charger capable of charging a
+high-capacity lithium-ion battery over micro-USB or USB Type-C ultrafast with
+Quick Charge 2.0, Quick Charge 3.0, and USB Power Delivery support. Wireless
+charging features full A4WP Rezence 1.2, WPC 1.2, and PMA support.
+
+=======================
+Required Node Structure
+=======================
+
+SMB2 Charger must be described in two levels of devices nodes.
+
+===============================
+First Level Node - SMB2 Charger
+===============================
+
+Charger specific properties:
+- compatible
+  Usage:      required
+  Value type: <string>
+  Definition: "qcom,qpnp-smb2".
+
+- qcom,pmic-revid
+  Usage:      required
+  Value type: phandle
+  Definition: Should specify the phandle of PMI's revid module. This is used to
+		identify the PMI subtype.
+
+- qcom,batteryless-platform
+  Usage:      optional
+  Value type: <empty>
+  Definition: Boolean flag which indicates that the platform does not have a
+		battery, and therefore charging should be disabled. In
+		addition battery properties will be faked such that the device
+		assumes normal operation.
+
+- qcom,external-vconn
+  Usage:      optional
+  Value type: <empty>
+  Definition: Boolean flag which indicates VCONN is sourced externally.
+
+- qcom,fcc-max-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the maximum fast charge current in micro-amps.
+		If the value is not present, 1Amp is used as default.
+
+- qcom,fv-max-uv
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the maximum float voltage in micro-volts.
+		If the value is not present, 4.35V is used as default.
+
+- qcom,usb-icl-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the USB input current limit in micro-amps.
+		 If the value is not present, 1.5Amps is used as default.
+
+- qcom,usb-ocl-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the OTG output current limit in micro-amps.
+		If the value is not present, 1.5Amps is used as default
+
+- qcom,dc-icl-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the DC input current limit in micro-amps.
+
+- qcom,boost-threshold-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the boost current threshold in micro-amps.
+		If the value is not present, 100mA is used as default.
+
+- qcom,wipower-max-uw
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the DC input power limit in micro-watts.
+		If the value is not present, 8W is used as default.
+
+- qcom,thermal-mitigation
+  Usage:      optional
+  Value type: Array of <u32>
+  Definition: Array of fast charge current limit values for
+		different system thermal mitigation levels.
+		This should be a flat array that denotes the
+		maximum charge current in mA for each thermal
+		level.
+
+- qcom,step-soc-thresholds
+  Usage:      optional
+  Value type: Array of <u32>
+  Definition: Array of SOC threshold values, size of 4. This should be a
+		flat array that denotes the percentage ranging from 0 to 100.
+		If the array is not present, step charging is disabled.
+
+- qcom,step-current-deltas
+  Usage:      optional
+  Value type: Array of <s32>
+  Definition: Array of delta values for charging current, size of 5, with
+		FCC as base.  This should be a flat array that denotes the
+		offset of charging current in uA, from -3100000 to 3200000.
+		If the array is not present, step charging is disabled.
+
+- io-channels
+  Usage:      optional
+  Value type: List of <phandle u32>
+  Definition: List of phandle and IIO specifier pairs, one pair
+		for each IIO input to the device. Note: if the
+		IIO provider specifies '0' for #io-channel-cells,
+		then only the phandle portion of the pair will appear.
+
+- io-channel-names
+  Usage:      optional
+  Value type: List of <string>
+  Definition: List of IIO input name strings sorted in the same
+		order as the io-channels property. Consumer drivers
+		will use io-channel-names to match IIO input names
+		with IIO specifiers.
+
+- qcom,float-option
+  Usage:      optional
+  Value type: <u32>
+  Definition: Configures how the charger behaves when a float charger is
+	      detected by APSD
+	        1 - Treat as a DCP
+	        2 - Treat as a SDP
+	        3 - Disable charging
+                4 - Suspend USB input
+
+- qcom,hvdcp-disable
+  Usage:      optional
+  Value type: <empty>
+  Definition: Specifies if hvdcp charging is to be enabled or not.
+		If this property is not specified hvdcp will be enabled.
+		If this property is specified, hvdcp 2.0 detection will still
+		happen but the adapter won't be asked to switch to a higher
+		voltage point.
+
+- qcom,chg-inhibit-threshold-mv
+  Usage:      optional
+  Value type: <u32>
+  Definition: Charge inhibit threshold in milli-volts. Charging will be
+		inhibited when the battery voltage is within this threshold
+		from Vfloat at charger insertion. If this is not specified
+		then charge inhibit will be disabled by default.
+		Allowed values are: 50, 100, 200, 300.
+
+- qcom,auto-recharge-soc
+  Usage:      optional
+  Value type: <empty>
+  Definition: Specifies if automatic recharge needs to be based off battery
+		SOC. If this property is not specified, then auto recharge will
+		be based off battery voltage. For both SOC and battery voltage,
+		charger receives the signal from FG to resume charging.
+
+- qcom,micro-usb
+  Usage:      optional
+  Value type: <empty>
+  Definition: Boolean flag which indicates that the platform only support
+		micro usb port.
+
+- qcom,suspend-input-on-debug-batt
+  Usage:      optional
+  Value type: <empty>
+  Definition: Boolean flag which when present enables input suspend for
+		debug battery.
+
+=============================================
+Second Level Nodes - SMB2 Charger Peripherals
+=============================================
+
+Peripheral specific properties:
+- reg
+  Usage:      required
+  Value type: <prop-encoded-array>
+  Definition: Address and size of the peripheral's register block.
+
+- interrupts
+  Usage:      required
+  Value type: <prop-encoded-array>
+  Definition: Peripheral interrupt specifier.
+
+- interrupt-names
+  Usage:      required
+  Value type: <stringlist>
+  Definition: Interrupt names.  This list must match up 1-to-1 with the
+	      interrupts specified in the 'interrupts' property.
+
+=======
+Example
+=======
+
+pmi8998_charger: qcom,qpnp-smb2 {
+	compatible = "qcom,qpnp-smb2";
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	io-channels = <&pmic_rradc 0>;
+	io-channel-names = "rradc_batt_id";
+
+	dpdm-supply = <&qusb_phy0>;
+
+	qcom,step-soc-thresholds = <60 70 80 90>;
+	qcom,step-current-deltas = <500000 250000 150000 0 (-150000)>;
+
+	qcom,chgr@1000 {
+		reg = <0x1000 0x100>;
+		interrupts =    <0x2 0x10 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x10 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x10 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x10 0x3 IRQ_TYPE_NONE>,
+				<0x2 0x10 0x4 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "chg-error",
+					"chg-state-change",
+					"step-chg-state-change",
+					"step-chg-soc-update-fail",
+					"step-chg-soc-update-request";
+	};
+
+	qcom,otg@1100 {
+		reg = <0x1100 0x100>;
+		interrupts =    <0x2 0x11 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x11 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x11 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x11 0x3 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "otg-fail",
+					"otg-overcurrent",
+					"otg-oc-dis-sw-sts",
+					"testmode-change-detect";
+	};
+
+	qcom,bat-if@1200 {
+		reg = <0x1200 0x100>;
+		interrupts =    <0x2 0x12 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x12 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x12 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x12 0x3 IRQ_TYPE_NONE>,
+				<0x2 0x12 0x4 IRQ_TYPE_NONE>,
+				<0x2 0x12 0x5 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "bat-temp",
+					"bat-ocp",
+					"bat-ov",
+					"bat-low",
+					"bat-therm-or-id-missing",
+					"bat-terminal-missing";
+	};
+
+	qcom,usb-chgpth@1300 {
+		reg = <0x1300 0x100>;
+		interrupts =    <0x2 0x13 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x3 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x4 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x5 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x6 IRQ_TYPE_NONE>,
+				<0x2 0x13 0x7 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "usbin-collapse",
+					"usbin-lt-3p6v",
+					"usbin-uv",
+					"usbin-ov",
+					"usbin-plugin",
+					"usbin-src-change",
+					"usbin-icl-change",
+					"type-c-change";
+	};
+
+	qcom,dc-chgpth@1400 {
+		reg = <0x1400 0x100>;
+		interrupts =    <0x2 0x14 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x3 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x4 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x5 IRQ_TYPE_NONE>,
+				<0x2 0x14 0x6 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "dcin-collapse",
+					"dcin-lt-3p6v",
+					"dcin-uv",
+					"dcin-ov",
+					"dcin-plugin",
+					"div2-en-dg",
+					"dcin-icl-change";
+	};
+
+	qcom,chgr-misc@1600 {
+		reg = <0x1600 0x100>;
+		interrupts =    <0x2 0x16 0x0 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x1 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x2 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x3 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x4 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x5 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x6 IRQ_TYPE_NONE>,
+				<0x2 0x16 0x7 IRQ_TYPE_NONE>;
+
+		interrupt-names =       "wdog-snarl",
+					"wdog-bark",
+					"aicl-fail",
+					"aicl-done",
+					"high-duty-cycle",
+					"input-current-limiting",
+					"temperature-change",
+					"switcher-power-ok";
+	};
+};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/smb1351-charger.txt b/Documentation/devicetree/bindings/power/supply/qcom/smb1351-charger.txt
new file mode 100644
index 0000000..c200f94
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/smb1351-charger.txt
@@ -0,0 +1,115 @@
+Summit smb1351 battery charger
+
+SMB1351 is a single-cell battery charger. It can charge
+the battery and power the system via the USB/AC adapter input.
+
+The smb1351 interface is via I2C bus.
+
+Required Properties:
+- compatible			Must be "qcom,smb1351-charger".
+- reg				The device 7-bit I2C address.
+
+Required Properties for standalone charger:
+- regulator-name		A string used as a descriptive name for OTG regulator.
+- pinctrl-names			The state name of the pin configuration. Only
+				support "default".
+- pinctrl-0			The phandle of the pin configuration node in
+				pinctrl for smb_int_pin.
+
+Optional Properties:
+
+- interrupts			This indicates the IRQ number of the GPIO
+				connected to the STAT pin.
+- qcom,fastchg-current-max-ma 	Fast Charging current in mA. Supported range is
+				from 1000mA to 4500mA.
+- qcom,chg-autonomous-mode	This is a bool property and it indicates that the
+				charger is configured for autonomous operation and
+				does not require any software configuration.
+- qcom,disable-apsd		This is a bool property which disables automatic
+				power source detection (APSD). If this is set
+				charger detection is done by DCIN UV irq.
+- qcom,charging-disabled	This is a bool property which disables charging.
+- qcom,using-pmic-therm		This property indicates thermal pin connected to pmic or smb.
+- qcom,bms-psy-name		This is a string and it points to the bms
+				power supply name.
+- qcom,iterm-ma			Specifies the termination current to indicate end-of-charge.
+				Possible values in mA - 70, 100, 200, 300, 400, 500, 600, 700.
+- qcom,iterm-disabled		Disables the termination current feature. This is a bool
+				property.
+- qcom,float-voltage-mv	 	Float Voltage in mV - the maximum voltage up to which
+				the battery is charged. Supported range 3500mV to 4500mV
+- qcom,recharge-mv		Recharge threshold in mV - the offset from the float-volatge
+				as which the charger restarts charging. Possible
+				values are 50mV and 100mV.
+- qcom,recharge-disabled	Boolean value which disables the auto-recharge.
+- qcom,bms-controlled-charging	This property enables BMS to control EOC and
+				recharge. BMS and charger communicates with each
+				other via power_supply framework. This
+				property should be used with 'qcom,iterm-disabled'
+				to ensure EOC detection in charger is disabled.
+- qcom,force-hvdcp-2p0		Boolean value which allows to force hvdcp working on 2.0 mode.
+- qcom,parallel-charger		Boolean value which enables the parallel charger.
+- qcom,chg-vadc			Corresponding VADC device's phandle.
+- qcom,chg-adc_tm		phandle to the corresponding VADC device to read the ADC channels.
+- qcom,batt-cold-decidegc	Cold battery temperature in decidegC.
+- qcom,batt-hot-decidegc	Hot battery temperature in decidegC.
+- qcom,batt-missing-decidegc	This is a property indicating battery missing temperature, if
+				higher than it, battery should exist.
+- qcom,batt-warm-decidegc:	Warm battery temperature in decidegC. After hitting this threshold,
+				"qcom,warm-bat-ma" defines maximum charging current and
+				"qcom,warm-bat-mv" defines maximum target voltage.
+- qcom,batt-cool-decidegc:       Cool battery temperature in decidegC. After hitting this threshold,
+				"qcom,cool-bat-ma" defines maximum charging current and
+				"qcom,cool-bat-mv" defines maximum target voltage.
+- qcom,batt-warm-ma:		Maximum warm battery charge current in milli-amps.
+- qcom,batt-cool-ma:		Maximum cool battery charge current in milli-amps.
+- qcom,batt-warm-mv:		Maximum warm battery target voltage in milli-volts.
+- qcom,batt-cool-mv:		Maximum cool battery target voltage in milli-volts.
+- qcom,parallel-en-pin-polarity Specify the polarity of enable signal controlled
+				via pin in a parallel-charger configuration.
+				0 - Active low and 1  - Active high.
+				If not specified the default value is active-low.
+- qcom,parallel-external-current-sense If present specifies external rsense is
+				used for charge current sensing.
+
+Example for standalone charger:
+
+&i2c_4 {
+	smb1351_otg_supply: smb1351-charger@57 {
+		compatible = "qcom,smb1351-charger";
+		reg = <0x57>;
+		interrupt-parent = <&msm_gpio>;
+		interrupts = <62 2>;
+		pinctrl-names = "default";
+		pinctrl-0 = <&smb_int_default>;
+		qcom,float-voltage-mv = <4350>;
+		qcom,iterm-ma = <100>;
+		qcom,recharge-mv = <100>;
+		qcom,bms-psy-name = "bms";
+		regulator-name = "smb1351_otg_vreg";
+		qcom,using-pmic-therm;
+		qcom,chg-adc_tm = <&pm8916_adc_tm>;
+		qcom,chg-vadc = <&pm8916_vadc>;
+		qcom,batt-hot-decidegc = <550>;
+		qcom,batt-cold-decidegc = <0>;
+		qcom,batt-missing-decidegc = <(-200)>;
+		qcom,batt-warm-decidegc = <500>;
+		qcom,batt-cool-decidegc = <50>;
+		qcom,batt-warm-ma = <350>;
+		qcom,batt-cool-ma = <350>;
+		qcom,batt-warm-mv = <4200>;
+		qcom,batt-cool-mv = <4200>;
+	};
+};
+
+Example for parallel charger:
+
+&i2c_11 {
+	smb1351-charger@1d {
+		compatible = "qcom,smb1351-charger";
+		reg = <0x1d>;
+		qcom,parallel-charger;
+		qcom,float-voltage-mv = <4400>;
+		qcom,recharge-mv = <100>;
+	};
+};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/smb135x-charger.txt b/Documentation/devicetree/bindings/power/supply/qcom/smb135x-charger.txt
new file mode 100644
index 0000000..90527f3
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/smb135x-charger.txt
@@ -0,0 +1,101 @@
+SMB135x battery charger
+
+SMB135x is a single-cell switching mode battery charger. It can charge
+the battery and power the system via the USB and AC adapter input.
+
+The smb135x interface is via I2C bus.
+
+Required Properties:
+- compatible:			Must be "qcom,smb1356-charger", "qcom,smb1357-charger",
+				"qcom,smb1358-charger" or "qcom,smb1359-charger".
+- reg:				The device 7-bit I2C address.
+
+Optional Properties:
+
+- interrupts			This indicates the IRQ number of the GPIO
+				connected to the STAT pin.
+- qcom,bms-psy-name	 	the psy name to use for reporting battery capacity. If left
+				unspecified it uses a preprogrammed default value.
+- qcom,float-voltage-mv	 	Float Voltage in mV - the maximum voltage up to which
+				the battery is charged. Supported range 3600mV to 4500mV
+- qcom,charging-timeout		Maximum duration in minutes that a single charge
+				cycle may last.  Supported values are: 0, 192, 384,
+				768, and 1536.  A value of 0 means that no
+				charge cycle timeout is used and charging can
+				continue indefinitely.
+- qcom,dc-psy-type		The type of charger connected to the DC path.
+				Can be "Mains" or "Wireless"
+- qcom,dc-psy-ma		The current in mA dc path can support. Must be specified if
+				dc-psy-type is specified. Valid range 300mA to 2000mA.
+- qcom,charging-disabled	Set this if charging should be disabled in the build
+				by default. Useful in usecases where battery current
+				needs to be profiled even when USB is present.
+- qcom,recharge-thresh-mv	Specifies the minimum voltage drop in millivolts
+				below the float voltage that is required in
+				order to initiate a new charging cycle.
+				Supported values are: 50, 100, 200 and 300mV.
+- qcom,bmd-algo-disabled	Indicates if the battery missing detection algorithm
+				is disabled. If this node is present SMB uses
+				the THERM pin for battery missing detection.
+- qcom,iterm-ma			Specifies the termination current to indicate end-of-charge.
+				Possible values in mA - 50, 100, 150, 200, 250, 300, 500, 600.
+- qcom,iterm-disabled		Disables the termination current feature. This is a bool
+				property.
+- qcom,soft-vfloat-comp-disabled	Set this property when the battery is powered via external
+					source and could go above the float voltage.  smb135x chips
+					go in to unintentional reverse boost in such a situation and
+					the float voltage compensation needs to be disabled to avoid
+					that reverse boost.
+- qcom,soft-current-comp-disabled	Set this property to disable charging current compensation
+					if battery temperature exceeds soft JEITA thresholds.
+- qcom,gamma-setting			Array of gamma values for JEITA. The sequence is
+					<"Cold Hard" "Hot Hard" "Cold Soft" "Hot Soft">. Gamma value
+					indicates the ratio of the pull up resistors and NTC
+					resistor in battery pack. There are 4 options referring to
+					the graphic user interface.
+- qcom,thermal-mitigation:		Array of input current limit values for different
+					system thermal mitigation level.
+- regulator-name			A string used as a descriptive name for OTG regulator.
+- therm-bias-supply			The supply that provides bias voltage to the battery
+					thermistor. This is useful in designs that do not use the SYSON
+					pin to bias the thermistor.
+- usb-pullup-supply			The supply regulator that act as pull-up for USB data lines.
+- qcom,parallel-charger:		A flag to indicate if the charger merely assists for USB
+					charging. In this case the input current from USB is split
+					between a main charger and smb135x for reducing thermal impact
+					of high current charging from USB path.
+- qcom,inhibit-disabled:	Disables the charger-inhibit function.
+- qcom,bms-controlled-charging: This property enables BMS to control EOC and
+				recharge. BMS and charger communicates with each
+				other via power_supply framework. This
+				property should be used with 'qcom,iterm-disabled'
+				to ensure EOC detection in charger is
+				disabled.
+- qcom,fastchg-ma:		Specifies the maximum fastcharge current.
+				The possible range for fastcharge current is
+				from 300mA to 3000mA.
+- qcom,id-line-not-connected:	Specifies if smb135x charger is not monitoring the USB_ID line.
+- qcom,parallel-en-pin-polarity Specify the polarity of enable signal controlled
+				via pin in a parallel-charger configuration.
+				0 - Active low and 1  - Active high.
+				If not specified the default value is active-low.
+
+Example:
+	i2c@f9967000 {
+		smb1357-charger@1b {
+			compatible = "qcom,smb1357-charger";
+			reg = <0x1b>;
+			interrupt-parent = <&spmi_bus>;
+			interrupts = <0x00 0xCD 0>;
+			qcom,float-voltage-mv = <4200>;
+			qcom,iterm-ma = <100>;
+			qcom,dc-psy-type = <8>;
+			qcom,dc-psy-ma = <800>;
+			qcom,charging-disabled;
+			qcom,recharge-thresh-mv = <100>;
+			regulator-name = "smb1357-otg";
+			qcom,thermal-mitigation = <1500 700 600 325>;
+			qcom,gamma-setting = <3 2 0 2>;
+			qcom,fastchg-ma = <3000>;
+		};
+	};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/smb138x-charger.txt b/Documentation/devicetree/bindings/power/supply/qcom/smb138x-charger.txt
new file mode 100644
index 0000000..c8f2a5a
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/smb138x-charger.txt
@@ -0,0 +1,234 @@
+Qualcomm Technologies, Inc. SMB138X Charger Specific Bindings
+
+SMB138X Charger is an efficient programmable battery charger capable of charging
+a high-capacity lithium-ion battery over micro-USB or USB Type-C ultrafast with
+Quick Charge 2.0, Quick Charge 3.0 support. Wireless charging features full
+A4WP Rezence 1.2, WPC 1.2, and PMA support.
+
+=======================
+Required Node Structure
+=======================
+
+SMB138X Charger must be described in two levels of devices nodes.
+
+==================================
+First Level Node - SMB138X Charger
+==================================
+
+Charger specific properties:
+- compatible
+  Usage:      required
+  Value type: <string>
+  Definition: String which indicates the charging mode. Can be one of the
+	      following:
+              Standalone/Parallel Master	- "qcom,smb138x-charger"
+	      Parallel Slave			- "qcom,smb138x-parallel-slave"
+
+- qcom,pmic-revid
+  Usage:      required
+  Value type: phandle
+  Definition: Should specify the phandle of SMB's
+	revid module. This is used to identify
+	the SMB subtype.
+
+- qcom,suspend-input
+  Usage:      optional
+  Value type: <empty>
+  Definition: Boolean flag which indicates that the charger should not draw
+	      current from any of its input sources (USB, DC).
+
+- qcom,fcc-max-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the maximum fast charge current in micro-amps.
+
+- qcom,usb-icl-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the USB input current limit in micro-amps.
+
+- qcom,dc-icl-ua
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the DC input current limit in micro-amps.
+
+- qcom,charger-temp-max-mdegc
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the maximum charger temperature in milli-degrees
+	      Celsius. If unspecified a default of 80000 will be used.
+
+- qcom,connector-temp-max-mdegc
+  Usage:      optional
+  Value type: <u32>
+  Definition: Specifies the maximum connector temperature in milli-degrees
+	      Celsius. If unspecified a default value of 105000 will be used.
+
+- io-channels
+  Usage:      optional
+  Value type: List of <phandle u32>
+  Definition: List of phandle and IIO specifier pairs, one pair
+		for each IIO input to the device. Note: if the
+		IIO provider specifies '0' for #io-channel-cells,
+		then only the phandle portion of the pair will appear.
+
+- io-channel-names
+  Usage:      optional
+  Value type: List of <string>
+  Definition: List of IIO input name strings sorted in the same
+		order as the io-channels property. Consumer drivers
+		will use io-channel-names to match IIO input names
+		with IIO specifiers.
+
+================================================
+Second Level Nodes - SMB138X Charger Peripherals
+================================================
+
+Peripheral specific properties:
+- reg
+  Usage:      required
+  Value type: <prop-encoded-array>
+  Definition: Address and size of the peripheral's register block.
+
+- interrupts
+  Usage:      required
+  Value type: <prop-encoded-array>
+  Definition: Peripheral interrupt specifier.
+
+- interrupt-names
+  Usage:      required
+  Value type: <stringlist>
+  Definition: Interrupt names.  This list must match up 1-to-1 with the
+	      interrupts specified in the 'interrupts' property.
+
+=======================================
+Second Level Nodes - SMB138X Regulators
+=======================================
+
+The following regulator nodes are supported:
+"qcom,smb138x-vbus"	- Regulator for enabling VBUS
+"qcom,smb138x-vconn"	- Regulator for enabling VCONN
+
+- regulator-name
+  Usage:      required
+  Value type: <string>
+  Definition: Specifies the name for this regulator.
+
+=======
+Example
+=======
+
+smb138x_charger: qcom,smb138x-charger {
+	compatible = "qcom,qpnp-smb138x-charger";
+	#address-cells = <1>;
+	#size-cells = <1>;
+
+	qcom,suspend-input;
+	dpdm-supply = <&qusb_phy0>;
+
+	qcom,chgr@1000 {
+		reg = <0x1000 0x100>;
+		interrupts =    <0x10 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x10 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x10 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x10 0x3 IRQ_TYPE_EDGE_BOTH>,
+				<0x10 0x4 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "chg-error",
+					"chg-state-change",
+					"step-chg-state-change",
+					"step-chg-soc-update-fail",
+					"step-chg-soc-update-request";
+	};
+
+	qcom,otg@1100 {
+		reg = <0x1100 0x100>;
+		interrupts =    <0x11 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x11 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x11 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x11 0x3 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "otg-fail",
+					"otg-overcurrent",
+					"otg-oc-dis-sw-sts",
+					"testmode-change-detect";
+	};
+
+	qcom,bat-if@1200 {
+		reg = <0x1200 0x100>;
+		interrupts =    <0x12 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x12 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x12 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x12 0x3 IRQ_TYPE_EDGE_BOTH>,
+				<0x12 0x4 IRQ_TYPE_EDGE_BOTH>,
+				<0x12 0x5 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "bat-temp",
+					"bat-ocp",
+					"bat-ov",
+					"bat-low",
+					"bat-therm-or-id-missing",
+					"bat-terminal-missing";
+	};
+
+	qcom,usb-chgpth@1300 {
+		reg = <0x1300 0x100>;
+		interrupts =    <0x13 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x3 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x4 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x5 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x6 IRQ_TYPE_EDGE_BOTH>,
+				<0x13 0x7 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "usbin-collapse",
+					"usbin-lt-3p6v",
+					"usbin-uv",
+					"usbin-ov",
+					"usbin-plugin",
+					"usbin-src-change",
+					"usbin-icl-change",
+					"type-c-change";
+	};
+
+	qcom,dc-chgpth@1400 {
+		reg = <0x1400 0x100>;
+		interrupts =    <0x14 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x3 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x4 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x5 IRQ_TYPE_EDGE_BOTH>,
+				<0x14 0x6 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "dcin-collapse",
+					"dcin-lt-3p6v",
+					"dcin-uv",
+					"dcin-ov",
+					"dcin-plugin",
+					"div2-en-dg",
+					"dcin-icl-change";
+	};
+
+	qcom,chgr-misc@1600 {
+		reg = <0x1600 0x100>;
+		interrupts =    <0x16 0x0 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x1 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x2 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x3 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x4 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x5 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x6 IRQ_TYPE_EDGE_BOTH>,
+				<0x16 0x7 IRQ_TYPE_EDGE_BOTH>;
+
+		interrupt-names =       "wdog-snarl",
+					"wdog-bark",
+					"aicl-fail",
+					"aicl-done",
+					"high-duty-cycle",
+					"input-current-limiting",
+					"temperature-change",
+					"switcher-power-ok";
+	};
+};
diff --git a/Documentation/devicetree/bindings/usb/msm-phy.txt b/Documentation/devicetree/bindings/usb/msm-phy.txt
index d20a7cb..8e5782a 100644
--- a/Documentation/devicetree/bindings/usb/msm-phy.txt
+++ b/Documentation/devicetree/bindings/usb/msm-phy.txt
@@ -106,8 +106,7 @@
 
 Optional properties:
  - reg-names: Additional registers corresponding with the following:
-   "tune2_efuse_addr": EFUSE based register address to read TUNE2 parameter.
-   via the QSCRATCH interface.
+   "efuse_addr": EFUSE address to read and update analog tune parameter.
    "emu_phy_base" : phy base address used for programming emulation target phy.
    "ref_clk_addr" : ref_clk bcr address used for on/off ref_clk before reset.
  - clocks: a list of phandles to the PHY clocks. Use as per
@@ -120,8 +119,8 @@
  - qcom,emu-init-seq : emulation initialization sequence with value,reg pair.
  - qcom,phy-pll-reset-seq : emulation PLL reset sequence with value,reg pair.
  - qcom,emu-dcm-reset-seq : emulation DCM reset sequence with value,reg pair.
- - qcom,tune2-efuse-bit-pos: TUNE2 parameter related start bit position with EFUSE register
- - qcom,tune2-efuse-num-bits: Number of bits based value to use for TUNE2 high nibble
+ - qcom,efuse-bit-pos: start bit position within EFUSE register
+ - qcom,efuse-num-bits: Number of bits to read from EFUSE register
  - qcom,emulation: Indicates that we are running on emulation platform.
  - qcom,hold-reset: Indicates that hold QUSB PHY into reset state.
  - qcom,phy-clk-scheme: Should be one of "cml" or "cmos" if ref_clk_addr is provided.
@@ -136,8 +135,8 @@
 		vdda18-supply = <&pm8994_l6>;
 		vdda33-supply = <&pm8994_l24>;
 		qcom,vdd-voltage-level = <1 5 7>;
-		qcom,tune2-efuse-bit-pos = <21>;
-		qcom,tune2-efuse-num-bits = <3>;
+		qcom,efuse-bit-pos = <21>;
+		qcom,efuse-num-bits = <3>;
 
 		clocks = <&clock_rpm clk_ln_bb_clk>,
 			 <&clock_gcc clk_gcc_rx2_usb1_clkref_clk>,
diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt
index e206560..0f0fc7d 100644
--- a/Documentation/networking/ip-sysctl.txt
+++ b/Documentation/networking/ip-sysctl.txt
@@ -1462,11 +1462,20 @@
 	Functional default: enabled if accept_ra is enabled.
 			    disabled if accept_ra is disabled.
 
+accept_ra_rt_info_min_plen - INTEGER
+	Minimum prefix length of Route Information in RA.
+
+	Route Information w/ prefix smaller than this variable shall
+	be ignored.
+
+	Functional default: 0 if accept_ra_rtr_pref is enabled.
+			    -1 if accept_ra_rtr_pref is disabled.
+
 accept_ra_rt_info_max_plen - INTEGER
 	Maximum prefix length of Route Information in RA.
 
-	Route Information w/ prefix larger than or equal to this
-	variable shall be ignored.
+	Route Information w/ prefix larger than this variable shall
+	be ignored.
 
 	Functional default: 0 if accept_ra_rtr_pref is enabled.
 			    -1 if accept_ra_rtr_pref is disabled.
diff --git a/Makefile b/Makefile
index 71a7187..d8f21b3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 VERSION = 4
 PATCHLEVEL = 9
-SUBLEVEL = 17
+SUBLEVEL = 18
 EXTRAVERSION =
 NAME = Roaring Lionus
 
diff --git a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
index b157e04..c5340a8 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-sde.dtsi
@@ -127,6 +127,8 @@
 		qcom,sde-vbif-size = <0x1040>;
 		qcom,sde-vbif-id = <0>;
 
+		qcom,sde-inline-rotator = <&mdss_rotator 0>;
+
 		qcom,sde-sspp-vig-blocks {
 			qcom,sde-vig-csc-off = <0x1a00>;
 			qcom,sde-vig-qseed-off = <0xa00>;
@@ -204,8 +206,12 @@
 		reg-names = "mdp_phys",
 			"rot_vbif_phys";
 
+		#list-cells = <1>;
+
 		qcom,mdss-rot-mode = <1>;
 		qcom,mdss-highest-bank-bit = <0x2>;
+		qcom,sde-ubwc-malsize = <1>;
+		qcom,sde-ubwc-swizzle = <1>;
 
 		/* Bus Scale Settings */
 		qcom,msm-bus,name = "mdss_rotator";
@@ -238,6 +244,11 @@
 		qcom,mdss-default-ot-rd-limit = <32>;
 		qcom,mdss-default-ot-wr-limit = <32>;
 
+		qcom,mdss-sbuf-headroom = <20>;
+
+		cache-slice-names = "rotator";
+		cache-slices = <&llcc 4>;
+
 		smmu_rot_unsec: qcom,smmu_rot_unsec_cb {
 			compatible = "qcom,smmu_sde_rot_unsec";
 			iommus = <&apps_smmu 0x1090>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi b/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
index 5399e99..7f090ad 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-usb.dtsi
@@ -19,6 +19,7 @@
 		reg = <0x0a600000 0xf8c00>,
 		      <0x088ee000 0x400>;
 		reg-names = "core_base", "ahb2phy_base";
+		iommus = <&apps_smmu 0x740>;
 		#address-cells = <1>;
 		#size-cells = <1>;
 		ranges;
@@ -56,6 +57,7 @@
 			interrupts = <0 133 0>;
 			usb-phy = <&qusb_phy0>, <&usb_nop_phy>;
 			tx-fifo-resize;
+			linux,sysdev_is_parent;
 			snps,disable-clk-gating;
 			snps,has-lpm-erratum;
 			snps,hird-threshold = /bits/ 8 <0x10>;
@@ -109,6 +111,7 @@
 		reg = <0x0a800000 0xf8c00>,
 		      <0x088ee000 0x400>;
 		reg-names = "core_base", "ahb2phy_base";
+		iommus = <&apps_smmu 0x760>;
 		#address-cells = <1>;
 		#size-cells = <1>;
 		ranges;
@@ -144,6 +147,7 @@
 			interrupts = <0 138 0>;
 			usb-phy = <&qusb_phy1>, <&usb_qmp_phy>;
 			tx-fifo-resize;
+			linux,sysdev_is_parent;
 			snps,disable-clk-gating;
 			snps,has-lpm-erratum;
 			snps,hird-threshold = /bits/ 8 <0x10>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index b2382d1..5c33436 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -500,6 +500,58 @@
 		cell-index = <0>;
 	};
 
+	msm_cpufreq: qcom,msm-cpufreq {
+		compatible = "qcom,msm-cpufreq";
+		clock-names = "cpu0_clk", "cpu4_clk";
+		clocks = <&clock_cpucc CPU0_PWRCL_CLK>,
+			 <&clock_cpucc CPU4_PERFCL_CLK>;
+
+		qcom,governor-per-policy;
+
+		qcom,cpufreq-table-0 =
+			<  300000 >,
+			<  422400 >,
+			<  499200 >,
+			<  576000 >,
+			<  652800 >,
+			<  748800 >,
+			<  825600 >,
+			<  902400 >,
+			<  979200 >,
+			< 1056000 >,
+			< 1132800 >,
+			< 1209600 >,
+			< 1286400 >,
+			< 1363200 >,
+			< 1440000 >,
+			< 1516800 >,
+			< 1593600 >;
+
+		qcom,cpufreq-table-4 =
+			<  300000 >,
+			<  422400 >,
+			<  499200 >,
+			<  576000 >,
+			<  652800 >,
+			<  729600 >,
+			<  806400 >,
+			<  883200 >,
+			<  960000 >,
+			< 1036800 >,
+			< 1113600 >,
+			< 1190400 >,
+			< 1267200 >,
+			< 1344000 >,
+			< 1420800 >,
+			< 1497600 >,
+			< 1574400 >,
+			< 1651200 >,
+			< 1728000 >,
+			< 1804800 >,
+			< 1881600 >,
+			< 1958400 >;
+	};
+
 	clock_gcc: qcom,gcc@100000 {
 		compatible = "qcom,gcc-sdm845";
 		reg = <0x100000 0x1f0000>;
diff --git a/arch/arm64/configs/sdm845-perf_defconfig b/arch/arm64/configs/sdm845-perf_defconfig
index 1be0423..2e72456 100644
--- a/arch/arm64/configs/sdm845-perf_defconfig
+++ b/arch/arm64/configs/sdm845-perf_defconfig
@@ -17,6 +17,7 @@
 CONFIG_RT_GROUP_SCHED=y
 CONFIG_SCHED_HMP=y
 CONFIG_SCHED_HMP_CSTATE_AWARE=y
+CONFIG_SCHED_CORE_CTL=y
 CONFIG_NAMESPACES=y
 # CONFIG_UTS_NS is not set
 # CONFIG_PID_NS is not set
@@ -74,7 +75,10 @@
 CONFIG_CPU_FREQ_GOV_POWERSAVE=y
 CONFIG_CPU_FREQ_GOV_USERSPACE=y
 CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_INTERACTIVE=y
 CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_BOOST=y
+CONFIG_CPU_FREQ_MSM=y
 CONFIG_NET=y
 CONFIG_PACKET=y
 CONFIG_UNIX=y
@@ -282,10 +286,15 @@
 CONFIG_POWER_RESET_QCOM=y
 CONFIG_POWER_RESET_XGENE=y
 CONFIG_POWER_RESET_SYSCON=y
+CONFIG_QPNP_FG_GEN3=y
+CONFIG_QPNP_SMB2=y
+CONFIG_SMB138X_CHARGER=y
+CONFIG_QPNP_QNOVO=y
 CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y
 CONFIG_THERMAL=y
 CONFIG_THERMAL_QPNP=y
 CONFIG_THERMAL_QPNP_ADC_TM=y
+CONFIG_MFD_I2C_PMIC=y
 CONFIG_MFD_SPMI_PMIC=y
 CONFIG_WCD934X_CODEC=y
 CONFIG_REGULATOR_FIXED_VOLTAGE=y
@@ -467,6 +476,7 @@
 CONFIG_TIMER_STATS=y
 # CONFIG_DEBUG_PREEMPT is not set
 CONFIG_IPC_LOGGING=y
+CONFIG_CPU_FREQ_SWITCH_PROFILER=y
 CONFIG_DEBUG_ALIGN_RODATA=y
 CONFIG_CORESIGHT=y
 CONFIG_CORESIGHT_LINK_AND_SINK_TMC=y
diff --git a/arch/arm64/configs/sdm845_defconfig b/arch/arm64/configs/sdm845_defconfig
index b825da9..688e130 100644
--- a/arch/arm64/configs/sdm845_defconfig
+++ b/arch/arm64/configs/sdm845_defconfig
@@ -23,6 +23,7 @@
 CONFIG_RT_GROUP_SCHED=y
 CONFIG_SCHED_HMP=y
 CONFIG_SCHED_HMP_CSTATE_AWARE=y
+CONFIG_SCHED_CORE_CTL=y
 CONFIG_NAMESPACES=y
 # CONFIG_UTS_NS is not set
 # CONFIG_PID_NS is not set
@@ -80,7 +81,10 @@
 CONFIG_CPU_FREQ_GOV_POWERSAVE=y
 CONFIG_CPU_FREQ_GOV_USERSPACE=y
 CONFIG_CPU_FREQ_GOV_ONDEMAND=y
+CONFIG_CPU_FREQ_GOV_INTERACTIVE=y
 CONFIG_CPU_FREQ_GOV_CONSERVATIVE=y
+CONFIG_CPU_BOOST=y
+CONFIG_CPU_FREQ_MSM=y
 CONFIG_NET=y
 CONFIG_PACKET=y
 CONFIG_UNIX=y
@@ -294,10 +298,15 @@
 CONFIG_POWER_RESET_QCOM=y
 CONFIG_POWER_RESET_XGENE=y
 CONFIG_POWER_RESET_SYSCON=y
+CONFIG_QPNP_FG_GEN3=y
+CONFIG_QPNP_SMB2=y
+CONFIG_SMB138X_CHARGER=y
+CONFIG_QPNP_QNOVO=y
 CONFIG_SENSORS_QPNP_ADC_VOLTAGE=y
 CONFIG_THERMAL=y
 CONFIG_THERMAL_QPNP=y
 CONFIG_THERMAL_QPNP_ADC_TM=y
+CONFIG_MFD_I2C_PMIC=y
 CONFIG_MFD_SPMI_PMIC=y
 CONFIG_WCD934X_CODEC=y
 CONFIG_REGULATOR_FIXED_VOLTAGE=y
@@ -531,6 +540,7 @@
 CONFIG_FUNCTION_TRACER=y
 CONFIG_TRACER_SNAPSHOT=y
 CONFIG_BLK_DEV_IO_TRACE=y
+CONFIG_CPU_FREQ_SWITCH_PROFILER=y
 CONFIG_LKDTM=y
 CONFIG_MEMTEST=y
 CONFIG_ARM64_PTDUMP=y
diff --git a/arch/parisc/include/asm/cacheflush.h b/arch/parisc/include/asm/cacheflush.h
index 7bd69bd..1d8c24d 100644
--- a/arch/parisc/include/asm/cacheflush.h
+++ b/arch/parisc/include/asm/cacheflush.h
@@ -45,28 +45,9 @@
 
 #define flush_kernel_dcache_range(start,size) \
 	flush_kernel_dcache_range_asm((start), (start)+(size));
-/* vmap range flushes and invalidates.  Architecturally, we don't need
- * the invalidate, because the CPU should refuse to speculate once an
- * area has been flushed, so invalidate is left empty */
-static inline void flush_kernel_vmap_range(void *vaddr, int size)
-{
-	unsigned long start = (unsigned long)vaddr;
 
-	flush_kernel_dcache_range_asm(start, start + size);
-}
-static inline void invalidate_kernel_vmap_range(void *vaddr, int size)
-{
-	unsigned long start = (unsigned long)vaddr;
-	void *cursor = vaddr;
-
-	for ( ; cursor < vaddr + size; cursor += PAGE_SIZE) {
-		struct page *page = vmalloc_to_page(cursor);
-
-		if (test_and_clear_bit(PG_dcache_dirty, &page->flags))
-			flush_kernel_dcache_page(page);
-	}
-	flush_kernel_dcache_range_asm(start, start + size);
-}
+void flush_kernel_vmap_range(void *vaddr, int size);
+void invalidate_kernel_vmap_range(void *vaddr, int size);
 
 #define flush_cache_vmap(start, end)		flush_cache_all()
 #define flush_cache_vunmap(start, end)		flush_cache_all()
diff --git a/arch/parisc/kernel/cache.c b/arch/parisc/kernel/cache.c
index 977f0a4f..53ec75f 100644
--- a/arch/parisc/kernel/cache.c
+++ b/arch/parisc/kernel/cache.c
@@ -633,3 +633,25 @@
 		__flush_cache_page(vma, vmaddr, PFN_PHYS(pfn));
 	}
 }
+
+void flush_kernel_vmap_range(void *vaddr, int size)
+{
+	unsigned long start = (unsigned long)vaddr;
+
+	if ((unsigned long)size > parisc_cache_flush_threshold)
+		flush_data_cache();
+	else
+		flush_kernel_dcache_range_asm(start, start + size);
+}
+EXPORT_SYMBOL(flush_kernel_vmap_range);
+
+void invalidate_kernel_vmap_range(void *vaddr, int size)
+{
+	unsigned long start = (unsigned long)vaddr;
+
+	if ((unsigned long)size > parisc_cache_flush_threshold)
+		flush_data_cache();
+	else
+		flush_kernel_dcache_range_asm(start, start + size);
+}
+EXPORT_SYMBOL(invalidate_kernel_vmap_range);
diff --git a/arch/parisc/kernel/process.c b/arch/parisc/kernel/process.c
index 4063943..e81afc37 100644
--- a/arch/parisc/kernel/process.c
+++ b/arch/parisc/kernel/process.c
@@ -139,6 +139,8 @@
 
 	printk(KERN_EMERG "System shut down completed.\n"
 	       "Please power this system off now.");
+
+	for (;;);
 }
 
 void (*pm_power_off)(void) = machine_power_off;
diff --git a/arch/powerpc/boot/zImage.lds.S b/arch/powerpc/boot/zImage.lds.S
index 861e721..f080abf 100644
--- a/arch/powerpc/boot/zImage.lds.S
+++ b/arch/powerpc/boot/zImage.lds.S
@@ -68,6 +68,7 @@
   }
 
 #ifdef CONFIG_PPC64_BOOT_WRAPPER
+  . = ALIGN(256);
   .got :
   {
     __toc_start = .;
diff --git a/drivers/clk/qcom/Kconfig b/drivers/clk/qcom/Kconfig
index d327826..31ea544 100644
--- a/drivers/clk/qcom/Kconfig
+++ b/drivers/clk/qcom/Kconfig
@@ -204,3 +204,15 @@
 	  SoCs. It accepts requests from other hardware subsystems via RSC.
 	  Say Y to support the clocks managed by RPMh VRM/ARC on platforms
 	  such as sdm845.
+
+config CLOCK_CPU_OSM
+	tristate "OSM CPU Clock Controller"
+	depends on COMMON_CLK_QCOM
+	help
+	 Support for the OSM clock controller.
+	 Operating State Manager (OSM) is a hardware engine used by some
+	 Qualcomm Technologies, Inc. (QTI) SoCs to manage frequency and
+	 voltage scaling in hardware. OSM is capable of controlling
+	 frequency and voltage requests for multiple clusters via the
+	 existence of multiple OSM domains.
+	 Say Y if you want to support OSM clocks.
diff --git a/drivers/clk/qcom/Makefile b/drivers/clk/qcom/Makefile
index ebcf4fc..d52a751 100644
--- a/drivers/clk/qcom/Makefile
+++ b/drivers/clk/qcom/Makefile
@@ -16,23 +16,24 @@
 # Keep alphabetically sorted by config
 obj-$(CONFIG_APQ_GCC_8084) += gcc-apq8084.o
 obj-$(CONFIG_APQ_MMCC_8084) += mmcc-apq8084.o
+obj-$(CONFIG_CLOCK_CPU_OSM) += clk-cpu-osm.o
+obj-$(CONFIG_CLOCK_QPNP_DIV) += clk-qpnp-div.o
 obj-$(CONFIG_IPQ_GCC_4019) += gcc-ipq4019.o
 obj-$(CONFIG_IPQ_GCC_806X) += gcc-ipq806x.o
 obj-$(CONFIG_IPQ_LCC_806X) += lcc-ipq806x.o
 obj-$(CONFIG_MDM_GCC_9615) += gcc-mdm9615.o
 obj-$(CONFIG_MDM_LCC_9615) += lcc-mdm9615.o
 obj-$(CONFIG_MSM_CAMCC_SDM845) += camcc-sdm845.o
+obj-$(CONFIG_MSM_CLK_RPMH) += clk-rpmh.o
+obj-$(CONFIG_MSM_DISPCC_SDM845) += dispcc-sdm845.o
 obj-$(CONFIG_MSM_GCC_8660) += gcc-msm8660.o
 obj-$(CONFIG_MSM_GCC_8916) += gcc-msm8916.o
 obj-$(CONFIG_MSM_GCC_8960) += gcc-msm8960.o
 obj-$(CONFIG_MSM_GCC_8974) += gcc-msm8974.o
 obj-$(CONFIG_MSM_GCC_8996) += gcc-msm8996.o
 obj-$(CONFIG_MSM_GCC_SDM845) += gcc-sdm845.o
-obj-$(CONFIG_MSM_DISPCC_SDM845) += dispcc-sdm845.o
-obj-$(CONFIG_MSM_VIDEOCC_SDM845) += videocc-sdm845.o
 obj-$(CONFIG_MSM_LCC_8960) += lcc-msm8960.o
 obj-$(CONFIG_MSM_MMCC_8960) += mmcc-msm8960.o
 obj-$(CONFIG_MSM_MMCC_8974) += mmcc-msm8974.o
 obj-$(CONFIG_MSM_MMCC_8996) += mmcc-msm8996.o
-obj-$(CONFIG_CLOCK_QPNP_DIV) += clk-qpnp-div.o
-obj-$(CONFIG_MSM_CLK_RPMH) += clk-rpmh.o
+obj-$(CONFIG_MSM_VIDEOCC_SDM845) += videocc-sdm845.o
diff --git a/drivers/clk/qcom/clk-cpu-osm.c b/drivers/clk/qcom/clk-cpu-osm.c
new file mode 100644
index 0000000..d5e2be6
--- /dev/null
+++ b/drivers/clk/qcom/clk-cpu-osm.c
@@ -0,0 +1,2619 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/debugfs.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/errno.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/cpu.h>
+#include <linux/platform_device.h>
+#include <linux/of_platform.h>
+#include <linux/pm_opp.h>
+#include <linux/pm_qos.h>
+#include <linux/interrupt.h>
+#include <linux/regulator/driver.h>
+#include <linux/regmap.h>
+#include <linux/uaccess.h>
+#include <linux/sched.h>
+#include <soc/qcom/scm.h>
+#include <dt-bindings/clock/qcom,cpucc-sdm845.h>
+
+#include "common.h"
+#include "clk-regmap.h"
+#include "clk-rcg.h"
+#include "clk-voter.h"
+
+#define OSM_TABLE_SIZE			40
+#define SINGLE_CORE			1
+#define MAX_CLUSTER_CNT			3
+#define MAX_MEM_ACC_VAL_PER_LEVEL	3
+#define MAX_CORE_COUNT			4
+#define CORE_COUNT_VAL(val)		((val & GENMASK(18, 16)) >> 16)
+
+#define OSM_CYCLE_COUNTER_CTRL_REG		0x760
+#define OSM_CYCLE_COUNTER_USE_XO_EDGE_EN	BIT(8)
+
+#define OSM_REG_SIZE			32
+
+#define L3_EFUSE_SHIFT			0
+#define L3_EFUSE_MASK			0
+#define PWRCL_EFUSE_SHIFT		0
+#define PWRCL_EFUSE_MASK		0
+#define PERFCL_EFUSE_SHIFT		29
+#define PERFCL_EFUSE_MASK		0x7
+
+#define ENABLE_REG			0x0
+#define FREQ_REG			0x110
+#define VOLT_REG			0x114
+#define OVERRIDE_REG			0x118
+#define SPM_CC_INC_HYSTERESIS		0x1c
+#define SPM_CC_DEC_HYSTERESIS		0x20
+#define SPM_CORE_INACTIVE_MAPPING	0x28
+#define CC_ZERO_BEHAV_CTRL		0xc
+#define ENABLE_OVERRIDE			BIT(0)
+#define SPM_CC_DCVS_DISABLE		0x24
+#define LLM_FREQ_VOTE_INC_HYSTERESIS	0x30
+#define LLM_FREQ_VOTE_DEC_HYSTERESIS	0x34
+#define LLM_INTF_DCVS_DISABLE		0x40
+#define LLM_VOLTAGE_VOTE_INC_HYSTERESIS	0x38
+#define LLM_VOLTAGE_VOTE_DEC_HYSTERESIS	0x3c
+#define VMIN_REDUCTION_ENABLE_REG	0x48
+#define VMIN_REDUCTION_TIMER_REG	0x4c
+#define PDN_FSM_CTRL_REG		0x54
+#define DELTA_DEX_VAL			BVAL(31, 23, 0xa)
+#define IGNORE_PLL_LOCK			BIT(15)
+#define CC_BOOST_FSM_EN			BIT(0)
+#define CC_BOOST_FSM_TIMERS_REG0	0x58
+#define CC_BOOST_FSM_TIMERS_REG1	0x5c
+#define CC_BOOST_FSM_TIMERS_REG2	0x60
+#define DCVS_BOOST_FSM_EN_MASK		BIT(2)
+#define DCVS_BOOST_FSM_TIMERS_REG0	0x64
+#define DCVS_BOOST_FSM_TIMERS_REG1	0x68
+#define DCVS_BOOST_FSM_TIMERS_REG2	0x6c
+#define PS_BOOST_FSM_EN_MASK		BIT(1)
+#define PS_BOOST_FSM_TIMERS_REG0	0x74
+#define PS_BOOST_FSM_TIMERS_REG1	0x78
+#define PS_BOOST_FSM_TIMERS_REG2	0x7c
+#define BOOST_PROG_SYNC_DELAY_REG	0x80
+#define DCVS_DROOP_FSM_EN_MASK		BIT(5)
+#define DROOP_PROG_SYNC_DELAY_REG	0x9c
+#define DROOP_RELEASE_TIMER_CTRL	0x88
+#define DROOP_CTRL_REG			0x84
+#define DCVS_DROOP_TIMER_CTRL		0x98
+#define PLL_SW_OVERRIDE_ENABLE		0xa0
+#define PLL_SW_OVERRIDE_DROOP_EN	BIT(0)
+#define SPM_CORE_COUNT_CTRL		0x2c
+#define CORE_DCVS_CTRL			0xbc
+#define OVERRIDE_CLUSTER_IDLE_ACK	0x800
+#define REQ_GEN_FSM_STATUS		0x70c
+
+#define PLL_MIN_LVAL			0x21
+#define PLL_MIN_FREQ_REG		0x94
+#define PLL_POST_DIV1			0x1F
+#define PLL_POST_DIV2			0x11F
+#define PLL_MODE			0x0
+#define PLL_L_VAL			0x4
+#define PLL_USER_CTRL			0xc
+#define PLL_CONFIG_CTL_LO		0x10
+#define PLL_CONFIG_CTL_HI		0x14
+#define MIN_VCO_VAL			0x2b
+
+#define MAX_VC				63
+#define MAX_MEM_ACC_LEVELS		3
+#define MAX_MEM_ACC_VAL_PER_LEVEL	3
+#define MAX_MEM_ACC_VALUES		(MAX_MEM_ACC_LEVELS * \
+					MAX_MEM_ACC_VAL_PER_LEVEL)
+#define MEM_ACC_ADDRS			3
+
+#define ISENSE_ON_DATA			0xf
+#define ISENSE_OFF_DATA			0x0
+#define CONSTANT_32			0x20
+
+#define APM_MX_MODE			0x0
+#define APM_APC_MODE			0x2
+#define APM_READ_DATA_MASK		0xc
+#define APM_MX_MODE_VAL			0x4
+#define APM_APC_READ_VAL		0x8
+#define APM_MX_READ_VAL			0x4
+#define APM_CROSSOVER_VC		0xb0
+
+#define MEM_ACC_SEQ_CONST(n)		(n)
+#define MEM_ACC_APM_READ_MASK		0xff
+#define MEMACC_CROSSOVER_VC		0xb8
+
+#define PLL_WAIT_LOCK_TIME_US		10
+#define PLL_WAIT_LOCK_TIME_NS		(PLL_WAIT_LOCK_TIME_US * 1000)
+#define SAFE_FREQ_WAIT_NS		5000
+#define DEXT_DECREMENT_WAIT_NS		1000
+
+#define DATA_MEM(n)			(0x400 + (n) * 4)
+
+#define DCVS_PERF_STATE_DESIRED_REG_0	0x780
+#define DCVS_PERF_STATE_DESIRED_REG(n) (DCVS_PERF_STATE_DESIRED_REG_0 + \
+					(4 * n))
+#define OSM_CYCLE_COUNTER_STATUS_REG_0	0x7d0
+#define OSM_CYCLE_COUNTER_STATUS_REG(n)	(OSM_CYCLE_COUNTER_STATUS_REG_0 + \
+					(4 * n))
+
+static const struct regmap_config osm_qcom_regmap_config = {
+	.reg_bits       = 32,
+	.reg_stride     = 4,
+	.val_bits       = 32,
+	.fast_io	= true,
+};
+
+enum clk_osm_bases {
+	OSM_BASE,
+	PLL_BASE,
+	EFUSE_BASE,
+	SEQ_BASE,
+	NUM_BASES,
+};
+
+enum clk_osm_lut_data {
+	FREQ,
+	FREQ_DATA,
+	PLL_OVERRIDES,
+	MEM_ACC_LEVEL,
+	VIRTUAL_CORNER,
+	NUM_FIELDS,
+};
+
+struct osm_entry {
+	u16 virtual_corner;
+	u16 open_loop_volt;
+	u32 freq_data;
+	u32 override_data;
+	u32 mem_acc_level;
+	long frequency;
+};
+
+struct clk_osm {
+	struct clk_hw hw;
+	struct osm_entry osm_table[OSM_TABLE_SIZE];
+	struct dentry *debugfs;
+	struct regulator *vdd_reg;
+	struct platform_device *vdd_dev;
+	void *vbases[NUM_BASES];
+	unsigned long pbases[NUM_BASES];
+	spinlock_t lock;
+
+	u32 cpu_reg_mask;
+	u32 num_entries;
+	u32 cluster_num;
+	u32 core_num;
+	u32 apm_crossover_vc;
+	u32 apm_threshold_vc;
+	u32 mem_acc_crossover_vc;
+	u32 mem_acc_threshold_vc;
+	u32 min_cpr_vc;
+	u32 cycle_counter_reads;
+	u32 cycle_counter_delay;
+	u32 cycle_counter_factor;
+	u64 total_cycle_counter;
+	u32 prev_cycle_counter;
+	u32 l_val_base;
+	u32 apcs_pll_user_ctl;
+	u32 apcs_pll_min_freq;
+	u32 cfg_gfmux_addr;
+	u32 apcs_cbc_addr;
+	u32 speedbin;
+	u32 mem_acc_crossover_vc_addr;
+	u32 mem_acc_addr[MEM_ACC_ADDRS];
+	u32 ramp_ctl_addr;
+	u32 apm_mode_ctl;
+	u32 apm_status_ctl;
+	u32 osm_clk_rate;
+	u32 xo_clk_rate;
+	bool secure_init;
+	bool red_fsm_en;
+	bool boost_fsm_en;
+	bool safe_fsm_en;
+	bool ps_fsm_en;
+	bool droop_fsm_en;
+
+	struct notifier_block panic_notifier;
+	u32 trace_periodic_timer;
+	bool trace_en;
+	bool wdog_trace_en;
+};
+
+static struct regulator *vdd_l3;
+static struct regulator *vdd_pwrcl;
+static struct regulator *vdd_perfcl;
+
+static inline struct clk_osm *to_clk_osm(struct clk_hw *_hw)
+{
+	return container_of(_hw, struct clk_osm, hw);
+}
+
+static inline void clk_osm_masked_write_reg(struct clk_osm *c, u32 val,
+					    u32 offset, u32 mask)
+{
+	u32 val2, orig_val;
+
+	val2 = orig_val = readl_relaxed((char *)c->vbases[OSM_BASE] + offset);
+	val2 &= ~mask;
+	val2 |= val & mask;
+
+	if (val2 != orig_val)
+		writel_relaxed(val2, (char *)c->vbases[OSM_BASE] + offset);
+}
+
+static inline void clk_osm_write_seq_reg(struct clk_osm *c, u32 val, u32 offset)
+{
+	writel_relaxed(val, (char *)c->vbases[SEQ_BASE] + offset);
+}
+
+static inline void clk_osm_write_reg(struct clk_osm *c, u32 val, u32 offset)
+{
+	writel_relaxed(val, (char *)c->vbases[OSM_BASE] + offset);
+}
+
+static inline int clk_osm_read_reg(struct clk_osm *c, u32 offset)
+{
+	return readl_relaxed((char *)c->vbases[OSM_BASE] + offset);
+}
+
+static inline int clk_osm_read_reg_no_log(struct clk_osm *c, u32 offset)
+{
+	return readl_relaxed_no_log((char *)c->vbases[OSM_BASE] + offset);
+}
+
+static inline int clk_osm_mb(struct clk_osm *c, int base)
+{
+	return readl_relaxed_no_log((char *)c->vbases[base] + ENABLE_REG);
+}
+
+static long clk_osm_list_rate(struct clk_hw *hw, unsigned int n,
+					unsigned long rate_max)
+{
+	if (n >= hw->init->num_rate_max)
+		return -ENXIO;
+	return hw->init->rate_max[n];
+}
+
+static inline bool is_better_rate(unsigned long req, unsigned long best,
+			unsigned long new)
+{
+	if (IS_ERR_VALUE(new))
+		return false;
+
+	return (req <= new && new < best) || (best < req && best < new);
+}
+
+static long clk_osm_round_rate(struct clk_hw *hw, unsigned long rate,
+				unsigned long *parent_rate)
+{
+	int i;
+	unsigned long rrate = 0;
+
+	/*
+	 * If the rate passed in is 0, return the first frequency in the
+	 * FMAX table.
+	 */
+	if (!rate)
+		return hw->init->rate_max[0];
+
+	for (i = 0; i < hw->init->num_rate_max; i++) {
+		if (is_better_rate(rate, rrate, hw->init->rate_max[i])) {
+			rrate = hw->init->rate_max[i];
+			if (rate == rrate)
+				break;
+		}
+	}
+
+	pr_debug("%s: rate %lu, rrate %ld, Rate max %ld\n", __func__, rate,
+						rrate, hw->init->rate_max[i]);
+
+	return rrate;
+}
+
+static int clk_osm_search_table(struct osm_entry *table, int entries, long rate)
+{
+	int quad_core_index, single_core_index = 0;
+	int core_count;
+
+	for (quad_core_index = 0; quad_core_index < entries;
+						quad_core_index++) {
+		core_count = CORE_COUNT_VAL(table[quad_core_index].freq_data);
+		if (rate == table[quad_core_index].frequency &&
+					core_count == SINGLE_CORE) {
+			single_core_index = quad_core_index;
+			continue;
+		}
+		if (rate == table[quad_core_index].frequency &&
+					core_count == MAX_CORE_COUNT)
+			return quad_core_index;
+	}
+	if (single_core_index)
+		return single_core_index;
+
+	return -EINVAL;
+}
+
+static int clk_osm_enable(struct clk_hw *hw)
+{
+	struct clk_osm *cpuclk = to_clk_osm(hw);
+
+	clk_osm_write_reg(cpuclk, 1, ENABLE_REG);
+
+	/* Make sure the write goes through before proceeding */
+	clk_osm_mb(cpuclk, OSM_BASE);
+
+	/* Wait for 5us for OSM hardware to enable */
+	udelay(5);
+
+	pr_debug("OSM clk enabled for cluster=%d\n", cpuclk->cluster_num);
+
+	return 0;
+}
+
+const struct clk_ops clk_ops_cpu_osm = {
+	.enable = clk_osm_enable,
+	.round_rate = clk_osm_round_rate,
+	.list_rate = clk_osm_list_rate,
+};
+
+static struct clk_ops clk_ops_core;
+
+static int cpu_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct clk_osm *cpuclk = to_clk_osm(hw);
+	struct clk_hw *p_hw = clk_hw_get_parent(hw);
+	struct clk_osm *parent = to_clk_osm(p_hw);
+	int index = 0;
+	unsigned long r_rate;
+
+	if (!cpuclk || !parent)
+		return -EINVAL;
+
+	r_rate = clk_osm_round_rate(p_hw, rate, NULL);
+
+	if (rate != r_rate) {
+		pr_err("invalid requested rate=%ld\n", rate);
+		return -EINVAL;
+	}
+
+	/* Convert rate to table index */
+	index = clk_osm_search_table(parent->osm_table,
+				     parent->num_entries, r_rate);
+	if (index < 0) {
+		pr_err("cannot set %s to %lu\n", clk_hw_get_name(hw), rate);
+		return -EINVAL;
+	}
+	pr_debug("rate: %lu --> index %d\n", rate, index);
+	/*
+	 * Choose index and send request to OSM hardware.
+	 * TODO: Program INACTIVE_OS_REQUEST if needed.
+	 */
+	clk_osm_write_reg(parent, index,
+			DCVS_PERF_STATE_DESIRED_REG(cpuclk->core_num));
+
+	/* Make sure the write goes through before proceeding */
+	clk_osm_mb(parent, OSM_BASE);
+
+	return 0;
+}
+
+static int l3_clk_set_rate(struct clk_hw *hw, unsigned long rate,
+				    unsigned long parent_rate)
+{
+	struct clk_osm *cpuclk = to_clk_osm(hw);
+	int index = 0;
+	unsigned long r_rate;
+
+	if (!cpuclk)
+		return -EINVAL;
+
+	r_rate = clk_osm_round_rate(hw, rate, NULL);
+
+	if (rate != r_rate) {
+		pr_err("invalid requested rate=%ld\n", rate);
+		return -EINVAL;
+	}
+
+	/* Convert rate to table index */
+	index = clk_osm_search_table(cpuclk->osm_table,
+				     cpuclk->num_entries, r_rate);
+	if (index < 0) {
+		pr_err("cannot set %s to %lu\n", clk_hw_get_name(hw), rate);
+		return -EINVAL;
+	}
+	pr_debug("rate: %lu --> index %d\n", rate, index);
+
+	clk_osm_write_reg(cpuclk, index, DCVS_PERF_STATE_DESIRED_REG_0);
+
+	/* Make sure the write goes through before proceeding */
+	clk_osm_mb(cpuclk, OSM_BASE);
+
+	return 0;
+}
+
+static unsigned long cpu_clk_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct clk_osm *cpuclk = to_clk_osm(hw);
+	struct clk_hw *p_hw = clk_hw_get_parent(hw);
+	struct clk_osm *parent = to_clk_osm(p_hw);
+	int index = 0;
+
+	if (!cpuclk || !parent)
+		return -EINVAL;
+
+	index = clk_osm_read_reg(parent,
+			DCVS_PERF_STATE_DESIRED_REG(cpuclk->core_num));
+
+	pr_debug("%s: Index %d, freq %ld\n", __func__, index,
+				parent->osm_table[index].frequency);
+
+	/* Convert index to frequency */
+	return parent->osm_table[index].frequency;
+}
+
+static unsigned long l3_clk_recalc_rate(struct clk_hw *hw,
+					unsigned long parent_rate)
+{
+	struct clk_osm *cpuclk = to_clk_osm(hw);
+	int index = 0;
+
+	if (!cpuclk)
+		return -EINVAL;
+
+	index = clk_osm_read_reg(cpuclk, DCVS_PERF_STATE_DESIRED_REG_0);
+
+	pr_debug("%s: Index %d, freq %ld\n", __func__, index,
+				cpuclk->osm_table[index].frequency);
+
+	/* Convert index to frequency */
+	return cpuclk->osm_table[index].frequency;
+}
+
+
+const struct clk_ops clk_ops_l3_osm = {
+	.enable = clk_osm_enable,
+	.round_rate = clk_osm_round_rate,
+	.list_rate = clk_osm_list_rate,
+	.recalc_rate = l3_clk_recalc_rate,
+	.set_rate = l3_clk_set_rate,
+};
+
+enum {
+	P_XO,
+};
+
+static const struct parent_map gcc_parent_map_1[] = {
+	{ P_XO, 0 },
+};
+
+static const char * const gcc_parent_names_1[] = {
+	"xo",
+};
+
+static struct clk_init_data osm_clks_init[] = {
+	[0] = {
+		.name = "l3_clk",
+		.parent_names = (const char *[]){ "bi_tcxo" },
+		.num_parents = 1,
+		.ops = &clk_ops_l3_osm,
+	},
+	[1] = {
+		.name = "pwrcl_clk",
+		.parent_names = (const char *[]){ "bi_tcxo" },
+		.num_parents = 1,
+		.ops = &clk_ops_cpu_osm,
+	},
+	[2] = {
+		.name = "perfcl_clk",
+		.parent_names = (const char *[]){ "bi_tcxo" },
+		.num_parents = 1,
+		.ops = &clk_ops_cpu_osm,
+	},
+};
+
+static struct clk_osm l3_clk = {
+	.cluster_num = 0,
+	.cpu_reg_mask = 0x0,
+	.hw.init = &osm_clks_init[0],
+};
+
+static DEFINE_CLK_VOTER(l3_cluster0_vote_clk, l3_clk, 0);
+static DEFINE_CLK_VOTER(l3_cluster1_vote_clk, l3_clk, 0);
+
+static struct clk_osm pwrcl_clk = {
+	.cluster_num = 1,
+	.cpu_reg_mask = 0x300,
+	.hw.init = &osm_clks_init[1],
+};
+
+static struct clk_osm cpu0_pwrcl_clk = {
+	.core_num = 0,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu0_pwrcl_clk",
+		.parent_names = (const char *[]){ "pwrcl_clk" },
+		.num_parents = 1,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu1_pwrcl_clk = {
+	.core_num = 1,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu1_pwrcl_clk",
+		.parent_names = (const char *[]){ "pwrcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu2_pwrcl_clk = {
+	.core_num = 2,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu2_pwrcl_clk",
+		.parent_names = (const char *[]){ "pwrcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu3_pwrcl_clk = {
+	.core_num = 3,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu3_pwrcl_clk",
+		.parent_names = (const char *[]){ "pwrcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm perfcl_clk = {
+	.cluster_num = 2,
+	.cpu_reg_mask = 0x700,
+	.hw.init = &osm_clks_init[2],
+};
+
+
+static struct clk_osm cpu4_perfcl_clk = {
+	.core_num = 0,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu4_perfcl_clk",
+		.parent_names = (const char *[]){ "perfcl_clk" },
+		.num_parents = 1,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu5_perfcl_clk = {
+	.core_num = 1,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu5_perfcl_clk",
+		.parent_names = (const char *[]){ "perfcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu6_perfcl_clk = {
+	.core_num = 2,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu6_perfcl_clk",
+		.parent_names = (const char *[]){ "perfcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+static struct clk_osm cpu7_perfcl_clk = {
+	.core_num = 3,
+	.total_cycle_counter = 0,
+	.prev_cycle_counter = 0,
+	.hw.init = &(struct clk_init_data){
+		.name = "cpu7_perfcl_clk",
+		.parent_names = (const char *[]){ "perfcl_clk" },
+		.num_parents = 1,
+		.flags = CLK_SET_RATE_PARENT,
+		.ops = &clk_ops_core,
+	},
+};
+
+/*
+ * Use the cpu* clocks only for writing to the PERF_STATE_DESIRED registers.
+ * Note that we are currently NOT programming the APSS_LMH_GFMUX_CFG &
+ * APSS_OSM_GFMUX_CFG registers.
+ */
+
+static struct clk_hw *osm_qcom_clk_hws[] = {
+	[L3_CLK] = &l3_clk.hw,
+	[L3_CLUSTER0_VOTE_CLK] = &l3_cluster0_vote_clk.hw,
+	[L3_CLUSTER1_VOTE_CLK] = &l3_cluster1_vote_clk.hw,
+	[PWRCL_CLK] = &pwrcl_clk.hw,
+	[CPU0_PWRCL_CLK] = &cpu0_pwrcl_clk.hw,
+	[CPU1_PWRCL_CLK] = &cpu1_pwrcl_clk.hw,
+	[CPU2_PWRCL_CLK] = &cpu2_pwrcl_clk.hw,
+	[CPU3_PWRCL_CLK] = &cpu3_pwrcl_clk.hw,
+	[PERFCL_CLK] = &perfcl_clk.hw,
+	[CPU4_PERFCL_CLK] = &cpu4_perfcl_clk.hw,
+	[CPU5_PERFCL_CLK] = &cpu5_perfcl_clk.hw,
+	[CPU6_PERFCL_CLK] = &cpu6_perfcl_clk.hw,
+	[CPU7_PERFCL_CLK] = &cpu7_perfcl_clk.hw,
+};
+
+static struct clk_osm *logical_cpu_to_clk(int cpu)
+{
+	struct device_node *cpu_node;
+	const u32 *cell;
+	u64 hwid;
+	static struct clk_osm *cpu_clk_map[NR_CPUS];
+
+	if (cpu_clk_map[cpu])
+		return cpu_clk_map[cpu];
+
+	cpu_node = of_get_cpu_node(cpu, NULL);
+	if (!cpu_node)
+		goto fail;
+
+	cell = of_get_property(cpu_node, "reg", NULL);
+	if (!cell) {
+		pr_err("%s: missing reg property\n", cpu_node->full_name);
+		goto fail;
+	}
+
+	hwid = of_read_number(cell, of_n_addr_cells(cpu_node));
+	if ((hwid | pwrcl_clk.cpu_reg_mask) == pwrcl_clk.cpu_reg_mask) {
+		switch (cpu) {
+		case 0:
+			cpu_clk_map[cpu] = &cpu0_pwrcl_clk;
+			break;
+		case 1:
+			cpu_clk_map[cpu] = &cpu1_pwrcl_clk;
+			break;
+		case 2:
+			cpu_clk_map[cpu] = &cpu2_pwrcl_clk;
+			break;
+		case 3:
+			cpu_clk_map[cpu] = &cpu3_pwrcl_clk;
+			break;
+		default:
+			pr_err("unsupported CPU number for power cluster\n");
+			return NULL;
+		}
+		return cpu_clk_map[cpu];
+	}
+
+	if ((hwid | perfcl_clk.cpu_reg_mask) == perfcl_clk.cpu_reg_mask) {
+		switch (cpu) {
+		case 4:
+			cpu_clk_map[cpu] = &cpu4_perfcl_clk;
+			break;
+		case 5:
+			cpu_clk_map[cpu] = &cpu5_perfcl_clk;
+			break;
+		case 6:
+			cpu_clk_map[cpu] = &cpu6_perfcl_clk;
+			break;
+		case 7:
+			cpu_clk_map[cpu] = &cpu7_perfcl_clk;
+			break;
+		default:
+			pr_err("unsupported CPU number for perf cluster\n");
+			return NULL;
+		}
+		return cpu_clk_map[cpu];
+	}
+
+fail:
+	return NULL;
+}
+
+static inline int clk_osm_count_ns(struct clk_osm *c, u64 nsec)
+{
+	u64 temp;
+
+	temp = (u64)c->osm_clk_rate * nsec;
+	do_div(temp, 1000000000);
+
+	return temp;
+}
+
+static void clk_osm_program_mem_acc_regs(struct clk_osm *c)
+{
+	int curr_level, i, j = 0;
+	int mem_acc_level_map[MAX_MEM_ACC_LEVELS] = {MAX_VC, MAX_VC, MAX_VC};
+
+	curr_level = c->osm_table[0].mem_acc_level;
+	for (i = 0; i < c->num_entries; i++) {
+		if (curr_level == MAX_MEM_ACC_LEVELS)
+			break;
+
+		if (c->osm_table[i].mem_acc_level != curr_level) {
+			mem_acc_level_map[j++] =
+				c->osm_table[i].virtual_corner;
+			curr_level = c->osm_table[i].mem_acc_level;
+		}
+	}
+
+	if (c->secure_init) {
+		clk_osm_write_seq_reg(c,
+				c->pbases[OSM_BASE] + MEMACC_CROSSOVER_VC,
+				DATA_MEM(57));
+		clk_osm_write_seq_reg(c, c->mem_acc_addr[0], DATA_MEM(48));
+		clk_osm_write_seq_reg(c, c->mem_acc_addr[1], DATA_MEM(49));
+		clk_osm_write_seq_reg(c, c->mem_acc_addr[2], DATA_MEM(50));
+		clk_osm_write_seq_reg(c, c->mem_acc_crossover_vc,
+							DATA_MEM(78));
+		clk_osm_write_seq_reg(c, mem_acc_level_map[0], DATA_MEM(79));
+		if (c == &perfcl_clk)
+			clk_osm_write_seq_reg(c, c->mem_acc_threshold_vc,
+								DATA_MEM(80));
+		else
+			clk_osm_write_seq_reg(c, mem_acc_level_map[1],
+								DATA_MEM(80));
+		/*
+		 * Note that DATA_MEM[81] -> DATA_MEM[89] values will be
+		 * confirmed post-si. Use a value of 1 for DATA_MEM[89] and
+		 * leave the rest of them as 0.
+		 */
+		clk_osm_write_seq_reg(c, 1, DATA_MEM(89));
+	} else {
+		scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(78),
+						c->mem_acc_crossover_vc);
+		scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(79),
+						mem_acc_level_map[0]);
+		if (c == &perfcl_clk)
+			scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(80),
+						c->mem_acc_threshold_vc);
+		else
+			scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(80),
+						mem_acc_level_map[1]);
+	}
+}
+
+static void clk_osm_program_apm_regs(struct clk_osm *c)
+{
+	if (c == &l3_clk || c == &pwrcl_clk)
+		return;
+
+	/*
+	 * Program address of the control register used to configure
+	 * the Array Power Mux controller
+	 */
+	clk_osm_write_seq_reg(c, c->apm_mode_ctl, DATA_MEM(41));
+
+	/* Program address of controller status register */
+	clk_osm_write_seq_reg(c, c->apm_status_ctl, DATA_MEM(43));
+
+	/* Program address of crossover register */
+	clk_osm_write_seq_reg(c, c->pbases[OSM_BASE] + APM_CROSSOVER_VC,
+						DATA_MEM(44));
+
+	/* Program mode value to switch APM to VDD_APC */
+	clk_osm_write_seq_reg(c, APM_APC_MODE, DATA_MEM(72));
+
+	/* Program mode value to switch APM to VDD_MX */
+	clk_osm_write_seq_reg(c, APM_MX_MODE, DATA_MEM(73));
+
+	/* Program mask used to move into read_mask port */
+	clk_osm_write_seq_reg(c, APM_READ_DATA_MASK, DATA_MEM(74));
+
+	/* Value used to move into read_exp port */
+	clk_osm_write_seq_reg(c, APM_APC_READ_VAL, DATA_MEM(75));
+	clk_osm_write_seq_reg(c, APM_MX_READ_VAL, DATA_MEM(76));
+}
+
+static void clk_osm_do_additional_setup(struct clk_osm *c,
+					struct platform_device *pdev)
+{
+	if (!c->secure_init)
+		return;
+
+	dev_info(&pdev->dev, "Performing additional OSM setup due to lack of TZ for cluster=%d\n",
+						 c->cluster_num);
+
+	/* PLL L_VAL & post-div programming */
+	clk_osm_write_seq_reg(c, c->apcs_pll_min_freq, DATA_MEM(32));
+	clk_osm_write_seq_reg(c, c->l_val_base, DATA_MEM(33));
+	clk_osm_write_seq_reg(c, c->apcs_pll_user_ctl, DATA_MEM(34));
+	clk_osm_write_seq_reg(c, PLL_POST_DIV1, DATA_MEM(35));
+	clk_osm_write_seq_reg(c, PLL_POST_DIV2, DATA_MEM(36));
+
+	/* APM Programming */
+	clk_osm_program_apm_regs(c);
+
+	/* GFMUX Programming */
+	clk_osm_write_seq_reg(c, c->cfg_gfmux_addr, DATA_MEM(37));
+	clk_osm_write_seq_reg(c, 0x1, DATA_MEM(65));
+	clk_osm_write_seq_reg(c, 0x2, DATA_MEM(66));
+	clk_osm_write_seq_reg(c, 0x3, DATA_MEM(67));
+	clk_osm_write_seq_reg(c, 0x40000000, DATA_MEM(68));
+	clk_osm_write_seq_reg(c, 0x20000000, DATA_MEM(69));
+	clk_osm_write_seq_reg(c, 0x10000000, DATA_MEM(70));
+	clk_osm_write_seq_reg(c, 0x70000000, DATA_MEM(71));
+
+	/* Override programming */
+	clk_osm_write_seq_reg(c, c->pbases[OSM_BASE] +
+			OVERRIDE_CLUSTER_IDLE_ACK, DATA_MEM(54));
+	clk_osm_write_seq_reg(c, 0x3, DATA_MEM(55));
+	clk_osm_write_seq_reg(c, c->pbases[OSM_BASE] + PDN_FSM_CTRL_REG,
+					DATA_MEM(40));
+	clk_osm_write_seq_reg(c, c->pbases[OSM_BASE] + REQ_GEN_FSM_STATUS,
+					DATA_MEM(60));
+	clk_osm_write_seq_reg(c, 0x10, DATA_MEM(61));
+	clk_osm_write_seq_reg(c, 0x70, DATA_MEM(62));
+	clk_osm_write_seq_reg(c, c->apcs_cbc_addr, DATA_MEM(112));
+	clk_osm_write_seq_reg(c, 0x2, DATA_MEM(113));
+
+	if (c == &perfcl_clk) {
+		int rc;
+		u32 isense_addr;
+
+		/* Performance cluster isense programming */
+		rc = of_property_read_u32(pdev->dev.of_node,
+				"qcom,perfcl-isense-addr", &isense_addr);
+		if (rc) {
+			dev_err(&pdev->dev, "unable to find qcom,perfcl-isense-addr property, rc=%d\n",
+				rc);
+			return;
+		}
+		clk_osm_write_seq_reg(c, isense_addr, DATA_MEM(45));
+		clk_osm_write_seq_reg(c, ISENSE_ON_DATA, DATA_MEM(46));
+		clk_osm_write_seq_reg(c, ISENSE_OFF_DATA, DATA_MEM(47));
+	}
+
+	clk_osm_write_seq_reg(c, c->ramp_ctl_addr, DATA_MEM(105));
+	clk_osm_write_seq_reg(c, CONSTANT_32, DATA_MEM(92));
+
+	/* Enable/disable CPR ramp settings */
+	clk_osm_write_seq_reg(c, 0x101C031, DATA_MEM(106));
+	clk_osm_write_seq_reg(c, 0x1010031, DATA_MEM(107));
+}
+
+static void clk_osm_setup_fsms(struct clk_osm *c)
+{
+	u32 val;
+
+	/* Voltage Reduction FSM */
+	if (c->red_fsm_en) {
+		val = clk_osm_read_reg(c, VMIN_REDUCTION_ENABLE_REG) | BIT(0);
+		val |= BVAL(6, 1, c->min_cpr_vc);
+		clk_osm_write_reg(c, val, VMIN_REDUCTION_ENABLE_REG);
+
+		clk_osm_write_reg(c, clk_osm_count_ns(c, 10000),
+				  VMIN_REDUCTION_TIMER_REG);
+	}
+
+	/* Boost FSM */
+	if (c->boost_fsm_en) {
+		val = clk_osm_read_reg(c, PDN_FSM_CTRL_REG);
+		val |= DELTA_DEX_VAL | CC_BOOST_FSM_EN | IGNORE_PLL_LOCK;
+		clk_osm_write_reg(c, val, PDN_FSM_CTRL_REG);
+
+		val = clk_osm_read_reg(c, CC_BOOST_FSM_TIMERS_REG0);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, PLL_WAIT_LOCK_TIME_NS));
+		val |= BVAL(31, 16, clk_osm_count_ns(c, SAFE_FREQ_WAIT_NS));
+		clk_osm_write_reg(c, val, CC_BOOST_FSM_TIMERS_REG0);
+
+		val = clk_osm_read_reg(c, CC_BOOST_FSM_TIMERS_REG1);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, PLL_WAIT_LOCK_TIME_NS));
+		val |= BVAL(31, 16, clk_osm_count_ns(c, PLL_WAIT_LOCK_TIME_NS));
+		clk_osm_write_reg(c, val, CC_BOOST_FSM_TIMERS_REG1);
+
+		val = clk_osm_read_reg(c, CC_BOOST_FSM_TIMERS_REG2);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, DEXT_DECREMENT_WAIT_NS));
+		clk_osm_write_reg(c, val, CC_BOOST_FSM_TIMERS_REG2);
+	}
+
+	/* Safe Freq FSM */
+	if (c->safe_fsm_en) {
+		val = clk_osm_read_reg(c, PDN_FSM_CTRL_REG);
+		clk_osm_write_reg(c, val | DCVS_BOOST_FSM_EN_MASK,
+				  PDN_FSM_CTRL_REG);
+
+		val = clk_osm_read_reg(c, DCVS_BOOST_FSM_TIMERS_REG0);
+		val |= BVAL(31, 16, clk_osm_count_ns(c, 1000));
+		clk_osm_write_reg(c, val, DCVS_BOOST_FSM_TIMERS_REG0);
+
+		val = clk_osm_read_reg(c, DCVS_BOOST_FSM_TIMERS_REG1);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, SAFE_FREQ_WAIT_NS));
+		clk_osm_write_reg(c, val, DCVS_BOOST_FSM_TIMERS_REG1);
+
+		val = clk_osm_read_reg(c, DCVS_BOOST_FSM_TIMERS_REG2);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, DEXT_DECREMENT_WAIT_NS));
+		clk_osm_write_reg(c, val, DCVS_BOOST_FSM_TIMERS_REG2);
+
+	}
+
+	/* Pulse Swallowing FSM */
+	if (c->ps_fsm_en) {
+		val = clk_osm_read_reg(c, PDN_FSM_CTRL_REG);
+		clk_osm_write_reg(c, val | PS_BOOST_FSM_EN_MASK,
+							PDN_FSM_CTRL_REG);
+
+		val = clk_osm_read_reg(c, PS_BOOST_FSM_TIMERS_REG0);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, SAFE_FREQ_WAIT_NS));
+		val |= BVAL(31, 16, clk_osm_count_ns(c, 1000));
+		clk_osm_write_reg(c, val, PS_BOOST_FSM_TIMERS_REG0);
+
+		val = clk_osm_read_reg(c, PS_BOOST_FSM_TIMERS_REG1);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, SAFE_FREQ_WAIT_NS));
+		val |= BVAL(31, 16, clk_osm_count_ns(c, 1000));
+		clk_osm_write_reg(c, val, PS_BOOST_FSM_TIMERS_REG1);
+
+		val = clk_osm_read_reg(c, PS_BOOST_FSM_TIMERS_REG2);
+		val |= BVAL(15, 0, clk_osm_count_ns(c, DEXT_DECREMENT_WAIT_NS));
+		clk_osm_write_reg(c, val, PS_BOOST_FSM_TIMERS_REG2);
+	}
+
+	/* PLL signal timing control */
+	if (c->boost_fsm_en || c->safe_fsm_en || c->ps_fsm_en)
+		clk_osm_write_reg(c, 0x2, BOOST_PROG_SYNC_DELAY_REG);
+
+	/* DCVS droop FSM - only if RCGwRC is not used for di/dt control */
+	if (c->droop_fsm_en) {
+		val = clk_osm_read_reg(c, PDN_FSM_CTRL_REG);
+		clk_osm_write_reg(c, val | DCVS_DROOP_FSM_EN_MASK,
+				  PDN_FSM_CTRL_REG);
+	}
+
+	if (c->ps_fsm_en || c->droop_fsm_en) {
+		clk_osm_write_reg(c, 0x1, DROOP_PROG_SYNC_DELAY_REG);
+		clk_osm_write_reg(c, clk_osm_count_ns(c, 100),
+				  DROOP_RELEASE_TIMER_CTRL);
+		clk_osm_write_reg(c, clk_osm_count_ns(c, 150),
+				  DCVS_DROOP_TIMER_CTRL);
+		/*
+		 * TODO: Check if DCVS_DROOP_CODE used is correct. Also check
+		 * if RESYNC_CTRL should be set for L3.
+		 */
+		val = BIT(31) | BVAL(22, 16, 0x2) | BVAL(6, 0, 0x8);
+		clk_osm_write_reg(c, val, DROOP_CTRL_REG);
+	}
+}
+
+static int clk_osm_set_llm_volt_policy(struct platform_device *pdev)
+{
+	struct device_node *of = pdev->dev.of_node;
+	u32 *array;
+	int rc = 0, val, regval;
+
+	array = devm_kzalloc(&pdev->dev, MAX_CLUSTER_CNT * sizeof(u32),
+			     GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	/*
+	 * Setup Timer to control how long OSM should wait before performing
+	 * DCVS when a LLM up voltage request is received.
+	 * Time is specified in us.
+	 */
+	rc = of_property_read_u32_array(of, "qcom,llm-volt-up-timer",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No LLM voltage up timer value, rc=%d\n",
+			rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk, array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val,
+					LLM_VOLTAGE_VOTE_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+						array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val,
+					LLM_VOLTAGE_VOTE_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&perfcl_clk,
+						array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val,
+					LLM_VOLTAGE_VOTE_INC_HYSTERESIS);
+	}
+
+	/*
+	 * Setup Timer to control how long OSM should wait before performing
+	 * DCVS when a LLM down voltage request is received.
+	 * Time is specified in us.
+	 */
+	rc = of_property_read_u32_array(of, "qcom,llm-volt-down-timer",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No LLM Voltage down timer value: %d\n",
+									rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk, array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val,
+					LLM_VOLTAGE_VOTE_DEC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+					       array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val,
+					LLM_VOLTAGE_VOTE_DEC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&perfcl_clk,
+					       array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val,
+					LLM_VOLTAGE_VOTE_DEC_HYSTERESIS);
+	}
+
+	/* Enable or disable honoring of LLM Voltage requests */
+	rc = of_property_read_bool(pdev->dev.of_node,
+					"qcom,enable-llm-volt-vote");
+	if (rc) {
+		dev_dbg(&pdev->dev, "Honoring LLM Voltage requests\n");
+		val = 0;
+	} else
+		val = 1;
+
+	/* Enable or disable LLM VOLT DVCS */
+	regval = val | clk_osm_read_reg(&l3_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&l3_clk, regval, LLM_INTF_DCVS_DISABLE);
+	regval = val | clk_osm_read_reg(&pwrcl_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&pwrcl_clk, regval, LLM_INTF_DCVS_DISABLE);
+	regval = val | clk_osm_read_reg(&perfcl_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&perfcl_clk, regval, LLM_INTF_DCVS_DISABLE);
+
+	/* Wait for the writes to complete */
+	clk_osm_mb(&perfcl_clk, OSM_BASE);
+
+	devm_kfree(&pdev->dev, array);
+	return 0;
+}
+
+static int clk_osm_set_llm_freq_policy(struct platform_device *pdev)
+{
+	struct device_node *of = pdev->dev.of_node;
+	u32 *array;
+	int rc = 0, val, regval;
+
+	array = devm_kzalloc(&pdev->dev, MAX_CLUSTER_CNT * sizeof(u32),
+			     GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	/*
+	 * Setup Timer to control how long OSM should wait before performing
+	 * DCVS when a LLM up frequency request is received.
+	 * Time is specified in us.
+	 */
+	rc = of_property_read_u32_array(of, "qcom,llm-freq-up-timer", array,
+					MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "Unable to get CC up timer value: %d\n",
+			rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk, array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val, LLM_FREQ_VOTE_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+						array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val,
+						LLM_FREQ_VOTE_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&perfcl_clk,
+						array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val,
+						LLM_FREQ_VOTE_INC_HYSTERESIS);
+	}
+
+	/*
+	 * Setup Timer to control how long OSM should wait before performing
+	 * DCVS when a LLM down frequency request is received.
+	 * Time is specified in us.
+	 */
+	rc = of_property_read_u32_array(of, "qcom,llm-freq-down-timer",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No LLM Frequency down timer value: %d\n",
+			rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk, array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val, LLM_FREQ_VOTE_DEC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+					       array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val,
+						LLM_FREQ_VOTE_DEC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&perfcl_clk,
+					       array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val,
+						LLM_FREQ_VOTE_DEC_HYSTERESIS);
+	}
+
+	/* Enable or disable honoring of LLM frequency requests */
+	rc = of_property_read_bool(pdev->dev.of_node,
+					"qcom,enable-llm-freq-vote");
+	if (rc) {
+		dev_dbg(&pdev->dev, "Honoring LLM Frequency requests\n");
+		val = 0;
+	} else
+		val = BIT(1);
+
+	/* Enable or disable LLM FREQ DVCS */
+	regval = val | clk_osm_read_reg(&l3_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&l3_clk, regval, LLM_INTF_DCVS_DISABLE);
+	regval = val | clk_osm_read_reg(&pwrcl_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&pwrcl_clk, regval, LLM_INTF_DCVS_DISABLE);
+	regval = val | clk_osm_read_reg(&perfcl_clk, LLM_INTF_DCVS_DISABLE);
+	clk_osm_write_reg(&perfcl_clk, regval, LLM_INTF_DCVS_DISABLE);
+
+	/* Wait for the write to complete */
+	clk_osm_mb(&perfcl_clk, OSM_BASE);
+
+	devm_kfree(&pdev->dev, array);
+	return 0;
+}
+
+static int clk_osm_set_cc_policy(struct platform_device *pdev)
+{
+	int rc = 0, val;
+	u32 *array;
+	struct device_node *of = pdev->dev.of_node;
+
+	array = devm_kzalloc(&pdev->dev, MAX_CLUSTER_CNT * sizeof(u32),
+			     GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(of, "qcom,up-timer", array,
+					MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No up timer value, rc=%d\n",
+			 rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk,
+					array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val, SPM_CC_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+					array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val, SPM_CC_INC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&perfcl_clk,
+					array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val, SPM_CC_INC_HYSTERESIS);
+	}
+
+	rc = of_property_read_u32_array(of, "qcom,down-timer",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No down timer value, rc=%d\n", rc);
+	} else {
+		val = clk_osm_count_ns(&l3_clk,
+				       array[l3_clk.cluster_num]);
+		clk_osm_write_reg(&l3_clk, val, SPM_CC_DEC_HYSTERESIS);
+
+		val = clk_osm_count_ns(&pwrcl_clk,
+				       array[pwrcl_clk.cluster_num]);
+		clk_osm_write_reg(&pwrcl_clk, val, SPM_CC_DEC_HYSTERESIS);
+
+		clk_osm_count_ns(&perfcl_clk,
+				       array[perfcl_clk.cluster_num]);
+		clk_osm_write_reg(&perfcl_clk, val, SPM_CC_DEC_HYSTERESIS);
+	}
+
+	/* OSM index override for cluster PC */
+	rc = of_property_read_u32_array(of, "qcom,pc-override-index",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_dbg(&pdev->dev, "No PC override index value, rc=%d\n",
+			rc);
+		clk_osm_write_reg(&pwrcl_clk, 0, CC_ZERO_BEHAV_CTRL);
+		clk_osm_write_reg(&perfcl_clk, 0, CC_ZERO_BEHAV_CTRL);
+	} else {
+		val = BVAL(6, 1, array[pwrcl_clk.cluster_num])
+			| ENABLE_OVERRIDE;
+		clk_osm_write_reg(&pwrcl_clk, val, CC_ZERO_BEHAV_CTRL);
+		val = BVAL(6, 1, array[perfcl_clk.cluster_num])
+			| ENABLE_OVERRIDE;
+		clk_osm_write_reg(&perfcl_clk, val, CC_ZERO_BEHAV_CTRL);
+	}
+
+	/* Wait for the writes to complete */
+	clk_osm_mb(&perfcl_clk, OSM_BASE);
+
+	rc = of_property_read_bool(pdev->dev.of_node, "qcom,set-c3-active");
+	if (rc) {
+		dev_dbg(&pdev->dev, "Treat cores in C3 as active\n");
+
+		val = clk_osm_read_reg(&l3_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(2);
+		clk_osm_write_reg(&l3_clk, val, SPM_CORE_INACTIVE_MAPPING);
+
+		val = clk_osm_read_reg(&pwrcl_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(2);
+		clk_osm_write_reg(&pwrcl_clk, val, SPM_CORE_INACTIVE_MAPPING);
+
+		val = clk_osm_read_reg(&perfcl_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(2);
+		clk_osm_write_reg(&perfcl_clk, val, SPM_CORE_INACTIVE_MAPPING);
+	}
+
+	rc = of_property_read_bool(pdev->dev.of_node, "qcom,set-c2-active");
+	if (rc) {
+		dev_dbg(&pdev->dev, "Treat cores in C2 as active\n");
+
+		val = clk_osm_read_reg(&l3_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(1);
+		clk_osm_write_reg(&l3_clk, val, SPM_CORE_INACTIVE_MAPPING);
+
+		val = clk_osm_read_reg(&pwrcl_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(1);
+		clk_osm_write_reg(&pwrcl_clk, val, SPM_CORE_INACTIVE_MAPPING);
+
+		val = clk_osm_read_reg(&perfcl_clk, SPM_CORE_INACTIVE_MAPPING);
+		val &= ~BIT(1);
+		clk_osm_write_reg(&perfcl_clk, val, SPM_CORE_INACTIVE_MAPPING);
+	}
+
+	rc = of_property_read_bool(pdev->dev.of_node, "qcom,disable-cc-dvcs");
+	if (rc) {
+		dev_dbg(&pdev->dev, "Disabling CC based DCVS\n");
+		val = 1;
+	} else
+		val = 0;
+
+	clk_osm_write_reg(&l3_clk, val, SPM_CC_DCVS_DISABLE);
+	clk_osm_write_reg(&pwrcl_clk, val, SPM_CC_DCVS_DISABLE);
+	clk_osm_write_reg(&perfcl_clk, val, SPM_CC_DCVS_DISABLE);
+
+	/* Wait for the writes to complete */
+	clk_osm_mb(&perfcl_clk, OSM_BASE);
+
+	devm_kfree(&pdev->dev, array);
+	return 0;
+}
+
+static void clk_osm_setup_cluster_pll(struct clk_osm *c)
+{
+	writel_relaxed(0x0, c->vbases[PLL_BASE] + PLL_MODE);
+	writel_relaxed(0x26, c->vbases[PLL_BASE] + PLL_L_VAL);
+	writel_relaxed(0x8, c->vbases[PLL_BASE] +
+			PLL_USER_CTRL);
+	writel_relaxed(0x20000AA8, c->vbases[PLL_BASE] +
+			PLL_CONFIG_CTL_LO);
+	writel_relaxed(0x000003D2, c->vbases[PLL_BASE] +
+			PLL_CONFIG_CTL_HI);
+	writel_relaxed(0x2, c->vbases[PLL_BASE] +
+			PLL_MODE);
+
+	/* Ensure writes complete before delaying */
+	clk_osm_mb(c, PLL_BASE);
+
+	udelay(PLL_WAIT_LOCK_TIME_US);
+
+	writel_relaxed(0x6, c->vbases[PLL_BASE] + PLL_MODE);
+
+	/* Ensure write completes before delaying */
+	clk_osm_mb(c, PLL_BASE);
+
+	usleep_range(50, 75);
+
+	writel_relaxed(0x7, c->vbases[PLL_BASE] + PLL_MODE);
+}
+
+static void clk_osm_misc_programming(struct clk_osm *c)
+{
+	u32 lval = 0xFF, val;
+	int i;
+
+	clk_osm_write_reg(c, BVAL(23, 16, 0xF), SPM_CORE_COUNT_CTRL);
+	clk_osm_write_reg(c, PLL_MIN_LVAL, PLL_MIN_FREQ_REG);
+
+	/* Pattern to set/clear PLL lock in PDN_FSM_CTRL_REG */
+	val = clk_osm_read_reg(c, PDN_FSM_CTRL_REG);
+	if (c->secure_init) {
+		val |= IGNORE_PLL_LOCK;
+		clk_osm_write_seq_reg(c, val, DATA_MEM(108));
+		val &= ~IGNORE_PLL_LOCK;
+		clk_osm_write_seq_reg(c, val, DATA_MEM(109));
+		clk_osm_write_seq_reg(c, MIN_VCO_VAL, DATA_MEM(110));
+	} else {
+		val |= IGNORE_PLL_LOCK;
+		scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(108), val);
+		val &= ~IGNORE_PLL_LOCK;
+		scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(109), val);
+	}
+
+	/* Program LVAL corresponding to first turbo VC */
+	for (i = 0; i < c->num_entries; i++) {
+		if (c->osm_table[i].mem_acc_level == MAX_MEM_ACC_LEVELS) {
+			lval = c->osm_table[i].freq_data & GENMASK(7, 0);
+			break;
+		}
+	}
+
+	if (c->secure_init)
+		clk_osm_write_seq_reg(c, lval, DATA_MEM(114));
+	else
+		scm_io_write(c->pbases[SEQ_BASE] + DATA_MEM(114), lval);
+
+}
+
+static int clk_osm_setup_hw_table(struct clk_osm *c)
+{
+	struct osm_entry *entry = c->osm_table;
+	int i;
+	u32 freq_val = 0, volt_val = 0, override_val = 0;
+	u32 table_entry_offset, last_mem_acc_level, last_virtual_corner = 0;
+
+	for (i = 0; i < OSM_TABLE_SIZE; i++) {
+		if (i < c->num_entries) {
+			freq_val = entry[i].freq_data;
+			volt_val = BVAL(27, 24, entry[i].mem_acc_level)
+				| BVAL(21, 16, entry[i].virtual_corner)
+				| BVAL(11, 0, entry[i].open_loop_volt);
+			override_val = entry[i].override_data;
+
+			if (last_virtual_corner && last_virtual_corner ==
+			    entry[i].virtual_corner && last_mem_acc_level !=
+			    entry[i].mem_acc_level) {
+				pr_err("invalid LUT entry at row=%d virtual_corner=%d, mem_acc_level=%d\n",
+				       i, entry[i].virtual_corner,
+				       entry[i].mem_acc_level);
+				return -EINVAL;
+			}
+			last_virtual_corner = entry[i].virtual_corner;
+			last_mem_acc_level = entry[i].mem_acc_level;
+		}
+
+		table_entry_offset = i * OSM_REG_SIZE;
+		clk_osm_write_reg(c, freq_val, FREQ_REG + table_entry_offset);
+		clk_osm_write_reg(c, volt_val, VOLT_REG + table_entry_offset);
+		clk_osm_write_reg(c, override_val, OVERRIDE_REG +
+				  table_entry_offset);
+	}
+
+	/* Make sure all writes go through */
+	clk_osm_mb(c, OSM_BASE);
+
+	return 0;
+}
+
+static void clk_osm_print_osm_table(struct clk_osm *c)
+{
+	int i;
+	struct osm_entry *table = c->osm_table;
+	u32 pll_src, pll_div, lval, core_count;
+
+	pr_debug("Index, Frequency, VC, OLV (mv), Core Count, PLL Src, PLL Div, L-Val, ACC Level\n");
+	for (i = 0; i < c->num_entries; i++) {
+		pll_src = (table[i].freq_data & GENMASK(31, 30)) >> 30;
+		pll_div = (table[i].freq_data & GENMASK(29, 28)) >> 28;
+		lval = table[i].freq_data & GENMASK(7, 0);
+		core_count = (table[i].freq_data & GENMASK(18, 16)) >> 16;
+
+		pr_debug("%3d, %11lu, %2u, %5u, %2u, %6u, %8u, %7u, %5u\n",
+			i,
+			table[i].frequency,
+			table[i].virtual_corner,
+			table[i].open_loop_volt,
+			core_count,
+			pll_src,
+			pll_div,
+			lval,
+			table[i].mem_acc_level);
+	}
+	pr_debug("APM threshold corner=%d, crossover corner=%d\n",
+			c->apm_threshold_vc, c->apm_crossover_vc);
+	pr_debug("MEM-ACC threshold corner=%d, crossover corner=%d\n",
+			c->mem_acc_threshold_vc, c->mem_acc_crossover_vc);
+}
+
+static u32 find_voltage(struct clk_osm *c, unsigned long rate)
+{
+	struct osm_entry *table = c->osm_table;
+	int entries = c->num_entries, i;
+
+	for (i = 0; i < entries; i++) {
+		if (rate == table[i].frequency) {
+			/* OPP table voltages have units of mV */
+			return table[i].open_loop_volt * 1000;
+		}
+	}
+
+	return -EINVAL;
+}
+
+static int add_opp(struct clk_osm *c, struct device *dev)
+{
+	unsigned long rate = 0;
+	u32 uv;
+	long rc;
+	int j = 0;
+	unsigned long min_rate = c->hw.init->rate_max[0];
+	unsigned long max_rate =
+			c->hw.init->rate_max[c->hw.init->num_rate_max - 1];
+
+	while (1) {
+		rate = c->hw.init->rate_max[j++];
+		uv = find_voltage(c, rate);
+		if (uv <= 0) {
+			pr_warn("No voltage for %lu.\n", rate);
+			return -EINVAL;
+		}
+
+		rc = dev_pm_opp_add(dev, rate, uv);
+		if (rc) {
+			pr_warn("failed to add OPP for %lu\n", rate);
+			return rc;
+		}
+
+		/*
+		 * Print the OPP pair for the lowest and highest frequency for
+		 * each device that we're populating. This is important since
+		 * this information will be used by thermal mitigation and the
+		 * scheduler.
+		 */
+		if (rate == min_rate)
+			pr_info("Set OPP pair (%lu Hz, %d uv) on %s\n",
+				rate, uv, dev_name(dev));
+
+		if (rate == max_rate && max_rate != min_rate) {
+			pr_info("Set OPP pair (%lu Hz, %d uv) on %s\n",
+				rate, uv, dev_name(dev));
+			break;
+		}
+
+		if (min_rate == max_rate)
+			break;
+	}
+	return 0;
+}
+
+static void populate_opp_table(struct platform_device *pdev)
+{
+	int cpu;
+	struct device *cpu_dev;
+	struct clk_osm *c, *parent;
+	struct clk_hw *hw_parent;
+
+	for_each_possible_cpu(cpu) {
+		c = logical_cpu_to_clk(cpu);
+		if (!c) {
+			pr_err("no clock device for CPU=%d\n", cpu);
+			return;
+		}
+
+		hw_parent = clk_hw_get_parent(&c->hw);
+		parent = to_clk_osm(hw_parent);
+		cpu_dev = get_cpu_device(cpu);
+		if (cpu_dev)
+			if (add_opp(parent, cpu_dev))
+				pr_err("Failed to add OPP levels for %s\n",
+					dev_name(cpu_dev));
+	}
+
+	/*TODO: Figure out which device to tag the L3 table to */
+}
+
+static u64 clk_osm_get_cpu_cycle_counter(int cpu)
+{
+	u32 val;
+	unsigned long flags;
+	struct clk_osm *parent, *c = logical_cpu_to_clk(cpu);
+
+	if (IS_ERR_OR_NULL(c)) {
+		pr_err("no clock device for CPU=%d\n", cpu);
+		return 0;
+	}
+
+	parent = to_clk_osm(clk_hw_get_parent(&c->hw));
+
+	spin_lock_irqsave(&parent->lock, flags);
+	val = clk_osm_read_reg_no_log(parent,
+			OSM_CYCLE_COUNTER_STATUS_REG(c->core_num));
+
+	if (val < c->prev_cycle_counter) {
+		/* Handle counter overflow */
+		c->total_cycle_counter += UINT_MAX -
+			c->prev_cycle_counter + val;
+		c->prev_cycle_counter = val;
+	} else {
+		c->total_cycle_counter += val - c->prev_cycle_counter;
+		c->prev_cycle_counter = val;
+	}
+	spin_unlock_irqrestore(&parent->lock, flags);
+
+	return c->total_cycle_counter;
+}
+
+static void clk_osm_setup_cycle_counters(struct clk_osm *c)
+{
+	u32 ratio = c->osm_clk_rate;
+	u32 val = 0;
+
+	/* Enable cycle counter */
+	val = BIT(0);
+	/* Setup OSM clock to XO ratio */
+	do_div(ratio, c->xo_clk_rate);
+	val |= BVAL(5, 1, ratio - 1) | OSM_CYCLE_COUNTER_USE_XO_EDGE_EN;
+
+	clk_osm_write_reg(c, val, OSM_CYCLE_COUNTER_CTRL_REG);
+	pr_debug("OSM to XO clock ratio: %d\n", ratio);
+}
+
+static int clk_osm_resolve_crossover_corners(struct clk_osm *c,
+					struct platform_device *pdev)
+{
+	struct regulator *regulator = c->vdd_reg;
+	int count, vc, i, memacc_threshold, apm_threshold;
+	int rc = 0;
+	u32 corner_volt;
+
+	if (c == &l3_clk || c == &pwrcl_clk)
+		return rc;
+
+	rc = of_property_read_u32(pdev->dev.of_node,
+				  "qcom,perfcl-apcs-apm-threshold-voltage",
+				  &apm_threshold);
+	if (rc) {
+		pr_err("qcom,perfcl-apcs-apm-threshold-voltage property not specified\n");
+		return rc;
+	}
+
+	rc = of_property_read_u32(pdev->dev.of_node,
+				  "qcom,perfcl-apcs-mem-acc-threshold-voltage",
+				  &memacc_threshold);
+	if (rc) {
+		pr_err("qcom,perfcl-apcs-mem-acc-threshold-voltage property not specified\n");
+		return rc;
+	}
+
+	/*
+	 * Initialize VC settings in case none of them go above the voltage
+	 * limits
+	 */
+	c->apm_threshold_vc = c->apm_crossover_vc = c->mem_acc_crossover_vc =
+				c->mem_acc_threshold_vc = MAX_VC;
+
+	count = regulator_count_voltages(regulator);
+	if (count < 0) {
+		pr_err("Failed to get the number of virtual corners supported\n");
+		return count;
+	}
+
+	c->apm_crossover_vc = count - 2;
+	c->mem_acc_crossover_vc = count - 1;
+
+	for (i = 0; i < OSM_TABLE_SIZE; i++) {
+		vc = c->osm_table[i].virtual_corner + 1;
+		corner_volt = regulator_list_corner_voltage(regulator, vc);
+
+		if (c->apm_threshold_vc == MAX_VC &&
+				corner_volt >= apm_threshold)
+			c->apm_threshold_vc = c->osm_table[i].virtual_corner;
+
+		if (c->mem_acc_threshold_vc == MAX_VC &&
+				corner_volt >= memacc_threshold)
+			c->mem_acc_threshold_vc =
+				c->osm_table[i].virtual_corner;
+	}
+
+	return rc;
+}
+
+static int clk_osm_resolve_open_loop_voltages(struct clk_osm *c)
+{
+	struct regulator *regulator = c->vdd_reg;
+	u32 vc, mv;
+	int i;
+
+	for (i = 0; i < OSM_TABLE_SIZE; i++) {
+		vc = c->osm_table[i].virtual_corner + 1;
+		/* Voltage is in uv. Convert to mv */
+		mv = regulator_list_corner_voltage(regulator, vc) / 1000;
+		c->osm_table[i].open_loop_volt = mv;
+	}
+
+	return 0;
+}
+
+static int clk_osm_get_lut(struct platform_device *pdev,
+			   struct clk_osm *c, char *prop_name)
+{
+	struct device_node *of = pdev->dev.of_node;
+	int prop_len, total_elems, num_rows, i, j, k;
+	int rc = 0;
+	u32 *array;
+	u32 *fmax_temp;
+	u32 data;
+	unsigned long abs_fmax = 0;
+	bool last_entry = false;
+
+	if (!of_find_property(of, prop_name, &prop_len)) {
+		dev_err(&pdev->dev, "missing %s\n", prop_name);
+		return -EINVAL;
+	}
+
+	total_elems = prop_len / sizeof(u32);
+	if (total_elems % NUM_FIELDS) {
+		dev_err(&pdev->dev, "bad length %d\n", prop_len);
+		return -EINVAL;
+	}
+
+	num_rows = total_elems / NUM_FIELDS;
+
+	fmax_temp = devm_kzalloc(&pdev->dev, num_rows * sizeof(unsigned long),
+					GFP_KERNEL);
+	if (!fmax_temp)
+		return -ENOMEM;
+
+	array = devm_kzalloc(&pdev->dev, prop_len, GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(of, prop_name, array, total_elems);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to parse OSM table, rc=%d\n", rc);
+		goto exit;
+	}
+
+	pr_debug("%s: Entries in Table: %d\n", __func__, num_rows);
+	c->num_entries = num_rows;
+	if (c->num_entries > OSM_TABLE_SIZE) {
+		pr_err("LUT entries %d exceed maximum size %d\n",
+		       c->num_entries, OSM_TABLE_SIZE);
+		return -EINVAL;
+	}
+
+	for (i = 0, j = 0, k = 0; j < OSM_TABLE_SIZE; j++) {
+		c->osm_table[j].frequency = array[i + FREQ];
+		c->osm_table[j].freq_data = array[i + FREQ_DATA];
+		c->osm_table[j].override_data = array[i + PLL_OVERRIDES];
+		c->osm_table[j].mem_acc_level = array[i + MEM_ACC_LEVEL];
+		/* Voltage corners are 0 based in the OSM LUT */
+		c->osm_table[j].virtual_corner = array[i + VIRTUAL_CORNER] - 1;
+		pr_debug("index=%d freq=%ld virtual_corner=%d freq_data=0x%x override_data=0x%x mem_acc_level=0x%x\n",
+			 j, c->osm_table[j].frequency,
+			 c->osm_table[j].virtual_corner,
+			 c->osm_table[j].freq_data,
+			 c->osm_table[j].override_data,
+			 c->osm_table[j].mem_acc_level);
+
+		data = (array[i + FREQ_DATA] & GENMASK(18, 16)) >> 16;
+		if (!last_entry && data == MAX_CORE_COUNT) {
+			fmax_temp[k] = array[i];
+			k++;
+		}
+
+		if (i < total_elems - NUM_FIELDS)
+			i += NUM_FIELDS;
+		else {
+			abs_fmax = array[i];
+			last_entry = true;
+		}
+	}
+	fmax_temp[k] = abs_fmax;
+
+	osm_clks_init[c->cluster_num].rate_max = devm_kzalloc(&pdev->dev,
+						 k * sizeof(unsigned long),
+						       GFP_KERNEL);
+	if (!osm_clks_init[c->cluster_num].rate_max) {
+		rc = -ENOMEM;
+		goto exit;
+	}
+
+	for (i = 0; i < k; i++)
+		osm_clks_init[c->cluster_num].rate_max[i] = fmax_temp[i];
+
+	osm_clks_init[c->cluster_num].num_rate_max = k;
+exit:
+	devm_kfree(&pdev->dev, fmax_temp);
+	devm_kfree(&pdev->dev, array);
+	return rc;
+}
+
+static int clk_osm_parse_dt_configs(struct platform_device *pdev)
+{
+	struct device_node *of = pdev->dev.of_node;
+	u32 *array;
+	int rc = 0;
+	struct resource *res;
+	char l3_min_cpr_vc_str[] = "qcom,l3-min-cpr-vc-bin0";
+	char pwrcl_min_cpr_vc_str[] = "qcom,pwrcl-min-cpr-vc-bin0";
+	char perfcl_min_cpr_vc_str[] = "qcom,perfcl-min-cpr-vc-bin0";
+
+	array = devm_kzalloc(&pdev->dev, MAX_CLUSTER_CNT * sizeof(u32),
+			     GFP_KERNEL);
+	if (!array)
+		return -ENOMEM;
+
+	rc = of_property_read_u32_array(of, "qcom,l-val-base",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,l-val-base property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.l_val_base = array[l3_clk.cluster_num];
+	pwrcl_clk.l_val_base = array[pwrcl_clk.cluster_num];
+	perfcl_clk.l_val_base = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apcs-pll-user-ctl",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apcs-pll-user-ctl property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.apcs_pll_user_ctl = array[l3_clk.cluster_num];
+	pwrcl_clk.apcs_pll_user_ctl = array[pwrcl_clk.cluster_num];
+	perfcl_clk.apcs_pll_user_ctl = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apcs-pll-min-freq",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apcs-pll-min-freq property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.apcs_pll_min_freq = array[l3_clk.cluster_num];
+	pwrcl_clk.apcs_pll_min_freq = array[pwrcl_clk.cluster_num];
+	perfcl_clk.apcs_pll_min_freq = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apm-mode-ctl",
+				  array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apm-mode-ctl property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.apm_mode_ctl = array[l3_clk.cluster_num];
+	pwrcl_clk.apm_mode_ctl = array[pwrcl_clk.cluster_num];
+	perfcl_clk.apm_mode_ctl = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apm-status-ctrl",
+				  array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apm-status-ctrl property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.apm_status_ctl = array[l3_clk.cluster_num];
+	pwrcl_clk.apm_status_ctl = array[pwrcl_clk.cluster_num];
+	perfcl_clk.apm_status_ctl = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,cfg-gfmux-addr",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,cfg-gfmux-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.cfg_gfmux_addr = array[l3_clk.cluster_num];
+	pwrcl_clk.cfg_gfmux_addr = array[pwrcl_clk.cluster_num];
+	perfcl_clk.cfg_gfmux_addr = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apcs-cbc-addr",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apcs-cbc-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.apcs_cbc_addr = array[l3_clk.cluster_num];
+	pwrcl_clk.apcs_cbc_addr = array[pwrcl_clk.cluster_num];
+	perfcl_clk.apcs_cbc_addr = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32_array(of, "qcom,apcs-ramp-ctl-addr",
+					array, MAX_CLUSTER_CNT);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,apcs-ramp-ctl-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.ramp_ctl_addr = array[l3_clk.cluster_num];
+	pwrcl_clk.ramp_ctl_addr = array[pwrcl_clk.cluster_num];
+	perfcl_clk.ramp_ctl_addr = array[perfcl_clk.cluster_num];
+
+	rc = of_property_read_u32(of, "qcom,xo-clk-rate",
+				  &pwrcl_clk.xo_clk_rate);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,xo-clk-rate property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	l3_clk.xo_clk_rate = perfcl_clk.xo_clk_rate = pwrcl_clk.xo_clk_rate;
+
+	rc = of_property_read_u32(of, "qcom,osm-clk-rate",
+				  &pwrcl_clk.osm_clk_rate);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,osm-clk-rate property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+	l3_clk.osm_clk_rate = perfcl_clk.osm_clk_rate = pwrcl_clk.osm_clk_rate;
+
+	rc = of_property_read_u32(of, "qcom,cc-reads",
+				  &pwrcl_clk.cycle_counter_reads);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,cc-reads property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+	l3_clk.cycle_counter_reads = perfcl_clk.cycle_counter_reads =
+			pwrcl_clk.cycle_counter_reads;
+
+	rc = of_property_read_u32(of, "qcom,cc-delay",
+				  &pwrcl_clk.cycle_counter_delay);
+	if (rc)
+		dev_dbg(&pdev->dev, "no delays between cycle counter reads\n");
+	else
+		l3_clk.cycle_counter_delay = perfcl_clk.cycle_counter_delay =
+			pwrcl_clk.cycle_counter_delay;
+
+	rc = of_property_read_u32(of, "qcom,cc-factor",
+				  &pwrcl_clk.cycle_counter_factor);
+	if (rc)
+		dev_dbg(&pdev->dev, "no factor specified for cycle counter estimation\n");
+	else
+		l3_clk.cycle_counter_factor = perfcl_clk.cycle_counter_factor =
+			pwrcl_clk.cycle_counter_factor;
+
+	l3_clk.red_fsm_en = perfcl_clk.red_fsm_en = pwrcl_clk.red_fsm_en =
+		of_property_read_bool(of, "qcom,red-fsm-en");
+
+	l3_clk.boost_fsm_en = perfcl_clk.boost_fsm_en =
+		pwrcl_clk.boost_fsm_en =
+		of_property_read_bool(of, "qcom,boost-fsm-en");
+
+	l3_clk.safe_fsm_en = perfcl_clk.safe_fsm_en = pwrcl_clk.safe_fsm_en =
+		of_property_read_bool(of, "qcom,safe-fsm-en");
+
+	l3_clk.ps_fsm_en = perfcl_clk.ps_fsm_en = pwrcl_clk.ps_fsm_en =
+		of_property_read_bool(of, "qcom,ps-fsm-en");
+
+	l3_clk.droop_fsm_en = perfcl_clk.droop_fsm_en =
+		pwrcl_clk.droop_fsm_en =
+		of_property_read_bool(of, "qcom,droop-fsm-en");
+
+	devm_kfree(&pdev->dev, array);
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"l3_sequencer");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for l3_sequencer\n");
+		return -ENOMEM;
+	}
+
+	l3_clk.pbases[SEQ_BASE] = (unsigned long)res->start;
+	l3_clk.vbases[SEQ_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+
+	if (!l3_clk.vbases[SEQ_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in l3_sequencer base\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"pwrcl_sequencer");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for pwrcl_sequencer\n");
+		return -ENOMEM;
+	}
+
+	pwrcl_clk.pbases[SEQ_BASE] = (unsigned long)res->start;
+	pwrcl_clk.vbases[SEQ_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+
+	if (!pwrcl_clk.vbases[SEQ_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in pwrcl_sequencer base\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"perfcl_sequencer");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for perfcl_sequencer\n");
+		return -ENOMEM;
+	}
+
+	perfcl_clk.pbases[SEQ_BASE] = (unsigned long)res->start;
+	perfcl_clk.vbases[SEQ_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+
+	if (!perfcl_clk.vbases[SEQ_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in perfcl_sequencer base\n");
+		return -ENOMEM;
+	}
+
+	snprintf(l3_min_cpr_vc_str, ARRAY_SIZE(l3_min_cpr_vc_str),
+			"qcom,l3-min-cpr-vc-bin%d", l3_clk.speedbin);
+	rc = of_property_read_u32(of, l3_min_cpr_vc_str, &l3_clk.min_cpr_vc);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find %s property, rc=%d\n",
+			l3_min_cpr_vc_str, rc);
+		return -EINVAL;
+	}
+
+	snprintf(pwrcl_min_cpr_vc_str, ARRAY_SIZE(pwrcl_min_cpr_vc_str),
+			"qcom,pwrcl-min-cpr-vc-bin%d", pwrcl_clk.speedbin);
+	rc = of_property_read_u32(of, pwrcl_min_cpr_vc_str,
+						&pwrcl_clk.min_cpr_vc);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find %s property, rc=%d\n",
+			pwrcl_min_cpr_vc_str, rc);
+		return -EINVAL;
+	}
+
+	snprintf(perfcl_min_cpr_vc_str, ARRAY_SIZE(perfcl_min_cpr_vc_str),
+			"qcom,perfcl-min-cpr-vc-bin%d", perfcl_clk.speedbin);
+	rc = of_property_read_u32(of, perfcl_min_cpr_vc_str,
+						&perfcl_clk.min_cpr_vc);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find %s property, rc=%d\n",
+			perfcl_min_cpr_vc_str, rc);
+		return -EINVAL;
+	}
+
+	l3_clk.secure_init = perfcl_clk.secure_init = pwrcl_clk.secure_init =
+		of_property_read_bool(pdev->dev.of_node, "qcom,osm-no-tz");
+
+	if (!pwrcl_clk.secure_init)
+		return rc;
+
+	rc = of_property_read_u32_array(of, "qcom,l3-mem-acc-addr",
+					l3_clk.mem_acc_addr, MEM_ACC_ADDRS);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,l3-mem-acc-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_array(of, "qcom,pwrcl-mem-acc-addr",
+					pwrcl_clk.mem_acc_addr, MEM_ACC_ADDRS);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,pwrcl-mem-acc-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32_array(of, "qcom,perfcl-mem-acc-addr",
+					perfcl_clk.mem_acc_addr, MEM_ACC_ADDRS);
+	if (rc) {
+		dev_err(&pdev->dev, "unable to find qcom,perfcl-mem-acc-addr property, rc=%d\n",
+			rc);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int clk_osm_resources_init(struct platform_device *pdev)
+{
+	struct device_node *node;
+	struct resource *res;
+	unsigned long pbase;
+	int rc = 0;
+	void *vbase;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"osm_l3_base");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for osm_l3_base");
+		return -ENOMEM;
+	}
+
+	l3_clk.pbases[OSM_BASE] = (unsigned long)res->start;
+	l3_clk.vbases[OSM_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+
+	if (!l3_clk.vbases[OSM_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in osm_l3_base base\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"osm_pwrcl_base");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for osm_pwrcl_base");
+		return -ENOMEM;
+	}
+
+	pwrcl_clk.pbases[OSM_BASE] = (unsigned long)res->start;
+	pwrcl_clk.vbases[OSM_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+	if (!pwrcl_clk.vbases[OSM_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in osm_pwrcl_base base\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+						"osm_perfcl_base");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for osm_perfcl_base");
+		return -ENOMEM;
+	}
+
+	perfcl_clk.pbases[OSM_BASE] = (unsigned long)res->start;
+	perfcl_clk.vbases[OSM_BASE] = devm_ioremap(&pdev->dev, res->start,
+						  resource_size(res));
+
+	if (!perfcl_clk.vbases[OSM_BASE]) {
+		dev_err(&pdev->dev, "Unable to map in osm_perfcl_base base\n");
+		return -ENOMEM;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "l3_pll");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for l3_pll\n");
+		return -ENOMEM;
+	}
+	pbase = (unsigned long)res->start;
+	vbase = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+
+	if (!vbase) {
+		dev_err(&pdev->dev, "Unable to map l3_pll base\n");
+		return -ENOMEM;
+	}
+
+	l3_clk.pbases[PLL_BASE] = pbase;
+	l3_clk.vbases[PLL_BASE] = vbase;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "pwrcl_pll");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for pwrcl_pll\n");
+		return -ENOMEM;
+	}
+	pbase = (unsigned long)res->start;
+	vbase = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+
+	if (!vbase) {
+		dev_err(&pdev->dev, "Unable to map pwrcl_pll base\n");
+		return -ENOMEM;
+	}
+
+	pwrcl_clk.pbases[PLL_BASE] = pbase;
+	pwrcl_clk.vbases[PLL_BASE] = vbase;
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "perfcl_pll");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for perfcl_pll\n");
+		return -ENOMEM;
+	}
+	pbase = (unsigned long)res->start;
+	vbase = devm_ioremap(&pdev->dev, res->start, resource_size(res));
+
+	if (!vbase) {
+		dev_err(&pdev->dev, "Unable to map perfcl_pll base\n");
+		return -ENOMEM;
+	}
+
+	perfcl_clk.pbases[PLL_BASE] = pbase;
+	perfcl_clk.vbases[PLL_BASE] = vbase;
+
+	/* efuse speed bin fuses are optional */
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "pwrcl_efuse");
+	if (res) {
+		pbase = (unsigned long)res->start;
+		vbase = devm_ioremap(&pdev->dev, res->start,
+				     resource_size(res));
+		if (!vbase) {
+			dev_err(&pdev->dev, "Unable to map in pwrcl_efuse base\n");
+			return -ENOMEM;
+		}
+		pwrcl_clk.pbases[EFUSE_BASE] = pbase;
+		pwrcl_clk.vbases[EFUSE_BASE] = vbase;
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+					   "perfcl_efuse");
+	if (res) {
+		pbase = (unsigned long)res->start;
+		vbase = devm_ioremap(&pdev->dev, res->start,
+				     resource_size(res));
+		if (!vbase) {
+			dev_err(&pdev->dev, "Unable to map in perfcl_efuse base\n");
+			return -ENOMEM;
+		}
+		perfcl_clk.pbases[EFUSE_BASE] = pbase;
+		perfcl_clk.vbases[EFUSE_BASE] = vbase;
+	}
+
+	vdd_l3 = devm_regulator_get(&pdev->dev, "vdd-l3");
+	if (IS_ERR(vdd_l3)) {
+		rc = PTR_ERR(vdd_l3);
+		if (rc != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "Unable to get the l3 vreg, rc=%d\n",
+				rc);
+		return rc;
+	}
+	l3_clk.vdd_reg = vdd_l3;
+
+	vdd_pwrcl = devm_regulator_get(&pdev->dev, "vdd-pwrcl");
+	if (IS_ERR(vdd_pwrcl)) {
+		rc = PTR_ERR(vdd_pwrcl);
+		if (rc != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "Unable to get the pwrcl vreg, rc=%d\n",
+				rc);
+		return rc;
+	}
+	pwrcl_clk.vdd_reg = vdd_pwrcl;
+
+	vdd_perfcl = devm_regulator_get(&pdev->dev, "vdd-perfcl");
+	if (IS_ERR(vdd_perfcl)) {
+		rc = PTR_ERR(vdd_perfcl);
+		if (rc != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "Unable to get the perfcl vreg, rc=%d\n",
+				rc);
+		return rc;
+	}
+	perfcl_clk.vdd_reg = vdd_perfcl;
+
+	node = of_parse_phandle(pdev->dev.of_node, "vdd-l3-supply", 0);
+	if (!node) {
+		pr_err("Unable to find vdd-l3-supply\n");
+		return -EINVAL;
+	}
+
+	l3_clk.vdd_dev = of_find_device_by_node(node->parent->parent);
+	if (!l3_clk.vdd_dev) {
+		pr_err("Unable to find device for vdd-l3-supply node\n");
+		return -EINVAL;
+	}
+
+	node = of_parse_phandle(pdev->dev.of_node, "vdd-pwrcl-supply", 0);
+	if (!node) {
+		pr_err("Unable to find vdd-pwrcl-supply\n");
+		return -EINVAL;
+	}
+
+	pwrcl_clk.vdd_dev = of_find_device_by_node(node->parent->parent);
+	if (!pwrcl_clk.vdd_dev) {
+		pr_err("Unable to find device for vdd-pwrcl-supply node\n");
+		return -EINVAL;
+	}
+
+	node = of_parse_phandle(pdev->dev.of_node, "vdd-perfcl-supply", 0);
+	if (!node) {
+		pr_err("Unable to find vdd-perfcl-supply\n");
+		return -EINVAL;
+	}
+
+	perfcl_clk.vdd_dev = of_find_device_by_node(node->parent->parent);
+	if (!perfcl_clk.vdd_dev) {
+		pr_err("Unable to find device for vdd-perfcl-supply\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static unsigned long init_rate = 300000000;
+
+static int clk_cpu_osm_driver_probe(struct platform_device *pdev)
+{
+	int rc = 0, cpu, i;
+	int speedbin = 0, pvs_ver = 0;
+	u32 pte_efuse, val;
+	int num_clks = ARRAY_SIZE(osm_qcom_clk_hws);
+	struct clk *ext_xo_clk, *clk;
+	struct clk_osm *c;
+	struct device *dev = &pdev->dev;
+	struct clk_onecell_data *clk_data;
+	struct resource *res;
+	void *vbase;
+	char l3speedbinstr[] = "qcom,l3-speedbin0-v0";
+	char perfclspeedbinstr[] = "qcom,perfcl-speedbin0-v0";
+	char pwrclspeedbinstr[] = "qcom,pwrcl-speedbin0-v0";
+	struct cpu_cycle_counter_cb cb = {
+		.get_cpu_cycle_counter = clk_osm_get_cpu_cycle_counter,
+	};
+
+	/*
+	 * Require the RPM-XO clock to be registered before OSM.
+	 * The cpuss_gpll0_clk_src is listed to be configured by BL.
+	 */
+	ext_xo_clk = devm_clk_get(dev, "xo_ao");
+	if (IS_ERR(ext_xo_clk)) {
+		if (PTR_ERR(ext_xo_clk) != -EPROBE_DEFER)
+			dev_err(dev, "Unable to get xo clock\n");
+		return PTR_ERR(ext_xo_clk);
+	}
+
+	clk_data = devm_kzalloc(&pdev->dev, sizeof(struct clk_onecell_data),
+								GFP_KERNEL);
+	if (!clk_data)
+		goto exit;
+
+	clk_data->clks = devm_kzalloc(&pdev->dev, (num_clks *
+					sizeof(struct clk *)), GFP_KERNEL);
+	if (!clk_data->clks)
+		goto clk_err;
+
+	clk_data->clk_num = num_clks;
+
+	rc = clk_osm_parse_dt_configs(pdev);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to parse device tree configurations\n");
+		return rc;
+	}
+
+	rc = clk_osm_resources_init(pdev);
+	if (rc) {
+		if (rc != -EPROBE_DEFER)
+			dev_err(&pdev->dev, "resources init failed, rc=%d\n",
+									rc);
+		return rc;
+	}
+
+	if (l3_clk.vbases[EFUSE_BASE]) {
+		/* Multiple speed-bins are supported */
+		pte_efuse = readl_relaxed(l3_clk.vbases[EFUSE_BASE]);
+		l3_clk.speedbin = ((pte_efuse >> L3_EFUSE_SHIFT) &
+						    L3_EFUSE_MASK);
+		snprintf(l3speedbinstr, ARRAY_SIZE(l3speedbinstr),
+			 "qcom,l3-speedbin%d-v%d", speedbin, pvs_ver);
+	}
+
+	dev_info(&pdev->dev, "using L3 speed bin %u and pvs_ver %d\n",
+		 speedbin, pvs_ver);
+
+	rc = clk_osm_get_lut(pdev, &l3_clk, l3speedbinstr);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to get OSM LUT for L3, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	if (pwrcl_clk.vbases[EFUSE_BASE]) {
+		/* Multiple speed-bins are supported */
+		pte_efuse = readl_relaxed(pwrcl_clk.vbases[EFUSE_BASE]);
+		pwrcl_clk.speedbin = ((pte_efuse >> PWRCL_EFUSE_SHIFT) &
+						    PWRCL_EFUSE_MASK);
+		snprintf(pwrclspeedbinstr, ARRAY_SIZE(pwrclspeedbinstr),
+			 "qcom,pwrcl-speedbin%d-v%d", speedbin, pvs_ver);
+	}
+
+	dev_info(&pdev->dev, "using pwrcl speed bin %u and pvs_ver %d\n",
+		 speedbin, pvs_ver);
+
+	rc = clk_osm_get_lut(pdev, &pwrcl_clk, pwrclspeedbinstr);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to get OSM LUT for power cluster, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	if (perfcl_clk.vbases[EFUSE_BASE]) {
+		/* Multiple speed-bins are supported */
+		pte_efuse = readl_relaxed(perfcl_clk.vbases[EFUSE_BASE]);
+		perfcl_clk.speedbin = ((pte_efuse >> PERFCL_EFUSE_SHIFT) &
+							PERFCL_EFUSE_MASK);
+		snprintf(perfclspeedbinstr, ARRAY_SIZE(perfclspeedbinstr),
+			 "qcom,perfcl-speedbin%d-v%d", speedbin, pvs_ver);
+	}
+
+	dev_info(&pdev->dev, "using perfcl speed bin %u and pvs_ver %d\n",
+		 speedbin, pvs_ver);
+
+	rc = clk_osm_get_lut(pdev, &perfcl_clk, perfclspeedbinstr);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to get OSM LUT for perf cluster, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = clk_osm_resolve_open_loop_voltages(&l3_clk);
+	if (rc) {
+		if (rc == -EPROBE_DEFER)
+			return rc;
+		dev_err(&pdev->dev, "Unable to determine open-loop voltages for L3, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = clk_osm_resolve_open_loop_voltages(&pwrcl_clk);
+	if (rc) {
+		if (rc == -EPROBE_DEFER)
+			return rc;
+		dev_err(&pdev->dev, "Unable to determine open-loop voltages for power cluster, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = clk_osm_resolve_open_loop_voltages(&perfcl_clk);
+	if (rc) {
+		if (rc == -EPROBE_DEFER)
+			return rc;
+		dev_err(&pdev->dev, "Unable to determine open-loop voltages for perf cluster, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = clk_osm_resolve_crossover_corners(&l3_clk, pdev);
+	if (rc)
+		dev_info(&pdev->dev,
+			"No APM crossover corner programmed for L3\n");
+
+	rc = clk_osm_resolve_crossover_corners(&pwrcl_clk, pdev);
+	if (rc)
+		dev_info(&pdev->dev,
+			"No APM crossover corner programmed for pwrcl_clk\n");
+
+	rc = clk_osm_resolve_crossover_corners(&perfcl_clk, pdev);
+	if (rc)
+		dev_info(&pdev->dev, "No MEM-ACC crossover corner programmed\n");
+
+	clk_osm_setup_cycle_counters(&l3_clk);
+	clk_osm_setup_cycle_counters(&pwrcl_clk);
+	clk_osm_setup_cycle_counters(&perfcl_clk);
+
+	clk_osm_print_osm_table(&l3_clk);
+	clk_osm_print_osm_table(&pwrcl_clk);
+	clk_osm_print_osm_table(&perfcl_clk);
+
+	rc = clk_osm_setup_hw_table(&l3_clk);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to setup l3 hardware table\n");
+		goto exit;
+	}
+	rc = clk_osm_setup_hw_table(&pwrcl_clk);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to setup power cluster hardware table\n");
+		goto exit;
+	}
+	rc = clk_osm_setup_hw_table(&perfcl_clk);
+	if (rc) {
+		dev_err(&pdev->dev, "failed to setup perf cluster hardware table\n");
+		goto exit;
+	}
+
+	/* Policy tuning */
+	rc = clk_osm_set_cc_policy(pdev);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "cc policy setup failed");
+		goto exit;
+	}
+
+	/* LLM Freq Policy Tuning */
+	rc = clk_osm_set_llm_freq_policy(pdev);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "LLM Frequency Policy setup failed");
+		goto exit;
+	}
+
+	/* LLM Voltage Policy Tuning */
+	rc = clk_osm_set_llm_volt_policy(pdev);
+	if (rc < 0) {
+		dev_err(&pdev->dev, "Failed to set LLM voltage Policy");
+		goto exit;
+	}
+
+	clk_osm_setup_fsms(&l3_clk);
+	clk_osm_setup_fsms(&pwrcl_clk);
+	clk_osm_setup_fsms(&perfcl_clk);
+
+	/* Program VC at which the array power supply needs to be switched */
+	clk_osm_write_reg(&perfcl_clk, perfcl_clk.apm_threshold_vc,
+					APM_CROSSOVER_VC);
+	if (perfcl_clk.secure_init) {
+		clk_osm_write_seq_reg(&perfcl_clk, perfcl_clk.apm_crossover_vc,
+				DATA_MEM(77));
+		clk_osm_write_seq_reg(&perfcl_clk,
+				(0x39 | (perfcl_clk.apm_threshold_vc << 6)),
+				DATA_MEM(111));
+	} else {
+		scm_io_write(perfcl_clk.pbases[SEQ_BASE] + DATA_MEM(77),
+				perfcl_clk.apm_crossover_vc);
+		scm_io_write(perfcl_clk.pbases[SEQ_BASE] + DATA_MEM(111),
+				(0x39 | (perfcl_clk.apm_threshold_vc << 6)));
+	}
+
+	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+							"apps_itm_ctl");
+	if (!res) {
+		dev_err(&pdev->dev,
+			"Unable to get platform resource for apps_itm_ctl\n");
+		return -ENOMEM;
+	}
+
+	vbase = devm_ioremap(&pdev->dev, res->start,
+						resource_size(res));
+	if (!vbase) {
+		dev_err(&pdev->dev,
+				"Unable to map in apps_itm_ctl base\n");
+		return -ENOMEM;
+	}
+
+	val = readl_relaxed(vbase + 0x0);
+	val &= ~BIT(0);
+	writel_relaxed(val, vbase + 0x0);
+
+	val = readl_relaxed(vbase + 0x4);
+	val &= ~BIT(0);
+	writel_relaxed(val, vbase + 0x4);
+
+	/*
+	 * Perform typical secure-world HW initialization
+	 * as necessary.
+	 */
+	clk_osm_do_additional_setup(&l3_clk, pdev);
+	clk_osm_do_additional_setup(&pwrcl_clk, pdev);
+	clk_osm_do_additional_setup(&perfcl_clk, pdev);
+
+	/* MEM-ACC Programming */
+	clk_osm_program_mem_acc_regs(&l3_clk);
+	clk_osm_program_mem_acc_regs(&pwrcl_clk);
+	clk_osm_program_mem_acc_regs(&perfcl_clk);
+
+	if (of_property_read_bool(pdev->dev.of_node, "qcom,osm-pll-setup")) {
+		clk_osm_setup_cluster_pll(&l3_clk);
+		clk_osm_setup_cluster_pll(&pwrcl_clk);
+		clk_osm_setup_cluster_pll(&perfcl_clk);
+	}
+
+	/* Misc programming */
+	clk_osm_misc_programming(&l3_clk);
+	clk_osm_misc_programming(&pwrcl_clk);
+	clk_osm_misc_programming(&perfcl_clk);
+
+	if (of_property_read_bool(pdev->dev.of_node,
+				"qcom,enable-per-core-dcvs")) {
+		val = clk_osm_read_reg(&pwrcl_clk, CORE_DCVS_CTRL);
+		val |= BIT(0);
+		clk_osm_write_reg(&pwrcl_clk, val, CORE_DCVS_CTRL);
+
+		val = clk_osm_read_reg(&perfcl_clk, CORE_DCVS_CTRL);
+		val |= BIT(0);
+		clk_osm_write_reg(&perfcl_clk, val, CORE_DCVS_CTRL);
+	}
+
+	clk_ops_core = clk_dummy_ops;
+	clk_ops_core.set_rate = cpu_clk_set_rate;
+	clk_ops_core.recalc_rate = cpu_clk_recalc_rate;
+
+	spin_lock_init(&l3_clk.lock);
+	spin_lock_init(&pwrcl_clk.lock);
+	spin_lock_init(&perfcl_clk.lock);
+
+	/* Register OSM l3, pwr and perf clocks with Clock Framework */
+	for (i = 0; i < num_clks; i++) {
+		clk = devm_clk_register(&pdev->dev, osm_qcom_clk_hws[i]);
+		if (IS_ERR(clk)) {
+			dev_err(&pdev->dev, "Unable to register CPU clock at index %d\n",
+				i);
+			return PTR_ERR(clk);
+		}
+		clk_data->clks[i] = clk;
+	}
+
+	rc = of_clk_add_provider(pdev->dev.of_node, of_clk_src_onecell_get,
+								clk_data);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to register CPU clocks\n");
+			goto provider_err;
+	}
+
+	get_online_cpus();
+
+	/* Enable OSM */
+	for_each_online_cpu(cpu) {
+		c = logical_cpu_to_clk(cpu);
+		if (!c) {
+			pr_err("no clock device for CPU=%d\n", cpu);
+			return -EINVAL;
+		}
+
+		rc = clk_set_rate(c->hw.clk, init_rate);
+		if (rc) {
+			dev_err(&pdev->dev, "Unable to set init rate on CPU %d, rc=%d\n",
+			cpu, rc);
+			goto provider_err;
+		}
+		WARN(clk_prepare_enable(c->hw.clk),
+		     "Failed to enable clock for cpu %d\n", cpu);
+		udelay(300);
+	}
+
+	rc = clk_set_rate(l3_clk.hw.clk, init_rate);
+	if (rc) {
+		dev_err(&pdev->dev, "Unable to set init rate on L3 cluster, rc=%d\n",
+			rc);
+		goto provider_err;
+	}
+	WARN(clk_prepare_enable(l3_clk.hw.clk),
+		     "Failed to enable clock for L3\n");
+	udelay(300);
+
+	populate_opp_table(pdev);
+
+	of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
+	register_cpu_cycle_counter_cb(&cb);
+	pr_info("OSM driver inited\n");
+	put_online_cpus();
+
+	return 0;
+provider_err:
+	if (clk_data)
+		devm_kfree(&pdev->dev, clk_data->clks);
+clk_err:
+	devm_kfree(&pdev->dev, clk_data);
+exit:
+	dev_err(&pdev->dev, "OSM driver failed to initialize, rc=%d\n", rc);
+	panic("Unable to Setup OSM");
+}
+
+static const struct of_device_id match_table[] = {
+	{ .compatible = "qcom,clk-cpu-osm" },
+	{}
+};
+
+static struct platform_driver clk_cpu_osm_driver = {
+	.probe = clk_cpu_osm_driver_probe,
+	.driver = {
+		.name = "clk-cpu-osm",
+		.of_match_table = match_table,
+		.owner = THIS_MODULE,
+	},
+};
+
+static int __init clk_cpu_osm_init(void)
+{
+	return platform_driver_register(&clk_cpu_osm_driver);
+}
+arch_initcall(clk_cpu_osm_init);
+
+static void __exit clk_cpu_osm_exit(void)
+{
+	platform_driver_unregister(&clk_cpu_osm_driver);
+}
+module_exit(clk_cpu_osm_exit);
+
+MODULE_DESCRIPTION("QTI CPU clock driver for OSM");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/clk/qcom/common.h b/drivers/clk/qcom/common.h
index eface18..e728dec 100644
--- a/drivers/clk/qcom/common.h
+++ b/drivers/clk/qcom/common.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2014-2017, The Linux Foundation. All rights reserved.
  *
  * This software is licensed under the terms of the GNU General Public
  * License version 2, as published by the Free Software Foundation, and
@@ -57,4 +57,8 @@
 extern int qcom_cc_probe(struct platform_device *pdev,
 			 const struct qcom_cc_desc *desc);
 extern struct clk_ops clk_dummy_ops;
+
+#define BM(msb, lsb)	(((((uint32_t)-1) << (31-msb)) >> (31-msb+lsb)) << lsb)
+#define BVAL(msb, lsb, val)	(((val) << lsb) & BM(msb, lsb))
+
 #endif
diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c
index f7e1c1b..66e604e 100644
--- a/drivers/cpufreq/cpufreq.c
+++ b/drivers/cpufreq/cpufreq.c
@@ -752,9 +752,11 @@
 					char *buf)
 {
 	unsigned int cur_freq = __cpufreq_get(policy);
-	if (!cur_freq)
-		return sprintf(buf, "<unknown>");
-	return sprintf(buf, "%u\n", cur_freq);
+
+	if (cur_freq)
+		return sprintf(buf, "%u\n", cur_freq);
+
+	return sprintf(buf, "<unknown>\n");
 }
 
 /**
diff --git a/drivers/cpufreq/cpufreq_interactive.c b/drivers/cpufreq/cpufreq_interactive.c
index 1b8c739..12eb6d8 100644
--- a/drivers/cpufreq/cpufreq_interactive.c
+++ b/drivers/cpufreq/cpufreq_interactive.c
@@ -35,8 +35,12 @@
 #define CREATE_TRACE_POINTS
 #include <trace/events/cpufreq_interactive.h>
 
+static DEFINE_PER_CPU(struct update_util_data, update_util);
+
 struct cpufreq_interactive_policyinfo {
-	struct timer_list policy_timer;
+	bool work_in_progress;
+	struct irq_work irq_work;
+	spinlock_t irq_work_lock; /* protects work_in_progress */
 	struct timer_list policy_slack_timer;
 	struct hrtimer notif_timer;
 	spinlock_t load_lock; /* protects load tracking stat */
@@ -215,9 +219,6 @@
 			pcpu->cputime_speedadj_timestamp =
 						pcpu->time_in_idle_timestamp;
 		}
-		del_timer(&ppol->policy_timer);
-		ppol->policy_timer.expires = expires;
-		add_timer(&ppol->policy_timer);
 	}
 
 	if (tunables->timer_slack_val >= 0 &&
@@ -231,9 +232,51 @@
 	spin_unlock_irqrestore(&ppol->load_lock, flags);
 }
 
+static void update_util_handler(struct update_util_data *data, u64 time,
+				unsigned int sched_flags)
+{
+	struct cpufreq_interactive_policyinfo *ppol;
+	unsigned long flags;
+
+	ppol = *this_cpu_ptr(&polinfo);
+	spin_lock_irqsave(&ppol->irq_work_lock, flags);
+	/*
+	 * The irq-work may not be allowed to be queued up right now
+	 * because work has already been queued up or is in progress.
+	 */
+	if (ppol->work_in_progress ||
+	    sched_flags & SCHED_CPUFREQ_INTERCLUSTER_MIG)
+		goto out;
+
+	ppol->work_in_progress = true;
+	irq_work_queue(&ppol->irq_work);
+out:
+	spin_unlock_irqrestore(&ppol->irq_work_lock, flags);
+}
+
+static inline void gov_clear_update_util(struct cpufreq_policy *policy)
+{
+	int i;
+
+	for_each_cpu(i, policy->cpus)
+		cpufreq_remove_update_util_hook(i);
+
+	synchronize_sched();
+}
+
+static void gov_set_update_util(struct cpufreq_policy *policy)
+{
+	struct update_util_data *util;
+	int cpu;
+
+	for_each_cpu(cpu, policy->cpus) {
+		util = &per_cpu(update_util, cpu);
+		cpufreq_add_update_util_hook(cpu, util, update_util_handler);
+	}
+}
+
 /* The caller shall take enable_sem write semaphore to avoid any timer race.
- * The policy_timer and policy_slack_timer must be deactivated when calling
- * this function.
+ * The policy_slack_timer must be deactivated when calling this function.
  */
 static void cpufreq_interactive_timer_start(
 	struct cpufreq_interactive_tunables *tunables, int cpu)
@@ -245,8 +288,7 @@
 	int i;
 
 	spin_lock_irqsave(&ppol->load_lock, flags);
-	ppol->policy_timer.expires = expires;
-	add_timer(&ppol->policy_timer);
+	gov_set_update_util(ppol->policy);
 	if (tunables->timer_slack_val >= 0 &&
 	    ppol->target_freq > ppol->policy->min) {
 		expires += usecs_to_jiffies(tunables->timer_slack_val);
@@ -265,6 +307,7 @@
 	spin_unlock_irqrestore(&ppol->load_lock, flags);
 }
 
+
 static unsigned int freq_to_above_hispeed_delay(
 	struct cpufreq_interactive_tunables *tunables,
 	unsigned int freq)
@@ -448,7 +491,7 @@
 
 #define NEW_TASK_RATIO 75
 #define PRED_TOLERANCE_PCT 10
-static void cpufreq_interactive_timer(unsigned long data)
+static void cpufreq_interactive_timer(int data)
 {
 	s64 now;
 	unsigned int delta_time;
@@ -467,7 +510,7 @@
 	unsigned int index;
 	unsigned long flags;
 	unsigned long max_cpu;
-	int cpu, i;
+	int i, cpu;
 	int new_load_pct = 0;
 	int prev_l, pred_l = 0;
 	struct cpufreq_govinfo govinfo;
@@ -659,8 +702,7 @@
 	wake_up_process_no_notif(speedchange_task);
 
 rearm:
-	if (!timer_pending(&ppol->policy_timer))
-		cpufreq_interactive_timer_resched(data, false);
+	cpufreq_interactive_timer_resched(data, false);
 
 	/*
 	 * Send govinfo notification.
@@ -822,7 +864,6 @@
 	}
 	cpu = ppol->notif_cpu;
 	trace_cpufreq_interactive_load_change(cpu);
-	del_timer(&ppol->policy_timer);
 	del_timer(&ppol->policy_slack_timer);
 	cpufreq_interactive_timer(cpu);
 
@@ -1569,6 +1610,20 @@
 	return tunables;
 }
 
+static void irq_work(struct irq_work *irq_work)
+{
+	struct cpufreq_interactive_policyinfo *ppol;
+	unsigned long flags;
+
+	ppol = container_of(irq_work, struct cpufreq_interactive_policyinfo,
+			    irq_work);
+
+	cpufreq_interactive_timer(smp_processor_id());
+	spin_lock_irqsave(&ppol->irq_work_lock, flags);
+	ppol->work_in_progress = false;
+	spin_unlock_irqrestore(&ppol->irq_work_lock, flags);
+}
+
 static struct cpufreq_interactive_policyinfo *get_policyinfo(
 					struct cpufreq_policy *policy)
 {
@@ -1593,12 +1648,12 @@
 	}
 	ppol->sl = sl;
 
-	init_timer_deferrable(&ppol->policy_timer);
-	ppol->policy_timer.function = cpufreq_interactive_timer;
 	init_timer(&ppol->policy_slack_timer);
 	ppol->policy_slack_timer.function = cpufreq_interactive_nop_timer;
 	hrtimer_init(&ppol->notif_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
 	ppol->notif_timer.function = cpufreq_interactive_hrtimer;
+	init_irq_work(&ppol->irq_work, irq_work);
+	spin_lock_init(&ppol->irq_work_lock);
 	spin_lock_init(&ppol->load_lock);
 	spin_lock_init(&ppol->target_freq_lock);
 	init_rwsem(&ppol->enable_sem);
@@ -1775,9 +1830,7 @@
 	ppol->reject_notification = true;
 	ppol->notif_pending = false;
 	down_write(&ppol->enable_sem);
-	del_timer_sync(&ppol->policy_timer);
 	del_timer_sync(&ppol->policy_slack_timer);
-	ppol->policy_timer.data = policy->cpu;
 	ppol->last_evaluated_jiffy = get_jiffies_64();
 	cpufreq_interactive_timer_start(tunables, policy->cpu);
 	ppol->governor_enabled = 1;
@@ -1807,7 +1860,9 @@
 	down_write(&ppol->enable_sem);
 	ppol->governor_enabled = 0;
 	ppol->target_freq = 0;
-	del_timer_sync(&ppol->policy_timer);
+	gov_clear_update_util(ppol->policy);
+	irq_work_sync(&ppol->irq_work);
+	ppol->work_in_progress = false;
 	del_timer_sync(&ppol->policy_slack_timer);
 	up_write(&ppol->enable_sem);
 	ppol->reject_notification = false;
diff --git a/drivers/cpufreq/qcom-cpufreq.c b/drivers/cpufreq/qcom-cpufreq.c
index 0caa8d1..f968ffd9 100644
--- a/drivers/cpufreq/qcom-cpufreq.c
+++ b/drivers/cpufreq/qcom-cpufreq.c
@@ -364,7 +364,7 @@
 	char clk_name[] = "cpu??_clk";
 	char tbl_name[] = "qcom,cpufreq-table-??";
 	struct clk *c;
-	int cpu;
+	int cpu, ret;
 	struct cpufreq_frequency_table *ftbl;
 
 	l2_clk = devm_clk_get(dev, "l2_clk");
@@ -431,7 +431,15 @@
 		per_cpu(freq_table, cpu) = ftbl;
 	}
 
-	return 0;
+	ret = register_pm_notifier(&msm_cpufreq_pm_notifier);
+	if (ret)
+		return ret;
+
+	ret = cpufreq_register_driver(&msm_cpufreq_driver);
+	if (ret)
+		unregister_pm_notifier(&msm_cpufreq_pm_notifier);
+
+	return ret;
 }
 
 static const struct of_device_id msm_cpufreq_match_table[] = {
@@ -467,8 +475,7 @@
 		return rc;
 	}
 
-	register_pm_notifier(&msm_cpufreq_pm_notifier);
-	return cpufreq_register_driver(&msm_cpufreq_driver);
+	return 0;
 }
 
 subsys_initcall(msm_cpufreq_register);
diff --git a/drivers/gpu/drm/amd/amdgpu/si_dpm.c b/drivers/gpu/drm/amd/amdgpu/si_dpm.c
index b447a01..09e6a73 100644
--- a/drivers/gpu/drm/amd/amdgpu/si_dpm.c
+++ b/drivers/gpu/drm/amd/amdgpu/si_dpm.c
@@ -3506,6 +3506,12 @@
 			max_sclk = 75000;
 			max_mclk = 80000;
 		}
+	} else if (adev->asic_type == CHIP_OLAND) {
+		if ((adev->pdev->device == 0x6604) &&
+		    (adev->pdev->subsystem_vendor == 0x1028) &&
+		    (adev->pdev->subsystem_device == 0x066F)) {
+			max_sclk = 75000;
+		}
 	}
 	/* Apply dpm quirks */
 	while (p && p->chip_device != 0) {
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h
index 0e2e7ec..2a84a2d 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk.h
@@ -19,6 +19,7 @@
 #include <linux/platform_device.h>
 #include <linux/types.h>
 #include <linux/clk.h>
+#include "sde_power_handle.h"
 
 #define MAX_STRING_LEN 32
 #define MAX_DSI_CTRL 2
@@ -67,6 +68,8 @@
  * @core_mmss_clk:       Handle to MMSS core clock.
  * @bus_clk:             Handle to bus clock.
  * @mnoc_clk:            Handle to MMSS NOC clock.
+ * @dsi_core_client:	 Pointer to SDE power client
+ * @phandle:             Pointer to SDE power handle
  */
 struct dsi_core_clk_info {
 	struct clk *mdp_core_clk;
@@ -74,6 +77,8 @@
 	struct clk *core_mmss_clk;
 	struct clk *bus_clk;
 	struct clk *mnoc_clk;
+	struct sde_power_client *dsi_core_client;
+	struct sde_power_handle *phandle;
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
index 9650a0b..2fcf10ba 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_clk_manager.c
@@ -185,10 +185,12 @@
 {
 	int rc = 0;
 
-	rc = clk_prepare_enable(c_clks->clks.mdp_core_clk);
-	if (rc) {
-		pr_err("failed to enable mdp_core_clk, rc=%d\n", rc);
-		goto error;
+	if (c_clks->clks.mdp_core_clk) {
+		rc = clk_prepare_enable(c_clks->clks.mdp_core_clk);
+		if (rc) {
+			pr_err("failed to enable mdp_core_clk, rc=%d\n", rc);
+			goto error;
+		}
 	}
 
 	if (c_clks->clks.mnoc_clk) {
@@ -199,28 +201,36 @@
 		}
 	}
 
-	rc = clk_prepare_enable(c_clks->clks.iface_clk);
-	if (rc) {
-		pr_err("failed to enable iface_clk, rc=%d\n", rc);
-		goto error_disable_mnoc_clk;
+	if (c_clks->clks.iface_clk) {
+		rc = clk_prepare_enable(c_clks->clks.iface_clk);
+		if (rc) {
+			pr_err("failed to enable iface_clk, rc=%d\n", rc);
+			goto error_disable_mnoc_clk;
+		}
 	}
 
-	rc = clk_prepare_enable(c_clks->clks.bus_clk);
-	if (rc) {
-		pr_err("failed to enable bus_clk, rc=%d\n", rc);
-		goto error_disable_iface_clk;
+	if (c_clks->clks.bus_clk) {
+		rc = clk_prepare_enable(c_clks->clks.bus_clk);
+		if (rc) {
+			pr_err("failed to enable bus_clk, rc=%d\n", rc);
+			goto error_disable_iface_clk;
+		}
 	}
 
-	rc = clk_prepare_enable(c_clks->clks.core_mmss_clk);
-	if (rc) {
-		pr_err("failed to enable core_mmss_clk, rc=%d\n", rc);
-		goto error_disable_bus_clk;
+	if (c_clks->clks.core_mmss_clk) {
+		rc = clk_prepare_enable(c_clks->clks.core_mmss_clk);
+		if (rc) {
+			pr_err("failed to enable core_mmss_clk, rc=%d\n", rc);
+			goto error_disable_bus_clk;
+		}
 	}
 
-	rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 1);
-	if (rc) {
-		pr_err("bus scale client enable failed, rc=%d\n", rc);
-		goto error_disable_mmss_clk;
+	if (c_clks->bus_handle) {
+		rc = msm_bus_scale_client_update_request(c_clks->bus_handle, 1);
+		if (rc) {
+			pr_err("bus scale client enable failed, rc=%d\n", rc);
+			goto error_disable_mmss_clk;
+		}
 	}
 
 	return rc;
@@ -458,11 +468,18 @@
 	 */
 
 	m_clks = &clks[master_ndx];
+	rc = sde_power_resource_enable(m_clks->clks.phandle,
+			m_clks->clks.dsi_core_client, true);
+
+	if (rc) {
+		pr_err("Power resource enable failed, rc=%d\n", rc);
+		goto error;
+	}
 
 	rc = dsi_core_clk_start(m_clks);
 	if (rc) {
 		pr_err("failed to turn on master clocks, rc=%d\n", rc);
-		goto error;
+		goto error_disable_master_resource;
 	}
 
 	/* Turn on rest of the core clocks */
@@ -471,15 +488,28 @@
 		if (!clk || (clk == m_clks))
 			continue;
 
+		rc = sde_power_resource_enable(clk->clks.phandle,
+				clk->clks.dsi_core_client, true);
+		if (rc) {
+			pr_err("Power resource enable failed, rc=%d\n", rc);
+			goto error_disable_master;
+		}
+
 		rc = dsi_core_clk_start(clk);
 		if (rc) {
 			pr_err("failed to turn on clocks, rc=%d\n", rc);
+			(void)sde_power_resource_enable(clk->clks.phandle,
+					clk->clks.dsi_core_client, false);
 			goto error_disable_master;
 		}
 	}
-	return rc;
+
 error_disable_master:
 	(void)dsi_core_clk_stop(m_clks);
+
+error_disable_master_resource:
+	(void)sde_power_resource_enable(m_clks->clks.phandle,
+				m_clks->clks.dsi_core_client, false);
 error:
 	return rc;
 }
@@ -547,14 +577,30 @@
 			continue;
 
 		rc = dsi_core_clk_stop(clk);
-		if (rc)
-			pr_err("failed to turn off clocks, rc=%d\n", rc);
+		if (rc) {
+			pr_debug("failed to turn off clocks, rc=%d\n", rc);
+			goto error;
+		}
+
+		rc = sde_power_resource_enable(clk->clks.phandle,
+				clk->clks.dsi_core_client, false);
+		if (rc) {
+			pr_err("Power resource disable failed: %d\n", rc);
+			goto error;
+		}
 	}
 
 	rc = dsi_core_clk_stop(m_clks);
-	if (rc)
+	if (rc) {
 		pr_err("failed to turn off master clocks, rc=%d\n", rc);
+		goto error;
+	}
 
+	rc = sde_power_resource_enable(m_clks->clks.phandle,
+				m_clks->clks.dsi_core_client, false);
+	if (rc)
+		pr_err("Power resource disable failed: %d\n", rc);
+error:
 	return rc;
 }
 
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c
index cd851bc..5df48c3 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_ctrl.c
@@ -490,30 +490,26 @@
 
 	core->mdp_core_clk = devm_clk_get(&pdev->dev, "mdp_core_clk");
 	if (IS_ERR(core->mdp_core_clk)) {
-		rc = PTR_ERR(core->mdp_core_clk);
-		pr_err("failed to get mdp_core_clk, rc=%d\n", rc);
-		goto fail;
+		core->mdp_core_clk = NULL;
+		pr_debug("failed to get mdp_core_clk, rc=%d\n", rc);
 	}
 
 	core->iface_clk = devm_clk_get(&pdev->dev, "iface_clk");
 	if (IS_ERR(core->iface_clk)) {
-		rc = PTR_ERR(core->iface_clk);
-		pr_err("failed to get iface_clk, rc=%d\n", rc);
-		goto fail;
+		core->iface_clk = NULL;
+		pr_debug("failed to get iface_clk, rc=%d\n", rc);
 	}
 
 	core->core_mmss_clk = devm_clk_get(&pdev->dev, "core_mmss_clk");
 	if (IS_ERR(core->core_mmss_clk)) {
-		rc = PTR_ERR(core->core_mmss_clk);
-		pr_err("failed to get core_mmss_clk, rc=%d\n", rc);
-		goto fail;
+		core->core_mmss_clk = NULL;
+		pr_debug("failed to get core_mmss_clk, rc=%d\n", rc);
 	}
 
 	core->bus_clk = devm_clk_get(&pdev->dev, "bus_clk");
 	if (IS_ERR(core->bus_clk)) {
-		rc = PTR_ERR(core->bus_clk);
-		pr_err("failed to get bus_clk, rc=%d\n", rc);
-		goto fail;
+		core->bus_clk = NULL;
+		pr_debug("failed to get bus_clk, rc=%d\n", rc);
 	}
 
 	core->mnoc_clk = devm_clk_get(&pdev->dev, "mnoc_clk");
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
index ddf791c..bcaf428 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.c
@@ -2228,10 +2228,12 @@
 	struct dsi_display *display;
 	struct dsi_clk_info info;
 	struct clk_ctrl_cb clk_cb;
+	struct msm_drm_private *priv;
 	void *handle = NULL;
 	struct platform_device *pdev = to_platform_device(dev);
 	char *client1 = "dsi_clk_client";
 	char *client2 = "mdp_event_client";
+	char dsi_client_name[DSI_CLIENT_NAME_SIZE];
 	int i, rc = 0;
 
 	if (!dev || !pdev || !master) {
@@ -2247,6 +2249,7 @@
 				drm, display);
 		return -EINVAL;
 	}
+	priv = drm->dev_private;
 
 	mutex_lock(&display->display_lock);
 
@@ -2260,7 +2263,6 @@
 
 	for (i = 0; i < display->ctrl_count; i++) {
 		display_ctrl = &display->ctrl[i];
-
 		rc = dsi_ctrl_drv_init(display_ctrl->ctrl, display->root);
 		if (rc) {
 			pr_err("[%s] failed to initialize ctrl[%d], rc=%d\n",
@@ -2280,9 +2282,19 @@
 			sizeof(struct dsi_core_clk_info));
 		memcpy(&info.l_clks[i], &display_ctrl->ctrl->clk_info.link_clks,
 			sizeof(struct dsi_link_clk_info));
+		info.c_clks[i].phandle = &priv->phandle;
 		info.bus_handle[i] =
 			display_ctrl->ctrl->axi_bus_info.bus_handle;
 		info.ctrl_index[i] = display_ctrl->ctrl->cell_index;
+		snprintf(dsi_client_name, DSI_CLIENT_NAME_SIZE,
+						"dsi_core_client%u", i);
+		info.c_clks[i].dsi_core_client = sde_power_client_create(
+				info.c_clks[i].phandle, dsi_client_name);
+		if (IS_ERR_OR_NULL(info.c_clks[i].dsi_core_client)) {
+			pr_err("[%s] client creation failed for ctrl[%d]",
+					dsi_client_name, i);
+			goto error_ctrl_deinit;
+		}
 	}
 
 	info.pre_clkoff_cb = dsi_pre_clkoff_cb;
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
index 89bba96..cfbb14ec 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_display.h
@@ -29,7 +29,7 @@
 #include "dsi_panel.h"
 
 #define MAX_DSI_CTRLS_PER_DISPLAY             2
-
+#define DSI_CLIENT_NAME_SIZE		20
 /*
  * DSI Validate Mode modifiers
  * @DSI_VALIDATE_FLAG_ALLOW_ADJUST:	Allow mode validation to also do fixup
diff --git a/drivers/gpu/drm/msm/msm_drv.h b/drivers/gpu/drm/msm/msm_drv.h
index a01383b..b51187d 100644
--- a/drivers/gpu/drm/msm/msm_drv.h
+++ b/drivers/gpu/drm/msm/msm_drv.h
@@ -134,6 +134,7 @@
 	CRTC_PROP_CORE_CLK,
 	CRTC_PROP_CORE_AB,
 	CRTC_PROP_CORE_IB,
+	CRTC_PROP_ROT_PREFILL_BW,
 
 	/* total # of properties */
 	CRTC_PROP_COUNT
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 44f6169..b6a37b7 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -365,9 +365,11 @@
 		if (!sbuf_mode) {
 			cstate->sbuf_cfg.rot_op_mode =
 					SDE_CTL_ROT_OP_MODE_OFFLINE;
+			cstate->sbuf_prefill_line = 0;
 		} else {
 			cstate->sbuf_cfg.rot_op_mode =
 					SDE_CTL_ROT_OP_MODE_INLINE_SYNC;
+			cstate->sbuf_prefill_line = prefill;
 		}
 
 		ctl->ops.setup_sbuf_cfg(ctl, &cstate->sbuf_cfg);
@@ -1024,6 +1026,7 @@
 	struct sde_crtc *sde_crtc;
 	struct msm_drm_private *priv;
 	struct sde_kms *sde_kms;
+	struct sde_crtc_state *cstate;
 
 	if (!crtc) {
 		SDE_ERROR("invalid argument\n");
@@ -1033,8 +1036,11 @@
 	sde_crtc = to_sde_crtc(crtc);
 	sde_kms = _sde_crtc_get_kms(crtc);
 	priv = sde_kms->dev->dev_private;
+	cstate = to_sde_crtc_state(crtc->state);
 
 	list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
+		struct sde_encoder_kickoff_params params = { 0 };
+
 		if (encoder->crtc != crtc)
 			continue;
 
@@ -1042,7 +1048,8 @@
 		 * Encoder will flush/start now, unless it has a tx pending.
 		 * If so, it may delay and flush at an irq event (e.g. ppdone)
 		 */
-		sde_encoder_prepare_for_kickoff(encoder);
+		params.inline_rotate_prefill = cstate->sbuf_prefill_line;
+		sde_encoder_prepare_for_kickoff(encoder, &params);
 	}
 
 	if (atomic_read(&sde_crtc->frame_pending) > 2) {
@@ -1715,6 +1722,10 @@
 			"core_ib", 0x0, 0, U64_MAX,
 			SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA,
 			CRTC_PROP_CORE_IB);
+	msm_property_install_range(&sde_crtc->property_info,
+			"rot_prefill_bw", 0, 0, U64_MAX,
+			catalog->perf.max_bw_high * 1000ULL,
+			CRTC_PROP_ROT_PREFILL_BW);
 
 	msm_property_install_blob(&sde_crtc->property_info, "capabilities",
 		DRM_MODE_PROP_IMMUTABLE, CRTC_PROP_INFO);
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index d288752..f389196 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -202,6 +202,7 @@
  * @cur_perf: current performance state
  * @new_perf: new performance state
  * @sbuf_cfg: stream buffer configuration
+ * @sbuf_prefill_line: number of line for inline rotator prefetch
  */
 struct sde_crtc_state {
 	struct drm_crtc_state base;
@@ -221,6 +222,7 @@
 	struct sde_core_perf_params cur_perf;
 	struct sde_core_perf_params new_perf;
 	struct sde_ctl_sbuf_cfg sbuf_cfg;
+	u64 sbuf_prefill_line;
 };
 
 #define to_sde_crtc_state(x) \
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 7db44d3..69d21fb 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -1203,7 +1203,8 @@
 	spin_unlock_irqrestore(&sde_enc->enc_spinlock, lock_flags);
 }
 
-void sde_encoder_prepare_for_kickoff(struct drm_encoder *drm_enc)
+void sde_encoder_prepare_for_kickoff(struct drm_encoder *drm_enc,
+		struct sde_encoder_kickoff_params *params)
 {
 	struct sde_encoder_virt *sde_enc;
 	struct sde_encoder_phys *phys;
@@ -1224,7 +1225,7 @@
 		phys = sde_enc->phys_encs[i];
 		if (phys) {
 			if (phys->ops.prepare_for_kickoff)
-				phys->ops.prepare_for_kickoff(phys);
+				phys->ops.prepare_for_kickoff(phys, params);
 			if (phys->enable_state == SDE_ENC_ERR_NEEDS_HW_RESET)
 				needs_hw_reset = true;
 		}
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.h b/drivers/gpu/drm/msm/sde/sde_encoder.h
index bd7ef69..cdecd08 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.h
@@ -45,6 +45,14 @@
 };
 
 /**
+ * sde_encoder_kickoff_params - info encoder requires at kickoff
+ * @inline_rotate_prefill: number of lines to prefill for inline rotation
+ */
+struct sde_encoder_kickoff_params {
+	u32 inline_rotate_prefill;
+};
+
+/**
  * sde_encoder_get_hw_resources - Populate table of required hardware resources
  * @encoder:	encoder pointer
  * @hw_res:	resource table to populate with encoder required resources
@@ -89,8 +97,10 @@
  *	Immediately: if no previous commit is outstanding.
  *	Delayed: Block until next trigger can be issued.
  * @encoder:	encoder pointer
+ * @params:	kickoff time parameters
  */
-void sde_encoder_prepare_for_kickoff(struct drm_encoder *encoder);
+void sde_encoder_prepare_for_kickoff(struct drm_encoder *encoder,
+		struct sde_encoder_kickoff_params *params);
 
 /**
  * sde_encoder_kickoff - trigger a double buffer flip of the ctl path
@@ -116,6 +126,24 @@
 enum sde_intf_mode sde_encoder_get_intf_mode(struct drm_encoder *encoder);
 
 /**
+ * enum sde_encoder_property - property tags for sde enoder
+ * @SDE_ENCODER_PROPERTY_INLINE_ROTATE_REFILL: # of prefill line, 0 to disable
+ */
+enum sde_encoder_property {
+	SDE_ENCODER_PROPERTY_INLINE_ROTATE_PREFILL,
+	SDE_ENCODER_PROPERTY_MAX,
+};
+
+/*
+ * sde_encoder_set_property - set the property tag to the given value
+ * @encoder: Pointer to drm encoder object
+ * @tag: property tag
+ * @val: property value
+ * return: 0 if success; errror code otherwise
+ */
+int sde_encoder_set_property(struct drm_encoder *encoder, u32 tag, u64 val);
+
+/**
  * sde_encoder_init - initialize virtual encoder object
  * @dev:        Pointer to drm device structure
  * @disp_info:  Pointer to display information structure
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
index 6d50c53..7aa9a29 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys.h
@@ -141,7 +141,8 @@
 			struct drm_connector_state *conn_state);
 	int (*control_vblank_irq)(struct sde_encoder_phys *enc, bool enable);
 	int (*wait_for_commit_done)(struct sde_encoder_phys *phys_enc);
-	void (*prepare_for_kickoff)(struct sde_encoder_phys *phys_enc);
+	void (*prepare_for_kickoff)(struct sde_encoder_phys *phys_enc,
+			struct sde_encoder_kickoff_params *params);
 	void (*handle_post_kickoff)(struct sde_encoder_phys *phys_enc);
 	void (*trigger_start)(struct sde_encoder_phys *phys_enc);
 	bool (*needs_single_flush)(struct sde_encoder_phys *phys_enc);
@@ -238,12 +239,16 @@
  * @irq_idx:	IRQ interface lookup index
  * @irq_cb:	interrupt callback
  * @hw_intf:	Hardware interface to the intf registers
+ * @timing_params: Current timing parameter
+ * @rot_prefill_line: number of line to prefill for inline rotation; 0 disable
  */
 struct sde_encoder_phys_vid {
 	struct sde_encoder_phys base;
 	int irq_idx[INTR_IDX_MAX];
 	struct sde_irq_callback irq_cb[INTR_IDX_MAX];
 	struct sde_hw_intf *hw_intf;
+	struct intf_timing_params timing_params;
+	u64 rot_prefill_line;
 };
 
 /**
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
index afc21ed..86e292f 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_cmd.c
@@ -653,7 +653,8 @@
 }
 
 static void sde_encoder_phys_cmd_prepare_for_kickoff(
-		struct sde_encoder_phys *phys_enc)
+		struct sde_encoder_phys *phys_enc,
+		struct sde_encoder_kickoff_params *params)
 {
 	struct sde_encoder_phys_cmd *cmd_enc =
 			to_sde_encoder_phys_cmd(phys_enc);
@@ -687,7 +688,7 @@
 			to_sde_encoder_phys_cmd(phys_enc);
 
 	if (cmd_enc->serialize_wait4pp)
-		sde_encoder_phys_cmd_prepare_for_kickoff(phys_enc);
+		sde_encoder_phys_cmd_prepare_for_kickoff(phys_enc, NULL);
 
 	/*
 	 * following statement is true serialize_wait4pp is false.
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
index 01dd982..82d32dc 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_vid.c
@@ -211,6 +211,54 @@
 	spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
 }
 
+/*
+ * programmable_rot_fetch_config: Programs ROT to prefetch lines by offsetting
+ *	the start of fetch into the vertical front porch for cases where the
+ *	vsync pulse width and vertical back porch time is insufficient
+ *
+ *	Gets # of lines to pre-fetch, then calculate VSYNC counter value.
+ *	HW layer requires VSYNC counter of first pixel of tgt VFP line.
+ * @phys_enc: Pointer to physical encoder
+ * @rot_fetch_lines: number of line to prefill, or 0 to disable
+ */
+static void programmable_rot_fetch_config(struct sde_encoder_phys *phys_enc,
+		u64 rot_fetch_lines)
+{
+	struct sde_encoder_phys_vid *vid_enc =
+		to_sde_encoder_phys_vid(phys_enc);
+	struct intf_prog_fetch f = { 0 };
+	struct intf_timing_params *timing = &vid_enc->timing_params;
+	u32 vfp_fetch_lines = 0;
+	u32 horiz_total = 0;
+	u32 vert_total = 0;
+	u32 rot_fetch_start_vsync_counter = 0;
+	unsigned long lock_flags;
+
+	if (WARN_ON_ONCE(!vid_enc->hw_intf->ops.setup_rot_start))
+		return;
+
+	vfp_fetch_lines = programmable_fetch_get_num_lines(vid_enc, timing);
+	if (vfp_fetch_lines && rot_fetch_lines) {
+		vert_total = get_vertical_total(timing);
+		horiz_total = get_horizontal_total(timing);
+		if (vert_total >= (vfp_fetch_lines + rot_fetch_lines)) {
+			rot_fetch_start_vsync_counter =
+			    (vert_total - vfp_fetch_lines - rot_fetch_lines) *
+			    horiz_total + 1;
+			f.enable = 1;
+			f.fetch_start = rot_fetch_start_vsync_counter;
+		}
+	}
+
+	SDE_DEBUG_VIDENC(vid_enc,
+		"rot_fetch_lines %llu rot_fetch_start_vsync_counter %u\n",
+		rot_fetch_lines, rot_fetch_start_vsync_counter);
+
+	spin_lock_irqsave(phys_enc->enc_spinlock, lock_flags);
+	vid_enc->hw_intf->ops.setup_rot_start(vid_enc->hw_intf, &f);
+	spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
+}
+
 static bool sde_encoder_phys_vid_mode_fixup(
 		struct sde_encoder_phys *phys_enc,
 		const struct drm_display_mode *mode,
@@ -281,6 +329,8 @@
 	spin_unlock_irqrestore(phys_enc->enc_spinlock, lock_flags);
 
 	programmable_fetch_config(phys_enc, &timing_params);
+
+	vid_enc->timing_params = timing_params;
 }
 
 static void sde_encoder_phys_vid_vblank_irq(void *arg, int irq_idx)
@@ -655,14 +705,15 @@
 }
 
 static void sde_encoder_phys_vid_prepare_for_kickoff(
-		struct sde_encoder_phys *phys_enc)
+		struct sde_encoder_phys *phys_enc,
+		struct sde_encoder_kickoff_params *params)
 {
 	struct sde_encoder_phys_vid *vid_enc;
 	struct sde_hw_ctl *ctl;
 	int rc;
 
-	if (!phys_enc) {
-		SDE_ERROR("invalid encoder\n");
+	if (!phys_enc || !params) {
+		SDE_ERROR("invalid encoder/parameters\n");
 		return;
 	}
 	vid_enc = to_sde_encoder_phys_vid(phys_enc);
@@ -681,6 +732,8 @@
 				ctl->idx, rc);
 		SDE_DBG_DUMP("panic");
 	}
+
+	programmable_rot_fetch_config(phys_enc, params->inline_rotate_prefill);
 }
 
 static void sde_encoder_phys_vid_disable(struct sde_encoder_phys *phys_enc)
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
index 5187627..28a2b16 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder_phys_wb.c
@@ -777,9 +777,11 @@
 /**
  * sde_encoder_phys_wb_prepare_for_kickoff - pre-kickoff processing
  * @phys_enc:	Pointer to physical encoder
+ * @params:	kickoff parameters
  */
 static void sde_encoder_phys_wb_prepare_for_kickoff(
-		struct sde_encoder_phys *phys_enc)
+		struct sde_encoder_phys *phys_enc,
+		struct sde_encoder_kickoff_params *params)
 {
 	struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
 	int ret;
@@ -992,7 +994,7 @@
 		goto exit;
 
 	phys_enc->enable_state = SDE_ENC_DISABLING;
-	sde_encoder_phys_wb_prepare_for_kickoff(phys_enc);
+	sde_encoder_phys_wb_prepare_for_kickoff(phys_enc, NULL);
 	if (phys_enc->hw_ctl->ops.trigger_flush)
 		phys_enc->hw_ctl->ops.trigger_flush(phys_enc->hw_ctl);
 	sde_encoder_helper_trigger_start(phys_enc);
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.c b/drivers/gpu/drm/msm/sde/sde_formats.c
index 2adb4cb..00b6c85 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.c
+++ b/drivers/gpu/drm/msm/sde/sde_formats.c
@@ -779,6 +779,24 @@
 	return _sde_format_get_plane_sizes_linear(fmt, w, h, layout);
 }
 
+int sde_format_get_block_size(const struct sde_format *fmt,
+		uint32_t *w, uint32_t *h)
+{
+	if (!fmt || !w || !h) {
+		DRM_ERROR("invalid pointer\n");
+		return -EINVAL;
+	}
+
+	/* TP10 is 96x96 and all others are 128x128 */
+	if (SDE_FORMAT_IS_YUV(fmt) && SDE_FORMAT_IS_DX(fmt) &&
+			(fmt->num_planes == 2) && fmt->unpack_tight)
+		*w = *h = 96;
+	else
+		*w = *h = 128;
+
+	return 0;
+}
+
 uint32_t sde_format_get_framebuffer_size(
 		const uint32_t format,
 		const uint32_t width,
diff --git a/drivers/gpu/drm/msm/sde/sde_formats.h b/drivers/gpu/drm/msm/sde/sde_formats.h
index 544f436..40aab22 100644
--- a/drivers/gpu/drm/msm/sde/sde_formats.h
+++ b/drivers/gpu/drm/msm/sde/sde_formats.h
@@ -73,6 +73,18 @@
 		struct sde_hw_fmt_layout *layout);
 
 /**
+ * sde_format_get_block_size - get block size of given format when
+ *	operating in block mode
+ * @fmt:             pointer to sde_format
+ * @w:               pointer to width of the block
+ * @h:               pointer to height of the block
+ *
+ * Return: 0 if success; error oode otherwise
+ */
+int sde_format_get_block_size(const struct sde_format *fmt,
+		uint32_t *w, uint32_t *h);
+
+/**
  * sde_format_check_modified_format - validate format and buffers for
  *                   sde non-standard, i.e. modified format
  * @kms:             kms driver
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.c b/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
index 25222c3..9285487 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.c
@@ -1378,6 +1378,9 @@
 			intf->controller_id = none_count;
 			none_count++;
 		}
+
+		if (sde_cfg->has_sbuf)
+			set_bit(SDE_INTF_ROT_START, &intf->features);
 	}
 
 end:
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
index 962cd0d..97da08f 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_catalog.h
@@ -212,6 +212,16 @@
 };
 
 /**
+ * INTF sub-blocks
+ * @SDE_INTF_ROT_START          INTF supports rotator start trigger
+ * @SDE_INTF_MAX
+ */
+enum {
+	SDE_INTF_ROT_START = 0x1,
+	SDE_INTF_MAX
+};
+
+/**
  * WB sub-blocks and features
  * @SDE_WB_LINE_MODE        Writeback module supports line/linear mode
  * @SDE_WB_BLOCK_MODE       Writeback module supports block mode read
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_intf.c b/drivers/gpu/drm/msm/sde/sde_hw_intf.c
index c17844d..d96e49a 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_intf.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_intf.c
@@ -58,6 +58,7 @@
 #define   INTF_TPG_BLK_WHITE_PATTERN_FRAMES   0x118
 #define   INTF_TPG_RGB_MAPPING          0x11C
 #define   INTF_PROG_FETCH_START         0x170
+#define   INTF_PROG_ROT_START           0x174
 
 #define   INTF_FRAME_LINE_COUNT_EN      0x0A8
 #define   INTF_FRAME_COUNT              0x0AC
@@ -234,6 +235,25 @@
 	SDE_REG_WRITE(c, INTF_CONFIG, fetch_enable);
 }
 
+static void sde_hw_intf_setup_rot_start(
+		struct sde_hw_intf *intf,
+		const struct intf_prog_fetch *fetch)
+{
+	struct sde_hw_blk_reg_map *c = &intf->hw;
+	int fetch_enable;
+
+	fetch_enable = SDE_REG_READ(c, INTF_CONFIG);
+	if (fetch->enable) {
+		fetch_enable |= BIT(19);
+		SDE_REG_WRITE(c, INTF_PROG_ROT_START,
+				fetch->fetch_start);
+	} else {
+		fetch_enable &= ~BIT(19);
+	}
+
+	SDE_REG_WRITE(c, INTF_CONFIG, fetch_enable);
+}
+
 static void sde_hw_intf_get_status(
 		struct sde_hw_intf *intf,
 		struct intf_status *s)
@@ -303,6 +323,8 @@
 	ops->enable_timing = sde_hw_intf_enable_timing_engine;
 	ops->setup_misr = sde_hw_intf_set_misr;
 	ops->collect_misr = sde_hw_intf_collect_misr;
+	if (cap & BIT(SDE_INTF_ROT_START))
+		ops->setup_rot_start = sde_hw_intf_setup_rot_start;
 }
 
 struct sde_hw_intf *sde_hw_intf_init(enum sde_intf idx,
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_intf.h b/drivers/gpu/drm/msm/sde/sde_hw_intf.h
index f4a01cb..c6428ca 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_intf.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_intf.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2015-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2015-2017, 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
@@ -74,6 +74,7 @@
  *  Assumption is these functions will be called after clocks are enabled
  * @ setup_timing_gen : programs the timing engine
  * @ setup_prog_fetch : enables/disables the programmable fetch logic
+ * @ setup_rot_start  : enables/disables the rotator start trigger
  * @ enable_timing: enable/disable timing engine
  * @ get_status: returns if timing engine is enabled or not
  * @ setup_misr: enables/disables MISR in HW register
@@ -87,6 +88,9 @@
 	void (*setup_prg_fetch)(struct sde_hw_intf *intf,
 			const struct intf_prog_fetch *fetch);
 
+	void (*setup_rot_start)(struct sde_hw_intf *intf,
+			const struct intf_prog_fetch *fetch);
+
 	void (*enable_timing)(struct sde_hw_intf *intf,
 			u8 enable);
 
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_rot.c b/drivers/gpu/drm/msm/sde/sde_hw_rot.c
index 049d877..f79dc08 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_rot.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_rot.c
@@ -180,10 +180,16 @@
 		break;
 	case DRM_FORMAT_NV12:
 		if (SDE_MODIFIER_IS_UBWC(drm_modifier)) {
-			if (SDE_MODIFIER_IS_10B(drm_modifier))
-				*pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC;
-			else
+			if (SDE_MODIFIER_IS_10B(drm_modifier)) {
+				if (SDE_MODIFIER_IS_TIGHT(drm_modifier))
+					*pixfmt =
+					SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC;
+				else
+					*pixfmt =
+					SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC;
+			} else {
 				*pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_UBWC;
+			}
 		} else if (SDE_MODIFIER_IS_TILE(drm_modifier)) {
 			if (SDE_MODIFIER_IS_10B(drm_modifier)) {
 				if (SDE_MODIFIER_IS_TIGHT(drm_modifier))
@@ -452,6 +458,12 @@
 		*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE |
 				DRM_FORMAT_MOD_QCOM_DX;
 		break;
+	case SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC:
+		*drm_pixfmt = DRM_FORMAT_NV12;
+		*drm_modifier = DRM_FORMAT_MOD_QCOM_COMPRESSED |
+				DRM_FORMAT_MOD_QCOM_TILE |
+				DRM_FORMAT_MOD_QCOM_DX;
+		break;
 	case SDE_PIX_FMT_Y_CBCR_H2V2_TP10:
 		*drm_pixfmt = DRM_FORMAT_NV12;
 		*drm_modifier = DRM_FORMAT_MOD_QCOM_TILE |
@@ -557,6 +569,8 @@
 	rot_cmd.vflip = data->vflip;
 	rot_cmd.secure = data->secure;
 	rot_cmd.clkrate = data->clkrate;
+	rot_cmd.data_bw = 0;
+	rot_cmd.prefill_bw = data->prefill_bw;
 	rot_cmd.src_width = data->src_width;
 	rot_cmd.src_height = data->src_height;
 	rot_cmd.src_rect_x = data->src_rect_x;
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_rot.h b/drivers/gpu/drm/msm/sde/sde_hw_rot.h
index 37215bb..949f9bd 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_rot.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_rot.h
@@ -49,6 +49,7 @@
  * @secure: true if image content is in secure domain
  * @video_mode: true if rotator is feeding into video interface
  * @clkrate : clock rate in Hz
+ * @prefill_bw: prefill bandwidth in Bps (video mode only)
  * @src_iova: source i/o virtual address
  * @src_len: source i/o buffer length
  * @src_planes: source plane number
@@ -84,6 +85,7 @@
 	bool secure;
 	bool video_mode;
 	u64 clkrate;
+	u64 prefill_bw;
 	dma_addr_t src_iova[4];
 	u32 src_len[4];
 	u32 src_planes;
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.c b/drivers/gpu/drm/msm/sde/sde_kms.c
index 3add5e5..ef2c80e 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.c
+++ b/drivers/gpu/drm/msm/sde/sde_kms.c
@@ -929,13 +929,9 @@
 		fbo->dma_buf = NULL;
 	}
 
-	for (i = 0; i < fbo->layout.num_planes; i++) {
-		if (fbo->bo[i]) {
-			mutex_lock(&dev->struct_mutex);
-			drm_gem_object_unreference(fbo->bo[i]);
-			mutex_unlock(&dev->struct_mutex);
-			fbo->bo[i] = NULL;
-		}
+	if (sde_kms->iclient && fbo->ihandle) {
+		ion_free(sde_kms->iclient, fbo->ihandle);
+		fbo->ihandle = NULL;
 	}
 }
 
@@ -994,17 +990,52 @@
 	}
 
 	/* allocate backing buffer object */
-	mutex_lock(&dev->struct_mutex);
-	fbo->bo[0] = msm_gem_new(dev, fbo->layout.total_size,
-			MSM_BO_SCANOUT | MSM_BO_WC);
-	if (IS_ERR(fbo->bo[0])) {
+	if (sde_kms->iclient) {
+		u32 heap_id = fbo->flags & DRM_MODE_FB_SECURE ?
+				ION_HEAP(ION_SECURE_DISPLAY_HEAP_ID) :
+				ION_HEAP(ION_SYSTEM_HEAP_ID);
+
+		fbo->ihandle = ion_alloc(sde_kms->iclient,
+				fbo->layout.total_size, SZ_4K, heap_id, 0);
+		if (IS_ERR_OR_NULL(fbo->ihandle)) {
+			SDE_ERROR("failed to alloc ion memory\n");
+			ret = PTR_ERR(fbo->ihandle);
+			fbo->ihandle = NULL;
+			goto done;
+		}
+
+		fbo->dma_buf = ion_share_dma_buf(sde_kms->iclient,
+				fbo->ihandle);
+		if (IS_ERR(fbo->dma_buf)) {
+			SDE_ERROR("failed to share ion memory\n");
+			ret = -ENOMEM;
+			fbo->dma_buf = NULL;
+			goto done;
+		}
+
+		fbo->bo[0] = dev->driver->gem_prime_import(dev,
+				fbo->dma_buf);
+		if (IS_ERR(fbo->bo[0])) {
+			SDE_ERROR("failed to import ion memory\n");
+			ret = PTR_ERR(fbo->bo[0]);
+			fbo->bo[0] = NULL;
+			goto done;
+		}
+	} else {
+		mutex_lock(&dev->struct_mutex);
+		fbo->bo[0] = msm_gem_new(dev, fbo->layout.total_size,
+				MSM_BO_SCANOUT | MSM_BO_WC);
+		if (IS_ERR(fbo->bo[0])) {
+			mutex_unlock(&dev->struct_mutex);
+			SDE_ERROR("failed to new gem buffer\n");
+			ret = PTR_ERR(fbo->bo[0]);
+			fbo->bo[0] = NULL;
+			goto done;
+		}
 		mutex_unlock(&dev->struct_mutex);
-		SDE_ERROR("failed to new gem buffer\n");
-		ret = PTR_ERR(fbo->bo[0]);
-		fbo->bo[0] = NULL;
-		goto done;
 	}
 
+	mutex_lock(&dev->struct_mutex);
 	for (i = 1; i < fbo->layout.num_planes; i++) {
 		fbo->bo[i] = fbo->bo[0];
 		drm_gem_object_reference(fbo->bo[i]);
@@ -1110,6 +1141,11 @@
 	_sde_debugfs_destroy(sde_kms);
 	_sde_kms_mmu_destroy(sde_kms);
 
+	if (sde_kms->iclient) {
+		ion_client_destroy(sde_kms->iclient);
+		sde_kms->iclient = NULL;
+	}
+
 	if (sde_kms->catalog) {
 		for (i = 0; i < sde_kms->catalog->vbif_count; i++) {
 			u32 vbif_idx = sde_kms->catalog->vbif[i].id;
@@ -1462,6 +1498,13 @@
 		}
 	}
 
+	sde_kms->iclient = msm_ion_client_create(dev->unique);
+	if (IS_ERR(sde_kms->iclient)) {
+		rc = PTR_ERR(sde_kms->iclient);
+		SDE_DEBUG("msm_ion_client not available: %d\n", rc);
+		sde_kms->iclient = NULL;
+	}
+
 	/*
 	 * Now we need to read the HW catalog and initialize resources such as
 	 * clocks, regulators, GDSC/MMAGIC, ioremap the register ranges etc
diff --git a/drivers/gpu/drm/msm/sde/sde_kms.h b/drivers/gpu/drm/msm/sde/sde_kms.h
index 83edde3..ebc277e 100644
--- a/drivers/gpu/drm/msm/sde/sde_kms.h
+++ b/drivers/gpu/drm/msm/sde/sde_kms.h
@@ -19,6 +19,8 @@
 #ifndef __SDE_KMS_H__
 #define __SDE_KMS_H__
 
+#include <linux/msm_ion.h>
+
 #include "msm_drv.h"
 #include "msm_kms.h"
 #include "msm_mmu.h"
@@ -140,6 +142,7 @@
 	int nplane;
 	const struct sde_format *fmt;
 	struct sde_hw_fmt_layout layout;
+	struct ion_handle *ihandle;
 	struct dma_buf *dma_buf;
 	struct drm_gem_object *bo[4];
 	struct list_head fb_list;
@@ -155,6 +158,8 @@
 	int mmu_id[MSM_SMMU_DOMAIN_MAX];
 	struct sde_power_client *core_client;
 
+	struct ion_client *iclient;
+
 	/* directory entry for debugfs */
 	struct dentry *debugfs_danger;
 	struct dentry *debugfs_vbif;
diff --git a/drivers/gpu/drm/msm/sde/sde_plane.c b/drivers/gpu/drm/msm/sde/sde_plane.c
index fc0bce6..2eb947d 100644
--- a/drivers/gpu/drm/msm/sde/sde_plane.c
+++ b/drivers/gpu/drm/msm/sde/sde_plane.c
@@ -38,6 +38,14 @@
 #include "sde_color_processing.h"
 #include "sde_hw_rot.h"
 
+static bool suspend_blank = true;
+module_param(suspend_blank, bool, 0400);
+MODULE_PARM_DESC(suspend_blank,
+		"If set, active planes will force their outputs to black,\n"
+		"by temporarily enabling the color fill, when recovering\n"
+		"from a system resume instead of attempting to display the\n"
+		"last provided frame buffer.");
+
 #define SDE_DEBUG_PLANE(pl, fmt, ...) SDE_DEBUG("plane%d " fmt,\
 		(pl) ? (pl)->base.base.id : -1, ##__VA_ARGS__)
 
@@ -165,6 +173,27 @@
 	return to_sde_kms(priv->kms);
 }
 
+/**
+ * _sde_plane_get_crtc_state - obtain crtc state attached to given plane state
+ * @pstate: Pointer to drm plane state
+ * return: Pointer to crtc state if success; pointer error, otherwise
+ */
+static struct drm_crtc_state *_sde_plane_get_crtc_state(
+		struct drm_plane_state *pstate)
+{
+	struct drm_crtc_state *cstate;
+
+	if (!pstate || !pstate->crtc)
+		return NULL;
+
+	if (pstate->state)
+		cstate = drm_atomic_get_crtc_state(pstate->state, pstate->crtc);
+	else
+		cstate = pstate->crtc->state;
+
+	return cstate;
+}
+
 static bool sde_plane_enabled(struct drm_plane_state *state)
 {
 	return state && state->fb && state->crtc;
@@ -1193,7 +1222,7 @@
 }
 
 /**
- * sde_plane_rot_calc_perfill - calculate rotator start prefill
+ * sde_plane_rot_calc_prefill - calculate rotator start prefill
  * @plane: Pointer to drm plane
  * return: prefill time in line
  */
@@ -1225,8 +1254,8 @@
 		return 0;
 	}
 
-	/* if rstate->out_fb_format is TP10, then block size is 96 */
-
+	sde_format_get_block_size(rstate->out_fb_format, &blocksize,
+			&blocksize);
 	prefill_line = blocksize + sde_kms->catalog->sbuf_headroom;
 
 	SDE_DEBUG("plane%d prefill:%u\n", plane->base.id, prefill_line);
@@ -1442,6 +1471,8 @@
 	struct sde_plane_state *pstate = to_sde_plane_state(state);
 	struct sde_plane_rot_state *rstate = &pstate->rot;
 	struct sde_hw_rot_cmd *rot_cmd;
+	struct drm_crtc_state *cstate;
+	struct sde_crtc_state *sde_cstate;
 	int ret, i;
 
 	if (!plane || !state || !state->fb || !rstate->rot_hw) {
@@ -1449,6 +1480,13 @@
 		return -EINVAL;
 	}
 
+	cstate = _sde_plane_get_crtc_state(state);
+	if (IS_ERR_OR_NULL(cstate)) {
+		SDE_ERROR("invalid crtc state %ld\n", PTR_ERR(cstate));
+		return -EINVAL;
+	}
+	sde_cstate = to_sde_crtc_state(cstate);
+
 	rot_cmd = &rstate->rot_cmd;
 
 	rot_cmd->master = (rstate->out_xpos == 0);
@@ -1460,6 +1498,8 @@
 	rot_cmd->hflip = rstate->hflip;
 	rot_cmd->vflip = rstate->vflip;
 	rot_cmd->secure = state->fb->flags & DRM_MODE_FB_SECURE ? true : false;
+	rot_cmd->prefill_bw = sde_crtc_get_property(sde_cstate,
+			CRTC_PROP_ROT_PREFILL_BW);
 	rot_cmd->dst_writeback = psde->sbuf_writeback;
 
 	if (sde_crtc_get_intf_mode(state->crtc) == INTF_MODE_VIDEO)
@@ -2863,6 +2903,10 @@
 	else if (psde->pipe_hw && psde->csc_ptr && psde->pipe_hw->ops.setup_csc)
 		psde->pipe_hw->ops.setup_csc(psde->pipe_hw, psde->csc_ptr);
 
+	/* force black color fill during suspend */
+	if (msm_is_suspend_state(plane->dev) && suspend_blank)
+		_sde_plane_color_fill(psde, 0x0, 0x0);
+
 	/* flag h/w flush complete */
 	if (plane->state)
 		to_sde_plane_state(plane->state)->pending = false;
diff --git a/drivers/gpu/drm/msm/sde_power_handle.h b/drivers/gpu/drm/msm/sde_power_handle.h
index 4c172a4..4e262a3 100644
--- a/drivers/gpu/drm/msm/sde_power_handle.h
+++ b/drivers/gpu/drm/msm/sde_power_handle.h
@@ -21,6 +21,8 @@
 #define SDE_POWER_HANDLE_ENABLE_BUS_IB_QUOTA	64000
 #define SDE_POWER_HANDLE_DISABLE_BUS_IB_QUOTA	0
 
+#include <linux/sde_io_util.h>
+
 /**
  * mdss_bus_vote_type: register bus vote type
  * VOTE_INDEX_DISABLE: removes the client vote
diff --git a/drivers/gpu/drm/vc4/vc4_drv.c b/drivers/gpu/drm/vc4/vc4_drv.c
index 8703f56..246d1ae 100644
--- a/drivers/gpu/drm/vc4/vc4_drv.c
+++ b/drivers/gpu/drm/vc4/vc4_drv.c
@@ -61,21 +61,24 @@
 		if (ret < 0)
 			return ret;
 		args->value = V3D_READ(V3D_IDENT0);
-		pm_runtime_put(&vc4->v3d->pdev->dev);
+		pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
+		pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev);
 		break;
 	case DRM_VC4_PARAM_V3D_IDENT1:
 		ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev);
 		if (ret < 0)
 			return ret;
 		args->value = V3D_READ(V3D_IDENT1);
-		pm_runtime_put(&vc4->v3d->pdev->dev);
+		pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
+		pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev);
 		break;
 	case DRM_VC4_PARAM_V3D_IDENT2:
 		ret = pm_runtime_get_sync(&vc4->v3d->pdev->dev);
 		if (ret < 0)
 			return ret;
 		args->value = V3D_READ(V3D_IDENT2);
-		pm_runtime_put(&vc4->v3d->pdev->dev);
+		pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
+		pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev);
 		break;
 	case DRM_VC4_PARAM_SUPPORTS_BRANCHES:
 		args->value = true;
diff --git a/drivers/gpu/drm/vc4/vc4_gem.c b/drivers/gpu/drm/vc4/vc4_gem.c
index 18e3717..ab30169 100644
--- a/drivers/gpu/drm/vc4/vc4_gem.c
+++ b/drivers/gpu/drm/vc4/vc4_gem.c
@@ -711,8 +711,10 @@
 	}
 
 	mutex_lock(&vc4->power_lock);
-	if (--vc4->power_refcount == 0)
-		pm_runtime_put(&vc4->v3d->pdev->dev);
+	if (--vc4->power_refcount == 0) {
+		pm_runtime_mark_last_busy(&vc4->v3d->pdev->dev);
+		pm_runtime_put_autosuspend(&vc4->v3d->pdev->dev);
+	}
 	mutex_unlock(&vc4->power_lock);
 
 	kfree(exec);
diff --git a/drivers/gpu/drm/vc4/vc4_v3d.c b/drivers/gpu/drm/vc4/vc4_v3d.c
index e6d3c60..7cc346a 100644
--- a/drivers/gpu/drm/vc4/vc4_v3d.c
+++ b/drivers/gpu/drm/vc4/vc4_v3d.c
@@ -222,6 +222,8 @@
 		return ret;
 	}
 
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_autosuspend_delay(dev, 40); /* a little over 2 frames. */
 	pm_runtime_enable(dev);
 
 	return 0;
diff --git a/drivers/gpu/drm/vc4/vc4_validate_shaders.c b/drivers/gpu/drm/vc4/vc4_validate_shaders.c
index 2543cf5..917321c 100644
--- a/drivers/gpu/drm/vc4/vc4_validate_shaders.c
+++ b/drivers/gpu/drm/vc4/vc4_validate_shaders.c
@@ -608,9 +608,7 @@
 vc4_validate_branches(struct vc4_shader_validation_state *validation_state)
 {
 	uint32_t max_branch_target = 0;
-	bool found_shader_end = false;
 	int ip;
-	int shader_end_ip = 0;
 	int last_branch = -2;
 
 	for (ip = 0; ip < validation_state->max_ip; ip++) {
@@ -621,8 +619,13 @@
 		uint32_t branch_target_ip;
 
 		if (sig == QPU_SIG_PROG_END) {
-			shader_end_ip = ip;
-			found_shader_end = true;
+			/* There are two delay slots after program end is
+			 * signaled that are still executed, then we're
+			 * finished.  validation_state->max_ip is the
+			 * instruction after the last valid instruction in the
+			 * program.
+			 */
+			validation_state->max_ip = ip + 3;
 			continue;
 		}
 
@@ -676,15 +679,9 @@
 		}
 		set_bit(after_delay_ip, validation_state->branch_targets);
 		max_branch_target = max(max_branch_target, after_delay_ip);
-
-		/* There are two delay slots after program end is signaled
-		 * that are still executed, then we're finished.
-		 */
-		if (found_shader_end && ip == shader_end_ip + 2)
-			break;
 	}
 
-	if (max_branch_target > shader_end_ip) {
+	if (max_branch_target > validation_state->max_ip - 3) {
 		DRM_ERROR("Branch landed after QPU_SIG_PROG_END");
 		return false;
 	}
diff --git a/drivers/gpu/msm/a6xx_reg.h b/drivers/gpu/msm/a6xx_reg.h
index f50dae9..7cdd2b2 100644
--- a/drivers/gpu/msm/a6xx_reg.h
+++ b/drivers/gpu/msm/a6xx_reg.h
@@ -676,7 +676,7 @@
 #define A6XX_GMU_HOST_INTERRUPT_CLR		0x23B04
 #define A6XX_GMU_HOST_INTERRUPT_STATUS		0x23B05
 #define A6XX_GMU_HOST_INTERRUPT_MASK		0x23B06
-#define A6XX_GMU_GPU_CX_BUSY_STATUS		0x23B0C
+#define A6XX_GPU_GMU_AO_GPU_CX_BUSY_STATUS	0x23B0C
 #define A6XX_GMU_AHB_FENCE_STATUS		0x23B13
 #define A6XX_GMU_RBBM_INT_UNMASKED_STATUS	0x23B15
 #define A6XX_GMU_AO_SPARE_CNTL			0x23B16
diff --git a/drivers/gpu/msm/adreno.c b/drivers/gpu/msm/adreno.c
index 787ca6b..f0d8746 100644
--- a/drivers/gpu/msm/adreno.c
+++ b/drivers/gpu/msm/adreno.c
@@ -2243,6 +2243,11 @@
 {
 	const struct adreno_gpu_core *gpucore = adreno_dev->gpucore;
 	unsigned int reg_rbbm_status;
+	struct adreno_gpudev *gpudev  = ADRENO_GPU_DEVICE(adreno_dev);
+
+	/* if hw driver implements idle check - use it */
+	if (gpudev->hw_isidle)
+		return gpudev->hw_isidle(adreno_dev);
 
 	if (adreno_is_a540(adreno_dev))
 		/**
diff --git a/drivers/gpu/msm/adreno.h b/drivers/gpu/msm/adreno.h
index 1e08a5e..e23d6a0 100644
--- a/drivers/gpu/msm/adreno.h
+++ b/drivers/gpu/msm/adreno.h
@@ -852,10 +852,10 @@
 				unsigned int clear_mask);
 	void (*oob_clear)(struct adreno_device *adreno_dev,
 				unsigned int clear_mask);
-	bool (*hw_isidle)(struct adreno_device *);
 	int (*rpmh_gpu_pwrctrl)(struct adreno_device *, unsigned int ops,
 				unsigned int arg1, unsigned int arg2);
-	bool (*gmu_isidle)(struct adreno_device *);
+	bool (*hw_isidle)(struct adreno_device *);
+	int (*wait_for_gmu_idle)(struct adreno_device *);
 };
 
 /**
diff --git a/drivers/gpu/msm/adreno_a6xx.c b/drivers/gpu/msm/adreno_a6xx.c
index ca0e79d..0211a17 100644
--- a/drivers/gpu/msm/adreno_a6xx.c
+++ b/drivers/gpu/msm/adreno_a6xx.c
@@ -193,6 +193,11 @@
 	unsigned int bit, mal, mode, glbl_inv;
 	unsigned int amsbc = 0;
 
+	/* runtime adjust callbacks based on feature sets */
+	if (!kgsl_gmu_isenabled(device))
+		/* Legacy idle management if gmu is disabled */
+		ADRENO_GPU_DEVICE(adreno_dev)->hw_isidle = NULL;
+
 	adreno_vbif_start(adreno_dev, a6xx_vbif_platforms,
 			ARRAY_SIZE(a6xx_vbif_platforms));
 	/*
@@ -925,8 +930,6 @@
 	return ret;
 }
 
-#define GMU_POWER_STATE_SLUMBER 15
-
 /*
  * a6xx_notify_slumber() - initiate request to GMU to prepare to slumber
  * @device: Pointer to KGSL device
@@ -959,7 +962,7 @@
 		dev_err(&gmu->pdev->dev, "OOB set for slumber timed out\n");
 	else {
 		kgsl_gmu_regread(device, A6XX_GMU_RPMH_POWER_STATE, &state);
-		if (state != GMU_POWER_STATE_SLUMBER) {
+		if (state != GPU_HW_SLUMBER) {
 			dev_err(&gmu->pdev->dev,
 					"Failed to prepare for slumber\n");
 			ret = -EINVAL;
@@ -1258,29 +1261,35 @@
 	return ret;
 }
 
-static bool a6xx_gmu_isidle(struct adreno_device *adreno_dev)
+static bool a6xx_hw_isidle(struct adreno_device *adreno_dev)
+{
+	unsigned int reg;
+
+	kgsl_gmu_regread(KGSL_DEVICE(adreno_dev),
+		A6XX_GPU_GMU_AO_GPU_CX_BUSY_STATUS, &reg);
+	return ((~reg & GPUBUSYIGNAHB) != 0);
+}
+
+static int a6xx_wait_for_gmu_idle(struct adreno_device *adreno_dev)
 {
 	struct kgsl_device *device = KGSL_DEVICE(adreno_dev);
 	struct gmu_device *gmu = &device->gmu;
-	unsigned int value;
 
-	/* Check if GMU on */
-	if (!(gmu->flags & GMU_CLK_ON))
-		return true;
+	if (timed_poll_check(device, A6XX_GMU_RPMH_POWER_STATE,
+		gmu->idle_level, GMU_START_TIMEOUT, 0xf)) {
+		dev_err(&gmu->pdev->dev,
+			"GMU is not going to powerstate %d\n",
+			gmu->idle_level);
+		return -ETIMEDOUT;
+	}
 
-	/* Ensure GPU is in its lowest power state */
-	kgsl_gmu_regread(device, A6XX_GMU_RPMH_POWER_STATE, &value);
+	if (timed_poll_check(device, A6XX_GPU_GMU_AO_GPU_CX_BUSY_STATUS,
+		0, GMU_START_TIMEOUT, CXGXCPUBUSYIGNAHB)) {
+		dev_err(&gmu->pdev->dev, "GMU is not idling\n");
+		return -ETIMEDOUT;
+	}
 
-	if (value < gmu->idle_level)
-		return false;
-
-	/* Ensure GPU and GMU are both idle */
-	kgsl_gmu_regread(device->reg_virt, A6XX_GMU_GPU_CX_BUSY_STATUS,
-			&value);
-	if ((value & SLUMBER_CHECK_MASK) != SLUMBER_CHECK_MASK)
-		return false;
-
-	return true;
+	return 0;
 }
 
 /*
@@ -2040,5 +2049,6 @@
 	.oob_set = a6xx_oob_set,
 	.oob_clear = a6xx_oob_clear,
 	.rpmh_gpu_pwrctrl = a6xx_rpmh_gpu_pwrctrl,
-	.gmu_isidle = a6xx_gmu_isidle,
+	.hw_isidle = a6xx_hw_isidle, /* Replaced by NULL if GMU is disabled */
+	.wait_for_gmu_idle = a6xx_wait_for_gmu_idle
 };
diff --git a/drivers/gpu/msm/kgsl_gmu.c b/drivers/gpu/msm/kgsl_gmu.c
index 56e4f23..2e9f108 100644
--- a/drivers/gpu/msm/kgsl_gmu.c
+++ b/drivers/gpu/msm/kgsl_gmu.c
@@ -1086,7 +1086,7 @@
 
 	hfi_init(&gmu->hfi, mem_addr, HFI_QUEUE_SIZE);
 
-	gmu->idle_level = GPU_HW_CGC;
+	gmu->idle_level = GPU_HW_ACTIVE;
 
 	return 0;
 
@@ -1312,7 +1312,11 @@
 	if (!test_bit(GMU_CLK_ON, &gmu->flags))
 		return;
 
-	/* TODO: Check for conditions to enter slumber */
+	if (gpudev->wait_for_gmu_idle &&
+		!gpudev->wait_for_gmu_idle(adreno_dev)) {
+		dev_err(&gmu->pdev->dev, "Failure to stop gmu");
+		return;
+	}
 
 	gpudev->rpmh_gpu_pwrctrl(adreno_dev, GMU_NOTIFY_SLUMBER, 0, 0);
 
diff --git a/drivers/gpu/msm/kgsl_gmu.h b/drivers/gpu/msm/kgsl_gmu.h
index b5c0c96..ac2c151 100644
--- a/drivers/gpu/msm/kgsl_gmu.h
+++ b/drivers/gpu/msm/kgsl_gmu.h
@@ -53,11 +53,9 @@
 				CX_VOTE_ENABLE		| \
 				GFX_VOTE_ENABLE)
 
-/* Bitmask for GMU idle status check */
-#define CXGX_CPUBUSY_IGNAHB_IDLE	BIT(30)
-#define GPUBUSY_IGNAHB_IDLE		BIT(23)
-#define SLUMBER_CHECK_MASK		(CXGX_CPUBUSY_IGNAHB_IDLE  | \
-					GPUBUSY_IGNAHB_IDLE)
+/* Bitmask for GPU idle status check */
+#define GPUBUSYIGNAHB		BIT(23)
+#define CXGXCPUBUSYIGNAHB	BIT(30)
 
 /* Constants for GMU OOBs */
 #define OOB_BOOT_OPTION         0
@@ -143,12 +141,13 @@
 };
 
 enum gpu_idle_level {
-	GPU_HW_ACTIVE,
-	GPU_HW_CGC,
-	GPU_HW_SPTP_PC,
-	GPU_HW_IFPC,
-	GPU_HW_NAP,
-	GPU_HW_MIN_VOLT,
+	GPU_HW_ACTIVE = 0x0,
+	GPU_HW_SPTP_PC = 0x2,
+	GPU_HW_IFPC = 0x3,
+	GPU_HW_NAP = 0x4,
+	GPU_HW_MIN_VOLT = 0x5,
+	GPU_HW_MIN_DDR = 0x6,
+	GPU_HW_SLUMBER = 0xF
 };
 
 /**
diff --git a/drivers/isdn/gigaset/bas-gigaset.c b/drivers/isdn/gigaset/bas-gigaset.c
index aecec6d..7f1c625 100644
--- a/drivers/isdn/gigaset/bas-gigaset.c
+++ b/drivers/isdn/gigaset/bas-gigaset.c
@@ -2317,6 +2317,9 @@
 		return -ENODEV;
 	}
 
+	if (hostif->desc.bNumEndpoints < 1)
+		return -ENODEV;
+
 	dev_info(&udev->dev,
 		 "%s: Device matched (Vendor: 0x%x, Product: 0x%x)\n",
 		 __func__, le16_to_cpu(udev->descriptor.idVendor),
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index 39fddda..55b5e0e 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -1470,7 +1470,25 @@
 			split = bio;
 		}
 
+		/*
+		 * If a bio is splitted, the first part of bio will pass
+		 * barrier but the bio is queued in current->bio_list (see
+		 * generic_make_request). If there is a raise_barrier() called
+		 * here, the second part of bio can't pass barrier. But since
+		 * the first part bio isn't dispatched to underlaying disks
+		 * yet, the barrier is never released, hence raise_barrier will
+		 * alays wait. We have a deadlock.
+		 * Note, this only happens in read path. For write path, the
+		 * first part of bio is dispatched in a schedule() call
+		 * (because of blk plug) or offloaded to raid10d.
+		 * Quitting from the function immediately can change the bio
+		 * order queued in bio_list and avoid the deadlock.
+		 */
 		__make_request(mddev, split);
+		if (split != bio && bio_data_dir(bio) == READ) {
+			generic_make_request(bio);
+			break;
+		}
 	} while (split != bio);
 
 	/* In case raid10d snuck in to freeze_array */
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h
index c7abd9d..a0b53bb 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_base.h
@@ -98,6 +98,7 @@
  * @SDE_CAPS_R3_1P5_DOWNSCALE: 1.5x downscale rotator support
  * @SDE_CAPS_MIN_BUS_VOTE: minimum bus vote prior to power enable
  * @SDE_CAPS_SBUF_1: stream buffer support for inline rotation
+ * @SDE_CAPS_UBWC_2: universal bandwidth compression version 2
  */
 enum sde_caps_settings {
 	SDE_CAPS_R1_WB,
@@ -106,6 +107,7 @@
 	SDE_CAPS_SEC_ATTACH_DETACH_SMMU,
 	SDE_CAPS_MIN_BUS_VOTE,
 	SDE_CAPS_SBUF_1,
+	SDE_CAPS_UBWC_2,
 	SDE_CAPS_MAX,
 };
 
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_formats.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_formats.c
index c78c513..573e0a8 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_formats.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_formats.c
@@ -284,6 +284,27 @@
 		},
 	},
 	{
+		.mdp_format = {
+			FMT_YUV_COMMON(SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC),
+			.description = "SDE/Y_CBCR_H2V2_P010_UBWC",
+			.flag = 0,
+			.fetch_planes = SDE_MDP_PLANE_PSEUDO_PLANAR,
+			.chroma_sample = SDE_MDP_CHROMA_420,
+			.unpack_count = 2,
+			.bpp = 2,
+			.frame_format = SDE_MDP_FMT_TILE_A5X,
+			.pixel_mode = SDE_MDP_PIXEL_10BIT,
+			.element = { C1_B_Cb, C2_R_Cr },
+			.unpack_tight = 0,
+			.unpack_align_msb = 1,
+			.is_ubwc = SDE_MDP_COMPRESS_UBWC
+		},
+		.micro = {
+			.tile_height = 4,
+			.tile_width = 32,
+		},
+	},
+	{
 		.mdp_format =
 			FMT_RGB_1010102(SDE_PIX_FMT_RGBA_1010102_TILE,
 			"SDE/RGBA_1010102_TILE",
@@ -517,6 +538,27 @@
 			.tile_width = 16,
 		},
 	},
+	{
+		.mdp_format = {
+			FMT_YUV_COMMON(SDE_PIX_FMT_Y_CBCR_H2V2_P010_TILE),
+			.description = "SDE/Y_CBCR_H2V2_P010_TILE",
+			.flag = SDE_MDP_FORMAT_FLAG_PRIVATE,
+			.fetch_planes = SDE_MDP_PLANE_PSEUDO_PLANAR,
+			.chroma_sample = SDE_MDP_CHROMA_420,
+			.unpack_count = 2,
+			.bpp = 2,
+			.frame_format = SDE_MDP_FMT_TILE_A5X,
+			.pixel_mode = SDE_MDP_PIXEL_10BIT,
+			.element = { C1_B_Cb, C2_R_Cr },
+			.unpack_tight = 0,
+			.unpack_align_msb = 1,
+			.is_ubwc = SDE_MDP_COMPRESS_NONE,
+		},
+		.micro = {
+			.tile_height = 4,
+			.tile_width = 32,
+		},
+	},
 };
 
 static struct sde_mdp_format_params sde_mdp_format_map[] = {
@@ -853,6 +895,11 @@
 	case SDE_PIX_FMT_BGRX_1010102_TILE:
 		*dst_pixfmt = SDE_PIX_FMT_BGRX_1010102_TILE;
 		break;
+	case SDE_PIX_FMT_Y_CBCR_H2V2_P010:
+	case SDE_PIX_FMT_Y_CBCR_H2V2_P010_TILE:
+	case SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC:
+		*dst_pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_P010_TILE;
+		break;
 	case SDE_PIX_FMT_Y_CBCR_H2V2_TP10:
 	case SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC:
 		*dst_pixfmt = SDE_PIX_FMT_Y_CBCR_H2V2_TP10;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
index 4278b6d..27e2d28 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3.c
@@ -52,6 +52,8 @@
 
 /* default stream buffer headroom in lines */
 #define DEFAULT_SBUF_HEADROOM	20
+#define DEFAULT_UBWC_MALSIZE	1
+#define DEFAULT_UBWC_SWIZZLE	1
 
 /* Macro for constructing the REGDMA command */
 #define SDE_REGDMA_WRITE(p, off, data) \
@@ -278,6 +280,8 @@
 	SDE_PIX_FMT_Y_CBCR_H2V2_P010,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TP10,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC,
+	SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC,
+	SDE_PIX_FMT_Y_CBCR_H2V2_P010_TILE,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TILE,
 	SDE_PIX_FMT_Y_CRCB_H2V2_TILE,
 	SDE_PIX_FMT_XRGB_8888_TILE,
@@ -356,6 +360,8 @@
 	SDE_PIX_FMT_Y_CBCR_H2V2_P010,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TP10,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC,
+	SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC,
+	SDE_PIX_FMT_Y_CBCR_H2V2_P010_TILE,
 	SDE_PIX_FMT_Y_CBCR_H2V2_TILE,
 	SDE_PIX_FMT_Y_CRCB_H2V2_TILE,
 	SDE_PIX_FMT_XRGB_8888_TILE,
@@ -877,6 +883,12 @@
 			SDE_ROT_SSPP_FETCH_CONFIG_RESET_VALUE |
 			((rot->highest_bank & 0x3) << 18));
 
+	if (test_bit(SDE_CAPS_UBWC_2, mdata->sde_caps_map))
+		SDE_REGDMA_WRITE(wrptr, ROT_SSPP_UBWC_STATIC_CTRL, BIT(31) |
+				((ctx->rot->ubwc_malsize & 0x3) << 8) |
+				((ctx->rot->highest_bank & 0x3) << 4) |
+				((ctx->rot->ubwc_swizzle & 0x1) << 0));
+
 	/* setup source buffer plane security status */
 	if (flags & (SDE_ROT_FLAG_SECURE_OVERLAY_SESSION |
 			SDE_ROT_FLAG_SECURE_CAMERA_SESSION)) {
@@ -1009,6 +1021,12 @@
 	SDE_REGDMA_WRITE(wrptr, ROT_WB_DST_WRITE_CONFIG,
 			(ctx->rot->highest_bank & 0x3) << 8);
 
+	if (test_bit(SDE_CAPS_UBWC_2, mdata->sde_caps_map))
+		SDE_REGDMA_WRITE(wrptr, ROT_WB_UBWC_STATIC_CTRL,
+				((ctx->rot->ubwc_malsize & 0x3) << 8) |
+				((ctx->rot->highest_bank & 0x3) << 4) |
+				((ctx->rot->ubwc_swizzle & 0x1) << 0));
+
 	if (test_bit(SDE_CAPS_SBUF_1, mdata->sde_caps_map))
 		SDE_REGDMA_WRITE(wrptr, ROT_WB_SYS_CACHE_MODE,
 				ctx->sys_cache_mode);
@@ -2212,6 +2230,7 @@
 		SDEROT_DBG("Supporting sys cache inline rotation\n");
 		set_bit(SDE_CAPS_MIN_BUS_VOTE,  mdata->sde_caps_map);
 		set_bit(SDE_CAPS_SBUF_1,  mdata->sde_caps_map);
+		set_bit(SDE_CAPS_UBWC_2,  mdata->sde_caps_map);
 		rot->inpixfmts = sde_hw_rotator_v4_inpixfmts;
 		rot->num_inpixfmt = ARRAY_SIZE(sde_hw_rotator_v4_inpixfmts);
 		rot->outpixfmts = sde_hw_rotator_v4_outpixfmts;
@@ -2739,6 +2758,26 @@
 	}
 
 	ret = of_property_read_u32(dev->dev.of_node,
+			"qcom,sde-ubwc-malsize", &data);
+	if (ret) {
+		ret = 0;
+		hw_data->ubwc_malsize = DEFAULT_UBWC_MALSIZE;
+	} else {
+		SDEROT_DBG("set ubwc malsize to %d\n", data);
+		hw_data->ubwc_malsize = data;
+	}
+
+	ret = of_property_read_u32(dev->dev.of_node,
+			"qcom,sde-ubwc_swizzle", &data);
+	if (ret) {
+		ret = 0;
+		hw_data->ubwc_swizzle = DEFAULT_UBWC_SWIZZLE;
+	} else {
+		SDEROT_DBG("set ubwc swizzle to %d\n", data);
+		hw_data->ubwc_swizzle = data;
+	}
+
+	ret = of_property_read_u32(dev->dev.of_node,
 			"qcom,mdss-sbuf-headroom", &data);
 	if (ret) {
 		ret = 0;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
index f86f54b..dc97bdf 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_hwio.h
@@ -70,6 +70,7 @@
 #define ROT_SSPP_SRC_UNPACK_PATTERN             (SDE_ROT_SSPP_OFFSET+0x34)
 #define ROT_SSPP_SRC_OP_MODE                    (SDE_ROT_SSPP_OFFSET+0x38)
 #define ROT_SSPP_SRC_CONSTANT_COLOR             (SDE_ROT_SSPP_OFFSET+0x3C)
+#define ROT_SSPP_UBWC_STATIC_CTRL               (SDE_ROT_SSPP_OFFSET+0x44)
 #define ROT_SSPP_FETCH_CONFIG                   (SDE_ROT_SSPP_OFFSET+0x48)
 #define ROT_SSPP_VC1_RANGE                      (SDE_ROT_SSPP_OFFSET+0x4C)
 #define ROT_SSPP_REQPRIORITY_FIFO_WATERMARK_0   (SDE_ROT_SSPP_OFFSET+0x50)
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
index c011d7a..d1607d9 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_r3_internal.h
@@ -248,6 +248,9 @@
  * struct sde_hw_rotator : Rotator description
  * @hw:           mdp register mapped offset
  * @ops:          pointer to operations possible for the rotator HW
+ * @highest_bank: highest bank size of memory
+ * @ubwc_malsize: ubwc minimum allowable length
+ * @ubwc_swizzle: ubwc swizzle enable
  * @sbuf_headroom: stream buffer headroom in lines
  * @solid_fill: true if solid fill is requested
  * @constant_color: solid fill constant color
@@ -296,6 +299,8 @@
 	void *swts_buffer;
 
 	u32    highest_bank;
+	u32    ubwc_malsize;
+	u32    ubwc_swizzle;
 	u32    sbuf_headroom;
 	u32    solid_fill;
 	u32    constant_color;
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_util.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_util.c
index 4cf9dfc..9ef4282 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_util.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_util.c
@@ -210,6 +210,32 @@
 		ps->ystride[3] = ALIGN(DIV_ROUND_UP(width / 2, 16), 64);
 		ps->plane_size[3] = ALIGN(ps->ystride[3] *
 			ALIGN(DIV_ROUND_UP(height / 2, 8), 16), 4096);
+	} else if (sde_mdp_is_p010_format(fmt)) {
+		ps->num_planes = 2;
+		/* Y bitstream stride and plane size */
+		ps->ystride[0] = ALIGN(width * 2, 256);
+		ps->plane_size[0] = ALIGN(ps->ystride[0] * ALIGN(height, 16),
+					4096);
+
+		/* CbCr bitstream stride and plane size */
+		ps->ystride[1] = ALIGN(width * 2, 256);
+		ps->plane_size[1] = ALIGN(ps->ystride[1] *
+			ALIGN(height / 2, 16), 4096);
+
+		if (!sde_mdp_is_ubwc_format(fmt))
+			goto done;
+
+		ps->num_planes += 2;
+
+		/* Y meta data stride and plane size */
+		ps->ystride[2] = ALIGN(DIV_ROUND_UP(width, 32), 64);
+		ps->plane_size[2] = ALIGN(ps->ystride[2] *
+			ALIGN(DIV_ROUND_UP(height, 4), 16), 4096);
+
+		/* CbCr meta data stride and plane size */
+		ps->ystride[3] = ALIGN(DIV_ROUND_UP(width / 2, 16), 64);
+		ps->plane_size[3] = ALIGN(ps->ystride[3] *
+			ALIGN(DIV_ROUND_UP(height / 2, 4), 16), 4096);
 	} else if (sde_mdp_is_tp10_format(fmt)) {
 		u32 yWidth   = sde_mdp_general_align(width, 192);
 		u32 yHeight  = ALIGN(height, 16);
diff --git a/drivers/media/platform/msm/vidc/hfi_packetization.c b/drivers/media/platform/msm/vidc/hfi_packetization.c
index 49fe5fb..16c2aae 100644
--- a/drivers/media/platform/msm/vidc/hfi_packetization.c
+++ b/drivers/media/platform/msm/vidc/hfi_packetization.c
@@ -1225,6 +1225,10 @@
 			HFI_PROPERTY_PARAM_PROFILE_LEVEL_CURRENT;
 		hfi = (struct hfi_profile_level *)
 			&pkt->rg_property_data[1];
+
+		/* There is an assumption here that HAL level is same as
+		 * HFI level
+		 */
 		hfi->level = prop->level;
 		hfi->profile = hal_to_hfi_type(HAL_PARAM_PROFILE_LEVEL_CURRENT,
 				prop->profile);
@@ -1235,13 +1239,6 @@
 					prop->profile);
 		}
 
-		if (!hfi->level) {
-			hfi->level = 1;
-			dprintk(VIDC_WARN,
-					"Level %d not supported, falling back to high\n",
-					prop->level);
-		}
-
 		pkt->size += sizeof(u32) + sizeof(struct hfi_profile_level);
 		break;
 	}
diff --git a/drivers/media/platform/msm/vidc/msm_venc.c b/drivers/media/platform/msm/vidc/msm_venc.c
index 2ec5155..64937aa 100644
--- a/drivers/media/platform/msm/vidc/msm_venc.c
+++ b/drivers/media/platform/msm/vidc/msm_venc.c
@@ -91,30 +91,8 @@
 	NULL
 };
 
-static const char *const h263_level[] = {
-	"1.0",
-	"2.0",
-	"3.0",
-	"4.0",
-	"4.5",
-	"5.0",
-	"6.0",
-	"7.0",
-};
-
-static const char *const h263_profile[] = {
-	"Baseline",
-	"H320 Coding",
-	"Backward Compatible",
-	"ISWV2",
-	"ISWV3",
-	"High Compression",
-	"Internet",
-	"Interlace",
-	"High Latency",
-};
-
 static const char *const hevc_tier_level[] = {
+	"Level unknown"
 	"Main Tier Level 1",
 	"Main Tier Level 2",
 	"Main Tier Level 2.1",
@@ -454,8 +432,8 @@
 		.name = "H264 Level",
 		.type = V4L2_CTRL_TYPE_MENU,
 		.minimum = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
-		.maximum = V4L2_MPEG_VIDEO_H264_LEVEL_5_2,
-		.default_value = V4L2_MPEG_VIDEO_H264_LEVEL_1_0,
+		.maximum = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN,
+		.default_value = V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN,
 		.menu_skip_mask = 0,
 	},
 	{
@@ -491,9 +469,9 @@
 		.name = "HEVC Tier and Level",
 		.type = V4L2_CTRL_TYPE_MENU,
 		.minimum = V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_1,
-		.maximum = V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_5_2,
+		.maximum = V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN,
 		.default_value =
-			V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_1,
+			V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN,
 		.menu_skip_mask = ~(
 		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_1) |
 		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_2) |
@@ -513,7 +491,8 @@
 		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_4) |
 		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_4_1) |
 		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_5) |
-		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_5_1)
+		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_5_1) |
+		(1 << V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN)
 		),
 		.qmenu = hevc_tier_level,
 	},
diff --git a/drivers/media/platform/msm/vidc/msm_vidc_common.c b/drivers/media/platform/msm/vidc/msm_vidc_common.c
index 4aaa525..74e68e4 100644
--- a/drivers/media/platform/msm/vidc/msm_vidc_common.c
+++ b/drivers/media/platform/msm/vidc/msm_vidc_common.c
@@ -304,6 +304,8 @@
 			return HAL_H264_LEVEL_51;
 		case V4L2_MPEG_VIDEO_H264_LEVEL_5_2:
 			return HAL_H264_LEVEL_52;
+		case V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN:
+			return HAL_H264_LEVEL_UNKNOWN;
 		default:
 			goto unknown_value;
 		}
@@ -405,6 +407,8 @@
 			return HAL_HEVC_HIGH_TIER_LEVEL_6;
 		case V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_6_1:
 			return HAL_HEVC_HIGH_TIER_LEVEL_6_1;
+		case V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN:
+			return HAL_HEVC_TIER_LEVEL_UNKNOWN;
 		default:
 			goto unknown_value;
 		}
diff --git a/drivers/media/platform/msm/vidc/vidc_hfi_api.h b/drivers/media/platform/msm/vidc/vidc_hfi_api.h
index eff16f2..d60a0d1 100644
--- a/drivers/media/platform/msm/vidc/vidc_hfi_api.h
+++ b/drivers/media/platform/msm/vidc/vidc_hfi_api.h
@@ -384,6 +384,7 @@
 };
 
 enum hal_h264_level {
+	HAL_H264_LEVEL_UNKNOWN = 0x00000000,
 	HAL_H264_LEVEL_1  = 0x00000001,
 	HAL_H264_LEVEL_1b = 0x00000002,
 	HAL_H264_LEVEL_11 = 0x00000004,
@@ -401,7 +402,6 @@
 	HAL_H264_LEVEL_5  = 0x00004000,
 	HAL_H264_LEVEL_51 = 0x00008000,
 	HAL_H264_LEVEL_52 = 0x00010000,
-	HAL_UNUSED_H264_LEVEL = 0x10000000,
 };
 
 enum hal_hevc_profile {
@@ -412,6 +412,7 @@
 };
 
 enum hal_hevc_level {
+	HAL_HEVC_TIER_LEVEL_UNKNOWN     = 0x00000000,
 	HAL_HEVC_MAIN_TIER_LEVEL_1      = 0x10000001,
 	HAL_HEVC_MAIN_TIER_LEVEL_2      = 0x10000002,
 	HAL_HEVC_MAIN_TIER_LEVEL_2_1    = 0x10000004,
@@ -438,7 +439,6 @@
 	HAL_HEVC_HIGH_TIER_LEVEL_6      = 0x20000400,
 	HAL_HEVC_HIGH_TIER_LEVEL_6_1    = 0x20000800,
 	HAL_HEVC_HIGH_TIER_LEVEL_6_2    = 0x20001000,
-	HAL_UNUSED_HEVC_TIER_LEVEL      = 0x80000000,
 };
 
 enum hal_hevc_tier {
diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
index ce5a7dc..5818986 100644
--- a/drivers/media/v4l2-core/v4l2-ioctl.c
+++ b/drivers/media/v4l2-core/v4l2-ioctl.c
@@ -1322,6 +1322,8 @@
 					descr = "Y/CbCr 4:2:0 P10"; break;
 	case V4L2_PIX_FMT_NV12_TP10_UBWC:
 					descr = "Y/CbCr 4:2:0 TP10 UBWC"; break;
+	case V4L2_PIX_FMT_NV12_P010_UBWC:
+					descr = "Y/CbCr 4:2:0 P010 UBWC"; break;
 
 	default:
 		/* Compressed formats */
diff --git a/drivers/of/Kconfig b/drivers/of/Kconfig
index 7810bad..52a297d 100644
--- a/drivers/of/Kconfig
+++ b/drivers/of/Kconfig
@@ -118,4 +118,9 @@
 config OF_NUMA
 	bool
 
+config OF_BATTERYDATA
+	def_bool y
+	help
+	  OpenFirmware BatteryData accessors
+
 endif # OF
diff --git a/drivers/of/Makefile b/drivers/of/Makefile
index 4b8dabe..b2f474a 100644
--- a/drivers/of/Makefile
+++ b/drivers/of/Makefile
@@ -15,5 +15,6 @@
 obj-$(CONFIG_OF_OVERLAY) += overlay.o
 obj-$(CONFIG_OF_SLIMBUS)        += of_slimbus.o
 obj-$(CONFIG_OF_NUMA) += of_numa.o
+obj-$(CONFIG_OF_BATTERYDATA) += of_batterydata.o
 
 obj-$(CONFIG_OF_UNITTEST) += unittest-data/
diff --git a/drivers/of/of_batterydata.c b/drivers/of/of_batterydata.c
new file mode 100644
index 0000000..43417b2
--- /dev/null
+++ b/drivers/of/of_batterydata.c
@@ -0,0 +1,457 @@
+/* Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)	"%s: " fmt, __func__
+
+#include <linux/err.h>
+#include <linux/of.h>
+#include <linux/slab.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/batterydata-lib.h>
+#include <linux/power_supply.h>
+
+static int of_batterydata_read_lut(const struct device_node *np,
+			int max_cols, int max_rows, int *ncols, int *nrows,
+			int *col_legend_data, int *row_legend_data,
+			int *lut_data)
+{
+	struct property *prop;
+	const __be32 *data;
+	int cols, rows, size, i, j, *out_values;
+
+	prop = of_find_property(np, "qcom,lut-col-legend", NULL);
+	if (!prop) {
+		pr_err("%s: No col legend found\n", np->name);
+		return -EINVAL;
+	} else if (!prop->value) {
+		pr_err("%s: No col legend value found, np->name\n", np->name);
+		return -ENODATA;
+	} else if (prop->length > max_cols * sizeof(int)) {
+		pr_err("%s: Too many columns\n", np->name);
+		return -EINVAL;
+	}
+
+	cols = prop->length/sizeof(int);
+	*ncols = cols;
+	data = prop->value;
+	for (i = 0; i < cols; i++)
+		*col_legend_data++ = be32_to_cpup(data++);
+
+	rows = 0;
+
+	prop = of_find_property(np, "qcom,lut-row-legend", NULL);
+	if (!prop || row_legend_data == NULL) {
+		/* single row lut */
+		rows = 1;
+	} else if (!prop->value) {
+		pr_err("%s: No row legend value found\n", np->name);
+		return -ENODATA;
+	} else if (prop->length > max_rows * sizeof(int)) {
+		pr_err("%s: Too many rows\n", np->name);
+		return -EINVAL;
+	}
+
+	if (rows != 1) {
+		rows = prop->length/sizeof(int);
+		*nrows = rows;
+		data = prop->value;
+		for (i = 0; i < rows; i++)
+			*row_legend_data++ = be32_to_cpup(data++);
+	}
+
+	prop = of_find_property(np, "qcom,lut-data", NULL);
+	if (!prop) {
+		pr_err("prop 'qcom,lut-data' not found\n");
+		return -EINVAL;
+	}
+	data = prop->value;
+	size = prop->length/sizeof(int);
+	if (size != cols * rows) {
+		pr_err("%s: data size mismatch, %dx%d != %d\n",
+				np->name, cols, rows, size);
+		return -EINVAL;
+	}
+	for (i = 0; i < rows; i++) {
+		out_values = lut_data + (max_cols * i);
+		for (j = 0; j < cols; j++) {
+			*out_values++ = be32_to_cpup(data++);
+			pr_debug("Value = %d\n", *(out_values-1));
+		}
+	}
+
+	return 0;
+}
+
+static int of_batterydata_read_sf_lut(struct device_node *data_node,
+				const char *name, struct sf_lut *lut)
+{
+	struct device_node *node = of_find_node_by_name(data_node, name);
+	int rc;
+
+	if (!lut) {
+		pr_debug("No lut provided, skipping\n");
+		return 0;
+	} else if (!node) {
+		pr_err("Couldn't find %s node.\n", name);
+		return -EINVAL;
+	}
+
+	rc = of_batterydata_read_lut(node, PC_CC_COLS, PC_CC_ROWS,
+			&lut->cols, &lut->rows, lut->row_entries,
+			lut->percent, *lut->sf);
+	if (rc) {
+		pr_err("Failed to read %s node.\n", name);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int of_batterydata_read_pc_temp_ocv_lut(struct device_node *data_node,
+				const char *name, struct pc_temp_ocv_lut *lut)
+{
+	struct device_node *node = of_find_node_by_name(data_node, name);
+	int rc;
+
+	if (!lut) {
+		pr_debug("No lut provided, skipping\n");
+		return 0;
+	} else if (!node) {
+		pr_err("Couldn't find %s node.\n", name);
+		return -EINVAL;
+	}
+	rc = of_batterydata_read_lut(node, PC_TEMP_COLS, PC_TEMP_ROWS,
+			&lut->cols, &lut->rows, lut->temp, lut->percent,
+			*lut->ocv);
+	if (rc) {
+		pr_err("Failed to read %s node.\n", name);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int of_batterydata_read_ibat_temp_acc_lut(struct device_node *data_node,
+			const char *name, struct ibat_temp_acc_lut *lut)
+{
+	struct device_node *node = of_find_node_by_name(data_node, name);
+	int rc;
+
+	if (!lut) {
+		pr_debug("No lut provided, skipping\n");
+		return 0;
+	} else if (!node) {
+		pr_debug("Couldn't find %s node.\n", name);
+		return 0;
+	}
+	rc = of_batterydata_read_lut(node, ACC_TEMP_COLS, ACC_IBAT_ROWS,
+			&lut->cols, &lut->rows, lut->temp, lut->ibat,
+			*lut->acc);
+	if (rc) {
+		pr_err("Failed to read %s node.\n", name);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int of_batterydata_read_single_row_lut(struct device_node *data_node,
+				const char *name, struct single_row_lut *lut)
+{
+	struct device_node *node = of_find_node_by_name(data_node, name);
+	int rc;
+
+	if (!lut) {
+		pr_debug("No lut provided, skipping\n");
+		return 0;
+	} else if (!node) {
+		pr_err("Couldn't find %s node.\n", name);
+		return -EINVAL;
+	}
+
+	rc = of_batterydata_read_lut(node, MAX_SINGLE_LUT_COLS, 1,
+			&lut->cols, NULL, lut->x, NULL, lut->y);
+	if (rc) {
+		pr_err("Failed to read %s node.\n", name);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int of_batterydata_read_batt_id_kohm(const struct device_node *np,
+				const char *propname, struct batt_ids *batt_ids)
+{
+	struct property *prop;
+	const __be32 *data;
+	int num, i, *id_kohm = batt_ids->kohm;
+
+	prop = of_find_property(np, "qcom,batt-id-kohm", NULL);
+	if (!prop) {
+		pr_err("%s: No battery id resistor found\n", np->name);
+		return -EINVAL;
+	} else if (!prop->value) {
+		pr_err("%s: No battery id resistor value found, np->name\n",
+						np->name);
+		return -ENODATA;
+	} else if (prop->length > MAX_BATT_ID_NUM * sizeof(__be32)) {
+		pr_err("%s: Too many battery id resistors\n", np->name);
+		return -EINVAL;
+	}
+
+	num = prop->length/sizeof(__be32);
+	batt_ids->num = num;
+	data = prop->value;
+	for (i = 0; i < num; i++)
+		*id_kohm++ = be32_to_cpup(data++);
+
+	return 0;
+}
+
+#define OF_PROP_READ(property, qpnp_dt_property, node, rc, optional)	\
+do {									\
+	if (rc)								\
+		break;							\
+	rc = of_property_read_u32(node, "qcom," qpnp_dt_property,	\
+					&property);			\
+									\
+	if ((rc == -EINVAL) && optional) {				\
+		property = -EINVAL;					\
+		rc = 0;							\
+	} else if (rc) {						\
+		pr_err("Error reading " #qpnp_dt_property		\
+				" property rc = %d\n", rc);		\
+	}								\
+} while (0)
+
+static int of_batterydata_load_battery_data(struct device_node *node,
+				int best_id_kohm,
+				struct bms_battery_data *batt_data)
+{
+	int rc;
+
+	rc = of_batterydata_read_single_row_lut(node, "qcom,fcc-temp-lut",
+			batt_data->fcc_temp_lut);
+	if (rc)
+		return rc;
+
+	rc = of_batterydata_read_pc_temp_ocv_lut(node,
+			"qcom,pc-temp-ocv-lut",
+			batt_data->pc_temp_ocv_lut);
+	if (rc)
+		return rc;
+
+	rc = of_batterydata_read_sf_lut(node, "qcom,rbatt-sf-lut",
+			batt_data->rbatt_sf_lut);
+	if (rc)
+		return rc;
+
+	rc = of_batterydata_read_ibat_temp_acc_lut(node, "qcom,ibat-acc-lut",
+						batt_data->ibat_acc_lut);
+	if (rc)
+		return rc;
+
+	rc = of_property_read_string(node, "qcom,battery-type",
+					&batt_data->battery_type);
+	if (rc) {
+		pr_err("Error reading qcom,battery-type property rc=%d\n", rc);
+		batt_data->battery_type = NULL;
+		return rc;
+	}
+
+	OF_PROP_READ(batt_data->fcc, "fcc-mah", node, rc, false);
+	OF_PROP_READ(batt_data->default_rbatt_mohm,
+			"default-rbatt-mohm", node, rc, false);
+	OF_PROP_READ(batt_data->rbatt_capacitive_mohm,
+			"rbatt-capacitive-mohm", node, rc, false);
+	OF_PROP_READ(batt_data->flat_ocv_threshold_uv,
+			"flat-ocv-threshold-uv", node, rc, true);
+	OF_PROP_READ(batt_data->max_voltage_uv,
+			"max-voltage-uv", node, rc, true);
+	OF_PROP_READ(batt_data->cutoff_uv, "v-cutoff-uv", node, rc, true);
+	OF_PROP_READ(batt_data->iterm_ua, "chg-term-ua", node, rc, true);
+	OF_PROP_READ(batt_data->fastchg_current_ma,
+			"fastchg-current-ma", node, rc, true);
+	OF_PROP_READ(batt_data->fg_cc_cv_threshold_mv,
+			"fg-cc-cv-threshold-mv", node, rc, true);
+
+	batt_data->batt_id_kohm = best_id_kohm;
+
+	return rc;
+}
+
+static int64_t of_batterydata_convert_battery_id_kohm(int batt_id_uv,
+				int rpull_up, int vadc_vdd)
+{
+	int64_t resistor_value_kohm, denom;
+
+	if (batt_id_uv == 0) {
+		/* vadc not correct or batt id line grounded, report 0 kohms */
+		return 0;
+	}
+	/* calculate the battery id resistance reported via ADC */
+	denom = div64_s64(vadc_vdd * 1000000LL, batt_id_uv) - 1000000LL;
+
+	if (denom == 0) {
+		/* batt id connector might be open, return 0 kohms */
+		return 0;
+	}
+	resistor_value_kohm = div64_s64(rpull_up * 1000000LL + denom/2, denom);
+
+	pr_debug("batt id voltage = %d, resistor value = %lld\n",
+			batt_id_uv, resistor_value_kohm);
+
+	return resistor_value_kohm;
+}
+
+struct device_node *of_batterydata_get_best_profile(
+		const struct device_node *batterydata_container_node,
+		int batt_id_kohm, const char *batt_type)
+{
+	struct batt_ids batt_ids;
+	struct device_node *node, *best_node = NULL;
+	const char *battery_type = NULL;
+	int delta = 0, best_delta = 0, best_id_kohm = 0, id_range_pct,
+		i = 0, rc = 0, limit = 0;
+	bool in_range = false;
+
+	/* read battery id range percentage for best profile */
+	rc = of_property_read_u32(batterydata_container_node,
+			"qcom,batt-id-range-pct", &id_range_pct);
+
+	if (rc) {
+		if (rc == -EINVAL) {
+			id_range_pct = 0;
+		} else {
+			pr_err("failed to read battery id range\n");
+			return ERR_PTR(-ENXIO);
+		}
+	}
+
+	/*
+	 * Find the battery data with a battery id resistor closest to this one
+	 */
+	for_each_child_of_node(batterydata_container_node, node) {
+		if (batt_type != NULL) {
+			rc = of_property_read_string(node, "qcom,battery-type",
+							&battery_type);
+			if (!rc && strcmp(battery_type, batt_type) == 0) {
+				best_node = node;
+				best_id_kohm = batt_id_kohm;
+				break;
+			}
+		} else {
+			rc = of_batterydata_read_batt_id_kohm(node,
+							"qcom,batt-id-kohm",
+							&batt_ids);
+			if (rc)
+				continue;
+			for (i = 0; i < batt_ids.num; i++) {
+				delta = abs(batt_ids.kohm[i] - batt_id_kohm);
+				limit = (batt_ids.kohm[i] * id_range_pct) / 100;
+				in_range = (delta <= limit);
+				/*
+				 * Check if the delta is the lowest one
+				 * and also if the limits are in range
+				 * before selecting the best node.
+				 */
+				if ((delta < best_delta || !best_node)
+					&& in_range) {
+					best_node = node;
+					best_delta = delta;
+					best_id_kohm = batt_ids.kohm[i];
+				}
+			}
+		}
+	}
+
+	if (best_node == NULL) {
+		pr_err("No battery data found\n");
+		return best_node;
+	}
+
+	/* check that profile id is in range of the measured batt_id */
+	if (abs(best_id_kohm - batt_id_kohm) >
+			((best_id_kohm * id_range_pct) / 100)) {
+		pr_err("out of range: profile id %d batt id %d pct %d",
+			best_id_kohm, batt_id_kohm, id_range_pct);
+		return NULL;
+	}
+
+	rc = of_property_read_string(best_node, "qcom,battery-type",
+							&battery_type);
+	if (!rc)
+		pr_info("%s found\n", battery_type);
+	else
+		pr_info("%s found\n", best_node->name);
+
+	return best_node;
+}
+
+int of_batterydata_read_data(struct device_node *batterydata_container_node,
+				struct bms_battery_data *batt_data,
+				int batt_id_uv)
+{
+	struct device_node *node, *best_node;
+	struct batt_ids batt_ids;
+	const char *battery_type = NULL;
+	int delta, best_delta, batt_id_kohm, rpull_up_kohm,
+		vadc_vdd_uv, best_id_kohm, i, rc = 0;
+
+	node = batterydata_container_node;
+	OF_PROP_READ(rpull_up_kohm, "rpull-up-kohm", node, rc, false);
+	OF_PROP_READ(vadc_vdd_uv, "vref-batt-therm", node, rc, false);
+	if (rc)
+		return rc;
+
+	batt_id_kohm = of_batterydata_convert_battery_id_kohm(batt_id_uv,
+					rpull_up_kohm, vadc_vdd_uv);
+	best_node = NULL;
+	best_delta = 0;
+	best_id_kohm = 0;
+
+	/*
+	 * Find the battery data with a battery id resistor closest to this one
+	 */
+	for_each_child_of_node(batterydata_container_node, node) {
+		rc = of_batterydata_read_batt_id_kohm(node,
+						"qcom,batt-id-kohm",
+						&batt_ids);
+		if (rc)
+			continue;
+		for (i = 0; i < batt_ids.num; i++) {
+			delta = abs(batt_ids.kohm[i] - batt_id_kohm);
+			if (delta < best_delta || !best_node) {
+				best_node = node;
+				best_delta = delta;
+				best_id_kohm = batt_ids.kohm[i];
+			}
+		}
+	}
+
+	if (best_node == NULL) {
+		pr_err("No battery data found\n");
+		return -ENODATA;
+	}
+	rc = of_property_read_string(best_node, "qcom,battery-type",
+							&battery_type);
+	if (!rc)
+		pr_info("%s loaded\n", battery_type);
+	else
+		pr_info("%s loaded\n", best_node->name);
+
+	return of_batterydata_load_battery_data(best_node,
+					best_id_kohm, batt_data);
+}
+
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/platform/msm/gsi/gsi.c b/drivers/platform/msm/gsi/gsi.c
index fff8966..b7685cb 100644
--- a/drivers/platform/msm/gsi/gsi.c
+++ b/drivers/platform/msm/gsi/gsi.c
@@ -415,6 +415,16 @@
 {
 	uint32_t val;
 
+	/*
+	 * allocate new events for this channel first
+	 * before submitting the new TREs.
+	 * for TO_GSI channels the event ring doorbell is rang as part of
+	 * interrupt handling.
+	 */
+	if (ctx->evtr && ctx->props.dir == GSI_CHAN_DIR_FROM_GSI)
+		gsi_ring_evt_doorbell(ctx->evtr);
+	ctx->ring.wp = ctx->ring.wp_local;
+
 	/* write order MUST be MSB followed by LSB */
 	val = ((ctx->ring.wp_local >> 32) &
 			GSI_EE_n_GSI_CH_k_DOORBELL_1_WRITE_PTR_MSB_BMSK) <<
@@ -470,8 +480,8 @@
 			cntr = 0;
 			rp = gsi_readl(gsi_ctx->base +
 				GSI_EE_n_EV_CH_k_CNTXT_4_OFFS(i, ee));
-			rp |= ((uint64_t)gsi_readl(gsi_ctx->base +
-				GSI_EE_n_EV_CH_k_CNTXT_5_OFFS(i, ee))) << 32;
+			rp |= ctx->ring.rp & 0xFFFFFFFF00000000;
+
 			ctx->ring.rp = rp;
 			while (ctx->ring.rp_local != rp) {
 				++cntr;
@@ -1529,6 +1539,7 @@
 static int gsi_validate_channel_props(struct gsi_chan_props *props)
 {
 	uint64_t ra;
+	uint64_t last;
 
 	if (props->ch_id >= gsi_ctx->max_ch) {
 		GSIERR("ch_id %u invalid\n", props->ch_id);
@@ -1556,6 +1567,17 @@
 		return -GSI_STATUS_INVALID_PARAMS;
 	}
 
+	last = props->ring_base_addr + props->ring_len - props->re_size;
+
+	/* MSB should stay same within the ring */
+	if ((props->ring_base_addr & 0xFFFFFFFF00000000ULL) !=
+	    (last & 0xFFFFFFFF00000000ULL)) {
+		GSIERR("MSB is not fixed on ring base 0x%llx size 0x%x\n",
+			props->ring_base_addr,
+			props->ring_len);
+		return -GSI_STATUS_INVALID_PARAMS;
+	}
+
 	if (props->prot == GSI_CHAN_PROT_GPI &&
 			!props->ring_base_vaddr) {
 		GSIERR("protocol %u requires ring base VA\n", props->prot);
@@ -2128,29 +2150,22 @@
 		uint16_t *num_free_re)
 {
 	uint16_t start;
-	uint16_t start_hw;
 	uint16_t end;
 	uint64_t rp;
-	uint64_t rp_hw;
 	int ee = gsi_ctx->per.ee;
 	uint16_t used;
-	uint16_t used_hw;
-
-	rp_hw = gsi_readl(gsi_ctx->base +
-		GSI_EE_n_GSI_CH_k_CNTXT_4_OFFS(ctx->props.ch_id, ee));
-	rp_hw |= ((uint64_t)gsi_readl(gsi_ctx->base +
-		GSI_EE_n_GSI_CH_k_CNTXT_5_OFFS(ctx->props.ch_id, ee)))
-		<< 32;
 
 	if (!ctx->evtr) {
-		rp = rp_hw;
+		rp = gsi_readl(gsi_ctx->base +
+			GSI_EE_n_GSI_CH_k_CNTXT_4_OFFS(ctx->props.ch_id, ee));
+		rp |= ctx->ring.rp & 0xFFFFFFFF00000000;
+
 		ctx->ring.rp = rp;
 	} else {
 		rp = ctx->ring.rp_local;
 	}
 
 	start = gsi_find_idx_from_addr(&ctx->ring, rp);
-	start_hw = gsi_find_idx_from_addr(&ctx->ring, rp_hw);
 	end = gsi_find_idx_from_addr(&ctx->ring, ctx->ring.wp_local);
 
 	if (end >= start)
@@ -2158,13 +2173,7 @@
 	else
 		used = ctx->ring.max_num_elem + 1 - (start - end);
 
-	if (end >= start_hw)
-		used_hw = end - start_hw;
-	else
-		used_hw = ctx->ring.max_num_elem + 1 - (start_hw - end);
-
 	*num_free_re = ctx->ring.max_num_elem - used;
-	gsi_update_ch_dp_stats(ctx, used_hw);
 }
 
 int gsi_query_channel_info(unsigned long chan_hdl,
@@ -2274,14 +2283,12 @@
 
 	rp = gsi_readl(gsi_ctx->base +
 		GSI_EE_n_GSI_CH_k_CNTXT_4_OFFS(ctx->props.ch_id, ee));
-	rp |= ((uint64_t)gsi_readl(gsi_ctx->base +
-		GSI_EE_n_GSI_CH_k_CNTXT_5_OFFS(ctx->props.ch_id, ee))) << 32;
+	rp |= ctx->ring.rp & 0xFFFFFFFF00000000;
 	ctx->ring.rp = rp;
 
 	wp = gsi_readl(gsi_ctx->base +
 		GSI_EE_n_GSI_CH_k_CNTXT_6_OFFS(ctx->props.ch_id, ee));
-	wp |= ((uint64_t)gsi_readl(gsi_ctx->base +
-		GSI_EE_n_GSI_CH_k_CNTXT_7_OFFS(ctx->props.ch_id, ee))) << 32;
+	wp |= ctx->ring.wp & 0xFFFFFFFF00000000;
 	ctx->ring.wp = wp;
 
 	if (ctx->props.dir == GSI_CHAN_DIR_FROM_GSI)
@@ -2353,6 +2360,8 @@
 			tre.re_type = GSI_RE_XFER;
 		} else if (xfer[i].type == GSI_XFER_ELEM_IMME_CMD) {
 			tre.re_type = GSI_RE_IMMD_CMD;
+		} else if (xfer[i].type == GSI_XFER_ELEM_NOP) {
+			tre.re_type = GSI_RE_NOP;
 		} else {
 			GSIERR("chan_hdl=%lu bad RE type=%u\n", chan_hdl,
 				xfer[i].type);
@@ -2420,6 +2429,9 @@
 		return -GSI_STATUS_UNSUPPORTED_OP;
 	}
 
+	if (ctx->ring.wp == ctx->ring.wp_local)
+		return GSI_STATUS_SUCCESS;
+
 	gsi_ring_chan_doorbell(ctx);
 
 	return GSI_STATUS_SUCCESS;
@@ -2457,19 +2469,22 @@
 	}
 
 	spin_lock_irqsave(&ctx->evtr->ring.slock, flags);
-	rp = gsi_readl(gsi_ctx->base +
-		GSI_EE_n_EV_CH_k_CNTXT_4_OFFS(ctx->evtr->id, ee));
-	rp |= ((uint64_t)gsi_readl(gsi_ctx->base +
-		GSI_EE_n_EV_CH_k_CNTXT_5_OFFS(ctx->evtr->id, ee))) << 32;
-	ctx->evtr->ring.rp = rp;
-	if (rp == ctx->evtr->ring.rp_local) {
+	if (ctx->evtr->ring.rp == ctx->evtr->ring.rp_local) {
+		/* update rp to see of we have anything new to process */
+		rp = gsi_readl(gsi_ctx->base +
+			GSI_EE_n_EV_CH_k_CNTXT_4_OFFS(ctx->evtr->id, ee));
+		rp |= ctx->ring.rp & 0xFFFFFFFF00000000;
+
+		ctx->evtr->ring.rp = rp;
+	}
+
+	if (ctx->evtr->ring.rp == ctx->evtr->ring.rp_local) {
 		spin_unlock_irqrestore(&ctx->evtr->ring.slock, flags);
 		ctx->stats.poll_empty++;
 		return GSI_STATUS_POLL_EMPTY;
 	}
 
 	gsi_process_evt_re(ctx->evtr, notify, false);
-	gsi_ring_evt_doorbell(ctx->evtr);
 	spin_unlock_irqrestore(&ctx->evtr->ring.slock, flags);
 	ctx->stats.poll_ok++;
 
diff --git a/drivers/platform/msm/gsi/gsi.h b/drivers/platform/msm/gsi/gsi.h
index f53a4bd..32fb178 100644
--- a/drivers/platform/msm/gsi/gsi.h
+++ b/drivers/platform/msm/gsi/gsi.h
@@ -209,6 +209,7 @@
 enum gsi_re_type {
 	GSI_RE_XFER = 0x2,
 	GSI_RE_IMMD_CMD = 0x3,
+	GSI_RE_NOP = 0x4,
 };
 
 struct __packed gsi_tre {
diff --git a/drivers/platform/msm/gsi/gsi_dbg.c b/drivers/platform/msm/gsi/gsi_dbg.c
index 717c8917..b1d1dfa 100644
--- a/drivers/platform/msm/gsi/gsi_dbg.c
+++ b/drivers/platform/msm/gsi/gsi_dbg.c
@@ -490,11 +490,6 @@
 		goto error;
 	}
 
-	if (gsi_ctx->chan[ch_id].props.prot == GSI_CHAN_PROT_GPI) {
-		TERR("valid for non GPI channels only\n");
-		goto error;
-	}
-
 	if (gsi_ctx->chan[ch_id].enable_dp_stats == enable) {
 		TERR("ch_%d: already enabled/disabled\n", ch_id);
 		return -EFAULT;
@@ -631,7 +626,7 @@
 	else
 		used_hw = ctx->ring.max_num_elem + 1 - (start_hw - end_hw);
 
-	TERR("ch %d used %d\n", ctx->props.ch_id, used_hw);
+	TDBG("ch %d used %d\n", ctx->props.ch_id, used_hw);
 	gsi_update_ch_dp_stats(ctx, used_hw);
 }
 
@@ -641,7 +636,6 @@
 
 	for (ch_id = 0; ch_id < gsi_ctx->max_ch; ch_id++) {
 		if (gsi_ctx->chan[ch_id].allocated &&
-		    gsi_ctx->chan[ch_id].props.prot != GSI_CHAN_PROT_GPI &&
 		    gsi_ctx->chan[ch_id].enable_dp_stats)
 			gsi_dbg_update_ch_dp_stats(&gsi_ctx->chan[ch_id]);
 	}
diff --git a/drivers/platform/msm/ipa/ipa_api.c b/drivers/platform/msm/ipa/ipa_api.c
index 6dd371e..d45fa51 100644
--- a/drivers/platform/msm/ipa/ipa_api.c
+++ b/drivers/platform/msm/ipa/ipa_api.c
@@ -1635,6 +1635,25 @@
 EXPORT_SYMBOL(ipa_get_smem_restr_bytes);
 
 /**
+ * ipa_broadcast_wdi_quota_reach_ind() - quota reach
+ * @uint32_t fid: [in] input netdev ID
+ * @uint64_t num_bytes: [in] used bytes
+ *
+ * Returns:	0 on success, negative on failure
+ */
+int ipa_broadcast_wdi_quota_reach_ind(uint32_t fid,
+		uint64_t num_bytes)
+{
+	int ret;
+
+	IPA_API_DISPATCH_RETURN(ipa_broadcast_wdi_quota_reach_ind,
+		fid, num_bytes);
+
+	return ret;
+}
+EXPORT_SYMBOL(ipa_broadcast_wdi_quota_reach_ind);
+
+/**
  * ipa_uc_wdi_get_dbpa() - To retrieve
  * doorbell physical address of wlan pipes
  * @param:  [in/out] input/output parameters
diff --git a/drivers/platform/msm/ipa/ipa_api.h b/drivers/platform/msm/ipa/ipa_api.h
index 1b8e3d6..bfe1608 100644
--- a/drivers/platform/msm/ipa/ipa_api.h
+++ b/drivers/platform/msm/ipa/ipa_api.h
@@ -183,6 +183,9 @@
 
 	u16 (*ipa_get_smem_restr_bytes)(void);
 
+	int (*ipa_broadcast_wdi_quota_reach_ind)(uint32_t fid,
+		uint64_t num_bytes);
+
 	int (*ipa_uc_wdi_get_dbpa)(struct ipa_wdi_db_params *out);
 
 	int (*ipa_uc_reg_rdyCB)(struct ipa_wdi_uc_ready_params *param);
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
index 244c80c..1a0d3ad 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_i.h
@@ -937,6 +937,10 @@
 	struct IpaHwStatsWDIInfoData_t *wdi_uc_stats_mmio;
 	void *priv;
 	ipa_uc_ready_cb uc_ready_cb;
+	/* for AP+STA stats update */
+#ifdef IPA_WAN_MSG_IPv6_ADDR_GW_LEN
+	ipa_wdi_meter_notifier_cb stats_notify;
+#endif
 };
 
 /**
@@ -1611,6 +1615,7 @@
 int ipa3_suspend_wdi_pipe(u32 clnt_hdl);
 int ipa3_get_wdi_stats(struct IpaHwStatsWDIInfoData_t *stats);
 u16 ipa3_get_smem_restr_bytes(void);
+int ipa3_broadcast_wdi_quota_reach_ind(uint32_t fid, uint64_t num_bytes);
 int ipa3_setup_uc_ntn_pipes(struct ipa_ntn_conn_in_params *in,
 		ipa_notify_cb notify, void *priv, u8 hdr_len,
 		struct ipa_ntn_conn_out_params *outp);
@@ -1651,6 +1656,9 @@
 
 bool ipa3_get_client_uplink(int pipe_idx);
 
+int ipa3_get_wlan_stats(struct ipa_get_wdi_sap_stats *wdi_sap_stats);
+
+int ipa3_set_wlan_quota(struct ipa_set_wifi_quota *wdi_quota);
 /*
  * IPADMA
  */
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 343901f..19c3de4a 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c
@@ -884,7 +884,8 @@
 		IPAWANDBG("Quota reached indication on qmux(%d) Mbytes(%lu)\n",
 			  qmi_ind.apn.mux_id,
 			  (unsigned long int) qmi_ind.apn.num_Mbytes);
-		ipa3_broadcast_quota_reach_ind(qmi_ind.apn.mux_id);
+		ipa3_broadcast_quota_reach_ind(qmi_ind.apn.mux_id,
+			IPA_UPSTEAM_MODEM);
 	}
 }
 
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.h b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.h
index 3659a22..4fde261 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.h
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.h
@@ -181,7 +181,8 @@
 
 int rmnet_ipa3_set_data_quota(struct wan_ioctl_set_data_quota *data);
 
-void ipa3_broadcast_quota_reach_ind(uint32_t mux_id);
+void ipa3_broadcast_quota_reach_ind(uint32_t mux_id,
+	enum ipa_upstream_type upstream_type);
 
 int rmnet_ipa3_set_tether_client_pipe(struct wan_ioctl_set_tether_client_pipe
 	*data);
@@ -189,6 +190,8 @@
 int rmnet_ipa3_query_tethering_stats(struct wan_ioctl_query_tether_stats *data,
 	bool reset);
 
+int rmnet_ipa3_reset_tethering_stats(struct wan_ioctl_reset_tether_stats *data);
+
 int ipa3_qmi_get_data_stats(struct ipa_get_data_stats_req_msg_v01 *req,
 	struct ipa_get_data_stats_resp_msg_v01 *resp);
 
@@ -283,7 +286,8 @@
 	return -EPERM;
 }
 
-static inline void ipa3_broadcast_quota_reach_ind(uint32_t mux_id) { }
+static inline void ipa3_broadcast_quota_reach_ind(uint32_t mux_id,
+	enum ipa_upstream_type upstream_type) { }
 
 static inline int ipa3_qmi_get_data_stats(
 	struct ipa_get_data_stats_req_msg_v01 *req,
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_wdi.c b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_wdi.c
index 8f87baf..c69a3d0 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_uc_wdi.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_uc_wdi.c
@@ -13,6 +13,7 @@
 #include <linux/dmapool.h>
 #include <linux/delay.h>
 #include <linux/mm.h>
+#include "ipa_qmi_service.h"
 
 #define IPA_HOLB_TMR_DIS 0x0
 
@@ -1190,6 +1191,12 @@
 	ep->client_notify = in->sys.notify;
 	ep->priv = in->sys.priv;
 
+	/* for AP+STA stats update */
+	if (in->wdi_notify)
+		ipa3_ctx->uc_wdi_ctx.stats_notify = in->wdi_notify;
+	else
+		IPADBG("in->wdi_notify is null\n");
+
 	if (!ep->skip_ep_cfg) {
 		if (ipa3_cfg_ep(ipa_ep_idx, &in->sys.ipa_ep_cfg)) {
 			IPAERR("fail to configure EP.\n");
@@ -1281,6 +1288,12 @@
 
 	IPADBG("client (ep: %d) disconnected\n", clnt_hdl);
 
+	/* for AP+STA stats update */
+	if (ipa3_ctx->uc_wdi_ctx.stats_notify)
+		ipa3_ctx->uc_wdi_ctx.stats_notify = NULL;
+	else
+		IPADBG("uc_wdi_ctx.stats_notify already null\n");
+
 uc_timeout:
 	return result;
 }
@@ -1626,6 +1639,23 @@
 	return result;
 }
 
+/**
+ * ipa_broadcast_wdi_quota_reach_ind() - quota reach
+ * @uint32_t fid: [in] input netdev ID
+ * @uint64_t num_bytes: [in] used bytes
+ *
+ * Returns:	0 on success, negative on failure
+ */
+int ipa3_broadcast_wdi_quota_reach_ind(uint32_t fid,
+	uint64_t num_bytes)
+{
+	IPAERR("Quota reached indication on fid(%d) Mbytes(%lu)\n",
+			  fid,
+			  (unsigned long int) num_bytes);
+	ipa3_broadcast_quota_reach_ind(0, IPA_UPSTEAM_WLAN);
+	return 0;
+}
+
 int ipa3_write_qmapid_wdi_pipe(u32 clnt_hdl, u8 qmap_id)
 {
 	int result = 0;
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
index 0519563..757541b 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_utils.c
@@ -1738,11 +1738,39 @@
 	}
 }
 
+/* ipa3_get_wlan_stats() - get ipa wifi stats
+ *
+ * Return value: success or failure
+ */
+int ipa3_get_wlan_stats(struct ipa_get_wdi_sap_stats *wdi_sap_stats)
+{
+	if (ipa3_ctx->uc_wdi_ctx.stats_notify) {
+		ipa3_ctx->uc_wdi_ctx.stats_notify(IPA_GET_WDI_SAP_STATS,
+			wdi_sap_stats);
+	} else {
+		IPAERR("uc_wdi_ctx.stats_notify NULL\n");
+		return -EFAULT;
+	}
+	return 0;
+}
+
+int ipa3_set_wlan_quota(struct ipa_set_wifi_quota *wdi_quota)
+{
+	if (ipa3_ctx->uc_wdi_ctx.stats_notify) {
+		ipa3_ctx->uc_wdi_ctx.stats_notify(IPA_SET_WIFI_QUOTA,
+			wdi_quota);
+	} else {
+		IPAERR("uc_wdi_ctx.stats_notify NULL\n");
+		return -EFAULT;
+	}
+	return 0;
+}
+
 /**
  * ipa3_get_client() - provide client mapping
  * @client: client type
  *
- * Return value: none
+ * Return value: client mapping enum
  */
 enum ipacm_client_enum ipa3_get_client(int pipe_idx)
 {
@@ -3742,6 +3770,8 @@
 	api_ctrl->ipa_suspend_wdi_pipe = ipa3_suspend_wdi_pipe;
 	api_ctrl->ipa_get_wdi_stats = ipa3_get_wdi_stats;
 	api_ctrl->ipa_get_smem_restr_bytes = ipa3_get_smem_restr_bytes;
+	api_ctrl->ipa_broadcast_wdi_quota_reach_ind =
+			ipa3_broadcast_wdi_quota_reach_ind;
 	api_ctrl->ipa_uc_wdi_get_dbpa = ipa3_uc_wdi_get_dbpa;
 	api_ctrl->ipa_uc_reg_rdyCB = ipa3_uc_reg_rdyCB;
 	api_ctrl->ipa_uc_dereg_rdyCB = ipa3_uc_dereg_rdyCB;
diff --git a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
index d747771..cf9775b 100644
--- a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
+++ b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa.c
@@ -52,6 +52,7 @@
 #define DEFAULT_OUTSTANDING_LOW 64
 
 #define IPA_WWAN_DEV_NAME "rmnet_ipa%d"
+#define IPA_UPSTEAM_WLAN_IFACE_NAME "wlan0"
 
 #define IPA_WWAN_RX_SOFTIRQ_THRESH 16
 
@@ -781,6 +782,22 @@
 	return MAX_NUM_OF_MUX_CHANNEL;
 }
 
+static enum ipa_upstream_type find_upstream_type(const char *upstreamIface)
+{
+	int i;
+
+	for (i = 0; i < MAX_NUM_OF_MUX_CHANNEL; i++) {
+		if (strcmp(rmnet_ipa3_ctx->mux_channel[i].vchannel_name,
+					upstreamIface) == 0)
+			return IPA_UPSTEAM_MODEM;
+	}
+
+	if (strcmp(IPA_UPSTEAM_WLAN_IFACE_NAME, upstreamIface) == 0)
+		return IPA_UPSTEAM_WLAN;
+	else
+		return MAX_NUM_OF_MUX_CHANNEL;
+}
+
 static int ipa3_wwan_register_to_ipa(int index)
 {
 	struct ipa_tx_intf tx_properties = {0};
@@ -2627,10 +2644,10 @@
 }
 
 /**
- * rmnet_ipa_set_data_quota() - Data quota setting IOCTL handler
+ * rmnet_ipa_set_data_quota_modem() - Data quota setting IOCTL handler
  * @data - IOCTL data
  *
- * This function handles WAN_IOC_SET_DATA_QUOTA.
+ * This function handles WAN_IOC_SET_DATA_QUOTA on modem interface.
  * It translates the given interface name to the Modem MUX ID and
  * sends the request of the quota to the IPA Modem driver via QMI.
  *
@@ -2639,12 +2656,17 @@
  * -EFAULT: Invalid interface name provided
  * other: See ipa_qmi_set_data_quota
  */
-int rmnet_ipa3_set_data_quota(struct wan_ioctl_set_data_quota *data)
+static int rmnet_ipa3_set_data_quota_modem(
+	struct wan_ioctl_set_data_quota *data)
 {
 	u32 mux_id;
 	int index;
 	struct ipa_set_data_usage_quota_req_msg_v01 req;
 
+	/* stop quota */
+	if (!data->set_quota)
+		ipa3_qmi_stop_data_qouta();
+
 	index = find_vchannel_name_index(data->interface_name);
 	IPAWANERR("iface name %s, quota %lu\n",
 		  data->interface_name,
@@ -2668,6 +2690,64 @@
 	return ipa3_qmi_set_data_quota(&req);
 }
 
+static int rmnet_ipa3_set_data_quota_wifi(struct wan_ioctl_set_data_quota *data)
+{
+	struct ipa_set_wifi_quota wifi_quota;
+	int rc = 0;
+
+	memset(&wifi_quota, 0, sizeof(struct ipa_set_wifi_quota));
+	wifi_quota.set_quota = data->set_quota;
+	wifi_quota.quota_bytes = data->quota_mbytes;
+	IPAWANERR("iface name %s, quota %lu\n",
+		  data->interface_name,
+		  (unsigned long int) data->quota_mbytes);
+
+	rc = ipa3_set_wlan_quota(&wifi_quota);
+	/* check if wlan-fw takes this quota-set */
+	if (!wifi_quota.set_valid)
+		rc = -EFAULT;
+	return rc;
+}
+
+/**
+ * rmnet_ipa_set_data_quota() - Data quota setting IOCTL handler
+ * @data - IOCTL data
+ *
+ * This function handles WAN_IOC_SET_DATA_QUOTA.
+ * It translates the given interface name to the Modem MUX ID and
+ * sends the request of the quota to the IPA Modem driver via QMI.
+ *
+ * Return codes:
+ * 0: Success
+ * -EFAULT: Invalid interface name provided
+ * other: See ipa_qmi_set_data_quota
+ */
+int rmnet_ipa3_set_data_quota(struct wan_ioctl_set_data_quota *data)
+{
+	enum ipa_upstream_type upstream_type;
+	int rc = 0;
+
+	/* get IPA backhaul type */
+	upstream_type = find_upstream_type(data->interface_name);
+
+	if (upstream_type == IPA_UPSTEAM_MAX) {
+		IPAWANERR("Wrong interface_name name %s\n",
+			data->interface_name);
+	} else if (upstream_type == IPA_UPSTEAM_WLAN) {
+		rc = rmnet_ipa3_set_data_quota_wifi(data);
+		if (rc) {
+			IPAWANERR("set quota on wifi failed\n");
+			return rc;
+		}
+	} else {
+		rc = rmnet_ipa3_set_data_quota_modem(data);
+		if (rc) {
+			IPAWANERR("set quota on modem failed\n");
+			return rc;
+		}
+	}
+	return rc;
+}
  /* rmnet_ipa_set_tether_client_pipe() -
  * @data - IOCTL data
  *
@@ -2732,8 +2812,61 @@
 	return 0;
 }
 
-int rmnet_ipa3_query_tethering_stats(struct wan_ioctl_query_tether_stats *data,
-	bool reset)
+static int rmnet_ipa3_query_tethering_stats_wifi(
+	struct wan_ioctl_query_tether_stats *data, bool reset)
+{
+	struct ipa_get_wdi_sap_stats *sap_stats;
+	int rc;
+
+	sap_stats = kzalloc(sizeof(struct ipa_get_wdi_sap_stats),
+			GFP_KERNEL);
+	if (!sap_stats) {
+		IPAWANERR("Can't allocate memory for stats message\n");
+		return -ENOMEM;
+	}
+	memset(sap_stats, 0, sizeof(struct ipa_get_wdi_sap_stats));
+
+	sap_stats->reset_stats = reset;
+	IPAWANDBG("reset the pipe stats %d\n", sap_stats->reset_stats);
+
+	rc = ipa3_get_wlan_stats(sap_stats);
+	if (rc) {
+		IPAWANERR("can't get ipa3_get_wlan_stats\n");
+		kfree(sap_stats);
+		return rc;
+	} else if (reset) {
+		kfree(sap_stats);
+		return 0;
+	}
+
+	if (sap_stats->stats_valid) {
+		data->ipv4_tx_packets = sap_stats->ipv4_tx_packets;
+		data->ipv4_tx_bytes = sap_stats->ipv4_tx_bytes;
+		data->ipv4_rx_packets = sap_stats->ipv4_rx_packets;
+		data->ipv4_rx_bytes = sap_stats->ipv4_rx_bytes;
+		data->ipv6_tx_packets = sap_stats->ipv6_tx_packets;
+		data->ipv6_tx_bytes = sap_stats->ipv6_tx_bytes;
+		data->ipv6_rx_packets = sap_stats->ipv6_rx_packets;
+		data->ipv6_rx_bytes = sap_stats->ipv6_rx_bytes;
+	}
+
+	IPAWANDBG("v4_rx_p(%lu) v6_rx_p(%lu) v4_rx_b(%lu) v6_rx_b(%lu)\n",
+		(unsigned long int) data->ipv4_rx_packets,
+		(unsigned long int) data->ipv6_rx_packets,
+		(unsigned long int) data->ipv4_rx_bytes,
+		(unsigned long int) data->ipv6_rx_bytes);
+	IPAWANDBG("tx_p_v4(%lu)v6(%lu)tx_b_v4(%lu) v6(%lu)\n",
+		(unsigned long int) data->ipv4_tx_packets,
+		(unsigned long  int) data->ipv6_tx_packets,
+		(unsigned long int) data->ipv4_tx_bytes,
+		(unsigned long int) data->ipv6_tx_bytes);
+
+	kfree(sap_stats);
+	return rc;
+}
+
+static int rmnet_ipa3_query_tethering_stats_modem(
+	struct wan_ioctl_query_tether_stats *data, bool reset)
 {
 	struct ipa_get_data_stats_req_msg_v01 *req;
 	struct ipa_get_data_stats_resp_msg_v01 *resp;
@@ -2820,7 +2953,7 @@
 			}
 		}
 	}
-	IPAWANDBG_LOW("v4_rx_p(%lu) v6_rx_p(%lu) v4_rx_b(%lu) v6_rx_b(%lu)\n",
+	IPAWANDBG("v4_rx_p(%lu) v6_rx_p(%lu) v4_rx_b(%lu) v6_rx_b(%lu)\n",
 		(unsigned long int) data->ipv4_rx_packets,
 		(unsigned long int) data->ipv6_rx_packets,
 		(unsigned long int) data->ipv4_rx_bytes,
@@ -2870,7 +3003,7 @@
 			}
 		}
 	}
-	IPAWANDBG_LOW("tx_p_v4(%lu)v6(%lu)tx_b_v4(%lu) v6(%lu)\n",
+	IPAWANDBG("tx_p_v4(%lu)v6(%lu)tx_b_v4(%lu) v6(%lu)\n",
 		(unsigned long int) data->ipv4_tx_packets,
 		(unsigned long  int) data->ipv6_tx_packets,
 		(unsigned long int) data->ipv4_tx_bytes,
@@ -2880,6 +3013,69 @@
 	return 0;
 }
 
+int rmnet_ipa3_query_tethering_stats(struct wan_ioctl_query_tether_stats *data,
+	bool reset)
+{
+	enum ipa_upstream_type upstream_type;
+	int rc = 0;
+
+	/* get IPA backhaul type */
+	upstream_type = find_upstream_type(data->upstreamIface);
+
+	if (upstream_type == IPA_UPSTEAM_MAX) {
+		IPAWANERR(" Wrong upstreamIface name %s\n",
+			data->upstreamIface);
+	} else if (upstream_type == IPA_UPSTEAM_WLAN) {
+		IPAWANDBG_LOW(" query wifi-backhaul stats\n");
+		rc = rmnet_ipa3_query_tethering_stats_wifi(
+			data, false);
+		if (rc) {
+			IPAWANERR("wlan WAN_IOC_QUERY_TETHER_STATS failed\n");
+			return rc;
+		}
+	} else {
+		IPAWANDBG_LOW(" query modem-backhaul stats\n");
+		rc = rmnet_ipa3_query_tethering_stats_modem(
+			data, false);
+		if (rc) {
+			IPAWANERR("modem WAN_IOC_QUERY_TETHER_STATS failed\n");
+			return rc;
+		}
+	}
+	return rc;
+}
+
+int rmnet_ipa3_reset_tethering_stats(struct wan_ioctl_reset_tether_stats *data)
+{
+	enum ipa_upstream_type upstream_type;
+	int rc = 0;
+
+	/* get IPA backhaul type */
+	upstream_type = find_upstream_type(data->upstreamIface);
+
+	if (upstream_type == IPA_UPSTEAM_MAX) {
+		IPAWANERR(" Wrong upstreamIface name %s\n",
+			data->upstreamIface);
+	} else if (upstream_type == IPA_UPSTEAM_WLAN) {
+		IPAWANERR(" reset wifi-backhaul stats\n");
+		rc = rmnet_ipa3_query_tethering_stats_wifi(
+			NULL, true);
+		if (rc) {
+			IPAWANERR("reset WLAN stats failed\n");
+			return rc;
+		}
+	} else {
+		IPAWANERR(" reset modem-backhaul stats\n");
+		rc = rmnet_ipa3_query_tethering_stats_modem(
+			NULL, true);
+		if (rc) {
+			IPAWANERR("reset MODEM stats failed\n");
+			return rc;
+		}
+	}
+	return rc;
+}
+
 /**
  * ipa3_broadcast_quota_reach_ind() - Send Netlink broadcast on Quota
  * @mux_id - The MUX ID on which the quota has been reached
@@ -2889,23 +3085,28 @@
  * on the specific interface which matches the mux_id has been reached.
  *
  */
-void ipa3_broadcast_quota_reach_ind(u32 mux_id)
+void ipa3_broadcast_quota_reach_ind(u32 mux_id,
+	enum ipa_upstream_type upstream_type)
 {
 	char alert_msg[IPA_QUOTA_REACH_ALERT_MAX_SIZE];
 	char iface_name_m[IPA_QUOTA_REACH_IF_NAME_MAX_SIZE];
 	char iface_name_l[IPA_QUOTA_REACH_IF_NAME_MAX_SIZE];
 	char *envp[IPA_UEVENT_NUM_EVNP] = {
-		alert_msg, iface_name_l, iface_name_m, NULL };
+		alert_msg, iface_name_l, iface_name_m, NULL};
 	int res;
 	int index;
 
-	index = ipa3_find_mux_channel_index(mux_id);
-
-	if (index == MAX_NUM_OF_MUX_CHANNEL) {
-		IPAWANERR("%u is an mux ID\n", mux_id);
+	/* check upstream_type*/
+	if (upstream_type == IPA_UPSTEAM_MAX) {
+		IPAWANERR(" Wrong upstreamIface type %d\n", upstream_type);
 		return;
+	} else if (upstream_type == IPA_UPSTEAM_MODEM) {
+		index = ipa3_find_mux_channel_index(mux_id);
+		if (index == MAX_NUM_OF_MUX_CHANNEL) {
+			IPAWANERR("%u is an mux ID\n", mux_id);
+			return;
+		}
 	}
-
 	res = snprintf(alert_msg, IPA_QUOTA_REACH_ALERT_MAX_SIZE,
 			"ALERT_NAME=%s", "quotaReachedAlert");
 	if (res >= IPA_QUOTA_REACH_ALERT_MAX_SIZE) {
@@ -2913,15 +3114,25 @@
 		return;
 	}
 	/* posting msg for L-release for CNE */
+	if (upstream_type == IPA_UPSTEAM_MODEM) {
 	res = snprintf(iface_name_l, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
 	    "UPSTREAM=%s", rmnet_ipa3_ctx->mux_channel[index].vchannel_name);
+	} else {
+		res = snprintf(iface_name_l, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
+			"UPSTREAM=%s", IPA_UPSTEAM_WLAN_IFACE_NAME);
+	}
 	if (res >= IPA_QUOTA_REACH_IF_NAME_MAX_SIZE) {
 		IPAWANERR("message too long (%d)", res);
 		return;
 	}
 	/* posting msg for M-release for CNE */
+	if (upstream_type == IPA_UPSTEAM_MODEM) {
 	res = snprintf(iface_name_m, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
 	    "INTERFACE=%s", rmnet_ipa3_ctx->mux_channel[index].vchannel_name);
+	} else {
+		res = snprintf(iface_name_m, IPA_QUOTA_REACH_IF_NAME_MAX_SIZE,
+			"INTERFACE=%s", IPA_UPSTEAM_WLAN_IFACE_NAME);
+	}
 	if (res >= IPA_QUOTA_REACH_IF_NAME_MAX_SIZE) {
 		IPAWANERR("message too long (%d)", res);
 		return;
diff --git a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa_fd_ioctl.c b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa_fd_ioctl.c
index 2abfe17..3ef17f6 100644
--- a/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa_fd_ioctl.c
+++ b/drivers/platform/msm/ipa/ipa_v3/rmnet_ipa_fd_ioctl.c
@@ -279,8 +279,9 @@
 			break;
 		}
 
-		if (rmnet_ipa3_query_tethering_stats(NULL, true)) {
-			IPAWANERR("WAN_IOC_QUERY_TETHER_STATS failed\n");
+		if (rmnet_ipa3_reset_tethering_stats(
+				(struct wan_ioctl_reset_tether_stats *)param)) {
+			IPAWANERR("WAN_IOC_RESET_TETHER_STATS failed\n");
 			retval = -EFAULT;
 			break;
 		}
diff --git a/drivers/power/Makefile b/drivers/power/Makefile
index b0cafa9..b8bdffd 100644
--- a/drivers/power/Makefile
+++ b/drivers/power/Makefile
@@ -2,3 +2,5 @@
 obj-$(CONFIG_POWER_RESET)	+= reset/
 obj-$(CONFIG_POWER_SUPPLY)	+= supply/
 obj-$(CONFIG_ARCH_QCOM)		+= qcom/
+obj-$(CONFIG_ARCH_QCOM)		+= qcom/
+obj-$(CONFIG_POWER_SUPPLY)	+= supply/
diff --git a/drivers/power/supply/Kconfig b/drivers/power/supply/Kconfig
index 76806a0..2d5d9bf 100644
--- a/drivers/power/supply/Kconfig
+++ b/drivers/power/supply/Kconfig
@@ -511,4 +511,6 @@
 	  This driver provides support for the power supply features of
 	  AXP20x PMIC.
 
+source "drivers/power/supply/qcom/Kconfig"
+
 endif # POWER_SUPPLY
diff --git a/drivers/power/supply/Makefile b/drivers/power/supply/Makefile
index 36c599d..cfbc992 100644
--- a/drivers/power/supply/Makefile
+++ b/drivers/power/supply/Makefile
@@ -71,4 +71,5 @@
 obj-$(CONFIG_CHARGER_TPS65090)	+= tps65090-charger.o
 obj-$(CONFIG_CHARGER_TPS65217)	+= tps65217_charger.o
 obj-$(CONFIG_AXP288_FUEL_GAUGE) += axp288_fuel_gauge.o
+obj-$(CONFIG_ARCH_QCOM)         += qcom/
 obj-$(CONFIG_AXP288_CHARGER)	+= axp288_charger.o
diff --git a/drivers/power/supply/power_supply_sysfs.c b/drivers/power/supply/power_supply_sysfs.c
index 1480d9a..f6fa78f 100644
--- a/drivers/power/supply/power_supply_sysfs.c
+++ b/drivers/power/supply/power_supply_sysfs.c
@@ -44,20 +44,23 @@
 					  struct device_attribute *attr,
 					  char *buf) {
 	static char *type_text[] = {
-		"Unknown", "Battery", "UPS", "Mains", "USB",
-		"USB_DCP", "USB_CDP", "USB_ACA", "USB_C",
-		"USB_PD", "USB_PD_DRP"
+		"Unknown", "Battery", "UPS", "Mains", "USB", "USB_DCP",
+		"USB_CDP", "USB_ACA", "USB_HVDCP", "USB_HVDCP_3", "USB_PD",
+		"Wireless", "BMS", "Parallel", "Main", "Wipower",
+		"TYPEC", "TYPEC_UFP", "TYPEC_DFP"
 	};
 	static char *status_text[] = {
 		"Unknown", "Charging", "Discharging", "Not charging", "Full"
 	};
 	static char *charge_type[] = {
-		"Unknown", "N/A", "Trickle", "Fast"
+		"Unknown", "N/A", "Trickle", "Fast",
+		"Taper"
 	};
 	static char *health_text[] = {
 		"Unknown", "Good", "Overheat", "Dead", "Over voltage",
 		"Unspecified failure", "Cold", "Watchdog timer expire",
-		"Safety timer expire"
+		"Safety timer expire",
+		"Warm", "Cool", "Hot"
 	};
 	static char *technology_text[] = {
 		"Unknown", "NiMH", "Li-ion", "Li-poly", "LiFe", "NiCd",
@@ -102,30 +105,48 @@
 	}
 
 	if (off == POWER_SUPPLY_PROP_STATUS)
-		return sprintf(buf, "%s\n", status_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				status_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_CHARGE_TYPE)
-		return sprintf(buf, "%s\n", charge_type[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				charge_type[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_HEALTH)
-		return sprintf(buf, "%s\n", health_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				health_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_TECHNOLOGY)
-		return sprintf(buf, "%s\n", technology_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				technology_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_CAPACITY_LEVEL)
-		return sprintf(buf, "%s\n", capacity_level_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				capacity_level_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_TYPE)
-		return sprintf(buf, "%s\n", type_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				type_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_SCOPE)
-		return sprintf(buf, "%s\n", scope_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				scope_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_TYPEC_MODE)
-		return sprintf(buf, "%s\n", typec_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				typec_text[value.intval]);
 	else if (off == POWER_SUPPLY_PROP_TYPEC_POWER_ROLE)
-		return sprintf(buf, "%s\n", typec_pr_text[value.intval]);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				typec_pr_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_DIE_HEALTH)
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				health_text[value.intval]);
+	else if (off == POWER_SUPPLY_PROP_CONNECTOR_HEALTH)
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				health_text[value.intval]);
 	else if (off >= POWER_SUPPLY_PROP_MODEL_NAME)
-		return sprintf(buf, "%s\n", value.strval);
+		return scnprintf(buf, PAGE_SIZE, "%s\n",
+				value.strval);
 
 	if (off == POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT)
-		return sprintf(buf, "%lld\n", value.int64val);
+		return scnprintf(buf, PAGE_SIZE, "%lld\n",
+				value.int64val);
 	else
-		return sprintf(buf, "%d\n", value.intval);
+		return scnprintf(buf, PAGE_SIZE, "%d\n",
+				value.intval);
 }
 
 static ssize_t power_supply_store_property(struct device *dev,
@@ -181,6 +202,8 @@
 	POWER_SUPPLY_ATTR(charge_full),
 	POWER_SUPPLY_ATTR(charge_empty),
 	POWER_SUPPLY_ATTR(charge_now),
+	POWER_SUPPLY_ATTR(charge_now_raw),
+	POWER_SUPPLY_ATTR(charge_now_error),
 	POWER_SUPPLY_ATTR(charge_avg),
 	POWER_SUPPLY_ATTR(charge_counter),
 	POWER_SUPPLY_ATTR(constant_charge_current),
@@ -200,6 +223,7 @@
 	POWER_SUPPLY_ATTR(capacity_alert_min),
 	POWER_SUPPLY_ATTR(capacity_alert_max),
 	POWER_SUPPLY_ATTR(capacity_level),
+	POWER_SUPPLY_ATTR(capacity_raw),
 	POWER_SUPPLY_ATTR(temp),
 	POWER_SUPPLY_ATTR(temp_max),
 	POWER_SUPPLY_ATTR(temp_min),
@@ -216,11 +240,51 @@
 	POWER_SUPPLY_ATTR(scope),
 	POWER_SUPPLY_ATTR(charge_term_current),
 	POWER_SUPPLY_ATTR(calibrate),
-	POWER_SUPPLY_ATTR(resistance),
 	/* Local extensions */
 	POWER_SUPPLY_ATTR(usb_hc),
 	POWER_SUPPLY_ATTR(usb_otg),
-	POWER_SUPPLY_ATTR(charge_enabled),
+	POWER_SUPPLY_ATTR(battery_charging_enabled),
+	POWER_SUPPLY_ATTR(charging_enabled),
+	POWER_SUPPLY_ATTR(step_charging_enabled),
+	POWER_SUPPLY_ATTR(step_charging_step),
+	POWER_SUPPLY_ATTR(pin_enabled),
+	POWER_SUPPLY_ATTR(input_suspend),
+	POWER_SUPPLY_ATTR(input_voltage_regulation),
+	POWER_SUPPLY_ATTR(input_current_max),
+	POWER_SUPPLY_ATTR(input_current_trim),
+	POWER_SUPPLY_ATTR(input_current_settled),
+	POWER_SUPPLY_ATTR(input_voltage_settled),
+	POWER_SUPPLY_ATTR(bypass_vchg_loop_debouncer),
+	POWER_SUPPLY_ATTR(charge_counter_shadow),
+	POWER_SUPPLY_ATTR(hi_power),
+	POWER_SUPPLY_ATTR(low_power),
+	POWER_SUPPLY_ATTR(temp_cool),
+	POWER_SUPPLY_ATTR(temp_warm),
+	POWER_SUPPLY_ATTR(system_temp_level),
+	POWER_SUPPLY_ATTR(resistance),
+	POWER_SUPPLY_ATTR(resistance_capacitive),
+	POWER_SUPPLY_ATTR(resistance_id),
+	POWER_SUPPLY_ATTR(resistance_now),
+	POWER_SUPPLY_ATTR(flash_current_max),
+	POWER_SUPPLY_ATTR(update_now),
+	POWER_SUPPLY_ATTR(esr_count),
+	POWER_SUPPLY_ATTR(buck_freq),
+	POWER_SUPPLY_ATTR(boost_current),
+	POWER_SUPPLY_ATTR(safety_timer_enabled),
+	POWER_SUPPLY_ATTR(charge_done),
+	POWER_SUPPLY_ATTR(flash_active),
+	POWER_SUPPLY_ATTR(flash_trigger),
+	POWER_SUPPLY_ATTR(force_tlim),
+	POWER_SUPPLY_ATTR(dp_dm),
+	POWER_SUPPLY_ATTR(input_current_limited),
+	POWER_SUPPLY_ATTR(input_current_now),
+	POWER_SUPPLY_ATTR(current_qnovo),
+	POWER_SUPPLY_ATTR(voltage_qnovo),
+	POWER_SUPPLY_ATTR(rerun_aicl),
+	POWER_SUPPLY_ATTR(cycle_count_id),
+	POWER_SUPPLY_ATTR(safety_timer_expired),
+	POWER_SUPPLY_ATTR(restricted_charging),
+	POWER_SUPPLY_ATTR(current_capability),
 	POWER_SUPPLY_ATTR(typec_mode),
 	POWER_SUPPLY_ATTR(typec_cc_orientation),
 	POWER_SUPPLY_ATTR(typec_power_role),
@@ -229,16 +293,26 @@
 	POWER_SUPPLY_ATTR(pd_in_hard_reset),
 	POWER_SUPPLY_ATTR(pd_current_max),
 	POWER_SUPPLY_ATTR(pd_usb_suspend_supported),
+	POWER_SUPPLY_ATTR(charger_temp),
+	POWER_SUPPLY_ATTR(charger_temp_max),
+	POWER_SUPPLY_ATTR(parallel_disable),
 	POWER_SUPPLY_ATTR(pe_start),
 	POWER_SUPPLY_ATTR(set_ship_mode),
-	POWER_SUPPLY_ATTR(boost_current),
-	POWER_SUPPLY_ATTR(force_tlim),
+	POWER_SUPPLY_ATTR(soc_reporting_ready),
+	POWER_SUPPLY_ATTR(debug_battery),
+	POWER_SUPPLY_ATTR(fcc_delta),
+	POWER_SUPPLY_ATTR(icl_reduction),
+	POWER_SUPPLY_ATTR(parallel_mode),
+	POWER_SUPPLY_ATTR(die_health),
+	POWER_SUPPLY_ATTR(connector_health),
+	POWER_SUPPLY_ATTR(ctm_current_max),
 	/* Local extensions of type int64_t */
 	POWER_SUPPLY_ATTR(charge_counter_ext),
 	/* Properties of type `const char *' */
 	POWER_SUPPLY_ATTR(model_name),
 	POWER_SUPPLY_ATTR(manufacturer),
 	POWER_SUPPLY_ATTR(serial_number),
+	POWER_SUPPLY_ATTR(battery_type),
 };
 
 static struct attribute *
diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig
new file mode 100644
index 0000000..79ea712
--- /dev/null
+++ b/drivers/power/supply/qcom/Kconfig
@@ -0,0 +1,66 @@
+menu "Qualcomm Technologies Inc Charger and Fuel Gauge support"
+
+config QPNP_FG_GEN3
+	tristate "QPNP GEN3 fuel gauge driver"
+	depends on MFD_SPMI_PMIC
+	help
+	  Say Y here to enable the GEN3 Fuel Gauge driver. This adds support
+	  for battery fuel gauging and state of charge of battery connected to
+	  the fuel gauge. The state of charge is reported through a BMS power
+	  supply property and also sends uevents when the capacity is updated.
+
+config SMB135X_CHARGER
+	tristate "SMB135X Battery Charger"
+	depends on I2C
+	help
+	  Say Y to include support for SMB135X Battery Charger.
+	  SMB135X is a dual path switching mode charger capable of charging
+	  the battery with 3Amps of current.
+	  The driver supports charger enable/disable.
+	  The driver reports the charger status via the power supply framework.
+	  A charger status change triggers an IRQ via the device STAT pin.
+
+config SMB1351_USB_CHARGER
+	tristate "smb1351 usb charger (with VBUS detection)"
+	depends on I2C
+	help
+	 Say Y to enable support for the SMB1351 switching mode based charger.
+	 The driver supports charging control (enable/disable) and
+	 charge-current limiting. It also provides USB VBUS detection and
+	 notification support. The driver controls SMB1351 via I2C and
+	 supports device-tree interface.
+
+config QPNP_SMB2
+	tristate "SMB2 Battery Charger"
+	depends on MFD_SPMI_PMIC
+	help
+	  Say Y to enables support for the SMB2 charging peripheral.
+	  The QPNP SMB2 charger driver supports the charger peripheral
+	  present in the PMICOBALT chip.
+	  The power supply framework is used to communicate battery and
+	  usb properties to userspace and other driver consumers such
+	  as fuel gauge, USB, and USB-PD.
+	  VBUS and VCONN regulators are registered for supporting OTG,
+	  and powered Type-C cables respectively.
+
+config SMB138X_CHARGER
+	tristate "SMB138X Battery Charger"
+	depends on MFD_I2C_PMIC
+	help
+	  Say Y to include support for SMB138X Battery Charger.
+	  SMB1380 is a dual phase 6A battery charger, and SMB1381 is a single
+	  phase 5A battery charger.
+	  The driver supports charger enable/disable.
+	  The driver reports the charger status via the power supply framework.
+	  A charger status change triggers an IRQ via the device STAT pin.
+
+config QPNP_QNOVO
+	bool "QPNP QNOVO driver"
+	depends on MFD_SPMI_PMIC
+	help
+	  Say Y here to enable the Qnovo pulse charging engine. Qnovo driver
+	  accepts pulse parameters via sysfs entries and programs the hardware
+	  module. It also allows userspace code to read diagnostics of voltage
+	  and current measured during certain phases of the pulses.
+
+endmenu
diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile
new file mode 100644
index 0000000..171444f
--- /dev/null
+++ b/drivers/power/supply/qcom/Makefile
@@ -0,0 +1,6 @@
+obj-$(CONFIG_QPNP_FG_GEN3)     += qpnp-fg-gen3.o fg-memif.o fg-util.o
+obj-$(CONFIG_SMB135X_CHARGER)   += smb135x-charger.o pmic-voter.o
+obj-$(CONFIG_SMB1351_USB_CHARGER) += smb1351-charger.o pmic-voter.o battery.o
+obj-$(CONFIG_QPNP_SMB2)		+= qpnp-smb2.o smb-lib.o pmic-voter.o storm-watch.o battery.o
+obj-$(CONFIG_SMB138X_CHARGER)	+= smb138x-charger.o smb-lib.o pmic-voter.o storm-watch.o battery.o
+obj-$(CONFIG_QPNP_QNOVO)	+= qpnp-qnovo.o battery.o
diff --git a/drivers/power/supply/qcom/battery.c b/drivers/power/supply/qcom/battery.c
new file mode 100644
index 0000000..3659b92
--- /dev/null
+++ b/drivers/power/supply/qcom/battery.c
@@ -0,0 +1,936 @@
+/* Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "QCOM-BATT: %s: " fmt, __func__
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/printk.h>
+#include <linux/pm_wakeup.h>
+#include <linux/slab.h>
+#include "pmic-voter.h"
+
+#define DRV_MAJOR_VERSION	1
+#define DRV_MINOR_VERSION	0
+
+#define CHG_STATE_VOTER			"CHG_STATE_VOTER"
+#define TAPER_END_VOTER			"TAPER_END_VOTER"
+#define PL_TAPER_EARLY_BAD_VOTER	"PL_TAPER_EARLY_BAD_VOTER"
+#define PARALLEL_PSY_VOTER		"PARALLEL_PSY_VOTER"
+#define PL_HW_ABSENT_VOTER		"PL_HW_ABSENT_VOTER"
+#define PL_VOTER			"PL_VOTER"
+#define RESTRICT_CHG_VOTER		"RESTRICT_CHG_VOTER"
+
+struct pl_data {
+	int			pl_mode;
+	int			slave_pct;
+	int			taper_pct;
+	int			slave_fcc_ua;
+	int			restricted_current;
+	bool			restricted_charging_enabled;
+	struct votable		*fcc_votable;
+	struct votable		*fv_votable;
+	struct votable		*pl_disable_votable;
+	struct votable		*pl_awake_votable;
+	struct votable		*hvdcp_hw_inov_dis_votable;
+	struct work_struct	status_change_work;
+	struct work_struct	pl_disable_forever_work;
+	struct delayed_work	pl_taper_work;
+	struct power_supply	*main_psy;
+	struct power_supply	*pl_psy;
+	struct power_supply	*batt_psy;
+	int			charge_type;
+	int			main_settled_ua;
+	int			pl_settled_ua;
+	struct class		qcom_batt_class;
+	struct wakeup_source	*pl_ws;
+	struct notifier_block	nb;
+};
+
+struct pl_data *the_chip;
+
+enum print_reason {
+	PR_PARALLEL	= BIT(0),
+};
+
+static int debug_mask;
+module_param_named(debug_mask, debug_mask, int, 0600);
+
+#define pl_dbg(chip, reason, fmt, ...)				\
+	do {								\
+		if (debug_mask & (reason))				\
+			pr_info(fmt, ##__VA_ARGS__);	\
+		else							\
+			pr_debug(fmt, ##__VA_ARGS__);		\
+	} while (0)
+
+enum {
+	VER = 0,
+	SLAVE_PCT,
+	RESTRICT_CHG_ENABLE,
+	RESTRICT_CHG_CURRENT,
+};
+
+/*******
+ * ICL *
+ ********/
+static void split_settled(struct pl_data *chip)
+{
+	int slave_icl_pct;
+	int slave_ua = 0, main_settled_ua = 0;
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	/* TODO some parallel chargers do not have a fine ICL resolution. For
+	 * them implement a psy interface which returns the closest lower ICL
+	 * for desired split
+	 */
+
+	if ((chip->pl_mode != POWER_SUPPLY_PL_USBIN_USBIN)
+		&& (chip->pl_mode != POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+		return;
+
+	if (!chip->main_psy)
+		return;
+
+	if (!get_effective_result_locked(chip->pl_disable_votable)) {
+		/* read the aicl settled value */
+		rc = power_supply_get_property(chip->main_psy,
+			       POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
+		if (rc < 0) {
+			pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+			return;
+		}
+		main_settled_ua = pval.intval;
+		/* slave gets 10 percent points less for ICL */
+		slave_icl_pct = max(0, chip->slave_pct - 10);
+		slave_ua = ((main_settled_ua + chip->pl_settled_ua)
+						* slave_icl_pct) / 100;
+	}
+
+	/* ICL_REDUCTION on main could be 0mA when pl is disabled */
+	pval.intval = slave_ua;
+	rc = power_supply_set_property(chip->main_psy,
+			POWER_SUPPLY_PROP_ICL_REDUCTION, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't change slave suspend state rc=%d\n", rc);
+		return;
+	}
+
+	/* set parallel's ICL  could be 0mA when pl is disabled */
+	pval.intval = slave_ua;
+	rc = power_supply_set_property(chip->pl_psy,
+			POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't set parallel icl, rc=%d\n", rc);
+		return;
+	}
+
+	/* main_settled_ua represents the total capability of adapter */
+	if (!chip->main_settled_ua)
+		chip->main_settled_ua = main_settled_ua;
+	chip->pl_settled_ua = slave_ua;
+}
+
+static ssize_t version_show(struct class *c, struct class_attribute *attr,
+			char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+			DRV_MAJOR_VERSION, DRV_MINOR_VERSION);
+}
+
+/*************
+ * SLAVE PCT *
+ **************/
+static ssize_t slave_pct_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+
+	return snprintf(ubuf, PAGE_SIZE, "%d\n", chip->slave_pct);
+}
+
+static ssize_t slave_pct_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+	unsigned long val;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	chip->slave_pct = val;
+	rerun_election(chip->fcc_votable);
+	rerun_election(chip->fv_votable);
+	split_settled(chip);
+
+	return count;
+}
+
+/************************
+ * RESTRICTED CHARGIGNG *
+ ************************/
+static ssize_t restrict_chg_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+
+	return snprintf(ubuf, PAGE_SIZE, "%d\n",
+			chip->restricted_charging_enabled);
+}
+
+static ssize_t restrict_chg_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+	unsigned long val;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	if (chip->restricted_charging_enabled == !!val)
+		goto no_change;
+
+	chip->restricted_charging_enabled = !!val;
+
+	vote(chip->fcc_votable, RESTRICT_CHG_VOTER,
+				chip->restricted_charging_enabled,
+				chip->restricted_current);
+
+no_change:
+	return count;
+}
+
+static ssize_t restrict_cur_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+
+	return snprintf(ubuf, PAGE_SIZE, "%d\n", chip->restricted_current);
+}
+
+static ssize_t restrict_cur_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	struct pl_data *chip = container_of(c, struct pl_data,
+			qcom_batt_class);
+	unsigned long val;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	chip->restricted_current = val;
+
+	vote(chip->fcc_votable, RESTRICT_CHG_VOTER,
+				chip->restricted_charging_enabled,
+				chip->restricted_current);
+
+	return count;
+}
+
+static struct class_attribute pl_attributes[] = {
+	[VER]			= __ATTR_RO(version),
+	[SLAVE_PCT]		= __ATTR(parallel_pct, 0644,
+					slave_pct_show, slave_pct_store),
+	[RESTRICT_CHG_ENABLE]	= __ATTR(restricted_charging, 0644,
+					restrict_chg_show, restrict_chg_store),
+	[RESTRICT_CHG_CURRENT]	= __ATTR(restricted_current, 0644,
+					restrict_cur_show, restrict_cur_store),
+	__ATTR_NULL,
+};
+
+/***********
+ *  TAPER  *
+ ************/
+#define MINIMUM_PARALLEL_FCC_UA		500000
+#define PL_TAPER_WORK_DELAY_MS		100
+#define TAPER_RESIDUAL_PCT		75
+static void pl_taper_work(struct work_struct *work)
+{
+	struct pl_data *chip = container_of(work, struct pl_data,
+						pl_taper_work.work);
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	/* exit immediately if parallel is disabled */
+	if (get_effective_result(chip->pl_disable_votable)) {
+		pl_dbg(chip, PR_PARALLEL, "terminating parallel not in progress\n");
+		goto done;
+	}
+
+	pl_dbg(chip, PR_PARALLEL, "entering parallel taper work slave_fcc = %d\n",
+			chip->slave_fcc_ua);
+	if (chip->slave_fcc_ua < MINIMUM_PARALLEL_FCC_UA) {
+		pl_dbg(chip, PR_PARALLEL, "terminating parallel's share lower than 500mA\n");
+		vote(chip->pl_disable_votable, TAPER_END_VOTER, true, 0);
+		goto done;
+	}
+
+	rc = power_supply_get_property(chip->batt_psy,
+			       POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't get batt charge type rc=%d\n", rc);
+		goto done;
+	}
+
+	chip->charge_type = pval.intval;
+	if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+		pl_dbg(chip, PR_PARALLEL, "master is taper charging; reducing slave FCC\n");
+
+		vote(chip->pl_awake_votable, TAPER_END_VOTER, true, 0);
+		/* Reduce the taper percent by 25 percent */
+		chip->taper_pct = chip->taper_pct * TAPER_RESIDUAL_PCT / 100;
+		rerun_election(chip->fcc_votable);
+		pl_dbg(chip, PR_PARALLEL, "taper entry scheduling work after %d ms\n",
+				PL_TAPER_WORK_DELAY_MS);
+		schedule_delayed_work(&chip->pl_taper_work,
+				msecs_to_jiffies(PL_TAPER_WORK_DELAY_MS));
+		return;
+	}
+
+	/*
+	 * Master back to Fast Charge, get out of this round of taper reduction
+	 */
+	pl_dbg(chip, PR_PARALLEL, "master is fast charging; waiting for next taper\n");
+
+done:
+	vote(chip->pl_awake_votable, TAPER_END_VOTER, false, 0);
+}
+
+/*********
+ *  FCC  *
+ **********/
+#define EFFICIENCY_PCT	80
+static void split_fcc(struct pl_data *chip, int total_ua,
+			int *master_ua, int *slave_ua)
+{
+	int rc, effective_total_ua, slave_limited_ua, hw_cc_delta_ua = 0,
+		icl_ua, adapter_uv, bcl_ua;
+	union power_supply_propval pval = {0, };
+
+	rc = power_supply_get_property(chip->main_psy,
+			       POWER_SUPPLY_PROP_FCC_DELTA, &pval);
+	if (rc < 0)
+		hw_cc_delta_ua = 0;
+	else
+		hw_cc_delta_ua = pval.intval;
+
+	bcl_ua = INT_MAX;
+	if (chip->pl_mode == POWER_SUPPLY_PL_USBMID_USBMID) {
+		rc = power_supply_get_property(chip->main_psy,
+			       POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, &pval);
+		if (rc < 0) {
+			pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+			return;
+		}
+		icl_ua = pval.intval;
+
+		rc = power_supply_get_property(chip->main_psy,
+			       POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED, &pval);
+		if (rc < 0) {
+			pr_err("Couldn't get adaptive voltage rc=%d\n", rc);
+			return;
+		}
+		adapter_uv = pval.intval;
+
+		bcl_ua = div64_s64((s64)icl_ua * adapter_uv * EFFICIENCY_PCT,
+			(s64)get_effective_result(chip->fv_votable) * 100);
+	}
+
+	effective_total_ua = max(0, total_ua + hw_cc_delta_ua);
+	slave_limited_ua = min(effective_total_ua, bcl_ua);
+	*slave_ua = (slave_limited_ua * chip->slave_pct) / 100;
+	*slave_ua = (*slave_ua * chip->taper_pct) / 100;
+	/*
+	 * In USBIN_USBIN configuration with internal rsense parallel
+	 * charger's current goes through main charger's BATFET, keep
+	 * the main charger's FCC to the votable result.
+	 */
+	if (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+		*master_ua = max(0, total_ua);
+	else
+		*master_ua = max(0, total_ua - *slave_ua);
+}
+
+static int pl_fcc_vote_callback(struct votable *votable, void *data,
+			int total_fcc_ua, const char *client)
+{
+	struct pl_data *chip = data;
+	union power_supply_propval pval = {0, };
+	int rc, master_fcc_ua = total_fcc_ua, slave_fcc_ua = 0;
+
+	if (total_fcc_ua < 0)
+		return 0;
+
+	if (!chip->main_psy)
+		return 0;
+
+	if (chip->batt_psy) {
+		rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CURRENT_QNOVO,
+			&pval);
+		if (rc < 0) {
+			pr_err("Couldn't get qnovo fcc, rc=%d\n", rc);
+			return rc;
+		}
+
+		if (pval.intval != -EINVAL)
+			total_fcc_ua = pval.intval;
+	}
+
+	if (chip->pl_mode == POWER_SUPPLY_PL_NONE
+	    || get_effective_result_locked(chip->pl_disable_votable)) {
+		pval.intval = total_fcc_ua;
+		rc = power_supply_set_property(chip->main_psy,
+				POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+				&pval);
+		if (rc < 0)
+			pr_err("Couldn't set main fcc, rc=%d\n", rc);
+		return rc;
+	}
+
+	split_fcc(chip, total_fcc_ua, &master_fcc_ua, &slave_fcc_ua);
+	pval.intval = slave_fcc_ua;
+	rc = power_supply_set_property(chip->pl_psy,
+			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't set parallel fcc, rc=%d\n", rc);
+		return rc;
+	}
+
+	chip->slave_fcc_ua = slave_fcc_ua;
+
+	pval.intval = master_fcc_ua;
+	rc = power_supply_set_property(chip->main_psy,
+			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+	if (rc < 0) {
+		pr_err("Could not set main fcc, rc=%d\n", rc);
+		return rc;
+	}
+
+	pl_dbg(chip, PR_PARALLEL, "master_fcc=%d slave_fcc=%d distribution=(%d/%d)\n",
+		   master_fcc_ua, slave_fcc_ua,
+		   (master_fcc_ua * 100) / total_fcc_ua,
+		   (slave_fcc_ua * 100) / total_fcc_ua);
+
+	return 0;
+}
+
+#define PARALLEL_FLOAT_VOLTAGE_DELTA_UV 50000
+static int pl_fv_vote_callback(struct votable *votable, void *data,
+			int fv_uv, const char *client)
+{
+	struct pl_data *chip = data;
+	union power_supply_propval pval = {0, };
+	int rc = 0;
+	int effective_fv_uv = fv_uv;
+
+	if (fv_uv < 0)
+		return 0;
+
+	if (!chip->main_psy)
+		return 0;
+
+	if (chip->batt_psy) {
+		rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
+			&pval);
+		if (rc < 0) {
+			pr_err("Couldn't get qnovo fv, rc=%d\n", rc);
+			return rc;
+		}
+
+		if (pval.intval != -EINVAL)
+			effective_fv_uv = pval.intval;
+	}
+
+	pval.intval = effective_fv_uv;
+
+	rc = power_supply_set_property(chip->main_psy,
+			POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't set main fv, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->pl_mode != POWER_SUPPLY_PL_NONE) {
+		pval.intval += PARALLEL_FLOAT_VOLTAGE_DELTA_UV;
+		rc = power_supply_set_property(chip->pl_psy,
+				POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+		if (rc < 0) {
+			pr_err("Couldn't set float on parallel rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static void pl_disable_forever_work(struct work_struct *work)
+{
+	struct pl_data *chip = container_of(work,
+			struct pl_data, pl_disable_forever_work);
+
+	/* Disable Parallel charger forever */
+	vote(chip->pl_disable_votable, PL_HW_ABSENT_VOTER, true, 0);
+
+	/* Re-enable autonomous mode */
+	if (chip->hvdcp_hw_inov_dis_votable)
+		vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER, false, 0);
+}
+
+static int pl_disable_vote_callback(struct votable *votable,
+		void *data, int pl_disable, const char *client)
+{
+	struct pl_data *chip = data;
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	chip->taper_pct = 100;
+	chip->main_settled_ua = 0;
+	chip->pl_settled_ua = 0;
+
+	if (!pl_disable) { /* enable */
+		rc = power_supply_get_property(chip->pl_psy,
+				POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+		if (rc == -ENODEV) {
+			/*
+			 * -ENODEV is returned only if parallel chip
+			 * is not present in the system.
+			 * Disable parallel charger forever.
+			 */
+			schedule_work(&chip->pl_disable_forever_work);
+			return rc;
+		}
+
+		rerun_election(chip->fv_votable);
+		rerun_election(chip->fcc_votable);
+		/*
+		 * Enable will be called with a valid pl_psy always. The
+		 * PARALLEL_PSY_VOTER keeps it disabled unless a pl_psy
+		 * is seen.
+		 */
+		pval.intval = 0;
+		rc = power_supply_set_property(chip->pl_psy,
+				POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
+		if (rc < 0)
+			pr_err("Couldn't change slave suspend state rc=%d\n",
+				rc);
+
+		if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+			|| (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+			split_settled(chip);
+		/*
+		 * we could have been enabled while in taper mode,
+		 *  start the taper work if so
+		 */
+		rc = power_supply_get_property(chip->batt_psy,
+				       POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+		if (rc < 0) {
+			pr_err("Couldn't get batt charge type rc=%d\n", rc);
+		} else {
+			if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+				pl_dbg(chip, PR_PARALLEL,
+					"pl enabled in Taper scheduing work\n");
+				schedule_delayed_work(&chip->pl_taper_work, 0);
+			}
+		}
+	} else {
+		if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+			|| (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT))
+			split_settled(chip);
+
+		/* pl_psy may be NULL while in the disable branch */
+		if (chip->pl_psy) {
+			pval.intval = 1;
+			rc = power_supply_set_property(chip->pl_psy,
+					POWER_SUPPLY_PROP_INPUT_SUSPEND, &pval);
+			if (rc < 0)
+				pr_err("Couldn't change slave suspend state rc=%d\n",
+					rc);
+		}
+		rerun_election(chip->fcc_votable);
+		rerun_election(chip->fv_votable);
+	}
+
+	pl_dbg(chip, PR_PARALLEL, "parallel charging %s\n",
+		   pl_disable ? "disabled" : "enabled");
+
+	return 0;
+}
+
+static int pl_awake_vote_callback(struct votable *votable,
+			void *data, int awake, const char *client)
+{
+	struct pl_data *chip = data;
+
+	if (awake)
+		__pm_stay_awake(chip->pl_ws);
+	else
+		__pm_relax(chip->pl_ws);
+
+	pr_debug("client: %s awake: %d\n", client, awake);
+	return 0;
+}
+
+static bool is_main_available(struct pl_data *chip)
+{
+	if (!chip->main_psy)
+		chip->main_psy = power_supply_get_by_name("main");
+
+	if (!chip->main_psy)
+		return false;
+
+	return true;
+}
+
+static bool is_batt_available(struct pl_data *chip)
+{
+	if (!chip->batt_psy)
+		chip->batt_psy = power_supply_get_by_name("battery");
+
+	if (!chip->batt_psy)
+		return false;
+
+	return true;
+}
+
+static bool is_parallel_available(struct pl_data *chip)
+{
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	if (chip->pl_psy)
+		return true;
+
+	chip->pl_psy = power_supply_get_by_name("parallel");
+	if (!chip->pl_psy)
+		return false;
+
+	rc = power_supply_get_property(chip->pl_psy,
+			       POWER_SUPPLY_PROP_PARALLEL_MODE, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't get parallel mode from parallel rc=%d\n",
+				rc);
+		return false;
+	}
+	/*
+	 * Note that pl_mode will be updated to anything other than a _NONE
+	 * only after pl_psy is found. IOW pl_mode != _NONE implies that
+	 * pl_psy is present and valid.
+	 */
+	chip->pl_mode = pval.intval;
+
+	/* Disable autonomous votage increments for USBIN-USBIN */
+	if ((chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN)
+		|| (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT)) {
+		if (!chip->hvdcp_hw_inov_dis_votable)
+			chip->hvdcp_hw_inov_dis_votable =
+					find_votable("HVDCP_HW_INOV_DIS");
+		if (chip->hvdcp_hw_inov_dis_votable)
+			/* Read current pulse count */
+			vote(chip->hvdcp_hw_inov_dis_votable, PL_VOTER,
+					true, 0);
+		else
+			return false;
+	}
+
+	vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, false, 0);
+
+	return true;
+}
+
+static void handle_main_charge_type(struct pl_data *chip)
+{
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	rc = power_supply_get_property(chip->batt_psy,
+			       POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't get batt charge type rc=%d\n", rc);
+		return;
+	}
+
+	/* not fast/not taper state to disables parallel */
+	if ((pval.intval != POWER_SUPPLY_CHARGE_TYPE_FAST)
+		&& (pval.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER)) {
+		vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0);
+		chip->taper_pct = 100;
+		vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0);
+		vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER,
+				false, 0);
+		chip->charge_type = pval.intval;
+		return;
+	}
+
+	/* handle taper charge entry */
+	if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_FAST
+		&& (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER)) {
+		chip->charge_type = pval.intval;
+		pl_dbg(chip, PR_PARALLEL, "taper entry scheduling work\n");
+		schedule_delayed_work(&chip->pl_taper_work, 0);
+		return;
+	}
+
+	/* handle fast/taper charge entry */
+	if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER
+			|| pval.intval == POWER_SUPPLY_CHARGE_TYPE_FAST) {
+		pl_dbg(chip, PR_PARALLEL, "chg_state enabling parallel\n");
+		vote(chip->pl_disable_votable, CHG_STATE_VOTER, false, 0);
+		chip->charge_type = pval.intval;
+		return;
+	}
+
+	/* remember the new state only if it isn't any of the above */
+	chip->charge_type = pval.intval;
+}
+
+#define MIN_ICL_CHANGE_DELTA_UA		300000
+static void handle_settled_icl_change(struct pl_data *chip)
+{
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	if (get_effective_result(chip->pl_disable_votable))
+		return;
+
+	if (chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN
+			|| chip->pl_mode == POWER_SUPPLY_PL_USBIN_USBIN_EXT) {
+		/*
+		 * call aicl split only when USBIN_USBIN and enabled
+		 * and if aicl changed
+		 */
+		rc = power_supply_get_property(chip->main_psy,
+				       POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+				       &pval);
+		if (rc < 0) {
+			pr_err("Couldn't get aicl settled value rc=%d\n", rc);
+			return;
+		}
+
+		/* If ICL change is small skip splitting */
+		if (abs((chip->main_settled_ua - chip->pl_settled_ua)
+				- pval.intval) > MIN_ICL_CHANGE_DELTA_UA)
+			split_settled(chip);
+	} else {
+		rerun_election(chip->fcc_votable);
+	}
+}
+
+static void handle_parallel_in_taper(struct pl_data *chip)
+{
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	if (get_effective_result_locked(chip->pl_disable_votable))
+		return;
+
+	if (!chip->pl_psy)
+		return;
+
+	rc = power_supply_get_property(chip->pl_psy,
+			       POWER_SUPPLY_PROP_CHARGE_TYPE, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't get pl charge type rc=%d\n", rc);
+		return;
+	}
+
+	/*
+	 * if parallel is seen in taper mode ever, that is an anomaly and
+	 * we disable parallel charger
+	 */
+	if (pval.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER) {
+		vote(chip->pl_disable_votable, PL_TAPER_EARLY_BAD_VOTER,
+				true, 0);
+		return;
+	}
+}
+
+static void status_change_work(struct work_struct *work)
+{
+	struct pl_data *chip = container_of(work,
+			struct pl_data, status_change_work);
+
+	if (!is_main_available(chip))
+		return;
+
+	if (!is_batt_available(chip))
+		return;
+
+	is_parallel_available(chip);
+
+	handle_main_charge_type(chip);
+	handle_settled_icl_change(chip);
+	handle_parallel_in_taper(chip);
+}
+
+static int pl_notifier_call(struct notifier_block *nb,
+		unsigned long ev, void *v)
+{
+	struct power_supply *psy = v;
+	struct pl_data *chip = container_of(nb, struct pl_data, nb);
+
+	if (ev != PSY_EVENT_PROP_CHANGED)
+		return NOTIFY_OK;
+
+	if ((strcmp(psy->desc->name, "parallel") == 0)
+	    || (strcmp(psy->desc->name, "battery") == 0)
+	    || (strcmp(psy->desc->name, "main") == 0))
+		schedule_work(&chip->status_change_work);
+
+	return NOTIFY_OK;
+}
+
+static int pl_register_notifier(struct pl_data *chip)
+{
+	int rc;
+
+	chip->nb.notifier_call = pl_notifier_call;
+	rc = power_supply_reg_notifier(&chip->nb);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int pl_determine_initial_status(struct pl_data *chip)
+{
+	status_change_work(&chip->status_change_work);
+	return 0;
+}
+
+#define DEFAULT_RESTRICTED_CURRENT_UA	1000000
+static int pl_init(void)
+{
+	struct pl_data *chip;
+	int rc = 0;
+
+	chip = kzalloc(sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+	chip->slave_pct = 50;
+	chip->restricted_current = DEFAULT_RESTRICTED_CURRENT_UA;
+
+	chip->pl_ws = wakeup_source_register("qcom-battery");
+	if (!chip->pl_ws)
+		goto cleanup;
+
+	chip->fcc_votable = create_votable("FCC", VOTE_MIN,
+					pl_fcc_vote_callback,
+					chip);
+	if (IS_ERR(chip->fcc_votable)) {
+		rc = PTR_ERR(chip->fcc_votable);
+		goto release_wakeup_source;
+	}
+
+	chip->fv_votable = create_votable("FV", VOTE_MAX,
+					pl_fv_vote_callback,
+					chip);
+	if (IS_ERR(chip->fv_votable)) {
+		rc = PTR_ERR(chip->fv_votable);
+		goto destroy_votable;
+	}
+
+	chip->pl_disable_votable = create_votable("PL_DISABLE", VOTE_SET_ANY,
+					pl_disable_vote_callback,
+					chip);
+	if (IS_ERR(chip->pl_disable_votable)) {
+		rc = PTR_ERR(chip->pl_disable_votable);
+		goto destroy_votable;
+	}
+	vote(chip->pl_disable_votable, CHG_STATE_VOTER, true, 0);
+	vote(chip->pl_disable_votable, TAPER_END_VOTER, false, 0);
+	vote(chip->pl_disable_votable, PARALLEL_PSY_VOTER, true, 0);
+
+	chip->pl_awake_votable = create_votable("PL_AWAKE", VOTE_SET_ANY,
+					pl_awake_vote_callback,
+					chip);
+	if (IS_ERR(chip->pl_awake_votable)) {
+		rc = PTR_ERR(chip->pl_disable_votable);
+		goto destroy_votable;
+	}
+
+	INIT_WORK(&chip->status_change_work, status_change_work);
+	INIT_DELAYED_WORK(&chip->pl_taper_work, pl_taper_work);
+	INIT_WORK(&chip->pl_disable_forever_work, pl_disable_forever_work);
+
+	rc = pl_register_notifier(chip);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier rc = %d\n", rc);
+		goto unreg_notifier;
+	}
+
+	rc = pl_determine_initial_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n", rc);
+		goto unreg_notifier;
+	}
+
+	chip->qcom_batt_class.name = "qcom-battery",
+	chip->qcom_batt_class.owner = THIS_MODULE,
+	chip->qcom_batt_class.class_attrs = pl_attributes;
+
+	rc = class_register(&chip->qcom_batt_class);
+	if (rc < 0) {
+		pr_err("couldn't register pl_data sysfs class rc = %d\n", rc);
+		goto unreg_notifier;
+	}
+
+	return rc;
+
+unreg_notifier:
+	power_supply_unreg_notifier(&chip->nb);
+destroy_votable:
+	destroy_votable(chip->pl_awake_votable);
+	destroy_votable(chip->pl_disable_votable);
+	destroy_votable(chip->fv_votable);
+	destroy_votable(chip->fcc_votable);
+release_wakeup_source:
+	wakeup_source_unregister(chip->pl_ws);
+cleanup:
+	kfree(chip);
+	return rc;
+}
+
+static void pl_deinit(void)
+{
+	struct pl_data *chip = the_chip;
+
+	power_supply_unreg_notifier(&chip->nb);
+	destroy_votable(chip->pl_awake_votable);
+	destroy_votable(chip->pl_disable_votable);
+	destroy_votable(chip->fv_votable);
+	destroy_votable(chip->fcc_votable);
+	wakeup_source_unregister(chip->pl_ws);
+	kfree(chip);
+}
+
+module_init(pl_init);
+module_exit(pl_deinit)
+
+MODULE_DESCRIPTION("");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/fg-core.h b/drivers/power/supply/qcom/fg-core.h
new file mode 100644
index 0000000..c0ba5a9
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-core.h
@@ -0,0 +1,442 @@
+/* Copyright (c) 2016-2017, 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 __FG_CORE_H__
+#define __FG_CORE_H__
+
+#include <linux/atomic.h>
+#include <linux/bitops.h>
+#include <linux/debugfs.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/slab.h>
+#include <linux/string_helpers.h>
+#include <linux/types.h>
+#include <linux/uaccess.h>
+#include "pmic-voter.h"
+
+#define fg_dbg(chip, reason, fmt, ...)			\
+	do {							\
+		if (*chip->debug_mask & (reason))		\
+			pr_info(fmt, ##__VA_ARGS__);	\
+		else						\
+			pr_debug(fmt, ##__VA_ARGS__);	\
+	} while (0)
+
+#define is_between(left, right, value) \
+		(((left) >= (right) && (left) >= (value) \
+			&& (value) >= (right)) \
+		|| ((left) <= (right) && (left) <= (value) \
+			&& (value) <= (right)))
+
+/* Awake votable reasons */
+#define SRAM_READ	"fg_sram_read"
+#define SRAM_WRITE	"fg_sram_write"
+#define PROFILE_LOAD	"fg_profile_load"
+#define DELTA_SOC	"fg_delta_soc"
+
+#define DEBUG_PRINT_BUFFER_SIZE		64
+/* 3 byte address + 1 space character */
+#define ADDR_LEN			4
+/* Format is 'XX ' */
+#define CHARS_PER_ITEM			3
+/* 4 data items per line */
+#define ITEMS_PER_LINE			4
+#define MAX_LINE_LENGTH			(ADDR_LEN + (ITEMS_PER_LINE *	\
+					CHARS_PER_ITEM) + 1)		\
+
+#define FG_SRAM_ADDRESS_MAX		255
+#define FG_SRAM_LEN			504
+#define PROFILE_LEN			224
+#define PROFILE_COMP_LEN		148
+#define BUCKET_COUNT			8
+#define BUCKET_SOC_PCT			(256 / BUCKET_COUNT)
+
+#define KI_COEFF_MAX			62200
+#define KI_COEFF_SOC_LEVELS		3
+
+#define SLOPE_LIMIT_COEFF_MAX		31
+
+#define BATT_THERM_NUM_COEFFS		3
+
+/* Debug flag definitions */
+enum fg_debug_flag {
+	FG_IRQ			= BIT(0), /* Show interrupts */
+	FG_STATUS		= BIT(1), /* Show FG status changes */
+	FG_POWER_SUPPLY		= BIT(2), /* Show POWER_SUPPLY */
+	FG_SRAM_WRITE		= BIT(3), /* Show SRAM writes */
+	FG_SRAM_READ		= BIT(4), /* Show SRAM reads */
+	FG_BUS_WRITE		= BIT(5), /* Show REGMAP writes */
+	FG_BUS_READ		= BIT(6), /* Show REGMAP reads */
+	FG_CAP_LEARN		= BIT(7), /* Show capacity learning */
+	FG_TTF			= BIT(8), /* Show time to full */
+};
+
+/* SRAM access */
+enum sram_access_flags {
+	FG_IMA_DEFAULT	= 0,
+	FG_IMA_ATOMIC	= BIT(0),
+	FG_IMA_NO_WLOCK	= BIT(1),
+};
+
+/* JEITA */
+enum {
+	JEITA_COLD = 0,
+	JEITA_COOL,
+	JEITA_WARM,
+	JEITA_HOT,
+	NUM_JEITA_LEVELS,
+};
+
+/* FG irqs */
+enum fg_irq_index {
+	MSOC_FULL_IRQ = 0,
+	MSOC_HIGH_IRQ,
+	MSOC_EMPTY_IRQ,
+	MSOC_LOW_IRQ,
+	MSOC_DELTA_IRQ,
+	BSOC_DELTA_IRQ,
+	SOC_READY_IRQ,
+	SOC_UPDATE_IRQ,
+	BATT_TEMP_DELTA_IRQ,
+	BATT_MISSING_IRQ,
+	ESR_DELTA_IRQ,
+	VBATT_LOW_IRQ,
+	VBATT_PRED_DELTA_IRQ,
+	DMA_GRANT_IRQ,
+	MEM_XCP_IRQ,
+	IMA_RDY_IRQ,
+	FG_IRQ_MAX,
+};
+
+/* WA flags */
+enum {
+	DELTA_SOC_IRQ_WA = BIT(0),
+};
+
+/*
+ * List of FG_SRAM parameters. Please add a parameter only if it is an entry
+ * that will be used either to configure an entity (e.g. termination current)
+ * which might need some encoding (or) it is an entry that will be read from
+ * SRAM and decoded (e.g. CC_SOC_SW) for SW to use at various places. For
+ * generic read/writes to SRAM registers, please use fg_sram_read/write APIs
+ * directly without adding an entry here.
+ */
+enum fg_sram_param_id {
+	FG_SRAM_BATT_SOC = 0,
+	FG_SRAM_FULL_SOC,
+	FG_SRAM_VOLTAGE_PRED,
+	FG_SRAM_OCV,
+	FG_SRAM_ESR,
+	FG_SRAM_RSLOW,
+	FG_SRAM_ALG_FLAGS,
+	FG_SRAM_CC_SOC,
+	FG_SRAM_CC_SOC_SW,
+	FG_SRAM_ACT_BATT_CAP,
+	/* Entries below here are configurable during initialization */
+	FG_SRAM_CUTOFF_VOLT,
+	FG_SRAM_EMPTY_VOLT,
+	FG_SRAM_VBATT_LOW,
+	FG_SRAM_FLOAT_VOLT,
+	FG_SRAM_VBATT_FULL,
+	FG_SRAM_ESR_TIMER_DISCHG_MAX,
+	FG_SRAM_ESR_TIMER_DISCHG_INIT,
+	FG_SRAM_ESR_TIMER_CHG_MAX,
+	FG_SRAM_ESR_TIMER_CHG_INIT,
+	FG_SRAM_SYS_TERM_CURR,
+	FG_SRAM_CHG_TERM_CURR,
+	FG_SRAM_DELTA_MSOC_THR,
+	FG_SRAM_DELTA_BSOC_THR,
+	FG_SRAM_RECHARGE_SOC_THR,
+	FG_SRAM_RECHARGE_VBATT_THR,
+	FG_SRAM_KI_COEFF_MED_DISCHG,
+	FG_SRAM_KI_COEFF_HI_DISCHG,
+	FG_SRAM_ESR_TIGHT_FILTER,
+	FG_SRAM_ESR_BROAD_FILTER,
+	FG_SRAM_SLOPE_LIMIT,
+	FG_SRAM_MAX,
+};
+
+struct fg_sram_param {
+	u16 addr_word;
+	int addr_byte;
+	u8  len;
+	int value;
+	int numrtr;
+	int denmtr;
+	int offset;
+	void (*encode)(struct fg_sram_param *sp, enum fg_sram_param_id id,
+		int val, u8 *buf);
+	int (*decode)(struct fg_sram_param *sp, enum fg_sram_param_id id,
+		int val);
+};
+
+enum fg_alg_flag_id {
+	ALG_FLAG_SOC_LT_OTG_MIN = 0,
+	ALG_FLAG_SOC_LT_RECHARGE,
+	ALG_FLAG_IBATT_LT_ITERM,
+	ALG_FLAG_IBATT_GT_HPM,
+	ALG_FLAG_IBATT_GT_UPM,
+	ALG_FLAG_VBATT_LT_RECHARGE,
+	ALG_FLAG_VBATT_GT_VFLOAT,
+	ALG_FLAG_MAX,
+};
+
+struct fg_alg_flag {
+	char	*name;
+	u8	bit;
+	bool	invalid;
+};
+
+enum wa_flags {
+	PMI8998_V1_REV_WA = BIT(0),
+};
+
+enum slope_limit_status {
+	LOW_TEMP_DISCHARGE = 0,
+	LOW_TEMP_CHARGE,
+	HIGH_TEMP_DISCHARGE,
+	HIGH_TEMP_CHARGE,
+	SLOPE_LIMIT_NUM_COEFFS,
+};
+
+/* DT parameters for FG device */
+struct fg_dt_props {
+	bool	force_load_profile;
+	bool	hold_soc_while_full;
+	bool	auto_recharge_soc;
+	int	cutoff_volt_mv;
+	int	empty_volt_mv;
+	int	vbatt_low_thr_mv;
+	int	chg_term_curr_ma;
+	int	sys_term_curr_ma;
+	int	delta_soc_thr;
+	int	recharge_soc_thr;
+	int	recharge_volt_thr_mv;
+	int	rsense_sel;
+	int	esr_timer_charging;
+	int	esr_timer_awake;
+	int	esr_timer_asleep;
+	int	rconn_mohms;
+	int	esr_clamp_mohms;
+	int	cl_start_soc;
+	int	cl_max_temp;
+	int	cl_min_temp;
+	int	cl_max_cap_inc;
+	int	cl_max_cap_dec;
+	int	cl_max_cap_limit;
+	int	cl_min_cap_limit;
+	int	jeita_hyst_temp;
+	int	batt_temp_delta;
+	int	esr_flt_switch_temp;
+	int	esr_tight_flt_upct;
+	int	esr_broad_flt_upct;
+	int	esr_tight_lt_flt_upct;
+	int	esr_broad_lt_flt_upct;
+	int	slope_limit_temp;
+	int	jeita_thresholds[NUM_JEITA_LEVELS];
+	int	ki_coeff_soc[KI_COEFF_SOC_LEVELS];
+	int	ki_coeff_med_dischg[KI_COEFF_SOC_LEVELS];
+	int	ki_coeff_hi_dischg[KI_COEFF_SOC_LEVELS];
+	int	slope_limit_coeffs[SLOPE_LIMIT_NUM_COEFFS];
+	u8	batt_therm_coeffs[BATT_THERM_NUM_COEFFS];
+};
+
+/* parameters from battery profile */
+struct fg_batt_props {
+	const char	*batt_type_str;
+	char		*batt_profile;
+	int		float_volt_uv;
+	int		vbatt_full_mv;
+	int		fastchg_curr_ma;
+};
+
+struct fg_cyc_ctr_data {
+	bool		en;
+	bool		started[BUCKET_COUNT];
+	u16		count[BUCKET_COUNT];
+	u8		last_soc[BUCKET_COUNT];
+	int		id;
+	struct mutex	lock;
+};
+
+struct fg_cap_learning {
+	bool		active;
+	int		init_cc_soc_sw;
+	int64_t		nom_cap_uah;
+	int64_t		init_cc_uah;
+	int64_t		final_cc_uah;
+	int64_t		learned_cc_uah;
+	struct mutex	lock;
+};
+
+struct fg_irq_info {
+	const char		*name;
+	const irq_handler_t	handler;
+	bool			wakeable;
+	int			irq;
+};
+
+struct fg_circ_buf {
+	int	arr[20];
+	int	size;
+	int	head;
+};
+
+struct fg_pt {
+	s32 x;
+	s32 y;
+};
+
+static const struct fg_pt fg_ln_table[] = {
+	{ 1000,		0 },
+	{ 2000,		693 },
+	{ 4000,		1386 },
+	{ 6000,		1792 },
+	{ 8000,		2079 },
+	{ 16000,	2773 },
+	{ 32000,	3466 },
+	{ 64000,	4159 },
+	{ 128000,	4852 },
+};
+
+struct fg_chip {
+	struct device		*dev;
+	struct pmic_revid_data	*pmic_rev_id;
+	struct regmap		*regmap;
+	struct dentry		*dfs_root;
+	struct power_supply	*fg_psy;
+	struct power_supply	*batt_psy;
+	struct power_supply	*usb_psy;
+	struct power_supply	*dc_psy;
+	struct power_supply	*parallel_psy;
+	struct iio_channel	*batt_id_chan;
+	struct fg_memif		*sram;
+	struct fg_irq_info	*irqs;
+	struct votable		*awake_votable;
+	struct fg_sram_param	*sp;
+	struct fg_alg_flag	*alg_flags;
+	int			*debug_mask;
+	char			batt_profile[PROFILE_LEN];
+	struct fg_dt_props	dt;
+	struct fg_batt_props	bp;
+	struct fg_cyc_ctr_data	cyc_ctr;
+	struct notifier_block	nb;
+	struct fg_cap_learning  cl;
+	struct mutex		bus_lock;
+	struct mutex		sram_rw_lock;
+	struct mutex		batt_avg_lock;
+	struct mutex		charge_full_lock;
+	u32			batt_soc_base;
+	u32			batt_info_base;
+	u32			mem_if_base;
+	u32			rradc_base;
+	u32			wa_flags;
+	int			batt_id_ohms;
+	int			charge_status;
+	int			prev_charge_status;
+	int			charge_done;
+	int			charge_type;
+	int			last_soc;
+	int			last_batt_temp;
+	int			health;
+	int			maint_soc;
+	int			delta_soc;
+	int			last_msoc;
+	enum slope_limit_status	slope_limit_sts;
+	bool			profile_available;
+	bool			profile_loaded;
+	bool			battery_missing;
+	bool			fg_restarting;
+	bool			charge_full;
+	bool			recharge_soc_adjusted;
+	bool			ki_coeff_dischg_en;
+	bool			esr_fcc_ctrl_en;
+	bool			soc_reporting_ready;
+	bool			esr_flt_cold_temp_en;
+	bool			bsoc_delta_irq_en;
+	bool			slope_limit_en;
+	struct completion	soc_update;
+	struct completion	soc_ready;
+	struct delayed_work	profile_load_work;
+	struct work_struct	status_change_work;
+	struct work_struct	cycle_count_work;
+	struct delayed_work	batt_avg_work;
+	struct delayed_work	sram_dump_work;
+	struct fg_circ_buf	ibatt_circ_buf;
+	struct fg_circ_buf	vbatt_circ_buf;
+};
+
+/* Debugfs data structures are below */
+
+/* Log buffer */
+struct fg_log_buffer {
+	size_t		rpos;
+	size_t		wpos;
+	size_t		len;
+	char		data[0];
+};
+
+/* transaction parameters */
+struct fg_trans {
+	struct fg_chip		*chip;
+	struct mutex		fg_dfs_lock; /* Prevent thread concurrency */
+	struct fg_log_buffer	*log;
+	u32			cnt;
+	u16			addr;
+	u32			offset;
+	u8			*data;
+};
+
+struct fg_dbgfs {
+	struct debugfs_blob_wrapper	help_msg;
+	struct fg_chip			*chip;
+	struct dentry			*root;
+	u32				cnt;
+	u32				addr;
+};
+
+extern int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset,
+			u8 *val, int len, int flags);
+extern int fg_sram_read(struct fg_chip *chip, u16 address, u8 offset,
+			u8 *val, int len, int flags);
+extern int fg_sram_masked_write(struct fg_chip *chip, u16 address, u8 offset,
+			u8 mask, u8 val, int flags);
+extern int fg_interleaved_mem_read(struct fg_chip *chip, u16 address,
+			u8 offset, u8 *val, int len);
+extern int fg_interleaved_mem_write(struct fg_chip *chip, u16 address,
+			u8 offset, u8 *val, int len, bool atomic_access);
+extern int fg_read(struct fg_chip *chip, int addr, u8 *val, int len);
+extern int fg_write(struct fg_chip *chip, int addr, u8 *val, int len);
+extern int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val);
+extern int fg_ima_init(struct fg_chip *chip);
+extern int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts);
+extern int fg_clear_dma_errors_if_any(struct fg_chip *chip);
+extern int fg_debugfs_create(struct fg_chip *chip);
+extern void fill_string(char *str, size_t str_len, u8 *buf, int buf_len);
+extern void dump_sram(u8 *buf, int addr, int len);
+extern int64_t twos_compliment_extend(int64_t val, int s_bit_pos);
+extern s64 fg_float_decode(u16 val);
+extern bool is_input_present(struct fg_chip *chip);
+extern void fg_circ_buf_add(struct fg_circ_buf *buf, int val);
+extern void fg_circ_buf_clr(struct fg_circ_buf *buf);
+extern int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg);
+extern int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input,
+			s32 *output);
+#endif
diff --git a/drivers/power/supply/qcom/fg-memif.c b/drivers/power/supply/qcom/fg-memif.c
new file mode 100644
index 0000000..2dc7618
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-memif.c
@@ -0,0 +1,740 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)	"FG: %s: " fmt, __func__
+
+#include "fg-core.h"
+#include "fg-reg.h"
+
+/* Generic definitions */
+#define RETRY_COUNT		3
+#define BYTES_PER_SRAM_WORD	4
+
+enum {
+	FG_READ = 0,
+	FG_WRITE,
+};
+
+static int fg_set_address(struct fg_chip *chip, u16 address)
+{
+	u8 buffer[2];
+	int rc;
+
+	buffer[0] = address & 0xFF;
+	/* MSB has to be written zero */
+	buffer[1] = 0;
+
+	rc = fg_write(chip, MEM_IF_ADDR_LSB(chip), buffer, 2);
+	if (rc < 0) {
+		pr_err("failed to write to 0x%04X, rc=%d\n",
+			MEM_IF_ADDR_LSB(chip), rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int fg_config_access_mode(struct fg_chip *chip, bool access, bool burst)
+{
+	int rc;
+	u8 intf_ctl = 0;
+
+	intf_ctl = ((access == FG_WRITE) ? IMA_WR_EN_BIT : 0) |
+			(burst ? MEM_ACS_BURST_BIT : 0);
+
+	rc = fg_masked_write(chip, MEM_IF_IMA_CTL(chip), IMA_CTL_MASK,
+			intf_ctl);
+	if (rc < 0) {
+		pr_err("failed to write to 0x%04x, rc=%d\n",
+			MEM_IF_IMA_CTL(chip), rc);
+		return -EIO;
+	}
+
+	return rc;
+}
+
+static int fg_run_iacs_clear_sequence(struct fg_chip *chip)
+{
+	u8 val, hw_sts, exp_sts;
+	int rc, tries = 250;
+
+	/*
+	 * Values to write for running IACS clear sequence comes from
+	 * hardware documentation.
+	 */
+	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip),
+			IACS_CLR_BIT | STATIC_CLK_EN_BIT,
+			IACS_CLR_BIT | STATIC_CLK_EN_BIT);
+	if (rc < 0) {
+		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
+			rc);
+		return rc;
+	}
+
+	rc = fg_config_access_mode(chip, FG_READ, false);
+	if (rc < 0) {
+		pr_err("failed to write to 0x%04x, rc=%d\n",
+			MEM_IF_IMA_CTL(chip), rc);
+		return rc;
+	}
+
+	rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT,
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT);
+	if (rc < 0) {
+		pr_err("failed to set ima_req_access bit rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Delay for the clock to reach FG */
+	usleep_range(35, 40);
+
+	while (1) {
+		val = 0;
+		rc = fg_write(chip, MEM_IF_ADDR_MSB(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to write 0x%04x, rc=%d\n",
+				MEM_IF_ADDR_MSB(chip), rc);
+			return rc;
+		}
+
+		val = 0;
+		rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to write 0x%04x, rc=%d\n",
+				MEM_IF_WR_DATA3(chip), rc);
+			return rc;
+		}
+
+		rc = fg_read(chip, MEM_IF_RD_DATA3(chip), &val, 1);
+		if (rc < 0) {
+			pr_err("failed to read 0x%04x, rc=%d\n",
+				MEM_IF_RD_DATA3(chip), rc);
+			return rc;
+		}
+
+		/* Delay for IMA hardware to clear */
+		usleep_range(35, 40);
+
+		rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+		if (rc < 0) {
+			pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+			return rc;
+		}
+
+		if (hw_sts != 0)
+			continue;
+
+		rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+		if (rc < 0) {
+			pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+			return rc;
+		}
+
+		if (exp_sts == 0 || !(--tries))
+			break;
+	}
+
+	if (!tries)
+		pr_err("Failed to clear the error? hw_sts: %x exp_sts: %d\n",
+			hw_sts, exp_sts);
+
+	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_CLR_BIT, 0);
+	if (rc < 0) {
+		pr_err("failed to write 0x%04x, rc=%d\n", MEM_IF_IMA_CFG(chip),
+			rc);
+		return rc;
+	}
+
+	udelay(5);
+
+	rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+	if (rc < 0) {
+		pr_err("failed to write to 0x%04x, rc=%d\n",
+			MEM_IF_MEM_INTF_CFG(chip), rc);
+		return rc;
+	}
+
+	/* Delay before next transaction is attempted */
+	usleep_range(35, 40);
+	fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "IACS clear sequence complete\n");
+	return rc;
+}
+
+int fg_clear_dma_errors_if_any(struct fg_chip *chip)
+{
+	int rc;
+	u8 dma_sts;
+
+	rc = fg_read(chip, MEM_IF_DMA_STS(chip), &dma_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			MEM_IF_DMA_STS(chip), rc);
+		return rc;
+	}
+	fg_dbg(chip, FG_STATUS, "dma_sts: %x\n", dma_sts);
+
+	if (dma_sts & (DMA_WRITE_ERROR_BIT | DMA_READ_ERROR_BIT)) {
+		rc = fg_masked_write(chip, MEM_IF_DMA_CTL(chip),
+				DMA_CLEAR_LOG_BIT, DMA_CLEAR_LOG_BIT);
+		if (rc < 0) {
+			pr_err("failed to write addr=0x%04x, rc=%d\n",
+				MEM_IF_DMA_CTL(chip), rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+int fg_clear_ima_errors_if_any(struct fg_chip *chip, bool check_hw_sts)
+{
+	int rc = 0;
+	u8 err_sts, exp_sts = 0, hw_sts = 0;
+	bool run_err_clr_seq = false;
+
+	rc = fg_read(chip, MEM_IF_IMA_EXP_STS(chip), &exp_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read ima_exp_sts rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_read(chip, MEM_IF_IMA_HW_STS(chip), &hw_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read ima_hw_sts rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_read(chip, MEM_IF_IMA_ERR_STS(chip), &err_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read ima_err_sts rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n",
+		err_sts, exp_sts, hw_sts);
+
+	if (check_hw_sts) {
+		/*
+		 * Lower nibble should be equal to upper nibble before SRAM
+		 * transactions begins from SW side. If they are unequal, then
+		 * the error clear sequence should be run irrespective of IMA
+		 * exception errors.
+		 */
+		if ((hw_sts & 0x0F) != hw_sts >> 4) {
+			pr_err("IMA HW not in correct state, hw_sts=%x\n",
+				hw_sts);
+			run_err_clr_seq = true;
+		}
+	}
+
+	if (exp_sts & (IACS_ERR_BIT | XCT_TYPE_ERR_BIT | DATA_RD_ERR_BIT |
+		DATA_WR_ERR_BIT | ADDR_BURST_WRAP_BIT | ADDR_STABLE_ERR_BIT)) {
+		pr_err("IMA exception bit set, exp_sts=%x\n", exp_sts);
+		run_err_clr_seq = true;
+	}
+
+	if (run_err_clr_seq) {
+		/* clear the error */
+		rc = fg_run_iacs_clear_sequence(chip);
+		if (rc < 0) {
+			pr_err("failed to run iacs clear sequence rc=%d\n", rc);
+			return rc;
+		}
+
+		/* Retry again as there was an error in the transaction */
+		return -EAGAIN;
+	}
+
+	return rc;
+}
+
+static int fg_check_iacs_ready(struct fg_chip *chip)
+{
+	int rc = 0, tries = 250;
+	u8 ima_opr_sts = 0;
+
+	/*
+	 * Additional delay to make sure IACS ready bit is set after
+	 * Read/Write operation.
+	 */
+
+	usleep_range(30, 35);
+	while (1) {
+		rc = fg_read(chip, MEM_IF_IMA_OPR_STS(chip), &ima_opr_sts, 1);
+		if (rc < 0) {
+			pr_err("failed to read 0x%04x, rc=%d\n",
+				MEM_IF_IMA_OPR_STS(chip), rc);
+			return rc;
+		}
+
+		if (ima_opr_sts & IACS_RDY_BIT)
+			break;
+
+		if (!(--tries))
+			break;
+
+		/* delay for iacs_ready to be asserted */
+		usleep_range(5000, 7000);
+	}
+
+	if (!tries) {
+		pr_err("IACS_RDY not set, opr_sts: %d\n", ima_opr_sts);
+		/* check for error condition */
+		rc = fg_clear_ima_errors_if_any(chip, false);
+		if (rc < 0) {
+			pr_err("Failed to check for ima errors rc=%d\n", rc);
+			return rc;
+		}
+
+		return -EBUSY;
+	}
+
+	return 0;
+}
+
+static int __fg_interleaved_mem_write(struct fg_chip *chip, u16 address,
+				int offset, u8 *val, int len)
+{
+	int rc = 0, i;
+	u8 *ptr = val, byte_enable = 0, num_bytes = 0;
+
+	fg_dbg(chip, FG_SRAM_WRITE, "length %d addr=%02X offset=%d\n", len,
+		address, offset);
+
+	while (len > 0) {
+		num_bytes = (offset + len) > BYTES_PER_SRAM_WORD ?
+				(BYTES_PER_SRAM_WORD - offset) : len;
+
+		/* write to byte_enable */
+		for (i = offset; i < (offset + num_bytes); i++)
+			byte_enable |= BIT(i);
+
+		rc = fg_write(chip, MEM_IF_IMA_BYTE_EN(chip), &byte_enable, 1);
+		if (rc < 0) {
+			pr_err("Unable to write to byte_en_reg rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		/* write data */
+		rc = fg_write(chip, MEM_IF_WR_DATA0(chip) + offset, ptr,
+				num_bytes);
+		if (rc < 0) {
+			pr_err("failed to write to 0x%04x, rc=%d\n",
+				MEM_IF_WR_DATA0(chip) + offset, rc);
+			return rc;
+		}
+
+		/*
+		 * The last-byte WR_DATA3 starts the write transaction.
+		 * Write a dummy value to WR_DATA3 if it does not have
+		 * valid data. This dummy data is not written to the
+		 * SRAM as byte_en for WR_DATA3 is not set.
+		 */
+		if (!(byte_enable & BIT(3))) {
+			u8 dummy_byte = 0x0;
+
+			rc = fg_write(chip, MEM_IF_WR_DATA3(chip), &dummy_byte,
+					1);
+			if (rc < 0) {
+				pr_err("failed to write dummy-data to WR_DATA3 rc=%d\n",
+					rc);
+				return rc;
+			}
+		}
+
+		/* check for error condition */
+		rc = fg_clear_ima_errors_if_any(chip, false);
+		if (rc < 0) {
+			pr_err("Failed to check for ima errors rc=%d\n", rc);
+			return rc;
+		}
+
+		ptr += num_bytes;
+		len -= num_bytes;
+		offset = byte_enable = 0;
+
+		rc = fg_check_iacs_ready(chip);
+		if (rc < 0) {
+			pr_debug("IACS_RDY failed rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int __fg_interleaved_mem_read(struct fg_chip *chip, u16 address,
+				int offset, u8 *val, int len)
+{
+	int rc = 0, total_len;
+	u8 *rd_data = val, num_bytes;
+	char str[DEBUG_PRINT_BUFFER_SIZE];
+
+	fg_dbg(chip, FG_SRAM_READ, "length %d addr=%02X\n", len, address);
+
+	total_len = len;
+	while (len > 0) {
+		num_bytes = (offset + len) > BYTES_PER_SRAM_WORD ?
+				(BYTES_PER_SRAM_WORD - offset) : len;
+		rc = fg_read(chip, MEM_IF_RD_DATA0(chip) + offset, rd_data,
+				num_bytes);
+		if (rc < 0) {
+			pr_err("failed to read 0x%04x, rc=%d\n",
+				MEM_IF_RD_DATA0(chip) + offset, rc);
+			return rc;
+		}
+
+		rd_data += num_bytes;
+		len -= num_bytes;
+		offset = 0;
+
+		/* check for error condition */
+		rc = fg_clear_ima_errors_if_any(chip, false);
+		if (rc < 0) {
+			pr_err("Failed to check for ima errors rc=%d\n", rc);
+			return rc;
+		}
+
+		if (len && len < BYTES_PER_SRAM_WORD) {
+			/*
+			 * Move to single mode. Changing address is not
+			 * required here as it must be in burst mode. Address
+			 * will get incremented internally by FG HW once the MSB
+			 * of RD_DATA is read.
+			 */
+			rc = fg_config_access_mode(chip, FG_READ, 0);
+			if (rc < 0) {
+				pr_err("failed to move to single mode rc=%d\n",
+					rc);
+				return -EIO;
+			}
+		}
+
+		rc = fg_check_iacs_ready(chip);
+		if (rc < 0) {
+			pr_debug("IACS_RDY failed rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (*chip->debug_mask & FG_SRAM_READ) {
+		fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len);
+		pr_info("data read: %s\n", str);
+	}
+
+	return rc;
+}
+
+static int fg_get_mem_access_status(struct fg_chip *chip, bool *status)
+{
+	int rc;
+	u8 mem_if_sts;
+
+	rc = fg_read(chip, MEM_IF_MEM_INTF_CFG(chip), &mem_if_sts, 1);
+	if (rc < 0) {
+		pr_err("failed to read rif_mem status rc=%d\n", rc);
+		return rc;
+	}
+
+	*status = mem_if_sts & MEM_ACCESS_REQ_BIT;
+	return 0;
+}
+
+static bool is_mem_access_available(struct fg_chip *chip, int access)
+{
+	bool rif_mem_sts = true;
+	int rc, time_count = 0;
+
+	while (1) {
+		rc = fg_get_mem_access_status(chip, &rif_mem_sts);
+		if (rc < 0)
+			return rc;
+
+		/* This is an inverting logic */
+		if (!rif_mem_sts)
+			break;
+
+		fg_dbg(chip, FG_SRAM_READ | FG_SRAM_WRITE, "MEM_ACCESS_REQ is not clear yet for IMA_%s\n",
+			access ? "write" : "read");
+
+		/*
+		 * Try this no more than 4 times. If MEM_ACCESS_REQ is not
+		 * clear, then return an error instead of waiting for it again.
+		 */
+		if  (time_count > 4) {
+			pr_err("Tried 4 times(~16ms) polling MEM_ACCESS_REQ\n");
+			return false;
+		}
+
+		/* Wait for 4ms before reading MEM_ACCESS_REQ again */
+		usleep_range(4000, 4100);
+		time_count++;
+	}
+	return true;
+}
+
+static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val,
+		u16 address, int offset, int len, bool access)
+{
+	int rc = 0;
+
+	if (!is_mem_access_available(chip, access))
+		return -EBUSY;
+
+	/* configure for IMA access */
+	rc = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT,
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT);
+	if (rc < 0) {
+		pr_err("failed to set ima_req_access bit rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure for the read/write, single/burst mode */
+	rc = fg_config_access_mode(chip, access, (offset + len) > 4);
+	if (rc < 0) {
+		pr_err("failed to set memory access rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = fg_check_iacs_ready(chip);
+	if (rc < 0) {
+		pr_err_ratelimited("IACS_RDY failed rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_set_address(chip, address);
+	if (rc < 0) {
+		pr_err("failed to set address rc = %d\n", rc);
+		return rc;
+	}
+
+	if (access == FG_READ) {
+		rc = fg_check_iacs_ready(chip);
+		if (rc < 0) {
+			pr_debug("IACS_RDY failed rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int fg_get_beat_count(struct fg_chip *chip, u8 *count)
+{
+	int rc;
+
+	rc = fg_read(chip, MEM_IF_FG_BEAT_COUNT(chip), count, 1);
+	*count &= BEAT_COUNT_MASK;
+	return rc;
+}
+
+int fg_interleaved_mem_read(struct fg_chip *chip, u16 address, u8 offset,
+				u8 *val, int len)
+{
+	int rc = 0, ret;
+	u8 start_beat_count, end_beat_count, count = 0;
+	bool retry = false;
+
+	if (offset > 3) {
+		pr_err("offset too large %d\n", offset);
+		return -EINVAL;
+	}
+
+retry:
+	if (count >= RETRY_COUNT) {
+		pr_err("Tried %d times\n", RETRY_COUNT);
+		retry = false;
+		goto out;
+	}
+
+	rc = fg_interleaved_mem_config(chip, val, address, offset, len,
+					FG_READ);
+	if (rc < 0) {
+		pr_err("failed to configure SRAM for IMA rc = %d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	/* read the start beat count */
+	rc = fg_get_beat_count(chip, &start_beat_count);
+	if (rc < 0) {
+		pr_err("failed to read beat count rc=%d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	/* read data */
+	rc = __fg_interleaved_mem_read(chip, address, offset, val, len);
+	if (rc < 0) {
+		count++;
+		if (rc == -EAGAIN) {
+			pr_err("IMA access failed retry_count = %d\n", count);
+			goto retry;
+		}
+		pr_err("failed to read SRAM address rc = %d\n", rc);
+		retry = true;
+		goto out;
+	}
+
+	/* read the end beat count */
+	rc = fg_get_beat_count(chip, &end_beat_count);
+	if (rc < 0) {
+		pr_err("failed to read beat count rc=%d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	fg_dbg(chip, FG_SRAM_READ, "Start beat_count = %x End beat_count = %x\n",
+		start_beat_count, end_beat_count);
+
+	if (start_beat_count != end_beat_count) {
+		fg_dbg(chip, FG_SRAM_READ, "Beat count(%d/%d) do not match - retry transaction\n",
+			start_beat_count, end_beat_count);
+		count++;
+		retry = true;
+	}
+out:
+	/* Release IMA access */
+	ret = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+	if (rc < 0 && ret < 0) {
+		pr_err("failed to reset IMA access bit ret = %d\n", ret);
+		return ret;
+	}
+
+	if (retry) {
+		retry = false;
+		goto retry;
+	}
+
+	return rc;
+}
+
+int fg_interleaved_mem_write(struct fg_chip *chip, u16 address, u8 offset,
+				u8 *val, int len, bool atomic_access)
+{
+	int rc = 0, ret;
+	u8 start_beat_count, end_beat_count, count = 0;
+	bool retry = false;
+
+	if (offset > 3) {
+		pr_err("offset too large %d\n", offset);
+		return -EINVAL;
+	}
+
+retry:
+	if (count >= RETRY_COUNT) {
+		pr_err("Tried %d times\n", RETRY_COUNT);
+		retry = false;
+		goto out;
+	}
+
+	rc = fg_interleaved_mem_config(chip, val, address, offset, len,
+					FG_WRITE);
+	if (rc < 0) {
+		pr_err("failed to configure SRAM for IMA rc = %d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	/* read the start beat count */
+	rc = fg_get_beat_count(chip, &start_beat_count);
+	if (rc < 0) {
+		pr_err("failed to read beat count rc=%d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	/* write data */
+	rc = __fg_interleaved_mem_write(chip, address, offset, val, len);
+	if (rc < 0) {
+		count++;
+		if ((rc == -EAGAIN) && (count < RETRY_COUNT)) {
+			pr_err("IMA access failed retry_count = %d\n", count);
+			goto retry;
+		}
+		pr_err("failed to write SRAM address rc = %d\n", rc);
+		retry = true;
+		goto out;
+	}
+
+	/* read the end beat count */
+	rc = fg_get_beat_count(chip, &end_beat_count);
+	if (rc < 0) {
+		pr_err("failed to read beat count rc=%d\n", rc);
+		count++;
+		retry = true;
+		goto out;
+	}
+
+	if (atomic_access && start_beat_count != end_beat_count)
+		pr_err("Start beat_count = %x End beat_count = %x\n",
+			start_beat_count, end_beat_count);
+out:
+	/* Release IMA access */
+	ret = fg_masked_write(chip, MEM_IF_MEM_INTF_CFG(chip),
+				MEM_ACCESS_REQ_BIT | IACS_SLCT_BIT, 0);
+	if (rc < 0 && ret < 0) {
+		pr_err("failed to reset IMA access bit ret = %d\n", ret);
+		return ret;
+	}
+
+	if (retry) {
+		retry = false;
+		goto retry;
+	}
+
+	/* Return the error we got before releasing memory access */
+	return rc;
+}
+
+int fg_ima_init(struct fg_chip *chip)
+{
+	int rc;
+
+	/*
+	 * Change the FG_MEM_INT interrupt to track IACS_READY
+	 * condition instead of end-of-transaction. This makes sure
+	 * that the next transaction starts only after the hw is ready.
+	 */
+	rc = fg_masked_write(chip, MEM_IF_IMA_CFG(chip), IACS_INTR_SRC_SLCT_BIT,
+				IACS_INTR_SRC_SLCT_BIT);
+	if (rc < 0) {
+		pr_err("failed to configure interrupt source %d\n", rc);
+		return rc;
+	}
+
+	/* Clear DMA errors if any before clearing IMA errors */
+	rc = fg_clear_dma_errors_if_any(chip);
+	if (rc < 0) {
+		pr_err("Error in checking DMA errors rc:%d\n", rc);
+		return rc;
+	}
+
+	/* Clear IMA errors if any before SRAM transactions can begin */
+	rc = fg_clear_ima_errors_if_any(chip, true);
+	if (rc < 0 && rc != -EAGAIN) {
+		pr_err("Error in checking IMA errors rc:%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
diff --git a/drivers/power/supply/qcom/fg-reg.h b/drivers/power/supply/qcom/fg-reg.h
new file mode 100644
index 0000000..bf2827f
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-reg.h
@@ -0,0 +1,328 @@
+/* Copyright (c) 2016-2017, 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 __FG_REG_H__
+#define __FG_REG_H__
+
+/* FG_ADC_RR register definitions used only for READ */
+#define ADC_RR_FAKE_BATT_LOW_LSB(chip)		(chip->rradc_base + 0x58)
+#define ADC_RR_FAKE_BATT_HIGH_LSB(chip)		(chip->rradc_base + 0x5A)
+
+/* FG_BATT_SOC register definitions */
+#define BATT_SOC_FG_ALG_STS(chip)		(chip->batt_soc_base + 0x06)
+#define BATT_SOC_FG_ALG_AUX_STS0(chip)		(chip->batt_soc_base + 0x07)
+#define BATT_SOC_SLEEP_SHUTDOWN_STS(chip)	(chip->batt_soc_base + 0x08)
+#define BATT_SOC_FG_MONOTONIC_SOC(chip)		(chip->batt_soc_base + 0x09)
+#define BATT_SOC_FG_MONOTONIC_SOC_CP(chip)	(chip->batt_soc_base + 0x0A)
+#define BATT_SOC_INT_RT_STS(chip)		(chip->batt_soc_base + 0x10)
+#define BATT_SOC_EN_CTL(chip)			(chip->batt_soc_base + 0x46)
+#define BATT_SOC_RESTART(chip)			(chip->batt_soc_base + 0x48)
+#define BATT_SOC_STS_CLR(chip)			(chip->batt_soc_base + 0x4A)
+#define BATT_SOC_LOW_PWR_CFG(chip)		(chip->batt_soc_base + 0x52)
+#define BATT_SOC_LOW_PWR_STS(chip)		(chip->batt_soc_base + 0x56)
+
+/* BATT_SOC_INT_RT_STS */
+#define MSOC_EMPTY_BIT				BIT(5)
+
+/* BATT_SOC_EN_CTL */
+#define FG_ALGORITHM_EN_BIT			BIT(7)
+
+/* BATT_SOC_RESTART */
+#define RESTART_GO_BIT				BIT(0)
+
+/* FG_BATT_INFO register definitions */
+#define BATT_INFO_BATT_TEMP_STS(chip)		(chip->batt_info_base + 0x06)
+#define BATT_INFO_SYS_BATT(chip)		(chip->batt_info_base + 0x07)
+#define BATT_INFO_FG_STS(chip)			(chip->batt_info_base + 0x09)
+#define BATT_INFO_INT_RT_STS(chip)		(chip->batt_info_base + 0x10)
+#define BATT_INFO_BATT_REM_LATCH(chip)		(chip->batt_info_base + 0x4F)
+#define BATT_INFO_BATT_TEMP_LSB(chip)		(chip->batt_info_base + 0x50)
+#define BATT_INFO_BATT_TEMP_MSB(chip)		(chip->batt_info_base + 0x51)
+#define BATT_INFO_BATT_TEMP_CFG(chip)		(chip->batt_info_base + 0x56)
+#define BATT_INFO_BATT_TMPR_INTR(chip)		(chip->batt_info_base + 0x59)
+#define BATT_INFO_THERM_C1(chip)		(chip->batt_info_base + 0x5C)
+#define BATT_INFO_THERM_C2(chip)		(chip->batt_info_base + 0x5D)
+#define BATT_INFO_THERM_C3(chip)		(chip->batt_info_base + 0x5E)
+#define BATT_INFO_THERM_HALF_RANGE(chip)	(chip->batt_info_base + 0x5F)
+#define BATT_INFO_JEITA_CTLS(chip)		(chip->batt_info_base + 0x61)
+#define BATT_INFO_JEITA_TOO_COLD(chip)		(chip->batt_info_base + 0x62)
+#define BATT_INFO_JEITA_COLD(chip)		(chip->batt_info_base + 0x63)
+#define BATT_INFO_JEITA_HOT(chip)		(chip->batt_info_base + 0x64)
+#define BATT_INFO_JEITA_TOO_HOT(chip)		(chip->batt_info_base + 0x65)
+
+/* only for v1.1 */
+#define BATT_INFO_ESR_CFG(chip)			(chip->batt_info_base + 0x69)
+/* starting from v2.0 */
+#define BATT_INFO_ESR_GENERAL_CFG(chip)		(chip->batt_info_base + 0x68)
+#define BATT_INFO_ESR_PULL_DN_CFG(chip)		(chip->batt_info_base + 0x69)
+#define BATT_INFO_ESR_FAST_CRG_CFG(chip)	(chip->batt_info_base + 0x6A)
+
+#define BATT_INFO_BATT_MISS_CFG(chip)		(chip->batt_info_base + 0x6B)
+#define BATT_INFO_WATCHDOG_COUNT(chip)		(chip->batt_info_base + 0x70)
+#define BATT_INFO_WATCHDOG_CFG(chip)		(chip->batt_info_base + 0x71)
+#define BATT_INFO_IBATT_SENSING_CFG(chip)	(chip->batt_info_base + 0x73)
+#define BATT_INFO_QNOVO_CFG(chip)		(chip->batt_info_base + 0x74)
+#define BATT_INFO_QNOVO_SCALER(chip)		(chip->batt_info_base + 0x75)
+
+/* starting from v2.0 */
+#define BATT_INFO_CRG_SERVICES(chip)		(chip->batt_info_base + 0x90)
+
+/* Following LSB/MSB address are for v2.0 and above; v1.1 have them swapped */
+#define BATT_INFO_VBATT_LSB(chip)		(chip->batt_info_base + 0xA0)
+#define BATT_INFO_VBATT_MSB(chip)		(chip->batt_info_base + 0xA1)
+#define BATT_INFO_IBATT_LSB(chip)		(chip->batt_info_base + 0xA2)
+#define BATT_INFO_IBATT_MSB(chip)		(chip->batt_info_base + 0xA3)
+#define BATT_INFO_ESR_LSB(chip)			(chip->batt_info_base + 0xA4)
+#define BATT_INFO_ESR_MSB(chip)			(chip->batt_info_base + 0xA5)
+#define BATT_INFO_VBATT_LSB_CP(chip)		(chip->batt_info_base + 0xA6)
+#define BATT_INFO_VBATT_MSB_CP(chip)		(chip->batt_info_base + 0xA7)
+#define BATT_INFO_IBATT_LSB_CP(chip)		(chip->batt_info_base + 0xA8)
+#define BATT_INFO_IBATT_MSB_CP(chip)		(chip->batt_info_base + 0xA9)
+#define BATT_INFO_ESR_LSB_CP(chip)		(chip->batt_info_base + 0xAA)
+#define BATT_INFO_ESR_MSB_CP(chip)		(chip->batt_info_base + 0xAB)
+#define BATT_INFO_VADC_LSB(chip)		(chip->batt_info_base + 0xAC)
+#define BATT_INFO_VADC_MSB(chip)		(chip->batt_info_base + 0xAD)
+#define BATT_INFO_IADC_LSB(chip)		(chip->batt_info_base + 0xAE)
+#define BATT_INFO_IADC_MSB(chip)		(chip->batt_info_base + 0xAF)
+#define BATT_INFO_TM_MISC(chip)			(chip->batt_info_base + 0xE5)
+#define BATT_INFO_TM_MISC1(chip)		(chip->batt_info_base + 0xE6)
+
+/* BATT_INFO_BATT_TEMP_STS */
+#define JEITA_TOO_HOT_STS_BIT			BIT(7)
+#define JEITA_HOT_STS_BIT			BIT(6)
+#define JEITA_COLD_STS_BIT			BIT(5)
+#define JEITA_TOO_COLD_STS_BIT			BIT(4)
+#define BATT_TEMP_DELTA_BIT			BIT(1)
+#define BATT_TEMP_AVAIL_BIT			BIT(0)
+
+/* BATT_INFO_SYS_BATT */
+#define BATT_REM_LATCH_STS_BIT			BIT(4)
+#define BATT_MISSING_HW_BIT			BIT(2)
+#define BATT_MISSING_ALG_BIT			BIT(1)
+#define BATT_MISSING_CMP_BIT			BIT(0)
+
+/* BATT_INFO_FG_STS */
+#define FG_WD_RESET_BIT				BIT(7)
+/* This bit is not present in v1.1 */
+#define FG_CRG_TRM_BIT				BIT(0)
+
+/* BATT_INFO_INT_RT_STS */
+#define BT_TMPR_DELTA_BIT			BIT(6)
+#define WDOG_EXP_BIT				BIT(5)
+#define BT_ATTN_BIT				BIT(4)
+#define BT_MISS_BIT				BIT(3)
+#define ESR_DELTA_BIT				BIT(2)
+#define VBT_LOW_BIT				BIT(1)
+#define VBT_PRD_DELTA_BIT			BIT(0)
+
+/* BATT_INFO_INT_RT_STS */
+#define BATT_REM_LATCH_CLR_BIT			BIT(7)
+
+/* BATT_INFO_BATT_TEMP_LSB/MSB */
+#define BATT_TEMP_LSB_MASK			GENMASK(7, 0)
+#define BATT_TEMP_MSB_MASK			GENMASK(2, 0)
+
+/* BATT_INFO_BATT_TEMP_CFG */
+#define JEITA_TEMP_HYST_MASK			GENMASK(5, 4)
+#define JEITA_TEMP_HYST_SHIFT			4
+#define JEITA_TEMP_NO_HYST			0x0
+#define JEITA_TEMP_HYST_1C			0x1
+#define JEITA_TEMP_HYST_2C			0x2
+#define JEITA_TEMP_HYST_3C			0x3
+
+/* BATT_INFO_BATT_TMPR_INTR */
+#define CHANGE_THOLD_MASK			GENMASK(1, 0)
+#define BTEMP_DELTA_2K				0x0
+#define BTEMP_DELTA_4K				0x1
+#define BTEMP_DELTA_6K				0x2
+#define BTEMP_DELTA_10K				0x3
+
+/* BATT_INFO_THERM_C1/C2/C3 */
+#define BATT_INFO_THERM_COEFF_MASK		GENMASK(7, 0)
+
+/* BATT_INFO_THERM_HALF_RANGE */
+#define BATT_INFO_THERM_TEMP_MASK		GENMASK(7, 0)
+
+/* BATT_INFO_JEITA_CTLS */
+#define JEITA_STS_CLEAR_BIT			BIT(0)
+
+/* BATT_INFO_JEITA_TOO_COLD/COLD/HOT/TOO_HOT */
+#define JEITA_THOLD_MASK			GENMASK(7, 0)
+
+/* BATT_INFO_ESR_CFG */
+#define CFG_ACTIVE_PD_MASK			GENMASK(2, 1)
+#define CFG_FCC_DEC_MASK			GENMASK(4, 3)
+
+/* BATT_INFO_ESR_GENERAL_CFG */
+#define ESR_DEEP_TAPER_EN_BIT			BIT(0)
+
+/* BATT_INFO_ESR_PULL_DN_CFG */
+#define ESR_PULL_DOWN_IVAL_MASK			GENMASK(3, 2)
+#define ESR_MEAS_CUR_60MA			0x0
+#define ESR_MEAS_CUR_120MA			0x1
+#define ESR_MEAS_CUR_180MA			0x2
+#define ESR_MEAS_CUR_240MA			0x3
+#define ESR_PULL_DOWN_MODE_MASK			GENMASK(1, 0)
+#define ESR_NO_PULL_DOWN			0x0
+#define ESR_STATIC_PULL_DOWN			0x1
+#define ESR_CRG_DSC_PULL_DOWN			0x2
+#define ESR_DSC_PULL_DOWN			0x3
+
+/* BATT_INFO_ESR_FAST_CRG_CFG */
+#define ESR_FAST_CRG_IVAL_MASK			GENMASK(3, 1)
+#define ESR_FCC_300MA				0x0
+#define ESR_FCC_600MA				0x1
+#define ESR_FCC_1A				0x2
+#define ESR_FCC_2A				0x3
+#define ESR_FCC_3A				0x4
+#define ESR_FCC_4A				0x5
+#define ESR_FCC_5A				0x6
+#define ESR_FCC_6A				0x7
+#define ESR_FAST_CRG_CTL_EN_BIT			BIT(0)
+
+/* BATT_INFO_BATT_MISS_CFG */
+#define BM_THERM_TH_MASK			GENMASK(5, 4)
+#define RES_TH_0P75_MOHM			0x0
+#define RES_TH_1P00_MOHM			0x1
+#define RES_TH_1P50_MOHM			0x2
+#define RES_TH_3P00_MOHM			0x3
+#define BM_BATT_ID_TH_MASK			GENMASK(3, 2)
+#define BM_FROM_THERM_BIT			BIT(1)
+#define BM_FROM_BATT_ID_BIT			BIT(0)
+
+/* BATT_INFO_WATCHDOG_COUNT */
+#define WATCHDOG_COUNTER			GENMASK(7, 0)
+
+/* BATT_INFO_WATCHDOG_CFG */
+#define RESET_CAPABLE_BIT			BIT(2)
+#define PET_CTRL_BIT				BIT(1)
+#define ENABLE_CTRL_BIT				BIT(0)
+
+/* BATT_INFO_IBATT_SENSING_CFG */
+#define ADC_BITSTREAM_INV_BIT			BIT(4)
+#define SOURCE_SELECT_MASK			GENMASK(1, 0)
+#define SRC_SEL_BATFET				0x0
+#define SRC_SEL_BATFET_SMB			0x2
+#define SRC_SEL_RESERVED			0x3
+
+/* BATT_INFO_QNOVO_CFG */
+#define LD_REG_FORCE_CTL_BIT			BIT(2)
+#define LD_REG_CTRL_FORCE_HIGH			LD_REG_FORCE_CTL_BIT
+#define LD_REG_CTRL_FORCE_LOW			0
+#define LD_REG_CTRL_BIT				BIT(1)
+#define LD_REG_CTRL_REGISTER			LD_REG_CTRL_BIT
+#define LD_REG_CTRL_LOGIC			0
+#define BIT_STREAM_CFG_BIT			BIT(0)
+
+/* BATT_INFO_QNOVO_SCALER */
+#define QNOVO_SCALER_MASK			GENMASK(7, 0)
+
+/* BATT_INFO_CRG_SERVICES */
+#define FG_CRC_TRM_EN_BIT			BIT(0)
+
+/* BATT_INFO_VBATT_LSB/MSB */
+#define VBATT_MASK				GENMASK(7, 0)
+
+/* BATT_INFO_IBATT_LSB/MSB */
+#define IBATT_MASK				GENMASK(7, 0)
+
+/* BATT_INFO_ESR_LSB/MSB */
+#define ESR_LSB_MASK				GENMASK(7, 0)
+#define ESR_MSB_MASK				GENMASK(5, 0)
+
+/* BATT_INFO_VADC_LSB/MSB */
+#define VADC_LSB_MASK				GENMASK(7, 0)
+#define VADC_MSB_MASK				GENMASK(6, 0)
+
+/* BATT_INFO_IADC_LSB/MSB */
+#define IADC_LSB_MASK				GENMASK(7, 0)
+#define IADC_MSB_MASK				GENMASK(6, 0)
+
+/* BATT_INFO_TM_MISC */
+#define FORCE_SEQ_RESP_TOGGLE_BIT		BIT(6)
+#define ALG_DIRECT_VALID_DATA_BIT		BIT(5)
+#define ALG_DIRECT_MODE_EN_BIT			BIT(4)
+#define BATT_VADC_CONV_BIT			BIT(3)
+#define BATT_IADC_CONV_BIT			BIT(2)
+#define ADC_ENABLE_REG_CTRL_BIT			BIT(1)
+#define WDOG_FORCE_EXP_BIT			BIT(0)
+/* only for v1.1 */
+#define ESR_PULSE_FORCE_CTRL_BIT		BIT(7)
+
+/* BATT_INFO_TM_MISC1 */
+/* for v2.0 and above */
+#define ESR_REQ_CTL_BIT				BIT(1)
+#define ESR_REQ_CTL_EN_BIT			BIT(0)
+
+/* FG_MEM_IF register and bit definitions */
+#define MEM_IF_INT_RT_STS(chip)			((chip->mem_if_base) + 0x10)
+#define MEM_IF_MEM_INTF_CFG(chip)		((chip->mem_if_base) + 0x50)
+#define MEM_IF_IMA_CTL(chip)			((chip->mem_if_base) + 0x51)
+#define MEM_IF_IMA_CFG(chip)			((chip->mem_if_base) + 0x52)
+#define MEM_IF_IMA_OPR_STS(chip)		((chip->mem_if_base) + 0x54)
+#define MEM_IF_IMA_EXP_STS(chip)		((chip->mem_if_base) + 0x55)
+#define MEM_IF_IMA_HW_STS(chip)			((chip->mem_if_base) + 0x56)
+#define MEM_IF_FG_BEAT_COUNT(chip)		((chip->mem_if_base) + 0x57)
+#define MEM_IF_IMA_ERR_STS(chip)		((chip->mem_if_base) + 0x5F)
+#define MEM_IF_IMA_BYTE_EN(chip)		((chip->mem_if_base) + 0x60)
+#define MEM_IF_ADDR_LSB(chip)			((chip->mem_if_base) + 0x61)
+#define MEM_IF_ADDR_MSB(chip)			((chip->mem_if_base) + 0x62)
+#define MEM_IF_WR_DATA0(chip)			((chip->mem_if_base) + 0x63)
+#define MEM_IF_WR_DATA3(chip)			((chip->mem_if_base) + 0x66)
+#define MEM_IF_RD_DATA0(chip)			((chip->mem_if_base) + 0x67)
+#define MEM_IF_RD_DATA3(chip)			((chip->mem_if_base) + 0x6A)
+#define MEM_IF_DMA_STS(chip)			((chip->mem_if_base) + 0x70)
+#define MEM_IF_DMA_CTL(chip)			((chip->mem_if_base) + 0x71)
+
+/* MEM_IF_INT_RT_STS */
+#define MEM_XCP_BIT				BIT(1)
+
+/* MEM_IF_MEM_INTF_CFG */
+#define MEM_ACCESS_REQ_BIT			BIT(7)
+#define IACS_SLCT_BIT				BIT(5)
+
+/* MEM_IF_IMA_CTL */
+#define MEM_ACS_BURST_BIT			BIT(7)
+#define IMA_WR_EN_BIT				BIT(6)
+#define IMA_CTL_MASK				GENMASK(7, 6)
+
+/* MEM_IF_IMA_CFG */
+#define IACS_CLR_BIT				BIT(2)
+#define IACS_INTR_SRC_SLCT_BIT			BIT(3)
+#define STATIC_CLK_EN_BIT			BIT(4)
+
+/* MEM_IF_IMA_OPR_STS */
+#define IACS_RDY_BIT				BIT(1)
+
+/* MEM_IF_IMA_EXP_STS */
+#define IACS_ERR_BIT				BIT(0)
+#define XCT_TYPE_ERR_BIT			BIT(1)
+#define DATA_RD_ERR_BIT				BIT(3)
+#define DATA_WR_ERR_BIT				BIT(4)
+#define ADDR_BURST_WRAP_BIT			BIT(5)
+#define ADDR_STABLE_ERR_BIT			BIT(7)
+
+/* MEM_IF_IMA_ERR_STS */
+#define ADDR_STBL_ERR_BIT			BIT(7)
+#define WR_ACS_ERR_BIT				BIT(6)
+#define RD_ACS_ERR_BIT				BIT(5)
+
+/* MEM_IF_FG_BEAT_COUNT */
+#define BEAT_COUNT_MASK				GENMASK(3, 0)
+
+/* MEM_IF_DMA_STS */
+#define DMA_WRITE_ERROR_BIT			BIT(1)
+#define DMA_READ_ERROR_BIT			BIT(2)
+
+/* MEM_IF_DMA_CTL */
+#define DMA_CLEAR_LOG_BIT			BIT(0)
+#endif
diff --git a/drivers/power/supply/qcom/fg-util.c b/drivers/power/supply/qcom/fg-util.c
new file mode 100644
index 0000000..839a771
--- /dev/null
+++ b/drivers/power/supply/qcom/fg-util.c
@@ -0,0 +1,901 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "fg-core.h"
+
+void fg_circ_buf_add(struct fg_circ_buf *buf, int val)
+{
+	buf->arr[buf->head] = val;
+	buf->head = (buf->head + 1) % ARRAY_SIZE(buf->arr);
+	buf->size = min(++buf->size, (int)ARRAY_SIZE(buf->arr));
+}
+
+void fg_circ_buf_clr(struct fg_circ_buf *buf)
+{
+	memset(buf, 0, sizeof(*buf));
+}
+
+int fg_circ_buf_avg(struct fg_circ_buf *buf, int *avg)
+{
+	s64 result = 0;
+	int i;
+
+	if (buf->size == 0)
+		return -ENODATA;
+
+	for (i = 0; i < buf->size; i++)
+		result += buf->arr[i];
+
+	*avg = div_s64(result, buf->size);
+	return 0;
+}
+
+int fg_lerp(const struct fg_pt *pts, size_t tablesize, s32 input, s32 *output)
+{
+	int i;
+	s64 temp;
+
+	if (pts == NULL) {
+		pr_err("Table is NULL\n");
+		return -EINVAL;
+	}
+
+	if (tablesize < 1) {
+		pr_err("Table has no entries\n");
+		return -ENOENT;
+	}
+
+	if (tablesize == 1) {
+		*output = pts[0].y;
+		return 0;
+	}
+
+	if (pts[0].x > pts[1].x) {
+		pr_err("Table is not in acending order\n");
+		return -EINVAL;
+	}
+
+	if (input <= pts[0].x) {
+		*output = pts[0].y;
+		return 0;
+	}
+
+	if (input >= pts[tablesize - 1].x) {
+		*output = pts[tablesize - 1].y;
+		return 0;
+	}
+
+	for (i = 1; i < tablesize; i++) {
+		if (input >= pts[i].x)
+			continue;
+
+		temp = (s64)(pts[i].y - pts[i - 1].y) *
+						(s64)(input - pts[i - 1].x);
+		temp = div_s64(temp, pts[i].x - pts[i - 1].x);
+		*output = temp + pts[i - 1].y;
+		return 0;
+	}
+
+	return -EINVAL;
+}
+
+static struct fg_dbgfs dbgfs_data = {
+	.help_msg = {
+	.data =
+	"FG Debug-FS support\n"
+	"\n"
+	"Hierarchy schema:\n"
+	"/sys/kernel/debug/fg_sram\n"
+	"       /help            -- Static help text\n"
+	"       /address  -- Starting register address for reads or writes\n"
+	"       /count    -- Number of registers to read (only used for reads)\n"
+	"       /data     -- Initiates the SRAM read (formatted output)\n"
+	"\n",
+	},
+};
+
+static bool is_usb_present(struct fg_chip *chip)
+{
+	union power_supply_propval pval = {0, };
+
+	if (!chip->usb_psy)
+		chip->usb_psy = power_supply_get_by_name("usb");
+
+	if (chip->usb_psy)
+		power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT, &pval);
+	else
+		return false;
+
+	return pval.intval != 0;
+}
+
+static bool is_dc_present(struct fg_chip *chip)
+{
+	union power_supply_propval pval = {0, };
+
+	if (!chip->dc_psy)
+		chip->dc_psy = power_supply_get_by_name("dc");
+
+	if (chip->dc_psy)
+		power_supply_get_property(chip->dc_psy,
+				POWER_SUPPLY_PROP_PRESENT, &pval);
+	else
+		return false;
+
+	return pval.intval != 0;
+}
+
+bool is_input_present(struct fg_chip *chip)
+{
+	return is_usb_present(chip) || is_dc_present(chip);
+}
+
+#define EXPONENT_SHIFT		11
+#define EXPONENT_OFFSET		-9
+#define MANTISSA_SIGN_BIT	10
+#define MICRO_UNIT		1000000
+s64 fg_float_decode(u16 val)
+{
+	s8 exponent;
+	s32 mantissa;
+
+	/* mantissa bits are shifted out during sign extension */
+	exponent = ((s16)val >> EXPONENT_SHIFT) + EXPONENT_OFFSET;
+	/* exponent bits are shifted out during sign extension */
+	mantissa = sign_extend32(val, MANTISSA_SIGN_BIT) * MICRO_UNIT;
+
+	if (exponent < 0)
+		return (s64)mantissa >> -exponent;
+
+	return (s64)mantissa << exponent;
+}
+
+void fill_string(char *str, size_t str_len, u8 *buf, int buf_len)
+{
+	int pos = 0;
+	int i;
+
+	for (i = 0; i < buf_len; i++) {
+		pos += scnprintf(str + pos, str_len - pos, "%02x", buf[i]);
+		if (i < buf_len - 1)
+			pos += scnprintf(str + pos, str_len - pos, " ");
+	}
+}
+
+void dump_sram(u8 *buf, int addr, int len)
+{
+	int i;
+	char str[16];
+
+	/*
+	 * Length passed should be in multiple of 4 as each FG SRAM word
+	 * holds 4 bytes. To keep this simple, even if a length which is
+	 * not a multiple of 4 bytes or less than 4 bytes is passed, SRAM
+	 * registers dumped will be always in multiple of 4 bytes.
+	 */
+	for (i = 0; i < len; i += 4) {
+		str[0] = '\0';
+		fill_string(str, sizeof(str), buf + i, 4);
+		pr_info("%03d %s\n", addr + (i / 4), str);
+	}
+}
+
+static inline bool fg_sram_address_valid(u16 address, int len)
+{
+	if (address > FG_SRAM_ADDRESS_MAX)
+		return false;
+
+	if ((address + DIV_ROUND_UP(len, 4)) > FG_SRAM_ADDRESS_MAX + 1)
+		return false;
+
+	return true;
+}
+
+#define SOC_UPDATE_WAIT_MS	1500
+int fg_sram_write(struct fg_chip *chip, u16 address, u8 offset,
+			u8 *val, int len, int flags)
+{
+	int rc = 0;
+	bool tried_again = false;
+	bool atomic_access = false;
+
+	if (!chip)
+		return -ENXIO;
+
+	if (chip->battery_missing)
+		return -ENODATA;
+
+	if (!fg_sram_address_valid(address, len))
+		return -EFAULT;
+
+	if (!(flags & FG_IMA_NO_WLOCK))
+		vote(chip->awake_votable, SRAM_WRITE, true, 0);
+	mutex_lock(&chip->sram_rw_lock);
+
+	if ((flags & FG_IMA_ATOMIC) && chip->irqs[SOC_UPDATE_IRQ].irq) {
+		/*
+		 * This interrupt need to be enabled only when it is
+		 * required. It will be kept disabled other times.
+		 */
+		reinit_completion(&chip->soc_update);
+		enable_irq(chip->irqs[SOC_UPDATE_IRQ].irq);
+		atomic_access = true;
+	} else {
+		flags = FG_IMA_DEFAULT;
+	}
+wait:
+	/*
+	 * Atomic access mean waiting upon SOC_UPDATE interrupt from
+	 * FG_ALG and do the transaction after that. This is to make
+	 * sure that there will be no SOC update happening when an
+	 * IMA write is happening. SOC_UPDATE interrupt fires every
+	 * FG cycle (~1.47 seconds).
+	 */
+	if (atomic_access) {
+		/* Wait for SOC_UPDATE completion */
+		rc = wait_for_completion_interruptible_timeout(
+			&chip->soc_update,
+			msecs_to_jiffies(SOC_UPDATE_WAIT_MS));
+
+		/* If we were interrupted wait again one more time. */
+		if (rc == -ERESTARTSYS && !tried_again) {
+			tried_again = true;
+			goto wait;
+		} else if (rc <= 0) {
+			pr_err("wait for soc_update timed out rc=%d\n", rc);
+			goto out;
+		}
+	}
+
+	rc = fg_interleaved_mem_write(chip, address, offset, val, len,
+			atomic_access);
+	if (rc < 0)
+		pr_err("Error in writing SRAM address 0x%x[%d], rc=%d\n",
+			address, offset, rc);
+out:
+	if (atomic_access)
+		disable_irq_nosync(chip->irqs[SOC_UPDATE_IRQ].irq);
+
+	mutex_unlock(&chip->sram_rw_lock);
+	if (!(flags & FG_IMA_NO_WLOCK))
+		vote(chip->awake_votable, SRAM_WRITE, false, 0);
+	return rc;
+}
+
+int fg_sram_read(struct fg_chip *chip, u16 address, u8 offset,
+			u8 *val, int len, int flags)
+{
+	int rc = 0;
+
+	if (!chip)
+		return -ENXIO;
+
+	if (chip->battery_missing)
+		return -ENODATA;
+
+	if (!fg_sram_address_valid(address, len))
+		return -EFAULT;
+
+	if (!(flags & FG_IMA_NO_WLOCK))
+		vote(chip->awake_votable, SRAM_READ, true, 0);
+	mutex_lock(&chip->sram_rw_lock);
+
+	rc = fg_interleaved_mem_read(chip, address, offset, val, len);
+	if (rc < 0)
+		pr_err("Error in reading SRAM address 0x%x[%d], rc=%d\n",
+			address, offset, rc);
+
+	mutex_unlock(&chip->sram_rw_lock);
+	if (!(flags & FG_IMA_NO_WLOCK))
+		vote(chip->awake_votable, SRAM_READ, false, 0);
+	return rc;
+}
+
+int fg_sram_masked_write(struct fg_chip *chip, u16 address, u8 offset,
+			u8 mask, u8 val, int flags)
+{
+	int rc = 0;
+	u8 buf[4];
+
+	rc = fg_sram_read(chip, address, 0, buf, 4, flags);
+	if (rc < 0) {
+		pr_err("sram read failed: address=%03X, rc=%d\n", address, rc);
+		return rc;
+	}
+
+	buf[offset] &= ~mask;
+	buf[offset] |= val & mask;
+
+	rc = fg_sram_write(chip, address, 0, buf, 4, flags);
+	if (rc < 0) {
+		pr_err("sram write failed: address=%03X, rc=%d\n", address, rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int fg_read(struct fg_chip *chip, int addr, u8 *val, int len)
+{
+	int rc, i;
+
+	if (!chip || !chip->regmap)
+		return -ENXIO;
+
+	rc = regmap_bulk_read(chip->regmap, addr, val, len);
+
+	if (rc < 0) {
+		dev_err(chip->dev, "regmap_read failed for address %04x rc=%d\n",
+			addr, rc);
+		return rc;
+	}
+
+	if (*chip->debug_mask & FG_BUS_READ) {
+		pr_info("length %d addr=%04x\n", len, addr);
+		for (i = 0; i < len; i++)
+			pr_info("val[%d]: %02x\n", i, val[i]);
+	}
+
+	return 0;
+}
+
+int fg_write(struct fg_chip *chip, int addr, u8 *val, int len)
+{
+	int rc, i;
+	bool sec_access = false;
+
+	if (!chip || !chip->regmap)
+		return -ENXIO;
+
+	mutex_lock(&chip->bus_lock);
+	sec_access = (addr & 0x00FF) > 0xD0;
+	if (sec_access) {
+		rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+		if (rc < 0) {
+			dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n",
+				addr, rc);
+			goto out;
+		}
+	}
+
+	if (len > 1)
+		rc = regmap_bulk_write(chip->regmap, addr, val, len);
+	else
+		rc = regmap_write(chip->regmap, addr, *val);
+
+	if (rc < 0) {
+		dev_err(chip->dev, "regmap_write failed for address %04x rc=%d\n",
+			addr, rc);
+		goto out;
+	}
+
+	if (*chip->debug_mask & FG_BUS_WRITE) {
+		pr_info("length %d addr=%04x\n", len, addr);
+		for (i = 0; i < len; i++)
+			pr_info("val[%d]: %02x\n", i, val[i]);
+	}
+out:
+	mutex_unlock(&chip->bus_lock);
+	return rc;
+}
+
+int fg_masked_write(struct fg_chip *chip, int addr, u8 mask, u8 val)
+{
+	int rc;
+	bool sec_access = false;
+
+	if (!chip || !chip->regmap)
+		return -ENXIO;
+
+	mutex_lock(&chip->bus_lock);
+	sec_access = (addr & 0x00FF) > 0xD0;
+	if (sec_access) {
+		rc = regmap_write(chip->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+		if (rc < 0) {
+			dev_err(chip->dev, "regmap_write failed for address %x rc=%d\n",
+				addr, rc);
+			goto out;
+		}
+	}
+
+	rc = regmap_update_bits(chip->regmap, addr, mask, val);
+	if (rc < 0) {
+		dev_err(chip->dev, "regmap_update_bits failed for address %04x rc=%d\n",
+			addr, rc);
+		goto out;
+	}
+
+	fg_dbg(chip, FG_BUS_WRITE, "addr=%04x mask: %02x val: %02x\n", addr,
+		mask, val);
+out:
+	mutex_unlock(&chip->bus_lock);
+	return rc;
+}
+
+int64_t twos_compliment_extend(int64_t val, int sign_bit_pos)
+{
+	int i, nbytes = DIV_ROUND_UP(sign_bit_pos, 8);
+	int64_t mask, val_out;
+
+	val_out = val;
+	mask = 1 << sign_bit_pos;
+	if (val & mask) {
+		for (i = 8; i > nbytes; i--) {
+			mask = 0xFFLL << ((i - 1) * 8);
+			val_out |= mask;
+		}
+
+		if ((nbytes * 8) - 1 > sign_bit_pos) {
+			mask = 1 << sign_bit_pos;
+			for (i = 1; i <= (nbytes * 8) - sign_bit_pos; i++)
+				val_out |= mask << i;
+		}
+	}
+
+	pr_debug("nbytes: %d val: %llx val_out: %llx\n", nbytes, val, val_out);
+	return val_out;
+}
+
+/* All the debugfs related functions are defined below */
+static int fg_sram_dfs_open(struct inode *inode, struct file *file)
+{
+	struct fg_log_buffer *log;
+	struct fg_trans *trans;
+	u8 *data_buf;
+
+	size_t logbufsize = SZ_4K;
+	size_t databufsize = SZ_4K;
+
+	if (!dbgfs_data.chip) {
+		pr_err("Not initialized data\n");
+		return -EINVAL;
+	}
+
+	/* Per file "transaction" data */
+	trans = devm_kzalloc(dbgfs_data.chip->dev, sizeof(*trans), GFP_KERNEL);
+	if (!trans)
+		return -ENOMEM;
+
+	/* Allocate log buffer */
+	log = devm_kzalloc(dbgfs_data.chip->dev, logbufsize, GFP_KERNEL);
+	if (!log)
+		return -ENOMEM;
+
+	log->rpos = 0;
+	log->wpos = 0;
+	log->len = logbufsize - sizeof(*log);
+
+	/* Allocate data buffer */
+	data_buf = devm_kzalloc(dbgfs_data.chip->dev, databufsize, GFP_KERNEL);
+	if (!data_buf)
+		return -ENOMEM;
+
+	trans->log = log;
+	trans->data = data_buf;
+	trans->cnt = dbgfs_data.cnt;
+	trans->addr = dbgfs_data.addr;
+	trans->chip = dbgfs_data.chip;
+	trans->offset = trans->addr;
+	mutex_init(&trans->fg_dfs_lock);
+
+	file->private_data = trans;
+	return 0;
+}
+
+static int fg_sram_dfs_close(struct inode *inode, struct file *file)
+{
+	struct fg_trans *trans = file->private_data;
+
+	if (trans && trans->log && trans->data) {
+		file->private_data = NULL;
+		mutex_destroy(&trans->fg_dfs_lock);
+		devm_kfree(trans->chip->dev, trans->log);
+		devm_kfree(trans->chip->dev, trans->data);
+		devm_kfree(trans->chip->dev, trans);
+	}
+
+	return 0;
+}
+
+/**
+ * print_to_log: format a string and place into the log buffer
+ * @log: The log buffer to place the result into.
+ * @fmt: The format string to use.
+ * @...: The arguments for the format string.
+ *
+ * The return value is the number of characters written to @log buffer
+ * not including the trailing '\0'.
+ */
+static int print_to_log(struct fg_log_buffer *log, const char *fmt, ...)
+{
+	va_list args;
+	int cnt;
+	char *buf = &log->data[log->wpos];
+	size_t size = log->len - log->wpos;
+
+	va_start(args, fmt);
+	cnt = vscnprintf(buf, size, fmt, args);
+	va_end(args);
+
+	log->wpos += cnt;
+	return cnt;
+}
+
+/**
+ * write_next_line_to_log: Writes a single "line" of data into the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ * @offset: SRAM address offset to start reading from.
+ * @pcnt: Pointer to 'cnt' variable.  Indicates the number of bytes to read.
+ *
+ * The 'offset' is a 12-bit SRAM address.
+ *
+ * On a successful read, the pcnt is decremented by the number of data
+ * bytes read from the SRAM.  When the cnt reaches 0, all requested bytes have
+ * been read.
+ */
+static int write_next_line_to_log(struct fg_trans *trans, int offset,
+				size_t *pcnt)
+{
+	int i;
+	u8 data[ITEMS_PER_LINE];
+	u16 address;
+	struct fg_log_buffer *log = trans->log;
+	int cnt = 0;
+	int items_to_read = min(ARRAY_SIZE(data), *pcnt);
+	int items_to_log = min(ITEMS_PER_LINE, items_to_read);
+
+	/* Buffer needs enough space for an entire line */
+	if ((log->len - log->wpos) < MAX_LINE_LENGTH)
+		goto done;
+
+	memcpy(data, trans->data + (offset - trans->addr), items_to_read);
+	*pcnt -= items_to_read;
+
+	/* address is in word now and it increments by 1. */
+	address = trans->addr + ((offset - trans->addr) / ITEMS_PER_LINE);
+	cnt = print_to_log(log, "%3.3d ", address & 0xfff);
+	if (cnt == 0)
+		goto done;
+
+	/* Log the data items */
+	for (i = 0; i < items_to_log; ++i) {
+		cnt = print_to_log(log, "%2.2X ", data[i]);
+		if (cnt == 0)
+			goto done;
+	}
+
+	/* If the last character was a space, then replace it with a newline */
+	if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+		log->data[log->wpos - 1] = '\n';
+
+done:
+	return cnt;
+}
+
+/**
+ * get_log_data - reads data from SRAM and saves to the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ *
+ * Returns the number of "items" read or SPMI error code for read failures.
+ */
+static int get_log_data(struct fg_trans *trans)
+{
+	int cnt, rc;
+	int last_cnt;
+	int items_read;
+	int total_items_read = 0;
+	u32 offset = trans->offset;
+	size_t item_cnt = trans->cnt;
+	struct fg_log_buffer *log = trans->log;
+
+	if (item_cnt == 0)
+		return 0;
+
+	if (item_cnt > SZ_4K) {
+		pr_err("Reading too many bytes\n");
+		return -EINVAL;
+	}
+
+	pr_debug("addr: %d offset: %d count: %d\n", trans->addr, trans->offset,
+		trans->cnt);
+	rc = fg_sram_read(trans->chip, trans->addr, 0,
+			trans->data, trans->cnt, 0);
+	if (rc < 0) {
+		pr_err("SRAM read failed: rc = %d\n", rc);
+		return rc;
+	}
+	/* Reset the log buffer 'pointers' */
+	log->wpos = log->rpos = 0;
+
+	/* Keep reading data until the log is full */
+	do {
+		last_cnt = item_cnt;
+		cnt = write_next_line_to_log(trans, offset, &item_cnt);
+		items_read = last_cnt - item_cnt;
+		offset += items_read;
+		total_items_read += items_read;
+	} while (cnt && item_cnt > 0);
+
+	/* Adjust the transaction offset and count */
+	trans->cnt = item_cnt;
+	trans->offset += total_items_read;
+
+	return total_items_read;
+}
+
+/**
+ * fg_sram_dfs_reg_read: reads value(s) from SRAM and fills user's buffer a
+ *  byte array (coded as string)
+ * @file: file pointer
+ * @buf: where to put the result
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user bytes read, or negative error value
+ */
+static ssize_t fg_sram_dfs_reg_read(struct file *file, char __user *buf,
+	size_t count, loff_t *ppos)
+{
+	struct fg_trans *trans = file->private_data;
+	struct fg_log_buffer *log = trans->log;
+	size_t ret;
+	size_t len;
+
+	mutex_lock(&trans->fg_dfs_lock);
+	/* Is the the log buffer empty */
+	if (log->rpos >= log->wpos) {
+		if (get_log_data(trans) <= 0) {
+			len = 0;
+			goto unlock_mutex;
+		}
+	}
+
+	len = min(count, log->wpos - log->rpos);
+
+	ret = copy_to_user(buf, &log->data[log->rpos], len);
+	if (ret == len) {
+		pr_err("error copy sram register values to user\n");
+		len = -EFAULT;
+		goto unlock_mutex;
+	}
+
+	/* 'ret' is the number of bytes not copied */
+	len -= ret;
+
+	*ppos += len;
+	log->rpos += len;
+
+unlock_mutex:
+	mutex_unlock(&trans->fg_dfs_lock);
+	return len;
+}
+
+/**
+ * fg_sram_dfs_reg_write: write user's byte array (coded as string) to SRAM.
+ * @file: file pointer
+ * @buf: user data to be written.
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user byte written, or negative error value
+ */
+static ssize_t fg_sram_dfs_reg_write(struct file *file, const char __user *buf,
+			size_t count, loff_t *ppos)
+{
+	int bytes_read;
+	int data;
+	int pos = 0;
+	int cnt = 0;
+	u8  *values;
+	char *kbuf;
+	size_t ret = 0;
+	struct fg_trans *trans = file->private_data;
+	u32 address = trans->addr;
+
+	mutex_lock(&trans->fg_dfs_lock);
+	/* Make a copy of the user data */
+	kbuf = kmalloc(count + 1, GFP_KERNEL);
+	if (!kbuf) {
+		ret = -ENOMEM;
+		goto unlock_mutex;
+	}
+
+	ret = copy_from_user(kbuf, buf, count);
+	if (ret == count) {
+		pr_err("failed to copy data from user\n");
+		ret = -EFAULT;
+		goto free_buf;
+	}
+
+	count -= ret;
+	*ppos += count;
+	kbuf[count] = '\0';
+
+	/* Override the text buffer with the raw data */
+	values = kbuf;
+
+	/* Parse the data in the buffer.  It should be a string of numbers */
+	while ((pos < count) &&
+		sscanf(kbuf + pos, "%i%n", &data, &bytes_read) == 1) {
+		/*
+		 * We shouldn't be receiving a string of characters that
+		 * exceeds a size of 5 to keep this functionally correct.
+		 * Also, we should make sure that pos never gets overflowed
+		 * beyond the limit.
+		 */
+		if (bytes_read > 5 || bytes_read > INT_MAX - pos) {
+			cnt = 0;
+			ret = -EINVAL;
+			break;
+		}
+		pos += bytes_read;
+		values[cnt++] = data & 0xff;
+	}
+
+	if (!cnt)
+		goto free_buf;
+
+	pr_debug("address %d, count %d\n", address, cnt);
+	/* Perform the write(s) */
+
+	ret = fg_sram_write(trans->chip, address, 0, values, cnt, 0);
+	if (ret) {
+		pr_err("SRAM write failed, err = %zu\n", ret);
+	} else {
+		ret = count;
+		trans->offset += cnt > 4 ? 4 : cnt;
+	}
+
+free_buf:
+	kfree(kbuf);
+unlock_mutex:
+	mutex_unlock(&trans->fg_dfs_lock);
+	return ret;
+}
+
+static const struct file_operations fg_sram_dfs_reg_fops = {
+	.open		= fg_sram_dfs_open,
+	.release	= fg_sram_dfs_close,
+	.read		= fg_sram_dfs_reg_read,
+	.write		= fg_sram_dfs_reg_write,
+};
+
+/*
+ * fg_debugfs_create: adds new fg_sram debugfs entry
+ * @return zero on success
+ */
+static int fg_sram_debugfs_create(struct fg_chip *chip)
+{
+	struct dentry *dfs_sram;
+	struct dentry *file;
+	mode_t dfs_mode = 0600;
+
+	pr_debug("Creating FG_SRAM debugfs file-system\n");
+	dfs_sram = debugfs_create_dir("sram", chip->dfs_root);
+	if (!dfs_sram) {
+		pr_err("error creating fg sram dfs rc=%ld\n",
+		       (long)dfs_sram);
+		return -ENOMEM;
+	}
+
+	dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data);
+	file = debugfs_create_blob("help", 0444, dfs_sram,
+					&dbgfs_data.help_msg);
+	if (!file) {
+		pr_err("error creating help entry\n");
+		goto err_remove_fs;
+	}
+
+	dbgfs_data.chip = chip;
+
+	file = debugfs_create_u32("count", dfs_mode, dfs_sram,
+					&(dbgfs_data.cnt));
+	if (!file) {
+		pr_err("error creating 'count' entry\n");
+		goto err_remove_fs;
+	}
+
+	file = debugfs_create_x32("address", dfs_mode, dfs_sram,
+					&(dbgfs_data.addr));
+	if (!file) {
+		pr_err("error creating 'address' entry\n");
+		goto err_remove_fs;
+	}
+
+	file = debugfs_create_file("data", dfs_mode, dfs_sram, &dbgfs_data,
+					&fg_sram_dfs_reg_fops);
+	if (!file) {
+		pr_err("error creating 'data' entry\n");
+		goto err_remove_fs;
+	}
+
+	return 0;
+
+err_remove_fs:
+	debugfs_remove_recursive(dfs_sram);
+	return -ENOMEM;
+}
+
+static int fg_alg_flags_open(struct inode *inode, struct file *file)
+{
+	file->private_data = inode->i_private;
+	return 0;
+}
+
+static ssize_t fg_alg_flags_read(struct file *file, char __user *userbuf,
+				 size_t count, loff_t *ppos)
+{
+	struct fg_chip *chip = file->private_data;
+	char buf[512];
+	u8 alg_flags = 0;
+	int rc, i, len;
+
+	rc = fg_sram_read(chip, chip->sp[FG_SRAM_ALG_FLAGS].addr_word,
+			  chip->sp[FG_SRAM_ALG_FLAGS].addr_byte, &alg_flags, 1,
+			  FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("failed to read algorithm flags rc=%d\n", rc);
+		return -EFAULT;
+	}
+
+	len = 0;
+	for (i = 0; i < ALG_FLAG_MAX; ++i) {
+		if (len > ARRAY_SIZE(buf) - 1)
+			return -EFAULT;
+		if (chip->alg_flags[i].invalid)
+			continue;
+
+		len += snprintf(buf + len, sizeof(buf) - sizeof(*buf) * len,
+				"%s = %d\n", chip->alg_flags[i].name,
+				(bool)(alg_flags & chip->alg_flags[i].bit));
+	}
+
+	return simple_read_from_buffer(userbuf, count, ppos, buf, len);
+}
+
+static const struct file_operations fg_alg_flags_fops = {
+	.open = fg_alg_flags_open,
+	.read = fg_alg_flags_read,
+};
+
+int fg_debugfs_create(struct fg_chip *chip)
+{
+	int rc;
+
+	pr_debug("Creating debugfs file-system\n");
+	chip->dfs_root = debugfs_create_dir("fg", NULL);
+	if (IS_ERR_OR_NULL(chip->dfs_root)) {
+		if (PTR_ERR(chip->dfs_root) == -ENODEV)
+			pr_err("debugfs is not enabled in the kernel\n");
+		else
+			pr_err("error creating fg dfs root rc=%ld\n",
+			       (long)chip->dfs_root);
+		return -ENODEV;
+	}
+
+	rc = fg_sram_debugfs_create(chip);
+	if (rc < 0) {
+		pr_err("failed to create sram dfs rc=%d\n", rc);
+		goto err_remove_fs;
+	}
+
+	if (!debugfs_create_file("alg_flags", 0400, chip->dfs_root, chip,
+				 &fg_alg_flags_fops)) {
+		pr_err("failed to create alg_flags file\n");
+		goto err_remove_fs;
+	}
+
+	return 0;
+
+err_remove_fs:
+	debugfs_remove_recursive(chip->dfs_root);
+	return -ENOMEM;
+}
diff --git a/drivers/power/supply/qcom/pmic-voter.c b/drivers/power/supply/qcom/pmic-voter.c
new file mode 100644
index 0000000..39a0dcb6
--- /dev/null
+++ b/drivers/power/supply/qcom/pmic-voter.c
@@ -0,0 +1,662 @@
+/* Copyright (c) 2015-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/spinlock.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
+#include <linux/printk.h>
+#include <linux/slab.h>
+#include <linux/string.h>
+
+#include "pmic-voter.h"
+
+#define NUM_MAX_CLIENTS	8
+#define DEBUG_FORCE_CLIENT	"DEBUG_FORCE_CLIENT"
+
+static DEFINE_SPINLOCK(votable_list_slock);
+static LIST_HEAD(votable_list);
+
+static struct dentry *debug_root;
+
+struct client_vote {
+	bool	enabled;
+	int	value;
+};
+
+struct votable {
+	const char		*name;
+	struct list_head	list;
+	struct client_vote	votes[NUM_MAX_CLIENTS];
+	int			num_clients;
+	int			type;
+	int			effective_client_id;
+	int			effective_result;
+	struct mutex		vote_lock;
+	void			*data;
+	int			(*callback)(struct votable *votable,
+						void *data,
+						int effective_result,
+						const char *effective_client);
+	char			*client_strs[NUM_MAX_CLIENTS];
+	bool			voted_on;
+	struct dentry		*root;
+	struct dentry		*status_ent;
+	u32			force_val;
+	struct dentry		*force_val_ent;
+	bool			force_active;
+	struct dentry		*force_active_ent;
+};
+
+/**
+ * vote_set_any()
+ * @votable:	votable object
+ * @client_id:	client number of the latest voter
+ * @eff_res:	sets 0 or 1 based on the voting
+ * @eff_id:	Always returns the client_id argument
+ *
+ * Note that for SET_ANY voter, the value is always same as enabled. There is
+ * no idea of a voter abstaining from the election. Hence there is never a
+ * situation when the effective_id will be invalid, during election.
+ *
+ * Context:
+ *	Must be called with the votable->lock held
+ */
+static void vote_set_any(struct votable *votable, int client_id,
+				int *eff_res, int *eff_id)
+{
+	int i;
+
+	*eff_res = 0;
+
+	for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
+		*eff_res |= votable->votes[i].enabled;
+
+	*eff_id = client_id;
+}
+
+/**
+ * vote_min() -
+ * @votable:	votable object
+ * @client_id:	client number of the latest voter
+ * @eff_res:	sets this to the min. of all the values amongst enabled voters.
+ *		If there is no enabled client, this is set to INT_MAX
+ * @eff_id:	sets this to the client id that has the min value amongst all
+ *		the enabled clients. If there is no enabled client, sets this
+ *		to -EINVAL
+ *
+ * Context:
+ *	Must be called with the votable->lock held
+ */
+static void vote_min(struct votable *votable, int client_id,
+				int *eff_res, int *eff_id)
+{
+	int i;
+
+	*eff_res = INT_MAX;
+	*eff_id = -EINVAL;
+	for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
+		if (votable->votes[i].enabled
+			&& *eff_res > votable->votes[i].value) {
+			*eff_res = votable->votes[i].value;
+			*eff_id = i;
+		}
+	}
+	if (*eff_id == -EINVAL)
+		*eff_res = -EINVAL;
+}
+
+/**
+ * vote_max() -
+ * @votable:	votable object
+ * @client_id:	client number of the latest voter
+ * @eff_res:	sets this to the max. of all the values amongst enabled voters.
+ *		If there is no enabled client, this is set to -EINVAL
+ * @eff_id:	sets this to the client id that has the max value amongst all
+ *		the enabled clients. If there is no enabled client, sets this to
+ *		-EINVAL
+ *
+ * Context:
+ *	Must be called with the votable->lock held
+ */
+static void vote_max(struct votable *votable, int client_id,
+				int *eff_res, int *eff_id)
+{
+	int i;
+
+	*eff_res = INT_MIN;
+	*eff_id = -EINVAL;
+	for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++) {
+		if (votable->votes[i].enabled &&
+				*eff_res < votable->votes[i].value) {
+			*eff_res = votable->votes[i].value;
+			*eff_id = i;
+		}
+	}
+	if (*eff_id == -EINVAL)
+		*eff_res = -EINVAL;
+}
+
+static int get_client_id(struct votable *votable, const char *client_str)
+{
+	int i;
+
+	for (i = 0; i < votable->num_clients; i++) {
+		if (votable->client_strs[i]
+		 && (strcmp(votable->client_strs[i], client_str) == 0))
+			return i;
+	}
+
+	/* new client */
+	for (i = 0; i < votable->num_clients; i++) {
+		if (!votable->client_strs[i]) {
+			votable->client_strs[i]
+				= kstrdup(client_str, GFP_KERNEL);
+			if (!votable->client_strs[i])
+				return -ENOMEM;
+			return i;
+		}
+	}
+	return -EINVAL;
+}
+
+static char *get_client_str(struct votable *votable, int client_id)
+{
+	if (client_id == -EINVAL)
+		return NULL;
+
+	return votable->client_strs[client_id];
+}
+
+void lock_votable(struct votable *votable)
+{
+	mutex_lock(&votable->vote_lock);
+}
+
+void unlock_votable(struct votable *votable)
+{
+	mutex_unlock(&votable->vote_lock);
+}
+
+/**
+ * get_client_vote() -
+ * get_client_vote_locked() -
+ *		The unlocked and locked variants of getting a client's voted
+ *		value.
+ * @votable:	the votable object
+ * @client_str: client of interest
+ *
+ * Returns:
+ *	The value the client voted for. -EINVAL is returned if the client
+ *	is not enabled or the client is not found.
+ */
+int get_client_vote_locked(struct votable *votable, const char *client_str)
+{
+	int client_id = get_client_id(votable, client_str);
+
+	if (client_id < 0)
+		return -EINVAL;
+
+	if ((votable->type != VOTE_SET_ANY)
+		&& !votable->votes[client_id].enabled)
+		return -EINVAL;
+
+	return votable->votes[client_id].value;
+}
+
+int get_client_vote(struct votable *votable, const char *client_str)
+{
+	int value;
+
+	lock_votable(votable);
+	value = get_client_vote_locked(votable, client_str);
+	unlock_votable(votable);
+	return value;
+}
+
+/**
+ * get_effective_result() -
+ * get_effective_result_locked() -
+ *		The unlocked and locked variants of getting the effective value
+ *		amongst all the enabled voters.
+ *
+ * @votable:	the votable object
+ *
+ * Returns:
+ *	The effective result.
+ *	For MIN and MAX votable, returns -EINVAL when the votable
+ *	object has been created but no clients have casted their votes or
+ *	the last enabled client disables its vote.
+ *	For SET_ANY votable it returns 0 when no clients have casted their votes
+ *	because for SET_ANY there is no concept of abstaining from election. The
+ *	votes for all the clients of SET_ANY votable is defaulted to false.
+ */
+int get_effective_result_locked(struct votable *votable)
+{
+	if (votable->force_active)
+		return votable->force_val;
+
+	return votable->effective_result;
+}
+
+int get_effective_result(struct votable *votable)
+{
+	int value;
+
+	lock_votable(votable);
+	value = get_effective_result_locked(votable);
+	unlock_votable(votable);
+	return value;
+}
+
+/**
+ * get_effective_client() -
+ * get_effective_client_locked() -
+ *		The unlocked and locked variants of getting the effective client
+ *		amongst all the enabled voters.
+ *
+ * @votable:	the votable object
+ *
+ * Returns:
+ *	The effective client.
+ *	For MIN and MAX votable, returns NULL when the votable
+ *	object has been created but no clients have casted their votes or
+ *	the last enabled client disables its vote.
+ *	For SET_ANY votable it returns NULL too when no clients have casted
+ *	their votes. But for SET_ANY since there is no concept of abstaining
+ *	from election, the only client that casts a vote or the client that
+ *	caused the result to change is returned.
+ */
+const char *get_effective_client_locked(struct votable *votable)
+{
+	if (votable->force_active)
+		return DEBUG_FORCE_CLIENT;
+
+	return get_client_str(votable, votable->effective_client_id);
+}
+
+const char *get_effective_client(struct votable *votable)
+{
+	const char *client_str;
+
+	lock_votable(votable);
+	client_str = get_effective_client_locked(votable);
+	unlock_votable(votable);
+	return client_str;
+}
+
+/**
+ * vote() -
+ *
+ * @votable:	the votable object
+ * @client_str: the voting client
+ * @enabled:	This provides a means for the client to exclude himself from
+ *		election. This clients val (the next argument) will be
+ *		considered only when he has enabled his participation.
+ *		Note that this takes a differnt meaning for SET_ANY type, as
+ *		there is no concept of abstaining from participation.
+ *		Enabled is treated as the boolean value the client is voting.
+ * @val:	The vote value. This is ignored for SET_ANY votable types.
+ *		For MIN, MAX votable types this value is used as the
+ *		clients vote value when the enabled is true, this value is
+ *		ignored if enabled is false.
+ *
+ * The callback is called only when there is a change in the election results or
+ * if it is the first time someone is voting.
+ *
+ * Returns:
+ *	The return from the callback when present and needs to be called
+ *	or zero.
+ */
+int vote(struct votable *votable, const char *client_str, bool enabled, int val)
+{
+	int effective_id = -EINVAL;
+	int effective_result;
+	int client_id;
+	int rc = 0;
+	bool similar_vote = false;
+
+	lock_votable(votable);
+
+	client_id = get_client_id(votable, client_str);
+	if (client_id < 0) {
+		rc = client_id;
+		goto out;
+	}
+
+	/*
+	 * for SET_ANY the val is to be ignored, set it
+	 * to enabled so that the election still works based on
+	 * value regardless of the type
+	 */
+	if (votable->type == VOTE_SET_ANY)
+		val = enabled;
+
+	if ((votable->votes[client_id].enabled == enabled) &&
+		(votable->votes[client_id].value == val)) {
+		pr_debug("%s: %s,%d same vote %s of val=%d\n",
+				votable->name,
+				client_str, client_id,
+				enabled ? "on" : "off",
+				val);
+		similar_vote = true;
+	}
+
+	votable->votes[client_id].enabled = enabled;
+	votable->votes[client_id].value = val;
+
+	if (similar_vote && votable->voted_on) {
+		pr_debug("%s: %s,%d Ignoring similar vote %s of val=%d\n",
+			votable->name,
+			client_str, client_id, enabled ? "on" : "off", val);
+		goto out;
+	}
+
+	pr_debug("%s: %s,%d voting %s of val=%d\n",
+		votable->name,
+		client_str, client_id, enabled ? "on" : "off", val);
+	switch (votable->type) {
+	case VOTE_MIN:
+		vote_min(votable, client_id, &effective_result, &effective_id);
+		break;
+	case VOTE_MAX:
+		vote_max(votable, client_id, &effective_result, &effective_id);
+		break;
+	case VOTE_SET_ANY:
+		vote_set_any(votable, client_id,
+				&effective_result, &effective_id);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	/*
+	 * Note that the callback is called with a NULL string and -EINVAL
+	 * result when there are no enabled votes
+	 */
+	if (!votable->voted_on
+			|| (effective_result != votable->effective_result)) {
+		votable->effective_client_id = effective_id;
+		votable->effective_result = effective_result;
+		pr_debug("%s: effective vote is now %d voted by %s,%d\n",
+			votable->name, effective_result,
+			get_client_str(votable, effective_id),
+			effective_id);
+		if (votable->callback && !votable->force_active)
+			rc = votable->callback(votable, votable->data,
+					effective_result,
+					get_client_str(votable, effective_id));
+	}
+
+	votable->voted_on = true;
+out:
+	unlock_votable(votable);
+	return rc;
+}
+
+int rerun_election(struct votable *votable)
+{
+	int rc = 0;
+
+	lock_votable(votable);
+	if (votable->callback)
+		rc = votable->callback(votable,
+				votable->data,
+			votable->effective_result,
+			get_client_str(votable, votable->effective_client_id));
+	unlock_votable(votable);
+	return rc;
+}
+
+struct votable *find_votable(const char *name)
+{
+	unsigned long flags;
+	struct votable *v;
+	bool found = false;
+
+	spin_lock_irqsave(&votable_list_slock, flags);
+	if (list_empty(&votable_list))
+		goto out;
+
+	list_for_each_entry(v, &votable_list, list) {
+		if (strcmp(v->name, name) == 0) {
+			found = true;
+			break;
+		}
+	}
+out:
+	spin_unlock_irqrestore(&votable_list_slock, flags);
+
+	if (found)
+		return v;
+	else
+		return NULL;
+}
+
+static int force_active_get(void *data, u64 *val)
+{
+	struct votable *votable = data;
+
+	*val = votable->force_active;
+
+	return 0;
+}
+
+static int force_active_set(void *data, u64 val)
+{
+	struct votable *votable = data;
+	int rc = 0;
+
+	lock_votable(votable);
+	votable->force_active = !!val;
+
+	if (!votable->callback)
+		goto out;
+
+	if (votable->force_active) {
+		rc = votable->callback(votable, votable->data,
+			votable->force_val,
+			DEBUG_FORCE_CLIENT);
+	} else {
+		rc = votable->callback(votable, votable->data,
+			votable->effective_result,
+			get_client_str(votable, votable->effective_client_id));
+	}
+out:
+	unlock_votable(votable);
+	return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(votable_force_ops, force_active_get, force_active_set,
+		"%lld\n");
+
+static int show_votable_clients(struct seq_file *m, void *data)
+{
+	struct votable *votable = m->private;
+	int i;
+	char *type_str = "Unkonwn";
+	const char *effective_client_str;
+
+	lock_votable(votable);
+
+	seq_printf(m, "Votable %s:\n", votable->name);
+	seq_puts(m, "clients:\n");
+	for (i = 0; i < votable->num_clients; i++) {
+		if (votable->client_strs[i]) {
+			seq_printf(m, "%-15s:\t\ten=%d\t\tv=%d\n",
+					votable->client_strs[i],
+					votable->votes[i].enabled,
+					votable->votes[i].value);
+		}
+	}
+
+	switch (votable->type) {
+	case VOTE_MIN:
+		type_str = "Min";
+		break;
+	case VOTE_MAX:
+		type_str = "Max";
+		break;
+	case VOTE_SET_ANY:
+		type_str = "Set_any";
+		break;
+	}
+
+	seq_printf(m, "type: %s\n", type_str);
+	seq_puts(m, "Effective:\n");
+	effective_client_str = get_effective_client_locked(votable);
+	seq_printf(m, "%-15s:\t\tv=%d\n",
+			effective_client_str ? effective_client_str : "none",
+			get_effective_result_locked(votable));
+	unlock_votable(votable);
+
+	return 0;
+}
+
+static int votable_status_open(struct inode *inode, struct file *file)
+{
+	struct votable *votable = inode->i_private;
+
+	return single_open(file, show_votable_clients, votable);
+}
+
+static const struct file_operations votable_status_ops = {
+	.owner		= THIS_MODULE,
+	.open		= votable_status_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+struct votable *create_votable(const char *name,
+				int votable_type,
+				int (*callback)(struct votable *votable,
+					void *data,
+					int effective_result,
+					const char *effective_client),
+				void *data)
+{
+	struct votable *votable;
+	unsigned long flags;
+
+	votable = find_votable(name);
+	if (votable)
+		return ERR_PTR(-EEXIST);
+
+	if (debug_root == NULL) {
+		debug_root = debugfs_create_dir("pmic-votable", NULL);
+		if (!debug_root) {
+			pr_err("Couldn't create debug dir\n");
+			return ERR_PTR(-ENOMEM);
+		}
+	}
+
+	if (votable_type >= NUM_VOTABLE_TYPES) {
+		pr_err("Invalid votable_type specified for voter\n");
+		return ERR_PTR(-EINVAL);
+	}
+
+	votable = kzalloc(sizeof(struct votable), GFP_KERNEL);
+	if (!votable)
+		return ERR_PTR(-ENOMEM);
+
+	votable->name = kstrdup(name, GFP_KERNEL);
+	if (!votable->name) {
+		kfree(votable);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	votable->num_clients = NUM_MAX_CLIENTS;
+	votable->callback = callback;
+	votable->type = votable_type;
+	votable->data = data;
+	mutex_init(&votable->vote_lock);
+
+	/*
+	 * Because effective_result and client states are invalid
+	 * before the first vote, initialize them to -EINVAL
+	 */
+	votable->effective_result = -EINVAL;
+	if (votable->type == VOTE_SET_ANY)
+		votable->effective_result = 0;
+	votable->effective_client_id = -EINVAL;
+
+	spin_lock_irqsave(&votable_list_slock, flags);
+	list_add(&votable->list, &votable_list);
+	spin_unlock_irqrestore(&votable_list_slock, flags);
+
+	votable->root = debugfs_create_dir(name, debug_root);
+	if (!votable->root) {
+		pr_err("Couldn't create debug dir %s\n", name);
+		kfree(votable->name);
+		kfree(votable);
+		return ERR_PTR(-ENOMEM);
+	}
+
+	votable->status_ent = debugfs_create_file("status", S_IFREG | 0444,
+				  votable->root, votable,
+				  &votable_status_ops);
+	if (!votable->status_ent) {
+		pr_err("Couldn't create status dbg file for %s\n", name);
+		debugfs_remove_recursive(votable->root);
+		kfree(votable->name);
+		kfree(votable);
+		return ERR_PTR(-EEXIST);
+	}
+
+	votable->force_val_ent = debugfs_create_u32("force_val",
+					S_IFREG | 0644,
+					votable->root,
+					&(votable->force_val));
+
+	if (!votable->force_val_ent) {
+		pr_err("Couldn't create force_val dbg file for %s\n", name);
+		debugfs_remove_recursive(votable->root);
+		kfree(votable->name);
+		kfree(votable);
+		return ERR_PTR(-EEXIST);
+	}
+
+	votable->force_active_ent = debugfs_create_file("force_active",
+					S_IFREG | 0444,
+					votable->root, votable,
+					&votable_force_ops);
+	if (!votable->force_active_ent) {
+		pr_err("Couldn't create force_active dbg file for %s\n", name);
+		debugfs_remove_recursive(votable->root);
+		kfree(votable->name);
+		kfree(votable);
+		return ERR_PTR(-EEXIST);
+	}
+
+	return votable;
+}
+
+void destroy_votable(struct votable *votable)
+{
+	unsigned long flags;
+	int i;
+
+	if (!votable)
+		return;
+
+	spin_lock_irqsave(&votable_list_slock, flags);
+	list_del(&votable->list);
+	spin_unlock_irqrestore(&votable_list_slock, flags);
+
+	debugfs_remove_recursive(votable->root);
+
+	for (i = 0; i < votable->num_clients && votable->client_strs[i]; i++)
+		kfree(votable->client_strs[i]);
+
+	kfree(votable->name);
+	kfree(votable);
+}
diff --git a/drivers/power/supply/qcom/pmic-voter.h b/drivers/power/supply/qcom/pmic-voter.h
new file mode 100644
index 0000000..031b9a0
--- /dev/null
+++ b/drivers/power/supply/qcom/pmic-voter.h
@@ -0,0 +1,47 @@
+/* Copyright (c) 2015-2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __PMIC_VOTER_H
+#define __PMIC_VOTER_H
+
+#include <linux/mutex.h>
+
+struct votable;
+
+enum votable_type {
+	VOTE_MIN,
+	VOTE_MAX,
+	VOTE_SET_ANY,
+	NUM_VOTABLE_TYPES,
+};
+
+int get_client_vote(struct votable *votable, const char *client_str);
+int get_client_vote_locked(struct votable *votable, const char *client_str);
+int get_effective_result(struct votable *votable);
+int get_effective_result_locked(struct votable *votable);
+const char *get_effective_client(struct votable *votable);
+const char *get_effective_client_locked(struct votable *votable);
+int vote(struct votable *votable, const char *client_str, bool state, int val);
+int rerun_election(struct votable *votable);
+struct votable *find_votable(const char *name);
+struct votable *create_votable(const char *name,
+				int votable_type,
+				int (*callback)(struct votable *votable,
+						void *data,
+						int effective_result,
+						const char *effective_client),
+				void *data);
+void destroy_votable(struct votable *votable);
+void lock_votable(struct votable *votable);
+void unlock_votable(struct votable *votable);
+
+#endif /* __PMIC_VOTER_H */
diff --git a/drivers/power/supply/qcom/qpnp-fg-gen3.c b/drivers/power/supply/qcom/qpnp-fg-gen3.c
new file mode 100644
index 0000000..304d0cf
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-fg-gen3.c
@@ -0,0 +1,4262 @@
+/* Copyright (c) 2016-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt)	"FG: %s: " fmt, __func__
+
+#include <linux/ktime.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_platform.h>
+#include <linux/of_batterydata.h>
+#include <linux/platform_device.h>
+#include <linux/iio/consumer.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "fg-core.h"
+#include "fg-reg.h"
+
+#define FG_GEN3_DEV_NAME	"qcom,fg-gen3"
+
+#define PERPH_SUBTYPE_REG		0x05
+#define FG_BATT_SOC_PMI8998		0x10
+#define FG_BATT_INFO_PMI8998		0x11
+#define FG_MEM_INFO_PMI8998		0x0D
+
+/* SRAM address and offset in ascending order */
+#define SLOPE_LIMIT_WORD		3
+#define SLOPE_LIMIT_OFFSET		0
+#define CUTOFF_VOLT_WORD		5
+#define CUTOFF_VOLT_OFFSET		0
+#define SYS_TERM_CURR_WORD		6
+#define SYS_TERM_CURR_OFFSET		0
+#define VBATT_FULL_WORD			7
+#define VBATT_FULL_OFFSET		0
+#define ESR_FILTER_WORD			8
+#define ESR_UPD_TIGHT_OFFSET		0
+#define ESR_UPD_BROAD_OFFSET		1
+#define ESR_UPD_TIGHT_LOW_TEMP_OFFSET	2
+#define ESR_UPD_BROAD_LOW_TEMP_OFFSET	3
+#define KI_COEFF_MED_DISCHG_WORD	9
+#define KI_COEFF_MED_DISCHG_OFFSET	3
+#define KI_COEFF_HI_DISCHG_WORD		10
+#define KI_COEFF_HI_DISCHG_OFFSET	0
+#define KI_COEFF_LOW_DISCHG_WORD	10
+#define KI_COEFF_LOW_DISCHG_OFFSET	2
+#define DELTA_MSOC_THR_WORD		12
+#define DELTA_MSOC_THR_OFFSET		3
+#define DELTA_BSOC_THR_WORD		13
+#define DELTA_BSOC_THR_OFFSET		2
+#define RECHARGE_SOC_THR_WORD		14
+#define RECHARGE_SOC_THR_OFFSET		0
+#define CHG_TERM_CURR_WORD		14
+#define CHG_TERM_CURR_OFFSET		1
+#define EMPTY_VOLT_WORD			15
+#define EMPTY_VOLT_OFFSET		0
+#define VBATT_LOW_WORD			15
+#define VBATT_LOW_OFFSET		1
+#define ESR_TIMER_DISCHG_MAX_WORD	17
+#define ESR_TIMER_DISCHG_MAX_OFFSET	0
+#define ESR_TIMER_DISCHG_INIT_WORD	17
+#define ESR_TIMER_DISCHG_INIT_OFFSET	2
+#define ESR_TIMER_CHG_MAX_WORD		18
+#define ESR_TIMER_CHG_MAX_OFFSET	0
+#define ESR_TIMER_CHG_INIT_WORD		18
+#define ESR_TIMER_CHG_INIT_OFFSET	2
+#define PROFILE_LOAD_WORD		24
+#define PROFILE_LOAD_OFFSET		0
+#define ESR_RSLOW_DISCHG_WORD		34
+#define ESR_RSLOW_DISCHG_OFFSET		0
+#define ESR_RSLOW_CHG_WORD		51
+#define ESR_RSLOW_CHG_OFFSET		0
+#define NOM_CAP_WORD			58
+#define NOM_CAP_OFFSET			0
+#define ACT_BATT_CAP_BKUP_WORD		74
+#define ACT_BATT_CAP_BKUP_OFFSET	0
+#define CYCLE_COUNT_WORD		75
+#define CYCLE_COUNT_OFFSET		0
+#define PROFILE_INTEGRITY_WORD		79
+#define SW_CONFIG_OFFSET		0
+#define PROFILE_INTEGRITY_OFFSET	3
+#define BATT_SOC_WORD			91
+#define BATT_SOC_OFFSET			0
+#define FULL_SOC_WORD			93
+#define FULL_SOC_OFFSET			2
+#define MONOTONIC_SOC_WORD		94
+#define MONOTONIC_SOC_OFFSET		2
+#define CC_SOC_WORD			95
+#define CC_SOC_OFFSET			0
+#define CC_SOC_SW_WORD			96
+#define CC_SOC_SW_OFFSET		0
+#define VOLTAGE_PRED_WORD		97
+#define VOLTAGE_PRED_OFFSET		0
+#define OCV_WORD			97
+#define OCV_OFFSET			2
+#define ESR_WORD			99
+#define ESR_OFFSET			0
+#define RSLOW_WORD			101
+#define RSLOW_OFFSET			0
+#define ACT_BATT_CAP_WORD		117
+#define ACT_BATT_CAP_OFFSET		0
+#define LAST_BATT_SOC_WORD		119
+#define LAST_BATT_SOC_OFFSET		0
+#define LAST_MONOTONIC_SOC_WORD		119
+#define LAST_MONOTONIC_SOC_OFFSET	2
+#define ALG_FLAGS_WORD			120
+#define ALG_FLAGS_OFFSET		1
+
+/* v2 SRAM address and offset in ascending order */
+#define KI_COEFF_LOW_DISCHG_v2_WORD	9
+#define KI_COEFF_LOW_DISCHG_v2_OFFSET	3
+#define KI_COEFF_MED_DISCHG_v2_WORD	10
+#define KI_COEFF_MED_DISCHG_v2_OFFSET	0
+#define KI_COEFF_HI_DISCHG_v2_WORD	10
+#define KI_COEFF_HI_DISCHG_v2_OFFSET	1
+#define DELTA_BSOC_THR_v2_WORD		12
+#define DELTA_BSOC_THR_v2_OFFSET	3
+#define DELTA_MSOC_THR_v2_WORD		13
+#define DELTA_MSOC_THR_v2_OFFSET	0
+#define RECHARGE_SOC_THR_v2_WORD	14
+#define RECHARGE_SOC_THR_v2_OFFSET	1
+#define CHG_TERM_CURR_v2_WORD		15
+#define CHG_TERM_CURR_v2_OFFSET		1
+#define EMPTY_VOLT_v2_WORD		15
+#define EMPTY_VOLT_v2_OFFSET		3
+#define VBATT_LOW_v2_WORD		16
+#define VBATT_LOW_v2_OFFSET		0
+#define RECHARGE_VBATT_THR_v2_WORD	16
+#define RECHARGE_VBATT_THR_v2_OFFSET	1
+#define FLOAT_VOLT_v2_WORD		16
+#define FLOAT_VOLT_v2_OFFSET		2
+
+static int fg_decode_voltage_15b(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val);
+static int fg_decode_value_16b(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val);
+static int fg_decode_default(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val);
+static int fg_decode_cc_soc(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int value);
+static void fg_encode_voltage(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val_mv, u8 *buf);
+static void fg_encode_current(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val_ma, u8 *buf);
+static void fg_encode_default(struct fg_sram_param *sp,
+	enum fg_sram_param_id id, int val, u8 *buf);
+
+static struct fg_irq_info fg_irqs[FG_IRQ_MAX];
+
+#define PARAM(_id, _addr_word, _addr_byte, _len, _num, _den, _offset,	\
+	      _enc, _dec)						\
+	[FG_SRAM_##_id] = {						\
+		.addr_word	= _addr_word,				\
+		.addr_byte	= _addr_byte,				\
+		.len		= _len,					\
+		.numrtr		= _num,					\
+		.denmtr		= _den,					\
+		.offset		= _offset,				\
+		.encode		= _enc,					\
+		.decode		= _dec,					\
+	}								\
+
+static struct fg_sram_param pmi8998_v1_sram_params[] = {
+	PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000,
+		244141, 0, NULL, fg_decode_voltage_15b),
+	PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL,
+		fg_decode_voltage_15b),
+	PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default,
+		fg_decode_value_16b),
+	PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL,
+		fg_decode_value_16b),
+	PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_cc_soc),
+	PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_cc_soc),
+	PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2,
+		1, 1, 0, NULL, fg_decode_default),
+	/* Entries below here are configurable during initialization */
+	PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000,
+		244141, 0, fg_encode_voltage, NULL),
+	PARAM(EMPTY_VOLT, EMPTY_VOLT_WORD, EMPTY_VOLT_OFFSET, 1, 100000, 390625,
+		-2500, fg_encode_voltage, NULL),
+	PARAM(VBATT_LOW, VBATT_LOW_WORD, VBATT_LOW_OFFSET, 1, 100000, 390625,
+		-2500, fg_encode_voltage, NULL),
+	PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000,
+		244141, 0, fg_encode_voltage, fg_decode_voltage_15b),
+	PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3,
+		1000000, 122070, 0, fg_encode_current, NULL),
+	PARAM(CHG_TERM_CURR, CHG_TERM_CURR_WORD, CHG_TERM_CURR_OFFSET, 1,
+		100000, 390625, 0, fg_encode_current, NULL),
+	PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_WORD, DELTA_MSOC_THR_OFFSET, 1,
+		2048, 100, 0, fg_encode_default, NULL),
+	PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_WORD, DELTA_BSOC_THR_OFFSET, 1,
+		2048, 100, 0, fg_encode_default, NULL),
+	PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_WORD, RECHARGE_SOC_THR_OFFSET,
+		1, 256, 100, 0, fg_encode_default, NULL),
+	PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD,
+		ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default,
+		NULL),
+	PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD,
+		ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default,
+		NULL),
+	PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD,
+		ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+	PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD,
+		ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+	PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_WORD,
+		KI_COEFF_MED_DISCHG_OFFSET, 1, 1000, 244141, 0,
+		fg_encode_default, NULL),
+	PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_WORD,
+		KI_COEFF_HI_DISCHG_OFFSET, 1, 1000, 244141, 0,
+		fg_encode_default, NULL),
+	PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET,
+		1, 512, 1000000, 0, fg_encode_default, NULL),
+	PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET,
+		1, 512, 1000000, 0, fg_encode_default, NULL),
+	PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000,
+		0, fg_encode_default, NULL),
+};
+
+static struct fg_sram_param pmi8998_v2_sram_params[] = {
+	PARAM(BATT_SOC, BATT_SOC_WORD, BATT_SOC_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(FULL_SOC, FULL_SOC_WORD, FULL_SOC_OFFSET, 2, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(VOLTAGE_PRED, VOLTAGE_PRED_WORD, VOLTAGE_PRED_OFFSET, 2, 1000,
+		244141, 0, NULL, fg_decode_voltage_15b),
+	PARAM(OCV, OCV_WORD, OCV_OFFSET, 2, 1000, 244141, 0, NULL,
+		fg_decode_voltage_15b),
+	PARAM(ESR, ESR_WORD, ESR_OFFSET, 2, 1000, 244141, 0, fg_encode_default,
+		fg_decode_value_16b),
+	PARAM(RSLOW, RSLOW_WORD, RSLOW_OFFSET, 2, 1000, 244141, 0, NULL,
+		fg_decode_value_16b),
+	PARAM(ALG_FLAGS, ALG_FLAGS_WORD, ALG_FLAGS_OFFSET, 1, 1, 1, 0, NULL,
+		fg_decode_default),
+	PARAM(CC_SOC, CC_SOC_WORD, CC_SOC_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_cc_soc),
+	PARAM(CC_SOC_SW, CC_SOC_SW_WORD, CC_SOC_SW_OFFSET, 4, 1, 1, 0, NULL,
+		fg_decode_cc_soc),
+	PARAM(ACT_BATT_CAP, ACT_BATT_CAP_BKUP_WORD, ACT_BATT_CAP_BKUP_OFFSET, 2,
+		1, 1, 0, NULL, fg_decode_default),
+	/* Entries below here are configurable during initialization */
+	PARAM(CUTOFF_VOLT, CUTOFF_VOLT_WORD, CUTOFF_VOLT_OFFSET, 2, 1000000,
+		244141, 0, fg_encode_voltage, NULL),
+	PARAM(EMPTY_VOLT, EMPTY_VOLT_v2_WORD, EMPTY_VOLT_v2_OFFSET, 1, 1000,
+		15625, -2000, fg_encode_voltage, NULL),
+	PARAM(VBATT_LOW, VBATT_LOW_v2_WORD, VBATT_LOW_v2_OFFSET, 1, 1000,
+		15625, -2000, fg_encode_voltage, NULL),
+	PARAM(FLOAT_VOLT, FLOAT_VOLT_v2_WORD, FLOAT_VOLT_v2_OFFSET, 1, 1000,
+		15625, -2000, fg_encode_voltage, NULL),
+	PARAM(VBATT_FULL, VBATT_FULL_WORD, VBATT_FULL_OFFSET, 2, 1000,
+		244141, 0, fg_encode_voltage, fg_decode_voltage_15b),
+	PARAM(SYS_TERM_CURR, SYS_TERM_CURR_WORD, SYS_TERM_CURR_OFFSET, 3,
+		1000000, 122070, 0, fg_encode_current, NULL),
+	PARAM(CHG_TERM_CURR, CHG_TERM_CURR_v2_WORD, CHG_TERM_CURR_v2_OFFSET, 1,
+		100000, 390625, 0, fg_encode_current, NULL),
+	PARAM(DELTA_MSOC_THR, DELTA_MSOC_THR_v2_WORD, DELTA_MSOC_THR_v2_OFFSET,
+		1, 2048, 100, 0, fg_encode_default, NULL),
+	PARAM(DELTA_BSOC_THR, DELTA_BSOC_THR_v2_WORD, DELTA_BSOC_THR_v2_OFFSET,
+		1, 2048, 100, 0, fg_encode_default, NULL),
+	PARAM(RECHARGE_SOC_THR, RECHARGE_SOC_THR_v2_WORD,
+		RECHARGE_SOC_THR_v2_OFFSET, 1, 256, 100, 0, fg_encode_default,
+		NULL),
+	PARAM(RECHARGE_VBATT_THR, RECHARGE_VBATT_THR_v2_WORD,
+		RECHARGE_VBATT_THR_v2_OFFSET, 1, 1000, 15625, -2000,
+		fg_encode_voltage, NULL),
+	PARAM(ESR_TIMER_DISCHG_MAX, ESR_TIMER_DISCHG_MAX_WORD,
+		ESR_TIMER_DISCHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default,
+		NULL),
+	PARAM(ESR_TIMER_DISCHG_INIT, ESR_TIMER_DISCHG_INIT_WORD,
+		ESR_TIMER_DISCHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default,
+		NULL),
+	PARAM(ESR_TIMER_CHG_MAX, ESR_TIMER_CHG_MAX_WORD,
+		ESR_TIMER_CHG_MAX_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+	PARAM(ESR_TIMER_CHG_INIT, ESR_TIMER_CHG_INIT_WORD,
+		ESR_TIMER_CHG_INIT_OFFSET, 2, 1, 1, 0, fg_encode_default, NULL),
+	PARAM(KI_COEFF_MED_DISCHG, KI_COEFF_MED_DISCHG_v2_WORD,
+		KI_COEFF_MED_DISCHG_v2_OFFSET, 1, 1000, 244141, 0,
+		fg_encode_default, NULL),
+	PARAM(KI_COEFF_HI_DISCHG, KI_COEFF_HI_DISCHG_v2_WORD,
+		KI_COEFF_HI_DISCHG_v2_OFFSET, 1, 1000, 244141, 0,
+		fg_encode_default, NULL),
+	PARAM(ESR_TIGHT_FILTER, ESR_FILTER_WORD, ESR_UPD_TIGHT_OFFSET,
+		1, 512, 1000000, 0, fg_encode_default, NULL),
+	PARAM(ESR_BROAD_FILTER, ESR_FILTER_WORD, ESR_UPD_BROAD_OFFSET,
+		1, 512, 1000000, 0, fg_encode_default, NULL),
+	PARAM(SLOPE_LIMIT, SLOPE_LIMIT_WORD, SLOPE_LIMIT_OFFSET, 1, 8192, 1000,
+		0, fg_encode_default, NULL),
+};
+
+static struct fg_alg_flag pmi8998_v1_alg_flags[] = {
+	[ALG_FLAG_SOC_LT_OTG_MIN]	= {
+		.name	= "SOC_LT_OTG_MIN",
+		.bit	= BIT(0),
+	},
+	[ALG_FLAG_SOC_LT_RECHARGE]	= {
+		.name	= "SOC_LT_RECHARGE",
+		.bit	= BIT(1),
+	},
+	[ALG_FLAG_IBATT_LT_ITERM]	= {
+		.name	= "IBATT_LT_ITERM",
+		.bit	= BIT(2),
+	},
+	[ALG_FLAG_IBATT_GT_HPM]		= {
+		.name	= "IBATT_GT_HPM",
+		.bit	= BIT(3),
+	},
+	[ALG_FLAG_IBATT_GT_UPM]		= {
+		.name	= "IBATT_GT_UPM",
+		.bit	= BIT(4),
+	},
+	[ALG_FLAG_VBATT_LT_RECHARGE]	= {
+		.name	= "VBATT_LT_RECHARGE",
+		.bit	= BIT(5),
+	},
+	[ALG_FLAG_VBATT_GT_VFLOAT]	= {
+		.invalid = true,
+	},
+};
+
+static struct fg_alg_flag pmi8998_v2_alg_flags[] = {
+	[ALG_FLAG_SOC_LT_OTG_MIN]	= {
+		.name	= "SOC_LT_OTG_MIN",
+		.bit	= BIT(0),
+	},
+	[ALG_FLAG_SOC_LT_RECHARGE]	= {
+		.name	= "SOC_LT_RECHARGE",
+		.bit	= BIT(1),
+	},
+	[ALG_FLAG_IBATT_LT_ITERM]	= {
+		.name	= "IBATT_LT_ITERM",
+		.bit	= BIT(2),
+	},
+	[ALG_FLAG_IBATT_GT_HPM]		= {
+		.name	= "IBATT_GT_HPM",
+		.bit	= BIT(4),
+	},
+	[ALG_FLAG_IBATT_GT_UPM]		= {
+		.name	= "IBATT_GT_UPM",
+		.bit	= BIT(5),
+	},
+	[ALG_FLAG_VBATT_LT_RECHARGE]	= {
+		.name	= "VBATT_LT_RECHARGE",
+		.bit	= BIT(6),
+	},
+	[ALG_FLAG_VBATT_GT_VFLOAT]	= {
+		.name	= "VBATT_GT_VFLOAT",
+		.bit	= BIT(7),
+	},
+};
+
+static int fg_gen3_debug_mask;
+module_param_named(
+	debug_mask, fg_gen3_debug_mask, int, 0600
+);
+
+static bool fg_profile_dump;
+module_param_named(
+	profile_dump, fg_profile_dump, bool, 0600
+);
+
+static int fg_sram_dump_period_ms = 20000;
+module_param_named(
+	sram_dump_period_ms, fg_sram_dump_period_ms, int, 0600
+);
+
+static int fg_restart;
+static bool fg_sram_dump;
+
+/* All getters HERE */
+
+#define VOLTAGE_15BIT_MASK	GENMASK(14, 0)
+static int fg_decode_voltage_15b(struct fg_sram_param *sp,
+				enum fg_sram_param_id id, int value)
+{
+	value &= VOLTAGE_15BIT_MASK;
+	sp[id].value = div_u64((u64)value * sp[id].denmtr, sp[id].numrtr);
+	pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+		sp[id].value);
+	return sp[id].value;
+}
+
+static int fg_decode_cc_soc(struct fg_sram_param *sp,
+				enum fg_sram_param_id id, int value)
+{
+	sp[id].value = div_s64((s64)value * sp[id].denmtr, sp[id].numrtr);
+	sp[id].value = sign_extend32(sp[id].value, 31);
+	pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+		sp[id].value);
+	return sp[id].value;
+}
+
+static int fg_decode_value_16b(struct fg_sram_param *sp,
+				enum fg_sram_param_id id, int value)
+{
+	sp[id].value = div_u64((u64)(u16)value * sp[id].denmtr, sp[id].numrtr);
+	pr_debug("id: %d raw value: %x decoded value: %x\n", id, value,
+		sp[id].value);
+	return sp[id].value;
+}
+
+static int fg_decode_default(struct fg_sram_param *sp, enum fg_sram_param_id id,
+				int value)
+{
+	sp[id].value = value;
+	return sp[id].value;
+}
+
+static int fg_decode(struct fg_sram_param *sp, enum fg_sram_param_id id,
+			int value)
+{
+	if (!sp[id].decode) {
+		pr_err("No decoding function for parameter %d\n", id);
+		return -EINVAL;
+	}
+
+	return sp[id].decode(sp, id, value);
+}
+
+static void fg_encode_voltage(struct fg_sram_param *sp,
+				enum fg_sram_param_id  id, int val_mv, u8 *buf)
+{
+	int i, mask = 0xff;
+	int64_t temp;
+
+	val_mv += sp[id].offset;
+	temp = (int64_t)div_u64((u64)val_mv * sp[id].numrtr, sp[id].denmtr);
+	pr_debug("temp: %llx id: %d, val_mv: %d, buf: [ ", temp, id, val_mv);
+	for (i = 0; i < sp[id].len; i++) {
+		buf[i] = temp & mask;
+		temp >>= 8;
+		pr_debug("%x ", buf[i]);
+	}
+	pr_debug("]\n");
+}
+
+static void fg_encode_current(struct fg_sram_param *sp,
+				enum fg_sram_param_id  id, int val_ma, u8 *buf)
+{
+	int i, mask = 0xff;
+	int64_t temp;
+	s64 current_ma;
+
+	current_ma = val_ma;
+	temp = (int64_t)div_s64(current_ma * sp[id].numrtr, sp[id].denmtr);
+	pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val_ma);
+	for (i = 0; i < sp[id].len; i++) {
+		buf[i] = temp & mask;
+		temp >>= 8;
+		pr_debug("%x ", buf[i]);
+	}
+	pr_debug("]\n");
+}
+
+static void fg_encode_default(struct fg_sram_param *sp,
+				enum fg_sram_param_id  id, int val, u8 *buf)
+{
+	int i, mask = 0xff;
+	int64_t temp;
+
+	temp = DIV_ROUND_CLOSEST(val * sp[id].numrtr, sp[id].denmtr);
+	pr_debug("temp: %llx id: %d, val: %d, buf: [ ", temp, id, val);
+	for (i = 0; i < sp[id].len; i++) {
+		buf[i] = temp & mask;
+		temp >>= 8;
+		pr_debug("%x ", buf[i]);
+	}
+	pr_debug("]\n");
+}
+
+static void fg_encode(struct fg_sram_param *sp, enum fg_sram_param_id id,
+			int val, u8 *buf)
+{
+	if (!sp[id].encode) {
+		pr_err("No encoding function for parameter %d\n", id);
+		return;
+	}
+
+	sp[id].encode(sp, id, val, buf);
+}
+
+/*
+ * Please make sure *_sram_params table has the entry for the parameter
+ * obtained through this function. In addition to address, offset,
+ * length from where this SRAM parameter is read, a decode function
+ * need to be specified.
+ */
+static int fg_get_sram_prop(struct fg_chip *chip, enum fg_sram_param_id id,
+				int *val)
+{
+	int temp, rc, i;
+	u8 buf[4];
+
+	if (id < 0 || id > FG_SRAM_MAX || chip->sp[id].len > sizeof(buf))
+		return -EINVAL;
+
+	if (chip->battery_missing)
+		return -ENODATA;
+
+	rc = fg_sram_read(chip, chip->sp[id].addr_word, chip->sp[id].addr_byte,
+		buf, chip->sp[id].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error reading address 0x%04x[%d] rc=%d\n",
+			chip->sp[id].addr_word, chip->sp[id].addr_byte, rc);
+		return rc;
+	}
+
+	for (i = 0, temp = 0; i < chip->sp[id].len; i++)
+		temp |= buf[i] << (8 * i);
+
+	*val = fg_decode(chip->sp, id, temp);
+	return 0;
+}
+
+#define CC_SOC_30BIT	GENMASK(29, 0)
+static int fg_get_cc_soc(struct fg_chip *chip, int *val)
+{
+	int rc, cc_soc;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC, &cc_soc);
+	if (rc < 0) {
+		pr_err("Error in getting CC_SOC, rc=%d\n", rc);
+		return rc;
+	}
+
+	*val = div_s64(cc_soc * chip->cl.nom_cap_uah, CC_SOC_30BIT);
+	return 0;
+}
+
+static int fg_get_cc_soc_sw(struct fg_chip *chip, int *val)
+{
+	int rc, cc_soc;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc);
+	if (rc < 0) {
+		pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
+		return rc;
+	}
+
+	*val = div_s64(cc_soc * chip->cl.learned_cc_uah, CC_SOC_30BIT);
+	return 0;
+}
+
+#define BATT_TEMP_NUMR		1
+#define BATT_TEMP_DENR		1
+static int fg_get_battery_temp(struct fg_chip *chip, int *val)
+{
+	int rc = 0, temp;
+	u8 buf[2];
+
+	rc = fg_read(chip, BATT_INFO_BATT_TEMP_LSB(chip), buf, 2);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			BATT_INFO_BATT_TEMP_LSB(chip), rc);
+		return rc;
+	}
+
+	temp = ((buf[1] & BATT_TEMP_MSB_MASK) << 8) |
+		(buf[0] & BATT_TEMP_LSB_MASK);
+	temp = DIV_ROUND_CLOSEST(temp, 4);
+
+	/* Value is in Kelvin; Convert it to deciDegC */
+	temp = (temp - 273) * 10;
+	*val = temp;
+	return 0;
+}
+
+static int fg_get_battery_resistance(struct fg_chip *chip, int *val)
+{
+	int rc, esr_uohms, rslow_uohms;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+	if (rc < 0) {
+		pr_err("failed to get ESR, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_RSLOW, &rslow_uohms);
+	if (rc < 0) {
+		pr_err("failed to get Rslow, rc=%d\n", rc);
+		return rc;
+	}
+
+	*val = esr_uohms + rslow_uohms;
+	return 0;
+}
+
+#define BATT_CURRENT_NUMR	488281
+#define BATT_CURRENT_DENR	1000
+static int fg_get_battery_current(struct fg_chip *chip, int *val)
+{
+	int rc = 0;
+	int64_t temp = 0;
+	u8 buf[2];
+
+	rc = fg_read(chip, BATT_INFO_IBATT_LSB(chip), buf, 2);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			BATT_INFO_IBATT_LSB(chip), rc);
+		return rc;
+	}
+
+	if (chip->wa_flags & PMI8998_V1_REV_WA)
+		temp = buf[0] << 8 | buf[1];
+	else
+		temp = buf[1] << 8 | buf[0];
+
+	pr_debug("buf: %x %x temp: %llx\n", buf[0], buf[1], temp);
+	/* Sign bit is bit 15 */
+	temp = twos_compliment_extend(temp, 15);
+	*val = div_s64((s64)temp * BATT_CURRENT_NUMR, BATT_CURRENT_DENR);
+	return 0;
+}
+
+#define BATT_VOLTAGE_NUMR	122070
+#define BATT_VOLTAGE_DENR	1000
+static int fg_get_battery_voltage(struct fg_chip *chip, int *val)
+{
+	int rc = 0;
+	u16 temp = 0;
+	u8 buf[2];
+
+	rc = fg_read(chip, BATT_INFO_VBATT_LSB(chip), buf, 2);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			BATT_INFO_VBATT_LSB(chip), rc);
+		return rc;
+	}
+
+	if (chip->wa_flags & PMI8998_V1_REV_WA)
+		temp = buf[0] << 8 | buf[1];
+	else
+		temp = buf[1] << 8 | buf[0];
+
+	pr_debug("buf: %x %x temp: %x\n", buf[0], buf[1], temp);
+	*val = div_u64((u64)temp * BATT_VOLTAGE_NUMR, BATT_VOLTAGE_DENR);
+	return 0;
+}
+
+#define MAX_TRIES_SOC		5
+static int fg_get_msoc_raw(struct fg_chip *chip, int *val)
+{
+	u8 cap[2];
+	int rc, tries = 0;
+
+	while (tries < MAX_TRIES_SOC) {
+		rc = fg_read(chip, BATT_SOC_FG_MONOTONIC_SOC(chip), cap, 2);
+		if (rc < 0) {
+			pr_err("failed to read addr=0x%04x, rc=%d\n",
+				BATT_SOC_FG_MONOTONIC_SOC(chip), rc);
+			return rc;
+		}
+
+		if (cap[0] == cap[1])
+			break;
+
+		tries++;
+	}
+
+	if (tries == MAX_TRIES_SOC) {
+		pr_err("shadow registers do not match\n");
+		return -EINVAL;
+	}
+
+	fg_dbg(chip, FG_POWER_SUPPLY, "raw: 0x%02x\n", cap[0]);
+	*val = cap[0];
+	return 0;
+}
+
+#define FULL_CAPACITY	100
+#define FULL_SOC_RAW	255
+static int fg_get_msoc(struct fg_chip *chip, int *msoc)
+{
+	int rc;
+
+	rc = fg_get_msoc_raw(chip, msoc);
+	if (rc < 0)
+		return rc;
+
+	*msoc = DIV_ROUND_CLOSEST(*msoc * FULL_CAPACITY, FULL_SOC_RAW);
+	return 0;
+}
+
+static bool is_batt_empty(struct fg_chip *chip)
+{
+	u8 status;
+	int rc, vbatt_uv, msoc;
+
+	rc = fg_read(chip, BATT_SOC_INT_RT_STS(chip), &status, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			BATT_SOC_INT_RT_STS(chip), rc);
+		return false;
+	}
+
+	if (!(status & MSOC_EMPTY_BIT))
+		return false;
+
+	rc = fg_get_battery_voltage(chip, &vbatt_uv);
+	if (rc < 0) {
+		pr_err("failed to get battery voltage, rc=%d\n", rc);
+		return false;
+	}
+
+	rc = fg_get_msoc(chip, &msoc);
+	if (!rc)
+		pr_warn("batt_soc_rt_sts: %x vbatt: %d uV msoc:%d\n", status,
+			vbatt_uv, msoc);
+
+	return ((vbatt_uv < chip->dt.cutoff_volt_mv * 1000) ? true : false);
+}
+
+static int fg_get_debug_batt_id(struct fg_chip *chip, int *batt_id)
+{
+	int rc;
+	u64 temp;
+	u8 buf[2];
+
+	rc = fg_read(chip, ADC_RR_FAKE_BATT_LOW_LSB(chip), buf, 2);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			ADC_RR_FAKE_BATT_LOW_LSB(chip), rc);
+		return rc;
+	}
+
+	/*
+	 * Fake battery threshold is encoded in the following format.
+	 * Threshold (code) = (battery_id in Ohms) * 0.00015 * 2^10 / 2.5
+	 */
+	temp = (buf[1] << 8 | buf[0]) * 2500000;
+	do_div(temp, 150 * 1024);
+	batt_id[0] = temp;
+	rc = fg_read(chip, ADC_RR_FAKE_BATT_HIGH_LSB(chip), buf, 2);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			ADC_RR_FAKE_BATT_HIGH_LSB(chip), rc);
+		return rc;
+	}
+
+	temp = (buf[1] << 8 | buf[0]) * 2500000;
+	do_div(temp, 150 * 1024);
+	batt_id[1] = temp;
+	pr_debug("debug batt_id range: [%d %d]\n", batt_id[0], batt_id[1]);
+	return 0;
+}
+
+static bool is_debug_batt_id(struct fg_chip *chip)
+{
+	int debug_batt_id[2], rc;
+
+	if (!chip->batt_id_ohms)
+		return false;
+
+	rc = fg_get_debug_batt_id(chip, debug_batt_id);
+	if (rc < 0) {
+		pr_err("Failed to get debug batt_id, rc=%d\n", rc);
+		return false;
+	}
+
+	if (is_between(debug_batt_id[0], debug_batt_id[1],
+		chip->batt_id_ohms)) {
+		fg_dbg(chip, FG_POWER_SUPPLY, "Debug battery id: %dohms\n",
+			chip->batt_id_ohms);
+		return true;
+	}
+
+	return false;
+}
+
+#define DEBUG_BATT_SOC	67
+#define BATT_MISS_SOC	50
+#define EMPTY_SOC	0
+static int fg_get_prop_capacity(struct fg_chip *chip, int *val)
+{
+	int rc, msoc;
+
+	if (is_debug_batt_id(chip)) {
+		*val = DEBUG_BATT_SOC;
+		return 0;
+	}
+
+	if (chip->fg_restarting) {
+		*val = chip->last_soc;
+		return 0;
+	}
+
+	if (chip->battery_missing) {
+		*val = BATT_MISS_SOC;
+		return 0;
+	}
+
+	if (is_batt_empty(chip)) {
+		*val = EMPTY_SOC;
+		return 0;
+	}
+
+	if (chip->charge_full) {
+		*val = FULL_CAPACITY;
+		return 0;
+	}
+
+	rc = fg_get_msoc(chip, &msoc);
+	if (rc < 0)
+		return rc;
+
+	if (chip->delta_soc > 0)
+		*val = chip->maint_soc;
+	else
+		*val = msoc;
+	return 0;
+}
+
+#define DEFAULT_BATT_TYPE	"Unknown Battery"
+#define MISSING_BATT_TYPE	"Missing Battery"
+#define LOADING_BATT_TYPE	"Loading Battery"
+static const char *fg_get_battery_type(struct fg_chip *chip)
+{
+	if (chip->battery_missing)
+		return MISSING_BATT_TYPE;
+
+	if (chip->bp.batt_type_str) {
+		if (chip->profile_loaded)
+			return chip->bp.batt_type_str;
+		else if (chip->profile_available)
+			return LOADING_BATT_TYPE;
+	}
+
+	return DEFAULT_BATT_TYPE;
+}
+
+static int fg_batt_missing_config(struct fg_chip *chip, bool enable)
+{
+	int rc;
+
+	rc = fg_masked_write(chip, BATT_INFO_BATT_MISS_CFG(chip),
+			BM_FROM_BATT_ID_BIT, enable ? BM_FROM_BATT_ID_BIT : 0);
+	if (rc < 0)
+		pr_err("Error in writing to %04x, rc=%d\n",
+			BATT_INFO_BATT_MISS_CFG(chip), rc);
+	return rc;
+}
+
+static int fg_get_batt_id(struct fg_chip *chip)
+{
+	int rc, ret, batt_id = 0;
+
+	if (!chip->batt_id_chan)
+		return -EINVAL;
+
+	rc = fg_batt_missing_config(chip, false);
+	if (rc < 0) {
+		pr_err("Error in disabling BMD, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = iio_read_channel_processed(chip->batt_id_chan, &batt_id);
+	if (rc < 0) {
+		pr_err("Error in reading batt_id channel, rc:%d\n", rc);
+		goto out;
+	}
+
+	/* Wait for 200ms before enabling BMD again */
+	msleep(200);
+
+	fg_dbg(chip, FG_STATUS, "batt_id: %d\n", batt_id);
+	chip->batt_id_ohms = batt_id;
+out:
+	ret = fg_batt_missing_config(chip, true);
+	if (ret < 0) {
+		pr_err("Error in enabling BMD, ret=%d\n", ret);
+		return ret;
+	}
+
+	return rc;
+}
+
+static int fg_get_batt_profile(struct fg_chip *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	struct device_node *batt_node, *profile_node;
+	const char *data;
+	int rc, len;
+
+	batt_node = of_find_node_by_name(node, "qcom,battery-data");
+	if (!batt_node) {
+		pr_err("Batterydata not available\n");
+		return -ENXIO;
+	}
+
+	profile_node = of_batterydata_get_best_profile(batt_node,
+				chip->batt_id_ohms / 1000, NULL);
+	if (IS_ERR(profile_node))
+		return PTR_ERR(profile_node);
+
+	if (!profile_node) {
+		pr_err("couldn't find profile handle\n");
+		return -ENODATA;
+	}
+
+	rc = of_property_read_string(profile_node, "qcom,battery-type",
+			&chip->bp.batt_type_str);
+	if (rc < 0) {
+		pr_err("battery type unavailable, rc:%d\n", rc);
+		return rc;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
+			&chip->bp.float_volt_uv);
+	if (rc < 0) {
+		pr_err("battery float voltage unavailable, rc:%d\n", rc);
+		chip->bp.float_volt_uv = -EINVAL;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,fastchg-current-ma",
+			&chip->bp.fastchg_curr_ma);
+	if (rc < 0) {
+		pr_err("battery fastchg current unavailable, rc:%d\n", rc);
+		chip->bp.fastchg_curr_ma = -EINVAL;
+	}
+
+	rc = of_property_read_u32(profile_node, "qcom,fg-cc-cv-threshold-mv",
+			&chip->bp.vbatt_full_mv);
+	if (rc < 0) {
+		pr_err("battery cc_cv threshold unavailable, rc:%d\n", rc);
+		chip->bp.vbatt_full_mv = -EINVAL;
+	}
+
+	data = of_get_property(profile_node, "qcom,fg-profile-data", &len);
+	if (!data) {
+		pr_err("No profile data available\n");
+		return -ENODATA;
+	}
+
+	if (len != PROFILE_LEN) {
+		pr_err("battery profile incorrect size: %d\n", len);
+		return -EINVAL;
+	}
+
+	chip->profile_available = true;
+	memcpy(chip->batt_profile, data, len);
+
+	return 0;
+}
+
+static inline void get_temp_setpoint(int threshold, u8 *val)
+{
+	/* Resolution is 0.5C. Base is -30C. */
+	*val = DIV_ROUND_CLOSEST((threshold + 30) * 10, 5);
+}
+
+static inline void get_batt_temp_delta(int delta, u8 *val)
+{
+	switch (delta) {
+	case 2:
+		*val = BTEMP_DELTA_2K;
+		break;
+	case 4:
+		*val = BTEMP_DELTA_4K;
+		break;
+	case 6:
+		*val = BTEMP_DELTA_6K;
+		break;
+	case 10:
+		*val = BTEMP_DELTA_10K;
+		break;
+	default:
+		*val = BTEMP_DELTA_2K;
+		break;
+	};
+}
+
+static int fg_set_esr_timer(struct fg_chip *chip, int cycles, bool charging,
+				int flags)
+{
+	u8 buf[2];
+	int rc, timer_max, timer_init;
+
+	if (charging) {
+		timer_max = FG_SRAM_ESR_TIMER_CHG_MAX;
+		timer_init = FG_SRAM_ESR_TIMER_CHG_INIT;
+	} else {
+		timer_max = FG_SRAM_ESR_TIMER_DISCHG_MAX;
+		timer_init = FG_SRAM_ESR_TIMER_DISCHG_INIT;
+	}
+
+	fg_encode(chip->sp, timer_max, cycles, buf);
+	rc = fg_sram_write(chip,
+			chip->sp[timer_max].addr_word,
+			chip->sp[timer_max].addr_byte, buf,
+			chip->sp[timer_max].len, flags);
+	if (rc < 0) {
+		pr_err("Error in writing esr_timer_dischg_max, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	fg_encode(chip->sp, timer_init, cycles, buf);
+	rc = fg_sram_write(chip,
+			chip->sp[timer_init].addr_word,
+			chip->sp[timer_init].addr_byte, buf,
+			chip->sp[timer_init].len, flags);
+	if (rc < 0) {
+		pr_err("Error in writing esr_timer_dischg_init, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+/* Other functions HERE */
+
+static void fg_notify_charger(struct fg_chip *chip)
+{
+	union power_supply_propval prop = {0, };
+	int rc;
+
+	if (!chip->batt_psy)
+		return;
+
+	if (!chip->profile_available)
+		return;
+
+	prop.intval = chip->bp.float_volt_uv;
+	rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
+	if (rc < 0) {
+		pr_err("Error in setting voltage_max property on batt_psy, rc=%d\n",
+			rc);
+		return;
+	}
+
+	prop.intval = chip->bp.fastchg_curr_ma * 1000;
+	rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &prop);
+	if (rc < 0) {
+		pr_err("Error in setting constant_charge_current_max property on batt_psy, rc=%d\n",
+			rc);
+		return;
+	}
+
+	fg_dbg(chip, FG_STATUS, "Notified charger on float voltage and FCC\n");
+}
+
+static int fg_awake_cb(struct votable *votable, void *data, int awake,
+			const char *client)
+{
+	struct fg_chip *chip = data;
+
+	if (awake)
+		pm_stay_awake(chip->dev);
+	else
+		pm_relax(chip->dev);
+
+	pr_debug("client: %s awake: %d\n", client, awake);
+	return 0;
+}
+
+static bool batt_psy_initialized(struct fg_chip *chip)
+{
+	if (chip->batt_psy)
+		return true;
+
+	chip->batt_psy = power_supply_get_by_name("battery");
+	if (!chip->batt_psy)
+		return false;
+
+	/* batt_psy is initialized, set the fcc and fv */
+	fg_notify_charger(chip);
+
+	return true;
+}
+
+static bool is_parallel_charger_available(struct fg_chip *chip)
+{
+	if (!chip->parallel_psy)
+		chip->parallel_psy = power_supply_get_by_name("parallel");
+
+	if (!chip->parallel_psy)
+		return false;
+
+	return true;
+}
+
+static int fg_save_learned_cap_to_sram(struct fg_chip *chip)
+{
+	int16_t cc_mah;
+	int rc;
+
+	if (chip->battery_missing || !chip->cl.learned_cc_uah)
+		return -EPERM;
+
+	cc_mah = div64_s64(chip->cl.learned_cc_uah, 1000);
+	/* Write to a backup register to use across reboot */
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ACT_BATT_CAP].addr_word,
+			chip->sp[FG_SRAM_ACT_BATT_CAP].addr_byte, (u8 *)&cc_mah,
+			chip->sp[FG_SRAM_ACT_BATT_CAP].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing act_batt_cap_bkup, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Write to actual capacity register for coulomb counter operation */
+	rc = fg_sram_write(chip, ACT_BATT_CAP_WORD, ACT_BATT_CAP_OFFSET,
+			(u8 *)&cc_mah, chip->sp[FG_SRAM_ACT_BATT_CAP].len,
+			FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing act_batt_cap, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_dbg(chip, FG_CAP_LEARN, "learned capacity %llduah/%dmah stored\n",
+		chip->cl.learned_cc_uah, cc_mah);
+	return 0;
+}
+
+#define CAPACITY_DELTA_DECIPCT	500
+static int fg_load_learned_cap_from_sram(struct fg_chip *chip)
+{
+	int rc, act_cap_mah;
+	int64_t delta_cc_uah, pct_nom_cap_uah;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_mah);
+	if (rc < 0) {
+		pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+		return rc;
+	}
+
+	chip->cl.learned_cc_uah = act_cap_mah * 1000;
+
+	if (chip->cl.learned_cc_uah != chip->cl.nom_cap_uah) {
+		if (chip->cl.learned_cc_uah == 0)
+			chip->cl.learned_cc_uah = chip->cl.nom_cap_uah;
+
+		delta_cc_uah = abs(chip->cl.learned_cc_uah -
+					chip->cl.nom_cap_uah);
+		pct_nom_cap_uah = div64_s64((int64_t)chip->cl.nom_cap_uah *
+				CAPACITY_DELTA_DECIPCT, 1000);
+		/*
+		 * If the learned capacity is out of range by 50% from the
+		 * nominal capacity, then overwrite the learned capacity with
+		 * the nominal capacity.
+		 */
+		if (chip->cl.nom_cap_uah && delta_cc_uah > pct_nom_cap_uah) {
+			fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah: %lld is higher than expected, capping it to nominal: %lld\n",
+				chip->cl.learned_cc_uah, chip->cl.nom_cap_uah);
+			chip->cl.learned_cc_uah = chip->cl.nom_cap_uah;
+		}
+
+		rc = fg_save_learned_cap_to_sram(chip);
+		if (rc < 0)
+			pr_err("Error in saving learned_cc_uah, rc=%d\n", rc);
+	}
+
+	fg_dbg(chip, FG_CAP_LEARN, "learned_cc_uah:%lld nom_cap_uah: %lld\n",
+		chip->cl.learned_cc_uah, chip->cl.nom_cap_uah);
+	return 0;
+}
+
+static bool is_temp_valid_cap_learning(struct fg_chip *chip)
+{
+	int rc, batt_temp;
+
+	rc = fg_get_battery_temp(chip, &batt_temp);
+	if (rc < 0) {
+		pr_err("Error in getting batt_temp\n");
+		return false;
+	}
+
+	if (batt_temp > chip->dt.cl_max_temp ||
+		batt_temp < chip->dt.cl_min_temp) {
+		fg_dbg(chip, FG_CAP_LEARN, "batt temp %d out of range [%d %d]\n",
+			batt_temp, chip->dt.cl_min_temp, chip->dt.cl_max_temp);
+		return false;
+	}
+
+	return true;
+}
+
+static void fg_cap_learning_post_process(struct fg_chip *chip)
+{
+	int64_t max_inc_val, min_dec_val, old_cap;
+	int rc;
+
+	max_inc_val = chip->cl.learned_cc_uah
+			* (1000 + chip->dt.cl_max_cap_inc);
+	do_div(max_inc_val, 1000);
+
+	min_dec_val = chip->cl.learned_cc_uah
+			* (1000 - chip->dt.cl_max_cap_dec);
+	do_div(min_dec_val, 1000);
+
+	old_cap = chip->cl.learned_cc_uah;
+	if (chip->cl.final_cc_uah > max_inc_val)
+		chip->cl.learned_cc_uah = max_inc_val;
+	else if (chip->cl.final_cc_uah < min_dec_val)
+		chip->cl.learned_cc_uah = min_dec_val;
+	else
+		chip->cl.learned_cc_uah =
+			chip->cl.final_cc_uah;
+
+	if (chip->dt.cl_max_cap_limit) {
+		max_inc_val = (int64_t)chip->cl.nom_cap_uah * (1000 +
+				chip->dt.cl_max_cap_limit);
+		do_div(max_inc_val, 1000);
+		if (chip->cl.final_cc_uah > max_inc_val) {
+			fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes above max limit %lld\n",
+				chip->cl.final_cc_uah, max_inc_val);
+			chip->cl.learned_cc_uah = max_inc_val;
+		}
+	}
+
+	if (chip->dt.cl_min_cap_limit) {
+		min_dec_val = (int64_t)chip->cl.nom_cap_uah * (1000 -
+				chip->dt.cl_min_cap_limit);
+		do_div(min_dec_val, 1000);
+		if (chip->cl.final_cc_uah < min_dec_val) {
+			fg_dbg(chip, FG_CAP_LEARN, "learning capacity %lld goes below min limit %lld\n",
+				chip->cl.final_cc_uah, min_dec_val);
+			chip->cl.learned_cc_uah = min_dec_val;
+		}
+	}
+
+	rc = fg_save_learned_cap_to_sram(chip);
+	if (rc < 0)
+		pr_err("Error in saving learned_cc_uah, rc=%d\n", rc);
+
+	fg_dbg(chip, FG_CAP_LEARN, "final cc_uah = %lld, learned capacity %lld -> %lld uah\n",
+		chip->cl.final_cc_uah, old_cap, chip->cl.learned_cc_uah);
+}
+
+static int  fg_cap_learning_process_full_data(struct fg_chip *chip)
+{
+	int rc, cc_soc_sw, cc_soc_delta_pct;
+	int64_t delta_cc_uah;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc_sw);
+	if (rc < 0) {
+		pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
+		return rc;
+	}
+
+	cc_soc_delta_pct = DIV_ROUND_CLOSEST(
+				abs(cc_soc_sw - chip->cl.init_cc_soc_sw) * 100,
+				CC_SOC_30BIT);
+	delta_cc_uah = div64_s64(chip->cl.learned_cc_uah * cc_soc_delta_pct,
+				100);
+	chip->cl.final_cc_uah = chip->cl.init_cc_uah + delta_cc_uah;
+	fg_dbg(chip, FG_CAP_LEARN, "Current cc_soc=%d cc_soc_delta_pct=%d total_cc_uah=%lld\n",
+		cc_soc_sw, cc_soc_delta_pct, chip->cl.final_cc_uah);
+	return 0;
+}
+
+static int fg_cap_learning_begin(struct fg_chip *chip, int batt_soc)
+{
+	int rc, cc_soc_sw;
+
+	if (DIV_ROUND_CLOSEST(batt_soc * 100, FULL_SOC_RAW) >
+		chip->dt.cl_start_soc) {
+		fg_dbg(chip, FG_CAP_LEARN, "Battery SOC %d is high!, not starting\n",
+			batt_soc);
+		return -EINVAL;
+	}
+
+	chip->cl.init_cc_uah = div64_s64(chip->cl.learned_cc_uah * batt_soc,
+					FULL_SOC_RAW);
+	rc = fg_get_sram_prop(chip, FG_SRAM_CC_SOC_SW, &cc_soc_sw);
+	if (rc < 0) {
+		pr_err("Error in getting CC_SOC_SW, rc=%d\n", rc);
+		return rc;
+	}
+
+	chip->cl.init_cc_soc_sw = cc_soc_sw;
+	chip->cl.active = true;
+	fg_dbg(chip, FG_CAP_LEARN, "Capacity learning started @ battery SOC %d init_cc_soc_sw:%d\n",
+		batt_soc, chip->cl.init_cc_soc_sw);
+	return 0;
+}
+
+static int fg_cap_learning_done(struct fg_chip *chip)
+{
+	int rc, cc_soc_sw;
+
+	rc = fg_cap_learning_process_full_data(chip);
+	if (rc < 0) {
+		pr_err("Error in processing cap learning full data, rc=%d\n",
+			rc);
+		goto out;
+	}
+
+	/* Write a FULL value to cc_soc_sw */
+	cc_soc_sw = CC_SOC_30BIT;
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_CC_SOC_SW].addr_word,
+		chip->sp[FG_SRAM_CC_SOC_SW].addr_byte, (u8 *)&cc_soc_sw,
+		chip->sp[FG_SRAM_CC_SOC_SW].len, FG_IMA_ATOMIC);
+	if (rc < 0) {
+		pr_err("Error in writing cc_soc_sw, rc=%d\n", rc);
+		goto out;
+	}
+
+	fg_cap_learning_post_process(chip);
+out:
+	return rc;
+}
+
+#define FULL_SOC_RAW	255
+static void fg_cap_learning_update(struct fg_chip *chip)
+{
+	int rc, batt_soc;
+
+	mutex_lock(&chip->cl.lock);
+
+	if (!is_temp_valid_cap_learning(chip) || !chip->cl.learned_cc_uah ||
+		chip->battery_missing) {
+		fg_dbg(chip, FG_CAP_LEARN, "Aborting cap_learning %lld\n",
+			chip->cl.learned_cc_uah);
+		chip->cl.active = false;
+		chip->cl.init_cc_uah = 0;
+		goto out;
+	}
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &batt_soc);
+	if (rc < 0) {
+		pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+		goto out;
+	}
+
+	/* We need only the most significant byte here */
+	batt_soc = (u32)batt_soc >> 24;
+
+	fg_dbg(chip, FG_CAP_LEARN, "Chg_status: %d cl_active: %d batt_soc: %d\n",
+		chip->charge_status, chip->cl.active, batt_soc);
+
+	/* Initialize the starting point of learning capacity */
+	if (!chip->cl.active) {
+		if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+			rc = fg_cap_learning_begin(chip, batt_soc);
+			chip->cl.active = (rc == 0);
+		}
+
+	} else {
+		if (chip->charge_done) {
+			rc = fg_cap_learning_done(chip);
+			if (rc < 0)
+				pr_err("Error in completing capacity learning, rc=%d\n",
+					rc);
+
+			chip->cl.active = false;
+			chip->cl.init_cc_uah = 0;
+		}
+
+		if (chip->charge_status == POWER_SUPPLY_STATUS_NOT_CHARGING) {
+			fg_dbg(chip, FG_CAP_LEARN, "Capacity learning aborted @ battery SOC %d\n",
+				batt_soc);
+			chip->cl.active = false;
+			chip->cl.init_cc_uah = 0;
+		}
+	}
+
+out:
+	mutex_unlock(&chip->cl.lock);
+}
+
+#define KI_COEFF_MED_DISCHG_DEFAULT	1500
+#define KI_COEFF_HI_DISCHG_DEFAULT	2200
+static int fg_adjust_ki_coeff_dischg(struct fg_chip *chip)
+{
+	int rc, i, msoc;
+	int ki_coeff_med = KI_COEFF_MED_DISCHG_DEFAULT;
+	int ki_coeff_hi = KI_COEFF_HI_DISCHG_DEFAULT;
+	u8 val;
+
+	if (!chip->ki_coeff_dischg_en)
+		return 0;
+
+	rc = fg_get_prop_capacity(chip, &msoc);
+	if (rc < 0) {
+		pr_err("Error in getting capacity, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->charge_status == POWER_SUPPLY_STATUS_DISCHARGING) {
+		for (i = KI_COEFF_SOC_LEVELS - 1; i >= 0; i--) {
+			if (msoc < chip->dt.ki_coeff_soc[i]) {
+				ki_coeff_med = chip->dt.ki_coeff_med_dischg[i];
+				ki_coeff_hi = chip->dt.ki_coeff_hi_dischg[i];
+			}
+		}
+	}
+
+	fg_encode(chip->sp, FG_SRAM_KI_COEFF_MED_DISCHG, ki_coeff_med, &val);
+	rc = fg_sram_write(chip,
+			chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_word,
+			chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].addr_byte, &val,
+			chip->sp[FG_SRAM_KI_COEFF_MED_DISCHG].len,
+			FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ki_coeff_med, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_KI_COEFF_HI_DISCHG, ki_coeff_hi, &val);
+	rc = fg_sram_write(chip,
+			chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_word,
+			chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].addr_byte, &val,
+			chip->sp[FG_SRAM_KI_COEFF_HI_DISCHG].len,
+			FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ki_coeff_hi, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_dbg(chip, FG_STATUS, "Wrote ki_coeff_med %d ki_coeff_hi %d\n",
+		ki_coeff_med, ki_coeff_hi);
+	return 0;
+}
+
+static int fg_set_recharge_voltage(struct fg_chip *chip, int voltage_mv)
+{
+	u8 buf;
+	int rc;
+
+	if (chip->dt.auto_recharge_soc)
+		return 0;
+
+	/* This configuration is available only for pmicobalt v2.0 and above */
+	if (chip->wa_flags & PMI8998_V1_REV_WA)
+		return 0;
+
+	fg_dbg(chip, FG_STATUS, "Setting recharge voltage to %dmV\n",
+		voltage_mv);
+	fg_encode(chip->sp, FG_SRAM_RECHARGE_VBATT_THR, voltage_mv, &buf);
+	rc = fg_sram_write(chip,
+			chip->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_word,
+			chip->sp[FG_SRAM_RECHARGE_VBATT_THR].addr_byte,
+			&buf, chip->sp[FG_SRAM_RECHARGE_VBATT_THR].len,
+			FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing recharge_vbatt_thr, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+#define AUTO_RECHG_VOLT_LOW_LIMIT_MV	3700
+static int fg_charge_full_update(struct fg_chip *chip)
+{
+	union power_supply_propval prop = {0, };
+	int rc, msoc, bsoc, recharge_soc;
+	u8 full_soc[2] = {0xFF, 0xFF};
+
+	if (!chip->dt.hold_soc_while_full)
+		return 0;
+
+	if (!batt_psy_initialized(chip))
+		return 0;
+
+	mutex_lock(&chip->charge_full_lock);
+	if (!chip->charge_done && chip->bsoc_delta_irq_en) {
+		disable_irq_wake(fg_irqs[BSOC_DELTA_IRQ].irq);
+		disable_irq_nosync(fg_irqs[BSOC_DELTA_IRQ].irq);
+		chip->bsoc_delta_irq_en = false;
+	} else if (chip->charge_done && !chip->bsoc_delta_irq_en) {
+		enable_irq(fg_irqs[BSOC_DELTA_IRQ].irq);
+		enable_irq_wake(fg_irqs[BSOC_DELTA_IRQ].irq);
+		chip->bsoc_delta_irq_en = true;
+	}
+
+	rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_HEALTH,
+		&prop);
+	if (rc < 0) {
+		pr_err("Error in getting battery health, rc=%d\n", rc);
+		goto out;
+	}
+
+	chip->health = prop.intval;
+	recharge_soc = chip->dt.recharge_soc_thr;
+	recharge_soc = DIV_ROUND_CLOSEST(recharge_soc * FULL_SOC_RAW,
+				FULL_CAPACITY);
+	rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &bsoc);
+	if (rc < 0) {
+		pr_err("Error in getting BATT_SOC, rc=%d\n", rc);
+		goto out;
+	}
+
+	/* We need 2 most significant bytes here */
+	bsoc = (u32)bsoc >> 16;
+	rc = fg_get_msoc(chip, &msoc);
+	if (rc < 0) {
+		pr_err("Error in getting msoc, rc=%d\n", rc);
+		goto out;
+	}
+
+	fg_dbg(chip, FG_STATUS, "msoc: %d bsoc: %x health: %d status: %d full: %d\n",
+		msoc, bsoc, chip->health, chip->charge_status,
+		chip->charge_full);
+	if (chip->charge_done && !chip->charge_full) {
+		if (msoc >= 99 && chip->health == POWER_SUPPLY_HEALTH_GOOD) {
+			fg_dbg(chip, FG_STATUS, "Setting charge_full to true\n");
+			chip->charge_full = true;
+			/*
+			 * Lower the recharge voltage so that VBAT_LT_RECHG
+			 * signal will not be asserted soon.
+			 */
+			rc = fg_set_recharge_voltage(chip,
+					AUTO_RECHG_VOLT_LOW_LIMIT_MV);
+			if (rc < 0) {
+				pr_err("Error in reducing recharge voltage, rc=%d\n",
+					rc);
+				goto out;
+			}
+		} else {
+			fg_dbg(chip, FG_STATUS, "Terminated charging @ SOC%d\n",
+				msoc);
+		}
+	} else if ((bsoc >> 8) <= recharge_soc && chip->charge_full) {
+		chip->delta_soc = FULL_CAPACITY - msoc;
+
+		/*
+		 * We're spreading out the delta SOC over every 10% change
+		 * in monotonic SOC. We cannot spread more than 9% in the
+		 * range of 0-100 skipping the first 10%.
+		 */
+		if (chip->delta_soc > 9) {
+			chip->delta_soc = 0;
+			chip->maint_soc = 0;
+		} else {
+			chip->maint_soc = FULL_CAPACITY;
+			chip->last_msoc = msoc;
+		}
+
+		chip->charge_full = false;
+
+		/*
+		 * Raise the recharge voltage so that VBAT_LT_RECHG signal
+		 * will be asserted soon as battery SOC had dropped below
+		 * the recharge SOC threshold.
+		 */
+		rc = fg_set_recharge_voltage(chip,
+					chip->dt.recharge_volt_thr_mv);
+		if (rc < 0) {
+			pr_err("Error in setting recharge voltage, rc=%d\n",
+				rc);
+			goto out;
+		}
+		fg_dbg(chip, FG_STATUS, "bsoc: %d recharge_soc: %d delta_soc: %d\n",
+			bsoc >> 8, recharge_soc, chip->delta_soc);
+	} else {
+		goto out;
+	}
+
+	if (!chip->charge_full)
+		goto out;
+
+	/*
+	 * During JEITA conditions, charge_full can happen early. FULL_SOC
+	 * and MONOTONIC_SOC needs to be updated to reflect the same. Write
+	 * battery SOC to FULL_SOC and write a full value to MONOTONIC_SOC.
+	 */
+	rc = fg_sram_write(chip, FULL_SOC_WORD, FULL_SOC_OFFSET, (u8 *)&bsoc, 2,
+			FG_IMA_ATOMIC);
+	if (rc < 0) {
+		pr_err("failed to write full_soc rc=%d\n", rc);
+		goto out;
+	}
+
+	rc = fg_sram_write(chip, MONOTONIC_SOC_WORD, MONOTONIC_SOC_OFFSET,
+			full_soc, 2, FG_IMA_ATOMIC);
+	if (rc < 0) {
+		pr_err("failed to write monotonic_soc rc=%d\n", rc);
+		goto out;
+	}
+
+	fg_dbg(chip, FG_STATUS, "Set charge_full to true @ soc %d\n", msoc);
+out:
+	mutex_unlock(&chip->charge_full_lock);
+	return rc;
+}
+
+#define RCONN_CONFIG_BIT	BIT(0)
+static int fg_rconn_config(struct fg_chip *chip)
+{
+	int rc, esr_uohms;
+	u64 scaling_factor;
+	u32 val = 0;
+
+	rc = fg_sram_read(chip, PROFILE_INTEGRITY_WORD,
+			SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in reading SW_CONFIG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (val & RCONN_CONFIG_BIT) {
+		fg_dbg(chip, FG_STATUS, "Rconn already configured: %x\n", val);
+		return 0;
+	}
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+	if (rc < 0) {
+		pr_err("failed to get ESR, rc=%d\n", rc);
+		return rc;
+	}
+
+	scaling_factor = div64_u64((u64)esr_uohms * 1000,
+				esr_uohms + (chip->dt.rconn_mohms * 1000));
+
+	rc = fg_sram_read(chip, ESR_RSLOW_CHG_WORD,
+			ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in reading ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+
+	val *= scaling_factor;
+	do_div(val, 1000);
+	rc = fg_sram_write(chip, ESR_RSLOW_CHG_WORD,
+			ESR_RSLOW_CHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR_RSLOW_CHG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+	fg_dbg(chip, FG_STATUS, "esr_rslow_chg modified to %x\n", val & 0xFF);
+
+	rc = fg_sram_read(chip, ESR_RSLOW_DISCHG_WORD,
+			ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in reading ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+
+	val *= scaling_factor;
+	do_div(val, 1000);
+	rc = fg_sram_write(chip, ESR_RSLOW_DISCHG_WORD,
+			ESR_RSLOW_DISCHG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR_RSLOW_DISCHG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+	fg_dbg(chip, FG_STATUS, "esr_rslow_dischg modified to %x\n",
+		val & 0xFF);
+
+	val = RCONN_CONFIG_BIT;
+	rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+			SW_CONFIG_OFFSET, (u8 *)&val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing SW_CONFIG_OFFSET, rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int fg_set_constant_chg_voltage(struct fg_chip *chip, int volt_uv)
+{
+	u8 buf[2];
+	int rc;
+
+	if (volt_uv <= 0 || volt_uv > 15590000) {
+		pr_err("Invalid voltage %d\n", volt_uv);
+		return -EINVAL;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_VBATT_FULL, volt_uv, buf);
+
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_VBATT_FULL].addr_word,
+		chip->sp[FG_SRAM_VBATT_FULL].addr_byte, buf,
+		chip->sp[FG_SRAM_VBATT_FULL].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing vbatt_full, rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int fg_set_recharge_soc(struct fg_chip *chip, int recharge_soc)
+{
+	u8 buf;
+	int rc;
+
+	if (!chip->dt.auto_recharge_soc)
+		return 0;
+
+	fg_encode(chip->sp, FG_SRAM_RECHARGE_SOC_THR, recharge_soc, &buf);
+	rc = fg_sram_write(chip,
+			chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_word,
+			chip->sp[FG_SRAM_RECHARGE_SOC_THR].addr_byte, &buf,
+			chip->sp[FG_SRAM_RECHARGE_SOC_THR].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing recharge_soc_thr, rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int fg_adjust_recharge_soc(struct fg_chip *chip)
+{
+	int rc, msoc, recharge_soc, new_recharge_soc = 0;
+
+	if (!chip->dt.auto_recharge_soc)
+		return 0;
+
+	recharge_soc = chip->dt.recharge_soc_thr;
+	/*
+	 * If the input is present and charging had been terminated, adjust
+	 * the recharge SOC threshold based on the monotonic SOC at which
+	 * the charge termination had happened.
+	 */
+	if (is_input_present(chip) && !chip->recharge_soc_adjusted
+		&& chip->charge_done) {
+		/* Get raw monotonic SOC for calculation */
+		rc = fg_get_msoc(chip, &msoc);
+		if (rc < 0) {
+			pr_err("Error in getting msoc, rc=%d\n", rc);
+			return rc;
+		}
+
+		/* Adjust the recharge_soc threshold */
+		new_recharge_soc = msoc - (FULL_CAPACITY - recharge_soc);
+	} else if (chip->recharge_soc_adjusted && (!is_input_present(chip)
+				|| chip->health == POWER_SUPPLY_HEALTH_GOOD)) {
+		/* Restore the default value */
+		new_recharge_soc = recharge_soc;
+	}
+
+	if (new_recharge_soc > 0 && new_recharge_soc < FULL_CAPACITY) {
+		rc = fg_set_recharge_soc(chip, new_recharge_soc);
+		if (rc) {
+			pr_err("Couldn't set resume SOC for FG, rc=%d\n", rc);
+			return rc;
+		}
+
+		chip->recharge_soc_adjusted = (new_recharge_soc !=
+						recharge_soc);
+		fg_dbg(chip, FG_STATUS, "resume soc set to %d\n",
+			new_recharge_soc);
+	}
+
+	return 0;
+}
+
+static int fg_slope_limit_config(struct fg_chip *chip, int batt_temp)
+{
+	enum slope_limit_status status;
+	int rc;
+	u8 buf;
+
+	if (!chip->slope_limit_en)
+		return 0;
+
+	if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING ||
+		chip->charge_status == POWER_SUPPLY_STATUS_FULL) {
+		if (batt_temp < chip->dt.slope_limit_temp)
+			status = LOW_TEMP_CHARGE;
+		else
+			status = HIGH_TEMP_CHARGE;
+	} else {
+		if (batt_temp < chip->dt.slope_limit_temp)
+			status = LOW_TEMP_DISCHARGE;
+		else
+			status = HIGH_TEMP_DISCHARGE;
+	}
+
+	if (chip->slope_limit_sts == status)
+		return 0;
+
+	fg_encode(chip->sp, FG_SRAM_SLOPE_LIMIT,
+		chip->dt.slope_limit_coeffs[status], &buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_SLOPE_LIMIT].addr_word,
+			chip->sp[FG_SRAM_SLOPE_LIMIT].addr_byte, &buf,
+			chip->sp[FG_SRAM_SLOPE_LIMIT].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in configuring slope_limit coefficient, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	chip->slope_limit_sts = status;
+	fg_dbg(chip, FG_STATUS, "Slope limit status: %d value: %x\n", status,
+		buf);
+	return 0;
+}
+
+static int fg_esr_filter_config(struct fg_chip *chip, int batt_temp)
+{
+	u8 esr_tight_lt_flt, esr_broad_lt_flt;
+	bool cold_temp = false;
+	int rc;
+
+	/*
+	 * If the battery temperature is lower than -20 C, then skip modifying
+	 * ESR filter.
+	 */
+	if (batt_temp < -210)
+		return 0;
+
+	/*
+	 * If battery temperature is lesser than 10 C (default), then apply the
+	 * ESR low temperature tight and broad filter values to ESR room
+	 * temperature tight and broad filters. If battery temperature is higher
+	 * than 10 C, then apply back the room temperature ESR filter
+	 * coefficients to ESR room temperature tight and broad filters.
+	 */
+	if (batt_temp > chip->dt.esr_flt_switch_temp
+		&& chip->esr_flt_cold_temp_en) {
+		fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+			chip->dt.esr_tight_flt_upct, &esr_tight_lt_flt);
+		fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+			chip->dt.esr_broad_flt_upct, &esr_broad_lt_flt);
+	} else if (batt_temp <= chip->dt.esr_flt_switch_temp
+			&& !chip->esr_flt_cold_temp_en) {
+		fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+			chip->dt.esr_tight_lt_flt_upct, &esr_tight_lt_flt);
+		fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+			chip->dt.esr_broad_lt_flt_upct, &esr_broad_lt_flt);
+		cold_temp = true;
+	} else {
+		return 0;
+	}
+
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word,
+			chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte,
+			&esr_tight_lt_flt,
+			chip->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR LT tight filter, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word,
+			chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte,
+			&esr_broad_lt_flt,
+			chip->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR LT broad filter, rc=%d\n", rc);
+		return rc;
+	}
+
+	chip->esr_flt_cold_temp_en = cold_temp;
+	fg_dbg(chip, FG_STATUS, "applied %s ESR filter values\n",
+		cold_temp ? "cold" : "normal");
+	return 0;
+}
+
+static int fg_esr_fcc_config(struct fg_chip *chip)
+{
+	union power_supply_propval prop = {0, };
+	int rc;
+	bool parallel_en = false;
+
+	if (is_parallel_charger_available(chip)) {
+		rc = power_supply_get_property(chip->parallel_psy,
+			POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
+		if (rc < 0) {
+			pr_err("Error in reading charging_enabled from parallel_psy, rc=%d\n",
+				rc);
+			return rc;
+		}
+		parallel_en = prop.intval;
+	}
+
+	fg_dbg(chip, FG_POWER_SUPPLY, "charge_status: %d parallel_en: %d esr_fcc_ctrl_en: %d\n",
+		chip->charge_status, parallel_en, chip->esr_fcc_ctrl_en);
+
+	if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING &&
+								parallel_en) {
+		if (chip->esr_fcc_ctrl_en)
+			return 0;
+
+		/*
+		 * When parallel charging is enabled, configure ESR FCC to
+		 * 300mA to trigger an ESR pulse. Without this, FG can ask
+		 * the main charger to increase FCC when it is supposed to
+		 * decrease it.
+		 */
+		rc = fg_masked_write(chip, BATT_INFO_ESR_FAST_CRG_CFG(chip),
+				ESR_FAST_CRG_IVAL_MASK |
+				ESR_FAST_CRG_CTL_EN_BIT,
+				ESR_FCC_300MA | ESR_FAST_CRG_CTL_EN_BIT);
+		if (rc < 0) {
+			pr_err("Error in writing to %04x, rc=%d\n",
+				BATT_INFO_ESR_FAST_CRG_CFG(chip), rc);
+			return rc;
+		}
+
+		chip->esr_fcc_ctrl_en = true;
+	} else {
+		if (!chip->esr_fcc_ctrl_en)
+			return 0;
+
+		/*
+		 * If we're here, then it means either the device is not in
+		 * charging state or parallel charging is disabled. Disable
+		 * ESR fast charge current control in SW.
+		 */
+		rc = fg_masked_write(chip, BATT_INFO_ESR_FAST_CRG_CFG(chip),
+				ESR_FAST_CRG_CTL_EN_BIT, 0);
+		if (rc < 0) {
+			pr_err("Error in writing to %04x, rc=%d\n",
+				BATT_INFO_ESR_FAST_CRG_CFG(chip), rc);
+			return rc;
+		}
+
+		chip->esr_fcc_ctrl_en = false;
+	}
+
+	fg_dbg(chip, FG_STATUS, "esr_fcc_ctrl_en set to %d\n",
+		chip->esr_fcc_ctrl_en);
+	return 0;
+}
+
+static void fg_batt_avg_update(struct fg_chip *chip)
+{
+	if (chip->charge_status == chip->prev_charge_status)
+		return;
+
+	cancel_delayed_work_sync(&chip->batt_avg_work);
+	fg_circ_buf_clr(&chip->ibatt_circ_buf);
+	fg_circ_buf_clr(&chip->vbatt_circ_buf);
+
+	if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING ||
+			chip->charge_status == POWER_SUPPLY_STATUS_DISCHARGING)
+		schedule_delayed_work(&chip->batt_avg_work,
+							msecs_to_jiffies(2000));
+}
+
+static void status_change_work(struct work_struct *work)
+{
+	struct fg_chip *chip = container_of(work,
+			struct fg_chip, status_change_work);
+	union power_supply_propval prop = {0, };
+	int rc, batt_temp;
+
+	if (!batt_psy_initialized(chip)) {
+		fg_dbg(chip, FG_STATUS, "Charger not available?!\n");
+		goto out;
+	}
+
+	rc = power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_STATUS,
+			&prop);
+	if (rc < 0) {
+		pr_err("Error in getting charging status, rc=%d\n", rc);
+		goto out;
+	}
+
+	chip->prev_charge_status = chip->charge_status;
+	chip->charge_status = prop.intval;
+	rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CHARGE_TYPE, &prop);
+	if (rc < 0) {
+		pr_err("Error in getting charge type, rc=%d\n", rc);
+		goto out;
+	}
+
+	chip->charge_type = prop.intval;
+	rc = power_supply_get_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CHARGE_DONE, &prop);
+	if (rc < 0) {
+		pr_err("Error in getting charge_done, rc=%d\n", rc);
+		goto out;
+	}
+
+	chip->charge_done = prop.intval;
+	if (chip->cyc_ctr.en)
+		schedule_work(&chip->cycle_count_work);
+
+	fg_cap_learning_update(chip);
+
+	rc = fg_charge_full_update(chip);
+	if (rc < 0)
+		pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+	rc = fg_adjust_recharge_soc(chip);
+	if (rc < 0)
+		pr_err("Error in adjusting recharge_soc, rc=%d\n", rc);
+
+	rc = fg_adjust_ki_coeff_dischg(chip);
+	if (rc < 0)
+		pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc);
+
+	rc = fg_esr_fcc_config(chip);
+	if (rc < 0)
+		pr_err("Error in adjusting FCC for ESR, rc=%d\n", rc);
+
+	rc = fg_get_battery_temp(chip, &batt_temp);
+	if (!rc) {
+		rc = fg_slope_limit_config(chip, batt_temp);
+		if (rc < 0)
+			pr_err("Error in configuring slope limiter rc:%d\n",
+				rc);
+	}
+
+	fg_batt_avg_update(chip);
+
+out:
+	fg_dbg(chip, FG_POWER_SUPPLY, "charge_status:%d charge_type:%d charge_done:%d\n",
+		chip->charge_status, chip->charge_type, chip->charge_done);
+	pm_relax(chip->dev);
+}
+
+static void restore_cycle_counter(struct fg_chip *chip)
+{
+	int rc = 0, i;
+	u8 data[2];
+
+	mutex_lock(&chip->cyc_ctr.lock);
+	for (i = 0; i < BUCKET_COUNT; i++) {
+		rc = fg_sram_read(chip, CYCLE_COUNT_WORD + (i / 2),
+				CYCLE_COUNT_OFFSET + (i % 2) * 2, data, 2,
+				FG_IMA_DEFAULT);
+		if (rc < 0)
+			pr_err("failed to read bucket %d rc=%d\n", i, rc);
+		else
+			chip->cyc_ctr.count[i] = data[0] | data[1] << 8;
+	}
+	mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static void clear_cycle_counter(struct fg_chip *chip)
+{
+	int rc = 0, i;
+
+	if (!chip->cyc_ctr.en)
+		return;
+
+	mutex_lock(&chip->cyc_ctr.lock);
+	memset(chip->cyc_ctr.count, 0, sizeof(chip->cyc_ctr.count));
+	for (i = 0; i < BUCKET_COUNT; i++) {
+		chip->cyc_ctr.started[i] = false;
+		chip->cyc_ctr.last_soc[i] = 0;
+	}
+	rc = fg_sram_write(chip, CYCLE_COUNT_WORD, CYCLE_COUNT_OFFSET,
+			(u8 *)&chip->cyc_ctr.count,
+			sizeof(chip->cyc_ctr.count) / sizeof(u8 *),
+			FG_IMA_DEFAULT);
+	if (rc < 0)
+		pr_err("failed to clear cycle counter rc=%d\n", rc);
+
+	mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket)
+{
+	int rc = 0;
+	u16 cyc_count;
+	u8 data[2];
+
+	if (bucket < 0 || (bucket > BUCKET_COUNT - 1))
+		return 0;
+
+	cyc_count = chip->cyc_ctr.count[bucket];
+	cyc_count++;
+	data[0] = cyc_count & 0xFF;
+	data[1] = cyc_count >> 8;
+
+	rc = fg_sram_write(chip, CYCLE_COUNT_WORD + (bucket / 2),
+			CYCLE_COUNT_OFFSET + (bucket % 2) * 2, data, 2,
+			FG_IMA_DEFAULT);
+	if (rc < 0)
+		pr_err("failed to write BATT_CYCLE[%d] rc=%d\n",
+			bucket, rc);
+	else
+		chip->cyc_ctr.count[bucket] = cyc_count;
+	return rc;
+}
+
+static void cycle_count_work(struct work_struct *work)
+{
+	int rc = 0, bucket, i, batt_soc;
+	struct fg_chip *chip = container_of(work,
+				struct fg_chip,
+				cycle_count_work);
+
+	mutex_lock(&chip->cyc_ctr.lock);
+	rc = fg_get_sram_prop(chip, FG_SRAM_BATT_SOC, &batt_soc);
+	if (rc < 0) {
+		pr_err("Failed to read battery soc rc: %d\n", rc);
+		goto out;
+	}
+
+	/* We need only the most significant byte here */
+	batt_soc = (u32)batt_soc >> 24;
+
+	if (chip->charge_status == POWER_SUPPLY_STATUS_CHARGING) {
+		/* Find out which bucket the SOC falls in */
+		bucket = batt_soc / BUCKET_SOC_PCT;
+		pr_debug("batt_soc: %d bucket: %d\n", batt_soc, bucket);
+
+		/*
+		 * If we've started counting for the previous bucket,
+		 * then store the counter for that bucket if the
+		 * counter for current bucket is getting started.
+		 */
+		if (bucket > 0 && chip->cyc_ctr.started[bucket - 1] &&
+			!chip->cyc_ctr.started[bucket]) {
+			rc = fg_inc_store_cycle_ctr(chip, bucket - 1);
+			if (rc < 0) {
+				pr_err("Error in storing cycle_ctr rc: %d\n",
+					rc);
+				goto out;
+			} else {
+				chip->cyc_ctr.started[bucket - 1] = false;
+				chip->cyc_ctr.last_soc[bucket - 1] = 0;
+			}
+		}
+		if (!chip->cyc_ctr.started[bucket]) {
+			chip->cyc_ctr.started[bucket] = true;
+			chip->cyc_ctr.last_soc[bucket] = batt_soc;
+		}
+	} else {
+		for (i = 0; i < BUCKET_COUNT; i++) {
+			if (chip->cyc_ctr.started[i] &&
+				batt_soc > chip->cyc_ctr.last_soc[i]) {
+				rc = fg_inc_store_cycle_ctr(chip, i);
+				if (rc < 0)
+					pr_err("Error in storing cycle_ctr rc: %d\n",
+						rc);
+				chip->cyc_ctr.last_soc[i] = 0;
+			}
+			chip->cyc_ctr.started[i] = false;
+		}
+	}
+out:
+	mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static int fg_get_cycle_count(struct fg_chip *chip)
+{
+	int count;
+
+	if (!chip->cyc_ctr.en)
+		return 0;
+
+	if ((chip->cyc_ctr.id <= 0) || (chip->cyc_ctr.id > BUCKET_COUNT))
+		return -EINVAL;
+
+	mutex_lock(&chip->cyc_ctr.lock);
+	count = chip->cyc_ctr.count[chip->cyc_ctr.id - 1];
+	mutex_unlock(&chip->cyc_ctr.lock);
+	return count;
+}
+
+#define PROFILE_LOAD_BIT	BIT(0)
+#define BOOTLOADER_LOAD_BIT	BIT(1)
+#define BOOTLOADER_RESTART_BIT	BIT(2)
+#define HLOS_RESTART_BIT	BIT(3)
+static bool is_profile_load_required(struct fg_chip *chip)
+{
+	u8 buf[PROFILE_COMP_LEN], val;
+	bool profiles_same = false;
+	int rc;
+
+	rc = fg_sram_read(chip, PROFILE_INTEGRITY_WORD,
+			PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("failed to read profile integrity rc=%d\n", rc);
+		return false;
+	}
+
+	/* Check if integrity bit is set */
+	if (val & PROFILE_LOAD_BIT) {
+		fg_dbg(chip, FG_STATUS, "Battery profile integrity bit is set\n");
+		rc = fg_sram_read(chip, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET,
+				buf, PROFILE_COMP_LEN, FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in reading battery profile, rc:%d\n", rc);
+			return false;
+		}
+		profiles_same = memcmp(chip->batt_profile, buf,
+					PROFILE_COMP_LEN) == 0;
+		if (profiles_same) {
+			fg_dbg(chip, FG_STATUS, "Battery profile is same, not loading it\n");
+			return false;
+		}
+
+		if (!chip->dt.force_load_profile) {
+			pr_warn("Profiles doesn't match, skipping loading it since force_load_profile is disabled\n");
+			if (fg_profile_dump) {
+				pr_info("FG: loaded profile:\n");
+				dump_sram(buf, PROFILE_LOAD_WORD,
+					PROFILE_COMP_LEN);
+				pr_info("FG: available profile:\n");
+				dump_sram(chip->batt_profile, PROFILE_LOAD_WORD,
+					PROFILE_LEN);
+			}
+			return false;
+		}
+
+		fg_dbg(chip, FG_STATUS, "Profiles are different, loading the correct one\n");
+	} else {
+		fg_dbg(chip, FG_STATUS, "Profile integrity bit is not set\n");
+		if (fg_profile_dump) {
+			pr_info("FG: profile to be loaded:\n");
+			dump_sram(chip->batt_profile, PROFILE_LOAD_WORD,
+				PROFILE_LEN);
+		}
+	}
+	return true;
+}
+
+static void clear_battery_profile(struct fg_chip *chip)
+{
+	u8 val = 0;
+	int rc;
+
+	rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+			PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+	if (rc < 0)
+		pr_err("failed to write profile integrity rc=%d\n", rc);
+}
+
+#define SOC_READY_WAIT_MS		2000
+static int __fg_restart(struct fg_chip *chip)
+{
+	int rc, msoc;
+	bool tried_again = false;
+
+	rc = fg_get_prop_capacity(chip, &msoc);
+	if (rc < 0) {
+		pr_err("Error in getting capacity, rc=%d\n", rc);
+		return rc;
+	}
+
+	chip->last_soc = msoc;
+	chip->fg_restarting = true;
+	reinit_completion(&chip->soc_ready);
+	rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT,
+			RESTART_GO_BIT);
+	if (rc < 0) {
+		pr_err("Error in writing to %04x, rc=%d\n",
+			BATT_SOC_RESTART(chip), rc);
+		goto out;
+	}
+
+wait:
+	rc = wait_for_completion_interruptible_timeout(&chip->soc_ready,
+		msecs_to_jiffies(SOC_READY_WAIT_MS));
+
+	/* If we were interrupted wait again one more time. */
+	if (rc == -ERESTARTSYS && !tried_again) {
+		tried_again = true;
+		goto wait;
+	} else if (rc <= 0) {
+		pr_err("wait for soc_ready timed out rc=%d\n", rc);
+	}
+
+	rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT, 0);
+	if (rc < 0) {
+		pr_err("Error in writing to %04x, rc=%d\n",
+			BATT_SOC_RESTART(chip), rc);
+		goto out;
+	}
+out:
+	chip->fg_restarting = false;
+	return rc;
+}
+
+static void profile_load_work(struct work_struct *work)
+{
+	struct fg_chip *chip = container_of(work,
+				struct fg_chip,
+				profile_load_work.work);
+	u8 buf[2], val;
+	int rc;
+
+	vote(chip->awake_votable, PROFILE_LOAD, true, 0);
+	if (!is_profile_load_required(chip))
+		goto done;
+
+	clear_cycle_counter(chip);
+	mutex_lock(&chip->cl.lock);
+	chip->cl.learned_cc_uah = 0;
+	chip->cl.active = false;
+	mutex_unlock(&chip->cl.lock);
+
+	fg_dbg(chip, FG_STATUS, "profile loading started\n");
+	rc = fg_masked_write(chip, BATT_SOC_RESTART(chip), RESTART_GO_BIT, 0);
+	if (rc < 0) {
+		pr_err("Error in writing to %04x, rc=%d\n",
+			BATT_SOC_RESTART(chip), rc);
+		goto out;
+	}
+
+	/* load battery profile */
+	rc = fg_sram_write(chip, PROFILE_LOAD_WORD, PROFILE_LOAD_OFFSET,
+			chip->batt_profile, PROFILE_LEN, FG_IMA_ATOMIC);
+	if (rc < 0) {
+		pr_err("Error in writing battery profile, rc:%d\n", rc);
+		goto out;
+	}
+
+	rc = __fg_restart(chip);
+	if (rc < 0) {
+		pr_err("Error in restarting FG, rc=%d\n", rc);
+		goto out;
+	}
+
+	fg_dbg(chip, FG_STATUS, "SOC is ready\n");
+
+	/* Set the profile integrity bit */
+	val = HLOS_RESTART_BIT | PROFILE_LOAD_BIT;
+	rc = fg_sram_write(chip, PROFILE_INTEGRITY_WORD,
+			PROFILE_INTEGRITY_OFFSET, &val, 1, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("failed to write profile integrity rc=%d\n", rc);
+		goto out;
+	}
+
+done:
+	rc = fg_sram_read(chip, NOM_CAP_WORD, NOM_CAP_OFFSET, buf, 2,
+			FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in reading %04x[%d] rc=%d\n", NOM_CAP_WORD,
+			NOM_CAP_OFFSET, rc);
+	} else {
+		chip->cl.nom_cap_uah = (int)(buf[0] | buf[1] << 8) * 1000;
+		rc = fg_load_learned_cap_from_sram(chip);
+		if (rc < 0)
+			pr_err("Error in loading capacity learning data, rc:%d\n",
+				rc);
+	}
+
+	batt_psy_initialized(chip);
+	fg_notify_charger(chip);
+	chip->profile_loaded = true;
+	chip->soc_reporting_ready = true;
+	fg_dbg(chip, FG_STATUS, "profile loaded successfully");
+out:
+	vote(chip->awake_votable, PROFILE_LOAD, false, 0);
+}
+
+static void sram_dump_work(struct work_struct *work)
+{
+	struct fg_chip *chip = container_of(work, struct fg_chip,
+					    sram_dump_work.work);
+	u8 buf[FG_SRAM_LEN];
+	int rc;
+	s64 timestamp_ms, quotient;
+	s32 remainder;
+
+	rc = fg_sram_read(chip, 0, 0, buf, FG_SRAM_LEN, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in reading FG SRAM, rc:%d\n", rc);
+		goto resched;
+	}
+
+	timestamp_ms = ktime_to_ms(ktime_get_boottime());
+	quotient = div_s64_rem(timestamp_ms, 1000, &remainder);
+	fg_dbg(chip, FG_STATUS, "SRAM Dump Started at %lld.%d\n",
+		quotient, remainder);
+	dump_sram(buf, 0, FG_SRAM_LEN);
+	timestamp_ms = ktime_to_ms(ktime_get_boottime());
+	quotient = div_s64_rem(timestamp_ms, 1000, &remainder);
+	fg_dbg(chip, FG_STATUS, "SRAM Dump done at %lld.%d\n",
+		quotient, remainder);
+resched:
+	schedule_delayed_work(&chip->sram_dump_work,
+			msecs_to_jiffies(fg_sram_dump_period_ms));
+}
+
+static int fg_sram_dump_sysfs(const char *val, const struct kernel_param *kp)
+{
+	int rc;
+	struct power_supply *bms_psy;
+	struct fg_chip *chip;
+	bool old_val = fg_sram_dump;
+
+	rc = param_set_bool(val, kp);
+	if (rc) {
+		pr_err("Unable to set fg_sram_dump: %d\n", rc);
+		return rc;
+	}
+
+	if (fg_sram_dump == old_val)
+		return 0;
+
+	bms_psy = power_supply_get_by_name("bms");
+	if (!bms_psy) {
+		pr_err("bms psy not found\n");
+		return -ENODEV;
+	}
+
+	chip = power_supply_get_drvdata(bms_psy);
+	if (fg_sram_dump)
+		schedule_delayed_work(&chip->sram_dump_work,
+				msecs_to_jiffies(fg_sram_dump_period_ms));
+	else
+		cancel_delayed_work_sync(&chip->sram_dump_work);
+
+	return 0;
+}
+
+static struct kernel_param_ops fg_sram_dump_ops = {
+	.set = fg_sram_dump_sysfs,
+	.get = param_get_bool,
+};
+
+module_param_cb(sram_dump_en, &fg_sram_dump_ops, &fg_sram_dump, 0644);
+
+static int fg_restart_sysfs(const char *val, const struct kernel_param *kp)
+{
+	int rc;
+	struct power_supply *bms_psy;
+	struct fg_chip *chip;
+
+	rc = param_set_int(val, kp);
+	if (rc) {
+		pr_err("Unable to set fg_restart: %d\n", rc);
+		return rc;
+	}
+
+	if (fg_restart != 1) {
+		pr_err("Bad value %d\n", fg_restart);
+		return -EINVAL;
+	}
+
+	bms_psy = power_supply_get_by_name("bms");
+	if (!bms_psy) {
+		pr_err("bms psy not found\n");
+		return 0;
+	}
+
+	chip = power_supply_get_drvdata(bms_psy);
+	rc = __fg_restart(chip);
+	if (rc < 0) {
+		pr_err("Error in restarting FG, rc=%d\n", rc);
+		return rc;
+	}
+
+	pr_info("FG restart done\n");
+	return rc;
+}
+
+static struct kernel_param_ops fg_restart_ops = {
+	.set = fg_restart_sysfs,
+	.get = param_get_int,
+};
+
+module_param_cb(restart, &fg_restart_ops, &fg_restart, 0644);
+
+#define BATT_AVG_POLL_PERIOD_MS	10000
+static void batt_avg_work(struct work_struct *work)
+{
+	struct fg_chip *chip = container_of(work, struct fg_chip,
+					    batt_avg_work.work);
+	int rc, ibatt_now, vbatt_now;
+
+	mutex_lock(&chip->batt_avg_lock);
+	rc = fg_get_battery_current(chip, &ibatt_now);
+	if (rc < 0) {
+		pr_err("failed to get battery current, rc=%d\n", rc);
+		goto reschedule;
+	}
+
+	rc = fg_get_battery_voltage(chip, &vbatt_now);
+	if (rc < 0) {
+		pr_err("failed to get battery voltage, rc=%d\n", rc);
+		goto reschedule;
+	}
+
+	fg_circ_buf_add(&chip->ibatt_circ_buf, ibatt_now);
+	fg_circ_buf_add(&chip->vbatt_circ_buf, vbatt_now);
+
+reschedule:
+	mutex_unlock(&chip->batt_avg_lock);
+	schedule_delayed_work(&chip->batt_avg_work,
+			      msecs_to_jiffies(BATT_AVG_POLL_PERIOD_MS));
+}
+
+#define HOURS_TO_SECONDS	3600
+#define OCV_SLOPE_UV		10869
+#define MILLI_UNIT		1000
+#define MICRO_UNIT		1000000
+static int fg_get_time_to_full(struct fg_chip *chip, int *val)
+{
+	int rc, ibatt_avg, vbatt_avg, rbatt, msoc, ocv_cc2cv, full_soc,
+		act_cap_uah;
+	s32 i_cc2cv, soc_cc2cv, ln_val, centi_tau_scale;
+	s64 t_predicted_cc = 0, t_predicted_cv = 0;
+
+	if (chip->bp.float_volt_uv <= 0) {
+		pr_err("battery profile is not loaded\n");
+		return -ENODATA;
+	}
+
+	if (!batt_psy_initialized(chip)) {
+		fg_dbg(chip, FG_TTF, "charger is not available\n");
+		return -ENODATA;
+	}
+
+	rc = fg_get_prop_capacity(chip, &msoc);
+	if (rc < 0) {
+		pr_err("failed to get msoc rc=%d\n", rc);
+		return rc;
+	}
+	fg_dbg(chip, FG_TTF, "msoc=%d\n", msoc);
+
+	if (msoc >= 100) {
+		*val = 0;
+		return 0;
+	}
+
+	mutex_lock(&chip->batt_avg_lock);
+	rc = fg_circ_buf_avg(&chip->ibatt_circ_buf, &ibatt_avg);
+	if (rc < 0) {
+		/* try to get instantaneous current */
+		rc = fg_get_battery_current(chip, &ibatt_avg);
+		if (rc < 0) {
+			mutex_unlock(&chip->batt_avg_lock);
+			pr_err("failed to get battery current, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	rc = fg_circ_buf_avg(&chip->vbatt_circ_buf, &vbatt_avg);
+	if (rc < 0) {
+		/* try to get instantaneous voltage */
+		rc = fg_get_battery_voltage(chip, &vbatt_avg);
+		if (rc < 0) {
+			mutex_unlock(&chip->batt_avg_lock);
+			pr_err("failed to get battery voltage, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	mutex_unlock(&chip->batt_avg_lock);
+	fg_dbg(chip, FG_TTF, "vbatt_avg=%d\n", vbatt_avg);
+
+	/* clamp ibatt_avg to -150mA */
+	if (ibatt_avg > -150000)
+		ibatt_avg = -150000;
+	fg_dbg(chip, FG_TTF, "ibatt_avg=%d\n", ibatt_avg);
+
+	/* reverse polarity to be consistent with unsigned current settings */
+	ibatt_avg = abs(ibatt_avg);
+
+	/* estimated battery current at the CC to CV transition */
+	i_cc2cv = div_s64((s64)ibatt_avg * vbatt_avg, chip->bp.float_volt_uv);
+	fg_dbg(chip, FG_TTF, "i_cc2cv=%d\n", i_cc2cv);
+
+	rc = fg_get_battery_resistance(chip, &rbatt);
+	if (rc < 0) {
+		pr_err("failed to get battery resistance rc=%d\n", rc);
+		return rc;
+	}
+
+	/* clamp rbatt to 50mOhms */
+	if (rbatt < 50000)
+		rbatt = 50000;
+
+	fg_dbg(chip, FG_TTF, "rbatt=%d\n", rbatt);
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_uah);
+	if (rc < 0) {
+		pr_err("failed to get ACT_BATT_CAP rc=%d\n", rc);
+		return rc;
+	}
+	act_cap_uah *= MILLI_UNIT;
+	fg_dbg(chip, FG_TTF, "actual_capacity_uah=%d\n", act_cap_uah);
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_FULL_SOC, &full_soc);
+	if (rc < 0) {
+		pr_err("failed to get full soc rc=%d\n", rc);
+		return rc;
+	}
+	full_soc = DIV_ROUND_CLOSEST(((u16)full_soc >> 8) * FULL_CAPACITY,
+								FULL_SOC_RAW);
+	fg_dbg(chip, FG_TTF, "full_soc=%d\n", full_soc);
+
+	/* if we are already in CV state then we can skip estimating CC */
+	if (chip->charge_type == POWER_SUPPLY_CHARGE_TYPE_TAPER)
+		goto skip_cc_estimate;
+
+	/* if the charger is current limited then use power approximation */
+	if (ibatt_avg > chip->bp.fastchg_curr_ma * MILLI_UNIT - 50000)
+		ocv_cc2cv = div_s64((s64)rbatt * ibatt_avg, MICRO_UNIT);
+	else
+		ocv_cc2cv = div_s64((s64)rbatt * i_cc2cv, MICRO_UNIT);
+	ocv_cc2cv = chip->bp.float_volt_uv - ocv_cc2cv;
+	fg_dbg(chip, FG_TTF, "ocv_cc2cv=%d\n", ocv_cc2cv);
+
+	soc_cc2cv = div_s64(chip->bp.float_volt_uv - ocv_cc2cv, OCV_SLOPE_UV);
+	/* estimated SOC at the CC to CV transition */
+	soc_cc2cv = 100 - soc_cc2cv;
+	fg_dbg(chip, FG_TTF, "soc_cc2cv=%d\n", soc_cc2cv);
+
+	/* the esimated SOC may be lower than the current SOC */
+	if (soc_cc2cv - msoc <= 0)
+		goto skip_cc_estimate;
+
+	t_predicted_cc = div_s64((s64)full_soc * act_cap_uah, 100);
+	t_predicted_cc = div_s64(t_predicted_cc * (soc_cc2cv - msoc), 100);
+	t_predicted_cc *= HOURS_TO_SECONDS;
+	t_predicted_cc = div_s64(t_predicted_cc, (ibatt_avg + i_cc2cv) / 2);
+
+skip_cc_estimate:
+	fg_dbg(chip, FG_TTF, "t_predicted_cc=%lld\n", t_predicted_cc);
+
+	/* CV estimate starts here */
+	if (chip->charge_type >= POWER_SUPPLY_CHARGE_TYPE_TAPER)
+		ln_val = ibatt_avg / (abs(chip->dt.sys_term_curr_ma) + 200);
+	else
+		ln_val = i_cc2cv / (abs(chip->dt.sys_term_curr_ma) + 200);
+
+	if (msoc < 95)
+		centi_tau_scale = 100;
+	else
+		centi_tau_scale = 20 * (100 - msoc);
+
+	fg_dbg(chip, FG_TTF, "ln_in=%d\n", ln_val);
+	rc = fg_lerp(fg_ln_table, ARRAY_SIZE(fg_ln_table), ln_val, &ln_val);
+	fg_dbg(chip, FG_TTF, "ln_out=%d\n", ln_val);
+	t_predicted_cv = div_s64((s64)act_cap_uah * rbatt, MICRO_UNIT);
+	t_predicted_cv = div_s64(t_predicted_cv * centi_tau_scale, 100);
+	t_predicted_cv = div_s64(t_predicted_cv * ln_val, MILLI_UNIT);
+	t_predicted_cv = div_s64(t_predicted_cv * HOURS_TO_SECONDS, MICRO_UNIT);
+	fg_dbg(chip, FG_TTF, "t_predicted_cv=%lld\n", t_predicted_cv);
+	*val = t_predicted_cc + t_predicted_cv;
+	return 0;
+}
+
+#define CENTI_ICORRECT_C0	105
+#define CENTI_ICORRECT_C1	20
+static int fg_get_time_to_empty(struct fg_chip *chip, int *val)
+{
+	int rc, ibatt_avg, msoc, act_cap_uah;
+	s32 divisor;
+	s64 t_predicted;
+
+	rc = fg_circ_buf_avg(&chip->ibatt_circ_buf, &ibatt_avg);
+	if (rc < 0) {
+		/* try to get instantaneous current */
+		rc = fg_get_battery_current(chip, &ibatt_avg);
+		if (rc < 0) {
+			pr_err("failed to get battery current, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/* clamp ibatt_avg to 150mA */
+	if (ibatt_avg < 150000)
+		ibatt_avg = 150000;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ACT_BATT_CAP, &act_cap_uah);
+	if (rc < 0) {
+		pr_err("Error in getting ACT_BATT_CAP, rc=%d\n", rc);
+		return rc;
+	}
+	act_cap_uah *= MILLI_UNIT;
+
+	rc = fg_get_prop_capacity(chip, &msoc);
+	if (rc < 0) {
+		pr_err("Error in getting capacity, rc=%d\n", rc);
+		return rc;
+	}
+
+	t_predicted = div_s64((s64)msoc * act_cap_uah, 100);
+	t_predicted *= HOURS_TO_SECONDS;
+	divisor = CENTI_ICORRECT_C0 * 100 + CENTI_ICORRECT_C1 * msoc;
+	divisor = div_s64((s64)divisor * ibatt_avg, 10000);
+	if (divisor > 0)
+		t_predicted = div_s64(t_predicted, divisor);
+
+	*val = t_predicted;
+	return 0;
+}
+
+static int fg_update_maint_soc(struct fg_chip *chip)
+{
+	int rc = 0, msoc;
+
+	mutex_lock(&chip->charge_full_lock);
+	if (chip->delta_soc <= 0)
+		goto out;
+
+	rc = fg_get_msoc(chip, &msoc);
+	if (rc < 0) {
+		pr_err("Error in getting msoc, rc=%d\n", rc);
+		goto out;
+	}
+
+	if (msoc > chip->maint_soc) {
+		/*
+		 * When the monotonic SOC goes above maintenance SOC, we should
+		 * stop showing the maintenance SOC.
+		 */
+		chip->delta_soc = 0;
+		chip->maint_soc = 0;
+	} else if (msoc <= chip->last_msoc) {
+		/* MSOC is decreasing. Decrease maintenance SOC as well */
+		chip->maint_soc -= 1;
+		if (!(msoc % 10)) {
+			/*
+			 * Reduce the maintenance SOC additionally by 1 whenever
+			 * it crosses a SOC multiple of 10.
+			 */
+			chip->maint_soc -= 1;
+			chip->delta_soc -= 1;
+		}
+	}
+
+	fg_dbg(chip, FG_IRQ, "msoc: %d last_msoc: %d maint_soc: %d delta_soc: %d\n",
+		msoc, chip->last_msoc, chip->maint_soc, chip->delta_soc);
+	chip->last_msoc = msoc;
+out:
+	mutex_unlock(&chip->charge_full_lock);
+	return rc;
+}
+
+static int fg_esr_validate(struct fg_chip *chip)
+{
+	int rc, esr_uohms;
+	u8 buf[2];
+
+	if (chip->dt.esr_clamp_mohms <= 0)
+		return 0;
+
+	rc = fg_get_sram_prop(chip, FG_SRAM_ESR, &esr_uohms);
+	if (rc < 0) {
+		pr_err("failed to get ESR, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (esr_uohms >= chip->dt.esr_clamp_mohms * 1000) {
+		pr_debug("ESR %d is > ESR_clamp\n", esr_uohms);
+		return 0;
+	}
+
+	esr_uohms = chip->dt.esr_clamp_mohms * 1000;
+	fg_encode(chip->sp, FG_SRAM_ESR, esr_uohms, buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR].addr_word,
+			chip->sp[FG_SRAM_ESR].addr_byte, buf,
+			chip->sp[FG_SRAM_ESR].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_dbg(chip, FG_STATUS, "ESR clamped to %duOhms\n", esr_uohms);
+	return 0;
+}
+
+/* PSY CALLBACKS STAY HERE */
+
+static int fg_psy_get_property(struct power_supply *psy,
+				       enum power_supply_property psp,
+				       union power_supply_propval *pval)
+{
+	struct fg_chip *chip = power_supply_get_drvdata(psy);
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = fg_get_prop_capacity(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		if (chip->battery_missing)
+			pval->intval = 3700000;
+		else
+			rc = fg_get_battery_voltage(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		rc = fg_get_battery_current(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		rc = fg_get_battery_temp(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_RESISTANCE:
+		rc = fg_get_battery_resistance(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+		rc = fg_get_sram_prop(chip, FG_SRAM_OCV, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+		pval->intval = chip->cl.nom_cap_uah;
+		break;
+	case POWER_SUPPLY_PROP_RESISTANCE_ID:
+		pval->intval = chip->batt_id_ohms;
+		break;
+	case POWER_SUPPLY_PROP_BATTERY_TYPE:
+		pval->strval = fg_get_battery_type(chip);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+		pval->intval = chip->bp.float_volt_uv;
+		break;
+	case POWER_SUPPLY_PROP_CYCLE_COUNT:
+		pval->intval = fg_get_cycle_count(chip);
+		break;
+	case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+		pval->intval = chip->cyc_ctr.id;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW_RAW:
+		rc = fg_get_cc_soc(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_NOW:
+		pval->intval = chip->cl.init_cc_uah;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_FULL:
+		pval->intval = chip->cl.learned_cc_uah;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_COUNTER:
+		rc = fg_get_cc_soc_sw(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_TIME_TO_FULL_AVG:
+		rc = fg_get_time_to_full(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG:
+		rc = fg_get_time_to_empty(chip, &pval->intval);
+		break;
+	case POWER_SUPPLY_PROP_SOC_REPORTING_READY:
+		pval->intval = chip->soc_reporting_ready;
+		break;
+	case POWER_SUPPLY_PROP_DEBUG_BATTERY:
+		pval->intval = is_debug_batt_id(chip);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		rc = fg_get_sram_prop(chip, FG_SRAM_VBATT_FULL, &pval->intval);
+		break;
+	default:
+		pr_err("unsupported property %d\n", psp);
+		rc = -EINVAL;
+		break;
+	}
+
+	if (rc < 0)
+		return -ENODATA;
+
+	return 0;
+}
+
+static int fg_psy_set_property(struct power_supply *psy,
+				  enum power_supply_property psp,
+				  const union power_supply_propval *pval)
+{
+	struct fg_chip *chip = power_supply_get_drvdata(psy);
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+		if ((pval->intval > 0) && (pval->intval <= BUCKET_COUNT)) {
+			chip->cyc_ctr.id = pval->intval;
+		} else {
+			pr_err("rejecting invalid cycle_count_id = %d\n",
+				pval->intval);
+			return -EINVAL;
+		}
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		rc = fg_set_constant_chg_voltage(chip, pval->intval);
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+static int fg_property_is_writeable(struct power_supply *psy,
+						enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static void fg_external_power_changed(struct power_supply *psy)
+{
+	pr_debug("power supply changed\n");
+}
+
+static int fg_notifier_cb(struct notifier_block *nb,
+		unsigned long event, void *data)
+{
+	struct power_supply *psy = data;
+	struct fg_chip *chip = container_of(nb, struct fg_chip, nb);
+
+	if (event != PSY_EVENT_PROP_CHANGED)
+		return NOTIFY_OK;
+
+	if (work_pending(&chip->status_change_work))
+		return NOTIFY_OK;
+
+	if ((strcmp(psy->desc->name, "battery") == 0)
+		|| (strcmp(psy->desc->name, "usb") == 0)) {
+		/*
+		 * We cannot vote for awake votable here as that takes
+		 * a mutex lock and this is executed in an atomic context.
+		 */
+		pm_stay_awake(chip->dev);
+		schedule_work(&chip->status_change_work);
+	}
+
+	return NOTIFY_OK;
+}
+
+static enum power_supply_property fg_psy_props[] = {
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_OCV,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_RESISTANCE_ID,
+	POWER_SUPPLY_PROP_RESISTANCE,
+	POWER_SUPPLY_PROP_BATTERY_TYPE,
+	POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+	POWER_SUPPLY_PROP_CYCLE_COUNT,
+	POWER_SUPPLY_PROP_CYCLE_COUNT_ID,
+	POWER_SUPPLY_PROP_CHARGE_NOW_RAW,
+	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CHARGE_FULL,
+	POWER_SUPPLY_PROP_CHARGE_COUNTER,
+	POWER_SUPPLY_PROP_TIME_TO_FULL_AVG,
+	POWER_SUPPLY_PROP_TIME_TO_EMPTY_AVG,
+	POWER_SUPPLY_PROP_SOC_REPORTING_READY,
+	POWER_SUPPLY_PROP_DEBUG_BATTERY,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_VOLTAGE,
+};
+
+static const struct power_supply_desc fg_psy_desc = {
+	.name = "bms",
+	.type = POWER_SUPPLY_TYPE_BMS,
+	.properties = fg_psy_props,
+	.num_properties = ARRAY_SIZE(fg_psy_props),
+	.get_property = fg_psy_get_property,
+	.set_property = fg_psy_set_property,
+	.external_power_changed = fg_external_power_changed,
+	.property_is_writeable = fg_property_is_writeable,
+};
+
+/* INIT FUNCTIONS STAY HERE */
+
+static int fg_hw_init(struct fg_chip *chip)
+{
+	int rc;
+	u8 buf[4], val;
+
+	fg_encode(chip->sp, FG_SRAM_CUTOFF_VOLT, chip->dt.cutoff_volt_mv, buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_CUTOFF_VOLT].addr_word,
+			chip->sp[FG_SRAM_CUTOFF_VOLT].addr_byte, buf,
+			chip->sp[FG_SRAM_CUTOFF_VOLT].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing cutoff_volt, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_EMPTY_VOLT, chip->dt.empty_volt_mv, buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_EMPTY_VOLT].addr_word,
+			chip->sp[FG_SRAM_EMPTY_VOLT].addr_byte, buf,
+			chip->sp[FG_SRAM_EMPTY_VOLT].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing empty_volt, rc=%d\n", rc);
+		return rc;
+	}
+
+	/* This SRAM register is only present in v2.0 and above */
+	if (!(chip->wa_flags & PMI8998_V1_REV_WA) &&
+					chip->bp.float_volt_uv > 0) {
+		fg_encode(chip->sp, FG_SRAM_FLOAT_VOLT,
+			chip->bp.float_volt_uv / 1000, buf);
+		rc = fg_sram_write(chip, chip->sp[FG_SRAM_FLOAT_VOLT].addr_word,
+			chip->sp[FG_SRAM_FLOAT_VOLT].addr_byte, buf,
+			chip->sp[FG_SRAM_FLOAT_VOLT].len, FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in writing float_volt, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->bp.vbatt_full_mv > 0) {
+		rc = fg_set_constant_chg_voltage(chip,
+				chip->bp.vbatt_full_mv * 1000);
+		if (rc < 0)
+			return rc;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_CHG_TERM_CURR, chip->dt.chg_term_curr_ma,
+		buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_CHG_TERM_CURR].addr_word,
+			chip->sp[FG_SRAM_CHG_TERM_CURR].addr_byte, buf,
+			chip->sp[FG_SRAM_CHG_TERM_CURR].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing chg_term_curr, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_SYS_TERM_CURR, chip->dt.sys_term_curr_ma,
+		buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_SYS_TERM_CURR].addr_word,
+			chip->sp[FG_SRAM_SYS_TERM_CURR].addr_byte, buf,
+			chip->sp[FG_SRAM_SYS_TERM_CURR].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing sys_term_curr, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->dt.vbatt_low_thr_mv > 0) {
+		fg_encode(chip->sp, FG_SRAM_VBATT_LOW,
+			chip->dt.vbatt_low_thr_mv, buf);
+		rc = fg_sram_write(chip, chip->sp[FG_SRAM_VBATT_LOW].addr_word,
+				chip->sp[FG_SRAM_VBATT_LOW].addr_byte, buf,
+				chip->sp[FG_SRAM_VBATT_LOW].len,
+				FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in writing vbatt_low_thr, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.delta_soc_thr > 0 && chip->dt.delta_soc_thr < 100) {
+		fg_encode(chip->sp, FG_SRAM_DELTA_MSOC_THR,
+			chip->dt.delta_soc_thr, buf);
+		rc = fg_sram_write(chip,
+				chip->sp[FG_SRAM_DELTA_MSOC_THR].addr_word,
+				chip->sp[FG_SRAM_DELTA_MSOC_THR].addr_byte,
+				buf, chip->sp[FG_SRAM_DELTA_MSOC_THR].len,
+				FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in writing delta_msoc_thr, rc=%d\n", rc);
+			return rc;
+		}
+
+		fg_encode(chip->sp, FG_SRAM_DELTA_BSOC_THR,
+			chip->dt.delta_soc_thr, buf);
+		rc = fg_sram_write(chip,
+				chip->sp[FG_SRAM_DELTA_BSOC_THR].addr_word,
+				chip->sp[FG_SRAM_DELTA_BSOC_THR].addr_byte,
+				buf, chip->sp[FG_SRAM_DELTA_BSOC_THR].len,
+				FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in writing delta_bsoc_thr, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	/*
+	 * configure battery thermal coefficients c1,c2,c3
+	 * if its value is not zero.
+	 */
+	if (chip->dt.batt_therm_coeffs[0] > 0) {
+		rc = fg_write(chip, BATT_INFO_THERM_C1(chip),
+			chip->dt.batt_therm_coeffs, BATT_THERM_NUM_COEFFS);
+		if (rc < 0) {
+			pr_err("Error in writing battery thermal coefficients, rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+
+	if (chip->dt.recharge_soc_thr > 0 && chip->dt.recharge_soc_thr < 100) {
+		rc = fg_set_recharge_soc(chip, chip->dt.recharge_soc_thr);
+		if (rc < 0) {
+			pr_err("Error in setting recharge_soc, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.recharge_volt_thr_mv > 0) {
+		rc = fg_set_recharge_voltage(chip,
+			chip->dt.recharge_volt_thr_mv);
+		if (rc < 0) {
+			pr_err("Error in setting recharge_voltage, rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.rsense_sel >= SRC_SEL_BATFET &&
+			chip->dt.rsense_sel < SRC_SEL_RESERVED) {
+		rc = fg_masked_write(chip, BATT_INFO_IBATT_SENSING_CFG(chip),
+				SOURCE_SELECT_MASK, chip->dt.rsense_sel);
+		if (rc < 0) {
+			pr_err("Error in writing rsense_sel, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_COLD], &val);
+	rc = fg_write(chip, BATT_INFO_JEITA_TOO_COLD(chip), &val, 1);
+	if (rc < 0) {
+		pr_err("Error in writing jeita_cold, rc=%d\n", rc);
+		return rc;
+	}
+
+	get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_COOL], &val);
+	rc = fg_write(chip, BATT_INFO_JEITA_COLD(chip), &val, 1);
+	if (rc < 0) {
+		pr_err("Error in writing jeita_cool, rc=%d\n", rc);
+		return rc;
+	}
+
+	get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_WARM], &val);
+	rc = fg_write(chip, BATT_INFO_JEITA_HOT(chip), &val, 1);
+	if (rc < 0) {
+		pr_err("Error in writing jeita_warm, rc=%d\n", rc);
+		return rc;
+	}
+
+	get_temp_setpoint(chip->dt.jeita_thresholds[JEITA_HOT], &val);
+	rc = fg_write(chip, BATT_INFO_JEITA_TOO_HOT(chip), &val, 1);
+	if (rc < 0) {
+		pr_err("Error in writing jeita_hot, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->dt.esr_timer_charging > 0) {
+		rc = fg_set_esr_timer(chip, chip->dt.esr_timer_charging, true,
+				      FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in setting ESR timer, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->dt.esr_timer_awake > 0) {
+		rc = fg_set_esr_timer(chip, chip->dt.esr_timer_awake, false,
+				      FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in setting ESR timer, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	if (chip->cyc_ctr.en)
+		restore_cycle_counter(chip);
+
+	if (chip->dt.jeita_hyst_temp >= 0) {
+		val = chip->dt.jeita_hyst_temp << JEITA_TEMP_HYST_SHIFT;
+		rc = fg_masked_write(chip, BATT_INFO_BATT_TEMP_CFG(chip),
+			JEITA_TEMP_HYST_MASK, val);
+		if (rc < 0) {
+			pr_err("Error in writing batt_temp_cfg, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	get_batt_temp_delta(chip->dt.batt_temp_delta, &val);
+	rc = fg_masked_write(chip, BATT_INFO_BATT_TMPR_INTR(chip),
+			CHANGE_THOLD_MASK, val);
+	if (rc < 0) {
+		pr_err("Error in writing batt_temp_delta, rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->dt.rconn_mohms > 0) {
+		rc = fg_rconn_config(chip);
+		if (rc < 0) {
+			pr_err("Error in configuring Rconn, rc=%d\n", rc);
+			return rc;
+		}
+	}
+
+	fg_encode(chip->sp, FG_SRAM_ESR_TIGHT_FILTER,
+		chip->dt.esr_tight_flt_upct, buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_word,
+			chip->sp[FG_SRAM_ESR_TIGHT_FILTER].addr_byte, buf,
+			chip->sp[FG_SRAM_ESR_TIGHT_FILTER].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR tight filter, rc=%d\n", rc);
+		return rc;
+	}
+
+	fg_encode(chip->sp, FG_SRAM_ESR_BROAD_FILTER,
+		chip->dt.esr_broad_flt_upct, buf);
+	rc = fg_sram_write(chip, chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_word,
+			chip->sp[FG_SRAM_ESR_BROAD_FILTER].addr_byte, buf,
+			chip->sp[FG_SRAM_ESR_BROAD_FILTER].len, FG_IMA_DEFAULT);
+	if (rc < 0) {
+		pr_err("Error in writing ESR broad filter, rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int fg_memif_init(struct fg_chip *chip)
+{
+	return fg_ima_init(chip);
+}
+
+/* INTERRUPT HANDLERS STAY HERE */
+
+static irqreturn_t fg_mem_xcp_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	u8 status;
+	int rc;
+
+	rc = fg_read(chip, MEM_IF_INT_RT_STS(chip), &status, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			MEM_IF_INT_RT_STS(chip), rc);
+		return IRQ_HANDLED;
+	}
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered, status:%d\n", irq, status);
+	if (status & MEM_XCP_BIT) {
+		rc = fg_clear_dma_errors_if_any(chip);
+		if (rc < 0) {
+			pr_err("Error in clearing DMA error, rc=%d\n", rc);
+			return IRQ_HANDLED;
+		}
+
+		mutex_lock(&chip->sram_rw_lock);
+		rc = fg_clear_ima_errors_if_any(chip, true);
+		if (rc < 0 && rc != -EAGAIN)
+			pr_err("Error in checking IMA errors rc:%d\n", rc);
+		mutex_unlock(&chip->sram_rw_lock);
+	}
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_vbatt_low_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_batt_missing_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	u8 status;
+	int rc;
+
+	rc = fg_read(chip, BATT_INFO_INT_RT_STS(chip), &status, 1);
+	if (rc < 0) {
+		pr_err("failed to read addr=0x%04x, rc=%d\n",
+			BATT_INFO_INT_RT_STS(chip), rc);
+		return IRQ_HANDLED;
+	}
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered sts:%d\n", irq, status);
+	chip->battery_missing = (status & BT_MISS_BIT);
+
+	if (chip->battery_missing) {
+		chip->profile_available = false;
+		chip->profile_loaded = false;
+		chip->soc_reporting_ready = false;
+		return IRQ_HANDLED;
+	}
+
+	rc = fg_get_batt_id(chip);
+	if (rc < 0) {
+		chip->soc_reporting_ready = true;
+		pr_err("Error in getting battery id, rc:%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	rc = fg_get_batt_profile(chip);
+	if (rc < 0) {
+		chip->soc_reporting_ready = true;
+		pr_err("Error in getting battery profile, rc:%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	clear_battery_profile(chip);
+	schedule_delayed_work(&chip->profile_load_work, 0);
+
+	if (chip->fg_psy)
+		power_supply_changed(chip->fg_psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_batt_temp_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	union power_supply_propval prop = {0, };
+	int rc, batt_temp;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	rc = fg_get_battery_temp(chip, &batt_temp);
+	if (rc < 0) {
+		pr_err("Error in getting batt_temp\n");
+		return IRQ_HANDLED;
+	}
+
+	rc = fg_esr_filter_config(chip, batt_temp);
+	if (rc < 0)
+		pr_err("Error in configuring ESR filter rc:%d\n", rc);
+
+	rc = fg_slope_limit_config(chip, batt_temp);
+	if (rc < 0)
+		pr_err("Error in configuring slope limiter rc:%d\n", rc);
+
+	if (!batt_psy_initialized(chip)) {
+		chip->last_batt_temp = batt_temp;
+		return IRQ_HANDLED;
+	}
+
+	power_supply_get_property(chip->batt_psy, POWER_SUPPLY_PROP_HEALTH,
+		&prop);
+	chip->health = prop.intval;
+
+	if (chip->last_batt_temp != batt_temp) {
+		chip->last_batt_temp = batt_temp;
+		power_supply_changed(chip->batt_psy);
+	}
+
+	if (abs(chip->last_batt_temp - batt_temp) > 30)
+		pr_warn("Battery temperature last:%d current: %d\n",
+			chip->last_batt_temp, batt_temp);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_first_est_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	complete_all(&chip->soc_ready);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_soc_update_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	complete_all(&chip->soc_update);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_bsoc_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	int rc;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	rc = fg_charge_full_update(chip);
+	if (rc < 0)
+		pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_delta_msoc_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+	int rc;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	if (chip->cyc_ctr.en)
+		schedule_work(&chip->cycle_count_work);
+
+	if (chip->cl.active)
+		fg_cap_learning_update(chip);
+
+	rc = fg_charge_full_update(chip);
+	if (rc < 0)
+		pr_err("Error in charge_full_update, rc=%d\n", rc);
+
+	rc = fg_adjust_ki_coeff_dischg(chip);
+	if (rc < 0)
+		pr_err("Error in adjusting ki_coeff_dischg, rc=%d\n", rc);
+
+	rc = fg_update_maint_soc(chip);
+	if (rc < 0)
+		pr_err("Error in updating maint_soc, rc=%d\n", rc);
+
+	rc = fg_esr_validate(chip);
+	if (rc < 0)
+		pr_err("Error in validating ESR, rc=%d\n", rc);
+
+	if (batt_psy_initialized(chip))
+		power_supply_changed(chip->batt_psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_empty_soc_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	if (batt_psy_initialized(chip))
+		power_supply_changed(chip->batt_psy);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_soc_irq_handler(int irq, void *data)
+{
+	struct fg_chip *chip = data;
+
+	fg_dbg(chip, FG_IRQ, "irq %d triggered\n", irq);
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_dummy_irq_handler(int irq, void *data)
+{
+	pr_debug("irq %d triggered\n", irq);
+	return IRQ_HANDLED;
+}
+
+static struct fg_irq_info fg_irqs[FG_IRQ_MAX] = {
+	/* BATT_SOC irqs */
+	[MSOC_FULL_IRQ] = {
+		.name		= "msoc-full",
+		.handler	= fg_soc_irq_handler,
+	},
+	[MSOC_HIGH_IRQ] = {
+		.name		= "msoc-high",
+		.handler	= fg_soc_irq_handler,
+		.wakeable	= true,
+	},
+	[MSOC_EMPTY_IRQ] = {
+		.name		= "msoc-empty",
+		.handler	= fg_empty_soc_irq_handler,
+		.wakeable	= true,
+	},
+	[MSOC_LOW_IRQ] = {
+		.name		= "msoc-low",
+		.handler	= fg_soc_irq_handler,
+		.wakeable	= true,
+	},
+	[MSOC_DELTA_IRQ] = {
+		.name		= "msoc-delta",
+		.handler	= fg_delta_msoc_irq_handler,
+		.wakeable	= true,
+	},
+	[BSOC_DELTA_IRQ] = {
+		.name		= "bsoc-delta",
+		.handler	= fg_delta_bsoc_irq_handler,
+		.wakeable	= true,
+	},
+	[SOC_READY_IRQ] = {
+		.name		= "soc-ready",
+		.handler	= fg_first_est_irq_handler,
+		.wakeable	= true,
+	},
+	[SOC_UPDATE_IRQ] = {
+		.name		= "soc-update",
+		.handler	= fg_soc_update_irq_handler,
+	},
+	/* BATT_INFO irqs */
+	[BATT_TEMP_DELTA_IRQ] = {
+		.name		= "batt-temp-delta",
+		.handler	= fg_delta_batt_temp_irq_handler,
+		.wakeable	= true,
+	},
+	[BATT_MISSING_IRQ] = {
+		.name		= "batt-missing",
+		.handler	= fg_batt_missing_irq_handler,
+		.wakeable	= true,
+	},
+	[ESR_DELTA_IRQ] = {
+		.name		= "esr-delta",
+		.handler	= fg_dummy_irq_handler,
+	},
+	[VBATT_LOW_IRQ] = {
+		.name		= "vbatt-low",
+		.handler	= fg_vbatt_low_irq_handler,
+		.wakeable	= true,
+	},
+	[VBATT_PRED_DELTA_IRQ] = {
+		.name		= "vbatt-pred-delta",
+		.handler	= fg_dummy_irq_handler,
+	},
+	/* MEM_IF irqs */
+	[DMA_GRANT_IRQ] = {
+		.name		= "dma-grant",
+		.handler	= fg_dummy_irq_handler,
+	},
+	[MEM_XCP_IRQ] = {
+		.name		= "mem-xcp",
+		.handler	= fg_mem_xcp_irq_handler,
+	},
+	[IMA_RDY_IRQ] = {
+		.name		= "ima-rdy",
+		.handler	= fg_dummy_irq_handler,
+	},
+};
+
+static int fg_get_irq_index_byname(const char *name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(fg_irqs); i++) {
+		if (strcmp(fg_irqs[i].name, name) == 0)
+			return i;
+	}
+
+	pr_err("%s is not in irq list\n", name);
+	return -ENOENT;
+}
+
+static int fg_register_interrupts(struct fg_chip *chip)
+{
+	struct device_node *child, *node = chip->dev->of_node;
+	struct property *prop;
+	const char *name;
+	int rc, irq, irq_index;
+
+	for_each_available_child_of_node(node, child) {
+		of_property_for_each_string(child, "interrupt-names", prop,
+						name) {
+			irq = of_irq_get_byname(child, name);
+			if (irq < 0) {
+				dev_err(chip->dev, "failed to get irq %s irq:%d\n",
+					name, irq);
+				return irq;
+			}
+
+			irq_index = fg_get_irq_index_byname(name);
+			if (irq_index < 0)
+				return irq_index;
+
+			rc = devm_request_threaded_irq(chip->dev, irq, NULL,
+					fg_irqs[irq_index].handler,
+					IRQF_ONESHOT, name, chip);
+			if (rc < 0) {
+				dev_err(chip->dev, "failed to register irq handler for %s rc:%d\n",
+					name, rc);
+				return rc;
+			}
+
+			fg_irqs[irq_index].irq = irq;
+			if (fg_irqs[irq_index].wakeable)
+				enable_irq_wake(fg_irqs[irq_index].irq);
+		}
+	}
+
+	return 0;
+}
+
+static int fg_parse_slope_limit_coefficients(struct fg_chip *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	int rc, i;
+
+	rc = of_property_read_u32(node, "qcom,slope-limit-temp-threshold",
+			&chip->dt.slope_limit_temp);
+	if (rc < 0)
+		return 0;
+
+	rc = of_property_count_elems_of_size(node, "qcom,slope-limit-coeffs",
+			sizeof(u32));
+	if (rc != SLOPE_LIMIT_NUM_COEFFS)
+		return -EINVAL;
+
+	rc = of_property_read_u32_array(node, "qcom,slope-limit-coeffs",
+			chip->dt.slope_limit_coeffs, SLOPE_LIMIT_NUM_COEFFS);
+	if (rc < 0) {
+		pr_err("Error in reading qcom,slope-limit-coeffs, rc=%d\n", rc);
+		return rc;
+	}
+
+	for (i = 0; i < SLOPE_LIMIT_NUM_COEFFS; i++) {
+		if (chip->dt.slope_limit_coeffs[i] > SLOPE_LIMIT_COEFF_MAX ||
+			chip->dt.slope_limit_coeffs[i] < 0) {
+			pr_err("Incorrect slope limit coefficient\n");
+			return -EINVAL;
+		}
+	}
+
+	chip->slope_limit_en = true;
+	return 0;
+}
+
+static int fg_parse_ki_coefficients(struct fg_chip *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	int rc, i;
+
+	rc = of_property_count_elems_of_size(node, "qcom,ki-coeff-soc-dischg",
+		sizeof(u32));
+	if (rc != KI_COEFF_SOC_LEVELS)
+		return 0;
+
+	rc = of_property_read_u32_array(node, "qcom,ki-coeff-soc-dischg",
+			chip->dt.ki_coeff_soc, KI_COEFF_SOC_LEVELS);
+	if (rc < 0) {
+		pr_err("Error in reading ki-coeff-soc-dischg, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_count_elems_of_size(node, "qcom,ki-coeff-med-dischg",
+		sizeof(u32));
+	if (rc != KI_COEFF_SOC_LEVELS)
+		return 0;
+
+	rc = of_property_read_u32_array(node, "qcom,ki-coeff-med-dischg",
+			chip->dt.ki_coeff_med_dischg, KI_COEFF_SOC_LEVELS);
+	if (rc < 0) {
+		pr_err("Error in reading ki-coeff-med-dischg, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = of_property_count_elems_of_size(node, "qcom,ki-coeff-hi-dischg",
+		sizeof(u32));
+	if (rc != KI_COEFF_SOC_LEVELS)
+		return 0;
+
+	rc = of_property_read_u32_array(node, "qcom,ki-coeff-hi-dischg",
+			chip->dt.ki_coeff_hi_dischg, KI_COEFF_SOC_LEVELS);
+	if (rc < 0) {
+		pr_err("Error in reading ki-coeff-hi-dischg, rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	for (i = 0; i < KI_COEFF_SOC_LEVELS; i++) {
+		if (chip->dt.ki_coeff_soc[i] < 0 ||
+			chip->dt.ki_coeff_soc[i] > FULL_CAPACITY) {
+			pr_err("Error in ki_coeff_soc_dischg values\n");
+			return -EINVAL;
+		}
+
+		if (chip->dt.ki_coeff_med_dischg[i] < 0 ||
+			chip->dt.ki_coeff_med_dischg[i] > KI_COEFF_MAX) {
+			pr_err("Error in ki_coeff_med_dischg values\n");
+			return -EINVAL;
+		}
+
+		if (chip->dt.ki_coeff_med_dischg[i] < 0 ||
+			chip->dt.ki_coeff_med_dischg[i] > KI_COEFF_MAX) {
+			pr_err("Error in ki_coeff_med_dischg values\n");
+			return -EINVAL;
+		}
+	}
+	chip->ki_coeff_dischg_en = true;
+	return 0;
+}
+
+#define DEFAULT_CUTOFF_VOLT_MV		3200
+#define DEFAULT_EMPTY_VOLT_MV		2800
+#define DEFAULT_RECHARGE_VOLT_MV	4250
+#define DEFAULT_CHG_TERM_CURR_MA	100
+#define DEFAULT_SYS_TERM_CURR_MA	-125
+#define DEFAULT_DELTA_SOC_THR		1
+#define DEFAULT_RECHARGE_SOC_THR	95
+#define DEFAULT_BATT_TEMP_COLD		0
+#define DEFAULT_BATT_TEMP_COOL		5
+#define DEFAULT_BATT_TEMP_WARM		45
+#define DEFAULT_BATT_TEMP_HOT		50
+#define DEFAULT_CL_START_SOC		15
+#define DEFAULT_CL_MIN_TEMP_DECIDEGC	150
+#define DEFAULT_CL_MAX_TEMP_DECIDEGC	450
+#define DEFAULT_CL_MAX_INC_DECIPERC	5
+#define DEFAULT_CL_MAX_DEC_DECIPERC	100
+#define DEFAULT_CL_MIN_LIM_DECIPERC	0
+#define DEFAULT_CL_MAX_LIM_DECIPERC	0
+#define BTEMP_DELTA_LOW			2
+#define BTEMP_DELTA_HIGH		10
+#define DEFAULT_ESR_FLT_TEMP_DECIDEGC	100
+#define DEFAULT_ESR_TIGHT_FLT_UPCT	3907
+#define DEFAULT_ESR_BROAD_FLT_UPCT	99610
+#define DEFAULT_ESR_TIGHT_LT_FLT_UPCT	48829
+#define DEFAULT_ESR_BROAD_LT_FLT_UPCT	148438
+#define DEFAULT_ESR_CLAMP_MOHMS		20
+static int fg_parse_dt(struct fg_chip *chip)
+{
+	struct device_node *child, *revid_node, *node = chip->dev->of_node;
+	u32 base, temp;
+	u8 subtype;
+	int rc;
+
+	if (!node)  {
+		dev_err(chip->dev, "device tree node missing\n");
+		return -ENXIO;
+	}
+
+	revid_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+	if (!revid_node) {
+		pr_err("Missing qcom,pmic-revid property - driver failed\n");
+		return -EINVAL;
+	}
+
+	chip->pmic_rev_id = get_revid_data(revid_node);
+	if (IS_ERR_OR_NULL(chip->pmic_rev_id)) {
+		pr_err("Unable to get pmic_revid rc=%ld\n",
+			PTR_ERR(chip->pmic_rev_id));
+		/*
+		 * the revid peripheral must be registered, any failure
+		 * here only indicates that the rev-id module has not
+		 * probed yet.
+		 */
+		return -EPROBE_DEFER;
+	}
+
+	pr_debug("PMIC subtype %d Digital major %d\n",
+		chip->pmic_rev_id->pmic_subtype, chip->pmic_rev_id->rev4);
+
+	switch (chip->pmic_rev_id->pmic_subtype) {
+	case PMI8998_SUBTYPE:
+		if (chip->pmic_rev_id->rev4 < PMI8998_V2P0_REV4) {
+			chip->sp = pmi8998_v1_sram_params;
+			chip->alg_flags = pmi8998_v1_alg_flags;
+			chip->wa_flags |= PMI8998_V1_REV_WA;
+		} else if (chip->pmic_rev_id->rev4 == PMI8998_V2P0_REV4) {
+			chip->sp = pmi8998_v2_sram_params;
+			chip->alg_flags = pmi8998_v2_alg_flags;
+		} else {
+			return -EINVAL;
+		}
+		break;
+	case PM660_SUBTYPE:
+		chip->sp = pmi8998_v2_sram_params;
+		chip->alg_flags = pmi8998_v2_alg_flags;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	if (of_get_available_child_count(node) == 0) {
+		dev_err(chip->dev, "No child nodes specified!\n");
+		return -ENXIO;
+	}
+
+	for_each_available_child_of_node(node, child) {
+		rc = of_property_read_u32(child, "reg", &base);
+		if (rc < 0) {
+			dev_err(chip->dev, "reg not specified in node %s, rc=%d\n",
+				child->full_name, rc);
+			return rc;
+		}
+
+		rc = fg_read(chip, base + PERPH_SUBTYPE_REG, &subtype, 1);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't read subtype for base %d, rc=%d\n",
+				base, rc);
+			return rc;
+		}
+
+		switch (subtype) {
+		case FG_BATT_SOC_PMI8998:
+			chip->batt_soc_base = base;
+			break;
+		case FG_BATT_INFO_PMI8998:
+			chip->batt_info_base = base;
+			break;
+		case FG_MEM_INFO_PMI8998:
+			chip->mem_if_base = base;
+			break;
+		default:
+			dev_err(chip->dev, "Invalid peripheral subtype 0x%x\n",
+				subtype);
+			return -ENXIO;
+		}
+	}
+
+	rc = of_property_read_u32(node, "qcom,rradc-base", &base);
+	if (rc < 0) {
+		dev_err(chip->dev, "rradc-base not specified, rc=%d\n", rc);
+		return rc;
+	}
+	chip->rradc_base = base;
+
+	/* Read all the optional properties below */
+	rc = of_property_read_u32(node, "qcom,fg-cutoff-voltage", &temp);
+	if (rc < 0)
+		chip->dt.cutoff_volt_mv = DEFAULT_CUTOFF_VOLT_MV;
+	else
+		chip->dt.cutoff_volt_mv = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-empty-voltage", &temp);
+	if (rc < 0)
+		chip->dt.empty_volt_mv = DEFAULT_EMPTY_VOLT_MV;
+	else
+		chip->dt.empty_volt_mv = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-vbatt-low-thr", &temp);
+	if (rc < 0)
+		chip->dt.vbatt_low_thr_mv = -EINVAL;
+	else
+		chip->dt.vbatt_low_thr_mv = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-chg-term-current", &temp);
+	if (rc < 0)
+		chip->dt.chg_term_curr_ma = DEFAULT_CHG_TERM_CURR_MA;
+	else
+		chip->dt.chg_term_curr_ma = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-sys-term-current", &temp);
+	if (rc < 0)
+		chip->dt.sys_term_curr_ma = DEFAULT_SYS_TERM_CURR_MA;
+	else
+		chip->dt.sys_term_curr_ma = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-delta-soc-thr", &temp);
+	if (rc < 0)
+		chip->dt.delta_soc_thr = DEFAULT_DELTA_SOC_THR;
+	else
+		chip->dt.delta_soc_thr = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-recharge-soc-thr", &temp);
+	if (rc < 0)
+		chip->dt.recharge_soc_thr = DEFAULT_RECHARGE_SOC_THR;
+	else
+		chip->dt.recharge_soc_thr = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-recharge-voltage", &temp);
+	if (rc < 0)
+		chip->dt.recharge_volt_thr_mv = DEFAULT_RECHARGE_VOLT_MV;
+	else
+		chip->dt.recharge_volt_thr_mv = temp;
+
+	chip->dt.auto_recharge_soc = of_property_read_bool(node,
+					"qcom,fg-auto-recharge-soc");
+
+	rc = of_property_read_u32(node, "qcom,fg-rsense-sel", &temp);
+	if (rc < 0)
+		chip->dt.rsense_sel = SRC_SEL_BATFET_SMB;
+	else
+		chip->dt.rsense_sel = (u8)temp & SOURCE_SELECT_MASK;
+
+	chip->dt.jeita_thresholds[JEITA_COLD] = DEFAULT_BATT_TEMP_COLD;
+	chip->dt.jeita_thresholds[JEITA_COOL] = DEFAULT_BATT_TEMP_COOL;
+	chip->dt.jeita_thresholds[JEITA_WARM] = DEFAULT_BATT_TEMP_WARM;
+	chip->dt.jeita_thresholds[JEITA_HOT] = DEFAULT_BATT_TEMP_HOT;
+	if (of_property_count_elems_of_size(node, "qcom,fg-jeita-thresholds",
+		sizeof(u32)) == NUM_JEITA_LEVELS) {
+		rc = of_property_read_u32_array(node,
+				"qcom,fg-jeita-thresholds",
+				chip->dt.jeita_thresholds, NUM_JEITA_LEVELS);
+		if (rc < 0)
+			pr_warn("Error reading Jeita thresholds, default values will be used rc:%d\n",
+				rc);
+	}
+
+	if (of_property_count_elems_of_size(node,
+		"qcom,battery-thermal-coefficients",
+		sizeof(u8)) == BATT_THERM_NUM_COEFFS) {
+		rc = of_property_read_u8_array(node,
+				"qcom,battery-thermal-coefficients",
+				chip->dt.batt_therm_coeffs,
+				BATT_THERM_NUM_COEFFS);
+		if (rc < 0)
+			pr_warn("Error reading battery thermal coefficients, rc:%d\n",
+				rc);
+	}
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-timer-charging", &temp);
+	if (rc < 0)
+		chip->dt.esr_timer_charging = -EINVAL;
+	else
+		chip->dt.esr_timer_charging = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-timer-awake", &temp);
+	if (rc < 0)
+		chip->dt.esr_timer_awake = -EINVAL;
+	else
+		chip->dt.esr_timer_awake = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-timer-asleep", &temp);
+	if (rc < 0)
+		chip->dt.esr_timer_asleep = -EINVAL;
+	else
+		chip->dt.esr_timer_asleep = temp;
+
+	chip->cyc_ctr.en = of_property_read_bool(node, "qcom,cycle-counter-en");
+	if (chip->cyc_ctr.en)
+		chip->cyc_ctr.id = 1;
+
+	chip->dt.force_load_profile = of_property_read_bool(node,
+					"qcom,fg-force-load-profile");
+
+	rc = of_property_read_u32(node, "qcom,cl-start-capacity", &temp);
+	if (rc < 0)
+		chip->dt.cl_start_soc = DEFAULT_CL_START_SOC;
+	else
+		chip->dt.cl_start_soc = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-min-temp", &temp);
+	if (rc < 0)
+		chip->dt.cl_min_temp = DEFAULT_CL_MIN_TEMP_DECIDEGC;
+	else
+		chip->dt.cl_min_temp = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-max-temp", &temp);
+	if (rc < 0)
+		chip->dt.cl_max_temp = DEFAULT_CL_MAX_TEMP_DECIDEGC;
+	else
+		chip->dt.cl_max_temp = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-max-increment", &temp);
+	if (rc < 0)
+		chip->dt.cl_max_cap_inc = DEFAULT_CL_MAX_INC_DECIPERC;
+	else
+		chip->dt.cl_max_cap_inc = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-max-decrement", &temp);
+	if (rc < 0)
+		chip->dt.cl_max_cap_dec = DEFAULT_CL_MAX_DEC_DECIPERC;
+	else
+		chip->dt.cl_max_cap_dec = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-min-limit", &temp);
+	if (rc < 0)
+		chip->dt.cl_min_cap_limit = DEFAULT_CL_MIN_LIM_DECIPERC;
+	else
+		chip->dt.cl_min_cap_limit = temp;
+
+	rc = of_property_read_u32(node, "qcom,cl-max-limit", &temp);
+	if (rc < 0)
+		chip->dt.cl_max_cap_limit = DEFAULT_CL_MAX_LIM_DECIPERC;
+	else
+		chip->dt.cl_max_cap_limit = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-jeita-hyst-temp", &temp);
+	if (rc < 0)
+		chip->dt.jeita_hyst_temp = -EINVAL;
+	else
+		chip->dt.jeita_hyst_temp = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-batt-temp-delta", &temp);
+	if (rc < 0)
+		chip->dt.batt_temp_delta = -EINVAL;
+	else if (temp > BTEMP_DELTA_LOW && temp <= BTEMP_DELTA_HIGH)
+		chip->dt.batt_temp_delta = temp;
+
+	chip->dt.hold_soc_while_full = of_property_read_bool(node,
+					"qcom,hold-soc-while-full");
+
+	rc = fg_parse_ki_coefficients(chip);
+	if (rc < 0)
+		pr_err("Error in parsing Ki coefficients, rc=%d\n", rc);
+
+	rc = of_property_read_u32(node, "qcom,fg-rconn-mohms", &temp);
+	if (rc < 0)
+		chip->dt.rconn_mohms = -EINVAL;
+	else
+		chip->dt.rconn_mohms = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-filter-switch-temp",
+			&temp);
+	if (rc < 0)
+		chip->dt.esr_flt_switch_temp = DEFAULT_ESR_FLT_TEMP_DECIDEGC;
+	else
+		chip->dt.esr_flt_switch_temp = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-tight-filter-micro-pct",
+			&temp);
+	if (rc < 0)
+		chip->dt.esr_tight_flt_upct = DEFAULT_ESR_TIGHT_FLT_UPCT;
+	else
+		chip->dt.esr_tight_flt_upct = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-broad-filter-micro-pct",
+			&temp);
+	if (rc < 0)
+		chip->dt.esr_broad_flt_upct = DEFAULT_ESR_BROAD_FLT_UPCT;
+	else
+		chip->dt.esr_broad_flt_upct = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-tight-lt-filter-micro-pct",
+			&temp);
+	if (rc < 0)
+		chip->dt.esr_tight_lt_flt_upct = DEFAULT_ESR_TIGHT_LT_FLT_UPCT;
+	else
+		chip->dt.esr_tight_lt_flt_upct = temp;
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-broad-lt-filter-micro-pct",
+			&temp);
+	if (rc < 0)
+		chip->dt.esr_broad_lt_flt_upct = DEFAULT_ESR_BROAD_LT_FLT_UPCT;
+	else
+		chip->dt.esr_broad_lt_flt_upct = temp;
+
+	rc = fg_parse_slope_limit_coefficients(chip);
+	if (rc < 0)
+		pr_err("Error in parsing slope limit coeffs, rc=%d\n", rc);
+
+	rc = of_property_read_u32(node, "qcom,fg-esr-clamp-mohms", &temp);
+	if (rc < 0)
+		chip->dt.esr_clamp_mohms = DEFAULT_ESR_CLAMP_MOHMS;
+	else
+		chip->dt.esr_clamp_mohms = temp;
+
+	return 0;
+}
+
+static void fg_cleanup(struct fg_chip *chip)
+{
+	power_supply_unreg_notifier(&chip->nb);
+	debugfs_remove_recursive(chip->dfs_root);
+	if (chip->awake_votable)
+		destroy_votable(chip->awake_votable);
+
+	if (chip->batt_id_chan)
+		iio_channel_release(chip->batt_id_chan);
+
+	dev_set_drvdata(chip->dev, NULL);
+}
+
+static int fg_gen3_probe(struct platform_device *pdev)
+{
+	struct fg_chip *chip;
+	struct power_supply_config fg_psy_cfg;
+	int rc, msoc, volt_uv, batt_temp;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->dev = &pdev->dev;
+	chip->debug_mask = &fg_gen3_debug_mask;
+	chip->irqs = fg_irqs;
+	chip->charge_status = -EINVAL;
+	chip->prev_charge_status = -EINVAL;
+	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+	if (!chip->regmap) {
+		dev_err(chip->dev, "Parent regmap is unavailable\n");
+		return -ENXIO;
+	}
+
+	chip->batt_id_chan = iio_channel_get(chip->dev, "rradc_batt_id");
+	if (IS_ERR(chip->batt_id_chan)) {
+		if (PTR_ERR(chip->batt_id_chan) != -EPROBE_DEFER)
+			pr_err("batt_id_chan unavailable %ld\n",
+				PTR_ERR(chip->batt_id_chan));
+		rc = PTR_ERR(chip->batt_id_chan);
+		chip->batt_id_chan = NULL;
+		return rc;
+	}
+
+	chip->awake_votable = create_votable("FG_WS", VOTE_SET_ANY, fg_awake_cb,
+					chip);
+	if (IS_ERR(chip->awake_votable)) {
+		rc = PTR_ERR(chip->awake_votable);
+		return rc;
+	}
+
+	rc = fg_parse_dt(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Error in reading DT parameters, rc:%d\n",
+			rc);
+		goto exit;
+	}
+
+	mutex_init(&chip->bus_lock);
+	mutex_init(&chip->sram_rw_lock);
+	mutex_init(&chip->cyc_ctr.lock);
+	mutex_init(&chip->cl.lock);
+	mutex_init(&chip->batt_avg_lock);
+	mutex_init(&chip->charge_full_lock);
+	init_completion(&chip->soc_update);
+	init_completion(&chip->soc_ready);
+	INIT_DELAYED_WORK(&chip->profile_load_work, profile_load_work);
+	INIT_WORK(&chip->status_change_work, status_change_work);
+	INIT_WORK(&chip->cycle_count_work, cycle_count_work);
+	INIT_DELAYED_WORK(&chip->batt_avg_work, batt_avg_work);
+	INIT_DELAYED_WORK(&chip->sram_dump_work, sram_dump_work);
+
+	rc = fg_get_batt_id(chip);
+	if (rc < 0) {
+		pr_err("Error in getting battery id, rc:%d\n", rc);
+		return rc;
+	}
+
+	rc = fg_get_batt_profile(chip);
+	if (rc < 0) {
+		chip->soc_reporting_ready = true;
+		pr_warn("profile for batt_id=%dKOhms not found..using OTP, rc:%d\n",
+			chip->batt_id_ohms / 1000, rc);
+	}
+
+	rc = fg_memif_init(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Error in initializing FG_MEMIF, rc:%d\n",
+			rc);
+		goto exit;
+	}
+
+	rc = fg_hw_init(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Error in initializing FG hardware, rc:%d\n",
+			rc);
+		goto exit;
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	/* Register the power supply */
+	fg_psy_cfg.drv_data = chip;
+	fg_psy_cfg.of_node = NULL;
+	fg_psy_cfg.supplied_to = NULL;
+	fg_psy_cfg.num_supplicants = 0;
+	chip->fg_psy = devm_power_supply_register(chip->dev, &fg_psy_desc,
+			&fg_psy_cfg);
+	if (IS_ERR(chip->fg_psy)) {
+		pr_err("failed to register fg_psy rc = %ld\n",
+				PTR_ERR(chip->fg_psy));
+		goto exit;
+	}
+
+	chip->nb.notifier_call = fg_notifier_cb;
+	rc = power_supply_reg_notifier(&chip->nb);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier rc = %d\n", rc);
+		goto exit;
+	}
+
+	rc = fg_register_interrupts(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Error in registering interrupts, rc:%d\n",
+			rc);
+		goto exit;
+	}
+
+	/* Keep SOC_UPDATE irq disabled until we require it */
+	if (fg_irqs[SOC_UPDATE_IRQ].irq)
+		disable_irq_nosync(fg_irqs[SOC_UPDATE_IRQ].irq);
+
+	/* Keep BSOC_DELTA_IRQ irq disabled until we require it */
+	if (fg_irqs[BSOC_DELTA_IRQ].irq) {
+		disable_irq_wake(fg_irqs[BSOC_DELTA_IRQ].irq);
+		disable_irq_nosync(fg_irqs[BSOC_DELTA_IRQ].irq);
+		chip->bsoc_delta_irq_en = false;
+	}
+
+	rc = fg_debugfs_create(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Error in creating debugfs entries, rc:%d\n",
+			rc);
+		goto exit;
+	}
+
+	rc = fg_get_battery_voltage(chip, &volt_uv);
+	if (!rc)
+		rc = fg_get_prop_capacity(chip, &msoc);
+
+	if (!rc)
+		rc = fg_get_battery_temp(chip, &batt_temp);
+
+	if (!rc) {
+		pr_info("battery SOC:%d voltage: %duV temp: %d id: %dKOhms\n",
+			msoc, volt_uv, batt_temp, chip->batt_id_ohms / 1000);
+		rc = fg_esr_filter_config(chip, batt_temp);
+		if (rc < 0)
+			pr_err("Error in configuring ESR filter rc:%d\n", rc);
+	}
+
+	device_init_wakeup(chip->dev, true);
+	if (chip->profile_available)
+		schedule_delayed_work(&chip->profile_load_work, 0);
+
+	pr_debug("FG GEN3 driver probed successfully\n");
+	return 0;
+exit:
+	fg_cleanup(chip);
+	return rc;
+}
+
+static int fg_gen3_suspend(struct device *dev)
+{
+	struct fg_chip *chip = dev_get_drvdata(dev);
+	int rc;
+
+	if (chip->dt.esr_timer_awake > 0 && chip->dt.esr_timer_asleep > 0) {
+		rc = fg_set_esr_timer(chip, chip->dt.esr_timer_asleep, false,
+				      FG_IMA_NO_WLOCK);
+		if (rc < 0) {
+			pr_err("Error in setting ESR timer during suspend, rc=%d\n",
+			       rc);
+			return rc;
+		}
+	}
+
+	cancel_delayed_work_sync(&chip->batt_avg_work);
+	if (fg_sram_dump)
+		cancel_delayed_work_sync(&chip->sram_dump_work);
+	return 0;
+}
+
+static int fg_gen3_resume(struct device *dev)
+{
+	struct fg_chip *chip = dev_get_drvdata(dev);
+	int rc;
+
+	if (chip->dt.esr_timer_awake > 0 && chip->dt.esr_timer_asleep > 0) {
+		rc = fg_set_esr_timer(chip, chip->dt.esr_timer_awake, false,
+				      FG_IMA_DEFAULT);
+		if (rc < 0) {
+			pr_err("Error in setting ESR timer during resume, rc=%d\n",
+			       rc);
+			return rc;
+		}
+	}
+
+	fg_circ_buf_clr(&chip->ibatt_circ_buf);
+	fg_circ_buf_clr(&chip->vbatt_circ_buf);
+	schedule_delayed_work(&chip->batt_avg_work, 0);
+	if (fg_sram_dump)
+		schedule_delayed_work(&chip->sram_dump_work,
+				msecs_to_jiffies(fg_sram_dump_period_ms));
+	return 0;
+}
+
+static const struct dev_pm_ops fg_gen3_pm_ops = {
+	.suspend	= fg_gen3_suspend,
+	.resume		= fg_gen3_resume,
+};
+
+static int fg_gen3_remove(struct platform_device *pdev)
+{
+	struct fg_chip *chip = dev_get_drvdata(&pdev->dev);
+
+	fg_cleanup(chip);
+	return 0;
+}
+
+static const struct of_device_id fg_gen3_match_table[] = {
+	{.compatible = FG_GEN3_DEV_NAME},
+	{},
+};
+
+static struct platform_driver fg_gen3_driver = {
+	.driver = {
+		.name = FG_GEN3_DEV_NAME,
+		.owner = THIS_MODULE,
+		.of_match_table = fg_gen3_match_table,
+		.pm		= &fg_gen3_pm_ops,
+	},
+	.probe		= fg_gen3_probe,
+	.remove		= fg_gen3_remove,
+};
+
+static int __init fg_gen3_init(void)
+{
+	return platform_driver_register(&fg_gen3_driver);
+}
+
+static void __exit fg_gen3_exit(void)
+{
+	return platform_driver_unregister(&fg_gen3_driver);
+}
+
+module_init(fg_gen3_init);
+module_exit(fg_gen3_exit);
+
+MODULE_DESCRIPTION("QPNP Fuel gauge GEN3 driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" FG_GEN3_DEV_NAME);
diff --git a/drivers/power/supply/qcom/qpnp-qnovo.c b/drivers/power/supply/qcom/qpnp-qnovo.c
new file mode 100644
index 0000000..cbfab30
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-qnovo.c
@@ -0,0 +1,1456 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/interrupt.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "pmic-voter.h"
+
+#define QNOVO_REVISION1		0x00
+#define QNOVO_REVISION2		0x01
+#define QNOVO_PERPH_TYPE	0x04
+#define QNOVO_PERPH_SUBTYPE	0x05
+#define QNOVO_PTTIME_STS	0x07
+#define QNOVO_PTRAIN_STS	0x08
+#define QNOVO_ERROR_STS		0x09
+#define QNOVO_ERROR_BIT		BIT(0)
+#define QNOVO_INT_RT_STS	0x10
+#define QNOVO_INT_SET_TYPE	0x11
+#define QNOVO_INT_POLARITY_HIGH	0x12
+#define QNOVO_INT_POLARITY_LOW	0x13
+#define QNOVO_INT_LATCHED_CLR	0x14
+#define QNOVO_INT_EN_SET	0x15
+#define QNOVO_INT_EN_CLR	0x16
+#define QNOVO_INT_LATCHED_STS	0x18
+#define QNOVO_INT_PENDING_STS	0x19
+#define QNOVO_INT_MID_SEL	0x1A
+#define QNOVO_INT_PRIORITY	0x1B
+#define QNOVO_PE_CTRL		0x40
+#define QNOVO_PREST1_CTRL	0x41
+#define QNOVO_PPULS1_LSB_CTRL	0x42
+#define QNOVO_PPULS1_MSB_CTRL	0x43
+#define QNOVO_NREST1_CTRL	0x44
+#define QNOVO_NPULS1_CTRL	0x45
+#define QNOVO_PPCNT_CTRL	0x46
+#define QNOVO_VLIM1_LSB_CTRL	0x47
+#define QNOVO_VLIM1_MSB_CTRL	0x48
+#define QNOVO_PTRAIN_EN		0x49
+#define QNOVO_PTRAIN_EN_BIT	BIT(0)
+#define QNOVO_PE_CTRL2		0x4A
+#define QNOVO_PREST2_LSB_CTRL	0x50
+#define QNOVO_PREST2_MSB_CTRL	0x51
+#define QNOVO_PPULS2_LSB_CTRL	0x52
+#define QNOVO_PPULS2_MSB_CTRL	0x53
+#define QNOVO_NREST2_CTRL	0x54
+#define QNOVO_NPULS2_CTRL	0x55
+#define QNOVO_VLIM2_LSB_CTRL	0x56
+#define QNOVO_VLIM2_MSB_CTRL	0x57
+#define QNOVO_PVOLT1_LSB	0x60
+#define QNOVO_PVOLT1_MSB	0x61
+#define QNOVO_PCUR1_LSB		0x62
+#define QNOVO_PCUR1_MSB		0x63
+#define QNOVO_PVOLT2_LSB	0x70
+#define QNOVO_PVOLT2_MSB	0x71
+#define QNOVO_RVOLT2_LSB	0x72
+#define QNOVO_RVOLT2_MSB	0x73
+#define QNOVO_PCUR2_LSB		0x74
+#define QNOVO_PCUR2_MSB		0x75
+#define QNOVO_SCNT		0x80
+#define QNOVO_VMAX_LSB		0x90
+#define QNOVO_VMAX_MSB		0x91
+#define QNOVO_SNUM		0x92
+
+/* Registers ending in 0 imply external rsense */
+#define QNOVO_IADC_OFFSET_0	0xA0
+#define QNOVO_IADC_OFFSET_1	0xA1
+#define QNOVO_IADC_GAIN_0	0xA2
+#define QNOVO_IADC_GAIN_1	0xA3
+#define QNOVO_VADC_OFFSET	0xA4
+#define QNOVO_VADC_GAIN		0xA5
+#define QNOVO_IADC_GAIN_2	0xA6
+#define QNOVO_SPARE		0xA7
+#define QNOVO_STRM_CTRL		0xA8
+#define QNOVO_IADC_OFFSET_OVR_VAL	0xA9
+#define QNOVO_IADC_OFFSET_OVR		0xAA
+#define QNOVO_DISABLE_CHARGING		0xAB
+
+#define QNOVO_TR_IADC_OFFSET_0	0xF1
+#define QNOVO_TR_IADC_OFFSET_1	0xF2
+
+#define DRV_MAJOR_VERSION	1
+#define DRV_MINOR_VERSION	0
+
+#define IADC_LSB_NA	2441400
+#define VADC_LSB_NA	1220700
+#define GAIN_LSB_FACTOR	976560
+
+#define USER_VOTER		"user_voter"
+#define OK_TO_QNOVO_VOTER	"ok_to_qnovo_voter"
+
+#define QNOVO_VOTER		"qnovo_voter"
+
+struct qnovo_dt_props {
+	bool			external_rsense;
+	struct device_node	*revid_dev_node;
+};
+
+enum {
+	QNOVO_NO_ERR_STS_BIT		= BIT(0),
+};
+
+struct chg_props {
+	bool		charging;
+	bool		usb_online;
+	bool		dc_online;
+};
+
+struct chg_status {
+	bool		ok_to_qnovo;
+};
+
+struct qnovo {
+	int			base;
+	struct mutex		write_lock;
+	struct regmap		*regmap;
+	struct qnovo_dt_props	dt;
+	struct device		*dev;
+	struct votable		*disable_votable;
+	struct class		qnovo_class;
+	struct pmic_revid_data	*pmic_rev_id;
+	u32			wa_flags;
+	s64			external_offset_nA;
+	s64			internal_offset_nA;
+	s64			offset_nV;
+	s64			external_i_gain_mega;
+	s64			internal_i_gain_mega;
+	s64			v_gain_mega;
+	struct notifier_block	nb;
+	struct power_supply	*batt_psy;
+	struct power_supply	*usb_psy;
+	struct power_supply	*dc_psy;
+	struct chg_props	cp;
+	struct chg_status	cs;
+	struct work_struct	status_change_work;
+	int			fv_uV_request;
+	int			fcc_uA_request;
+};
+
+static int debug_mask;
+module_param_named(debug_mask, debug_mask, int, 0600);
+
+#define qnovo_dbg(chip, reason, fmt, ...)				\
+	do {								\
+		if (debug_mask & (reason))				\
+			dev_info(chip->dev, fmt, ##__VA_ARGS__);	\
+		else							\
+			dev_dbg(chip->dev, fmt, ##__VA_ARGS__);		\
+	} while (0)
+
+static bool is_secure(struct qnovo *chip, int addr)
+{
+	/* assume everything above 0x40 is secure */
+	return (bool)(addr >= 0x40);
+}
+
+static int qnovo_read(struct qnovo *chip, u16 addr, u8 *buf, int len)
+{
+	return regmap_bulk_read(chip->regmap, chip->base + addr, buf, len);
+}
+
+static int qnovo_masked_write(struct qnovo *chip, u16 addr, u8 mask, u8 val)
+{
+	int rc = 0;
+
+	mutex_lock(&chip->write_lock);
+	if (is_secure(chip, addr)) {
+		rc = regmap_write(chip->regmap,
+				((chip->base + addr) & ~(0xFF)) | 0xD0, 0xA5);
+		if (rc < 0)
+			goto unlock;
+	}
+
+	rc = regmap_update_bits(chip->regmap, chip->base + addr, mask, val);
+
+unlock:
+	mutex_unlock(&chip->write_lock);
+	return rc;
+}
+
+static int qnovo_write(struct qnovo *chip, u16 addr, u8 *buf, int len)
+{
+	int i, rc = 0;
+	bool is_start_secure, is_end_secure;
+
+	is_start_secure = is_secure(chip, addr);
+	is_end_secure = is_secure(chip, addr + len);
+
+	if (!is_start_secure && !is_end_secure) {
+		mutex_lock(&chip->write_lock);
+		rc = regmap_bulk_write(chip->regmap, chip->base + addr,
+					buf, len);
+		goto unlock;
+	}
+
+	mutex_lock(&chip->write_lock);
+	for (i = addr; i < addr + len; i++) {
+		if (is_secure(chip, i)) {
+			rc = regmap_write(chip->regmap,
+				((chip->base + i) & ~(0xFF)) | 0xD0, 0xA5);
+			if (rc < 0)
+				goto unlock;
+		}
+		rc = regmap_write(chip->regmap, chip->base + i, buf[i - addr]);
+		if (rc < 0)
+			goto unlock;
+	}
+
+unlock:
+	mutex_unlock(&chip->write_lock);
+	return rc;
+}
+
+static bool is_batt_available(struct qnovo *chip)
+{
+	if (!chip->batt_psy)
+		chip->batt_psy = power_supply_get_by_name("battery");
+
+	if (!chip->batt_psy)
+		return false;
+
+	return true;
+}
+
+static int qnovo_batt_psy_update(struct qnovo *chip, bool disable)
+{
+	union power_supply_propval pval = {0};
+	int rc = 0;
+
+	if (!is_batt_available(chip))
+		return -EINVAL;
+
+	if (chip->fv_uV_request != -EINVAL) {
+		pval.intval = disable ? -EINVAL : chip->fv_uV_request;
+		rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
+			&pval);
+		if (rc < 0) {
+			pr_err("Couldn't set prop qnovo_fv rc = %d\n", rc);
+			return -EINVAL;
+		}
+	}
+
+	if (chip->fcc_uA_request != -EINVAL) {
+		pval.intval = disable ? -EINVAL : chip->fcc_uA_request;
+		rc = power_supply_set_property(chip->batt_psy,
+			POWER_SUPPLY_PROP_CURRENT_QNOVO,
+			&pval);
+		if (rc < 0) {
+			pr_err("Couldn't set prop qnovo_fcc rc = %d\n", rc);
+			return -EINVAL;
+		}
+	}
+
+	return rc;
+}
+
+static int qnovo_disable_cb(struct votable *votable, void *data, int disable,
+					const char *client)
+{
+	struct qnovo *chip = data;
+	int rc = 0;
+
+	if (disable) {
+		rc = qnovo_batt_psy_update(chip, true);
+		if (rc < 0)
+			return rc;
+	}
+
+	rc = qnovo_masked_write(chip, QNOVO_PTRAIN_EN, QNOVO_PTRAIN_EN_BIT,
+				 disable ? 0 : QNOVO_PTRAIN_EN_BIT);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't %s pulse train rc=%d\n",
+			disable ? "disable" : "enable", rc);
+		return rc;
+	}
+
+	if (!disable) {
+		rc = qnovo_batt_psy_update(chip, false);
+		if (rc < 0)
+			return rc;
+	}
+
+	return rc;
+}
+
+static int qnovo_parse_dt(struct qnovo *chip)
+{
+	struct device_node *node = chip->dev->of_node;
+	int rc;
+
+	if (!node) {
+		pr_err("device tree node missing\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32(node, "reg", &chip->base);
+	if (rc < 0) {
+		pr_err("Couldn't read base rc = %d\n", rc);
+		return rc;
+	}
+
+	chip->dt.external_rsense = of_property_read_bool(node,
+			"qcom,external-rsense");
+
+	chip->dt.revid_dev_node = of_parse_phandle(node, "qcom,pmic-revid", 0);
+	if (!chip->dt.revid_dev_node) {
+		pr_err("Missing qcom,pmic-revid property - driver failed\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int qnovo_check_chg_version(struct qnovo *chip)
+{
+	int rc;
+
+	chip->pmic_rev_id = get_revid_data(chip->dt.revid_dev_node);
+	if (IS_ERR(chip->pmic_rev_id)) {
+		rc = PTR_ERR(chip->pmic_rev_id);
+		if (rc != -EPROBE_DEFER)
+			pr_err("Unable to get pmic_revid rc=%d\n", rc);
+		return rc;
+	}
+
+	if ((chip->pmic_rev_id->pmic_subtype == PMI8998_SUBTYPE)
+		   && (chip->pmic_rev_id->rev4 < PMI8998_V2P0_REV4)) {
+		chip->wa_flags |= QNOVO_NO_ERR_STS_BIT;
+	}
+
+	return 0;
+}
+
+enum {
+	VER = 0,
+	OK_TO_QNOVO,
+	ENABLE,
+	FV_REQUEST,
+	FCC_REQUEST,
+	PE_CTRL_REG,
+	PE_CTRL2_REG,
+	PTRAIN_STS_REG,
+	INT_RT_STS_REG,
+	PREST1,
+	PPULS1,
+	NREST1,
+	NPULS1,
+	PPCNT,
+	VLIM1,
+	PVOLT1,
+	PCUR1,
+	PTTIME,
+	PREST2,
+	PPULS2,
+	NREST2,
+	NPULS2,
+	VLIM2,
+	PVOLT2,
+	RVOLT2,
+	PCUR2,
+	SCNT,
+	VMAX,
+	SNUM,
+	VBATT,
+	IBATT,
+	BATTTEMP,
+	BATTSOC,
+};
+
+struct param_info {
+	char	*name;
+	int	start_addr;
+	int	num_regs;
+	int	reg_to_unit_multiplier;
+	int	reg_to_unit_divider;
+	int	reg_to_unit_offset;
+	int	min_val;
+	int	max_val;
+	char	*units_str;
+};
+
+static struct param_info params[] = {
+	[FV_REQUEST] = {
+		.units_str		= "uV",
+	},
+	[FCC_REQUEST] = {
+		.units_str		= "uA",
+	},
+	[PE_CTRL_REG] = {
+		.name			= "CTRL_REG",
+		.start_addr		= QNOVO_PE_CTRL,
+		.num_regs		= 1,
+		.units_str		= "",
+	},
+	[PE_CTRL2_REG] = {
+		.name			= "PE_CTRL2_REG",
+		.start_addr		= QNOVO_PE_CTRL2,
+		.num_regs		= 1,
+		.units_str		= "",
+	},
+	[PTRAIN_STS_REG] = {
+		.name			= "PTRAIN_STS",
+		.start_addr		= QNOVO_PTRAIN_STS,
+		.num_regs		= 1,
+		.units_str		= "",
+	},
+	[INT_RT_STS_REG] = {
+		.name			= "INT_RT_STS",
+		.start_addr		= QNOVO_INT_RT_STS,
+		.num_regs		= 1,
+		.units_str		= "",
+	},
+	[PREST1] = {
+		.name			= "PREST1",
+		.start_addr		= QNOVO_PREST1_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 1275,
+		.units_str		= "mS",
+	},
+	[PPULS1] = {
+		.name			= "PPULS1",
+		.start_addr		= QNOVO_PPULS1_LSB_CTRL,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 1600, /* converts to uC */
+		.reg_to_unit_divider	= 1,
+		.min_val		= 0,
+		.max_val		= 104856000,
+		.units_str		= "uC",
+	},
+	[NREST1] = {
+		.name			= "NREST1",
+		.start_addr		= QNOVO_NREST1_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 1275,
+		.units_str		= "mS",
+	},
+	[NPULS1] = {
+		.name			= "NPULS1",
+		.start_addr		= QNOVO_NPULS1_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 1275,
+		.units_str		= "mS",
+	},
+	[PPCNT] = {
+		.name			= "PPCNT",
+		.start_addr		= QNOVO_PPCNT_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 1,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 0,
+		.max_val		= 255,
+		.units_str		= "pulses",
+	},
+	[VLIM1] = {
+		.name			= "VLIM1",
+		.start_addr		= QNOVO_VLIM1_LSB_CTRL,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 610350, /* converts to nV */
+		.reg_to_unit_divider	= 1,
+		.min_val		= 0,
+		.max_val		= 5000000,
+		.units_str		= "uV",
+	},
+	[PVOLT1] = {
+		.name			= "PVOLT1",
+		.start_addr		= QNOVO_PVOLT1_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 610350, /* converts to nV */
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uV",
+	},
+	[PCUR1] = {
+		.name			= "PCUR1",
+		.start_addr		= QNOVO_PCUR1_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 1220700, /* converts to nA */
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uA",
+	},
+	[PTTIME] = {
+		.name			= "PTTIME",
+		.start_addr		= QNOVO_PTTIME_STS,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 2,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 1275,
+		.units_str		= "S",
+	},
+	[PREST2] = {
+		.name			= "PREST2",
+		.start_addr		= QNOVO_PREST2_LSB_CTRL,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 327675,
+		.units_str		= "mS",
+	},
+	[PPULS2] = {
+		.name			= "PPULS2",
+		.start_addr		= QNOVO_PPULS2_LSB_CTRL,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 1600, /* converts to uC */
+		.reg_to_unit_divider	= 1,
+		.min_val		= 0,
+		.max_val		= 104856000,
+		.units_str		= "uC",
+	},
+	[NREST2] = {
+		.name			= "NREST2",
+		.start_addr		= QNOVO_NREST2_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.reg_to_unit_offset	= -5,
+		.min_val		= 5,
+		.max_val		= 1280,
+		.units_str		= "mS",
+	},
+	[NPULS2] = {
+		.name			= "NPULS2",
+		.start_addr		= QNOVO_NPULS2_CTRL,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 5,
+		.reg_to_unit_divider	= 1,
+		.min_val		= 5,
+		.max_val		= 1275,
+		.units_str		= "mS",
+	},
+	[VLIM2] = {
+		.name			= "VLIM1",
+		.start_addr		= QNOVO_VLIM2_LSB_CTRL,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 610350, /* converts to nV */
+		.reg_to_unit_divider	= 1,
+		.min_val		= 0,
+		.max_val		= 5000000,
+		.units_str		= "uV",
+	},
+	[PVOLT2] = {
+		.name			= "PVOLT2",
+		.start_addr		= QNOVO_PVOLT2_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 610350, /* converts to nV */
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uV",
+	},
+	[RVOLT2] = {
+		.name			= "RVOLT2",
+		.start_addr		= QNOVO_RVOLT2_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 610350,
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uV",
+	},
+	[PCUR2] = {
+		.name			= "PCUR2",
+		.start_addr		= QNOVO_PCUR2_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 1220700, /* converts to nA */
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uA",
+	},
+	[SCNT] = {
+		.name			= "SCNT",
+		.start_addr		= QNOVO_SCNT,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 1,
+		.reg_to_unit_divider	= 1,
+		.units_str		= "pulses",
+	},
+	[VMAX] = {
+		.name			= "VMAX",
+		.start_addr		= QNOVO_VMAX_LSB,
+		.num_regs		= 2,
+		.reg_to_unit_multiplier	= 814000, /* converts to nV */
+		.reg_to_unit_divider	= 1,
+		.units_str		= "uV",
+	},
+	[SNUM] = {
+		.name			= "SNUM",
+		.start_addr		= QNOVO_SNUM,
+		.num_regs		= 1,
+		.reg_to_unit_multiplier	= 1,
+		.reg_to_unit_divider	= 1,
+		.units_str		= "pulses",
+	},
+	[VBATT]	= {
+		.name			= "POWER_SUPPLY_PROP_VOLTAGE_NOW",
+		.start_addr		= POWER_SUPPLY_PROP_VOLTAGE_NOW,
+		.units_str		= "uV",
+	},
+	[IBATT]	= {
+		.name			= "POWER_SUPPLY_PROP_CURRENT_NOW",
+		.start_addr		= POWER_SUPPLY_PROP_CURRENT_NOW,
+		.units_str		= "uA",
+	},
+	[BATTTEMP] = {
+		.name			= "POWER_SUPPLY_PROP_TEMP",
+		.start_addr		= POWER_SUPPLY_PROP_TEMP,
+		.units_str		= "uV",
+	},
+	[BATTSOC] = {
+		.name			= "POWER_SUPPLY_PROP_CAPACITY",
+		.start_addr		= POWER_SUPPLY_PROP_CAPACITY,
+		.units_str		= "%",
+	},
+};
+
+static struct class_attribute qnovo_attributes[];
+
+static ssize_t version_show(struct class *c, struct class_attribute *attr,
+			char *buf)
+{
+	return snprintf(buf, PAGE_SIZE, "%d.%d\n",
+			DRV_MAJOR_VERSION, DRV_MINOR_VERSION);
+}
+
+static ssize_t ok_to_qnovo_show(struct class *c, struct class_attribute *attr,
+			char *buf)
+{
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+
+	return snprintf(buf, PAGE_SIZE, "%d\n", chip->cs.ok_to_qnovo);
+}
+
+static ssize_t enable_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	int val;
+
+	val = get_client_vote(chip->disable_votable, USER_VOTER);
+	val = !val;
+	return snprintf(ubuf, PAGE_SIZE, "%d\n", val);
+}
+
+static ssize_t enable_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	unsigned long val;
+	bool disable;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	disable = !val;
+
+	vote(chip->disable_votable, USER_VOTER, disable, 0);
+	return count;
+}
+
+static ssize_t val_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	int i = attr - qnovo_attributes;
+	int val = 0;
+
+	if (i == FV_REQUEST)
+		val = chip->fv_uV_request;
+
+	if (i == FCC_REQUEST)
+		val = chip->fcc_uA_request;
+
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n", val, params[i].units_str);
+}
+
+static ssize_t val_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	int i = attr - qnovo_attributes;
+	unsigned long val;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	if (i == FV_REQUEST)
+		chip->fv_uV_request = val;
+
+	if (i == FCC_REQUEST)
+		chip->fcc_uA_request = val;
+
+	return count;
+}
+
+static ssize_t reg_show(struct class *c, struct class_attribute *attr,
+			char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	u16 regval;
+	int rc;
+
+	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	regval = buf[1] << 8 | buf[0];
+
+	return snprintf(ubuf, PAGE_SIZE, "0x%04x%s\n",
+			regval, params[i].units_str);
+}
+
+static ssize_t reg_store(struct class *c, struct class_attribute *attr,
+			const char *ubuf, size_t count)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	unsigned long val;
+	int rc;
+
+	if (kstrtoul(ubuf, 16, &val))
+		return -EINVAL;
+
+	buf[0] = val & 0xFF;
+	buf[1] = (val >> 8) & 0xFF;
+
+	rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	return count;
+}
+
+static ssize_t time_show(struct class *c, struct class_attribute *attr,
+		char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	u16 regval;
+	int val;
+	int rc;
+
+	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	regval = buf[1] << 8 | buf[0];
+
+	val = ((regval * params[i].reg_to_unit_multiplier)
+			/ params[i].reg_to_unit_divider)
+		- params[i].reg_to_unit_offset;
+
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n", val, params[i].units_str);
+}
+
+static ssize_t time_store(struct class *c, struct class_attribute *attr,
+		       const char *ubuf, size_t count)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	u16 regval;
+	unsigned long val;
+	int rc;
+
+	if (kstrtoul(ubuf, 10, &val))
+		return -EINVAL;
+
+	if (val < params[i].min_val || val > params[i].max_val) {
+		pr_err("Out of Range %d%s for %s\n", (int)val,
+				params[i].units_str,
+				params[i].name);
+		return -ERANGE;
+	}
+
+	regval = (((int)val + params[i].reg_to_unit_offset)
+			* params[i].reg_to_unit_divider)
+		/ params[i].reg_to_unit_multiplier;
+	buf[0] = regval & 0xFF;
+	buf[1] = (regval >> 8) & 0xFF;
+
+	rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static ssize_t current_show(struct class *c, struct class_attribute *attr,
+				char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	int rc;
+	int comp_val_uA;
+	s64 regval_nA;
+	s64 gain, offset_nA, comp_val_nA;
+
+	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	regval_nA = buf[1] << 8 | buf[0];
+	regval_nA = div_s64(regval_nA * params[i].reg_to_unit_multiplier,
+					params[i].reg_to_unit_divider)
+			- params[i].reg_to_unit_offset;
+
+	if (chip->dt.external_rsense) {
+		offset_nA = chip->external_offset_nA;
+		gain = chip->external_i_gain_mega;
+	} else {
+		offset_nA = chip->internal_offset_nA;
+		gain = chip->internal_i_gain_mega;
+	}
+
+	comp_val_nA = div_s64(regval_nA * gain, 1000000) + offset_nA;
+	comp_val_uA = div_s64(comp_val_nA, 1000);
+
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n",
+			comp_val_uA, params[i].units_str);
+}
+
+static ssize_t voltage_show(struct class *c, struct class_attribute *attr,
+				char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	int rc;
+	int comp_val_uV;
+	s64 regval_nV;
+	s64 gain, offset_nV, comp_val_nV;
+
+	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	regval_nV = buf[1] << 8 | buf[0];
+	regval_nV = div_s64(regval_nV * params[i].reg_to_unit_multiplier,
+					params[i].reg_to_unit_divider)
+			- params[i].reg_to_unit_offset;
+
+	offset_nV = chip->offset_nV;
+	gain = chip->v_gain_mega;
+
+	comp_val_nV = div_s64(regval_nV * gain, 1000000) + offset_nV;
+	comp_val_uV = div_s64(comp_val_nV, 1000);
+
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n",
+				comp_val_uV, params[i].units_str);
+}
+
+static ssize_t voltage_store(struct class *c, struct class_attribute *attr,
+		       const char *ubuf, size_t count)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	int rc;
+	unsigned long val_uV;
+	s64 regval_nV;
+	s64 gain, offset_nV;
+
+	if (kstrtoul(ubuf, 10, &val_uV))
+		return -EINVAL;
+
+	if (val_uV < params[i].min_val || val_uV > params[i].max_val) {
+		pr_err("Out of Range %d%s for %s\n", (int)val_uV,
+				params[i].units_str,
+				params[i].name);
+		return -ERANGE;
+	}
+
+	offset_nV = chip->offset_nV;
+	gain = chip->v_gain_mega;
+
+	regval_nV = (s64)val_uV * 1000 - offset_nV;
+	regval_nV = div_s64(regval_nV * 1000000, gain);
+
+	regval_nV = div_s64((regval_nV + params[i].reg_to_unit_offset)
+			* params[i].reg_to_unit_divider,
+			params[i].reg_to_unit_multiplier);
+	buf[0] = regval_nV & 0xFF;
+	buf[1] = ((u64)regval_nV >> 8) & 0xFF;
+
+	rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static ssize_t coulomb_show(struct class *c, struct class_attribute *attr,
+				char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	int rc;
+	int comp_val_uC;
+	s64 regval_uC, gain;
+
+	rc = qnovo_read(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't read %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+	regval_uC = buf[1] << 8 | buf[0];
+	regval_uC = div_s64(regval_uC * params[i].reg_to_unit_multiplier,
+					params[i].reg_to_unit_divider)
+			- params[i].reg_to_unit_offset;
+
+	if (chip->dt.external_rsense)
+		gain = chip->external_i_gain_mega;
+	else
+		gain = chip->internal_i_gain_mega;
+
+	comp_val_uC = div_s64(regval_uC * gain, 1000000);
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n",
+			comp_val_uC, params[i].units_str);
+}
+
+static ssize_t coulomb_store(struct class *c, struct class_attribute *attr,
+		       const char *ubuf, size_t count)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	u8 buf[2] = {0, 0};
+	int rc;
+	unsigned long val_uC;
+	s64 regval;
+	s64 gain;
+
+	if (kstrtoul(ubuf, 10, &val_uC))
+		return -EINVAL;
+
+	if (val_uC < params[i].min_val || val_uC > params[i].max_val) {
+		pr_err("Out of Range %d%s for %s\n", (int)val_uC,
+				params[i].units_str,
+				params[i].name);
+		return -ERANGE;
+	}
+
+	if (chip->dt.external_rsense)
+		gain = chip->external_i_gain_mega;
+	else
+		gain = chip->internal_i_gain_mega;
+
+	regval = div_s64((s64)val_uC * 1000000, gain);
+
+	regval = div_s64((regval + params[i].reg_to_unit_offset)
+			* params[i].reg_to_unit_divider,
+			params[i].reg_to_unit_multiplier);
+
+	buf[0] = regval & 0xFF;
+	buf[1] = ((u64)regval >> 8) & 0xFF;
+
+	rc = qnovo_write(chip, params[i].start_addr, buf, params[i].num_regs);
+	if (rc < 0) {
+		pr_err("Couldn't write %s rc = %d\n", params[i].name, rc);
+		return -EINVAL;
+	}
+
+	return count;
+}
+
+static ssize_t batt_prop_show(struct class *c, struct class_attribute *attr,
+				char *ubuf)
+{
+	int i = attr - qnovo_attributes;
+	struct qnovo *chip = container_of(c, struct qnovo, qnovo_class);
+	int rc = -EINVAL;
+	int prop = params[i].start_addr;
+	union power_supply_propval pval = {0};
+
+	if (!is_batt_available(chip))
+		return -EINVAL;
+
+	rc = power_supply_get_property(chip->batt_psy, prop, &pval);
+	if (rc < 0) {
+		pr_err("Couldn't read battery prop %s rc = %d\n",
+				params[i].name, rc);
+		return -EINVAL;
+	}
+
+	return snprintf(ubuf, PAGE_SIZE, "%d%s\n",
+			pval.intval, params[i].units_str);
+}
+
+static struct class_attribute qnovo_attributes[] = {
+	[VER]			= __ATTR_RO(version),
+	[OK_TO_QNOVO]		= __ATTR_RO(ok_to_qnovo),
+	[ENABLE]		= __ATTR(enable, 0644,
+					enable_show, enable_store),
+	[FV_REQUEST]		= __ATTR(fv_uV_request, 0644,
+					val_show, val_store),
+	[FCC_REQUEST]		= __ATTR(fcc_uA_request, 0644,
+					val_show, val_store),
+	[PE_CTRL_REG]		= __ATTR(PE_CTRL_REG, 0644,
+					reg_show, reg_store),
+	[PE_CTRL2_REG]		= __ATTR(PE_CTRL2_REG, 0644,
+					reg_show, reg_store),
+	[PTRAIN_STS_REG]	= __ATTR(PTRAIN_STS_REG, 0644,
+					reg_show, reg_store),
+	[INT_RT_STS_REG]	= __ATTR(INT_RT_STS_REG, 0644,
+					reg_show, reg_store),
+	[PREST1]		= __ATTR(PREST1_mS, 0644,
+					time_show, time_store),
+	[PPULS1]		= __ATTR(PPULS1_uC, 0644,
+					coulomb_show, coulomb_store),
+	[NREST1]		= __ATTR(NREST1_mS, 0644,
+					time_show, time_store),
+	[NPULS1]		= __ATTR(NPULS1_mS, 0644,
+					time_show, time_store),
+	[PPCNT]			= __ATTR(PPCNT, 0644,
+					time_show, time_store),
+	[VLIM1]			= __ATTR(VLIM1_uV, 0644,
+					voltage_show, voltage_store),
+	[PVOLT1]		= __ATTR(PVOLT1_uV, 0444,
+					voltage_show, NULL),
+	[PCUR1]			= __ATTR(PCUR1_uA, 0444,
+					current_show, NULL),
+	[PTTIME]		= __ATTR(PTTIME_S, 0444,
+					time_show, NULL),
+	[PREST2]		= __ATTR(PREST2_mS, 0644,
+					time_show, time_store),
+	[PPULS2]		= __ATTR(PPULS2_mS, 0644,
+					coulomb_show, coulomb_store),
+	[NREST2]		= __ATTR(NREST2_mS, 0644,
+					time_show, time_store),
+	[NPULS2]		= __ATTR(NPULS2_mS, 0644,
+					time_show, time_store),
+	[VLIM2]			= __ATTR(VLIM2_uV, 0644,
+					voltage_show, voltage_store),
+	[PVOLT2]		= __ATTR(PVOLT2_uV, 0444,
+					voltage_show, NULL),
+	[RVOLT2]		= __ATTR(RVOLT2_uV, 0444,
+					voltage_show, NULL),
+	[PCUR2]			= __ATTR(PCUR2_uA, 0444,
+					current_show, NULL),
+	[SCNT]			= __ATTR(SCNT, 0644,
+					time_show, time_store),
+	[VMAX]			= __ATTR(VMAX_uV, 0444,
+					voltage_show, NULL),
+	[SNUM]			= __ATTR(SNUM, 0644,
+					time_show, time_store),
+	[VBATT]			= __ATTR(VBATT_uV, 0444,
+					batt_prop_show, NULL),
+	[IBATT]			= __ATTR(IBATT_uA, 0444,
+					batt_prop_show, NULL),
+	[BATTTEMP]		= __ATTR(BATTTEMP_deciDegC, 0444,
+					batt_prop_show, NULL),
+	[BATTSOC]		= __ATTR(BATTSOC, 0444,
+					batt_prop_show, NULL),
+	__ATTR_NULL,
+};
+
+static void get_chg_props(struct qnovo *chip, struct chg_props *cp)
+{
+	union power_supply_propval pval;
+	u8 val = 0;
+	int rc;
+
+	cp->charging = true;
+	rc = qnovo_read(chip, QNOVO_ERROR_STS, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read error sts rc = %d\n", rc);
+		cp->charging = false;
+	} else {
+		cp->charging = (!(val & QNOVO_ERROR_BIT));
+	}
+
+	if (chip->wa_flags & QNOVO_NO_ERR_STS_BIT) {
+		/*
+		 * on v1.0 and v1.1 pmic's force charging to true
+		 * if things are not good to charge s/w gets a PTRAIN_DONE
+		 * interrupt
+		 */
+		cp->charging = true;
+	}
+
+	cp->usb_online = false;
+	if (!chip->usb_psy)
+		chip->usb_psy = power_supply_get_by_name("usb");
+	if (chip->usb_psy) {
+		rc = power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_ONLINE, &pval);
+		if (rc < 0)
+			pr_err("Couldn't read usb online rc = %d\n", rc);
+		else
+			cp->usb_online = (bool)pval.intval;
+	}
+
+	cp->dc_online = false;
+	if (!chip->dc_psy)
+		chip->dc_psy = power_supply_get_by_name("dc");
+	if (chip->dc_psy) {
+		rc = power_supply_get_property(chip->dc_psy,
+				POWER_SUPPLY_PROP_ONLINE, &pval);
+		if (rc < 0)
+			pr_err("Couldn't read dc online rc = %d\n", rc);
+		else
+			cp->dc_online = (bool)pval.intval;
+	}
+}
+
+static void get_chg_status(struct qnovo *chip, const struct chg_props *cp,
+				struct chg_status *cs)
+{
+	cs->ok_to_qnovo = false;
+
+	if (cp->charging &&
+		(cp->usb_online || cp->dc_online))
+		cs->ok_to_qnovo = true;
+}
+
+static void status_change_work(struct work_struct *work)
+{
+	struct qnovo *chip = container_of(work,
+			struct qnovo, status_change_work);
+	bool notify_uevent = false;
+	struct chg_props cp;
+	struct chg_status cs;
+
+	get_chg_props(chip, &cp);
+	get_chg_status(chip, &cp, &cs);
+
+	if (cs.ok_to_qnovo ^ chip->cs.ok_to_qnovo) {
+		/*
+		 * when it is not okay to Qnovo charge, disable both voters,
+		 * so that when it becomes okay to Qnovo charge the user voter
+		 * has to specifically enable its vote to being Qnovo charging
+		 */
+		if (!cs.ok_to_qnovo) {
+			vote(chip->disable_votable, OK_TO_QNOVO_VOTER, 1, 0);
+			vote(chip->disable_votable, USER_VOTER, 1, 0);
+		} else {
+			vote(chip->disable_votable, OK_TO_QNOVO_VOTER, 0, 0);
+		}
+		notify_uevent = true;
+	}
+
+	memcpy(&chip->cp, &cp, sizeof(struct chg_props));
+	memcpy(&chip->cs, &cs, sizeof(struct chg_status));
+
+	if (notify_uevent)
+		kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+}
+
+static int qnovo_notifier_call(struct notifier_block *nb,
+		unsigned long ev, void *v)
+{
+	struct power_supply *psy = v;
+	struct qnovo *chip = container_of(nb, struct qnovo, nb);
+
+	if (ev != PSY_EVENT_PROP_CHANGED)
+		return NOTIFY_OK;
+	if ((strcmp(psy->desc->name, "battery") == 0)
+		|| (strcmp(psy->desc->name, "usb") == 0))
+		schedule_work(&chip->status_change_work);
+
+	return NOTIFY_OK;
+}
+
+static irqreturn_t handle_ptrain_done(int irq, void *data)
+{
+	struct qnovo *chip = data;
+
+	/* disable user voter here */
+	vote(chip->disable_votable, USER_VOTER, 0, 0);
+	kobject_uevent(&chip->dev->kobj, KOBJ_CHANGE);
+	return IRQ_HANDLED;
+}
+
+static int qnovo_hw_init(struct qnovo *chip)
+{
+	int rc;
+	u8 iadc_offset_external, iadc_offset_internal;
+	u8 iadc_gain_external, iadc_gain_internal;
+	u8 vadc_offset, vadc_gain;
+	u8 val;
+
+	vote(chip->disable_votable, USER_VOTER, 1, 0);
+
+	rc = qnovo_read(chip, QNOVO_IADC_OFFSET_0, &iadc_offset_external, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc exernal offset rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_IADC_OFFSET_1, &iadc_offset_internal, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc internal offset rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_IADC_GAIN_0, &iadc_gain_external, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc external gain rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_IADC_GAIN_1, &iadc_gain_internal, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc internal gain rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_VADC_OFFSET, &vadc_offset, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read vadc offset rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_VADC_GAIN, &vadc_gain, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read vadc external gain rc = %d\n", rc);
+		return rc;
+	}
+
+	chip->external_offset_nA = (s64)iadc_offset_external * IADC_LSB_NA;
+	chip->internal_offset_nA = (s64)iadc_offset_internal * IADC_LSB_NA;
+	chip->offset_nV = (s64)vadc_offset * VADC_LSB_NA;
+	chip->external_i_gain_mega
+		= 1000000000 + (s64)iadc_gain_external * GAIN_LSB_FACTOR;
+	chip->external_i_gain_mega
+		= div_s64(chip->external_i_gain_mega, 1000);
+	chip->internal_i_gain_mega
+		= 1000000000 + (s64)iadc_gain_internal * GAIN_LSB_FACTOR;
+	chip->internal_i_gain_mega
+		= div_s64(chip->internal_i_gain_mega, 1000);
+	chip->v_gain_mega = 1000000000 + (s64)vadc_gain * GAIN_LSB_FACTOR;
+	chip->v_gain_mega = div_s64(chip->v_gain_mega, 1000);
+
+	val = 0;
+	rc = qnovo_write(chip, QNOVO_STRM_CTRL, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't write iadc bitsteam control rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_TR_IADC_OFFSET_0, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc offset rc = %d\n", rc);
+		return rc;
+	}
+
+	val *= -1;
+	rc = qnovo_write(chip, QNOVO_TR_IADC_OFFSET_0, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't write iadc offset rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_read(chip, QNOVO_TR_IADC_OFFSET_1, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't read iadc offset rc = %d\n", rc);
+		return rc;
+	}
+
+	val *= -1;
+	rc = qnovo_write(chip, QNOVO_TR_IADC_OFFSET_1, &val, 1);
+	if (rc < 0) {
+		pr_err("Couldn't write iadc offset rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int qnovo_register_notifier(struct qnovo *chip)
+{
+	int rc;
+
+	chip->nb.notifier_call = qnovo_notifier_call;
+	rc = power_supply_reg_notifier(&chip->nb);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int qnovo_determine_initial_status(struct qnovo *chip)
+{
+	status_change_work(&chip->status_change_work);
+	return 0;
+}
+
+static int qnovo_request_interrupts(struct qnovo *chip)
+{
+	int rc = 0;
+	int irq_ptrain_done = of_irq_get_byname(chip->dev->of_node,
+						"ptrain-done");
+
+	rc = devm_request_threaded_irq(chip->dev, irq_ptrain_done, NULL,
+					handle_ptrain_done,
+					IRQF_ONESHOT, "ptrain-done", chip);
+	if (rc < 0) {
+		pr_err("Couldn't request irq %d rc = %d\n",
+					irq_ptrain_done, rc);
+		return rc;
+	}
+	return rc;
+}
+
+static int qnovo_probe(struct platform_device *pdev)
+{
+	struct qnovo *chip;
+	int rc = 0;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->fv_uV_request = -EINVAL;
+	chip->fcc_uA_request = -EINVAL;
+	chip->dev = &pdev->dev;
+	mutex_init(&chip->write_lock);
+
+	chip->regmap = dev_get_regmap(chip->dev->parent, NULL);
+	if (!chip->regmap) {
+		pr_err("parent regmap is missing\n");
+		return -EINVAL;
+	}
+
+	rc = qnovo_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = qnovo_check_chg_version(chip);
+	if (rc < 0) {
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't check version rc=%d\n", rc);
+		return rc;
+	}
+
+	/* set driver data before resources request it */
+	platform_set_drvdata(pdev, chip);
+
+	chip->disable_votable = create_votable("QNOVO_DISABLE", VOTE_SET_ANY,
+					qnovo_disable_cb, chip);
+	if (IS_ERR(chip->disable_votable)) {
+		rc = PTR_ERR(chip->disable_votable);
+		goto cleanup;
+	}
+
+	INIT_WORK(&chip->status_change_work, status_change_work);
+
+	rc = qnovo_hw_init(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		goto destroy_votable;
+	}
+
+	rc = qnovo_register_notifier(chip);
+	if (rc < 0) {
+		pr_err("Couldn't register psy notifier rc = %d\n", rc);
+		goto unreg_notifier;
+	}
+
+	rc = qnovo_determine_initial_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n", rc);
+		goto unreg_notifier;
+	}
+
+	rc = qnovo_request_interrupts(chip);
+	if (rc < 0) {
+		pr_err("Couldn't request interrupts rc=%d\n", rc);
+		goto unreg_notifier;
+	}
+	chip->qnovo_class.name = "qnovo",
+	chip->qnovo_class.owner = THIS_MODULE,
+	chip->qnovo_class.class_attrs = qnovo_attributes;
+
+	rc = class_register(&chip->qnovo_class);
+	if (rc < 0) {
+		pr_err("couldn't register qnovo sysfs class rc = %d\n", rc);
+		goto unreg_notifier;
+	}
+
+	return rc;
+
+unreg_notifier:
+	power_supply_unreg_notifier(&chip->nb);
+destroy_votable:
+	destroy_votable(chip->disable_votable);
+cleanup:
+	platform_set_drvdata(pdev, NULL);
+	return rc;
+}
+
+static int qnovo_remove(struct platform_device *pdev)
+{
+	struct qnovo *chip = platform_get_drvdata(pdev);
+
+	class_unregister(&chip->qnovo_class);
+	power_supply_unreg_notifier(&chip->nb);
+	destroy_votable(chip->disable_votable);
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static const struct of_device_id match_table[] = {
+	{ .compatible = "qcom,qpnp-qnovo", },
+	{ },
+};
+
+static struct platform_driver qnovo_driver = {
+	.driver		= {
+		.name		= "qcom,qnovo-driver",
+		.owner		= THIS_MODULE,
+		.of_match_table	= match_table,
+	},
+	.probe		= qnovo_probe,
+	.remove		= qnovo_remove,
+};
+module_platform_driver(qnovo_driver);
+
+MODULE_DESCRIPTION("QPNP Qnovo Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c
new file mode 100644
index 0000000..dab7888
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-smb2.c
@@ -0,0 +1,2240 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/debugfs.h>
+#include <linux/device.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "storm-watch.h"
+#include "pmic-voter.h"
+
+#define SMB2_DEFAULT_WPWR_UW	8000000
+
+static struct smb_params v1_params = {
+	.fcc			= {
+		.name	= "fast charge current",
+		.reg	= FAST_CHARGE_CURRENT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 4500000,
+		.step_u	= 25000,
+	},
+	.fv			= {
+		.name	= "float voltage",
+		.reg	= FLOAT_VOLTAGE_CFG_REG,
+		.min_u	= 3487500,
+		.max_u	= 4920000,
+		.step_u	= 7500,
+	},
+	.usb_icl		= {
+		.name	= "usb input current limit",
+		.reg	= USBIN_CURRENT_LIMIT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 4800000,
+		.step_u	= 25000,
+	},
+	.icl_stat		= {
+		.name	= "input current limit status",
+		.reg	= ICL_STATUS_REG,
+		.min_u	= 0,
+		.max_u	= 4800000,
+		.step_u	= 25000,
+	},
+	.otg_cl			= {
+		.name	= "usb otg current limit",
+		.reg	= OTG_CURRENT_LIMIT_CFG_REG,
+		.min_u	= 250000,
+		.max_u	= 2000000,
+		.step_u	= 250000,
+	},
+	.dc_icl			= {
+		.name	= "dc input current limit",
+		.reg	= DCIN_CURRENT_LIMIT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 6000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_pt_lv		= {
+		.name	= "dc icl PT <8V",
+		.reg	= ZIN_ICL_PT_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_pt_hv		= {
+		.name	= "dc icl PT >8V",
+		.reg	= ZIN_ICL_PT_HV_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_div2_lv		= {
+		.name	= "dc icl div2 <5.5V",
+		.reg	= ZIN_ICL_LV_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_div2_mid_lv	= {
+		.name	= "dc icl div2 5.5-6.5V",
+		.reg	= ZIN_ICL_MID_LV_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_div2_mid_hv	= {
+		.name	= "dc icl div2 6.5-8.0V",
+		.reg	= ZIN_ICL_MID_HV_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.dc_icl_div2_hv		= {
+		.name	= "dc icl div2 >8.0V",
+		.reg	= ZIN_ICL_HV_REG,
+		.min_u	= 0,
+		.max_u	= 3000000,
+		.step_u	= 25000,
+	},
+	.jeita_cc_comp		= {
+		.name	= "jeita fcc reduction",
+		.reg	= JEITA_CCCOMP_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 1575000,
+		.step_u	= 25000,
+	},
+	.step_soc_threshold[0]		= {
+		.name	= "step charge soc threshold 1",
+		.reg	= STEP_CHG_SOC_OR_BATT_V_TH1_REG,
+		.min_u	= 0,
+		.max_u	= 100,
+		.step_u	= 1,
+	},
+	.step_soc_threshold[1]		= {
+		.name	= "step charge soc threshold 2",
+		.reg	= STEP_CHG_SOC_OR_BATT_V_TH2_REG,
+		.min_u	= 0,
+		.max_u	= 100,
+		.step_u	= 1,
+	},
+	.step_soc_threshold[2]         = {
+		.name	= "step charge soc threshold 3",
+		.reg	= STEP_CHG_SOC_OR_BATT_V_TH3_REG,
+		.min_u	= 0,
+		.max_u	= 100,
+		.step_u	= 1,
+	},
+	.step_soc_threshold[3]         = {
+		.name	= "step charge soc threshold 4",
+		.reg	= STEP_CHG_SOC_OR_BATT_V_TH4_REG,
+		.min_u	= 0,
+		.max_u	= 100,
+		.step_u	= 1,
+	},
+	.step_soc			= {
+		.name	= "step charge soc",
+		.reg	= STEP_CHG_SOC_VBATT_V_REG,
+		.min_u	= 0,
+		.max_u	= 100,
+		.step_u	= 1,
+		.set_proc	= smblib_mapping_soc_from_field_value,
+	},
+	.step_cc_delta[0]	= {
+		.name	= "step charge current delta 1",
+		.reg	= STEP_CHG_CURRENT_DELTA1_REG,
+		.min_u	= 100000,
+		.max_u	= 3200000,
+		.step_u	= 100000,
+		.get_proc	= smblib_mapping_cc_delta_to_field_value,
+		.set_proc	= smblib_mapping_cc_delta_from_field_value,
+	},
+	.step_cc_delta[1]	= {
+		.name	= "step charge current delta 2",
+		.reg	= STEP_CHG_CURRENT_DELTA2_REG,
+		.min_u	= 100000,
+		.max_u	= 3200000,
+		.step_u	= 100000,
+		.get_proc	= smblib_mapping_cc_delta_to_field_value,
+		.set_proc	= smblib_mapping_cc_delta_from_field_value,
+	},
+	.step_cc_delta[2]	= {
+		.name	= "step charge current delta 3",
+		.reg	= STEP_CHG_CURRENT_DELTA3_REG,
+		.min_u	= 100000,
+		.max_u	= 3200000,
+		.step_u	= 100000,
+		.get_proc	= smblib_mapping_cc_delta_to_field_value,
+		.set_proc	= smblib_mapping_cc_delta_from_field_value,
+	},
+	.step_cc_delta[3]	= {
+		.name	= "step charge current delta 4",
+		.reg	= STEP_CHG_CURRENT_DELTA4_REG,
+		.min_u	= 100000,
+		.max_u	= 3200000,
+		.step_u	= 100000,
+		.get_proc	= smblib_mapping_cc_delta_to_field_value,
+		.set_proc	= smblib_mapping_cc_delta_from_field_value,
+	},
+	.step_cc_delta[4]	= {
+		.name	= "step charge current delta 5",
+		.reg	= STEP_CHG_CURRENT_DELTA5_REG,
+		.min_u	= 100000,
+		.max_u	= 3200000,
+		.step_u	= 100000,
+		.get_proc	= smblib_mapping_cc_delta_to_field_value,
+		.set_proc	= smblib_mapping_cc_delta_from_field_value,
+	},
+	.freq_buck		= {
+		.name	= "buck switching frequency",
+		.reg	= CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG,
+		.min_u	= 600,
+		.max_u	= 2000,
+		.step_u	= 200,
+	},
+	.freq_boost		= {
+		.name	= "boost switching frequency",
+		.reg	= CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG,
+		.min_u	= 600,
+		.max_u	= 2000,
+		.step_u	= 200,
+	},
+};
+
+static struct smb_params pm660_params = {
+	.freq_buck		= {
+		.name	= "buck switching frequency",
+		.reg	= FREQ_CLK_DIV_REG,
+		.min_u	= 600,
+		.max_u	= 1600,
+		.set_proc = smblib_set_chg_freq,
+	},
+	.freq_boost		= {
+		.name	= "boost switching frequency",
+		.reg	= FREQ_CLK_DIV_REG,
+		.min_u	= 600,
+		.max_u	= 1600,
+		.set_proc = smblib_set_chg_freq,
+	},
+};
+
+#define STEP_CHARGING_MAX_STEPS	5
+struct smb_dt_props {
+	int	fcc_ua;
+	int	usb_icl_ua;
+	int	otg_cl_ua;
+	int	dc_icl_ua;
+	int	boost_threshold_ua;
+	int	fv_uv;
+	int	wipower_max_uw;
+	u32	step_soc_threshold[STEP_CHARGING_MAX_STEPS - 1];
+	s32	step_cc_delta[STEP_CHARGING_MAX_STEPS];
+	struct	device_node *revid_dev_node;
+	int	float_option;
+	int	chg_inhibit_thr_mv;
+	bool	no_battery;
+	bool	hvdcp_disable;
+	bool	auto_recharge_soc;
+};
+
+struct smb2 {
+	struct smb_charger	chg;
+	struct dentry		*dfs_root;
+	struct smb_dt_props	dt;
+	bool			bad_part;
+};
+
+static int __debug_mask;
+module_param_named(
+	debug_mask, __debug_mask, int, 0600
+);
+
+#define MICRO_1P5A	1500000
+#define MICRO_P1A	100000
+static int smb2_parse_dt(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct device_node *node = chg->dev->of_node;
+	int rc, byte_len;
+
+	if (!node) {
+		pr_err("device tree node missing\n");
+		return -EINVAL;
+	}
+
+	chg->step_chg_enabled = true;
+
+	if (of_property_count_u32_elems(node, "qcom,step-soc-thresholds")
+			!= STEP_CHARGING_MAX_STEPS - 1)
+		chg->step_chg_enabled = false;
+
+	rc = of_property_read_u32_array(node, "qcom,step-soc-thresholds",
+			chip->dt.step_soc_threshold,
+			STEP_CHARGING_MAX_STEPS - 1);
+	if (rc < 0)
+		chg->step_chg_enabled = false;
+
+	if (of_property_count_u32_elems(node, "qcom,step-current-deltas")
+			!= STEP_CHARGING_MAX_STEPS)
+		chg->step_chg_enabled = false;
+
+	rc = of_property_read_u32_array(node, "qcom,step-current-deltas",
+			chip->dt.step_cc_delta,
+			STEP_CHARGING_MAX_STEPS);
+	if (rc < 0)
+		chg->step_chg_enabled = false;
+
+	chip->dt.no_battery = of_property_read_bool(node,
+						"qcom,batteryless-platform");
+
+	chg->external_vconn = of_property_read_bool(node,
+						"qcom,external-vconn");
+
+	rc = of_property_read_u32(node,
+				"qcom,fcc-max-ua", &chip->dt.fcc_ua);
+	if (rc < 0)
+		chip->dt.fcc_ua = -EINVAL;
+
+	rc = of_property_read_u32(node,
+				"qcom,fv-max-uv", &chip->dt.fv_uv);
+	if (rc < 0)
+		chip->dt.fv_uv = -EINVAL;
+
+	rc = of_property_read_u32(node,
+				"qcom,usb-icl-ua", &chip->dt.usb_icl_ua);
+	if (rc < 0)
+		chip->dt.usb_icl_ua = -EINVAL;
+
+	rc = of_property_read_u32(node,
+				"qcom,otg-cl-ua", &chip->dt.otg_cl_ua);
+	if (rc < 0)
+		chip->dt.otg_cl_ua = MICRO_1P5A;
+
+	rc = of_property_read_u32(node,
+				"qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+	if (rc < 0)
+		chip->dt.dc_icl_ua = -EINVAL;
+
+	rc = of_property_read_u32(node,
+				"qcom,boost-threshold-ua",
+				&chip->dt.boost_threshold_ua);
+	if (rc < 0)
+		chip->dt.boost_threshold_ua = MICRO_P1A;
+
+	rc = of_property_read_u32(node, "qcom,wipower-max-uw",
+				&chip->dt.wipower_max_uw);
+	if (rc < 0)
+		chip->dt.wipower_max_uw = -EINVAL;
+
+	if (of_find_property(node, "qcom,thermal-mitigation", &byte_len)) {
+		chg->thermal_mitigation = devm_kzalloc(chg->dev, byte_len,
+			GFP_KERNEL);
+
+		if (chg->thermal_mitigation == NULL)
+			return -ENOMEM;
+
+		chg->thermal_levels = byte_len / sizeof(u32);
+		rc = of_property_read_u32_array(node,
+				"qcom,thermal-mitigation",
+				chg->thermal_mitigation,
+				chg->thermal_levels);
+		if (rc < 0) {
+			dev_err(chg->dev,
+				"Couldn't read threm limits rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	of_property_read_u32(node, "qcom,float-option", &chip->dt.float_option);
+	if (chip->dt.float_option < 0 || chip->dt.float_option > 4) {
+		pr_err("qcom,float-option is out of range [0, 4]\n");
+		return -EINVAL;
+	}
+
+	chip->dt.hvdcp_disable = of_property_read_bool(node,
+						"qcom,hvdcp-disable");
+
+	of_property_read_u32(node, "qcom,chg-inhibit-threshold-mv",
+				&chip->dt.chg_inhibit_thr_mv);
+	if ((chip->dt.chg_inhibit_thr_mv < 0 ||
+		chip->dt.chg_inhibit_thr_mv > 300)) {
+		pr_err("qcom,chg-inhibit-threshold-mv is incorrect\n");
+		return -EINVAL;
+	}
+
+	chip->dt.auto_recharge_soc = of_property_read_bool(node,
+						"qcom,auto-recharge-soc");
+
+	chg->micro_usb_mode = of_property_read_bool(node, "qcom,micro-usb");
+
+	chg->dcp_icl_ua = chip->dt.usb_icl_ua;
+
+	chg->suspend_input_on_debug_batt = of_property_read_bool(node,
+					"qcom,suspend-input-on-debug-batt");
+	return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb2_usb_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_PD_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_TYPE,
+	POWER_SUPPLY_PROP_TYPEC_MODE,
+	POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
+	POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION,
+	POWER_SUPPLY_PROP_PD_ALLOWED,
+	POWER_SUPPLY_PROP_PD_ACTIVE,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
+	POWER_SUPPLY_PROP_BOOST_CURRENT,
+	POWER_SUPPLY_PROP_PE_START,
+	POWER_SUPPLY_PROP_CTM_CURRENT_MAX,
+};
+
+static int smb2_usb_get_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		if (chip->bad_part)
+			val->intval = 1;
+		else
+			rc = smblib_get_prop_usb_present(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		rc = smblib_get_prop_usb_online(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		val->intval = chg->voltage_min_uv;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		val->intval = chg->voltage_max_uv;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		rc = smblib_get_prop_usb_voltage_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+		rc = smblib_get_prop_pd_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_get_prop_usb_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPE:
+		if (chip->bad_part)
+			val->intval = POWER_SUPPLY_TYPE_USB;
+		else
+			val->intval = chg->usb_psy_desc.type;
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_MODE:
+		if (chg->micro_usb_mode)
+			val->intval = POWER_SUPPLY_TYPEC_NONE;
+		else if (chip->bad_part)
+			val->intval = POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+		else
+			rc = smblib_get_prop_typec_mode(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+		if (chg->micro_usb_mode)
+			val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+		else
+			rc = smblib_get_prop_typec_power_role(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
+		if (chg->micro_usb_mode)
+			val->intval = 0;
+		else
+			rc = smblib_get_prop_typec_cc_orientation(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_ALLOWED:
+		rc = smblib_get_prop_pd_allowed(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_ACTIVE:
+		val->intval = chg->pd_active;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
+		rc = smblib_get_prop_input_current_settled(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW:
+		rc = smblib_get_prop_usb_current_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_BOOST_CURRENT:
+		val->intval = chg->boost_current_ua;
+		break;
+	case POWER_SUPPLY_PROP_PD_IN_HARD_RESET:
+		rc = smblib_get_prop_pd_in_hard_reset(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED:
+		val->intval = chg->system_suspend_supported;
+		break;
+	case POWER_SUPPLY_PROP_PE_START:
+		rc = smblib_get_pe_start(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+		val->intval = get_client_vote(chg->usb_icl_votable, CTM_VOTER);
+		break;
+	default:
+		pr_err("get prop %d is not supported in usb\n", psp);
+		rc = -EINVAL;
+		break;
+	}
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+		return -ENODATA;
+	}
+	return 0;
+}
+
+static int smb2_usb_set_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		const union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		rc = smblib_set_prop_usb_voltage_min(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_set_prop_usb_voltage_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_CURRENT_MAX:
+		rc = smblib_set_prop_pd_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_set_prop_usb_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+		rc = smblib_set_prop_typec_power_role(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_ACTIVE:
+		rc = smblib_set_prop_pd_active(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_IN_HARD_RESET:
+		rc = smblib_set_prop_pd_in_hard_reset(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED:
+		chg->system_suspend_supported = val->intval;
+		break;
+	case POWER_SUPPLY_PROP_BOOST_CURRENT:
+		rc = smblib_set_prop_boost_current(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+		rc = vote(chg->usb_icl_votable, CTM_VOTER,
+						val->intval >= 0, val->intval);
+		break;
+	default:
+		pr_err("set prop %d is not supported\n", psp);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+static int smb2_usb_prop_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+	case POWER_SUPPLY_PROP_CTM_CURRENT_MAX:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int smb2_init_usb_psy(struct smb2 *chip)
+{
+	struct power_supply_config usb_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+
+	chg->usb_psy_desc.name			= "usb";
+	chg->usb_psy_desc.type			= POWER_SUPPLY_TYPE_UNKNOWN;
+	chg->usb_psy_desc.properties		= smb2_usb_props;
+	chg->usb_psy_desc.num_properties	= ARRAY_SIZE(smb2_usb_props);
+	chg->usb_psy_desc.get_property		= smb2_usb_get_prop;
+	chg->usb_psy_desc.set_property		= smb2_usb_set_prop;
+	chg->usb_psy_desc.property_is_writeable	= smb2_usb_prop_is_writeable;
+
+	usb_cfg.drv_data = chip;
+	usb_cfg.of_node = chg->dev->of_node;
+	chg->usb_psy = devm_power_supply_register(chg->dev,
+						  &chg->usb_psy_desc,
+						  &usb_cfg);
+	if (IS_ERR(chg->usb_psy)) {
+		pr_err("Couldn't register USB power supply\n");
+		return PTR_ERR(chg->usb_psy);
+	}
+
+	return 0;
+}
+
+/*****************************
+ * USB MAIN PSY REGISTRATION *
+ *****************************/
+
+static enum power_supply_property smb2_usb_main_props[] = {
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_ICL_REDUCTION,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_TYPE,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+	POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED,
+	POWER_SUPPLY_PROP_FCC_DELTA,
+	/*
+	 * TODO move the TEMP and TEMP_MAX properties here,
+	 * and update the thermal balancer to look here
+	 */
+};
+
+static int smb2_usb_main_get_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_ICL_REDUCTION:
+		val->intval = chg->icl_reduction_ua;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		rc = smblib_get_charge_param(chg, &chg->param.fcc,
+							&val->intval);
+		break;
+	case POWER_SUPPLY_PROP_TYPE:
+		val->intval = POWER_SUPPLY_TYPE_MAIN;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
+		rc = smblib_get_prop_input_current_settled(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED:
+		rc = smblib_get_prop_input_voltage_settled(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_FCC_DELTA:
+		rc = smblib_get_prop_fcc_delta(chg, val);
+		break;
+	default:
+		pr_debug("get prop %d is not supported in usb-main\n", psp);
+		rc = -EINVAL;
+		break;
+	}
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+		return -ENODATA;
+	}
+	return 0;
+}
+
+static int smb2_usb_main_set_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		const union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_ICL_REDUCTION:
+		rc = smblib_set_icl_reduction(chg, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval);
+		break;
+	default:
+		pr_err("set prop %d is not supported\n", psp);
+		rc = -EINVAL;
+		break;
+	}
+
+	return rc;
+}
+
+static const struct power_supply_desc usb_main_psy_desc = {
+	.name		= "main",
+	.type		= POWER_SUPPLY_TYPE_MAIN,
+	.properties	= smb2_usb_main_props,
+	.num_properties	= ARRAY_SIZE(smb2_usb_main_props),
+	.get_property	= smb2_usb_main_get_prop,
+	.set_property	= smb2_usb_main_set_prop,
+};
+
+static int smb2_init_usb_main_psy(struct smb2 *chip)
+{
+	struct power_supply_config usb_main_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+
+	usb_main_cfg.drv_data = chip;
+	usb_main_cfg.of_node = chg->dev->of_node;
+	chg->usb_main_psy = devm_power_supply_register(chg->dev,
+						  &usb_main_psy_desc,
+						  &usb_main_cfg);
+	if (IS_ERR(chg->usb_main_psy)) {
+		pr_err("Couldn't register USB main power supply\n");
+		return PTR_ERR(chg->usb_main_psy);
+	}
+
+	return 0;
+}
+
+/*************************
+ * DC PSY REGISTRATION   *
+ *************************/
+
+static enum power_supply_property smb2_dc_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smb2_dc_get_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		rc = smblib_get_prop_dc_present(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		rc = smblib_get_prop_dc_online(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_get_prop_dc_current_max(chg, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+		return -ENODATA;
+	}
+	return 0;
+}
+
+static int smb2_dc_set_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		const union power_supply_propval *val)
+{
+	struct smb2 *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_set_prop_dc_current_max(chg, val);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int smb2_dc_prop_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	int rc;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = 1;
+		break;
+	default:
+		rc = 0;
+		break;
+	}
+
+	return rc;
+}
+
+static const struct power_supply_desc dc_psy_desc = {
+	.name = "dc",
+	.type = POWER_SUPPLY_TYPE_WIPOWER,
+	.properties = smb2_dc_props,
+	.num_properties = ARRAY_SIZE(smb2_dc_props),
+	.get_property = smb2_dc_get_prop,
+	.set_property = smb2_dc_set_prop,
+	.property_is_writeable = smb2_dc_prop_is_writeable,
+};
+
+static int smb2_init_dc_psy(struct smb2 *chip)
+{
+	struct power_supply_config dc_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+
+	dc_cfg.drv_data = chip;
+	dc_cfg.of_node = chg->dev->of_node;
+	chg->dc_psy = devm_power_supply_register(chg->dev,
+						  &dc_psy_desc,
+						  &dc_cfg);
+	if (IS_ERR(chg->dc_psy)) {
+		pr_err("Couldn't register USB power supply\n");
+		return PTR_ERR(chg->dc_psy);
+	}
+
+	return 0;
+}
+
+/*************************
+ * BATT PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb2_batt_props[] = {
+	POWER_SUPPLY_PROP_INPUT_SUSPEND,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+	POWER_SUPPLY_PROP_CHARGER_TEMP,
+	POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_STEP_CHARGING_STEP,
+	POWER_SUPPLY_PROP_CHARGE_DONE,
+	POWER_SUPPLY_PROP_PARALLEL_DISABLE,
+	POWER_SUPPLY_PROP_SET_SHIP_MODE,
+	POWER_SUPPLY_PROP_DIE_HEALTH,
+	POWER_SUPPLY_PROP_RERUN_AICL,
+	POWER_SUPPLY_PROP_DP_DM,
+};
+
+static int smb2_batt_get_prop(struct power_supply *psy,
+		enum power_supply_property psp,
+		union power_supply_propval *val)
+{
+	struct smb_charger *chg = power_supply_get_drvdata(psy);
+	int rc = 0;
+
+	switch (psp) {
+	case POWER_SUPPLY_PROP_STATUS:
+		rc = smblib_get_prop_batt_status(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		rc = smblib_get_prop_batt_health(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		rc = smblib_get_prop_batt_present(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smblib_get_prop_input_suspend(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		rc = smblib_get_prop_batt_charge_type(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = smblib_get_prop_batt_capacity(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+		rc = smblib_get_prop_system_temp_level(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP:
+		rc = smblib_get_prop_charger_temp(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+		rc = smblib_get_prop_charger_temp_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+		rc = smblib_get_prop_input_current_limited(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED:
+		val->intval = chg->step_chg_enabled;
+		break;
+	case POWER_SUPPLY_PROP_STEP_CHARGING_STEP:
+		rc = smblib_get_prop_step_chg_step(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		rc = smblib_get_prop_batt_voltage_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		val->intval = get_client_vote(chg->fv_votable, DEFAULT_VOTER);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_QNOVO:
+		val->intval = chg->qnovo_fv_uv;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		rc = smblib_get_prop_batt_current_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_QNOVO:
+		val->intval = chg->qnovo_fcc_ua;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		val->intval = get_client_vote(chg->fcc_votable,
+					      DEFAULT_VOTER);
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		rc = smblib_get_prop_batt_temp(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_DONE:
+		rc = smblib_get_prop_batt_charge_done(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+		val->intval = get_client_vote(chg->pl_disable_votable,
+					      USER_VOTER);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as device is active */
+		val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_DIE_HEALTH:
+		rc = smblib_get_prop_die_health(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_DP_DM:
+		val->intval = chg->pulse_cnt;
+		break;
+	case POWER_SUPPLY_PROP_RERUN_AICL:
+		val->intval = 0;
+		break;
+	default:
+		pr_err("batt power supply prop %d not supported\n", psp);
+		return -EINVAL;
+	}
+
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", psp, rc);
+		return -ENODATA;
+	}
+
+	return 0;
+}
+
+static int smb2_batt_set_prop(struct power_supply *psy,
+		enum power_supply_property prop,
+		const union power_supply_propval *val)
+{
+	int rc = 0;
+	struct smb_charger *chg = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smblib_set_prop_input_suspend(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+		rc = smblib_set_prop_system_temp_level(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = smblib_set_prop_batt_capacity(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+		vote(chg->pl_disable_votable, USER_VOTER, (bool)val->intval, 0);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		vote(chg->fv_votable, DEFAULT_VOTER, true, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_QNOVO:
+		chg->qnovo_fv_uv = val->intval;
+		rc = rerun_election(chg->fv_votable);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_QNOVO:
+		chg->qnovo_fcc_ua = val->intval;
+		rc = rerun_election(chg->fcc_votable);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		vote(chg->fcc_votable, DEFAULT_VOTER, true, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as the device is active */
+		if (!val->intval)
+			break;
+		if (chg->pl.psy)
+			power_supply_set_property(chg->pl.psy,
+				POWER_SUPPLY_PROP_SET_SHIP_MODE, val);
+		rc = smblib_set_prop_ship_mode(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_RERUN_AICL:
+		rc = smblib_rerun_aicl(chg);
+		break;
+	case POWER_SUPPLY_PROP_DP_DM:
+		rc = smblib_dp_dm(chg, val->intval);
+		break;
+	default:
+		rc = -EINVAL;
+	}
+
+	return rc;
+}
+
+static int smb2_batt_prop_is_writeable(struct power_supply *psy,
+		enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+	case POWER_SUPPLY_PROP_CAPACITY:
+	case POWER_SUPPLY_PROP_PARALLEL_DISABLE:
+	case POWER_SUPPLY_PROP_DP_DM:
+	case POWER_SUPPLY_PROP_RERUN_AICL:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+	.name = "battery",
+	.type = POWER_SUPPLY_TYPE_BATTERY,
+	.properties = smb2_batt_props,
+	.num_properties = ARRAY_SIZE(smb2_batt_props),
+	.get_property = smb2_batt_get_prop,
+	.set_property = smb2_batt_set_prop,
+	.property_is_writeable = smb2_batt_prop_is_writeable,
+};
+
+static int smb2_init_batt_psy(struct smb2 *chip)
+{
+	struct power_supply_config batt_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	batt_cfg.drv_data = chg;
+	batt_cfg.of_node = chg->dev->of_node;
+	chg->batt_psy = devm_power_supply_register(chg->dev,
+						   &batt_psy_desc,
+						   &batt_cfg);
+	if (IS_ERR(chg->batt_psy)) {
+		pr_err("Couldn't register battery power supply\n");
+		return PTR_ERR(chg->batt_psy);
+	}
+
+	return rc;
+}
+
+/******************************
+ * VBUS REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb2_vbus_reg_ops = {
+	.enable = smblib_vbus_regulator_enable,
+	.disable = smblib_vbus_regulator_disable,
+	.is_enabled = smblib_vbus_regulator_is_enabled,
+};
+
+static int smb2_init_vbus_regulator(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct regulator_config cfg = {};
+	int rc = 0;
+
+	chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg),
+				      GFP_KERNEL);
+	if (!chg->vbus_vreg)
+		return -ENOMEM;
+
+	cfg.dev = chg->dev;
+	cfg.driver_data = chip;
+
+	chg->vbus_vreg->rdesc.owner = THIS_MODULE;
+	chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE;
+	chg->vbus_vreg->rdesc.ops = &smb2_vbus_reg_ops;
+	chg->vbus_vreg->rdesc.of_match = "qcom,smb2-vbus";
+	chg->vbus_vreg->rdesc.name = "qcom,smb2-vbus";
+
+	chg->vbus_vreg->rdev = devm_regulator_register(chg->dev,
+						&chg->vbus_vreg->rdesc, &cfg);
+	if (IS_ERR(chg->vbus_vreg->rdev)) {
+		rc = PTR_ERR(chg->vbus_vreg->rdev);
+		chg->vbus_vreg->rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't register VBUS regualtor rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb2_vconn_reg_ops = {
+	.enable = smblib_vconn_regulator_enable,
+	.disable = smblib_vconn_regulator_disable,
+	.is_enabled = smblib_vconn_regulator_is_enabled,
+};
+
+static int smb2_init_vconn_regulator(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct regulator_config cfg = {};
+	int rc = 0;
+
+	chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg),
+				      GFP_KERNEL);
+	if (!chg->vconn_vreg)
+		return -ENOMEM;
+
+	cfg.dev = chg->dev;
+	cfg.driver_data = chip;
+
+	chg->vconn_vreg->rdesc.owner = THIS_MODULE;
+	chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE;
+	chg->vconn_vreg->rdesc.ops = &smb2_vconn_reg_ops;
+	chg->vconn_vreg->rdesc.of_match = "qcom,smb2-vconn";
+	chg->vconn_vreg->rdesc.name = "qcom,smb2-vconn";
+
+	chg->vconn_vreg->rdev = devm_regulator_register(chg->dev,
+						&chg->vconn_vreg->rdesc, &cfg);
+	if (IS_ERR(chg->vconn_vreg->rdev)) {
+		rc = PTR_ERR(chg->vconn_vreg->rdev);
+		chg->vconn_vreg->rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't register VCONN regualtor rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+static int smb2_config_step_charging(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+	int i;
+
+	if (!chg->step_chg_enabled)
+		return rc;
+
+	for (i = 0; i < STEP_CHARGING_MAX_STEPS - 1; i++) {
+		rc = smblib_set_charge_param(chg,
+					     &chg->param.step_soc_threshold[i],
+					     chip->dt.step_soc_threshold[i]);
+		if (rc < 0) {
+			pr_err("Couldn't configure soc thresholds rc = %d\n",
+				rc);
+			goto err_out;
+		}
+	}
+
+	for (i = 0; i < STEP_CHARGING_MAX_STEPS; i++) {
+		rc = smblib_set_charge_param(chg, &chg->param.step_cc_delta[i],
+					     chip->dt.step_cc_delta[i]);
+		if (rc < 0) {
+			pr_err("Couldn't configure cc delta rc = %d\n",
+				rc);
+			goto err_out;
+		}
+	}
+
+	rc = smblib_write(chg, STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_REG,
+			  STEP_CHG_UPDATE_REQUEST_TIMEOUT_40S);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure soc request timeout reg rc=%d\n",
+			 rc);
+		goto err_out;
+	}
+
+	rc = smblib_write(chg, STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_REG,
+			  STEP_CHG_UPDATE_FAIL_TIMEOUT_120S);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure soc fail timeout reg rc=%d\n",
+			rc);
+		goto err_out;
+	}
+
+	/*
+	 *  enable step charging, source soc, standard mode, go to final
+	 *  state in case of failure.
+	 */
+	rc = smblib_write(chg, CHGR_STEP_CHG_MODE_CFG_REG,
+			       STEP_CHARGING_ENABLE_BIT |
+			       STEP_CHARGING_SOURCE_SELECT_BIT |
+			       STEP_CHARGING_SOC_FAIL_OPTION_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure charger rc=%d\n", rc);
+		goto err_out;
+	}
+
+	return 0;
+err_out:
+	chg->step_chg_enabled = false;
+	return rc;
+}
+
+static int smb2_config_wipower_input_power(struct smb2 *chip, int uw)
+{
+	int rc;
+	int ua;
+	struct smb_charger *chg = &chip->chg;
+	s64 nw = (s64)uw * 1000;
+
+	if (uw < 0)
+		return 0;
+
+	ua = div_s64(nw, ZIN_ICL_PT_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_lv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_pt_lv rc = %d\n", rc);
+		return rc;
+	}
+
+	ua = div_s64(nw, ZIN_ICL_PT_HV_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_pt_hv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_pt_hv rc = %d\n", rc);
+		return rc;
+	}
+
+	ua = div_s64(nw, ZIN_ICL_LV_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_lv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_div2_lv rc = %d\n", rc);
+		return rc;
+	}
+
+	ua = div_s64(nw, ZIN_ICL_MID_LV_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_lv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_div2_mid_lv rc = %d\n", rc);
+		return rc;
+	}
+
+	ua = div_s64(nw, ZIN_ICL_MID_HV_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_mid_hv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_div2_mid_hv rc = %d\n", rc);
+		return rc;
+	}
+
+	ua = div_s64(nw, ZIN_ICL_HV_MAX_MV);
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl_div2_hv, ua);
+	if (rc < 0) {
+		pr_err("Couldn't configure dc_icl_div2_hv rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int smb2_configure_typec(struct smb_charger *chg)
+{
+	int rc;
+
+	/*
+	 * trigger the usb-typec-change interrupt only when the CC state
+	 * changes
+	 */
+	rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+			  TYPEC_CCSTATE_CHANGE_INT_EN_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure Type-C interrupts rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure power role for dual-role */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 TYPEC_POWER_ROLE_CMD_MASK, 0);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure power role for DRP rc=%d\n", rc);
+		return rc;
+	}
+
+	/*
+	 * disable Type-C factory mode and stay in Attached.SRC state when VCONN
+	 * over-current happens
+	 */
+	rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+			FACTORY_MODE_DETECTION_EN_BIT | VCONN_OC_CFG_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure Type-C rc=%d\n", rc);
+		return rc;
+	}
+
+	/* increase VCONN softstart */
+	rc = smblib_masked_write(chg, TYPE_C_CFG_2_REG,
+			VCONN_SOFTSTART_CFG_MASK, VCONN_SOFTSTART_CFG_MASK);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't increase VCONN softstart rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	/* disable try.SINK mode */
+	rc = smblib_masked_write(chg, TYPE_C_CFG_3_REG, EN_TRYSINK_MODE_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't set TRYSINK_MODE rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int smb2_disable_typec(struct smb_charger *chg)
+{
+	int rc;
+
+	/* configure FSM in idle state */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+			TYPEC_DISABLE_CMD_BIT, TYPEC_DISABLE_CMD_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't put FSM in idle rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure micro USB mode */
+	rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+			TYPE_C_OR_U_USB_BIT, TYPE_C_OR_U_USB_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't enable micro USB mode rc=%d\n", rc);
+		return rc;
+	}
+
+	/* release FSM from idle state */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+			TYPEC_DISABLE_CMD_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't release FSM rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int smb2_init_hw(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc;
+	u8 stat;
+
+	if (chip->dt.no_battery)
+		chg->fake_capacity = 50;
+
+	if (chip->dt.fcc_ua < 0)
+		smblib_get_charge_param(chg, &chg->param.fcc, &chip->dt.fcc_ua);
+
+	if (chip->dt.fv_uv < 0)
+		smblib_get_charge_param(chg, &chg->param.fv, &chip->dt.fv_uv);
+
+	smblib_get_charge_param(chg, &chg->param.usb_icl,
+				&chg->default_icl_ua);
+	if (chip->dt.usb_icl_ua < 0)
+		chip->dt.usb_icl_ua = chg->default_icl_ua;
+
+	if (chip->dt.dc_icl_ua < 0)
+		smblib_get_charge_param(chg, &chg->param.dc_icl,
+					&chip->dt.dc_icl_ua);
+
+	/* set a slower soft start setting for OTG */
+	rc = smblib_masked_write(chg, DC_ENG_SSUPPLY_CFG2_REG,
+				ENG_SSUPPLY_IVREF_OTG_SS_MASK, OTG_SS_SLOW);
+	if (rc < 0) {
+		pr_err("Couldn't set otg soft start rc=%d\n", rc);
+		return rc;
+	}
+
+	/* set OTG current limit */
+	rc = smblib_set_charge_param(chg, &chg->param.otg_cl,
+							chip->dt.otg_cl_ua);
+	if (rc < 0) {
+		pr_err("Couldn't set otg current limit rc=%d\n", rc);
+		return rc;
+	}
+
+	chg->boost_threshold_ua = chip->dt.boost_threshold_ua;
+
+	rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
+	if (rc < 0) {
+		pr_err("Couldn't read APSD_RESULT_STATUS rc=%d\n", rc);
+		return rc;
+	}
+
+	smblib_rerun_apsd_if_required(chg);
+
+	/* clear the ICL override if it is set */
+	if (smblib_icl_override(chg, false) < 0) {
+		pr_err("Couldn't disable ICL override rc=%d\n", rc);
+		return rc;
+	}
+
+	/* votes must be cast before configuring software control */
+	/* vote 0mA on usb_icl for non battery platforms */
+	vote(chg->usb_icl_votable,
+		DEFAULT_VOTER, chip->dt.no_battery, 0);
+	vote(chg->dc_suspend_votable,
+		DEFAULT_VOTER, chip->dt.no_battery, 0);
+	vote(chg->fcc_votable,
+		DEFAULT_VOTER, true, chip->dt.fcc_ua);
+	vote(chg->fv_votable,
+		DEFAULT_VOTER, true, chip->dt.fv_uv);
+	vote(chg->dc_icl_votable,
+		DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+	vote(chg->hvdcp_disable_votable_indirect, DEFAULT_VOTER,
+		chip->dt.hvdcp_disable, 0);
+	vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
+			true, 0);
+	vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER,
+			true, 0);
+	vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+			true, 0);
+	vote(chg->pd_disallowed_votable_indirect, MICRO_USB_VOTER,
+			chg->micro_usb_mode, 0);
+	vote(chg->hvdcp_enable_votable, MICRO_USB_VOTER,
+			chg->micro_usb_mode, 0);
+
+	/*
+	 * AICL configuration:
+	 * start from min and AICL ADC disable
+	 */
+	rc = smblib_masked_write(chg, USBIN_AICL_OPTIONS_CFG_REG,
+			USBIN_AICL_START_AT_MAX_BIT
+				| USBIN_AICL_ADC_EN_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure AICL rc=%d\n", rc);
+		return rc;
+	}
+
+	/* Configure charge enable for software control; active high */
+	rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+				 CHG_EN_POLARITY_BIT |
+				 CHG_EN_SRC_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure charger rc=%d\n", rc);
+		return rc;
+	}
+
+	/* enable the charging path */
+	rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't enable charging rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chg->micro_usb_mode)
+		rc = smb2_disable_typec(chg);
+	else
+		rc = smb2_configure_typec(chg);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure Type-C interrupts rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure VCONN for software control */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT,
+				 VCONN_EN_SRC_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure VCONN for SW control rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure VBUS for software control */
+	rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev,
+			"Couldn't configure VBUS for SW control rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smblib_masked_write(chg, QNOVO_PT_ENABLE_CMD_REG,
+			QNOVO_PT_ENABLE_CMD_BIT, QNOVO_PT_ENABLE_CMD_BIT);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't enable qnovo rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure step charging */
+	rc = smb2_config_step_charging(chip);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure step charging rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	/* configure wipower watts */
+	rc = smb2_config_wipower_input_power(chip, chip->dt.wipower_max_uw);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure wipower rc=%d\n", rc);
+		return rc;
+	}
+
+	/* disable SW STAT override */
+	rc = smblib_masked_write(chg, STAT_CFG_REG,
+				 STAT_SW_OVERRIDE_CFG_BIT, 0);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't disable SW STAT override rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	/* configure float charger options */
+	switch (chip->dt.float_option) {
+	case 1:
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+				FLOAT_OPTIONS_MASK, 0);
+		break;
+	case 2:
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+				FLOAT_OPTIONS_MASK, FORCE_FLOAT_SDP_CFG_BIT);
+		break;
+	case 3:
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+				FLOAT_OPTIONS_MASK, FLOAT_DIS_CHGING_CFG_BIT);
+		break;
+	case 4:
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_2_CFG_REG,
+				FLOAT_OPTIONS_MASK, SUSPEND_FLOAT_CFG_BIT);
+		break;
+	default:
+		rc = 0;
+		break;
+	}
+
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure float charger options rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	switch (chip->dt.chg_inhibit_thr_mv) {
+	case 50:
+		rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+				CHARGE_INHIBIT_THRESHOLD_MASK,
+				CHARGE_INHIBIT_THRESHOLD_50MV);
+		break;
+	case 100:
+		rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+				CHARGE_INHIBIT_THRESHOLD_MASK,
+				CHARGE_INHIBIT_THRESHOLD_100MV);
+		break;
+	case 200:
+		rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+				CHARGE_INHIBIT_THRESHOLD_MASK,
+				CHARGE_INHIBIT_THRESHOLD_200MV);
+		break;
+	case 300:
+		rc = smblib_masked_write(chg, CHARGE_INHIBIT_THRESHOLD_CFG_REG,
+				CHARGE_INHIBIT_THRESHOLD_MASK,
+				CHARGE_INHIBIT_THRESHOLD_300MV);
+		break;
+	case 0:
+		rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+				CHARGER_INHIBIT_BIT, 0);
+	default:
+		break;
+	}
+
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't configure charge inhibit threshold rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	if (chip->dt.auto_recharge_soc) {
+		rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+				SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+				VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+				VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+		if (rc < 0) {
+			dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+				rc);
+			return rc;
+		}
+	} else {
+		rc = smblib_masked_write(chg, FG_UPDATE_CFG_2_SEL_REG,
+				SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT |
+				VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT,
+				SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT);
+		if (rc < 0) {
+			dev_err(chg->dev, "Couldn't configure FG_UPDATE_CFG2_SEL_REG rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int smb2_chg_config_init(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct pmic_revid_data *pmic_rev_id;
+	struct device_node *revid_dev_node;
+
+	revid_dev_node = of_parse_phandle(chip->chg.dev->of_node,
+					  "qcom,pmic-revid", 0);
+	if (!revid_dev_node) {
+		pr_err("Missing qcom,pmic-revid property\n");
+		return -EINVAL;
+	}
+
+	pmic_rev_id = get_revid_data(revid_dev_node);
+	if (IS_ERR_OR_NULL(pmic_rev_id)) {
+		/*
+		 * the revid peripheral must be registered, any failure
+		 * here only indicates that the rev-id module has not
+		 * probed yet.
+		 */
+		return -EPROBE_DEFER;
+	}
+
+	switch (pmic_rev_id->pmic_subtype) {
+	case PMI8998_SUBTYPE:
+		chip->chg.smb_version = PMI8998_SUBTYPE;
+		chip->chg.wa_flags |= BOOST_BACK_WA | QC_AUTH_INTERRUPT_WA_BIT;
+		if (pmic_rev_id->rev4 == PMI8998_V1P1_REV4) /* PMI rev 1.1 */
+			chg->wa_flags |= QC_CHARGER_DETECTION_WA_BIT;
+		if (pmic_rev_id->rev4 == PMI8998_V2P0_REV4) /* PMI rev 2.0 */
+			chg->wa_flags |= TYPEC_CC2_REMOVAL_WA_BIT;
+		chg->chg_freq.freq_5V		= 600;
+		chg->chg_freq.freq_6V_8V	= 800;
+		chg->chg_freq.freq_9V		= 1000;
+		chg->chg_freq.freq_12V		= 1200;
+		chg->chg_freq.freq_removal	= 1000;
+		chg->chg_freq.freq_below_otg_threshold = 2000;
+		chg->chg_freq.freq_above_otg_threshold = 800;
+		break;
+	case PM660_SUBTYPE:
+		chip->chg.smb_version = PM660_SUBTYPE;
+		chip->chg.wa_flags |= BOOST_BACK_WA;
+		chg->param.freq_buck = pm660_params.freq_buck;
+		chg->param.freq_boost = pm660_params.freq_boost;
+		chg->chg_freq.freq_5V		= 600;
+		chg->chg_freq.freq_6V_8V	= 800;
+		chg->chg_freq.freq_9V		= 1050;
+		chg->chg_freq.freq_12V		= 1200;
+		chg->chg_freq.freq_removal	= 1050;
+		chg->chg_freq.freq_below_otg_threshold = 1600;
+		chg->chg_freq.freq_above_otg_threshold = 800;
+		break;
+	default:
+		pr_err("PMIC subtype %d not supported\n",
+				pmic_rev_id->pmic_subtype);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static int smb2_determine_initial_status(struct smb2 *chip)
+{
+	struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+	struct smb_charger *chg = &chip->chg;
+
+	if (chg->bms_psy)
+		smblib_suspend_on_debug_battery(chg);
+	smblib_handle_usb_plugin(0, &irq_data);
+	smblib_handle_usb_typec_change(0, &irq_data);
+	smblib_handle_usb_source_change(0, &irq_data);
+	smblib_handle_chg_state_change(0, &irq_data);
+	smblib_handle_icl_change(0, &irq_data);
+	smblib_handle_step_chg_state_change(0, &irq_data);
+	smblib_handle_step_chg_soc_update_request(0, &irq_data);
+
+	return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+static struct smb_irq_info smb2_irqs[] = {
+/* CHARGER IRQs */
+	[CHG_ERROR_IRQ] = {
+		.name		= "chg-error",
+		.handler	= smblib_handle_debug,
+	},
+	[CHG_STATE_CHANGE_IRQ] = {
+		.name		= "chg-state-change",
+		.handler	= smblib_handle_chg_state_change,
+		.wake		= true,
+	},
+	[STEP_CHG_STATE_CHANGE_IRQ] = {
+		.name		= "step-chg-state-change",
+		.handler	= smblib_handle_step_chg_state_change,
+		.wake		= true,
+	},
+	[STEP_CHG_SOC_UPDATE_FAIL_IRQ] = {
+		.name		= "step-chg-soc-update-fail",
+		.handler	= smblib_handle_step_chg_soc_update_fail,
+		.wake		= true,
+	},
+	[STEP_CHG_SOC_UPDATE_REQ_IRQ] = {
+		.name		= "step-chg-soc-update-request",
+		.handler	= smblib_handle_step_chg_soc_update_request,
+		.wake		= true,
+	},
+/* OTG IRQs */
+	[OTG_FAIL_IRQ] = {
+		.name		= "otg-fail",
+		.handler	= smblib_handle_debug,
+	},
+	[OTG_OVERCURRENT_IRQ] = {
+		.name		= "otg-overcurrent",
+		.handler	= smblib_handle_otg_overcurrent,
+	},
+	[OTG_OC_DIS_SW_STS_IRQ] = {
+		.name		= "otg-oc-dis-sw-sts",
+		.handler	= smblib_handle_debug,
+	},
+	[TESTMODE_CHANGE_DET_IRQ] = {
+		.name		= "testmode-change-detect",
+		.handler	= smblib_handle_debug,
+	},
+/* BATTERY IRQs */
+	[BATT_TEMP_IRQ] = {
+		.name		= "bat-temp",
+		.handler	= smblib_handle_batt_temp_changed,
+	},
+	[BATT_OCP_IRQ] = {
+		.name		= "bat-ocp",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_OV_IRQ] = {
+		.name		= "bat-ov",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_LOW_IRQ] = {
+		.name		= "bat-low",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_THERM_ID_MISS_IRQ] = {
+		.name		= "bat-therm-or-id-missing",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_TERM_MISS_IRQ] = {
+		.name		= "bat-terminal-missing",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+/* USB INPUT IRQs */
+	[USBIN_COLLAPSE_IRQ] = {
+		.name		= "usbin-collapse",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_LT_3P6V_IRQ] = {
+		.name		= "usbin-lt-3p6v",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_UV_IRQ] = {
+		.name		= "usbin-uv",
+		.handler	= smblib_handle_usbin_uv,
+	},
+	[USBIN_OV_IRQ] = {
+		.name		= "usbin-ov",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_PLUGIN_IRQ] = {
+		.name		= "usbin-plugin",
+		.handler	= smblib_handle_usb_plugin,
+		.wake		= true,
+	},
+	[USBIN_SRC_CHANGE_IRQ] = {
+		.name		= "usbin-src-change",
+		.handler	= smblib_handle_usb_source_change,
+		.wake		= true,
+	},
+	[USBIN_ICL_CHANGE_IRQ] = {
+		.name		= "usbin-icl-change",
+		.handler	= smblib_handle_icl_change,
+		.wake		= true,
+	},
+	[TYPE_C_CHANGE_IRQ] = {
+		.name		= "type-c-change",
+		.handler	= smblib_handle_usb_typec_change,
+		.wake		= true,
+	},
+/* DC INPUT IRQs */
+	[DCIN_COLLAPSE_IRQ] = {
+		.name		= "dcin-collapse",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_LT_3P6V_IRQ] = {
+		.name		= "dcin-lt-3p6v",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_UV_IRQ] = {
+		.name		= "dcin-uv",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_OV_IRQ] = {
+		.name		= "dcin-ov",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_PLUGIN_IRQ] = {
+		.name		= "dcin-plugin",
+		.handler	= smblib_handle_dc_plugin,
+		.wake		= true,
+	},
+	[DIV2_EN_DG_IRQ] = {
+		.name		= "div2-en-dg",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_ICL_CHANGE_IRQ] = {
+		.name		= "dcin-icl-change",
+		.handler	= smblib_handle_debug,
+	},
+/* MISCELLANEOUS IRQs */
+	[WDOG_SNARL_IRQ] = {
+		.name		= "wdog-snarl",
+		.handler	= NULL,
+	},
+	[WDOG_BARK_IRQ] = {
+		.name		= "wdog-bark",
+		.handler	= NULL,
+	},
+	[AICL_FAIL_IRQ] = {
+		.name		= "aicl-fail",
+		.handler	= smblib_handle_debug,
+	},
+	[AICL_DONE_IRQ] = {
+		.name		= "aicl-done",
+		.handler	= smblib_handle_debug,
+	},
+	[HIGH_DUTY_CYCLE_IRQ] = {
+		.name		= "high-duty-cycle",
+		.handler	= smblib_handle_high_duty_cycle,
+		.wake		= true,
+	},
+	[INPUT_CURRENT_LIMIT_IRQ] = {
+		.name		= "input-current-limiting",
+		.handler	= smblib_handle_debug,
+	},
+	[TEMPERATURE_CHANGE_IRQ] = {
+		.name		= "temperature-change",
+		.handler	= smblib_handle_debug,
+	},
+	[SWITCH_POWER_OK_IRQ] = {
+		.name		= "switcher-power-ok",
+		.handler	= smblib_handle_switcher_power_ok,
+		.storm_data	= {true, 1000, 3},
+	},
+};
+
+static int smb2_get_irq_index_byname(const char *irq_name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(smb2_irqs); i++) {
+		if (strcmp(smb2_irqs[i].name, irq_name) == 0)
+			return i;
+	}
+
+	return -ENOENT;
+}
+
+static int smb2_request_interrupt(struct smb2 *chip,
+				struct device_node *node, const char *irq_name)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc, irq, irq_index;
+	struct smb_irq_data *irq_data;
+
+	irq = of_irq_get_byname(node, irq_name);
+	if (irq < 0) {
+		pr_err("Couldn't get irq %s byname\n", irq_name);
+		return irq;
+	}
+
+	irq_index = smb2_get_irq_index_byname(irq_name);
+	if (irq_index < 0) {
+		pr_err("%s is not a defined irq\n", irq_name);
+		return irq_index;
+	}
+
+	if (!smb2_irqs[irq_index].handler)
+		return 0;
+
+	irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL);
+	if (!irq_data)
+		return -ENOMEM;
+
+	irq_data->parent_data = chip;
+	irq_data->name = irq_name;
+	irq_data->storm_data = smb2_irqs[irq_index].storm_data;
+	mutex_init(&irq_data->storm_data.storm_lock);
+
+	rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+					smb2_irqs[irq_index].handler,
+					IRQF_ONESHOT, irq_name, irq_data);
+	if (rc < 0) {
+		pr_err("Couldn't request irq %d\n", irq);
+		return rc;
+	}
+
+	smb2_irqs[irq_index].irq = irq;
+	smb2_irqs[irq_index].irq_data = irq_data;
+	if (smb2_irqs[irq_index].wake)
+		enable_irq_wake(irq);
+
+	return rc;
+}
+
+static int smb2_request_interrupts(struct smb2 *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct device_node *node = chg->dev->of_node;
+	struct device_node *child;
+	int rc = 0;
+	const char *name;
+	struct property *prop;
+
+	for_each_available_child_of_node(node, child) {
+		of_property_for_each_string(child, "interrupt-names",
+					    prop, name) {
+			rc = smb2_request_interrupt(chip, child, name);
+			if (rc < 0)
+				return rc;
+		}
+	}
+
+	return rc;
+}
+
+#if defined(CONFIG_DEBUG_FS)
+
+static int force_batt_psy_update_write(void *data, u64 val)
+{
+	struct smb_charger *chg = data;
+
+	power_supply_changed(chg->batt_psy);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_batt_psy_update_ops, NULL,
+			force_batt_psy_update_write, "0x%02llx\n");
+
+static int force_usb_psy_update_write(void *data, u64 val)
+{
+	struct smb_charger *chg = data;
+
+	power_supply_changed(chg->usb_psy);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_usb_psy_update_ops, NULL,
+			force_usb_psy_update_write, "0x%02llx\n");
+
+static int force_dc_psy_update_write(void *data, u64 val)
+{
+	struct smb_charger *chg = data;
+
+	power_supply_changed(chg->dc_psy);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_dc_psy_update_ops, NULL,
+			force_dc_psy_update_write, "0x%02llx\n");
+
+static void smb2_create_debugfs(struct smb2 *chip)
+{
+	struct dentry *file;
+
+	chip->dfs_root = debugfs_create_dir("charger", NULL);
+	if (IS_ERR_OR_NULL(chip->dfs_root)) {
+		pr_err("Couldn't create charger debugfs rc=%ld\n",
+			(long)chip->dfs_root);
+		return;
+	}
+
+	file = debugfs_create_file("force_batt_psy_update", 0600,
+			    chip->dfs_root, chip, &force_batt_psy_update_ops);
+	if (IS_ERR_OR_NULL(file))
+		pr_err("Couldn't create force_batt_psy_update file rc=%ld\n",
+			(long)file);
+
+	file = debugfs_create_file("force_usb_psy_update", 0600,
+			    chip->dfs_root, chip, &force_usb_psy_update_ops);
+	if (IS_ERR_OR_NULL(file))
+		pr_err("Couldn't create force_usb_psy_update file rc=%ld\n",
+			(long)file);
+
+	file = debugfs_create_file("force_dc_psy_update", 0600,
+			    chip->dfs_root, chip, &force_dc_psy_update_ops);
+	if (IS_ERR_OR_NULL(file))
+		pr_err("Couldn't create force_dc_psy_update file rc=%ld\n",
+			(long)file);
+}
+
+#else
+
+static void smb2_create_debugfs(struct smb2 *chip)
+{}
+
+#endif
+
+static int smb2_probe(struct platform_device *pdev)
+{
+	struct smb2 *chip;
+	struct smb_charger *chg;
+	int rc = 0;
+	union power_supply_propval val;
+	int usb_present, batt_present, batt_health, batt_charge_type;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chg = &chip->chg;
+	chg->dev = &pdev->dev;
+	chg->param = v1_params;
+	chg->debug_mask = &__debug_mask;
+	chg->mode = PARALLEL_MASTER;
+	chg->irq_info = smb2_irqs;
+	chg->name = "PMI";
+
+	chg->regmap = dev_get_regmap(chg->dev->parent, NULL);
+	if (!chg->regmap) {
+		pr_err("parent regmap is missing\n");
+		return -EINVAL;
+	}
+
+	rc = smb2_chg_config_init(chip);
+	if (rc < 0) {
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't setup chg_config rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smblib_init(chg);
+	if (rc < 0) {
+		pr_err("Smblib_init failed rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	/* set driver data before resources request it */
+	platform_set_drvdata(pdev, chip);
+
+	rc = smb2_init_vbus_regulator(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize vbus regulator rc=%d\n",
+			rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_vconn_regulator(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize vconn regulator rc=%d\n",
+			rc);
+		goto cleanup;
+	}
+
+	/* extcon registration */
+	chg->extcon = devm_extcon_dev_allocate(chg->dev, smblib_extcon_cable);
+	if (IS_ERR(chg->extcon)) {
+		rc = PTR_ERR(chg->extcon);
+		dev_err(chg->dev, "failed to allocate extcon device rc=%d\n",
+				rc);
+		goto cleanup;
+	}
+
+	rc = devm_extcon_dev_register(chg->dev, chg->extcon);
+	if (rc < 0) {
+		dev_err(chg->dev, "failed to register extcon device rc=%d\n",
+				rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_hw(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_dc_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize dc psy rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_usb_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_usb_main_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_init_batt_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb2_determine_initial_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n",
+			rc);
+		goto cleanup;
+	}
+
+	rc = smb2_request_interrupts(chip);
+	if (rc < 0) {
+		pr_err("Couldn't request interrupts rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	smb2_create_debugfs(chip);
+
+	rc = smblib_get_prop_usb_present(chg, &val);
+	if (rc < 0) {
+		pr_err("Couldn't get usb present rc=%d\n", rc);
+		goto cleanup;
+	}
+	usb_present = val.intval;
+
+	rc = smblib_get_prop_batt_present(chg, &val);
+	if (rc < 0) {
+		pr_err("Couldn't get batt present rc=%d\n", rc);
+		goto cleanup;
+	}
+	batt_present = val.intval;
+
+	rc = smblib_get_prop_batt_health(chg, &val);
+	if (rc < 0) {
+		pr_err("Couldn't get batt health rc=%d\n", rc);
+		goto cleanup;
+	}
+	batt_health = val.intval;
+
+	rc = smblib_get_prop_batt_charge_type(chg, &val);
+	if (rc < 0) {
+		pr_err("Couldn't get batt charge type rc=%d\n", rc);
+		goto cleanup;
+	}
+	batt_charge_type = val.intval;
+
+	pr_info("QPNP SMB2 probed successfully usb:present=%d type=%d batt:present = %d health = %d charge = %d\n",
+		usb_present, chg->usb_psy_desc.type,
+		batt_present, batt_health, batt_charge_type);
+	return rc;
+
+cleanup:
+	smblib_deinit(chg);
+	if (chg->usb_psy)
+		power_supply_unregister(chg->usb_psy);
+	if (chg->batt_psy)
+		power_supply_unregister(chg->batt_psy);
+	if (chg->vconn_vreg && chg->vconn_vreg->rdev)
+		regulator_unregister(chg->vconn_vreg->rdev);
+	if (chg->vbus_vreg && chg->vbus_vreg->rdev)
+		regulator_unregister(chg->vbus_vreg->rdev);
+	platform_set_drvdata(pdev, NULL);
+	return rc;
+}
+
+static int smb2_remove(struct platform_device *pdev)
+{
+	struct smb2 *chip = platform_get_drvdata(pdev);
+	struct smb_charger *chg = &chip->chg;
+
+	power_supply_unregister(chg->batt_psy);
+	power_supply_unregister(chg->usb_psy);
+	regulator_unregister(chg->vconn_vreg->rdev);
+	regulator_unregister(chg->vbus_vreg->rdev);
+
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static void smb2_shutdown(struct platform_device *pdev)
+{
+	struct smb2 *chip = platform_get_drvdata(pdev);
+	struct smb_charger *chg = &chip->chg;
+
+	/* configure power role for UFP */
+	smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				TYPEC_POWER_ROLE_CMD_MASK, UFP_EN_CMD_BIT);
+
+	/* force HVDCP to 5V */
+	smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+				HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT, 0);
+	smblib_write(chg, CMD_HVDCP_2_REG, FORCE_5V_BIT);
+
+	/* force enable APSD */
+	smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+				 AUTO_SRC_DETECT_BIT, AUTO_SRC_DETECT_BIT);
+}
+
+static const struct of_device_id match_table[] = {
+	{ .compatible = "qcom,qpnp-smb2", },
+	{ },
+};
+
+static struct platform_driver smb2_driver = {
+	.driver		= {
+		.name		= "qcom,qpnp-smb2",
+		.owner		= THIS_MODULE,
+		.of_match_table	= match_table,
+	},
+	.probe		= smb2_probe,
+	.remove		= smb2_remove,
+	.shutdown	= smb2_shutdown,
+};
+module_platform_driver(smb2_driver);
+
+MODULE_DESCRIPTION("QPNP SMB2 Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c
new file mode 100644
index 0000000..eb6727b
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-lib.c
@@ -0,0 +1,4290 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/device.h>
+#include <linux/regmap.h>
+#include <linux/delay.h>
+#include <linux/iio/consumer.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/irq.h>
+#include "smb-lib.h"
+#include "smb-reg.h"
+#include "storm-watch.h"
+#include "pmic-voter.h"
+
+#define smblib_err(chg, fmt, ...)		\
+	pr_err("%s: %s: " fmt, chg->name,	\
+		__func__, ##__VA_ARGS__)	\
+
+#define smblib_dbg(chg, reason, fmt, ...)			\
+	do {							\
+		if (*chg->debug_mask & (reason))		\
+			pr_info("%s: %s: " fmt, chg->name,	\
+				__func__, ##__VA_ARGS__);	\
+		else						\
+			pr_debug("%s: %s: " fmt, chg->name,	\
+				__func__, ##__VA_ARGS__);	\
+	} while (0)
+
+static bool is_secure(struct smb_charger *chg, int addr)
+{
+	if (addr == SHIP_MODE_REG || addr == FREQ_CLK_DIV_REG)
+		return true;
+	/* assume everything above 0xA0 is secure */
+	return (bool)((addr & 0xFF) >= 0xA0);
+}
+
+int smblib_read(struct smb_charger *chg, u16 addr, u8 *val)
+{
+	unsigned int temp;
+	int rc = 0;
+
+	rc = regmap_read(chg->regmap, addr, &temp);
+	if (rc >= 0)
+		*val = (u8)temp;
+
+	return rc;
+}
+
+int smblib_multibyte_read(struct smb_charger *chg, u16 addr, u8 *val,
+				int count)
+{
+	return regmap_bulk_read(chg->regmap, addr, val, count);
+}
+
+int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val)
+{
+	int rc = 0;
+
+	mutex_lock(&chg->write_lock);
+	if (is_secure(chg, addr)) {
+		rc = regmap_write(chg->regmap, (addr & 0xFF00) | 0xD0, 0xA5);
+		if (rc < 0)
+			goto unlock;
+	}
+
+	rc = regmap_update_bits(chg->regmap, addr, mask, val);
+
+unlock:
+	mutex_unlock(&chg->write_lock);
+	return rc;
+}
+
+int smblib_write(struct smb_charger *chg, u16 addr, u8 val)
+{
+	int rc = 0;
+
+	mutex_lock(&chg->write_lock);
+
+	if (is_secure(chg, addr)) {
+		rc = regmap_write(chg->regmap, (addr & ~(0xFF)) | 0xD0, 0xA5);
+		if (rc < 0)
+			goto unlock;
+	}
+
+	rc = regmap_write(chg->regmap, addr, val);
+
+unlock:
+	mutex_unlock(&chg->write_lock);
+	return rc;
+}
+
+static int smblib_get_step_cc_delta(struct smb_charger *chg, int *cc_delta_ua)
+{
+	int rc, step_state;
+	u8 stat;
+
+	if (!chg->step_chg_enabled) {
+		*cc_delta_ua = 0;
+		return 0;
+	}
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	step_state = (stat & STEP_CHARGING_STATUS_MASK) >>
+				STEP_CHARGING_STATUS_SHIFT;
+	rc = smblib_get_charge_param(chg, &chg->param.step_cc_delta[step_state],
+				     cc_delta_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int smblib_get_jeita_cc_delta(struct smb_charger *chg, int *cc_delta_ua)
+{
+	int rc, cc_minus_ua;
+	u8 stat;
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	if (!(stat & BAT_TEMP_STATUS_SOFT_LIMIT_MASK)) {
+		*cc_delta_ua = 0;
+		return 0;
+	}
+
+	rc = smblib_get_charge_param(chg, &chg->param.jeita_cc_comp,
+				     &cc_minus_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get jeita cc minus rc=%d\n", rc);
+		return rc;
+	}
+
+	*cc_delta_ua = -cc_minus_ua;
+	return 0;
+}
+
+int smblib_icl_override(struct smb_charger *chg, bool override)
+{
+	int rc;
+	bool override_status;
+	u8 stat;
+	u16 reg;
+
+	switch (chg->smb_version) {
+	case PMI8998_SUBTYPE:
+		reg = APSD_RESULT_STATUS_REG;
+		break;
+	case PM660_SUBTYPE:
+		reg = AICL_STATUS_REG;
+		break;
+	default:
+		smblib_dbg(chg, PR_MISC, "Unknown chip version=%x\n",
+				chg->smb_version);
+		return -EINVAL;
+	}
+
+	rc = smblib_read(chg, reg, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read reg=%x rc=%d\n", reg, rc);
+		return rc;
+	}
+	override_status = (bool)(stat & ICL_OVERRIDE_LATCH_BIT);
+
+	if (override != override_status) {
+		rc = smblib_masked_write(chg, CMD_APSD_REG,
+				ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't override ICL rc=%d\n", rc);
+			return rc;
+		}
+	}
+	return 0;
+}
+
+/********************
+ * REGISTER GETTERS *
+ ********************/
+
+int smblib_get_charge_param(struct smb_charger *chg,
+			    struct smb_chg_param *param, int *val_u)
+{
+	int rc = 0;
+	u8 val_raw;
+
+	rc = smblib_read(chg, param->reg, &val_raw);
+	if (rc < 0) {
+		smblib_err(chg, "%s: Couldn't read from 0x%04x rc=%d\n",
+			param->name, param->reg, rc);
+		return rc;
+	}
+
+	if (param->get_proc)
+		*val_u = param->get_proc(param, val_raw);
+	else
+		*val_u = val_raw * param->step_u + param->min_u;
+	smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
+		   param->name, *val_u, val_raw);
+
+	return rc;
+}
+
+int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend)
+{
+	int rc = 0;
+	u8 temp;
+
+	rc = smblib_read(chg, USBIN_CMD_IL_REG, &temp);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read USBIN_CMD_IL rc=%d\n", rc);
+		return rc;
+	}
+	*suspend = temp & USBIN_SUSPEND_BIT;
+
+	return rc;
+}
+
+struct apsd_result {
+	const char * const name;
+	const u8 bit;
+	const enum power_supply_type pst;
+};
+
+enum {
+	UNKNOWN,
+	SDP,
+	CDP,
+	DCP,
+	OCP,
+	FLOAT,
+	HVDCP2,
+	HVDCP3,
+	MAX_TYPES
+};
+
+static const struct apsd_result const smblib_apsd_results[] = {
+	[UNKNOWN] = {
+		.name	= "UNKNOWN",
+		.bit	= 0,
+		.pst	= POWER_SUPPLY_TYPE_UNKNOWN
+	},
+	[SDP] = {
+		.name	= "SDP",
+		.bit	= SDP_CHARGER_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB
+	},
+	[CDP] = {
+		.name	= "CDP",
+		.bit	= CDP_CHARGER_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_CDP
+	},
+	[DCP] = {
+		.name	= "DCP",
+		.bit	= DCP_CHARGER_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_DCP
+	},
+	[OCP] = {
+		.name	= "OCP",
+		.bit	= OCP_CHARGER_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_DCP
+	},
+	[FLOAT] = {
+		.name	= "FLOAT",
+		.bit	= FLOAT_CHARGER_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_DCP
+	},
+	[HVDCP2] = {
+		.name	= "HVDCP2",
+		.bit	= DCP_CHARGER_BIT | QC_2P0_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_HVDCP
+	},
+	[HVDCP3] = {
+		.name	= "HVDCP3",
+		.bit	= DCP_CHARGER_BIT | QC_3P0_BIT,
+		.pst	= POWER_SUPPLY_TYPE_USB_HVDCP_3,
+	},
+};
+
+static const struct apsd_result *smblib_get_apsd_result(struct smb_charger *chg)
+{
+	int rc, i;
+	u8 apsd_stat, stat;
+	const struct apsd_result *result = &smblib_apsd_results[UNKNOWN];
+
+	rc = smblib_read(chg, APSD_STATUS_REG, &apsd_stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+		return result;
+	}
+	smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", apsd_stat);
+
+	if (!(apsd_stat & APSD_DTC_STATUS_DONE_BIT))
+		return result;
+
+	rc = smblib_read(chg, APSD_RESULT_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read APSD_RESULT_STATUS rc=%d\n",
+			rc);
+		return result;
+	}
+	stat &= APSD_RESULT_STATUS_MASK;
+
+	for (i = 0; i < ARRAY_SIZE(smblib_apsd_results); i++) {
+		if (smblib_apsd_results[i].bit == stat)
+			result = &smblib_apsd_results[i];
+	}
+
+	if (apsd_stat & QC_CHARGER_BIT) {
+		/* since its a qc_charger, either return HVDCP3 or HVDCP2 */
+		if (result != &smblib_apsd_results[HVDCP3])
+			result = &smblib_apsd_results[HVDCP2];
+	}
+
+	return result;
+}
+
+/********************
+ * REGISTER SETTERS *
+ ********************/
+
+static int chg_freq_list[] = {
+	9600, 9600, 6400, 4800, 3800, 3200, 2700, 2400, 2100, 1900, 1700,
+	1600, 1500, 1400, 1300, 1200,
+};
+
+int smblib_set_chg_freq(struct smb_chg_param *param,
+				int val_u, u8 *val_raw)
+{
+	u8 i;
+
+	if (val_u > param->max_u || val_u < param->min_u)
+		return -EINVAL;
+
+	/* Charger FSW is the configured freqency / 2 */
+	val_u *= 2;
+	for (i = 0; i < ARRAY_SIZE(chg_freq_list); i++) {
+		if (chg_freq_list[i] == val_u)
+			break;
+	}
+	if (i == ARRAY_SIZE(chg_freq_list)) {
+		pr_err("Invalid frequency %d Hz\n", val_u / 2);
+		return -EINVAL;
+	}
+
+	*val_raw = i;
+
+	return 0;
+}
+
+static int smblib_set_opt_freq_buck(struct smb_charger *chg, int fsw_khz)
+{
+	union power_supply_propval pval = {0, };
+	int rc = 0;
+
+	rc = smblib_set_charge_param(chg, &chg->param.freq_buck, fsw_khz);
+	if (rc < 0)
+		dev_err(chg->dev, "Error in setting freq_buck rc=%d\n", rc);
+
+	if (chg->mode == PARALLEL_MASTER && chg->pl.psy) {
+		pval.intval = fsw_khz;
+		/*
+		 * Some parallel charging implementations may not have
+		 * PROP_BUCK_FREQ property - they could be running
+		 * with a fixed frequency
+		 */
+		power_supply_set_property(chg->pl.psy,
+				POWER_SUPPLY_PROP_BUCK_FREQ, &pval);
+	}
+
+	return rc;
+}
+
+int smblib_set_charge_param(struct smb_charger *chg,
+			    struct smb_chg_param *param, int val_u)
+{
+	int rc = 0;
+	u8 val_raw;
+
+	if (param->set_proc) {
+		rc = param->set_proc(param, val_u, &val_raw);
+		if (rc < 0)
+			return -EINVAL;
+	} else {
+		if (val_u > param->max_u || val_u < param->min_u) {
+			smblib_err(chg, "%s: %d is out of range [%d, %d]\n",
+				param->name, val_u, param->min_u, param->max_u);
+			return -EINVAL;
+		}
+
+		val_raw = (val_u - param->min_u) / param->step_u;
+	}
+
+	rc = smblib_write(chg, param->reg, val_raw);
+	if (rc < 0) {
+		smblib_err(chg, "%s: Couldn't write 0x%02x to 0x%04x rc=%d\n",
+			param->name, val_raw, param->reg, rc);
+		return rc;
+	}
+
+	smblib_dbg(chg, PR_REGISTER, "%s = %d (0x%02x)\n",
+		   param->name, val_u, val_raw);
+
+	return rc;
+}
+
+static int step_charge_soc_update(struct smb_charger *chg, int capacity)
+{
+	int rc = 0;
+
+	rc = smblib_set_charge_param(chg, &chg->param.step_soc, capacity);
+	if (rc < 0) {
+		smblib_err(chg, "Error in updating soc, rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smblib_write(chg, STEP_CHG_SOC_VBATT_V_UPDATE_REG,
+			STEP_CHG_SOC_VBATT_V_UPDATE_BIT);
+	if (rc < 0) {
+		smblib_err(chg,
+			"Couldn't set STEP_CHG_SOC_VBATT_V_UPDATE_REG rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend)
+{
+	int rc = 0;
+
+	rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
+				 suspend ? USBIN_SUSPEND_BIT : 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't write %s to USBIN_SUSPEND_BIT rc=%d\n",
+			suspend ? "suspend" : "resume", rc);
+
+	return rc;
+}
+
+int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend)
+{
+	int rc = 0;
+
+	rc = smblib_masked_write(chg, DCIN_CMD_IL_REG, DCIN_SUSPEND_BIT,
+				 suspend ? DCIN_SUSPEND_BIT : 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't write %s to DCIN_SUSPEND_BIT rc=%d\n",
+			suspend ? "suspend" : "resume", rc);
+
+	return rc;
+}
+
+static int smblib_set_adapter_allowance(struct smb_charger *chg,
+					u8 allowed_voltage)
+{
+	int rc = 0;
+
+	switch (allowed_voltage) {
+	case USBIN_ADAPTER_ALLOW_12V:
+	case USBIN_ADAPTER_ALLOW_5V_OR_12V:
+	case USBIN_ADAPTER_ALLOW_9V_TO_12V:
+	case USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V:
+	case USBIN_ADAPTER_ALLOW_5V_TO_12V:
+		/* PM660 only support max. 9V */
+		if (chg->smb_version == PM660_SUBTYPE) {
+			smblib_dbg(chg, PR_MISC, "voltage not supported=%d\n",
+					allowed_voltage);
+			allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
+		}
+		break;
+	}
+
+	rc = smblib_write(chg, USBIN_ADAPTER_ALLOW_CFG_REG, allowed_voltage);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't write 0x%02x to USBIN_ADAPTER_ALLOW_CFG rc=%d\n",
+			allowed_voltage, rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+#define MICRO_5V	5000000
+#define MICRO_9V	9000000
+#define MICRO_12V	12000000
+static int smblib_set_usb_pd_allowed_voltage(struct smb_charger *chg,
+					int min_allowed_uv, int max_allowed_uv)
+{
+	int rc;
+	u8 allowed_voltage;
+
+	if (min_allowed_uv == MICRO_5V && max_allowed_uv == MICRO_5V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_5V;
+		smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_5V);
+	} else if (min_allowed_uv == MICRO_9V && max_allowed_uv == MICRO_9V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_9V;
+		smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_9V);
+	} else if (min_allowed_uv == MICRO_12V && max_allowed_uv == MICRO_12V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_12V;
+		smblib_set_opt_freq_buck(chg, chg->chg_freq.freq_12V);
+	} else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_9V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_9V;
+	} else if (min_allowed_uv < MICRO_9V && max_allowed_uv <= MICRO_12V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_5V_TO_12V;
+	} else if (min_allowed_uv < MICRO_12V && max_allowed_uv <= MICRO_12V) {
+		allowed_voltage = USBIN_ADAPTER_ALLOW_9V_TO_12V;
+	} else {
+		smblib_err(chg, "invalid allowed voltage [%d, %d]\n",
+			min_allowed_uv, max_allowed_uv);
+		return -EINVAL;
+	}
+
+	rc = smblib_set_adapter_allowance(chg, allowed_voltage);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't configure adapter allowance rc=%d\n",
+				rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+/********************
+ * HELPER FUNCTIONS *
+ ********************/
+
+static int try_rerun_apsd_for_hvdcp(struct smb_charger *chg)
+{
+	const struct apsd_result *apsd_result;
+
+	/*
+	 * PD_INACTIVE_VOTER on hvdcp_disable_votable indicates whether
+	 * apsd rerun was tried earlier
+	 */
+	if (get_client_vote(chg->hvdcp_disable_votable_indirect,
+						PD_INACTIVE_VOTER)) {
+		vote(chg->hvdcp_disable_votable_indirect,
+				PD_INACTIVE_VOTER, false, 0);
+		/* ensure hvdcp is enabled */
+		if (!get_effective_result(
+				chg->hvdcp_disable_votable_indirect)) {
+			apsd_result = smblib_get_apsd_result(chg);
+			if (apsd_result->bit & (QC_2P0_BIT | QC_3P0_BIT)) {
+				/* rerun APSD */
+				smblib_dbg(chg, PR_MISC, "rerun APSD\n");
+				smblib_masked_write(chg, CMD_APSD_REG,
+						APSD_RERUN_BIT,
+						APSD_RERUN_BIT);
+			}
+		}
+	}
+	return 0;
+}
+
+static const struct apsd_result *smblib_update_usb_type(struct smb_charger *chg)
+{
+	const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+
+	/* if PD is active, APSD is disabled so won't have a valid result */
+	if (chg->pd_active) {
+		chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_USB_PD;
+		return apsd_result;
+	}
+
+	chg->usb_psy_desc.type = apsd_result->pst;
+	return apsd_result;
+}
+
+static int smblib_notifier_call(struct notifier_block *nb,
+		unsigned long ev, void *v)
+{
+	struct power_supply *psy = v;
+	struct smb_charger *chg = container_of(nb, struct smb_charger, nb);
+
+	if (!strcmp(psy->desc->name, "bms")) {
+		if (!chg->bms_psy)
+			chg->bms_psy = psy;
+		if (ev == PSY_EVENT_PROP_CHANGED)
+			schedule_work(&chg->bms_update_work);
+	}
+
+	if (!chg->pl.psy && !strcmp(psy->desc->name, "parallel"))
+		chg->pl.psy = psy;
+
+	return NOTIFY_OK;
+}
+
+static int smblib_register_notifier(struct smb_charger *chg)
+{
+	int rc;
+
+	chg->nb.notifier_call = smblib_notifier_call;
+	rc = power_supply_reg_notifier(&chg->nb);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't register psy notifier rc = %d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
+					     int val_u, u8 *val_raw)
+{
+	if (val_u > param->max_u || val_u < param->min_u)
+		return -EINVAL;
+
+	*val_raw = val_u << 1;
+
+	return 0;
+}
+
+int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
+					   u8 val_raw)
+{
+	int val_u  = val_raw * param->step_u + param->min_u;
+
+	if (val_u > param->max_u)
+		val_u -= param->max_u * 2;
+
+	return val_u;
+}
+
+int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
+					     int val_u, u8 *val_raw)
+{
+	if (val_u > param->max_u || val_u < param->min_u - param->max_u)
+		return -EINVAL;
+
+	val_u += param->max_u * 2 - param->min_u;
+	val_u %= param->max_u * 2;
+	*val_raw = val_u / param->step_u;
+
+	return 0;
+}
+
+static void smblib_uusb_removal(struct smb_charger *chg)
+{
+	int rc;
+
+	/* reset both usbin current and voltage votes */
+	vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+	vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
+	vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER, true, 0);
+
+	cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+
+	if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+		/* re-enable AUTH_IRQ_EN_CFG_BIT */
+		rc = smblib_masked_write(chg,
+				USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+				AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
+		if (rc < 0)
+			smblib_err(chg,
+				"Couldn't enable QC auth setting rc=%d\n", rc);
+	}
+
+	/* reconfigure allowed voltage for HVDCP */
+	rc = smblib_set_adapter_allowance(chg,
+			USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
+			rc);
+
+	chg->voltage_min_uv = MICRO_5V;
+	chg->voltage_max_uv = MICRO_5V;
+	chg->usb_icl_delta_ua = 0;
+	chg->pulse_cnt = 0;
+
+	/* clear USB ICL vote for USB_PSY_VOTER */
+	rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't un-vote for USB ICL rc=%d\n", rc);
+
+	/* clear USB ICL vote for DCP_VOTER */
+	rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg,
+			"Couldn't un-vote DCP from USB ICL rc=%d\n", rc);
+
+	/* clear USB ICL vote for PL_USBIN_USBIN_VOTER */
+	rc = vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg,
+			"Couldn't un-vote PL_USBIN_USBIN from USB ICL rc=%d\n",
+			rc);
+}
+
+static bool smblib_sysok_reason_usbin(struct smb_charger *chg)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, SYSOK_REASON_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get SYSOK_REASON_STATUS rc=%d\n", rc);
+		/* assuming 'not usbin' in case of read failure */
+		return false;
+	}
+
+	return stat & SYSOK_REASON_USBIN_BIT;
+}
+
+void smblib_suspend_on_debug_battery(struct smb_charger *chg)
+{
+	int rc;
+	union power_supply_propval val;
+
+	if (!chg->suspend_input_on_debug_batt)
+		return;
+
+	rc = power_supply_get_property(chg->bms_psy,
+			POWER_SUPPLY_PROP_DEBUG_BATTERY, &val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get debug battery prop rc=%d\n", rc);
+		return;
+	}
+
+	vote(chg->usb_icl_votable, DEBUG_BOARD_VOTER, val.intval, 0);
+	vote(chg->dc_suspend_votable, DEBUG_BOARD_VOTER, val.intval, 0);
+	if (val.intval)
+		pr_info("Input suspended: Fake battery\n");
+}
+
+int smblib_rerun_apsd_if_required(struct smb_charger *chg)
+{
+	const struct apsd_result *apsd_result;
+	union power_supply_propval val;
+	int rc;
+
+	rc = smblib_get_prop_usb_present(chg, &val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get usb present rc = %d\n", rc);
+		return rc;
+	}
+
+	if (!val.intval)
+		return 0;
+
+	apsd_result = smblib_get_apsd_result(chg);
+	if ((apsd_result->pst == POWER_SUPPLY_TYPE_UNKNOWN)
+		|| (apsd_result->pst == POWER_SUPPLY_TYPE_USB)) {
+		/* rerun APSD */
+		pr_info("Reruning APSD type = %s at bootup\n",
+				apsd_result->name);
+		rc = smblib_masked_write(chg, CMD_APSD_REG,
+					APSD_RERUN_BIT,
+					APSD_RERUN_BIT);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't rerun APSD rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int smblib_get_pulse_cnt(struct smb_charger *chg, int *count)
+{
+	int rc;
+	u8 val[2];
+
+	switch (chg->smb_version) {
+	case PMI8998_SUBTYPE:
+		rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, val);
+		if (rc) {
+			pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
+					rc);
+			return rc;
+		}
+		*count = val[0] & QC_PULSE_COUNT_MASK;
+		break;
+	case PM660_SUBTYPE:
+		rc = smblib_multibyte_read(chg,
+				QC_PULSE_COUNT_STATUS_1_REG, val, 2);
+		if (rc) {
+			pr_err("failed to read QC_PULSE_COUNT_STATUS_1_REG rc=%d\n",
+					rc);
+			return rc;
+		}
+		*count = (val[1] << 8) | val[0];
+		break;
+	default:
+		smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n",
+				chg->smb_version);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/*********************
+ * VOTABLE CALLBACKS *
+ *********************/
+
+static int smblib_dc_suspend_vote_callback(struct votable *votable, void *data,
+			int suspend, const char *client)
+{
+	struct smb_charger *chg = data;
+
+	/* resume input if suspend is invalid */
+	if (suspend < 0)
+		suspend = 0;
+
+	return smblib_set_dc_suspend(chg, (bool)suspend);
+}
+
+#define USBIN_25MA	25000
+#define USBIN_100MA	100000
+#define USBIN_150MA	150000
+#define USBIN_500MA	500000
+#define USBIN_900MA	900000
+
+
+static int set_sdp_current(struct smb_charger *chg, int icl_ua)
+{
+	int rc;
+	u8 icl_options;
+
+	/* power source is SDP */
+	switch (icl_ua) {
+	case USBIN_100MA:
+		/* USB 2.0 100mA */
+		icl_options = 0;
+		break;
+	case USBIN_150MA:
+		/* USB 3.0 150mA */
+		icl_options = CFG_USB3P0_SEL_BIT;
+		break;
+	case USBIN_500MA:
+		/* USB 2.0 500mA */
+		icl_options = USB51_MODE_BIT;
+		break;
+	case USBIN_900MA:
+		/* USB 3.0 900mA */
+		icl_options = CFG_USB3P0_SEL_BIT | USB51_MODE_BIT;
+		break;
+	default:
+		smblib_err(chg, "ICL %duA isn't supported for SDP\n", icl_ua);
+		return -EINVAL;
+	}
+
+	rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
+		CFG_USB3P0_SEL_BIT | USB51_MODE_BIT, icl_options);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't set ICL options rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int smblib_usb_icl_vote_callback(struct votable *votable, void *data,
+			int icl_ua, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc = 0;
+	bool override;
+	union power_supply_propval pval;
+
+	/* suspend and return if 25mA or less is requested */
+	if (client && (icl_ua < USBIN_25MA))
+		return smblib_set_usb_suspend(chg, true);
+
+	disable_irq_nosync(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
+	if (!client)
+		goto override_suspend_config;
+
+	rc = smblib_get_prop_typec_mode(chg, &pval);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get typeC mode rc = %d\n", rc);
+		goto enable_icl_changed_interrupt;
+	}
+
+	/* configure current */
+	if (pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT
+		&& (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)) {
+		rc = set_sdp_current(chg, icl_ua);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't set SDP ICL rc=%d\n", rc);
+			goto enable_icl_changed_interrupt;
+		}
+	} else {
+		rc = smblib_set_charge_param(chg, &chg->param.usb_icl,
+				icl_ua - chg->icl_reduction_ua);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't set HC ICL rc=%d\n", rc);
+			goto enable_icl_changed_interrupt;
+		}
+	}
+
+override_suspend_config:
+	/* determine if override needs to be enforced */
+	override = true;
+	if (client == NULL) {
+		/* remove override if no voters - hw defaults is desired */
+		override = false;
+	} else if (pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) {
+		if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB)
+			/* For std cable with type = SDP never override */
+			override = false;
+		else if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_CDP
+			&& icl_ua - chg->icl_reduction_ua == 1500000)
+			/*
+			 * For std cable with type = CDP override only if
+			 * current is not 1500mA
+			 */
+			override = false;
+	}
+
+	/* enforce override */
+	rc = smblib_masked_write(chg, USBIN_ICL_OPTIONS_REG,
+		USBIN_MODE_CHG_BIT, override ? USBIN_MODE_CHG_BIT : 0);
+
+	rc = smblib_icl_override(chg, override);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't set ICL override rc=%d\n", rc);
+		goto enable_icl_changed_interrupt;
+	}
+
+	/* unsuspend after configuring current and override */
+	rc = smblib_set_usb_suspend(chg, false);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't resume input rc=%d\n", rc);
+		goto enable_icl_changed_interrupt;
+	}
+
+enable_icl_changed_interrupt:
+	enable_irq(chg->irq_info[USBIN_ICL_CHANGE_IRQ].irq);
+	return rc;
+}
+
+static int smblib_dc_icl_vote_callback(struct votable *votable, void *data,
+			int icl_ua, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc = 0;
+	bool suspend;
+
+	if (icl_ua < 0) {
+		smblib_dbg(chg, PR_MISC, "No Voter hence suspending\n");
+		icl_ua = 0;
+	}
+
+	suspend = (icl_ua < USBIN_25MA);
+	if (suspend)
+		goto suspend;
+
+	rc = smblib_set_charge_param(chg, &chg->param.dc_icl, icl_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't set DC input current limit rc=%d\n",
+			rc);
+		return rc;
+	}
+
+suspend:
+	rc = vote(chg->dc_suspend_votable, USER_VOTER, suspend, 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
+			suspend ? "suspend" : "resume", rc);
+		return rc;
+	}
+	return rc;
+}
+
+static int smblib_pd_disallowed_votable_indirect_callback(
+	struct votable *votable, void *data, int disallowed, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc;
+
+	rc = vote(chg->pd_allowed_votable, PD_DISALLOWED_INDIRECT_VOTER,
+		!disallowed, 0);
+
+	return rc;
+}
+
+static int smblib_awake_vote_callback(struct votable *votable, void *data,
+			int awake, const char *client)
+{
+	struct smb_charger *chg = data;
+
+	if (awake)
+		pm_stay_awake(chg->dev);
+	else
+		pm_relax(chg->dev);
+
+	return 0;
+}
+
+static int smblib_chg_disable_vote_callback(struct votable *votable, void *data,
+			int chg_disable, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc;
+
+	rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
+				 CHARGING_ENABLE_CMD_BIT,
+				 chg_disable ? 0 : CHARGING_ENABLE_CMD_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't %s charging rc=%d\n",
+			chg_disable ? "disable" : "enable", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int smblib_pl_enable_indirect_vote_callback(struct votable *votable,
+			void *data, int chg_enable, const char *client)
+{
+	struct smb_charger *chg = data;
+
+	vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, !chg_enable, 0);
+
+	return 0;
+}
+
+static int smblib_hvdcp_enable_vote_callback(struct votable *votable,
+			void *data,
+			int hvdcp_enable, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc;
+	u8 val = HVDCP_AUTH_ALG_EN_CFG_BIT | HVDCP_EN_BIT;
+
+	/* vote to enable/disable HW autonomous INOV */
+	vote(chg->hvdcp_hw_inov_dis_votable, client, !hvdcp_enable, 0);
+
+	/*
+	 * Disable the autonomous bit and auth bit for disabling hvdcp.
+	 * This ensures only qc 2.0 detection runs but no vbus
+	 * negotiation happens.
+	 */
+	if (!hvdcp_enable)
+		val = HVDCP_EN_BIT;
+
+	rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+				 HVDCP_EN_BIT | HVDCP_AUTH_ALG_EN_CFG_BIT,
+				 val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
+			hvdcp_enable ? "enable" : "disable", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int smblib_hvdcp_disable_indirect_vote_callback(struct votable *votable,
+			void *data, int hvdcp_disable, const char *client)
+{
+	struct smb_charger *chg = data;
+
+	vote(chg->hvdcp_enable_votable, HVDCP_INDIRECT_VOTER,
+			!hvdcp_disable, 0);
+
+	return 0;
+}
+
+static int smblib_apsd_disable_vote_callback(struct votable *votable,
+			void *data,
+			int apsd_disable, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc;
+
+	if (apsd_disable) {
+		/* Don't run APSD on CC debounce when APSD is disabled */
+		rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+							APSD_START_ON_CC_BIT,
+							0);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't disable APSD_START_ON_CC rc=%d\n",
+									rc);
+			return rc;
+		}
+
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+							AUTO_SRC_DETECT_BIT,
+							0);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't disable APSD rc=%d\n", rc);
+			return rc;
+		}
+	} else {
+		rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+							AUTO_SRC_DETECT_BIT,
+							AUTO_SRC_DETECT_BIT);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't enable APSD rc=%d\n", rc);
+			return rc;
+		}
+
+		rc = smblib_masked_write(chg, TYPE_C_CFG_REG,
+							APSD_START_ON_CC_BIT,
+							APSD_START_ON_CC_BIT);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't enable APSD_START_ON_CC rc=%d\n",
+									rc);
+			return rc;
+		}
+	}
+
+	return 0;
+}
+
+static int smblib_hvdcp_hw_inov_dis_vote_callback(struct votable *votable,
+				void *data, int disable, const char *client)
+{
+	struct smb_charger *chg = data;
+	int rc;
+
+	if (disable) {
+		/*
+		 * the pulse count register get zeroed when autonomous mode is
+		 * disabled. Track that in variables before disabling
+		 */
+		rc = smblib_get_pulse_cnt(chg, &chg->pulse_cnt);
+		if (rc < 0) {
+			pr_err("failed to read QC_PULSE_COUNT_STATUS_REG rc=%d\n",
+					rc);
+			return rc;
+		}
+	}
+
+	rc = smblib_masked_write(chg, USBIN_OPTIONS_1_CFG_REG,
+			HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT,
+			disable ? 0 : HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't %s hvdcp rc=%d\n",
+				disable ? "disable" : "enable", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+/*******************
+ * VCONN REGULATOR *
+ * *****************/
+
+#define MAX_OTG_SS_TRIES 2
+static int _smblib_vconn_regulator_enable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	u8 otg_stat, stat4;
+	int rc = 0, i;
+
+	if (!chg->external_vconn) {
+		/*
+		 * Hardware based OTG soft start should complete within 1ms, so
+		 * wait for 2ms in the worst case.
+		 */
+		for (i = 0; i < MAX_OTG_SS_TRIES; ++i) {
+			usleep_range(1000, 1100);
+			rc = smblib_read(chg, OTG_STATUS_REG, &otg_stat);
+			if (rc < 0) {
+				smblib_err(chg, "Couldn't read OTG status rc=%d\n",
+									rc);
+				return rc;
+			}
+
+			if (otg_stat & BOOST_SOFTSTART_DONE_BIT)
+				break;
+		}
+
+		if (!(otg_stat & BOOST_SOFTSTART_DONE_BIT)) {
+			smblib_err(chg, "Couldn't enable VCONN; OTG soft start failed\n");
+			return -EAGAIN;
+		}
+	}
+
+	/*
+	 * VCONN_EN_ORIENTATION is overloaded with overriding the CC pin used
+	 * for Vconn, and it should be set with reverse polarity of CC_OUT.
+	 */
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+		return rc;
+	}
+
+	smblib_dbg(chg, PR_OTG, "enabling VCONN\n");
+	stat4 = stat4 & CC_ORIENTATION_BIT ? 0 : VCONN_EN_ORIENTATION_BIT;
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 VCONN_EN_VALUE_BIT | VCONN_EN_ORIENTATION_BIT,
+				 VCONN_EN_VALUE_BIT | stat4);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't enable vconn setting rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int smblib_vconn_regulator_enable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	mutex_lock(&chg->otg_oc_lock);
+	if (chg->vconn_en)
+		goto unlock;
+
+	rc = _smblib_vconn_regulator_enable(rdev);
+	if (rc >= 0)
+		chg->vconn_en = true;
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+	return rc;
+}
+
+static int _smblib_vconn_regulator_disable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	smblib_dbg(chg, PR_OTG, "disabling VCONN\n");
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 VCONN_EN_VALUE_BIT, 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't disable vconn regulator rc=%d\n", rc);
+
+	return rc;
+}
+
+int smblib_vconn_regulator_disable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	mutex_lock(&chg->otg_oc_lock);
+	if (!chg->vconn_en)
+		goto unlock;
+
+	rc = _smblib_vconn_regulator_disable(rdev);
+	if (rc >= 0)
+		chg->vconn_en = false;
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+	return rc;
+}
+
+int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int ret;
+
+	mutex_lock(&chg->otg_oc_lock);
+	ret = chg->vconn_en;
+	mutex_unlock(&chg->otg_oc_lock);
+	return ret;
+}
+
+/*****************
+ * OTG REGULATOR *
+ *****************/
+
+static int _smblib_vbus_regulator_enable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc;
+
+	smblib_dbg(chg, PR_OTG, "halt 1 in 8 mode\n");
+	rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
+				 ENG_BUCKBOOST_HALT1_8_MODE_BIT,
+				 ENG_BUCKBOOST_HALT1_8_MODE_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	smblib_dbg(chg, PR_OTG, "enabling OTG\n");
+	rc = smblib_write(chg, CMD_OTG_REG, OTG_EN_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't enable OTG regulator rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int smblib_vbus_regulator_enable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	mutex_lock(&chg->otg_oc_lock);
+	if (chg->otg_en)
+		goto unlock;
+
+	rc = _smblib_vbus_regulator_enable(rdev);
+	if (rc >= 0)
+		chg->otg_en = true;
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+	return rc;
+}
+
+static int _smblib_vbus_regulator_disable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc;
+
+	if (!chg->external_vconn && chg->vconn_en) {
+		smblib_dbg(chg, PR_OTG, "Killing VCONN before disabling OTG\n");
+		rc = _smblib_vconn_regulator_disable(rdev);
+		if (rc < 0)
+			smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc);
+	}
+
+	smblib_dbg(chg, PR_OTG, "disabling OTG\n");
+	rc = smblib_write(chg, CMD_OTG_REG, 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't disable OTG regulator rc=%d\n", rc);
+		return rc;
+	}
+
+	smblib_dbg(chg, PR_OTG, "start 1 in 8 mode\n");
+	rc = smblib_write(chg, CMD_OTG_REG, 0);
+	rc = smblib_masked_write(chg, OTG_ENG_OTG_CFG_REG,
+				 ENG_BUCKBOOST_HALT1_8_MODE_BIT, 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't set OTG_ENG_OTG_CFG_REG rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+int smblib_vbus_regulator_disable(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int rc = 0;
+
+	mutex_lock(&chg->otg_oc_lock);
+	if (!chg->otg_en)
+		goto unlock;
+
+	rc = _smblib_vbus_regulator_disable(rdev);
+	if (rc >= 0)
+		chg->otg_en = false;
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+	return rc;
+}
+
+int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev)
+{
+	struct smb_charger *chg = rdev_get_drvdata(rdev);
+	int ret;
+
+	mutex_lock(&chg->otg_oc_lock);
+	ret = chg->otg_en;
+	mutex_unlock(&chg->otg_oc_lock);
+	return ret;
+}
+
+/********************
+ * BATT PSY GETTERS *
+ ********************/
+
+int smblib_get_prop_input_suspend(struct smb_charger *chg,
+				  union power_supply_propval *val)
+{
+	val->intval
+		= (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0)
+		 && get_client_vote(chg->dc_suspend_votable, USER_VOTER);
+	return 0;
+}
+
+int smblib_get_prop_batt_present(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, BATIF_BASE + INT_RT_STS_OFFSET, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATIF_INT_RT_STS rc=%d\n", rc);
+		return rc;
+	}
+
+	val->intval = !(stat & (BAT_THERM_OR_ID_MISSING_RT_STS_BIT
+					| BAT_TERMINAL_MISSING_RT_STS_BIT));
+
+	return rc;
+}
+
+int smblib_get_prop_batt_capacity(struct smb_charger *chg,
+				  union power_supply_propval *val)
+{
+	int rc = -EINVAL;
+
+	if (chg->fake_capacity >= 0) {
+		val->intval = chg->fake_capacity;
+		return 0;
+	}
+
+	if (chg->bms_psy)
+		rc = power_supply_get_property(chg->bms_psy,
+				POWER_SUPPLY_PROP_CAPACITY, val);
+	return rc;
+}
+
+int smblib_get_prop_batt_status(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	union power_supply_propval pval = {0, };
+	bool usb_online, dc_online;
+	u8 stat;
+	int rc;
+
+	rc = smblib_get_prop_usb_online(chg, &pval);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get usb online property rc=%d\n",
+			rc);
+		return rc;
+	}
+	usb_online = (bool)pval.intval;
+
+	rc = smblib_get_prop_dc_online(chg, &pval);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get dc online property rc=%d\n",
+			rc);
+		return rc;
+	}
+	dc_online = (bool)pval.intval;
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return rc;
+	}
+	stat = stat & BATTERY_CHARGER_STATUS_MASK;
+
+	if (!usb_online && !dc_online) {
+		switch (stat) {
+		case TERMINATE_CHARGE:
+		case INHIBIT_CHARGE:
+			val->intval = POWER_SUPPLY_STATUS_FULL;
+			break;
+		default:
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+			break;
+		}
+		return rc;
+	}
+
+	switch (stat) {
+	case TRICKLE_CHARGE:
+	case PRE_CHARGE:
+	case FAST_CHARGE:
+	case FULLON_CHARGE:
+	case TAPER_CHARGE:
+		val->intval = POWER_SUPPLY_STATUS_CHARGING;
+		break;
+	case TERMINATE_CHARGE:
+	case INHIBIT_CHARGE:
+		val->intval = POWER_SUPPLY_STATUS_FULL;
+		break;
+	case DISABLE_CHARGE:
+		val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		break;
+	default:
+		val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+		break;
+	}
+
+	return 0;
+}
+
+int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	switch (stat & BATTERY_CHARGER_STATUS_MASK) {
+	case TRICKLE_CHARGE:
+	case PRE_CHARGE:
+		val->intval = POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+		break;
+	case FAST_CHARGE:
+	case FULLON_CHARGE:
+		val->intval = POWER_SUPPLY_CHARGE_TYPE_FAST;
+		break;
+	case TAPER_CHARGE:
+		val->intval = POWER_SUPPLY_CHARGE_TYPE_TAPER;
+		break;
+	default:
+		val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+	}
+
+	return rc;
+}
+
+int smblib_get_prop_batt_health(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	union power_supply_propval pval;
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_2_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_2 rc=%d\n",
+			rc);
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "BATTERY_CHARGER_STATUS_2 = 0x%02x\n",
+		   stat);
+
+	if (stat & CHARGER_ERROR_STATUS_BAT_OV_BIT) {
+		rc = smblib_get_prop_batt_voltage_now(chg, &pval);
+		if (!rc) {
+			/*
+			 * If Vbatt is within 40mV above Vfloat, then don't
+			 * treat it as overvoltage.
+			 */
+			if (pval.intval >=
+				get_effective_result(chg->fv_votable) + 40000) {
+				val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+				smblib_err(chg, "battery over-voltage\n");
+				goto done;
+			}
+		}
+	}
+
+	if (stat & BAT_TEMP_STATUS_TOO_COLD_BIT)
+		val->intval = POWER_SUPPLY_HEALTH_COLD;
+	else if (stat & BAT_TEMP_STATUS_TOO_HOT_BIT)
+		val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+	else if (stat & BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT)
+		val->intval = POWER_SUPPLY_HEALTH_COOL;
+	else if (stat & BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT)
+		val->intval = POWER_SUPPLY_HEALTH_WARM;
+	else
+		val->intval = POWER_SUPPLY_HEALTH_GOOD;
+
+done:
+	return rc;
+}
+
+int smblib_get_prop_system_temp_level(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	val->intval = chg->system_temp_level;
+	return 0;
+}
+
+int smblib_get_prop_input_current_limited(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	u8 stat;
+	int rc;
+
+	rc = smblib_read(chg, AICL_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n", rc);
+		return rc;
+	}
+	val->intval = (stat & SOFT_ILIMIT_BIT) || chg->is_hdc;
+	return 0;
+}
+
+int smblib_get_prop_batt_voltage_now(struct smb_charger *chg,
+				     union power_supply_propval *val)
+{
+	int rc;
+
+	if (!chg->bms_psy)
+		return -EINVAL;
+
+	rc = power_supply_get_property(chg->bms_psy,
+				       POWER_SUPPLY_PROP_VOLTAGE_NOW, val);
+	return rc;
+}
+
+int smblib_get_prop_batt_current_now(struct smb_charger *chg,
+				     union power_supply_propval *val)
+{
+	int rc;
+
+	if (!chg->bms_psy)
+		return -EINVAL;
+
+	rc = power_supply_get_property(chg->bms_psy,
+				       POWER_SUPPLY_PROP_CURRENT_NOW, val);
+	return rc;
+}
+
+int smblib_get_prop_batt_temp(struct smb_charger *chg,
+			      union power_supply_propval *val)
+{
+	int rc;
+
+	if (!chg->bms_psy)
+		return -EINVAL;
+
+	rc = power_supply_get_property(chg->bms_psy,
+				       POWER_SUPPLY_PROP_TEMP, val);
+	return rc;
+}
+
+int smblib_get_prop_step_chg_step(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	if (!chg->step_chg_enabled) {
+		val->intval = -1;
+		return 0;
+	}
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	val->intval = (stat & STEP_CHARGING_STATUS_MASK) >>
+				STEP_CHARGING_STATUS_SHIFT;
+
+	return rc;
+}
+
+int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
+					union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	stat = stat & BATTERY_CHARGER_STATUS_MASK;
+	val->intval = (stat == TERMINATE_CHARGE);
+	return 0;
+}
+
+/***********************
+ * BATTERY PSY SETTERS *
+ ***********************/
+
+int smblib_set_prop_input_suspend(struct smb_charger *chg,
+				  const union power_supply_propval *val)
+{
+	int rc;
+
+	/* vote 0mA when suspended */
+	rc = vote(chg->usb_icl_votable, USER_VOTER, (bool)val->intval, 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't vote to %s USB rc=%d\n",
+			(bool)val->intval ? "suspend" : "resume", rc);
+		return rc;
+	}
+
+	rc = vote(chg->dc_suspend_votable, USER_VOTER, (bool)val->intval, 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't vote to %s DC rc=%d\n",
+			(bool)val->intval ? "suspend" : "resume", rc);
+		return rc;
+	}
+
+	power_supply_changed(chg->batt_psy);
+	return rc;
+}
+
+int smblib_set_prop_batt_capacity(struct smb_charger *chg,
+				  const union power_supply_propval *val)
+{
+	chg->fake_capacity = val->intval;
+
+	power_supply_changed(chg->batt_psy);
+
+	return 0;
+}
+
+int smblib_set_prop_system_temp_level(struct smb_charger *chg,
+				const union power_supply_propval *val)
+{
+	if (val->intval < 0)
+		return -EINVAL;
+
+	if (chg->thermal_levels <= 0)
+		return -EINVAL;
+
+	if (val->intval > chg->thermal_levels)
+		return -EINVAL;
+
+	chg->system_temp_level = val->intval;
+	if (chg->system_temp_level == chg->thermal_levels)
+		return vote(chg->chg_disable_votable,
+			THERMAL_DAEMON_VOTER, true, 0);
+
+	vote(chg->chg_disable_votable, THERMAL_DAEMON_VOTER, false, 0);
+	if (chg->system_temp_level == 0)
+		return vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, false, 0);
+
+	vote(chg->fcc_votable, THERMAL_DAEMON_VOTER, true,
+			chg->thermal_mitigation[chg->system_temp_level]);
+	return 0;
+}
+
+int smblib_rerun_aicl(struct smb_charger *chg)
+{
+	int rc, settled_icl_ua;
+	u8 stat;
+
+	rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+								rc);
+		return rc;
+	}
+
+	/* USB is suspended so skip re-running AICL */
+	if (stat & USBIN_SUSPEND_STS_BIT)
+		return rc;
+
+	smblib_dbg(chg, PR_MISC, "re-running AICL\n");
+	switch (chg->smb_version) {
+	case PMI8998_SUBTYPE:
+		rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
+							&settled_icl_ua);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
+			return rc;
+		}
+
+		vote(chg->usb_icl_votable, AICL_RERUN_VOTER, true,
+				max(settled_icl_ua - chg->param.usb_icl.step_u,
+				chg->param.usb_icl.step_u));
+		vote(chg->usb_icl_votable, AICL_RERUN_VOTER, false, 0);
+		break;
+	case PM660_SUBTYPE:
+		/*
+		 * Use restart_AICL instead of trigger_AICL as it runs the
+		 * complete AICL instead of starting from the last settled
+		 * value.
+		 */
+		rc = smblib_masked_write(chg, CMD_HVDCP_2_REG,
+					RESTART_AICL_BIT, RESTART_AICL_BIT);
+		if (rc < 0)
+			smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
+									rc);
+		break;
+	default:
+		smblib_dbg(chg, PR_PARALLEL, "unknown SMB chip %d\n",
+				chg->smb_version);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smblib_dp_pulse(struct smb_charger *chg)
+{
+	int rc;
+
+	/* QC 3.0 increment */
+	rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_INCREMENT_BIT,
+			SINGLE_INCREMENT_BIT);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
+				rc);
+
+	return rc;
+}
+
+static int smblib_dm_pulse(struct smb_charger *chg)
+{
+	int rc;
+
+	/* QC 3.0 decrement */
+	rc = smblib_masked_write(chg, CMD_HVDCP_2_REG, SINGLE_DECREMENT_BIT,
+			SINGLE_DECREMENT_BIT);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't write to CMD_HVDCP_2_REG rc=%d\n",
+				rc);
+
+	return rc;
+}
+
+int smblib_dp_dm(struct smb_charger *chg, int val)
+{
+	int target_icl_ua, rc = 0;
+
+	switch (val) {
+	case POWER_SUPPLY_DP_DM_DP_PULSE:
+		rc = smblib_dp_pulse(chg);
+		if (!rc)
+			chg->pulse_cnt++;
+		smblib_dbg(chg, PR_PARALLEL, "DP_DM_DP_PULSE rc=%d cnt=%d\n",
+				rc, chg->pulse_cnt);
+		break;
+	case POWER_SUPPLY_DP_DM_DM_PULSE:
+		rc = smblib_dm_pulse(chg);
+		if (!rc && chg->pulse_cnt)
+			chg->pulse_cnt--;
+		smblib_dbg(chg, PR_PARALLEL, "DP_DM_DM_PULSE rc=%d cnt=%d\n",
+				rc, chg->pulse_cnt);
+		break;
+	case POWER_SUPPLY_DP_DM_ICL_DOWN:
+		chg->usb_icl_delta_ua -= 100000;
+		target_icl_ua = get_effective_result(chg->usb_icl_votable);
+		vote(chg->usb_icl_votable, SW_QC3_VOTER, true,
+				target_icl_ua + chg->usb_icl_delta_ua);
+		break;
+	case POWER_SUPPLY_DP_DM_ICL_UP:
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+/*******************
+ * DC PSY GETTERS *
+ *******************/
+
+int smblib_get_prop_dc_present(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, DCIN_BASE + INT_RT_STS_OFFSET, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read DCIN_RT_STS rc=%d\n", rc);
+		return rc;
+	}
+
+	val->intval = (bool)(stat & DCIN_PLUGIN_RT_STS_BIT);
+	return 0;
+}
+
+int smblib_get_prop_dc_online(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	int rc = 0;
+	u8 stat;
+
+	if (get_client_vote(chg->dc_suspend_votable, USER_VOTER)) {
+		val->intval = false;
+		return rc;
+	}
+
+	rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+			rc);
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
+		   stat);
+
+	val->intval = (stat & USE_DCIN_BIT) &&
+		      (stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
+
+	return rc;
+}
+
+int smblib_get_prop_dc_current_max(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	val->intval = get_effective_result_locked(chg->dc_icl_votable);
+	return 0;
+}
+
+/*******************
+ * DC PSY SETTERS *
+ * *****************/
+
+int smblib_set_prop_dc_current_max(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc;
+
+	rc = vote(chg->dc_icl_votable, USER_VOTER, true, val->intval);
+	return rc;
+}
+
+/*******************
+ * USB PSY GETTERS *
+ *******************/
+
+int smblib_get_prop_usb_present(struct smb_charger *chg,
+				union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read USBIN_RT_STS rc=%d\n", rc);
+		return rc;
+	}
+
+	val->intval = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
+	return 0;
+}
+
+int smblib_get_prop_usb_online(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	int rc = 0;
+	u8 stat;
+
+	if (get_client_vote(chg->usb_icl_votable, USER_VOTER) == 0) {
+		val->intval = false;
+		return rc;
+	}
+
+	rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n",
+			rc);
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "POWER_PATH_STATUS = 0x%02x\n",
+		   stat);
+
+	val->intval = (stat & USE_USBIN_BIT) &&
+		      (stat & VALID_INPUT_POWER_SOURCE_STS_BIT);
+	return rc;
+}
+
+int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	int rc = 0;
+
+	rc = smblib_get_prop_usb_present(chg, val);
+	if (rc < 0 || !val->intval)
+		return rc;
+
+	if (!chg->iio.usbin_v_chan ||
+		PTR_ERR(chg->iio.usbin_v_chan) == -EPROBE_DEFER)
+		chg->iio.usbin_v_chan = iio_channel_get(chg->dev, "usbin_v");
+
+	if (IS_ERR(chg->iio.usbin_v_chan))
+		return PTR_ERR(chg->iio.usbin_v_chan);
+
+	return iio_read_channel_processed(chg->iio.usbin_v_chan, &val->intval);
+}
+
+int smblib_get_prop_pd_current_max(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	val->intval = get_client_vote_locked(chg->usb_icl_votable, PD_VOTER);
+	return 0;
+}
+
+int smblib_get_prop_usb_current_max(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	val->intval = get_client_vote_locked(chg->usb_icl_votable,
+			USB_PSY_VOTER);
+	return 0;
+}
+
+int smblib_get_prop_usb_current_now(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	int rc = 0;
+
+	rc = smblib_get_prop_usb_present(chg, val);
+	if (rc < 0 || !val->intval)
+		return rc;
+
+	if (!chg->iio.usbin_i_chan ||
+		PTR_ERR(chg->iio.usbin_i_chan) == -EPROBE_DEFER)
+		chg->iio.usbin_i_chan = iio_channel_get(chg->dev, "usbin_i");
+
+	if (IS_ERR(chg->iio.usbin_i_chan))
+		return PTR_ERR(chg->iio.usbin_i_chan);
+
+	return iio_read_channel_processed(chg->iio.usbin_i_chan, &val->intval);
+}
+
+int smblib_get_prop_charger_temp(struct smb_charger *chg,
+				 union power_supply_propval *val)
+{
+	int rc;
+
+	if (!chg->iio.temp_chan ||
+		PTR_ERR(chg->iio.temp_chan) == -EPROBE_DEFER)
+		chg->iio.temp_chan = iio_channel_get(chg->dev, "charger_temp");
+
+	if (IS_ERR(chg->iio.temp_chan))
+		return PTR_ERR(chg->iio.temp_chan);
+
+	rc = iio_read_channel_processed(chg->iio.temp_chan, &val->intval);
+	val->intval /= 100;
+	return rc;
+}
+
+int smblib_get_prop_charger_temp_max(struct smb_charger *chg,
+				    union power_supply_propval *val)
+{
+	int rc;
+
+	if (!chg->iio.temp_max_chan ||
+		PTR_ERR(chg->iio.temp_max_chan) == -EPROBE_DEFER)
+		chg->iio.temp_max_chan = iio_channel_get(chg->dev,
+							 "charger_temp_max");
+	if (IS_ERR(chg->iio.temp_max_chan))
+		return PTR_ERR(chg->iio.temp_max_chan);
+
+	rc = iio_read_channel_processed(chg->iio.temp_max_chan, &val->intval);
+	val->intval /= 100;
+	return rc;
+}
+
+int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
+					 union power_supply_propval *val)
+{
+	int rc = 0;
+	u8 stat;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n",
+		   stat);
+
+	if (stat & CC_ATTACHED_BIT)
+		val->intval = (bool)(stat & CC_ORIENTATION_BIT) + 1;
+	else
+		val->intval = 0;
+
+	return rc;
+}
+
+static const char * const smblib_typec_mode_name[] = {
+	[POWER_SUPPLY_TYPEC_NONE]		  = "NONE",
+	[POWER_SUPPLY_TYPEC_SOURCE_DEFAULT]	  = "SOURCE_DEFAULT",
+	[POWER_SUPPLY_TYPEC_SOURCE_MEDIUM]	  = "SOURCE_MEDIUM",
+	[POWER_SUPPLY_TYPEC_SOURCE_HIGH]	  = "SOURCE_HIGH",
+	[POWER_SUPPLY_TYPEC_NON_COMPLIANT]	  = "NON_COMPLIANT",
+	[POWER_SUPPLY_TYPEC_SINK]		  = "SINK",
+	[POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE]   = "SINK_POWERED_CABLE",
+	[POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY] = "SINK_DEBUG_ACCESSORY",
+	[POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER]   = "SINK_AUDIO_ADAPTER",
+	[POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY]   = "POWERED_CABLE_ONLY",
+};
+
+static int smblib_get_prop_ufp_mode(struct smb_charger *chg)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_1 rc=%d\n", rc);
+		return POWER_SUPPLY_TYPEC_NONE;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_1 = 0x%02x\n", stat);
+
+	switch (stat) {
+	case 0:
+		return POWER_SUPPLY_TYPEC_NONE;
+	case UFP_TYPEC_RDSTD_BIT:
+		return POWER_SUPPLY_TYPEC_SOURCE_DEFAULT;
+	case UFP_TYPEC_RD1P5_BIT:
+		return POWER_SUPPLY_TYPEC_SOURCE_MEDIUM;
+	case UFP_TYPEC_RD3P0_BIT:
+		return POWER_SUPPLY_TYPEC_SOURCE_HIGH;
+	default:
+		break;
+	}
+
+	return POWER_SUPPLY_TYPEC_NON_COMPLIANT;
+}
+
+static int smblib_get_prop_dfp_mode(struct smb_charger *chg)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_2_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_2 rc=%d\n", rc);
+		return POWER_SUPPLY_TYPEC_NONE;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_2 = 0x%02x\n", stat);
+
+	switch (stat & DFP_TYPEC_MASK) {
+	case DFP_RA_RA_BIT:
+		return POWER_SUPPLY_TYPEC_SINK_AUDIO_ADAPTER;
+	case DFP_RD_RD_BIT:
+		return POWER_SUPPLY_TYPEC_SINK_DEBUG_ACCESSORY;
+	case DFP_RD_RA_VCONN_BIT:
+		return POWER_SUPPLY_TYPEC_SINK_POWERED_CABLE;
+	case DFP_RD_OPEN_BIT:
+		return POWER_SUPPLY_TYPEC_SINK;
+	case DFP_RA_OPEN_BIT:
+		return POWER_SUPPLY_TYPEC_POWERED_CABLE_ONLY;
+	default:
+		break;
+	}
+
+	return POWER_SUPPLY_TYPEC_NONE;
+}
+
+int smblib_get_prop_typec_mode(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+		val->intval = POWER_SUPPLY_TYPEC_NONE;
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat);
+
+	if (!(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT)) {
+		val->intval = POWER_SUPPLY_TYPEC_NONE;
+		return rc;
+	}
+
+	if (stat & UFP_DFP_MODE_STATUS_BIT)
+		val->intval = smblib_get_prop_dfp_mode(chg);
+	else
+		val->intval = smblib_get_prop_ufp_mode(chg);
+
+	return rc;
+}
+
+int smblib_get_prop_typec_power_role(struct smb_charger *chg,
+				     union power_supply_propval *val)
+{
+	int rc = 0;
+	u8 ctrl;
+
+	rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
+			rc);
+		return rc;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL = 0x%02x\n",
+		   ctrl);
+
+	if (ctrl & TYPEC_DISABLE_CMD_BIT) {
+		val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+		return rc;
+	}
+
+	switch (ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT)) {
+	case 0:
+		val->intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+		break;
+	case DFP_EN_CMD_BIT:
+		val->intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
+		break;
+	case UFP_EN_CMD_BIT:
+		val->intval = POWER_SUPPLY_TYPEC_PR_SINK;
+		break;
+	default:
+		val->intval = POWER_SUPPLY_TYPEC_PR_NONE;
+		smblib_err(chg, "unsupported power role 0x%02lx\n",
+			ctrl & (DFP_EN_CMD_BIT | UFP_EN_CMD_BIT));
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+int smblib_get_prop_pd_allowed(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	val->intval = get_effective_result(chg->pd_allowed_votable);
+	return 0;
+}
+
+int smblib_get_prop_input_current_settled(struct smb_charger *chg,
+					  union power_supply_propval *val)
+{
+	return smblib_get_charge_param(chg, &chg->param.icl_stat, &val->intval);
+}
+
+#define HVDCP3_STEP_UV	200000
+int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
+						union power_supply_propval *val)
+{
+	const struct apsd_result *apsd_result = smblib_get_apsd_result(chg);
+	int rc, pulses;
+	u8 stat;
+
+	val->intval = MICRO_5V;
+	if (apsd_result == NULL) {
+		smblib_err(chg, "APSD result is NULL\n");
+		return 0;
+	}
+
+	switch (apsd_result->pst) {
+	case POWER_SUPPLY_TYPE_USB_HVDCP_3:
+		rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, &stat);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
+			return 0;
+		}
+		pulses = (stat & QC_PULSE_COUNT_MASK);
+		val->intval = MICRO_5V + HVDCP3_STEP_UV * pulses;
+		break;
+	default:
+		val->intval = MICRO_5V;
+		break;
+	}
+
+	return 0;
+}
+
+int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	int rc;
+	u8 ctrl;
+
+	rc = smblib_read(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG, &ctrl);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG rc=%d\n",
+			rc);
+		return rc;
+	}
+	val->intval = ctrl & EXIT_SNK_BASED_ON_CC_BIT;
+	return 0;
+}
+
+int smblib_get_pe_start(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	/*
+	 * hvdcp timeout voter is the last one to allow pd. Use its vote
+	 * to indicate start of pe engine
+	 */
+	val->intval
+		= !get_client_vote_locked(chg->pd_disallowed_votable_indirect,
+			HVDCP_TIMEOUT_VOTER);
+	return 0;
+}
+
+int smblib_get_prop_die_health(struct smb_charger *chg,
+						union power_supply_propval *val)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, TEMP_RANGE_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TEMP_RANGE_STATUS_REG rc=%d\n",
+									rc);
+		return rc;
+	}
+
+	/* TEMP_RANGE bits are mutually exclusive */
+	switch (stat & TEMP_RANGE_MASK) {
+	case TEMP_BELOW_RANGE_BIT:
+		val->intval = POWER_SUPPLY_HEALTH_COOL;
+		break;
+	case TEMP_WITHIN_RANGE_BIT:
+		val->intval = POWER_SUPPLY_HEALTH_WARM;
+		break;
+	case TEMP_ABOVE_RANGE_BIT:
+		val->intval = POWER_SUPPLY_HEALTH_HOT;
+		break;
+	case ALERT_LEVEL_BIT:
+		val->intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+		break;
+	default:
+		val->intval = POWER_SUPPLY_HEALTH_UNKNOWN;
+	}
+
+	return 0;
+}
+
+/*******************
+ * USB PSY SETTERS *
+ * *****************/
+
+int smblib_set_prop_pd_current_max(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc;
+
+	if (chg->pd_active)
+		rc = vote(chg->usb_icl_votable, PD_VOTER, true, val->intval);
+	else
+		rc = -EPERM;
+
+	return rc;
+}
+
+int smblib_set_prop_usb_current_max(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc = 0;
+
+	if (!chg->pd_active) {
+		rc = vote(chg->usb_icl_votable, USB_PSY_VOTER,
+				true, val->intval);
+	} else if (chg->system_suspend_supported) {
+		if (val->intval <= USBIN_25MA)
+			rc = vote(chg->usb_icl_votable,
+				PD_SUSPEND_SUPPORTED_VOTER, true, val->intval);
+		else
+			rc = vote(chg->usb_icl_votable,
+				PD_SUSPEND_SUPPORTED_VOTER, false, 0);
+	}
+	return rc;
+}
+
+int smblib_set_prop_boost_current(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc = 0;
+
+	rc = smblib_set_charge_param(chg, &chg->param.freq_boost,
+				val->intval <= chg->boost_threshold_ua ?
+				chg->chg_freq.freq_below_otg_threshold :
+				chg->chg_freq.freq_above_otg_threshold);
+	if (rc < 0) {
+		dev_err(chg->dev, "Error in setting freq_boost rc=%d\n", rc);
+		return rc;
+	}
+
+	chg->boost_current_ua = val->intval;
+	return rc;
+}
+
+int smblib_set_prop_typec_power_role(struct smb_charger *chg,
+				     const union power_supply_propval *val)
+{
+	int rc = 0;
+	u8 power_role;
+
+	switch (val->intval) {
+	case POWER_SUPPLY_TYPEC_PR_NONE:
+		power_role = TYPEC_DISABLE_CMD_BIT;
+		break;
+	case POWER_SUPPLY_TYPEC_PR_DUAL:
+		power_role = 0;
+		break;
+	case POWER_SUPPLY_TYPEC_PR_SINK:
+		power_role = UFP_EN_CMD_BIT;
+		break;
+	case POWER_SUPPLY_TYPEC_PR_SOURCE:
+		power_role = DFP_EN_CMD_BIT;
+		break;
+	default:
+		smblib_err(chg, "power role %d not supported\n", val->intval);
+		return -EINVAL;
+	}
+
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 TYPEC_POWER_ROLE_CMD_MASK, power_role);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't write 0x%02x to TYPE_C_INTRPT_ENB_SOFTWARE_CTRL rc=%d\n",
+			power_role, rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+int smblib_set_prop_usb_voltage_min(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc, min_uv;
+
+	min_uv = min(val->intval, chg->voltage_max_uv);
+	rc = smblib_set_usb_pd_allowed_voltage(chg, min_uv,
+					       chg->voltage_max_uv);
+	if (rc < 0) {
+		smblib_err(chg, "invalid max voltage %duV rc=%d\n",
+			val->intval, rc);
+		return rc;
+	}
+
+	if (chg->mode == PARALLEL_MASTER)
+		vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER,
+		     min_uv > MICRO_5V, 0);
+
+	chg->voltage_min_uv = min_uv;
+	return rc;
+}
+
+int smblib_set_prop_usb_voltage_max(struct smb_charger *chg,
+				    const union power_supply_propval *val)
+{
+	int rc, max_uv;
+
+	max_uv = max(val->intval, chg->voltage_min_uv);
+	rc = smblib_set_usb_pd_allowed_voltage(chg, chg->voltage_min_uv,
+					       max_uv);
+	if (rc < 0) {
+		smblib_err(chg, "invalid min voltage %duV rc=%d\n",
+			val->intval, rc);
+		return rc;
+	}
+
+	chg->voltage_max_uv = max_uv;
+	rc = smblib_rerun_aicl(chg);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't re-run AICL rc=%d\n", rc);
+
+	return rc;
+}
+
+int smblib_set_prop_pd_active(struct smb_charger *chg,
+			      const union power_supply_propval *val)
+{
+	int rc;
+	u8 stat = 0;
+	bool cc_debounced;
+	bool orientation;
+	bool pd_active = val->intval;
+
+	if (!get_effective_result(chg->pd_allowed_votable)) {
+		smblib_err(chg, "PD is not allowed\n");
+		return -EINVAL;
+	}
+
+	vote(chg->apsd_disable_votable, PD_VOTER, pd_active, 0);
+	vote(chg->pd_allowed_votable, PD_VOTER, pd_active, 0);
+
+	/*
+	 * VCONN_EN_ORIENTATION_BIT controls whether to use CC1 or CC2 line
+	 * when TYPEC_SPARE_CFG_BIT (CC pin selection s/w override) is set
+	 * or when VCONN_EN_VALUE_BIT is set.
+	 */
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+		return rc;
+	}
+
+	if (pd_active) {
+		orientation = stat & CC_ORIENTATION_BIT;
+		rc = smblib_masked_write(chg,
+				TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				VCONN_EN_ORIENTATION_BIT,
+				orientation ? 0 : VCONN_EN_ORIENTATION_BIT);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't enable vconn on CC line rc=%d\n", rc);
+			return rc;
+		}
+		/*
+		 * Enforce 500mA for PD until the real vote comes in later.
+		 * It is guaranteed that pd_active is set prior to
+		 * pd_current_max
+		 */
+		rc = vote(chg->usb_icl_votable, PD_VOTER, true, USBIN_500MA);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't vote for USB ICL rc=%d\n",
+					rc);
+			return rc;
+		}
+
+		/* clear USB ICL vote for DCP_VOTER */
+		rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+		if (rc < 0)
+			smblib_err(chg,
+				"Couldn't un-vote DCP from USB ICL rc=%d\n",
+				rc);
+
+		/* clear USB ICL vote for PL_USBIN_USBIN_VOTER */
+		rc = vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0);
+		if (rc < 0)
+			smblib_err(chg,
+					"Couldn't un-vote PL_USBIN_USBIN from USB ICL rc=%d\n",
+					rc);
+
+		/* remove USB_PSY_VOTER */
+		rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't unvote USB_PSY rc=%d\n", rc);
+			return rc;
+		}
+
+		/* pd active set, parallel charger can be enabled now */
+		rc = vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER,
+				false, 0);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't unvote PL_DELAY_HVDCP_VOTER rc=%d\n",
+				rc);
+			return rc;
+		}
+	}
+
+	/* CC pin selection s/w override in PD session; h/w otherwise. */
+	rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
+				 TYPEC_SPARE_CFG_BIT,
+				 pd_active ? TYPEC_SPARE_CFG_BIT : 0);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't change cc_out ctrl to %s rc=%d\n",
+			pd_active ? "SW" : "HW", rc);
+		return rc;
+	}
+
+	cc_debounced = (bool)(stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT);
+	if (!pd_active && cc_debounced)
+		try_rerun_apsd_for_hvdcp(chg);
+
+	chg->pd_active = pd_active;
+	smblib_update_usb_type(chg);
+	power_supply_changed(chg->usb_psy);
+
+	return rc;
+}
+
+int smblib_set_prop_ship_mode(struct smb_charger *chg,
+				const union power_supply_propval *val)
+{
+	int rc;
+
+	smblib_dbg(chg, PR_MISC, "Set ship mode: %d!!\n", !!val->intval);
+
+	rc = smblib_masked_write(chg, SHIP_MODE_REG, SHIP_MODE_EN_BIT,
+			!!val->intval ? SHIP_MODE_EN_BIT : 0);
+	if (rc < 0)
+		dev_err(chg->dev, "Couldn't %s ship mode, rc=%d\n",
+				!!val->intval ? "enable" : "disable", rc);
+
+	return rc;
+}
+
+int smblib_reg_block_update(struct smb_charger *chg,
+				struct reg_info *entry)
+{
+	int rc = 0;
+
+	while (entry && entry->reg) {
+		rc = smblib_read(chg, entry->reg, &entry->bak);
+		if (rc < 0) {
+			dev_err(chg->dev, "Error in reading %s rc=%d\n",
+				entry->desc, rc);
+			break;
+		}
+		entry->bak &= entry->mask;
+
+		rc = smblib_masked_write(chg, entry->reg,
+					 entry->mask, entry->val);
+		if (rc < 0) {
+			dev_err(chg->dev, "Error in writing %s rc=%d\n",
+				entry->desc, rc);
+			break;
+		}
+		entry++;
+	}
+
+	return rc;
+}
+
+int smblib_reg_block_restore(struct smb_charger *chg,
+				struct reg_info *entry)
+{
+	int rc = 0;
+
+	while (entry && entry->reg) {
+		rc = smblib_masked_write(chg, entry->reg,
+					 entry->mask, entry->bak);
+		if (rc < 0) {
+			dev_err(chg->dev, "Error in writing %s rc=%d\n",
+				entry->desc, rc);
+			break;
+		}
+		entry++;
+	}
+
+	return rc;
+}
+
+static struct reg_info cc2_detach_settings[] = {
+	{
+		.reg	= TYPE_C_CFG_REG,
+		.mask	= APSD_START_ON_CC_BIT,
+		.val	= 0,
+		.desc	= "TYPE_C_CFG_REG",
+	},
+	{
+		.reg	= TYPE_C_CFG_2_REG,
+		.mask	= TYPE_C_UFP_MODE_BIT | EN_TRY_SOURCE_MODE_BIT,
+		.val	= TYPE_C_UFP_MODE_BIT,
+		.desc	= "TYPE_C_CFG_2_REG",
+	},
+	{
+		.reg	= TYPE_C_CFG_3_REG,
+		.mask	= EN_TRYSINK_MODE_BIT,
+		.val	= 0,
+		.desc	= "TYPE_C_CFG_3_REG",
+	},
+	{
+		.reg	= TAPER_TIMER_SEL_CFG_REG,
+		.mask	= TYPEC_SPARE_CFG_BIT,
+		.val	= TYPEC_SPARE_CFG_BIT,
+		.desc	= "TAPER_TIMER_SEL_CFG_REG",
+	},
+	{
+		.reg	= TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+		.mask	= VCONN_EN_ORIENTATION_BIT,
+		.val	= 0,
+		.desc	= "TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG",
+	},
+	{
+		.reg	= MISC_CFG_REG,
+		.mask	= TCC_DEBOUNCE_20MS_BIT,
+		.val	= TCC_DEBOUNCE_20MS_BIT,
+		.desc	= "Tccdebounce time"
+	},
+	{
+	},
+};
+
+static int smblib_cc2_sink_removal_enter(struct smb_charger *chg)
+{
+	int rc = 0;
+	union power_supply_propval cc2_val = {0, };
+
+	if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
+		return rc;
+
+	if (chg->cc2_sink_detach_flag != CC2_SINK_NONE)
+		return rc;
+
+	rc = smblib_get_prop_typec_cc_orientation(chg, &cc2_val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get cc orientation rc=%d\n", rc);
+		return rc;
+	}
+	if (cc2_val.intval == 1)
+		return rc;
+
+	rc = smblib_get_prop_typec_mode(chg, &cc2_val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc);
+		return rc;
+	}
+
+	switch (cc2_val.intval) {
+	case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+		smblib_reg_block_update(chg, cc2_detach_settings);
+		chg->cc2_sink_detach_flag = CC2_SINK_STD;
+		schedule_work(&chg->rdstd_cc2_detach_work);
+		break;
+	case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+	case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+		chg->cc2_sink_detach_flag = CC2_SINK_MEDIUM_HIGH;
+		break;
+	default:
+		break;
+	}
+
+	return rc;
+}
+
+static int smblib_cc2_sink_removal_exit(struct smb_charger *chg)
+{
+	int rc = 0;
+
+	if ((chg->wa_flags & TYPEC_CC2_REMOVAL_WA_BIT) == 0)
+		return rc;
+
+	if (chg->cc2_sink_detach_flag == CC2_SINK_STD) {
+		cancel_work_sync(&chg->rdstd_cc2_detach_work);
+		smblib_reg_block_restore(chg, cc2_detach_settings);
+	}
+
+	chg->cc2_sink_detach_flag = CC2_SINK_NONE;
+
+	return rc;
+}
+
+int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
+				const union power_supply_propval *val)
+{
+	int rc;
+
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 EXIT_SNK_BASED_ON_CC_BIT,
+				 (val->intval) ? EXIT_SNK_BASED_ON_CC_BIT : 0);
+	if (rc < 0) {
+		smblib_err(chg, "Could not set EXIT_SNK_BASED_ON_CC rc=%d\n",
+				rc);
+		return rc;
+	}
+
+	vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, val->intval, 0);
+
+	if (val->intval)
+		rc = smblib_cc2_sink_removal_enter(chg);
+	else
+		rc = smblib_cc2_sink_removal_exit(chg);
+
+	if (rc < 0) {
+		smblib_err(chg, "Could not detect cc2 removal rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+/************************
+ * USB MAIN PSY GETTERS *
+ ************************/
+int smblib_get_prop_fcc_delta(struct smb_charger *chg,
+			       union power_supply_propval *val)
+{
+	int rc, jeita_cc_delta_ua, step_cc_delta_ua, hw_cc_delta_ua = 0;
+
+	rc = smblib_get_step_cc_delta(chg, &step_cc_delta_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get step cc delta rc=%d\n", rc);
+		step_cc_delta_ua = 0;
+	} else {
+		hw_cc_delta_ua = step_cc_delta_ua;
+	}
+
+	rc = smblib_get_jeita_cc_delta(chg, &jeita_cc_delta_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get jeita cc delta rc=%d\n", rc);
+		jeita_cc_delta_ua = 0;
+	} else if (jeita_cc_delta_ua < 0) {
+		/* HW will take the min between JEITA and step charge */
+		hw_cc_delta_ua = min(hw_cc_delta_ua, jeita_cc_delta_ua);
+	}
+
+	val->intval = hw_cc_delta_ua;
+	return 0;
+}
+
+/************************
+ * USB MAIN PSY SETTERS *
+ ************************/
+
+#define SDP_CURRENT_MA			500000
+#define CDP_CURRENT_MA			1500000
+#define DCP_CURRENT_MA			1500000
+#define HVDCP_CURRENT_MA		3000000
+#define TYPEC_DEFAULT_CURRENT_MA	900000
+#define TYPEC_MEDIUM_CURRENT_MA		1500000
+#define TYPEC_HIGH_CURRENT_MA		3000000
+static int smblib_get_charge_current(struct smb_charger *chg,
+				int *total_current_ua)
+{
+	const struct apsd_result *apsd_result = smblib_update_usb_type(chg);
+	union power_supply_propval val = {0, };
+	int rc, typec_source_rd, current_ua;
+	bool non_compliant;
+	u8 stat5;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc);
+		return rc;
+	}
+	non_compliant = stat5 & TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT;
+
+	/* get settled ICL */
+	rc = smblib_get_prop_input_current_settled(chg, &val);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get settled ICL rc=%d\n", rc);
+		return rc;
+	}
+
+	typec_source_rd = smblib_get_prop_ufp_mode(chg);
+
+	/* QC 2.0/3.0 adapter */
+	if (apsd_result->bit & (QC_3P0_BIT | QC_2P0_BIT)) {
+		*total_current_ua = HVDCP_CURRENT_MA;
+		return 0;
+	}
+
+	if (non_compliant) {
+		switch (apsd_result->bit) {
+		case CDP_CHARGER_BIT:
+			current_ua = CDP_CURRENT_MA;
+			break;
+		case DCP_CHARGER_BIT:
+		case OCP_CHARGER_BIT:
+		case FLOAT_CHARGER_BIT:
+			current_ua = DCP_CURRENT_MA;
+			break;
+		default:
+			current_ua = 0;
+			break;
+		}
+
+		*total_current_ua = max(current_ua, val.intval);
+		return 0;
+	}
+
+	switch (typec_source_rd) {
+	case POWER_SUPPLY_TYPEC_SOURCE_DEFAULT:
+		switch (apsd_result->bit) {
+		case CDP_CHARGER_BIT:
+			current_ua = CDP_CURRENT_MA;
+			break;
+		case DCP_CHARGER_BIT:
+		case OCP_CHARGER_BIT:
+		case FLOAT_CHARGER_BIT:
+			current_ua = chg->default_icl_ua;
+			break;
+		default:
+			current_ua = 0;
+			break;
+		}
+		break;
+	case POWER_SUPPLY_TYPEC_SOURCE_MEDIUM:
+		current_ua = TYPEC_MEDIUM_CURRENT_MA;
+		break;
+	case POWER_SUPPLY_TYPEC_SOURCE_HIGH:
+		current_ua = TYPEC_HIGH_CURRENT_MA;
+		break;
+	case POWER_SUPPLY_TYPEC_NON_COMPLIANT:
+	case POWER_SUPPLY_TYPEC_NONE:
+	default:
+		current_ua = 0;
+		break;
+	}
+
+	*total_current_ua = max(current_ua, val.intval);
+	return 0;
+}
+
+int smblib_set_icl_reduction(struct smb_charger *chg, int reduction_ua)
+{
+	int current_ua, rc;
+
+	if (reduction_ua == 0) {
+		vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0);
+	} else {
+		/*
+		 * No usb_icl voter means we are defaulting to hw chosen
+		 * max limit. We need a vote from s/w to enforce the reduction.
+		 */
+		if (get_effective_result(chg->usb_icl_votable) == -EINVAL) {
+			rc = smblib_get_charge_current(chg, &current_ua);
+			if (rc < 0) {
+				pr_err("Failed to get ICL rc=%d\n", rc);
+				return rc;
+			}
+			vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, true,
+					current_ua);
+		}
+	}
+
+	chg->icl_reduction_ua = reduction_ua;
+
+	return rerun_election(chg->usb_icl_votable);
+}
+
+/************************
+ * PARALLEL PSY GETTERS *
+ ************************/
+
+int smblib_get_prop_slave_current_now(struct smb_charger *chg,
+				      union power_supply_propval *pval)
+{
+	if (IS_ERR_OR_NULL(chg->iio.batt_i_chan))
+		chg->iio.batt_i_chan = iio_channel_get(chg->dev, "batt_i");
+
+	if (IS_ERR(chg->iio.batt_i_chan))
+		return PTR_ERR(chg->iio.batt_i_chan);
+
+	return iio_read_channel_processed(chg->iio.batt_i_chan, &pval->intval);
+}
+
+/**********************
+ * INTERRUPT HANDLERS *
+ **********************/
+
+irqreturn_t smblib_handle_debug(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't read OTG_INT_RT_STS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	if (stat & OTG_OVERCURRENT_RT_STS_BIT)
+		schedule_work(&chg->otg_oc_work);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_chg_state_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	u8 stat;
+	int rc;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+	rc = smblib_read(chg, BATTERY_CHARGER_STATUS_1_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read BATTERY_CHARGER_STATUS_1 rc=%d\n",
+			rc);
+		return IRQ_HANDLED;
+	}
+
+	stat = stat & BATTERY_CHARGER_STATUS_MASK;
+	power_supply_changed(chg->batt_psy);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_step_chg_state_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+	if (chg->step_chg_enabled)
+		rerun_election(chg->fcc_votable);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_step_chg_soc_update_fail(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+	if (chg->step_chg_enabled)
+		rerun_election(chg->fcc_votable);
+
+	return IRQ_HANDLED;
+}
+
+#define STEP_SOC_REQ_MS	3000
+irqreturn_t smblib_handle_step_chg_soc_update_request(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+	union power_supply_propval pval = {0, };
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+
+	if (!chg->bms_psy) {
+		schedule_delayed_work(&chg->step_soc_req_work,
+				      msecs_to_jiffies(STEP_SOC_REQ_MS));
+		return IRQ_HANDLED;
+	}
+
+	rc = smblib_get_prop_batt_capacity(chg, &pval);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc);
+	else
+		step_charge_soc_update(chg, pval.intval);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	rerun_election(chg->fcc_votable);
+	power_supply_changed(chg->batt_psy);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+	power_supply_changed(chg->batt_psy);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+	power_supply_changed(chg->usb_psy);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usbin_uv(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	struct storm_watch *wdata;
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s\n", irq_data->name);
+	if (!chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data)
+		return IRQ_HANDLED;
+
+	wdata = &chg->irq_info[SWITCH_POWER_OK_IRQ].irq_data->storm_data;
+	reset_storm_count(wdata);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usb_plugin(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+	u8 stat;
+	bool vbus_rising;
+
+	rc = smblib_read(chg, USBIN_BASE + INT_RT_STS_OFFSET, &stat);
+	if (rc < 0) {
+		dev_err(chg->dev, "Couldn't read USB_INT_RT_STS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	vbus_rising = (bool)(stat & USBIN_PLUGIN_RT_STS_BIT);
+	smblib_set_opt_freq_buck(chg,
+		vbus_rising ? chg->chg_freq.freq_5V :
+			chg->chg_freq.freq_removal);
+
+	/* fetch the DPDM regulator */
+	if (!chg->dpdm_reg && of_get_property(chg->dev->of_node,
+						"dpdm-supply", NULL)) {
+		chg->dpdm_reg = devm_regulator_get(chg->dev, "dpdm");
+		if (IS_ERR(chg->dpdm_reg)) {
+			smblib_err(chg, "Couldn't get dpdm regulator rc=%ld\n",
+				PTR_ERR(chg->dpdm_reg));
+			chg->dpdm_reg = NULL;
+		}
+	}
+
+	if (vbus_rising) {
+		if (chg->dpdm_reg && !regulator_is_enabled(chg->dpdm_reg)) {
+			smblib_dbg(chg, PR_MISC, "enabling DPDM regulator\n");
+			rc = regulator_enable(chg->dpdm_reg);
+			if (rc < 0)
+				smblib_err(chg, "Couldn't enable dpdm regulator rc=%d\n",
+					rc);
+		}
+	} else {
+		if (chg->wa_flags & BOOST_BACK_WA)
+			vote(chg->usb_icl_votable, BOOST_BACK_VOTER, false, 0);
+
+		if (chg->dpdm_reg && regulator_is_enabled(chg->dpdm_reg)) {
+			smblib_dbg(chg, PR_MISC, "disabling DPDM regulator\n");
+			rc = regulator_disable(chg->dpdm_reg);
+			if (rc < 0)
+				smblib_err(chg, "Couldn't disable dpdm regulator rc=%d\n",
+					rc);
+		}
+
+		if (chg->micro_usb_mode) {
+			smblib_update_usb_type(chg);
+			extcon_set_cable_state_(chg->extcon, EXTCON_USB, false);
+			smblib_uusb_removal(chg);
+		}
+	}
+
+	power_supply_changed(chg->usb_psy);
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s %s\n",
+		irq_data->name, vbus_rising ? "attached" : "detached");
+	return IRQ_HANDLED;
+}
+
+#define USB_WEAK_INPUT_UA	1400000
+#define ICL_CHANGE_DELAY_MS	1000
+irqreturn_t smblib_handle_icl_change(int irq, void *data)
+{
+	u8 stat;
+	int rc, settled_ua, delay = ICL_CHANGE_DELAY_MS;
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	if (chg->mode == PARALLEL_MASTER) {
+		rc = smblib_read(chg, AICL_STATUS_REG, &stat);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't read AICL_STATUS rc=%d\n",
+					rc);
+			return IRQ_HANDLED;
+		}
+
+		rc = smblib_get_charge_param(chg, &chg->param.icl_stat,
+				&settled_ua);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
+			return IRQ_HANDLED;
+		}
+
+		/* If AICL settled then schedule work now */
+		if ((settled_ua == get_effective_result(chg->usb_icl_votable))
+				|| (stat & AICL_DONE_BIT))
+			delay = 0;
+
+		schedule_delayed_work(&chg->icl_change_work,
+						msecs_to_jiffies(delay));
+	}
+
+	return IRQ_HANDLED;
+}
+
+static void smblib_handle_slow_plugin_timeout(struct smb_charger *chg,
+					      bool rising)
+{
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: slow-plugin-timeout %s\n",
+		   rising ? "rising" : "falling");
+}
+
+static void smblib_handle_sdp_enumeration_done(struct smb_charger *chg,
+					       bool rising)
+{
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: sdp-enumeration-done %s\n",
+		   rising ? "rising" : "falling");
+}
+
+#define QC3_PULSES_FOR_6V	5
+#define QC3_PULSES_FOR_9V	20
+#define QC3_PULSES_FOR_12V	35
+static void smblib_hvdcp_adaptive_voltage_change(struct smb_charger *chg)
+{
+	int rc;
+	u8 stat;
+	int pulses;
+
+	power_supply_changed(chg->usb_main_psy);
+	if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_HVDCP) {
+		rc = smblib_read(chg, QC_CHANGE_STATUS_REG, &stat);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't read QC_CHANGE_STATUS rc=%d\n", rc);
+			return;
+		}
+
+		switch (stat & QC_2P0_STATUS_MASK) {
+		case QC_5V_BIT:
+			smblib_set_opt_freq_buck(chg,
+					chg->chg_freq.freq_5V);
+			break;
+		case QC_9V_BIT:
+			smblib_set_opt_freq_buck(chg,
+					chg->chg_freq.freq_9V);
+			break;
+		case QC_12V_BIT:
+			smblib_set_opt_freq_buck(chg,
+					chg->chg_freq.freq_12V);
+			break;
+		default:
+			smblib_set_opt_freq_buck(chg,
+					chg->chg_freq.freq_removal);
+			break;
+		}
+	}
+
+	if (chg->usb_psy_desc.type == POWER_SUPPLY_TYPE_USB_HVDCP_3) {
+		rc = smblib_read(chg, QC_PULSE_COUNT_STATUS_REG, &stat);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't read QC_PULSE_COUNT rc=%d\n", rc);
+			return;
+		}
+		pulses = (stat & QC_PULSE_COUNT_MASK);
+
+		if (pulses < QC3_PULSES_FOR_6V)
+			smblib_set_opt_freq_buck(chg,
+				chg->chg_freq.freq_5V);
+		else if (pulses < QC3_PULSES_FOR_9V)
+			smblib_set_opt_freq_buck(chg,
+				chg->chg_freq.freq_6V_8V);
+		else if (pulses < QC3_PULSES_FOR_12V)
+			smblib_set_opt_freq_buck(chg,
+				chg->chg_freq.freq_9V);
+		else
+			smblib_set_opt_freq_buck(chg,
+				chg->chg_freq.freq_12V);
+	}
+}
+
+/* triggers when HVDCP 3.0 authentication has finished */
+static void smblib_handle_hvdcp_3p0_auth_done(struct smb_charger *chg,
+					      bool rising)
+{
+	const struct apsd_result *apsd_result;
+	int rc;
+
+	if (!rising)
+		return;
+
+	if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+		/*
+		 * Disable AUTH_IRQ_EN_CFG_BIT to receive adapter voltage
+		 * change interrupt.
+		 */
+		rc = smblib_masked_write(chg,
+				USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+				AUTH_IRQ_EN_CFG_BIT, 0);
+		if (rc < 0)
+			smblib_err(chg,
+				"Couldn't enable QC auth setting rc=%d\n", rc);
+	}
+
+	if (chg->mode == PARALLEL_MASTER)
+		vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, true, 0);
+
+	/* QC authentication done, parallel charger can be enabled now */
+	vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER, false, 0);
+
+	/* the APSD done handler will set the USB supply type */
+	apsd_result = smblib_get_apsd_result(chg);
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-3p0-auth-done rising; %s detected\n",
+		   apsd_result->name);
+}
+
+static void smblib_handle_hvdcp_check_timeout(struct smb_charger *chg,
+					      bool rising, bool qc_charger)
+{
+	const struct apsd_result *apsd_result = smblib_update_usb_type(chg);
+
+	/* Hold off PD only until hvdcp 2.0 detection timeout */
+	if (rising) {
+		vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+								false, 0);
+		if (get_effective_result(chg->pd_disallowed_votable_indirect))
+			/* could be a legacy cable, try doing hvdcp */
+			try_rerun_apsd_for_hvdcp(chg);
+
+		/*
+		 * HVDCP detection timeout done
+		 * If adapter is not QC2.0/QC3.0 - it is a plain old DCP.
+		 */
+		if (!qc_charger && (apsd_result->bit & DCP_CHARGER_BIT))
+			/* enforce DCP ICL if specified */
+			vote(chg->usb_icl_votable, DCP_VOTER,
+				chg->dcp_icl_ua != -EINVAL, chg->dcp_icl_ua);
+		/*
+		 * If adapter is not QC2.0/QC3.0 remove vote for parallel
+		 * disable.
+		 * Otherwise if adapter is QC2.0/QC3.0 wait for authentication
+		 * to complete.
+		 */
+		if (!qc_charger)
+			vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER,
+					false, 0);
+	}
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: smblib_handle_hvdcp_check_timeout %s\n",
+		   rising ? "rising" : "falling");
+}
+
+/* triggers when HVDCP is detected */
+static void smblib_handle_hvdcp_detect_done(struct smb_charger *chg,
+					    bool rising)
+{
+	if (!rising)
+		return;
+
+	/* the APSD done handler will set the USB supply type */
+	cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: hvdcp-detect-done %s\n",
+		   rising ? "rising" : "falling");
+}
+
+#define HVDCP_DET_MS 2500
+static void smblib_handle_apsd_done(struct smb_charger *chg, bool rising)
+{
+	const struct apsd_result *apsd_result;
+
+	if (!rising)
+		return;
+
+	apsd_result = smblib_update_usb_type(chg);
+	switch (apsd_result->bit) {
+	case SDP_CHARGER_BIT:
+	case CDP_CHARGER_BIT:
+		if (chg->micro_usb_mode)
+			extcon_set_cable_state_(chg->extcon, EXTCON_USB,
+					true);
+		/* if not DCP then no hvdcp timeout happens. Enable pd here */
+		vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+				false, 0);
+		break;
+	case OCP_CHARGER_BIT:
+	case FLOAT_CHARGER_BIT:
+		/*
+		 * if not DCP then no hvdcp timeout happens. Enable
+		 * pd/parallel here.
+		 */
+		vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+				false, 0);
+		vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER, false, 0);
+		break;
+	case DCP_CHARGER_BIT:
+		if (chg->wa_flags & QC_CHARGER_DETECTION_WA_BIT)
+			schedule_delayed_work(&chg->hvdcp_detect_work,
+					      msecs_to_jiffies(HVDCP_DET_MS));
+		break;
+	default:
+		break;
+	}
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: apsd-done rising; %s detected\n",
+		   apsd_result->name);
+}
+
+irqreturn_t smblib_handle_usb_source_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc = 0;
+	u8 stat;
+
+	rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+	smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
+
+	smblib_handle_apsd_done(chg,
+		(bool)(stat & APSD_DTC_STATUS_DONE_BIT));
+
+	smblib_handle_hvdcp_detect_done(chg,
+		(bool)(stat & QC_CHARGER_BIT));
+
+	smblib_handle_hvdcp_check_timeout(chg,
+		(bool)(stat & HVDCP_CHECK_TIMEOUT_BIT),
+		(bool)(stat & QC_CHARGER_BIT));
+
+	smblib_handle_hvdcp_3p0_auth_done(chg,
+		(bool)(stat & QC_AUTH_DONE_STATUS_BIT));
+
+	smblib_handle_sdp_enumeration_done(chg,
+		(bool)(stat & ENUMERATION_DONE_BIT));
+
+	smblib_handle_slow_plugin_timeout(chg,
+		(bool)(stat & SLOW_PLUGIN_TIMEOUT_BIT));
+
+	smblib_hvdcp_adaptive_voltage_change(chg);
+
+	power_supply_changed(chg->usb_psy);
+
+	rc = smblib_read(chg, APSD_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read APSD_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+	smblib_dbg(chg, PR_REGISTER, "APSD_STATUS = 0x%02x\n", stat);
+
+	return IRQ_HANDLED;
+}
+
+static void typec_source_removal(struct smb_charger *chg)
+{
+	int rc;
+
+	/* reset both usbin current and voltage votes */
+	vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER, false, 0);
+	vote(chg->pl_enable_votable_indirect, USBIN_V_VOTER, false, 0);
+
+	cancel_delayed_work_sync(&chg->hvdcp_detect_work);
+
+	if (chg->wa_flags & QC_AUTH_INTERRUPT_WA_BIT) {
+		/* re-enable AUTH_IRQ_EN_CFG_BIT */
+		rc = smblib_masked_write(chg,
+				USBIN_SOURCE_CHANGE_INTRPT_ENB_REG,
+				AUTH_IRQ_EN_CFG_BIT, AUTH_IRQ_EN_CFG_BIT);
+		if (rc < 0)
+			smblib_err(chg,
+				"Couldn't enable QC auth setting rc=%d\n", rc);
+	}
+
+	/* reconfigure allowed voltage for HVDCP */
+	rc = smblib_set_adapter_allowance(chg,
+			USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't set USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V rc=%d\n",
+			rc);
+
+	chg->voltage_min_uv = MICRO_5V;
+	chg->voltage_max_uv = MICRO_5V;
+
+	/* clear USB ICL vote for PD_VOTER */
+	rc = vote(chg->usb_icl_votable, PD_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't un-vote PD from USB ICL rc=%d\n", rc);
+
+	/* clear USB ICL vote for USB_PSY_VOTER */
+	rc = vote(chg->usb_icl_votable, USB_PSY_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg,
+			"Couldn't un-vote USB_PSY from USB ICL rc=%d\n", rc);
+
+	/* clear USB ICL vote for DCP_VOTER */
+	rc = vote(chg->usb_icl_votable, DCP_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg,
+			"Couldn't un-vote DCP from USB ICL rc=%d\n", rc);
+
+	/* clear USB ICL vote for PL_USBIN_USBIN_VOTER */
+	rc = vote(chg->usb_icl_votable, PL_USBIN_USBIN_VOTER, false, 0);
+	if (rc < 0)
+		smblib_err(chg,
+			"Couldn't un-vote PL_USBIN_USBIN from USB ICL rc=%d\n",
+			rc);
+}
+
+static void typec_source_insertion(struct smb_charger *chg)
+{
+}
+
+static void typec_sink_insertion(struct smb_charger *chg)
+{
+	/* when a sink is inserted we should not wait on hvdcp timeout to
+	 * enable pd
+	 */
+	vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+			false, 0);
+}
+
+static void typec_sink_removal(struct smb_charger *chg)
+{
+	smblib_set_charge_param(chg, &chg->param.freq_boost,
+			chg->chg_freq.freq_above_otg_threshold);
+	chg->boost_current_ua = 0;
+}
+
+static void smblib_handle_typec_removal(struct smb_charger *chg)
+{
+	vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, true, 0);
+	vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER, true, 0);
+	vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER, true, 0);
+	vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER, true, 0);
+	vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER, true, 0);
+
+	/* reset votes from vbus_cc_short */
+	vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
+			true, 0);
+	vote(chg->hvdcp_disable_votable_indirect, PD_INACTIVE_VOTER,
+			true, 0);
+	/*
+	 * cable could be removed during hard reset, remove its vote to
+	 * disable apsd
+	 */
+	vote(chg->apsd_disable_votable, PD_HARD_RESET_VOTER, false, 0);
+
+	chg->vconn_attempts = 0;
+	chg->otg_attempts = 0;
+	chg->pulse_cnt = 0;
+	chg->usb_icl_delta_ua = 0;
+
+	chg->usb_ever_removed = true;
+
+	smblib_update_usb_type(chg);
+
+	typec_source_removal(chg);
+	typec_sink_removal(chg);
+}
+
+static void smblib_handle_typec_insertion(struct smb_charger *chg,
+		bool sink_attached, bool legacy_cable)
+{
+	int rp;
+	bool vbus_cc_short = false;
+	bool valid_legacy_cable;
+
+	vote(chg->pd_disallowed_votable_indirect, CC_DETACHED_VOTER, false, 0);
+
+	if (sink_attached) {
+		typec_source_removal(chg);
+		typec_sink_insertion(chg);
+	} else {
+		typec_source_insertion(chg);
+		typec_sink_removal(chg);
+	}
+
+	valid_legacy_cable = legacy_cable &&
+		(chg->usb_ever_removed || !smblib_sysok_reason_usbin(chg));
+	vote(chg->pd_disallowed_votable_indirect, LEGACY_CABLE_VOTER,
+			valid_legacy_cable, 0);
+
+	if (valid_legacy_cable) {
+		rp = smblib_get_prop_ufp_mode(chg);
+		if (rp == POWER_SUPPLY_TYPEC_SOURCE_HIGH
+				|| rp == POWER_SUPPLY_TYPEC_NON_COMPLIANT) {
+			vbus_cc_short = true;
+			smblib_err(chg, "Disabling PD and HVDCP, VBUS-CC shorted, rp = %d found\n",
+					rp);
+		}
+	}
+
+	vote(chg->hvdcp_disable_votable_indirect, VBUS_CC_SHORT_VOTER,
+			vbus_cc_short, 0);
+	vote(chg->pd_disallowed_votable_indirect, VBUS_CC_SHORT_VOTER,
+			vbus_cc_short, 0);
+}
+
+static void smblib_handle_typec_debounce_done(struct smb_charger *chg,
+			bool rising, bool sink_attached, bool legacy_cable)
+{
+	int rc;
+	union power_supply_propval pval = {0, };
+
+	if (rising)
+		smblib_handle_typec_insertion(chg, sink_attached, legacy_cable);
+	else
+		smblib_handle_typec_removal(chg);
+
+	rc = smblib_get_prop_typec_mode(chg, &pval);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't get prop typec mode rc=%d\n", rc);
+
+	/*
+	 * HW BUG - after cable is removed, medium or high rd reading
+	 * falls to std. Use it for signal of typec cc detachment in
+	 * software WA.
+	 */
+	if (chg->cc2_sink_detach_flag == CC2_SINK_MEDIUM_HIGH
+		&& pval.intval == POWER_SUPPLY_TYPEC_SOURCE_DEFAULT) {
+
+		chg->cc2_sink_detach_flag = CC2_SINK_WA_DONE;
+
+		rc = smblib_masked_write(chg,
+				TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				EXIT_SNK_BASED_ON_CC_BIT, 0);
+		if (rc < 0)
+			smblib_err(chg, "Couldn't get prop typec mode rc=%d\n",
+				rc);
+	}
+
+	smblib_dbg(chg, PR_INTERRUPT, "IRQ: debounce-done %s; Type-C %s detected\n",
+		   rising ? "rising" : "falling",
+		   smblib_typec_mode_name[pval.intval]);
+}
+
+irqreturn_t smblib_handle_usb_typec_change_for_uusb(struct smb_charger *chg)
+{
+	int rc;
+	u8 stat;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_3_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_3 rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_3 = 0x%02x OTG=%d\n",
+		stat, !!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
+
+	extcon_set_cable_state_(chg->extcon, EXTCON_USB_HOST,
+			!!(stat & (U_USB_GND_NOVBUS_BIT | U_USB_GND_BIT)));
+	power_supply_changed(chg->usb_psy);
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+	u8 stat4, stat5;
+	bool debounce_done, sink_attached, legacy_cable;
+
+	if (chg->micro_usb_mode)
+		return smblib_handle_usb_typec_change_for_uusb(chg);
+
+	/* WA - not when PD hard_reset WIP on cc2 in sink mode */
+	if (chg->cc2_sink_detach_flag == CC2_SINK_STD)
+		return IRQ_HANDLED;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat4);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat5);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_5 rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	debounce_done = (bool)(stat4 & TYPEC_DEBOUNCE_DONE_STATUS_BIT);
+	sink_attached = (bool)(stat4 & UFP_DFP_MODE_STATUS_BIT);
+	legacy_cable = (bool)(stat5 & TYPEC_LEGACY_CABLE_STATUS_BIT);
+
+	smblib_handle_typec_debounce_done(chg,
+			debounce_done, sink_attached, legacy_cable);
+
+	if (stat4 & TYPEC_VBUS_ERROR_STATUS_BIT)
+		smblib_dbg(chg, PR_INTERRUPT, "IRQ: %s vbus-error\n",
+			irq_data->name);
+
+	if (stat4 & TYPEC_VCONN_OVERCURR_STATUS_BIT)
+		schedule_work(&chg->vconn_oc_work);
+
+	power_supply_changed(chg->usb_psy);
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_4 = 0x%02x\n", stat4);
+	smblib_dbg(chg, PR_REGISTER, "TYPE_C_STATUS_5 = 0x%02x\n", stat5);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_dc_plugin(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	power_supply_changed(chg->dc_psy);
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+
+	chg->is_hdc = true;
+	schedule_delayed_work(&chg->clear_hdc_work, msecs_to_jiffies(60));
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+	u8 stat;
+
+	if (!(chg->wa_flags & BOOST_BACK_WA))
+		return IRQ_HANDLED;
+
+	rc = smblib_read(chg, POWER_PATH_STATUS_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read POWER_PATH_STATUS rc=%d\n", rc);
+		return IRQ_HANDLED;
+	}
+
+	if ((stat & USE_USBIN_BIT) &&
+			get_effective_result(chg->usb_icl_votable) < USBIN_25MA)
+		return IRQ_HANDLED;
+
+	if (stat & USE_DCIN_BIT)
+		return IRQ_HANDLED;
+
+	if (is_storming(&irq_data->storm_data)) {
+		smblib_err(chg, "Reverse boost detected: voting 0mA to suspend input\n");
+		vote(chg->usb_icl_votable, BOOST_BACK_VOTER, true, 0);
+	}
+
+	return IRQ_HANDLED;
+}
+
+irqreturn_t smblib_handle_wdog_bark(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb_charger *chg = irq_data->parent_data;
+	int rc;
+
+	rc = smblib_write(chg, BARK_BITE_WDOG_PET_REG, BARK_BITE_WDOG_PET_BIT);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't pet the dog rc=%d\n", rc);
+
+	return IRQ_HANDLED;
+}
+
+/***************
+ * Work Queues *
+ ***************/
+
+static void smblib_hvdcp_detect_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+					       hvdcp_detect_work.work);
+
+	vote(chg->pd_disallowed_votable_indirect, HVDCP_TIMEOUT_VOTER,
+				false, 0);
+	if (get_effective_result(chg->pd_disallowed_votable_indirect))
+		/* pd is still disabled, try hvdcp */
+		try_rerun_apsd_for_hvdcp(chg);
+	else
+		/* notify pd now that pd is allowed */
+		power_supply_changed(chg->usb_psy);
+}
+
+static void bms_update_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+						bms_update_work);
+
+	smblib_suspend_on_debug_battery(chg);
+
+	if (chg->batt_psy)
+		power_supply_changed(chg->batt_psy);
+}
+
+static void step_soc_req_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+						step_soc_req_work.work);
+	union power_supply_propval pval = {0, };
+	int rc;
+
+	rc = smblib_get_prop_batt_capacity(chg, &pval);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get batt capacity rc=%d\n", rc);
+		return;
+	}
+
+	step_charge_soc_update(chg, pval.intval);
+}
+
+static void clear_hdc_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+						clear_hdc_work.work);
+
+	chg->is_hdc = 0;
+}
+
+static void rdstd_cc2_detach_work(struct work_struct *work)
+{
+	int rc;
+	u8 stat;
+	struct smb_irq_data irq_data = {NULL, "cc2-removal-workaround"};
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+						rdstd_cc2_detach_work);
+
+	/*
+	 * WA steps -
+	 * 1. Enable both UFP and DFP, wait for 10ms.
+	 * 2. Disable DFP, wait for 30ms.
+	 * 3. Removal detected if both TYPEC_DEBOUNCE_DONE_STATUS
+	 *    and TIMER_STAGE bits are gone, otherwise repeat all by
+	 *    work rescheduling.
+	 * Note, work will be cancelled when pd_hard_reset is 0.
+	 */
+
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
+				 UFP_EN_CMD_BIT | DFP_EN_CMD_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
+		return;
+	}
+
+	usleep_range(10000, 11000);
+
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 UFP_EN_CMD_BIT | DFP_EN_CMD_BIT,
+				 UFP_EN_CMD_BIT);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't write TYPE_C_CTRL_REG rc=%d\n", rc);
+		return;
+	}
+
+	usleep_range(30000, 31000);
+
+	rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't read TYPE_C_STATUS_4 rc=%d\n",
+			rc);
+		return;
+	}
+	if (stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT)
+		goto rerun;
+
+	rc = smblib_read(chg, TYPE_C_STATUS_5_REG, &stat);
+	if (rc < 0) {
+		smblib_err(chg,
+			"Couldn't read TYPE_C_STATUS_5_REG rc=%d\n", rc);
+		return;
+	}
+	if (stat & TIMER_STAGE_2_BIT)
+		goto rerun;
+
+	/* Bingo, cc2 removal detected */
+	smblib_reg_block_restore(chg, cc2_detach_settings);
+	chg->cc2_sink_detach_flag = CC2_SINK_WA_DONE;
+	irq_data.parent_data = chg;
+	smblib_handle_usb_typec_change(0, &irq_data);
+
+	return;
+
+rerun:
+	schedule_work(&chg->rdstd_cc2_detach_work);
+}
+
+static void smblib_otg_oc_exit(struct smb_charger *chg, bool success)
+{
+	int rc;
+
+	chg->otg_attempts = 0;
+	if (!success) {
+		smblib_err(chg, "OTG soft start failed\n");
+		chg->otg_en = false;
+	}
+
+	smblib_dbg(chg, PR_OTG, "enabling VBUS < 1V check\n");
+	rc = smblib_masked_write(chg, OTG_CFG_REG,
+					QUICKSTART_OTG_FASTROLESWAP_BIT, 0);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't enable VBUS < 1V check rc=%d\n", rc);
+
+	if (!chg->external_vconn && chg->vconn_en) {
+		chg->vconn_attempts = 0;
+		if (success) {
+			rc = _smblib_vconn_regulator_enable(
+							chg->vconn_vreg->rdev);
+			if (rc < 0)
+				smblib_err(chg, "Couldn't enable VCONN rc=%d\n",
+									rc);
+		} else {
+			chg->vconn_en = false;
+		}
+	}
+}
+
+#define MAX_OC_FALLING_TRIES 10
+static void smblib_otg_oc_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+								otg_oc_work);
+	int rc, i;
+	u8 stat;
+
+	if (!chg->vbus_vreg || !chg->vbus_vreg->rdev)
+		return;
+
+	smblib_err(chg, "over-current detected on VBUS\n");
+	mutex_lock(&chg->otg_oc_lock);
+	if (!chg->otg_en)
+		goto unlock;
+
+	smblib_dbg(chg, PR_OTG, "disabling VBUS < 1V check\n");
+	smblib_masked_write(chg, OTG_CFG_REG,
+					QUICKSTART_OTG_FASTROLESWAP_BIT,
+					QUICKSTART_OTG_FASTROLESWAP_BIT);
+
+	/*
+	 * If 500ms has passed and another over-current interrupt has not
+	 * triggered then it is likely that the software based soft start was
+	 * successful and the VBUS < 1V restriction should be re-enabled.
+	 */
+	schedule_delayed_work(&chg->otg_ss_done_work, msecs_to_jiffies(500));
+
+	rc = _smblib_vbus_regulator_disable(chg->vbus_vreg->rdev);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't disable VBUS rc=%d\n", rc);
+		goto unlock;
+	}
+
+	if (++chg->otg_attempts > OTG_MAX_ATTEMPTS) {
+		cancel_delayed_work_sync(&chg->otg_ss_done_work);
+		smblib_err(chg, "OTG failed to enable after %d attempts\n",
+			   chg->otg_attempts - 1);
+		smblib_otg_oc_exit(chg, false);
+		goto unlock;
+	}
+
+	/*
+	 * The real time status should go low within 10ms. Poll every 1-2ms to
+	 * minimize the delay when re-enabling OTG.
+	 */
+	for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
+		usleep_range(1000, 2000);
+		rc = smblib_read(chg, OTG_BASE + INT_RT_STS_OFFSET, &stat);
+		if (rc >= 0 && !(stat & OTG_OVERCURRENT_RT_STS_BIT))
+			break;
+	}
+
+	if (i >= MAX_OC_FALLING_TRIES) {
+		cancel_delayed_work_sync(&chg->otg_ss_done_work);
+		smblib_err(chg, "OTG OC did not fall after %dms\n",
+						2 * MAX_OC_FALLING_TRIES);
+		smblib_otg_oc_exit(chg, false);
+		goto unlock;
+	}
+
+	smblib_dbg(chg, PR_OTG, "OTG OC fell after %dms\n", 2 * i + 1);
+	rc = _smblib_vbus_regulator_enable(chg->vbus_vreg->rdev);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't enable VBUS rc=%d\n", rc);
+		goto unlock;
+	}
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+}
+
+static void smblib_vconn_oc_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+								vconn_oc_work);
+	int rc, i;
+	u8 stat;
+
+	smblib_err(chg, "over-current detected on VCONN\n");
+	if (!chg->vconn_vreg || !chg->vconn_vreg->rdev)
+		return;
+
+	mutex_lock(&chg->otg_oc_lock);
+	rc = _smblib_vconn_regulator_disable(chg->vconn_vreg->rdev);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't disable VCONN rc=%d\n", rc);
+		goto unlock;
+	}
+
+	if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
+		smblib_err(chg, "VCONN failed to enable after %d attempts\n",
+			   chg->otg_attempts - 1);
+		chg->vconn_en = false;
+		chg->vconn_attempts = 0;
+		goto unlock;
+	}
+
+	/*
+	 * The real time status should go low within 10ms. Poll every 1-2ms to
+	 * minimize the delay when re-enabling OTG.
+	 */
+	for (i = 0; i < MAX_OC_FALLING_TRIES; ++i) {
+		usleep_range(1000, 2000);
+		rc = smblib_read(chg, TYPE_C_STATUS_4_REG, &stat);
+		if (rc >= 0 && !(stat & TYPEC_VCONN_OVERCURR_STATUS_BIT))
+			break;
+	}
+
+	if (i >= MAX_OC_FALLING_TRIES) {
+		smblib_err(chg, "VCONN OC did not fall after %dms\n",
+						2 * MAX_OC_FALLING_TRIES);
+		chg->vconn_en = false;
+		chg->vconn_attempts = 0;
+		goto unlock;
+	}
+
+	smblib_dbg(chg, PR_OTG, "VCONN OC fell after %dms\n", 2 * i + 1);
+	if (++chg->vconn_attempts > VCONN_MAX_ATTEMPTS) {
+		smblib_err(chg, "VCONN failed to enable after %d attempts\n",
+			   chg->vconn_attempts - 1);
+		chg->vconn_en = false;
+		goto unlock;
+	}
+
+	rc = _smblib_vconn_regulator_enable(chg->vconn_vreg->rdev);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't enable VCONN rc=%d\n", rc);
+		goto unlock;
+	}
+
+unlock:
+	mutex_unlock(&chg->otg_oc_lock);
+}
+
+static void smblib_otg_ss_done_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+							otg_ss_done_work.work);
+	int rc;
+	bool success = false;
+	u8 stat;
+
+	mutex_lock(&chg->otg_oc_lock);
+	rc = smblib_read(chg, OTG_STATUS_REG, &stat);
+	if (rc < 0)
+		smblib_err(chg, "Couldn't read OTG status rc=%d\n", rc);
+	else if (stat & BOOST_SOFTSTART_DONE_BIT)
+		success = true;
+
+	smblib_otg_oc_exit(chg, success);
+	mutex_unlock(&chg->otg_oc_lock);
+}
+
+static void smblib_icl_change_work(struct work_struct *work)
+{
+	struct smb_charger *chg = container_of(work, struct smb_charger,
+							icl_change_work.work);
+	int rc, settled_ua;
+
+	rc = smblib_get_charge_param(chg, &chg->param.icl_stat, &settled_ua);
+	if (rc < 0) {
+		smblib_err(chg, "Couldn't get ICL status rc=%d\n", rc);
+		return;
+	}
+
+	power_supply_changed(chg->usb_main_psy);
+	vote(chg->pl_enable_votable_indirect, USBIN_I_VOTER,
+				settled_ua >= USB_WEAK_INPUT_UA, 0);
+
+	smblib_dbg(chg, PR_INTERRUPT, "icl_settled=%d\n", settled_ua);
+}
+
+static int smblib_create_votables(struct smb_charger *chg)
+{
+	int rc = 0;
+
+	chg->fcc_votable = find_votable("FCC");
+	if (!chg->fcc_votable) {
+		rc = -EPROBE_DEFER;
+		return rc;
+	}
+
+	chg->fv_votable = find_votable("FV");
+	if (!chg->fv_votable) {
+		rc = -EPROBE_DEFER;
+		return rc;
+	}
+
+	chg->pl_disable_votable = find_votable("PL_DISABLE");
+	if (!chg->pl_disable_votable) {
+		rc = -EPROBE_DEFER;
+		return rc;
+	}
+	vote(chg->pl_disable_votable, PL_INDIRECT_VOTER, true, 0);
+	vote(chg->pl_disable_votable, PL_DELAY_HVDCP_VOTER, true, 0);
+
+	chg->dc_suspend_votable = create_votable("DC_SUSPEND", VOTE_SET_ANY,
+					smblib_dc_suspend_vote_callback,
+					chg);
+	if (IS_ERR(chg->dc_suspend_votable)) {
+		rc = PTR_ERR(chg->dc_suspend_votable);
+		return rc;
+	}
+
+	chg->usb_icl_votable = create_votable("USB_ICL", VOTE_MIN,
+					smblib_usb_icl_vote_callback,
+					chg);
+	if (IS_ERR(chg->usb_icl_votable)) {
+		rc = PTR_ERR(chg->usb_icl_votable);
+		return rc;
+	}
+
+	chg->dc_icl_votable = create_votable("DC_ICL", VOTE_MIN,
+					smblib_dc_icl_vote_callback,
+					chg);
+	if (IS_ERR(chg->dc_icl_votable)) {
+		rc = PTR_ERR(chg->dc_icl_votable);
+		return rc;
+	}
+
+	chg->pd_disallowed_votable_indirect
+		= create_votable("PD_DISALLOWED_INDIRECT", VOTE_SET_ANY,
+			smblib_pd_disallowed_votable_indirect_callback, chg);
+	if (IS_ERR(chg->pd_disallowed_votable_indirect)) {
+		rc = PTR_ERR(chg->pd_disallowed_votable_indirect);
+		return rc;
+	}
+
+	chg->pd_allowed_votable = create_votable("PD_ALLOWED",
+					VOTE_SET_ANY, NULL, NULL);
+	if (IS_ERR(chg->pd_allowed_votable)) {
+		rc = PTR_ERR(chg->pd_allowed_votable);
+		return rc;
+	}
+
+	chg->awake_votable = create_votable("AWAKE", VOTE_SET_ANY,
+					smblib_awake_vote_callback,
+					chg);
+	if (IS_ERR(chg->awake_votable)) {
+		rc = PTR_ERR(chg->awake_votable);
+		return rc;
+	}
+
+	chg->chg_disable_votable = create_votable("CHG_DISABLE", VOTE_SET_ANY,
+					smblib_chg_disable_vote_callback,
+					chg);
+	if (IS_ERR(chg->chg_disable_votable)) {
+		rc = PTR_ERR(chg->chg_disable_votable);
+		return rc;
+	}
+
+	chg->pl_enable_votable_indirect = create_votable("PL_ENABLE_INDIRECT",
+					VOTE_SET_ANY,
+					smblib_pl_enable_indirect_vote_callback,
+					chg);
+	if (IS_ERR(chg->pl_enable_votable_indirect)) {
+		rc = PTR_ERR(chg->pl_enable_votable_indirect);
+		return rc;
+	}
+
+	chg->hvdcp_disable_votable_indirect = create_votable(
+				"HVDCP_DISABLE_INDIRECT",
+				VOTE_SET_ANY,
+				smblib_hvdcp_disable_indirect_vote_callback,
+				chg);
+	if (IS_ERR(chg->hvdcp_disable_votable_indirect)) {
+		rc = PTR_ERR(chg->hvdcp_disable_votable_indirect);
+		return rc;
+	}
+
+	chg->hvdcp_enable_votable = create_votable("HVDCP_ENABLE",
+					VOTE_SET_ANY,
+					smblib_hvdcp_enable_vote_callback,
+					chg);
+	if (IS_ERR(chg->hvdcp_enable_votable)) {
+		rc = PTR_ERR(chg->hvdcp_enable_votable);
+		return rc;
+	}
+
+	chg->apsd_disable_votable = create_votable("APSD_DISABLE",
+					VOTE_SET_ANY,
+					smblib_apsd_disable_vote_callback,
+					chg);
+	if (IS_ERR(chg->apsd_disable_votable)) {
+		rc = PTR_ERR(chg->apsd_disable_votable);
+		return rc;
+	}
+
+	chg->hvdcp_hw_inov_dis_votable = create_votable("HVDCP_HW_INOV_DIS",
+					VOTE_SET_ANY,
+					smblib_hvdcp_hw_inov_dis_vote_callback,
+					chg);
+	if (IS_ERR(chg->hvdcp_hw_inov_dis_votable)) {
+		rc = PTR_ERR(chg->hvdcp_hw_inov_dis_votable);
+		return rc;
+	}
+
+	return rc;
+}
+
+static void smblib_destroy_votables(struct smb_charger *chg)
+{
+	if (chg->dc_suspend_votable)
+		destroy_votable(chg->dc_suspend_votable);
+	if (chg->usb_icl_votable)
+		destroy_votable(chg->usb_icl_votable);
+	if (chg->dc_icl_votable)
+		destroy_votable(chg->dc_icl_votable);
+	if (chg->pd_disallowed_votable_indirect)
+		destroy_votable(chg->pd_disallowed_votable_indirect);
+	if (chg->pd_allowed_votable)
+		destroy_votable(chg->pd_allowed_votable);
+	if (chg->awake_votable)
+		destroy_votable(chg->awake_votable);
+	if (chg->chg_disable_votable)
+		destroy_votable(chg->chg_disable_votable);
+	if (chg->pl_enable_votable_indirect)
+		destroy_votable(chg->pl_enable_votable_indirect);
+	if (chg->apsd_disable_votable)
+		destroy_votable(chg->apsd_disable_votable);
+	if (chg->hvdcp_hw_inov_dis_votable)
+		destroy_votable(chg->hvdcp_hw_inov_dis_votable);
+}
+
+static void smblib_iio_deinit(struct smb_charger *chg)
+{
+	if (!IS_ERR_OR_NULL(chg->iio.temp_chan))
+		iio_channel_release(chg->iio.temp_chan);
+	if (!IS_ERR_OR_NULL(chg->iio.temp_max_chan))
+		iio_channel_release(chg->iio.temp_max_chan);
+	if (!IS_ERR_OR_NULL(chg->iio.usbin_i_chan))
+		iio_channel_release(chg->iio.usbin_i_chan);
+	if (!IS_ERR_OR_NULL(chg->iio.usbin_v_chan))
+		iio_channel_release(chg->iio.usbin_v_chan);
+	if (!IS_ERR_OR_NULL(chg->iio.batt_i_chan))
+		iio_channel_release(chg->iio.batt_i_chan);
+}
+
+int smblib_init(struct smb_charger *chg)
+{
+	int rc = 0;
+
+	mutex_init(&chg->write_lock);
+	mutex_init(&chg->otg_oc_lock);
+	INIT_WORK(&chg->bms_update_work, bms_update_work);
+	INIT_WORK(&chg->rdstd_cc2_detach_work, rdstd_cc2_detach_work);
+	INIT_DELAYED_WORK(&chg->hvdcp_detect_work, smblib_hvdcp_detect_work);
+	INIT_DELAYED_WORK(&chg->step_soc_req_work, step_soc_req_work);
+	INIT_DELAYED_WORK(&chg->clear_hdc_work, clear_hdc_work);
+	INIT_WORK(&chg->otg_oc_work, smblib_otg_oc_work);
+	INIT_WORK(&chg->vconn_oc_work, smblib_vconn_oc_work);
+	INIT_DELAYED_WORK(&chg->otg_ss_done_work, smblib_otg_ss_done_work);
+	INIT_DELAYED_WORK(&chg->icl_change_work, smblib_icl_change_work);
+	chg->fake_capacity = -EINVAL;
+
+	switch (chg->mode) {
+	case PARALLEL_MASTER:
+		chg->qnovo_fcc_ua = -EINVAL;
+		chg->qnovo_fv_uv = -EINVAL;
+		rc = smblib_create_votables(chg);
+		if (rc < 0) {
+			smblib_err(chg, "Couldn't create votables rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		rc = smblib_register_notifier(chg);
+		if (rc < 0) {
+			smblib_err(chg,
+				"Couldn't register notifier rc=%d\n", rc);
+			return rc;
+		}
+
+		chg->bms_psy = power_supply_get_by_name("bms");
+		chg->pl.psy = power_supply_get_by_name("parallel");
+		break;
+	case PARALLEL_SLAVE:
+		break;
+	default:
+		smblib_err(chg, "Unsupported mode %d\n", chg->mode);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+int smblib_deinit(struct smb_charger *chg)
+{
+	switch (chg->mode) {
+	case PARALLEL_MASTER:
+		power_supply_unreg_notifier(&chg->nb);
+		smblib_destroy_votables(chg);
+		break;
+	case PARALLEL_SLAVE:
+		break;
+	default:
+		smblib_err(chg, "Unsupported mode %d\n", chg->mode);
+		return -EINVAL;
+	}
+
+	smblib_iio_deinit(chg);
+
+	return 0;
+}
diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h
new file mode 100644
index 0000000..21ccd3c
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-lib.h
@@ -0,0 +1,487 @@
+/* Copyright (c) 2016-2017 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 __SMB2_CHARGER_H
+#define __SMB2_CHARGER_H
+#include <linux/types.h>
+#include <linux/interrupt.h>
+#include <linux/irqreturn.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/consumer.h>
+#include <linux/extcon.h>
+#include "storm-watch.h"
+
+enum print_reason {
+	PR_INTERRUPT	= BIT(0),
+	PR_REGISTER	= BIT(1),
+	PR_MISC		= BIT(2),
+	PR_PARALLEL	= BIT(3),
+	PR_OTG		= BIT(4),
+};
+
+#define DEFAULT_VOTER			"DEFAULT_VOTER"
+#define USER_VOTER			"USER_VOTER"
+#define PD_VOTER			"PD_VOTER"
+#define DCP_VOTER			"DCP_VOTER"
+#define PL_USBIN_USBIN_VOTER		"PL_USBIN_USBIN_VOTER"
+#define USB_PSY_VOTER			"USB_PSY_VOTER"
+#define PL_TAPER_WORK_RUNNING_VOTER	"PL_TAPER_WORK_RUNNING_VOTER"
+#define PL_INDIRECT_VOTER		"PL_INDIRECT_VOTER"
+#define USBIN_I_VOTER			"USBIN_I_VOTER"
+#define USBIN_V_VOTER			"USBIN_V_VOTER"
+#define CHG_STATE_VOTER			"CHG_STATE_VOTER"
+#define TYPEC_SRC_VOTER			"TYPEC_SRC_VOTER"
+#define TAPER_END_VOTER			"TAPER_END_VOTER"
+#define THERMAL_DAEMON_VOTER		"THERMAL_DAEMON_VOTER"
+#define CC_DETACHED_VOTER		"CC_DETACHED_VOTER"
+#define HVDCP_TIMEOUT_VOTER		"HVDCP_TIMEOUT_VOTER"
+#define PD_DISALLOWED_INDIRECT_VOTER	"PD_DISALLOWED_INDIRECT_VOTER"
+#define PD_HARD_RESET_VOTER		"PD_HARD_RESET_VOTER"
+#define VBUS_CC_SHORT_VOTER		"VBUS_CC_SHORT_VOTER"
+#define LEGACY_CABLE_VOTER		"LEGACY_CABLE_VOTER"
+#define PD_INACTIVE_VOTER		"PD_INACTIVE_VOTER"
+#define BOOST_BACK_VOTER		"BOOST_BACK_VOTER"
+#define HVDCP_INDIRECT_VOTER		"HVDCP_INDIRECT_VOTER"
+#define MICRO_USB_VOTER			"MICRO_USB_VOTER"
+#define DEBUG_BOARD_VOTER		"DEBUG_BOARD_VOTER"
+#define PD_SUSPEND_SUPPORTED_VOTER	"PD_SUSPEND_SUPPORTED_VOTER"
+#define PL_DELAY_HVDCP_VOTER		"PL_DELAY_HVDCP_VOTER"
+#define CTM_VOTER			"CTM_VOTER"
+#define SW_QC3_VOTER			"SW_QC3_VOTER"
+#define AICL_RERUN_VOTER		"AICL_RERUN_VOTER"
+
+#define VCONN_MAX_ATTEMPTS	3
+#define OTG_MAX_ATTEMPTS	3
+
+enum smb_mode {
+	PARALLEL_MASTER = 0,
+	PARALLEL_SLAVE,
+	NUM_MODES,
+};
+
+enum cc2_sink_type {
+	CC2_SINK_NONE = 0,
+	CC2_SINK_STD,
+	CC2_SINK_MEDIUM_HIGH,
+	CC2_SINK_WA_DONE,
+};
+
+enum {
+	QC_CHARGER_DETECTION_WA_BIT	= BIT(0),
+	BOOST_BACK_WA			= BIT(1),
+	TYPEC_CC2_REMOVAL_WA_BIT	= BIT(2),
+	QC_AUTH_INTERRUPT_WA_BIT	= BIT(3),
+};
+
+enum smb_irq_index {
+	CHG_ERROR_IRQ = 0,
+	CHG_STATE_CHANGE_IRQ,
+	STEP_CHG_STATE_CHANGE_IRQ,
+	STEP_CHG_SOC_UPDATE_FAIL_IRQ,
+	STEP_CHG_SOC_UPDATE_REQ_IRQ,
+	OTG_FAIL_IRQ,
+	OTG_OVERCURRENT_IRQ,
+	OTG_OC_DIS_SW_STS_IRQ,
+	TESTMODE_CHANGE_DET_IRQ,
+	BATT_TEMP_IRQ,
+	BATT_OCP_IRQ,
+	BATT_OV_IRQ,
+	BATT_LOW_IRQ,
+	BATT_THERM_ID_MISS_IRQ,
+	BATT_TERM_MISS_IRQ,
+	USBIN_COLLAPSE_IRQ,
+	USBIN_LT_3P6V_IRQ,
+	USBIN_UV_IRQ,
+	USBIN_OV_IRQ,
+	USBIN_PLUGIN_IRQ,
+	USBIN_SRC_CHANGE_IRQ,
+	USBIN_ICL_CHANGE_IRQ,
+	TYPE_C_CHANGE_IRQ,
+	DCIN_COLLAPSE_IRQ,
+	DCIN_LT_3P6V_IRQ,
+	DCIN_UV_IRQ,
+	DCIN_OV_IRQ,
+	DCIN_PLUGIN_IRQ,
+	DIV2_EN_DG_IRQ,
+	DCIN_ICL_CHANGE_IRQ,
+	WDOG_SNARL_IRQ,
+	WDOG_BARK_IRQ,
+	AICL_FAIL_IRQ,
+	AICL_DONE_IRQ,
+	HIGH_DUTY_CYCLE_IRQ,
+	INPUT_CURRENT_LIMIT_IRQ,
+	TEMPERATURE_CHANGE_IRQ,
+	SWITCH_POWER_OK_IRQ,
+	SMB_IRQ_MAX,
+};
+
+struct smb_irq_info {
+	const char			*name;
+	const irq_handler_t		handler;
+	const bool			wake;
+	const struct storm_watch	storm_data;
+	struct smb_irq_data		*irq_data;
+	int				irq;
+};
+
+static const unsigned int smblib_extcon_cable[] = {
+	EXTCON_USB,
+	EXTCON_USB_HOST,
+	EXTCON_NONE,
+};
+
+struct smb_regulator {
+	struct regulator_dev	*rdev;
+	struct regulator_desc	rdesc;
+};
+
+struct smb_irq_data {
+	void			*parent_data;
+	const char		*name;
+	struct storm_watch	storm_data;
+};
+
+struct smb_chg_param {
+	const char	*name;
+	u16		reg;
+	int		min_u;
+	int		max_u;
+	int		step_u;
+	int		(*get_proc)(struct smb_chg_param *param,
+				    u8 val_raw);
+	int		(*set_proc)(struct smb_chg_param *param,
+				    int val_u,
+				    u8 *val_raw);
+};
+
+struct smb_chg_freq {
+	unsigned int		freq_5V;
+	unsigned int		freq_6V_8V;
+	unsigned int		freq_9V;
+	unsigned int		freq_12V;
+	unsigned int		freq_removal;
+	unsigned int		freq_below_otg_threshold;
+	unsigned int		freq_above_otg_threshold;
+};
+
+struct smb_params {
+	struct smb_chg_param	fcc;
+	struct smb_chg_param	fv;
+	struct smb_chg_param	usb_icl;
+	struct smb_chg_param	icl_stat;
+	struct smb_chg_param	otg_cl;
+	struct smb_chg_param	dc_icl;
+	struct smb_chg_param	dc_icl_pt_lv;
+	struct smb_chg_param	dc_icl_pt_hv;
+	struct smb_chg_param	dc_icl_div2_lv;
+	struct smb_chg_param	dc_icl_div2_mid_lv;
+	struct smb_chg_param	dc_icl_div2_mid_hv;
+	struct smb_chg_param	dc_icl_div2_hv;
+	struct smb_chg_param	jeita_cc_comp;
+	struct smb_chg_param	step_soc_threshold[4];
+	struct smb_chg_param	step_soc;
+	struct smb_chg_param	step_cc_delta[5];
+	struct smb_chg_param	freq_buck;
+	struct smb_chg_param	freq_boost;
+};
+
+struct parallel_params {
+	struct power_supply	*psy;
+};
+
+struct smb_iio {
+	struct iio_channel	*temp_chan;
+	struct iio_channel	*temp_max_chan;
+	struct iio_channel	*usbin_i_chan;
+	struct iio_channel	*usbin_v_chan;
+	struct iio_channel	*batt_i_chan;
+	struct iio_channel	*connector_temp_chan;
+	struct iio_channel	*connector_temp_thr1_chan;
+	struct iio_channel	*connector_temp_thr2_chan;
+	struct iio_channel	*connector_temp_thr3_chan;
+};
+
+struct reg_info {
+	u16		reg;
+	u8		mask;
+	u8		val;
+	u8		bak;
+	const char	*desc;
+};
+
+struct smb_charger {
+	struct device		*dev;
+	char			*name;
+	struct regmap		*regmap;
+	struct smb_irq_info	*irq_info;
+	struct smb_params	param;
+	struct smb_iio		iio;
+	int			*debug_mask;
+	enum smb_mode		mode;
+	bool			external_vconn;
+	struct smb_chg_freq	chg_freq;
+	int			smb_version;
+
+	/* locks */
+	struct mutex		write_lock;
+	struct mutex		ps_change_lock;
+	struct mutex		otg_oc_lock;
+
+	/* power supplies */
+	struct power_supply		*batt_psy;
+	struct power_supply		*usb_psy;
+	struct power_supply		*dc_psy;
+	struct power_supply		*bms_psy;
+	struct power_supply_desc	usb_psy_desc;
+	struct power_supply		*usb_main_psy;
+
+	/* notifiers */
+	struct notifier_block	nb;
+
+	/* parallel charging */
+	struct parallel_params	pl;
+
+	/* regulators */
+	struct smb_regulator	*vbus_vreg;
+	struct smb_regulator	*vconn_vreg;
+	struct regulator	*dpdm_reg;
+
+	/* votables */
+	struct votable		*dc_suspend_votable;
+	struct votable		*fcc_votable;
+	struct votable		*fv_votable;
+	struct votable		*usb_icl_votable;
+	struct votable		*dc_icl_votable;
+	struct votable		*pd_disallowed_votable_indirect;
+	struct votable		*pd_allowed_votable;
+	struct votable		*awake_votable;
+	struct votable		*pl_disable_votable;
+	struct votable		*chg_disable_votable;
+	struct votable		*pl_enable_votable_indirect;
+	struct votable		*hvdcp_disable_votable_indirect;
+	struct votable		*hvdcp_enable_votable;
+	struct votable		*apsd_disable_votable;
+	struct votable		*hvdcp_hw_inov_dis_votable;
+
+	/* work */
+	struct work_struct	bms_update_work;
+	struct work_struct	rdstd_cc2_detach_work;
+	struct delayed_work	hvdcp_detect_work;
+	struct delayed_work	ps_change_timeout_work;
+	struct delayed_work	step_soc_req_work;
+	struct delayed_work	clear_hdc_work;
+	struct work_struct	otg_oc_work;
+	struct work_struct	vconn_oc_work;
+	struct delayed_work	otg_ss_done_work;
+	struct delayed_work	icl_change_work;
+
+	/* cached status */
+	int			voltage_min_uv;
+	int			voltage_max_uv;
+	int			pd_active;
+	bool			system_suspend_supported;
+	int			boost_threshold_ua;
+	int			system_temp_level;
+	int			thermal_levels;
+	int			*thermal_mitigation;
+	int			dcp_icl_ua;
+	int			fake_capacity;
+	bool			step_chg_enabled;
+	bool			is_hdc;
+	bool			chg_done;
+	bool			micro_usb_mode;
+	bool			otg_en;
+	bool			vconn_en;
+	bool			suspend_input_on_debug_batt;
+	int			otg_attempts;
+	int			vconn_attempts;
+	int			default_icl_ua;
+
+	/* workaround flag */
+	u32			wa_flags;
+	enum cc2_sink_type	cc2_sink_detach_flag;
+	int			boost_current_ua;
+
+	/* extcon for VBUS / ID notification to USB for uUSB */
+	struct extcon_dev	*extcon;
+	bool			usb_ever_removed;
+
+	int			icl_reduction_ua;
+
+	/* qnovo */
+	int			qnovo_fcc_ua;
+	int			qnovo_fv_uv;
+	int			usb_icl_delta_ua;
+	int			pulse_cnt;
+};
+
+int smblib_read(struct smb_charger *chg, u16 addr, u8 *val);
+int smblib_masked_write(struct smb_charger *chg, u16 addr, u8 mask, u8 val);
+int smblib_write(struct smb_charger *chg, u16 addr, u8 val);
+
+int smblib_get_charge_param(struct smb_charger *chg,
+			    struct smb_chg_param *param, int *val_u);
+int smblib_get_usb_suspend(struct smb_charger *chg, int *suspend);
+
+int smblib_enable_charging(struct smb_charger *chg, bool enable);
+int smblib_set_charge_param(struct smb_charger *chg,
+			    struct smb_chg_param *param, int val_u);
+int smblib_set_usb_suspend(struct smb_charger *chg, bool suspend);
+int smblib_set_dc_suspend(struct smb_charger *chg, bool suspend);
+
+int smblib_mapping_soc_from_field_value(struct smb_chg_param *param,
+					     int val_u, u8 *val_raw);
+int smblib_mapping_cc_delta_to_field_value(struct smb_chg_param *param,
+					   u8 val_raw);
+int smblib_mapping_cc_delta_from_field_value(struct smb_chg_param *param,
+					     int val_u, u8 *val_raw);
+int smblib_set_chg_freq(struct smb_chg_param *param,
+				int val_u, u8 *val_raw);
+
+int smblib_vbus_regulator_enable(struct regulator_dev *rdev);
+int smblib_vbus_regulator_disable(struct regulator_dev *rdev);
+int smblib_vbus_regulator_is_enabled(struct regulator_dev *rdev);
+
+int smblib_vconn_regulator_enable(struct regulator_dev *rdev);
+int smblib_vconn_regulator_disable(struct regulator_dev *rdev);
+int smblib_vconn_regulator_is_enabled(struct regulator_dev *rdev);
+
+irqreturn_t smblib_handle_debug(int irq, void *data);
+irqreturn_t smblib_handle_otg_overcurrent(int irq, void *data);
+irqreturn_t smblib_handle_chg_state_change(int irq, void *data);
+irqreturn_t smblib_handle_step_chg_state_change(int irq, void *data);
+irqreturn_t smblib_handle_step_chg_soc_update_fail(int irq, void *data);
+irqreturn_t smblib_handle_step_chg_soc_update_request(int irq, void *data);
+irqreturn_t smblib_handle_batt_temp_changed(int irq, void *data);
+irqreturn_t smblib_handle_batt_psy_changed(int irq, void *data);
+irqreturn_t smblib_handle_usb_psy_changed(int irq, void *data);
+irqreturn_t smblib_handle_usbin_uv(int irq, void *data);
+irqreturn_t smblib_handle_usb_plugin(int irq, void *data);
+irqreturn_t smblib_handle_usb_source_change(int irq, void *data);
+irqreturn_t smblib_handle_icl_change(int irq, void *data);
+irqreturn_t smblib_handle_usb_typec_change(int irq, void *data);
+irqreturn_t smblib_handle_dc_plugin(int irq, void *data);
+irqreturn_t smblib_handle_high_duty_cycle(int irq, void *data);
+irqreturn_t smblib_handle_switcher_power_ok(int irq, void *data);
+irqreturn_t smblib_handle_wdog_bark(int irq, void *data);
+
+int smblib_get_prop_input_suspend(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_present(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_capacity(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_status(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_charge_type(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_charge_done(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_health(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_system_temp_level(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_input_current_limited(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_voltage_now(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_current_now(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_batt_temp(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_step_chg_step(struct smb_charger *chg,
+				union power_supply_propval *val);
+
+int smblib_set_prop_input_suspend(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_batt_capacity(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_system_temp_level(struct smb_charger *chg,
+				const union power_supply_propval *val);
+
+int smblib_get_prop_dc_present(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_dc_online(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_dc_current_max(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_set_prop_dc_current_max(struct smb_charger *chg,
+				const union power_supply_propval *val);
+
+int smblib_get_prop_usb_present(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_usb_online(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_usb_suspend(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_usb_voltage_now(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_pd_current_max(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_usb_current_max(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_usb_current_now(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_typec_cc_orientation(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_typec_mode(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_typec_power_role(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_pd_allowed(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_input_current_settled(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_input_voltage_settled(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_pd_in_hard_reset(struct smb_charger *chg,
+			       union power_supply_propval *val);
+int smblib_get_pe_start(struct smb_charger *chg,
+			       union power_supply_propval *val);
+int smblib_get_prop_charger_temp(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_charger_temp_max(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_get_prop_die_health(struct smb_charger *chg,
+			       union power_supply_propval *val);
+int smblib_set_prop_pd_current_max(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_usb_current_max(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_usb_voltage_min(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_usb_voltage_max(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_boost_current(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_typec_power_role(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_pd_active(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_set_prop_pd_in_hard_reset(struct smb_charger *chg,
+				const union power_supply_propval *val);
+int smblib_get_prop_slave_current_now(struct smb_charger *chg,
+				union power_supply_propval *val);
+int smblib_set_prop_ship_mode(struct smb_charger *chg,
+				const union power_supply_propval *val);
+void smblib_suspend_on_debug_battery(struct smb_charger *chg);
+int smblib_rerun_apsd_if_required(struct smb_charger *chg);
+int smblib_get_prop_fcc_delta(struct smb_charger *chg,
+			       union power_supply_propval *val);
+int smblib_icl_override(struct smb_charger *chg, bool override);
+int smblib_set_icl_reduction(struct smb_charger *chg, int reduction_ua);
+int smblib_dp_dm(struct smb_charger *chg, int val);
+int smblib_rerun_aicl(struct smb_charger *chg);
+
+int smblib_init(struct smb_charger *chg);
+int smblib_deinit(struct smb_charger *chg);
+#endif /* __SMB2_CHARGER_H */
diff --git a/drivers/power/supply/qcom/smb-reg.h b/drivers/power/supply/qcom/smb-reg.h
new file mode 100644
index 0000000..54b6b38
--- /dev/null
+++ b/drivers/power/supply/qcom/smb-reg.h
@@ -0,0 +1,1024 @@
+/* Copyright (c) 2016-2017 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 __SMB2_CHARGER_REG_H
+#define __SMB2_CHARGER_REG_H
+
+#include <linux/bitops.h>
+
+#define CHGR_BASE	0x1000
+#define OTG_BASE	0x1100
+#define BATIF_BASE	0x1200
+#define USBIN_BASE	0x1300
+#define DCIN_BASE	0x1400
+#define MISC_BASE	0x1600
+#define CHGR_FREQ_BASE	0x1900
+
+#define PERPH_TYPE_OFFSET		0x04
+#define TYPE_MASK			GENMASK(7, 0)
+#define PERPH_SUBTYPE_OFFSET		0x05
+#define SUBTYPE_MASK			GENMASK(7, 0)
+#define INT_RT_STS_OFFSET		0x10
+
+/* CHGR Peripheral Registers */
+#define BATTERY_CHARGER_STATUS_1_REG	(CHGR_BASE + 0x06)
+#define BVR_INITIAL_RAMP_BIT		BIT(7)
+#define CC_SOFT_TERMINATE_BIT		BIT(6)
+#define STEP_CHARGING_STATUS_SHIFT	3
+#define STEP_CHARGING_STATUS_MASK	GENMASK(5, 3)
+#define BATTERY_CHARGER_STATUS_MASK	GENMASK(2, 0)
+enum {
+	TRICKLE_CHARGE = 0,
+	PRE_CHARGE,
+	FAST_CHARGE,
+	FULLON_CHARGE,
+	TAPER_CHARGE,
+	TERMINATE_CHARGE,
+	INHIBIT_CHARGE,
+	DISABLE_CHARGE,
+};
+
+#define BATTERY_CHARGER_STATUS_2_REG			(CHGR_BASE + 0x07)
+#define INPUT_CURRENT_LIMITED_BIT			BIT(7)
+#define CHARGER_ERROR_STATUS_SFT_EXPIRE_BIT		BIT(6)
+#define CHARGER_ERROR_STATUS_BAT_OV_BIT			BIT(5)
+#define CHARGER_ERROR_STATUS_BAT_TERM_MISSING_BIT	BIT(4)
+#define BAT_TEMP_STATUS_MASK				GENMASK(3, 0)
+#define BAT_TEMP_STATUS_SOFT_LIMIT_MASK			GENMASK(3, 2)
+#define BAT_TEMP_STATUS_HOT_SOFT_LIMIT_BIT		BIT(3)
+#define BAT_TEMP_STATUS_COLD_SOFT_LIMIT_BIT		BIT(2)
+#define BAT_TEMP_STATUS_TOO_HOT_BIT			BIT(1)
+#define BAT_TEMP_STATUS_TOO_COLD_BIT			BIT(0)
+
+#define CHG_OPTION_REG					(CHGR_BASE + 0x08)
+#define PIN_BIT						BIT(7)
+
+#define BATTERY_CHARGER_STATUS_3_REG			(CHGR_BASE + 0x09)
+#define FV_POST_JEITA_MASK				GENMASK(7, 0)
+
+#define BATTERY_CHARGER_STATUS_4_REG			(CHGR_BASE + 0x0A)
+#define CHARGE_CURRENT_POST_JEITA_MASK			GENMASK(7, 0)
+
+#define BATTERY_CHARGER_STATUS_5_REG			(CHGR_BASE + 0x0B)
+#define VALID_INPUT_POWER_SOURCE_BIT			BIT(7)
+#define DISABLE_CHARGING_BIT				BIT(6)
+#define FORCE_ZERO_CHARGE_CURRENT_BIT			BIT(5)
+#define CHARGING_ENABLE_BIT				BIT(4)
+#define TAPER_BIT					BIT(3)
+#define ENABLE_CHG_SENSORS_BIT				BIT(2)
+#define ENABLE_TAPER_SENSOR_BIT				BIT(1)
+#define TAPER_REGION_BIT				BIT(0)
+
+#define BATTERY_CHARGER_STATUS_6_REG			(CHGR_BASE + 0x0C)
+#define GF_BATT_OV_BIT					BIT(7)
+#define DROP_IN_BATTERY_VOLTAGE_REFERENCE_BIT		BIT(6)
+#define VBATT_LTET_RECHARGE_BIT				BIT(5)
+#define VBATT_GTET_INHIBIT_BIT				BIT(4)
+#define VBATT_GTET_FLOAT_VOLTAGE_BIT			BIT(3)
+#define BATT_GT_PRE_TO_FAST_BIT				BIT(2)
+#define BATT_GT_FULL_ON_BIT				BIT(1)
+#define VBATT_LT_2V_BIT					BIT(0)
+
+#define BATTERY_CHARGER_STATUS_7_REG			(CHGR_BASE + 0x0D)
+#define ENABLE_TRICKLE_BIT				BIT(7)
+#define ENABLE_PRE_CHARGING_BIT				BIT(6)
+#define ENABLE_FAST_CHARGING_BIT			BIT(5)
+#define ENABLE_FULLON_MODE_BIT				BIT(4)
+#define TOO_COLD_ADC_BIT				BIT(3)
+#define TOO_HOT_ADC_BIT					BIT(2)
+#define HOT_SL_ADC_BIT					BIT(1)
+#define COLD_SL_ADC_BIT					BIT(0)
+
+#define BATTERY_CHARGER_STATUS_8_REG			(CHGR_BASE + 0x0E)
+#define PRE_FAST_BIT					BIT(7)
+#define PRE_FULLON_BIT					BIT(6)
+#define PRE_RCHG_BIT					BIT(5)
+#define PRE_INHIBIT_BIT					BIT(4)
+#define PRE_OVRV_BIT					BIT(3)
+#define PRE_TERM_BIT					BIT(2)
+#define BAT_ID_BMISS_CMP_BIT				BIT(1)
+#define THERM_CMP_BIT					BIT(0)
+
+/* CHGR Interrupt Bits */
+#define CHGR_7_RT_STS_BIT				BIT(7)
+#define CHGR_6_RT_STS_BIT				BIT(6)
+#define FG_FVCAL_QUALIFIED_RT_STS_BIT			BIT(5)
+#define STEP_CHARGING_SOC_UPDATE_REQUEST_RT_STS_BIT	BIT(4)
+#define STEP_CHARGING_SOC_UPDATE_FAIL_RT_STS_BIT	BIT(3)
+#define STEP_CHARGING_STATE_CHANGE_RT_STS_BIT		BIT(2)
+#define CHARGING_STATE_CHANGE_RT_STS_BIT		BIT(1)
+#define CHGR_ERROR_RT_STS_BIT				BIT(0)
+
+#define STEP_CHG_SOC_VBATT_V_REG			(CHGR_BASE + 0x40)
+#define STEP_CHG_SOC_VBATT_V_MASK			GENMASK(7, 0)
+
+#define STEP_CHG_SOC_VBATT_V_UPDATE_REG			(CHGR_BASE + 0x41)
+#define STEP_CHG_SOC_VBATT_V_UPDATE_BIT			BIT(0)
+
+#define CHARGING_ENABLE_CMD_REG				(CHGR_BASE + 0x42)
+#define CHARGING_ENABLE_CMD_BIT				BIT(0)
+
+#define ALLOW_FAST_CHARGING_CMD_REG			(CHGR_BASE + 0x43)
+#define ALLOW_FAST_CHARGING_CMD_BIT			BIT(0)
+
+#define QNOVO_PT_ENABLE_CMD_REG				(CHGR_BASE + 0x44)
+#define QNOVO_PT_ENABLE_CMD_BIT				BIT(0)
+
+#define CHGR_CFG1_REG					(CHGR_BASE + 0x50)
+#define INCREASE_RCHG_TIMEOUT_CFG_BIT			BIT(1)
+#define LOAD_BAT_BIT					BIT(0)
+
+#define CHGR_CFG2_REG					(CHGR_BASE + 0x51)
+#define CHG_EN_SRC_BIT					BIT(7)
+#define CHG_EN_POLARITY_BIT				BIT(6)
+#define PRETOFAST_TRANSITION_CFG_BIT			BIT(5)
+#define BAT_OV_ECC_BIT					BIT(4)
+#define I_TERM_BIT					BIT(3)
+#define AUTO_RECHG_BIT					BIT(2)
+#define EN_ANALOG_DROP_IN_VBATT_BIT			BIT(1)
+#define CHARGER_INHIBIT_BIT				BIT(0)
+
+#define CHARGER_ENABLE_CFG_REG				(CHGR_BASE + 0x52)
+#define CHG_ENB_TIMEOUT_SETTING_BIT			BIT(1)
+#define FORCE_ZERO_CFG_BIT				BIT(0)
+
+#define CFG_REG						(CHGR_BASE + 0x53)
+#define CHG_OPTION_PIN_TRIM_BIT				BIT(7)
+#define BATN_SNS_CFG_BIT				BIT(4)
+#define CFG_TAPER_DIS_AFVC_BIT				BIT(3)
+#define BATFET_SHUTDOWN_CFG_BIT				BIT(2)
+#define VDISCHG_EN_CFG_BIT				BIT(1)
+#define VCHG_EN_CFG_BIT					BIT(0)
+
+#define CHARGER_SPARE_REG				(CHGR_BASE + 0x54)
+#define CHARGER_SPARE_MASK				GENMASK(5, 0)
+
+#define PRE_CHARGE_CURRENT_CFG_REG			(CHGR_BASE + 0x60)
+#define PRE_CHARGE_CURRENT_SETTING_MASK			GENMASK(5, 0)
+
+#define FAST_CHARGE_CURRENT_CFG_REG			(CHGR_BASE + 0x61)
+#define FAST_CHARGE_CURRENT_SETTING_MASK		GENMASK(7, 0)
+
+#define CHARGE_CURRENT_TERMINATION_CFG_REG		(CHGR_BASE + 0x62)
+#define ANALOG_CHARGE_CURRENT_TERMINATION_SETTING_MASK	GENMASK(2, 0)
+
+#define TCCC_CHARGE_CURRENT_TERMINATION_CFG_REG		(CHGR_BASE + 0x63)
+#define TCCC_CHARGE_CURRENT_TERMINATION_SETTING_MASK	GENMASK(3, 0)
+
+#define CHARGE_CURRENT_SOFTSTART_SETTING_CFG_REG	(CHGR_BASE + 0x64)
+#define CHARGE_CURRENT_SOFTSTART_SETTING_MASK		GENMASK(1, 0)
+
+#define FLOAT_VOLTAGE_CFG_REG				(CHGR_BASE + 0x70)
+#define FLOAT_VOLTAGE_SETTING_MASK			GENMASK(7, 0)
+
+#define AUTO_FLOAT_VOLTAGE_COMPENSATION_CFG_REG		(CHGR_BASE + 0x71)
+#define AUTO_FLOAT_VOLTAGE_COMPENSATION_MASK		GENMASK(2, 0)
+
+#define CHARGE_INHIBIT_THRESHOLD_CFG_REG		(CHGR_BASE + 0x72)
+#define CHARGE_INHIBIT_THRESHOLD_MASK			GENMASK(1, 0)
+#define CHARGE_INHIBIT_THRESHOLD_50MV			0
+#define CHARGE_INHIBIT_THRESHOLD_100MV			1
+#define CHARGE_INHIBIT_THRESHOLD_200MV			2
+#define CHARGE_INHIBIT_THRESHOLD_300MV			3
+
+#define RECHARGE_THRESHOLD_CFG_REG			(CHGR_BASE + 0x73)
+#define RECHARGE_THRESHOLD_MASK				GENMASK(1, 0)
+
+#define PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG		(CHGR_BASE + 0x74)
+#define PRE_TO_FAST_CHARGE_THRESHOLD_MASK		GENMASK(1, 0)
+
+#define FV_HYSTERESIS_CFG_REG				(CHGR_BASE + 0x75)
+#define FV_DROP_HYSTERESIS_CFG_MASK			GENMASK(7, 4)
+#define THRESH_HYSTERESIS_CFG_MASK			GENMASK(3, 0)
+
+#define FVC_CHARGE_INHIBIT_THRESHOLD_CFG_REG		(CHGR_BASE + 0x80)
+#define FVC_CHARGE_INHIBIT_THRESHOLD_MASK		GENMASK(5, 0)
+
+#define FVC_RECHARGE_THRESHOLD_CFG_REG			(CHGR_BASE + 0x81)
+#define FVC_RECHARGE_THRESHOLD_MASK			GENMASK(7, 0)
+
+#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_CFG_REG	(CHGR_BASE + 0x82)
+#define FVC_PRE_TO_FAST_CHARGE_THRESHOLD_MASK		GENMASK(7, 0)
+
+#define FVC_FULL_ON_THRESHOLD_CFG_REG			(CHGR_BASE + 0x83)
+#define FVC_FULL_ON_THRESHOLD_MASK			GENMASK(7, 0)
+
+#define FVC_CC_MODE_GLITCH_FILTER_SEL_CFG_REG		(CHGR_BASE + 0x84)
+#define FVC_CC_MODE_GLITCH_FILTER_SEL_MASK		GENMASK(1, 0)
+
+#define FVC_TERMINATION_GLITCH_FILTER_SEL_CFG_REG	(CHGR_BASE + 0x85)
+#define FVC_TERMINATION_GLITCH_FILTER_SEL_MASK		GENMASK(1, 0)
+
+#define JEITA_EN_CFG_REG		(CHGR_BASE + 0x90)
+#define JEITA_EN_HARDLIMIT_BIT		BIT(4)
+#define JEITA_EN_HOT_SL_FCV_BIT		BIT(3)
+#define JEITA_EN_COLD_SL_FCV_BIT	BIT(2)
+#define JEITA_EN_HOT_SL_CCC_BIT		BIT(1)
+#define JEITA_EN_COLD_SL_CCC_BIT	BIT(0)
+
+#define JEITA_FVCOMP_CFG_REG		(CHGR_BASE + 0x91)
+#define JEITA_FVCOMP_MASK		GENMASK(7, 0)
+
+#define JEITA_CCCOMP_CFG_REG		(CHGR_BASE + 0x92)
+#define JEITA_CCCOMP_MASK		GENMASK(7, 0)
+
+#define FV_CAL_CFG_REG			(CHGR_BASE + 0x76)
+#define FV_CALIBRATION_CFG_MASK		GENMASK(2, 0)
+
+#define FV_ADJUST_REG			(CHGR_BASE + 0x77)
+#define FLOAT_VOLTAGE_ADJUSTMENT_MASK	GENMASK(4, 0)
+
+#define FG_VADC_DISQ_THRESH_REG		(CHGR_BASE + 0x78)
+#define VADC_DISQUAL_THRESH_MASK	GENMASK(7, 0)
+
+#define FG_IADC_DISQ_THRESH_REG		(CHGR_BASE + 0x79)
+#define IADC_DISQUAL_THRESH_MASK	GENMASK(7, 0)
+
+#define FG_UPDATE_CFG_1_REG	(CHGR_BASE + 0x7A)
+#define BT_TMPR_TCOLD_BIT	BIT(7)
+#define BT_TMPR_COLD_BIT	BIT(6)
+#define BT_TMPR_HOT_BIT		BIT(5)
+#define BT_TMPR_THOT_BIT	BIT(4)
+#define CHG_DIE_TMPR_HOT_BIT	BIT(3)
+#define CHG_DIE_TMPR_THOT_BIT	BIT(2)
+#define SKIN_TMPR_HOT_BIT	BIT(1)
+#define SKIN_TMPR_THOT_BIT	BIT(0)
+
+#define FG_UPDATE_CFG_1_SEL_REG		(CHGR_BASE + 0x7B)
+#define BT_TMPR_TCOLD_SEL_BIT		BIT(7)
+#define BT_TMPR_COLD_SEL_BIT		BIT(6)
+#define BT_TMPR_HOT_SEL_BIT		BIT(5)
+#define BT_TMPR_THOT_SEL_BIT		BIT(4)
+#define CHG_DIE_TMPR_HOT_SEL_BIT	BIT(3)
+#define CHG_DIE_TMPR_THOT_SEL_BIT	BIT(2)
+#define SKIN_TMPR_HOT_SEL_BIT		BIT(1)
+#define SKIN_TMPR_THOT_SEL_BIT		BIT(0)
+
+#define FG_UPDATE_CFG_2_REG		(CHGR_BASE + 0x7C)
+#define SOC_LT_OTG_THRESH_BIT		BIT(3)
+#define SOC_LT_CHG_RECHARGE_THRESH_BIT	BIT(2)
+#define VBT_LT_CHG_RECHARGE_THRESH_BIT	BIT(1)
+#define IBT_LT_CHG_TERM_THRESH_BIT	BIT(0)
+
+#define FG_UPDATE_CFG_2_SEL_REG			(CHGR_BASE + 0x7D)
+#define SOC_LT_OTG_THRESH_SEL_BIT		BIT(3)
+#define SOC_LT_CHG_RECHARGE_THRESH_SEL_BIT	BIT(2)
+#define VBT_LT_CHG_RECHARGE_THRESH_SEL_BIT	BIT(1)
+#define IBT_LT_CHG_TERM_THRESH_SEL_BIT		BIT(0)
+
+#define FG_CHG_INTERFACE_CFG_REG	(CHGR_BASE + 0x7E)
+#define ESR_ISINK_CFG_MASK		GENMASK(7, 6)
+#define ESR_FASTCHG_DECR_CFG_MASK	GENMASK(5, 4)
+#define FG_CHARGER_INHIBIT_BIT		BIT(3)
+#define FG_BATFET_BIT			BIT(2)
+#define IADC_SYNC_CNV_BIT		BIT(1)
+#define VADC_SYNC_CNV_BIT		BIT(0)
+
+#define FG_CHG_INTERFACE_CFG_SEL_REG	(CHGR_BASE + 0x7F)
+#define ESR_ISINK_CFG_SEL_BIT		BIT(5)
+#define ESR_FASTCHG_DECR_CFG_SEL_BIT	BIT(4)
+#define FG_CHARGER_INHIBIT_SEL_BIT	BIT(3)
+#define FG_BATFET_SEL_BIT		BIT(2)
+#define IADC_SYNC_CNV_SEL_BIT		BIT(1)
+#define VADC_SYNC_CNV_SEL_BIT		BIT(0)
+
+#define CHGR_STEP_CHG_MODE_CFG_REG		(CHGR_BASE + 0xB0)
+#define STEP_CHARGING_SOC_FAIL_OPTION_BIT	BIT(3)
+#define STEP_CHARGING_MODE_SELECT_BIT		BIT(2)
+#define STEP_CHARGING_SOURCE_SELECT_BIT		BIT(1)
+#define STEP_CHARGING_ENABLE_BIT		BIT(0)
+
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_REG		(CHGR_BASE + 0xB1)
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_CFG_MASK	GENMASK(0, 1)
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_5S		0
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_10S		1
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_20S		2
+#define STEP_CHG_UPDATE_REQUEST_TIMEOUT_40S		3
+
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_REG		(CHGR_BASE + 0xB2)
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_CFG_MASK		GENMASK(0, 1)
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_10S		0
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_30S		1
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_60S		2
+#define STEP_CHG_UPDATE_FAIL_TIMEOUT_120S		3
+
+#define STEP_CHG_SOC_OR_BATT_V_TH1_REG	(CHGR_BASE + 0xB3)
+#define STEP_CHG_SOC_OR_BATT_V_TH2_REG	(CHGR_BASE + 0xB4)
+#define STEP_CHG_SOC_OR_BATT_V_TH3_REG	(CHGR_BASE + 0xB5)
+#define STEP_CHG_SOC_OR_BATT_V_TH4_REG	(CHGR_BASE + 0xB6)
+#define STEP_CHG_CURRENT_DELTA1_REG	(CHGR_BASE + 0xB7)
+#define STEP_CHG_CURRENT_DELTA2_REG	(CHGR_BASE + 0xB8)
+#define STEP_CHG_CURRENT_DELTA3_REG	(CHGR_BASE + 0xB9)
+#define STEP_CHG_CURRENT_DELTA4_REG	(CHGR_BASE + 0xBA)
+#define STEP_CHG_CURRENT_DELTA5_REG	(CHGR_BASE + 0xBB)
+
+/* OTG Peripheral Registers */
+#define RID_CC_CONTROL_23_16_REG	(OTG_BASE + 0x06)
+#define RID_CC_CONTROL_23_BIT		BIT(7)
+#define VCONN_SOFTSTART_EN_BIT		BIT(6)
+#define VCONN_SFTST_CFG_MASK		GENMASK(5, 4)
+#define CONNECT_RIDCC_SENSOR_TO_CC_MASK	GENMASK(3, 2)
+#define EN_CC_1P1CLAMP_BIT		BIT(1)
+#define ENABLE_CRUDESEN_CC_1_BIT	BIT(0)
+
+#define RID_CC_CONTROL_15_8_REG		(OTG_BASE + 0x07)
+#define ENABLE_CRUDESEN_CC_0_BIT	BIT(7)
+#define EN_FMB_2P5UA_CC_MASK		GENMASK(6, 5)
+#define EN_ISRC_180UA_BIT		BIT(4)
+#define ENABLE_CURRENTSOURCE_CC_MASK	GENMASK(3, 2)
+#define EN_BANDGAP_RID_C_DET_BIT	BIT(1)
+#define ENABLE_RD_CC_1_BIT		BIT(0)
+
+#define RID_CC_CONTROL_7_0_REG		(OTG_BASE + 0x08)
+#define ENABLE_RD_CC_0_BIT		BIT(7)
+#define VCONN_ILIM500MA_BIT		BIT(6)
+#define EN_MICRO_USB_MODE_BIT		BIT(5)
+#define UFP_DFP_MODE_BIT		BIT(4)
+#define VCONN_EN_CC_MASK		GENMASK(3, 2)
+#define VREF_SEL_RIDCC_SENSOR_MASK	GENMASK(1, 0)
+
+#define OTG_STATUS_REG			(OTG_BASE + 0x09)
+#define BOOST_SOFTSTART_DONE_BIT	BIT(3)
+#define OTG_STATE_MASK			GENMASK(2, 0)
+#define OTG_STATE_ENABLED		0x2
+
+/* OTG Interrupt Bits */
+#define TESTMODE_CHANGE_DETECT_RT_STS_BIT	BIT(3)
+#define OTG_OC_DIS_SW_STS_RT_STS_BIT		BIT(2)
+#define OTG_OVERCURRENT_RT_STS_BIT		BIT(1)
+#define OTG_FAIL_RT_STS_BIT			BIT(0)
+
+#define CMD_OTG_REG			(OTG_BASE + 0x40)
+#define OTG_EN_BIT			BIT(0)
+
+#define BAT_UVLO_THRESHOLD_CFG_REG	(OTG_BASE + 0x51)
+#define BAT_UVLO_THRESHOLD_MASK		GENMASK(1, 0)
+
+#define OTG_CURRENT_LIMIT_CFG_REG	(OTG_BASE + 0x52)
+#define OTG_CURRENT_LIMIT_MASK		GENMASK(2, 0)
+
+#define OTG_CFG_REG			(OTG_BASE + 0x53)
+#define OTG_RESERVED_MASK		GENMASK(7, 6)
+#define DIS_OTG_ON_TLIM_BIT		BIT(5)
+#define QUICKSTART_OTG_FASTROLESWAP_BIT	BIT(4)
+#define INCREASE_DFP_TIME_BIT		BIT(3)
+#define ENABLE_OTG_IN_DEBUG_MODE_BIT	BIT(2)
+#define OTG_EN_SRC_CFG_BIT		BIT(1)
+#define CONCURRENT_MODE_CFG_BIT		BIT(0)
+
+#define OTG_ENG_OTG_CFG_REG		(OTG_BASE + 0xC0)
+#define ENG_BUCKBOOST_HALT1_8_MODE_BIT	BIT(0)
+
+/* BATIF Peripheral Registers */
+/* BATIF Interrupt Bits */
+#define BAT_7_RT_STS_BIT			BIT(7)
+#define BAT_6_RT_STS_BIT			BIT(6)
+#define BAT_TERMINAL_MISSING_RT_STS_BIT		BIT(5)
+#define BAT_THERM_OR_ID_MISSING_RT_STS_BIT	BIT(4)
+#define BAT_LOW_RT_STS_BIT			BIT(3)
+#define BAT_OV_RT_STS_BIT			BIT(2)
+#define BAT_OCP_RT_STS_BIT			BIT(1)
+#define BAT_TEMP_RT_STS_BIT			BIT(0)
+
+#define SHIP_MODE_REG			(BATIF_BASE + 0x40)
+#define SHIP_MODE_EN_BIT		BIT(0)
+
+#define BATOCP_THRESHOLD_CFG_REG	(BATIF_BASE + 0x50)
+#define BATOCP_ENABLE_CFG_BIT		BIT(3)
+#define BATOCP_THRESHOLD_MASK		GENMASK(2, 0)
+
+#define BATOCP_INTRPT_DELAY_TMR_CFG_REG	(BATIF_BASE + 0x51)
+#define BATOCP_INTRPT_TIMEOUT_MASK	GENMASK(5, 3)
+#define BATOCP_DELAY_TIMEOUT_MASK	GENMASK(2, 0)
+
+#define BATOCP_RESET_TMR_CFG_REG	(BATIF_BASE + 0x52)
+#define EN_BATOCP_RESET_TMR_BIT		BIT(3)
+#define BATOCP_RESET_TIMEOUT_MASK	GENMASK(2, 0)
+
+#define LOW_BATT_DETECT_EN_CFG_REG	(BATIF_BASE + 0x60)
+#define LOW_BATT_DETECT_EN_BIT		BIT(0)
+
+#define LOW_BATT_THRESHOLD_CFG_REG	(BATIF_BASE + 0x61)
+#define LOW_BATT_THRESHOLD_MASK		GENMASK(3, 0)
+
+#define BAT_FET_CFG_REG			(BATIF_BASE + 0x62)
+#define BAT_FET_CFG_BIT			BIT(0)
+
+#define BAT_MISS_SRC_CFG_REG		(BATIF_BASE + 0x70)
+#define BAT_MISS_ALG_EN_BIT		BIT(2)
+#define BAT_MISS_RESERVED_BIT		BIT(1)
+#define BAT_MISS_PIN_SRC_EN_BIT		BIT(0)
+
+#define BAT_MISS_ALG_OPTIONS_CFG_REG	(BATIF_BASE + 0x71)
+#define BAT_MISS_INPUT_PLUGIN_BIT	BIT(2)
+#define BAT_MISS_TMR_START_OPTION_BIT	BIT(1)
+#define BAT_MISS_POLL_EN_BIT		BIT(0)
+
+#define BAT_MISS_PIN_GF_CFG_REG		(BATIF_BASE + 0x72)
+#define BAT_MISS_PIN_GF_MASK		GENMASK(1, 0)
+
+/* USBIN Peripheral Registers */
+#define USBIN_INPUT_STATUS_REG		(USBIN_BASE + 0x06)
+#define USBIN_INPUT_STATUS_7_BIT	BIT(7)
+#define USBIN_INPUT_STATUS_6_BIT	BIT(6)
+#define USBIN_12V_BIT			BIT(5)
+#define USBIN_9V_TO_12V_BIT		BIT(4)
+#define USBIN_9V_BIT			BIT(3)
+#define USBIN_5V_TO_12V_BIT		BIT(2)
+#define USBIN_5V_TO_9V_BIT		BIT(1)
+#define USBIN_5V_BIT			BIT(0)
+#define QC_2P0_STATUS_MASK		GENMASK(2, 0)
+
+#define APSD_STATUS_REG			(USBIN_BASE + 0x07)
+#define APSD_STATUS_7_BIT		BIT(7)
+#define HVDCP_CHECK_TIMEOUT_BIT		BIT(6)
+#define SLOW_PLUGIN_TIMEOUT_BIT		BIT(5)
+#define ENUMERATION_DONE_BIT		BIT(4)
+#define VADP_CHANGE_DONE_AFTER_AUTH_BIT	BIT(3)
+#define QC_AUTH_DONE_STATUS_BIT		BIT(2)
+#define QC_CHARGER_BIT			BIT(1)
+#define APSD_DTC_STATUS_DONE_BIT	BIT(0)
+
+#define APSD_RESULT_STATUS_REG		(USBIN_BASE + 0x08)
+#define ICL_OVERRIDE_LATCH_BIT		BIT(7)
+#define APSD_RESULT_STATUS_MASK		GENMASK(6, 0)
+#define QC_3P0_BIT			BIT(6)
+#define QC_2P0_BIT			BIT(5)
+#define FLOAT_CHARGER_BIT		BIT(4)
+#define DCP_CHARGER_BIT			BIT(3)
+#define CDP_CHARGER_BIT			BIT(2)
+#define OCP_CHARGER_BIT			BIT(1)
+#define SDP_CHARGER_BIT			BIT(0)
+
+#define QC_CHANGE_STATUS_REG		(USBIN_BASE + 0x09)
+#define QC_CHANGE_STATUS_7_BIT		BIT(7)
+#define QC_CHANGE_STATUS_6_BIT		BIT(6)
+#define QC_9V_TO_12V_REASON_BIT		BIT(5)
+#define QC_5V_TO_9V_REASON_BIT		BIT(4)
+#define QC_CONTINUOUS_BIT		BIT(3)
+#define QC_12V_BIT			BIT(2)
+#define QC_9V_BIT			BIT(1)
+#define QC_5V_BIT			BIT(0)
+
+#define QC_PULSE_COUNT_STATUS_REG		(USBIN_BASE + 0x0A)
+#define QC_PULSE_COUNT_STATUS_7_BIT		BIT(7)
+#define QC_PULSE_COUNT_STATUS_6_BIT		BIT(6)
+#define QC_PULSE_COUNT_MASK			GENMASK(5, 0)
+
+#define TYPE_C_STATUS_1_REG			(USBIN_BASE + 0x0B)
+#define UFP_TYPEC_MASK				GENMASK(7, 5)
+#define UFP_TYPEC_RDSTD_BIT			BIT(7)
+#define UFP_TYPEC_RD1P5_BIT			BIT(6)
+#define UFP_TYPEC_RD3P0_BIT			BIT(5)
+#define UFP_TYPEC_FMB_255K_BIT			BIT(4)
+#define UFP_TYPEC_FMB_301K_BIT			BIT(3)
+#define UFP_TYPEC_FMB_523K_BIT			BIT(2)
+#define UFP_TYPEC_FMB_619K_BIT			BIT(1)
+#define UFP_TYPEC_OPEN_OPEN_BIT			BIT(0)
+
+#define TYPE_C_STATUS_2_REG			(USBIN_BASE + 0x0C)
+#define DFP_TYPEC_MASK				0x8F
+#define DFP_RA_OPEN_BIT				BIT(7)
+#define TIMER_STAGE_BIT				BIT(6)
+#define EXIT_UFP_MODE_BIT			BIT(5)
+#define EXIT_DFP_MODE_BIT			BIT(4)
+#define DFP_RD_OPEN_BIT				BIT(3)
+#define DFP_RD_RA_VCONN_BIT			BIT(2)
+#define DFP_RD_RD_BIT				BIT(1)
+#define DFP_RA_RA_BIT				BIT(0)
+
+#define TYPE_C_STATUS_3_REG			(USBIN_BASE + 0x0D)
+#define ENABLE_BANDGAP_BIT			BIT(7)
+#define U_USB_GND_NOVBUS_BIT			BIT(6)
+#define U_USB_FLOAT_NOVBUS_BIT			BIT(5)
+#define U_USB_GND_BIT				BIT(4)
+#define U_USB_FMB1_BIT				BIT(3)
+#define U_USB_FLOAT1_BIT			BIT(2)
+#define U_USB_FMB2_BIT				BIT(1)
+#define U_USB_FLOAT2_BIT			BIT(0)
+
+#define TYPE_C_STATUS_4_REG			(USBIN_BASE + 0x0E)
+#define UFP_DFP_MODE_STATUS_BIT			BIT(7)
+#define TYPEC_VBUS_STATUS_BIT			BIT(6)
+#define TYPEC_VBUS_ERROR_STATUS_BIT		BIT(5)
+#define TYPEC_DEBOUNCE_DONE_STATUS_BIT		BIT(4)
+#define TYPEC_UFP_AUDIO_ADAPT_STATUS_BIT	BIT(3)
+#define TYPEC_VCONN_OVERCURR_STATUS_BIT		BIT(2)
+#define CC_ORIENTATION_BIT			BIT(1)
+#define CC_ATTACHED_BIT				BIT(0)
+
+#define TYPE_C_STATUS_5_REG			(USBIN_BASE + 0x0F)
+#define TRY_SOURCE_FAILED_BIT			BIT(6)
+#define TRY_SINK_FAILED_BIT			BIT(5)
+#define TIMER_STAGE_2_BIT			BIT(4)
+#define TYPEC_LEGACY_CABLE_STATUS_BIT		BIT(3)
+#define TYPEC_NONCOMP_LEGACY_CABLE_STATUS_BIT	BIT(2)
+#define TYPEC_TRYSOURCE_DETECT_STATUS_BIT	BIT(1)
+#define TYPEC_TRYSINK_DETECT_STATUS_BIT		BIT(0)
+
+/* USBIN Interrupt Bits */
+#define TYPE_C_CHANGE_RT_STS_BIT		BIT(7)
+#define USBIN_ICL_CHANGE_RT_STS_BIT		BIT(6)
+#define USBIN_SOURCE_CHANGE_RT_STS_BIT		BIT(5)
+#define USBIN_PLUGIN_RT_STS_BIT			BIT(4)
+#define USBIN_OV_RT_STS_BIT			BIT(3)
+#define USBIN_UV_RT_STS_BIT			BIT(2)
+#define USBIN_LT_3P6V_RT_STS_BIT		BIT(1)
+#define USBIN_COLLAPSE_RT_STS_BIT		BIT(0)
+
+#define QC_PULSE_COUNT_STATUS_1_REG		(USBIN_BASE + 0x30)
+
+#define USBIN_CMD_IL_REG			(USBIN_BASE + 0x40)
+#define BAT_2_SYS_FET_DIS_BIT			BIT(1)
+#define USBIN_SUSPEND_BIT			BIT(0)
+
+#define CMD_APSD_REG				(USBIN_BASE + 0x41)
+#define ICL_OVERRIDE_BIT			BIT(1)
+#define APSD_RERUN_BIT				BIT(0)
+
+#define CMD_HVDCP_2_REG				(USBIN_BASE + 0x43)
+#define RESTART_AICL_BIT			BIT(7)
+#define TRIGGER_AICL_BIT			BIT(6)
+#define FORCE_12V_BIT				BIT(5)
+#define FORCE_9V_BIT				BIT(4)
+#define FORCE_5V_BIT				BIT(3)
+#define IDLE_BIT				BIT(2)
+#define SINGLE_DECREMENT_BIT			BIT(1)
+#define SINGLE_INCREMENT_BIT			BIT(0)
+
+#define USB_MISC2_REG				(USBIN_BASE + 0x57)
+#define USB_MISC2_MASK				GENMASK(1, 0)
+
+#define TYPE_C_CFG_REG				(USBIN_BASE + 0x58)
+#define APSD_START_ON_CC_BIT			BIT(7)
+#define WAIT_FOR_APSD_BIT			BIT(6)
+#define FACTORY_MODE_DETECTION_EN_BIT		BIT(5)
+#define FACTORY_MODE_ICL_3A_4A_BIT		BIT(4)
+#define FACTORY_MODE_DIS_CHGING_CFG_BIT		BIT(3)
+#define SUSPEND_NON_COMPLIANT_CFG_BIT		BIT(2)
+#define VCONN_OC_CFG_BIT			BIT(1)
+#define TYPE_C_OR_U_USB_BIT			BIT(0)
+
+#define TYPE_C_CFG_2_REG			(USBIN_BASE + 0x59)
+#define TYPE_C_DFP_CURRSRC_MODE_BIT		BIT(7)
+#define VCONN_ILIM500MA_CFG_BIT			BIT(6)
+#define VCONN_SOFTSTART_CFG_MASK		GENMASK(5, 4)
+#define EN_TRY_SOURCE_MODE_BIT			BIT(3)
+#define USB_FACTORY_MODE_ENABLE_BIT		BIT(2)
+#define TYPE_C_UFP_MODE_BIT			BIT(1)
+#define EN_80UA_180UA_CUR_SOURCE_BIT		BIT(0)
+
+#define TYPE_C_CFG_3_REG			(USBIN_BASE + 0x5A)
+#define TVBUS_DEBOUNCE_BIT			BIT(7)
+#define TYPEC_LEGACY_CABLE_INT_EN_BIT		BIT(6)
+#define TYPEC_NONCOMPLIANT_LEGACY_CABLE_INT_EN_BIT		BIT(5)
+#define TYPEC_TRYSOURCE_DETECT_INT_EN_BIT	BIT(4)
+#define TYPEC_TRYSINK_DETECT_INT_EN_BIT		BIT(3)
+#define EN_TRYSINK_MODE_BIT			BIT(2)
+#define EN_LEGACY_CABLE_DETECTION_BIT		BIT(1)
+#define ALLOW_PD_DRING_UFP_TCCDB_BIT		BIT(0)
+
+#define USBIN_ADAPTER_ALLOW_CFG_REG		(USBIN_BASE + 0x60)
+#define USBIN_ADAPTER_ALLOW_MASK		GENMASK(3, 0)
+enum {
+	USBIN_ADAPTER_ALLOW_5V		= 0,
+	USBIN_ADAPTER_ALLOW_9V		= 2,
+	USBIN_ADAPTER_ALLOW_5V_OR_9V	= 3,
+	USBIN_ADAPTER_ALLOW_12V		= 4,
+	USBIN_ADAPTER_ALLOW_5V_OR_12V	= 5,
+	USBIN_ADAPTER_ALLOW_9V_TO_12V	= 6,
+	USBIN_ADAPTER_ALLOW_5V_OR_9V_TO_12V = 7,
+	USBIN_ADAPTER_ALLOW_5V_TO_9V	= 8,
+	USBIN_ADAPTER_ALLOW_5V_TO_12V	= 12,
+};
+
+#define USBIN_OPTIONS_1_CFG_REG			(USBIN_BASE + 0x62)
+#define CABLE_R_SEL_BIT				BIT(7)
+#define HVDCP_AUTH_ALG_EN_CFG_BIT		BIT(6)
+#define HVDCP_AUTONOMOUS_MODE_EN_CFG_BIT	BIT(5)
+#define INPUT_PRIORITY_BIT			BIT(4)
+#define AUTO_SRC_DETECT_BIT			BIT(3)
+#define HVDCP_EN_BIT				BIT(2)
+#define VADP_INCREMENT_VOLTAGE_LIMIT_BIT	BIT(1)
+#define VADP_TAPER_TIMER_EN_BIT			BIT(0)
+
+#define USBIN_OPTIONS_2_CFG_REG			(USBIN_BASE + 0x63)
+#define WIPWR_RST_EUD_CFG_BIT			BIT(7)
+#define SWITCHER_START_CFG_BIT			BIT(6)
+#define DCD_TIMEOUT_SEL_BIT			BIT(5)
+#define OCD_CURRENT_SEL_BIT			BIT(4)
+#define SLOW_PLUGIN_TIMER_EN_CFG_BIT		BIT(3)
+#define FLOAT_OPTIONS_MASK			GENMASK(2, 0)
+#define FLOAT_DIS_CHGING_CFG_BIT		BIT(2)
+#define SUSPEND_FLOAT_CFG_BIT			BIT(1)
+#define FORCE_FLOAT_SDP_CFG_BIT			BIT(0)
+
+#define TAPER_TIMER_SEL_CFG_REG			(USBIN_BASE + 0x64)
+#define TYPEC_SPARE_CFG_BIT			BIT(7)
+#define TAPER_TIMER_SEL_MASK			GENMASK(1, 0)
+
+#define USBIN_LOAD_CFG_REG			(USBIN_BASE + 0x65)
+#define USBIN_OV_CH_LOAD_OPTION_BIT		BIT(7)
+
+#define USBIN_ICL_OPTIONS_REG			(USBIN_BASE + 0x66)
+#define CFG_USB3P0_SEL_BIT			BIT(2)
+#define USB51_MODE_BIT				BIT(1)
+#define USBIN_MODE_CHG_BIT			BIT(0)
+
+#define TYPE_C_INTRPT_ENB_REG			(USBIN_BASE + 0x67)
+#define TYPEC_CCOUT_DETACH_INT_EN_BIT		BIT(7)
+#define TYPEC_CCOUT_ATTACH_INT_EN_BIT		BIT(6)
+#define TYPEC_VBUS_ERROR_INT_EN_BIT		BIT(5)
+#define TYPEC_UFP_AUDIOADAPT_INT_EN_BIT		BIT(4)
+#define TYPEC_DEBOUNCE_DONE_INT_EN_BIT		BIT(3)
+#define TYPEC_CCSTATE_CHANGE_INT_EN_BIT		BIT(2)
+#define TYPEC_VBUS_DEASSERT_INT_EN_BIT		BIT(1)
+#define TYPEC_VBUS_ASSERT_INT_EN_BIT		BIT(0)
+
+#define TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG	(USBIN_BASE + 0x68)
+#define EXIT_SNK_BASED_ON_CC_BIT		BIT(7)
+#define VCONN_EN_ORIENTATION_BIT		BIT(6)
+#define TYPEC_VCONN_OVERCURR_INT_EN_BIT		BIT(5)
+#define VCONN_EN_SRC_BIT			BIT(4)
+#define VCONN_EN_VALUE_BIT			BIT(3)
+#define TYPEC_POWER_ROLE_CMD_MASK		GENMASK(2, 0)
+#define UFP_EN_CMD_BIT				BIT(2)
+#define DFP_EN_CMD_BIT				BIT(1)
+#define TYPEC_DISABLE_CMD_BIT			BIT(0)
+
+#define USBIN_SOURCE_CHANGE_INTRPT_ENB_REG	(USBIN_BASE + 0x69)
+#define SLOW_IRQ_EN_CFG_BIT			BIT(5)
+#define ENUMERATION_IRQ_EN_CFG_BIT		BIT(4)
+#define VADP_IRQ_EN_CFG_BIT			BIT(3)
+#define AUTH_IRQ_EN_CFG_BIT			BIT(2)
+#define HVDCP_IRQ_EN_CFG_BIT			BIT(1)
+#define APSD_IRQ_EN_CFG_BIT			BIT(0)
+
+#define USBIN_CURRENT_LIMIT_CFG_REG		(USBIN_BASE + 0x70)
+#define USBIN_CURRENT_LIMIT_MASK		GENMASK(7, 0)
+
+#define USBIN_AICL_OPTIONS_CFG_REG		(USBIN_BASE + 0x80)
+#define SUSPEND_ON_COLLAPSE_USBIN_BIT		BIT(7)
+#define USBIN_AICL_HDC_EN_BIT			BIT(6)
+#define USBIN_AICL_START_AT_MAX_BIT		BIT(5)
+#define USBIN_AICL_RERUN_EN_BIT			BIT(4)
+#define USBIN_AICL_ADC_EN_BIT			BIT(3)
+#define USBIN_AICL_EN_BIT			BIT(2)
+#define USBIN_HV_COLLAPSE_RESPONSE_BIT		BIT(1)
+#define USBIN_LV_COLLAPSE_RESPONSE_BIT		BIT(0)
+
+#define USBIN_5V_AICL_THRESHOLD_CFG_REG		(USBIN_BASE + 0x81)
+#define USBIN_5V_AICL_THRESHOLD_CFG_MASK	GENMASK(2, 0)
+
+#define USBIN_9V_AICL_THRESHOLD_CFG_REG		(USBIN_BASE + 0x82)
+#define USBIN_9V_AICL_THRESHOLD_CFG_MASK	GENMASK(2, 0)
+
+#define USBIN_12V_AICL_THRESHOLD_CFG_REG	(USBIN_BASE + 0x83)
+#define USBIN_12V_AICL_THRESHOLD_CFG_MASK	GENMASK(2, 0)
+
+#define USBIN_CONT_AICL_THRESHOLD_CFG_REG	(USBIN_BASE + 0x84)
+#define USBIN_CONT_AICL_THRESHOLD_CFG_MASK	GENMASK(5, 0)
+
+/* DCIN Peripheral Registers */
+#define DCIN_INPUT_STATUS_REG		(DCIN_BASE + 0x06)
+#define DCIN_INPUT_STATUS_7_BIT		BIT(7)
+#define DCIN_INPUT_STATUS_6_BIT		BIT(6)
+#define DCIN_12V_BIT			BIT(5)
+#define DCIN_9V_TO_12V_BIT		BIT(4)
+#define DCIN_9V_BIT			BIT(3)
+#define DCIN_5V_TO_12V_BIT		BIT(2)
+#define DCIN_5V_TO_9V_BIT		BIT(1)
+#define DCIN_5V_BIT			BIT(0)
+
+#define WIPWR_STATUS_REG		(DCIN_BASE + 0x07)
+#define WIPWR_STATUS_7_BIT		BIT(7)
+#define WIPWR_STATUS_6_BIT		BIT(6)
+#define WIPWR_STATUS_5_BIT		BIT(5)
+#define DCIN_WIPWR_OV_DG_BIT		BIT(4)
+#define DIV2_EN_DG_BIT			BIT(3)
+#define SHUTDOWN_N_LATCH_BIT		BIT(2)
+#define CHG_OK_PIN_BIT			BIT(1)
+#define WIPWR_CHARGING_ENABLED_BIT	BIT(0)
+
+#define WIPWR_RANGE_STATUS_REG		(DCIN_BASE + 0x08)
+#define WIPWR_RANGE_STATUS_MASK		GENMASK(4, 0)
+
+/* DCIN Interrupt Bits */
+#define WIPWR_VOLTAGE_RANGE_RT_STS_BIT	BIT(7)
+#define DCIN_ICL_CHANGE_RT_STS_BIT	BIT(6)
+#define DIV2_EN_DG_RT_STS_BIT		BIT(5)
+#define DCIN_PLUGIN_RT_STS_BIT		BIT(4)
+#define DCIN_OV_RT_STS_BIT		BIT(3)
+#define DCIN_UV_RT_STS_BIT		BIT(2)
+#define DCIN_LT_3P6V_RT_STS_BIT		BIT(1)
+#define DCIN_COLLAPSE_RT_STS_BIT	BIT(0)
+
+#define DCIN_CMD_IL_REG				(DCIN_BASE + 0x40)
+#define WIRELESS_CHG_DIS_BIT			BIT(3)
+#define SHDN_N_CLEAR_CMD_BIT			BIT(2)
+#define SHDN_N_SET_CMD_BIT			BIT(1)
+#define DCIN_SUSPEND_BIT			BIT(0)
+
+#define DC_SPARE_REG				(DCIN_BASE + 0x58)
+#define DC_SPARE_MASK				GENMASK(3, 0)
+
+#define DCIN_ADAPTER_ALLOW_CFG_REG		(DCIN_BASE + 0x60)
+#define DCIN_ADAPTER_ALLOW_MASK			GENMASK(3, 0)
+
+#define DCIN_LOAD_CFG_REG			(DCIN_BASE + 0x65)
+#define DCIN_OV_CH_LOAD_OPTION_BIT		BIT(7)
+
+#define DCIN_CURRENT_LIMIT_CFG_REG		(DCIN_BASE + 0x70)
+#define DCIN_CURRENT_LIMIT_MASK			GENMASK(7, 0)
+
+#define DCIN_AICL_OPTIONS_CFG_REG		(DCIN_BASE + 0x80)
+#define SUSPEND_ON_COLLAPSE_DCIN_BIT		BIT(7)
+#define DCIN_AICL_HDC_EN_BIT			BIT(6)
+#define DCIN_AICL_START_AT_MAX_BIT		BIT(5)
+#define DCIN_AICL_RERUN_EN_BIT			BIT(4)
+#define DCIN_AICL_ADC_EN_BIT			BIT(3)
+#define DCIN_AICL_EN_BIT			BIT(2)
+#define DCIN_HV_COLLAPSE_RESPONSE_BIT		BIT(1)
+#define DCIN_LV_COLLAPSE_RESPONSE_BIT		BIT(0)
+
+#define DCIN_AICL_REF_SEL_CFG_REG		(DCIN_BASE + 0x81)
+#define DCIN_CONT_AICL_THRESHOLD_CFG_MASK	GENMASK(5, 0)
+
+#define DCIN_ICL_START_CFG_REG			(DCIN_BASE + 0x82)
+#define DCIN_ICL_START_CFG_BIT			BIT(0)
+
+#define DIV2_EN_GF_TIME_CFG_REG			(DCIN_BASE + 0x90)
+#define DIV2_EN_GF_TIME_CFG_MASK		GENMASK(1, 0)
+
+#define WIPWR_IRQ_TMR_CFG_REG			(DCIN_BASE + 0x91)
+#define WIPWR_IRQ_TMR_MASK			GENMASK(2, 0)
+
+#define ZIN_ICL_PT_REG				(DCIN_BASE + 0x92)
+#define ZIN_ICL_PT_MASK				GENMASK(7, 0)
+
+#define ZIN_ICL_LV_REG				(DCIN_BASE + 0x93)
+#define ZIN_ICL_LV_MASK				GENMASK(7, 0)
+
+#define ZIN_ICL_HV_REG				(DCIN_BASE + 0x94)
+#define ZIN_ICL_HV_MASK				GENMASK(7, 0)
+
+#define WI_PWR_OPTIONS_REG			(DCIN_BASE + 0x95)
+#define CHG_OK_BIT				BIT(7)
+#define WIPWR_UVLO_IRQ_OPT_BIT			BIT(6)
+#define BUCK_HOLDOFF_ENABLE_BIT			BIT(5)
+#define CHG_OK_HW_SW_SELECT_BIT			BIT(4)
+#define WIPWR_RST_ENABLE_BIT			BIT(3)
+#define DCIN_WIPWR_IRQ_SELECT_BIT		BIT(2)
+#define AICL_SWITCH_ENABLE_BIT			BIT(1)
+#define ZIN_ICL_ENABLE_BIT			BIT(0)
+
+#define ZIN_ICL_PT_HV_REG			(DCIN_BASE + 0x96)
+#define ZIN_ICL_PT_HV_MASK			GENMASK(7, 0)
+
+#define ZIN_ICL_MID_LV_REG			(DCIN_BASE + 0x97)
+#define ZIN_ICL_MID_LV_MASK			GENMASK(7, 0)
+
+#define ZIN_ICL_MID_HV_REG			(DCIN_BASE + 0x98)
+#define ZIN_ICL_MID_HV_MASK			GENMASK(7, 0)
+
+enum {
+	ZIN_ICL_PT_MAX_MV = 8000,
+	ZIN_ICL_PT_HV_MAX_MV = 9000,
+	ZIN_ICL_LV_MAX_MV = 5500,
+	ZIN_ICL_MID_LV_MAX_MV = 6500,
+	ZIN_ICL_MID_HV_MAX_MV = 8000,
+	ZIN_ICL_HV_MAX_MV = 11000,
+};
+
+#define DC_ENG_SSUPPLY_CFG2_REG			(DCIN_BASE + 0xC1)
+#define ENG_SSUPPLY_IVREF_OTG_SS_MASK		GENMASK(2, 0)
+#define OTG_SS_SLOW				0x3
+
+#define DC_ENG_SSUPPLY_CFG3_REG			(DCIN_BASE + 0xC2)
+#define ENG_SSUPPLY_HI_CAP_BIT			BIT(6)
+#define ENG_SSUPPLY_HI_RES_BIT			BIT(5)
+#define ENG_SSUPPLY_CFG_SKIP_TH_V0P2_BIT	BIT(3)
+#define ENG_SSUPPLY_CFG_SYSOV_TH_4P8_BIT	BIT(2)
+#define ENG_SSUPPLY_5V_OV_OPT_BIT		BIT(0)
+
+/* MISC Peripheral Registers */
+#define REVISION1_REG				(MISC_BASE + 0x00)
+#define DIG_MINOR_MASK				GENMASK(7, 0)
+
+#define REVISION2_REG				(MISC_BASE + 0x01)
+#define DIG_MAJOR_MASK				GENMASK(7, 0)
+
+#define REVISION3_REG				(MISC_BASE + 0x02)
+#define ANA_MINOR_MASK				GENMASK(7, 0)
+
+#define REVISION4_REG				(MISC_BASE + 0x03)
+#define ANA_MAJOR_MASK				GENMASK(7, 0)
+
+#define TEMP_RANGE_STATUS_REG			(MISC_BASE + 0x06)
+#define TEMP_RANGE_STATUS_7_BIT			BIT(7)
+#define THERM_REG_ACTIVE_BIT			BIT(6)
+#define TLIM_BIT				BIT(5)
+#define TEMP_RANGE_MASK				GENMASK(4, 1)
+#define ALERT_LEVEL_BIT				BIT(4)
+#define TEMP_ABOVE_RANGE_BIT			BIT(3)
+#define TEMP_WITHIN_RANGE_BIT			BIT(2)
+#define TEMP_BELOW_RANGE_BIT			BIT(1)
+#define THERMREG_DISABLED_BIT			BIT(0)
+
+#define ICL_STATUS_REG				(MISC_BASE + 0x07)
+#define INPUT_CURRENT_LIMIT_MASK		GENMASK(7, 0)
+
+#define ADAPTER_5V_ICL_STATUS_REG		(MISC_BASE + 0x08)
+#define ADAPTER_5V_ICL_MASK			GENMASK(7, 0)
+
+#define ADAPTER_9V_ICL_STATUS_REG		(MISC_BASE + 0x09)
+#define ADAPTER_9V_ICL_MASK			GENMASK(7, 0)
+
+#define AICL_STATUS_REG				(MISC_BASE + 0x0A)
+#define AICL_STATUS_7_BIT			BIT(7)
+#define SOFT_ILIMIT_BIT				BIT(6)
+#define HIGHEST_DC_BIT				BIT(5)
+#define USBIN_CH_COLLAPSE_BIT			BIT(4)
+#define DCIN_CH_COLLAPSE_BIT			BIT(3)
+#define ICL_IMIN_BIT				BIT(2)
+#define AICL_FAIL_BIT				BIT(1)
+#define AICL_DONE_BIT				BIT(0)
+
+#define POWER_PATH_STATUS_REG			(MISC_BASE + 0x0B)
+#define INPUT_SS_DONE_BIT			BIT(7)
+#define USBIN_SUSPEND_STS_BIT			BIT(6)
+#define DCIN_SUSPEND_STS_BIT			BIT(5)
+#define USE_USBIN_BIT				BIT(4)
+#define USE_DCIN_BIT				BIT(3)
+#define POWER_PATH_MASK				GENMASK(2, 1)
+#define VALID_INPUT_POWER_SOURCE_STS_BIT	BIT(0)
+
+#define WDOG_STATUS_REG				(MISC_BASE + 0x0C)
+#define WDOG_STATUS_7_BIT			BIT(7)
+#define WDOG_STATUS_6_BIT			BIT(6)
+#define WDOG_STATUS_5_BIT			BIT(5)
+#define WDOG_STATUS_4_BIT			BIT(4)
+#define WDOG_STATUS_3_BIT			BIT(3)
+#define WDOG_STATUS_2_BIT			BIT(2)
+#define WDOG_STATUS_1_BIT			BIT(1)
+#define BARK_BITE_STATUS_BIT			BIT(0)
+
+#define SYSOK_REASON_STATUS_REG			(MISC_BASE + 0x0D)
+#define SYSOK_REASON_DCIN_BIT			BIT(1)
+#define SYSOK_REASON_USBIN_BIT			BIT(0)
+
+/* MISC Interrupt Bits */
+#define SWITCHER_POWER_OK_RT_STS_BIT		BIT(7)
+#define TEMPERATURE_CHANGE_RT_STS_BIT		BIT(6)
+#define INPUT_CURRENT_LIMITING_RT_STS_BIT	BIT(5)
+#define HIGH_DUTY_CYCLE_RT_STS_BIT		BIT(4)
+#define AICL_DONE_RT_STS_BIT			BIT(3)
+#define AICL_FAIL_RT_STS_BIT			BIT(2)
+#define WDOG_BARK_RT_STS_BIT			BIT(1)
+#define WDOG_SNARL_RT_STS_BIT			BIT(0)
+
+#define WDOG_RST_REG				(MISC_BASE + 0x40)
+#define WDOG_RST_BIT				BIT(0)
+
+#define AFP_MODE_REG				(MISC_BASE + 0x41)
+#define AFP_MODE_EN_BIT				BIT(0)
+
+#define GSM_PA_ON_ADJ_EN_REG			(MISC_BASE + 0x42)
+#define GSM_PA_ON_ADJ_EN_BIT			BIT(0)
+
+#define BARK_BITE_WDOG_PET_REG			(MISC_BASE + 0x43)
+#define BARK_BITE_WDOG_PET_BIT			BIT(0)
+
+#define PHYON_CMD_REG				(MISC_BASE + 0x44)
+#define PHYON_CMD_BIT				BIT(0)
+
+#define SHDN_CMD_REG				(MISC_BASE + 0x45)
+#define SHDN_CMD_BIT				BIT(0)
+
+#define FINISH_COPY_COMMAND_REG			(MISC_BASE + 0x4F)
+#define START_COPY_BIT				BIT(0)
+
+#define WD_CFG_REG				(MISC_BASE + 0x51)
+#define WATCHDOG_TRIGGER_AFP_EN_BIT		BIT(7)
+#define BARK_WDOG_INT_EN_BIT			BIT(6)
+#define BITE_WDOG_INT_EN_BIT			BIT(5)
+#define SFT_AFTER_WDOG_IRQ_MASK			GENMASK(4, 3)
+#define WDOG_IRQ_SFT_BIT			BIT(2)
+#define WDOG_TIMER_EN_ON_PLUGIN_BIT		BIT(1)
+#define WDOG_TIMER_EN_BIT			BIT(0)
+
+#define MISC_CFG_REG				(MISC_BASE + 0x52)
+#define GSM_PA_ON_ADJ_SEL_BIT			BIT(0)
+#define TCC_DEBOUNCE_20MS_BIT			BIT(5)
+
+#define SNARL_BARK_BITE_WD_CFG_REG		(MISC_BASE + 0x53)
+#define BITE_WDOG_DISABLE_CHARGING_CFG_BIT	BIT(7)
+#define SNARL_WDOG_TIMEOUT_MASK			GENMASK(6, 4)
+#define BARK_WDOG_TIMEOUT_MASK			GENMASK(3, 2)
+#define BITE_WDOG_TIMEOUT_MASK			GENMASK(1, 0)
+
+#define PHYON_CFG_REG				(MISC_BASE + 0x54)
+#define USBPHYON_PUSHPULL_CFG_BIT		BIT(1)
+#define PHYON_SW_SEL_BIT			BIT(0)
+
+#define CHGR_TRIM_OPTIONS_7_0_REG		(MISC_BASE + 0x55)
+#define TLIM_DIS_TBIT_BIT			BIT(0)
+
+#define CH_OV_OPTION_CFG_REG			(MISC_BASE + 0x56)
+#define OV_OPTION_TBIT_BIT			BIT(0)
+
+#define AICL_CFG_REG				(MISC_BASE + 0x60)
+#define TREG_ALLOW_DECREASE_BIT			BIT(1)
+#define AICL_HIGH_DC_INC_BIT			BIT(0)
+
+#define AICL_RERUN_TIME_CFG_REG			(MISC_BASE + 0x61)
+#define AICL_RERUN_TIME_MASK			GENMASK(1, 0)
+
+#define AICL_RERUN_TEMP_TIME_CFG_REG		(MISC_BASE + 0x62)
+#define AICL_RERUN_TEMP_TIME_MASK		GENMASK(1, 0)
+
+#define THERMREG_SRC_CFG_REG			(MISC_BASE + 0x70)
+#define SKIN_ADC_CFG_BIT			BIT(3)
+#define THERMREG_SKIN_ADC_SRC_EN_BIT		BIT(2)
+#define THERMREG_DIE_ADC_SRC_EN_BIT		BIT(1)
+#define THERMREG_DIE_CMP_SRC_EN_BIT		BIT(0)
+
+#define TREG_DIE_CMP_INC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x71)
+#define TREG_DIE_CMP_INC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define TREG_DIE_CMP_DEC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x72)
+#define TREG_DIE_CMP_DEC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define TREG_DIE_ADC_INC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x73)
+#define TREG_DIE_ADC_INC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define TREG_DIE_ADC_DEC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x74)
+#define TREG_DIE_ADC_DEC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define TREG_SKIN_ADC_INC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x75)
+#define TREG_SKIN_ADC_INC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define TREG_SKIN_ADC_DEC_CYCLE_TIME_CFG_REG	(MISC_BASE + 0x76)
+#define TREG_SKIN_ADC_DEC_CYCLE_TIME_MASK	GENMASK(1, 0)
+
+#define BUCK_OPTIONS_CFG_REG			(MISC_BASE + 0x80)
+#define CHG_EN_PIN_SUSPEND_CFG_BIT		BIT(6)
+#define HICCUP_OPTIONS_MASK			GENMASK(5, 4)
+#define INPUT_CURRENT_LIMIT_SOFTSTART_EN_BIT	BIT(3)
+#define HV_HIGH_DUTY_CYCLE_PROTECT_EN_BIT	BIT(2)
+#define BUCK_OC_PROTECT_EN_BIT			BIT(1)
+#define INPUT_MISS_POLL_EN_BIT			BIT(0)
+
+#define ICL_SOFTSTART_RATE_CFG_REG		(MISC_BASE + 0x81)
+#define ICL_SOFTSTART_RATE_MASK			GENMASK(1, 0)
+
+#define ICL_SOFTSTOP_RATE_CFG_REG		(MISC_BASE + 0x82)
+#define ICL_SOFTSTOP_RATE_MASK			GENMASK(1, 0)
+
+#define VSYS_MIN_SEL_CFG_REG			(MISC_BASE + 0x83)
+#define VSYS_MIN_SEL_MASK			GENMASK(1, 0)
+
+#define TRACKING_VOLTAGE_SEL_CFG_REG		(MISC_BASE + 0x84)
+#define TRACKING_VOLTAGE_SEL_BIT		BIT(0)
+
+#define STAT_CFG_REG				(MISC_BASE + 0x90)
+#define STAT_SW_OVERRIDE_VALUE_BIT		BIT(7)
+#define STAT_SW_OVERRIDE_CFG_BIT		BIT(6)
+#define STAT_PARALLEL_OFF_DG_CFG_MASK		GENMASK(5, 4)
+#define STAT_POLARITY_CFG_BIT			BIT(3)
+#define STAT_PARALLEL_CFG_BIT			BIT(2)
+#define STAT_FUNCTION_CFG_BIT			BIT(1)
+#define STAT_IRQ_PULSING_EN_BIT			BIT(0)
+
+#define LBC_EN_CFG_REG				(MISC_BASE + 0x91)
+#define LBC_DURING_CHARGING_CFG_BIT		BIT(1)
+#define LBC_EN_BIT				BIT(0)
+
+#define LBC_PERIOD_CFG_REG			(MISC_BASE + 0x92)
+#define LBC_PERIOD_MASK				GENMASK(2, 0)
+
+#define LBC_DUTY_CYCLE_CFG_REG			(MISC_BASE + 0x93)
+#define LBC_DUTY_CYCLE_MASK			GENMASK(2, 0)
+
+#define SYSOK_CFG_REG				(MISC_BASE + 0x94)
+#define SYSOK_PUSHPULL_CFG_BIT			BIT(5)
+#define SYSOK_B_OR_C_SEL_BIT			BIT(4)
+#define SYSOK_POL_BIT				BIT(3)
+#define SYSOK_OPTIONS_MASK			GENMASK(2, 0)
+
+#define CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG	(MISC_BASE + 0xA0)
+#define CFG_BUCKBOOST_FREQ_SELECT_BOOST_REG	(MISC_BASE + 0xA1)
+
+/* CHGR FREQ Peripheral registers */
+#define FREQ_CLK_DIV_REG			(CHGR_FREQ_BASE + 0x50)
+
+#endif /* __SMB2_CHARGER_REG_H */
diff --git a/drivers/power/supply/qcom/smb1351-charger.c b/drivers/power/supply/qcom/smb1351-charger.c
new file mode 100644
index 0000000..0d1f2a6
--- /dev/null
+++ b/drivers/power/supply/qcom/smb1351-charger.c
@@ -0,0 +1,3335 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/of.h>
+#include <linux/mutex.h>
+#include <linux/delay.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/pinctrl/consumer.h>
+
+/* Mask/Bit helpers */
+#define _SMB1351_MASK(BITS, POS) \
+	((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB1351_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+		_SMB1351_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+				(RIGHT_BIT_POS))
+
+/* Configuration registers */
+#define CHG_CURRENT_CTRL_REG			0x0
+#define FAST_CHG_CURRENT_MASK			SMB1351_MASK(7, 4)
+#define AC_INPUT_CURRENT_LIMIT_MASK		SMB1351_MASK(3, 0)
+
+#define CHG_OTH_CURRENT_CTRL_REG		0x1
+#define PRECHG_CURRENT_MASK			SMB1351_MASK(7, 5)
+#define ITERM_MASK				SMB1351_MASK(4, 2)
+#define USB_2_3_MODE_SEL_BIT			BIT(1)
+#define USB_2_3_MODE_SEL_BY_I2C			0
+#define USB_2_3_MODE_SEL_BY_PIN			0x2
+#define USB_5_1_CMD_POLARITY_BIT		BIT(0)
+#define USB_CMD_POLARITY_500_1_100_0		0
+#define USB_CMD_POLARITY_500_0_100_1		0x1
+
+#define VARIOUS_FUNC_REG			0x2
+#define SUSPEND_MODE_CTRL_BIT			BIT(7)
+#define SUSPEND_MODE_CTRL_BY_PIN		0
+#define SUSPEND_MODE_CTRL_BY_I2C		0x80
+#define BATT_TO_SYS_POWER_CTRL_BIT		BIT(6)
+#define MAX_SYS_VOLTAGE				BIT(5)
+#define AICL_EN_BIT				BIT(4)
+#define AICL_DET_TH_BIT				BIT(3)
+#define APSD_EN_BIT				BIT(2)
+#define BATT_OV_BIT				BIT(1)
+#define VCHG_FUNC_BIT				BIT(0)
+
+#define VFLOAT_REG				0x3
+#define PRECHG_TO_FAST_VOLTAGE_CFG_MASK		SMB1351_MASK(7, 6)
+#define VFLOAT_MASK				SMB1351_MASK(5, 0)
+
+#define CHG_CTRL_REG				0x4
+#define AUTO_RECHG_BIT				BIT(7)
+#define AUTO_RECHG_ENABLE			0
+#define AUTO_RECHG_DISABLE			0x80
+#define ITERM_EN_BIT				BIT(6)
+#define ITERM_ENABLE				0
+#define ITERM_DISABLE				0x40
+#define MAPPED_AC_INPUT_CURRENT_LIMIT_MASK	SMB1351_MASK(5, 4)
+#define AUTO_RECHG_TH_BIT			BIT(3)
+#define AUTO_RECHG_TH_50MV			0
+#define AUTO_RECHG_TH_100MV			0x8
+#define AFCV_MASK				SMB1351_MASK(2, 0)
+
+#define CHG_STAT_TIMERS_CTRL_REG		0x5
+#define STAT_OUTPUT_POLARITY_BIT		BIT(7)
+#define STAT_OUTPUT_MODE_BIT			BIT(6)
+#define STAT_OUTPUT_CTRL_BIT			BIT(5)
+#define OTH_CHG_IL_BIT				BIT(4)
+#define COMPLETE_CHG_TIMEOUT_MASK		SMB1351_MASK(3, 2)
+#define PRECHG_TIMEOUT_MASK			SMB1351_MASK(1, 0)
+
+#define CHG_PIN_EN_CTRL_REG			0x6
+#define LED_BLINK_FUNC_BIT			BIT(7)
+#define EN_PIN_CTRL_MASK			SMB1351_MASK(6, 5)
+#define EN_BY_I2C_0_DISABLE			0
+#define EN_BY_I2C_0_ENABLE			0x20
+#define EN_BY_PIN_HIGH_ENABLE			0x40
+#define EN_BY_PIN_LOW_ENABLE			0x60
+#define USBCS_CTRL_BIT				BIT(4)
+#define USBCS_CTRL_BY_I2C			0
+#define USBCS_CTRL_BY_PIN			0x10
+#define USBCS_INPUT_STATE_BIT			BIT(3)
+#define CHG_ERR_BIT				BIT(2)
+#define APSD_DONE_BIT				BIT(1)
+#define USB_FAIL_BIT				BIT(0)
+
+#define THERM_A_CTRL_REG			0x7
+#define MIN_SYS_VOLTAGE_MASK			SMB1351_MASK(7, 6)
+#define LOAD_BATT_10MA_FVC_BIT			BIT(5)
+#define THERM_MONITOR_BIT			BIT(4)
+#define THERM_MONITOR_EN			0
+#define SOFT_COLD_TEMP_LIMIT_MASK		SMB1351_MASK(3, 2)
+#define SOFT_HOT_TEMP_LIMIT_MASK		SMB1351_MASK(1, 0)
+
+#define WDOG_SAFETY_TIMER_CTRL_REG		0x8
+#define AICL_FAIL_OPTION_BIT			BIT(7)
+#define AICL_FAIL_TO_SUSPEND			0
+#define AICL_FAIL_TO_150_MA			0x80
+#define WDOG_TIMEOUT_MASK			SMB1351_MASK(6, 5)
+#define WDOG_IRQ_SAFETY_TIMER_MASK		SMB1351_MASK(4, 3)
+#define WDOG_IRQ_SAFETY_TIMER_EN_BIT		BIT(2)
+#define WDOG_OPTION_BIT				BIT(1)
+#define WDOG_TIMER_EN_BIT			BIT(0)
+
+#define OTG_USBIN_AICL_CTRL_REG			0x9
+#define OTG_ID_PIN_CTRL_MASK			SMB1351_MASK(7, 6)
+#define OTG_PIN_POLARITY_BIT			BIT(5)
+#define DCIN_IC_GLITCH_FILTER_HV_ADAPTER_MASK	SMB1351_MASK(4, 3)
+#define DCIN_IC_GLITCH_FILTER_LV_ADAPTER_BIT	BIT(2)
+#define USBIN_AICL_CFG1_BIT			BIT(1)
+#define USBIN_AICL_CFG0_BIT			BIT(0)
+
+#define OTG_TLIM_CTRL_REG			0xA
+#define SWITCH_FREQ_MASK			SMB1351_MASK(7, 6)
+#define THERM_LOOP_TEMP_SEL_MASK		SMB1351_MASK(5, 4)
+#define OTG_OC_LIMIT_MASK			SMB1351_MASK(3, 2)
+#define OTG_BATT_UVLO_TH_MASK			SMB1351_MASK(1, 0)
+
+#define HARD_SOFT_LIMIT_CELL_TEMP_REG		0xB
+#define HARD_LIMIT_COLD_TEMP_ALARM_TRIP_MASK	SMB1351_MASK(7, 6)
+#define HARD_LIMIT_HOT_TEMP_ALARM_TRIP_MASK	SMB1351_MASK(5, 4)
+#define SOFT_LIMIT_COLD_TEMP_ALARM_TRIP_MASK	SMB1351_MASK(3, 2)
+#define SOFT_LIMIT_HOT_TEMP_ALARM_TRIP_MASK	SMB1351_MASK(1, 0)
+
+#define FAULT_INT_REG				0xC
+#define HOT_COLD_HARD_LIMIT_BIT			BIT(7)
+#define HOT_COLD_SOFT_LIMIT_BIT			BIT(6)
+#define BATT_UVLO_IN_OTG_BIT			BIT(5)
+#define OTG_OC_BIT				BIT(4)
+#define INPUT_OVLO_BIT				BIT(3)
+#define INPUT_UVLO_BIT				BIT(2)
+#define AICL_DONE_FAIL_BIT			BIT(1)
+#define INTERNAL_OVER_TEMP_BIT			BIT(0)
+
+#define STATUS_INT_REG				0xD
+#define CHG_OR_PRECHG_TIMEOUT_BIT		BIT(7)
+#define RID_CHANGE_BIT				BIT(6)
+#define BATT_OVP_BIT				BIT(5)
+#define FAST_TERM_TAPER_RECHG_INHIBIT_BIT	BIT(4)
+#define WDOG_TIMER_BIT				BIT(3)
+#define POK_BIT					BIT(2)
+#define BATT_MISSING_BIT			BIT(1)
+#define BATT_LOW_BIT				BIT(0)
+
+#define VARIOUS_FUNC_2_REG			0xE
+#define CHG_HOLD_OFF_TIMER_AFTER_PLUGIN_BIT	BIT(7)
+#define CHG_INHIBIT_BIT				BIT(6)
+#define FAST_CHG_CC_IN_BATT_SOFT_LIMIT_MODE_BIT	BIT(5)
+#define FVCL_IN_BATT_SOFT_LIMIT_MODE_MASK	SMB1351_MASK(4, 3)
+#define HARD_TEMP_LIMIT_BEHAVIOR_BIT		BIT(2)
+#define PRECHG_TO_FASTCHG_BIT			BIT(1)
+#define STAT_PIN_CONFIG_BIT			BIT(0)
+
+#define FLEXCHARGER_REG				0x10
+#define AFVC_IRQ_BIT				BIT(7)
+#define CHG_CONFIG_MASK				SMB1351_MASK(6, 4)
+#define LOW_BATT_VOLTAGE_DET_TH_MASK		SMB1351_MASK(3, 0)
+
+#define VARIOUS_FUNC_3_REG			0x11
+#define SAFETY_TIMER_EN_MASK			SMB1351_MASK(7, 6)
+#define BLOCK_SUSPEND_DURING_VBATT_LOW_BIT	BIT(5)
+#define TIMEOUT_SEL_FOR_APSD_BIT		BIT(4)
+#define SDP_SUSPEND_BIT				BIT(3)
+#define QC_2P1_AUTO_INCREMENT_MODE_BIT		BIT(2)
+#define QC_2P1_AUTH_ALGO_BIT			BIT(1)
+#define DCD_EN_BIT				BIT(0)
+
+#define HVDCP_BATT_MISSING_CTRL_REG		0x12
+#define HVDCP_ADAPTER_SEL_MASK			SMB1351_MASK(7, 6)
+#define HVDCP_EN_BIT				BIT(5)
+#define HVDCP_AUTO_INCREMENT_LIMIT_BIT		BIT(4)
+#define BATT_MISSING_ON_INPUT_PLUGIN_BIT	BIT(3)
+#define BATT_MISSING_2P6S_POLLER_BIT		BIT(2)
+#define BATT_MISSING_ALGO_BIT			BIT(1)
+#define BATT_MISSING_THERM_PIN_SOURCE_BIT	BIT(0)
+
+#define PON_OPTIONS_REG				0x13
+#define SYSOK_INOK_POLARITY_BIT			BIT(7)
+#define SYSOK_OPTIONS_MASK			SMB1351_MASK(6, 4)
+#define INPUT_MISSING_POLLER_CONFIG_BIT		BIT(3)
+#define VBATT_LOW_DISABLED_OR_RESET_STATE_BIT	BIT(2)
+#define QC_2P1_AUTH_ALGO_IRQ_EN_BIT		BIT(0)
+
+#define OTG_MODE_POWER_OPTIONS_REG		0x14
+#define ADAPTER_CONFIG_MASK			SMB1351_MASK(7, 6)
+#define MAP_HVDCP_BIT				BIT(5)
+#define SDP_LOW_BATT_FORCE_USB5_OVER_USB1_BIT	BIT(4)
+#define OTG_HICCUP_MODE_BIT			BIT(2)
+#define INPUT_CURRENT_LIMIT_MASK		SMB1351_MASK(1, 0)
+
+#define CHARGER_I2C_CTRL_REG			0x15
+#define FULLON_MODE_EN_BIT			BIT(7)
+#define I2C_HS_MODE_EN_BIT			BIT(6)
+#define SYSON_LDO_OUTPUT_SEL_BIT		BIT(5)
+#define VBATT_TRACKING_VOLTAGE_DIFF_BIT		BIT(4)
+#define DISABLE_AFVC_WHEN_ENTER_TAPER_BIT	BIT(3)
+#define VCHG_IINV_BIT				BIT(2)
+#define AFVC_OVERRIDE_BIT			BIT(1)
+#define SYSOK_PIN_CONFIG_BIT			BIT(0)
+
+#define VERSION_REG				0x2E
+#define VERSION_MASK				BIT(1)
+
+/* Command registers */
+#define CMD_I2C_REG				0x30
+#define CMD_RELOAD_BIT				BIT(7)
+#define CMD_BQ_CFG_ACCESS_BIT			BIT(6)
+
+#define CMD_INPUT_LIMIT_REG			0x31
+#define CMD_OVERRIDE_BIT			BIT(7)
+#define CMD_SUSPEND_MODE_BIT			BIT(6)
+#define CMD_INPUT_CURRENT_MODE_BIT		BIT(3)
+#define CMD_INPUT_CURRENT_MODE_APSD		0
+#define CMD_INPUT_CURRENT_MODE_CMD		0x08
+#define CMD_USB_2_3_SEL_BIT			BIT(2)
+#define CMD_USB_2_MODE				0
+#define CMD_USB_3_MODE				0x4
+#define CMD_USB_1_5_AC_CTRL_MASK		SMB1351_MASK(1, 0)
+#define CMD_USB_100_MODE			0
+#define CMD_USB_500_MODE			0x2
+#define CMD_USB_AC_MODE				0x1
+
+#define CMD_CHG_REG				0x32
+#define CMD_DISABLE_THERM_MONITOR_BIT		BIT(4)
+#define CMD_TURN_OFF_STAT_PIN_BIT		BIT(3)
+#define CMD_PRE_TO_FAST_EN_BIT			BIT(2)
+#define CMD_CHG_EN_BIT				BIT(1)
+#define CMD_CHG_DISABLE				0
+#define CMD_CHG_ENABLE				0x2
+#define CMD_OTG_EN_BIT				BIT(0)
+
+#define CMD_DEAD_BATT_REG			0x33
+#define CMD_STOP_DEAD_BATT_TIMER_MASK		SMB1351_MASK(7, 0)
+
+#define CMD_HVDCP_REG				0x34
+#define CMD_APSD_RE_RUN_BIT			BIT(7)
+#define CMD_FORCE_HVDCP_2P0_BIT			BIT(5)
+#define CMD_HVDCP_MODE_MASK			SMB1351_MASK(5, 0)
+
+/* Status registers */
+#define STATUS_0_REG				0x36
+#define STATUS_AICL_BIT				BIT(7)
+#define STATUS_INPUT_CURRENT_LIMIT_MASK		SMB1351_MASK(6, 5)
+#define STATUS_DCIN_INPUT_CURRENT_LIMIT_MASK	SMB1351_MASK(4, 0)
+
+#define STATUS_1_REG				0x37
+#define STATUS_INPUT_RANGE_MASK			SMB1351_MASK(7, 4)
+#define STATUS_INPUT_USB_BIT			BIT(0)
+
+#define STATUS_2_REG				0x38
+#define STATUS_FAST_CHG_BIT			BIT(7)
+#define STATUS_HARD_LIMIT_BIT			BIT(6)
+#define STATUS_FLOAT_VOLTAGE_MASK		SMB1351_MASK(5, 0)
+
+#define STATUS_3_REG				0x39
+#define STATUS_CHG_BIT				BIT(7)
+#define STATUS_PRECHG_CURRENT_MASK		SMB1351_MASK(6, 4)
+#define STATUS_FAST_CHG_CURRENT_MASK		SMB1351_MASK(3, 0)
+
+#define STATUS_4_REG				0x3A
+#define STATUS_OTG_BIT				BIT(7)
+#define STATUS_AFVC_BIT				BIT(6)
+#define STATUS_DONE_BIT				BIT(5)
+#define STATUS_BATT_LESS_THAN_2V_BIT		BIT(4)
+#define STATUS_HOLD_OFF_BIT			BIT(3)
+#define STATUS_CHG_MASK				SMB1351_MASK(2, 1)
+#define STATUS_NO_CHARGING			0
+#define STATUS_FAST_CHARGING			0x4
+#define STATUS_PRE_CHARGING			0x2
+#define STATUS_TAPER_CHARGING			0x6
+#define STATUS_CHG_EN_STATUS_BIT		BIT(0)
+
+#define STATUS_5_REG				0x3B
+#define STATUS_SOURCE_DETECTED_MASK		SMB1351_MASK(7, 0)
+#define STATUS_PORT_CDP				0x80
+#define STATUS_PORT_DCP				0x40
+#define STATUS_PORT_OTHER			0x20
+#define STATUS_PORT_SDP				0x10
+#define STATUS_PORT_ACA_A			0x8
+#define STATUS_PORT_ACA_B			0x4
+#define STATUS_PORT_ACA_C			0x2
+#define STATUS_PORT_ACA_DOCK			0x1
+
+#define STATUS_6_REG				0x3C
+#define STATUS_DCD_TIMEOUT_BIT			BIT(7)
+#define STATUS_DCD_GOOD_DG_BIT			BIT(6)
+#define STATUS_OCD_GOOD_DG_BIT			BIT(5)
+#define STATUS_RID_ABD_DG_BIT			BIT(4)
+#define STATUS_RID_FLOAT_STATE_MACHINE_BIT	BIT(3)
+#define STATUS_RID_A_STATE_MACHINE_BIT		BIT(2)
+#define STATUS_RID_B_STATE_MACHINE_BIT		BIT(1)
+#define STATUS_RID_C_STATE_MACHINE_BIT		BIT(0)
+
+#define STATUS_7_REG				0x3D
+#define STATUS_HVDCP_MASK			SMB1351_MASK(7, 0)
+
+#define STATUS_8_REG				0x3E
+#define STATUS_USNIN_HV_INPUT_SEL_BIT		BIT(5)
+#define STATUS_USBIN_LV_UNDER_INPUT_SEL_BIT	BIT(4)
+#define STATUS_USBIN_LV_INPUT_SEL_BIT		BIT(3)
+
+/* Revision register */
+#define CHG_REVISION_REG			0x3F
+#define GUI_REVISION_MASK			SMB1351_MASK(7, 4)
+#define DEVICE_REVISION_MASK			SMB1351_MASK(3, 0)
+
+/* IRQ status registers */
+#define IRQ_A_REG				0x40
+#define IRQ_HOT_HARD_BIT			BIT(6)
+#define IRQ_COLD_HARD_BIT			BIT(4)
+#define IRQ_HOT_SOFT_BIT			BIT(2)
+#define IRQ_COLD_SOFT_BIT			BIT(0)
+
+#define IRQ_B_REG				0x41
+#define IRQ_BATT_TERMINAL_REMOVED_BIT		BIT(6)
+#define IRQ_BATT_MISSING_BIT			BIT(4)
+#define IRQ_LOW_BATT_VOLTAGE_BIT		BIT(2)
+#define IRQ_INTERNAL_TEMP_LIMIT_BIT		BIT(0)
+
+#define IRQ_C_REG				0x42
+#define IRQ_PRE_TO_FAST_VOLTAGE_BIT		BIT(6)
+#define IRQ_RECHG_BIT				BIT(4)
+#define IRQ_TAPER_BIT				BIT(2)
+#define IRQ_TERM_BIT				BIT(0)
+
+#define IRQ_D_REG				0x43
+#define IRQ_BATT_OV_BIT				BIT(6)
+#define IRQ_CHG_ERROR_BIT			BIT(4)
+#define IRQ_CHG_TIMEOUT_BIT			BIT(2)
+#define IRQ_PRECHG_TIMEOUT_BIT			BIT(0)
+
+#define IRQ_E_REG				0x44
+#define IRQ_USBIN_OV_BIT			BIT(6)
+#define IRQ_USBIN_UV_BIT			BIT(4)
+#define IRQ_AFVC_BIT				BIT(2)
+#define IRQ_POWER_OK_BIT			BIT(0)
+
+#define IRQ_F_REG				0x45
+#define IRQ_OTG_OVER_CURRENT_BIT		BIT(6)
+#define IRQ_OTG_FAIL_BIT			BIT(4)
+#define IRQ_RID_BIT				BIT(2)
+#define IRQ_OTG_OC_RETRY_BIT			BIT(0)
+
+#define IRQ_G_REG				0x46
+#define IRQ_SOURCE_DET_BIT			BIT(6)
+#define IRQ_AICL_DONE_BIT			BIT(4)
+#define IRQ_AICL_FAIL_BIT			BIT(2)
+#define IRQ_CHG_INHIBIT_BIT			BIT(0)
+
+#define IRQ_H_REG				0x47
+#define IRQ_IC_LIMIT_STATUS_BIT			BIT(5)
+#define IRQ_HVDCP_2P1_STATUS_BIT		BIT(4)
+#define IRQ_HVDCP_AUTH_DONE_BIT			BIT(2)
+#define IRQ_WDOG_TIMEOUT_BIT			BIT(0)
+
+/* constants */
+#define USB2_MIN_CURRENT_MA			100
+#define USB2_MAX_CURRENT_MA			500
+#define USB3_MIN_CURRENT_MA			150
+#define USB3_MAX_CURRENT_MA			900
+#define SMB1351_IRQ_REG_COUNT			8
+#define SMB1351_CHG_PRE_MIN_MA			100
+#define SMB1351_CHG_FAST_MIN_MA			1000
+#define SMB1351_CHG_FAST_MAX_MA			4500
+#define SMB1351_CHG_PRE_SHIFT			5
+#define SMB1351_CHG_FAST_SHIFT			4
+#define DEFAULT_BATT_CAPACITY			50
+#define DEFAULT_BATT_TEMP			250
+#define SUSPEND_CURRENT_MA			2
+
+#define CHG_ITERM_200MA				0x0
+#define CHG_ITERM_300MA				0x04
+#define CHG_ITERM_400MA				0x08
+#define CHG_ITERM_500MA				0x0C
+#define CHG_ITERM_600MA				0x10
+#define CHG_ITERM_700MA				0x14
+
+#define ADC_TM_WARM_COOL_THR_ENABLE		ADC_TM_HIGH_LOW_THR_ENABLE
+
+enum reason {
+	USER	= BIT(0),
+	THERMAL = BIT(1),
+	CURRENT = BIT(2),
+	SOC	= BIT(3),
+};
+
+static char *pm_batt_supplied_to[] = {
+	"bms",
+};
+
+struct smb1351_regulator {
+	struct regulator_desc	rdesc;
+	struct regulator_dev	*rdev;
+};
+
+enum chip_version {
+	SMB_UNKNOWN = 0,
+	SMB1350,
+	SMB1351,
+	SMB_MAX_TYPE,
+};
+
+static const char *smb1351_version_str[SMB_MAX_TYPE] = {
+	[SMB_UNKNOWN] = "Unknown",
+	[SMB1350] = "SMB1350",
+	[SMB1351] = "SMB1351",
+};
+
+struct smb1351_charger {
+	struct i2c_client	*client;
+	struct device		*dev;
+
+	bool			recharge_disabled;
+	int			recharge_mv;
+	bool			iterm_disabled;
+	int			iterm_ma;
+	int			vfloat_mv;
+	int			chg_present;
+	int			fake_battery_soc;
+	bool			chg_autonomous_mode;
+	bool			disable_apsd;
+	bool			using_pmic_therm;
+	bool			jeita_supported;
+	bool			battery_missing;
+	const char		*bms_psy_name;
+	bool			resume_completed;
+	bool			irq_waiting;
+	struct delayed_work	chg_remove_work;
+	struct delayed_work	hvdcp_det_work;
+
+	/* status tracking */
+	bool			batt_full;
+	bool			batt_hot;
+	bool			batt_cold;
+	bool			batt_warm;
+	bool			batt_cool;
+
+	int			battchg_disabled_status;
+	int			usb_suspended_status;
+	int			target_fastchg_current_max_ma;
+	int			fastchg_current_max_ma;
+	int			workaround_flags;
+
+	int			parallel_pin_polarity_setting;
+	int			parallel_mode;
+	bool			parallel_charger;
+	bool			parallel_charger_suspended;
+	bool			bms_controlled_charging;
+	bool			apsd_rerun;
+	bool			usbin_ov;
+	bool			chg_remove_work_scheduled;
+	bool			force_hvdcp_2p0;
+	enum chip_version	version;
+
+	/* psy */
+	struct power_supply	*usb_psy;
+	int			usb_psy_ma;
+	struct power_supply	*bms_psy;
+	struct power_supply_desc	batt_psy_d;
+	struct power_supply	*batt_psy;
+	struct power_supply	*parallel_psy;
+	struct power_supply_desc	parallel_psy_d;
+
+	struct smb1351_regulator	otg_vreg;
+	struct mutex		irq_complete;
+
+	struct dentry		*debug_root;
+	u32			peek_poke_address;
+
+	/* adc_tm parameters */
+	struct qpnp_vadc_chip	*vadc_dev;
+	struct qpnp_adc_tm_chip	*adc_tm_dev;
+	struct qpnp_adc_tm_btm_param	adc_param;
+
+	/* jeita parameters */
+	int			batt_hot_decidegc;
+	int			batt_cold_decidegc;
+	int			batt_warm_decidegc;
+	int			batt_cool_decidegc;
+	int			batt_missing_decidegc;
+	unsigned int		batt_warm_ma;
+	unsigned int		batt_warm_mv;
+	unsigned int		batt_cool_ma;
+	unsigned int		batt_cool_mv;
+
+	/* pinctrl parameters */
+	const char		*pinctrl_state_name;
+	struct pinctrl		*smb_pinctrl;
+};
+
+struct smb_irq_info {
+	const char		*name;
+	int (*smb_irq)(struct smb1351_charger *chip, u8 rt_stat);
+	int			high;
+	int			low;
+};
+
+struct irq_handler_info {
+	u8			stat_reg;
+	u8			val;
+	u8			prev_val;
+	struct smb_irq_info	irq_info[4];
+};
+
+/* USB input charge current */
+static int usb_chg_current[] = {
+	500, 685, 1000, 1100, 1200, 1300, 1500, 1600,
+	1700, 1800, 2000, 2200, 2500, 3000,
+};
+
+static int fast_chg_current[] = {
+	1000, 1200, 1400, 1600, 1800, 2000, 2200,
+	2400, 2600, 2800, 3000, 3400, 3600, 3800,
+	4000, 4640,
+};
+
+static int pre_chg_current[] = {
+	200, 300, 400, 500, 600, 700,
+};
+
+struct battery_status {
+	bool			batt_hot;
+	bool			batt_warm;
+	bool			batt_cool;
+	bool			batt_cold;
+	bool			batt_present;
+};
+
+enum {
+	BATT_HOT = 0,
+	BATT_WARM,
+	BATT_NORMAL,
+	BATT_COOL,
+	BATT_COLD,
+	BATT_MISSING,
+	BATT_STATUS_MAX,
+};
+
+static struct battery_status batt_s[] = {
+	[BATT_HOT] = {1, 0, 0, 0, 1},
+	[BATT_WARM] = {0, 1, 0, 0, 1},
+	[BATT_NORMAL] = {0, 0, 0, 0, 1},
+	[BATT_COOL] = {0, 0, 1, 0, 1},
+	[BATT_COLD] = {0, 0, 0, 1, 1},
+	[BATT_MISSING] = {0, 0, 0, 1, 0},
+};
+
+static int smb1351_read_reg(struct smb1351_charger *chip, int reg, u8 *val)
+{
+	s32 ret;
+
+	pm_stay_awake(chip->dev);
+	ret = i2c_smbus_read_byte_data(chip->client, reg);
+	if (ret < 0) {
+		pr_err("i2c read fail: can't read from %02x: %d\n", reg, ret);
+		pm_relax(chip->dev);
+		return ret;
+	}
+
+	*val = ret;
+
+	pm_relax(chip->dev);
+	pr_debug("Reading 0x%02x=0x%02x\n", reg, *val);
+	return 0;
+}
+
+static int smb1351_write_reg(struct smb1351_charger *chip, int reg, u8 val)
+{
+	s32 ret;
+
+	pm_stay_awake(chip->dev);
+	ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+	if (ret < 0) {
+		pr_err("i2c write fail: can't write %02x to %02x: %d\n",
+			val, reg, ret);
+		pm_relax(chip->dev);
+		return ret;
+	}
+	pm_relax(chip->dev);
+	pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+	return 0;
+}
+
+static int smb1351_masked_write(struct smb1351_charger *chip, int reg,
+							u8 mask, u8 val)
+{
+	s32 rc;
+	u8 temp;
+
+	rc = smb1351_read_reg(chip, reg, &temp);
+	if (rc) {
+		pr_err("read failed: reg=%03X, rc=%d\n", reg, rc);
+		return rc;
+	}
+	temp &= ~mask;
+	temp |= val & mask;
+	rc = smb1351_write_reg(chip, reg, temp);
+	if (rc) {
+		pr_err("write failed: reg=%03X, rc=%d\n", reg, rc);
+		return rc;
+	}
+	return 0;
+}
+
+static int smb1351_enable_volatile_writes(struct smb1351_charger *chip)
+{
+	int rc;
+
+	rc = smb1351_masked_write(chip, CMD_I2C_REG, CMD_BQ_CFG_ACCESS_BIT,
+							CMD_BQ_CFG_ACCESS_BIT);
+	if (rc)
+		pr_err("Couldn't write CMD_BQ_CFG_ACCESS_BIT rc=%d\n", rc);
+
+	return rc;
+}
+
+static int smb1351_usb_suspend(struct smb1351_charger *chip, int reason,
+					bool suspend)
+{
+	int rc = 0;
+	int suspended;
+
+	suspended = chip->usb_suspended_status;
+
+	pr_debug("reason = %d requested_suspend = %d suspended_status = %d\n",
+						reason, suspend, suspended);
+
+	if (suspend == false)
+		suspended &= ~reason;
+	else
+		suspended |= reason;
+
+	pr_debug("new suspended_status = %d\n", suspended);
+
+	rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG,
+				CMD_SUSPEND_MODE_BIT,
+				suspended ? CMD_SUSPEND_MODE_BIT : 0);
+	if (rc)
+		pr_err("Couldn't suspend rc = %d\n", rc);
+	else
+		chip->usb_suspended_status = suspended;
+
+	return rc;
+}
+
+static int smb1351_battchg_disable(struct smb1351_charger *chip,
+					int reason, int disable)
+{
+	int rc = 0;
+	int disabled;
+
+	if (chip->chg_autonomous_mode) {
+		pr_debug("Charger in autonomous mode\n");
+		return 0;
+	}
+
+	disabled = chip->battchg_disabled_status;
+
+	pr_debug("reason = %d requested_disable = %d disabled_status = %d\n",
+						reason, disable, disabled);
+	if (disable == true)
+		disabled |= reason;
+	else
+		disabled &= ~reason;
+
+	pr_debug("new disabled_status = %d\n", disabled);
+
+	rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_CHG_EN_BIT,
+					disabled ? 0 : CMD_CHG_ENABLE);
+	if (rc)
+		pr_err("Couldn't %s charging rc=%d\n",
+					disable ? "disable" : "enable", rc);
+	else
+		chip->battchg_disabled_status = disabled;
+
+	return rc;
+}
+
+static int smb1351_fastchg_current_set(struct smb1351_charger *chip,
+					unsigned int fastchg_current)
+{
+	int i, rc;
+	bool is_pre_chg = false;
+
+
+	if ((fastchg_current < SMB1351_CHG_PRE_MIN_MA) ||
+		(fastchg_current > SMB1351_CHG_FAST_MAX_MA)) {
+		pr_err("bad pre_fastchg current mA=%d asked to set\n",
+					fastchg_current);
+		return -EINVAL;
+	}
+
+	/*
+	 * fast chg current could not support less than 1000mA
+	 * use pre chg to instead for the parallel charging
+	 */
+	if (fastchg_current < SMB1351_CHG_FAST_MIN_MA) {
+		is_pre_chg = true;
+		pr_debug("is_pre_chg true, current is %d\n", fastchg_current);
+	}
+
+	if (is_pre_chg) {
+		/* set prechg current */
+		for (i = ARRAY_SIZE(pre_chg_current) - 1; i >= 0; i--) {
+			if (pre_chg_current[i] <= fastchg_current)
+				break;
+		}
+		if (i < 0)
+			i = 0;
+		chip->fastchg_current_max_ma = pre_chg_current[i];
+		pr_debug("prechg setting %02x\n", i);
+
+		i = i << SMB1351_CHG_PRE_SHIFT;
+
+		rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+				PRECHG_CURRENT_MASK, i);
+		if (rc)
+			pr_err("Couldn't write CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+									rc);
+
+		return smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+				PRECHG_TO_FASTCHG_BIT, PRECHG_TO_FASTCHG_BIT);
+	} else {
+		if (chip->version == SMB_UNKNOWN)
+			return -EINVAL;
+
+		/* SMB1350 supports FCC upto 2600 mA */
+		if (chip->version == SMB1350 && fastchg_current > 2600)
+			fastchg_current = 2600;
+
+		/* set fastchg current */
+		for (i = ARRAY_SIZE(fast_chg_current) - 1; i >= 0; i--) {
+			if (fast_chg_current[i] <= fastchg_current)
+				break;
+		}
+		if (i < 0)
+			i = 0;
+		chip->fastchg_current_max_ma = fast_chg_current[i];
+
+		i = i << SMB1351_CHG_FAST_SHIFT;
+		pr_debug("fastchg limit=%d setting %02x\n",
+					chip->fastchg_current_max_ma, i);
+
+		/* make sure pre chg mode is disabled */
+		rc = smb1351_masked_write(chip, VARIOUS_FUNC_2_REG,
+					PRECHG_TO_FASTCHG_BIT, 0);
+		if (rc)
+			pr_err("Couldn't write VARIOUS_FUNC_2_REG rc=%d\n", rc);
+
+		return smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+					FAST_CHG_CURRENT_MASK, i);
+	}
+}
+
+#define MIN_FLOAT_MV		3500
+#define MAX_FLOAT_MV		4500
+#define VFLOAT_STEP_MV		20
+
+static int smb1351_float_voltage_set(struct smb1351_charger *chip,
+								int vfloat_mv)
+{
+	u8 temp;
+
+	if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+		pr_err("bad float voltage mv =%d asked to set\n", vfloat_mv);
+		return -EINVAL;
+	}
+
+	temp = (vfloat_mv - MIN_FLOAT_MV) / VFLOAT_STEP_MV;
+
+	return smb1351_masked_write(chip, VFLOAT_REG, VFLOAT_MASK, temp);
+}
+
+static int smb1351_iterm_set(struct smb1351_charger *chip, int iterm_ma)
+{
+	int rc;
+	u8 reg;
+
+	if (iterm_ma <= 200)
+		reg = CHG_ITERM_200MA;
+	else if (iterm_ma <= 300)
+		reg = CHG_ITERM_300MA;
+	else if (iterm_ma <= 400)
+		reg = CHG_ITERM_400MA;
+	else if (iterm_ma <= 500)
+		reg = CHG_ITERM_500MA;
+	else if (iterm_ma <= 600)
+		reg = CHG_ITERM_600MA;
+	else
+		reg = CHG_ITERM_700MA;
+
+	rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG,
+				ITERM_MASK, reg);
+	if (rc) {
+		pr_err("Couldn't set iterm rc = %d\n", rc);
+		return rc;
+	}
+	/* enable the iterm */
+	rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+				ITERM_EN_BIT, ITERM_ENABLE);
+	if (rc) {
+		pr_err("Couldn't enable iterm rc = %d\n", rc);
+		return rc;
+	}
+	return 0;
+}
+
+static int smb1351_chg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+	rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT,
+							CMD_OTG_EN_BIT);
+	if (rc)
+		pr_err("Couldn't enable  OTG mode rc=%d\n", rc);
+	return rc;
+}
+
+static int smb1351_chg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+	rc = smb1351_masked_write(chip, CMD_CHG_REG, CMD_OTG_EN_BIT, 0);
+	if (rc)
+		pr_err("Couldn't disable OTG mode rc=%d\n", rc);
+	return rc;
+}
+
+static int smb1351_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	u8 reg = 0;
+	struct smb1351_charger *chip = rdev_get_drvdata(rdev);
+
+	rc = smb1351_read_reg(chip, CMD_CHG_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read OTG enable bit rc=%d\n", rc);
+		return rc;
+	}
+
+	return (reg & CMD_OTG_EN_BIT) ? 1 : 0;
+}
+
+struct regulator_ops smb1351_chg_otg_reg_ops = {
+	.enable		= smb1351_chg_otg_regulator_enable,
+	.disable	= smb1351_chg_otg_regulator_disable,
+	.is_enabled	= smb1351_chg_otg_regulator_is_enable,
+};
+
+static int smb1351_regulator_init(struct smb1351_charger *chip)
+{
+	int rc = 0;
+	struct regulator_config cfg = {};
+
+	chip->otg_vreg.rdesc.owner = THIS_MODULE;
+	chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+	chip->otg_vreg.rdesc.ops = &smb1351_chg_otg_reg_ops;
+	chip->otg_vreg.rdesc.name =
+		chip->dev->of_node->name;
+	chip->otg_vreg.rdesc.of_match =
+		chip->dev->of_node->name;
+
+	cfg.dev = chip->dev;
+	cfg.driver_data = chip;
+
+	chip->otg_vreg.rdev = regulator_register(
+					&chip->otg_vreg.rdesc, &cfg);
+	if (IS_ERR(chip->otg_vreg.rdev)) {
+		rc = PTR_ERR(chip->otg_vreg.rdev);
+		chip->otg_vreg.rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			pr_err("OTG reg failed, rc=%d\n", rc);
+	}
+	return rc;
+}
+
+static int smb_chip_get_version(struct smb1351_charger *chip)
+{
+	u8 ver;
+	int rc = 0;
+
+	if (chip->version == SMB_UNKNOWN) {
+		rc = smb1351_read_reg(chip, VERSION_REG, &ver);
+		if (rc) {
+			pr_err("Couldn't read version rc=%d\n", rc);
+			return rc;
+		}
+
+		/* If bit 1 is set, it is SMB1350 */
+		if (ver & VERSION_MASK)
+			chip->version = SMB1350;
+		else
+			chip->version = SMB1351;
+	}
+
+	return rc;
+}
+
+static int smb1351_hw_init(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg = 0, mask = 0;
+
+	/* configure smb_pinctrl to enable irqs */
+	if (chip->pinctrl_state_name) {
+		chip->smb_pinctrl = pinctrl_get_select(chip->dev,
+						chip->pinctrl_state_name);
+		if (IS_ERR(chip->smb_pinctrl)) {
+			pr_err("Could not get/set %s pinctrl state rc = %ld\n",
+						chip->pinctrl_state_name,
+						PTR_ERR(chip->smb_pinctrl));
+			return PTR_ERR(chip->smb_pinctrl);
+		}
+	}
+
+	/*
+	 * If the charger is pre-configured for autonomous operation,
+	 * do not apply additional settings
+	 */
+	if (chip->chg_autonomous_mode) {
+		pr_debug("Charger configured for autonomous mode\n");
+		return 0;
+	}
+
+	rc = smb_chip_get_version(chip);
+	if (rc) {
+		pr_err("Couldn't get version rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = smb1351_enable_volatile_writes(chip);
+	if (rc) {
+		pr_err("Couldn't configure volatile writes rc=%d\n", rc);
+		return rc;
+	}
+
+	/* setup battery missing source */
+	reg = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+	mask = BATT_MISSING_THERM_PIN_SOURCE_BIT;
+	rc = smb1351_masked_write(chip, HVDCP_BATT_MISSING_CTRL_REG,
+								mask, reg);
+	if (rc) {
+		pr_err("Couldn't set HVDCP_BATT_MISSING_CTRL_REG rc=%d\n", rc);
+		return rc;
+	}
+	/* setup defaults for CHG_PIN_EN_CTRL_REG */
+	reg = EN_BY_I2C_0_DISABLE | USBCS_CTRL_BY_I2C | CHG_ERR_BIT |
+		APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+	mask = EN_PIN_CTRL_MASK | USBCS_CTRL_BIT | CHG_ERR_BIT |
+		APSD_DONE_BIT | LED_BLINK_FUNC_BIT;
+	rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG, mask, reg);
+	if (rc) {
+		pr_err("Couldn't set CHG_PIN_EN_CTRL_REG rc=%d\n", rc);
+		return rc;
+	}
+	/* setup USB 2.0/3.0 detection and USB 500/100 command polarity */
+	reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+	mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+	rc = smb1351_masked_write(chip, CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+	if (rc) {
+		pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n", rc);
+		return rc;
+	}
+	/* setup USB suspend, AICL and APSD  */
+	reg = SUSPEND_MODE_CTRL_BY_I2C | AICL_EN_BIT;
+	if (!chip->disable_apsd)
+		reg |= APSD_EN_BIT;
+	mask = SUSPEND_MODE_CTRL_BIT | AICL_EN_BIT | APSD_EN_BIT;
+	rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG, mask, reg);
+	if (rc) {
+		pr_err("Couldn't set VARIOUS_FUNC_REG rc=%d\n",	rc);
+		return rc;
+	}
+	/* Fault and Status IRQ configuration */
+	reg = HOT_COLD_HARD_LIMIT_BIT | HOT_COLD_SOFT_LIMIT_BIT
+		| INPUT_OVLO_BIT | INPUT_UVLO_BIT | AICL_DONE_FAIL_BIT;
+	rc = smb1351_write_reg(chip, FAULT_INT_REG, reg);
+	if (rc) {
+		pr_err("Couldn't set FAULT_INT_REG rc=%d\n", rc);
+		return rc;
+	}
+	reg = CHG_OR_PRECHG_TIMEOUT_BIT | BATT_OVP_BIT |
+		FAST_TERM_TAPER_RECHG_INHIBIT_BIT |
+		BATT_MISSING_BIT | BATT_LOW_BIT;
+	rc = smb1351_write_reg(chip, STATUS_INT_REG, reg);
+	if (rc) {
+		pr_err("Couldn't set STATUS_INT_REG rc=%d\n", rc);
+		return rc;
+	}
+	/* setup THERM Monitor */
+	if (!chip->using_pmic_therm) {
+		rc = smb1351_masked_write(chip, THERM_A_CTRL_REG,
+			THERM_MONITOR_BIT, THERM_MONITOR_EN);
+		if (rc) {
+			pr_err("Couldn't set THERM_A_CTRL_REG rc=%d\n",	rc);
+			return rc;
+		}
+	}
+	/* set the fast charge current limit */
+	rc = smb1351_fastchg_current_set(chip,
+			chip->target_fastchg_current_max_ma);
+	if (rc) {
+		pr_err("Couldn't set fastchg current rc=%d\n", rc);
+		return rc;
+	}
+
+	/* set the float voltage */
+	if (chip->vfloat_mv != -EINVAL) {
+		rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+		if (rc) {
+			pr_err("Couldn't set float voltage rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	/* set iterm */
+	if (chip->iterm_ma != -EINVAL) {
+		if (chip->iterm_disabled) {
+			pr_err("Error: Both iterm_disabled and iterm_ma set\n");
+			return -EINVAL;
+		}
+		rc = smb1351_iterm_set(chip, chip->iterm_ma);
+		if (rc) {
+			pr_err("Couldn't set iterm rc = %d\n", rc);
+			return rc;
+		}
+	} else  if (chip->iterm_disabled) {
+		rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+					ITERM_EN_BIT, ITERM_DISABLE);
+		if (rc) {
+			pr_err("Couldn't set iterm rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	/* set recharge-threshold */
+	if (chip->recharge_mv != -EINVAL) {
+		if (chip->recharge_disabled) {
+			pr_err("Error: Both recharge_disabled and recharge_mv set\n");
+			return -EINVAL;
+		}
+
+		reg = AUTO_RECHG_ENABLE;
+		if (chip->recharge_mv > 50)
+			reg |= AUTO_RECHG_TH_100MV;
+		else
+			reg |= AUTO_RECHG_TH_50MV;
+
+		rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+				AUTO_RECHG_BIT |
+				AUTO_RECHG_TH_BIT, reg);
+		if (rc) {
+			pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+			return rc;
+		}
+	} else if (chip->recharge_disabled) {
+		rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+				AUTO_RECHG_BIT,
+				AUTO_RECHG_DISABLE);
+		if (rc) {
+			pr_err("Couldn't disable auto-rechg rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	/* enable/disable charging by suspending usb */
+	rc = smb1351_usb_suspend(chip, USER, chip->usb_suspended_status);
+	if (rc) {
+		pr_err("Unable to %s battery charging. rc=%d\n",
+			chip->usb_suspended_status ? "disable" : "enable",
+									rc);
+	}
+
+	return rc;
+}
+
+static enum power_supply_property smb1351_battery_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_TEMP,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+};
+
+static int smb1351_get_prop_batt_status(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg = 0;
+
+	if (chip->batt_full)
+		return POWER_SUPPLY_STATUS_FULL;
+
+	rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+		return POWER_SUPPLY_STATUS_UNKNOWN;
+	}
+
+	pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+	if (reg & STATUS_HOLD_OFF_BIT)
+		return POWER_SUPPLY_STATUS_NOT_CHARGING;
+
+	if (reg & STATUS_CHG_MASK)
+		return POWER_SUPPLY_STATUS_CHARGING;
+
+	return POWER_SUPPLY_STATUS_DISCHARGING;
+}
+
+static int smb1351_get_prop_batt_present(struct smb1351_charger *chip)
+{
+	return !chip->battery_missing;
+}
+
+static int smb1351_get_prop_batt_capacity(struct smb1351_charger *chip)
+{
+	union power_supply_propval ret = {0, };
+
+	if (chip->fake_battery_soc >= 0)
+		return chip->fake_battery_soc;
+
+	if (chip->bms_psy) {
+		power_supply_get_property(chip->bms_psy,
+				POWER_SUPPLY_PROP_CAPACITY, &ret);
+		return ret.intval;
+	}
+	pr_debug("return DEFAULT_BATT_CAPACITY\n");
+	return DEFAULT_BATT_CAPACITY;
+}
+
+static int smb1351_get_prop_batt_temp(struct smb1351_charger *chip)
+{
+	union power_supply_propval ret = {0, };
+	int rc = 0;
+	struct qpnp_vadc_result results;
+
+	if (chip->bms_psy) {
+		power_supply_get_property(chip->bms_psy,
+				POWER_SUPPLY_PROP_TEMP, &ret);
+		return ret.intval;
+	}
+	if (chip->vadc_dev) {
+		rc = qpnp_vadc_read(chip->vadc_dev,
+				LR_MUX1_BATT_THERM, &results);
+		if (rc)
+			pr_debug("Unable to read adc batt temp rc=%d\n", rc);
+		else
+			return (int)results.physical;
+	}
+
+	pr_debug("return default temperature\n");
+	return DEFAULT_BATT_TEMP;
+}
+
+static int smb1351_get_prop_charge_type(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg = 0;
+
+	rc = smb1351_read_reg(chip, STATUS_4_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read STATUS_4 rc = %d\n", rc);
+		return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+	}
+
+	pr_debug("STATUS_4_REG(0x3A)=%x\n", reg);
+
+	reg &= STATUS_CHG_MASK;
+
+	if (reg == STATUS_FAST_CHARGING)
+		return POWER_SUPPLY_CHARGE_TYPE_FAST;
+	else if (reg == STATUS_TAPER_CHARGING)
+		return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+	else if (reg == STATUS_PRE_CHARGING)
+		return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+	else
+		return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int smb1351_get_prop_batt_health(struct smb1351_charger *chip)
+{
+	union power_supply_propval ret = {0, };
+
+	if (chip->batt_hot)
+		ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+	else if (chip->batt_cold)
+		ret.intval = POWER_SUPPLY_HEALTH_COLD;
+	else if (chip->batt_warm)
+		ret.intval = POWER_SUPPLY_HEALTH_WARM;
+	else if (chip->batt_cool)
+		ret.intval = POWER_SUPPLY_HEALTH_COOL;
+	else
+		ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+	return ret.intval;
+}
+
+static int smb1351_set_usb_chg_current(struct smb1351_charger *chip,
+							int current_ma)
+{
+	int i, rc = 0;
+	u8 reg = 0, mask = 0;
+
+	pr_debug("USB current_ma = %d\n", current_ma);
+
+	if (chip->chg_autonomous_mode) {
+		pr_debug("Charger in autonomous mode\n");
+		return 0;
+	}
+
+	/* set suspend bit when urrent_ma <= 2 */
+	if (current_ma <= SUSPEND_CURRENT_MA) {
+		smb1351_usb_suspend(chip, CURRENT, true);
+		pr_debug("USB suspend\n");
+		return 0;
+	}
+
+	if (current_ma > SUSPEND_CURRENT_MA &&
+			current_ma < USB2_MIN_CURRENT_MA)
+		current_ma = USB2_MIN_CURRENT_MA;
+
+	if (current_ma == USB2_MIN_CURRENT_MA) {
+		/* USB 2.0 - 100mA */
+		reg = CMD_USB_2_MODE | CMD_USB_100_MODE;
+	} else if (current_ma == USB3_MIN_CURRENT_MA) {
+		/* USB 3.0 - 150mA */
+		reg = CMD_USB_3_MODE | CMD_USB_100_MODE;
+	} else if (current_ma == USB2_MAX_CURRENT_MA) {
+		/* USB 2.0 - 500mA */
+		reg = CMD_USB_2_MODE | CMD_USB_500_MODE;
+	} else if (current_ma == USB3_MAX_CURRENT_MA) {
+		/* USB 3.0 - 900mA */
+		reg = CMD_USB_3_MODE | CMD_USB_500_MODE;
+	} else if (current_ma > USB2_MAX_CURRENT_MA) {
+		/* HC mode  - if none of the above */
+		reg = CMD_USB_AC_MODE;
+
+		for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) {
+			if (usb_chg_current[i] <= current_ma)
+				break;
+		}
+		if (i < 0)
+			i = 0;
+		rc = smb1351_masked_write(chip, CHG_CURRENT_CTRL_REG,
+						AC_INPUT_CURRENT_LIMIT_MASK, i);
+		if (rc) {
+			pr_err("Couldn't set input mA rc=%d\n", rc);
+			return rc;
+		}
+	}
+	/* control input current mode by command */
+	reg |= CMD_INPUT_CURRENT_MODE_CMD;
+	mask = CMD_INPUT_CURRENT_MODE_BIT | CMD_USB_2_3_SEL_BIT |
+		CMD_USB_1_5_AC_CTRL_MASK;
+	rc = smb1351_masked_write(chip, CMD_INPUT_LIMIT_REG, mask, reg);
+	if (rc) {
+		pr_err("Couldn't set charging mode rc = %d\n", rc);
+		return rc;
+	}
+
+	/* unset the suspend bit here */
+	smb1351_usb_suspend(chip, CURRENT, false);
+
+	return rc;
+}
+
+static int smb1351_batt_property_is_writeable(struct power_supply *psy,
+					enum power_supply_property psp)
+{
+	switch (psp) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+	case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+	case POWER_SUPPLY_PROP_CAPACITY:
+		return 1;
+	default:
+		break;
+	}
+	return 0;
+}
+
+static int smb1351_battery_set_property(struct power_supply *psy,
+					enum power_supply_property prop,
+					const union power_supply_propval *val)
+{
+	int rc;
+	struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!chip->bms_controlled_charging)
+			return -EINVAL;
+		switch (val->intval) {
+		case POWER_SUPPLY_STATUS_FULL:
+			rc = smb1351_battchg_disable(chip, SOC, true);
+			if (rc) {
+				pr_err("Couldn't disable charging  rc = %d\n",
+									rc);
+			} else {
+				chip->batt_full = true;
+				pr_debug("status = FULL, batt_full = %d\n",
+							chip->batt_full);
+			}
+			break;
+		case POWER_SUPPLY_STATUS_DISCHARGING:
+			chip->batt_full = false;
+			power_supply_changed(chip->batt_psy);
+			pr_debug("status = DISCHARGING, batt_full = %d\n",
+							chip->batt_full);
+			break;
+		case POWER_SUPPLY_STATUS_CHARGING:
+			rc = smb1351_battchg_disable(chip, SOC, false);
+			if (rc) {
+				pr_err("Couldn't enable charging rc = %d\n",
+									rc);
+			} else {
+				chip->batt_full = false;
+				pr_debug("status = CHARGING, batt_full = %d\n",
+							chip->batt_full);
+			}
+			break;
+		default:
+			return -EINVAL;
+		}
+		break;
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		smb1351_usb_suspend(chip, USER, !val->intval);
+		break;
+	case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+		smb1351_battchg_disable(chip, USER, !val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		chip->fake_battery_soc = val->intval;
+		power_supply_changed(chip->batt_psy);
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int smb1351_battery_get_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = smb1351_get_prop_batt_status(chip);
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = smb1351_get_prop_batt_present(chip);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = smb1351_get_prop_batt_capacity(chip);
+		break;
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		val->intval = !chip->usb_suspended_status;
+		break;
+	case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+		val->intval = !chip->battchg_disabled_status;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		val->intval = smb1351_get_prop_charge_type(chip);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = smb1351_get_prop_batt_health(chip);
+		break;
+	case POWER_SUPPLY_PROP_TEMP:
+		val->intval = smb1351_get_prop_batt_temp(chip);
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = "smb1351";
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static enum power_supply_property smb1351_parallel_properties[] = {
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_PARALLEL_MODE,
+};
+
+static int smb1351_parallel_set_chg_suspend(struct smb1351_charger *chip,
+						int suspend)
+{
+	int rc;
+	u8 reg, mask = 0;
+
+	if (chip->parallel_charger_suspended == suspend) {
+		pr_debug("Skip same state request suspended = %d suspend=%d\n",
+				chip->parallel_charger_suspended, !suspend);
+		return 0;
+	}
+
+	if (!suspend) {
+		rc = smb_chip_get_version(chip);
+		if (rc) {
+			pr_err("Couldn't get version rc = %d\n", rc);
+			return rc;
+		}
+
+		rc = smb1351_enable_volatile_writes(chip);
+		if (rc) {
+			pr_err("Couldn't configure for volatile rc = %d\n", rc);
+			return rc;
+		}
+
+		/* set the float voltage */
+		if (chip->vfloat_mv != -EINVAL) {
+			rc = smb1351_float_voltage_set(chip, chip->vfloat_mv);
+			if (rc) {
+				pr_err("Couldn't set float voltage rc = %d\n",
+									rc);
+				return rc;
+			}
+		}
+
+		/* set recharge-threshold and enable auto recharge */
+		if (chip->recharge_mv != -EINVAL) {
+			reg = AUTO_RECHG_ENABLE;
+			if (chip->recharge_mv > 50)
+				reg |= AUTO_RECHG_TH_100MV;
+			else
+				reg |= AUTO_RECHG_TH_50MV;
+
+			rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+					AUTO_RECHG_BIT |
+					AUTO_RECHG_TH_BIT, reg);
+			if (rc) {
+				pr_err("Couldn't set rechg-cfg rc = %d\n", rc);
+				return rc;
+			}
+		}
+
+		/* control USB suspend via command bits */
+		rc = smb1351_masked_write(chip, VARIOUS_FUNC_REG,
+					APSD_EN_BIT | SUSPEND_MODE_CTRL_BIT,
+						SUSPEND_MODE_CTRL_BY_I2C);
+		if (rc) {
+			pr_err("Couldn't set USB suspend rc=%d\n", rc);
+			return rc;
+		}
+
+		/*
+		 * When present is being set force USB suspend, start charging
+		 * only when POWER_SUPPLY_PROP_CURRENT_MAX is set.
+		 */
+		rc = smb1351_usb_suspend(chip, CURRENT, true);
+		if (rc) {
+			pr_err("failed to suspend rc=%d\n", rc);
+			return rc;
+		}
+		chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+
+		/* set chg en by pin active low  */
+		reg = chip->parallel_pin_polarity_setting | USBCS_CTRL_BY_I2C;
+		rc = smb1351_masked_write(chip, CHG_PIN_EN_CTRL_REG,
+					EN_PIN_CTRL_MASK | USBCS_CTRL_BIT, reg);
+		if (rc) {
+			pr_err("Couldn't set en pin rc=%d\n", rc);
+			return rc;
+		}
+
+		/*
+		 * setup USB 2.0/3.0 detection and USB 500/100
+		 * command polarity
+		 */
+		reg = USB_2_3_MODE_SEL_BY_I2C | USB_CMD_POLARITY_500_1_100_0;
+		mask = USB_2_3_MODE_SEL_BIT | USB_5_1_CMD_POLARITY_BIT;
+		rc = smb1351_masked_write(chip,
+				CHG_OTH_CURRENT_CTRL_REG, mask, reg);
+		if (rc) {
+			pr_err("Couldn't set CHG_OTH_CURRENT_CTRL_REG rc=%d\n",
+					rc);
+			return rc;
+		}
+
+		rc = smb1351_fastchg_current_set(chip,
+					chip->target_fastchg_current_max_ma);
+		if (rc) {
+			pr_err("Couldn't set fastchg current rc=%d\n", rc);
+			return rc;
+		}
+		chip->parallel_charger_suspended = false;
+	} else {
+		rc = smb1351_usb_suspend(chip, CURRENT, true);
+		if (rc)
+			pr_debug("failed to suspend rc=%d\n", rc);
+
+		chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+		chip->parallel_charger_suspended = true;
+	}
+
+	return 0;
+}
+
+static int smb1351_get_closest_usb_setpoint(int val)
+{
+	int i;
+
+	for (i = ARRAY_SIZE(usb_chg_current) - 1; i >= 0; i--) {
+		if (usb_chg_current[i] <= val)
+			break;
+	}
+	if (i < 0)
+		i = 0;
+
+	if (i >= ARRAY_SIZE(usb_chg_current) - 1)
+		return ARRAY_SIZE(usb_chg_current) - 1;
+
+	/* check what is closer, i or i + 1 */
+	if (abs(usb_chg_current[i] - val) < abs(usb_chg_current[i + 1] - val))
+		return i;
+	else
+		return i + 1;
+}
+
+static bool smb1351_is_input_current_limited(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb1351_read_reg(chip, IRQ_H_REG, &reg);
+	if (rc) {
+		pr_err("Failed to read IRQ_H_REG for ICL status: %d\n", rc);
+		return false;
+	}
+
+	return !!(reg & IRQ_IC_LIMIT_STATUS_BIT);
+}
+
+static bool smb1351_is_usb_present(struct smb1351_charger *chip)
+{
+	int rc;
+	union power_supply_propval val = {0, };
+
+	if (!chip->usb_psy)
+		chip->usb_psy = power_supply_get_by_name("usb");
+	if (!chip->usb_psy) {
+		pr_err("USB psy not found\n");
+		return false;
+	}
+
+	rc = power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_ONLINE, &val);
+	if (rc < 0) {
+		pr_err("Failed to get present property rc=%d\n", rc);
+		return false;
+	}
+
+	if (val.intval)
+		return true;
+
+	return false;
+}
+
+static int smb1351_parallel_set_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       const union power_supply_propval *val)
+{
+	int rc = 0, index;
+	struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		/*
+		 *CHG EN is controlled by pin in the parallel charging.
+		 *Use suspend if disable charging by command.
+		 */
+		if (!chip->parallel_charger_suspended)
+			rc = smb1351_usb_suspend(chip, USER, !val->intval);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smb1351_parallel_set_chg_suspend(chip, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		chip->target_fastchg_current_max_ma =
+						val->intval / 1000;
+		if (!chip->parallel_charger_suspended)
+			rc = smb1351_fastchg_current_set(chip,
+					chip->target_fastchg_current_max_ma);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		index = smb1351_get_closest_usb_setpoint(val->intval / 1000);
+		chip->usb_psy_ma = usb_chg_current[index];
+		if (!chip->parallel_charger_suspended)
+			rc = smb1351_set_usb_chg_current(chip,
+						chip->usb_psy_ma);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		chip->vfloat_mv = val->intval / 1000;
+		if (!chip->parallel_charger_suspended)
+			rc = smb1351_float_voltage_set(chip, val->intval);
+		break;
+	default:
+		return -EINVAL;
+	}
+	return rc;
+}
+
+static int smb1351_parallel_is_writeable(struct power_supply *psy,
+				       enum power_supply_property prop)
+{
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		return 1;
+	default:
+		return 0;
+	}
+}
+
+static int smb1351_parallel_get_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		val->intval = !chip->usb_suspended_status;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		if (!chip->parallel_charger_suspended)
+			val->intval = chip->usb_psy_ma * 1000;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		if (!chip->parallel_charger_suspended)
+			val->intval = chip->vfloat_mv;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		val->intval = POWER_SUPPLY_CHARGE_TYPE_NONE;
+		/* Check if SMB1351 is present */
+		if (smb1351_is_usb_present(chip)) {
+			val->intval = smb1351_get_prop_charge_type(chip);
+			if (val->intval == POWER_SUPPLY_CHARGE_TYPE_UNKNOWN) {
+				pr_debug("Failed to charge type, charger may be absent\n");
+				return -ENODEV;
+			}
+		}
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		if (!chip->parallel_charger_suspended)
+			val->intval = chip->fastchg_current_max_ma * 1000;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!chip->parallel_charger_suspended)
+			val->intval = smb1351_get_prop_batt_status(chip);
+		else
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+		if (!chip->parallel_charger_suspended)
+			val->intval =
+				smb1351_is_input_current_limited(chip) ? 1 : 0;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_PARALLEL_MODE:
+		val->intval = chip->parallel_mode;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void smb1351_chg_set_appropriate_battery_current(
+				struct smb1351_charger *chip)
+{
+	int rc;
+	unsigned int current_max = chip->target_fastchg_current_max_ma;
+
+	if (chip->batt_cool)
+		current_max = min(current_max, chip->batt_cool_ma);
+	if (chip->batt_warm)
+		current_max = min(current_max, chip->batt_warm_ma);
+
+	pr_debug("setting %dmA", current_max);
+
+	rc = smb1351_fastchg_current_set(chip, current_max);
+	if (rc)
+		pr_err("Couldn't set charging current rc = %d\n", rc);
+}
+
+static void smb1351_chg_set_appropriate_vddmax(struct smb1351_charger *chip)
+{
+	int rc;
+	unsigned int vddmax = chip->vfloat_mv;
+
+	if (chip->batt_cool)
+		vddmax = min(vddmax, chip->batt_cool_mv);
+	if (chip->batt_warm)
+		vddmax = min(vddmax, chip->batt_warm_mv);
+
+	pr_debug("setting %dmV\n", vddmax);
+
+	rc = smb1351_float_voltage_set(chip, vddmax);
+	if (rc)
+		pr_err("Couldn't set float voltage rc = %d\n", rc);
+}
+
+static void smb1351_chg_ctrl_in_jeita(struct smb1351_charger *chip)
+{
+	union power_supply_propval ret = {0, };
+	int rc;
+
+	/* enable the iterm to prevent the reverse boost */
+	if (chip->iterm_disabled) {
+		if (chip->batt_cool || chip->batt_warm) {
+			rc = smb1351_iterm_set(chip, 100);
+			pr_debug("set the iterm due to JEITA\n");
+		} else {
+			rc = smb1351_masked_write(chip, CHG_CTRL_REG,
+						ITERM_EN_BIT, ITERM_DISABLE);
+			pr_debug("disable the iterm when exits warm/cool\n");
+		}
+		if (rc) {
+			pr_err("Couldn't set iterm rc = %d\n", rc);
+			return;
+		}
+	}
+	/*
+	 * When JEITA back to normal, the charging maybe disabled due to
+	 * the current termination. So re-enable the charging if the soc
+	 * is less than 100 in the normal mode. A 200ms delay is required
+	 * before the disabe and enable operation.
+	 */
+	if (chip->bms_psy) {
+		rc = power_supply_get_property(chip->bms_psy,
+				POWER_SUPPLY_PROP_CAPACITY, &ret);
+		if (rc) {
+			pr_err("Couldn't read the bms capacity rc = %d\n",
+									rc);
+			return;
+		}
+		if (!chip->batt_cool && !chip->batt_warm
+				&& !chip->batt_cold && !chip->batt_hot
+				&& ret.intval < 100) {
+			rc = smb1351_battchg_disable(chip, THERMAL, true);
+			if (rc) {
+				pr_err("Couldn't disable charging rc = %d\n",
+									rc);
+				return;
+			}
+			/* delay for resetting the charging */
+			msleep(200);
+			rc = smb1351_battchg_disable(chip, THERMAL, false);
+			if (rc) {
+				pr_err("Couldn't enable charging rc = %d\n",
+									rc);
+				return;
+			}
+
+			chip->batt_full = false;
+			pr_debug("re-enable charging, batt_full = %d\n",
+						chip->batt_full);
+			power_supply_changed(chip->batt_psy);
+		}
+	}
+}
+
+#define HYSTERESIS_DECIDEGC 20
+static void smb1351_chg_adc_notification(enum qpnp_tm_state state, void *ctx)
+{
+	struct smb1351_charger *chip = ctx;
+	struct battery_status *cur = NULL;
+	int temp;
+
+	if (state >= ADC_TM_STATE_NUM) {
+		pr_err("invalid state parameter %d\n", state);
+		return;
+	}
+
+	temp = smb1351_get_prop_batt_temp(chip);
+
+	pr_debug("temp = %d state = %s\n", temp,
+				state == ADC_TM_WARM_STATE ? "hot" : "cold");
+
+	/* reset the adc status request */
+	chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+
+	/* temp from low to high */
+	if (state == ADC_TM_WARM_STATE) {
+		/* WARM -> HOT */
+		if (temp >= chip->batt_hot_decidegc) {
+			cur = &batt_s[BATT_HOT];
+			chip->adc_param.low_temp =
+				chip->batt_hot_decidegc - HYSTERESIS_DECIDEGC;
+			chip->adc_param.state_request =	ADC_TM_COOL_THR_ENABLE;
+		/* NORMAL -> WARM */
+		} else if (temp >= chip->batt_warm_decidegc &&
+					chip->jeita_supported) {
+			cur = &batt_s[BATT_WARM];
+			chip->adc_param.low_temp =
+				chip->batt_warm_decidegc - HYSTERESIS_DECIDEGC;
+			chip->adc_param.high_temp = chip->batt_hot_decidegc;
+		/* COOL -> NORMAL */
+		} else if (temp >= chip->batt_cool_decidegc &&
+					chip->jeita_supported) {
+			cur = &batt_s[BATT_NORMAL];
+			chip->adc_param.low_temp =
+				chip->batt_cool_decidegc - HYSTERESIS_DECIDEGC;
+			chip->adc_param.high_temp = chip->batt_warm_decidegc;
+		/* COLD -> COOL */
+		} else if (temp >= chip->batt_cold_decidegc) {
+			cur = &batt_s[BATT_COOL];
+			chip->adc_param.low_temp =
+				chip->batt_cold_decidegc - HYSTERESIS_DECIDEGC;
+			if (chip->jeita_supported)
+				chip->adc_param.high_temp =
+						chip->batt_cool_decidegc;
+			else
+				chip->adc_param.high_temp =
+						chip->batt_hot_decidegc;
+		/* MISSING -> COLD */
+		} else if (temp >= chip->batt_missing_decidegc) {
+			cur = &batt_s[BATT_COLD];
+			chip->adc_param.high_temp = chip->batt_cold_decidegc;
+			chip->adc_param.low_temp = chip->batt_missing_decidegc
+							- HYSTERESIS_DECIDEGC;
+		}
+	/* temp from high to low */
+	} else {
+		/* COLD -> MISSING */
+		if (temp <= chip->batt_missing_decidegc) {
+			cur = &batt_s[BATT_MISSING];
+			chip->adc_param.high_temp = chip->batt_missing_decidegc
+							+ HYSTERESIS_DECIDEGC;
+			chip->adc_param.state_request = ADC_TM_WARM_THR_ENABLE;
+		/* COOL -> COLD */
+		} else if (temp <= chip->batt_cold_decidegc) {
+			cur = &batt_s[BATT_COLD];
+			chip->adc_param.high_temp =
+				chip->batt_cold_decidegc + HYSTERESIS_DECIDEGC;
+			/* add low_temp to enable batt present check */
+			chip->adc_param.low_temp = chip->batt_missing_decidegc;
+		/* NORMAL -> COOL */
+		} else if (temp <= chip->batt_cool_decidegc &&
+					chip->jeita_supported) {
+			cur = &batt_s[BATT_COOL];
+			chip->adc_param.high_temp =
+				chip->batt_cool_decidegc + HYSTERESIS_DECIDEGC;
+			chip->adc_param.low_temp = chip->batt_cold_decidegc;
+		/* WARM -> NORMAL */
+		} else if (temp <= chip->batt_warm_decidegc &&
+					chip->jeita_supported) {
+			cur = &batt_s[BATT_NORMAL];
+			chip->adc_param.high_temp =
+				chip->batt_warm_decidegc + HYSTERESIS_DECIDEGC;
+			chip->adc_param.low_temp = chip->batt_cool_decidegc;
+		/* HOT -> WARM */
+		} else if (temp <= chip->batt_hot_decidegc) {
+			cur = &batt_s[BATT_WARM];
+			if (chip->jeita_supported)
+				chip->adc_param.low_temp =
+					chip->batt_warm_decidegc;
+			else
+				chip->adc_param.low_temp =
+					chip->batt_cold_decidegc;
+			chip->adc_param.high_temp =
+				chip->batt_hot_decidegc + HYSTERESIS_DECIDEGC;
+		}
+	}
+
+	if (!cur) {
+		pr_debug("Couldn't choose batt state, adc state=%d and temp=%d\n",
+			state, temp);
+		return;
+	}
+
+	if (cur->batt_present)
+		chip->battery_missing = false;
+	else
+		chip->battery_missing = true;
+
+	if (cur->batt_hot ^ chip->batt_hot ||
+			cur->batt_cold ^ chip->batt_cold) {
+		chip->batt_hot = cur->batt_hot;
+		chip->batt_cold = cur->batt_cold;
+		/* stop charging explicitly since we use PMIC thermal pin*/
+		if (cur->batt_hot || cur->batt_cold ||
+							chip->battery_missing)
+			smb1351_battchg_disable(chip, THERMAL, 1);
+		else
+			smb1351_battchg_disable(chip, THERMAL, 0);
+	}
+
+	if ((chip->batt_warm ^ cur->batt_warm ||
+				chip->batt_cool ^ cur->batt_cool)
+						&& chip->jeita_supported) {
+		chip->batt_warm = cur->batt_warm;
+		chip->batt_cool = cur->batt_cool;
+		smb1351_chg_set_appropriate_battery_current(chip);
+		smb1351_chg_set_appropriate_vddmax(chip);
+		smb1351_chg_ctrl_in_jeita(chip);
+	}
+
+	pr_debug("hot %d, cold %d, warm %d, cool %d, soft jeita supported %d, missing %d, low = %d deciDegC, high = %d deciDegC\n",
+		chip->batt_hot, chip->batt_cold, chip->batt_warm,
+		chip->batt_cool, chip->jeita_supported,
+		chip->battery_missing, chip->adc_param.low_temp,
+		chip->adc_param.high_temp);
+	if (qpnp_adc_tm_channel_measure(chip->adc_tm_dev, &chip->adc_param))
+		pr_err("request ADC error\n");
+}
+
+static int rerun_apsd(struct smb1351_charger *chip)
+{
+	int rc;
+
+	pr_debug("Reruning APSD\nDisabling APSD\n");
+
+	rc = smb1351_masked_write(chip, CMD_HVDCP_REG, CMD_APSD_RE_RUN_BIT,
+						CMD_APSD_RE_RUN_BIT);
+	if (rc)
+		pr_err("Couldn't re-run APSD algo\n");
+
+	return 0;
+}
+
+static void smb1351_hvdcp_det_work(struct work_struct *work)
+{
+	int rc;
+	u8 reg;
+	union power_supply_propval pval = {0, };
+	struct smb1351_charger *chip = container_of(work,
+						struct smb1351_charger,
+						hvdcp_det_work.work);
+
+	rc = smb1351_read_reg(chip, STATUS_7_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc);
+		goto end;
+	}
+	pr_debug("STATUS_7_REG = 0x%02X\n", reg);
+
+	if (reg) {
+		pr_debug("HVDCP detected; notifying USB PSY\n");
+		pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP;
+		power_supply_set_property(chip->usb_psy,
+			POWER_SUPPLY_PROP_TYPE, &pval);
+	}
+end:
+	pm_relax(chip->dev);
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static int smb1351_apsd_complete_handler(struct smb1351_charger *chip,
+						u8 status)
+{
+	int rc;
+	u8 reg = 0;
+	union power_supply_propval prop = {0, };
+	enum power_supply_type type = POWER_SUPPLY_TYPE_UNKNOWN;
+
+	/*
+	 * If apsd is disabled, charger detection is done by
+	 * USB phy driver.
+	 */
+	if (chip->disable_apsd || chip->usbin_ov) {
+		pr_debug("APSD %s, status = %d\n",
+			chip->disable_apsd ? "disabled" : "enabled", !!status);
+		pr_debug("USBIN ov, status = %d\n", chip->usbin_ov);
+		return 0;
+	}
+
+	rc = smb1351_read_reg(chip, STATUS_5_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read STATUS_5 rc = %d\n", rc);
+		return rc;
+	}
+
+	pr_debug("STATUS_5_REG(0x3B)=%x\n", reg);
+
+	switch (reg) {
+	case STATUS_PORT_ACA_DOCK:
+	case STATUS_PORT_ACA_C:
+	case STATUS_PORT_ACA_B:
+	case STATUS_PORT_ACA_A:
+		type = POWER_SUPPLY_TYPE_USB_ACA;
+		break;
+	case STATUS_PORT_CDP:
+		type = POWER_SUPPLY_TYPE_USB_CDP;
+		break;
+	case STATUS_PORT_DCP:
+		type = POWER_SUPPLY_TYPE_USB_DCP;
+		break;
+	case STATUS_PORT_SDP:
+		type = POWER_SUPPLY_TYPE_USB;
+		break;
+	case STATUS_PORT_OTHER:
+		type = POWER_SUPPLY_TYPE_USB_DCP;
+		break;
+	default:
+		type = POWER_SUPPLY_TYPE_USB;
+		break;
+	}
+
+	if (status) {
+		chip->chg_present = true;
+		pr_debug("APSD complete. USB type detected=%d chg_present=%d\n",
+						type, chip->chg_present);
+		if (!chip->battery_missing && !chip->apsd_rerun) {
+			if (type == POWER_SUPPLY_TYPE_USB) {
+				pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n");
+				prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+				power_supply_set_property(chip->usb_psy,
+						POWER_SUPPLY_PROP_DP_DM, &prop);
+				chip->apsd_rerun = true;
+				rerun_apsd(chip);
+				return 0;
+			}
+			pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n");
+			prop.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_DP_DM, &prop);
+		}
+		/*
+		 * If defined force hvdcp 2p0 property,
+		 * we force to hvdcp 2p0 in the APSD handler.
+		 */
+		if (chip->force_hvdcp_2p0) {
+			pr_debug("Force set to HVDCP 2.0 mode\n");
+			smb1351_masked_write(chip, VARIOUS_FUNC_3_REG,
+						QC_2P1_AUTH_ALGO_BIT, 0);
+			smb1351_masked_write(chip, CMD_HVDCP_REG,
+						CMD_FORCE_HVDCP_2P0_BIT,
+						CMD_FORCE_HVDCP_2P0_BIT);
+			type = POWER_SUPPLY_TYPE_USB_HVDCP;
+		} else if (type == POWER_SUPPLY_TYPE_USB_DCP) {
+			pr_debug("schedule hvdcp detection worker\n");
+			pm_stay_awake(chip->dev);
+			schedule_delayed_work(&chip->hvdcp_det_work,
+					msecs_to_jiffies(HVDCP_NOTIFY_MS));
+		}
+
+		prop.intval = type;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &prop);
+		/*
+		 * SMB is now done sampling the D+/D- lines,
+		 * indicate USB driver
+		 */
+		pr_debug("updating usb_psy present=%d\n", chip->chg_present);
+		prop.intval = chip->chg_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT,
+				&prop);
+		chip->apsd_rerun = false;
+	} else if (!chip->apsd_rerun) {
+		/* Handle Charger removal */
+		prop.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &prop);
+
+		chip->chg_present = false;
+		prop.intval = chip->chg_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT,
+				&prop);
+
+		pr_debug("Set usb psy dm=r df=r\n");
+		prop.intval = POWER_SUPPLY_DP_DM_DPR_DMR;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_DP_DM, &prop);
+	}
+
+	return 0;
+}
+
+/*
+ * As source detect interrupt is not triggered on the falling edge,
+ * we need to schedule a work for checking source detect status after
+ * charger UV interrupt fired.
+ */
+#define FIRST_CHECK_DELAY	100
+#define SECOND_CHECK_DELAY	1000
+static void smb1351_chg_remove_work(struct work_struct *work)
+{
+	int rc;
+	u8 reg;
+	struct smb1351_charger *chip = container_of(work,
+				struct smb1351_charger, chg_remove_work.work);
+
+	rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read IRQ_G_REG rc = %d\n", rc);
+		goto end;
+	}
+
+	if (!(reg & IRQ_SOURCE_DET_BIT)) {
+		pr_debug("chg removed\n");
+		smb1351_apsd_complete_handler(chip, 0);
+	} else if (!chip->chg_remove_work_scheduled) {
+		chip->chg_remove_work_scheduled = true;
+		goto reschedule;
+	} else {
+		pr_debug("charger is present\n");
+	}
+end:
+	chip->chg_remove_work_scheduled = false;
+	pm_relax(chip->dev);
+	return;
+
+reschedule:
+	pr_debug("reschedule after 1s\n");
+	schedule_delayed_work(&chip->chg_remove_work,
+				msecs_to_jiffies(SECOND_CHECK_DELAY));
+}
+
+static int smb1351_usbin_uv_handler(struct smb1351_charger *chip, u8 status)
+{
+	union power_supply_propval pval = {0, };
+
+	/* use this to detect USB insertion only if !apsd */
+	if (chip->disable_apsd) {
+		/*
+		 * If APSD is disabled, src det interrupt won't trigger.
+		 * Hence use usbin_uv for removal and insertion notification
+		 */
+		if (status == 0) {
+			chip->chg_present = true;
+			pr_debug("updating usb_psy present=%d\n",
+						chip->chg_present);
+			pval.intval = POWER_SUPPLY_TYPE_USB;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_TYPE, &pval);
+
+			pval.intval = chip->chg_present;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_PRESENT,
+					&pval);
+		} else {
+			chip->chg_present = false;
+
+			pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_TYPE, &pval);
+
+			pr_debug("updating usb_psy present=%d\n",
+							chip->chg_present);
+			pval.intval = chip->chg_present;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_PRESENT,
+					&pval);
+		}
+		return 0;
+	}
+
+	if (status) {
+		cancel_delayed_work_sync(&chip->hvdcp_det_work);
+		pm_relax(chip->dev);
+		pr_debug("schedule charger remove worker\n");
+		schedule_delayed_work(&chip->chg_remove_work,
+					msecs_to_jiffies(FIRST_CHECK_DELAY));
+		pm_stay_awake(chip->dev);
+	}
+
+	pr_debug("chip->chg_present = %d\n", chip->chg_present);
+
+	return 0;
+}
+
+static int smb1351_usbin_ov_handler(struct smb1351_charger *chip, u8 status)
+{
+	int rc;
+	u8 reg;
+	union power_supply_propval pval = {0, };
+
+	rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+	if (rc)
+		pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+
+	if (status != 0) {
+		chip->chg_present = false;
+		chip->usbin_ov = true;
+
+		pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &pval);
+
+		pval.intval = chip->chg_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT,
+				&pval);
+	} else {
+		chip->usbin_ov = false;
+		if (reg & IRQ_USBIN_UV_BIT)
+			pr_debug("Charger unplugged from OV\n");
+		else
+			smb1351_apsd_complete_handler(chip, 1);
+	}
+
+	if (chip->usb_psy) {
+		pval.intval = status ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
+					: POWER_SUPPLY_HEALTH_GOOD;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_HEALTH, &pval);
+		pr_debug("chip ov status is %d\n", pval.intval);
+	}
+	pr_debug("chip->chg_present = %d\n", chip->chg_present);
+
+	return 0;
+}
+
+static int smb1351_fast_chg_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("enter\n");
+	return 0;
+}
+
+static int smb1351_chg_term_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("enter\n");
+	if (!chip->bms_controlled_charging)
+		chip->batt_full = !!status;
+	return 0;
+}
+
+static int smb1351_safety_timeout_handler(struct smb1351_charger *chip,
+						u8 status)
+{
+	pr_debug("safety_timeout triggered\n");
+	return 0;
+}
+
+static int smb1351_aicl_done_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("aicl_done triggered\n");
+	return 0;
+}
+
+static int smb1351_hot_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("status = 0x%02x\n", status);
+	chip->batt_hot = !!status;
+	return 0;
+}
+static int smb1351_cold_hard_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("status = 0x%02x\n", status);
+	chip->batt_cold = !!status;
+	return 0;
+}
+static int smb1351_hot_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("status = 0x%02x\n", status);
+	chip->batt_warm = !!status;
+	return 0;
+}
+static int smb1351_cold_soft_handler(struct smb1351_charger *chip, u8 status)
+{
+	pr_debug("status = 0x%02x\n", status);
+	chip->batt_cool = !!status;
+	return 0;
+}
+
+static int smb1351_battery_missing_handler(struct smb1351_charger *chip,
+						u8 status)
+{
+	if (status)
+		chip->battery_missing = true;
+	else
+		chip->battery_missing = false;
+
+	return 0;
+}
+
+static struct irq_handler_info handlers[] = {
+	[0] = {
+		.stat_reg	= IRQ_A_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "cold_soft",
+				.smb_irq = smb1351_cold_soft_handler,
+			},
+			{	.name	 = "hot_soft",
+				.smb_irq = smb1351_hot_soft_handler,
+			},
+			{	.name	 = "cold_hard",
+				.smb_irq = smb1351_cold_hard_handler,
+			},
+			{	.name	 = "hot_hard",
+				.smb_irq = smb1351_hot_hard_handler,
+			},
+		},
+	},
+	[1] = {
+		.stat_reg	= IRQ_B_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "internal_temp_limit",
+			},
+			{	.name	 = "vbatt_low",
+			},
+			{	.name	 = "battery_missing",
+				.smb_irq = smb1351_battery_missing_handler,
+			},
+			{	.name	 = "batt_therm_removed",
+			},
+		},
+	},
+	[2] = {
+		.stat_reg	= IRQ_C_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "chg_term",
+				.smb_irq = smb1351_chg_term_handler,
+			},
+			{	.name	 = "taper",
+			},
+			{	.name	 = "recharge",
+			},
+			{	.name	 = "fast_chg",
+				.smb_irq = smb1351_fast_chg_handler,
+			},
+		},
+	},
+	[3] = {
+		.stat_reg	= IRQ_D_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "prechg_timeout",
+			},
+			{	.name	 = "safety_timeout",
+				.smb_irq = smb1351_safety_timeout_handler,
+			},
+			{	.name	 = "chg_error",
+			},
+			{	.name	 = "batt_ov",
+			},
+		},
+	},
+	[4] = {
+		.stat_reg	= IRQ_E_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "power_ok",
+			},
+			{	.name	 = "afvc",
+			},
+			{	.name	 = "usbin_uv",
+				.smb_irq = smb1351_usbin_uv_handler,
+			},
+			{	.name	 = "usbin_ov",
+				.smb_irq = smb1351_usbin_ov_handler,
+			},
+		},
+	},
+	[5] = {
+		.stat_reg	= IRQ_F_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "otg_oc_retry",
+			},
+			{	.name	 = "rid",
+			},
+			{	.name	 = "otg_fail",
+			},
+			{	.name	 = "otg_oc",
+			},
+		},
+	},
+	[6] = {
+		.stat_reg	= IRQ_G_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "chg_inhibit",
+			},
+			{	.name	 = "aicl_fail",
+			},
+			{	.name	 = "aicl_done",
+				.smb_irq = smb1351_aicl_done_handler,
+			},
+			{	.name	 = "apsd_complete",
+				.smb_irq = smb1351_apsd_complete_handler,
+			},
+		},
+	},
+	[7] = {
+		.stat_reg	= IRQ_H_REG,
+		.val		= 0,
+		.prev_val	= 0,
+		.irq_info	= {
+			{	.name	 = "wdog_timeout",
+			},
+			{	.name	 = "hvdcp_auth_done",
+			},
+		},
+	},
+};
+
+#define IRQ_LATCHED_MASK	0x02
+#define IRQ_STATUS_MASK		0x01
+#define BITS_PER_IRQ		2
+static irqreturn_t smb1351_chg_stat_handler(int irq, void *dev_id)
+{
+	struct smb1351_charger *chip = dev_id;
+	int i, j;
+	u8 triggered;
+	u8 changed;
+	u8 rt_stat, prev_rt_stat;
+	int rc;
+	int handler_count = 0;
+
+	mutex_lock(&chip->irq_complete);
+
+	chip->irq_waiting = true;
+	if (!chip->resume_completed) {
+		pr_debug("IRQ triggered before device-resume\n");
+		disable_irq_nosync(irq);
+		mutex_unlock(&chip->irq_complete);
+		return IRQ_HANDLED;
+	}
+	chip->irq_waiting = false;
+
+	for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+		rc = smb1351_read_reg(chip, handlers[i].stat_reg,
+						&handlers[i].val);
+		if (rc) {
+			pr_err("Couldn't read %d rc = %d\n",
+					handlers[i].stat_reg, rc);
+			continue;
+		}
+
+		for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
+			triggered = handlers[i].val
+			       & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
+			rt_stat = handlers[i].val
+				& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+			prev_rt_stat = handlers[i].prev_val
+				& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+			changed = prev_rt_stat ^ rt_stat;
+
+			if (triggered || changed)
+				rt_stat ? handlers[i].irq_info[j].high++ :
+						handlers[i].irq_info[j].low++;
+
+			if ((triggered || changed)
+				&& handlers[i].irq_info[j].smb_irq != NULL) {
+				handler_count++;
+				rc = handlers[i].irq_info[j].smb_irq(chip,
+								rt_stat);
+				if (rc)
+					pr_err("Couldn't handle %d irq for reg 0x%02x rc = %d\n",
+						j, handlers[i].stat_reg, rc);
+			}
+		}
+		handlers[i].prev_val = handlers[i].val;
+	}
+
+	pr_debug("handler count = %d\n", handler_count);
+	if (handler_count) {
+		pr_debug("batt psy changed\n");
+		power_supply_changed(chip->batt_psy);
+	}
+
+	mutex_unlock(&chip->irq_complete);
+
+	return IRQ_HANDLED;
+}
+
+static void smb1351_external_power_changed(struct power_supply *psy)
+{
+	struct smb1351_charger *chip = power_supply_get_drvdata(psy);
+	union power_supply_propval prop = {0,};
+	int rc, current_limit = 0, online = 0;
+
+	if (chip->bms_psy_name)
+		chip->bms_psy =
+			power_supply_get_by_name((char *)chip->bms_psy_name);
+
+	rc = power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_ONLINE, &prop);
+	if (rc)
+		pr_err("Couldn't read USB online property, rc=%d\n", rc);
+	else
+		online = prop.intval;
+
+	rc = power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+	if (rc)
+		pr_err("Couldn't read USB current_max property, rc=%d\n", rc);
+	else
+		current_limit = prop.intval / 1000;
+
+	pr_debug("online = %d, current_limit = %d\n", online, current_limit);
+
+	smb1351_enable_volatile_writes(chip);
+	smb1351_set_usb_chg_current(chip, current_limit);
+
+	pr_debug("updating batt psy\n");
+}
+
+#define LAST_CNFG_REG	0x16
+static int show_cnfg_regs(struct seq_file *m, void *data)
+{
+	struct smb1351_charger *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb1351_charger *chip = inode->i_private;
+
+	return single_open(file, show_cnfg_regs, chip);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= cnfg_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+#define FIRST_CMD_REG	0x30
+#define LAST_CMD_REG	0x34
+static int show_cmd_regs(struct seq_file *m, void *data)
+{
+	struct smb1351_charger *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int cmd_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb1351_charger *chip = inode->i_private;
+
+	return single_open(file, show_cmd_regs, chip);
+}
+
+static const struct file_operations cmd_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= cmd_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+#define FIRST_STATUS_REG	0x36
+#define LAST_STATUS_REG		0x3F
+static int show_status_regs(struct seq_file *m, void *data)
+{
+	struct smb1351_charger *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int status_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb1351_charger *chip = inode->i_private;
+
+	return single_open(file, show_status_regs, chip);
+}
+
+static const struct file_operations status_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= status_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int show_irq_count(struct seq_file *m, void *data)
+{
+	int i, j, total = 0;
+
+	for (i = 0; i < ARRAY_SIZE(handlers); i++)
+		for (j = 0; j < 4; j++) {
+			seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
+						handlers[i].irq_info[j].name,
+						handlers[i].irq_info[j].high
+						+ handlers[i].irq_info[j].low,
+						handlers[i].irq_info[j].high,
+						handlers[i].irq_info[j].low);
+			total += (handlers[i].irq_info[j].high
+					+ handlers[i].irq_info[j].low);
+		}
+
+	seq_printf(m, "\n\tTotal = %d\n", total);
+
+	return 0;
+}
+
+static int irq_count_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb1351_charger *chip = inode->i_private;
+
+	return single_open(file, show_irq_count, chip);
+}
+
+static const struct file_operations irq_count_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= irq_count_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int get_reg(void *data, u64 *val)
+{
+	struct smb1351_charger *chip = data;
+	int rc;
+	u8 temp;
+
+	rc = smb1351_read_reg(chip, chip->peek_poke_address, &temp);
+	if (rc) {
+		pr_err("Couldn't read reg %x rc = %d\n",
+			chip->peek_poke_address, rc);
+		return -EAGAIN;
+	}
+	*val = temp;
+	return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+	struct smb1351_charger *chip = data;
+	int rc;
+	u8 temp;
+
+	temp = (u8) val;
+	rc = smb1351_write_reg(chip, chip->peek_poke_address, temp);
+	if (rc) {
+		pr_err("Couldn't write 0x%02x to 0x%02x rc= %d\n",
+			temp, chip->peek_poke_address, rc);
+		return -EAGAIN;
+	}
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
+
+static int force_irq_set(void *data, u64 val)
+{
+	struct smb1351_charger *chip = data;
+
+	smb1351_chg_stat_handler(chip->client->irq, data);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
+
+#ifdef DEBUG
+static void dump_regs(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (rc)
+			pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (rc)
+			pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+		rc = smb1351_read_reg(chip, addr, &reg);
+		if (rc)
+			pr_err("Couldn't read 0x%02x rc = %d\n", addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+}
+#else
+static void dump_regs(struct smb1351_charger *chip)
+{
+}
+#endif
+
+static int smb1351_parse_dt(struct smb1351_charger *chip)
+{
+	int rc;
+	struct device_node *node = chip->dev->of_node;
+
+	if (!node) {
+		pr_err("device tree info. missing\n");
+		return -EINVAL;
+	}
+
+	chip->usb_suspended_status = of_property_read_bool(node,
+					"qcom,charging-disabled");
+
+	chip->chg_autonomous_mode = of_property_read_bool(node,
+					"qcom,chg-autonomous-mode");
+
+	chip->disable_apsd = of_property_read_bool(node, "qcom,disable-apsd");
+
+	chip->using_pmic_therm = of_property_read_bool(node,
+						"qcom,using-pmic-therm");
+	chip->bms_controlled_charging  = of_property_read_bool(node,
+					"qcom,bms-controlled-charging");
+	chip->force_hvdcp_2p0 = of_property_read_bool(node,
+					"qcom,force-hvdcp-2p0");
+
+	rc = of_property_read_string(node, "qcom,bms-psy-name",
+						&chip->bms_psy_name);
+	if (rc)
+		chip->bms_psy_name = NULL;
+
+	rc = of_property_read_u32(node, "qcom,fastchg-current-max-ma",
+					&chip->target_fastchg_current_max_ma);
+	if (rc)
+		chip->target_fastchg_current_max_ma = SMB1351_CHG_FAST_MAX_MA;
+
+	chip->iterm_disabled = of_property_read_bool(node,
+					"qcom,iterm-disabled");
+
+	rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
+	if (rc)
+		chip->iterm_ma = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+						&chip->vfloat_mv);
+	if (rc)
+		chip->vfloat_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,recharge-mv",
+						&chip->recharge_mv);
+	if (rc)
+		chip->recharge_mv = -EINVAL;
+
+	chip->recharge_disabled = of_property_read_bool(node,
+					"qcom,recharge-disabled");
+
+	/* thermal and jeita support */
+	rc = of_property_read_u32(node, "qcom,batt-cold-decidegc",
+						&chip->batt_cold_decidegc);
+	if (rc < 0)
+		chip->batt_cold_decidegc = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,batt-hot-decidegc",
+						&chip->batt_hot_decidegc);
+	if (rc < 0)
+		chip->batt_hot_decidegc = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,batt-warm-decidegc",
+						&chip->batt_warm_decidegc);
+
+	rc |= of_property_read_u32(node, "qcom,batt-cool-decidegc",
+						&chip->batt_cool_decidegc);
+
+	if (!rc) {
+		rc = of_property_read_u32(node, "qcom,batt-cool-mv",
+						&chip->batt_cool_mv);
+
+		rc |= of_property_read_u32(node, "qcom,batt-warm-mv",
+						&chip->batt_warm_mv);
+
+		rc |= of_property_read_u32(node, "qcom,batt-cool-ma",
+						&chip->batt_cool_ma);
+
+		rc |= of_property_read_u32(node, "qcom,batt-warm-ma",
+						&chip->batt_warm_ma);
+		if (rc)
+			chip->jeita_supported = false;
+		else
+			chip->jeita_supported = true;
+	}
+
+	pr_debug("jeita_supported = %d\n", chip->jeita_supported);
+
+	rc = of_property_read_u32(node, "qcom,batt-missing-decidegc",
+						&chip->batt_missing_decidegc);
+
+	chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
+
+	return 0;
+}
+
+static int smb1351_determine_initial_state(struct smb1351_charger *chip)
+{
+	int rc;
+	u8 reg = 0;
+
+	/*
+	 * It is okay to read the interrupt status here since
+	 * interrupts aren't requested. Reading interrupt status
+	 * clears the interrupt so be careful to read interrupt
+	 * status only in interrupt handling code
+	 */
+
+	rc = smb1351_read_reg(chip, IRQ_B_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read IRQ_B rc = %d\n", rc);
+		goto fail_init_status;
+	}
+
+	chip->battery_missing = (reg & IRQ_BATT_MISSING_BIT) ? true : false;
+
+	rc = smb1351_read_reg(chip, IRQ_C_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read IRQ_C rc = %d\n", rc);
+		goto fail_init_status;
+	}
+	chip->batt_full = (reg & IRQ_TERM_BIT) ? true : false;
+
+	rc = smb1351_read_reg(chip, IRQ_A_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read irq A rc = %d\n", rc);
+		return rc;
+	}
+
+	if (reg & IRQ_HOT_HARD_BIT)
+		chip->batt_hot = true;
+	if (reg & IRQ_COLD_HARD_BIT)
+		chip->batt_cold = true;
+	if (reg & IRQ_HOT_SOFT_BIT)
+		chip->batt_warm = true;
+	if (reg & IRQ_COLD_SOFT_BIT)
+		chip->batt_cool = true;
+
+	rc = smb1351_read_reg(chip, IRQ_E_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read IRQ_E rc = %d\n", rc);
+		goto fail_init_status;
+	}
+
+	if (reg & IRQ_USBIN_UV_BIT) {
+		smb1351_usbin_uv_handler(chip, 1);
+	} else {
+		smb1351_usbin_uv_handler(chip, 0);
+		smb1351_apsd_complete_handler(chip, 1);
+	}
+
+	rc = smb1351_read_reg(chip, IRQ_G_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read IRQ_G rc = %d\n", rc);
+		goto fail_init_status;
+	}
+
+	if (reg & IRQ_SOURCE_DET_BIT)
+		smb1351_apsd_complete_handler(chip, 1);
+
+	return 0;
+
+fail_init_status:
+	pr_err("Couldn't determine initial status\n");
+	return rc;
+}
+
+static int is_parallel_charger(struct i2c_client *client)
+{
+	struct device_node *node = client->dev.of_node;
+
+	return of_property_read_bool(node, "qcom,parallel-charger");
+}
+
+static int create_debugfs_entries(struct smb1351_charger *chip)
+{
+	struct dentry *ent;
+
+	chip->debug_root = debugfs_create_dir("smb1351", NULL);
+	if (!chip->debug_root) {
+		pr_err("Couldn't create debug dir\n");
+	} else {
+		ent = debugfs_create_file("config_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &cnfg_debugfs_ops);
+		if (!ent)
+			pr_err("Couldn't create cnfg debug file\n");
+
+		ent = debugfs_create_file("status_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &status_debugfs_ops);
+		if (!ent)
+			pr_err("Couldn't create status debug file\n");
+
+		ent = debugfs_create_file("cmd_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &cmd_debugfs_ops);
+		if (!ent)
+			pr_err("Couldn't create cmd debug file\n");
+
+		ent = debugfs_create_x32("address", S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->peek_poke_address));
+		if (!ent)
+			pr_err("Couldn't create address debug file\n");
+
+		ent = debugfs_create_file("data", S_IFREG | 0644,
+					  chip->debug_root, chip,
+					  &poke_poke_debug_ops);
+		if (!ent)
+			pr_err("Couldn't create data debug file\n");
+
+		ent = debugfs_create_file("force_irq",
+					  S_IFREG | 0644,
+					  chip->debug_root, chip,
+					  &force_irq_ops);
+		if (!ent)
+			pr_err("Couldn't create data debug file\n");
+
+		ent = debugfs_create_file("irq_count", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &irq_count_debugfs_ops);
+		if (!ent)
+			pr_err("Couldn't create count debug file\n");
+	}
+	return 0;
+}
+
+static int smb1351_main_charger_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	int rc;
+	struct smb1351_charger *chip;
+	struct power_supply *usb_psy;
+	struct power_supply_config batt_psy_cfg = {};
+	u8 reg = 0;
+
+	usb_psy = power_supply_get_by_name("usb");
+	if (!usb_psy) {
+		pr_debug("USB psy not found; deferring probe\n");
+		return -EPROBE_DEFER;
+	}
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client = client;
+	chip->dev = &client->dev;
+	chip->usb_psy = usb_psy;
+	chip->fake_battery_soc = -EINVAL;
+	INIT_DELAYED_WORK(&chip->chg_remove_work, smb1351_chg_remove_work);
+	INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb1351_hvdcp_det_work);
+	device_init_wakeup(chip->dev, true);
+
+	/* probe the device to check if its actually connected */
+	rc = smb1351_read_reg(chip, CHG_REVISION_REG, &reg);
+	if (rc) {
+		pr_err("Failed to detect smb1351, device may be absent\n");
+		return -ENODEV;
+	}
+	pr_debug("smb1351 chip revision is %d\n", reg);
+
+	rc = smb1351_parse_dt(chip);
+	if (rc) {
+		pr_err("Couldn't parse DT nodes rc=%d\n", rc);
+		return rc;
+	}
+
+	/* using vadc and adc_tm for implementing pmic therm */
+	if (chip->using_pmic_therm) {
+		chip->vadc_dev = qpnp_get_vadc(chip->dev, "chg");
+		if (IS_ERR(chip->vadc_dev)) {
+			rc = PTR_ERR(chip->vadc_dev);
+			if (rc != -EPROBE_DEFER)
+				pr_err("vadc property missing\n");
+			return rc;
+		}
+		chip->adc_tm_dev = qpnp_get_adc_tm(chip->dev, "chg");
+		if (IS_ERR(chip->adc_tm_dev)) {
+			rc = PTR_ERR(chip->adc_tm_dev);
+			if (rc != -EPROBE_DEFER)
+				pr_err("adc_tm property missing\n");
+			return rc;
+		}
+	}
+
+	i2c_set_clientdata(client, chip);
+
+	chip->batt_psy_d.name		= "battery";
+	chip->batt_psy_d.type		= POWER_SUPPLY_TYPE_BATTERY;
+	chip->batt_psy_d.get_property	= smb1351_battery_get_property;
+	chip->batt_psy_d.set_property	= smb1351_battery_set_property;
+	chip->batt_psy_d.property_is_writeable =
+					smb1351_batt_property_is_writeable;
+	chip->batt_psy_d.properties	= smb1351_battery_properties;
+	chip->batt_psy_d.num_properties	=
+				ARRAY_SIZE(smb1351_battery_properties);
+	chip->batt_psy_d.external_power_changed =
+					smb1351_external_power_changed;
+
+	chip->resume_completed = true;
+	mutex_init(&chip->irq_complete);
+
+	batt_psy_cfg.drv_data = chip;
+	batt_psy_cfg.supplied_to = pm_batt_supplied_to;
+	batt_psy_cfg.num_supplicants = ARRAY_SIZE(pm_batt_supplied_to);
+	chip->batt_psy = devm_power_supply_register(chip->dev,
+			&chip->batt_psy_d,
+			&batt_psy_cfg);
+	if (IS_ERR(chip->batt_psy)) {
+		pr_err("Couldn't register batt psy rc=%ld\n",
+				PTR_ERR(chip->batt_psy));
+		return rc;
+	}
+
+	dump_regs(chip);
+
+	rc = smb1351_regulator_init(chip);
+	if (rc) {
+		pr_err("Couldn't initialize smb1351 ragulator rc=%d\n", rc);
+		goto fail_smb1351_regulator_init;
+	}
+
+	rc = smb1351_hw_init(chip);
+	if (rc) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		goto fail_smb1351_hw_init;
+	}
+
+	rc = smb1351_determine_initial_state(chip);
+	if (rc) {
+		pr_err("Couldn't determine initial state rc=%d\n", rc);
+		goto fail_smb1351_hw_init;
+	}
+
+	/* STAT irq configuration */
+	if (client->irq) {
+		rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+				smb1351_chg_stat_handler,
+				IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				"smb1351_chg_stat_irq", chip);
+		if (rc) {
+			pr_err("Failed STAT irq=%d request rc = %d\n",
+				client->irq, rc);
+			goto fail_smb1351_hw_init;
+		}
+		enable_irq_wake(client->irq);
+	}
+
+	if (chip->using_pmic_therm) {
+		if (!chip->jeita_supported) {
+			/* add hot/cold temperature monitor */
+			chip->adc_param.low_temp = chip->batt_cold_decidegc;
+			chip->adc_param.high_temp = chip->batt_hot_decidegc;
+		} else {
+			chip->adc_param.low_temp = chip->batt_cool_decidegc;
+			chip->adc_param.high_temp = chip->batt_warm_decidegc;
+		}
+		chip->adc_param.timer_interval = ADC_MEAS1_INTERVAL_500MS;
+		chip->adc_param.state_request = ADC_TM_WARM_COOL_THR_ENABLE;
+		chip->adc_param.btm_ctx = chip;
+		chip->adc_param.threshold_notification =
+				smb1351_chg_adc_notification;
+		chip->adc_param.channel = LR_MUX1_BATT_THERM;
+
+		rc = qpnp_adc_tm_channel_measure(chip->adc_tm_dev,
+							&chip->adc_param);
+		if (rc) {
+			pr_err("requesting ADC error %d\n", rc);
+			goto fail_smb1351_hw_init;
+		}
+	}
+
+	create_debugfs_entries(chip);
+
+	dump_regs(chip);
+
+	pr_info("smb1351 successfully probed. charger=%d, batt=%d version=%s\n",
+			chip->chg_present,
+			smb1351_get_prop_batt_present(chip),
+			smb1351_version_str[chip->version]);
+	return 0;
+
+fail_smb1351_hw_init:
+	regulator_unregister(chip->otg_vreg.rdev);
+fail_smb1351_regulator_init:
+	return rc;
+}
+
+static int smb1351_parallel_charger_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	int rc;
+	struct smb1351_charger *chip;
+	struct device_node *node = client->dev.of_node;
+	struct power_supply_config parallel_psy_cfg = {};
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client = client;
+	chip->dev = &client->dev;
+	chip->parallel_charger = true;
+	chip->parallel_charger_suspended = true;
+
+	chip->usb_suspended_status = of_property_read_bool(node,
+					"qcom,charging-disabled");
+	rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+						&chip->vfloat_mv);
+	if (rc)
+		chip->vfloat_mv = -EINVAL;
+	rc = of_property_read_u32(node, "qcom,recharge-mv",
+						&chip->recharge_mv);
+	if (rc)
+		chip->recharge_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity",
+					&chip->parallel_pin_polarity_setting);
+	if (rc)
+		chip->parallel_pin_polarity_setting = EN_BY_PIN_LOW_ENABLE;
+	else
+		chip->parallel_pin_polarity_setting =
+				chip->parallel_pin_polarity_setting ?
+				EN_BY_PIN_HIGH_ENABLE : EN_BY_PIN_LOW_ENABLE;
+
+	if (of_property_read_bool(node,
+				"qcom,parallel-external-current-sense"))
+		chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN_EXT;
+	else
+		chip->parallel_mode = POWER_SUPPLY_PL_USBIN_USBIN;
+
+	i2c_set_clientdata(client, chip);
+
+	chip->parallel_psy_d.name = "parallel";
+	chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL;
+	chip->parallel_psy_d.get_property = smb1351_parallel_get_property;
+	chip->parallel_psy_d.set_property = smb1351_parallel_set_property;
+	chip->parallel_psy_d.properties	= smb1351_parallel_properties;
+	chip->parallel_psy_d.property_is_writeable
+				= smb1351_parallel_is_writeable;
+	chip->parallel_psy_d.num_properties
+				= ARRAY_SIZE(smb1351_parallel_properties);
+
+	parallel_psy_cfg.drv_data = chip;
+	parallel_psy_cfg.num_supplicants = 0;
+	chip->parallel_psy = devm_power_supply_register(chip->dev,
+			&chip->parallel_psy_d,
+			&parallel_psy_cfg);
+	if (IS_ERR(chip->parallel_psy)) {
+		pr_err("Couldn't register parallel psy rc=%ld\n",
+				PTR_ERR(chip->parallel_psy));
+		return rc;
+	}
+
+	chip->resume_completed = true;
+	mutex_init(&chip->irq_complete);
+
+	create_debugfs_entries(chip);
+
+	pr_info("smb1351 parallel successfully probed.\n");
+
+	return 0;
+}
+
+static int smb1351_charger_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	if (is_parallel_charger(client))
+		return smb1351_parallel_charger_probe(client, id);
+	else
+		return smb1351_main_charger_probe(client, id);
+}
+
+static int smb1351_charger_remove(struct i2c_client *client)
+{
+	struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+	cancel_delayed_work_sync(&chip->chg_remove_work);
+
+	mutex_destroy(&chip->irq_complete);
+	debugfs_remove_recursive(chip->debug_root);
+	return 0;
+}
+
+static int smb1351_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+
+	mutex_lock(&chip->irq_complete);
+	chip->resume_completed = false;
+	mutex_unlock(&chip->irq_complete);
+
+	return 0;
+}
+
+static int smb1351_suspend_noirq(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+
+	if (chip->irq_waiting) {
+		pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
+		return -EBUSY;
+	}
+	return 0;
+}
+
+static int smb1351_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb1351_charger *chip = i2c_get_clientdata(client);
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+
+	mutex_lock(&chip->irq_complete);
+	chip->resume_completed = true;
+	if (chip->irq_waiting) {
+		mutex_unlock(&chip->irq_complete);
+		smb1351_chg_stat_handler(client->irq, chip);
+		enable_irq(client->irq);
+	} else {
+		mutex_unlock(&chip->irq_complete);
+	}
+	return 0;
+}
+
+static const struct dev_pm_ops smb1351_pm_ops = {
+	.suspend	= smb1351_suspend,
+	.suspend_noirq	= smb1351_suspend_noirq,
+	.resume		= smb1351_resume,
+};
+
+static const struct of_device_id smb1351_match_table[] = {
+	{ .compatible = "qcom,smb1351-charger",},
+	{ },
+};
+
+static const struct i2c_device_id smb1351_charger_id[] = {
+	{"smb1351-charger", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, smb1351_charger_id);
+
+static struct i2c_driver smb1351_charger_driver = {
+	.driver		= {
+		.name		= "smb1351-charger",
+		.owner		= THIS_MODULE,
+		.of_match_table	= smb1351_match_table,
+		.pm		= &smb1351_pm_ops,
+	},
+	.probe		= smb1351_charger_probe,
+	.remove		= smb1351_charger_remove,
+	.id_table	= smb1351_charger_id,
+};
+
+module_i2c_driver(smb1351_charger_driver);
+
+MODULE_DESCRIPTION("smb1351 Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb1351-charger");
diff --git a/drivers/power/supply/qcom/smb135x-charger.c b/drivers/power/supply/qcom/smb135x-charger.c
new file mode 100644
index 0000000..803dd6e
--- /dev/null
+++ b/drivers/power/supply/qcom/smb135x-charger.c
@@ -0,0 +1,4578 @@
+/* Copyright (c) 2013-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/i2c.h>
+#include <linux/debugfs.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/bitops.h>
+#include <linux/mutex.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/pinctrl/consumer.h>
+
+#define SMB135X_BITS_PER_REG	8
+
+/* Mask/Bit helpers */
+#define _SMB135X_MASK(BITS, POS) \
+	((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB135X_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+		_SMB135X_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+				(RIGHT_BIT_POS))
+
+/* Config registers */
+#define CFG_3_REG			0x03
+#define CHG_ITERM_50MA			0x08
+#define CHG_ITERM_100MA			0x10
+#define CHG_ITERM_150MA			0x18
+#define CHG_ITERM_200MA			0x20
+#define CHG_ITERM_250MA			0x28
+#define CHG_ITERM_300MA			0x00
+#define CHG_ITERM_500MA			0x30
+#define CHG_ITERM_600MA			0x38
+#define CHG_ITERM_MASK			SMB135X_MASK(5, 3)
+
+#define CFG_4_REG			0x04
+#define CHG_INHIBIT_MASK		SMB135X_MASK(7, 6)
+#define CHG_INHIBIT_50MV_VAL		0x00
+#define CHG_INHIBIT_100MV_VAL		0x40
+#define CHG_INHIBIT_200MV_VAL		0x80
+#define CHG_INHIBIT_300MV_VAL		0xC0
+
+#define CFG_5_REG			0x05
+#define RECHARGE_200MV_BIT		BIT(2)
+#define USB_2_3_BIT			BIT(5)
+
+#define CFG_A_REG			0x0A
+#define DCIN_INPUT_MASK			SMB135X_MASK(4, 0)
+
+#define CFG_C_REG			0x0C
+#define USBIN_INPUT_MASK		SMB135X_MASK(4, 0)
+#define USBIN_ADAPTER_ALLOWANCE_MASK	SMB135X_MASK(7, 5)
+#define ALLOW_5V_ONLY			0x00
+#define ALLOW_5V_OR_9V			0x20
+#define ALLOW_5V_TO_9V			0x40
+#define ALLOW_9V_ONLY			0x60
+
+#define CFG_D_REG			0x0D
+
+#define CFG_E_REG			0x0E
+#define POLARITY_100_500_BIT		BIT(2)
+#define USB_CTRL_BY_PIN_BIT		BIT(1)
+#define HVDCP_5_9_BIT			BIT(4)
+
+#define CFG_11_REG			0x11
+#define PRIORITY_BIT			BIT(7)
+#define AUTO_SRC_DET_EN_BIT			BIT(0)
+
+#define USBIN_DCIN_CFG_REG		0x12
+#define USBIN_SUSPEND_VIA_COMMAND_BIT	BIT(6)
+
+#define CFG_14_REG			0x14
+#define CHG_EN_BY_PIN_BIT		BIT(7)
+#define CHG_EN_ACTIVE_LOW_BIT		BIT(6)
+#define CHG_EN_ACTIVE_HIGH_BIT		0x0
+#define PRE_TO_FAST_REQ_CMD_BIT		BIT(5)
+#define DISABLE_CURRENT_TERM_BIT	BIT(3)
+#define DISABLE_AUTO_RECHARGE_BIT	BIT(2)
+#define EN_CHG_INHIBIT_BIT		BIT(0)
+
+#define CFG_16_REG			0x16
+#define SAFETY_TIME_EN_BIT		BIT(5)
+#define SAFETY_TIME_EN_SHIFT		5
+#define SAFETY_TIME_MINUTES_MASK	SMB135X_MASK(3, 2)
+#define SAFETY_TIME_MINUTES_SHIFT	2
+
+#define CFG_17_REG			0x17
+#define CHG_STAT_DISABLE_BIT		BIT(0)
+#define CHG_STAT_ACTIVE_HIGH_BIT	BIT(1)
+#define CHG_STAT_IRQ_ONLY_BIT		BIT(4)
+
+#define CFG_19_REG			0x19
+#define BATT_MISSING_ALGO_BIT		BIT(2)
+#define BATT_MISSING_THERM_BIT		BIT(1)
+
+#define CFG_1A_REG			0x1A
+#define HOT_SOFT_VFLOAT_COMP_EN_BIT	BIT(3)
+#define COLD_SOFT_VFLOAT_COMP_EN_BIT	BIT(2)
+#define HOT_SOFT_CURRENT_COMP_EN_BIT	BIT(1)
+#define COLD_SOFT_CURRENT_COMP_EN_BIT	BIT(0)
+
+#define CFG_1B_REG			0x1B
+#define COLD_HARD_MASK			SMB135X_MASK(7, 6)
+#define COLD_HARD_SHIFT			6
+#define HOT_HARD_MASK			SMB135X_MASK(5, 4)
+#define HOT_HARD_SHIFT			4
+#define COLD_SOFT_MASK			SMB135X_MASK(3, 2)
+#define COLD_SOFT_SHIFT			2
+#define HOT_SOFT_MASK			SMB135X_MASK(1, 0)
+#define HOT_SOFT_SHIFT			0
+
+#define VFLOAT_REG			0x1E
+
+#define VERSION1_REG			0x2A
+#define VERSION1_MASK			SMB135X_MASK(7,	6)
+#define VERSION1_SHIFT			6
+#define VERSION2_REG			0x32
+#define VERSION2_MASK			SMB135X_MASK(1,	0)
+#define VERSION3_REG			0x34
+
+/* Irq Config registers */
+#define IRQ_CFG_REG			0x07
+#define IRQ_BAT_HOT_COLD_HARD_BIT	BIT(7)
+#define IRQ_BAT_HOT_COLD_SOFT_BIT	BIT(6)
+#define IRQ_OTG_OVER_CURRENT_BIT	BIT(4)
+#define IRQ_USBIN_UV_BIT		BIT(2)
+#define IRQ_INTERNAL_TEMPERATURE_BIT	BIT(0)
+
+#define IRQ2_CFG_REG			0x08
+#define IRQ2_SAFETY_TIMER_BIT		BIT(7)
+#define IRQ2_CHG_ERR_BIT		BIT(6)
+#define IRQ2_CHG_PHASE_CHANGE_BIT	BIT(4)
+#define IRQ2_CHG_INHIBIT_BIT		BIT(3)
+#define IRQ2_POWER_OK_BIT		BIT(2)
+#define IRQ2_BATT_MISSING_BIT		BIT(1)
+#define IRQ2_VBAT_LOW_BIT		BIT(0)
+
+#define IRQ3_CFG_REG			0x09
+#define IRQ3_RID_DETECT_BIT		BIT(4)
+#define IRQ3_SRC_DETECT_BIT		BIT(2)
+#define IRQ3_DCIN_UV_BIT		BIT(0)
+
+#define USBIN_OTG_REG			0x0F
+#define OTG_CNFG_MASK			SMB135X_MASK(3,	2)
+#define OTG_CNFG_PIN_CTRL		0x04
+#define OTG_CNFG_COMMAND_CTRL		0x08
+#define OTG_CNFG_AUTO_CTRL		0x0C
+
+/* Command Registers */
+#define CMD_I2C_REG			0x40
+#define ALLOW_VOLATILE_BIT		BIT(6)
+
+#define CMD_INPUT_LIMIT			0x41
+#define USB_SHUTDOWN_BIT		BIT(6)
+#define DC_SHUTDOWN_BIT			BIT(5)
+#define USE_REGISTER_FOR_CURRENT	BIT(2)
+#define USB_100_500_AC_MASK		SMB135X_MASK(1, 0)
+#define USB_100_VAL			0x02
+#define USB_500_VAL			0x00
+#define USB_AC_VAL			0x01
+
+#define CMD_CHG_REG			0x42
+#define CMD_CHG_EN			BIT(1)
+#define OTG_EN				BIT(0)
+
+/* Status registers */
+#define STATUS_1_REG			0x47
+#define USING_USB_BIT			BIT(1)
+#define USING_DC_BIT			BIT(0)
+
+#define STATUS_2_REG			0x48
+#define HARD_LIMIT_STS_BIT		BIT(6)
+
+#define STATUS_4_REG			0x4A
+#define BATT_NET_CHG_CURRENT_BIT	BIT(7)
+#define BATT_LESS_THAN_2V		BIT(4)
+#define CHG_HOLD_OFF_BIT		BIT(3)
+#define CHG_TYPE_MASK			SMB135X_MASK(2, 1)
+#define CHG_TYPE_SHIFT			1
+#define BATT_NOT_CHG_VAL		0x0
+#define BATT_PRE_CHG_VAL		0x1
+#define BATT_FAST_CHG_VAL		0x2
+#define BATT_TAPER_CHG_VAL		0x3
+#define CHG_EN_BIT			BIT(0)
+
+#define STATUS_5_REG			0x4B
+#define CDP_BIT				BIT(7)
+#define DCP_BIT				BIT(6)
+#define OTHER_BIT			BIT(5)
+#define SDP_BIT				BIT(4)
+#define ACA_A_BIT			BIT(3)
+#define ACA_B_BIT			BIT(2)
+#define ACA_C_BIT			BIT(1)
+#define ACA_DOCK_BIT			BIT(0)
+
+#define STATUS_6_REG			0x4C
+#define RID_FLOAT_BIT			BIT(3)
+#define RID_A_BIT			BIT(2)
+#define RID_B_BIT			BIT(1)
+#define RID_C_BIT			BIT(0)
+
+#define STATUS_7_REG			0x4D
+
+#define STATUS_8_REG			0x4E
+#define USBIN_9V			BIT(5)
+#define USBIN_UNREG			BIT(4)
+#define USBIN_LV			BIT(3)
+#define DCIN_9V				BIT(2)
+#define DCIN_UNREG			BIT(1)
+#define DCIN_LV				BIT(0)
+
+#define STATUS_9_REG			0x4F
+#define REV_MASK			SMB135X_MASK(3, 0)
+
+/* Irq Status registers */
+#define IRQ_A_REG			0x50
+#define IRQ_A_HOT_HARD_BIT		BIT(6)
+#define IRQ_A_COLD_HARD_BIT		BIT(4)
+#define IRQ_A_HOT_SOFT_BIT		BIT(2)
+#define IRQ_A_COLD_SOFT_BIT		BIT(0)
+
+#define IRQ_B_REG			0x51
+#define IRQ_B_BATT_TERMINAL_BIT		BIT(6)
+#define IRQ_B_BATT_MISSING_BIT		BIT(4)
+#define IRQ_B_VBAT_LOW_BIT		BIT(2)
+#define IRQ_B_TEMPERATURE_BIT		BIT(0)
+
+#define IRQ_C_REG			0x52
+#define IRQ_C_TERM_BIT			BIT(0)
+#define IRQ_C_FASTCHG_BIT		BIT(6)
+
+#define IRQ_D_REG			0x53
+#define IRQ_D_TIMEOUT_BIT		BIT(2)
+
+#define IRQ_E_REG			0x54
+#define IRQ_E_DC_OV_BIT			BIT(6)
+#define IRQ_E_DC_UV_BIT			BIT(4)
+#define IRQ_E_USB_OV_BIT		BIT(2)
+#define IRQ_E_USB_UV_BIT		BIT(0)
+
+#define IRQ_F_REG			0x55
+#define IRQ_F_POWER_OK_BIT		BIT(0)
+
+#define IRQ_G_REG			0x56
+#define IRQ_G_SRC_DETECT_BIT		BIT(6)
+
+enum {
+	WRKARND_USB100_BIT = BIT(0),
+	WRKARND_APSD_FAIL = BIT(1),
+};
+
+enum {
+	REV_1 = 1,	/* Rev 1.0 */
+	REV_1_1 = 2,	/* Rev 1.1 */
+	REV_2 = 3,		/* Rev 2 */
+	REV_2_1 = 5,	/* Rev 2.1 */
+	REV_MAX,
+};
+
+static char *revision_str[] = {
+	[REV_1] = "rev1",
+	[REV_1_1] = "rev1.1",
+	[REV_2] = "rev2",
+	[REV_2_1] = "rev2.1",
+};
+
+enum {
+	V_SMB1356,
+	V_SMB1357,
+	V_SMB1358,
+	V_SMB1359,
+	V_MAX,
+};
+
+static int version_data[] = {
+	[V_SMB1356] = V_SMB1356,
+	[V_SMB1357] = V_SMB1357,
+	[V_SMB1358] = V_SMB1358,
+	[V_SMB1359] = V_SMB1359,
+};
+
+static char *version_str[] = {
+	[V_SMB1356] = "smb1356",
+	[V_SMB1357] = "smb1357",
+	[V_SMB1358] = "smb1358",
+	[V_SMB1359] = "smb1359",
+};
+
+enum {
+	USER = BIT(0),
+	THERMAL = BIT(1),
+	CURRENT = BIT(2),
+};
+
+enum path_type {
+	USB,
+	DC,
+};
+
+static int chg_time[] = {
+	192,
+	384,
+	768,
+	1536,
+};
+
+static char *pm_batt_supplied_to[] = {
+	"bms",
+};
+
+struct smb135x_regulator {
+	struct regulator_desc	rdesc;
+	struct regulator_dev	*rdev;
+};
+
+struct smb135x_chg {
+	struct i2c_client		*client;
+	struct device			*dev;
+	struct mutex			read_write_lock;
+
+	u8				revision;
+	int				version;
+
+	bool				chg_enabled;
+	bool				chg_disabled_permanently;
+
+	bool				usb_present;
+	bool				dc_present;
+	bool				usb_slave_present;
+	bool				dc_ov;
+
+	bool				bmd_algo_disabled;
+	bool				iterm_disabled;
+	int				iterm_ma;
+	int				vfloat_mv;
+	int				safety_time;
+	int				resume_delta_mv;
+	int				fake_battery_soc;
+	struct dentry			*debug_root;
+	int				usb_current_arr_size;
+	int				*usb_current_table;
+	int				dc_current_arr_size;
+	int				*dc_current_table;
+	bool				inhibit_disabled;
+	int				fastchg_current_arr_size;
+	int				*fastchg_current_table;
+	int				fastchg_ma;
+	u8				irq_cfg_mask[3];
+	int				otg_oc_count;
+	struct delayed_work		reset_otg_oc_count_work;
+	struct mutex			otg_oc_count_lock;
+	struct delayed_work		hvdcp_det_work;
+
+	bool				parallel_charger;
+	bool				parallel_charger_present;
+	bool				bms_controlled_charging;
+	u32				parallel_pin_polarity_setting;
+
+	/* psy */
+	struct power_supply		*usb_psy;
+	int				usb_psy_ma;
+	int				real_usb_psy_ma;
+	struct power_supply_desc	batt_psy_d;
+	struct power_supply		*batt_psy;
+	struct power_supply_desc	dc_psy_d;
+	struct power_supply		*dc_psy;
+	struct power_supply_desc	parallel_psy_d;
+	struct power_supply		*parallel_psy;
+	struct power_supply		*bms_psy;
+	int				dc_psy_type;
+	int				dc_psy_ma;
+	const char			*bms_psy_name;
+
+	/* status tracking */
+	bool				chg_done_batt_full;
+	bool				batt_present;
+	bool				batt_hot;
+	bool				batt_cold;
+	bool				batt_warm;
+	bool				batt_cool;
+
+	bool				resume_completed;
+	bool				irq_waiting;
+	u32				usb_suspended;
+	u32				dc_suspended;
+	struct mutex			path_suspend_lock;
+
+	u32				peek_poke_address;
+	struct smb135x_regulator	otg_vreg;
+	int				skip_writes;
+	int				skip_reads;
+	u32				workaround_flags;
+	bool				soft_vfloat_comp_disabled;
+	bool				soft_current_comp_disabled;
+	struct mutex			irq_complete;
+	struct regulator		*therm_bias_vreg;
+	struct regulator		*usb_pullup_vreg;
+	struct delayed_work		wireless_insertion_work;
+
+	unsigned int			thermal_levels;
+	unsigned int			therm_lvl_sel;
+	unsigned int			*thermal_mitigation;
+	unsigned int			gamma_setting_num;
+	unsigned int			*gamma_setting;
+	struct mutex			current_change_lock;
+
+	const char			*pinctrl_state_name;
+	struct pinctrl			*smb_pinctrl;
+
+	bool				apsd_rerun;
+	bool				id_line_not_connected;
+};
+
+#define RETRY_COUNT 5
+int retry_sleep_ms[RETRY_COUNT] = {
+	10, 20, 30, 40, 50
+};
+
+static int __smb135x_read(struct smb135x_chg *chip, int reg,
+				u8 *val)
+{
+	s32 ret;
+	int retry_count = 0;
+
+retry:
+	ret = i2c_smbus_read_byte_data(chip->client, reg);
+	if (ret < 0 && retry_count < RETRY_COUNT) {
+		/* sleep for few ms before retrying */
+		msleep(retry_sleep_ms[retry_count++]);
+		goto retry;
+	}
+	if (ret < 0) {
+		dev_err(chip->dev,
+			"i2c read fail: can't read from %02x: %d\n", reg, ret);
+		return ret;
+	}
+	*val = ret;
+
+	return 0;
+}
+
+static int __smb135x_write(struct smb135x_chg *chip, int reg,
+						u8 val)
+{
+	s32 ret;
+	int retry_count = 0;
+
+retry:
+	ret = i2c_smbus_write_byte_data(chip->client, reg, val);
+	if (ret < 0 && retry_count < RETRY_COUNT) {
+		/* sleep for few ms before retrying */
+		msleep(retry_sleep_ms[retry_count++]);
+		goto retry;
+	}
+	if (ret < 0) {
+		dev_err(chip->dev,
+			"i2c write fail: can't write %02x to %02x: %d\n",
+			val, reg, ret);
+		return ret;
+	}
+	pr_debug("Writing 0x%02x=0x%02x\n", reg, val);
+	return 0;
+}
+
+static int smb135x_read(struct smb135x_chg *chip, int reg,
+				u8 *val)
+{
+	int rc;
+
+	if (chip->skip_reads) {
+		*val = 0;
+		return 0;
+	}
+	mutex_lock(&chip->read_write_lock);
+	pm_stay_awake(chip->dev);
+	rc = __smb135x_read(chip, reg, val);
+	pm_relax(chip->dev);
+	mutex_unlock(&chip->read_write_lock);
+
+	return rc;
+}
+
+static int smb135x_write(struct smb135x_chg *chip, int reg,
+						u8 val)
+{
+	int rc;
+
+	if (chip->skip_writes)
+		return 0;
+
+	mutex_lock(&chip->read_write_lock);
+	pm_stay_awake(chip->dev);
+	rc = __smb135x_write(chip, reg, val);
+	pm_relax(chip->dev);
+	mutex_unlock(&chip->read_write_lock);
+
+	return rc;
+}
+
+static int smb135x_masked_write(struct smb135x_chg *chip, int reg,
+						u8 mask, u8 val)
+{
+	s32 rc;
+	u8 temp;
+
+	if (chip->skip_writes || chip->skip_reads)
+		return 0;
+
+	mutex_lock(&chip->read_write_lock);
+	rc = __smb135x_read(chip, reg, &temp);
+	if (rc < 0) {
+		dev_err(chip->dev, "read failed: reg=%03X, rc=%d\n", reg, rc);
+		goto out;
+	}
+	temp &= ~mask;
+	temp |= val & mask;
+	rc = __smb135x_write(chip, reg, temp);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"write failed: reg=%03X, rc=%d\n", reg, rc);
+	}
+out:
+	mutex_unlock(&chip->read_write_lock);
+	return rc;
+}
+
+static int read_revision(struct smb135x_chg *chip, u8 *revision)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, STATUS_9_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
+		return rc;
+	}
+	*revision = (reg & REV_MASK);
+	return 0;
+}
+
+static int read_version1(struct smb135x_chg *chip, u8 *version)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, VERSION1_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read version 1 rc = %d\n", rc);
+		return rc;
+	}
+	*version = (reg & VERSION1_MASK) >> VERSION1_SHIFT;
+	return 0;
+}
+
+static int read_version2(struct smb135x_chg *chip, u8 *version)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, VERSION2_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read version 2 rc = %d\n", rc);
+		return rc;
+	}
+	*version = (reg & VERSION2_MASK);
+	return 0;
+}
+
+static int read_version3(struct smb135x_chg *chip, u8 *version)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, VERSION3_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read version 3 rc = %d\n", rc);
+		return rc;
+	}
+	*version = reg;
+	return 0;
+}
+
+#define TRIM_23_REG		0x23
+#define CHECK_USB100_GOOD_BIT	BIT(1)
+static bool is_usb100_broken(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, TRIM_23_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read status 9 rc = %d\n", rc);
+		return rc;
+	}
+	return !!(reg & CHECK_USB100_GOOD_BIT);
+}
+
+static bool is_usb_slave_present(struct smb135x_chg *chip)
+{
+	bool usb_slave_present;
+	u8 reg;
+	int rc;
+
+	if (chip->id_line_not_connected)
+		return false;
+
+	rc = smb135x_read(chip, STATUS_6_REG, &reg);
+	if (rc < 0) {
+		pr_err("Couldn't read stat 6 rc = %d\n", rc);
+		return false;
+	}
+
+	if ((reg & (RID_FLOAT_BIT | RID_A_BIT | RID_B_BIT | RID_C_BIT)) == 0)
+		usb_slave_present = 1;
+	else
+		usb_slave_present = 0;
+
+	pr_debug("stat6= 0x%02x slave_present = %d\n", reg, usb_slave_present);
+	return usb_slave_present;
+}
+
+static char *usb_type_str[] = {
+	"ACA_DOCK",	/* bit 0 */
+	"ACA_C",	/* bit 1 */
+	"ACA_B",	/* bit 2 */
+	"ACA_A",	/* bit 3 */
+	"SDP",		/* bit 4 */
+	"OTHER",	/* bit 5 */
+	"DCP",		/* bit 6 */
+	"CDP",		/* bit 7 */
+	"NONE",		/* bit 8  error case */
+};
+
+/* helper to return the string of USB type */
+static char *get_usb_type_name(u8 stat_5)
+{
+	unsigned long stat = stat_5;
+
+	return usb_type_str[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
+}
+
+static enum power_supply_type usb_type_enum[] = {
+	POWER_SUPPLY_TYPE_USB_ACA,	/* bit 0 */
+	POWER_SUPPLY_TYPE_USB_ACA,	/* bit 1 */
+	POWER_SUPPLY_TYPE_USB_ACA,	/* bit 2 */
+	POWER_SUPPLY_TYPE_USB_ACA,	/* bit 3 */
+	POWER_SUPPLY_TYPE_USB,		/* bit 4 */
+	POWER_SUPPLY_TYPE_UNKNOWN,	/* bit 5 */
+	POWER_SUPPLY_TYPE_USB_DCP,	/* bit 6 */
+	POWER_SUPPLY_TYPE_USB_CDP,	/* bit 7 */
+	POWER_SUPPLY_TYPE_UNKNOWN,	/* bit 8 error case, report UNKNWON */
+};
+
+/* helper to return enum power_supply_type of USB type */
+static enum power_supply_type get_usb_supply_type(u8 stat_5)
+{
+	unsigned long stat = stat_5;
+
+	return usb_type_enum[find_first_bit(&stat, SMB135X_BITS_PER_REG)];
+}
+
+static enum power_supply_property smb135x_battery_properties[] = {
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_TECHNOLOGY,
+	POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+};
+
+static int smb135x_get_prop_batt_status(struct smb135x_chg *chip)
+{
+	int rc;
+	int status = POWER_SUPPLY_STATUS_DISCHARGING;
+	u8 reg = 0;
+	u8 chg_type;
+
+	if (chip->chg_done_batt_full)
+		return POWER_SUPPLY_STATUS_FULL;
+
+	rc = smb135x_read(chip, STATUS_4_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Unable to read STATUS_4_REG rc = %d\n", rc);
+		return POWER_SUPPLY_STATUS_UNKNOWN;
+	}
+
+	if (reg & CHG_HOLD_OFF_BIT) {
+		/*
+		 * when chg hold off happens the battery is
+		 * not charging
+		 */
+		status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+		goto out;
+	}
+
+	chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+
+	if (chg_type == BATT_NOT_CHG_VAL)
+		status = POWER_SUPPLY_STATUS_DISCHARGING;
+	else
+		status = POWER_SUPPLY_STATUS_CHARGING;
+out:
+	pr_debug("STATUS_4_REG=%x\n", reg);
+	return status;
+}
+
+static int smb135x_get_prop_batt_present(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, STATUS_4_REG, &reg);
+	if (rc < 0)
+		return 0;
+
+	/* treat battery gone if less than 2V */
+	if (reg & BATT_LESS_THAN_2V)
+		return 0;
+
+	return chip->batt_present;
+}
+
+static int smb135x_get_prop_charge_type(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 reg;
+	u8 chg_type;
+
+	rc = smb135x_read(chip, STATUS_4_REG, &reg);
+	if (rc < 0)
+		return POWER_SUPPLY_CHARGE_TYPE_UNKNOWN;
+
+	chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+	if (chg_type == BATT_NOT_CHG_VAL)
+		return POWER_SUPPLY_CHARGE_TYPE_NONE;
+	else if (chg_type == BATT_FAST_CHG_VAL)
+		return POWER_SUPPLY_CHARGE_TYPE_FAST;
+	else if (chg_type == BATT_PRE_CHG_VAL)
+		return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+	else if (chg_type == BATT_TAPER_CHG_VAL)
+		return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+
+	return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+#define DEFAULT_BATT_CAPACITY	50
+static int smb135x_get_prop_batt_capacity(struct smb135x_chg *chip)
+{
+	union power_supply_propval ret = {0, };
+
+	if (chip->fake_battery_soc >= 0)
+		return chip->fake_battery_soc;
+	if (chip->bms_psy) {
+		power_supply_get_property(chip->bms_psy,
+				POWER_SUPPLY_PROP_CAPACITY, &ret);
+		return ret.intval;
+	}
+
+	return DEFAULT_BATT_CAPACITY;
+}
+
+static int smb135x_get_prop_batt_health(struct smb135x_chg *chip)
+{
+	union power_supply_propval ret = {0, };
+
+	if (chip->batt_hot)
+		ret.intval = POWER_SUPPLY_HEALTH_OVERHEAT;
+	else if (chip->batt_cold)
+		ret.intval = POWER_SUPPLY_HEALTH_COLD;
+	else if (chip->batt_warm)
+		ret.intval = POWER_SUPPLY_HEALTH_WARM;
+	else if (chip->batt_cool)
+		ret.intval = POWER_SUPPLY_HEALTH_COOL;
+	else
+		ret.intval = POWER_SUPPLY_HEALTH_GOOD;
+
+	return ret.intval;
+}
+
+static int smb135x_enable_volatile_writes(struct smb135x_chg *chip)
+{
+	int rc;
+
+	rc = smb135x_masked_write(chip, CMD_I2C_REG,
+			ALLOW_VOLATILE_BIT, ALLOW_VOLATILE_BIT);
+	if (rc < 0)
+		dev_err(chip->dev,
+			"Couldn't set VOLATILE_W_PERM_BIT rc=%d\n", rc);
+
+	return rc;
+}
+
+static int usb_current_table_smb1356[] = {
+	180,
+	240,
+	270,
+	285,
+	300,
+	330,
+	360,
+	390,
+	420,
+	540,
+	570,
+	600,
+	660,
+	720,
+	840,
+	900,
+	960,
+	1080,
+	1110,
+	1128,
+	1146,
+	1170,
+	1182,
+	1200,
+	1230,
+	1260,
+	1380,
+	1440,
+	1560,
+	1620,
+	1680,
+	1800
+};
+
+static int fastchg_current_table[] = {
+	300,
+	400,
+	450,
+	475,
+	500,
+	550,
+	600,
+	650,
+	700,
+	900,
+	950,
+	1000,
+	1100,
+	1200,
+	1400,
+	2700,
+	1500,
+	1600,
+	1800,
+	1850,
+	1880,
+	1910,
+	2800,
+	1950,
+	1970,
+	2000,
+	2050,
+	2100,
+	2300,
+	2400,
+	2500,
+	3000
+};
+
+static int usb_current_table_smb1357_smb1358[] = {
+	300,
+	400,
+	450,
+	475,
+	500,
+	550,
+	600,
+	650,
+	700,
+	900,
+	950,
+	1000,
+	1100,
+	1200,
+	1400,
+	1450,
+	1500,
+	1600,
+	1800,
+	1850,
+	1880,
+	1910,
+	1930,
+	1950,
+	1970,
+	2000,
+	2050,
+	2100,
+	2300,
+	2400,
+	2500,
+	3000
+};
+
+static int usb_current_table_smb1359[] = {
+	300,
+	400,
+	450,
+	475,
+	500,
+	550,
+	600,
+	650,
+	700,
+	900,
+	950,
+	1000,
+	1100,
+	1200,
+	1400,
+	1450,
+	1500,
+	1600,
+	1800,
+	1850,
+	1880,
+	1910,
+	1930,
+	1950,
+	1970,
+	2000,
+	2050,
+	2100,
+	2300,
+	2400,
+	2500
+};
+
+static int dc_current_table_smb1356[] = {
+	180,
+	240,
+	270,
+	285,
+	300,
+	330,
+	360,
+	390,
+	420,
+	540,
+	570,
+	600,
+	660,
+	720,
+	840,
+	870,
+	900,
+	960,
+	1080,
+	1110,
+	1128,
+	1146,
+	1158,
+	1170,
+	1182,
+	1200,
+};
+
+static int dc_current_table[] = {
+	300,
+	400,
+	450,
+	475,
+	500,
+	550,
+	600,
+	650,
+	700,
+	900,
+	950,
+	1000,
+	1100,
+	1200,
+	1400,
+	1450,
+	1500,
+	1600,
+	1800,
+	1850,
+	1880,
+	1910,
+	1930,
+	1950,
+	1970,
+	2000,
+};
+
+#define CURRENT_100_MA		100
+#define CURRENT_150_MA		150
+#define CURRENT_500_MA		500
+#define CURRENT_900_MA		900
+#define SUSPEND_CURRENT_MA	2
+
+static int __smb135x_usb_suspend(struct smb135x_chg *chip, bool suspend)
+{
+	int rc;
+
+	rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+			USB_SHUTDOWN_BIT, suspend ? USB_SHUTDOWN_BIT : 0);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
+	return rc;
+}
+
+static int __smb135x_dc_suspend(struct smb135x_chg *chip, bool suspend)
+{
+	int rc = 0;
+
+	rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+			DC_SHUTDOWN_BIT, suspend ? DC_SHUTDOWN_BIT : 0);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set cfg 11 rc = %d\n", rc);
+	return rc;
+}
+
+static int smb135x_path_suspend(struct smb135x_chg *chip, enum path_type path,
+						int reason, bool suspend)
+{
+	int rc = 0;
+	int suspended;
+	int *path_suspended;
+	int (*func)(struct smb135x_chg *chip, bool suspend);
+
+	mutex_lock(&chip->path_suspend_lock);
+	if (path == USB) {
+		suspended = chip->usb_suspended;
+		path_suspended = &chip->usb_suspended;
+		func = __smb135x_usb_suspend;
+	} else {
+		suspended = chip->dc_suspended;
+		path_suspended = &chip->dc_suspended;
+		func = __smb135x_dc_suspend;
+	}
+
+	if (suspend == false)
+		suspended &= ~reason;
+	else
+		suspended |= reason;
+
+	if (*path_suspended && !suspended)
+		rc = func(chip, 0);
+	if (!(*path_suspended) && suspended)
+		rc = func(chip, 1);
+
+	if (rc)
+		dev_err(chip->dev, "Couldn't set/unset suspend for %s path rc = %d\n",
+					path == USB ? "usb" : "dc",
+					rc);
+	else
+		*path_suspended = suspended;
+
+	mutex_unlock(&chip->path_suspend_lock);
+	return rc;
+}
+
+static int smb135x_get_usb_chg_current(struct smb135x_chg *chip)
+{
+	if (chip->usb_suspended)
+		return SUSPEND_CURRENT_MA;
+	else
+		return chip->real_usb_psy_ma;
+}
+#define FCC_MASK			SMB135X_MASK(4, 0)
+#define CFG_1C_REG			0x1C
+static int smb135x_get_fastchg_current(struct smb135x_chg *chip)
+{
+	u8 reg;
+	int rc;
+
+	rc = smb135x_read(chip, CFG_1C_REG, &reg);
+	if (rc < 0) {
+		pr_debug("cannot read 1c rc = %d\n", rc);
+		return 0;
+	}
+	reg &= FCC_MASK;
+	if (reg < 0 || chip->fastchg_current_arr_size == 0
+			|| reg > chip->fastchg_current_table[
+				chip->fastchg_current_arr_size - 1]) {
+		dev_err(chip->dev, "Current table out of range\n");
+		return -EINVAL;
+	}
+	return chip->fastchg_current_table[reg];
+}
+
+static int smb135x_set_fastchg_current(struct smb135x_chg *chip,
+							int current_ma)
+{
+	int i, rc, diff, best, best_diff;
+	u8 reg;
+
+	/*
+	 * if there is no array loaded or if the smallest current limit is
+	 * above the requested current, then do nothing
+	 */
+	if (chip->fastchg_current_arr_size == 0) {
+		dev_err(chip->dev, "no table loaded\n");
+		return -EINVAL;
+	} else if ((current_ma - chip->fastchg_current_table[0]) < 0) {
+		dev_err(chip->dev, "invalid current requested\n");
+		return -EINVAL;
+	}
+
+	/* use the closest setting under the requested current */
+	best = 0;
+	best_diff = current_ma - chip->fastchg_current_table[best];
+
+	for (i = 1; i < chip->fastchg_current_arr_size; i++) {
+		diff = current_ma - chip->fastchg_current_table[i];
+		if (diff >= 0 && diff < best_diff) {
+			best_diff = diff;
+			best = i;
+		}
+	}
+	i = best;
+
+	reg = i & FCC_MASK;
+	rc = smb135x_masked_write(chip, CFG_1C_REG, FCC_MASK, reg);
+	if (rc < 0)
+		dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
+	pr_debug("fastchg current set to %dma\n",
+			chip->fastchg_current_table[i]);
+	return rc;
+}
+
+static int smb135x_set_high_usb_chg_current(struct smb135x_chg *chip,
+							int current_ma)
+{
+	int i, rc;
+	u8 usb_cur_val;
+
+	for (i = chip->usb_current_arr_size - 1; i >= 0; i--) {
+		if (current_ma >= chip->usb_current_table[i])
+			break;
+	}
+	if (i < 0) {
+		dev_err(chip->dev,
+			"Cannot find %dma current_table using %d\n",
+			current_ma, CURRENT_150_MA);
+		rc = smb135x_masked_write(chip, CFG_5_REG,
+						USB_2_3_BIT, USB_2_3_BIT);
+		rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+				USB_100_500_AC_MASK, USB_100_VAL);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
+					CURRENT_150_MA, rc);
+		else
+			chip->real_usb_psy_ma = CURRENT_150_MA;
+		return rc;
+	}
+
+	usb_cur_val = i & USBIN_INPUT_MASK;
+	rc = smb135x_masked_write(chip, CFG_C_REG,
+				USBIN_INPUT_MASK, usb_cur_val);
+	if (rc < 0) {
+		dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
+		return rc;
+	}
+
+	rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+					USB_100_500_AC_MASK, USB_AC_VAL);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc);
+	else
+		chip->real_usb_psy_ma = chip->usb_current_table[i];
+	return rc;
+}
+
+#define MAX_VERSION			0xF
+#define USB_100_PROBLEM_VERSION		0x2
+/* if APSD results are used
+ *	if SDP is detected it will look at 500mA setting
+ *		if set it will draw 500mA
+ *		if unset it will draw 100mA
+ *	if CDP/DCP it will look at 0x0C setting
+ *		i.e. values in 0x41[1, 0] does not matter
+ */
+static int smb135x_set_usb_chg_current(struct smb135x_chg *chip,
+							int current_ma)
+{
+	int rc;
+
+	pr_debug("USB current_ma = %d\n", current_ma);
+
+	if (chip->workaround_flags & WRKARND_USB100_BIT) {
+		pr_info("USB requested = %dmA using %dmA\n", current_ma,
+						CURRENT_500_MA);
+		current_ma = CURRENT_500_MA;
+	}
+
+	if (current_ma == 0)
+		/* choose the lowest available value of 100mA */
+		current_ma = CURRENT_100_MA;
+
+	if (current_ma == SUSPEND_CURRENT_MA) {
+		/* force suspend bit */
+		rc = smb135x_path_suspend(chip, USB, CURRENT, true);
+		chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
+		goto out;
+	}
+	if (current_ma < CURRENT_150_MA) {
+		/* force 100mA */
+		rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
+		rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+				USB_100_500_AC_MASK, USB_100_VAL);
+		rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+		chip->real_usb_psy_ma = CURRENT_100_MA;
+		goto out;
+	}
+	/* specific current values */
+	if (current_ma == CURRENT_150_MA) {
+		rc = smb135x_masked_write(chip, CFG_5_REG,
+						USB_2_3_BIT, USB_2_3_BIT);
+		rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+				USB_100_500_AC_MASK, USB_100_VAL);
+		rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+		chip->real_usb_psy_ma = CURRENT_150_MA;
+		goto out;
+	}
+	if (current_ma == CURRENT_500_MA) {
+		rc = smb135x_masked_write(chip, CFG_5_REG, USB_2_3_BIT, 0);
+		rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+				USB_100_500_AC_MASK, USB_500_VAL);
+		rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+		chip->real_usb_psy_ma = CURRENT_500_MA;
+		goto out;
+	}
+	if (current_ma == CURRENT_900_MA) {
+		rc = smb135x_masked_write(chip, CFG_5_REG,
+						USB_2_3_BIT, USB_2_3_BIT);
+		rc |= smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+				USB_100_500_AC_MASK, USB_500_VAL);
+		rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+		chip->real_usb_psy_ma = CURRENT_900_MA;
+		goto out;
+	}
+
+	rc = smb135x_set_high_usb_chg_current(chip, current_ma);
+	rc |= smb135x_path_suspend(chip, USB, CURRENT, false);
+out:
+	if (rc < 0)
+		dev_err(chip->dev,
+			"Couldn't set %dmA rc = %d\n", current_ma, rc);
+	return rc;
+}
+
+static int smb135x_set_dc_chg_current(struct smb135x_chg *chip,
+							int current_ma)
+{
+	int i, rc;
+	u8 dc_cur_val;
+
+	for (i = chip->dc_current_arr_size - 1; i >= 0; i--) {
+		if (chip->dc_psy_ma >= chip->dc_current_table[i])
+			break;
+	}
+	dc_cur_val = i & DCIN_INPUT_MASK;
+	rc = smb135x_masked_write(chip, CFG_A_REG,
+				DCIN_INPUT_MASK, dc_cur_val);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
+				rc);
+		return rc;
+	}
+	return 0;
+}
+
+static int smb135x_set_appropriate_current(struct smb135x_chg *chip,
+						enum path_type path)
+{
+	int therm_ma, current_ma;
+	int path_current = (path == USB) ? chip->usb_psy_ma : chip->dc_psy_ma;
+	int (*func)(struct smb135x_chg *chip, int current_ma);
+	int rc = 0;
+
+	if (!chip->usb_psy && path == USB)
+		return 0;
+
+	/*
+	 * If battery is absent do not modify the current at all, these
+	 * would be some appropriate values set by the bootloader or default
+	 * configuration and since it is the only source of power we should
+	 * not change it
+	 */
+	if (!chip->batt_present) {
+		pr_debug("ignoring current request since battery is absent\n");
+		return 0;
+	}
+
+	if (path == USB) {
+		path_current = chip->usb_psy_ma;
+		func = smb135x_set_usb_chg_current;
+	} else {
+		path_current = chip->dc_psy_ma;
+		func = smb135x_set_dc_chg_current;
+		if (chip->dc_psy_type == -EINVAL)
+			func = NULL;
+	}
+
+	if (chip->therm_lvl_sel > 0
+			&& chip->therm_lvl_sel < (chip->thermal_levels - 1))
+		/*
+		 * consider thermal limit only when it is active and not at
+		 * the highest level
+		 */
+		therm_ma = chip->thermal_mitigation[chip->therm_lvl_sel];
+	else
+		therm_ma = path_current;
+
+	current_ma = min(therm_ma, path_current);
+	if (func != NULL)
+		rc = func(chip, current_ma);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set %s current to min(%d, %d)rc = %d\n",
+				path == USB ? "usb" : "dc",
+				therm_ma, path_current,
+				rc);
+	return rc;
+}
+
+static int smb135x_charging_enable(struct smb135x_chg *chip, int enable)
+{
+	int rc;
+
+	rc = smb135x_masked_write(chip, CMD_CHG_REG,
+				CMD_CHG_EN, enable ? CMD_CHG_EN : 0);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't set CHG_ENABLE_BIT enable = %d rc = %d\n",
+			enable, rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int __smb135x_charging(struct smb135x_chg *chip, int enable)
+{
+	int rc = 0;
+
+	pr_debug("charging enable = %d\n", enable);
+
+	if (chip->chg_disabled_permanently) {
+		pr_debug("charging is disabled permanetly\n");
+		return -EINVAL;
+	}
+
+	rc = smb135x_charging_enable(chip, enable);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't %s charging  rc = %d\n",
+			enable ? "enable" : "disable", rc);
+		return rc;
+	}
+	chip->chg_enabled = enable;
+
+	/* set the suspended status */
+	rc = smb135x_path_suspend(chip, DC, USER, !enable);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't set dc suspend to %d rc = %d\n",
+			enable, rc);
+		return rc;
+	}
+	rc = smb135x_path_suspend(chip, USB, USER, !enable);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't set usb suspend to %d rc = %d\n",
+			enable, rc);
+		return rc;
+	}
+
+	pr_debug("charging %s\n",
+			enable ?  "enabled" : "disabled running from batt");
+	return rc;
+}
+
+static int smb135x_charging(struct smb135x_chg *chip, int enable)
+{
+	int rc = 0;
+
+	pr_debug("charging enable = %d\n", enable);
+
+	__smb135x_charging(chip, enable);
+
+	if (chip->usb_psy) {
+		pr_debug("usb psy changed\n");
+		power_supply_changed(chip->usb_psy);
+	}
+	if (chip->dc_psy_type != -EINVAL) {
+		pr_debug("dc psy changed\n");
+		power_supply_changed(chip->dc_psy);
+	}
+	pr_debug("charging %s\n",
+			enable ?  "enabled" : "disabled running from batt");
+	return rc;
+}
+
+static int smb135x_system_temp_level_set(struct smb135x_chg *chip,
+								int lvl_sel)
+{
+	int rc = 0;
+	int prev_therm_lvl;
+
+	if (!chip->thermal_mitigation) {
+		pr_err("Thermal mitigation not supported\n");
+		return -EINVAL;
+	}
+
+	if (lvl_sel < 0) {
+		pr_err("Unsupported level selected %d\n", lvl_sel);
+		return -EINVAL;
+	}
+
+	if (lvl_sel >= chip->thermal_levels) {
+		pr_err("Unsupported level selected %d forcing %d\n", lvl_sel,
+				chip->thermal_levels - 1);
+		lvl_sel = chip->thermal_levels - 1;
+	}
+
+	if (lvl_sel == chip->therm_lvl_sel)
+		return 0;
+
+	mutex_lock(&chip->current_change_lock);
+	prev_therm_lvl = chip->therm_lvl_sel;
+	chip->therm_lvl_sel = lvl_sel;
+	if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) {
+		/*
+		 * Disable charging if highest value selected by
+		 * setting the DC and USB path in suspend
+		 */
+		rc = smb135x_path_suspend(chip, DC, THERMAL, true);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set dc suspend rc %d\n", rc);
+			goto out;
+		}
+		rc = smb135x_path_suspend(chip, USB, THERMAL, true);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set usb suspend rc %d\n", rc);
+			goto out;
+		}
+		goto out;
+	}
+
+	smb135x_set_appropriate_current(chip, USB);
+	smb135x_set_appropriate_current(chip, DC);
+
+	if (prev_therm_lvl == chip->thermal_levels - 1) {
+		/*
+		 * If previously highest value was selected charging must have
+		 * been disabed. Enable charging by taking the DC and USB path
+		 * out of suspend.
+		 */
+		rc = smb135x_path_suspend(chip, DC, THERMAL, false);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set dc suspend rc %d\n", rc);
+			goto out;
+		}
+		rc = smb135x_path_suspend(chip, USB, THERMAL, false);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set usb suspend rc %d\n", rc);
+			goto out;
+		}
+	}
+out:
+	mutex_unlock(&chip->current_change_lock);
+	return rc;
+}
+
+static int smb135x_battery_set_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       const union power_supply_propval *val)
+{
+	int rc = 0, update_psy = 0;
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		if (!chip->bms_controlled_charging) {
+			rc = -EINVAL;
+			break;
+		}
+		switch (val->intval) {
+		case POWER_SUPPLY_STATUS_FULL:
+			rc = smb135x_charging_enable(chip, false);
+			if (rc < 0) {
+				dev_err(chip->dev, "Couldn't disable charging rc = %d\n",
+						rc);
+			} else {
+				chip->chg_done_batt_full = true;
+				update_psy = 1;
+				dev_dbg(chip->dev, "status = FULL chg_done_batt_full = %d",
+						chip->chg_done_batt_full);
+			}
+			break;
+		case POWER_SUPPLY_STATUS_DISCHARGING:
+			chip->chg_done_batt_full = false;
+			update_psy = 1;
+			dev_dbg(chip->dev, "status = DISCHARGING chg_done_batt_full = %d",
+					chip->chg_done_batt_full);
+			break;
+		case POWER_SUPPLY_STATUS_CHARGING:
+			rc = smb135x_charging_enable(chip, true);
+			if (rc < 0) {
+				dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+						rc);
+			} else {
+				chip->chg_done_batt_full = false;
+				dev_dbg(chip->dev, "status = CHARGING chg_done_batt_full = %d",
+						chip->chg_done_batt_full);
+			}
+			break;
+		default:
+			update_psy = 0;
+			rc = -EINVAL;
+		}
+		break;
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		smb135x_charging(chip, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		chip->fake_battery_soc = val->intval;
+		update_psy = 1;
+		break;
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+		smb135x_system_temp_level_set(chip, val->intval);
+		break;
+	default:
+		rc = -EINVAL;
+	}
+
+	if (!rc && update_psy)
+		power_supply_changed(chip->batt_psy);
+	return rc;
+}
+
+static int smb135x_battery_is_writeable(struct power_supply *psy,
+				       enum power_supply_property prop)
+{
+	int rc;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+	case POWER_SUPPLY_PROP_CAPACITY:
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+		rc = 1;
+		break;
+	default:
+		rc = 0;
+		break;
+	}
+	return rc;
+}
+
+static int smb135x_battery_get_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		val->intval = smb135x_get_prop_batt_status(chip);
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = smb135x_get_prop_batt_present(chip);
+		break;
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		val->intval = chip->chg_enabled;
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		val->intval = smb135x_get_prop_charge_type(chip);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		val->intval = smb135x_get_prop_batt_capacity(chip);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = smb135x_get_prop_batt_health(chip);
+		break;
+	case POWER_SUPPLY_PROP_TECHNOLOGY:
+		val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+		break;
+	case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+		val->intval = chip->therm_lvl_sel;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static enum power_supply_property smb135x_dc_properties[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_HEALTH,
+};
+
+static int smb135x_dc_get_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = chip->dc_present;
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		val->intval = chip->chg_enabled ? chip->dc_present : 0;
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		val->intval = chip->dc_present;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+#define MIN_FLOAT_MV	3600
+#define MAX_FLOAT_MV	4500
+
+#define MID_RANGE_FLOAT_MV_MIN		3600
+#define MID_RANGE_FLOAT_MIN_VAL		0x05
+#define MID_RANGE_FLOAT_STEP_MV		20
+
+#define HIGH_RANGE_FLOAT_MIN_MV		4340
+#define HIGH_RANGE_FLOAT_MIN_VAL	0x2A
+#define HIGH_RANGE_FLOAT_STEP_MV	10
+
+#define VHIGH_RANGE_FLOAT_MIN_MV	4400
+#define VHIGH_RANGE_FLOAT_MIN_VAL	0x2E
+#define VHIGH_RANGE_FLOAT_STEP_MV	20
+static int smb135x_float_voltage_set(struct smb135x_chg *chip, int vfloat_mv)
+{
+	u8 temp;
+
+	if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+		dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
+					vfloat_mv);
+		return -EINVAL;
+	}
+
+	if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) {
+		/* mid range */
+		temp = MID_RANGE_FLOAT_MIN_VAL
+			+ (vfloat_mv - MID_RANGE_FLOAT_MV_MIN)
+				/ MID_RANGE_FLOAT_STEP_MV;
+	} else if (vfloat_mv < VHIGH_RANGE_FLOAT_MIN_MV) {
+		/* high range */
+		temp = HIGH_RANGE_FLOAT_MIN_VAL
+			+ (vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV)
+				/ HIGH_RANGE_FLOAT_STEP_MV;
+	} else {
+		/* very high range */
+		temp = VHIGH_RANGE_FLOAT_MIN_VAL
+			+ (vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV)
+				/ VHIGH_RANGE_FLOAT_STEP_MV;
+	}
+
+	return smb135x_write(chip, VFLOAT_REG, temp);
+}
+
+static int smb135x_set_resume_threshold(struct smb135x_chg *chip,
+		int resume_delta_mv)
+{
+	int rc;
+	u8 reg;
+
+	if (!chip->inhibit_disabled) {
+		if (resume_delta_mv < 100)
+			reg = CHG_INHIBIT_50MV_VAL;
+		else if (resume_delta_mv < 200)
+			reg = CHG_INHIBIT_100MV_VAL;
+		else if (resume_delta_mv < 300)
+			reg = CHG_INHIBIT_200MV_VAL;
+		else
+			reg = CHG_INHIBIT_300MV_VAL;
+
+		rc = smb135x_masked_write(chip, CFG_4_REG, CHG_INHIBIT_MASK,
+						reg);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
+						rc);
+			return rc;
+		}
+	}
+
+	if (resume_delta_mv < 200)
+		reg = 0;
+	else
+		reg = RECHARGE_200MV_BIT;
+
+	rc = smb135x_masked_write(chip, CFG_5_REG, RECHARGE_200MV_BIT, reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set recharge  rc = %d\n", rc);
+		return rc;
+	}
+	return 0;
+}
+
+static enum power_supply_property smb135x_parallel_properties[] = {
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+};
+
+static bool smb135x_is_input_current_limited(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 reg;
+
+	rc = smb135x_read(chip, STATUS_2_REG, &reg);
+	if (rc) {
+		pr_debug("Couldn't read _REG for ICL status rc = %d\n", rc);
+		return false;
+	}
+
+	return !!(reg & HARD_LIMIT_STS_BIT);
+}
+
+static int smb135x_parallel_set_chg_present(struct smb135x_chg *chip,
+						int present)
+{
+	u8 val;
+	int rc;
+
+	if (present == chip->parallel_charger_present) {
+		pr_debug("present %d -> %d, skipping\n",
+				chip->parallel_charger_present, present);
+		return 0;
+	}
+
+	if (present) {
+		/* Check if SMB135x is present */
+		rc = smb135x_read(chip, VERSION1_REG, &val);
+		if (rc) {
+			pr_debug("Failed to detect smb135x-parallel charger may be absent\n");
+			return -ENODEV;
+		}
+
+		rc = smb135x_enable_volatile_writes(chip);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't configure for volatile rc = %d\n",
+				rc);
+			return rc;
+		}
+
+		/* set the float voltage */
+		if (chip->vfloat_mv != -EINVAL) {
+			rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
+			if (rc < 0) {
+				dev_err(chip->dev,
+					"Couldn't set float voltage rc = %d\n",
+					rc);
+				return rc;
+			}
+		}
+
+		/* resume threshold */
+		if (chip->resume_delta_mv != -EINVAL) {
+			smb135x_set_resume_threshold(chip,
+					chip->resume_delta_mv);
+		}
+
+		rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT,
+					USE_REGISTER_FOR_CURRENT,
+					USE_REGISTER_FOR_CURRENT);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set input limit cmd rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		/* set chg en by pin active low and enable auto recharge */
+		rc = smb135x_masked_write(chip, CFG_14_REG,
+				CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
+				| DISABLE_AUTO_RECHARGE_BIT,
+				CHG_EN_BY_PIN_BIT |
+				chip->parallel_pin_polarity_setting);
+
+		/* set bit 0 = 100mA bit 1 = 500mA and set register control */
+		rc = smb135x_masked_write(chip, CFG_E_REG,
+				POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
+				POLARITY_100_500_BIT);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set usbin cfg rc=%d\n",
+				rc);
+			return rc;
+		}
+
+		/* control USB suspend via command bits */
+		rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
+			USBIN_SUSPEND_VIA_COMMAND_BIT,
+			USBIN_SUSPEND_VIA_COMMAND_BIT);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set cfg rc=%d\n", rc);
+			return rc;
+		}
+
+		/* set the fastchg_current to the lowest setting */
+		if (chip->fastchg_current_arr_size > 0)
+			rc = smb135x_set_fastchg_current(chip,
+					chip->fastchg_current_table[0]);
+
+		/*
+		 * enforce chip->chg_enabled since this could be the first
+		 * time we have i2c access to the charger after
+		 * chip->chg_enabled has been modified
+		 */
+		smb135x_charging(chip, chip->chg_enabled);
+	}
+
+	chip->parallel_charger_present = present;
+	/*
+	 * When present is being set force USB suspend, start charging
+	 * only when CURRENT_MAX is set.
+	 *
+	 * Usually the chip will be shutdown (no i2c access to the chip)
+	 * when USB is removed, however there could be situations when
+	 * it is not.  To cover for USB reinsetions in such situations
+	 * force USB suspend when present is being unset.
+	 * It is likely that i2c access could fail here - do not return error.
+	 * (It is not possible to detect whether the chip is in shutdown state
+	 * or not except for the i2c error).
+	 */
+	chip->usb_psy_ma = SUSPEND_CURRENT_MA;
+	rc = smb135x_path_suspend(chip, USB, CURRENT, true);
+
+	if (present) {
+		if (rc) {
+			dev_err(chip->dev,
+				"Couldn't set usb suspend to true rc = %d\n",
+				rc);
+			return rc;
+		}
+		/* Check if the USB is configured for suspend. If not, do it */
+		mutex_lock(&chip->path_suspend_lock);
+		rc = smb135x_read(chip, CMD_INPUT_LIMIT, &val);
+		if (rc) {
+			dev_err(chip->dev,
+				"Couldn't read 0x%02x rc:%d\n", CMD_INPUT_LIMIT,
+				rc);
+			mutex_unlock(&chip->path_suspend_lock);
+			return rc;
+		} else if (!(val & BIT(6))) {
+			rc = __smb135x_usb_suspend(chip, 1);
+		}
+		mutex_unlock(&chip->path_suspend_lock);
+		if (rc) {
+			dev_err(chip->dev,
+				"Couldn't set usb to suspend rc:%d\n", rc);
+			return rc;
+		}
+	} else {
+		chip->real_usb_psy_ma = SUSPEND_CURRENT_MA;
+	}
+	return 0;
+}
+
+static int smb135x_parallel_set_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       const union power_supply_propval *val)
+{
+	int rc = 0;
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		if (chip->parallel_charger_present)
+			smb135x_charging(chip, val->intval);
+		else
+			chip->chg_enabled = val->intval;
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		rc = smb135x_parallel_set_chg_present(chip, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		if (chip->parallel_charger_present) {
+			rc = smb135x_set_fastchg_current(chip,
+						val->intval / 1000);
+		}
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		if (chip->parallel_charger_present) {
+			chip->usb_psy_ma = val->intval / 1000;
+			rc = smb135x_set_usb_chg_current(chip,
+							chip->usb_psy_ma);
+		}
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		if (chip->parallel_charger_present &&
+			(chip->vfloat_mv != val->intval)) {
+			rc = smb135x_float_voltage_set(chip, val->intval);
+			if (!rc)
+				chip->vfloat_mv = val->intval;
+		} else {
+			chip->vfloat_mv = val->intval;
+		}
+		break;
+	default:
+		return -EINVAL;
+	}
+	return rc;
+}
+
+static int smb135x_parallel_is_writeable(struct power_supply *psy,
+				       enum power_supply_property prop)
+{
+	int rc;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		rc = 1;
+		break;
+	default:
+		rc = 0;
+		break;
+	}
+	return rc;
+}
+static int smb135x_parallel_get_property(struct power_supply *psy,
+				       enum power_supply_property prop,
+				       union power_supply_propval *val)
+{
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		val->intval = chip->chg_enabled;
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		if (chip->parallel_charger_present)
+			val->intval = smb135x_get_usb_chg_current(chip) * 1000;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		val->intval = chip->vfloat_mv;
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		val->intval = chip->parallel_charger_present;
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		if (chip->parallel_charger_present)
+			val->intval = smb135x_get_fastchg_current(chip) * 1000;
+		else
+			val->intval = 0;
+		break;
+	case POWER_SUPPLY_PROP_STATUS:
+		if (chip->parallel_charger_present)
+			val->intval = smb135x_get_prop_batt_status(chip);
+		else
+			val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+		break;
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+		if (chip->parallel_charger_present)
+			val->intval = smb135x_is_input_current_limited(chip);
+		else
+			val->intval = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+	return 0;
+}
+
+static void smb135x_external_power_changed(struct power_supply *psy)
+{
+	struct smb135x_chg *chip = power_supply_get_drvdata(psy);
+	union power_supply_propval prop = {0,};
+	int rc, current_limit = 0;
+
+	if (!chip->usb_psy)
+		return;
+
+	if (chip->bms_psy_name)
+		chip->bms_psy =
+			power_supply_get_by_name((char *)chip->bms_psy_name);
+
+	rc = power_supply_get_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_CURRENT_MAX, &prop);
+	if (rc < 0)
+		dev_err(chip->dev,
+			"could not read USB current_max property, rc=%d\n", rc);
+	else
+		current_limit = prop.intval / 1000;
+
+	pr_debug("current_limit = %d\n", current_limit);
+
+	if (chip->usb_psy_ma != current_limit) {
+		mutex_lock(&chip->current_change_lock);
+		chip->usb_psy_ma = current_limit;
+		rc = smb135x_set_appropriate_current(chip, USB);
+		mutex_unlock(&chip->current_change_lock);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't set usb current rc = %d\n",
+					rc);
+	}
+
+	rc = power_supply_get_property(chip->usb_psy,
+			POWER_SUPPLY_PROP_ONLINE, &prop);
+	if (rc < 0)
+		dev_err(chip->dev,
+			"could not read USB ONLINE property, rc=%d\n", rc);
+
+	/* update online property */
+	rc = 0;
+	if (chip->usb_present && chip->chg_enabled && chip->usb_psy_ma != 0) {
+		if (prop.intval == 0) {
+			prop.intval = 1;
+			rc = power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_ONLINE, &prop);
+		}
+	} else {
+		if (prop.intval == 1) {
+			prop.intval = 0;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_ONLINE, &prop);
+		}
+	}
+	if (rc < 0)
+		dev_err(chip->dev, "could not set usb online, rc=%d\n", rc);
+}
+
+static bool elapsed_msec_greater(struct timeval *start_time,
+				struct timeval *end_time, int ms)
+{
+	int msec_elapsed;
+
+	msec_elapsed = (end_time->tv_sec - start_time->tv_sec) * 1000 +
+		DIV_ROUND_UP(end_time->tv_usec - start_time->tv_usec, 1000);
+
+	return (msec_elapsed > ms);
+}
+
+#define MAX_STEP_MS		10
+static int smb135x_chg_otg_enable(struct smb135x_chg *chip)
+{
+	int rc = 0;
+	int restart_count = 0;
+	struct timeval time_a, time_b, time_c, time_d;
+	u8 reg;
+
+	if (chip->revision == REV_2) {
+		/*
+		 * Workaround for a hardware bug where the OTG needs to be
+		 * enabled disabled and enabled for it to be actually enabled.
+		 * The time between each step should be atmost MAX_STEP_MS
+		 *
+		 * Note that if enable-disable executes within the timeframe
+		 * but the final enable takes more than MAX_STEP_ME, we treat
+		 * it as the first enable and try disabling again. We don't
+		 * want to issue enable back to back.
+		 *
+		 * Notice the instances when time is captured and the
+		 * successive steps.
+		 * timeA-enable-timeC-disable-timeB-enable-timeD.
+		 * When
+		 * (timeB - timeA) < MAX_STEP_MS AND
+		 *			(timeC - timeD) < MAX_STEP_MS
+		 * then it is guaranteed that the successive steps
+		 * must have executed within MAX_STEP_MS
+		 */
+		do_gettimeofday(&time_a);
+restart_from_enable:
+		/* first step - enable otg */
+		rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+					rc);
+			return rc;
+		}
+
+restart_from_disable:
+		/* second step - disable otg */
+		do_gettimeofday(&time_c);
+		rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+					rc);
+			return rc;
+		}
+		do_gettimeofday(&time_b);
+
+		if (elapsed_msec_greater(&time_a, &time_b, MAX_STEP_MS)) {
+			restart_count++;
+			if (restart_count > 10) {
+				dev_err(chip->dev,
+						"Couldn't enable OTG restart_count=%d\n",
+						restart_count);
+				return -EAGAIN;
+			}
+			time_a = time_b;
+			pr_debug("restarting from first enable\n");
+			goto restart_from_enable;
+		}
+
+		/* third step (first step in case of a failure) - enable otg */
+		time_a = time_b;
+		rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+					rc);
+			return rc;
+		}
+		do_gettimeofday(&time_d);
+
+		if (elapsed_msec_greater(&time_c, &time_d, MAX_STEP_MS)) {
+			restart_count++;
+			if (restart_count > 10) {
+				dev_err(chip->dev,
+						"Couldn't enable OTG restart_count=%d\n",
+						restart_count);
+				return -EAGAIN;
+			}
+			pr_debug("restarting from disable\n");
+			goto restart_from_disable;
+		}
+	} else {
+		rc = smb135x_read(chip, CMD_CHG_REG, &reg);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't read cmd reg rc=%d\n",
+					rc);
+			return rc;
+		}
+		if (reg & OTG_EN) {
+			/* if it is set, disable it before re-enabling it */
+			rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+			if (rc < 0) {
+				dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n",
+						rc);
+				return rc;
+			}
+		}
+		rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, OTG_EN);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n",
+					rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int smb135x_chg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+	chip->otg_oc_count = 0;
+	rc = smb135x_chg_otg_enable(chip);
+	if (rc)
+		dev_err(chip->dev, "Couldn't enable otg regulator rc=%d\n", rc);
+
+	return rc;
+}
+
+static int smb135x_chg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+	mutex_lock(&chip->otg_oc_count_lock);
+	cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
+	mutex_unlock(&chip->otg_oc_count_lock);
+	rc = smb135x_masked_write(chip, CMD_CHG_REG, OTG_EN, 0);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n", rc);
+	return rc;
+}
+
+static int smb135x_chg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+	int rc = 0;
+	u8 reg = 0;
+	struct smb135x_chg *chip = rdev_get_drvdata(rdev);
+
+	rc = smb135x_read(chip, CMD_CHG_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev,
+				"Couldn't read OTG enable bit rc=%d\n", rc);
+		return rc;
+	}
+
+	return  (reg & OTG_EN) ? 1 : 0;
+}
+
+struct regulator_ops smb135x_chg_otg_reg_ops = {
+	.enable		= smb135x_chg_otg_regulator_enable,
+	.disable	= smb135x_chg_otg_regulator_disable,
+	.is_enabled	= smb135x_chg_otg_regulator_is_enable,
+};
+
+static int smb135x_set_current_tables(struct smb135x_chg *chip)
+{
+	switch (chip->version) {
+	case V_SMB1356:
+		chip->usb_current_table = usb_current_table_smb1356;
+		chip->usb_current_arr_size
+			= ARRAY_SIZE(usb_current_table_smb1356);
+		chip->dc_current_table = dc_current_table_smb1356;
+		chip->dc_current_arr_size
+			= ARRAY_SIZE(dc_current_table_smb1356);
+		chip->fastchg_current_table = NULL;
+		chip->fastchg_current_arr_size = 0;
+		break;
+	case V_SMB1357:
+		chip->usb_current_table = usb_current_table_smb1357_smb1358;
+		chip->usb_current_arr_size
+			= ARRAY_SIZE(usb_current_table_smb1357_smb1358);
+		chip->dc_current_table = dc_current_table;
+		chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+		chip->fastchg_current_table = fastchg_current_table;
+		chip->fastchg_current_arr_size
+			= ARRAY_SIZE(fastchg_current_table);
+		break;
+	case V_SMB1358:
+		chip->usb_current_table = usb_current_table_smb1357_smb1358;
+		chip->usb_current_arr_size
+			= ARRAY_SIZE(usb_current_table_smb1357_smb1358);
+		chip->dc_current_table = dc_current_table;
+		chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+		chip->fastchg_current_table = fastchg_current_table;
+		chip->fastchg_current_arr_size
+			= ARRAY_SIZE(fastchg_current_table);
+		break;
+	case V_SMB1359:
+		chip->usb_current_table = usb_current_table_smb1359;
+		chip->usb_current_arr_size
+			= ARRAY_SIZE(usb_current_table_smb1359);
+		chip->dc_current_table = dc_current_table;
+		chip->dc_current_arr_size = ARRAY_SIZE(dc_current_table);
+		chip->fastchg_current_table = NULL;
+		chip->fastchg_current_arr_size = 0;
+		break;
+	}
+	return 0;
+}
+
+#define SMB1356_VERSION3_BIT	BIT(7)
+#define SMB1357_VERSION1_VAL	0x01
+#define SMB1358_VERSION1_VAL	0x02
+#define SMB1359_VERSION1_VAL	0x00
+#define SMB1357_VERSION2_VAL	0x01
+#define SMB1358_VERSION2_VAL	0x02
+#define SMB1359_VERSION2_VAL	0x00
+static int smb135x_chip_version_and_revision(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 version1, version2, version3;
+
+	/* read the revision */
+	rc = read_revision(chip, &chip->revision);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read revision rc = %d\n", rc);
+		return rc;
+	}
+
+	if (chip->revision >= REV_MAX || revision_str[chip->revision] == NULL) {
+		dev_err(chip->dev, "Bad revision found = %d\n", chip->revision);
+		return -EINVAL;
+	}
+
+	/* check if it is smb1356 */
+	rc = read_version3(chip, &version3);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read version3 rc = %d\n", rc);
+		return rc;
+	}
+
+	if (version3 & SMB1356_VERSION3_BIT) {
+		chip->version = V_SMB1356;
+		goto wrkarnd_and_input_current_values;
+	}
+
+	/* check if it is smb1357, smb1358 or smb1359 based on revision */
+	if (chip->revision <= REV_1_1) {
+		rc = read_version1(chip, &version1);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't read version 1 rc = %d\n", rc);
+			return rc;
+		}
+		switch (version1) {
+		case SMB1357_VERSION1_VAL:
+			chip->version = V_SMB1357;
+			break;
+		case SMB1358_VERSION1_VAL:
+			chip->version = V_SMB1358;
+			break;
+		case SMB1359_VERSION1_VAL:
+			chip->version = V_SMB1359;
+			break;
+		default:
+			dev_err(chip->dev,
+				"Unknown version 1 = 0x%02x rc = %d\n",
+				version1, rc);
+			return rc;
+		}
+	} else {
+		rc = read_version2(chip, &version2);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't read version 2 rc = %d\n", rc);
+			return rc;
+		}
+		switch (version2) {
+		case SMB1357_VERSION2_VAL:
+			chip->version = V_SMB1357;
+			break;
+		case SMB1358_VERSION2_VAL:
+			chip->version = V_SMB1358;
+			break;
+		case SMB1359_VERSION2_VAL:
+			chip->version = V_SMB1359;
+			break;
+		default:
+			dev_err(chip->dev,
+					"Unknown version 2 = 0x%02x rc = %d\n",
+					version2, rc);
+			return rc;
+		}
+	}
+
+wrkarnd_and_input_current_values:
+	if (is_usb100_broken(chip))
+		chip->workaround_flags |= WRKARND_USB100_BIT;
+	/*
+	 * Rev v1.0 and v1.1 of SMB135x fails charger type detection
+	 * (apsd) due to interference on the D+/- lines by the USB phy.
+	 * Set the workaround flag to disable charger type reporting
+	 * for this revision.
+	 */
+	if (chip->revision <= REV_1_1)
+		chip->workaround_flags |= WRKARND_APSD_FAIL;
+
+	pr_debug("workaround_flags = %x\n", chip->workaround_flags);
+
+	return smb135x_set_current_tables(chip);
+}
+
+static int smb135x_regulator_init(struct smb135x_chg *chip)
+{
+	int rc = 0;
+	struct regulator_config cfg = {};
+
+	chip->otg_vreg.rdesc.owner = THIS_MODULE;
+	chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+	chip->otg_vreg.rdesc.ops = &smb135x_chg_otg_reg_ops;
+	chip->otg_vreg.rdesc.name = chip->dev->of_node->name;
+	chip->otg_vreg.rdesc.of_match = chip->dev->of_node->name;
+	cfg.dev = chip->dev;
+	cfg.driver_data = chip;
+
+	chip->otg_vreg.rdev = regulator_register(&chip->otg_vreg.rdesc, &cfg);
+	if (IS_ERR(chip->otg_vreg.rdev)) {
+		rc = PTR_ERR(chip->otg_vreg.rdev);
+		chip->otg_vreg.rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			dev_err(chip->dev,
+				"OTG reg failed, rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+static void smb135x_regulator_deinit(struct smb135x_chg *chip)
+{
+	if (chip->otg_vreg.rdev)
+		regulator_unregister(chip->otg_vreg.rdev);
+}
+
+static void wireless_insertion_work(struct work_struct *work)
+{
+	struct smb135x_chg *chip =
+		container_of(work, struct smb135x_chg,
+				wireless_insertion_work.work);
+
+	/* unsuspend dc */
+	smb135x_path_suspend(chip, DC, CURRENT, false);
+}
+
+static int hot_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	chip->batt_hot = !!rt_stat;
+	return 0;
+}
+static int cold_hard_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	chip->batt_cold = !!rt_stat;
+	return 0;
+}
+static int hot_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	chip->batt_warm = !!rt_stat;
+	return 0;
+}
+static int cold_soft_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	chip->batt_cool = !!rt_stat;
+	return 0;
+}
+static int battery_missing_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	chip->batt_present = !rt_stat;
+	return 0;
+}
+static int vbat_low_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_warn("vbat low\n");
+	return 0;
+}
+static int chg_hot_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_warn("chg hot\n");
+	return 0;
+}
+static int chg_term_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+	/*
+	 * This handler gets called even when the charger based termination
+	 * is disabled (due to change in RT status). However, in a bms
+	 * controlled design the battery status should not be updated.
+	 */
+	if (!chip->iterm_disabled)
+		chip->chg_done_batt_full = !!rt_stat;
+	return 0;
+}
+
+static int taper_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	return 0;
+}
+
+static int fast_chg_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+	if (rt_stat & IRQ_C_FASTCHG_BIT)
+		chip->chg_done_batt_full = false;
+
+	return 0;
+}
+
+static int recharge_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	int rc;
+
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+	if (chip->bms_controlled_charging) {
+		rc = smb135x_charging_enable(chip, true);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+					rc);
+	}
+
+	return 0;
+}
+
+static int safety_timeout_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_warn("safety timeout rt_stat = 0x%02x\n", rt_stat);
+	return 0;
+}
+
+/**
+ * power_ok_handler() - called when the switcher turns on or turns off
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating switcher turning on or off
+ */
+static int power_ok_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	return 0;
+}
+
+static int rid_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	bool usb_slave_present;
+	union power_supply_propval pval = {0, };
+
+	usb_slave_present = is_usb_slave_present(chip);
+
+	if (chip->usb_slave_present ^ usb_slave_present) {
+		chip->usb_slave_present = usb_slave_present;
+		if (chip->usb_psy) {
+			pr_debug("setting usb psy usb_otg = %d\n",
+					chip->usb_slave_present);
+			pval.intval = chip->usb_slave_present;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_USB_OTG, &pval);
+		}
+	}
+	return 0;
+}
+
+#define RESET_OTG_OC_COUNT_MS	100
+static void reset_otg_oc_count_work(struct work_struct *work)
+{
+	struct smb135x_chg *chip =
+		container_of(work, struct smb135x_chg,
+				reset_otg_oc_count_work.work);
+
+	mutex_lock(&chip->otg_oc_count_lock);
+	pr_debug("It has been %dmS since OverCurrent interrupt resetting the count\n",
+			RESET_OTG_OC_COUNT_MS);
+	chip->otg_oc_count = 0;
+	mutex_unlock(&chip->otg_oc_count_lock);
+}
+
+#define MAX_OTG_RETRY	3
+static int otg_oc_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	int rc;
+
+	mutex_lock(&chip->otg_oc_count_lock);
+	cancel_delayed_work_sync(&chip->reset_otg_oc_count_work);
+	++chip->otg_oc_count;
+	if (chip->otg_oc_count < MAX_OTG_RETRY) {
+		rc = smb135x_chg_otg_enable(chip);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't enable  OTG mode rc=%d\n",
+				rc);
+	} else {
+		pr_warn_ratelimited("Tried enabling OTG %d times, the USB slave is nonconformant.\n",
+			chip->otg_oc_count);
+	}
+
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+	schedule_delayed_work(&chip->reset_otg_oc_count_work,
+			msecs_to_jiffies(RESET_OTG_OC_COUNT_MS));
+	mutex_unlock(&chip->otg_oc_count_lock);
+	return 0;
+}
+
+static int handle_dc_removal(struct smb135x_chg *chip)
+{
+	union power_supply_propval prop;
+
+	if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
+		cancel_delayed_work_sync(&chip->wireless_insertion_work);
+		smb135x_path_suspend(chip, DC, CURRENT, true);
+	}
+	if (chip->dc_psy_type != -EINVAL) {
+		prop.intval = chip->dc_present;
+		power_supply_set_property(chip->dc_psy,
+				POWER_SUPPLY_PROP_ONLINE, &prop);
+	}
+	return 0;
+}
+
+#define DCIN_UNSUSPEND_DELAY_MS		1000
+static int handle_dc_insertion(struct smb135x_chg *chip)
+{
+	union power_supply_propval prop;
+
+	if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS)
+		schedule_delayed_work(&chip->wireless_insertion_work,
+			msecs_to_jiffies(DCIN_UNSUSPEND_DELAY_MS));
+	if (chip->dc_psy_type != -EINVAL) {
+		prop.intval = chip->dc_present;
+		power_supply_set_property(chip->dc_psy,
+				POWER_SUPPLY_PROP_ONLINE, &prop);
+	}
+	return 0;
+}
+/**
+ * dcin_uv_handler() - called when the dc voltage crosses the uv threshold
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating whether dc voltage is uv
+ */
+static int dcin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	/*
+	 * rt_stat indicates if dc is undervolted. If so dc_present
+	 * should be marked removed
+	 */
+	bool dc_present = !rt_stat;
+
+	pr_debug("chip->dc_present = %d dc_present = %d\n",
+			chip->dc_present, dc_present);
+
+	if (chip->dc_present && !dc_present) {
+		/* dc removed */
+		chip->dc_present = dc_present;
+		handle_dc_removal(chip);
+	}
+
+	if (!chip->dc_present && dc_present) {
+		/* dc inserted */
+		chip->dc_present = dc_present;
+		handle_dc_insertion(chip);
+	}
+
+	return 0;
+}
+
+static int dcin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	/*
+	 * rt_stat indicates if dc is overvolted. If so dc_present
+	 * should be marked removed
+	 */
+	bool dc_present = !rt_stat;
+
+	pr_debug("chip->dc_present = %d dc_present = %d\n",
+			chip->dc_present, dc_present);
+
+	chip->dc_ov = !!rt_stat;
+
+	if (chip->dc_present && !dc_present) {
+		/* dc removed */
+		chip->dc_present = dc_present;
+		handle_dc_removal(chip);
+	}
+
+	if (!chip->dc_present && dc_present) {
+		/* dc inserted */
+		chip->dc_present = dc_present;
+		handle_dc_insertion(chip);
+	}
+	return 0;
+}
+
+static int handle_usb_removal(struct smb135x_chg *chip)
+{
+	union power_supply_propval pval = {0,};
+
+	if (chip->usb_psy) {
+		cancel_delayed_work_sync(&chip->hvdcp_det_work);
+		pm_relax(chip->dev);
+		pr_debug("setting usb psy type = %d\n",
+				POWER_SUPPLY_TYPE_UNKNOWN);
+		pval.intval = POWER_SUPPLY_TYPE_UNKNOWN;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &pval);
+
+		pr_debug("setting usb psy present = %d\n", chip->usb_present);
+		pval.intval = chip->usb_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT,
+				&pval);
+
+		pr_debug("Setting usb psy dp=r dm=r\n");
+		pval.intval = POWER_SUPPLY_DP_DM_DPR_DMR;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_DP_DM,
+				&pval);
+	}
+	return 0;
+}
+
+static int rerun_apsd(struct smb135x_chg *chip)
+{
+	int rc;
+
+	pr_debug("Reruning APSD\nDisabling APSD\n");
+	rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 0);
+	if (rc) {
+		dev_err(chip->dev, "Couldn't Disable APSD rc=%d\n", rc);
+		return rc;
+	}
+	pr_debug("Allow only 9V chargers\n");
+	rc = smb135x_masked_write(chip, CFG_C_REG,
+			USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_9V_ONLY);
+	if (rc)
+		dev_err(chip->dev, "Couldn't Allow 9V rc=%d\n", rc);
+	pr_debug("Enabling APSD\n");
+	rc = smb135x_masked_write(chip, CFG_11_REG, AUTO_SRC_DET_EN_BIT, 1);
+	if (rc)
+		dev_err(chip->dev, "Couldn't Enable APSD rc=%d\n", rc);
+	pr_debug("Allow 5V-9V\n");
+	rc = smb135x_masked_write(chip, CFG_C_REG,
+			USBIN_ADAPTER_ALLOWANCE_MASK, ALLOW_5V_TO_9V);
+	if (rc)
+		dev_err(chip->dev, "Couldn't Allow 5V-9V rc=%d\n", rc);
+	return rc;
+}
+
+static void smb135x_hvdcp_det_work(struct work_struct *work)
+{
+	int rc;
+	u8 reg;
+	struct smb135x_chg *chip = container_of(work, struct smb135x_chg,
+							hvdcp_det_work.work);
+	union power_supply_propval pval = {0,};
+
+	rc = smb135x_read(chip, STATUS_7_REG, &reg);
+	if (rc) {
+		pr_err("Couldn't read STATUS_7_REG rc == %d\n", rc);
+		goto end;
+	}
+	pr_debug("STATUS_7_REG = 0x%02X\n", reg);
+
+	if (reg) {
+		pr_debug("HVDCP detected; notifying USB PSY\n");
+		pval.intval = POWER_SUPPLY_TYPE_USB_HVDCP;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &pval);
+	}
+end:
+	pm_relax(chip->dev);
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static int handle_usb_insertion(struct smb135x_chg *chip)
+{
+	u8 reg;
+	int rc;
+	char *usb_type_name = "null";
+	enum power_supply_type usb_supply_type;
+	union power_supply_propval pval = {0,};
+
+	/* usb inserted */
+	rc = smb135x_read(chip, STATUS_5_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
+		return rc;
+	}
+	/*
+	 * Report the charger type as UNKNOWN if the
+	 * apsd-fail flag is set. This nofifies the USB driver
+	 * to initiate a s/w based charger type detection.
+	 */
+	if (chip->workaround_flags & WRKARND_APSD_FAIL)
+		reg = 0;
+
+	usb_type_name = get_usb_type_name(reg);
+	usb_supply_type = get_usb_supply_type(reg);
+	pr_debug("inserted %s, usb psy type = %d stat_5 = 0x%02x apsd_rerun = %d\n",
+			usb_type_name, usb_supply_type, reg, chip->apsd_rerun);
+
+	if (chip->batt_present && !chip->apsd_rerun && chip->usb_psy) {
+		if (usb_supply_type == POWER_SUPPLY_TYPE_USB) {
+			pr_debug("Setting usb psy dp=f dm=f SDP and rerun\n");
+			pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+			power_supply_set_property(chip->usb_psy,
+					POWER_SUPPLY_PROP_DP_DM,
+					&pval);
+			chip->apsd_rerun = true;
+			rerun_apsd(chip);
+			/* rising edge of src detect will happen in few mS */
+			return 0;
+		}
+
+		pr_debug("Set usb psy dp=f dm=f DCP and no rerun\n");
+		pval.intval = POWER_SUPPLY_DP_DM_DPF_DMF;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_DP_DM,
+				&pval);
+	}
+
+	if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP) {
+		pr_debug("schedule hvdcp detection worker\n");
+		pm_stay_awake(chip->dev);
+		schedule_delayed_work(&chip->hvdcp_det_work,
+					msecs_to_jiffies(HVDCP_NOTIFY_MS));
+	}
+
+	if (chip->usb_psy) {
+		if (chip->bms_controlled_charging) {
+			/* enable charging on USB insertion */
+			rc = smb135x_charging_enable(chip, true);
+			if (rc < 0)
+				dev_err(chip->dev, "Couldn't enable charging rc = %d\n",
+						rc);
+		}
+		pr_debug("setting usb psy type = %d\n", usb_supply_type);
+		pval.intval = usb_supply_type;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_TYPE, &pval);
+
+		pr_debug("setting usb psy present = %d\n", chip->usb_present);
+		pval.intval = chip->usb_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_PRESENT,
+				&pval);
+	}
+	chip->apsd_rerun = false;
+	return 0;
+}
+
+/**
+ * usbin_uv_handler()
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+static int usbin_uv_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	/*
+	 * rt_stat indicates if usb is undervolted
+	 */
+	bool usb_present = !rt_stat;
+
+	pr_debug("chip->usb_present = %d usb_present = %d\n",
+			chip->usb_present, usb_present);
+
+	return 0;
+}
+
+static int usbin_ov_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	union power_supply_propval pval = {0, };
+	/*
+	 * rt_stat indicates if usb is overvolted. If so usb_present
+	 * should be marked removed
+	 */
+	bool usb_present = !rt_stat;
+
+	pr_debug("chip->usb_present = %d usb_present = %d\n",
+			chip->usb_present, usb_present);
+	if (chip->usb_present && !usb_present) {
+		/* USB removed */
+		chip->usb_present = usb_present;
+		handle_usb_removal(chip);
+	} else if (!chip->usb_present && usb_present) {
+		/* USB inserted */
+		chip->usb_present = usb_present;
+		handle_usb_insertion(chip);
+	}
+
+	if (chip->usb_psy) {
+		pval.intval = rt_stat ? POWER_SUPPLY_HEALTH_OVERVOLTAGE
+					: POWER_SUPPLY_HEALTH_GOOD;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_HEALTH, &pval);
+	}
+
+	return 0;
+}
+
+/**
+ * src_detect_handler() - this is called on rising edge when USB
+ *			charger type is detected and on falling edge when
+ *			USB voltage falls below the coarse detect voltage
+ *			(1V), use it for handling USB charger insertion
+ *			and removal.
+ * @chip: pointer to smb135x_chg chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+static int src_detect_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	bool usb_present = !!rt_stat;
+
+	pr_debug("chip->usb_present = %d usb_present = %d\n",
+			chip->usb_present, usb_present);
+
+	if (!chip->usb_present && usb_present) {
+		/* USB inserted */
+		chip->usb_present = usb_present;
+		handle_usb_insertion(chip);
+	} else if (usb_present && chip->apsd_rerun) {
+		handle_usb_insertion(chip);
+	} else if (chip->usb_present && !usb_present) {
+		chip->usb_present = !chip->usb_present;
+		handle_usb_removal(chip);
+	}
+
+	return 0;
+}
+
+static int chg_inhibit_handler(struct smb135x_chg *chip, u8 rt_stat)
+{
+	/*
+	 * charger is inserted when the battery voltage is high
+	 * so h/w won't start charging just yet. Treat this as
+	 * battery full
+	 */
+	pr_debug("rt_stat = 0x%02x\n", rt_stat);
+
+	if (!chip->inhibit_disabled)
+		chip->chg_done_batt_full = !!rt_stat;
+	return 0;
+}
+
+struct smb_irq_info {
+	const char		*name;
+	int			(*smb_irq)(struct smb135x_chg *chip,
+							u8 rt_stat);
+	int			high;
+	int			low;
+};
+
+struct irq_handler_info {
+	u8			stat_reg;
+	u8			val;
+	u8			prev_val;
+	struct smb_irq_info	irq_info[4];
+};
+
+static struct irq_handler_info handlers[] = {
+	{IRQ_A_REG, 0, 0,
+		{
+			{
+				.name		= "cold_soft",
+				.smb_irq	= cold_soft_handler,
+			},
+			{
+				.name		= "hot_soft",
+				.smb_irq	= hot_soft_handler,
+			},
+			{
+				.name		= "cold_hard",
+				.smb_irq	= cold_hard_handler,
+			},
+			{
+				.name		= "hot_hard",
+				.smb_irq	= hot_hard_handler,
+			},
+		},
+	},
+	{IRQ_B_REG, 0, 0,
+		{
+			{
+				.name		= "chg_hot",
+				.smb_irq	= chg_hot_handler,
+			},
+			{
+				.name		= "vbat_low",
+				.smb_irq	= vbat_low_handler,
+			},
+			{
+				.name		= "battery_missing",
+				.smb_irq	= battery_missing_handler,
+			},
+			{
+				.name		= "battery_missing",
+				.smb_irq	= battery_missing_handler,
+			},
+		},
+	},
+	{IRQ_C_REG, 0, 0,
+		{
+			{
+				.name		= "chg_term",
+				.smb_irq	= chg_term_handler,
+			},
+			{
+				.name		= "taper",
+				.smb_irq	= taper_handler,
+			},
+			{
+				.name		= "recharge",
+				.smb_irq	= recharge_handler,
+			},
+			{
+				.name		= "fast_chg",
+				.smb_irq	= fast_chg_handler,
+			},
+		},
+	},
+	{IRQ_D_REG, 0, 0,
+		{
+			{
+				.name		= "prechg_timeout",
+			},
+			{
+				.name		= "safety_timeout",
+				.smb_irq	= safety_timeout_handler,
+			},
+			{
+				.name		= "aicl_done",
+			},
+			{
+				.name		= "battery_ov",
+			},
+		},
+	},
+	{IRQ_E_REG, 0, 0,
+		{
+			{
+				.name		= "usbin_uv",
+				.smb_irq	= usbin_uv_handler,
+			},
+			{
+				.name		= "usbin_ov",
+				.smb_irq	= usbin_ov_handler,
+			},
+			{
+				.name		= "dcin_uv",
+				.smb_irq	= dcin_uv_handler,
+			},
+			{
+				.name		= "dcin_ov",
+				.smb_irq	= dcin_ov_handler,
+			},
+		},
+	},
+	{IRQ_F_REG, 0, 0,
+		{
+			{
+				.name		= "power_ok",
+				.smb_irq	= power_ok_handler,
+			},
+			{
+				.name		= "rid",
+				.smb_irq	= rid_handler,
+			},
+			{
+				.name		= "otg_fail",
+			},
+			{
+				.name		= "otg_oc",
+				.smb_irq	= otg_oc_handler,
+			},
+		},
+	},
+	{IRQ_G_REG, 0, 0,
+		{
+			{
+				.name		= "chg_inhibit",
+				.smb_irq	= chg_inhibit_handler,
+			},
+			{
+				.name		= "chg_error",
+			},
+			{
+				.name		= "wd_timeout",
+			},
+			{
+				.name		= "src_detect",
+				.smb_irq	= src_detect_handler,
+			},
+		},
+	},
+};
+
+static int smb135x_irq_read(struct smb135x_chg *chip)
+{
+	int rc, i;
+
+	/*
+	 * When dcin path is suspended the irq triggered status is not cleared
+	 * causing a storm. To prevent this situation unsuspend dcin path while
+	 * reading interrupts and restore its status back.
+	 */
+	mutex_lock(&chip->path_suspend_lock);
+
+	if (chip->dc_suspended)
+		__smb135x_dc_suspend(chip, false);
+
+	for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+		rc = smb135x_read(chip, handlers[i].stat_reg,
+						&handlers[i].val);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't read %d rc = %d\n",
+					handlers[i].stat_reg, rc);
+			handlers[i].val = 0;
+			continue;
+		}
+	}
+
+	if (chip->dc_suspended)
+		__smb135x_dc_suspend(chip, true);
+
+	mutex_unlock(&chip->path_suspend_lock);
+
+	return rc;
+}
+#define IRQ_LATCHED_MASK	0x02
+#define IRQ_STATUS_MASK		0x01
+#define BITS_PER_IRQ		2
+static irqreturn_t smb135x_chg_stat_handler(int irq, void *dev_id)
+{
+	struct smb135x_chg *chip = dev_id;
+	int i, j;
+	u8 triggered;
+	u8 changed;
+	u8 rt_stat, prev_rt_stat;
+	int rc;
+	int handler_count = 0;
+
+	mutex_lock(&chip->irq_complete);
+	chip->irq_waiting = true;
+	if (!chip->resume_completed) {
+		dev_dbg(chip->dev, "IRQ triggered before device-resume\n");
+		disable_irq_nosync(irq);
+		mutex_unlock(&chip->irq_complete);
+		return IRQ_HANDLED;
+	}
+	chip->irq_waiting = false;
+
+	smb135x_irq_read(chip);
+	for (i = 0; i < ARRAY_SIZE(handlers); i++) {
+		for (j = 0; j < ARRAY_SIZE(handlers[i].irq_info); j++) {
+			triggered = handlers[i].val
+			       & (IRQ_LATCHED_MASK << (j * BITS_PER_IRQ));
+			rt_stat = handlers[i].val
+				& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+			prev_rt_stat = handlers[i].prev_val
+				& (IRQ_STATUS_MASK << (j * BITS_PER_IRQ));
+			changed = prev_rt_stat ^ rt_stat;
+
+			if (triggered || changed)
+				rt_stat ? handlers[i].irq_info[j].high++ :
+						handlers[i].irq_info[j].low++;
+
+			if ((triggered || changed)
+				&& handlers[i].irq_info[j].smb_irq != NULL) {
+				handler_count++;
+				rc = handlers[i].irq_info[j].smb_irq(chip,
+								rt_stat);
+				if (rc < 0)
+					dev_err(chip->dev,
+						"Couldn't handle %d irq for reg 0x%02x rc = %d\n",
+						j, handlers[i].stat_reg, rc);
+			}
+		}
+		handlers[i].prev_val = handlers[i].val;
+	}
+
+	pr_debug("handler count = %d\n", handler_count);
+	if (handler_count) {
+		pr_debug("batt psy changed\n");
+		power_supply_changed(chip->batt_psy);
+		if (chip->usb_psy) {
+			pr_debug("usb psy changed\n");
+			power_supply_changed(chip->usb_psy);
+		}
+		if (chip->dc_psy_type != -EINVAL) {
+			pr_debug("dc psy changed\n");
+			power_supply_changed(chip->dc_psy);
+		}
+	}
+
+	mutex_unlock(&chip->irq_complete);
+
+	return IRQ_HANDLED;
+}
+
+#define LAST_CNFG_REG	0x1F
+static int show_cnfg_regs(struct seq_file *m, void *data)
+{
+	struct smb135x_chg *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int cnfg_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb135x_chg *chip = inode->i_private;
+
+	return single_open(file, show_cnfg_regs, chip);
+}
+
+static const struct file_operations cnfg_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= cnfg_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+#define FIRST_CMD_REG	0x40
+#define LAST_CMD_REG	0x42
+static int show_cmd_regs(struct seq_file *m, void *data)
+{
+	struct smb135x_chg *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int cmd_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb135x_chg *chip = inode->i_private;
+
+	return single_open(file, show_cmd_regs, chip);
+}
+
+static const struct file_operations cmd_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= cmd_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+#define FIRST_STATUS_REG	0x46
+#define LAST_STATUS_REG		0x56
+static int show_status_regs(struct seq_file *m, void *data)
+{
+	struct smb135x_chg *chip = m->private;
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (!rc)
+			seq_printf(m, "0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	return 0;
+}
+
+static int status_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb135x_chg *chip = inode->i_private;
+
+	return single_open(file, show_status_regs, chip);
+}
+
+static const struct file_operations status_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= status_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int show_irq_count(struct seq_file *m, void *data)
+{
+	int i, j, total = 0;
+
+	for (i = 0; i < ARRAY_SIZE(handlers); i++)
+		for (j = 0; j < 4; j++) {
+			seq_printf(m, "%s=%d\t(high=%d low=%d)\n",
+						handlers[i].irq_info[j].name,
+						handlers[i].irq_info[j].high
+						+ handlers[i].irq_info[j].low,
+						handlers[i].irq_info[j].high,
+						handlers[i].irq_info[j].low);
+			total += (handlers[i].irq_info[j].high
+					+ handlers[i].irq_info[j].low);
+		}
+
+	seq_printf(m, "\n\tTotal = %d\n", total);
+
+	return 0;
+}
+
+static int irq_count_debugfs_open(struct inode *inode, struct file *file)
+{
+	struct smb135x_chg *chip = inode->i_private;
+
+	return single_open(file, show_irq_count, chip);
+}
+
+static const struct file_operations irq_count_debugfs_ops = {
+	.owner		= THIS_MODULE,
+	.open		= irq_count_debugfs_open,
+	.read		= seq_read,
+	.llseek		= seq_lseek,
+	.release	= single_release,
+};
+
+static int get_reg(void *data, u64 *val)
+{
+	struct smb135x_chg *chip = data;
+	int rc;
+	u8 temp;
+
+	rc = smb135x_read(chip, chip->peek_poke_address, &temp);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't read reg %x rc = %d\n",
+			chip->peek_poke_address, rc);
+		return -EAGAIN;
+	}
+	*val = temp;
+	return 0;
+}
+
+static int set_reg(void *data, u64 val)
+{
+	struct smb135x_chg *chip = data;
+	int rc;
+	u8 temp;
+
+	temp = (u8) val;
+	rc = smb135x_write(chip, chip->peek_poke_address, temp);
+	if (rc < 0) {
+		dev_err(chip->dev,
+			"Couldn't write 0x%02x to 0x%02x rc= %d\n",
+			chip->peek_poke_address, temp, rc);
+		return -EAGAIN;
+	}
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(poke_poke_debug_ops, get_reg, set_reg, "0x%02llx\n");
+
+static int force_irq_set(void *data, u64 val)
+{
+	struct smb135x_chg *chip = data;
+
+	smb135x_chg_stat_handler(chip->client->irq, data);
+	return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_irq_ops, NULL, force_irq_set, "0x%02llx\n");
+
+static int force_rechg_set(void *data, u64 val)
+{
+	int rc = 0;
+	struct smb135x_chg *chip = data;
+
+	if (!chip->chg_enabled) {
+		pr_debug("Charging Disabled force recharge not allowed\n");
+		return -EINVAL;
+	}
+
+	if (!chip->inhibit_disabled) {
+		rc = smb135x_masked_write(chip, CFG_14_REG, EN_CHG_INHIBIT_BIT,
+					0);
+		if (rc)
+			dev_err(chip->dev,
+				"Couldn't disable charge-inhibit rc=%d\n", rc);
+
+		/* delay for charge-inhibit to take affect */
+		msleep(500);
+	}
+
+	rc |= smb135x_charging(chip, false);
+	rc |= smb135x_charging(chip, true);
+
+	if (!chip->inhibit_disabled) {
+		rc |= smb135x_masked_write(chip, CFG_14_REG,
+				EN_CHG_INHIBIT_BIT, EN_CHG_INHIBIT_BIT);
+		if (rc)
+			dev_err(chip->dev,
+				"Couldn't enable charge-inhibit rc=%d\n", rc);
+	}
+
+	return rc;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_rechg_ops, NULL, force_rechg_set, "0x%02llx\n");
+
+#ifdef DEBUG
+static void dump_regs(struct smb135x_chg *chip)
+{
+	int rc;
+	u8 reg;
+	u8 addr;
+
+	for (addr = 0; addr <= LAST_CNFG_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+					addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	for (addr = FIRST_STATUS_REG; addr <= LAST_STATUS_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+					addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+
+	for (addr = FIRST_CMD_REG; addr <= LAST_CMD_REG; addr++) {
+		rc = smb135x_read(chip, addr, &reg);
+		if (rc < 0)
+			dev_err(chip->dev, "Couldn't read 0x%02x rc = %d\n",
+					addr, rc);
+		else
+			pr_debug("0x%02x = 0x%02x\n", addr, reg);
+	}
+}
+#else
+static void dump_regs(struct smb135x_chg *chip)
+{
+}
+#endif
+static int determine_initial_status(struct smb135x_chg *chip)
+{
+	union power_supply_propval pval = {0, };
+	int rc;
+	u8 reg;
+
+	/*
+	 * It is okay to read the interrupt status here since
+	 * interrupts aren't requested. reading interrupt status
+	 * clears the interrupt so be careful to read interrupt
+	 * status only in interrupt handling code
+	 */
+
+	chip->batt_present = true;
+	rc = smb135x_read(chip, IRQ_B_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read irq b rc = %d\n", rc);
+		return rc;
+	}
+	if (reg & IRQ_B_BATT_TERMINAL_BIT || reg & IRQ_B_BATT_MISSING_BIT)
+		chip->batt_present = false;
+	rc = smb135x_read(chip, STATUS_4_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read status 4 rc = %d\n", rc);
+		return rc;
+	}
+	/* treat battery gone if less than 2V */
+	if (reg & BATT_LESS_THAN_2V)
+		chip->batt_present = false;
+
+	rc = smb135x_read(chip, IRQ_A_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
+		return rc;
+	}
+
+	if (reg & IRQ_A_HOT_HARD_BIT)
+		chip->batt_hot = true;
+	if (reg & IRQ_A_COLD_HARD_BIT)
+		chip->batt_cold = true;
+	if (reg & IRQ_A_HOT_SOFT_BIT)
+		chip->batt_warm = true;
+	if (reg & IRQ_A_COLD_SOFT_BIT)
+		chip->batt_cool = true;
+
+	rc = smb135x_read(chip, IRQ_C_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read irq A rc = %d\n", rc);
+		return rc;
+	}
+	if (reg & IRQ_C_TERM_BIT)
+		chip->chg_done_batt_full = true;
+
+	rc = smb135x_read(chip, IRQ_E_REG, &reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't read irq E rc = %d\n", rc);
+		return rc;
+	}
+	chip->usb_present = !(reg & IRQ_E_USB_OV_BIT)
+				&& !(reg & IRQ_E_USB_UV_BIT);
+	chip->dc_present = !(reg & IRQ_E_DC_OV_BIT) && !(reg & IRQ_E_DC_UV_BIT);
+
+	if (chip->usb_present)
+		handle_usb_insertion(chip);
+	else
+		handle_usb_removal(chip);
+
+	if (chip->dc_psy_type != -EINVAL) {
+		if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIRELESS) {
+			/*
+			 * put the dc path in suspend state if it is powered
+			 * by wireless charger
+			 */
+			if (chip->dc_present)
+				smb135x_path_suspend(chip, DC, CURRENT, false);
+			else
+				smb135x_path_suspend(chip, DC, CURRENT, true);
+		}
+	}
+
+	chip->usb_slave_present = is_usb_slave_present(chip);
+	if (chip->usb_psy && !chip->id_line_not_connected) {
+		pr_debug("setting usb psy usb_otg = %d\n",
+				chip->usb_slave_present);
+		pval.intval = chip->usb_slave_present;
+		power_supply_set_property(chip->usb_psy,
+				POWER_SUPPLY_PROP_USB_OTG, &pval);
+	}
+	return 0;
+}
+
+static int smb135x_hw_init(struct smb135x_chg *chip)
+{
+	int rc;
+	int i;
+	u8 reg, mask;
+
+	if (chip->pinctrl_state_name) {
+		chip->smb_pinctrl = pinctrl_get_select(chip->dev,
+						chip->pinctrl_state_name);
+		if (IS_ERR(chip->smb_pinctrl)) {
+			pr_err("Could not get/set %s pinctrl state rc = %ld\n",
+						chip->pinctrl_state_name,
+						PTR_ERR(chip->smb_pinctrl));
+			return PTR_ERR(chip->smb_pinctrl);
+		}
+	}
+
+	if (chip->therm_bias_vreg) {
+		rc = regulator_enable(chip->therm_bias_vreg);
+		if (rc) {
+			pr_err("Couldn't enable therm-bias rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	/*
+	 * Enable USB data line pullup regulator this is needed for the D+
+	 * line to be at proper voltage for HVDCP charger detection.
+	 */
+	if (chip->usb_pullup_vreg) {
+		rc = regulator_enable(chip->usb_pullup_vreg);
+		if (rc) {
+			pr_err("Unable to enable data line pull-up regulator rc=%d\n",
+					rc);
+			if (chip->therm_bias_vreg)
+				regulator_disable(chip->therm_bias_vreg);
+			return rc;
+		}
+	}
+
+	rc = smb135x_enable_volatile_writes(chip);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't configure for volatile rc = %d\n",
+				rc);
+		goto free_regulator;
+	}
+
+	/*
+	 * force using current from the register i.e. ignore auto
+	 * power source detect (APSD) mA ratings
+	 */
+	mask = USE_REGISTER_FOR_CURRENT;
+
+	if (chip->workaround_flags & WRKARND_USB100_BIT)
+		reg = 0;
+	else
+		/* this ignores APSD results */
+		reg = USE_REGISTER_FOR_CURRENT;
+
+	rc = smb135x_masked_write(chip, CMD_INPUT_LIMIT, mask, reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
+		goto free_regulator;
+	}
+
+	/* set bit 0 = 100mA bit 1 = 500mA and set register control */
+	rc = smb135x_masked_write(chip, CFG_E_REG,
+			POLARITY_100_500_BIT | USB_CTRL_BY_PIN_BIT,
+			POLARITY_100_500_BIT);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set usbin cfg rc=%d\n", rc);
+		goto free_regulator;
+	}
+
+	/*
+	 * set chg en by cmd register, set chg en by writing bit 1,
+	 * enable auto pre to fast, enable current termination, enable
+	 * auto recharge, enable chg inhibition based on the dt flag
+	 */
+	if (chip->inhibit_disabled)
+		reg = 0;
+	else
+		reg = EN_CHG_INHIBIT_BIT;
+
+	rc = smb135x_masked_write(chip, CFG_14_REG,
+			CHG_EN_BY_PIN_BIT | CHG_EN_ACTIVE_LOW_BIT
+			| PRE_TO_FAST_REQ_CMD_BIT | DISABLE_AUTO_RECHARGE_BIT
+			| EN_CHG_INHIBIT_BIT, reg);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set cfg 14 rc=%d\n", rc);
+		goto free_regulator;
+	}
+
+	/* control USB suspend via command bits */
+	rc = smb135x_masked_write(chip, USBIN_DCIN_CFG_REG,
+		USBIN_SUSPEND_VIA_COMMAND_BIT, USBIN_SUSPEND_VIA_COMMAND_BIT);
+
+	/* set the float voltage */
+	if (chip->vfloat_mv != -EINVAL) {
+		rc = smb135x_float_voltage_set(chip, chip->vfloat_mv);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't set float voltage rc = %d\n", rc);
+			goto free_regulator;
+		}
+	}
+
+	/* set iterm */
+	if (chip->iterm_ma != -EINVAL) {
+		if (chip->iterm_disabled) {
+			dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
+			rc = -EINVAL;
+			goto free_regulator;
+		} else {
+			if (chip->iterm_ma <= 50)
+				reg = CHG_ITERM_50MA;
+			else if (chip->iterm_ma <= 100)
+				reg = CHG_ITERM_100MA;
+			else if (chip->iterm_ma <= 150)
+				reg = CHG_ITERM_150MA;
+			else if (chip->iterm_ma <= 200)
+				reg = CHG_ITERM_200MA;
+			else if (chip->iterm_ma <= 250)
+				reg = CHG_ITERM_250MA;
+			else if (chip->iterm_ma <= 300)
+				reg = CHG_ITERM_300MA;
+			else if (chip->iterm_ma <= 500)
+				reg = CHG_ITERM_500MA;
+			else
+				reg = CHG_ITERM_600MA;
+
+			rc = smb135x_masked_write(chip, CFG_3_REG,
+							CHG_ITERM_MASK, reg);
+			if (rc) {
+				dev_err(chip->dev,
+					"Couldn't set iterm rc = %d\n", rc);
+				goto free_regulator;
+			}
+
+			rc = smb135x_masked_write(chip, CFG_14_REG,
+						DISABLE_CURRENT_TERM_BIT, 0);
+			if (rc) {
+				dev_err(chip->dev,
+					"Couldn't enable iterm rc = %d\n", rc);
+				goto free_regulator;
+			}
+		}
+	} else  if (chip->iterm_disabled) {
+		rc = smb135x_masked_write(chip, CFG_14_REG,
+					DISABLE_CURRENT_TERM_BIT,
+					DISABLE_CURRENT_TERM_BIT);
+		if (rc) {
+			dev_err(chip->dev, "Couldn't set iterm rc = %d\n",
+								rc);
+			goto free_regulator;
+		}
+	}
+
+	/* set the safety time voltage */
+	if (chip->safety_time != -EINVAL) {
+		if (chip->safety_time == 0) {
+			/* safety timer disabled */
+			reg = 1 << SAFETY_TIME_EN_SHIFT;
+			rc = smb135x_masked_write(chip, CFG_16_REG,
+						SAFETY_TIME_EN_BIT, reg);
+			if (rc < 0) {
+				dev_err(chip->dev,
+				"Couldn't disable safety timer rc = %d\n",
+				rc);
+				goto free_regulator;
+			}
+		} else {
+			for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
+				if (chip->safety_time <= chg_time[i]) {
+					reg = i << SAFETY_TIME_MINUTES_SHIFT;
+					break;
+				}
+			}
+			rc = smb135x_masked_write(chip, CFG_16_REG,
+				SAFETY_TIME_EN_BIT | SAFETY_TIME_MINUTES_MASK,
+				reg);
+			if (rc < 0) {
+				dev_err(chip->dev,
+					"Couldn't set safety timer rc = %d\n",
+					rc);
+				goto free_regulator;
+			}
+		}
+	}
+
+	/* battery missing detection */
+	rc = smb135x_masked_write(chip, CFG_19_REG,
+			BATT_MISSING_ALGO_BIT | BATT_MISSING_THERM_BIT,
+			chip->bmd_algo_disabled ? BATT_MISSING_THERM_BIT :
+						BATT_MISSING_ALGO_BIT);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
+									rc);
+		goto free_regulator;
+	}
+
+	/* set maximum fastchg current */
+	if (chip->fastchg_ma != -EINVAL) {
+		rc = smb135x_set_fastchg_current(chip, chip->fastchg_ma);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set fastchg current = %d\n",
+									rc);
+			goto free_regulator;
+		}
+	}
+
+	if (chip->usb_pullup_vreg) {
+		/* enable 9V HVDCP adapter support */
+		rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT,
+					HVDCP_5_9_BIT);
+		if (rc < 0) {
+			dev_err(chip->dev,
+				"Couldn't request for 5 or 9V rc=%d\n", rc);
+			goto free_regulator;
+		}
+	}
+
+	if (chip->gamma_setting) {
+		rc = smb135x_masked_write(chip, CFG_1B_REG, COLD_HARD_MASK,
+				chip->gamma_setting[0] << COLD_HARD_SHIFT);
+
+		rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_HARD_MASK,
+				chip->gamma_setting[1] << HOT_HARD_SHIFT);
+
+		rc |= smb135x_masked_write(chip, CFG_1B_REG, COLD_SOFT_MASK,
+				chip->gamma_setting[2] << COLD_SOFT_SHIFT);
+
+		rc |= smb135x_masked_write(chip, CFG_1B_REG, HOT_SOFT_MASK,
+				chip->gamma_setting[3] << HOT_SOFT_SHIFT);
+		if (rc < 0)
+			goto free_regulator;
+	}
+
+	__smb135x_charging(chip, chip->chg_enabled);
+
+	/* interrupt enabling - active low */
+	if (chip->client->irq) {
+		mask = CHG_STAT_IRQ_ONLY_BIT | CHG_STAT_ACTIVE_HIGH_BIT
+			| CHG_STAT_DISABLE_BIT;
+		reg = CHG_STAT_IRQ_ONLY_BIT;
+		rc = smb135x_masked_write(chip, CFG_17_REG, mask, reg);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set irq config rc = %d\n",
+					rc);
+			goto free_regulator;
+		}
+
+		/* enabling only interesting interrupts */
+		rc = smb135x_write(chip, IRQ_CFG_REG,
+			IRQ_BAT_HOT_COLD_HARD_BIT
+			| IRQ_BAT_HOT_COLD_SOFT_BIT
+			| IRQ_OTG_OVER_CURRENT_BIT
+			| IRQ_INTERNAL_TEMPERATURE_BIT
+			| IRQ_USBIN_UV_BIT);
+
+		rc |= smb135x_write(chip, IRQ2_CFG_REG,
+			IRQ2_SAFETY_TIMER_BIT
+			| IRQ2_CHG_ERR_BIT
+			| IRQ2_CHG_PHASE_CHANGE_BIT
+			| IRQ2_POWER_OK_BIT
+			| IRQ2_BATT_MISSING_BIT
+			| IRQ2_VBAT_LOW_BIT);
+
+		rc |= smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
+				| IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set irq enable rc = %d\n",
+					rc);
+			goto free_regulator;
+		}
+	}
+
+	/* resume threshold */
+	if (chip->resume_delta_mv != -EINVAL)
+		smb135x_set_resume_threshold(chip, chip->resume_delta_mv);
+
+	/* DC path current settings */
+	if (chip->dc_psy_type != -EINVAL) {
+		rc = smb135x_set_dc_chg_current(chip, chip->dc_psy_ma);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't set dc charge current rc = %d\n",
+					rc);
+			goto free_regulator;
+		}
+	}
+
+	/*
+	 * on some devices the battery is powered via external sources which
+	 * could raise its voltage above the float voltage. smb135x chips go
+	 * in to reverse boost in such a situation and the workaround is to
+	 * disable float voltage compensation (note that the battery will appear
+	 * hot/cold when powered via external source).
+	 */
+
+	if (chip->soft_vfloat_comp_disabled) {
+		mask = HOT_SOFT_VFLOAT_COMP_EN_BIT
+				| COLD_SOFT_VFLOAT_COMP_EN_BIT;
+		rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
+					rc);
+			goto free_regulator;
+		}
+	}
+
+	if (chip->soft_current_comp_disabled) {
+		mask = HOT_SOFT_CURRENT_COMP_EN_BIT
+				| COLD_SOFT_CURRENT_COMP_EN_BIT;
+		rc = smb135x_masked_write(chip, CFG_1A_REG, mask, 0);
+		if (rc < 0) {
+			dev_err(chip->dev, "Couldn't disable soft current rc = %d\n",
+					rc);
+			goto free_regulator;
+		}
+	}
+
+	/*
+	 * Command mode for OTG control. This gives us RID interrupts but keeps
+	 * enabling the 5V OTG via i2c register control
+	 */
+	rc = smb135x_masked_write(chip, USBIN_OTG_REG, OTG_CNFG_MASK,
+			OTG_CNFG_COMMAND_CTRL);
+	if (rc < 0) {
+		dev_err(chip->dev, "Couldn't write to otg cfg reg rc = %d\n",
+				rc);
+		goto free_regulator;
+	}
+	return 0;
+
+free_regulator:
+	if (chip->therm_bias_vreg)
+		regulator_disable(chip->therm_bias_vreg);
+	if (chip->usb_pullup_vreg)
+		regulator_disable(chip->usb_pullup_vreg);
+	return rc;
+}
+
+static const struct of_device_id smb135x_match_table[] = {
+	{
+		.compatible	= "qcom,smb1356-charger",
+		.data		= &version_data[V_SMB1356],
+	},
+	{
+		.compatible	= "qcom,smb1357-charger",
+		.data		= &version_data[V_SMB1357],
+	},
+	{
+		.compatible	= "qcom,smb1358-charger",
+		.data		= &version_data[V_SMB1358],
+	},
+	{
+		.compatible	= "qcom,smb1359-charger",
+		.data		= &version_data[V_SMB1359],
+	},
+	{ },
+};
+
+#define DC_MA_MIN 300
+#define DC_MA_MAX 2000
+#define NUM_GAMMA_VALUES 4
+static int smb_parse_dt(struct smb135x_chg *chip)
+{
+	int rc;
+	struct device_node *node = chip->dev->of_node;
+	const char *dc_psy_type;
+
+	if (!node) {
+		dev_err(chip->dev, "device tree info. missing\n");
+		return -EINVAL;
+	}
+
+	rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+						&chip->vfloat_mv);
+	if (rc < 0)
+		chip->vfloat_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,charging-timeout",
+						&chip->safety_time);
+	if (rc < 0)
+		chip->safety_time = -EINVAL;
+
+	if (!rc &&
+		(chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
+		dev_err(chip->dev, "Bad charging-timeout %d\n",
+						chip->safety_time);
+		return -EINVAL;
+	}
+
+	chip->bmd_algo_disabled = of_property_read_bool(node,
+						"qcom,bmd-algo-disabled");
+
+	chip->dc_psy_type = -EINVAL;
+	dc_psy_type = of_get_property(node, "qcom,dc-psy-type", NULL);
+	if (dc_psy_type) {
+		if (strcmp(dc_psy_type, "Mains") == 0)
+			chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
+		else if (strcmp(dc_psy_type, "Wireless") == 0)
+			chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
+	}
+
+	if (chip->dc_psy_type != -EINVAL) {
+		rc = of_property_read_u32(node, "qcom,dc-psy-ma",
+							&chip->dc_psy_ma);
+		if (rc < 0) {
+			dev_err(chip->dev,
+					"no mA current for dc rc = %d\n", rc);
+			return rc;
+		}
+
+		if (chip->dc_psy_ma < DC_MA_MIN
+				|| chip->dc_psy_ma > DC_MA_MAX) {
+			dev_err(chip->dev, "Bad dc mA %d\n", chip->dc_psy_ma);
+			return -EINVAL;
+		}
+	}
+
+	rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
+						&chip->resume_delta_mv);
+	if (rc < 0)
+		chip->resume_delta_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,iterm-ma", &chip->iterm_ma);
+	if (rc < 0)
+		chip->iterm_ma = -EINVAL;
+
+	chip->iterm_disabled = of_property_read_bool(node,
+						"qcom,iterm-disabled");
+
+	chip->chg_disabled_permanently = (of_property_read_bool(node,
+						"qcom,charging-disabled"));
+	chip->chg_enabled = !chip->chg_disabled_permanently;
+
+	chip->inhibit_disabled  = of_property_read_bool(node,
+						"qcom,inhibit-disabled");
+
+	chip->bms_controlled_charging  = of_property_read_bool(node,
+					"qcom,bms-controlled-charging");
+
+	rc = of_property_read_string(node, "qcom,bms-psy-name",
+						&chip->bms_psy_name);
+	if (rc)
+		chip->bms_psy_name = NULL;
+
+	rc = of_property_read_u32(node, "qcom,fastchg-ma", &chip->fastchg_ma);
+	if (rc < 0)
+		chip->fastchg_ma = -EINVAL;
+
+	chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
+					"qcom,soft-vfloat-comp-disabled");
+
+	chip->soft_current_comp_disabled = of_property_read_bool(node,
+					"qcom,soft-current-comp-disabled");
+
+	if (of_find_property(node, "therm-bias-supply", NULL)) {
+		/* get the thermistor bias regulator */
+		chip->therm_bias_vreg = devm_regulator_get(chip->dev,
+							"therm-bias");
+		if (IS_ERR(chip->therm_bias_vreg))
+			return PTR_ERR(chip->therm_bias_vreg);
+	}
+
+	/*
+	 * Gamma value indicates the ratio of the pull up resistors and NTC
+	 * resistor in battery pack. There are 4 options, refer to the graphic
+	 * user interface and choose the right one.
+	 */
+	if (of_find_property(node, "qcom,gamma-setting",
+					&chip->gamma_setting_num)) {
+		chip->gamma_setting_num = chip->gamma_setting_num /
+					sizeof(chip->gamma_setting_num);
+		if (chip->gamma_setting_num != NUM_GAMMA_VALUES) {
+			pr_err("Gamma setting not correct!\n");
+			return -EINVAL;
+		}
+
+		chip->gamma_setting = devm_kzalloc(chip->dev,
+			chip->gamma_setting_num *
+				sizeof(chip->gamma_setting_num), GFP_KERNEL);
+		if (!chip->gamma_setting) {
+			pr_err("gamma setting kzalloc failed!\n");
+			return -ENOMEM;
+		}
+
+		rc = of_property_read_u32_array(node,
+					"qcom,gamma-setting",
+				chip->gamma_setting, chip->gamma_setting_num);
+		if (rc) {
+			pr_err("Couldn't read gamma setting, rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	if (of_find_property(node, "qcom,thermal-mitigation",
+					&chip->thermal_levels)) {
+		chip->thermal_mitigation = devm_kzalloc(chip->dev,
+			chip->thermal_levels,
+			GFP_KERNEL);
+
+		if (chip->thermal_mitigation == NULL) {
+			pr_err("thermal mitigation kzalloc() failed.\n");
+			return -ENOMEM;
+		}
+
+		chip->thermal_levels /= sizeof(int);
+		rc = of_property_read_u32_array(node,
+				"qcom,thermal-mitigation",
+				chip->thermal_mitigation, chip->thermal_levels);
+		if (rc) {
+			pr_err("Couldn't read threm limits rc = %d\n", rc);
+			return rc;
+		}
+	}
+
+	if (of_find_property(node, "usb-pullup-supply", NULL)) {
+		/* get the data line pull-up regulator */
+		chip->usb_pullup_vreg = devm_regulator_get(chip->dev,
+							"usb-pullup");
+		if (IS_ERR(chip->usb_pullup_vreg))
+			return PTR_ERR(chip->usb_pullup_vreg);
+	}
+
+	chip->pinctrl_state_name = of_get_property(node, "pinctrl-names", NULL);
+
+	chip->id_line_not_connected = of_property_read_bool(node,
+						"qcom,id-line-not-connected");
+	return 0;
+}
+
+static int create_debugfs_entries(struct smb135x_chg *chip)
+{
+	chip->debug_root = debugfs_create_dir("smb135x", NULL);
+	if (!chip->debug_root)
+		dev_err(chip->dev, "Couldn't create debug dir\n");
+
+	if (chip->debug_root) {
+		struct dentry *ent;
+
+		ent = debugfs_create_file("config_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &cnfg_debugfs_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create cnfg debug file\n");
+
+		ent = debugfs_create_file("status_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &status_debugfs_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create status debug file\n");
+
+		ent = debugfs_create_file("cmd_registers", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &cmd_debugfs_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create cmd debug file\n");
+
+		ent = debugfs_create_x32("address", S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->peek_poke_address));
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create address debug file\n");
+
+		ent = debugfs_create_file("data", S_IFREG | 0644,
+					  chip->debug_root, chip,
+					  &poke_poke_debug_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create data debug file\n");
+
+		ent = debugfs_create_file("force_irq",
+					  S_IFREG | 0644,
+					  chip->debug_root, chip,
+					  &force_irq_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create force_irq debug file\n");
+
+		ent = debugfs_create_x32("skip_writes",
+					  S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->skip_writes));
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create skip writes debug file\n");
+
+		ent = debugfs_create_x32("skip_reads",
+					  S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->skip_reads));
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create skip reads debug file\n");
+
+		ent = debugfs_create_file("irq_count", S_IFREG | 0444,
+					  chip->debug_root, chip,
+					  &irq_count_debugfs_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create irq_count debug file\n");
+
+		ent = debugfs_create_file("force_recharge",
+					  S_IFREG | 0644,
+					  chip->debug_root, chip,
+					  &force_rechg_ops);
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create force recharge debug file\n");
+
+		ent = debugfs_create_x32("usb_suspend_votes",
+					  S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->usb_suspended));
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create usb_suspend_votes file\n");
+
+		ent = debugfs_create_x32("dc_suspend_votes",
+					  S_IFREG | 0644,
+					  chip->debug_root,
+					  &(chip->dc_suspended));
+		if (!ent)
+			dev_err(chip->dev,
+				"Couldn't create dc_suspend_votes file\n");
+	}
+	return 0;
+}
+
+static int is_parallel_charger(struct i2c_client *client)
+{
+	struct device_node *node = client->dev.of_node;
+
+	return of_property_read_bool(node, "qcom,parallel-charger");
+}
+
+static int smb135x_main_charger_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	int rc;
+	struct smb135x_chg *chip;
+	struct power_supply *usb_psy;
+	struct power_supply_config batt_psy_cfg = {};
+	struct power_supply_config dc_psy_cfg = {};
+	u8 reg = 0;
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client = client;
+	chip->dev = &client->dev;
+
+	rc = smb_parse_dt(chip);
+	if (rc < 0) {
+		dev_err(&client->dev, "Unable to parse DT nodes\n");
+		return rc;
+	}
+
+	usb_psy = power_supply_get_by_name("usb");
+	if (!usb_psy && chip->chg_enabled) {
+		dev_dbg(&client->dev, "USB supply not found; defer probe\n");
+		return -EPROBE_DEFER;
+	}
+	chip->usb_psy = usb_psy;
+
+	chip->fake_battery_soc = -EINVAL;
+
+	INIT_DELAYED_WORK(&chip->wireless_insertion_work,
+					wireless_insertion_work);
+
+	INIT_DELAYED_WORK(&chip->reset_otg_oc_count_work,
+					reset_otg_oc_count_work);
+	INIT_DELAYED_WORK(&chip->hvdcp_det_work, smb135x_hvdcp_det_work);
+	mutex_init(&chip->path_suspend_lock);
+	mutex_init(&chip->current_change_lock);
+	mutex_init(&chip->read_write_lock);
+	mutex_init(&chip->otg_oc_count_lock);
+	device_init_wakeup(chip->dev, true);
+	/* probe the device to check if its actually connected */
+	rc = smb135x_read(chip, CFG_4_REG, &reg);
+	if (rc) {
+		pr_err("Failed to detect SMB135x, device may be absent\n");
+		return -ENODEV;
+	}
+
+	i2c_set_clientdata(client, chip);
+
+	rc = smb135x_chip_version_and_revision(chip);
+	if (rc) {
+		dev_err(&client->dev,
+			"Couldn't detect version/revision rc=%d\n", rc);
+		return rc;
+	}
+
+	dump_regs(chip);
+
+	rc = smb135x_regulator_init(chip);
+	if  (rc) {
+		dev_err(&client->dev,
+			"Couldn't initialize regulator rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb135x_hw_init(chip);
+	if (rc < 0) {
+		dev_err(&client->dev,
+			"Unable to initialize hardware rc = %d\n", rc);
+		goto free_regulator;
+	}
+
+	rc = determine_initial_status(chip);
+	if (rc < 0) {
+		dev_err(&client->dev,
+			"Unable to determine init status rc = %d\n", rc);
+		goto free_regulator;
+	}
+
+	chip->batt_psy_d.name = "battery";
+	chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
+	chip->batt_psy_d.get_property = smb135x_battery_get_property;
+	chip->batt_psy_d.set_property = smb135x_battery_set_property;
+	chip->batt_psy_d.properties = smb135x_battery_properties;
+	chip->batt_psy_d.num_properties
+		= ARRAY_SIZE(smb135x_battery_properties);
+	chip->batt_psy_d.external_power_changed
+		= smb135x_external_power_changed;
+	chip->batt_psy_d.property_is_writeable = smb135x_battery_is_writeable;
+
+	batt_psy_cfg.drv_data = chip;
+	batt_psy_cfg.num_supplicants = 0;
+	if (chip->bms_controlled_charging) {
+		batt_psy_cfg.supplied_to = pm_batt_supplied_to;
+		batt_psy_cfg.num_supplicants
+					= ARRAY_SIZE(pm_batt_supplied_to);
+	}
+	chip->batt_psy = devm_power_supply_register(chip->dev,
+				&chip->batt_psy_d, &batt_psy_cfg);
+	if (IS_ERR(chip->batt_psy)) {
+		dev_err(&client->dev, "Unable to register batt_psy rc = %ld\n",
+				PTR_ERR(chip->batt_psy));
+		goto free_regulator;
+	}
+
+	if (chip->dc_psy_type != -EINVAL) {
+		chip->dc_psy_d.name = "dc";
+		chip->dc_psy_d.type = chip->dc_psy_type;
+		chip->dc_psy_d.get_property = smb135x_dc_get_property;
+		chip->dc_psy_d.properties = smb135x_dc_properties;
+		chip->dc_psy_d.num_properties
+			= ARRAY_SIZE(smb135x_dc_properties);
+
+		dc_psy_cfg.drv_data = chip;
+		dc_psy_cfg.num_supplicants = 0;
+		chip->dc_psy = devm_power_supply_register(chip->dev,
+				&chip->dc_psy_d,
+				&dc_psy_cfg);
+
+		if (IS_ERR(chip->dc_psy)) {
+			dev_err(&client->dev,
+				"Unable to register dc_psy rc = %ld\n",
+				PTR_ERR(chip->dc_psy));
+			goto free_regulator;
+		}
+	}
+
+	chip->resume_completed = true;
+	mutex_init(&chip->irq_complete);
+
+	/* STAT irq configuration */
+	if (client->irq) {
+		rc = devm_request_threaded_irq(&client->dev, client->irq, NULL,
+				smb135x_chg_stat_handler,
+				IRQF_TRIGGER_LOW | IRQF_ONESHOT,
+				"smb135x_chg_stat_irq", chip);
+		if (rc < 0) {
+			dev_err(&client->dev,
+				"request_irq for irq=%d  failed rc = %d\n",
+				client->irq, rc);
+			goto free_regulator;
+		}
+		enable_irq_wake(client->irq);
+	}
+
+	create_debugfs_entries(chip);
+	dev_info(chip->dev, "SMB135X version = %s revision = %s successfully probed batt=%d dc = %d usb = %d\n",
+			version_str[chip->version],
+			revision_str[chip->revision],
+			smb135x_get_prop_batt_present(chip),
+			chip->dc_present, chip->usb_present);
+	return 0;
+
+free_regulator:
+	smb135x_regulator_deinit(chip);
+	return rc;
+}
+
+static int smb135x_parallel_charger_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	int rc;
+	struct smb135x_chg *chip;
+	const struct of_device_id *match;
+	struct device_node *node = client->dev.of_node;
+	struct power_supply_config parallel_psy_cfg = {};
+
+	chip = devm_kzalloc(&client->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->client = client;
+	chip->dev = &client->dev;
+	chip->parallel_charger = true;
+	chip->dc_psy_type = -EINVAL;
+
+	chip->chg_enabled = !(of_property_read_bool(node,
+						"qcom,charging-disabled"));
+
+	rc = of_property_read_u32(node, "qcom,recharge-thresh-mv",
+						&chip->resume_delta_mv);
+	if (rc < 0)
+		chip->resume_delta_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,float-voltage-mv",
+						&chip->vfloat_mv);
+	if (rc < 0)
+		chip->vfloat_mv = -EINVAL;
+
+	rc = of_property_read_u32(node, "qcom,parallel-en-pin-polarity",
+					&chip->parallel_pin_polarity_setting);
+	if (rc)
+		chip->parallel_pin_polarity_setting = CHG_EN_ACTIVE_LOW_BIT;
+	else
+		chip->parallel_pin_polarity_setting =
+				chip->parallel_pin_polarity_setting ?
+				CHG_EN_ACTIVE_HIGH_BIT : CHG_EN_ACTIVE_LOW_BIT;
+
+	mutex_init(&chip->path_suspend_lock);
+	mutex_init(&chip->current_change_lock);
+	mutex_init(&chip->read_write_lock);
+
+	match = of_match_node(smb135x_match_table, node);
+	if (match == NULL) {
+		dev_err(chip->dev, "device tree match not found\n");
+		return -EINVAL;
+	}
+
+	chip->version = *(int *)match->data;
+	smb135x_set_current_tables(chip);
+
+	i2c_set_clientdata(client, chip);
+
+	chip->parallel_psy_d.name = "parallel";
+	chip->parallel_psy_d.type = POWER_SUPPLY_TYPE_PARALLEL;
+	chip->parallel_psy_d.get_property = smb135x_parallel_get_property;
+	chip->parallel_psy_d.set_property = smb135x_parallel_set_property;
+	chip->parallel_psy_d.properties	= smb135x_parallel_properties;
+	chip->parallel_psy_d.property_is_writeable
+				= smb135x_parallel_is_writeable;
+	chip->parallel_psy_d.num_properties
+				= ARRAY_SIZE(smb135x_parallel_properties);
+
+	parallel_psy_cfg.drv_data = chip;
+	parallel_psy_cfg.num_supplicants = 0;
+	chip->parallel_psy = devm_power_supply_register(chip->dev,
+			&chip->parallel_psy_d,
+			&parallel_psy_cfg);
+	if (IS_ERR(chip->parallel_psy)) {
+		dev_err(&client->dev,
+			"Unable to register parallel_psy rc = %ld\n",
+			PTR_ERR(chip->parallel_psy));
+		return rc;
+	}
+
+	chip->resume_completed = true;
+	mutex_init(&chip->irq_complete);
+
+	create_debugfs_entries(chip);
+
+	dev_info(chip->dev, "SMB135X USB PARALLEL CHARGER version = %s successfully probed\n",
+			version_str[chip->version]);
+	return 0;
+}
+
+static int smb135x_chg_probe(struct i2c_client *client,
+				const struct i2c_device_id *id)
+{
+	if (is_parallel_charger(client))
+		return smb135x_parallel_charger_probe(client, id);
+	else
+		return smb135x_main_charger_probe(client, id);
+}
+
+static int smb135x_chg_remove(struct i2c_client *client)
+{
+	int rc;
+	struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+	debugfs_remove_recursive(chip->debug_root);
+
+	if (chip->parallel_charger)
+		goto mutex_destroy;
+
+	if (chip->therm_bias_vreg) {
+		rc = regulator_disable(chip->therm_bias_vreg);
+		if (rc)
+			pr_err("Couldn't disable therm-bias rc = %d\n", rc);
+	}
+
+	if (chip->usb_pullup_vreg) {
+		rc = regulator_disable(chip->usb_pullup_vreg);
+		if (rc)
+			pr_err("Couldn't disable data-pullup rc = %d\n", rc);
+	}
+
+	smb135x_regulator_deinit(chip);
+
+mutex_destroy:
+	mutex_destroy(&chip->irq_complete);
+	return 0;
+}
+
+static int smb135x_suspend(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb135x_chg *chip = i2c_get_clientdata(client);
+	int i, rc;
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+
+	/* Save the current IRQ config */
+	for (i = 0; i < 3; i++) {
+		rc = smb135x_read(chip, IRQ_CFG_REG + i,
+					&chip->irq_cfg_mask[i]);
+		if (rc)
+			dev_err(chip->dev,
+				"Couldn't save irq cfg regs rc=%d\n", rc);
+	}
+
+	/* enable only important IRQs */
+	rc = smb135x_write(chip, IRQ_CFG_REG, IRQ_USBIN_UV_BIT);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set irq_cfg rc = %d\n", rc);
+
+	rc = smb135x_write(chip, IRQ2_CFG_REG, IRQ2_BATT_MISSING_BIT
+						| IRQ2_VBAT_LOW_BIT
+						| IRQ2_POWER_OK_BIT);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set irq2_cfg rc = %d\n", rc);
+
+	rc = smb135x_write(chip, IRQ3_CFG_REG, IRQ3_SRC_DETECT_BIT
+			| IRQ3_DCIN_UV_BIT | IRQ3_RID_DETECT_BIT);
+	if (rc < 0)
+		dev_err(chip->dev, "Couldn't set irq3_cfg rc = %d\n", rc);
+
+	mutex_lock(&chip->irq_complete);
+	chip->resume_completed = false;
+	mutex_unlock(&chip->irq_complete);
+
+	return 0;
+}
+
+static int smb135x_suspend_noirq(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+
+	if (chip->irq_waiting) {
+		pr_err_ratelimited("Aborting suspend, an interrupt was detected while suspending\n");
+		return -EBUSY;
+	}
+	return 0;
+}
+
+static int smb135x_resume(struct device *dev)
+{
+	struct i2c_client *client = to_i2c_client(dev);
+	struct smb135x_chg *chip = i2c_get_clientdata(client);
+	int i, rc;
+
+	/* no suspend resume activities for parallel charger */
+	if (chip->parallel_charger)
+		return 0;
+	/* Restore the IRQ config */
+	for (i = 0; i < 3; i++) {
+		rc = smb135x_write(chip, IRQ_CFG_REG + i,
+					chip->irq_cfg_mask[i]);
+		if (rc)
+			dev_err(chip->dev,
+				"Couldn't restore irq cfg regs rc=%d\n", rc);
+	}
+	mutex_lock(&chip->irq_complete);
+	chip->resume_completed = true;
+	if (chip->irq_waiting) {
+		mutex_unlock(&chip->irq_complete);
+		smb135x_chg_stat_handler(client->irq, chip);
+		enable_irq(client->irq);
+	} else {
+		mutex_unlock(&chip->irq_complete);
+	}
+	return 0;
+}
+
+static const struct dev_pm_ops smb135x_pm_ops = {
+	.resume		= smb135x_resume,
+	.suspend_noirq	= smb135x_suspend_noirq,
+	.suspend	= smb135x_suspend,
+};
+
+static const struct i2c_device_id smb135x_chg_id[] = {
+	{"smb135x-charger", 0},
+	{},
+};
+MODULE_DEVICE_TABLE(i2c, smb135x_chg_id);
+
+static void smb135x_shutdown(struct i2c_client *client)
+{
+	int rc;
+	struct smb135x_chg *chip = i2c_get_clientdata(client);
+
+	if (chip->usb_pullup_vreg) {
+		/*
+		 * switch to 5V adapter to prevent any errorneous request of 12V
+		 * when USB D+ line pull-up regulator turns off.
+		 */
+		rc = smb135x_masked_write(chip, CFG_E_REG, HVDCP_5_9_BIT, 0);
+		if (rc < 0)
+			dev_err(chip->dev,
+				"Couldn't request for 5V rc=%d\n", rc);
+	}
+}
+
+static struct i2c_driver smb135x_chg_driver = {
+	.driver		= {
+		.name		= "smb135x-charger",
+		.owner		= THIS_MODULE,
+		.of_match_table	= smb135x_match_table,
+		.pm		= &smb135x_pm_ops,
+	},
+	.probe		= smb135x_chg_probe,
+	.remove		= smb135x_chg_remove,
+	.id_table	= smb135x_chg_id,
+	.shutdown	= smb135x_shutdown,
+};
+
+module_i2c_driver(smb135x_chg_driver);
+
+MODULE_DESCRIPTION("SMB135x Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("i2c:smb135x-charger");
diff --git a/drivers/power/supply/qcom/smb138x-charger.c b/drivers/power/supply/qcom/smb138x-charger.c
new file mode 100644
index 0000000..1e89a721
--- /dev/null
+++ b/drivers/power/supply/qcom/smb138x-charger.c
@@ -0,0 +1,1573 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "SMB138X: %s: " fmt, __func__
+
+#include <linux/device.h>
+#include <linux/iio/consumer.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_device.h>
+#include <linux/of_irq.h>
+#include <linux/platform_device.h>
+#include <linux/power_supply.h>
+#include <linux/regmap.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include "smb-reg.h"
+#include "smb-lib.h"
+#include "storm-watch.h"
+#include "pmic-voter.h"
+
+#define SMB138X_DEFAULT_FCC_UA 1000000
+#define SMB138X_DEFAULT_ICL_UA 1500000
+
+/* Registers that are not common to be mentioned in smb-reg.h */
+#define SMB2CHG_MISC_ENG_SDCDC_CFG2	(MISC_BASE + 0xC1)
+#define ENG_SDCDC_SEL_OOB_VTH_BIT	BIT(0)
+
+#define SMB2CHG_MISC_ENG_SDCDC_CFG6	(MISC_BASE + 0xC5)
+#define DEAD_TIME_MASK			GENMASK(7, 4)
+#define HIGH_DEAD_TIME_MASK		GENMASK(7, 4)
+
+#define SMB2CHG_DC_TM_SREFGEN		(DCIN_BASE + 0xE2)
+#define STACKED_DIODE_EN_BIT		BIT(2)
+
+#define TDIE_AVG_COUNT	10
+
+enum {
+	OOB_COMP_WA_BIT = BIT(0),
+};
+
+static struct smb_params v1_params = {
+	.fcc		= {
+		.name	= "fast charge current",
+		.reg	= FAST_CHARGE_CURRENT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 6000000,
+		.step_u	= 25000,
+	},
+	.fv		= {
+		.name	= "float voltage",
+		.reg	= FLOAT_VOLTAGE_CFG_REG,
+		.min_u	= 2450000,
+		.max_u	= 4950000,
+		.step_u	= 10000,
+	},
+	.usb_icl	= {
+		.name	= "usb input current limit",
+		.reg	= USBIN_CURRENT_LIMIT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 6000000,
+		.step_u	= 25000,
+	},
+	.dc_icl		= {
+		.name	= "dc input current limit",
+		.reg	= DCIN_CURRENT_LIMIT_CFG_REG,
+		.min_u	= 0,
+		.max_u	= 6000000,
+		.step_u	= 25000,
+	},
+	.freq_buck	= {
+		.name	= "buck switching frequency",
+		.reg	= CFG_BUCKBOOST_FREQ_SELECT_BUCK_REG,
+		.min_u	= 500,
+		.max_u	= 2000,
+		.step_u	= 100,
+	},
+};
+
+struct smb_dt_props {
+	bool	suspend_input;
+	int	fcc_ua;
+	int	usb_icl_ua;
+	int	dc_icl_ua;
+	int	chg_temp_max_mdegc;
+	int	connector_temp_max_mdegc;
+};
+
+struct smb138x {
+	struct smb_charger	chg;
+	struct smb_dt_props	dt;
+	struct power_supply	*parallel_psy;
+	u32			wa_flags;
+};
+
+static int __debug_mask;
+module_param_named(
+	debug_mask, __debug_mask, int, 0600
+);
+
+irqreturn_t smb138x_handle_slave_chg_state_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb138x *chip = irq_data->parent_data;
+
+	if (chip->parallel_psy)
+		power_supply_changed(chip->parallel_psy);
+
+	return IRQ_HANDLED;
+}
+
+static int smb138x_get_prop_charger_temp(struct smb138x *chip,
+				 union power_supply_propval *val)
+{
+	union power_supply_propval pval;
+	int rc = 0, avg = 0, i;
+	struct smb_charger *chg = &chip->chg;
+
+	for (i = 0; i < TDIE_AVG_COUNT; i++) {
+		pval.intval = 0;
+		rc = smblib_get_prop_charger_temp(chg, &pval);
+		if (rc < 0) {
+			pr_err("Couldnt read chg temp at %dth iteration rc = %d\n",
+					i + 1, rc);
+			return rc;
+		}
+		avg += pval.intval;
+	}
+	val->intval = avg / TDIE_AVG_COUNT;
+	return rc;
+}
+
+static int smb138x_parse_dt(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct device_node *node = chg->dev->of_node;
+	int rc;
+
+	if (!node) {
+		pr_err("device tree node missing\n");
+		return -EINVAL;
+	}
+
+	chip->dt.suspend_input = of_property_read_bool(node,
+				"qcom,suspend-input");
+
+	rc = of_property_read_u32(node,
+				"qcom,fcc-max-ua", &chip->dt.fcc_ua);
+	if (rc < 0)
+		chip->dt.fcc_ua = SMB138X_DEFAULT_FCC_UA;
+
+	rc = of_property_read_u32(node,
+				"qcom,usb-icl-ua", &chip->dt.usb_icl_ua);
+	if (rc < 0)
+		chip->dt.usb_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+	rc = of_property_read_u32(node,
+				"qcom,dc-icl-ua", &chip->dt.dc_icl_ua);
+	if (rc < 0)
+		chip->dt.dc_icl_ua = SMB138X_DEFAULT_ICL_UA;
+
+	rc = of_property_read_u32(node,
+				"qcom,charger-temp-max-mdegc",
+				&chip->dt.chg_temp_max_mdegc);
+	if (rc < 0)
+		chip->dt.chg_temp_max_mdegc = 80000;
+
+	rc = of_property_read_u32(node,
+				"qcom,connector-temp-max-mdegc",
+				&chip->dt.connector_temp_max_mdegc);
+	if (rc < 0)
+		chip->dt.connector_temp_max_mdegc = 105000;
+
+	return 0;
+}
+
+/************************
+ * USB PSY REGISTRATION *
+ ************************/
+
+static enum power_supply_property smb138x_usb_props[] = {
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_ONLINE,
+	POWER_SUPPLY_PROP_VOLTAGE_MIN,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_VOLTAGE_NOW,
+	POWER_SUPPLY_PROP_CURRENT_MAX,
+	POWER_SUPPLY_PROP_TYPE,
+	POWER_SUPPLY_PROP_TYPEC_MODE,
+	POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
+	POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION,
+};
+
+static int smb138x_usb_get_prop(struct power_supply *psy,
+				enum power_supply_property prop,
+				union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_PRESENT:
+		rc = smblib_get_prop_usb_present(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_ONLINE:
+		rc = smblib_get_prop_usb_online(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		val->intval = chg->voltage_min_uv;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		val->intval = chg->voltage_max_uv;
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+		rc = smblib_get_prop_usb_voltage_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_get_prop_usb_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPE:
+		val->intval = chg->usb_psy_desc.type;
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_MODE:
+		rc = smblib_get_prop_typec_mode(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+		rc = smblib_get_prop_typec_power_role(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION:
+		rc = smblib_get_prop_typec_cc_orientation(chg, val);
+		break;
+	default:
+		pr_err("get prop %d is not supported\n", prop);
+		return -EINVAL;
+	}
+
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+		return -ENODATA;
+	}
+
+	return rc;
+}
+
+static int smb138x_usb_set_prop(struct power_supply *psy,
+				enum power_supply_property prop,
+				const union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+		rc = smblib_set_prop_usb_voltage_min(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_set_prop_usb_voltage_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_MAX:
+		rc = smblib_set_prop_usb_current_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+		rc = smblib_set_prop_typec_power_role(chg, val);
+		break;
+	default:
+		pr_err("set prop %d is not supported\n", prop);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int smb138x_usb_prop_is_writeable(struct power_supply *psy,
+					 enum power_supply_property prop)
+{
+	switch (prop) {
+	case POWER_SUPPLY_PROP_TYPEC_POWER_ROLE:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static int smb138x_init_usb_psy(struct smb138x *chip)
+{
+	struct power_supply_config usb_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+
+	chg->usb_psy_desc.name = "usb";
+	chg->usb_psy_desc.type = POWER_SUPPLY_TYPE_UNKNOWN;
+	chg->usb_psy_desc.properties = smb138x_usb_props;
+	chg->usb_psy_desc.num_properties = ARRAY_SIZE(smb138x_usb_props);
+	chg->usb_psy_desc.get_property = smb138x_usb_get_prop;
+	chg->usb_psy_desc.set_property = smb138x_usb_set_prop;
+	chg->usb_psy_desc.property_is_writeable = smb138x_usb_prop_is_writeable;
+
+	usb_cfg.drv_data = chip;
+	usb_cfg.of_node = chg->dev->of_node;
+	chg->usb_psy = devm_power_supply_register(chg->dev,
+						  &chg->usb_psy_desc,
+						  &usb_cfg);
+	if (IS_ERR(chg->usb_psy)) {
+		pr_err("Couldn't register USB power supply\n");
+		return PTR_ERR(chg->usb_psy);
+	}
+
+	return 0;
+}
+
+/*************************
+ * BATT PSY REGISTRATION *
+ *************************/
+
+static enum power_supply_property smb138x_batt_props[] = {
+	POWER_SUPPLY_PROP_INPUT_SUSPEND,
+	POWER_SUPPLY_PROP_STATUS,
+	POWER_SUPPLY_PROP_HEALTH,
+	POWER_SUPPLY_PROP_PRESENT,
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CAPACITY,
+	POWER_SUPPLY_PROP_CHARGER_TEMP,
+	POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+	POWER_SUPPLY_PROP_SET_SHIP_MODE,
+};
+
+static int smb138x_batt_get_prop(struct power_supply *psy,
+				 enum power_supply_property prop,
+				 union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_STATUS:
+		rc = smblib_get_prop_batt_status(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_HEALTH:
+		rc = smblib_get_prop_batt_health(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_PRESENT:
+		rc = smblib_get_prop_batt_present(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smblib_get_prop_input_suspend(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		rc = smblib_get_prop_batt_charge_type(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = smblib_get_prop_batt_capacity(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP:
+		rc = smb138x_get_prop_charger_temp(chip, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+		rc = smblib_get_prop_charger_temp_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as device is active */
+		val->intval = 0;
+		break;
+	default:
+		pr_err("batt power supply get prop %d not supported\n", prop);
+		return -EINVAL;
+	}
+
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+		return -ENODATA;
+	}
+
+	return rc;
+}
+
+static int smb138x_batt_set_prop(struct power_supply *psy,
+				 enum power_supply_property prop,
+				 const union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smblib_set_prop_input_suspend(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CAPACITY:
+		rc = smblib_set_prop_batt_capacity(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as the device is active */
+		if (!val->intval)
+			break;
+		rc = smblib_set_prop_ship_mode(chg, val);
+		break;
+	default:
+		pr_err("batt power supply set prop %d not supported\n", prop);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int smb138x_batt_prop_is_writeable(struct power_supply *psy,
+					  enum power_supply_property prop)
+{
+	switch (prop) {
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+	case POWER_SUPPLY_PROP_CAPACITY:
+		return 1;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct power_supply_desc batt_psy_desc = {
+	.name			= "battery",
+	.type			= POWER_SUPPLY_TYPE_BATTERY,
+	.properties		= smb138x_batt_props,
+	.num_properties		= ARRAY_SIZE(smb138x_batt_props),
+	.get_property		= smb138x_batt_get_prop,
+	.set_property		= smb138x_batt_set_prop,
+	.property_is_writeable	= smb138x_batt_prop_is_writeable,
+};
+
+static int smb138x_init_batt_psy(struct smb138x *chip)
+{
+	struct power_supply_config batt_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	batt_cfg.drv_data = chip;
+	batt_cfg.of_node = chg->dev->of_node;
+	chg->batt_psy = devm_power_supply_register(chg->dev,
+						   &batt_psy_desc,
+						   &batt_cfg);
+	if (IS_ERR(chg->batt_psy)) {
+		pr_err("Couldn't register battery power supply\n");
+		return PTR_ERR(chg->batt_psy);
+	}
+
+	return rc;
+}
+
+/*****************************
+ * PARALLEL PSY REGISTRATION *
+ *****************************/
+
+static int smb138x_get_prop_connector_health(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc, lb_mdegc, ub_mdegc, rst_mdegc, connector_mdegc;
+
+	if (!chg->iio.connector_temp_chan ||
+		PTR_ERR(chg->iio.connector_temp_chan) == -EPROBE_DEFER)
+		chg->iio.connector_temp_chan = iio_channel_get(chg->dev,
+							"connector_temp");
+
+	if (IS_ERR(chg->iio.connector_temp_chan))
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+
+	rc = iio_read_channel_processed(chg->iio.connector_temp_thr1_chan,
+							&lb_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't read connector lower bound rc=%d\n", rc);
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+	}
+
+	rc = iio_read_channel_processed(chg->iio.connector_temp_thr2_chan,
+							&ub_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't read connector upper bound rc=%d\n", rc);
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+	}
+
+	rc = iio_read_channel_processed(chg->iio.connector_temp_thr3_chan,
+							&rst_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't read connector reset bound rc=%d\n", rc);
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+	}
+
+	rc = iio_read_channel_processed(chg->iio.connector_temp_chan,
+							&connector_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't read connector temperature rc=%d\n", rc);
+		return POWER_SUPPLY_HEALTH_UNKNOWN;
+	}
+
+	if (connector_mdegc < lb_mdegc)
+		return POWER_SUPPLY_HEALTH_COOL;
+	else if (connector_mdegc < ub_mdegc)
+		return POWER_SUPPLY_HEALTH_WARM;
+	else if (connector_mdegc < rst_mdegc)
+		return POWER_SUPPLY_HEALTH_HOT;
+
+	return POWER_SUPPLY_HEALTH_OVERHEAT;
+}
+
+static enum power_supply_property smb138x_parallel_props[] = {
+	POWER_SUPPLY_PROP_CHARGE_TYPE,
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_PIN_ENABLED,
+	POWER_SUPPLY_PROP_INPUT_SUSPEND,
+	POWER_SUPPLY_PROP_VOLTAGE_MAX,
+	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+	POWER_SUPPLY_PROP_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CHARGER_TEMP,
+	POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+	POWER_SUPPLY_PROP_MODEL_NAME,
+	POWER_SUPPLY_PROP_PARALLEL_MODE,
+	POWER_SUPPLY_PROP_CONNECTOR_HEALTH,
+	POWER_SUPPLY_PROP_SET_SHIP_MODE,
+};
+
+static int smb138x_parallel_get_prop(struct power_supply *psy,
+				     enum power_supply_property prop,
+				     union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+	u8 temp;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_CHARGE_TYPE:
+		rc = smblib_get_prop_batt_charge_type(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+		rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG,
+				 &temp);
+		if (rc >= 0)
+			val->intval = (bool)(temp & CHARGING_ENABLE_BIT);
+		break;
+	case POWER_SUPPLY_PROP_PIN_ENABLED:
+		rc = smblib_read(chg, BATTERY_CHARGER_STATUS_5_REG,
+				 &temp);
+		if (rc >= 0)
+			val->intval = !(temp & DISABLE_CHARGING_BIT);
+		break;
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smblib_get_usb_suspend(chg, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_get_charge_param(chg, &chg->param.fv, &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		rc = smblib_get_charge_param(chg, &chg->param.fcc,
+					     &val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CURRENT_NOW:
+		rc = smblib_get_prop_slave_current_now(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP:
+		rc = smb138x_get_prop_charger_temp(chip, val);
+		break;
+	case POWER_SUPPLY_PROP_CHARGER_TEMP_MAX:
+		rc = smblib_get_prop_charger_temp_max(chg, val);
+		break;
+	case POWER_SUPPLY_PROP_MODEL_NAME:
+		val->strval = "smb138x";
+		break;
+	case POWER_SUPPLY_PROP_PARALLEL_MODE:
+		val->intval = POWER_SUPPLY_PL_USBMID_USBMID;
+		break;
+	case POWER_SUPPLY_PROP_CONNECTOR_HEALTH:
+		val->intval = smb138x_get_prop_connector_health(chip);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as device is active */
+		val->intval = 0;
+		break;
+	default:
+		pr_err("parallel power supply get prop %d not supported\n",
+			prop);
+		return -EINVAL;
+	}
+
+	if (rc < 0) {
+		pr_debug("Couldn't get prop %d rc = %d\n", prop, rc);
+		return -ENODATA;
+	}
+
+	return rc;
+}
+
+static int smb138x_set_parallel_suspend(struct smb138x *chip, bool suspend)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT,
+				 suspend ? 0 : WDOG_TIMER_EN_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't %s watchdog rc=%d\n",
+		       suspend ? "disable" : "enable", rc);
+		suspend = true;
+	}
+
+	rc = smblib_masked_write(chg, USBIN_CMD_IL_REG, USBIN_SUSPEND_BIT,
+				 suspend ? USBIN_SUSPEND_BIT : 0);
+	if (rc < 0) {
+		pr_err("Couldn't %s parallel charger rc=%d\n",
+		       suspend ? "suspend" : "resume", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int smb138x_parallel_set_prop(struct power_supply *psy,
+				     enum power_supply_property prop,
+				     const union power_supply_propval *val)
+{
+	struct smb138x *chip = power_supply_get_drvdata(psy);
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	switch (prop) {
+	case POWER_SUPPLY_PROP_INPUT_SUSPEND:
+		rc = smb138x_set_parallel_suspend(chip, (bool)val->intval);
+		break;
+	case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+		rc = smblib_set_charge_param(chg, &chg->param.fv, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+		rc = smblib_set_charge_param(chg, &chg->param.fcc, val->intval);
+		break;
+	case POWER_SUPPLY_PROP_SET_SHIP_MODE:
+		/* Not in ship mode as long as the device is active */
+		if (!val->intval)
+			break;
+		rc = smblib_set_prop_ship_mode(chg, val);
+		break;
+	default:
+		pr_debug("parallel power supply set prop %d not supported\n",
+			prop);
+		return -EINVAL;
+	}
+
+	return rc;
+}
+
+static int smb138x_parallel_prop_is_writeable(struct power_supply *psy,
+					      enum power_supply_property prop)
+{
+	return 0;
+}
+
+static const struct power_supply_desc parallel_psy_desc = {
+	.name			= "parallel",
+	.type			= POWER_SUPPLY_TYPE_PARALLEL,
+	.properties		= smb138x_parallel_props,
+	.num_properties		= ARRAY_SIZE(smb138x_parallel_props),
+	.get_property		= smb138x_parallel_get_prop,
+	.set_property		= smb138x_parallel_set_prop,
+	.property_is_writeable	= smb138x_parallel_prop_is_writeable,
+};
+
+static int smb138x_init_parallel_psy(struct smb138x *chip)
+{
+	struct power_supply_config parallel_cfg = {};
+	struct smb_charger *chg = &chip->chg;
+
+	parallel_cfg.drv_data = chip;
+	parallel_cfg.of_node = chg->dev->of_node;
+	chip->parallel_psy = devm_power_supply_register(chg->dev,
+						   &parallel_psy_desc,
+						   &parallel_cfg);
+	if (IS_ERR(chip->parallel_psy)) {
+		pr_err("Couldn't register parallel power supply\n");
+		return PTR_ERR(chip->parallel_psy);
+	}
+
+	return 0;
+}
+
+/******************************
+ * VBUS REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vbus_reg_ops = {
+	.enable		= smblib_vbus_regulator_enable,
+	.disable	= smblib_vbus_regulator_disable,
+	.is_enabled	= smblib_vbus_regulator_is_enabled,
+};
+
+static int smb138x_init_vbus_regulator(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct regulator_config cfg = {};
+	int rc = 0;
+
+	chg->vbus_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vbus_vreg),
+				      GFP_KERNEL);
+	if (!chg->vbus_vreg)
+		return -ENOMEM;
+
+	cfg.dev = chg->dev;
+	cfg.driver_data = chip;
+
+	chg->vbus_vreg->rdesc.owner = THIS_MODULE;
+	chg->vbus_vreg->rdesc.type = REGULATOR_VOLTAGE;
+	chg->vbus_vreg->rdesc.ops = &smb138x_vbus_reg_ops;
+	chg->vbus_vreg->rdesc.of_match = "qcom,smb138x-vbus";
+	chg->vbus_vreg->rdesc.name = "qcom,smb138x-vbus";
+
+	chg->vbus_vreg->rdev = devm_regulator_register(chg->dev,
+						&chg->vbus_vreg->rdesc, &cfg);
+	if (IS_ERR(chg->vbus_vreg->rdev)) {
+		rc = PTR_ERR(chg->vbus_vreg->rdev);
+		chg->vbus_vreg->rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't register VBUS regualtor rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+/******************************
+ * VCONN REGULATOR REGISTRATION *
+ ******************************/
+
+struct regulator_ops smb138x_vconn_reg_ops = {
+	.enable		= smblib_vconn_regulator_enable,
+	.disable	= smblib_vconn_regulator_disable,
+	.is_enabled	= smblib_vconn_regulator_is_enabled,
+};
+
+static int smb138x_init_vconn_regulator(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct regulator_config cfg = {};
+	int rc = 0;
+
+	chg->vconn_vreg = devm_kzalloc(chg->dev, sizeof(*chg->vconn_vreg),
+				      GFP_KERNEL);
+	if (!chg->vconn_vreg)
+		return -ENOMEM;
+
+	cfg.dev = chg->dev;
+	cfg.driver_data = chip;
+
+	chg->vconn_vreg->rdesc.owner = THIS_MODULE;
+	chg->vconn_vreg->rdesc.type = REGULATOR_VOLTAGE;
+	chg->vconn_vreg->rdesc.ops = &smb138x_vconn_reg_ops;
+	chg->vconn_vreg->rdesc.of_match = "qcom,smb138x-vconn";
+	chg->vconn_vreg->rdesc.name = "qcom,smb138x-vconn";
+
+	chg->vconn_vreg->rdev = devm_regulator_register(chg->dev,
+						&chg->vconn_vreg->rdesc, &cfg);
+	if (IS_ERR(chg->vconn_vreg->rdev)) {
+		rc = PTR_ERR(chg->vconn_vreg->rdev);
+		chg->vconn_vreg->rdev = NULL;
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't register VCONN regualtor rc=%d\n", rc);
+	}
+
+	return rc;
+}
+
+/***************************
+ * HARDWARE INITIALIZATION *
+ ***************************/
+
+#define MDEGC_3		3000
+#define MDEGC_15	15000
+static int smb138x_init_slave_hw(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc;
+
+	if (chip->wa_flags & OOB_COMP_WA_BIT) {
+		rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2,
+					ENG_SDCDC_SEL_OOB_VTH_BIT,
+					ENG_SDCDC_SEL_OOB_VTH_BIT);
+		if (rc < 0) {
+			pr_err("Couldn't configure the OOB comp threshold rc = %d\n",
+									rc);
+			return rc;
+		}
+
+		rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6,
+				DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK);
+		if (rc < 0) {
+			pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n",
+									rc);
+			return rc;
+		}
+	}
+
+	/* enable watchdog bark and bite interrupts, and disable the watchdog */
+	rc = smblib_masked_write(chg, WD_CFG_REG, WDOG_TIMER_EN_BIT
+			| WDOG_TIMER_EN_ON_PLUGIN_BIT | BITE_WDOG_INT_EN_BIT
+			| BARK_WDOG_INT_EN_BIT,
+			BITE_WDOG_INT_EN_BIT | BARK_WDOG_INT_EN_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't configure the watchdog rc=%d\n", rc);
+		return rc;
+	}
+
+	/* disable charging when watchdog bites */
+	rc = smblib_masked_write(chg, SNARL_BARK_BITE_WD_CFG_REG,
+				 BITE_WDOG_DISABLE_CHARGING_CFG_BIT,
+				 BITE_WDOG_DISABLE_CHARGING_CFG_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't configure the watchdog bite rc=%d\n", rc);
+		return rc;
+	}
+
+	/* suspend parallel charging */
+	rc = smb138x_set_parallel_suspend(chip, true);
+	if (rc < 0) {
+		pr_err("Couldn't suspend parallel charging rc=%d\n", rc);
+		return rc;
+	}
+
+	/* initialize FCC to 0 */
+	rc = smblib_set_charge_param(chg, &chg->param.fcc, 0);
+	if (rc < 0) {
+		pr_err("Couldn't set 0 FCC rc=%d\n", rc);
+		return rc;
+	}
+
+	/* enable the charging path */
+	rc = smblib_masked_write(chg, CHARGING_ENABLE_CMD_REG,
+				 CHARGING_ENABLE_CMD_BIT,
+				 CHARGING_ENABLE_CMD_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't enable charging rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure charge enable for software control; active high */
+	rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+				 CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+	if (rc < 0) {
+		pr_err("Couldn't configure charge enable source rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	/* enable parallel current sensing */
+	rc = smblib_masked_write(chg, CFG_REG,
+				 VCHG_EN_CFG_BIT, VCHG_EN_CFG_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't enable parallel current sensing rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	/* enable stacked diode */
+	rc = smblib_write(chg, SMB2CHG_DC_TM_SREFGEN, STACKED_DIODE_EN_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't enable stacked diode rc=%d\n", rc);
+		return rc;
+	}
+
+	/* initialize charger temperature threshold */
+	rc = iio_write_channel_processed(chg->iio.temp_max_chan,
+					chip->dt.chg_temp_max_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't set charger temp threshold rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = iio_write_channel_processed(chg->iio.connector_temp_thr1_chan,
+				chip->dt.connector_temp_max_mdegc);
+	if (rc < 0) {
+		pr_err("Couldn't set connector temp threshold1 rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = iio_write_channel_processed(chg->iio.connector_temp_thr2_chan,
+				chip->dt.connector_temp_max_mdegc + MDEGC_3);
+	if (rc < 0) {
+		pr_err("Couldn't set connector temp threshold2 rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = iio_write_channel_processed(chg->iio.connector_temp_thr3_chan,
+				chip->dt.connector_temp_max_mdegc + MDEGC_15);
+	if (rc < 0) {
+		pr_err("Couldn't set connector temp threshold3 rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smblib_write(chg, THERMREG_SRC_CFG_REG,
+						THERMREG_SKIN_ADC_SRC_EN_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't enable connector thermreg source rc=%d\n", rc);
+		return rc;
+	}
+
+	return 0;
+}
+
+static int smb138x_init_hw(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	/* votes must be cast before configuring software control */
+	vote(chg->dc_suspend_votable,
+		DEFAULT_VOTER, chip->dt.suspend_input, 0);
+	vote(chg->fcc_votable,
+		DEFAULT_VOTER, true, chip->dt.fcc_ua);
+	vote(chg->usb_icl_votable,
+		DCP_VOTER, true, chip->dt.usb_icl_ua);
+	vote(chg->dc_icl_votable,
+		DEFAULT_VOTER, true, chip->dt.dc_icl_ua);
+
+	chg->dcp_icl_ua = chip->dt.usb_icl_ua;
+
+	/* configure to a fixed 700khz freq to avoid tdie errors */
+	rc = smblib_set_charge_param(chg, &chg->param.freq_buck, 700);
+	if (rc < 0) {
+		pr_err("Couldn't configure 700Khz switch freq rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure charge enable for software control; active high */
+	rc = smblib_masked_write(chg, CHGR_CFG2_REG,
+				 CHG_EN_POLARITY_BIT | CHG_EN_SRC_BIT, 0);
+	if (rc < 0) {
+		pr_err("Couldn't configure charge enable source rc=%d\n", rc);
+		return rc;
+	}
+
+	/* enable the charging path */
+	rc = vote(chg->chg_disable_votable, DEFAULT_VOTER, false, 0);
+	if (rc < 0) {
+		pr_err("Couldn't enable charging rc=%d\n", rc);
+		return rc;
+	}
+
+	/*
+	 * trigger the usb-typec-change interrupt only when the CC state
+	 * changes, or there was a VBUS error
+	 */
+	rc = smblib_write(chg, TYPE_C_INTRPT_ENB_REG,
+			    TYPEC_CCSTATE_CHANGE_INT_EN_BIT
+			  | TYPEC_VBUS_ERROR_INT_EN_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't configure Type-C interrupts rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure VCONN for software control */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 VCONN_EN_SRC_BIT | VCONN_EN_VALUE_BIT,
+				 VCONN_EN_SRC_BIT);
+	if (rc < 0) {
+		pr_err("Couldn't configure VCONN for SW control rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure VBUS for software control */
+	rc = smblib_masked_write(chg, OTG_CFG_REG, OTG_EN_SRC_CFG_BIT, 0);
+	if (rc < 0) {
+		pr_err("Couldn't configure VBUS for SW control rc=%d\n", rc);
+		return rc;
+	}
+
+	/* configure power role for dual-role */
+	rc = smblib_masked_write(chg, TYPE_C_INTRPT_ENB_SOFTWARE_CTRL_REG,
+				 TYPEC_POWER_ROLE_CMD_MASK, 0);
+	if (rc < 0) {
+		pr_err("Couldn't configure power role for DRP rc=%d\n", rc);
+		return rc;
+	}
+
+	if (chip->wa_flags & OOB_COMP_WA_BIT) {
+		rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG2,
+					ENG_SDCDC_SEL_OOB_VTH_BIT,
+					ENG_SDCDC_SEL_OOB_VTH_BIT);
+		if (rc < 0) {
+			pr_err("Couldn't configure the OOB comp threshold rc = %d\n",
+									rc);
+			return rc;
+		}
+
+		rc = smblib_masked_write(chg, SMB2CHG_MISC_ENG_SDCDC_CFG6,
+				DEAD_TIME_MASK, HIGH_DEAD_TIME_MASK);
+		if (rc < 0) {
+			pr_err("Couldn't configure the sdcdc cfg 6 reg rc = %d\n",
+									rc);
+			return rc;
+		}
+	}
+
+	return rc;
+}
+
+static int smb138x_setup_wa_flags(struct smb138x *chip)
+{
+	struct pmic_revid_data *pmic_rev_id;
+	struct device_node *revid_dev_node;
+
+	revid_dev_node = of_parse_phandle(chip->chg.dev->of_node,
+					"qcom,pmic-revid", 0);
+	if (!revid_dev_node) {
+		pr_err("Missing qcom,pmic-revid property\n");
+		return -EINVAL;
+	}
+
+	pmic_rev_id = get_revid_data(revid_dev_node);
+	if (IS_ERR_OR_NULL(pmic_rev_id)) {
+		/*
+		 * the revid peripheral must be registered, any failure
+		 * here only indicates that the rev-id module has not
+		 * probed yet.
+		 */
+		return -EPROBE_DEFER;
+	}
+
+	switch (pmic_rev_id->pmic_subtype) {
+	case SMB1381_SUBTYPE:
+		if (pmic_rev_id->rev4 < 2) /* SMB1381 rev 1.0 */
+			chip->wa_flags |= OOB_COMP_WA_BIT;
+		break;
+	default:
+		pr_err("PMIC subtype %d not supported\n",
+				pmic_rev_id->pmic_subtype);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+/****************************
+ * DETERMINE INITIAL STATUS *
+ ****************************/
+
+static irqreturn_t smb138x_handle_temperature_change(int irq, void *data)
+{
+	struct smb_irq_data *irq_data = data;
+	struct smb138x *chip = irq_data->parent_data;
+
+	power_supply_changed(chip->parallel_psy);
+	return IRQ_HANDLED;
+}
+
+static int smb138x_determine_initial_slave_status(struct smb138x *chip)
+{
+	struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+	smb138x_handle_temperature_change(0, &irq_data);
+	return 0;
+}
+
+static int smb138x_determine_initial_status(struct smb138x *chip)
+{
+	struct smb_irq_data irq_data = {chip, "determine-initial-status"};
+
+	smblib_handle_usb_plugin(0, &irq_data);
+	smblib_handle_usb_typec_change(0, &irq_data);
+	smblib_handle_usb_source_change(0, &irq_data);
+	return 0;
+}
+
+/**************************
+ * INTERRUPT REGISTRATION *
+ **************************/
+
+static struct smb_irq_info smb138x_irqs[] = {
+/* CHARGER IRQs */
+	[CHG_ERROR_IRQ] = {
+		.name		= "chg-error",
+		.handler	= smblib_handle_debug,
+	},
+	[CHG_STATE_CHANGE_IRQ] = {
+		.name		= "chg-state-change",
+		.handler	= smb138x_handle_slave_chg_state_change,
+		.wake		= true,
+	},
+	[STEP_CHG_STATE_CHANGE_IRQ] = {
+		.name		= "step-chg-state-change",
+		.handler	= smblib_handle_debug,
+	},
+	[STEP_CHG_SOC_UPDATE_FAIL_IRQ] = {
+		.name		= "step-chg-soc-update-fail",
+		.handler	= smblib_handle_debug,
+	},
+	[STEP_CHG_SOC_UPDATE_REQ_IRQ] = {
+		.name		= "step-chg-soc-update-request",
+		.handler	= smblib_handle_debug,
+	},
+/* OTG IRQs */
+	[OTG_FAIL_IRQ] = {
+		.name		= "otg-fail",
+		.handler	= smblib_handle_debug,
+	},
+	[OTG_OVERCURRENT_IRQ] = {
+		.name		= "otg-overcurrent",
+		.handler	= smblib_handle_debug,
+	},
+	[OTG_OC_DIS_SW_STS_IRQ] = {
+		.name		= "otg-oc-dis-sw-sts",
+		.handler	= smblib_handle_debug,
+	},
+	[TESTMODE_CHANGE_DET_IRQ] = {
+		.name		= "testmode-change-detect",
+		.handler	= smblib_handle_debug,
+	},
+/* BATTERY IRQs */
+	[BATT_TEMP_IRQ] = {
+		.name		= "bat-temp",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_OCP_IRQ] = {
+		.name		= "bat-ocp",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_OV_IRQ] = {
+		.name		= "bat-ov",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_LOW_IRQ] = {
+		.name		= "bat-low",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_THERM_ID_MISS_IRQ] = {
+		.name		= "bat-therm-or-id-missing",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+	[BATT_TERM_MISS_IRQ] = {
+		.name		= "bat-terminal-missing",
+		.handler	= smblib_handle_batt_psy_changed,
+	},
+/* USB INPUT IRQs */
+	[USBIN_COLLAPSE_IRQ] = {
+		.name		= "usbin-collapse",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_LT_3P6V_IRQ] = {
+		.name		= "usbin-lt-3p6v",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_UV_IRQ] = {
+		.name		= "usbin-uv",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_OV_IRQ] = {
+		.name		= "usbin-ov",
+		.handler	= smblib_handle_debug,
+	},
+	[USBIN_PLUGIN_IRQ] = {
+		.name		= "usbin-plugin",
+		.handler	= smblib_handle_usb_plugin,
+	},
+	[USBIN_SRC_CHANGE_IRQ] = {
+		.name		= "usbin-src-change",
+		.handler	= smblib_handle_usb_source_change,
+	},
+	[USBIN_ICL_CHANGE_IRQ] = {
+		.name		= "usbin-icl-change",
+		.handler	= smblib_handle_debug,
+	},
+	[TYPE_C_CHANGE_IRQ] = {
+		.name		= "type-c-change",
+		.handler	= smblib_handle_usb_typec_change,
+	},
+/* DC INPUT IRQs */
+	[DCIN_COLLAPSE_IRQ] = {
+		.name		= "dcin-collapse",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_LT_3P6V_IRQ] = {
+		.name		= "dcin-lt-3p6v",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_UV_IRQ] = {
+		.name		= "dcin-uv",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_OV_IRQ] = {
+		.name		= "dcin-ov",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_PLUGIN_IRQ] = {
+		.name		= "dcin-plugin",
+		.handler	= smblib_handle_debug,
+	},
+	[DIV2_EN_DG_IRQ] = {
+		.name		= "div2-en-dg",
+		.handler	= smblib_handle_debug,
+	},
+	[DCIN_ICL_CHANGE_IRQ] = {
+		.name		= "dcin-icl-change",
+		.handler	= smblib_handle_debug,
+	},
+/* MISCELLANEOUS IRQs */
+	[WDOG_SNARL_IRQ] = {
+		.name		= "wdog-snarl",
+		.handler	= smblib_handle_debug,
+	},
+	[WDOG_BARK_IRQ] = {
+		.name		= "wdog-bark",
+		.handler	= smblib_handle_wdog_bark,
+		.wake		= true,
+	},
+	[AICL_FAIL_IRQ] = {
+		.name		= "aicl-fail",
+		.handler	= smblib_handle_debug,
+	},
+	[AICL_DONE_IRQ] = {
+		.name		= "aicl-done",
+		.handler	= smblib_handle_debug,
+	},
+	[HIGH_DUTY_CYCLE_IRQ] = {
+		.name		= "high-duty-cycle",
+		.handler	= smblib_handle_debug,
+	},
+	[INPUT_CURRENT_LIMIT_IRQ] = {
+		.name		= "input-current-limiting",
+		.handler	= smblib_handle_debug,
+	},
+	[TEMPERATURE_CHANGE_IRQ] = {
+		.name		= "temperature-change",
+		.handler	= smb138x_handle_temperature_change,
+	},
+	[SWITCH_POWER_OK_IRQ] = {
+		.name		= "switcher-power-ok",
+		.handler	= smblib_handle_debug,
+	},
+};
+
+static int smb138x_get_irq_index_byname(const char *irq_name)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(smb138x_irqs); i++) {
+		if (strcmp(smb138x_irqs[i].name, irq_name) == 0)
+			return i;
+	}
+
+	return -ENOENT;
+}
+
+static int smb138x_request_interrupt(struct smb138x *chip,
+				     struct device_node *node,
+				     const char *irq_name)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0, irq, irq_index;
+	struct smb_irq_data *irq_data;
+
+	irq = of_irq_get_byname(node, irq_name);
+	if (irq < 0) {
+		pr_err("Couldn't get irq %s byname\n", irq_name);
+		return irq;
+	}
+
+	irq_index = smb138x_get_irq_index_byname(irq_name);
+	if (irq_index < 0) {
+		pr_err("%s is not a defined irq\n", irq_name);
+		return irq_index;
+	}
+
+	if (!smb138x_irqs[irq_index].handler)
+		return 0;
+
+	irq_data = devm_kzalloc(chg->dev, sizeof(*irq_data), GFP_KERNEL);
+	if (!irq_data)
+		return -ENOMEM;
+
+	irq_data->parent_data = chip;
+	irq_data->name = irq_name;
+	irq_data->storm_data = smb138x_irqs[irq_index].storm_data;
+	mutex_init(&irq_data->storm_data.storm_lock);
+
+	rc = devm_request_threaded_irq(chg->dev, irq, NULL,
+					smb138x_irqs[irq_index].handler,
+					IRQF_ONESHOT, irq_name, irq_data);
+	if (rc < 0) {
+		pr_err("Couldn't request irq %d\n", irq);
+		return rc;
+	}
+
+	if (smb138x_irqs[irq_index].wake)
+		enable_irq_wake(irq);
+
+	return rc;
+}
+
+static int smb138x_request_interrupts(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	struct device_node *node = chg->dev->of_node;
+	struct device_node *child;
+	int rc = 0;
+	const char *name;
+	struct property *prop;
+
+	for_each_available_child_of_node(node, child) {
+		of_property_for_each_string(child, "interrupt-names",
+					    prop, name) {
+			rc = smb138x_request_interrupt(chip, child, name);
+			if (rc < 0) {
+				pr_err("Couldn't request interrupt %s rc=%d\n",
+				       name, rc);
+				return rc;
+			}
+		}
+	}
+
+	return rc;
+}
+
+/*********
+ * PROBE *
+ *********/
+
+static int smb138x_master_probe(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	chg->param = v1_params;
+
+	rc = smblib_init(chg);
+	if (rc < 0) {
+		pr_err("Couldn't initialize smblib rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb138x_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb138x_init_vbus_regulator(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize vbus regulator rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = smb138x_init_vconn_regulator(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize vconn regulator rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = smb138x_init_usb_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize usb psy rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb138x_init_batt_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize batt psy rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb138x_init_hw(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		return rc;
+	}
+
+	rc = smb138x_determine_initial_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n",
+			rc);
+		return rc;
+	}
+
+	rc = smb138x_request_interrupts(chip);
+	if (rc < 0) {
+		pr_err("Couldn't request interrupts rc=%d\n", rc);
+		return rc;
+	}
+
+	return rc;
+}
+
+static int smb138x_slave_probe(struct smb138x *chip)
+{
+	struct smb_charger *chg = &chip->chg;
+	int rc = 0;
+
+	chg->param = v1_params;
+
+	rc = smblib_init(chg);
+	if (rc < 0) {
+		pr_err("Couldn't initialize smblib rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	chg->iio.temp_max_chan = iio_channel_get(chg->dev, "charger_temp_max");
+	if (IS_ERR(chg->iio.temp_max_chan)) {
+		rc = PTR_ERR(chg->iio.temp_max_chan);
+		goto cleanup;
+	}
+
+	chg->iio.connector_temp_thr1_chan = iio_channel_get(chg->dev,
+							"connector_temp_thr1");
+	if (IS_ERR(chg->iio.connector_temp_thr1_chan)) {
+		rc = PTR_ERR(chg->iio.connector_temp_thr1_chan);
+		goto cleanup;
+	}
+
+	chg->iio.connector_temp_thr2_chan = iio_channel_get(chg->dev,
+							"connector_temp_thr2");
+	if (IS_ERR(chg->iio.connector_temp_thr2_chan)) {
+		rc = PTR_ERR(chg->iio.connector_temp_thr2_chan);
+		goto cleanup;
+	}
+
+	chg->iio.connector_temp_thr3_chan = iio_channel_get(chg->dev,
+							"connector_temp_thr3");
+	if (IS_ERR(chg->iio.connector_temp_thr3_chan)) {
+		rc = PTR_ERR(chg->iio.connector_temp_thr3_chan);
+		goto cleanup;
+	}
+
+	rc = smb138x_parse_dt(chip);
+	if (rc < 0) {
+		pr_err("Couldn't parse device tree rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb138x_init_slave_hw(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize hardware rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb138x_init_parallel_psy(chip);
+	if (rc < 0) {
+		pr_err("Couldn't initialize parallel psy rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb138x_determine_initial_slave_status(chip);
+	if (rc < 0) {
+		pr_err("Couldn't determine initial status rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	rc = smb138x_request_interrupts(chip);
+	if (rc < 0) {
+		pr_err("Couldn't request interrupts rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	return rc;
+
+cleanup:
+	smblib_deinit(chg);
+	if (chip->parallel_psy)
+		power_supply_unregister(chip->parallel_psy);
+	return rc;
+}
+
+static const struct of_device_id match_table[] = {
+	{
+		.compatible = "qcom,smb138x-charger",
+		.data = (void *) PARALLEL_MASTER
+	},
+	{
+		.compatible = "qcom,smb138x-parallel-slave",
+		.data = (void *) PARALLEL_SLAVE
+	},
+	{ },
+};
+
+static int smb138x_probe(struct platform_device *pdev)
+{
+	struct smb138x *chip;
+	const struct of_device_id *id;
+	int rc = 0;
+
+	chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+	if (!chip)
+		return -ENOMEM;
+
+	chip->chg.dev = &pdev->dev;
+	chip->chg.debug_mask = &__debug_mask;
+	chip->chg.irq_info = smb138x_irqs;
+	chip->chg.name = "SMB";
+
+	chip->chg.regmap = dev_get_regmap(chip->chg.dev->parent, NULL);
+	if (!chip->chg.regmap) {
+		pr_err("parent regmap is missing\n");
+		return -EINVAL;
+	}
+
+	id = of_match_device(of_match_ptr(match_table), chip->chg.dev);
+	if (!id) {
+		pr_err("Couldn't find a matching device\n");
+		return -ENODEV;
+	}
+
+	platform_set_drvdata(pdev, chip);
+
+	rc = smb138x_setup_wa_flags(chip);
+	if (rc < 0) {
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't setup wa flags rc = %d\n", rc);
+		return rc;
+	}
+
+	chip->chg.mode = (enum smb_mode) id->data;
+	switch (chip->chg.mode) {
+	case PARALLEL_MASTER:
+		rc = smb138x_master_probe(chip);
+		break;
+	case PARALLEL_SLAVE:
+		rc = smb138x_slave_probe(chip);
+		break;
+	default:
+		pr_err("Couldn't find a matching mode %d\n", chip->chg.mode);
+		rc = -EINVAL;
+		goto cleanup;
+	}
+
+	if (rc < 0) {
+		if (rc != -EPROBE_DEFER)
+			pr_err("Couldn't probe SMB138X rc=%d\n", rc);
+		goto cleanup;
+	}
+
+	pr_info("SMB138X probed successfully mode=%d\n", chip->chg.mode);
+	return rc;
+
+cleanup:
+	platform_set_drvdata(pdev, NULL);
+	return rc;
+}
+
+static int smb138x_remove(struct platform_device *pdev)
+{
+	platform_set_drvdata(pdev, NULL);
+	return 0;
+}
+
+static struct platform_driver smb138x_driver = {
+	.driver	= {
+		.name		= "qcom,smb138x-charger",
+		.owner		= THIS_MODULE,
+		.of_match_table	= match_table,
+	},
+	.probe	= smb138x_probe,
+	.remove	= smb138x_remove,
+};
+module_platform_driver(smb138x_driver);
+
+MODULE_DESCRIPTION("QPNP SMB138X Charger Driver");
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/power/supply/qcom/storm-watch.c b/drivers/power/supply/qcom/storm-watch.c
new file mode 100644
index 0000000..5275079
--- /dev/null
+++ b/drivers/power/supply/qcom/storm-watch.c
@@ -0,0 +1,66 @@
+/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include "storm-watch.h"
+
+/**
+ * is_storming(): Check if an event is storming
+ *
+ * @data: Data for tracking an event storm
+ *
+ * The return value will be true if a storm has been detected and
+ * false if a storm was not detected.
+ */
+bool is_storming(struct storm_watch *data)
+{
+	ktime_t curr_kt, delta_kt;
+	bool is_storming = false;
+
+	if (!data)
+		return false;
+
+	if (!data->enabled)
+		return false;
+
+	/* max storm count must be greater than 0 */
+	if (data->max_storm_count <= 0)
+		return false;
+
+	/* the period threshold must be greater than 0ms */
+	if (data->storm_period_ms <= 0)
+		return false;
+
+	mutex_lock(&data->storm_lock);
+	curr_kt = ktime_get_boottime();
+	delta_kt = ktime_sub(curr_kt, data->last_kt);
+
+	if (ktime_to_ms(delta_kt) < data->storm_period_ms)
+		data->storm_count++;
+	else
+		data->storm_count = 0;
+
+	if (data->storm_count > data->max_storm_count) {
+		is_storming = true;
+		data->storm_count = 0;
+	}
+
+	data->last_kt = curr_kt;
+	mutex_unlock(&data->storm_lock);
+	return is_storming;
+}
+
+void reset_storm_count(struct storm_watch *data)
+{
+	mutex_lock(&data->storm_lock);
+	data->storm_count = 0;
+	mutex_unlock(&data->storm_lock);
+}
diff --git a/drivers/power/supply/qcom/storm-watch.h b/drivers/power/supply/qcom/storm-watch.h
new file mode 100644
index 0000000..ff05c4a
--- /dev/null
+++ b/drivers/power/supply/qcom/storm-watch.h
@@ -0,0 +1,40 @@
+/* Copyright (c) 2016-2017 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 __STORM_WATCH_H
+#define __STORM_WATCH_H
+#include <linux/ktime.h>
+#include <linux/mutex.h>
+
+/**
+ * Data used to track an event storm.
+ *
+ * @storm_period_ms: The maximum time interval between two events. If this limit
+ *                   is exceeded then the event chain will be broken and removed
+ *                   from consideration for a storm.
+ * @max_storm_count: The number of chained events required to trigger a storm.
+ * @storm_count:     The current number of chained events.
+ * @last_kt:         Kernel time of the last event seen.
+ * @storm_lock:      Mutex lock to protect storm_watch data.
+ */
+struct storm_watch {
+	bool		enabled;
+	int		storm_period_ms;
+	int		max_storm_count;
+	int		storm_count;
+	ktime_t		last_kt;
+	struct mutex	storm_lock;
+};
+
+bool is_storming(struct storm_watch *data);
+void reset_storm_count(struct storm_watch *data);
+#endif
diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c
index f9b6fba..a530f08 100644
--- a/drivers/scsi/libiscsi.c
+++ b/drivers/scsi/libiscsi.c
@@ -560,8 +560,12 @@
 	WARN_ON_ONCE(task->state == ISCSI_TASK_FREE);
 	task->state = state;
 
-	if (!list_empty(&task->running))
+	spin_lock_bh(&conn->taskqueuelock);
+	if (!list_empty(&task->running)) {
+		pr_debug_once("%s while task on list", __func__);
 		list_del_init(&task->running);
+	}
+	spin_unlock_bh(&conn->taskqueuelock);
 
 	if (conn->task == task)
 		conn->task = NULL;
@@ -783,7 +787,9 @@
 		if (session->tt->xmit_task(task))
 			goto free_task;
 	} else {
+		spin_lock_bh(&conn->taskqueuelock);
 		list_add_tail(&task->running, &conn->mgmtqueue);
+		spin_unlock_bh(&conn->taskqueuelock);
 		iscsi_conn_queue_work(conn);
 	}
 
@@ -1474,8 +1480,10 @@
 	 * this may be on the requeue list already if the xmit_task callout
 	 * is handling the r2ts while we are adding new ones
 	 */
+	spin_lock_bh(&conn->taskqueuelock);
 	if (list_empty(&task->running))
 		list_add_tail(&task->running, &conn->requeue);
+	spin_unlock_bh(&conn->taskqueuelock);
 	iscsi_conn_queue_work(conn);
 }
 EXPORT_SYMBOL_GPL(iscsi_requeue_task);
@@ -1512,22 +1520,26 @@
 	 * only have one nop-out as a ping from us and targets should not
 	 * overflow us with nop-ins
 	 */
+	spin_lock_bh(&conn->taskqueuelock);
 check_mgmt:
 	while (!list_empty(&conn->mgmtqueue)) {
 		conn->task = list_entry(conn->mgmtqueue.next,
 					 struct iscsi_task, running);
 		list_del_init(&conn->task->running);
+		spin_unlock_bh(&conn->taskqueuelock);
 		if (iscsi_prep_mgmt_task(conn, conn->task)) {
 			/* regular RX path uses back_lock */
 			spin_lock_bh(&conn->session->back_lock);
 			__iscsi_put_task(conn->task);
 			spin_unlock_bh(&conn->session->back_lock);
 			conn->task = NULL;
+			spin_lock_bh(&conn->taskqueuelock);
 			continue;
 		}
 		rc = iscsi_xmit_task(conn);
 		if (rc)
 			goto done;
+		spin_lock_bh(&conn->taskqueuelock);
 	}
 
 	/* process pending command queue */
@@ -1535,19 +1547,24 @@
 		conn->task = list_entry(conn->cmdqueue.next, struct iscsi_task,
 					running);
 		list_del_init(&conn->task->running);
+		spin_unlock_bh(&conn->taskqueuelock);
 		if (conn->session->state == ISCSI_STATE_LOGGING_OUT) {
 			fail_scsi_task(conn->task, DID_IMM_RETRY);
+			spin_lock_bh(&conn->taskqueuelock);
 			continue;
 		}
 		rc = iscsi_prep_scsi_cmd_pdu(conn->task);
 		if (rc) {
 			if (rc == -ENOMEM || rc == -EACCES) {
+				spin_lock_bh(&conn->taskqueuelock);
 				list_add_tail(&conn->task->running,
 					      &conn->cmdqueue);
 				conn->task = NULL;
+				spin_unlock_bh(&conn->taskqueuelock);
 				goto done;
 			} else
 				fail_scsi_task(conn->task, DID_ABORT);
+			spin_lock_bh(&conn->taskqueuelock);
 			continue;
 		}
 		rc = iscsi_xmit_task(conn);
@@ -1558,6 +1575,7 @@
 		 * we need to check the mgmt queue for nops that need to
 		 * be sent to aviod starvation
 		 */
+		spin_lock_bh(&conn->taskqueuelock);
 		if (!list_empty(&conn->mgmtqueue))
 			goto check_mgmt;
 	}
@@ -1577,12 +1595,15 @@
 		conn->task = task;
 		list_del_init(&conn->task->running);
 		conn->task->state = ISCSI_TASK_RUNNING;
+		spin_unlock_bh(&conn->taskqueuelock);
 		rc = iscsi_xmit_task(conn);
 		if (rc)
 			goto done;
+		spin_lock_bh(&conn->taskqueuelock);
 		if (!list_empty(&conn->mgmtqueue))
 			goto check_mgmt;
 	}
+	spin_unlock_bh(&conn->taskqueuelock);
 	spin_unlock_bh(&conn->session->frwd_lock);
 	return -ENODATA;
 
@@ -1738,7 +1759,9 @@
 			goto prepd_reject;
 		}
 	} else {
+		spin_lock_bh(&conn->taskqueuelock);
 		list_add_tail(&task->running, &conn->cmdqueue);
+		spin_unlock_bh(&conn->taskqueuelock);
 		iscsi_conn_queue_work(conn);
 	}
 
@@ -2897,6 +2920,7 @@
 	INIT_LIST_HEAD(&conn->mgmtqueue);
 	INIT_LIST_HEAD(&conn->cmdqueue);
 	INIT_LIST_HEAD(&conn->requeue);
+	spin_lock_init(&conn->taskqueuelock);
 	INIT_WORK(&conn->xmitwork, iscsi_xmitworker);
 
 	/* allocate login_task used for the login/text sequences */
diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c
index 734a042..f7e3f27 100644
--- a/drivers/scsi/lpfc/lpfc_init.c
+++ b/drivers/scsi/lpfc/lpfc_init.c
@@ -11393,6 +11393,7 @@
 	.id_table	= lpfc_id_table,
 	.probe		= lpfc_pci_probe_one,
 	.remove		= lpfc_pci_remove_one,
+	.shutdown	= lpfc_pci_remove_one,
 	.suspend        = lpfc_pci_suspend_one,
 	.resume		= lpfc_pci_resume_one,
 	.err_handler    = &lpfc_err_handler,
diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c
index bff9689..feab7ea 100644
--- a/drivers/scsi/qla2xxx/qla_target.c
+++ b/drivers/scsi/qla2xxx/qla_target.c
@@ -5375,16 +5375,22 @@
 
 static int
 qlt_chk_qfull_thresh_hold(struct scsi_qla_host *vha,
-	struct atio_from_isp *atio)
+	struct atio_from_isp *atio, bool ha_locked)
 {
 	struct qla_hw_data *ha = vha->hw;
 	uint16_t status;
+	unsigned long flags;
 
 	if (ha->tgt.num_pend_cmds < Q_FULL_THRESH_HOLD(ha))
 		return 0;
 
+	if (!ha_locked)
+		spin_lock_irqsave(&ha->hardware_lock, flags);
 	status = temp_sam_status;
 	qlt_send_busy(vha, atio, status);
+	if (!ha_locked)
+		spin_unlock_irqrestore(&ha->hardware_lock, flags);
+
 	return 1;
 }
 
@@ -5429,7 +5435,7 @@
 
 
 		if (likely(atio->u.isp24.fcp_cmnd.task_mgmt_flags == 0)) {
-			rc = qlt_chk_qfull_thresh_hold(vha, atio);
+			rc = qlt_chk_qfull_thresh_hold(vha, atio, ha_locked);
 			if (rc != 0) {
 				tgt->atio_irq_cmd_count--;
 				return;
@@ -5552,7 +5558,7 @@
 			break;
 		}
 
-		rc = qlt_chk_qfull_thresh_hold(vha, atio);
+		rc = qlt_chk_qfull_thresh_hold(vha, atio, true);
 		if (rc != 0) {
 			tgt->irq_cmd_count--;
 			return;
@@ -6794,6 +6800,8 @@
 	spin_lock_irqsave(&ha->hardware_lock, flags);
 	qlt_response_pkt_all_vps(vha, (response_t *)&op->atio);
 	spin_unlock_irqrestore(&ha->hardware_lock, flags);
+
+	kfree(op);
 }
 
 void
diff --git a/drivers/soc/qcom/glink_smem_native_xprt.c b/drivers/soc/qcom/glink_smem_native_xprt.c
index 5633e1f..266c0a2 100644
--- a/drivers/soc/qcom/glink_smem_native_xprt.c
+++ b/drivers/soc/qcom/glink_smem_native_xprt.c
@@ -213,6 +213,7 @@
 	bool tx_blocked_signal_sent;
 	struct kthread_work kwork;
 	struct kthread_worker kworker;
+	struct work_struct wakeup_work;
 	struct task_struct *task;
 	struct tasklet_struct tasklet;
 	struct srcu_struct use_ref;
@@ -874,20 +875,10 @@
 		srcu_read_unlock(&einfo->use_ref, rcu_id);
 		return;
 	}
-	if (!atomic_ctx) {
-		if (einfo->tx_resume_needed && fifo_write_avail(einfo)) {
-			einfo->tx_resume_needed = false;
-			einfo->xprt_if.glink_core_if_ptr->tx_resume(
-							&einfo->xprt_if);
-		}
-		spin_lock_irqsave(&einfo->write_lock, flags);
-		if (einfo->tx_blocked_signal_sent) {
-			wake_up_all(&einfo->tx_blocked_queue);
-			einfo->tx_blocked_signal_sent = false;
-		}
-		spin_unlock_irqrestore(&einfo->write_lock, flags);
-	}
 
+	if ((atomic_ctx) && ((einfo->tx_resume_needed) ||
+		(waitqueue_active(&einfo->tx_blocked_queue)))) /* tx waiting ?*/
+		schedule_work(&einfo->wakeup_work);
 
 	/*
 	 * Access to the fifo needs to be synchronized, however only the calls
@@ -1195,6 +1186,39 @@
 }
 
 /**
+ * tx_wakeup_worker() - worker function to wakeup tx blocked thread
+ * @work:	kwork associated with the edge to process commands on.
+ */
+static void tx_wakeup_worker(struct work_struct *work)
+{
+	struct edge_info *einfo;
+	bool trigger_wakeup = false;
+	unsigned long flags;
+	int rcu_id;
+
+	einfo = container_of(work, struct edge_info, wakeup_work);
+	rcu_id = srcu_read_lock(&einfo->use_ref);
+	if (einfo->in_ssr) {
+		srcu_read_unlock(&einfo->use_ref, rcu_id);
+		return;
+	}
+	if (einfo->tx_resume_needed && fifo_write_avail(einfo)) {
+		einfo->tx_resume_needed = false;
+		einfo->xprt_if.glink_core_if_ptr->tx_resume(
+						&einfo->xprt_if);
+	}
+	spin_lock_irqsave(&einfo->write_lock, flags);
+	if (waitqueue_active(&einfo->tx_blocked_queue)) { /* tx waiting ?*/
+		einfo->tx_blocked_signal_sent = false;
+		trigger_wakeup = true;
+	}
+	spin_unlock_irqrestore(&einfo->write_lock, flags);
+	if (trigger_wakeup)
+		wake_up_all(&einfo->tx_blocked_queue);
+	srcu_read_unlock(&einfo->use_ref, rcu_id);
+}
+
+/**
  * rx_worker() - worker function to process received commands
  * @work:	kwork associated with the edge to process commands on.
  */
@@ -2303,6 +2327,7 @@
 	init_waitqueue_head(&einfo->tx_blocked_queue);
 	kthread_init_work(&einfo->kwork, rx_worker);
 	kthread_init_worker(&einfo->kworker);
+	INIT_WORK(&einfo->wakeup_work, tx_wakeup_worker);
 	tasklet_init(&einfo->tasklet, rx_worker_atomic, (unsigned long)einfo);
 	einfo->read_from_fifo = read_from_fifo;
 	einfo->write_to_fifo = write_to_fifo;
@@ -2402,6 +2427,7 @@
 reg_xprt_fail:
 smem_alloc_fail:
 	kthread_flush_worker(&einfo->kworker);
+	flush_work(&einfo->wakeup_work);
 	kthread_stop(einfo->task);
 	einfo->task = NULL;
 	tasklet_kill(&einfo->tasklet);
@@ -2489,6 +2515,7 @@
 	init_waitqueue_head(&einfo->tx_blocked_queue);
 	kthread_init_work(&einfo->kwork, rx_worker);
 	kthread_init_worker(&einfo->kworker);
+	INIT_WORK(&einfo->wakeup_work, tx_wakeup_worker);
 	tasklet_init(&einfo->tasklet, rx_worker_atomic, (unsigned long)einfo);
 	einfo->intentless = true;
 	einfo->read_from_fifo = memcpy32_fromio;
@@ -2649,6 +2676,7 @@
 reg_xprt_fail:
 toc_init_fail:
 	kthread_flush_worker(&einfo->kworker);
+	flush_work(&einfo->wakeup_work);
 	kthread_stop(einfo->task);
 	einfo->task = NULL;
 	tasklet_kill(&einfo->tasklet);
@@ -2780,6 +2808,7 @@
 	init_waitqueue_head(&einfo->tx_blocked_queue);
 	kthread_init_work(&einfo->kwork, rx_worker);
 	kthread_init_worker(&einfo->kworker);
+	INIT_WORK(&einfo->wakeup_work, tx_wakeup_worker);
 	tasklet_init(&einfo->tasklet, rx_worker_atomic, (unsigned long)einfo);
 	einfo->read_from_fifo = read_from_fifo;
 	einfo->write_to_fifo = write_to_fifo;
@@ -2900,6 +2929,7 @@
 reg_xprt_fail:
 smem_alloc_fail:
 	kthread_flush_worker(&einfo->kworker);
+	flush_work(&einfo->wakeup_work);
 	kthread_stop(einfo->task);
 	einfo->task = NULL;
 	tasklet_kill(&einfo->tasklet);
diff --git a/drivers/soc/qcom/ipc_router_glink_xprt.c b/drivers/soc/qcom/ipc_router_glink_xprt.c
index 9a9d73b..458e39d 100644
--- a/drivers/soc/qcom/ipc_router_glink_xprt.c
+++ b/drivers/soc/qcom/ipc_router_glink_xprt.c
@@ -43,8 +43,14 @@
 #define MIN_FRAG_SZ (IPC_ROUTER_HDR_SIZE + sizeof(union rr_control_msg))
 #define IPC_RTR_XPRT_NAME_LEN (2 * GLINK_NAME_SIZE)
 #define PIL_SUBSYSTEM_NAME_LEN 32
-#define DEFAULT_NUM_INTENTS 5
-#define DEFAULT_RX_INTENT_SIZE 2048
+
+#define MAX_NUM_LO_INTENTS 5
+#define MAX_NUM_MD_INTENTS 3
+#define MAX_NUM_HI_INTENTS 2
+#define LO_RX_INTENT_SIZE 2048
+#define MD_RX_INTENT_SIZE 8192
+#define HI_RX_INTENT_SIZE (17 * 1024)
+
 /**
  * ipc_router_glink_xprt - IPC Router's GLINK XPRT structure
  * @list: IPC router's GLINK XPRT list.
@@ -82,6 +88,9 @@
 	unsigned int xprt_version;
 	unsigned int xprt_option;
 	bool disable_pil_loading;
+	uint32_t cur_lo_intents_cnt;
+	uint32_t cur_md_intents_cnt;
+	uint32_t cur_hi_intents_cnt;
 };
 
 struct ipc_router_glink_xprt_work {
@@ -342,7 +351,7 @@
 	}
 
 	D("%s %zu bytes @ %p\n", __func__, rx_work->iovec_size, rx_work->iovec);
-	if (rx_work->iovec_size <= DEFAULT_RX_INTENT_SIZE)
+	if (rx_work->iovec_size <= HI_RX_INTENT_SIZE)
 		reuse_intent = true;
 
 	pkt = glink_xprt_copy_data(rx_work);
@@ -371,9 +380,14 @@
 				IPC_ROUTER_XPRT_EVENT_OPEN, NULL);
 	D("%s: Notified IPC Router of %s OPEN\n",
 	  __func__, glink_xprtp->xprt.name);
-	for (i = 0; i < DEFAULT_NUM_INTENTS; i++)
+	glink_xprtp->cur_lo_intents_cnt = 0;
+	glink_xprtp->cur_md_intents_cnt = 0;
+	glink_xprtp->cur_hi_intents_cnt = 0;
+	for (i = 0; i < MAX_NUM_LO_INTENTS; i++) {
 		glink_queue_rx_intent(glink_xprtp->ch_hndl, (void *)glink_xprtp,
-				      DEFAULT_RX_INTENT_SIZE);
+				      LO_RX_INTENT_SIZE);
+		glink_xprtp->cur_lo_intents_cnt++;
+	}
 	kfree(xprt_work);
 }
 
@@ -394,13 +408,32 @@
 
 static void glink_xprt_qrx_intent_worker(struct work_struct *work)
 {
+	size_t sz;
 	struct queue_rx_intent_work *qrx_intent_work =
 		container_of(work, struct queue_rx_intent_work, work);
 	struct ipc_router_glink_xprt *glink_xprtp =
 					qrx_intent_work->glink_xprtp;
+	uint32_t *cnt = NULL;
+	int ret;
 
-	glink_queue_rx_intent(glink_xprtp->ch_hndl, (void *)glink_xprtp,
-			      qrx_intent_work->intent_size);
+	sz = qrx_intent_work->intent_size;
+	if (sz <= MD_RX_INTENT_SIZE) {
+		if (glink_xprtp->cur_md_intents_cnt >= MAX_NUM_MD_INTENTS)
+			goto qrx_intent_worker_out;
+		sz = MD_RX_INTENT_SIZE;
+		cnt = &glink_xprtp->cur_md_intents_cnt;
+	} else if (sz <= HI_RX_INTENT_SIZE) {
+		if (glink_xprtp->cur_hi_intents_cnt >= MAX_NUM_HI_INTENTS)
+			goto qrx_intent_worker_out;
+		sz = HI_RX_INTENT_SIZE;
+		cnt = &glink_xprtp->cur_hi_intents_cnt;
+	}
+
+	ret = glink_queue_rx_intent(glink_xprtp->ch_hndl, (void *)glink_xprtp,
+					sz);
+	if (!ret && cnt)
+		(*cnt)++;
+qrx_intent_worker_out:
 	kfree(qrx_intent_work);
 }
 
@@ -470,7 +503,7 @@
 	struct ipc_router_glink_xprt *glink_xprtp =
 		(struct ipc_router_glink_xprt *)priv;
 
-	if (sz <= DEFAULT_RX_INTENT_SIZE)
+	if (sz <= LO_RX_INTENT_SIZE)
 		return true;
 
 	qrx_intent_work = kmalloc(sizeof(struct queue_rx_intent_work),
diff --git a/drivers/soc/qcom/llcc-core.c b/drivers/soc/qcom/llcc-core.c
index 2c9d0a0..fc88c71 100644
--- a/drivers/soc/qcom/llcc-core.c
+++ b/drivers/soc/qcom/llcc-core.c
@@ -20,7 +20,6 @@
 #include <linux/regmap.h>
 
 /* Config registers offsets*/
-#define COMMON_CFG0		0x00030004
 #define DRP_ECC_ERROR_CFG	0x00040000
 
 /* TRP, DRP interrupt register offsets */
@@ -29,7 +28,6 @@
 #define TRP_INTERRUPT_0_ENABLE		0x00020488
 #define DRP_INTERRUPT_ENABLE		0x0004100C
 
-#define DATA_RAM_ECC_ENABLE	0x1
 #define SB_ERROR_THRESHOLD	0x1
 #define SB_ERROR_THRESHOLD_SHIFT	24
 #define SB_DB_TRP_INTERRUPT_ENABLE	0x3
@@ -50,10 +48,6 @@
 	regmap_update_bits(llcc_regmap, TRP_INTERRUPT_0_ENABLE,
 		SB_DB_TRP_INTERRUPT_ENABLE, SB_DB_TRP_INTERRUPT_ENABLE);
 
-	/* Enable ECC for for data ram */
-	regmap_update_bits(llcc_regmap, COMMON_CFG0,
-				DATA_RAM_ECC_ENABLE, DATA_RAM_ECC_ENABLE);
-
 	/* Enable SB error for Data RAM */
 	sb_err_threshold = (SB_ERROR_THRESHOLD << SB_ERROR_THRESHOLD_SHIFT);
 	regmap_write(llcc_regmap, DRP_ECC_ERROR_CFG, sb_err_threshold);
diff --git a/drivers/soc/qcom/llcc-slice.c b/drivers/soc/qcom/llcc-slice.c
index 501b902..77c2ae6 100644
--- a/drivers/soc/qcom/llcc-slice.c
+++ b/drivers/soc/qcom/llcc-slice.c
@@ -49,8 +49,6 @@
 #define LLCC_TRP_STATUSn(n)   (4 + n * 0x1000)
 #define LLCC_TRP_ATTR0_CFGn(n) (0x21000 + 0x8 * n)
 #define LLCC_TRP_ATTR1_CFGn(n) (0x21004 + 0x8 * n)
-#define LLCC_TRP_PCB_ACT       0x23204
-#define LLCC_TRP_SCID_DIS_CAP_ALLOC 0x23200
 
 /**
  * Driver data for llcc
@@ -320,8 +318,6 @@
 	u32 attr0_cfg;
 	u32 attr1_val;
 	u32 attr0_val;
-	u32 pcb = 0;
-	u32 cad = 0;
 	u32 max_cap_cacheline;
 	u32 sz;
 	const struct llcc_slice_config *llcc_table;
@@ -358,14 +354,6 @@
 		regmap_write(drv->llcc_map, attr1_cfg, attr1_val);
 		regmap_write(drv->llcc_map, attr0_cfg, attr0_val);
 
-		/* Write the retain on power collapse bit for each scid */
-		pcb |= llcc_table[i].retain_on_pc << llcc_table[i].slice_id;
-		regmap_write(drv->llcc_map, LLCC_TRP_PCB_ACT, pcb);
-
-		/* Disable capacity alloc */
-		cad |= llcc_table[i].dis_cap_alloc << llcc_table[i].slice_id;
-		regmap_write(drv->llcc_map, LLCC_TRP_SCID_DIS_CAP_ALLOC, cad);
-
 		/* Make sure that the SCT is programmed before activating */
 		mb();
 
diff --git a/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c b/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
index e2ad422..1f5cfc0 100644
--- a/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
+++ b/drivers/soc/qcom/msm_bus/msm_bus_fabric_rpmh.c
@@ -17,7 +17,6 @@
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/slab.h>
-//#include <soc/qcom/rpm-smd.h>
 #include <soc/qcom/cmd-db.h>
 #include <soc/qcom/rpmh.h>
 #include <soc/qcom/tcs.h>
@@ -36,11 +35,6 @@
 #define BCM_TCS_CMD_VOTE_Y_SHFT		0
 #define BCM_TCS_CMD_VOTE_Y_MASK		0xFFFC000
 
-#define VCD_MAX_CNT			10
-
-#define RSC_HLOS_DRV_ID			2
-#define RSC_DISP_DRV_ID			0
-
 #define BCM_TCS_CMD(commit, valid, vote_x, vote_y) \
 	(((commit & 0x1) << BCM_TCS_CMD_COMMIT_SHFT) |\
 	((valid & 0x1) << BCM_TCS_CMD_VALID_SHFT) |\
@@ -49,10 +43,8 @@
 
 static int msm_bus_dev_init_qos(struct device *dev, void *data);
 
-struct list_head bcm_clist_inorder[VCD_MAX_CNT];
-struct list_head bcm_query_list_inorder[VCD_MAX_CNT];
-static struct rpmh_client *mbox_apps;
-static struct rpmh_client *mbox_disp;
+static struct list_head bcm_query_list_inorder[VCD_MAX_CNT];
+static struct msm_bus_node_device_type *cur_rsc;
 
 struct bcm_db {
 	uint32_t unit_size;
@@ -304,7 +296,8 @@
 				int *n_sleep,
 				struct tcs_cmd *cmdlist_active,
 				struct tcs_cmd *cmdlist_wake,
-				struct tcs_cmd *cmdlist_sleep)
+				struct tcs_cmd *cmdlist_sleep,
+				struct list_head *cur_bcm_clist)
 {
 	struct msm_bus_node_device_type *cur_bcm = NULL;
 	int i = 0;
@@ -318,13 +311,13 @@
 
 	for (i = 0; i < VCD_MAX_CNT; i++) {
 		last_tcs = -1;
-		if (list_empty(&bcm_clist_inorder[i]))
+		if (list_empty(&cur_bcm_clist[i]))
 			continue;
-		list_for_each_entry(cur_bcm, &bcm_clist_inorder[i], link) {
-			if (cur_bcm->dirty) {
+		list_for_each_entry(cur_bcm, &cur_bcm_clist[i], link) {
+			if (cur_bcm->updated) {
 				if (last_tcs != -1 &&
 					list_is_last(&cur_bcm->link,
-						&bcm_clist_inorder[i])) {
+						&cur_bcm_clist[i])) {
 					cmdlist_active[last_tcs].data |=
 						BCM_TCS_CMD_COMMIT_MASK;
 					cmdlist_active[last_tcs].complete
@@ -335,7 +328,7 @@
 			n_active[idx]++;
 			commit = false;
 			if (list_is_last(&cur_bcm->link,
-						&bcm_clist_inorder[i])) {
+						&cur_bcm_clist[i])) {
 				commit = true;
 				idx++;
 			}
@@ -344,7 +337,7 @@
 				cur_bcm->node_bw[ACTIVE_CTX].max_ab, commit);
 			k++;
 			last_tcs = k;
-			cur_bcm->dirty = true;
+			cur_bcm->updated = true;
 		}
 	}
 
@@ -355,9 +348,9 @@
 	idx = 0;
 	for (i = 0; i < VCD_MAX_CNT; i++) {
 		last_tcs = -1;
-		if (list_empty(&bcm_clist_inorder[i]))
+		if (list_empty(&cur_bcm_clist[i]))
 			continue;
-		list_for_each_entry(cur_bcm, &bcm_clist_inorder[i], link) {
+		list_for_each_entry(cur_bcm, &cur_bcm_clist[i], link) {
 			commit = false;
 			if ((cur_bcm->node_bw[DUAL_CTX].max_ab ==
 				cur_bcm->node_bw[ACTIVE_CTX].max_ab) &&
@@ -365,7 +358,7 @@
 				cur_bcm->node_bw[ACTIVE_CTX].max_ib)) {
 				if (last_tcs != -1 &&
 					list_is_last(&cur_bcm->link,
-					&bcm_clist_inorder[i])) {
+					&cur_bcm_clist[i])) {
 					cmdlist_wake[k].data |=
 						BCM_TCS_CMD_COMMIT_MASK;
 					cmdlist_sleep[k].data |=
@@ -379,7 +372,7 @@
 			last_tcs = k;
 			n_sleep[idx]++;
 			if (list_is_last(&cur_bcm->link,
-						&bcm_clist_inorder[i])) {
+						&cur_bcm_clist[i])) {
 				commit = true;
 				idx++;
 			}
@@ -445,10 +438,18 @@
 	cur_bcm = to_msm_bus_node(cur_dev->node_info->bcm_devs[0]);
 	cur_vcd = cur_bcm->bcmdev->clk_domain;
 
-	if (!cur_bcm->dirty)
-		list_add_tail(&cur_bcm->link, &bcm_clist_inorder[cur_vcd]);
-	else
-		cur_bcm->dirty = false;
+	if (!cur_bcm->node_info->num_rsc_devs)
+		goto exit_bcm_clist_add;
+
+	if (!cur_rsc)
+		cur_rsc = to_msm_bus_node(cur_bcm->node_info->rsc_devs[0]);
+
+	if (!cur_bcm->dirty) {
+		list_add_tail(&cur_bcm->link,
+					&cur_rsc->rscdev->bcm_clist[cur_vcd]);
+		cur_bcm->dirty = true;
+	}
+	cur_bcm->updated = false;
 
 exit_bcm_clist_add:
 	return ret;
@@ -480,7 +481,7 @@
 	struct msm_bus_node_device_type *cur_bcm = NULL;
 
 	if (!cur_dev->node_info->num_bcm_devs)
-		goto exit_bcm_clist_add;
+		goto exit_bcm_clist_clean;
 
 	cur_bcm = to_msm_bus_node(cur_dev->node_info->bcm_devs[0]);
 
@@ -492,7 +493,7 @@
 		list_del_init(&cur_bcm->link);
 	}
 
-exit_bcm_clist_add:
+exit_bcm_clist_clean:
 	return ret;
 }
 
@@ -506,7 +507,6 @@
 
 	cur_bcm = to_msm_bus_node(cur_dev->node_info->bcm_devs[0]);
 
-	MSM_BUS_ERR("%s: removing bcm %d\n", __func__, cur_bcm->node_info->id);
 	cur_bcm->query_dirty = false;
 	list_del_init(&cur_bcm->query_link);
 
@@ -525,6 +525,7 @@
 	struct tcs_cmd *cmdlist_wake = NULL;
 	struct tcs_cmd *cmdlist_sleep = NULL;
 	struct rpmh_client *cur_mbox = NULL;
+	struct list_head *cur_bcm_clist = NULL;
 	int *n_active = NULL;
 	int *n_wake = NULL;
 	int *n_sleep = NULL;
@@ -534,6 +535,7 @@
 	int cnt_sleep = 0;
 	int i = 0;
 
+
 	list_for_each_entry_safe(node, node_tmp, clist, link) {
 		if (unlikely(node->node_info->defer_qos))
 			msm_bus_dev_init_qos(&node->dev, NULL);
@@ -541,10 +543,13 @@
 		bcm_clist_add(node);
 	}
 
+	cur_mbox = cur_rsc->rscdev->mbox;
+	cur_bcm_clist = cur_rsc->rscdev->bcm_clist;
+
 	for (i = 0; i < VCD_MAX_CNT; i++) {
-		if (list_empty(&bcm_clist_inorder[i]))
+		if (list_empty(&cur_bcm_clist[i]))
 			continue;
-		list_for_each_entry(cur_bcm, &bcm_clist_inorder[i], link) {
+		list_for_each_entry(cur_bcm, &cur_bcm_clist[i], link) {
 			if ((cur_bcm->node_bw[DUAL_CTX].max_ab !=
 				cur_bcm->node_bw[ACTIVE_CTX].max_ab) ||
 				(cur_bcm->node_bw[DUAL_CTX].max_ib !=
@@ -552,18 +557,13 @@
 				cnt_sleep++;
 				cnt_wake++;
 			}
-			if (!cur_bcm->dirty)
+			if (!cur_bcm->updated)
 				cnt_active++;
-			if (!cur_mbox) {
-				if (cur_bcm->bcmdev->drv_id == RSC_HLOS_DRV_ID)
-					cur_mbox = mbox_apps;
-				else
-					cur_mbox = mbox_disp;
-			}
 		}
 		cnt_vcd++;
 	}
 
+	MSM_BUS_ERR("%s: cmd_gen\n", __func__);
 	n_active = kcalloc(cnt_vcd+1, sizeof(int), GFP_KERNEL);
 	n_wake = kcalloc(cnt_vcd+1, sizeof(int), GFP_KERNEL);
 	n_sleep = kcalloc(cnt_vcd+1, sizeof(int), GFP_KERNEL);
@@ -577,17 +577,13 @@
 								GFP_KERNEL);
 	}
 	bcm_cnt = tcs_cmd_list_gen(n_active, n_wake, n_sleep, cmdlist_active,
-					cmdlist_wake, cmdlist_sleep);
+				cmdlist_wake, cmdlist_sleep, cur_bcm_clist);
 
 	ret = rpmh_invalidate(cur_mbox);
-
-	ret = rpmh_write_passthru(cur_mbox, RPMH_ACTIVE_ONLY_STATE,
+	ret = rpmh_write_passthru(cur_mbox, cur_rsc->rscdev->req_state,
 						cmdlist_active, n_active);
-	if (cur_mbox == mbox_apps)
-		ret = rpmh_write_passthru(cur_mbox, RPMH_WAKE_ONLY_STATE,
-						cmdlist_wake, n_wake);
-	else
-		ret = rpmh_write_passthru(cur_mbox, RPMH_AWAKE_STATE,
+
+	ret = rpmh_write_passthru(cur_mbox, RPMH_WAKE_ONLY_STATE,
 						cmdlist_wake, n_wake);
 
 	ret = rpmh_write_passthru(cur_mbox, RPMH_SLEEP_STATE,
@@ -599,6 +595,7 @@
 		list_del_init(&node->link);
 	}
 
+	cur_rsc = NULL;
 	kfree(cmdlist_active);
 	kfree(cmdlist_wake);
 	kfree(cmdlist_sleep);
@@ -950,7 +947,6 @@
 
 	// Add way to count # of VCDs, initialize LL
 	for (i = 0; i < VCD_MAX_CNT; i++) {
-		INIT_LIST_HEAD(&bcm_clist_inorder[i]);
 		INIT_LIST_HEAD(&bcm_query_list_inorder[i]);
 	}
 
@@ -958,7 +954,45 @@
 	return ret;
 }
 
+static int msm_bus_rsc_init(struct platform_device *pdev,
+			struct device *dev,
+			struct msm_bus_node_device_type *pdata)
+{
+	struct msm_bus_rsc_device_type *rscdev;
+	struct msm_bus_node_device_type *node_dev = NULL;
+	int ret = 0;
+	int i = 0;
 
+	node_dev = to_msm_bus_node(dev);
+	if (!node_dev) {
+		ret = -ENXIO;
+		goto exit_rsc_init;
+	}
+
+	rscdev = devm_kzalloc(dev, sizeof(struct msm_bus_rsc_device_type),
+								GFP_KERNEL);
+	if (!rscdev) {
+		ret = -ENOMEM;
+		goto exit_rsc_init;
+	}
+
+	node_dev->rscdev = rscdev;
+	rscdev->req_state = pdata->rscdev->req_state;
+	rscdev->mbox = rpmh_get_byname(pdev, node_dev->node_info->name);
+
+	if (IS_ERR_OR_NULL(rscdev->mbox)) {
+		MSM_BUS_ERR("%s: Failed to get mbox:%s", __func__,
+						node_dev->node_info->name);
+		return PTR_ERR(rscdev->mbox);
+	}
+
+	// Add way to count # of VCDs, initialize LL
+	for (i = 0; i < VCD_MAX_CNT; i++)
+		INIT_LIST_HEAD(&rscdev->bcm_clist[i]);
+
+exit_rsc_init:
+	return ret;
+}
 
 static int msm_bus_init_clk(struct device *bus_dev,
 				struct msm_bus_node_device_type *pdata)
@@ -1059,10 +1093,12 @@
 	node_info->num_connections = pdata_node_info->num_connections;
 	node_info->num_blist = pdata_node_info->num_blist;
 	node_info->num_bcm_devs = pdata_node_info->num_bcm_devs;
+	node_info->num_rsc_devs = pdata_node_info->num_rsc_devs;
 	node_info->num_qports = pdata_node_info->num_qports;
 	node_info->virt_dev = pdata_node_info->virt_dev;
 	node_info->is_fab_dev = pdata_node_info->is_fab_dev;
 	node_info->is_bcm_dev = pdata_node_info->is_bcm_dev;
+	node_info->is_rsc_dev = pdata_node_info->is_rsc_dev;
 	node_info->qos_params.mode = pdata_node_info->qos_params.mode;
 	node_info->qos_params.prio1 = pdata_node_info->qos_params.prio1;
 	node_info->qos_params.prio0 = pdata_node_info->qos_params.prio0;
@@ -1176,6 +1212,30 @@
 		pdata_node_info->bcm_dev_ids,
 		sizeof(int) * pdata_node_info->num_bcm_devs);
 
+	node_info->rsc_devs = devm_kzalloc(bus_dev,
+			sizeof(struct device *) *
+				pdata_node_info->num_rsc_devs,
+			GFP_KERNEL);
+	if (!node_info->rsc_devs) {
+		MSM_BUS_ERR("%s:rsc dev connections alloc failed\n", __func__);
+		ret = -ENOMEM;
+		goto exit_copy_node_info;
+	}
+
+	node_info->rsc_dev_ids = devm_kzalloc(bus_dev,
+			sizeof(int) * pdata_node_info->num_rsc_devs,
+			GFP_KERNEL);
+	if (!node_info->rsc_devs) {
+		MSM_BUS_ERR("%s:Bus connections alloc failed\n", __func__);
+		devm_kfree(bus_dev, node_info->rsc_devs);
+		ret = -ENOMEM;
+		goto exit_copy_node_info;
+	}
+
+	memcpy(node_info->rsc_dev_ids,
+		pdata_node_info->rsc_dev_ids,
+		sizeof(int) * pdata_node_info->num_rsc_devs);
+
 	node_info->qport = devm_kzalloc(bus_dev,
 			sizeof(int) * pdata_node_info->num_qports,
 			GFP_KERNEL);
@@ -1268,6 +1328,7 @@
 {
 	struct msm_bus_node_device_type *bus_node = NULL;
 	struct msm_bus_node_device_type *bcm_node = NULL;
+	struct msm_bus_node_device_type *rsc_node = NULL;
 	int ret = 0;
 	int j;
 	struct msm_bus_node_device_type *fab;
@@ -1281,7 +1342,8 @@
 
 	/* Setup parent bus device for this node */
 	if (!bus_node->node_info->is_fab_dev &&
-		!bus_node->node_info->is_bcm_dev) {
+		!bus_node->node_info->is_bcm_dev &&
+		!bus_node->node_info->is_rsc_dev) {
 		struct device *bus_parent_device =
 			bus_find_device(&msm_bus_type, NULL,
 				(void *)&bus_node->node_info->bus_device_id,
@@ -1351,6 +1413,22 @@
 		bcm_node->bcmdev->num_bus_devs++;
 	}
 
+	for (j = 0; j < bus_node->node_info->num_rsc_devs; j++) {
+		bus_node->node_info->rsc_devs[j] =
+			bus_find_device(&msm_bus_type, NULL,
+				(void *)&bus_node->node_info->rsc_dev_ids[j],
+				msm_bus_device_match_adhoc);
+
+		if (!bus_node->node_info->rsc_devs[j]) {
+			MSM_BUS_ERR("%s: Error finding conn %d for device %d",
+				__func__, bus_node->node_info->rsc_dev_ids[j],
+				 bus_node->node_info->id);
+			ret = -ENODEV;
+			goto exit_setup_dev_conn;
+		}
+		rsc_node = to_msm_bus_node(bus_node->node_info->rsc_devs[j]);
+	}
+
 exit_setup_dev_conn:
 	return ret;
 }
@@ -1461,6 +1539,14 @@
 					__func__, pdata->info[i].node_info->id);
 				goto exit_device_probe;
 			}
+		if (pdata->info[i].node_info->is_rsc_dev)
+			ret = msm_bus_rsc_init(pdev, node_dev, &pdata->info[i]);
+			if (ret) {
+				MSM_BUS_ERR("%s: Error intializing rsc %d",
+					__func__, pdata->info[i].node_info->id);
+				goto exit_device_probe;
+			}
+
 	}
 
 	ret = bus_for_each_dev(&msm_bus_type, NULL, NULL,
@@ -1481,18 +1567,6 @@
 	msm_bus_arb_setops_adhoc(&arb_ops);
 	bus_for_each_dev(&msm_bus_type, NULL, NULL, msm_bus_node_debug);
 
-	mbox_apps = rpmh_get_byname(pdev, "apps_rsc");
-	if (IS_ERR_OR_NULL(mbox_apps)) {
-		MSM_BUS_ERR("%s: apps mbox failure", __func__);
-		return PTR_ERR(mbox_apps);
-	}
-
-	mbox_disp = rpmh_get_byname(pdev, "disp_rsc");
-	if (IS_ERR_OR_NULL(mbox_disp)) {
-		MSM_BUS_ERR("%s: disp mbox failure", __func__);
-		return PTR_ERR(mbox_disp);
-	}
-
 	devm_kfree(&pdev->dev, pdata->info);
 	devm_kfree(&pdev->dev, pdata);
 exit_device_probe:
diff --git a/drivers/soc/qcom/msm_bus/msm_bus_of_rpmh.c b/drivers/soc/qcom/msm_bus/msm_bus_of_rpmh.c
index c417ebe..5710bca 100644
--- a/drivers/soc/qcom/msm_bus/msm_bus_of_rpmh.c
+++ b/drivers/soc/qcom/msm_bus/msm_bus_of_rpmh.c
@@ -90,6 +90,37 @@
 	return NULL;
 }
 
+static struct msm_bus_rsc_device_type *get_rsc_device_info(
+		struct device_node *dev_node,
+		struct platform_device *pdev)
+{
+	struct msm_bus_rsc_device_type *rsc_dev;
+	int ret;
+
+	rsc_dev = devm_kzalloc(&pdev->dev,
+			sizeof(struct msm_bus_rsc_device_type),
+			GFP_KERNEL);
+	if (!rsc_dev) {
+		dev_err(&pdev->dev,
+			"Error: Unable to allocate memory for rsc_dev\n");
+		goto rsc_dev_err;
+	}
+
+	ret = of_property_read_u32(dev_node, "qcom,req_state",
+			&rsc_dev->req_state);
+	if (ret) {
+		dev_dbg(&pdev->dev, "req_state missing, using default\n");
+		rsc_dev->req_state = 2;
+	}
+
+	return rsc_dev;
+
+rsc_dev_err:
+	devm_kfree(&pdev->dev, rsc_dev);
+	rsc_dev = 0;
+	return NULL;
+}
+
 static struct msm_bus_bcm_device_type *get_bcm_device_info(
 		struct device_node *dev_node,
 		struct platform_device *pdev)
@@ -113,11 +144,6 @@
 		goto bcm_dev_err;
 	}
 
-	ret = of_property_read_u32(dev_node, "qcom,drv-id",
-			&bcm_dev->drv_id);
-	if (ret)
-		dev_dbg(&pdev->dev, "drv-id is missing\n");
-
 	return bcm_dev;
 
 bcm_dev_err:
@@ -359,6 +385,7 @@
 	struct device_node *con_node;
 	struct device_node *bus_dev;
 	struct device_node *bcm_dev;
+	struct device_node *rsc_dev;
 
 	node_info = devm_kzalloc(&pdev->dev,
 			sizeof(struct msm_bus_node_info_type),
@@ -456,13 +483,34 @@
 					node_info->id);
 			goto node_info_err;
 		}
-		dev_err(&pdev->dev, "found bcm device. Node %d BCM:%d\n",
-				node_info->id, node_info->bcm_dev_ids[0]);
-
 		of_node_put(bcm_dev);
 	}
 
+	if (of_get_property(dev_node, "qcom,rscs", &size)) {
+		node_info->num_rsc_devs = size / sizeof(int);
+		node_info->rsc_dev_ids = devm_kzalloc(&pdev->dev, size,
+				GFP_KERNEL);
+	} else {
+		node_info->num_rsc_devs = 0;
+		node_info->rsc_devs = 0;
+	}
+
+	for (i = 0; i < node_info->num_rsc_devs; i++) {
+		rsc_dev = of_parse_phandle(dev_node, "qcom,rscs", i);
+		if (IS_ERR_OR_NULL(rsc_dev))
+			goto node_info_err;
+
+		if (of_property_read_u32(rsc_dev, "cell-id",
+				&node_info->rsc_dev_ids[i])){
+			dev_err(&pdev->dev, "Can't find rsc device. Node %d",
+					node_info->id);
+			goto node_info_err;
+		}
+		of_node_put(rsc_dev);
+	}
+
 	node_info->is_bcm_dev = of_property_read_bool(dev_node, "qcom,bcm-dev");
+	node_info->is_rsc_dev = of_property_read_bool(dev_node, "qcom,rsc-dev");
 	node_info->is_fab_dev = of_property_read_bool(dev_node, "qcom,fab-dev");
 	node_info->virt_dev = of_property_read_bool(dev_node, "qcom,virt-dev");
 
@@ -505,6 +553,18 @@
 		}
 	}
 
+	if (node_device->node_info->is_rsc_dev) {
+
+		node_device->rscdev = get_rsc_device_info(dev_node, pdev);
+
+		if (IS_ERR_OR_NULL(node_device->rscdev)) {
+			dev_err(&pdev->dev,
+				"Error: RSC device info missing\n");
+			devm_kfree(&pdev->dev, node_device->node_info);
+			return -ENODATA;
+		}
+	}
+
 	if (node_device->node_info->is_fab_dev) {
 		struct device_node *qos_clk_node;
 
diff --git a/drivers/soc/qcom/msm_bus/msm_bus_rpmh.h b/drivers/soc/qcom/msm_bus/msm_bus_rpmh.h
index 3b5eabd..f415735 100644
--- a/drivers/soc/qcom/msm_bus/msm_bus_rpmh.h
+++ b/drivers/soc/qcom/msm_bus/msm_bus_rpmh.h
@@ -20,6 +20,8 @@
 #include <linux/msm_bus_rules.h>
 #include "msm_bus_core.h"
 
+#define VCD_MAX_CNT 16
+
 struct msm_bus_node_device_type;
 
 struct link_node {
@@ -64,6 +66,12 @@
 	uint32_t vrail_used;
 };
 
+struct msm_bus_rsc_device_type {
+	struct rpmh_client *mbox;
+	struct list_head bcm_clist[VCD_MAX_CNT];
+	int req_state;
+};
+
 struct msm_bus_bcm_device_type {
 	const char *name;
 	uint32_t width;
@@ -128,16 +136,20 @@
 	unsigned int num_connections;
 	unsigned int num_blist;
 	unsigned int num_bcm_devs;
+	unsigned int num_rsc_devs;
 	bool is_fab_dev;
 	bool virt_dev;
 	bool is_bcm_dev;
+	bool is_rsc_dev;
 	bool is_traversed;
 	unsigned int *connections;
 	unsigned int *black_listed_connections;
 	unsigned int *bcm_dev_ids;
+	unsigned int *rsc_dev_ids;
 	struct device **dev_connections;
 	struct device **black_connections;
 	struct device **bcm_devs;
+	struct device **rsc_devs;
 	int bcm_req_idx;
 	unsigned int bus_device_id;
 	struct device *bus_device;
@@ -151,6 +163,7 @@
 	struct msm_bus_node_info_type *node_info;
 	struct msm_bus_fab_device_type *fabdev;
 	struct msm_bus_bcm_device_type *bcmdev;
+	struct msm_bus_rsc_device_type *rscdev;
 	int num_lnodes;
 	struct link_node *lnode_list;
 	struct nodebw node_bw[NUM_CTX];
@@ -164,6 +177,7 @@
 	struct device_node *of_node;
 	struct device dev;
 	bool dirty;
+	bool updated;
 	bool query_dirty;
 	struct list_head dev_link;
 	struct list_head devlist;
diff --git a/drivers/soc/qcom/msm_glink_pkt.c b/drivers/soc/qcom/msm_glink_pkt.c
index 38d29e4..2471d27 100644
--- a/drivers/soc/qcom/msm_glink_pkt.c
+++ b/drivers/soc/qcom/msm_glink_pkt.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2014-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2014-2017, 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
@@ -502,13 +502,21 @@
 				struct queue_rx_intent_work, work);
 	struct glink_pkt_dev *devp = work_item->devp;
 
-	if (!devp || !devp->handle) {
+	if (!devp) {
+		GLINK_PKT_ERR("%s: Invalid device\n", __func__);
+		kfree(work_item);
+		return;
+	}
+	mutex_lock(&devp->ch_lock);
+	if (!devp->handle) {
 		GLINK_PKT_ERR("%s: Invalid device Handle\n", __func__);
+		mutex_unlock(&devp->ch_lock);
 		kfree(work_item);
 		return;
 	}
 
 	ret = glink_queue_rx_intent(devp->handle, devp, work_item->intent_size);
+	mutex_unlock(&devp->ch_lock);
 	GLINK_PKT_INFO("%s: Triggered with size[%zu] ret[%d]\n",
 				__func__, work_item->intent_size, ret);
 	if (ret)
@@ -664,8 +672,15 @@
 	spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
 
 	ret = copy_to_user(buf, pkt->data, pkt->size);
-	if (WARN_ON(ret != 0))
-		return ret;
+	if (ret) {
+		GLINK_PKT_ERR(
+		"%s copy_to_user failed ret[%d] on dev id:%d size %zu\n",
+		 __func__, ret, devp->i, pkt->size);
+		spin_lock_irqsave(&devp->pkt_list_lock, flags);
+		list_add_tail(&pkt->list, &devp->pkt_list);
+		spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+		return -EFAULT;
+	}
 
 	ret = pkt->size;
 	glink_rx_done(devp->handle, pkt->data, false);
@@ -739,8 +754,13 @@
 	}
 
 	ret = copy_from_user(data, buf, count);
-	if (WARN_ON(ret != 0))
-		return ret;
+	if (ret) {
+		GLINK_PKT_ERR(
+		"%s copy_from_user failed ret[%d] on dev id:%d size %zu\n",
+		 __func__, ret, devp->i, count);
+		kfree(data);
+		return -EFAULT;
+	}
 
 	ret = glink_tx(devp->handle, data, data, count, GLINK_TX_REQ_INTENT);
 	if (ret) {
@@ -1038,6 +1058,27 @@
 }
 
 /**
+ * pop_rx_pkt() - return first pkt from rx pkt_list
+ * devp:	pointer to G-Link packet device.
+ *
+ * This function return first item from rx pkt_list and NULL if list is empty.
+ */
+struct glink_rx_pkt *pop_rx_pkt(struct glink_pkt_dev *devp)
+{
+	unsigned long flags;
+	struct glink_rx_pkt *pkt = NULL;
+
+	spin_lock_irqsave(&devp->pkt_list_lock, flags);
+	if (!list_empty(&devp->pkt_list)) {
+		pkt = list_first_entry(&devp->pkt_list,
+				struct glink_rx_pkt, list);
+		list_del(&pkt->list);
+	}
+	spin_unlock_irqrestore(&devp->pkt_list_lock, flags);
+	return pkt;
+}
+
+/**
  * glink_pkt_release() - release operation on glink_pkt device
  * inode:	Pointer to the inode structure.
  * file:	Pointer to the file structure.
@@ -1051,6 +1092,7 @@
 	int ret = 0;
 	struct glink_pkt_dev *devp = file->private_data;
 	unsigned long flags;
+	struct glink_rx_pkt *pkt;
 
 	GLINK_PKT_INFO("%s() on dev id:%d by [%s] ref_cnt[%d]\n",
 			__func__, devp->i, current->comm, devp->ref_cnt);
@@ -1059,9 +1101,14 @@
 		devp->ref_cnt--;
 
 	if (devp->handle && devp->ref_cnt == 0) {
+		while ((pkt = pop_rx_pkt(devp))) {
+			glink_rx_done(devp->handle, pkt->data, false);
+			kfree(pkt);
+		}
 		wake_up(&devp->ch_read_wait_queue);
 		wake_up_interruptible(&devp->ch_opened_wait_queue);
 		ret = glink_close(devp->handle);
+		devp->handle = NULL;
 		if (ret)  {
 			GLINK_PKT_ERR("%s: close failed ret[%d]\n",
 						__func__, ret);
diff --git a/drivers/soc/qcom/msm_smem.c b/drivers/soc/qcom/msm_smem.c
index c2fb37b..1bbd751 100644
--- a/drivers/soc/qcom/msm_smem.c
+++ b/drivers/soc/qcom/msm_smem.c
@@ -79,6 +79,7 @@
 static void *smem_ramdump_dev;
 static DEFINE_MUTEX(spinlock_init_lock);
 static DEFINE_SPINLOCK(smem_init_check_lock);
+static struct device *smem_dev;
 static int smem_module_inited;
 static RAW_NOTIFIER_HEAD(smem_module_init_notifier_list);
 static DEFINE_MUTEX(smem_module_init_notifier_lock);
@@ -374,7 +375,7 @@
 	uint32_t a_hdr_size;
 	int rc;
 
-	SMEM_DBG("%s(%u, %u, %u, %u, %d, %d)\n", __func__, id, *size, to_proc,
+	SMEM_DBG("%s(%u, %u, %u, %d, %d)\n", __func__, id, to_proc,
 					flags, skip_init_check, use_rspinlock);
 
 	if (!skip_init_check && !smem_initialized_check())
@@ -817,7 +818,7 @@
 void *smem_get_entry(unsigned int id, unsigned int *size, unsigned int to_proc,
 							unsigned int flags)
 {
-	SMEM_DBG("%s(%u, %u, %u, %u)\n", __func__, id, *size, to_proc, flags);
+	SMEM_DBG("%s(%u, %u, %u)\n", __func__, id, to_proc, flags);
 
 	/*
 	 * Handle the circular dependecy between SMEM and software implemented
@@ -1084,7 +1085,8 @@
 	void *handle;
 	struct restart_notifier_block *nb;
 
-	smem_ramdump_dev = create_ramdump_device("smem", NULL);
+	if (smem_dev)
+		smem_ramdump_dev = create_ramdump_device("smem", smem_dev);
 	if (IS_ERR_OR_NULL(smem_ramdump_dev)) {
 		LOG_ERR("%s: Unable to create smem ramdump device.\n",
 			__func__);
@@ -1509,7 +1511,7 @@
 		SMEM_INFO("smem security enabled\n");
 		smem_init_security();
 	}
-
+	smem_dev = &pdev->dev;
 	probe_done = true;
 
 	ret = of_platform_populate(pdev->dev.of_node, NULL, NULL, &pdev->dev);
diff --git a/drivers/soc/qcom/qmi_interface.c b/drivers/soc/qcom/qmi_interface.c
index 9c3f9431..0b952a4 100644
--- a/drivers/soc/qcom/qmi_interface.c
+++ b/drivers/soc/qcom/qmi_interface.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2012-2016, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2012-2017, 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
@@ -101,6 +101,7 @@
 		.ei_array	= NULL,
 	},
 };
+EXPORT_SYMBOL(qmi_response_type_v01_ei);
 
 struct elem_info qmi_error_resp_type_v01_ei[] = {
 	{
diff --git a/drivers/soc/qcom/service-locator.c b/drivers/soc/qcom/service-locator.c
index 10caf22..8e9ad9b 100644
--- a/drivers/soc/qcom/service-locator.c
+++ b/drivers/soc/qcom/service-locator.c
@@ -266,10 +266,12 @@
 		if (!domains_read) {
 			db_rev_count = pd->db_rev_count = resp->db_rev_count;
 			pd->total_domains = resp->total_domains;
-			if (!pd->total_domains && resp->domain_list_len) {
-				pr_err("total domains not set\n");
-				pd->total_domains = resp->domain_list_len;
+			if (!resp->total_domains) {
+				pr_err("No matching domains found\n");
+				rc = -EIO;
+				goto out;
 			}
+
 			pd->domain_list = kmalloc(
 					sizeof(struct servreg_loc_entry_v01) *
 					resp->total_domains, GFP_KERNEL);
@@ -286,6 +288,10 @@
 			rc = -EAGAIN;
 			goto out;
 		}
+		if (resp->domain_list_len >  resp->total_domains) {
+			/* Always read total_domains from the response msg */
+			resp->domain_list_len = resp->total_domains;
+		}
 		/* Copy the response*/
 		store_get_domain_list_response(pd, resp, domains_read);
 		domains_read += resp->domain_list_len;
diff --git a/drivers/target/target_core_pscsi.c b/drivers/target/target_core_pscsi.c
index 9125d93..ef1c8c1 100644
--- a/drivers/target/target_core_pscsi.c
+++ b/drivers/target/target_core_pscsi.c
@@ -154,7 +154,7 @@
 
 	buf = kzalloc(12, GFP_KERNEL);
 	if (!buf)
-		return;
+		goto out_free;
 
 	memset(cdb, 0, MAX_COMMAND_SIZE);
 	cdb[0] = MODE_SENSE;
@@ -169,9 +169,10 @@
 	 * If MODE_SENSE still returns zero, set the default value to 1024.
 	 */
 	sdev->sector_size = (buf[9] << 16) | (buf[10] << 8) | (buf[11]);
+out_free:
 	if (!sdev->sector_size)
 		sdev->sector_size = 1024;
-out_free:
+
 	kfree(buf);
 }
 
@@ -314,9 +315,10 @@
 				sd->lun, sd->queue_depth);
 	}
 
-	dev->dev_attrib.hw_block_size = sd->sector_size;
+	dev->dev_attrib.hw_block_size =
+		min_not_zero((int)sd->sector_size, 512);
 	dev->dev_attrib.hw_max_sectors =
-		min_t(int, sd->host->max_sectors, queue_max_hw_sectors(q));
+		min_not_zero(sd->host->max_sectors, queue_max_hw_sectors(q));
 	dev->dev_attrib.hw_queue_depth = sd->queue_depth;
 
 	/*
@@ -339,8 +341,10 @@
 	/*
 	 * For TYPE_TAPE, attempt to determine blocksize with MODE_SENSE.
 	 */
-	if (sd->type == TYPE_TAPE)
+	if (sd->type == TYPE_TAPE) {
 		pscsi_tape_read_blocksize(dev, sd);
+		dev->dev_attrib.hw_block_size = sd->sector_size;
+	}
 	return 0;
 }
 
@@ -406,7 +410,7 @@
 /*
  * Called with struct Scsi_Host->host_lock called.
  */
-static int pscsi_create_type_rom(struct se_device *dev, struct scsi_device *sd)
+static int pscsi_create_type_nondisk(struct se_device *dev, struct scsi_device *sd)
 	__releases(sh->host_lock)
 {
 	struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr;
@@ -433,28 +437,6 @@
 	return 0;
 }
 
-/*
- * Called with struct Scsi_Host->host_lock called.
- */
-static int pscsi_create_type_other(struct se_device *dev,
-		struct scsi_device *sd)
-	__releases(sh->host_lock)
-{
-	struct pscsi_hba_virt *phv = dev->se_hba->hba_ptr;
-	struct Scsi_Host *sh = sd->host;
-	int ret;
-
-	spin_unlock_irq(sh->host_lock);
-	ret = pscsi_add_device_to_list(dev, sd);
-	if (ret)
-		return ret;
-
-	pr_debug("CORE_PSCSI[%d] - Added Type: %s for %d:%d:%d:%llu\n",
-		phv->phv_host_id, scsi_device_type(sd->type), sh->host_no,
-		sd->channel, sd->id, sd->lun);
-	return 0;
-}
-
 static int pscsi_configure_device(struct se_device *dev)
 {
 	struct se_hba *hba = dev->se_hba;
@@ -542,11 +524,8 @@
 		case TYPE_DISK:
 			ret = pscsi_create_type_disk(dev, sd);
 			break;
-		case TYPE_ROM:
-			ret = pscsi_create_type_rom(dev, sd);
-			break;
 		default:
-			ret = pscsi_create_type_other(dev, sd);
+			ret = pscsi_create_type_nondisk(dev, sd);
 			break;
 		}
 
@@ -611,8 +590,7 @@
 		else if (pdv->pdv_lld_host)
 			scsi_host_put(pdv->pdv_lld_host);
 
-		if ((sd->type == TYPE_DISK) || (sd->type == TYPE_ROM))
-			scsi_device_put(sd);
+		scsi_device_put(sd);
 
 		pdv->pdv_sd = NULL;
 	}
@@ -1069,7 +1047,6 @@
 	if (pdv->pdv_bd && pdv->pdv_bd->bd_part)
 		return pdv->pdv_bd->bd_part->nr_sects;
 
-	dump_stack();
 	return 0;
 }
 
diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c
index aabd660..a53fb23 100644
--- a/drivers/target/target_core_sbc.c
+++ b/drivers/target/target_core_sbc.c
@@ -1104,9 +1104,15 @@
 			return ret;
 		break;
 	case VERIFY:
+	case VERIFY_16:
 		size = 0;
-		sectors = transport_get_sectors_10(cdb);
-		cmd->t_task_lba = transport_lba_32(cdb);
+		if (cdb[0] == VERIFY) {
+			sectors = transport_get_sectors_10(cdb);
+			cmd->t_task_lba = transport_lba_32(cdb);
+		} else {
+			sectors = transport_get_sectors_16(cdb);
+			cmd->t_task_lba = transport_lba_64(cdb);
+		}
 		cmd->execute_cmd = sbc_emulate_noop;
 		goto check_lba;
 	case REZERO_UNIT:
diff --git a/drivers/usb/dwc3/core.c b/drivers/usb/dwc3/core.c
index 8c52a13..94f65e4 100644
--- a/drivers/usb/dwc3/core.c
+++ b/drivers/usb/dwc3/core.c
@@ -323,7 +323,7 @@
 static void dwc3_free_one_event_buffer(struct dwc3 *dwc,
 		struct dwc3_event_buffer *evt)
 {
-	dma_free_coherent(dwc->dev, evt->length, evt->buf, evt->dma);
+	dma_free_coherent(dwc->sysdev, evt->length, evt->buf, evt->dma);
 }
 
 /**
@@ -345,7 +345,7 @@
 
 	evt->dwc	= dwc;
 	evt->length	= length;
-	evt->buf	= dma_alloc_coherent(dwc->dev, length,
+	evt->buf	= dma_alloc_coherent(dwc->sysdev, length,
 			&evt->dma, GFP_KERNEL);
 	if (!evt->buf)
 		return ERR_PTR(-ENOMEM);
@@ -474,11 +474,11 @@
 	if (!WARN_ON(dwc->scratchbuf))
 		return 0;
 
-	scratch_addr = dma_map_single(dwc->dev, dwc->scratchbuf,
+	scratch_addr = dma_map_single(dwc->sysdev, dwc->scratchbuf,
 			dwc->nr_scratch * DWC3_SCRATCHBUF_SIZE,
 			DMA_BIDIRECTIONAL);
-	if (dma_mapping_error(dwc->dev, scratch_addr)) {
-		dev_err(dwc->dev, "failed to map scratch buffer\n");
+	if (dma_mapping_error(dwc->sysdev, scratch_addr)) {
+		dev_err(dwc->sysdev, "failed to map scratch buffer\n");
 		ret = -EFAULT;
 		goto err0;
 	}
@@ -502,7 +502,7 @@
 	return 0;
 
 err1:
-	dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch *
+	dma_unmap_single(dwc->sysdev, dwc->scratch_addr, dwc->nr_scratch *
 			DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
 
 err0:
@@ -521,7 +521,7 @@
 	if (!WARN_ON(dwc->scratchbuf))
 		return;
 
-	dma_unmap_single(dwc->dev, dwc->scratch_addr, dwc->nr_scratch *
+	dma_unmap_single(dwc->sysdev, dwc->scratch_addr, dwc->nr_scratch *
 			DWC3_SCRATCHBUF_SIZE, DMA_BIDIRECTIONAL);
 	kfree(dwc->scratchbuf);
 }
@@ -1214,6 +1214,13 @@
 
 	dwc->hsphy_mode = of_usb_get_phy_mode(dev->of_node);
 
+	dwc->sysdev_is_parent = device_property_read_bool(dev,
+				"linux,sysdev_is_parent");
+	if (dwc->sysdev_is_parent)
+		dwc->sysdev = dwc->dev->parent;
+	else
+		dwc->sysdev = dwc->dev;
+
 	dwc->has_lpm_erratum = device_property_read_bool(dev,
 				"snps,has-lpm-erratum");
 	device_property_read_u8(dev, "snps,lpm-nyet-threshold",
@@ -1288,12 +1295,6 @@
 
 	spin_lock_init(&dwc->lock);
 
-	if (!dev->dma_mask) {
-		dev->dma_mask = dev->parent->dma_mask;
-		dev->dma_parms = dev->parent->dma_parms;
-		dma_set_coherent_mask(dev, dev->parent->coherent_dma_mask);
-	}
-
 	pm_runtime_no_callbacks(dev);
 	pm_runtime_set_active(dev);
 	pm_runtime_enable(dev);
diff --git a/drivers/usb/dwc3/core.h b/drivers/usb/dwc3/core.h
index 7ded2b2..7968901 100644
--- a/drivers/usb/dwc3/core.h
+++ b/drivers/usb/dwc3/core.h
@@ -887,6 +887,7 @@
  * @ep0_bounced: true when we used bounce buffer
  * @ep0_expect_in: true when we expect a DATA IN transfer
  * @has_hibernation: true when dwc3 was configured with Hibernation
+ * @sysdev_is_parent: true when dwc3 device has a parent driver
  * @has_lpm_erratum: true when core was configured with LPM Erratum. Note that
  *			there's now way for software to detect this in runtime.
  * @is_utmi_l1_suspend: the core asserts output signal
@@ -956,6 +957,7 @@
 	spinlock_t		lock;
 
 	struct device		*dev;
+	struct device		*sysdev;
 
 	struct platform_device	*xhci;
 	struct resource		xhci_resources[DWC3_XHCI_RESOURCES_NUM];
@@ -1062,6 +1064,7 @@
 	unsigned		ep0_bounced:1;
 	unsigned		ep0_expect_in:1;
 	unsigned		has_hibernation:1;
+	unsigned		sysdev_is_parent:1;
 	unsigned		has_lpm_erratum:1;
 	unsigned		is_utmi_l1_suspend:1;
 	unsigned		is_fpga:1;
diff --git a/drivers/usb/dwc3/dwc3-msm.c b/drivers/usb/dwc3/dwc3-msm.c
index f456f12..8d2b429 100644
--- a/drivers/usb/dwc3/dwc3-msm.c
+++ b/drivers/usb/dwc3/dwc3-msm.c
@@ -21,6 +21,8 @@
 #include <linux/pm_runtime.h>
 #include <linux/ratelimit.h>
 #include <linux/interrupt.h>
+#include <asm/dma-iommu.h>
+#include <linux/iommu.h>
 #include <linux/ioport.h>
 #include <linux/clk.h>
 #include <linux/io.h>
@@ -155,6 +157,7 @@
 	void __iomem *base;
 	void __iomem *ahb2phy_base;
 	struct platform_device	*dwc3;
+	struct dma_iommu_mapping *iommu_map;
 	const struct usb_ep_ops *original_ep_ops[DWC3_ENDPOINTS_NUM];
 	struct list_head req_complete_list;
 	struct clk		*xo_clk;
@@ -1004,7 +1007,7 @@
 	int num_trbs = (dep->direction) ? (2 * (req->num_bufs) + 2)
 					: (req->num_bufs + 1);
 
-	dep->trb_dma_pool = dma_pool_create(ep->name, dwc->dev,
+	dep->trb_dma_pool = dma_pool_create(ep->name, dwc->sysdev,
 					num_trbs * sizeof(struct dwc3_trb),
 					num_trbs * sizeof(struct dwc3_trb), 0);
 	if (!dep->trb_dma_pool) {
@@ -2091,6 +2094,9 @@
 		dev_dbg(mdwc->dev, "%s: power collapse\n", __func__);
 		dwc3_msm_config_gdsc(mdwc, 0);
 		clk_disable_unprepare(mdwc->sleep_clk);
+
+		if (mdwc->iommu_map)
+			arm_iommu_detach_device(mdwc->dev);
 	}
 
 	/* Remove bus voting */
@@ -2236,6 +2242,16 @@
 					PWR_EVNT_POWERDOWN_IN_P3_MASK, 1);
 
 		mdwc->lpm_flags &= ~MDWC3_POWER_COLLAPSE;
+
+		if (mdwc->iommu_map) {
+			ret = arm_iommu_attach_device(mdwc->dev,
+					mdwc->iommu_map);
+			if (ret)
+				dev_err(mdwc->dev, "IOMMU attach failed (%d)\n",
+						ret);
+			else
+				dev_dbg(mdwc->dev, "attached to IOMMU\n");
+		}
 	}
 
 	atomic_set(&dwc->in_lpm, 0);
@@ -2766,6 +2782,41 @@
 	return ret;
 }
 
+#define SMMU_BASE	0x10000000 /* Device address range base */
+#define SMMU_SIZE	0x40000000 /* Device address range size */
+
+static int dwc3_msm_init_iommu(struct dwc3_msm *mdwc)
+{
+	struct device_node *node = mdwc->dev->of_node;
+	int atomic_ctx = 1;
+	int ret;
+
+	if (!of_property_read_bool(node, "iommus"))
+		return 0;
+
+	mdwc->iommu_map = arm_iommu_create_mapping(&platform_bus_type,
+			SMMU_BASE, SMMU_SIZE);
+	if (IS_ERR_OR_NULL(mdwc->iommu_map)) {
+		ret = PTR_ERR(mdwc->iommu_map) ?: -ENODEV;
+		dev_err(mdwc->dev, "Failed to create IOMMU mapping (%d)\n",
+				ret);
+		return ret;
+	}
+	dev_dbg(mdwc->dev, "IOMMU mapping created: %pK\n", mdwc->iommu_map);
+
+	ret = iommu_domain_set_attr(mdwc->iommu_map->domain, DOMAIN_ATTR_ATOMIC,
+			&atomic_ctx);
+	if (ret) {
+		dev_err(mdwc->dev, "IOMMU set atomic attribute failed (%d)\n",
+			ret);
+		arm_iommu_release_mapping(mdwc->iommu_map);
+		mdwc->iommu_map = NULL;
+		return ret;
+	}
+
+	return 0;
+}
+
 static ssize_t mode_show(struct device *dev, struct device_attribute *attr,
 		char *buf)
 {
@@ -3070,12 +3121,16 @@
 
 	dwc3_set_notifier(&dwc3_msm_notify_event);
 
+	ret = dwc3_msm_init_iommu(mdwc);
+	if (ret)
+		goto err;
+
 	/* Assumes dwc3 is the first DT child of dwc3-msm */
 	dwc3_node = of_get_next_available_child(node, NULL);
 	if (!dwc3_node) {
 		dev_err(&pdev->dev, "failed to find dwc3 child\n");
 		ret = -ENODEV;
-		goto err;
+		goto uninit_iommu;
 	}
 
 	ret = of_platform_populate(node, NULL, NULL, &pdev->dev);
@@ -3083,7 +3138,7 @@
 		dev_err(&pdev->dev,
 				"failed to add create dwc3 core\n");
 		of_node_put(dwc3_node);
-		goto err;
+		goto uninit_iommu;
 	}
 
 	mdwc->dwc3 = of_find_device_by_node(dwc3_node);
@@ -3194,6 +3249,9 @@
 	platform_device_put(mdwc->dwc3);
 	if (mdwc->bus_perf_client)
 		msm_bus_scale_unregister_client(mdwc->bus_perf_client);
+uninit_iommu:
+	if (mdwc->iommu_map)
+		arm_iommu_release_mapping(mdwc->iommu_map);
 err:
 	return ret;
 }
@@ -3271,6 +3329,12 @@
 
 	dwc3_msm_config_gdsc(mdwc, 0);
 
+	if (mdwc->iommu_map) {
+		if (!atomic_read(&dwc->in_lpm))
+			arm_iommu_detach_device(mdwc->dev);
+		arm_iommu_release_mapping(mdwc->iommu_map);
+	}
+
 	return 0;
 }
 
diff --git a/drivers/usb/dwc3/ep0.c b/drivers/usb/dwc3/ep0.c
index c2b2938..bb32978 100644
--- a/drivers/usb/dwc3/ep0.c
+++ b/drivers/usb/dwc3/ep0.c
@@ -1011,8 +1011,8 @@
 		u32	transfer_size = 0;
 		u32	maxpacket;
 
-		ret = usb_gadget_map_request(&dwc->gadget, &req->request,
-				dep->number);
+		ret = usb_gadget_map_request_by_dev(dwc->sysdev,
+				&req->request, dep->number);
 		if (ret) {
 			dwc3_trace(trace_dwc3_ep0, "failed to map request");
 			return;
@@ -1040,8 +1040,8 @@
 				DWC3_TRBCTL_CONTROL_DATA, false);
 		ret = dwc3_ep0_start_trans(dwc, dep->number);
 	} else {
-		ret = usb_gadget_map_request(&dwc->gadget, &req->request,
-				dep->number);
+		ret = usb_gadget_map_request_by_dev(dwc->sysdev,
+				&req->request, dep->number);
 		if (ret) {
 			dwc3_trace(trace_dwc3_ep0, "failed to map request");
 			return;
diff --git a/drivers/usb/dwc3/gadget.c b/drivers/usb/dwc3/gadget.c
index a6014fa..264d9af 100644
--- a/drivers/usb/dwc3/gadget.c
+++ b/drivers/usb/dwc3/gadget.c
@@ -295,8 +295,8 @@
 	if (dwc->ep0_bounced && dep->number <= 1)
 		dwc->ep0_bounced = false;
 
-	usb_gadget_unmap_request(&dwc->gadget, &req->request,
-			req->direction);
+	usb_gadget_unmap_request_by_dev(dwc->sysdev,
+			&req->request, req->direction);
 
 	trace_dwc3_gadget_giveback(req);
 
@@ -476,7 +476,7 @@
 	if (dep->trb_pool)
 		return 0;
 
-	dep->trb_pool = dma_zalloc_coherent(dwc->dev,
+	dep->trb_pool = dma_zalloc_coherent(dwc->sysdev,
 			sizeof(struct dwc3_trb) * num_trbs,
 			&dep->trb_pool_dma, GFP_KERNEL);
 	if (!dep->trb_pool) {
@@ -508,7 +508,7 @@
 		dbg_event(dep->number, "Clr_TRB", 0);
 		dev_dbg(dwc->dev, "Clr_TRB ring of %s\n", dep->name);
 
-		dma_free_coherent(dwc->dev,
+		dma_free_coherent(dwc->sysdev,
 				sizeof(struct dwc3_trb) * DWC3_TRB_NUM,
 				dep->trb_pool, dep->trb_pool_dma);
 		dep->trb_pool = NULL;
@@ -1150,8 +1150,8 @@
 		 * here and stop, unmap, free and del each of the linked
 		 * requests instead of what we do now.
 		 */
-		usb_gadget_unmap_request(&dwc->gadget, &req->request,
-				req->direction);
+		usb_gadget_unmap_request_by_dev(dwc->sysdev,
+				&req->request, req->direction);
 		list_del(&req->list);
 		return ret;
 	}
@@ -1233,8 +1233,8 @@
 
 	trace_dwc3_ep_queue(req);
 
-	ret = usb_gadget_map_request(&dwc->gadget, &req->request,
-			dep->direction);
+	ret = usb_gadget_map_request_by_dev(dwc->sysdev, &req->request,
+					    dep->direction);
 	if (ret)
 		return ret;
 
@@ -3615,7 +3615,7 @@
 
 	INIT_WORK(&dwc->wakeup_work, dwc3_gadget_wakeup_work);
 
-	dwc->ctrl_req = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ctrl_req),
+	dwc->ctrl_req = dma_alloc_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
 			&dwc->ctrl_req_addr, GFP_KERNEL);
 	if (!dwc->ctrl_req) {
 		dev_err(dwc->dev, "failed to allocate ctrl request\n");
@@ -3623,8 +3623,9 @@
 		goto err0;
 	}
 
-	dwc->ep0_trb = dma_alloc_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2,
-			&dwc->ep0_trb_addr, GFP_KERNEL);
+	dwc->ep0_trb = dma_alloc_coherent(dwc->sysdev,
+					  sizeof(*dwc->ep0_trb) * 2,
+					  &dwc->ep0_trb_addr, GFP_KERNEL);
 	if (!dwc->ep0_trb) {
 		dev_err(dwc->dev, "failed to allocate ep0 trb\n");
 		ret = -ENOMEM;
@@ -3637,7 +3638,7 @@
 		goto err2;
 	}
 
-	dwc->ep0_bounce = dma_alloc_coherent(dwc->dev,
+	dwc->ep0_bounce = dma_alloc_coherent(dwc->sysdev,
 			DWC3_EP0_BOUNCE_SIZE, &dwc->ep0_bounce_addr,
 			GFP_KERNEL);
 	if (!dwc->ep0_bounce) {
@@ -3716,18 +3717,18 @@
 
 err4:
 	dwc3_gadget_free_endpoints(dwc);
-	dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE,
+	dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE,
 			dwc->ep0_bounce, dwc->ep0_bounce_addr);
 
 err3:
 	kfree(dwc->setup_buf);
 
 err2:
-	dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2,
+	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
 			dwc->ep0_trb, dwc->ep0_trb_addr);
 
 err1:
-	dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req),
+	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
 			dwc->ctrl_req, dwc->ctrl_req_addr);
 
 err0:
@@ -3747,16 +3748,16 @@
 
 	dwc3_gadget_free_endpoints(dwc);
 
-	dma_free_coherent(dwc->dev, DWC3_EP0_BOUNCE_SIZE,
+	dma_free_coherent(dwc->sysdev, DWC3_EP0_BOUNCE_SIZE,
 			dwc->ep0_bounce, dwc->ep0_bounce_addr);
 
 	kfree(dwc->setup_buf);
 	kfree(dwc->zlp_buf);
 
-	dma_free_coherent(dwc->dev, sizeof(*dwc->ep0_trb) * 2,
+	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ep0_trb) * 2,
 			dwc->ep0_trb, dwc->ep0_trb_addr);
 
-	dma_free_coherent(dwc->dev, sizeof(*dwc->ctrl_req),
+	dma_free_coherent(dwc->sysdev, sizeof(*dwc->ctrl_req),
 			dwc->ctrl_req, dwc->ctrl_req_addr);
 }
 
diff --git a/drivers/usb/dwc3/host.c b/drivers/usb/dwc3/host.c
index 800bcae..e52bf45 100644
--- a/drivers/usb/dwc3/host.c
+++ b/drivers/usb/dwc3/host.c
@@ -72,12 +72,7 @@
 		return -ENOMEM;
 	}
 
-	arch_setup_dma_ops(&xhci->dev, 0, 0, NULL, 0);
-	dma_set_coherent_mask(&xhci->dev, dwc->dev->coherent_dma_mask);
-
 	xhci->dev.parent	= dwc->dev;
-	xhci->dev.dma_mask	= dwc->dev->dma_mask;
-	xhci->dev.dma_parms	= dwc->dev->dma_parms;
 
 	dwc->xhci = xhci;
 
@@ -100,9 +95,9 @@
 	}
 
 	phy_create_lookup(dwc->usb2_generic_phy, "usb2-phy",
-			  dev_name(&xhci->dev));
+			  dev_name(dwc->dev));
 	phy_create_lookup(dwc->usb3_generic_phy, "usb3-phy",
-			  dev_name(&xhci->dev));
+			  dev_name(dwc->dev));
 
 	/* Platform device gets added as part of state machine */
 
@@ -115,9 +110,9 @@
 void dwc3_host_exit(struct dwc3 *dwc)
 {
 	phy_remove_lookup(dwc->usb2_generic_phy, "usb2-phy",
-			  dev_name(&dwc->xhci->dev));
+			  dev_name(dwc->dev));
 	phy_remove_lookup(dwc->usb3_generic_phy, "usb3-phy",
-			  dev_name(&dwc->xhci->dev));
+			  dev_name(dwc->dev));
 	if (!dwc->is_drd)
 		platform_device_unregister(dwc->xhci);
 }
diff --git a/drivers/usb/phy/phy-msm-qusb-v2.c b/drivers/usb/phy/phy-msm-qusb-v2.c
index bf155ae9..c59e33f 100644
--- a/drivers/usb/phy/phy-msm-qusb-v2.c
+++ b/drivers/usb/phy/phy-msm-qusb-v2.c
@@ -33,12 +33,8 @@
 #define QUSB2PHY_PLL_COMMON_STATUS_ONE	0x1A0
 #define CORE_READY_STATUS		BIT(0)
 
-/* In case Efuse register shows zero, use this value */
-#define TUNE2_DEFAULT_HIGH_NIBBLE	0xB
-#define TUNE2_DEFAULT_LOW_NIBBLE	0x3
-
-/* Get TUNE2's high nibble value read from efuse */
-#define TUNE2_HIGH_NIBBLE_VAL(val, pos, mask)	((val >> pos) & mask)
+/* Get TUNE value from efuse bit-mask */
+#define TUNE_VAL_MASK(val, pos, mask)	((val >> pos) & mask)
 
 #define QUSB2PHY_INTR_CTRL		0x22C
 #define DMSE_INTR_HIGH_SEL              BIT(4)
@@ -51,7 +47,8 @@
 #define DMSE_INTERRUPT			BIT(1)
 #define DPSE_INTERRUPT			BIT(0)
 
-#define QUSB2PHY_PORT_TUNE2		0x240
+#define QUSB2PHY_PORT_TUNE1		0x23c
+#define QUSB2PHY_TEST1			0x24C
 
 #define QUSB2PHY_1P8_VOL_MIN           1800000 /* uV */
 #define QUSB2PHY_1P8_VOL_MAX           1800000 /* uV */
@@ -64,14 +61,14 @@
 #define LINESTATE_DP			BIT(0)
 #define LINESTATE_DM			BIT(1)
 
-unsigned int phy_tune2;
-module_param(phy_tune2, uint, S_IRUGO | S_IWUSR);
-MODULE_PARM_DESC(phy_tune2, "QUSB PHY v2 TUNE2");
+unsigned int phy_tune1;
+module_param(phy_tune1, uint, 0644);
+MODULE_PARM_DESC(phy_tune1, "QUSB PHY v2 TUNE1");
 
 struct qusb_phy {
 	struct usb_phy		phy;
 	void __iomem		*base;
-	void __iomem		*tune2_efuse_reg;
+	void __iomem		*efuse_reg;
 
 	struct clk		*ref_clk_src;
 	struct clk		*ref_clk;
@@ -87,9 +84,9 @@
 	int			host_init_seq_len;
 	int			*qusb_phy_host_init_seq;
 
-	u32			tune2_val;
-	int			tune2_efuse_bit_pos;
-	int			tune2_efuse_num_of_bits;
+	u32			tune_val;
+	int			efuse_bit_pos;
+	int			efuse_num_of_bits;
 
 	bool			power_enabled;
 	bool			clocks_enabled;
@@ -288,40 +285,35 @@
 	return ret;
 }
 
-static void qusb_phy_get_tune2_param(struct qusb_phy *qphy)
+static void qusb_phy_get_tune1_param(struct qusb_phy *qphy)
 {
-	u8 num_of_bits;
+	u8 reg;
 	u32 bit_mask = 1;
 
 	pr_debug("%s(): num_of_bits:%d bit_pos:%d\n", __func__,
-				qphy->tune2_efuse_num_of_bits,
-				qphy->tune2_efuse_bit_pos);
+				qphy->efuse_num_of_bits,
+				qphy->efuse_bit_pos);
 
 	/* get bit mask based on number of bits to use with efuse reg */
-	if (qphy->tune2_efuse_num_of_bits) {
-		num_of_bits = qphy->tune2_efuse_num_of_bits;
-		bit_mask = (bit_mask << num_of_bits) - 1;
-	}
+	bit_mask = (bit_mask << qphy->efuse_num_of_bits) - 1;
 
 	/*
-	 * Read EFUSE register having TUNE2 parameter's high nibble.
-	 * If efuse register shows value as 0x0, then use default value
-	 * as 0xB as high nibble. Otherwise use efuse register based
-	 * value for this purpose.
+	 * if efuse reg is updated (i.e non-zero) then use it to program
+	 * tune parameters
 	 */
-	qphy->tune2_val = readl_relaxed(qphy->tune2_efuse_reg);
-	pr_debug("%s(): bit_mask:%d efuse based tune2 value:%d\n",
-				__func__, bit_mask, qphy->tune2_val);
+	qphy->tune_val = readl_relaxed(qphy->efuse_reg);
+	pr_debug("%s(): bit_mask:%d efuse based tune1 value:%d\n",
+				__func__, bit_mask, qphy->tune_val);
 
-	qphy->tune2_val = TUNE2_HIGH_NIBBLE_VAL(qphy->tune2_val,
-				qphy->tune2_efuse_bit_pos, bit_mask);
+	qphy->tune_val = TUNE_VAL_MASK(qphy->tune_val,
+				qphy->efuse_bit_pos, bit_mask);
+	reg = readb_relaxed(qphy->base + QUSB2PHY_PORT_TUNE1);
+	if (qphy->tune_val) {
+		reg = reg & 0x0f;
+		reg |= (qphy->tune_val << 4);
+	}
 
-	if (!qphy->tune2_val)
-		qphy->tune2_val = TUNE2_DEFAULT_HIGH_NIBBLE;
-
-	/* Get TUNE2 byte value using high and low nibble value */
-	qphy->tune2_val = ((qphy->tune2_val << 0x4) |
-					TUNE2_DEFAULT_LOW_NIBBLE);
+	qphy->tune_val = reg;
 }
 
 static void qusb_phy_write_seq(void __iomem *base, u32 *seq, int cnt,
@@ -427,27 +419,22 @@
 	if (qphy->qusb_phy_init_seq)
 		qusb_phy_write_seq(qphy->base, qphy->qusb_phy_init_seq,
 				qphy->init_seq_len, 0);
-	/*
-	 * Check for EFUSE value only if tune2_efuse_reg is available
-	 * and try to read EFUSE value only once i.e. not every USB
-	 * cable connect case.
-	 */
-	if (qphy->tune2_efuse_reg) {
-		if (!qphy->tune2_val)
-			qusb_phy_get_tune2_param(qphy);
+	if (qphy->efuse_reg) {
+		if (!qphy->tune_val)
+			qusb_phy_get_tune1_param(qphy);
 
-		pr_debug("%s(): Programming TUNE2 parameter as:%x\n", __func__,
-				qphy->tune2_val);
-		writel_relaxed(qphy->tune2_val,
-				qphy->base + QUSB2PHY_PORT_TUNE2);
+		pr_debug("%s(): Programming TUNE1 parameter as:%x\n", __func__,
+				qphy->tune_val);
+		writel_relaxed(qphy->tune_val,
+				qphy->base + QUSB2PHY_PORT_TUNE1);
 	}
 
-	/* If phy_tune2 modparam set, override tune2 value */
-	if (phy_tune2) {
-		pr_debug("%s(): (modparam) TUNE2 val:0x%02x\n",
-						__func__, phy_tune2);
-		writel_relaxed(phy_tune2,
-				qphy->base + QUSB2PHY_PORT_TUNE2);
+	/* If phy_tune1 modparam set, override tune1 value */
+	if (phy_tune1) {
+		pr_debug("%s(): (modparam) TUNE1 val:0x%02x\n",
+						__func__, phy_tune1);
+		writel_relaxed(phy_tune1,
+				qphy->base + QUSB2PHY_PORT_TUNE1);
 	}
 
 	/* ensure above writes are completed before re-enabling PHY */
@@ -550,6 +537,14 @@
 			writel_relaxed(intr_mask,
 				qphy->base + QUSB2PHY_INTR_CTRL);
 
+			/* enable phy auto-resume */
+			writel_relaxed(0x91,
+					qphy->base + QUSB2PHY_TEST1);
+			/* flush the previous write before next write */
+			wmb();
+			writel_relaxed(0x90,
+				qphy->base + QUSB2PHY_TEST1);
+
 			dev_dbg(phy->dev, "%s: intr_mask = %x\n",
 			__func__, intr_mask);
 
@@ -730,23 +725,23 @@
 	}
 
 	res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
-							"tune2_efuse_addr");
+							"efuse_addr");
 	if (res) {
-		qphy->tune2_efuse_reg = devm_ioremap_nocache(dev, res->start,
+		qphy->efuse_reg = devm_ioremap_nocache(dev, res->start,
 							resource_size(res));
-		if (!IS_ERR_OR_NULL(qphy->tune2_efuse_reg)) {
+		if (!IS_ERR_OR_NULL(qphy->efuse_reg)) {
 			ret = of_property_read_u32(dev->of_node,
-					"qcom,tune2-efuse-bit-pos",
-					&qphy->tune2_efuse_bit_pos);
+					"qcom,efuse-bit-pos",
+					&qphy->efuse_bit_pos);
 			if (!ret) {
 				ret = of_property_read_u32(dev->of_node,
-						"qcom,tune2-efuse-num-bits",
-						&qphy->tune2_efuse_num_of_bits);
+						"qcom,efuse-num-bits",
+						&qphy->efuse_num_of_bits);
 			}
 
 			if (ret) {
 				dev_err(dev,
-				"DT Value for tune2 efuse is invalid.\n");
+				"DT Value for efuse is invalid.\n");
 				return -EINVAL;
 			}
 		}
diff --git a/fs/ext4/super.c b/fs/ext4/super.c
index afe29ba4..5fa9ba1 100644
--- a/fs/ext4/super.c
+++ b/fs/ext4/super.c
@@ -3830,7 +3830,7 @@
 	db_count = (sbi->s_groups_count + EXT4_DESC_PER_BLOCK(sb) - 1) /
 		   EXT4_DESC_PER_BLOCK(sb);
 	if (ext4_has_feature_meta_bg(sb)) {
-		if (le32_to_cpu(es->s_first_meta_bg) >= db_count) {
+		if (le32_to_cpu(es->s_first_meta_bg) > db_count) {
 			ext4_msg(sb, KERN_WARNING,
 				 "first meta block group too large: %u "
 				 "(group descriptor block count %u)",
diff --git a/fs/gfs2/incore.h b/fs/gfs2/incore.h
index a6a3389..51519c2 100644
--- a/fs/gfs2/incore.h
+++ b/fs/gfs2/incore.h
@@ -207,7 +207,7 @@
 	struct gfs2_sbd *ln_sbd;
 	u64 ln_number;
 	unsigned int ln_type;
-};
+} __packed __aligned(sizeof(int));
 
 #define lm_name_equal(name1, name2) \
         (((name1)->ln_number == (name2)->ln_number) &&	\
diff --git a/fs/nfs/nfs4proc.c b/fs/nfs/nfs4proc.c
index 609840d..1536aeb 100644
--- a/fs/nfs/nfs4proc.c
+++ b/fs/nfs/nfs4proc.c
@@ -7426,11 +7426,11 @@
 	struct nfs41_exchange_id_data *cdata =
 					(struct nfs41_exchange_id_data *)data;
 
-	nfs_put_client(cdata->args.client);
 	if (cdata->xprt) {
 		xprt_put(cdata->xprt);
 		rpc_clnt_xprt_switch_put(cdata->args.client->cl_rpcclient);
 	}
+	nfs_put_client(cdata->args.client);
 	kfree(cdata->res.impl_id);
 	kfree(cdata->res.server_scope);
 	kfree(cdata->res.server_owner);
@@ -7537,10 +7537,8 @@
 	task_setup_data.callback_data = calldata;
 
 	task = rpc_run_task(&task_setup_data);
-	if (IS_ERR(task)) {
-	status = PTR_ERR(task);
-		goto out_impl_id;
-	}
+	if (IS_ERR(task))
+		return PTR_ERR(task);
 
 	if (!xprt) {
 		status = rpc_wait_for_completion_task(task);
@@ -7568,6 +7566,7 @@
 	kfree(calldata->res.server_owner);
 out_calldata:
 	kfree(calldata);
+	nfs_put_client(clp);
 	goto out;
 }
 
diff --git a/fs/sdcardfs/dentry.c b/fs/sdcardfs/dentry.c
index 2797d2f..afd9771 100644
--- a/fs/sdcardfs/dentry.c
+++ b/fs/sdcardfs/dentry.c
@@ -46,7 +46,8 @@
 	spin_unlock(&dentry->d_lock);
 
 	/* check uninitialized obb_dentry and
-	 * whether the base obbpath has been changed or not */
+	 * whether the base obbpath has been changed or not
+	 */
 	if (is_obbpath_invalid(dentry)) {
 		d_drop(dentry);
 		return 0;
@@ -106,12 +107,10 @@
 static void sdcardfs_d_release(struct dentry *dentry)
 {
 	/* release and reset the lower paths */
-	if(has_graft_path(dentry)) {
+	if (has_graft_path(dentry))
 		sdcardfs_put_reset_orig_path(dentry);
-	}
 	sdcardfs_put_reset_lower_path(dentry);
 	free_dentry_private_data(dentry);
-	return;
 }
 
 static int sdcardfs_hash_ci(const struct dentry *dentry,
@@ -128,12 +127,10 @@
 	unsigned long hash;
 
 	name = qstr->name;
-	//len = vfat_striptail_len(qstr);
 	len = qstr->len;
 
 	hash = init_name_hash(dentry);
 	while (len--)
-		//hash = partial_name_hash(nls_tolower(t, *name++), hash);
 		hash = partial_name_hash(tolower(*name++), hash);
 	qstr->hash = end_name_hash(hash);
 
@@ -146,20 +143,8 @@
 static int sdcardfs_cmp_ci(const struct dentry *dentry,
 		unsigned int len, const char *str, const struct qstr *name)
 {
-	/* This function is copy of vfat_cmpi */
-	// FIXME Should we support national language?
-	//struct nls_table *t = MSDOS_SB(parent->d_sb)->nls_io;
-	//unsigned int alen, blen;
+	/* FIXME Should we support national language? */
 
-	/* A filename cannot end in '.' or we treat it like it has none */
-	/*
-	alen = vfat_striptail_len(name);
-	blen = __vfat_striptail_len(len, str);
-	if (alen == blen) {
-		if (nls_strnicmp(t, name->name, str, alen) == 0)
-			return 0;
-	}
-	*/
 	if (name->len == len) {
 		if (str_n_case_eq(name->name, str, len))
 			return 0;
@@ -167,14 +152,16 @@
 	return 1;
 }
 
-static void sdcardfs_canonical_path(const struct path *path, struct path *actual_path) {
+static void sdcardfs_canonical_path(const struct path *path,
+				struct path *actual_path)
+{
 	sdcardfs_get_real_lower(path->dentry, actual_path);
 }
 
 const struct dentry_operations sdcardfs_ci_dops = {
 	.d_revalidate	= sdcardfs_d_revalidate,
 	.d_release	= sdcardfs_d_release,
-	.d_hash 	= sdcardfs_hash_ci,
+	.d_hash	= sdcardfs_hash_ci,
 	.d_compare	= sdcardfs_cmp_ci,
 	.d_canonical_path = sdcardfs_canonical_path,
 };
diff --git a/fs/sdcardfs/derived_perm.c b/fs/sdcardfs/derived_perm.c
index fc5a632..14747a8 100644
--- a/fs/sdcardfs/derived_perm.c
+++ b/fs/sdcardfs/derived_perm.c
@@ -37,7 +37,8 @@
 
 /* helper function for derived state */
 void setup_derived_state(struct inode *inode, perm_t perm, userid_t userid,
-                        uid_t uid, bool under_android, struct inode *top)
+						uid_t uid, bool under_android,
+						struct inode *top)
 {
 	struct sdcardfs_inode_info *info = SDCARDFS_I(inode);
 
@@ -50,12 +51,17 @@
 	set_top(info, top);
 }
 
-/* While renaming, there is a point where we want the path from dentry, but the name from newdentry */
-void get_derived_permission_new(struct dentry *parent, struct dentry *dentry, const struct qstr *name)
+/* While renaming, there is a point where we want the path from dentry,
+ * but the name from newdentry
+ */
+void get_derived_permission_new(struct dentry *parent, struct dentry *dentry,
+				const struct qstr *name)
 {
 	struct sdcardfs_inode_info *info = SDCARDFS_I(d_inode(dentry));
-	struct sdcardfs_inode_info *parent_info= SDCARDFS_I(d_inode(parent));
+	struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent));
 	appid_t appid;
+	unsigned long user_num;
+	int err;
 	struct qstr q_Android = QSTR_LITERAL("Android");
 	struct qstr q_data = QSTR_LITERAL("data");
 	struct qstr q_obb = QSTR_LITERAL("obb");
@@ -84,7 +90,11 @@
 	case PERM_PRE_ROOT:
 		/* Legacy internal layout places users at top level */
 		info->perm = PERM_ROOT;
-		info->userid = simple_strtoul(name->name, NULL, 10);
+		err = kstrtoul(name->name, 10, &user_num);
+		if (err)
+			info->userid = 0;
+		else
+			info->userid = user_num;
 		set_top(info, &info->vfs_inode);
 		break;
 	case PERM_ROOT:
@@ -118,9 +128,8 @@
 	case PERM_ANDROID_MEDIA:
 		info->perm = PERM_ANDROID_PACKAGE;
 		appid = get_appid(name->name);
-		if (appid != 0 && !is_excluded(name->name, parent_info->userid)) {
+		if (appid != 0 && !is_excluded(name->name, parent_info->userid))
 			info->d_uid = multiuser_get_uid(parent_info->userid, appid);
-		}
 		set_top(info, &info->vfs_inode);
 		break;
 	case PERM_ANDROID_PACKAGE:
@@ -257,7 +266,8 @@
 	return 0;
 }
 
-static int needs_fixup(perm_t perm) {
+static int needs_fixup(perm_t perm)
+{
 	if (perm == PERM_ANDROID_DATA || perm == PERM_ANDROID_OBB
 			|| perm == PERM_ANDROID_MEDIA)
 		return 1;
@@ -295,9 +305,9 @@
 			}
 			spin_unlock(&child->d_lock);
 		}
-	} else 	if (descendant_may_need_fixup(info, limit)) {
+	} else if (descendant_may_need_fixup(info, limit)) {
 		list_for_each_entry(child, &dentry->d_subdirs, d_child) {
-				__fixup_perms_recursive(child, limit, depth + 1);
+			__fixup_perms_recursive(child, limit, depth + 1);
 		}
 	}
 	spin_unlock(&dentry->d_lock);
@@ -313,19 +323,17 @@
 {
 	struct dentry *parent;
 
-	if(!dentry || !d_inode(dentry)) {
-		printk(KERN_ERR "sdcardfs: %s: invalid dentry\n", __func__);
+	if (!dentry || !d_inode(dentry)) {
+		pr_err("sdcardfs: %s: invalid dentry\n", __func__);
 		return;
 	}
 	/* FIXME:
 	 * 1. need to check whether the dentry is updated or not
 	 * 2. remove the root dentry update
 	 */
-	if(IS_ROOT(dentry)) {
-		//setup_default_pre_root_state(d_inode(dentry));
-	} else {
+	if (!IS_ROOT(dentry)) {
 		parent = dget_parent(dentry);
-		if(parent) {
+		if (parent) {
 			get_derived_permission(parent, dentry);
 			dput(parent);
 		}
@@ -337,15 +345,15 @@
 {
 	int ret = 0;
 	struct dentry *parent = dget_parent(dentry);
-	struct sdcardfs_inode_info *parent_info= SDCARDFS_I(d_inode(parent));
+	struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent));
 	struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
 	struct qstr obb = QSTR_LITERAL("obb");
 
-	if(parent_info->perm == PERM_ANDROID &&
+	if (parent_info->perm == PERM_ANDROID &&
 			qstr_case_eq(&dentry->d_name, &obb)) {
 
 		/* /Android/obb is the base obbpath of DERIVED_UNIFIED */
-		if(!(sbi->options.multiuser == false
+		if (!(sbi->options.multiuser == false
 				&& parent_info->userid == 0)) {
 			ret = 1;
 		}
@@ -365,19 +373,19 @@
 
 	/* check the base obbpath has been changed.
 	 * this routine can check an uninitialized obb dentry as well.
-	 * regarding the uninitialized obb, refer to the sdcardfs_mkdir() */
+	 * regarding the uninitialized obb, refer to the sdcardfs_mkdir()
+	 */
 	spin_lock(&di->lock);
-	if(di->orig_path.dentry) {
- 		if(!di->lower_path.dentry) {
+	if (di->orig_path.dentry) {
+		if (!di->lower_path.dentry) {
 			ret = 1;
 		} else {
 			path_get(&di->lower_path);
-			//lower_parent = lock_parent(lower_path->dentry);
 
 			path_buf = kmalloc(PATH_MAX, GFP_ATOMIC);
-			if(!path_buf) {
+			if (!path_buf) {
 				ret = 1;
-				printk(KERN_ERR "sdcardfs: fail to allocate path_buf in %s.\n", __func__);
+				pr_err("sdcardfs: fail to allocate path_buf in %s.\n", __func__);
 			} else {
 				obbpath_s = d_path(&di->lower_path, path_buf, PATH_MAX);
 				if (d_unhashed(di->lower_path.dentry) ||
@@ -387,7 +395,6 @@
 				kfree(path_buf);
 			}
 
-			//unlock_dir(lower_parent);
 			pathcpy(&lower_path, &di->lower_path);
 			need_put = 1;
 		}
@@ -402,13 +409,13 @@
 {
 	int ret = 0;
 	struct dentry *parent = dget_parent(dentry);
-	struct sdcardfs_inode_info *parent_info= SDCARDFS_I(d_inode(parent));
+	struct sdcardfs_inode_info *parent_info = SDCARDFS_I(d_inode(parent));
 	struct sdcardfs_sb_info *sbi = SDCARDFS_SB(dentry->d_sb);
 	struct qstr q_obb = QSTR_LITERAL("obb");
 
 	spin_lock(&SDCARDFS_D(dentry)->lock);
 	if (sbi->options.multiuser) {
-		if(parent_info->perm == PERM_PRE_ROOT &&
+		if (parent_info->perm == PERM_PRE_ROOT &&
 				qstr_case_eq(&dentry->d_name, &q_obb)) {
 			ret = 1;
 		}
@@ -423,7 +430,8 @@
 /* The lower_path will be stored to the dentry's orig_path
  * and the base obbpath will be copyed to the lower_path variable.
  * if an error returned, there's no change in the lower_path
- * returns: -ERRNO if error (0: no error) */
+ * returns: -ERRNO if error (0: no error)
+ */
 int setup_obb_dentry(struct dentry *dentry, struct path *lower_path)
 {
 	int err = 0;
@@ -432,13 +440,14 @@
 
 	/* A local obb dentry must have its own orig_path to support rmdir
 	 * and mkdir of itself. Usually, we expect that the sbi->obbpath
-	 * is avaiable on this stage. */
+	 * is avaiable on this stage.
+	 */
 	sdcardfs_set_orig_path(dentry, lower_path);
 
 	err = kern_path(sbi->obbpath_s,
 			LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &obbpath);
 
-	if(!err) {
+	if (!err) {
 		/* the obbpath base has been found */
 		pathcpy(lower_path, &obbpath);
 	} else {
@@ -446,8 +455,9 @@
 		 * setup the lower_path with its orig_path.
 		 * but, the current implementation just returns an error
 		 * because the sdcard daemon also regards this case as
-		 * a lookup fail. */
-		printk(KERN_INFO "sdcardfs: the sbi->obbpath is not available\n");
+		 * a lookup fail.
+		 */
+		pr_info("sdcardfs: the sbi->obbpath is not available\n");
 	}
 	return err;
 }
diff --git a/fs/sdcardfs/file.c b/fs/sdcardfs/file.c
index 0f2db26..eee4eb5 100644
--- a/fs/sdcardfs/file.c
+++ b/fs/sdcardfs/file.c
@@ -65,7 +65,7 @@
 
 	/* check disk space */
 	if (!check_min_free_space(dentry, count, 0)) {
-		printk(KERN_INFO "No minimum free space.\n");
+		pr_err("No minimum free space.\n");
 		return -ENOSPC;
 	}
 
@@ -160,8 +160,7 @@
 	lower_file = sdcardfs_lower_file(file);
 	if (willwrite && !lower_file->f_mapping->a_ops->writepage) {
 		err = -EINVAL;
-		printk(KERN_ERR "sdcardfs: lower file system does not "
-		       "support writeable mmap\n");
+		pr_err("sdcardfs: lower file system does not support writeable mmap\n");
 		goto out;
 	}
 
@@ -173,14 +172,14 @@
 	if (!SDCARDFS_F(file)->lower_vm_ops) {
 		err = lower_file->f_op->mmap(lower_file, vma);
 		if (err) {
-			printk(KERN_ERR "sdcardfs: lower mmap failed %d\n", err);
+			pr_err("sdcardfs: lower mmap failed %d\n", err);
 			goto out;
 		}
 		saved_vm_ops = vma->vm_ops; /* save: came from lower ->mmap */
 		err = do_munmap(current->mm, vma->vm_start,
 				vma->vm_end - vma->vm_start);
 		if (err) {
-			printk(KERN_ERR "sdcardfs: do_munmap failed %d\n", err);
+			pr_err("sdcardfs: do_munmap failed %d\n", err);
 			goto out;
 		}
 	}
@@ -248,9 +247,8 @@
 
 	if (err)
 		kfree(SDCARDFS_F(file));
-	else {
+	else
 		sdcardfs_copy_and_fix_attrs(inode, sdcardfs_lower_inode(inode));
-	}
 
 out_revert_cred:
 	REVERT_CRED(saved_cred);
diff --git a/fs/sdcardfs/inode.c b/fs/sdcardfs/inode.c
index 96a9f87..92afceb 100644
--- a/fs/sdcardfs/inode.c
+++ b/fs/sdcardfs/inode.c
@@ -240,14 +240,15 @@
 }
 #endif
 
-static int touch(char *abs_path, mode_t mode) {
+static int touch(char *abs_path, mode_t mode)
+{
 	struct file *filp = filp_open(abs_path, O_RDWR|O_CREAT|O_EXCL|O_NOFOLLOW, mode);
+
 	if (IS_ERR(filp)) {
 		if (PTR_ERR(filp) == -EEXIST) {
 			return 0;
-		}
-		else {
-			printk(KERN_ERR "sdcardfs: failed to open(%s): %ld\n",
+		} else {
+			pr_err("sdcardfs: failed to open(%s): %ld\n",
 						abs_path, PTR_ERR(filp));
 			return PTR_ERR(filp);
 		}
@@ -283,7 +284,7 @@
 
 	/* check disk space */
 	if (!check_min_free_space(dentry, 0, 1)) {
-		printk(KERN_INFO "sdcardfs: No minimum free space.\n");
+		pr_err("sdcardfs: No minimum free space.\n");
 		err = -ENOSPC;
 		goto out_revert;
 	}
@@ -315,19 +316,21 @@
 	}
 
 	/* if it is a local obb dentry, setup it with the base obbpath */
-	if(need_graft_path(dentry)) {
+	if (need_graft_path(dentry)) {
 
 		err = setup_obb_dentry(dentry, &lower_path);
-		if(err) {
+		if (err) {
 			/* if the sbi->obbpath is not available, the lower_path won't be
 			 * changed by setup_obb_dentry() but the lower path is saved to
 			 * its orig_path. this dentry will be revalidated later.
-			 * but now, the lower_path should be NULL */
+			 * but now, the lower_path should be NULL
+			 */
 			sdcardfs_put_reset_lower_path(dentry);
 
 			/* the newly created lower path which saved to its orig_path or
 			 * the lower_path is the base obbpath.
-			 * therefore, an additional path_get is required */
+			 * therefore, an additional path_get is required
+			 */
 			path_get(&lower_path);
 		} else
 			make_nomedia_in_obb = 1;
@@ -357,7 +360,7 @@
 		set_fs_pwd(current->fs, &lower_path);
 		touch_err = touch(".nomedia", 0664);
 		if (touch_err) {
-			printk(KERN_ERR "sdcardfs: failed to create .nomedia in %s: %d\n",
+			pr_err("sdcardfs: failed to create .nomedia in %s: %d\n",
 							lower_path.dentry->d_name.name, touch_err);
 			goto out;
 		}
@@ -391,7 +394,8 @@
 	OVERRIDE_CRED(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
 
 	/* sdcardfs_get_real_lower(): in case of remove an user's obb dentry
-	 * the dentry on the original path should be deleted. */
+	 * the dentry on the original path should be deleted.
+	 */
 	sdcardfs_get_real_lower(dentry, &lower_path);
 
 	lower_dentry = lower_path.dentry;
@@ -642,7 +646,7 @@
 	release_top(SDCARDFS_I(inode));
 	tmp.i_sb = inode->i_sb;
 	if (IS_POSIXACL(inode))
-		printk(KERN_WARNING "%s: This may be undefined behavior... \n", __func__);
+		pr_warn("%s: This may be undefined behavior...\n", __func__);
 	err = generic_permission(&tmp, mask);
 	/* XXX
 	 * Original sdcardfs code calls inode_permission(lower_inode,.. )
@@ -660,6 +664,7 @@
 		 * we check it with AID_MEDIA_RW permission
 		 */
 		struct inode *lower_inode;
+
 		OVERRIDE_CRED(SDCARDFS_SB(inode->sb));
 
 		lower_inode = sdcardfs_lower_inode(inode);
@@ -730,7 +735,8 @@
 	/* prepare our own lower struct iattr (with the lower file) */
 	memcpy(&lower_ia, ia, sizeof(lower_ia));
 	/* Allow touch updating timestamps. A previous permission check ensures
-	 * we have write access. Changes to mode, owner, and group are ignored*/
+	 * we have write access. Changes to mode, owner, and group are ignored
+	 */
 	ia->ia_valid |= ATTR_FORCE;
 	err = setattr_prepare(&tmp_d, ia);
 
@@ -816,10 +822,12 @@
 	return err;
 }
 
-static int sdcardfs_fillattr(struct vfsmount *mnt, struct inode *inode, struct kstat *stat)
+static int sdcardfs_fillattr(struct vfsmount *mnt,
+				struct inode *inode, struct kstat *stat)
 {
 	struct sdcardfs_inode_info *info = SDCARDFS_I(inode);
 	struct inode *top = grab_top(info);
+
 	if (!top)
 		return -EINVAL;
 
diff --git a/fs/sdcardfs/lookup.c b/fs/sdcardfs/lookup.c
index 7d26c26..f028bfd 100644
--- a/fs/sdcardfs/lookup.c
+++ b/fs/sdcardfs/lookup.c
@@ -36,8 +36,7 @@
 
 void sdcardfs_destroy_dentry_cache(void)
 {
-	if (sdcardfs_dentry_cachep)
-		kmem_cache_destroy(sdcardfs_dentry_cachep);
+	kmem_cache_destroy(sdcardfs_dentry_cachep);
 }
 
 void free_dentry_private_data(struct dentry *dentry)
@@ -73,6 +72,7 @@
 {
 	struct inode *current_lower_inode = sdcardfs_lower_inode(inode);
 	userid_t current_userid = SDCARDFS_I(inode)->userid;
+
 	if (current_lower_inode == ((struct inode_data *)candidate_data)->lower_inode &&
 			current_userid == ((struct inode_data *)candidate_data)->id)
 		return 1; /* found a match */
@@ -102,7 +102,7 @@
 			      * instead.
 			      */
 			     lower_inode->i_ino, /* hashval */
-			     sdcardfs_inode_test,	/* inode comparison function */
+			     sdcardfs_inode_test, /* inode comparison function */
 			     sdcardfs_inode_set, /* inode init function */
 			     &data); /* data passed to test+set fxns */
 	if (!inode) {
@@ -213,8 +213,8 @@
 	bool found;
 };
 
-static int sdcardfs_name_match(struct dir_context *ctx, const char *name, int namelen,
-		loff_t offset, u64 ino, unsigned int d_type)
+static int sdcardfs_name_match(struct dir_context *ctx, const char *name,
+		int namelen, loff_t offset, u64 ino, unsigned int d_type)
 {
 	struct sdcardfs_name_data *buf = container_of(ctx, struct sdcardfs_name_data, ctx);
 	struct qstr candidate = QSTR_INIT(name, namelen);
@@ -303,24 +303,27 @@
 	if (!err) {
 		/* check if the dentry is an obb dentry
 		 * if true, the lower_inode must be replaced with
-		 * the inode of the graft path */
+		 * the inode of the graft path
+		 */
 
-		if(need_graft_path(dentry)) {
+		if (need_graft_path(dentry)) {
 
 			/* setup_obb_dentry()
- 			 * The lower_path will be stored to the dentry's orig_path
+			 * The lower_path will be stored to the dentry's orig_path
 			 * and the base obbpath will be copyed to the lower_path variable.
 			 * if an error returned, there's no change in the lower_path
-			 * 		returns: -ERRNO if error (0: no error) */
+			 * returns: -ERRNO if error (0: no error)
+			 */
 			err = setup_obb_dentry(dentry, &lower_path);
 
-			if(err) {
+			if (err) {
 				/* if the sbi->obbpath is not available, we can optionally
 				 * setup the lower_path with its orig_path.
 				 * but, the current implementation just returns an error
 				 * because the sdcard daemon also regards this case as
-				 * a lookup fail. */
-				printk(KERN_INFO "sdcardfs: base obbpath is not available\n");
+				 * a lookup fail.
+				 */
+				pr_info("sdcardfs: base obbpath is not available\n");
 				sdcardfs_put_reset_orig_path(dentry);
 				goto out;
 			}
@@ -374,9 +377,9 @@
 
 /*
  * On success:
- * 	fills dentry object appropriate values and returns NULL.
+ * fills dentry object appropriate values and returns NULL.
  * On fail (== error)
- * 	returns error ptr
+ * returns error ptr
  *
  * @dir : Parent inode.
  * @dentry : Target dentry to lookup. we should set each of fields.
@@ -396,7 +399,7 @@
 	if (!check_caller_access_to_name(d_inode(parent), &dentry->d_name)) {
 		ret = ERR_PTR(-EACCES);
 		goto out_err;
-        }
+	}
 
 	/* save current_cred and override it */
 	OVERRIDE_CRED_PTR(SDCARDFS_SB(dir->i_sb), saved_cred, SDCARDFS_I(dir));
@@ -412,9 +415,7 @@
 
 	ret = __sdcardfs_lookup(dentry, flags, &lower_parent_path, SDCARDFS_I(dir)->userid);
 	if (IS_ERR(ret))
-	{
 		goto out;
-	}
 	if (ret)
 		dentry = ret;
 	if (d_inode(dentry)) {
diff --git a/fs/sdcardfs/main.c b/fs/sdcardfs/main.c
index 4e2aded..7344635 100644
--- a/fs/sdcardfs/main.c
+++ b/fs/sdcardfs/main.c
@@ -29,7 +29,7 @@
 	Opt_gid,
 	Opt_debug,
 	Opt_mask,
-	Opt_multiuser, // May need?
+	Opt_multiuser,
 	Opt_userid,
 	Opt_reserved_mb,
 	Opt_err,
@@ -72,6 +72,7 @@
 
 	while ((p = strsep(&options, ",")) != NULL) {
 		int token;
+
 		if (!*p)
 			continue;
 
@@ -116,19 +117,17 @@
 			break;
 		/* unknown option */
 		default:
-			if (!silent) {
-				printk( KERN_ERR "Unrecognized mount option \"%s\" "
-						"or missing value", p);
-			}
+			if (!silent)
+				pr_err("Unrecognized mount option \"%s\" or missing value", p);
 			return -EINVAL;
 		}
 	}
 
 	if (*debug) {
-		printk( KERN_INFO "sdcardfs : options - debug:%d\n", *debug);
-		printk( KERN_INFO "sdcardfs : options - uid:%d\n",
+		pr_info("sdcardfs : options - debug:%d\n", *debug);
+		pr_info("sdcardfs : options - uid:%d\n",
 							opts->fs_low_uid);
-		printk( KERN_INFO "sdcardfs : options - gid:%d\n",
+		pr_info("sdcardfs : options - gid:%d\n",
 							opts->fs_low_gid);
 	}
 
@@ -148,6 +147,7 @@
 
 	while ((p = strsep(&options, ",")) != NULL) {
 		int token;
+
 		if (!*p)
 			continue;
 
@@ -173,22 +173,20 @@
 		case Opt_fsuid:
 		case Opt_fsgid:
 		case Opt_reserved_mb:
-			printk( KERN_WARNING "Option \"%s\" can't be changed during remount\n", p);
+			pr_warn("Option \"%s\" can't be changed during remount\n", p);
 			break;
 		/* unknown option */
 		default:
-			if (!silent) {
-				printk( KERN_ERR "Unrecognized mount option \"%s\" "
-						"or missing value", p);
-			}
+			if (!silent)
+				pr_err("Unrecognized mount option \"%s\" or missing value", p);
 			return -EINVAL;
 		}
 	}
 
 	if (debug) {
-		printk( KERN_INFO "sdcardfs : options - debug:%d\n", debug);
-		printk( KERN_INFO "sdcardfs : options - gid:%d\n", vfsopts->gid);
-		printk( KERN_INFO "sdcardfs : options - mask:%d\n", vfsopts->mask);
+		pr_info("sdcardfs : options - debug:%d\n", debug);
+		pr_info("sdcardfs : options - gid:%d\n", vfsopts->gid);
+		pr_info("sdcardfs : options - mask:%d\n", vfsopts->mask);
 	}
 
 	return 0;
@@ -223,8 +221,8 @@
 #endif
 
 DEFINE_MUTEX(sdcardfs_super_list_lock);
-LIST_HEAD(sdcardfs_super_list);
 EXPORT_SYMBOL_GPL(sdcardfs_super_list_lock);
+LIST_HEAD(sdcardfs_super_list);
 EXPORT_SYMBOL_GPL(sdcardfs_super_list);
 
 /*
@@ -242,31 +240,30 @@
 	struct sdcardfs_vfsmount_options *mnt_opt = mnt->data;
 	struct inode *inode;
 
-	printk(KERN_INFO "sdcardfs version 2.0\n");
+	pr_info("sdcardfs version 2.0\n");
 
 	if (!dev_name) {
-		printk(KERN_ERR
-		       "sdcardfs: read_super: missing dev_name argument\n");
+		pr_err("sdcardfs: read_super: missing dev_name argument\n");
 		err = -EINVAL;
 		goto out;
 	}
 
-	printk(KERN_INFO "sdcardfs: dev_name -> %s\n", dev_name);
-	printk(KERN_INFO "sdcardfs: options -> %s\n", (char *)raw_data);
-	printk(KERN_INFO "sdcardfs: mnt -> %p\n", mnt);
+	pr_info("sdcardfs: dev_name -> %s\n", dev_name);
+	pr_info("sdcardfs: options -> %s\n", (char *)raw_data);
+	pr_info("sdcardfs: mnt -> %p\n", mnt);
 
 	/* parse lower path */
 	err = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
 			&lower_path);
 	if (err) {
-		printk(KERN_ERR	"sdcardfs: error accessing lower directory '%s'\n", dev_name);
+		pr_err("sdcardfs: error accessing lower directory '%s'\n", dev_name);
 		goto out;
 	}
 
 	/* allocate superblock private data */
 	sb->s_fs_info = kzalloc(sizeof(struct sdcardfs_sb_info), GFP_KERNEL);
 	if (!SDCARDFS_SB(sb)) {
-		printk(KERN_CRIT "sdcardfs: read_super: out of memory\n");
+		pr_crit("sdcardfs: read_super: out of memory\n");
 		err = -ENOMEM;
 		goto out_free;
 	}
@@ -275,7 +272,7 @@
 	/* parse options */
 	err = parse_options(sb, raw_data, silent, &debug, mnt_opt, &sb_info->options);
 	if (err) {
-		printk(KERN_ERR	"sdcardfs: invalid options\n");
+		pr_err("sdcardfs: invalid options\n");
 		goto out_freesbi;
 	}
 
@@ -328,14 +325,15 @@
 	/* setup permission policy */
 	sb_info->obbpath_s = kzalloc(PATH_MAX, GFP_KERNEL);
 	mutex_lock(&sdcardfs_super_list_lock);
-	if(sb_info->options.multiuser) {
-		setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT, sb_info->options.fs_user_id, AID_ROOT, false, d_inode(sb->s_root));
+	if (sb_info->options.multiuser) {
+		setup_derived_state(d_inode(sb->s_root), PERM_PRE_ROOT,
+					sb_info->options.fs_user_id, AID_ROOT,
+					false, d_inode(sb->s_root));
 		snprintf(sb_info->obbpath_s, PATH_MAX, "%s/obb", dev_name);
-		/*err =  prepare_dir(sb_info->obbpath_s,
-					sb_info->options.fs_low_uid,
-					sb_info->options.fs_low_gid, 00755);*/
 	} else {
-		setup_derived_state(d_inode(sb->s_root), PERM_ROOT, sb_info->options.fs_user_id, AID_ROOT, false, d_inode(sb->s_root));
+		setup_derived_state(d_inode(sb->s_root), PERM_ROOT,
+					sb_info->options.fs_user_id, AID_ROOT,
+					false, d_inode(sb->s_root));
 		snprintf(sb_info->obbpath_s, PATH_MAX, "%s/Android/obb", dev_name);
 	}
 	fixup_tmp_permissions(d_inode(sb->s_root));
@@ -344,7 +342,7 @@
 	mutex_unlock(&sdcardfs_super_list_lock);
 
 	if (!silent)
-		printk(KERN_INFO "sdcardfs: mounted on top of %s type %s\n",
+		pr_info("sdcardfs: mounted on top of %s type %s\n",
 				dev_name, lower_sb->s_type->name);
 	goto out; /* all is well */
 
@@ -368,8 +366,10 @@
 
 /* A feature which supports mount_nodev() with options */
 static struct dentry *mount_nodev_with_options(struct vfsmount *mnt,
-	struct file_system_type *fs_type, int flags, const char *dev_name, void *data,
-        int (*fill_super)(struct vfsmount *, struct super_block *, const char *, void *, int))
+			struct file_system_type *fs_type, int flags,
+			const char *dev_name, void *data,
+			int (*fill_super)(struct vfsmount *, struct super_block *,
+						const char *, void *, int))
 
 {
 	int error;
@@ -401,19 +401,22 @@
 						raw_data, sdcardfs_read_super);
 }
 
-static struct dentry *sdcardfs_mount_wrn(struct file_system_type *fs_type, int flags,
-		    const char *dev_name, void *raw_data)
+static struct dentry *sdcardfs_mount_wrn(struct file_system_type *fs_type,
+		    int flags, const char *dev_name, void *raw_data)
 {
 	WARN(1, "sdcardfs does not support mount. Use mount2.\n");
 	return ERR_PTR(-EINVAL);
 }
 
-void *sdcardfs_alloc_mnt_data(void) {
+void *sdcardfs_alloc_mnt_data(void)
+{
 	return kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL);
 }
 
-void sdcardfs_kill_sb(struct super_block *sb) {
+void sdcardfs_kill_sb(struct super_block *sb)
+{
 	struct sdcardfs_sb_info *sbi;
+
 	if (sb->s_magic == SDCARDFS_SUPER_MAGIC) {
 		sbi = SDCARDFS_SB(sb);
 		mutex_lock(&sdcardfs_super_list_lock);
diff --git a/fs/sdcardfs/packagelist.c b/fs/sdcardfs/packagelist.c
index 2cc076c..5ea6469 100644
--- a/fs/sdcardfs/packagelist.c
+++ b/fs/sdcardfs/packagelist.c
@@ -48,6 +48,7 @@
 static unsigned int full_name_case_hash(const void *salt, const unsigned char *name, unsigned int len)
 {
 	unsigned long hash = init_name_hash(salt);
+
 	while (len--)
 		hash = partial_name_hash(tolower(*name++), hash);
 	return end_name_hash(hash);
@@ -89,6 +90,7 @@
 appid_t get_appid(const char *key)
 {
 	struct qstr q;
+
 	qstr_init(&q, key);
 	return __get_appid(&q);
 }
@@ -114,6 +116,7 @@
 appid_t get_ext_gid(const char *key)
 {
 	struct qstr q;
+
 	qstr_init(&q, key);
 	return __get_ext_gid(&q);
 }
@@ -144,7 +147,8 @@
 
 /* Kernel has already enforced everything we returned through
  * derive_permissions_locked(), so this is used to lock down access
- * even further, such as enforcing that apps hold sdcard_rw. */
+ * even further, such as enforcing that apps hold sdcard_rw.
+ */
 int check_caller_access_to_name(struct inode *parent_node, const struct qstr *name)
 {
 	struct qstr q_autorun = QSTR_LITERAL("autorun.inf");
@@ -161,26 +165,26 @@
 	}
 
 	/* Root always has access; access for any other UIDs should always
-	 * be controlled through packages.list. */
-	if (from_kuid(&init_user_ns, current_fsuid()) == 0) {
+	 * be controlled through packages.list.
+	 */
+	if (from_kuid(&init_user_ns, current_fsuid()) == 0)
 		return 1;
-	}
 
 	/* No extra permissions to enforce */
 	return 1;
 }
 
 /* This function is used when file opening. The open flags must be
- * checked before calling check_caller_access_to_name() */
-int open_flags_to_access_mode(int open_flags) {
-	if((open_flags & O_ACCMODE) == O_RDONLY) {
+ * checked before calling check_caller_access_to_name()
+ */
+int open_flags_to_access_mode(int open_flags)
+{
+	if ((open_flags & O_ACCMODE) == O_RDONLY)
 		return 0; /* R_OK */
-	} else if ((open_flags & O_ACCMODE) == O_WRONLY) {
+	if ((open_flags & O_ACCMODE) == O_WRONLY)
 		return 1; /* W_OK */
-	} else {
-		/* Probably O_RDRW, but treat as default to be safe */
+	/* Probably O_RDRW, but treat as default to be safe */
 		return 1; /* R_OK | W_OK */
-	}
 }
 
 static struct hashtable_entry *alloc_hashtable_entry(const struct qstr *key,
@@ -372,7 +376,6 @@
 	remove_packagelist_entry_locked(key);
 	fixup_all_perms_name(key);
 	mutex_unlock(&sdcardfs_super_list_lock);
-	return;
 }
 
 static void remove_ext_gid_entry_locked(const struct qstr *key, gid_t group)
@@ -395,7 +398,6 @@
 	mutex_lock(&sdcardfs_super_list_lock);
 	remove_ext_gid_entry_locked(key, group);
 	mutex_unlock(&sdcardfs_super_list_lock);
-	return;
 }
 
 static void remove_userid_all_entry_locked(userid_t userid)
@@ -423,7 +425,6 @@
 	remove_userid_all_entry_locked(userid);
 	fixup_all_perms_userid(userid);
 	mutex_unlock(&sdcardfs_super_list_lock);
-	return;
 }
 
 static void remove_userid_exclude_entry_locked(const struct qstr *key, userid_t userid)
@@ -448,7 +449,6 @@
 	remove_userid_exclude_entry_locked(key, userid);
 	fixup_all_perms_name_userid(key, userid);
 	mutex_unlock(&sdcardfs_super_list_lock);
-	return;
 }
 
 static void packagelist_destroy(void)
@@ -457,6 +457,7 @@
 	struct hlist_node *h_t;
 	HLIST_HEAD(free_list);
 	int i;
+
 	mutex_lock(&sdcardfs_super_list_lock);
 	hash_for_each_rcu(package_to_appid, i, hash_cur, hlist) {
 		hash_del_rcu(&hash_cur->hlist);
@@ -470,7 +471,7 @@
 	hlist_for_each_entry_safe(hash_cur, h_t, &free_list, dlist)
 		free_hashtable_entry(hash_cur);
 	mutex_unlock(&sdcardfs_super_list_lock);
-	printk(KERN_INFO "sdcardfs: destroyed packagelist pkgld\n");
+	pr_info("sdcardfs: destroyed packagelist pkgld\n");
 }
 
 #define SDCARDFS_CONFIGFS_ATTR(_pfx, _name)			\
@@ -586,7 +587,8 @@
 static void package_details_release(struct config_item *item)
 {
 	struct package_details *package_details = to_package_details(item);
-	printk(KERN_INFO "sdcardfs: removing %s\n", package_details->name.name);
+
+	pr_info("sdcardfs: removing %s\n", package_details->name.name);
 	remove_packagelist_entry(&package_details->name);
 	kfree(package_details->name.name);
 	kfree(package_details);
@@ -604,7 +606,7 @@
 };
 
 static struct configfs_item_operations package_details_item_ops = {
-      .release = package_details_release,
+	.release = package_details_release,
 };
 
 static struct config_item_type package_appid_type = {
@@ -638,7 +640,7 @@
 {
 	struct extension_details *extension_details = to_extension_details(item);
 
-	printk(KERN_INFO "sdcardfs: No longer mapping %s files to gid %d\n",
+	pr_info("sdcardfs: No longer mapping %s files to gid %d\n",
 			extension_details->name.name, extension_details->num);
 	remove_ext_gid_entry(&extension_details->name, extension_details->num);
 	kfree(extension_details->name.name);
@@ -660,6 +662,7 @@
 	struct extension_details *extension_details = kzalloc(sizeof(struct extension_details), GFP_KERNEL);
 	const char *tmp;
 	int ret;
+
 	if (!extension_details)
 		return ERR_PTR(-ENOMEM);
 
@@ -714,7 +717,8 @@
 static void extensions_drop_group(struct config_group *group, struct config_item *item)
 {
 	struct extensions_value *value = to_extensions_value(item);
-	printk(KERN_INFO "sdcardfs: No longer mapping any files to gid %d\n", value->num);
+
+	pr_info("sdcardfs: No longer mapping any files to gid %d\n", value->num);
 	kfree(value);
 }
 
@@ -856,7 +860,7 @@
 	mutex_init(&subsys->su_mutex);
 	ret = configfs_register_subsystem(subsys);
 	if (ret) {
-		printk(KERN_ERR "Error %d while registering subsystem %s\n",
+		pr_err("Error %d while registering subsystem %s\n",
 		       ret,
 		       subsys->su_group.cg_item.ci_namebuf);
 	}
@@ -874,18 +878,17 @@
 		kmem_cache_create("packagelist_hashtable_entry",
 					sizeof(struct hashtable_entry), 0, 0, NULL);
 	if (!hashtable_entry_cachep) {
-		printk(KERN_ERR "sdcardfs: failed creating pkgl_hashtable entry slab cache\n");
+		pr_err("sdcardfs: failed creating pkgl_hashtable entry slab cache\n");
 		return -ENOMEM;
 	}
 
 	configfs_sdcardfs_init();
-        return 0;
+	return 0;
 }
 
 void packagelist_exit(void)
 {
 	configfs_sdcardfs_exit();
 	packagelist_destroy();
-	if (hashtable_entry_cachep)
-		kmem_cache_destroy(hashtable_entry_cachep);
+	kmem_cache_destroy(hashtable_entry_cachep);
 }
diff --git a/fs/sdcardfs/sdcardfs.h b/fs/sdcardfs/sdcardfs.h
index 09ec1e4..380982b 100644
--- a/fs/sdcardfs/sdcardfs.h
+++ b/fs/sdcardfs/sdcardfs.h
@@ -53,7 +53,7 @@
 #define SDCARDFS_ROOT_INO     1
 
 /* useful for tracking code reachability */
-#define UDBG printk(KERN_DEFAULT "DBG:%s:%s:%d\n", __FILE__, __func__, __LINE__)
+#define UDBG pr_default("DBG:%s:%s:%d\n", __FILE__, __func__, __LINE__)
 
 #define SDCARDFS_DIRENT_SIZE 256
 
@@ -87,11 +87,11 @@
 	} while (0)
 
 /* OVERRIDE_CRED() and REVERT_CRED()
- * 	OVERRID_CRED()
- * 		backup original task->cred
- * 		and modifies task->cred->fsuid/fsgid to specified value.
+ *	OVERRIDE_CRED()
+ *		backup original task->cred
+ *		and modifies task->cred->fsuid/fsgid to specified value.
  *	REVERT_CRED()
- * 		restore original task->cred->fsuid/fsgid.
+ *		restore original task->cred->fsuid/fsgid.
  * These two macro should be used in pair, and OVERRIDE_CRED() should be
  * placed at the beginning of a function, right after variable declaration.
  */
@@ -111,36 +111,32 @@
 
 #define REVERT_CRED(saved_cred)	revert_fsids(saved_cred)
 
-#define DEBUG_CRED()		\
-	printk("KAKJAGI: %s:%d fsuid %d fsgid %d\n", 	\
-		__FUNCTION__, __LINE__, 		\
-		(int)current->cred->fsuid, 		\
-		(int)current->cred->fsgid);
-
 /* Android 5.0 support */
 
 /* Permission mode for a specific node. Controls how file permissions
- * are derived for children nodes. */
+ * are derived for children nodes.
+ */
 typedef enum {
-    /* Nothing special; this node should just inherit from its parent. */
-    PERM_INHERIT,
-    /* This node is one level above a normal root; used for legacy layouts
-     * which use the first level to represent user_id. */
-    PERM_PRE_ROOT,
-    /* This node is "/" */
-    PERM_ROOT,
-    /* This node is "/Android" */
-    PERM_ANDROID,
-    /* This node is "/Android/data" */
-    PERM_ANDROID_DATA,
-    /* This node is "/Android/obb" */
-    PERM_ANDROID_OBB,
-    /* This node is "/Android/media" */
-    PERM_ANDROID_MEDIA,
-    /* This node is "/Android/[data|media|obb]/[package]" */
-    PERM_ANDROID_PACKAGE,
-    /* This node is "/Android/[data|media|obb]/[package]/cache" */
-    PERM_ANDROID_PACKAGE_CACHE,
+	/* Nothing special; this node should just inherit from its parent. */
+	PERM_INHERIT,
+	/* This node is one level above a normal root; used for legacy layouts
+	 * which use the first level to represent user_id.
+	 */
+	PERM_PRE_ROOT,
+	/* This node is "/" */
+	PERM_ROOT,
+	/* This node is "/Android" */
+	PERM_ANDROID,
+	/* This node is "/Android/data" */
+	PERM_ANDROID_DATA,
+	/* This node is "/Android/obb" */
+	PERM_ANDROID_OBB,
+	/* This node is "/Android/media" */
+	PERM_ANDROID_MEDIA,
+	/* This node is "/Android/[data|media|obb]/[package]" */
+	PERM_ANDROID_PACKAGE,
+	/* This node is "/Android/[data|media|obb]/[package]/cache" */
+	PERM_ANDROID_PACKAGE_CACHE,
 } perm_t;
 
 struct sdcardfs_sb_info;
@@ -150,7 +146,7 @@
 /* Do not directly use this function. Use OVERRIDE_CRED() instead. */
 const struct cred *override_fsids(struct sdcardfs_sb_info *sbi, struct sdcardfs_inode_info *info);
 /* Do not directly use this function, use REVERT_CRED() instead. */
-void revert_fsids(const struct cred * old_cred);
+void revert_fsids(const struct cred *old_cred);
 
 /* operations vectors defined in specific files */
 extern const struct file_operations sdcardfs_main_fops;
@@ -227,7 +223,8 @@
 	struct super_block *sb;
 	struct super_block *lower_sb;
 	/* derived perm policy : some of options have been added
-	 * to sdcardfs_mount_options (Android 4.4 support) */
+	 * to sdcardfs_mount_options (Android 4.4 support)
+	 */
 	struct sdcardfs_mount_options options;
 	spinlock_t lock;	/* protects obbpath */
 	char *obbpath_s;
@@ -338,7 +335,7 @@
 { \
 	struct path pname; \
 	spin_lock(&SDCARDFS_D(dent)->lock); \
-	if(SDCARDFS_D(dent)->pname.dentry) { \
+	if (SDCARDFS_D(dent)->pname.dentry) { \
 		pathcpy(&pname, &SDCARDFS_D(dent)->pname); \
 		SDCARDFS_D(dent)->pname.dentry = NULL; \
 		SDCARDFS_D(dent)->pname.mnt = NULL; \
@@ -354,17 +351,17 @@
 
 static inline bool sbinfo_has_sdcard_magic(struct sdcardfs_sb_info *sbinfo)
 {
-  return sbinfo && sbinfo->sb && sbinfo->sb->s_magic == SDCARDFS_SUPER_MAGIC;
+	return sbinfo && sbinfo->sb && sbinfo->sb->s_magic == SDCARDFS_SUPER_MAGIC;
 }
 
 /* grab a refererence if we aren't linking to ourself */
 static inline void set_top(struct sdcardfs_inode_info *info, struct inode *top)
 {
 	struct inode *old_top = NULL;
+
 	BUG_ON(IS_ERR_OR_NULL(top));
-	if (info->top && info->top != &info->vfs_inode) {
+	if (info->top && info->top != &info->vfs_inode)
 		old_top = info->top;
-	}
 	if (top != &info->vfs_inode)
 		igrab(top);
 	info->top = top;
@@ -374,11 +371,11 @@
 static inline struct inode *grab_top(struct sdcardfs_inode_info *info)
 {
 	struct inode *top = info->top;
-	if (top) {
+
+	if (top)
 		return igrab(top);
-	} else {
+	else
 		return NULL;
-	}
 }
 
 static inline void release_top(struct sdcardfs_inode_info *info)
@@ -386,21 +383,24 @@
 	iput(info->top);
 }
 
-static inline int get_gid(struct vfsmount *mnt, struct sdcardfs_inode_info *info) {
+static inline int get_gid(struct vfsmount *mnt, struct sdcardfs_inode_info *info)
+{
 	struct sdcardfs_vfsmount_options *opts = mnt->data;
 
-	if (opts->gid == AID_SDCARD_RW) {
+	if (opts->gid == AID_SDCARD_RW)
 		/* As an optimization, certain trusted system components only run
 		 * as owner but operate across all users. Since we're now handing
 		 * out the sdcard_rw GID only to trusted apps, we're okay relaxing
 		 * the user boundary enforcement for the default view. The UIDs
-		 * assigned to app directories are still multiuser aware. */
+		 * assigned to app directories are still multiuser aware.
+		 */
 		return AID_SDCARD_RW;
-	} else {
+	else
 		return multiuser_get_uid(info->userid, opts->gid);
-	}
 }
-static inline int get_mode(struct vfsmount *mnt, struct sdcardfs_inode_info *info) {
+
+static inline int get_mode(struct vfsmount *mnt, struct sdcardfs_inode_info *info)
+{
 	int owner_mode;
 	int filtered_mode;
 	struct sdcardfs_vfsmount_options *opts = mnt->data;
@@ -409,17 +409,18 @@
 
 	if (info->perm == PERM_PRE_ROOT) {
 		/* Top of multi-user view should always be visible to ensure
-		* secondary users can traverse inside. */
+		* secondary users can traverse inside.
+		*/
 		visible_mode = 0711;
 	} else if (info->under_android) {
 		/* Block "other" access to Android directories, since only apps
 		* belonging to a specific user should be in there; we still
-		* leave +x open for the default view. */
-		if (opts->gid == AID_SDCARD_RW) {
+		* leave +x open for the default view.
+		*/
+		if (opts->gid == AID_SDCARD_RW)
 			visible_mode = visible_mode & ~0006;
-		} else {
+		else
 			visible_mode = visible_mode & ~0007;
-		}
 	}
 	owner_mode = info->lower_inode->i_mode & 0700;
 	filtered_mode = visible_mode & (owner_mode | (owner_mode >> 3) | (owner_mode >> 6));
@@ -444,7 +445,7 @@
 	/* in case of a local obb dentry
 	 * the orig_path should be returned
 	 */
-	if(has_graft_path(dent))
+	if (has_graft_path(dent))
 		sdcardfs_get_orig_path(dent, real_lower);
 	else
 		sdcardfs_get_lower_path(dent, real_lower);
@@ -453,7 +454,7 @@
 static inline void sdcardfs_put_real_lower(const struct dentry *dent,
 						struct path *real_lower)
 {
-	if(has_graft_path(dent))
+	if (has_graft_path(dent))
 		sdcardfs_put_orig_path(dent, real_lower);
 	else
 		sdcardfs_put_lower_path(dent, real_lower);
@@ -497,6 +498,7 @@
 static inline struct dentry *lock_parent(struct dentry *dentry)
 {
 	struct dentry *dir = dget_parent(dentry);
+
 	inode_lock_nested(d_inode(dir), I_MUTEX_PARENT);
 	return dir;
 }
diff --git a/fs/sdcardfs/super.c b/fs/sdcardfs/super.c
index edda32b..a3393e9 100644
--- a/fs/sdcardfs/super.c
+++ b/fs/sdcardfs/super.c
@@ -36,7 +36,7 @@
 	if (!spd)
 		return;
 
-	if(spd->obbpath_s) {
+	if (spd->obbpath_s) {
 		kfree(spd->obbpath_s);
 		path_put(&spd->obbpath);
 	}
@@ -64,7 +64,7 @@
 	if (sbi->options.reserved_mb) {
 		/* Invalid statfs informations. */
 		if (buf->f_bsize == 0) {
-			printk(KERN_ERR "Returned block size is zero.\n");
+			pr_err("Returned block size is zero.\n");
 			return -EINVAL;
 		}
 
@@ -100,8 +100,7 @@
 	 * SILENT, but anything else left over is an error.
 	 */
 	if ((*flags & ~(MS_RDONLY | MS_MANDLOCK | MS_SILENT)) != 0) {
-		printk(KERN_ERR
-		       "sdcardfs: remount flags 0x%x unsupported\n", *flags);
+		pr_err("sdcardfs: remount flags 0x%x unsupported\n", *flags);
 		err = -EINVAL;
 	}
 
@@ -125,29 +124,33 @@
 	 * SILENT, but anything else left over is an error.
 	 */
 	if ((*flags & ~(MS_RDONLY | MS_MANDLOCK | MS_SILENT | MS_REMOUNT)) != 0) {
-		printk(KERN_ERR
-		       "sdcardfs: remount flags 0x%x unsupported\n", *flags);
+		pr_err("sdcardfs: remount flags 0x%x unsupported\n", *flags);
 		err = -EINVAL;
 	}
-	printk(KERN_INFO "Remount options were %s for vfsmnt %p.\n", options, mnt);
+	pr_info("Remount options were %s for vfsmnt %p.\n", options, mnt);
 	err = parse_options_remount(sb, options, *flags & ~MS_SILENT, mnt->data);
 
 
 	return err;
 }
 
-static void* sdcardfs_clone_mnt_data(void *data) {
-	struct sdcardfs_vfsmount_options* opt = kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL);
-	struct sdcardfs_vfsmount_options* old = data;
-	if(!opt) return NULL;
+static void *sdcardfs_clone_mnt_data(void *data)
+{
+	struct sdcardfs_vfsmount_options *opt = kmalloc(sizeof(struct sdcardfs_vfsmount_options), GFP_KERNEL);
+	struct sdcardfs_vfsmount_options *old = data;
+
+	if (!opt)
+		return NULL;
 	opt->gid = old->gid;
 	opt->mask = old->mask;
 	return opt;
 }
 
-static void sdcardfs_copy_mnt_data(void *data, void *newdata) {
-	struct sdcardfs_vfsmount_options* old = data;
-	struct sdcardfs_vfsmount_options* new = newdata;
+static void sdcardfs_copy_mnt_data(void *data, void *newdata)
+{
+	struct sdcardfs_vfsmount_options *old = data;
+	struct sdcardfs_vfsmount_options *new = newdata;
+
 	old->gid = new->gid;
 	old->mask = new->mask;
 }
@@ -218,8 +221,7 @@
 /* sdcardfs inode cache destructor */
 void sdcardfs_destroy_inode_cache(void)
 {
-	if (sdcardfs_inode_cachep)
-		kmem_cache_destroy(sdcardfs_inode_cachep);
+	kmem_cache_destroy(sdcardfs_inode_cachep);
 }
 
 /*
@@ -235,7 +237,8 @@
 		lower_sb->s_op->umount_begin(lower_sb);
 }
 
-static int sdcardfs_show_options(struct vfsmount *mnt, struct seq_file *m, struct dentry *root)
+static int sdcardfs_show_options(struct vfsmount *mnt, struct seq_file *m,
+			struct dentry *root)
 {
 	struct sdcardfs_sb_info *sbi = SDCARDFS_SB(root->d_sb);
 	struct sdcardfs_mount_options *opts = &sbi->options;
@@ -248,7 +251,7 @@
 	if (vfsopts->gid != 0)
 		seq_printf(m, ",gid=%u", vfsopts->gid);
 	if (opts->multiuser)
-		seq_printf(m, ",multiuser");
+		seq_puts(m, ",multiuser");
 	if (vfsopts->mask)
 		seq_printf(m, ",mask=%u", vfsopts->mask);
 	if (opts->fs_user_id)
diff --git a/include/dt-bindings/clock/qcom,cpucc-sdm845.h b/include/dt-bindings/clock/qcom,cpucc-sdm845.h
index c1ff2a0..db3c940 100644
--- a/include/dt-bindings/clock/qcom,cpucc-sdm845.h
+++ b/include/dt-bindings/clock/qcom,cpucc-sdm845.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016, The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017, 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
@@ -14,18 +14,18 @@
 #ifndef _DT_BINDINGS_CLK_MSM_CPU_CC_SDM845_H
 #define _DT_BINDINGS_CLK_MSM_CPU_CC_SDM845_H
 
-#define L3_CLUSTER0_VOTE_CLK					0
-#define L3_CLUSTER1_VOTE_CLK					1
-#define L3_CLK							2
-#define CPU0_PWRCL_CLK						3
-#define CPU1_PWRCL_CLK						4
-#define CPU2_PWRCL_CLK						5
-#define CPU3_PWRCL_CLK						6
-#define PWRCL_CLK						7
-#define CPU4_PERFCL_CLK						8
-#define CPU5_PERFCL_CLK						9
-#define CPU6_PERFCL_CLK						10
-#define CPU7_PERFCL_CLK						11
-#define PERFCL_CLK						12
+#define L3_CLK							0
+#define PWRCL_CLK						1
+#define PERFCL_CLK						2
+#define L3_CLUSTER0_VOTE_CLK					3
+#define L3_CLUSTER1_VOTE_CLK					4
+#define CPU0_PWRCL_CLK						5
+#define CPU1_PWRCL_CLK						6
+#define CPU2_PWRCL_CLK						7
+#define CPU3_PWRCL_CLK						8
+#define CPU4_PERFCL_CLK						9
+#define CPU5_PERFCL_CLK						10
+#define CPU6_PERFCL_CLK						11
+#define CPU7_PERFCL_CLK						12
 
 #endif
diff --git a/include/dt-bindings/msm/msm-bus-ids.h b/include/dt-bindings/msm/msm-bus-ids.h
index 86ac8d4..900f268 100644
--- a/include/dt-bindings/msm/msm-bus-ids.h
+++ b/include/dt-bindings/msm/msm-bus-ids.h
@@ -87,6 +87,9 @@
 #define	MSM_BUS_BCM_ACV 7037
 #define	MSM_BUS_BCM_ALC 7038
 
+#define	MSM_BUS_RSC_APPS 8000
+#define	MSM_BUS_RSC_DISP 8001
+
 #define MSM_BUS_BCM_MC0_DISPLAY 27000
 #define MSM_BUS_BCM_SH0_DISPLAY 27001
 #define MSM_BUS_BCM_MM0_DISPLAY 27002
diff --git a/include/linux/batterydata-lib.h b/include/linux/batterydata-lib.h
new file mode 100644
index 0000000..39517f8
--- /dev/null
+++ b/include/linux/batterydata-lib.h
@@ -0,0 +1,218 @@
+/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef __BMS_BATTERYDATA_H
+#define __BMS_BATTERYDATA_H
+
+#include <linux/errno.h>
+
+#define FCC_CC_COLS		5
+#define FCC_TEMP_COLS		8
+
+#define PC_CC_ROWS             31
+#define PC_CC_COLS             13
+
+#define PC_TEMP_ROWS		31
+#define PC_TEMP_COLS		8
+
+#define ACC_IBAT_ROWS		4
+#define ACC_TEMP_COLS		3
+
+#define MAX_SINGLE_LUT_COLS	20
+
+#define MAX_BATT_ID_NUM		4
+#define DEGC_SCALE		10
+
+struct single_row_lut {
+	int x[MAX_SINGLE_LUT_COLS];
+	int y[MAX_SINGLE_LUT_COLS];
+	int cols;
+};
+
+/**
+ * struct sf_lut -
+ * @rows:	number of percent charge entries should be <= PC_CC_ROWS
+ * @cols:	number of charge cycle entries should be <= PC_CC_COLS
+ * @row_entries:	the charge cycles/temperature at which sf data
+ *			is available in the table.
+ *		The charge cycles must be in increasing order from 0 to rows.
+ * @percent:	the percent charge at which sf data is available in the table
+ *		The  percentcharge must be in decreasing order from 0 to cols.
+ * @sf:		the scaling factor data
+ */
+struct sf_lut {
+	int rows;
+	int cols;
+	int row_entries[PC_CC_COLS];
+	int percent[PC_CC_ROWS];
+	int sf[PC_CC_ROWS][PC_CC_COLS];
+};
+
+/**
+ * struct pc_temp_ocv_lut -
+ * @rows:	number of percent charge entries should be <= PC_TEMP_ROWS
+ * @cols:	number of temperature entries should be <= PC_TEMP_COLS
+ * @temp:	the temperatures at which ocv data is available in the table
+ *		The temperatures must be in increasing order from 0 to rows.
+ * @percent:	the percent charge at which ocv data is available in the table
+ *		The  percentcharge must be in decreasing order from 0 to cols.
+ * @ocv:	the open circuit voltage
+ */
+struct pc_temp_ocv_lut {
+	int rows;
+	int cols;
+	int temp[PC_TEMP_COLS];
+	int percent[PC_TEMP_ROWS];
+	int ocv[PC_TEMP_ROWS][PC_TEMP_COLS];
+};
+
+struct ibat_temp_acc_lut {
+	int rows;
+	int cols;
+	int temp[ACC_TEMP_COLS];
+	int ibat[ACC_IBAT_ROWS];
+	int acc[ACC_IBAT_ROWS][ACC_TEMP_COLS];
+};
+
+struct batt_ids {
+	int kohm[MAX_BATT_ID_NUM];
+	int num;
+};
+
+enum battery_type {
+	BATT_UNKNOWN = 0,
+	BATT_PALLADIUM,
+	BATT_DESAY,
+	BATT_OEM,
+	BATT_QRD_4V35_2000MAH,
+	BATT_QRD_4V2_1300MAH,
+};
+
+/**
+ * struct bms_battery_data -
+ * @fcc:		full charge capacity (mAmpHour)
+ * @fcc_temp_lut:	table to get fcc at a given temp
+ * @pc_temp_ocv_lut:	table to get percent charge given batt temp and cycles
+ * @pc_sf_lut:		table to get percent charge scaling factor given cycles
+ *			and percent charge
+ * @rbatt_sf_lut:	table to get battery resistance scaling factor given
+ *			temperature and percent charge
+ * @default_rbatt_mohm:	the default value of battery resistance to use when
+ *			readings from bms are not available.
+ * @delta_rbatt_mohm:	the resistance to be added towards lower soc to
+ *			compensate for battery capacitance.
+ * @rbatt_capacitve_mohm: the resistance to be added to compensate for
+ *				battery capacitance
+ * @flat_ocv_threshold_uv: the voltage where the battery's discharge curve
+ *				starts flattening out.
+ * @max_voltage_uv:	max voltage of the battery
+ * @cutoff_uv:		cutoff voltage of the battery
+ * @iterm_ua:		termination current of the battery when charging
+ *			to 100%
+ * @batt_id_kohm:	the best matched battery id resistor value
+ * @fastchg_current_ma: maximum fast charge current
+ * @fg_cc_cv_threshold_mv: CC to CV threashold voltage
+ */
+
+struct bms_battery_data {
+	unsigned int		fcc;
+	struct single_row_lut	*fcc_temp_lut;
+	struct single_row_lut	*fcc_sf_lut;
+	struct pc_temp_ocv_lut	*pc_temp_ocv_lut;
+	struct ibat_temp_acc_lut *ibat_acc_lut;
+	struct sf_lut		*pc_sf_lut;
+	struct sf_lut		*rbatt_sf_lut;
+	int			default_rbatt_mohm;
+	int			delta_rbatt_mohm;
+	int			rbatt_capacitive_mohm;
+	int			flat_ocv_threshold_uv;
+	int			max_voltage_uv;
+	int			cutoff_uv;
+	int			iterm_ua;
+	int			batt_id_kohm;
+	int			fastchg_current_ma;
+	int			fg_cc_cv_threshold_mv;
+	const char		*battery_type;
+};
+
+#define is_between(left, right, value) \
+		(((left) >= (right) && (left) >= (value) \
+			&& (value) >= (right)) \
+		|| ((left) <= (right) && (left) <= (value) \
+			&& (value) <= (right)))
+
+#if defined(CONFIG_PM8921_BMS) || \
+	defined(CONFIG_PM8921_BMS_MODULE) || \
+	defined(CONFIG_QPNP_BMS) || \
+	defined(CONFIG_QPNP_VM_BMS)
+extern struct bms_battery_data  palladium_1500_data;
+extern struct bms_battery_data  desay_5200_data;
+extern struct bms_battery_data  oem_batt_data;
+extern struct bms_battery_data QRD_4v35_2000mAh_data;
+extern struct bms_battery_data  qrd_4v2_1300mah_data;
+
+int interpolate_fcc(struct single_row_lut *fcc_temp_lut, int batt_temp);
+int interpolate_scalingfactor(struct sf_lut *sf_lut, int row_entry, int pc);
+int interpolate_scalingfactor_fcc(struct single_row_lut *fcc_sf_lut,
+				int cycles);
+int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv,
+				int batt_temp_degc, int ocv);
+int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv,
+				int batt_temp_degc, int pc);
+int interpolate_slope(struct pc_temp_ocv_lut *pc_temp_ocv,
+					int batt_temp, int pc);
+int interpolate_acc(struct ibat_temp_acc_lut *ibat_acc_lut,
+					int batt_temp, int ibat);
+int linear_interpolate(int y0, int x0, int y1, int x1, int x);
+#else
+static inline int interpolate_fcc(struct single_row_lut *fcc_temp_lut,
+			int batt_temp)
+{
+	return -EINVAL;
+}
+static inline int interpolate_scalingfactor(struct sf_lut *sf_lut,
+			int row_entry, int pc)
+{
+	return -EINVAL;
+}
+static inline int interpolate_scalingfactor_fcc(
+			struct single_row_lut *fcc_sf_lut, int cycles)
+{
+	return -EINVAL;
+}
+static inline int interpolate_pc(struct pc_temp_ocv_lut *pc_temp_ocv,
+			int batt_temp_degc, int ocv)
+{
+	return -EINVAL;
+}
+static inline int interpolate_ocv(struct pc_temp_ocv_lut *pc_temp_ocv,
+			int batt_temp_degc, int pc)
+{
+	return -EINVAL;
+}
+static inline int interpolate_slope(struct pc_temp_ocv_lut *pc_temp_ocv,
+					int batt_temp, int pc)
+{
+	return -EINVAL;
+}
+static inline int linear_interpolate(int y0, int x0, int y1, int x1, int x)
+{
+	return -EINVAL;
+}
+static inline int interpolate_acc(struct ibat_temp_acc_lut *ibat_acc_lut,
+						int batt_temp, int ibat)
+{
+	return -EINVAL;
+}
+#endif
+
+#endif
diff --git a/include/linux/ipa.h b/include/linux/ipa.h
index a83ac84..16d3d26 100644
--- a/include/linux/ipa.h
+++ b/include/linux/ipa.h
@@ -1015,6 +1015,12 @@
 	bool smmu_enabled;
 };
 
+enum ipa_upstream_type {
+	IPA_UPSTEAM_MODEM = 1,
+	IPA_UPSTEAM_WLAN,
+	IPA_UPSTEAM_MAX
+};
+
 /**
  * struct  ipa_wdi_out_params - information provided to WDI client
  * @uc_door_bell_pa: physical address of IPA uc doorbell
diff --git a/include/linux/ipv6.h b/include/linux/ipv6.h
index 7aebe23..3b94400 100644
--- a/include/linux/ipv6.h
+++ b/include/linux/ipv6.h
@@ -37,6 +37,7 @@
 	__s32		accept_ra_rtr_pref;
 	__s32		rtr_probe_interval;
 #ifdef CONFIG_IPV6_ROUTE_INFO
+	__s32		accept_ra_rt_info_min_plen;
 	__s32		accept_ra_rt_info_max_plen;
 #endif
 #endif
diff --git a/include/linux/log2.h b/include/linux/log2.h
index fd7ff3d..f38fae2 100644
--- a/include/linux/log2.h
+++ b/include/linux/log2.h
@@ -16,12 +16,6 @@
 #include <linux/bitops.h>
 
 /*
- * deal with unrepresentable constant logarithms
- */
-extern __attribute__((const, noreturn))
-int ____ilog2_NaN(void);
-
-/*
  * non-constant log of base 2 calculators
  * - the arch may override these in asm/bitops.h if they can be implemented
  *   more efficiently than using fls() and fls64()
@@ -85,7 +79,7 @@
 #define ilog2(n)				\
 (						\
 	__builtin_constant_p(n) ? (		\
-		(n) < 1 ? ____ilog2_NaN() :	\
+		(n) < 2 ? 0 :			\
 		(n) & (1ULL << 63) ? 63 :	\
 		(n) & (1ULL << 62) ? 62 :	\
 		(n) & (1ULL << 61) ? 61 :	\
@@ -148,10 +142,7 @@
 		(n) & (1ULL <<  4) ?  4 :	\
 		(n) & (1ULL <<  3) ?  3 :	\
 		(n) & (1ULL <<  2) ?  2 :	\
-		(n) & (1ULL <<  1) ?  1 :	\
-		(n) & (1ULL <<  0) ?  0 :	\
-		____ilog2_NaN()			\
-				   ) :		\
+		1 ) :				\
 	(sizeof(n) <= 4) ?			\
 	__ilog2_u32(n) :			\
 	__ilog2_u64(n)				\
diff --git a/include/linux/msm_gsi.h b/include/linux/msm_gsi.h
index 541b10e..f5d2f72 100644
--- a/include/linux/msm_gsi.h
+++ b/include/linux/msm_gsi.h
@@ -367,6 +367,7 @@
 enum gsi_xfer_elem_type {
 	GSI_XFER_ELEM_DATA,
 	GSI_XFER_ELEM_IMME_CMD,
+	GSI_XFER_ELEM_NOP,
 };
 
 /**
@@ -409,6 +410,7 @@
  *
  *		    GSI_XFER_ELEM_DATA: for all data transfers
  *		    GSI_XFER_ELEM_IMME_CMD: for IPA immediate commands
+ *		    GSI_XFER_ELEM_NOP: for event generation only
  *
  * @xfer_user_data: cookie used in xfer_cb
  *
diff --git a/include/linux/of_batterydata.h b/include/linux/of_batterydata.h
new file mode 100644
index 0000000..5505371
--- /dev/null
+++ b/include/linux/of_batterydata.h
@@ -0,0 +1,64 @@
+/* Copyright (c) 2013-2014, 2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/of.h>
+#include <linux/batterydata-lib.h>
+
+#ifdef CONFIG_OF_BATTERYDATA
+/**
+ * of_batterydata_read_data() - Populate battery data from the device tree
+ * @container_node: pointer to the battery-data container device node
+ *		containing the profile nodes.
+ * @batt_data: pointer to an allocated bms_battery_data structure that the
+ *		loaded profile will be written to.
+ * @batt_id_uv: ADC voltage of the battery id line used to differentiate
+ *		between different battery profiles. If there are multiple
+ *		battery data in the device tree, the one with the closest
+ *		battery id resistance will be automatically loaded.
+ *
+ * This routine loads the closest match battery data from device tree based on
+ * the battery id reading. Then, it will try to load all the relevant data from
+ * the device tree battery data profile.
+ *
+ * If any of the lookup table pointers are NULL, this routine will skip trying
+ * to read them.
+ */
+int of_batterydata_read_data(struct device_node *container_node,
+				struct bms_battery_data *batt_data,
+				int batt_id_uv);
+/**
+ * of_batterydata_get_best_profile() - Find matching battery data device node
+ * @batterydata_container_node: pointer to the battery-data container device
+ *		node containing the profile nodes.
+ * @batt_id_kohm: Battery ID in KOhms for which we want to find the profile.
+ * @batt_type: Battery type which we want to force load the profile.
+ *
+ * This routine returns a device_node pointer to the closest match battery data
+ * from device tree based on the battery id reading.
+ */
+struct device_node *of_batterydata_get_best_profile(
+		struct device_node *batterydata_container_node,
+		int batt_id_kohm, const char *batt_type);
+#else
+static inline int of_batterydata_read_data(struct device_node *container_node,
+				struct bms_battery_data *batt_data,
+				int batt_id_uv)
+{
+	return -ENXIO;
+}
+static inline struct device_node *of_batterydata_get_best_profile(
+		struct device_node *batterydata_container_node,
+		int batt_id_kohm, const char *batt_type)
+{
+	return -ENXIO;
+}
+#endif /* CONFIG_OF_QPNP */
diff --git a/include/linux/power_supply.h b/include/linux/power_supply.h
index 37fb247..b46d6a8 100644
--- a/include/linux/power_supply.h
+++ b/include/linux/power_supply.h
@@ -46,6 +46,7 @@
 	POWER_SUPPLY_CHARGE_TYPE_NONE,
 	POWER_SUPPLY_CHARGE_TYPE_TRICKLE,
 	POWER_SUPPLY_CHARGE_TYPE_FAST,
+	POWER_SUPPLY_CHARGE_TYPE_TAPER,
 };
 
 enum {
@@ -58,6 +59,9 @@
 	POWER_SUPPLY_HEALTH_COLD,
 	POWER_SUPPLY_HEALTH_WATCHDOG_TIMER_EXPIRE,
 	POWER_SUPPLY_HEALTH_SAFETY_TIMER_EXPIRE,
+	POWER_SUPPLY_HEALTH_WARM,
+	POWER_SUPPLY_HEALTH_COOL,
+	POWER_SUPPLY_HEALTH_HOT,
 };
 
 enum {
@@ -85,6 +89,29 @@
 	POWER_SUPPLY_SCOPE_DEVICE,
 };
 
+enum {
+	POWER_SUPPLY_DP_DM_UNKNOWN = 0,
+	POWER_SUPPLY_DP_DM_PREPARE = 1,
+	POWER_SUPPLY_DP_DM_UNPREPARE = 2,
+	POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3 = 3,
+	POWER_SUPPLY_DP_DM_DP_PULSE = 4,
+	POWER_SUPPLY_DP_DM_DM_PULSE = 5,
+	POWER_SUPPLY_DP_DM_DP0P6_DMF = 6,
+	POWER_SUPPLY_DP_DM_DP0P6_DM3P3 = 7,
+	POWER_SUPPLY_DP_DM_DPF_DMF = 8,
+	POWER_SUPPLY_DP_DM_DPR_DMR = 9,
+	POWER_SUPPLY_DP_DM_HVDCP3_SUPPORTED = 10,
+	POWER_SUPPLY_DP_DM_ICL_DOWN = 11,
+	POWER_SUPPLY_DP_DM_ICL_UP = 12,
+};
+
+enum {
+	POWER_SUPPLY_PL_NONE,
+	POWER_SUPPLY_PL_USBIN_USBIN,
+	POWER_SUPPLY_PL_USBIN_USBIN_EXT,
+	POWER_SUPPLY_PL_USBMID_USBMID,
+};
+
 enum power_supply_property {
 	/* Properties of type `int' */
 	POWER_SUPPLY_PROP_STATUS = 0,
@@ -114,6 +141,8 @@
 	POWER_SUPPLY_PROP_CHARGE_FULL,
 	POWER_SUPPLY_PROP_CHARGE_EMPTY,
 	POWER_SUPPLY_PROP_CHARGE_NOW,
+	POWER_SUPPLY_PROP_CHARGE_NOW_RAW,
+	POWER_SUPPLY_PROP_CHARGE_NOW_ERROR,
 	POWER_SUPPLY_PROP_CHARGE_AVG,
 	POWER_SUPPLY_PROP_CHARGE_COUNTER,
 	POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT,
@@ -133,6 +162,7 @@
 	POWER_SUPPLY_PROP_CAPACITY_ALERT_MIN, /* in percents! */
 	POWER_SUPPLY_PROP_CAPACITY_ALERT_MAX, /* in percents! */
 	POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+	POWER_SUPPLY_PROP_CAPACITY_RAW,
 	POWER_SUPPLY_PROP_TEMP,
 	POWER_SUPPLY_PROP_TEMP_MAX,
 	POWER_SUPPLY_PROP_TEMP_MIN,
@@ -149,11 +179,51 @@
 	POWER_SUPPLY_PROP_SCOPE,
 	POWER_SUPPLY_PROP_CHARGE_TERM_CURRENT,
 	POWER_SUPPLY_PROP_CALIBRATE,
-	POWER_SUPPLY_PROP_RESISTANCE,
 	/* Local extensions */
 	POWER_SUPPLY_PROP_USB_HC,
 	POWER_SUPPLY_PROP_USB_OTG,
-	POWER_SUPPLY_PROP_CHARGE_ENABLED,
+	POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_STEP_CHARGING_ENABLED,
+	POWER_SUPPLY_PROP_STEP_CHARGING_STEP,
+	POWER_SUPPLY_PROP_PIN_ENABLED,
+	POWER_SUPPLY_PROP_INPUT_SUSPEND,
+	POWER_SUPPLY_PROP_INPUT_VOLTAGE_REGULATION,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_TRIM,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+	POWER_SUPPLY_PROP_INPUT_VOLTAGE_SETTLED,
+	POWER_SUPPLY_PROP_VCHG_LOOP_DBC_BYPASS,
+	POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW,
+	POWER_SUPPLY_PROP_HI_POWER,
+	POWER_SUPPLY_PROP_LOW_POWER,
+	POWER_SUPPLY_PROP_COOL_TEMP,
+	POWER_SUPPLY_PROP_WARM_TEMP,
+	POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+	POWER_SUPPLY_PROP_RESISTANCE,
+	POWER_SUPPLY_PROP_RESISTANCE_CAPACITIVE,
+	POWER_SUPPLY_PROP_RESISTANCE_ID, /* in Ohms */
+	POWER_SUPPLY_PROP_RESISTANCE_NOW,
+	POWER_SUPPLY_PROP_FLASH_CURRENT_MAX,
+	POWER_SUPPLY_PROP_UPDATE_NOW,
+	POWER_SUPPLY_PROP_ESR_COUNT,
+	POWER_SUPPLY_PROP_BUCK_FREQ,
+	POWER_SUPPLY_PROP_BOOST_CURRENT,
+	POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE,
+	POWER_SUPPLY_PROP_CHARGE_DONE,
+	POWER_SUPPLY_PROP_FLASH_ACTIVE,
+	POWER_SUPPLY_PROP_FLASH_TRIGGER,
+	POWER_SUPPLY_PROP_FORCE_TLIM,
+	POWER_SUPPLY_PROP_DP_DM,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+	POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
+	POWER_SUPPLY_PROP_CURRENT_QNOVO,
+	POWER_SUPPLY_PROP_VOLTAGE_QNOVO,
+	POWER_SUPPLY_PROP_RERUN_AICL,
+	POWER_SUPPLY_PROP_CYCLE_COUNT_ID,
+	POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED,
+	POWER_SUPPLY_PROP_RESTRICTED_CHARGING,
+	POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
 	POWER_SUPPLY_PROP_TYPEC_MODE,
 	POWER_SUPPLY_PROP_TYPEC_CC_ORIENTATION, /* 0: N/C, 1: CC1, 2: CC2 */
 	POWER_SUPPLY_PROP_TYPEC_POWER_ROLE,
@@ -162,16 +232,26 @@
 	POWER_SUPPLY_PROP_PD_IN_HARD_RESET,
 	POWER_SUPPLY_PROP_PD_CURRENT_MAX,
 	POWER_SUPPLY_PROP_PD_USB_SUSPEND_SUPPORTED,
+	POWER_SUPPLY_PROP_CHARGER_TEMP,
+	POWER_SUPPLY_PROP_CHARGER_TEMP_MAX,
+	POWER_SUPPLY_PROP_PARALLEL_DISABLE,
 	POWER_SUPPLY_PROP_PE_START,
 	POWER_SUPPLY_PROP_SET_SHIP_MODE,
-	POWER_SUPPLY_PROP_BOOST_CURRENT,
-	POWER_SUPPLY_PROP_FORCE_TLIM,
+	POWER_SUPPLY_PROP_SOC_REPORTING_READY,
+	POWER_SUPPLY_PROP_DEBUG_BATTERY,
+	POWER_SUPPLY_PROP_FCC_DELTA,
+	POWER_SUPPLY_PROP_ICL_REDUCTION,
+	POWER_SUPPLY_PROP_PARALLEL_MODE,
+	POWER_SUPPLY_PROP_DIE_HEALTH,
+	POWER_SUPPLY_PROP_CONNECTOR_HEALTH,
+	POWER_SUPPLY_PROP_CTM_CURRENT_MAX,
 	/* Local extensions of type int64_t */
 	POWER_SUPPLY_PROP_CHARGE_COUNTER_EXT,
 	/* Properties of type `const char *' */
 	POWER_SUPPLY_PROP_MODEL_NAME,
 	POWER_SUPPLY_PROP_MANUFACTURER,
 	POWER_SUPPLY_PROP_SERIAL_NUMBER,
+	POWER_SUPPLY_PROP_BATTERY_TYPE,
 };
 
 enum power_supply_type {
@@ -183,9 +263,17 @@
 	POWER_SUPPLY_TYPE_USB_DCP,	/* Dedicated Charging Port */
 	POWER_SUPPLY_TYPE_USB_CDP,	/* Charging Downstream Port */
 	POWER_SUPPLY_TYPE_USB_ACA,	/* Accessory Charger Adapters */
-	POWER_SUPPLY_TYPE_USB_TYPE_C,	/* Type C Port */
-	POWER_SUPPLY_TYPE_USB_PD,	/* Power Delivery Port */
-	POWER_SUPPLY_TYPE_USB_PD_DRP,	/* PD Dual Role Port */
+	POWER_SUPPLY_TYPE_USB_HVDCP,	/* High Voltage DCP */
+	POWER_SUPPLY_TYPE_USB_HVDCP_3,	/* Efficient High Voltage DCP */
+	POWER_SUPPLY_TYPE_USB_PD,       /* Power Delivery */
+	POWER_SUPPLY_TYPE_WIRELESS,	/* Accessory Charger Adapters */
+	POWER_SUPPLY_TYPE_BMS,		/* Battery Monitor System */
+	POWER_SUPPLY_TYPE_PARALLEL,	/* Parallel Path */
+	POWER_SUPPLY_TYPE_MAIN,		/* Main Path */
+	POWER_SUPPLY_TYPE_WIPOWER,	/* Wipower */
+	POWER_SUPPLY_TYPE_TYPEC,	/* Type-C */
+	POWER_SUPPLY_TYPE_UFP,		/* Type-C UFP */
+	POWER_SUPPLY_TYPE_DFP,		/* TYpe-C DFP */
 };
 
 /* Indicates USB Type-C CC connection status */
@@ -359,7 +447,7 @@
 #ifdef CONFIG_POWER_SUPPLY
 extern int power_supply_is_system_supplied(void);
 #else
-static inline int power_supply_is_system_supplied(void) { return -ENOSYS; }
+static inline int power_supply_is_system_supplied(void) { return -EIO; }
 #endif
 
 extern int power_supply_get_property(struct power_supply *psy,
@@ -411,6 +499,9 @@
 	case POWER_SUPPLY_PROP_CURRENT_NOW:
 	case POWER_SUPPLY_PROP_CURRENT_AVG:
 	case POWER_SUPPLY_PROP_CURRENT_BOOT:
+	case POWER_SUPPLY_PROP_CHARGE_COUNTER_SHADOW:
+	case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+	case POWER_SUPPLY_PROP_FLASH_CURRENT_MAX:
 		return 1;
 	default:
 		break;
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 45b5f91..867de7d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -3885,6 +3885,7 @@
 #define SCHED_CPUFREQ_RT	(1U << 0)
 #define SCHED_CPUFREQ_DL	(1U << 1)
 #define SCHED_CPUFREQ_IOWAIT	(1U << 2)
+#define SCHED_CPUFREQ_INTERCLUSTER_MIG (1U << 3)
 
 #define SCHED_CPUFREQ_RT_DL	(SCHED_CPUFREQ_RT | SCHED_CPUFREQ_DL)
 
diff --git a/include/scsi/libiscsi.h b/include/scsi/libiscsi.h
index 4d1c46a..c7b1dc7 100644
--- a/include/scsi/libiscsi.h
+++ b/include/scsi/libiscsi.h
@@ -196,6 +196,7 @@
 	struct iscsi_task	*task;		/* xmit task in progress */
 
 	/* xmit */
+	spinlock_t		taskqueuelock;  /* protects the next three lists */
 	struct list_head	mgmtqueue;	/* mgmt (control) xmit queue */
 	struct list_head	cmdqueue;	/* data-path cmd queue */
 	struct list_head	requeue;	/* tasks needing another run */
diff --git a/include/trace/events/cma.h b/include/trace/events/cma.h
index d7cd961..693fceb 100644
--- a/include/trace/events/cma.h
+++ b/include/trace/events/cma.h
@@ -7,7 +7,7 @@
 #include <linux/types.h>
 #include <linux/tracepoint.h>
 
-TRACE_EVENT(cma_alloc,
+DECLARE_EVENT_CLASS(cma_alloc_class,
 
 	TP_PROTO(unsigned long pfn, const struct page *page,
 		 unsigned int count, unsigned int align),
@@ -60,6 +60,44 @@
 		  __entry->count)
 );
 
+TRACE_EVENT(cma_alloc_start,
+
+	TP_PROTO(unsigned int count, unsigned int align),
+
+	TP_ARGS(count, align),
+
+	TP_STRUCT__entry(
+		__field(unsigned int, count)
+		__field(unsigned int, align)
+	),
+
+	TP_fast_assign(
+		__entry->count = count;
+		__entry->align = align;
+	),
+
+	TP_printk("count=%u align=%u",
+		  __entry->count,
+		  __entry->align)
+);
+
+DEFINE_EVENT(cma_alloc_class, cma_alloc,
+
+	TP_PROTO(unsigned long pfn, const struct page *page,
+		 unsigned int count, unsigned int align),
+
+	TP_ARGS(pfn, page, count, align)
+);
+
+DEFINE_EVENT(cma_alloc_class, cma_alloc_busy_retry,
+
+	TP_PROTO(unsigned long pfn, const struct page *page,
+		 unsigned int count, unsigned int align),
+
+	TP_ARGS(pfn, page, count, align)
+);
+
+
 #endif /* _TRACE_CMA_H */
 
 /* This part must be outside protection */
diff --git a/include/trace/events/migrate.h b/include/trace/events/migrate.h
index 539b25a..0ee910d 100644
--- a/include/trace/events/migrate.h
+++ b/include/trace/events/migrate.h
@@ -96,6 +96,27 @@
 		__entry->dst_nid,
 		__entry->nr_pages)
 );
+
+TRACE_EVENT(mm_migrate_pages_start,
+
+	TP_PROTO(enum migrate_mode mode, int reason),
+
+	TP_ARGS(mode, reason),
+
+	TP_STRUCT__entry(
+		__field(enum migrate_mode, mode)
+		__field(int, reason)
+	),
+
+	TP_fast_assign(
+		__entry->mode	= mode;
+		__entry->reason	= reason;
+	),
+
+	TP_printk("mode=%s reason=%s",
+		__print_symbolic(__entry->mode, MIGRATE_MODE),
+		__print_symbolic(__entry->reason, MIGRATE_REASON))
+);
 #endif /* _TRACE_MIGRATE_H */
 
 /* This part must be outside protection */
diff --git a/include/uapi/linux/Kbuild b/include/uapi/linux/Kbuild
index 4823794..33ba430 100644
--- a/include/uapi/linux/Kbuild
+++ b/include/uapi/linux/Kbuild
@@ -428,6 +428,7 @@
 header-y += snmp.h
 header-y += sock_diag.h
 header-y += socket.h
+header-y += sockev.h
 header-y += sockios.h
 header-y += sonet.h
 header-y += sonypi.h
diff --git a/include/uapi/linux/ipv6.h b/include/uapi/linux/ipv6.h
index 1049c78..c462f1d 100644
--- a/include/uapi/linux/ipv6.h
+++ b/include/uapi/linux/ipv6.h
@@ -179,6 +179,12 @@
 	DEVCONF_DROP_UNSOLICITED_NA,
 	DEVCONF_KEEP_ADDR_ON_DOWN,
 	DEVCONF_RTR_SOLICIT_MAX_INTERVAL,
+	DEVCONF_SEG6_ENABLED,
+	DEVCONF_SEG6_REQUIRE_HMAC,
+	DEVCONF_ENHANCED_DAD,
+	DEVCONF_ADDR_GEN_MODE,
+	DEVCONF_DISABLE_POLICY,
+	DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN,
 	DEVCONF_MAX
 };
 
diff --git a/include/uapi/linux/netlink.h b/include/uapi/linux/netlink.h
index 0dba4e4..2817ca1 100644
--- a/include/uapi/linux/netlink.h
+++ b/include/uapi/linux/netlink.h
@@ -27,7 +27,7 @@
 #define NETLINK_ECRYPTFS	19
 #define NETLINK_RDMA		20
 #define NETLINK_CRYPTO		21	/* Crypto layer */
-
+#define NETLINK_SOCKEV          22      /* Socket Administrative Events */
 #define NETLINK_INET_DIAG	NETLINK_SOCK_DIAG
 
 #define MAX_LINKS 32		
diff --git a/include/uapi/linux/sockev.h b/include/uapi/linux/sockev.h
new file mode 100644
index 0000000..b274fbc
--- /dev/null
+++ b/include/uapi/linux/sockev.h
@@ -0,0 +1,31 @@
+#ifndef _SOCKEV_H_
+#define _SOCKEV_H_
+
+#include <linux/types.h>
+#include <linux/netlink.h>
+#include <linux/socket.h>
+
+enum sknetlink_groups {
+	SKNLGRP_UNICAST,
+	SKNLGRP_SOCKEV,
+	__SKNLGRP_MAX
+};
+
+#define SOCKEV_STR_MAX 32
+
+/********************************************************************
+ *		Socket operation messages
+ ****/
+
+struct sknlsockevmsg {
+	__u8 event[SOCKEV_STR_MAX];
+	__u32 pid; /* (struct task_struct*)->pid */
+	__u16 skfamily; /* (struct socket*)->sk->sk_family */
+	__u8 skstate; /* (struct socket*)->sk->sk_state */
+	__u8 skprotocol; /* (struct socket*)->sk->sk_protocol */
+	__u16 sktype; /* (struct socket*)->sk->sk_type */
+	__u64 skflags; /* (struct socket*)->sk->sk_flags */
+};
+
+#endif /* _SOCKEV_H_ */
+
diff --git a/include/uapi/linux/sysctl.h b/include/uapi/linux/sysctl.h
index e854785..08aa800 100644
--- a/include/uapi/linux/sysctl.h
+++ b/include/uapi/linux/sysctl.h
@@ -570,6 +570,7 @@
 	NET_IPV6_PROXY_NDP=23,
 	NET_IPV6_ACCEPT_SOURCE_ROUTE=25,
 	NET_IPV6_ACCEPT_RA_FROM_LOCAL=26,
+	NET_IPV6_ACCEPT_RA_RT_INFO_MIN_PLEN=27,
 	__NET_IPV6_MAX
 };
 
diff --git a/include/uapi/linux/v4l2-controls.h b/include/uapi/linux/v4l2-controls.h
index 37c6c00..a62870e 100644
--- a/include/uapi/linux/v4l2-controls.h
+++ b/include/uapi/linux/v4l2-controls.h
@@ -429,6 +429,9 @@
 	V4L2_MPEG_VIDEO_H264_LEVEL_5_0	= 14,
 	V4L2_MPEG_VIDEO_H264_LEVEL_5_1	= 15,
 	V4L2_MPEG_VIDEO_H264_LEVEL_5_2	= 16,
+#define V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN \
+	V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN
+	V4L2_MPEG_VIDEO_H264_LEVEL_UNKNOWN = 17,
 };
 #define V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_ALPHA	(V4L2_CID_MPEG_BASE+360)
 #define V4L2_CID_MPEG_VIDEO_H264_LOOP_FILTER_BETA	(V4L2_CID_MPEG_BASE+361)
@@ -919,6 +922,9 @@
 	V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_6_1	= 23,
 	V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_MAIN_TIER_LEVEL_6_2	= 24,
 	V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_HIGH_TIER_LEVEL_6_2	= 25,
+#define V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN \
+	V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN
+	V4L2_MPEG_VIDC_VIDEO_HEVC_LEVEL_UNKNOWN = 26,
 };
 
 #define V4L2_CID_MPEG_VIDC_VIDEO_HIER_B_NUM_LAYERS \
diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
index fd379ec..86cb858 100644
--- a/include/uapi/linux/videodev2.h
+++ b/include/uapi/linux/videodev2.h
@@ -539,6 +539,7 @@
 #define V4L2_PIX_FMT_NV12_UBWC        v4l2_fourcc('Q', '1', '2', '8')
 /* UBWC 10-bit Y/CbCr 4:2:0 */
 #define V4L2_PIX_FMT_NV12_TP10_UBWC   v4l2_fourcc('Q', '1', '2', 'A')
+#define V4L2_PIX_FMT_NV12_P010_UBWC   v4l2_fourcc('Q', '1', '2', 'B')
 
 /* two non contiguous planes - one Y, one Cr + Cb interleaved  */
 #define V4L2_PIX_FMT_NV12M   v4l2_fourcc('N', 'M', '1', '2') /* 12  Y/CbCr 4:2:0  */
diff --git a/include/uapi/media/msm_sde_rotator.h b/include/uapi/media/msm_sde_rotator.h
index 790135a..212eb26 100644
--- a/include/uapi/media/msm_sde_rotator.h
+++ b/include/uapi/media/msm_sde_rotator.h
@@ -63,6 +63,7 @@
 #define SDE_PIX_FMT_Y_CBCR_H2V2_P010	V4L2_PIX_FMT_SDE_Y_CBCR_H2V2_P010
 #define SDE_PIX_FMT_Y_CBCR_H2V2_TP10	V4L2_PIX_FMT_SDE_Y_CBCR_H2V2_TP10
 #define SDE_PIX_FMT_Y_CBCR_H2V2_TP10_UBWC	V4L2_PIX_FMT_NV12_TP10_UBWC
+#define SDE_PIX_FMT_Y_CBCR_H2V2_P010_UBWC	V4L2_PIX_FMT_NV12_P010_UBWC
 
 /*
  * struct msm_sde_rotator_fence - v4l2 buffer fence info
diff --git a/kernel/cgroup_pids.c b/kernel/cgroup_pids.c
index 2bd6737..a57242e 100644
--- a/kernel/cgroup_pids.c
+++ b/kernel/cgroup_pids.c
@@ -229,7 +229,7 @@
 		/* Only log the first time events_limit is incremented. */
 		if (atomic64_inc_return(&pids->events_limit) == 1) {
 			pr_info("cgroup: fork rejected by pids controller in ");
-			pr_cont_cgroup_path(task_cgroup(current, pids_cgrp_id));
+			pr_cont_cgroup_path(css->cgroup);
 			pr_cont("\n");
 		}
 		cgroup_file_notify(&pids->events_file);
diff --git a/kernel/events/core.c b/kernel/events/core.c
index 99c91f6..24f0d77 100644
--- a/kernel/events/core.c
+++ b/kernel/events/core.c
@@ -10342,6 +10342,17 @@
 			continue;
 
 		mutex_lock(&ctx->mutex);
+		raw_spin_lock_irq(&ctx->lock);
+		/*
+		 * Destroy the task <-> ctx relation and mark the context dead.
+		 *
+		 * This is important because even though the task hasn't been
+		 * exposed yet the context has been (through child_list).
+		 */
+		RCU_INIT_POINTER(task->perf_event_ctxp[ctxn], NULL);
+		WRITE_ONCE(ctx->task, TASK_TOMBSTONE);
+		put_task_struct(task); /* cannot be last */
+		raw_spin_unlock_irq(&ctx->lock);
 again:
 		list_for_each_entry_safe(event, tmp, &ctx->pinned_groups,
 				group_entry)
@@ -10595,7 +10606,7 @@
 		ret = inherit_task_group(event, parent, parent_ctx,
 					 child, ctxn, &inherited_all);
 		if (ret)
-			break;
+			goto out_unlock;
 	}
 
 	/*
@@ -10611,7 +10622,7 @@
 		ret = inherit_task_group(event, parent, parent_ctx,
 					 child, ctxn, &inherited_all);
 		if (ret)
-			break;
+			goto out_unlock;
 	}
 
 	raw_spin_lock_irqsave(&parent_ctx->lock, flags);
@@ -10639,6 +10650,7 @@
 	}
 
 	raw_spin_unlock_irqrestore(&parent_ctx->lock, flags);
+out_unlock:
 	mutex_unlock(&parent_ctx->mutex);
 
 	perf_unpin_context(parent_ctx);
diff --git a/kernel/sched/core.c b/kernel/sched/core.c
index fe084ef..f7f5256 100644
--- a/kernel/sched/core.c
+++ b/kernel/sched/core.c
@@ -2172,6 +2172,7 @@
 	wallclock = sched_ktime_clock();
 	update_task_ravg(rq->curr, rq, TASK_UPDATE, wallclock, 0);
 	update_task_ravg(p, rq, TASK_WAKE, wallclock, 0);
+	cpufreq_update_util(rq, 0);
 	raw_spin_unlock(&rq->lock);
 
 	rcu_read_lock();
@@ -2264,6 +2265,7 @@
 
 		update_task_ravg(rq->curr, rq, TASK_UPDATE, wallclock, 0);
 		update_task_ravg(p, rq, TASK_WAKE, wallclock, 0);
+		cpufreq_update_util(rq, 0);
 		ttwu_activate(rq, p, ENQUEUE_WAKEUP);
 		note_task_waking(p, wallclock);
 	}
@@ -3370,6 +3372,8 @@
 
 	wallclock = sched_ktime_clock();
 	update_task_ravg(rq->curr, rq, TASK_UPDATE, wallclock, 0);
+
+	cpufreq_update_util(rq, 0);
 	early_notif = early_detection_notify(rq, wallclock);
 
 	raw_spin_unlock(&rq->lock);
@@ -3704,6 +3708,7 @@
 	if (likely(prev != next)) {
 		update_task_ravg(prev, rq, PUT_PREV_TASK, wallclock, 0);
 		update_task_ravg(next, rq, PICK_NEXT_TASK, wallclock, 0);
+		cpufreq_update_util(rq, 0);
 		if (!is_idle_task(prev) && !prev->on_rq)
 			update_avg_burst(prev);
 
@@ -3717,6 +3722,7 @@
 		rq = context_switch(rq, prev, next, cookie); /* unlocks the rq */
 	} else {
 		update_task_ravg(prev, rq, TASK_UPDATE, wallclock, 0);
+		cpufreq_update_util(rq, 0);
 		lockdep_unpin_lock(&rq->lock, cookie);
 		raw_spin_unlock_irq(&rq->lock);
 	}
diff --git a/kernel/sched/hmp.c b/kernel/sched/hmp.c
index c0adf4e..e43344f 100644
--- a/kernel/sched/hmp.c
+++ b/kernel/sched/hmp.c
@@ -3610,6 +3610,11 @@
 
 	migrate_top_tasks(p, src_rq, dest_rq);
 
+	if (!same_freq_domain(new_cpu, task_cpu(p))) {
+		cpufreq_update_util(dest_rq, SCHED_CPUFREQ_INTERCLUSTER_MIG);
+		cpufreq_update_util(src_rq, SCHED_CPUFREQ_INTERCLUSTER_MIG);
+	}
+
 	if (p == src_rq->ed_task) {
 		src_rq->ed_task = NULL;
 		if (!dest_rq->ed_task)
diff --git a/kernel/sched/sched.h b/kernel/sched/sched.h
index e7f6794..5e25011 100644
--- a/kernel/sched/sched.h
+++ b/kernel/sched/sched.h
@@ -79,6 +79,7 @@
 	u64 time;
 };
 
+extern unsigned int sched_disable_window_stats;
 #endif /* CONFIG_SCHED_HMP */
 
 
@@ -770,6 +771,7 @@
 
 	int cstate, wakeup_latency, wakeup_energy;
 	u64 window_start;
+	u64 load_reported_window;
 	unsigned long hmp_flags;
 
 	u64 cur_irqload;
@@ -2142,6 +2144,18 @@
 {
 	struct update_util_data *data;
 
+#ifdef CONFIG_SCHED_HMP
+	/*
+	 * Skip if we've already reported, but not if this is an inter-cluster
+	 * migration
+	 */
+	if (!sched_disable_window_stats &&
+		(rq->load_reported_window == rq->window_start) &&
+		!(flags & SCHED_CPUFREQ_INTERCLUSTER_MIG))
+		return;
+	rq->load_reported_window = rq->window_start;
+#endif
+
 	data = rcu_dereference_sched(*this_cpu_ptr(&cpufreq_update_util_data));
 	if (data)
 		data->func(data, rq_clock(rq), flags);
diff --git a/lib/Makefile b/lib/Makefile
index e0eb131..6bde16d 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -31,6 +31,8 @@
 lib-y	+= kobject.o klist.o
 obj-y	+= lockref.o
 
+KASAN_SANITIZE_find_bit.o := n
+
 obj-y += bcd.o div64.o sort.o parser.o halfmd4.o debug_locks.o random32.o \
 	 bust_spinlocks.o kasprintf.o bitmap.o scatterlist.o \
 	 gcd.o lcm.o list_sort.o uuid.o flex_array.o iov_iter.o clz_ctz.o \
diff --git a/mm/cma.c b/mm/cma.c
index c960459..0306bab 100644
--- a/mm/cma.c
+++ b/mm/cma.c
@@ -134,6 +134,10 @@
 	spin_lock_init(&cma->mem_head_lock);
 #endif
 
+	if (!PageHighMem(pfn_to_page(cma->base_pfn)))
+		kmemleak_free_part(__va(cma->base_pfn << PAGE_SHIFT),
+				cma->count << PAGE_SHIFT);
+
 	return 0;
 
 err:
@@ -380,6 +384,8 @@
 	if (!count)
 		return NULL;
 
+	trace_cma_alloc_start(count, align);
+
 	mask = cma_bitmap_aligned_mask(cma, align);
 	offset = cma_bitmap_aligned_offset(cma, align);
 	bitmap_maxno = cma_bitmap_maxno(cma);
@@ -420,6 +426,8 @@
 
 		pr_debug("%s(): memory range at %p is busy, retrying\n",
 			 __func__, pfn_to_page(pfn));
+
+		trace_cma_alloc_busy_retry(pfn, pfn_to_page(pfn), count, align);
 		/* try again with a bit different memory target */
 		start = bitmap_no + mask + 1;
 	}
diff --git a/mm/memory.c b/mm/memory.c
index cbb1e5e..91e1653 100644
--- a/mm/memory.c
+++ b/mm/memory.c
@@ -3050,7 +3050,7 @@
 }
 
 static unsigned long fault_around_bytes __read_mostly =
-	rounddown_pow_of_two(65536);
+	rounddown_pow_of_two(4096);
 
 #ifdef CONFIG_DEBUG_FS
 static int fault_around_bytes_get(void *data, u64 *val)
diff --git a/mm/migrate.c b/mm/migrate.c
index 66ce6b4..f49de3cf 100644
--- a/mm/migrate.c
+++ b/mm/migrate.c
@@ -1319,6 +1319,8 @@
 	int swapwrite = current->flags & PF_SWAPWRITE;
 	int rc;
 
+	trace_mm_migrate_pages_start(mode, reason);
+
 	if (!swapwrite)
 		current->flags |= PF_SWAPWRITE;
 
diff --git a/mm/percpu.c b/mm/percpu.c
index 2557143..f014ceb 100644
--- a/mm/percpu.c
+++ b/mm/percpu.c
@@ -1010,8 +1010,11 @@
 		mutex_unlock(&pcpu_alloc_mutex);
 	}
 
-	if (chunk != pcpu_reserved_chunk)
+	if (chunk != pcpu_reserved_chunk) {
+		spin_lock_irqsave(&pcpu_lock, flags);
 		pcpu_nr_empty_pop_pages -= occ_pages;
+		spin_unlock_irqrestore(&pcpu_lock, flags);
+	}
 
 	if (pcpu_nr_empty_pop_pages < PCPU_EMPTY_POP_PAGES_LOW)
 		pcpu_schedule_balance_work();
diff --git a/mm/slub.c b/mm/slub.c
index 2b01429..30be24b 100644
--- a/mm/slub.c
+++ b/mm/slub.c
@@ -1673,6 +1673,7 @@
 	if (current->reclaim_state)
 		current->reclaim_state->reclaimed_slab += pages;
 	memcg_uncharge_slab(page, order, s);
+	kasan_alloc_pages(page, order);
 	__free_pages(page, order);
 }
 
@@ -3881,6 +3882,7 @@
 	if (unlikely(!PageSlab(page))) {
 		BUG_ON(!PageCompound(page));
 		kfree_hook(x);
+		kasan_alloc_pages(page, compound_order(page));
 		__free_pages(page, compound_order(page));
 		return;
 	}
diff --git a/net/Kconfig b/net/Kconfig
index cd20118..d5ff4f7 100644
--- a/net/Kconfig
+++ b/net/Kconfig
@@ -321,6 +321,15 @@
 	  with many clients some protection against DoS by a single (spoofed)
 	  flow that greatly exceeds average workload.
 
+config SOCKEV_NLMCAST
+	bool "Enable SOCKEV Netlink Multicast"
+	default n
+	---help---
+	  Default client for SOCKEV notifier events. Sends multicast netlink
+	  messages whenever the socket event notifier is invoked. Enable if
+	  user space entities need to be notified of socket events without
+	  having to poll /proc
+
 menu "Network testing"
 
 config NET_PKTGEN
diff --git a/net/core/Makefile b/net/core/Makefile
index d6508c2..77bb89b 100644
--- a/net/core/Makefile
+++ b/net/core/Makefile
@@ -24,6 +24,7 @@
 obj-$(CONFIG_CGROUP_NET_PRIO) += netprio_cgroup.o
 obj-$(CONFIG_CGROUP_NET_CLASSID) += netclassid_cgroup.o
 obj-$(CONFIG_LWTUNNEL) += lwtunnel.o
+obj-$(CONFIG_SOCKEV_NLMCAST) += sockev_nlmcast.o
 obj-$(CONFIG_DST_CACHE) += dst_cache.o
 obj-$(CONFIG_HWBM) += hwbm.o
 obj-$(CONFIG_NET_DEVLINK) += devlink.o
diff --git a/net/core/sockev_nlmcast.c b/net/core/sockev_nlmcast.c
new file mode 100644
index 0000000..04f61fc
--- /dev/null
+++ b/net/core/sockev_nlmcast.c
@@ -0,0 +1,147 @@
+/*
+ * Copyright (c) 2014-2015, 2017 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.
+ *
+ *
+ * Default SOCKEV client implementation
+ *
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/export.h>
+#include <linux/netlink.h>
+#include <linux/sockev.h>
+#include <net/sock.h>
+
+static int registration_status;
+static struct sock *socknlmsgsk;
+
+static void sockev_skmsg_recv(struct sk_buff *skb)
+{
+	pr_debug("%s(): Got unsolicited request\n", __func__);
+}
+
+static struct netlink_kernel_cfg nlcfg = {
+	.input = sockev_skmsg_recv
+};
+
+static void _sockev_event(unsigned long event, __u8 *evstr, int buflen)
+{
+	switch (event) {
+	case SOCKEV_SOCKET:
+		strlcpy(evstr, "SOCKEV_SOCKET", buflen);
+		break;
+	case SOCKEV_BIND:
+		strlcpy(evstr, "SOCKEV_BIND", buflen);
+		break;
+	case SOCKEV_LISTEN:
+		strlcpy(evstr, "SOCKEV_LISTEN", buflen);
+		break;
+	case SOCKEV_ACCEPT:
+		strlcpy(evstr, "SOCKEV_ACCEPT", buflen);
+		break;
+	case SOCKEV_CONNECT:
+		strlcpy(evstr, "SOCKEV_CONNECT", buflen);
+		break;
+	case SOCKEV_SHUTDOWN:
+		strlcpy(evstr, "SOCKEV_SHUTDOWN", buflen);
+		break;
+	default:
+		strlcpy(evstr, "UNKNOWN", buflen);
+	}
+}
+
+static int sockev_client_cb(struct notifier_block *nb,
+			    unsigned long event, void *data)
+{
+	struct sk_buff *skb;
+	struct nlmsghdr *nlh;
+	struct sknlsockevmsg *smsg;
+	struct socket *sock;
+
+	sock = (struct socket *)data;
+	if (socknlmsgsk == 0)
+		goto done;
+	if ((!socknlmsgsk) || (!sock) || (!sock->sk))
+		goto done;
+
+	if (sock->sk->sk_family != AF_INET && sock->sk->sk_family != AF_INET6)
+		goto done;
+
+	if (event != SOCKEV_BIND && event != SOCKEV_LISTEN)
+		goto done;
+
+	skb = nlmsg_new(sizeof(struct sknlsockevmsg), GFP_KERNEL);
+	if (!skb)
+		goto done;
+
+	nlh = nlmsg_put(skb, 0, 0, event, sizeof(struct sknlsockevmsg), 0);
+	if (!nlh) {
+		kfree_skb(skb);
+		goto done;
+	}
+
+	NETLINK_CB(skb).dst_group = SKNLGRP_SOCKEV;
+
+	smsg = nlmsg_data(nlh);
+	smsg->pid = current->pid;
+	_sockev_event(event, smsg->event, sizeof(smsg->event));
+	smsg->skfamily = sock->sk->sk_family;
+	smsg->skstate = sock->sk->sk_state;
+	smsg->skprotocol = sock->sk->sk_protocol;
+	smsg->sktype = sock->sk->sk_type;
+	smsg->skflags = sock->sk->sk_flags;
+
+	nlmsg_notify(socknlmsgsk, skb, 0, SKNLGRP_SOCKEV, 0, GFP_KERNEL);
+done:
+	return 0;
+}
+
+static struct notifier_block sockev_notifier_client = {
+	.notifier_call = sockev_client_cb,
+	.next = 0,
+	.priority = 0
+};
+
+/* ***************** Startup/Shutdown *************************************** */
+
+static int __init sockev_client_init(void)
+{
+	int rc;
+
+	registration_status = 1;
+	rc = sockev_register_notify(&sockev_notifier_client);
+	if (rc != 0) {
+		registration_status = 0;
+		pr_err("%s(): Failed to register cb (%d)\n", __func__, rc);
+	}
+	socknlmsgsk = netlink_kernel_create(&init_net, NETLINK_SOCKEV, &nlcfg);
+	if (!socknlmsgsk) {
+		pr_err("%s(): Failed to initialize netlink socket\n", __func__);
+		if (registration_status)
+			sockev_unregister_notify(&sockev_notifier_client);
+		registration_status = 0;
+	}
+
+	return rc;
+}
+
+static void __exit sockev_client_exit(void)
+{
+	if (registration_status)
+		sockev_unregister_notify(&sockev_notifier_client);
+}
+
+module_init(sockev_client_init)
+module_exit(sockev_client_exit)
+MODULE_LICENSE("GPL v2");
+
diff --git a/net/ipc_router/ipc_router_core.c b/net/ipc_router/ipc_router_core.c
index cdf372f..e057887 100644
--- a/net/ipc_router/ipc_router_core.c
+++ b/net/ipc_router/ipc_router_core.c
@@ -2798,6 +2798,9 @@
 	if (!port_ptr || !name)
 		return -EINVAL;
 
+	if (port_ptr->type != CLIENT_PORT)
+		return -EINVAL;
+
 	if (name->addrtype != MSM_IPC_ADDR_NAME)
 		return -EINVAL;
 
diff --git a/net/ipc_router/ipc_router_socket.c b/net/ipc_router/ipc_router_socket.c
index a84fc11..02242a1 100644
--- a/net/ipc_router/ipc_router_socket.c
+++ b/net/ipc_router/ipc_router_socket.c
@@ -543,10 +543,18 @@
 static int msm_ipc_router_close(struct socket *sock)
 {
 	struct sock *sk = sock->sk;
-	struct msm_ipc_port *port_ptr = msm_ipc_sk_port(sk);
+	struct msm_ipc_port *port_ptr;
 	int ret;
 
+	if (!sk)
+		return -EINVAL;
+
 	lock_sock(sk);
+	port_ptr = msm_ipc_sk_port(sk);
+	if (!port_ptr) {
+		release_sock(sk);
+		return -EINVAL;
+	}
 	ret = msm_ipc_router_close_port(port_ptr);
 	msm_ipc_unload_default_node(msm_ipc_sk(sk)->default_node_vote_info);
 	release_sock(sk);
diff --git a/net/ipv6/addrconf.c b/net/ipv6/addrconf.c
index 965ca86..0cfb91f 100644
--- a/net/ipv6/addrconf.c
+++ b/net/ipv6/addrconf.c
@@ -223,6 +223,7 @@
 	.accept_ra_rtr_pref	= 1,
 	.rtr_probe_interval	= 60 * HZ,
 #ifdef CONFIG_IPV6_ROUTE_INFO
+	.accept_ra_rt_info_min_plen = 0,
 	.accept_ra_rt_info_max_plen = 0,
 #endif
 #endif
@@ -270,6 +271,7 @@
 	.accept_ra_rtr_pref	= 1,
 	.rtr_probe_interval	= 60 * HZ,
 #ifdef CONFIG_IPV6_ROUTE_INFO
+	.accept_ra_rt_info_min_plen = 0,
 	.accept_ra_rt_info_max_plen = 0,
 #endif
 #endif
@@ -4963,6 +4965,7 @@
 	array[DEVCONF_RTR_PROBE_INTERVAL] =
 		jiffies_to_msecs(cnf->rtr_probe_interval);
 #ifdef CONFIG_IPV6_ROUTE_INFO
+	array[DEVCONF_ACCEPT_RA_RT_INFO_MIN_PLEN] = cnf->accept_ra_rt_info_min_plen;
 	array[DEVCONF_ACCEPT_RA_RT_INFO_MAX_PLEN] = cnf->accept_ra_rt_info_max_plen;
 #endif
 #endif
@@ -5939,6 +5942,13 @@
 	},
 #ifdef CONFIG_IPV6_ROUTE_INFO
 	{
+		.procname	= "accept_ra_rt_info_min_plen",
+		.data		= &ipv6_devconf.accept_ra_rt_info_min_plen,
+		.maxlen		= sizeof(int),
+		.mode		= 0644,
+		.proc_handler	= proc_dointvec,
+	},
+	{
 		.procname	= "accept_ra_rt_info_max_plen",
 		.data		= &ipv6_devconf.accept_ra_rt_info_max_plen,
 		.maxlen		= sizeof(int),
diff --git a/net/ipv6/ndisc.c b/net/ipv6/ndisc.c
index d8e6714..01858ac 100644
--- a/net/ipv6/ndisc.c
+++ b/net/ipv6/ndisc.c
@@ -1395,6 +1395,8 @@
 			if (ri->prefix_len == 0 &&
 			    !in6_dev->cnf.accept_ra_defrtr)
 				continue;
+			if (ri->prefix_len < in6_dev->cnf.accept_ra_rt_info_min_plen)
+				continue;
 			if (ri->prefix_len > in6_dev->cnf.accept_ra_rt_info_max_plen)
 				continue;
 			rt6_route_rcv(skb->dev, (u8 *)p, (p->nd_opt_len) << 3,
diff --git a/net/netfilter/xt_qtaguid.c b/net/netfilter/xt_qtaguid.c
index 3bf0c59..0f5628a 100644
--- a/net/netfilter/xt_qtaguid.c
+++ b/net/netfilter/xt_qtaguid.c
@@ -1814,8 +1814,11 @@
 }
 
 #ifdef DDEBUG
-/* This function is not in xt_qtaguid_print.c because of locks visibility */
-static void prdebug_full_state(int indent_level, const char *fmt, ...)
+/*
+ * This function is not in xt_qtaguid_print.c because of locks visibility.
+ * The lock of sock_tag_list must be aquired before calling this function
+ */
+static void prdebug_full_state_locked(int indent_level, const char *fmt, ...)
 {
 	va_list args;
 	char *fmt_buff;
@@ -1836,16 +1839,12 @@
 	kfree(buff);
 	va_end(args);
 
-	spin_lock_bh(&sock_tag_list_lock);
 	prdebug_sock_tag_tree(indent_level, &sock_tag_tree);
-	spin_unlock_bh(&sock_tag_list_lock);
 
-	spin_lock_bh(&sock_tag_list_lock);
 	spin_lock_bh(&uid_tag_data_tree_lock);
 	prdebug_uid_tag_data_tree(indent_level, &uid_tag_data_tree);
 	prdebug_proc_qtu_data_tree(indent_level, &proc_qtu_data_tree);
 	spin_unlock_bh(&uid_tag_data_tree_lock);
-	spin_unlock_bh(&sock_tag_list_lock);
 
 	spin_lock_bh(&iface_stat_list_lock);
 	prdebug_iface_stat_list(indent_level, &iface_stat_list);
@@ -1854,7 +1853,7 @@
 	pr_debug("qtaguid: %s(): }\n", __func__);
 }
 #else
-static void prdebug_full_state(int indent_level, const char *fmt, ...) {}
+static void prdebug_full_state_locked(int indent_level, const char *fmt, ...) {}
 #endif
 
 struct proc_ctrl_print_info {
@@ -1977,8 +1976,11 @@
 			   (u64)atomic64_read(&qtu_events.match_no_sk),
 			   (u64)atomic64_read(&qtu_events.match_no_sk_file));
 
-		/* Count the following as part of the last item_index */
-		prdebug_full_state(0, "proc ctrl");
+		/* Count the following as part of the last item_index. No need
+		 * to lock the sock_tag_list here since it is already locked when
+		 * starting the seq_file operation
+		 */
+		prdebug_full_state_locked(0, "proc ctrl");
 	}
 
 	return 0;
@@ -2887,8 +2889,10 @@
 
 	sock_tag_tree_erase(&st_to_free_tree);
 
-	prdebug_full_state(0, "%s(): pid=%u tgid=%u", __func__,
+	spin_lock_bh(&sock_tag_list_lock);
+	prdebug_full_state_locked(0, "%s(): pid=%u tgid=%u", __func__,
 			   current->pid, current->tgid);
+	spin_unlock_bh(&sock_tag_list_lock);
 	return 0;
 }
 
diff --git a/net/sunrpc/xprtrdma/verbs.c b/net/sunrpc/xprtrdma/verbs.c
index e2c37061..69502fa 100644
--- a/net/sunrpc/xprtrdma/verbs.c
+++ b/net/sunrpc/xprtrdma/verbs.c
@@ -486,7 +486,8 @@
 	struct ib_cq *sendcq, *recvcq;
 	int rc;
 
-	max_sge = min(ia->ri_device->attrs.max_sge, RPCRDMA_MAX_SEND_SGES);
+	max_sge = min_t(unsigned int, ia->ri_device->attrs.max_sge,
+			RPCRDMA_MAX_SEND_SGES);
 	if (max_sge < RPCRDMA_MIN_SEND_SGES) {
 		pr_warn("rpcrdma: HCA provides only %d send SGEs\n", max_sge);
 		return -ENOMEM;
diff --git a/tools/include/linux/log2.h b/tools/include/linux/log2.h
index 4144666..d5677d3 100644
--- a/tools/include/linux/log2.h
+++ b/tools/include/linux/log2.h
@@ -13,12 +13,6 @@
 #define _TOOLS_LINUX_LOG2_H
 
 /*
- * deal with unrepresentable constant logarithms
- */
-extern __attribute__((const, noreturn))
-int ____ilog2_NaN(void);
-
-/*
  * non-constant log of base 2 calculators
  * - the arch may override these in asm/bitops.h if they can be implemented
  *   more efficiently than using fls() and fls64()
@@ -78,7 +72,7 @@
 #define ilog2(n)				\
 (						\
 	__builtin_constant_p(n) ? (		\
-		(n) < 1 ? ____ilog2_NaN() :	\
+		(n) < 2 ? 0 :			\
 		(n) & (1ULL << 63) ? 63 :	\
 		(n) & (1ULL << 62) ? 62 :	\
 		(n) & (1ULL << 61) ? 61 :	\
@@ -141,10 +135,7 @@
 		(n) & (1ULL <<  4) ?  4 :	\
 		(n) & (1ULL <<  3) ?  3 :	\
 		(n) & (1ULL <<  2) ?  2 :	\
-		(n) & (1ULL <<  1) ?  1 :	\
-		(n) & (1ULL <<  0) ?  0 :	\
-		____ilog2_NaN()			\
-				   ) :		\
+		1 ) :				\
 	(sizeof(n) <= 4) ?			\
 	__ilog2_u32(n) :			\
 	__ilog2_u64(n)				\