summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Documentation/devicetree/bindings/leds/leds-qpnp-flash.txt180
-rw-r--r--Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt275
-rw-r--r--Documentation/devicetree/bindings/power/supply/qcom/qpnp-smbcharger.txt394
-rw-r--r--arch/arm/boot/dts/qcom/msm8996-cv2x.dtsi9
-rw-r--r--arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts5
-rw-r--r--arch/arm64/configs/msm-perf_defconfig4
-rw-r--r--arch/arm64/configs/msm_defconfig4
-rw-r--r--block/blk-cgroup.c6
-rw-r--r--block/blk-core.c37
-rw-r--r--block/blk-integrity.c4
-rw-r--r--block/blk-sysfs.c6
-rw-r--r--block/genhd.c6
-rw-r--r--drivers/block/aoe/aoeblk.c4
-rw-r--r--drivers/block/drbd/drbd_main.c6
-rw-r--r--drivers/block/drbd/drbd_nl.c8
-rw-r--r--drivers/block/drbd/drbd_proc.c2
-rw-r--r--drivers/block/drbd/drbd_req.c2
-rw-r--r--drivers/block/pktcdvd.c4
-rw-r--r--drivers/block/rbd.c2
-rw-r--r--drivers/char/diag/diag_debugfs.c7
-rw-r--r--drivers/gpu/drm/msm/sde/sde_core_perf.c112
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.c16
-rw-r--r--drivers/gpu/drm/msm/sde/sde_crtc.h7
-rw-r--r--drivers/leds/Kconfig11
-rw-r--r--drivers/leds/Makefile1
-rw-r--r--drivers/leds/leds-qpnp-flash.c2683
-rw-r--r--drivers/md/bcache/request.c10
-rw-r--r--drivers/md/bcache/super.c8
-rw-r--r--drivers/md/dm-cache-target.c2
-rw-r--r--drivers/md/dm-era-target.c2
-rw-r--r--drivers/md/dm-table.c2
-rw-r--r--drivers/md/dm-thin.c2
-rw-r--r--drivers/md/dm.c6
-rw-r--r--drivers/md/linear.c2
-rw-r--r--drivers/md/md.c6
-rw-r--r--drivers/md/multipath.c2
-rw-r--r--drivers/md/raid0.c6
-rw-r--r--drivers/md/raid1.c4
-rw-r--r--drivers/md/raid10.c10
-rw-r--r--drivers/md/raid5.c12
-rw-r--r--drivers/power/supply/qcom/Kconfig18
-rw-r--r--drivers/power/supply/qcom/Makefile2
-rw-r--r--drivers/power/supply/qcom/qpnp-fg.c7051
-rw-r--r--drivers/power/supply/qcom/qpnp-smb2.c18
-rw-r--r--drivers/power/supply/qcom/qpnp-smbcharger.c8472
-rw-r--r--drivers/power/supply/qcom/smb-lib.c162
-rw-r--r--drivers/power/supply/qcom/smb-lib.h9
-rw-r--r--drivers/power/supply/qcom/smb-reg.h1
-rw-r--r--fs/block_dev.c25
-rw-r--r--fs/gfs2/ops_fstype.c2
-rw-r--r--fs/nilfs2/super.c2
-rw-r--r--fs/super.c2
-rw-r--r--include/linux/backing-dev-defs.h2
-rw-r--r--include/linux/backing-dev.h10
-rw-r--r--include/linux/blkdev.h2
-rw-r--r--include/linux/fs.h2
-rw-r--r--mm/backing-dev.c34
-rw-r--r--mm/page-writeback.c4
-rw-r--r--sound/soc/msm/Kconfig29
-rw-r--r--sound/soc/msm/Makefile6
-rw-r--r--sound/soc/msm/qdsp6v2/Makefile7
-rw-r--r--sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c7
-rw-r--r--sound/usb/mixer.c1
63 files changed, 19587 insertions, 150 deletions
diff --git a/Documentation/devicetree/bindings/leds/leds-qpnp-flash.txt b/Documentation/devicetree/bindings/leds/leds-qpnp-flash.txt
new file mode 100644
index 000000000000..ed1ddf597016
--- /dev/null
+++ b/Documentation/devicetree/bindings/leds/leds-qpnp-flash.txt
@@ -0,0 +1,180 @@
+Qualcomm Technologies PNP Flash LED
+
+QPNP (Qualcomm Technologies Plug N Play) Flash LED (Light
+Emitting Diode) driver is used to provide illumination to
+camera sensor when background light is dim to capture good
+picture. It can also be used for flashlight/torch application.
+It is part of PMIC on Qualcomm Technologies reference platforms.
+The PMIC is connected to the host processor via SPMI bus.
+
+Required properties:
+- compatible : should be "qcom,qpnp-flash-led"
+- reg : base address and size for flash LED modules
+
+Optional properties:
+- qcom,headroom : headroom to use. Values should be 250, 300,
+ 400 and 500 in mV.
+- qcom,startup-dly : delay before flashing after flash executed.
+ Values should 10, 32, 64, and 128 in us.
+- qcom,clamp-curr : current to clamp at when voltage droop happens.
+ Values are in integer from 0 to 1000 inclusive,
+ indicating 0 to 1000 mA.
+- qcom,self-check-enabled : boolean type. self fault check enablement
+- qcom,thermal-derate-enabled : boolean type. derate enablement when module
+ temperature reaches threshold
+- qcom,thermal-derate-threshold : thermal threshold for derate. Values
+ should be 95, 105, 115, 125 in C.
+- qcom,thermal-derate-rate : derate rate when module temperature
+ reaches threshold. Values should be
+ "1_PERCENT", "1P25_PERCENT", "2_PERCENT",
+ "2P5_PERCENT", "5_PERCENT" in string.
+- qcom,current-ramp-enabled : boolean type. stepped current ramp enablement
+- qcom,ramp-up-step : current ramp up rate. Values should be
+ "0P2US", "0P4US", "0P8US", "1P6US", "3P3US",
+ "6P7US", "13P5US", "27US".
+- qcom,ramp-dn-step : current ramp down rate. Values should be
+ "0P2US", "0P4US", "0P8US", "1P6US", "3P3US",
+ "6P7US", "13P5US", "27US".
+- qcom,vph-pwr-droop-enabled : boolean type. VPH power droop enablement. Enablement
+ allows current clamp when phone power drops below
+ pre-determined threshold
+- qcom,vph-pwr-droop-threshold : VPH power threshold for module to clamp current.
+ Values are 2500 - 3200 in mV with 100 mV steps.
+- qcom,vph-pwr-droop-debounce-time : debounce time for module to confirm a voltage
+ droop is happening. Values are 0, 10, 32, 64
+ in us.
+- qcom,pmic-charger-support : Boolean type. This tells if flash utilizes charger boost
+ support
+- qcom,headroom-sense-ch0-enabled: Boolean type. This configures headroom sensing enablement
+ for LED channel 0
+- qcom,headroom-sense-ch1-enabled: Boolean type. This configures headroom sensing enablement
+ for LED channel 1
+- qcom,power-detect-enabled : Boolean type. This enables driver to get maximum flash LED
+ current at current battery level to avoid intensity clamp
+ when battery voltage is low
+- qcom,otst2-moduled-enabled : Boolean type. This enables driver to enable MASK to support
+ OTST2 connection.
+- qcom,follow-otst2-rb-disabled : Boolean type. This allows driver to reset/deset module.
+ By default, driver resets module. This entry allows driver to
+ bypass reset module sequence.
+- qcom,die-current-derate-enabled: Boolean type. This enables driver to get maximum flash LED
+ current, based on PMIC die temperature threshold to
+ avoid significant current derate from hardware. This property
+ is not needed if PMIC is older than PMI8994v2.0.
+- qcom,die-temp-vadc : VADC channel source for flash LED. This property is not
+ needed if PMIC is older than PMI8994v2.0.
+- qcom,die-temp-threshold : Integer type array for PMIC die temperature threshold.
+ Array should have at least one value. Values should be in
+ celcius. This property is not needed if PMIC is older than
+ PMI8994v2.0.
+- qcom,die-temp-derate-current : Integer type arrray for PMIC die temperature derate
+ current. Array should have at least one value. Values
+ should be in mA. This property is not needed if PMIC is older
+ than PMI8994v2.0.
+
+Required properties inside child node. Chile node contains settings for each individual LED.
+Each LED hardware needs a node for itself and a switch node to control brightness.
+For the purpose of turning on/off LED and better regulator control, "led:switch" node
+is introduced. "led:switch" acquires several existing properties from other nodes for
+operational simplification. For backward compatibility purpose, switch node can be optional:
+- label : type of led that will be used, either "flash" or "torch".
+- qcom,led-name : name of the LED. Accepted values are "led:flash_0",
+ "led:flash_1", "led:torch_0", "led:torch_1"
+- qcom,default-led-trigger : trigger for the camera flash and torch. Accepted values are
+ "flash0_trigger", "flash1_trigger", "torch0_trigger", torch1_trigger"
+- qcom,id : enumerated ID for each physical LED. Accepted values are "0",
+ "1", etc..
+- qcom,max-current : maximum current allowed on this LED. Valid values should be
+ integer from 0 to 1000 inclusive, indicating 0 to 1000 mA.
+- qcom,pmic-revid : PMIC revision id source. This property is needed for PMI8996
+ revision check.
+
+Optional properties inside child node:
+- qcom,current : default current intensity for LED. Accepted values should be
+ integer from 0 t 1000 inclusive, indicating 0 to 1000 mA.
+- qcom,duration : Duration for flash LED. When duration time expires, hardware will turn off
+ flash LED. Values should be from 10 ms to 1280 ms with 10 ms incremental
+ step. Not applicable to torch. It is required for LED:SWITCH node to handle
+ LED used as flash.
+- reg<n> : reg<n> (<n> represents number. eg 0,1,2,..) property is to add support for
+ multiple power sources. It includes two properties regulator-name and max-voltage.
+ Required property inside regulator node:
+ - regulator-name : This denotes this node is a regulator node and which
+ regulator to use.
+ Optional property inside regulator node:
+ - max-voltage : This specifies max voltage of regulator. Some switch
+ or boost regulator does not need this property.
+
+Example:
+ qcom,leds@d300 {
+ compatible = "qcom,qpnp-flash-led";
+ status = "okay";
+ reg = <0xd300 0x100>;
+ label = "flash";
+ qcom,headroom = <500>;
+ qcom,startup-dly = <128>;
+ qcom,clamp-curr = <200>;
+ qcom,pmic-charger-support;
+ qcom,self-check-enabled;
+ qcom,thermal-derate-enabled;
+ qcom,thermal-derate-threshold = <80>;
+ qcom,thermal-derate-rate = "4_PERCENT";
+ qcom,current-ramp-enabled;
+ qcom,ramp_up_step = "27US";
+ qcom,ramp_dn_step = "27US";
+ qcom,vph-pwr-droop-enabled;
+ qcom,vph-pwr-droop-threshold = <3200>;
+ qcom,vph-pwr-droop-debounce-time = <10>;
+ qcom,headroom-sense-ch0-enabled;
+ qcom,headroom-sense-ch1-enabled;
+ qcom,die-current-derate-enabled;
+ qcom,die-temp-vadc = <&pmi8994_vadc>;
+ qcom,die-temp-threshold = <85 80 75 70 65>;
+ qcom,die-temp-derate-current = <400 800 1200 1600 2000>;
+ qcom,pmic-revid = <&pmi8994_revid>;
+
+ pm8226_flash0: qcom,flash_0 {
+ label = "flash";
+ qcom,led-name = "led:flash_0";
+ qcom,default-led-trigger =
+ "flash0_trigger";
+ qcom,max-current = <1000>;
+ qcom,id = <0>;
+ qcom,duration = <1280>;
+ qcom,current = <625>;
+ };
+
+ pm8226_torch: qcom,torch_0 {
+ label = "torch";
+ qcom,led-name = "led:torch_0";
+ qcom,default-led-trigger =
+ "torch0_trigger";
+ boost-supply = <&pm8226_chg_boost>;
+ qcom,max-current = <200>;
+ qcom,id = <0>;
+ qcom,current = <120>;
+ qcom,max-current = <200>;
+ reg0 {
+ regulator-name =
+ "pm8226_chg_boost";
+ max-voltage = <3600000>;
+ };
+ };
+
+ pm8226_switch: qcom,switch {
+ lable = "switch";
+ qcom,led-name = "led:switch";
+ qcom,default-led-trigger =
+ "switch_trigger";
+ qcom,id = <2>;
+ qcom,current = <625>;
+ qcom,duration = <1280>;
+ qcom,max-current = <1000>;
+ reg0 {
+ regulator-name =
+ "pm8226_chg_boost";
+ max-voltage = <3600000>;
+ };
+ };
+ };
+
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt
new file mode 100644
index 000000000000..f6a7a1ba3005
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-fg.txt
@@ -0,0 +1,275 @@
+QTI's QPNP PMIC Fuel Gauge Device
+
+QPNP PMIC FG provides interface to clients to read properties related
+to the battery. Its main function is to retrieve the State of Charge (SOC),
+a 0-100 percentage representing the amount of charge left in the battery.
+
+There are two required peripherals in the FG driver, both implemented as
+subnodes in the example. These peripherals must not be disabled if the FG
+device is to enabled:
+
+- qcom,fg-soc : The main FG device. Supports battery fuel gauge controls and
+ sensors.
+- qcom,fg-batt : The FG battery device supports interrupts and controls with
+ respect to the state of the connected battery.For example: the
+ peripheral informs the driver if the battery has been identified
+ by the fuel gauge based on a given battery resistance range.
+
+Optionally ADC nodes can be added
+- qcom,revid-tp-rev: A subnode with a register address for the TP_REV register
+ in the REVID peripheral. This is used to apply workarounds that
+ may depend on the trim program.
+- qcom,fg-adc-vbat : A subnode with a register address for the FG_ADC_USR
+ peripheral which is used mainly for battery current limiting (BCL).
+ This node maps out the VBAT reading register which allows to have
+ a +/- 32 mV accurate reading of VBAT.
+- qcom,fg-adc-ibat : A subnode with a register address for the FG_ADC_USR
+ peripheral which is used mainly for battery current limiting (BCL).
+ This node maps out the IBAT current reading register which allows
+ to have a +/- 32 mA accurate reading of IBAT.
+
+Parent node required properties:
+- compatible : should be "qcom,qpnp-fg" for the FG driver.
+- qcom,pmic-revid : Should specify the phandle of PMIC
+ revid module. This is used to identify
+ the PMIC subtype.
+
+Parent node optional properties:
+- qcom,warm-bat-decidegc: Warm battery temperature in decidegC.
+- qcom,cool-bat-decidegc: Cool battery temperature in decidegC.
+- qcom,hot-bat-decidegc: Hot battery temperature in decidegC.
+- qcom,cold-bat-decidegc: Cold battery temperature in decidegC.
+- qcom,cold-hot-jeita-hysteresis: A tuple of 2. Index[0] is cold
+ hysteresis and index[1] is hot
+ hysterisis(in decidegC).
+- qcom,ext-sense-type: Current sense channel used by the FG.
+ Set this to use external rsense.
+- qcom,thermal-coefficients: Byte array of thermal coefficients for
+ reading battery thermistor. This should
+ be exactly 6 bytes in length.
+ Example: [01 02 03 04 05 06]
+- qcom,resume-soc: soc to resume charging in percentage.
+- qcom,resume-soc-raw: soc to resume charging in the scale of
+ [0-255]. This overrides qcom,resume-soc
+ if defined.
+- qcom,hold-soc-while-full: A boolean property that when defined
+ holds SOC at 100% when the battery is
+ full.
+- qcom,bcl-lm-threshold-ma: BCL LPM to MPM mode transition threshold
+ in milliAmpere.
+- qcom,bcl-mh-threshold-ma: BCL MPM to HPM mode transition threshold
+ in milliAmpere.
+- qcom,use-otp-profile: Specify this flag to avoid RAM loading
+ any battery profile.
+- qcom,sw-rbias-control: Boolean property which defines whether
+ the Rbias needs to be controlled by
+ software. If this is not set, it will
+ be controlled by hardware (default).
+- qcom,fg-iterm-ma: Battery current at which the fuel gauge
+ will try to scale 100% towards. When
+ the charge current goes above this, the
+ SoC should be at 100%.
+- qcom,fg-chg-iterm-ma: Battery current at which the fuel gauge
+ will issue end of charge if the charger
+ is configured to use the fuel gauge
+ ADCs for end of charge detection. This
+ property is in milliamps and should be
+ positive (e.g. 100mA to terminate at
+ -100mA).
+- qcom,irq-volt-empty-mv: The voltage threshold that the empty
+ soc interrupt will be triggered. When
+ the empty soc interrupt fires, battery
+ soc will be pulled to 0 and the
+ userspace will be notified via the
+ power supply framework. The userspace
+ will read 0% soc and immediately
+ shutdown.
+- qcom,fg-cutoff-voltage-mv: The voltage 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.
+- qcom,fg-vbat-estimate-diff-mv: If the estimated voltage based on SoC
+ and battery current/resistance differs
+ from the actual voltage by more than
+ this amount, the fuel gauge will
+ redo the first SoC estimate when the
+ driver probes.
+- qcom,fg-delta-soc: How many percent the monotonic SoC must
+ change before a new delta_soc interrupt
+ is asserted. If this value is raised
+ above 3-4, some period workarounds may
+ not function well, so it's best to
+ leave this at 1 or 2%.
+- qcom,fg-vbatt-low-threshold: Voltage (in mV) which upon set will be
+ used for configuring the low battery
+ voltage threshold. Interrupt will be
+ asserted and handled based upon
+ this. If this property is not specified,
+ low battery voltage threshold will be
+ configured to 4200 mV.
+- qcom,cycle-counter-en: Boolean property which enables the cycle
+ counter feature. If this property is
+ present, then the following properties
+ to specify low and high soc thresholds
+ should be defined.
+- qcom,capacity-learning-on: A boolean property to have the fuel
+ gauge driver attempt to learn the
+ battery capacity when charging. Takes
+ precedence over capacity-estimation-on.
+- qcom,capacity-learning-feedback: A boolean property to have the fuel
+ gauge driver to feedback the learned
+ capacity into the capacity learning
+ algorithm. This has to be used only if
+ the property "qcom,capacity-learning-on"
+ is specified.
+- qcom,cl-max-increment-deciperc: The maximum percent that the capacity
+ can rise as the result of a single
+ charge cycle. This property corresponds
+ to .1% increments.
+- qcom,cl-max-decrement-deciperc: The maximum percent that the capacity
+ can fall as the result of a single
+ charge cycle. This property corresponds
+ to .1% decrements.
+- qcom,cl-max-temp-decidegc: Above this temperature, capacity
+ learning will be canceled.
+- qcom,cl-mix-temp-decidegc: Below this temperature, capacity
+ learning will be canceled.
+- qcom,cl-max-start-soc: The battery soc has to be below this
+ value at the start of a charge cycle
+ for capacity learning to be run.
+- qcom,cl-vbat-est-thr-uv: The maximum difference between the
+ battery voltage shadow and the current
+ predicted voltage in uV to initiate
+ capacity learning.
+- qcom,capacity-estimation-on: A boolean property to have the fuel
+ gauge driver attempt to estimate the
+ battery capacity using battery
+ resistance.
+- qcom,aging-eval-current-ma: Current used to evaluate battery aging.
+ This value should be around the steady
+ state current drawn from the battery
+ when the phone is low on battery.
+- qcom,fg-cc-cv-threshold-mv: Voltage threshold in mV for configuring
+ constant charge (CC) to constant
+ voltage (CV) setpoint in FG upon
+ which the battery EOC status will
+ be determined. This value should be
+ 10 mV less than the float voltage
+ configured in the charger.
+ This property should only be specified
+ if "qcom,autoadjust-vfloat" property is
+ specified in the charger driver to
+ ensure a proper operation.
+- qcom,bad-battery-detection-enable: A boolean property to enable the fuel
+ gauge driver to detect the damaged battery
+ when the safety-timer expires by using the
+ coulomb count.
+- qcom,fg-therm-delay-us: The time in microseconds to delay battery
+ thermistor biasing.
+- qcom,esr-pulse-tuning-en: A boolean property to enable ESR pulse
+ tuning feature. If this is enabled,
+ ESR pulse extraction will be disabled
+ when state of charge (SOC) is less than
+ 2%. It will be enabled back when SOC
+ gets above 2%. In addition, for SOC
+ between 2% and 5%, ESR pulse timing
+ settings will be different from default.
+ Once SOC crosses 5%, ESR pulse timings
+ will be restored back to default.
+
+qcom,fg-soc node required properties:
+- reg : offset and length of the PMIC peripheral register map.
+- interrupts : the interrupt mappings.
+ The format should be
+ <slave-id peripheral-id interrupt-number>.
+- interrupt-names : names for the mapped fg soc interrupts
+ The following interrupts are required:
+ 0: high-soc
+ 1: low-soc
+ 2: full-soc
+ 3: empty-soc
+ 4: delta-soc
+ 5: first-est-done
+ 6: sw-fallbk-ocv
+ 7: sw-fallbk-new-batt
+
+qcom,fg-memif node required properties:
+- reg : offset and length of the PMIC peripheral register map.
+- interrupts : the interrupt mappings.
+ The format should be
+ <slave-id peripheral-id interrupt-number>.
+- interrupt-names : names for the mapped fg adc interrupts
+ The following interrupts are required:
+ 0: mem-avail
+
+Example:
+pmi8994_fg: qcom,fg {
+ compatible = "qcom,qpnp-fg";
+ #address-cells = <1>;
+ #size-cells = <1>;
+ status = "disabled";
+ qcom,pmic-revid = <&pmi8994_revid>;
+
+ qcom,fg-soc@4000 {
+ reg = <0x4000 0x100>;
+ interrupts = <0x2 0x40 0x0>,
+ <0x2 0x40 0x1>,
+ <0x2 0x40 0x2>,
+ <0x2 0x40 0x3>,
+ <0x2 0x40 0x4>,
+ <0x2 0x40 0x5>,
+ <0x2 0x40 0x6>,
+ <0x2 0x40 0x7>;
+
+ interrupt-names = "high-soc",
+ "low-soc",
+ "full-soc",
+ "empty-soc",
+ "delta-soc",
+ "first-est-done",
+ "sw-fallbk-ocv",
+ "sw-fallbk-new-batt";
+ };
+
+ qcom,fg-batt@4100 {
+ reg = <0x4100 0x100>;
+ interrupts = <0x2 0x41 0x0>,
+ <0x2 0x41 0x1>,
+ <0x2 0x41 0x2>,
+ <0x2 0x41 0x3>,
+ <0x2 0x41 0x4>,
+ <0x2 0x41 0x5>,
+ <0x2 0x41 0x6>,
+ <0x2 0x41 0x7>;
+
+ interrupt-names = "soft-cold",
+ "soft-hot",
+ "vbatt-low",
+ "batt-ided",
+ "batt-id-req",
+ "batt-unknown",
+ "batt-missing",
+ "batt-match";
+ };
+
+ qcom,fg-adc-vbat@4254 {
+ reg = <0x4254 0x1>;
+ };
+
+ qcom,fg-adc-ibat@4255 {
+ reg = <0x4255 0x1>;
+ };
+
+ qcom,fg-memif@4400 {
+ reg = <0x4400 0x100>;
+ interrupts = <0x2 0x44 0x0>,
+ <0x2 0x44 0x1>;
+
+ interrupt-names = "mem-avail",
+ "data-rcvry-sug";
+
+ qcom,cold-hot-jeita-hysteresis = <30 50>;
+ };
+};
diff --git a/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smbcharger.txt b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smbcharger.txt
new file mode 100644
index 000000000000..efd64cd90878
--- /dev/null
+++ b/Documentation/devicetree/bindings/power/supply/qcom/qpnp-smbcharger.txt
@@ -0,0 +1,394 @@
+QPNP SMB Battery Charger
+
+QPNP SMB Charger 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 QPNP SMB Charger interfaces via the SPMI bus.
+
+There are six different peripherals adding the following functionality.
+Each of these peripherals are implemented as subnodes in the example at the
+end of this file.
+
+- qcom,chgr: Supports charging control and status
+ reporting.
+- qcom,bat-if: Battery status reporting such as presence,
+ temperature reporting and voltage collapse
+ protection.
+- qcom,usb-chgpth: USB charge path detection and input current
+ limiting configuration.
+- qcom,dc-chgpth: DC charge path detection and input current
+ limiting configuration.
+- qcom,chg-misc: Miscellaneous features such as watchdog timers
+ and SYSOK pin control
+- qcom,chg-otg: OTG configuration control.
+
+Parent node required properties:
+- compatible: Must be "qcom,qpnp-smbcharger"
+- #address-cells: Must be <1>
+- #size-cells: Must be <1>
+- qcom,pmic-revid: Should specify the phandle of PMIC
+ revid module. This is used to identify
+ the PMIC subtype.
+
+
+
+Sub node required properties:
+- reg: The SPMI address for this peripheral
+- interrupts: Specifies the interrupt associated with the peripheral.
+- interrupt-names: Specifies the interrupt names for the peripheral. Every
+ available interrupt needs to have an associated name
+ with it to indentify its purpose.
+
+ The following lists each subnode and their corresponding
+ required interrupt names:
+
+ qcom,chgr:
+ - chg-tcc-thr: Triggers on charge completion.
+ - chg-taper-thr: Triggers on the taper charge
+ transtion.
+ - chg-inhibit: Notifies on battery voltage
+ being too high to resume
+ charging.
+ - chg-p2f-thr: Triggers on transitioning from
+ precharge to fastcharge.
+ - chg-rechg-thr: Triggers on battery voltage
+ falling below the resume
+ threshold.
+
+ qcom,bat-if:
+ - batt-hot: Triggers on battery temperature
+ hitting the hot threshold.
+ Charging stops.
+ - batt-warm: Triggers on battery temperature
+ hitting the warm threshold.
+ Charging current is reduced.
+ - batt-cool: Triggers on battery temperature
+ hitting the cool threshold.
+ Charging current is reduced
+ - batt-cold: Triggers on battery temperature
+ hitting the cold threshold.
+ Charging stops.
+ - batt-missing: Battery missing status
+ interrupt.
+ - batt-low: Triggers on battery voltage
+ falling across a low threshold.
+
+ qcom,usb-chgpth:
+ - usbin-uv: USB input voltage falls below a
+ valid threshold.
+ - usbin-src-det: USB automatic source detection
+ finishes.
+
+ qcom,dc-chgpth:
+ - dcin-uv: DC input voltage falls below a
+ valid threshold.
+
+ qcom,chgr-misc:
+ - wdog-timeout-mins: Charger watchdog timer
+ interrupt.
+ - temp-shutdown: Triggers when charger goes
+ overtemp and causes a shutdown.
+ - power-ok: Triggers when the charger
+ switcher turns on or off.
+
+Regulator Subnodes:
+- qcom,smbcharger-boost-otg A subnode for a regulator device that turns on
+ the charger boost for OTG operation.
+- qcom,smbcharger-external-otg A subnode for a regulator device that switches
+ off charging and the USB input charge path
+ in order to allow an external regulator to
+ operate. This can be used in place of the
+ qcom,smbcharger-boost-otg if an external boost
+ is available.
+
+Regulator Sub node required properties:
+- regulator-name A name string for the regulator in question
+
+Optional Properties:
+- qcom,battery-psy-name The name of the main battery power supply that
+ the charger will register. Failing to define
+ this property will default the name to
+ "battery".
+- qcom,bms-psy-name The psy name to use for reporting battery
+ capacity. If left unspecified the capacity uses
+ a preprogrammed default value of 50.
+- qcom,float-voltage-mv Float Voltage in mV - the maximum voltage up
+ to which the battery is charged. Supported
+ range 3600mV to 4500mV
+- qcom,float-voltage-comp Specifies the JEITA float voltage compensation.
+ Value ranges from 0 to 63.
+- qcom,fastchg-current-ma Specifies the fast charge current in mA. Supported
+ range is from 300mA to 3000mA.
+- qcom,fastchg-current-comp Specifies the fast charge current compensation in
+ mA. Supported values are 250, 700, 900 and 1200mA.
+- qcom,charging-timeout-mins 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,precharging-timeout-mins Maximum duration in minutes that a single
+ precharge cycle may last. Supported values
+ are: 0, 24, 48, 96, 192. A value of 0 means
+ that no precharge cycle timeout is used and
+ charging can continue indefinitely. Note that
+ the qcom,charging-timeout-mins property must
+ be specified in order for this to take effect.
+- qcom,dc-psy-type The type of charger connected to the DC path.
+ Can be "Mains", "Wireless" or "Wipower"
+- 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,dcin-vadc The phandle to pmi8994 voltage adc. The ADC is
+ used to get notifications when the DCIN voltage
+ crosses a programmed min/max threshold. This is
+ used to make configurations for optimized power
+ draw for Wipower.
+- qcom,wipower-div2-ilim-map
+- qcom,wipower-pt-ilim-map
+- qcom,wipower-default-ilim-map
+ Array of 5 elements to indicate the voltage ranges and their corresponding
+ current limits. The 5 elements with index [0..4] are:
+ [0] => voltage_low in uV
+ [1] => voltage_high in uV
+ [2] => current limit for pass through in mA
+ [3] => current limit for div2 mode dcin low voltage in mA
+ [4] => current limit for div2 mode dcin high voltage in mA
+ The div2 and pt tables indicate the current limits
+ to use when Wipower is operating in divide_by_2 mode
+ and pass through mode respectively.
+ The default table is used when the voltage ranges
+ are beyond the ones specified in the mapping table.
+ Note that if dcin-vadc or any of these mapping
+ tables are not specified, dynamic dcin input
+ is disabled.
+- qcom,charging-disabled Set this if charging should be disabled in the
+ build by default.
+- qcom,resume-delta-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,chg-inhibit-en Boolean that indicates whether the charge inhibit
+ feature needs to be enabled. If this is not set,
+ charge inhibit feature is disabled by default.
+- qcom,chg-inhibit-fg Indicates if the recharge threshold source has
+ to be Fuel gauge ADC. If this is not set, it
+ will be analog sensor by default.
+- 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,charge-unknown-battery Boolean that indicates whether an unknown
+ battery without a matching profile will be
+ charged. If this is not set, if the fuel gauge
+ does not recognize the battery based on its
+ battery ID, the charger will not start
+ charging.
+- qcom,bmd-pin-src A string that indicates the source pin for the
+ battery missind detection. This can be either:
+ - "bpd_none"
+ battery is considered always present
+ - "bpd_id"
+ battery id pin is used
+ - "bpd_thm"
+ battery therm pin is used
+ - "bpd_thm_id"
+ both pins are used (battery is
+ considered missing if either pin is
+ floating).
+- 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 boolean property.
+- otg-parent-supply A phandle to an external boost regulator for
+ OTG if it exists.
+- qcom,thermal-mitigation: Array of input current limit values for
+ different system thermal mitigation levels.
+ This should be a flat array that denotates the
+ maximum charge current in mA for each thermal
+ level.
+- qcom,rparasitics-uohm: The parasitic resistance of the board following
+ the line from the battery connectors through
+ vph_power. This is used to calculate maximum
+ available current of the battery.
+- qcom,vled-max-uv: The maximum input voltage of the flash leds.
+ This is used to calculate maximum available
+ current of the battery.
+- qcom,autoadjust-vfloat A boolean property that when set, makes the
+ driver automatically readjust vfloat using the
+ fuel gauge ADC readings to make charging more
+ accurate.
+- qcom,jeita-temp-hard-limit property when present will enable or disable
+ the jeita temperature hard limit based on the
+ value 1 or 0. Specify 0 if the jeita temp hard
+ limit needs to be disabled. If it is not present,
+ jeita temperature hard limit will be based on what
+ the bootloader had set earlier.
+- qcom,low-volt-dcin: A boolean property which upon set will enable the
+ AICL deglitch configuration dynamically. This needs
+ to be set if the DCIN supply is going to be less
+ than or equal to 5V.
+- qcom,force-aicl-rerun: A boolean property which upon set will enable the
+ AICL rerun by default along with the deglitch time
+ configured to long interval (20 ms). Also, specifying
+ this property will not adjust the AICL deglitch time
+ dynamically for handling the battery over-voltage
+ oscillations when the charger is headroom limited.
+- qcom,aicl-rerun-period-s If force-aicl-rerun is on, this property dictates
+ how often aicl is reran in seconds. Possible values
+ are 45, 90, 180, and 360.
+- qcom,ibat-ocp-threshold-ua Maximum current before the battery will trigger
+ overcurrent protection. Use the recommended
+ battery pack value minus some margin.
+- qcom,soft-vfloat-comp-disabled Set this property when the battery is
+ powered via external source and could
+ go above the float voltage.
+- qcom,parallel-usb-min-current-ma Minimum current drawn by the primary
+ charger before enabling the parallel
+ charger if one exists. Do not define
+ this property if no parallel chargers
+ exist.
+- qcom,parallel-usb-9v-min-current-ma Minimum current drawn by the primary
+ charger before enabling the parallel
+ charger if one exists. This property
+ applies only for 9V chargers.
+- qcom,parallel-allowed-lowering-ma Acceptable current drop from the initial limit
+ to keep parallel charger activated. If the
+ charger current reduces beyond this threshold
+ parallel charger is disabled. Must be specified
+ if parallel charger is used.
+- qcom,parallel-main-chg-fcc-percent Percentage of the fast charge current allotted to the
+ main charger when parallel charging is enabled and
+ operational. If this property is not defined, the
+ driver defaults to a 50%/50% split between the main
+ and parallel charger.
+- qcom,parallel-main-chg-icl-percent Percentage of the input current allotted to the
+ main charger when parallel charging is enabled and
+ operational. If this property is not defined, the
+ driver defaults to a 60%/40% split between the main
+ and parallel charger.
+- qcom,battery-data Points to the phandle of node which
+ contains the battery-profiles supported
+ by the charger/FG.
+- qcom,chg-led-support A bool property to support the charger led feature.
+- qcom,chg-led-sw-controls A bool property to allow the software to control
+ the charger led without a valid charger.
+- qcom,skip-usb-notification A boolean property to be used when usb gets present
+ and type from other means. Especially true on
+ liquid hardware, where usb presence is detected based on GPIO.
+- qcom,skip-usb-suspend-for-fake-battery A boolean property to skip
+ suspending USB path for fake
+ battery.
+- qcom,vchg_sns-vadc Phandle of the VADC node.
+- qcom,vchg-adc-channel-id The ADC channel to which the VCHG is routed.
+
+Example:
+ qcom,qpnp-smbcharger {
+ compatible = "qcom,qpnp-smbcharger";
+ #address-cells = <1>;
+ #size-cells = <1>;
+
+ qcom,iterm-ma = <100>;
+ qcom,float-voltage-mv = <4200>;
+ qcom,resume-delta-mv = <100>;
+ qcom,bmd-pin-src = "bpd_thm_id";
+ qcom,dc-psy-type = "Mains";
+ qcom,dc-psy-ma = <1500>;
+ qcom,bms-psy-name = "bms";
+ qcom,battery-psy-name = "battery";
+ qcom,thermal-mitigation = <1500 700 600 325>;
+ qcom,vchg_sns-vadc = <&pmi8950_vadc>;
+ qcom,vchg-adc-channel-id = <3>;
+
+ qcom,chgr@1000 {
+ reg = <0x1000 0x100>;
+ interrupts = <0x2 0x10 0x0>,
+ <0x2 0x10 0x1>,
+ <0x2 0x10 0x2>,
+ <0x2 0x10 0x3>,
+ <0x2 0x10 0x4>,
+ <0x2 0x10 0x5>,
+ <0x2 0x10 0x6>,
+ <0x2 0x10 0x7>;
+
+ interrupt-names = "chg-error",
+ "chg-inhibit",
+ "chg-prechg-sft",
+ "chg-complete-chg-sft",
+ "chg-p2f-thr",
+ "chg-rechg-thr",
+ "chg-taper-thr",
+ "chg-tcc-thr";
+ };
+
+ qcom,otg@1100 {
+ reg = <0x1100 0x100>;
+ };
+
+ qcom,bat-if@1200 {
+ reg = <0x1200 0x100>;
+ interrupts = <0x2 0x12 0x0>,
+ <0x2 0x12 0x1>,
+ <0x2 0x12 0x2>,
+ <0x2 0x12 0x3>,
+ <0x2 0x12 0x4>,
+ <0x2 0x12 0x5>,
+ <0x2 0x12 0x6>,
+ <0x2 0x12 0x7>;
+
+ interrupt-names = "batt-hot",
+ "batt-warm",
+ "batt-cold",
+ "batt-cool",
+ "batt-ov",
+ "batt-low",
+ "batt-missing",
+ "batt-term-missing";
+ };
+
+ qcom,usb-chgpth@1300 {
+ reg = <0x1300 0x100>;
+ interrupts = <0x2 0x13 0x0>,
+ <0x2 0x13 0x1>,
+ <0x2 0x13 0x2>,
+ <0x2 0x13 0x3>,
+ <0x2 0x13 0x4>,
+ <0x2 0x13 0x5>,
+ <0x2 0x13 0x6>;
+
+ interrupt-names = "usbin-uv",
+ "usbin-ov",
+ "usbin-src-det",
+ "otg-fail",
+ "otg-oc",
+ "aicl-done",
+ "usbid-change";
+ };
+
+ qcom,dc-chgpth@1400 {
+ reg = <0x1400 0x100>;
+ interrupts = <0x2 0x14 0x0>,
+ <0x2 0x14 0x1>;
+
+ interrupt-names = "dcin-uv",
+ "dcin-ov";
+ };
+
+ qcom,chgr-misc@1600 {
+ reg = <0x1600 0x100>;
+ interrupts = <0x2 0x16 0x0>,
+ <0x2 0x16 0x1>,
+ <0x2 0x16 0x2>,
+ <0x2 0x16 0x3>,
+ <0x2 0x16 0x4>,
+ <0x2 0x16 0x5>;
+
+ interrupt-names = "power-ok",
+ "temp-shutdown",
+ "wdog-timeout",
+ "flash-fail",
+ "otst2",
+ "otst3";
+ };
+ };
diff --git a/arch/arm/boot/dts/qcom/msm8996-cv2x.dtsi b/arch/arm/boot/dts/qcom/msm8996-cv2x.dtsi
index f1cf3136dbd0..ebf596202ad1 100644
--- a/arch/arm/boot/dts/qcom/msm8996-cv2x.dtsi
+++ b/arch/arm/boot/dts/qcom/msm8996-cv2x.dtsi
@@ -477,6 +477,15 @@
qcom,invert = <1>; /* Output high */
status = "okay";
};
+
+ mpp@a500 { /* MPP 6 */
+ qcom,mode = <1>; /* Digital output */
+ qcom,output-type = <0>; /* CMOS logic */
+ qcom,vin-sel = <2>; /* S4 1.8V */
+ qcom,src-sel = <0>; /* Constant */
+ qcom,master-en = <1>; /* Enable GPIO */
+ status = "okay";
+ };
};
&pm8994_vadc {
diff --git a/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts b/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts
index 8e0cb4e4efaf..67e7e6131bb1 100644
--- a/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts
+++ b/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts
@@ -184,6 +184,11 @@
iommus = <&lpass_q6_smmu 1>;
};
+ qcom,msm-audio-ion-vm {
+ compatible = "qcom,msm-audio-ion-vm";
+ qcom,smmu-enabled;
+ };
+
pcm0: qcom,msm-pcm {
compatible = "qcom,msm-pcm-dsp";
qcom,msm-pcm-dsp-id = <0>;
diff --git a/arch/arm64/configs/msm-perf_defconfig b/arch/arm64/configs/msm-perf_defconfig
index 61418724b897..842f495bb17e 100644
--- a/arch/arm64/configs/msm-perf_defconfig
+++ b/arch/arm64/configs/msm-perf_defconfig
@@ -330,6 +330,8 @@ CONFIG_POWER_RESET_QCOM=y
CONFIG_QCOM_DLOAD_MODE=y
CONFIG_POWER_RESET_XGENE=y
CONFIG_POWER_RESET_SYSCON=y
+CONFIG_QPNP_SMBCHARGER=y
+CONFIG_QPNP_FG=y
CONFIG_SMB135X_CHARGER=y
CONFIG_SMB1351_USB_CHARGER=y
CONFIG_MSM_BCL_CTL=y
@@ -461,7 +463,7 @@ CONFIG_MMC_SDHCI=y
CONFIG_MMC_SDHCI_PLTFM=y
CONFIG_MMC_SDHCI_MSM=y
CONFIG_LEDS_QPNP=y
-CONFIG_LEDS_QPNP_FLASH_V2=y
+CONFIG_LEDS_QPNP_FLASH=y
CONFIG_LEDS_QPNP_WLED=y
CONFIG_LEDS_TRIGGERS=y
CONFIG_SWITCH=y
diff --git a/arch/arm64/configs/msm_defconfig b/arch/arm64/configs/msm_defconfig
index ee2b9fa628ff..38f8092e7d8a 100644
--- a/arch/arm64/configs/msm_defconfig
+++ b/arch/arm64/configs/msm_defconfig
@@ -317,6 +317,8 @@ CONFIG_POWER_RESET_QCOM=y
CONFIG_QCOM_DLOAD_MODE=y
CONFIG_POWER_RESET_XGENE=y
CONFIG_POWER_RESET_SYSCON=y
+CONFIG_QPNP_SMBCHARGER=y
+CONFIG_QPNP_FG=y
CONFIG_SMB135X_CHARGER=y
CONFIG_SMB1351_USB_CHARGER=y
CONFIG_MSM_BCL_CTL=y
@@ -449,7 +451,7 @@ CONFIG_MMC_SPI=y
CONFIG_MMC_DW=y
CONFIG_MMC_DW_EXYNOS=y
CONFIG_LEDS_QPNP=y
-CONFIG_LEDS_QPNP_FLASH_V2=y
+CONFIG_LEDS_QPNP_FLASH=y
CONFIG_LEDS_QPNP_WLED=y
CONFIG_LEDS_SYSCON=y
CONFIG_LEDS_TRIGGERS=y
diff --git a/block/blk-cgroup.c b/block/blk-cgroup.c
index 8161090a1970..37ade035c956 100644
--- a/block/blk-cgroup.c
+++ b/block/blk-cgroup.c
@@ -184,7 +184,7 @@ static struct blkcg_gq *blkg_create(struct blkcg *blkcg,
goto err_free_blkg;
}
- wb_congested = wb_congested_get_create(&q->backing_dev_info,
+ wb_congested = wb_congested_get_create(q->backing_dev_info,
blkcg->css.id, GFP_NOWAIT);
if (!wb_congested) {
ret = -ENOMEM;
@@ -468,8 +468,8 @@ static int blkcg_reset_stats(struct cgroup_subsys_state *css,
const char *blkg_dev_name(struct blkcg_gq *blkg)
{
/* some drivers (floppy) instantiate a queue w/o disk registered */
- if (blkg->q->backing_dev_info.dev)
- return dev_name(blkg->q->backing_dev_info.dev);
+ if (blkg->q->backing_dev_info->dev)
+ return dev_name(blkg->q->backing_dev_info->dev);
return NULL;
}
EXPORT_SYMBOL_GPL(blkg_dev_name);
diff --git a/block/blk-core.c b/block/blk-core.c
index 1bc9f212b3a2..56652cd209db 100644
--- a/block/blk-core.c
+++ b/block/blk-core.c
@@ -87,7 +87,7 @@ static void blk_clear_congested(struct request_list *rl, int sync)
* flip its congestion state for events on other blkcgs.
*/
if (rl == &rl->q->root_rl)
- clear_wb_congested(rl->q->backing_dev_info.wb.congested, sync);
+ clear_wb_congested(rl->q->backing_dev_info->wb.congested, sync);
#endif
}
@@ -98,7 +98,7 @@ static void blk_set_congested(struct request_list *rl, int sync)
#else
/* see blk_clear_congested() */
if (rl == &rl->q->root_rl)
- set_wb_congested(rl->q->backing_dev_info.wb.congested, sync);
+ set_wb_congested(rl->q->backing_dev_info->wb.congested, sync);
#endif
}
@@ -122,14 +122,12 @@ void blk_queue_congestion_threshold(struct request_queue *q)
* @bdev: device
*
* Locates the passed device's request queue and returns the address of its
- * backing_dev_info. This function can only be called if @bdev is opened
- * and the return value is never NULL.
+ * backing_dev_info. The return value is never NULL however we may return
+ * &noop_backing_dev_info if the bdev is not currently open.
*/
struct backing_dev_info *blk_get_backing_dev_info(struct block_device *bdev)
{
- struct request_queue *q = bdev_get_queue(bdev);
-
- return &q->backing_dev_info;
+ return bdev->bd_bdi;
}
EXPORT_SYMBOL(blk_get_backing_dev_info);
@@ -597,7 +595,7 @@ void blk_cleanup_queue(struct request_queue *q)
blk_flush_integrity();
/* @q won't process any more request, flush async actions */
- del_timer_sync(&q->backing_dev_info.laptop_mode_wb_timer);
+ del_timer_sync(&q->backing_dev_info->laptop_mode_wb_timer);
blk_sync_queue(q);
if (q->mq_ops)
@@ -697,7 +695,6 @@ static void blk_queue_usage_counter_release(struct percpu_ref *ref)
struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)
{
struct request_queue *q;
- int err;
q = kmem_cache_alloc_node(blk_requestq_cachep,
gfp_mask | __GFP_ZERO, node_id);
@@ -712,17 +709,17 @@ struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)
if (!q->bio_split)
goto fail_id;
- q->backing_dev_info.ra_pages =
+ q->backing_dev_info = bdi_alloc_node(gfp_mask, node_id);
+ if (!q->backing_dev_info)
+ goto fail_split;
+
+ q->backing_dev_info->ra_pages =
(VM_MAX_READAHEAD * 1024) / PAGE_CACHE_SIZE;
- q->backing_dev_info.capabilities = BDI_CAP_CGROUP_WRITEBACK;
- q->backing_dev_info.name = "block";
+ q->backing_dev_info->capabilities = BDI_CAP_CGROUP_WRITEBACK;
+ q->backing_dev_info->name = "block";
q->node = node_id;
- err = bdi_init(&q->backing_dev_info);
- if (err)
- goto fail_split;
-
- setup_timer(&q->backing_dev_info.laptop_mode_wb_timer,
+ setup_timer(&q->backing_dev_info->laptop_mode_wb_timer,
laptop_mode_timer_fn, (unsigned long) q);
setup_timer(&q->timeout, blk_rq_timed_out_timer, (unsigned long) q);
INIT_LIST_HEAD(&q->queue_head);
@@ -772,7 +769,7 @@ struct request_queue *blk_alloc_queue_node(gfp_t gfp_mask, int node_id)
fail_ref:
percpu_ref_exit(&q->q_usage_counter);
fail_bdi:
- bdi_destroy(&q->backing_dev_info);
+ bdi_put(q->backing_dev_info);
fail_split:
bioset_free(q->bio_split);
fail_id:
@@ -1195,7 +1192,7 @@ fail_elvpriv:
* disturb iosched and blkcg but weird is bettern than dead.
*/
printk_ratelimited(KERN_WARNING "%s: dev %s: request aux data allocation failed, iosched may be disturbed\n",
- __func__, dev_name(q->backing_dev_info.dev));
+ __func__, dev_name(q->backing_dev_info->dev));
rq->cmd_flags &= ~REQ_ELVPRIV;
rq->elv.icq = NULL;
@@ -3251,7 +3248,7 @@ void blk_finish_request(struct request *req, int error)
BUG_ON(blk_queued_rq(req));
if (unlikely(laptop_mode) && req->cmd_type == REQ_TYPE_FS)
- laptop_io_completion(&req->q->backing_dev_info);
+ laptop_io_completion(req->q->backing_dev_info);
blk_delete_timer(req);
diff --git a/block/blk-integrity.c b/block/blk-integrity.c
index 478f572cb1e7..e4ebd79de679 100644
--- a/block/blk-integrity.c
+++ b/block/blk-integrity.c
@@ -418,7 +418,7 @@ void blk_integrity_register(struct gendisk *disk, struct blk_integrity *template
bi->tuple_size = template->tuple_size;
bi->tag_size = template->tag_size;
- disk->queue->backing_dev_info.capabilities |= BDI_CAP_STABLE_WRITES;
+ disk->queue->backing_dev_info->capabilities |= BDI_CAP_STABLE_WRITES;
}
EXPORT_SYMBOL(blk_integrity_register);
@@ -431,7 +431,7 @@ EXPORT_SYMBOL(blk_integrity_register);
*/
void blk_integrity_unregister(struct gendisk *disk)
{
- disk->queue->backing_dev_info.capabilities &= ~BDI_CAP_STABLE_WRITES;
+ disk->queue->backing_dev_info->capabilities &= ~BDI_CAP_STABLE_WRITES;
memset(&disk->queue->integrity, 0, sizeof(struct blk_integrity));
}
EXPORT_SYMBOL(blk_integrity_unregister);
diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c
index e140cc487ce1..a113dc1e3eb7 100644
--- a/block/blk-sysfs.c
+++ b/block/blk-sysfs.c
@@ -75,7 +75,7 @@ queue_requests_store(struct request_queue *q, const char *page, size_t count)
static ssize_t queue_ra_show(struct request_queue *q, char *page)
{
- unsigned long ra_kb = q->backing_dev_info.ra_pages <<
+ unsigned long ra_kb = q->backing_dev_info->ra_pages <<
(PAGE_CACHE_SHIFT - 10);
return queue_var_show(ra_kb, (page));
@@ -90,7 +90,7 @@ queue_ra_store(struct request_queue *q, const char *page, size_t count)
if (ret < 0)
return ret;
- q->backing_dev_info.ra_pages = ra_kb >> (PAGE_CACHE_SHIFT - 10);
+ q->backing_dev_info->ra_pages = ra_kb >> (PAGE_CACHE_SHIFT - 10);
return ret;
}
@@ -578,7 +578,7 @@ static void blk_release_queue(struct kobject *kobj)
struct request_queue *q =
container_of(kobj, struct request_queue, kobj);
- bdi_exit(&q->backing_dev_info);
+ bdi_put(q->backing_dev_info);
blkcg_exit_queue(q);
if (q->elevator) {
diff --git a/block/genhd.c b/block/genhd.c
index de2a6162e3f3..53a931b30d78 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -611,7 +611,7 @@ void add_disk(struct gendisk *disk)
disk_alloc_events(disk);
/* Register BDI before referencing it from bdev */
- bdi = &disk->queue->backing_dev_info;
+ bdi = disk->queue->backing_dev_info;
bdi_register_owner(bdi, disk_to_dev(disk));
blk_register_region(disk_devt(disk), disk->minors, NULL,
@@ -646,6 +646,8 @@ void del_gendisk(struct gendisk *disk)
disk_part_iter_init(&piter, disk,
DISK_PITER_INCL_EMPTY | DISK_PITER_REVERSE);
while ((part = disk_part_iter_next(&piter))) {
+ bdev_unhash_inode(MKDEV(disk->major,
+ disk->first_minor + part->partno));
invalidate_partition(disk, part->partno);
delete_partition(disk, part->partno);
}
@@ -661,7 +663,7 @@ void del_gendisk(struct gendisk *disk)
* Unregister bdi before releasing device numbers (as they can
* get reused and we'd get clashes in sysfs).
*/
- bdi_unregister(&disk->queue->backing_dev_info);
+ bdi_unregister(disk->queue->backing_dev_info);
blk_unregister_queue(disk);
} else {
WARN_ON(1);
diff --git a/drivers/block/aoe/aoeblk.c b/drivers/block/aoe/aoeblk.c
index dd73e1ff1759..aadab0381e0d 100644
--- a/drivers/block/aoe/aoeblk.c
+++ b/drivers/block/aoe/aoeblk.c
@@ -396,8 +396,8 @@ aoeblk_gdalloc(void *vp)
WARN_ON(d->gd);
WARN_ON(d->flags & DEVFL_UP);
blk_queue_max_hw_sectors(q, BLK_DEF_MAX_SECTORS);
- q->backing_dev_info.name = "aoe";
- q->backing_dev_info.ra_pages = READ_AHEAD / PAGE_CACHE_SIZE;
+ q->backing_dev_info->name = "aoe";
+ q->backing_dev_info->ra_pages = READ_AHEAD / PAGE_CACHE_SIZE;
d->bufpool = mp;
d->blkq = gd->queue = q;
q->queuedata = d;
diff --git a/drivers/block/drbd/drbd_main.c b/drivers/block/drbd/drbd_main.c
index 1d58854c4a9f..1f9c77609dd1 100644
--- a/drivers/block/drbd/drbd_main.c
+++ b/drivers/block/drbd/drbd_main.c
@@ -2393,7 +2393,7 @@ static int drbd_congested(void *congested_data, int bdi_bits)
if (get_ldev(device)) {
q = bdev_get_queue(device->ldev->backing_bdev);
- r = bdi_congested(&q->backing_dev_info, bdi_bits);
+ r = bdi_congested(q->backing_dev_info, bdi_bits);
put_ldev(device);
if (r)
reason = 'b';
@@ -2765,8 +2765,8 @@ enum drbd_ret_code drbd_create_device(struct drbd_config_context *adm_ctx, unsig
/* we have no partitions. we contain only ourselves. */
device->this_bdev->bd_contains = device->this_bdev;
- q->backing_dev_info.congested_fn = drbd_congested;
- q->backing_dev_info.congested_data = device;
+ q->backing_dev_info->congested_fn = drbd_congested;
+ q->backing_dev_info->congested_data = device;
blk_queue_make_request(q, drbd_make_request);
blk_queue_flush(q, REQ_FLUSH | REQ_FUA);
diff --git a/drivers/block/drbd/drbd_nl.c b/drivers/block/drbd/drbd_nl.c
index e80cbefbc2b5..ef03cb25f5bf 100644
--- a/drivers/block/drbd/drbd_nl.c
+++ b/drivers/block/drbd/drbd_nl.c
@@ -1170,11 +1170,11 @@ static void drbd_setup_queue_param(struct drbd_device *device, struct drbd_backi
blk_queue_stack_limits(q, b);
- if (q->backing_dev_info.ra_pages != b->backing_dev_info.ra_pages) {
+ if (q->backing_dev_info->ra_pages != b->backing_dev_info->ra_pages) {
drbd_info(device, "Adjusting my ra_pages to backing device's (%lu -> %lu)\n",
- q->backing_dev_info.ra_pages,
- b->backing_dev_info.ra_pages);
- q->backing_dev_info.ra_pages = b->backing_dev_info.ra_pages;
+ q->backing_dev_info->ra_pages,
+ b->backing_dev_info->ra_pages);
+ q->backing_dev_info->ra_pages = b->backing_dev_info->ra_pages;
}
}
}
diff --git a/drivers/block/drbd/drbd_proc.c b/drivers/block/drbd/drbd_proc.c
index 3b10fa6cb039..7a6b9f3e1a9f 100644
--- a/drivers/block/drbd/drbd_proc.c
+++ b/drivers/block/drbd/drbd_proc.c
@@ -288,7 +288,7 @@ static int drbd_seq_show(struct seq_file *seq, void *v)
seq_printf(seq, "%2d: cs:Unconfigured\n", i);
} else {
/* reset device->congestion_reason */
- bdi_rw_congested(&device->rq_queue->backing_dev_info);
+ bdi_rw_congested(device->rq_queue->backing_dev_info);
nc = rcu_dereference(first_peer_device(device)->connection->net_conf);
wp = nc ? nc->wire_protocol - DRBD_PROT_A + 'A' : ' ';
diff --git a/drivers/block/drbd/drbd_req.c b/drivers/block/drbd/drbd_req.c
index 3ae2c0086563..17ae4e1ab358 100644
--- a/drivers/block/drbd/drbd_req.c
+++ b/drivers/block/drbd/drbd_req.c
@@ -937,7 +937,7 @@ static bool remote_due_to_read_balancing(struct drbd_device *device, sector_t se
switch (rbm) {
case RB_CONGESTED_REMOTE:
- bdi = &device->ldev->backing_bdev->bd_disk->queue->backing_dev_info;
+ bdi = device->ldev->backing_bdev->bd_disk->queue->backing_dev_info;
return bdi_read_congested(bdi);
case RB_LEAST_PENDING:
return atomic_read(&device->local_cnt) >
diff --git a/drivers/block/pktcdvd.c b/drivers/block/pktcdvd.c
index d06c62eccdf0..f018318d4466 100644
--- a/drivers/block/pktcdvd.c
+++ b/drivers/block/pktcdvd.c
@@ -1276,7 +1276,7 @@ try_next_bio:
&& pd->bio_queue_size <= pd->write_congestion_off);
spin_unlock(&pd->lock);
if (wakeup) {
- clear_bdi_congested(&pd->disk->queue->backing_dev_info,
+ clear_bdi_congested(pd->disk->queue->backing_dev_info,
BLK_RW_ASYNC);
}
@@ -2405,7 +2405,7 @@ static void pkt_make_request_write(struct request_queue *q, struct bio *bio)
spin_lock(&pd->lock);
if (pd->write_congestion_on > 0
&& pd->bio_queue_size >= pd->write_congestion_on) {
- set_bdi_congested(&q->backing_dev_info, BLK_RW_ASYNC);
+ set_bdi_congested(q->backing_dev_info, BLK_RW_ASYNC);
do {
spin_unlock(&pd->lock);
congestion_wait(BLK_RW_ASYNC, HZ);
diff --git a/drivers/block/rbd.c b/drivers/block/rbd.c
index fbdddd6f94b8..55a8671f1979 100644
--- a/drivers/block/rbd.c
+++ b/drivers/block/rbd.c
@@ -3780,7 +3780,7 @@ static int rbd_init_disk(struct rbd_device *rbd_dev)
q->limits.discard_zeroes_data = 1;
if (!ceph_test_opt(rbd_dev->rbd_client->client, NOCRC))
- q->backing_dev_info.capabilities |= BDI_CAP_STABLE_WRITES;
+ q->backing_dev_info->capabilities |= BDI_CAP_STABLE_WRITES;
disk->queue = q;
diff --git a/drivers/char/diag/diag_debugfs.c b/drivers/char/diag/diag_debugfs.c
index c963e4658c07..5e455878ac3e 100644
--- a/drivers/char/diag/diag_debugfs.c
+++ b/drivers/char/diag/diag_debugfs.c
@@ -52,7 +52,7 @@ static int diag_dbgfs_bridgeinfo_index;
static int diag_dbgfs_finished;
static int diag_dbgfs_dci_data_index;
static int diag_dbgfs_dci_finished;
-
+static struct mutex diag_dci_dbgfs_mutex;
static ssize_t diag_dbgfs_read_status(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
@@ -159,6 +159,7 @@ static ssize_t diag_dbgfs_read_dcistats(struct file *file,
buf_size = ksize(buf);
bytes_remaining = buf_size;
+ mutex_lock(&diag_dci_dbgfs_mutex);
if (diag_dbgfs_dci_data_index == 0) {
bytes_written =
scnprintf(buf, buf_size,
@@ -214,8 +215,8 @@ static ssize_t diag_dbgfs_read_dcistats(struct file *file,
}
temp_data++;
}
-
diag_dbgfs_dci_data_index = (i >= DIAG_DCI_DEBUG_CNT) ? 0 : i + 1;
+ mutex_unlock(&diag_dci_dbgfs_mutex);
bytes_written = simple_read_from_buffer(ubuf, count, ppos, buf,
bytes_in_buf);
kfree(buf);
@@ -1186,6 +1187,7 @@ int diag_debugfs_init(void)
pr_warn("diag: could not allocate memory for dci debug info\n");
mutex_init(&dci_stat_mutex);
+ mutex_init(&diag_dci_dbgfs_mutex);
return 0;
err:
kfree(dci_traffic);
@@ -1202,6 +1204,7 @@ void diag_debugfs_cleanup(void)
kfree(dci_traffic);
mutex_destroy(&dci_stat_mutex);
+ mutex_destroy(&diag_dci_dbgfs_mutex);
}
#else
int diag_debugfs_init(void) { return 0; }
diff --git a/drivers/gpu/drm/msm/sde/sde_core_perf.c b/drivers/gpu/drm/msm/sde/sde_core_perf.c
index 0ba644d5519d..29e746e1fdf5 100644
--- a/drivers/gpu/drm/msm/sde/sde_core_perf.c
+++ b/drivers/gpu/drm/msm/sde/sde_core_perf.c
@@ -1,4 +1,4 @@
-/* Copyright (c) 2016, The Linux Foundation. All rights reserved.
+/* 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
@@ -75,6 +75,31 @@ static bool _sde_core_video_mode_intf_connected(struct drm_crtc *crtc)
return false;
}
+static void _sde_core_perf_calc_crtc(struct drm_crtc *crtc,
+ struct drm_crtc_state *state,
+ struct sde_core_perf_params *perf)
+{
+ struct sde_crtc_state *sde_cstate;
+
+ if (!crtc || !state || !perf) {
+ SDE_ERROR("invalid parameters\n");
+ return;
+ }
+
+ sde_cstate = to_sde_crtc_state(state);
+ memset(perf, 0, sizeof(struct sde_core_perf_params));
+
+ perf->bw_ctl = sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_AB);
+ perf->max_per_pipe_ib =
+ sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_IB);
+ perf->core_clk_rate =
+ sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_CLK);
+
+ SDE_DEBUG("crtc=%d clk_rate=%u ib=%llu ab=%llu\n",
+ crtc->base.id, perf->core_clk_rate,
+ perf->max_per_pipe_ib, perf->bw_ctl);
+}
+
int sde_core_perf_crtc_check(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
@@ -102,7 +127,9 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
sde_cstate = to_sde_crtc_state(state);
- bw_sum_of_intfs = sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_AB);
+ _sde_core_perf_calc_crtc(crtc, state, &sde_cstate->new_perf);
+
+ bw_sum_of_intfs = sde_cstate->new_perf.bw_ctl;
drm_for_each_crtc(tmp_crtc, crtc->dev) {
if (_sde_core_perf_crtc_is_power_on(tmp_crtc) &&
@@ -110,7 +137,7 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
struct sde_crtc_state *tmp_cstate =
to_sde_crtc_state(tmp_crtc->state);
- bw_sum_of_intfs += tmp_cstate->cur_perf.bw_ctl;
+ bw_sum_of_intfs += tmp_cstate->new_perf.bw_ctl;
}
}
@@ -126,11 +153,9 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
SDE_DEBUG("final threshold bw limit = %d\n", threshold);
if (!threshold) {
- sde_cstate->cur_perf.bw_ctl = 0;
SDE_ERROR("no bandwidth limits specified\n");
return -E2BIG;
} else if (bw > threshold) {
- sde_cstate->cur_perf.bw_ctl = 0;
SDE_DEBUG("exceeds bandwidth: %ukb > %ukb\n", bw, threshold);
return -E2BIG;
}
@@ -138,26 +163,6 @@ int sde_core_perf_crtc_check(struct drm_crtc *crtc,
return 0;
}
-static void _sde_core_perf_calc_crtc(struct sde_kms *kms,
- struct drm_crtc *crtc,
- struct sde_core_perf_params *perf)
-{
- struct sde_crtc_state *sde_cstate;
-
- sde_cstate = to_sde_crtc_state(crtc->state);
- memset(perf, 0, sizeof(struct sde_core_perf_params));
-
- perf->bw_ctl = sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_AB);
- perf->max_per_pipe_ib =
- sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_IB);
- perf->core_clk_rate =
- sde_crtc_get_property(sde_cstate, CRTC_PROP_CORE_CLK);
-
- SDE_DEBUG("crtc=%d clk_rate=%u ib=%llu ab=%llu\n",
- crtc->base.id, perf->core_clk_rate,
- perf->max_per_pipe_ib, perf->bw_ctl);
-}
-
static u64 _sde_core_perf_crtc_calc_client_vote(struct sde_kms *kms,
struct drm_crtc *crtc, struct sde_core_perf_params *perf,
bool nrt_client, u32 core_clk)
@@ -175,13 +180,13 @@ static u64 _sde_core_perf_crtc_calc_client_vote(struct sde_kms *kms,
to_sde_crtc_state(tmp_crtc->state);
perf->max_per_pipe_ib = max(perf->max_per_pipe_ib,
- sde_cstate->cur_perf.max_per_pipe_ib);
+ sde_cstate->new_perf.max_per_pipe_ib);
- bw_sum_of_intfs += sde_cstate->cur_perf.bw_ctl;
+ bw_sum_of_intfs += sde_cstate->new_perf.bw_ctl;
SDE_DEBUG("crtc=%d bw=%llu\n",
tmp_crtc->base.id,
- sde_cstate->cur_perf.bw_ctl);
+ sde_cstate->new_perf.bw_ctl);
}
}
@@ -249,6 +254,7 @@ static void _sde_core_perf_crtc_update_bus(struct sde_kms *kms,
void sde_core_perf_crtc_release_bw(struct drm_crtc *crtc)
{
struct drm_crtc *tmp_crtc;
+ struct sde_crtc *sde_crtc;
struct sde_crtc_state *sde_cstate;
struct sde_kms *kms;
@@ -263,6 +269,7 @@ void sde_core_perf_crtc_release_bw(struct drm_crtc *crtc)
return;
}
+ sde_crtc = to_sde_crtc(crtc);
sde_cstate = to_sde_crtc_state(crtc->state);
/* only do this for command panel or writeback */
@@ -285,8 +292,7 @@ void sde_core_perf_crtc_release_bw(struct drm_crtc *crtc)
/* Release the bandwidth */
if (kms->perf.enable_bw_release) {
trace_sde_cmd_release_bw(crtc->base.id);
- sde_cstate->cur_perf.bw_ctl = 0;
- sde_cstate->new_perf.bw_ctl = 0;
+ sde_crtc->cur_perf.bw_ctl = 0;
SDE_DEBUG("Release BW crtc=%d\n", crtc->base.id);
_sde_core_perf_crtc_update_bus(kms, crtc, 0);
}
@@ -298,18 +304,27 @@ static int _sde_core_select_clk_lvl(struct sde_kms *kms,
return clk_round_rate(kms->perf.core_clk, clk_rate);
}
-static u32 _sde_core_perf_get_core_clk_rate(struct sde_kms *kms)
+static u32 _sde_core_perf_get_core_clk_rate(struct sde_kms *kms,
+ struct sde_core_perf_params *crct_perf, struct drm_crtc *crtc)
{
u32 clk_rate = 0;
- struct drm_crtc *crtc;
+ struct drm_crtc *tmp_crtc;
struct sde_crtc_state *sde_cstate;
int ncrtc = 0;
+ u32 tmp_rate;
+
+ drm_for_each_crtc(tmp_crtc, kms->dev) {
+ if (_sde_core_perf_crtc_is_power_on(tmp_crtc)) {
- drm_for_each_crtc(crtc, kms->dev) {
- if (_sde_core_perf_crtc_is_power_on(crtc)) {
- sde_cstate = to_sde_crtc_state(crtc->state);
- clk_rate = max(sde_cstate->cur_perf.core_clk_rate,
- clk_rate);
+ if (crtc->base.id == tmp_crtc->base.id) {
+ /* for current CRTC, use the cached value */
+ tmp_rate = crct_perf->core_clk_rate;
+ } else {
+ sde_cstate = to_sde_crtc_state(tmp_crtc->state);
+ tmp_rate = sde_cstate->new_perf.core_clk_rate;
+ }
+
+ clk_rate = max(tmp_rate, clk_rate);
clk_rate = clk_round_rate(kms->perf.core_clk, clk_rate);
}
ncrtc++;
@@ -353,13 +368,20 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
SDE_ATRACE_BEGIN(__func__);
- old = &sde_cstate->cur_perf;
- new = &sde_cstate->new_perf;
+ /*
+ * cache the performance numbers in the crtc prior to the
+ * crtc kickoff, so the same numbers are used during the
+ * perf update that happens post kickoff.
+ */
+
+ if (params_changed)
+ memcpy(&sde_crtc->new_perf, &sde_cstate->new_perf,
+ sizeof(struct sde_core_perf_params));
- if (_sde_core_perf_crtc_is_power_on(crtc) && !stop_req) {
- if (params_changed)
- _sde_core_perf_calc_crtc(kms, crtc, new);
+ old = &sde_crtc->cur_perf;
+ new = &sde_crtc->new_perf;
+ if (_sde_core_perf_crtc_is_power_on(crtc) && !stop_req) {
/*
* cases for bus bandwidth update.
* 1. new bandwidth vote or writeback output vote
@@ -398,7 +420,7 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
* use the new clock for the rotator bw calculation.
*/
if (update_clk)
- clk_rate = _sde_core_perf_get_core_clk_rate(kms);
+ clk_rate = _sde_core_perf_get_core_clk_rate(kms, old, crtc);
if (update_bus)
_sde_core_perf_crtc_update_bus(kms, crtc, clk_rate);
@@ -409,7 +431,9 @@ void sde_core_perf_crtc_update(struct drm_crtc *crtc,
*/
if (update_clk) {
SDE_ATRACE_INT(kms->perf.clk_name, clk_rate);
- SDE_EVT32(kms->dev, stop_req, clk_rate);
+ SDE_EVT32(kms->dev, stop_req, clk_rate, params_changed,
+ old->core_clk_rate, new->core_clk_rate);
+
ret = sde_power_clk_set_rate(&priv->phandle,
kms->perf.clk_name, clk_rate);
if (ret) {
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index e99eba0dadb7..2a31bc7fedc7 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -473,6 +473,7 @@ static void sde_crtc_frame_event_work(struct kthread_work *work)
struct sde_crtc_frame_event *fevent;
struct drm_crtc *crtc;
struct sde_crtc *sde_crtc;
+ struct sde_crtc_state *cstate;
struct sde_kms *sde_kms;
unsigned long flags;
@@ -482,13 +483,14 @@ static void sde_crtc_frame_event_work(struct kthread_work *work)
}
fevent = container_of(work, struct sde_crtc_frame_event, work);
- if (!fevent->crtc) {
+ if (!fevent->crtc || !fevent->crtc->state) {
SDE_ERROR("invalid crtc\n");
return;
}
crtc = fevent->crtc;
sde_crtc = to_sde_crtc(crtc);
+ cstate = to_sde_crtc_state(crtc->state);
sde_kms = _sde_crtc_get_kms(crtc);
if (!sde_kms) {
@@ -522,6 +524,9 @@ static void sde_crtc_frame_event_work(struct kthread_work *work)
} else {
SDE_EVT32(DRMID(crtc), fevent->event, 2);
}
+
+ if (fevent->event == SDE_ENCODER_FRAME_EVENT_DONE)
+ sde_core_perf_crtc_update(crtc, 0, false);
} else {
SDE_ERROR("crtc%d ts:%lld unknown event %u\n", crtc->base.id,
ktime_to_ns(fevent->ts),
@@ -1883,15 +1888,18 @@ static const struct file_operations __prefix ## _fops = { \
static int sde_crtc_debugfs_state_show(struct seq_file *s, void *v)
{
struct drm_crtc *crtc = (struct drm_crtc *) s->private;
+ struct sde_crtc *sde_crtc = to_sde_crtc(crtc);
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc->state);
seq_printf(s, "num_connectors: %d\n", cstate->num_connectors);
seq_printf(s, "is_rt: %d\n", cstate->is_rt);
seq_printf(s, "intf_mode: %d\n", sde_crtc_get_intf_mode(crtc));
- seq_printf(s, "bw_ctl: %llu\n", cstate->cur_perf.bw_ctl);
- seq_printf(s, "core_clk_rate: %u\n", cstate->cur_perf.core_clk_rate);
+
+ seq_printf(s, "bw_ctl: %llu\n", sde_crtc->cur_perf.bw_ctl);
+ seq_printf(s, "core_clk_rate: %u\n",
+ sde_crtc->cur_perf.core_clk_rate);
seq_printf(s, "max_per_pipe_ib: %llu\n",
- cstate->cur_perf.max_per_pipe_ib);
+ sde_crtc->cur_perf.max_per_pipe_ib);
return 0;
}
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 0eed61580cd8..200073995d43 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -95,6 +95,8 @@ struct sde_crtc_frame_event {
* @frame_event_list : available frame event list
* @pending : Whether any page-flip events are pending signal
* @spin_lock : spin lock for frame event, transaction status, etc...
+ * @cur_perf : current performance committed to clock/bandwidth driver
+ * @new_perf : new performance committed to clock/bandwidth driver
*/
struct sde_crtc {
struct drm_crtc base;
@@ -134,6 +136,9 @@ struct sde_crtc {
struct sde_crtc_frame_event frame_events[SDE_CRTC_FRAME_EVENT_SIZE];
struct list_head frame_event_list;
spinlock_t spin_lock;
+
+ struct sde_core_perf_params cur_perf;
+ struct sde_core_perf_params new_perf;
};
#define to_sde_crtc(x) container_of(x, struct sde_crtc, base)
@@ -148,6 +153,7 @@ struct sde_crtc {
* @property_values: Current crtc property values
* @input_fence_timeout_ns : Cached input fence timeout, in ns
* @property_blobs: Reference pointers for blob properties
+ * @new_perf: new performance state being requested
*/
struct sde_crtc_state {
struct drm_crtc_state base;
@@ -161,7 +167,6 @@ struct sde_crtc_state {
uint64_t input_fence_timeout_ns;
struct drm_property_blob *property_blobs[CRTC_PROP_COUNT];
- struct sde_core_perf_params cur_perf;
struct sde_core_perf_params new_perf;
};
diff --git a/drivers/leds/Kconfig b/drivers/leds/Kconfig
index 620268b63b2a..966227a3df1a 100644
--- a/drivers/leds/Kconfig
+++ b/drivers/leds/Kconfig
@@ -596,9 +596,18 @@ config LEDS_QPNP
LEDs in both PWM and light pattern generator (LPG) modes. For older
PMICs, it also supports WLEDs and flash LEDs.
+config LEDS_QPNP_FLASH
+ tristate "Support for QPNP Flash LEDs"
+ depends on LEDS_CLASS && SPMI
+ help
+ This driver supports the flash LED functionality of Qualcomm
+ Technologies, Inc. QPNP PMICs. This driver supports PMICs up through
+ PM8994. It can configure the flash LED target current for several
+ independent channels.
+
config LEDS_QPNP_FLASH_V2
tristate "Support for QPNP V2 Flash LEDs"
- depends on LEDS_CLASS && MFD_SPMI_PMIC
+ depends on LEDS_CLASS && MFD_SPMI_PMIC && !LEDS_QPNP_FLASH
help
This driver supports the flash V2 LED functionality of Qualcomm
Technologies, Inc. QPNP PMICs. This driver supports PMICs starting
diff --git a/drivers/leds/Makefile b/drivers/leds/Makefile
index aa5ba0cf4de6..8d8ba9175810 100644
--- a/drivers/leds/Makefile
+++ b/drivers/leds/Makefile
@@ -61,6 +61,7 @@ obj-$(CONFIG_LEDS_MAX8997) += leds-max8997.o
obj-$(CONFIG_LEDS_LM355x) += leds-lm355x.o
obj-$(CONFIG_LEDS_BLINKM) += leds-blinkm.o
obj-$(CONFIG_LEDS_QPNP) += leds-qpnp.o
+obj-$(CONFIG_LEDS_QPNP_FLASH) += leds-qpnp-flash.o
obj-$(CONFIG_LEDS_QPNP_FLASH_V2) += leds-qpnp-flash-v2.o
obj-$(CONFIG_LEDS_QPNP_WLED) += leds-qpnp-wled.o
obj-$(CONFIG_LEDS_SYSCON) += leds-syscon.o
diff --git a/drivers/leds/leds-qpnp-flash.c b/drivers/leds/leds-qpnp-flash.c
new file mode 100644
index 000000000000..cd76941b87ca
--- /dev/null
+++ b/drivers/leds/leds-qpnp-flash.c
@@ -0,0 +1,2683 @@
+/* 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
+ * 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/module.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/errno.h>
+#include <linux/leds.h>
+#include <linux/slab.h>
+#include <linux/of_device.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/of.h>
+#include <linux/regulator/consumer.h>
+#include <linux/workqueue.h>
+#include <linux/power_supply.h>
+#include <linux/leds-qpnp-flash.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/qpnp/qpnp-revid.h>
+#include <linux/debugfs.h>
+#include <linux/uaccess.h>
+#include "leds.h"
+
+#define FLASH_LED_PERIPHERAL_SUBTYPE(base) (base + 0x05)
+#define FLASH_SAFETY_TIMER(base) (base + 0x40)
+#define FLASH_MAX_CURRENT(base) (base + 0x41)
+#define FLASH_LED0_CURRENT(base) (base + 0x42)
+#define FLASH_LED1_CURRENT(base) (base + 0x43)
+#define FLASH_CLAMP_CURRENT(base) (base + 0x44)
+#define FLASH_MODULE_ENABLE_CTRL(base) (base + 0x46)
+#define FLASH_LED_STROBE_CTRL(base) (base + 0x47)
+#define FLASH_LED_TMR_CTRL(base) (base + 0x48)
+#define FLASH_HEADROOM(base) (base + 0x4A)
+#define FLASH_STARTUP_DELAY(base) (base + 0x4B)
+#define FLASH_MASK_ENABLE(base) (base + 0x4C)
+#define FLASH_VREG_OK_FORCE(base) (base + 0x4F)
+#define FLASH_FAULT_DETECT(base) (base + 0x51)
+#define FLASH_THERMAL_DRATE(base) (base + 0x52)
+#define FLASH_CURRENT_RAMP(base) (base + 0x54)
+#define FLASH_VPH_PWR_DROOP(base) (base + 0x5A)
+#define FLASH_HDRM_SNS_ENABLE_CTRL0(base) (base + 0x5C)
+#define FLASH_HDRM_SNS_ENABLE_CTRL1(base) (base + 0x5D)
+#define FLASH_LED_UNLOCK_SECURE(base) (base + 0xD0)
+#define FLASH_PERPH_RESET_CTRL(base) (base + 0xDA)
+#define FLASH_TORCH(base) (base + 0xE4)
+
+#define FLASH_STATUS_REG_MASK 0xFF
+#define FLASH_LED_FAULT_STATUS(base) (base + 0x08)
+#define INT_LATCHED_STS(base) (base + 0x18)
+#define IN_POLARITY_HIGH(base) (base + 0x12)
+#define INT_SET_TYPE(base) (base + 0x11)
+#define INT_EN_SET(base) (base + 0x15)
+#define INT_LATCHED_CLR(base) (base + 0x14)
+
+#define FLASH_HEADROOM_MASK 0x03
+#define FLASH_STARTUP_DLY_MASK 0x03
+#define FLASH_VREG_OK_FORCE_MASK 0xC0
+#define FLASH_FAULT_DETECT_MASK 0x80
+#define FLASH_THERMAL_DERATE_MASK 0xBF
+#define FLASH_SECURE_MASK 0xFF
+#define FLASH_TORCH_MASK 0x03
+#define FLASH_CURRENT_MASK 0x7F
+#define FLASH_TMR_MASK 0x03
+#define FLASH_TMR_SAFETY 0x00
+#define FLASH_SAFETY_TIMER_MASK 0x7F
+#define FLASH_MODULE_ENABLE_MASK 0xE0
+#define FLASH_STROBE_MASK 0xC0
+#define FLASH_CURRENT_RAMP_MASK 0xBF
+#define FLASH_VPH_PWR_DROOP_MASK 0xF3
+#define FLASH_LED_HDRM_SNS_ENABLE_MASK 0x81
+#define FLASH_MASK_MODULE_CONTRL_MASK 0xE0
+#define FLASH_FOLLOW_OTST2_RB_MASK 0x08
+
+#define FLASH_LED_TRIGGER_DEFAULT "none"
+#define FLASH_LED_HEADROOM_DEFAULT_MV 500
+#define FLASH_LED_STARTUP_DELAY_DEFAULT_US 128
+#define FLASH_LED_CLAMP_CURRENT_DEFAULT_MA 200
+#define FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C 80
+#define FLASH_LED_RAMP_UP_STEP_DEFAULT_US 3
+#define FLASH_LED_RAMP_DN_STEP_DEFAULT_US 3
+#define FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV 3200
+#define FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US 10
+#define FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT 2
+#define FLASH_RAMP_UP_DELAY_US_MIN 1000
+#define FLASH_RAMP_UP_DELAY_US_MAX 1001
+#define FLASH_RAMP_DN_DELAY_US_MIN 2160
+#define FLASH_RAMP_DN_DELAY_US_MAX 2161
+#define FLASH_BOOST_REGULATOR_PROBE_DELAY_MS 2000
+#define FLASH_TORCH_MAX_LEVEL 0x0F
+#define FLASH_MAX_LEVEL 0x4F
+#define FLASH_LED_FLASH_HW_VREG_OK 0x40
+#define FLASH_LED_FLASH_SW_VREG_OK 0x80
+#define FLASH_LED_STROBE_TYPE_HW 0x04
+#define FLASH_DURATION_DIVIDER 10
+#define FLASH_LED_HEADROOM_DIVIDER 100
+#define FLASH_LED_HEADROOM_OFFSET 2
+#define FLASH_LED_MAX_CURRENT_MA 1000
+#define FLASH_LED_THERMAL_THRESHOLD_MIN 95
+#define FLASH_LED_THERMAL_DEVIDER 10
+#define FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV 2500
+#define FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER 100
+#define FLASH_LED_HDRM_SNS_ENABLE 0x81
+#define FLASH_LED_HDRM_SNS_DISABLE 0x01
+#define FLASH_LED_UA_PER_MA 1000
+#define FLASH_LED_MASK_MODULE_MASK2_ENABLE 0x20
+#define FLASH_LED_MASK3_ENABLE_SHIFT 7
+#define FLASH_LED_MODULE_CTRL_DEFAULT 0x60
+#define FLASH_LED_CURRENT_READING_DELAY_MIN 5000
+#define FLASH_LED_CURRENT_READING_DELAY_MAX 5001
+#define FLASH_LED_OPEN_FAULT_DETECTED 0xC
+
+#define FLASH_UNLOCK_SECURE 0xA5
+#define FLASH_LED_TORCH_ENABLE 0x00
+#define FLASH_LED_TORCH_DISABLE 0x03
+#define FLASH_MODULE_ENABLE 0x80
+#define FLASH_LED0_TRIGGER 0x80
+#define FLASH_LED1_TRIGGER 0x40
+#define FLASH_LED0_ENABLEMENT 0x40
+#define FLASH_LED1_ENABLEMENT 0x20
+#define FLASH_LED_DISABLE 0x00
+#define FLASH_LED_MIN_CURRENT_MA 13
+#define FLASH_SUBTYPE_DUAL 0x01
+#define FLASH_SUBTYPE_SINGLE 0x02
+
+/*
+ * ID represents physical LEDs for individual control purpose.
+ */
+enum flash_led_id {
+ FLASH_LED_0 = 0,
+ FLASH_LED_1,
+ FLASH_LED_SWITCH,
+};
+
+enum flash_led_type {
+ FLASH = 0,
+ TORCH,
+ SWITCH,
+};
+
+enum thermal_derate_rate {
+ RATE_1_PERCENT = 0,
+ RATE_1P25_PERCENT,
+ RATE_2_PERCENT,
+ RATE_2P5_PERCENT,
+ RATE_5_PERCENT,
+};
+
+enum current_ramp_steps {
+ RAMP_STEP_0P2_US = 0,
+ RAMP_STEP_0P4_US,
+ RAMP_STEP_0P8_US,
+ RAMP_STEP_1P6_US,
+ RAMP_STEP_3P3_US,
+ RAMP_STEP_6P7_US,
+ RAMP_STEP_13P5_US,
+ RAMP_STEP_27US,
+};
+
+struct flash_regulator_data {
+ struct regulator *regs;
+ const char *reg_name;
+ u32 max_volt_uv;
+};
+
+/*
+ * Configurations for each individual LED
+ */
+struct flash_node_data {
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct led_classdev cdev;
+ struct work_struct work;
+ struct flash_regulator_data *reg_data;
+ u16 max_current;
+ u16 prgm_current;
+ u16 prgm_current2;
+ u16 duration;
+ u8 id;
+ u8 type;
+ u8 trigger;
+ u8 enable;
+ u8 num_regulators;
+ bool flash_on;
+};
+
+/*
+ * Flash LED configuration read from device tree
+ */
+struct flash_led_platform_data {
+ unsigned int temp_threshold_num;
+ unsigned int temp_derate_curr_num;
+ unsigned int *die_temp_derate_curr_ma;
+ unsigned int *die_temp_threshold_degc;
+ u16 ramp_up_step;
+ u16 ramp_dn_step;
+ u16 vph_pwr_droop_threshold;
+ u16 headroom;
+ u16 clamp_current;
+ u8 thermal_derate_threshold;
+ u8 vph_pwr_droop_debounce_time;
+ u8 startup_dly;
+ u8 thermal_derate_rate;
+ bool pmic_charger_support;
+ bool self_check_en;
+ bool thermal_derate_en;
+ bool current_ramp_en;
+ bool vph_pwr_droop_en;
+ bool hdrm_sns_ch0_en;
+ bool hdrm_sns_ch1_en;
+ bool power_detect_en;
+ bool mask3_en;
+ bool follow_rb_disable;
+ bool die_current_derate_en;
+};
+
+struct qpnp_flash_led_buffer {
+ struct mutex debugfs_lock; /* Prevent thread concurrency */
+ size_t rpos;
+ size_t wpos;
+ size_t len;
+ char data[0];
+};
+
+/*
+ * Flash LED data structure containing flash LED attributes
+ */
+struct qpnp_flash_led {
+ struct pmic_revid_data *revid_data;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ struct flash_led_platform_data *pdata;
+ struct pinctrl *pinctrl;
+ struct pinctrl_state *gpio_state_active;
+ struct pinctrl_state *gpio_state_suspend;
+ struct flash_node_data *flash_node;
+ struct power_supply *battery_psy;
+ struct workqueue_struct *ordered_workq;
+ struct qpnp_vadc_chip *vadc_dev;
+ struct mutex flash_led_lock;
+ struct qpnp_flash_led_buffer *log;
+ struct dentry *dbgfs_root;
+ int num_leds;
+ u32 buffer_cnt;
+ u16 base;
+ u16 current_addr;
+ u16 current2_addr;
+ u8 peripheral_type;
+ u8 fault_reg;
+ bool gpio_enabled;
+ bool charging_enabled;
+ bool strobe_debug;
+ bool dbg_feature_en;
+ bool open_fault;
+};
+
+static u8 qpnp_flash_led_ctrl_dbg_regs[] = {
+ 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
+ 0x4A, 0x4B, 0x4C, 0x4F, 0x51, 0x52, 0x54, 0x55, 0x5A, 0x5C, 0x5D,
+};
+
+static int flash_led_dbgfs_file_open(struct qpnp_flash_led *led,
+ struct file *file)
+{
+ struct qpnp_flash_led_buffer *log;
+ size_t logbufsize = SZ_4K;
+
+ log = kzalloc(logbufsize, GFP_KERNEL);
+ if (!log)
+ return -ENOMEM;
+
+ log->rpos = 0;
+ log->wpos = 0;
+ log->len = logbufsize - sizeof(*log);
+ mutex_init(&log->debugfs_lock);
+ led->log = log;
+
+ led->buffer_cnt = 1;
+ file->private_data = led;
+
+ return 0;
+}
+
+static int flash_led_dfs_open(struct inode *inode, struct file *file)
+{
+ struct qpnp_flash_led *led = inode->i_private;
+
+ return flash_led_dbgfs_file_open(led, file);
+}
+
+static int flash_led_dfs_close(struct inode *inode, struct file *file)
+{
+ struct qpnp_flash_led *led = file->private_data;
+
+ if (led && led->log) {
+ file->private_data = NULL;
+ mutex_destroy(&led->log->debugfs_lock);
+ kfree(led->log);
+ }
+
+ return 0;
+}
+
+#define MIN_BUFFER_WRITE_LEN 20
+static int print_to_log(struct qpnp_flash_led_buffer *log,
+ const char *fmt, ...)
+{
+ va_list args;
+ int cnt;
+ char *log_buf;
+ size_t size = log->len - log->wpos;
+
+ if (size < MIN_BUFFER_WRITE_LEN)
+ return 0; /* not enough buffer left */
+
+ log_buf = &log->data[log->wpos];
+ va_start(args, fmt);
+ cnt = vscnprintf(log_buf, size, fmt, args);
+ va_end(args);
+
+ log->wpos += cnt;
+ return cnt;
+}
+
+static ssize_t flash_led_dfs_latched_reg_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos) {
+ struct qpnp_flash_led *led = fp->private_data;
+ struct qpnp_flash_led_buffer *log = led->log;
+ uint val;
+ int rc = 0;
+ size_t len;
+ size_t ret;
+
+ mutex_lock(&log->debugfs_lock);
+ if ((log->rpos >= log->wpos && led->buffer_cnt == 0) ||
+ ((log->len - log->wpos) < MIN_BUFFER_WRITE_LEN))
+ goto unlock_mutex;
+
+ rc = regmap_read(led->regmap, INT_LATCHED_STS(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from address %x, rc(%d)\n",
+ INT_LATCHED_STS(led->base), rc);
+ goto unlock_mutex;
+ }
+ led->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", INT_LATCHED_STS(led->base));
+ if (rc == 0)
+ goto unlock_mutex;
+
+ rc = print_to_log(log, "0x%02X ", val);
+ if (rc == 0)
+ goto unlock_mutex;
+
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+ len = min(count, log->wpos - log->rpos);
+
+ ret = copy_to_user(buf, &log->data[log->rpos], len);
+ if (ret) {
+ pr_err("error copy register value to user\n");
+ rc = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ rc = len;
+
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ return rc;
+}
+
+static ssize_t flash_led_dfs_fault_reg_read(struct file *fp, char __user *buf,
+ size_t count, loff_t *ppos) {
+ struct qpnp_flash_led *led = fp->private_data;
+ struct qpnp_flash_led_buffer *log = led->log;
+ int rc = 0;
+ size_t len;
+ size_t ret;
+
+ mutex_lock(&log->debugfs_lock);
+ if ((log->rpos >= log->wpos && led->buffer_cnt == 0) ||
+ ((log->len - log->wpos) < MIN_BUFFER_WRITE_LEN))
+ goto unlock_mutex;
+
+ led->buffer_cnt--;
+
+ rc = print_to_log(log, "0x%05X ", FLASH_LED_FAULT_STATUS(led->base));
+ if (rc == 0)
+ goto unlock_mutex;
+
+ rc = print_to_log(log, "0x%02X ", led->fault_reg);
+ if (rc == 0)
+ goto unlock_mutex;
+
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+ len = min(count, log->wpos - log->rpos);
+
+ ret = copy_to_user(buf, &log->data[log->rpos], len);
+ if (ret) {
+ pr_err("error copy register value to user\n");
+ rc = -EFAULT;
+ goto unlock_mutex;
+ }
+
+ len -= ret;
+ *ppos += len;
+ log->rpos += len;
+
+ rc = len;
+
+unlock_mutex:
+ mutex_unlock(&log->debugfs_lock);
+ return rc;
+}
+
+static ssize_t flash_led_dfs_fault_reg_enable(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos) {
+
+ u8 *val;
+ int pos = 0;
+ int cnt = 0;
+ int data;
+ size_t ret = 0;
+
+ struct qpnp_flash_led *led = file->private_data;
+ char *kbuf;
+
+ mutex_lock(&led->log->debugfs_lock);
+ kbuf = kmalloc(count + 1, GFP_KERNEL);
+ if (!kbuf) {
+ ret = -ENOMEM;
+ goto unlock_mutex;
+ }
+
+ ret = copy_from_user(kbuf, buf, count);
+ if (!ret) {
+ pr_err("failed to copy data from user\n");
+ ret = -EFAULT;
+ goto free_buf;
+ }
+
+ count -= ret;
+ *ppos += count;
+ kbuf[count] = '\0';
+ val = kbuf;
+ while (sscanf(kbuf + pos, "%i", &data) == 1) {
+ pos++;
+ val[cnt++] = data & 0xff;
+ }
+
+ if (!cnt)
+ goto free_buf;
+
+ ret = count;
+ if (*val == 1)
+ led->strobe_debug = true;
+ else
+ led->strobe_debug = false;
+
+free_buf:
+ kfree(kbuf);
+unlock_mutex:
+ mutex_unlock(&led->log->debugfs_lock);
+ return ret;
+}
+
+static ssize_t flash_led_dfs_dbg_enable(struct file *file,
+ const char __user *buf, size_t count, loff_t *ppos) {
+
+ u8 *val;
+ int pos = 0;
+ int cnt = 0;
+ int data;
+ size_t ret = 0;
+ struct qpnp_flash_led *led = file->private_data;
+ char *kbuf;
+
+ mutex_lock(&led->log->debugfs_lock);
+ 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';
+ val = kbuf;
+ while (sscanf(kbuf + pos, "%i", &data) == 1) {
+ pos++;
+ val[cnt++] = data & 0xff;
+ }
+
+ if (!cnt)
+ goto free_buf;
+
+ ret = count;
+ if (*val == 1)
+ led->dbg_feature_en = true;
+ else
+ led->dbg_feature_en = false;
+
+free_buf:
+ kfree(kbuf);
+unlock_mutex:
+ mutex_unlock(&led->log->debugfs_lock);
+ return ret;
+}
+
+static const struct file_operations flash_led_dfs_latched_reg_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .read = flash_led_dfs_latched_reg_read,
+};
+
+static const struct file_operations flash_led_dfs_strobe_reg_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .read = flash_led_dfs_fault_reg_read,
+ .write = flash_led_dfs_fault_reg_enable,
+};
+
+static const struct file_operations flash_led_dfs_dbg_feature_fops = {
+ .open = flash_led_dfs_open,
+ .release = flash_led_dfs_close,
+ .write = flash_led_dfs_dbg_enable,
+};
+
+static int
+qpnp_led_masked_write(struct qpnp_flash_led *led, u16 addr, u8 mask, u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(led->regmap, addr, mask, val);
+ if (rc)
+ dev_err(&led->pdev->dev,
+ "Unable to update_bits to addr=%x, rc(%d)\n", addr, rc);
+
+ dev_dbg(&led->pdev->dev, "Write 0x%02X to addr 0x%02X\n", val, addr);
+
+ return rc;
+}
+
+static int qpnp_flash_led_get_allowed_die_temp_curr(struct qpnp_flash_led *led,
+ int64_t die_temp_degc)
+{
+ int die_temp_curr_ma;
+
+ if (die_temp_degc >= led->pdata->die_temp_threshold_degc[0])
+ die_temp_curr_ma = 0;
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[1])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[0];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[2])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[1];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[3])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[2];
+ else if (die_temp_degc >= led->pdata->die_temp_threshold_degc[4])
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[3];
+ else
+ die_temp_curr_ma = led->pdata->die_temp_derate_curr_ma[4];
+
+ return die_temp_curr_ma;
+}
+
+static int64_t qpnp_flash_led_get_die_temp(struct qpnp_flash_led *led)
+{
+ struct qpnp_vadc_result die_temp_result;
+ int rc;
+
+ rc = qpnp_vadc_read(led->vadc_dev, SPARE2, &die_temp_result);
+ if (rc) {
+ pr_err("failed to read the die temp\n");
+ return -EINVAL;
+ }
+
+ return die_temp_result.physical;
+}
+
+static int qpnp_get_pmic_revid(struct qpnp_flash_led *led)
+{
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(led->pdev->dev.of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ dev_err(&led->pdev->dev,
+ "qcom,pmic-revid property missing\n");
+ return -EINVAL;
+ }
+
+ led->revid_data = get_revid_data(revid_dev_node);
+ if (IS_ERR(led->revid_data)) {
+ pr_err("Couldn't get revid data rc = %ld\n",
+ PTR_ERR(led->revid_data));
+ return PTR_ERR(led->revid_data);
+ }
+
+ return 0;
+}
+
+static int
+qpnp_flash_led_get_max_avail_current(struct flash_node_data *flash_node,
+ struct qpnp_flash_led *led)
+{
+ union power_supply_propval prop;
+ int64_t chg_temp_milidegc, die_temp_degc;
+ int max_curr_avail_ma = 2000;
+ int allowed_die_temp_curr_ma = 2000;
+ int rc;
+
+ if (led->pdata->power_detect_en) {
+ if (!led->battery_psy) {
+ dev_err(&led->pdev->dev,
+ "Failed to query power supply\n");
+ return -EINVAL;
+ }
+
+ /*
+ * When charging is enabled, enforce this new enablement
+ * sequence to reduce fuel gauge reading resolution.
+ */
+ if (led->charging_enabled) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ return -EINVAL;
+ }
+
+ usleep_range(FLASH_LED_CURRENT_READING_DELAY_MIN,
+ FLASH_LED_CURRENT_READING_DELAY_MAX);
+ }
+
+ power_supply_get_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, &prop);
+ if (!prop.intval) {
+ dev_err(&led->pdev->dev,
+ "battery too low for flash\n");
+ return -EINVAL;
+ }
+
+ max_curr_avail_ma = (prop.intval / FLASH_LED_UA_PER_MA);
+ }
+
+ /*
+ * When thermal mitigation is available, this logic will execute to
+ * derate current based upon the PMIC die temperature.
+ */
+ if (led->pdata->die_current_derate_en) {
+ chg_temp_milidegc = qpnp_flash_led_get_die_temp(led);
+ if (chg_temp_milidegc < 0)
+ return -EINVAL;
+
+ die_temp_degc = div_s64(chg_temp_milidegc, 1000);
+ allowed_die_temp_curr_ma =
+ qpnp_flash_led_get_allowed_die_temp_curr(led,
+ die_temp_degc);
+ if (allowed_die_temp_curr_ma < 0)
+ return -EINVAL;
+ }
+
+ max_curr_avail_ma = (max_curr_avail_ma >= allowed_die_temp_curr_ma)
+ ? allowed_die_temp_curr_ma : max_curr_avail_ma;
+
+ return max_curr_avail_ma;
+}
+
+static ssize_t qpnp_flash_led_die_temp_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ unsigned long val;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ /*'0' for disable die_temp feature; non-zero to enable feature*/
+ if (val == 0)
+ led->pdata->die_current_derate_en = false;
+ else
+ led->pdata->die_current_derate_en = true;
+
+ return count;
+}
+
+static ssize_t qpnp_led_strobe_type_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct flash_node_data *flash_node;
+ unsigned long state;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret = -EINVAL;
+
+ ret = kstrtoul(buf, 10, &state);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+
+ /* '0' for sw strobe; '1' for hw strobe */
+ if (state == 1)
+ flash_node->trigger |= FLASH_LED_STROBE_TYPE_HW;
+ else
+ flash_node->trigger &= ~FLASH_LED_STROBE_TYPE_HW;
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_dump_regs_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ int rc, i, count = 0;
+ u16 addr;
+ uint val;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+ for (i = 0; i < ARRAY_SIZE(qpnp_flash_led_ctrl_dbg_regs); i++) {
+ addr = led->base + qpnp_flash_led_ctrl_dbg_regs[i];
+ rc = regmap_read(led->regmap, addr, &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr=%x, rc(%d)\n",
+ addr, rc);
+ return -EINVAL;
+ }
+
+ count += snprintf(buf + count, PAGE_SIZE - count,
+ "REG_0x%x = 0x%02x\n", addr, val);
+
+ if (count >= PAGE_SIZE)
+ return PAGE_SIZE - 1;
+ }
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_current_derate_store(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ unsigned long val;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ ssize_t ret;
+
+ ret = kstrtoul(buf, 10, &val);
+ if (ret)
+ return ret;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ /*'0' for disable derate feature; non-zero to enable derate feature */
+ if (val == 0)
+ led->pdata->power_detect_en = false;
+ else
+ led->pdata->power_detect_en = true;
+
+ return count;
+}
+
+static ssize_t qpnp_flash_led_max_current_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct qpnp_flash_led *led;
+ struct flash_node_data *flash_node;
+ struct led_classdev *led_cdev = dev_get_drvdata(dev);
+ int max_curr_avail_ma = 0;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (led->flash_node[0].flash_on)
+ max_curr_avail_ma += led->flash_node[0].max_current;
+ if (led->flash_node[1].flash_on)
+ max_curr_avail_ma += led->flash_node[1].max_current;
+
+ if (led->pdata->power_detect_en ||
+ led->pdata->die_current_derate_en) {
+ max_curr_avail_ma =
+ qpnp_flash_led_get_max_avail_current(flash_node, led);
+
+ if (max_curr_avail_ma < 0)
+ return -EINVAL;
+ }
+
+ return snprintf(buf, PAGE_SIZE, "%u\n", max_curr_avail_ma);
+}
+
+static struct device_attribute qpnp_flash_led_attrs[] = {
+ __ATTR(strobe, 0664, NULL, qpnp_led_strobe_type_store),
+ __ATTR(reg_dump, 0664, qpnp_flash_led_dump_regs_show, NULL),
+ __ATTR(enable_current_derate, 0664, NULL,
+ qpnp_flash_led_current_derate_store),
+ __ATTR(max_allowed_current, 0664, qpnp_flash_led_max_current_show,
+ NULL),
+ __ATTR(enable_die_temp_current_derate, 0664, NULL,
+ qpnp_flash_led_die_temp_store),
+};
+
+static int qpnp_flash_led_get_thermal_derate_rate(const char *rate)
+{
+ /*
+ * return 5% derate as default value if user specifies
+ * a value un-supported
+ */
+ if (strcmp(rate, "1_PERCENT") == 0)
+ return RATE_1_PERCENT;
+ else if (strcmp(rate, "1P25_PERCENT") == 0)
+ return RATE_1P25_PERCENT;
+ else if (strcmp(rate, "2_PERCENT") == 0)
+ return RATE_2_PERCENT;
+ else if (strcmp(rate, "2P5_PERCENT") == 0)
+ return RATE_2P5_PERCENT;
+ else if (strcmp(rate, "5_PERCENT") == 0)
+ return RATE_5_PERCENT;
+ else
+ return RATE_5_PERCENT;
+}
+
+static int qpnp_flash_led_get_ramp_step(const char *step)
+{
+ /*
+ * return 27 us as default value if user specifies
+ * a value un-supported
+ */
+ if (strcmp(step, "0P2_US") == 0)
+ return RAMP_STEP_0P2_US;
+ else if (strcmp(step, "0P4_US") == 0)
+ return RAMP_STEP_0P4_US;
+ else if (strcmp(step, "0P8_US") == 0)
+ return RAMP_STEP_0P8_US;
+ else if (strcmp(step, "1P6_US") == 0)
+ return RAMP_STEP_1P6_US;
+ else if (strcmp(step, "3P3_US") == 0)
+ return RAMP_STEP_3P3_US;
+ else if (strcmp(step, "6P7_US") == 0)
+ return RAMP_STEP_6P7_US;
+ else if (strcmp(step, "13P5_US") == 0)
+ return RAMP_STEP_13P5_US;
+ else
+ return RAMP_STEP_27US;
+}
+
+static u8 qpnp_flash_led_get_droop_debounce_time(u8 val)
+{
+ /*
+ * return 10 us as default value if user specifies
+ * a value un-supported
+ */
+ switch (val) {
+ case 0:
+ return 0;
+ case 10:
+ return 1;
+ case 32:
+ return 2;
+ case 64:
+ return 3;
+ default:
+ return 1;
+ }
+}
+
+static u8 qpnp_flash_led_get_startup_dly(u8 val)
+{
+ /*
+ * return 128 us as default value if user specifies
+ * a value un-supported
+ */
+ switch (val) {
+ case 10:
+ return 0;
+ case 32:
+ return 1;
+ case 64:
+ return 2;
+ case 128:
+ return 3;
+ default:
+ return 3;
+ }
+}
+
+static int
+qpnp_flash_led_get_peripheral_type(struct qpnp_flash_led *led)
+{
+ int rc;
+ uint val;
+
+ rc = regmap_read(led->regmap,
+ FLASH_LED_PERIPHERAL_SUBTYPE(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read peripheral subtype\n");
+ return -EINVAL;
+ }
+
+ return val;
+}
+
+static int qpnp_flash_led_module_disable(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node)
+{
+ union power_supply_propval psy_prop;
+ int rc;
+ uint val, tmp;
+
+ rc = regmap_read(led->regmap, FLASH_LED_STROBE_CTRL(led->base), &val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Unable to read strobe reg\n");
+ return -EINVAL;
+ }
+
+ tmp = (~flash_node->trigger) & val;
+ if (!tmp) {
+ if (flash_node->type == TORCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_TORCH(led->base),
+ FLASH_TORCH_MASK, FLASH_LED_TORCH_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (led->battery_psy &&
+ led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+ !led->revid_data->rev3) {
+ psy_prop.intval = false;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to enble charger i/p current limit\n");
+ return -EINVAL;
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK,
+ FLASH_LED_MODULE_CTRL_DEFAULT);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ return -EINVAL;
+ }
+
+ if (led->pinctrl) {
+ rc = pinctrl_select_state(led->pinctrl,
+ led->gpio_state_suspend);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to disable GPIO\n");
+ return -EINVAL;
+ }
+ led->gpio_enabled = false;
+ }
+
+ if (led->battery_psy) {
+ psy_prop.intval = false;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to setup OTG pulse skip enable\n");
+ return -EINVAL;
+ }
+ }
+ }
+
+ if (flash_node->trigger & FLASH_LED0_TRIGGER) {
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, 0x00);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current register write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (flash_node->trigger & FLASH_LED1_TRIGGER) {
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, 0x00);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current register write failed\n");
+ return -EINVAL;
+ }
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH)
+ flash_node->trigger &= FLASH_LED_STROBE_TYPE_HW;
+
+ return 0;
+}
+
+static enum
+led_brightness qpnp_flash_led_brightness_get(struct led_classdev *led_cdev)
+{
+ return led_cdev->brightness;
+}
+
+static int flash_regulator_parse_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node) {
+
+ int i = 0, rc;
+ struct device_node *node = flash_node->cdev.dev->of_node;
+ struct device_node *temp = NULL;
+ const char *temp_string;
+ u32 val;
+
+ flash_node->reg_data = devm_kzalloc(&led->pdev->dev,
+ sizeof(struct flash_regulator_data *) *
+ flash_node->num_regulators,
+ GFP_KERNEL);
+ if (!flash_node->reg_data) {
+ dev_err(&led->pdev->dev,
+ "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(node, temp) {
+ rc = of_property_read_string(temp, "regulator-name",
+ &temp_string);
+ if (!rc)
+ flash_node->reg_data[i].reg_name = temp_string;
+ else {
+ dev_err(&led->pdev->dev,
+ "Unable to read regulator name\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(temp, "max-voltage", &val);
+ if (!rc) {
+ flash_node->reg_data[i].max_volt_uv = val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read max voltage\n");
+ return rc;
+ }
+
+ i++;
+ }
+
+ return 0;
+}
+
+static int flash_regulator_setup(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node, bool on)
+{
+ int i, rc = 0;
+
+ if (on == false) {
+ i = flash_node->num_regulators;
+ goto error_regulator_setup;
+ }
+
+ for (i = 0; i < flash_node->num_regulators; i++) {
+ flash_node->reg_data[i].regs =
+ regulator_get(flash_node->cdev.dev,
+ flash_node->reg_data[i].reg_name);
+ if (IS_ERR(flash_node->reg_data[i].regs)) {
+ rc = PTR_ERR(flash_node->reg_data[i].regs);
+ dev_err(&led->pdev->dev,
+ "Failed to get regulator\n");
+ goto error_regulator_setup;
+ }
+
+ if (regulator_count_voltages(flash_node->reg_data[i].regs)
+ > 0) {
+ rc = regulator_set_voltage(flash_node->reg_data[i].regs,
+ flash_node->reg_data[i].max_volt_uv,
+ flash_node->reg_data[i].max_volt_uv);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "regulator set voltage failed\n");
+ regulator_put(flash_node->reg_data[i].regs);
+ goto error_regulator_setup;
+ }
+ }
+ }
+
+ return rc;
+
+error_regulator_setup:
+ while (i--) {
+ if (regulator_count_voltages(flash_node->reg_data[i].regs)
+ > 0) {
+ regulator_set_voltage(flash_node->reg_data[i].regs,
+ 0, flash_node->reg_data[i].max_volt_uv);
+ }
+
+ regulator_put(flash_node->reg_data[i].regs);
+ }
+
+ return rc;
+}
+
+static int flash_regulator_enable(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node, bool on)
+{
+ int i, rc = 0;
+
+ if (on == false) {
+ i = flash_node->num_regulators;
+ goto error_regulator_enable;
+ }
+
+ for (i = 0; i < flash_node->num_regulators; i++) {
+ rc = regulator_enable(flash_node->reg_data[i].regs);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "regulator enable failed\n");
+ goto error_regulator_enable;
+ }
+ }
+
+ return rc;
+
+error_regulator_enable:
+ while (i--)
+ regulator_disable(flash_node->reg_data[i].regs);
+
+ return rc;
+}
+
+int qpnp_flash_led_prepare(struct led_trigger *trig, int options,
+ int *max_current)
+{
+ struct led_classdev *led_cdev = trigger_to_lcdev(trig);
+ struct flash_node_data *flash_node;
+ struct qpnp_flash_led *led;
+ int rc;
+
+ if (!led_cdev) {
+ pr_err("Invalid led_trigger provided\n");
+ return -EINVAL;
+ }
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (!(options & FLASH_LED_PREPARE_OPTIONS_MASK)) {
+ dev_err(&led->pdev->dev, "Invalid options %d\n", options);
+ return -EINVAL;
+ }
+
+ if (options & ENABLE_REGULATOR) {
+ rc = flash_regulator_enable(led, flash_node, true);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "enable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & DISABLE_REGULATOR) {
+ rc = flash_regulator_enable(led, flash_node, false);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "disable regulator failed, rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (options & QUERY_MAX_CURRENT) {
+ rc = qpnp_flash_led_get_max_avail_current(flash_node, led);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "query max current failed, rc=%d\n", rc);
+ return rc;
+ }
+ *max_current = rc;
+ }
+
+ return 0;
+}
+
+static void qpnp_flash_led_work(struct work_struct *work)
+{
+ struct flash_node_data *flash_node = container_of(work,
+ struct flash_node_data, work);
+ struct qpnp_flash_led *led = dev_get_drvdata(&flash_node->pdev->dev);
+ union power_supply_propval psy_prop;
+ int rc, brightness = flash_node->cdev.brightness;
+ int max_curr_avail_ma = 0;
+ int total_curr_ma = 0;
+ int i;
+ u8 val;
+ uint temp;
+
+ mutex_lock(&led->flash_led_lock);
+
+ if (!brightness)
+ goto turn_off;
+
+ if (led->open_fault) {
+ dev_err(&led->pdev->dev, "Open fault detected\n");
+ mutex_unlock(&led->flash_led_lock);
+ return;
+ }
+
+ if (!flash_node->flash_on && flash_node->num_regulators > 0) {
+ rc = flash_regulator_enable(led, flash_node, true);
+ if (rc) {
+ mutex_unlock(&led->flash_led_lock);
+ return;
+ }
+ }
+
+ if (!led->gpio_enabled && led->pinctrl) {
+ rc = pinctrl_select_state(led->pinctrl,
+ led->gpio_state_active);
+ if (rc) {
+ dev_err(&led->pdev->dev, "failed to enable GPIO\n");
+ goto error_enable_gpio;
+ }
+ led->gpio_enabled = true;
+ }
+
+ if (led->dbg_feature_en) {
+ rc = qpnp_led_masked_write(led,
+ INT_SET_TYPE(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "INT_SET_TYPE write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ IN_POLARITY_HIGH(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "IN_POLARITY_HIGH write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ INT_EN_SET(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev, "INT_EN_SET write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ INT_LATCHED_CLR(led->base),
+ FLASH_STATUS_REG_MASK, 0x1F);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "INT_LATCHED_CLR write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+ flash_node->id != FLASH_LED_SWITCH) {
+ led->flash_node[led->num_leds - 1].trigger |=
+ (0x80 >> flash_node->id);
+ if (flash_node->id == FLASH_LED_0)
+ led->flash_node[led->num_leds - 1].prgm_current =
+ flash_node->prgm_current;
+ else if (flash_node->id == FLASH_LED_1)
+ led->flash_node[led->num_leds - 1].prgm_current2 =
+ flash_node->prgm_current;
+ }
+
+ if (flash_node->type == TORCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_TORCH(led->base),
+ FLASH_TORCH_MASK, FLASH_LED_TORCH_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ val = (u8)(flash_node->prgm_current *
+ FLASH_TORCH_MAX_LEVEL
+ / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ val = (u8)(flash_node->prgm_current2 *
+ FLASH_TORCH_MAX_LEVEL
+ / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Torch reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ val = (u8)(flash_node->prgm_current *
+ FLASH_TORCH_MAX_LEVEL /
+ flash_node->max_current);
+ if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MAX_CURRENT(led->base),
+ FLASH_CURRENT_MASK, FLASH_TORCH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->pdata->hdrm_sns_ch0_en ||
+ led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ flash_node->trigger &
+ FLASH_LED0_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ flash_node->trigger &
+ FLASH_LED1_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger |
+ FLASH_LED_STROBE_TYPE_HW),
+ flash_node->trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->type == FLASH) {
+ if (flash_node->trigger & FLASH_LED0_TRIGGER)
+ max_curr_avail_ma += flash_node->max_current;
+ if (flash_node->trigger & FLASH_LED1_TRIGGER)
+ max_curr_avail_ma += flash_node->max_current;
+
+ psy_prop.intval = true;
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to setup OTG pulse skip enable\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->pdata->power_detect_en ||
+ led->pdata->die_current_derate_en) {
+ if (led->battery_psy) {
+ power_supply_get_property(led->battery_psy,
+ POWER_SUPPLY_PROP_STATUS,
+ &psy_prop);
+ if (psy_prop.intval < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid battery status\n");
+ goto exit_flash_led_work;
+ }
+
+ if (psy_prop.intval ==
+ POWER_SUPPLY_STATUS_CHARGING)
+ led->charging_enabled = true;
+ else if (psy_prop.intval ==
+ POWER_SUPPLY_STATUS_DISCHARGING
+ || psy_prop.intval ==
+ POWER_SUPPLY_STATUS_NOT_CHARGING)
+ led->charging_enabled = false;
+ }
+ max_curr_avail_ma =
+ qpnp_flash_led_get_max_avail_current
+ (flash_node, led);
+ if (max_curr_avail_ma < 0) {
+ dev_err(&led->pdev->dev,
+ "Failed to get max avail curr\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ if (flash_node->trigger & FLASH_LED0_TRIGGER)
+ total_curr_ma += flash_node->prgm_current;
+ if (flash_node->trigger & FLASH_LED1_TRIGGER)
+ total_curr_ma += flash_node->prgm_current2;
+
+ if (max_curr_avail_ma < total_curr_ma) {
+ flash_node->prgm_current =
+ (flash_node->prgm_current *
+ max_curr_avail_ma) / total_curr_ma;
+ flash_node->prgm_current2 =
+ (flash_node->prgm_current2 *
+ max_curr_avail_ma) / total_curr_ma;
+ }
+
+ val = (u8)(flash_node->prgm_current *
+ FLASH_MAX_LEVEL / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current_addr, FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current register write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ val = (u8)(flash_node->prgm_current2 *
+ FLASH_MAX_LEVEL / flash_node->max_current);
+ rc = qpnp_led_masked_write(led,
+ led->current2_addr, FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Current register write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else {
+ if (max_curr_avail_ma < flash_node->prgm_current) {
+ dev_err(&led->pdev->dev,
+ "battery only supprots %d mA\n",
+ max_curr_avail_ma);
+ flash_node->prgm_current =
+ (u16)max_curr_avail_ma;
+ }
+
+ val = (u8)(flash_node->prgm_current *
+ FLASH_MAX_LEVEL
+ / flash_node->max_current);
+ if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(
+ led,
+ led->current_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(
+ led,
+ led->current2_addr,
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ val = (u8)((flash_node->duration - FLASH_DURATION_DIVIDER)
+ / FLASH_DURATION_DIVIDER);
+ rc = qpnp_led_masked_write(led,
+ FLASH_SAFETY_TIMER(led->base),
+ FLASH_SAFETY_TIMER_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Safety timer reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MAX_CURRENT(led->base),
+ FLASH_CURRENT_MASK, FLASH_MAX_LEVEL);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Max current reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (!led->charging_enabled) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE, FLASH_MODULE_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Module enable reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ usleep_range(FLASH_RAMP_UP_DELAY_US_MIN,
+ FLASH_RAMP_UP_DELAY_US_MAX);
+ }
+
+ if (led->revid_data->pmic_subtype == PMI8996_SUBTYPE &&
+ !led->revid_data->rev3) {
+ rc = power_supply_set_property(led->battery_psy,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ &psy_prop);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to disable charger i/p curr limit\n");
+ goto exit_flash_led_work;
+ }
+ }
+
+ if (led->pdata->hdrm_sns_ch0_en ||
+ led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ (flash_node->trigger &
+ FLASH_LED0_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ (flash_node->trigger &
+ FLASH_LED1_TRIGGER ?
+ FLASH_LED_HDRM_SNS_ENABLE :
+ FLASH_LED_HDRM_SNS_DISABLE));
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense enable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_0) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ } else if (flash_node->id == FLASH_LED_1) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_ENABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_led_work;
+ }
+ }
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger |
+ FLASH_LED_STROBE_TYPE_HW),
+ flash_node->trigger);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe reg write failed\n");
+ goto exit_flash_led_work;
+ }
+
+ if (led->strobe_debug && led->dbg_feature_en) {
+ udelay(2000);
+ rc = regmap_read(led->regmap,
+ FLASH_LED_FAULT_STATUS(led->base),
+ &temp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from addr= %x, rc(%d)\n",
+ FLASH_LED_FAULT_STATUS(led->base), rc);
+ goto exit_flash_led_work;
+ }
+ led->fault_reg = temp;
+ }
+ } else {
+ pr_err("Both Torch and Flash cannot be select at same time\n");
+ for (i = 0; i < led->num_leds; i++)
+ led->flash_node[i].flash_on = false;
+ goto turn_off;
+ }
+
+ flash_node->flash_on = true;
+ mutex_unlock(&led->flash_led_lock);
+
+ return;
+
+turn_off:
+ if (led->flash_node[led->num_leds - 1].id == FLASH_LED_SWITCH &&
+ flash_node->id != FLASH_LED_SWITCH)
+ led->flash_node[led->num_leds - 1].trigger &=
+ ~(0x80 >> flash_node->id);
+ if (flash_node->type == TORCH) {
+ /*
+ * Checking LED fault status detects hardware open fault.
+ * If fault occurs, all subsequent LED enablement requests
+ * will be rejected to protect hardware.
+ */
+ rc = regmap_read(led->regmap,
+ FLASH_LED_FAULT_STATUS(led->base), &temp);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Failed to read out fault status register\n");
+ goto exit_flash_led_work;
+ }
+
+ led->open_fault |= (val & FLASH_LED_OPEN_FAULT_DETECTED);
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ (flash_node->id == FLASH_LED_SWITCH ? FLASH_STROBE_MASK
+ | FLASH_LED_STROBE_TYPE_HW
+ : flash_node->trigger
+ | FLASH_LED_STROBE_TYPE_HW),
+ FLASH_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe disable failed\n");
+ goto exit_flash_led_work;
+ }
+
+ usleep_range(FLASH_RAMP_DN_DELAY_US_MIN, FLASH_RAMP_DN_DELAY_US_MAX);
+exit_flash_hdrm_sns:
+ if (led->pdata->hdrm_sns_ch0_en) {
+ if (flash_node->id == FLASH_LED_0 ||
+ flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL0(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_hdrm_sns;
+ }
+ }
+ }
+
+ if (led->pdata->hdrm_sns_ch1_en) {
+ if (flash_node->id == FLASH_LED_1 ||
+ flash_node->id == FLASH_LED_SWITCH) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_HDRM_SNS_ENABLE_CTRL1(led->base),
+ FLASH_LED_HDRM_SNS_ENABLE_MASK,
+ FLASH_LED_HDRM_SNS_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Headroom sense disable failed\n");
+ goto exit_flash_hdrm_sns;
+ }
+ }
+ }
+exit_flash_led_work:
+ rc = qpnp_flash_led_module_disable(led, flash_node);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ goto exit_flash_led_work;
+ }
+error_enable_gpio:
+ if (flash_node->flash_on && flash_node->num_regulators > 0)
+ flash_regulator_enable(led, flash_node, false);
+
+ flash_node->flash_on = false;
+ mutex_unlock(&led->flash_led_lock);
+}
+
+static void qpnp_flash_led_brightness_set(struct led_classdev *led_cdev,
+ enum led_brightness value)
+{
+ struct flash_node_data *flash_node;
+ struct qpnp_flash_led *led;
+
+ flash_node = container_of(led_cdev, struct flash_node_data, cdev);
+ led = dev_get_drvdata(&flash_node->pdev->dev);
+
+ if (value < LED_OFF) {
+ pr_err("Invalid brightness value\n");
+ return;
+ }
+
+ if (value > flash_node->cdev.max_brightness)
+ value = flash_node->cdev.max_brightness;
+
+ flash_node->cdev.brightness = value;
+ if (led->flash_node[led->num_leds - 1].id ==
+ FLASH_LED_SWITCH) {
+ if (flash_node->type == TORCH)
+ led->flash_node[led->num_leds - 1].type = TORCH;
+ else if (flash_node->type == FLASH)
+ led->flash_node[led->num_leds - 1].type = FLASH;
+
+ led->flash_node[led->num_leds - 1].max_current
+ = flash_node->max_current;
+
+ if (flash_node->id == FLASH_LED_0 ||
+ flash_node->id == FLASH_LED_1) {
+ if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+ value = FLASH_LED_MIN_CURRENT_MA;
+
+ flash_node->prgm_current = value;
+ flash_node->flash_on = value ? true : false;
+ } else if (flash_node->id == FLASH_LED_SWITCH) {
+ if (!value) {
+ flash_node->prgm_current = 0;
+ flash_node->prgm_current2 = 0;
+ }
+ }
+ } else {
+ if (value < FLASH_LED_MIN_CURRENT_MA && value != 0)
+ value = FLASH_LED_MIN_CURRENT_MA;
+ flash_node->prgm_current = value;
+ }
+
+ queue_work(led->ordered_workq, &flash_node->work);
+}
+
+static int qpnp_flash_led_init_settings(struct qpnp_flash_led *led)
+{
+ int rc;
+ u8 val, temp_val;
+ uint val_int;
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_MODULE_ENABLE_CTRL(led->base),
+ FLASH_MODULE_ENABLE_MASK,
+ FLASH_LED_MODULE_CTRL_DEFAULT);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Module disable failed\n");
+ return rc;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_STROBE_CTRL(led->base),
+ FLASH_STROBE_MASK, FLASH_LED_DISABLE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Strobe disable failed\n");
+ return rc;
+ }
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_TMR_CTRL(led->base),
+ FLASH_TMR_MASK, FLASH_TMR_SAFETY);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "LED timer ctrl reg write failed(%d)\n", rc);
+ return rc;
+ }
+
+ val = (u8)(led->pdata->headroom / FLASH_LED_HEADROOM_DIVIDER -
+ FLASH_LED_HEADROOM_OFFSET);
+ rc = qpnp_led_masked_write(led,
+ FLASH_HEADROOM(led->base),
+ FLASH_HEADROOM_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Headroom reg write failed\n");
+ return rc;
+ }
+
+ val = qpnp_flash_led_get_startup_dly(led->pdata->startup_dly);
+
+ rc = qpnp_led_masked_write(led,
+ FLASH_STARTUP_DELAY(led->base),
+ FLASH_STARTUP_DLY_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Startup delay reg write failed\n");
+ return rc;
+ }
+
+ val = (u8)(led->pdata->clamp_current * FLASH_MAX_LEVEL /
+ FLASH_LED_MAX_CURRENT_MA);
+ rc = qpnp_led_masked_write(led,
+ FLASH_CLAMP_CURRENT(led->base),
+ FLASH_CURRENT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Clamp current reg write failed\n");
+ return rc;
+ }
+
+ if (led->pdata->pmic_charger_support)
+ val = FLASH_LED_FLASH_HW_VREG_OK;
+ else
+ val = FLASH_LED_FLASH_SW_VREG_OK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_VREG_OK_FORCE(led->base),
+ FLASH_VREG_OK_FORCE_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "VREG OK force reg write failed\n");
+ return rc;
+ }
+
+ if (led->pdata->self_check_en)
+ val = FLASH_MODULE_ENABLE;
+ else
+ val = FLASH_LED_DISABLE;
+ rc = qpnp_led_masked_write(led,
+ FLASH_FAULT_DETECT(led->base),
+ FLASH_FAULT_DETECT_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Fault detect reg write failed\n");
+ return rc;
+ }
+
+ val = 0x0;
+ val |= led->pdata->mask3_en << FLASH_LED_MASK3_ENABLE_SHIFT;
+ val |= FLASH_LED_MASK_MODULE_MASK2_ENABLE;
+ rc = qpnp_led_masked_write(led, FLASH_MASK_ENABLE(led->base),
+ FLASH_MASK_MODULE_CONTRL_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Mask module enable failed\n");
+ return rc;
+ }
+
+ rc = regmap_read(led->regmap, FLASH_PERPH_RESET_CTRL(led->base),
+ &val_int);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "Unable to read from address %x, rc(%d)\n",
+ FLASH_PERPH_RESET_CTRL(led->base), rc);
+ return -EINVAL;
+ }
+ val = (u8)val_int;
+
+ if (led->pdata->follow_rb_disable) {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ val |= FLASH_FOLLOW_OTST2_RB_MASK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_PERPH_RESET_CTRL(led->base),
+ FLASH_FOLLOW_OTST2_RB_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to reset OTST2_RB bit\n");
+ return rc;
+ }
+ } else {
+ rc = qpnp_led_masked_write(led,
+ FLASH_LED_UNLOCK_SECURE(led->base),
+ FLASH_SECURE_MASK, FLASH_UNLOCK_SECURE);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Secure reg write failed\n");
+ return -EINVAL;
+ }
+
+ val &= ~FLASH_FOLLOW_OTST2_RB_MASK;
+ rc = qpnp_led_masked_write(led,
+ FLASH_PERPH_RESET_CTRL(led->base),
+ FLASH_FOLLOW_OTST2_RB_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "failed to reset OTST2_RB bit\n");
+ return rc;
+ }
+ }
+
+ if (!led->pdata->thermal_derate_en)
+ val = 0x0;
+ else {
+ val = led->pdata->thermal_derate_en << 7;
+ val |= led->pdata->thermal_derate_rate << 3;
+ val |= (led->pdata->thermal_derate_threshold -
+ FLASH_LED_THERMAL_THRESHOLD_MIN) /
+ FLASH_LED_THERMAL_DEVIDER;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_THERMAL_DRATE(led->base),
+ FLASH_THERMAL_DERATE_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Thermal derate reg write failed\n");
+ return rc;
+ }
+
+ if (!led->pdata->current_ramp_en)
+ val = 0x0;
+ else {
+ val = led->pdata->current_ramp_en << 7;
+ val |= led->pdata->ramp_up_step << 3;
+ val |= led->pdata->ramp_dn_step;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_CURRENT_RAMP(led->base),
+ FLASH_CURRENT_RAMP_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "Current ramp reg write failed\n");
+ return rc;
+ }
+
+ if (!led->pdata->vph_pwr_droop_en)
+ val = 0x0;
+ else {
+ val = led->pdata->vph_pwr_droop_en << 7;
+ val |= ((led->pdata->vph_pwr_droop_threshold -
+ FLASH_LED_VPH_DROOP_THRESHOLD_MIN_MV) /
+ FLASH_LED_VPH_DROOP_THRESHOLD_DIVIDER) << 4;
+ temp_val =
+ qpnp_flash_led_get_droop_debounce_time(
+ led->pdata->vph_pwr_droop_debounce_time);
+ if (temp_val == 0xFF) {
+ dev_err(&led->pdev->dev, "Invalid debounce time\n");
+ return temp_val;
+ }
+
+ val |= temp_val;
+ }
+ rc = qpnp_led_masked_write(led,
+ FLASH_VPH_PWR_DROOP(led->base),
+ FLASH_VPH_PWR_DROOP_MASK, val);
+ if (rc) {
+ dev_err(&led->pdev->dev, "VPH PWR droop reg write failed\n");
+ return rc;
+ }
+
+ led->battery_psy = power_supply_get_by_name("battery");
+ if (!led->battery_psy) {
+ dev_err(&led->pdev->dev,
+ "Failed to get battery power supply\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_parse_each_led_dt(struct qpnp_flash_led *led,
+ struct flash_node_data *flash_node)
+{
+ const char *temp_string;
+ struct device_node *node = flash_node->cdev.dev->of_node;
+ struct device_node *temp = NULL;
+ int rc = 0, num_regs = 0;
+ u32 val;
+
+ rc = of_property_read_string(node, "label", &temp_string);
+ if (!rc) {
+ if (strcmp(temp_string, "flash") == 0)
+ flash_node->type = FLASH;
+ else if (strcmp(temp_string, "torch") == 0)
+ flash_node->type = TORCH;
+ else if (strcmp(temp_string, "switch") == 0)
+ flash_node->type = SWITCH;
+ else {
+ dev_err(&led->pdev->dev, "Wrong flash LED type\n");
+ return -EINVAL;
+ }
+ } else if (rc < 0) {
+ dev_err(&led->pdev->dev, "Unable to read flash type\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ flash_node->prgm_current = val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read current\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(node, "qcom,id", &val);
+ if (!rc)
+ flash_node->id = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read led ID\n");
+ return rc;
+ }
+
+ if (flash_node->type == SWITCH || flash_node->type == FLASH) {
+ rc = of_property_read_u32(node, "qcom,duration", &val);
+ if (!rc)
+ flash_node->duration = (u16)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read duration\n");
+ return rc;
+ }
+ }
+
+ switch (led->peripheral_type) {
+ case FLASH_SUBTYPE_SINGLE:
+ flash_node->trigger = FLASH_LED0_TRIGGER;
+ break;
+ case FLASH_SUBTYPE_DUAL:
+ if (flash_node->id == FLASH_LED_0)
+ flash_node->trigger = FLASH_LED0_TRIGGER;
+ else if (flash_node->id == FLASH_LED_1)
+ flash_node->trigger = FLASH_LED1_TRIGGER;
+ break;
+ default:
+ dev_err(&led->pdev->dev, "Invalid peripheral type\n");
+ }
+
+ while ((temp = of_get_next_child(node, temp))) {
+ if (of_find_property(temp, "regulator-name", NULL))
+ num_regs++;
+ }
+
+ if (num_regs)
+ flash_node->num_regulators = num_regs;
+
+ return rc;
+}
+
+static int qpnp_flash_led_parse_common_dt(
+ struct qpnp_flash_led *led,
+ struct device_node *node)
+{
+ int rc;
+ u32 val, temp_val;
+ const char *temp;
+
+ led->pdata->headroom = FLASH_LED_HEADROOM_DEFAULT_MV;
+ rc = of_property_read_u32(node, "qcom,headroom", &val);
+ if (!rc)
+ led->pdata->headroom = (u16)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read headroom\n");
+ return rc;
+ }
+
+ led->pdata->startup_dly = FLASH_LED_STARTUP_DELAY_DEFAULT_US;
+ rc = of_property_read_u32(node, "qcom,startup-dly", &val);
+ if (!rc)
+ led->pdata->startup_dly = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read startup delay\n");
+ return rc;
+ }
+
+ led->pdata->clamp_current = FLASH_LED_CLAMP_CURRENT_DEFAULT_MA;
+ rc = of_property_read_u32(node, "qcom,clamp-current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ led->pdata->clamp_current = (u16)val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev, "Unable to read clamp current\n");
+ return rc;
+ }
+
+ led->pdata->pmic_charger_support =
+ of_property_read_bool(node,
+ "qcom,pmic-charger-support");
+
+ led->pdata->self_check_en =
+ of_property_read_bool(node, "qcom,self-check-enabled");
+
+ led->pdata->thermal_derate_en =
+ of_property_read_bool(node,
+ "qcom,thermal-derate-enabled");
+
+ if (led->pdata->thermal_derate_en) {
+ led->pdata->thermal_derate_rate =
+ FLASH_LED_THERMAL_DERATE_RATE_DEFAULT_PERCENT;
+ rc = of_property_read_string(node, "qcom,thermal-derate-rate",
+ &temp);
+ if (!rc) {
+ temp_val =
+ qpnp_flash_led_get_thermal_derate_rate(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid thermal derate rate\n");
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_rate = (u8)temp_val;
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read thermal derate rate\n");
+ return -EINVAL;
+ }
+
+ led->pdata->thermal_derate_threshold =
+ FLASH_LED_THERMAL_DERATE_THRESHOLD_DEFAULT_C;
+ rc = of_property_read_u32(node, "qcom,thermal-derate-threshold",
+ &val);
+ if (!rc)
+ led->pdata->thermal_derate_threshold = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read thermal derate threshold\n");
+ return rc;
+ }
+ }
+
+ led->pdata->current_ramp_en =
+ of_property_read_bool(node,
+ "qcom,current-ramp-enabled");
+ if (led->pdata->current_ramp_en) {
+ led->pdata->ramp_up_step = FLASH_LED_RAMP_UP_STEP_DEFAULT_US;
+ rc = of_property_read_string(node, "qcom,ramp_up_step", &temp);
+ if (!rc) {
+ temp_val = qpnp_flash_led_get_ramp_step(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid ramp up step values\n");
+ return -EINVAL;
+ }
+ led->pdata->ramp_up_step = (u8)temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read ramp up steps\n");
+ return rc;
+ }
+
+ led->pdata->ramp_dn_step = FLASH_LED_RAMP_DN_STEP_DEFAULT_US;
+ rc = of_property_read_string(node, "qcom,ramp_dn_step", &temp);
+ if (!rc) {
+ temp_val = qpnp_flash_led_get_ramp_step(temp);
+ if (temp_val < 0) {
+ dev_err(&led->pdev->dev,
+ "Invalid ramp down step values\n");
+ return rc;
+ }
+ led->pdata->ramp_dn_step = (u8)temp_val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read ramp down steps\n");
+ return rc;
+ }
+ }
+
+ led->pdata->vph_pwr_droop_en = of_property_read_bool(node,
+ "qcom,vph-pwr-droop-enabled");
+ if (led->pdata->vph_pwr_droop_en) {
+ led->pdata->vph_pwr_droop_threshold =
+ FLASH_LED_VPH_PWR_DROOP_THRESHOLD_DEFAULT_MV;
+ rc = of_property_read_u32(node,
+ "qcom,vph-pwr-droop-threshold", &val);
+ if (!rc) {
+ led->pdata->vph_pwr_droop_threshold = (u16)val;
+ } else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read VPH PWR droop threshold\n");
+ return rc;
+ }
+
+ led->pdata->vph_pwr_droop_debounce_time =
+ FLASH_LED_VPH_PWR_DROOP_DEBOUNCE_TIME_DEFAULT_US;
+ rc = of_property_read_u32(node,
+ "qcom,vph-pwr-droop-debounce-time", &val);
+ if (!rc)
+ led->pdata->vph_pwr_droop_debounce_time = (u8)val;
+ else if (rc != -EINVAL) {
+ dev_err(&led->pdev->dev,
+ "Unable to read VPH PWR droop debounce time\n");
+ return rc;
+ }
+ }
+
+ led->pdata->hdrm_sns_ch0_en = of_property_read_bool(node,
+ "qcom,headroom-sense-ch0-enabled");
+
+ led->pdata->hdrm_sns_ch1_en = of_property_read_bool(node,
+ "qcom,headroom-sense-ch1-enabled");
+
+ led->pdata->power_detect_en = of_property_read_bool(node,
+ "qcom,power-detect-enabled");
+
+ led->pdata->mask3_en = of_property_read_bool(node,
+ "qcom,otst2-module-enabled");
+
+ led->pdata->follow_rb_disable = of_property_read_bool(node,
+ "qcom,follow-otst2-rb-disabled");
+
+ led->pdata->die_current_derate_en = of_property_read_bool(node,
+ "qcom,die-current-derate-enabled");
+
+ if (led->pdata->die_current_derate_en) {
+ led->vadc_dev = qpnp_get_vadc(&led->pdev->dev, "die-temp");
+ if (IS_ERR(led->vadc_dev)) {
+ pr_err("VADC channel property Missing\n");
+ return -EINVAL;
+ }
+
+ if (of_find_property(node, "qcom,die-temp-threshold",
+ &led->pdata->temp_threshold_num)) {
+ if (led->pdata->temp_threshold_num > 0) {
+ led->pdata->die_temp_threshold_degc =
+ devm_kzalloc(&led->pdev->dev,
+ led->pdata->temp_threshold_num,
+ GFP_KERNEL);
+
+ if (led->pdata->die_temp_threshold_degc
+ == NULL) {
+ dev_err(&led->pdev->dev,
+ "failed to allocate die temp array\n");
+ return -ENOMEM;
+ }
+ led->pdata->temp_threshold_num /=
+ sizeof(unsigned int);
+
+ rc = of_property_read_u32_array(node,
+ "qcom,die-temp-threshold",
+ led->pdata->die_temp_threshold_degc,
+ led->pdata->temp_threshold_num);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "couldn't read temp threshold rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ if (of_find_property(node, "qcom,die-temp-derate-current",
+ &led->pdata->temp_derate_curr_num)) {
+ if (led->pdata->temp_derate_curr_num > 0) {
+ led->pdata->die_temp_derate_curr_ma =
+ devm_kzalloc(&led->pdev->dev,
+ led->pdata->temp_derate_curr_num,
+ GFP_KERNEL);
+ if (led->pdata->die_temp_derate_curr_ma
+ == NULL) {
+ dev_err(&led->pdev->dev,
+ "failed to allocate die derate current array\n");
+ return -ENOMEM;
+ }
+ led->pdata->temp_derate_curr_num /=
+ sizeof(unsigned int);
+
+ rc = of_property_read_u32_array(node,
+ "qcom,die-temp-derate-current",
+ led->pdata->die_temp_derate_curr_ma,
+ led->pdata->temp_derate_curr_num);
+ if (rc) {
+ dev_err(&led->pdev->dev,
+ "couldn't read temp limits rc =%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+ if (led->pdata->temp_threshold_num !=
+ led->pdata->temp_derate_curr_num) {
+ pr_err("Both array size are not same\n");
+ return -EINVAL;
+ }
+ }
+
+ led->pinctrl = devm_pinctrl_get(&led->pdev->dev);
+ if (IS_ERR_OR_NULL(led->pinctrl)) {
+ dev_err(&led->pdev->dev, "Unable to acquire pinctrl\n");
+ led->pinctrl = NULL;
+ return 0;
+ }
+
+ led->gpio_state_active = pinctrl_lookup_state(led->pinctrl,
+ "flash_led_enable");
+ if (IS_ERR_OR_NULL(led->gpio_state_active)) {
+ dev_err(&led->pdev->dev, "Cannot lookup LED active state\n");
+ devm_pinctrl_put(led->pinctrl);
+ led->pinctrl = NULL;
+ return PTR_ERR(led->gpio_state_active);
+ }
+
+ led->gpio_state_suspend = pinctrl_lookup_state(led->pinctrl,
+ "flash_led_disable");
+ if (IS_ERR_OR_NULL(led->gpio_state_suspend)) {
+ dev_err(&led->pdev->dev, "Cannot lookup LED disable state\n");
+ devm_pinctrl_put(led->pinctrl);
+ led->pinctrl = NULL;
+ return PTR_ERR(led->gpio_state_suspend);
+ }
+
+ return 0;
+}
+
+static int qpnp_flash_led_probe(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led;
+ unsigned int base;
+ struct device_node *node, *temp;
+ struct dentry *root, *file;
+ int rc, i = 0, j, num_leds = 0;
+ u32 val;
+
+ root = NULL;
+ node = pdev->dev.of_node;
+ if (node == NULL) {
+ dev_info(&pdev->dev, "No flash device defined\n");
+ return -ENODEV;
+ }
+
+ rc = of_property_read_u32(pdev->dev.of_node, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ pdev->dev.of_node->full_name, rc);
+ return rc;
+ }
+
+ led = devm_kzalloc(&pdev->dev, sizeof(*led), GFP_KERNEL);
+ if (!led)
+ return -ENOMEM;
+
+ led->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!led->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ led->base = base;
+ led->pdev = pdev;
+ led->current_addr = FLASH_LED0_CURRENT(led->base);
+ led->current2_addr = FLASH_LED1_CURRENT(led->base);
+
+ led->pdata = devm_kzalloc(&pdev->dev, sizeof(*led->pdata), GFP_KERNEL);
+ if (!led->pdata)
+ return -ENOMEM;
+
+ led->peripheral_type = (u8)qpnp_flash_led_get_peripheral_type(led);
+ if (led->peripheral_type < 0) {
+ dev_err(&pdev->dev, "Failed to get peripheral type\n");
+ return rc;
+ }
+
+ rc = qpnp_flash_led_parse_common_dt(led, node);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Failed to get common config for flash LEDs\n");
+ return rc;
+ }
+
+ rc = qpnp_flash_led_init_settings(led);
+ if (rc) {
+ dev_err(&pdev->dev, "Failed to initialize flash LED\n");
+ return rc;
+ }
+
+ rc = qpnp_get_pmic_revid(led);
+ if (rc)
+ return rc;
+
+ temp = NULL;
+ while ((temp = of_get_next_child(node, temp)))
+ num_leds++;
+
+ if (!num_leds)
+ return -ECHILD;
+
+ led->flash_node = devm_kzalloc(&pdev->dev,
+ (sizeof(struct flash_node_data) * num_leds),
+ GFP_KERNEL);
+ if (!led->flash_node) {
+ dev_err(&pdev->dev, "Unable to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ mutex_init(&led->flash_led_lock);
+
+ led->ordered_workq = alloc_ordered_workqueue("flash_led_workqueue", 0);
+ if (!led->ordered_workq) {
+ dev_err(&pdev->dev, "Failed to allocate ordered workqueue\n");
+ return -ENOMEM;
+ }
+
+ for_each_child_of_node(node, temp) {
+ led->flash_node[i].cdev.brightness_set =
+ qpnp_flash_led_brightness_set;
+ led->flash_node[i].cdev.brightness_get =
+ qpnp_flash_led_brightness_get;
+ led->flash_node[i].pdev = pdev;
+
+ INIT_WORK(&led->flash_node[i].work, qpnp_flash_led_work);
+ rc = of_property_read_string(temp, "qcom,led-name",
+ &led->flash_node[i].cdev.name);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read flash name\n");
+ return rc;
+ }
+
+ rc = of_property_read_string(temp, "qcom,default-led-trigger",
+ &led->flash_node[i].cdev.default_trigger);
+ if (rc < 0) {
+ dev_err(&led->pdev->dev,
+ "Unable to read trigger name\n");
+ return rc;
+ }
+
+ rc = of_property_read_u32(temp, "qcom,max-current", &val);
+ if (!rc) {
+ if (val < FLASH_LED_MIN_CURRENT_MA)
+ val = FLASH_LED_MIN_CURRENT_MA;
+ led->flash_node[i].max_current = (u16)val;
+ led->flash_node[i].cdev.max_brightness = val;
+ } else {
+ dev_err(&led->pdev->dev,
+ "Unable to read max current\n");
+ return rc;
+ }
+ rc = led_classdev_register(&pdev->dev,
+ &led->flash_node[i].cdev);
+ if (rc) {
+ dev_err(&pdev->dev, "Unable to register led\n");
+ goto error_led_register;
+ }
+
+ led->flash_node[i].cdev.dev->of_node = temp;
+
+ rc = qpnp_flash_led_parse_each_led_dt(led, &led->flash_node[i]);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Failed to parse config for each LED\n");
+ goto error_led_register;
+ }
+
+ if (led->flash_node[i].num_regulators) {
+ rc = flash_regulator_parse_dt(led, &led->flash_node[i]);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to parse regulator data\n");
+ goto error_led_register;
+ }
+
+ rc = flash_regulator_setup(led, &led->flash_node[i],
+ true);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Unable to set up regulator\n");
+ goto error_led_register;
+ }
+ }
+
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++) {
+ rc =
+ sysfs_create_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ if (rc)
+ goto error_led_register;
+ }
+
+ i++;
+ }
+
+ led->num_leds = i;
+
+ root = debugfs_create_dir("flashLED", NULL);
+ if (IS_ERR_OR_NULL(root)) {
+ pr_err("Error creating top level directory err%ld",
+ (long)root);
+ if (PTR_ERR(root) == -ENODEV)
+ pr_err("debugfs is not enabled in kernel");
+ goto error_led_debugfs;
+ }
+
+ led->dbgfs_root = root;
+ file = debugfs_create_file("enable_debug", 0600, root, led,
+ &flash_led_dfs_dbg_feature_fops);
+ if (!file) {
+ pr_err("error creating 'enable_debug' entry\n");
+ goto error_led_debugfs;
+ }
+
+ file = debugfs_create_file("latched", 0600, root, led,
+ &flash_led_dfs_latched_reg_fops);
+ if (!file) {
+ pr_err("error creating 'latched' entry\n");
+ goto error_led_debugfs;
+ }
+
+ file = debugfs_create_file("strobe", 0600, root, led,
+ &flash_led_dfs_strobe_reg_fops);
+ if (!file) {
+ pr_err("error creating 'strobe' entry\n");
+ goto error_led_debugfs;
+ }
+
+ dev_set_drvdata(&pdev->dev, led);
+
+ return 0;
+
+error_led_debugfs:
+ i = led->num_leds - 1;
+ j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+error_led_register:
+ for (; i >= 0; i--) {
+ for (; j >= 0; j--)
+ sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ j = ARRAY_SIZE(qpnp_flash_led_attrs) - 1;
+ led_classdev_unregister(&led->flash_node[i].cdev);
+ }
+ debugfs_remove_recursive(root);
+ mutex_destroy(&led->flash_led_lock);
+ destroy_workqueue(led->ordered_workq);
+
+ return rc;
+}
+
+static int qpnp_flash_led_remove(struct platform_device *pdev)
+{
+ struct qpnp_flash_led *led = dev_get_drvdata(&pdev->dev);
+ int i, j;
+
+ for (i = led->num_leds - 1; i >= 0; i--) {
+ if (led->flash_node[i].reg_data) {
+ if (led->flash_node[i].flash_on)
+ flash_regulator_enable(led,
+ &led->flash_node[i], false);
+ flash_regulator_setup(led, &led->flash_node[i],
+ false);
+ }
+ for (j = 0; j < ARRAY_SIZE(qpnp_flash_led_attrs); j++)
+ sysfs_remove_file(&led->flash_node[i].cdev.dev->kobj,
+ &qpnp_flash_led_attrs[j].attr);
+ led_classdev_unregister(&led->flash_node[i].cdev);
+ }
+ debugfs_remove_recursive(led->dbgfs_root);
+ mutex_destroy(&led->flash_led_lock);
+ destroy_workqueue(led->ordered_workq);
+
+ return 0;
+}
+
+static const struct of_device_id spmi_match_table[] = {
+ { .compatible = "qcom,qpnp-flash-led",},
+ { },
+};
+
+static struct platform_driver qpnp_flash_led_driver = {
+ .driver = {
+ .name = "qcom,qpnp-flash-led",
+ .of_match_table = spmi_match_table,
+ },
+ .probe = qpnp_flash_led_probe,
+ .remove = qpnp_flash_led_remove,
+};
+
+static int __init qpnp_flash_led_init(void)
+{
+ return platform_driver_register(&qpnp_flash_led_driver);
+}
+late_initcall(qpnp_flash_led_init);
+
+static void __exit qpnp_flash_led_exit(void)
+{
+ platform_driver_unregister(&qpnp_flash_led_driver);
+}
+module_exit(qpnp_flash_led_exit);
+
+MODULE_DESCRIPTION("QPNP Flash LED driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("leds:leds-qpnp-flash");
diff --git a/drivers/md/bcache/request.c b/drivers/md/bcache/request.c
index 6c4c7caea693..0ee41fd9d850 100644
--- a/drivers/md/bcache/request.c
+++ b/drivers/md/bcache/request.c
@@ -1014,7 +1014,7 @@ static int cached_dev_congested(void *data, int bits)
struct request_queue *q = bdev_get_queue(dc->bdev);
int ret = 0;
- if (bdi_congested(&q->backing_dev_info, bits))
+ if (bdi_congested(q->backing_dev_info, bits))
return 1;
if (cached_dev_get(dc)) {
@@ -1023,7 +1023,7 @@ static int cached_dev_congested(void *data, int bits)
for_each_cache(ca, d->c, i) {
q = bdev_get_queue(ca->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
}
cached_dev_put(dc);
@@ -1037,7 +1037,7 @@ void bch_cached_dev_request_init(struct cached_dev *dc)
struct gendisk *g = dc->disk.disk;
g->queue->make_request_fn = cached_dev_make_request;
- g->queue->backing_dev_info.congested_fn = cached_dev_congested;
+ g->queue->backing_dev_info->congested_fn = cached_dev_congested;
dc->disk.cache_miss = cached_dev_cache_miss;
dc->disk.ioctl = cached_dev_ioctl;
}
@@ -1130,7 +1130,7 @@ static int flash_dev_congested(void *data, int bits)
for_each_cache(ca, d->c, i) {
q = bdev_get_queue(ca->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
}
return ret;
@@ -1141,7 +1141,7 @@ void bch_flash_dev_request_init(struct bcache_device *d)
struct gendisk *g = d->disk;
g->queue->make_request_fn = flash_dev_make_request;
- g->queue->backing_dev_info.congested_fn = flash_dev_congested;
+ g->queue->backing_dev_info->congested_fn = flash_dev_congested;
d->cache_miss = flash_dev_cache_miss;
d->ioctl = flash_dev_ioctl;
}
diff --git a/drivers/md/bcache/super.c b/drivers/md/bcache/super.c
index c5ceea9222ff..13acf48c5210 100644
--- a/drivers/md/bcache/super.c
+++ b/drivers/md/bcache/super.c
@@ -802,7 +802,7 @@ static int bcache_device_init(struct bcache_device *d, unsigned block_size,
blk_queue_make_request(q, NULL);
d->disk->queue = q;
q->queuedata = d;
- q->backing_dev_info.congested_data = d;
+ q->backing_dev_info->congested_data = d;
q->limits.max_hw_sectors = UINT_MAX;
q->limits.max_sectors = UINT_MAX;
q->limits.max_segment_size = UINT_MAX;
@@ -1129,9 +1129,9 @@ static int cached_dev_init(struct cached_dev *dc, unsigned block_size)
set_capacity(dc->disk.disk,
dc->bdev->bd_part->nr_sects - dc->sb.data_offset);
- dc->disk.disk->queue->backing_dev_info.ra_pages =
- max(dc->disk.disk->queue->backing_dev_info.ra_pages,
- q->backing_dev_info.ra_pages);
+ dc->disk.disk->queue->backing_dev_info->ra_pages =
+ max(dc->disk.disk->queue->backing_dev_info->ra_pages,
+ q->backing_dev_info->ra_pages);
bch_cached_dev_request_init(dc);
bch_cached_dev_writeback_init(dc);
diff --git a/drivers/md/dm-cache-target.c b/drivers/md/dm-cache-target.c
index 0da5efaad85c..54e50fc908e9 100644
--- a/drivers/md/dm-cache-target.c
+++ b/drivers/md/dm-cache-target.c
@@ -2288,7 +2288,7 @@ static void do_waker(struct work_struct *ws)
static int is_congested(struct dm_dev *dev, int bdi_bits)
{
struct request_queue *q = bdev_get_queue(dev->bdev);
- return bdi_congested(&q->backing_dev_info, bdi_bits);
+ return bdi_congested(q->backing_dev_info, bdi_bits);
}
static int cache_is_congested(struct dm_target_callbacks *cb, int bdi_bits)
diff --git a/drivers/md/dm-era-target.c b/drivers/md/dm-era-target.c
index 32e76c5ee741..11c52567304f 100644
--- a/drivers/md/dm-era-target.c
+++ b/drivers/md/dm-era-target.c
@@ -1379,7 +1379,7 @@ static void stop_worker(struct era *era)
static int dev_is_congested(struct dm_dev *dev, int bdi_bits)
{
struct request_queue *q = bdev_get_queue(dev->bdev);
- return bdi_congested(&q->backing_dev_info, bdi_bits);
+ return bdi_congested(q->backing_dev_info, bdi_bits);
}
static int era_is_congested(struct dm_target_callbacks *cb, int bdi_bits)
diff --git a/drivers/md/dm-table.c b/drivers/md/dm-table.c
index b3d78bba3a79..9411deaaddf9 100644
--- a/drivers/md/dm-table.c
+++ b/drivers/md/dm-table.c
@@ -1660,7 +1660,7 @@ int dm_table_any_congested(struct dm_table *t, int bdi_bits)
char b[BDEVNAME_SIZE];
if (likely(q))
- r |= bdi_congested(&q->backing_dev_info, bdi_bits);
+ r |= bdi_congested(q->backing_dev_info, bdi_bits);
else
DMWARN_LIMIT("%s: any_congested: nonexistent device %s",
dm_device_name(t->md),
diff --git a/drivers/md/dm-thin.c b/drivers/md/dm-thin.c
index a1cc797fe88f..5f1a943d9e81 100644
--- a/drivers/md/dm-thin.c
+++ b/drivers/md/dm-thin.c
@@ -2634,7 +2634,7 @@ static int pool_is_congested(struct dm_target_callbacks *cb, int bdi_bits)
return 1;
q = bdev_get_queue(pt->data_dev->bdev);
- return bdi_congested(&q->backing_dev_info, bdi_bits);
+ return bdi_congested(q->backing_dev_info, bdi_bits);
}
static void requeue_bios(struct pool *pool)
diff --git a/drivers/md/dm.c b/drivers/md/dm.c
index 1a7b11d57256..47ac131099d9 100644
--- a/drivers/md/dm.c
+++ b/drivers/md/dm.c
@@ -2220,7 +2220,7 @@ static int dm_any_congested(void *congested_data, int bdi_bits)
* the query about congestion status of request_queue
*/
if (dm_request_based(md))
- r = md->queue->backing_dev_info.wb.state &
+ r = md->queue->backing_dev_info->wb.state &
bdi_bits;
else
r = dm_table_any_congested(map, bdi_bits);
@@ -2302,7 +2302,7 @@ static void dm_init_md_queue(struct mapped_device *md)
* - must do so here (in alloc_dev callchain) before queue is used
*/
md->queue->queuedata = md;
- md->queue->backing_dev_info.congested_data = md;
+ md->queue->backing_dev_info->congested_data = md;
}
static void dm_init_old_md_queue(struct mapped_device *md)
@@ -2313,7 +2313,7 @@ static void dm_init_old_md_queue(struct mapped_device *md)
/*
* Initialize aspects of queue that aren't relevant for blk-mq
*/
- md->queue->backing_dev_info.congested_fn = dm_any_congested;
+ md->queue->backing_dev_info->congested_fn = dm_any_congested;
blk_queue_bounce_limit(md->queue, BLK_BOUNCE_ANY);
}
diff --git a/drivers/md/linear.c b/drivers/md/linear.c
index 6ba3227e29b2..79223dceb1c2 100644
--- a/drivers/md/linear.c
+++ b/drivers/md/linear.c
@@ -68,7 +68,7 @@ static int linear_congested(struct mddev *mddev, int bits)
for (i = 0; i < conf->raid_disks && !ret ; i++) {
struct request_queue *q = bdev_get_queue(conf->disks[i].rdev->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
}
rcu_read_unlock();
diff --git a/drivers/md/md.c b/drivers/md/md.c
index 0a856cb181e9..1cd819202553 100644
--- a/drivers/md/md.c
+++ b/drivers/md/md.c
@@ -5284,8 +5284,8 @@ int md_run(struct mddev *mddev)
return err;
}
if (mddev->queue) {
- mddev->queue->backing_dev_info.congested_data = mddev;
- mddev->queue->backing_dev_info.congested_fn = md_congested;
+ mddev->queue->backing_dev_info->congested_data = mddev;
+ mddev->queue->backing_dev_info->congested_fn = md_congested;
}
if (pers->sync_request) {
if (mddev->kobj.sd &&
@@ -5642,7 +5642,7 @@ static int do_md_stop(struct mddev *mddev, int mode,
__md_stop_writes(mddev);
__md_stop(mddev);
- mddev->queue->backing_dev_info.congested_fn = NULL;
+ mddev->queue->backing_dev_info->congested_fn = NULL;
/* tell userspace to handle 'inactive' */
sysfs_notify_dirent_safe(mddev->sysfs_state);
diff --git a/drivers/md/multipath.c b/drivers/md/multipath.c
index dd483bb2e111..fb03ed86d57a 100644
--- a/drivers/md/multipath.c
+++ b/drivers/md/multipath.c
@@ -166,7 +166,7 @@ static int multipath_congested(struct mddev *mddev, int bits)
if (rdev && !test_bit(Faulty, &rdev->flags)) {
struct request_queue *q = bdev_get_queue(rdev->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
/* Just like multipath_map, we just check the
* first available device
*/
diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c
index f8e5db0cb5aa..7a67e7dcf546 100644
--- a/drivers/md/raid0.c
+++ b/drivers/md/raid0.c
@@ -35,7 +35,7 @@ static int raid0_congested(struct mddev *mddev, int bits)
for (i = 0; i < raid_disks && !ret ; i++) {
struct request_queue *q = bdev_get_queue(devlist[i]->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
}
return ret;
}
@@ -415,8 +415,8 @@ static int raid0_run(struct mddev *mddev)
*/
int stripe = mddev->raid_disks *
(mddev->chunk_sectors << 9) / PAGE_SIZE;
- if (mddev->queue->backing_dev_info.ra_pages < 2* stripe)
- mddev->queue->backing_dev_info.ra_pages = 2* stripe;
+ if (mddev->queue->backing_dev_info->ra_pages < 2* stripe)
+ mddev->queue->backing_dev_info->ra_pages = 2* stripe;
}
dump_zones(mddev);
diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c
index f24a9e14021d..a3ec3c5a8ee9 100644
--- a/drivers/md/raid1.c
+++ b/drivers/md/raid1.c
@@ -730,9 +730,9 @@ static int raid1_congested(struct mddev *mddev, int bits)
* non-congested targets, it can be removed
*/
if ((bits & (1 << WB_async_congested)) || 1)
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
else
- ret &= bdi_congested(&q->backing_dev_info, bits);
+ ret &= bdi_congested(q->backing_dev_info, bits);
}
}
rcu_read_unlock();
diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c
index e5ee4e9e0ea5..186d753b7fdb 100644
--- a/drivers/md/raid10.c
+++ b/drivers/md/raid10.c
@@ -838,7 +838,7 @@ static int raid10_congested(struct mddev *mddev, int bits)
if (rdev && !test_bit(Faulty, &rdev->flags)) {
struct request_queue *q = bdev_get_queue(rdev->bdev);
- ret |= bdi_congested(&q->backing_dev_info, bits);
+ ret |= bdi_congested(q->backing_dev_info, bits);
}
}
rcu_read_unlock();
@@ -3698,8 +3698,8 @@ static int run(struct mddev *mddev)
* maybe...
*/
stripe /= conf->geo.near_copies;
- if (mddev->queue->backing_dev_info.ra_pages < 2 * stripe)
- mddev->queue->backing_dev_info.ra_pages = 2 * stripe;
+ if (mddev->queue->backing_dev_info->ra_pages < 2 * stripe)
+ mddev->queue->backing_dev_info->ra_pages = 2 * stripe;
}
if (md_integrity_register(mddev))
@@ -4493,8 +4493,8 @@ static void end_reshape(struct r10conf *conf)
int stripe = conf->geo.raid_disks *
((conf->mddev->chunk_sectors << 9) / PAGE_SIZE);
stripe /= conf->geo.near_copies;
- if (conf->mddev->queue->backing_dev_info.ra_pages < 2 * stripe)
- conf->mddev->queue->backing_dev_info.ra_pages = 2 * stripe;
+ if (conf->mddev->queue->backing_dev_info->ra_pages < 2 * stripe)
+ conf->mddev->queue->backing_dev_info->ra_pages = 2 * stripe;
}
conf->fullsync = 0;
}
diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c
index 5eac08ffc697..165da5b94999 100644
--- a/drivers/md/raid5.c
+++ b/drivers/md/raid5.c
@@ -6123,10 +6123,10 @@ raid5_store_skip_copy(struct mddev *mddev, const char *page, size_t len)
mddev_suspend(mddev);
conf->skip_copy = new;
if (new)
- mddev->queue->backing_dev_info.capabilities |=
+ mddev->queue->backing_dev_info->capabilities |=
BDI_CAP_STABLE_WRITES;
else
- mddev->queue->backing_dev_info.capabilities &=
+ mddev->queue->backing_dev_info->capabilities &=
~BDI_CAP_STABLE_WRITES;
mddev_resume(mddev);
}
@@ -6970,8 +6970,8 @@ static int run(struct mddev *mddev)
int data_disks = conf->previous_raid_disks - conf->max_degraded;
int stripe = data_disks *
((mddev->chunk_sectors << 9) / PAGE_SIZE);
- if (mddev->queue->backing_dev_info.ra_pages < 2 * stripe)
- mddev->queue->backing_dev_info.ra_pages = 2 * stripe;
+ if (mddev->queue->backing_dev_info->ra_pages < 2 * stripe)
+ mddev->queue->backing_dev_info->ra_pages = 2 * stripe;
chunk_size = mddev->chunk_sectors << 9;
blk_queue_io_min(mddev->queue, chunk_size);
@@ -7552,8 +7552,8 @@ static void end_reshape(struct r5conf *conf)
int data_disks = conf->raid_disks - conf->max_degraded;
int stripe = data_disks * ((conf->chunk_sectors << 9)
/ PAGE_SIZE);
- if (conf->mddev->queue->backing_dev_info.ra_pages < 2 * stripe)
- conf->mddev->queue->backing_dev_info.ra_pages = 2 * stripe;
+ if (conf->mddev->queue->backing_dev_info->ra_pages < 2 * stripe)
+ conf->mddev->queue->backing_dev_info->ra_pages = 2 * stripe;
}
}
}
diff --git a/drivers/power/supply/qcom/Kconfig b/drivers/power/supply/qcom/Kconfig
index 47b201738672..b919c688e627 100644
--- a/drivers/power/supply/qcom/Kconfig
+++ b/drivers/power/supply/qcom/Kconfig
@@ -1,5 +1,23 @@
menu "Qualcomm Technologies Inc Charger and Fuel Gauge support"
+config QPNP_SMBCHARGER
+ tristate "QPNP SMB Charger driver"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y here to enable the dual path switch mode battery charger which
+ supports USB detection and battery charging up to 3A.
+ The driver also offers relevant information to userspace via the
+ power supply framework.
+
+config QPNP_FG
+ tristate "QPNP fuel gauge driver"
+ depends on MFD_SPMI_PMIC
+ help
+ Say Y here to enable the Fuel Gauge driver. This adds support for
+ battery fuel gauging and state of charge of battery connected to the
+ fuel gauge. The state of charge is reported through a BMS power
+ supply property and also sends uevents when the capacity is updated.
+
config QPNP_FG_GEN3
tristate "QPNP GEN3 fuel gauge driver"
depends on MFD_SPMI_PMIC
diff --git a/drivers/power/supply/qcom/Makefile b/drivers/power/supply/qcom/Makefile
index 87ab2b24175f..92310ef5c803 100644
--- a/drivers/power/supply/qcom/Makefile
+++ b/drivers/power/supply/qcom/Makefile
@@ -1,3 +1,5 @@
+obj-$(CONFIG_QPNP_SMBCHARGER) += qpnp-smbcharger.o batterydata-lib.o pmic-voter.o
+obj-$(CONFIG_QPNP_FG) += qpnp-fg.o
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) += battery.o smb1351-charger.o pmic-voter.o
diff --git a/drivers/power/supply/qcom/qpnp-fg.c b/drivers/power/supply/qcom/qpnp-fg.c
new file mode 100644
index 000000000000..cfd2f64a9bb8
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-fg.c
@@ -0,0 +1,7051 @@
+/* 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
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#define pr_fmt(fmt) "FG: %s: " fmt, __func__
+
+#include <linux/atomic.h>
+#include <linux/delay.h>
+#include <linux/kernel.h>
+#include <linux/regmap.h>
+#include <linux/of.h>
+#include <linux/rtc.h>
+#include <linux/err.h>
+#include <linux/debugfs.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <linux/init.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/of_irq.h>
+#include <linux/interrupt.h>
+#include <linux/bitops.h>
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/ktime.h>
+#include <linux/power_supply.h>
+#include <linux/of_batterydata.h>
+#include <linux/string_helpers.h>
+#include <linux/alarmtimer.h>
+#include <linux/qpnp/qpnp-revid.h>
+
+/* Register offsets */
+
+/* Interrupt offsets */
+#define INT_RT_STS(base) (base + 0x10)
+#define INT_EN_CLR(base) (base + 0x16)
+
+/* SPMI Register offsets */
+#define SOC_MONOTONIC_SOC 0x09
+#define SOC_BOOT_MOD 0x50
+#define SOC_RESTART 0x51
+
+#define REG_OFFSET_PERP_SUBTYPE 0x05
+
+/* RAM register offsets */
+#define RAM_OFFSET 0x400
+
+/* Bit/Mask definitions */
+#define FULL_PERCENT 0xFF
+#define MAX_TRIES_SOC 5
+#define MA_MV_BIT_RES 39
+#define MSB_SIGN BIT(7)
+#define IBAT_VBAT_MASK 0x7F
+#define NO_OTP_PROF_RELOAD BIT(6)
+#define REDO_FIRST_ESTIMATE BIT(3)
+#define RESTART_GO BIT(0)
+#define THERM_DELAY_MASK 0xE0
+
+/* SUBTYPE definitions */
+#define FG_SOC 0x9
+#define FG_BATT 0xA
+#define FG_ADC 0xB
+#define FG_MEMIF 0xC
+
+#define QPNP_FG_DEV_NAME "qcom,qpnp-fg"
+#define MEM_IF_TIMEOUT_MS 5000
+#define BUCKET_COUNT 8
+#define BUCKET_SOC_PCT (256 / BUCKET_COUNT)
+
+#define BCL_MA_TO_ADC(_current, _adc_val) { \
+ _adc_val = (u8)((_current) * 100 / 976); \
+}
+
+/* Debug Flag Definitions */
+enum {
+ FG_SPMI_DEBUG_WRITES = BIT(0), /* Show SPMI writes */
+ FG_SPMI_DEBUG_READS = BIT(1), /* Show SPMI reads */
+ FG_IRQS = BIT(2), /* Show interrupts */
+ FG_MEM_DEBUG_WRITES = BIT(3), /* Show SRAM writes */
+ FG_MEM_DEBUG_READS = BIT(4), /* Show SRAM reads */
+ FG_POWER_SUPPLY = BIT(5), /* Show POWER_SUPPLY */
+ FG_STATUS = BIT(6), /* Show FG status changes */
+ FG_AGING = BIT(7), /* Show FG aging algorithm */
+};
+
+/* PMIC REVISIONS */
+#define REVID_RESERVED 0
+#define REVID_VARIANT 1
+#define REVID_ANA_MAJOR 2
+#define REVID_DIG_MAJOR 3
+
+enum dig_major {
+ DIG_REV_1 = 0x1,
+ DIG_REV_2 = 0x2,
+ DIG_REV_3 = 0x3,
+};
+
+enum pmic_subtype {
+ PMI8994 = 10,
+ PMI8950 = 17,
+ PMI8996 = 19,
+ PMI8937 = 55,
+};
+
+enum wa_flags {
+ IADC_GAIN_COMP_WA = BIT(0),
+ USE_CC_SOC_REG = BIT(1),
+ PULSE_REQUEST_WA = BIT(2),
+ BCL_HI_POWER_FOR_CHGLED_WA = BIT(3)
+};
+
+enum current_sense_type {
+ INTERNAL_CURRENT_SENSE,
+ EXTERNAL_CURRENT_SENSE,
+};
+
+struct fg_mem_setting {
+ u16 address;
+ u8 offset;
+ int value;
+};
+
+struct fg_mem_data {
+ u16 address;
+ u8 offset;
+ unsigned int len;
+ int value;
+};
+
+struct fg_learning_data {
+ int64_t cc_uah;
+ int64_t learned_cc_uah;
+ int init_cc_pc_val;
+ bool active;
+ bool feedback_on;
+ struct mutex learning_lock;
+ ktime_t time_stamp;
+ /* configuration properties */
+ int max_start_soc;
+ int max_increment;
+ int max_decrement;
+ int min_temp;
+ int max_temp;
+ int vbat_est_thr_uv;
+};
+
+struct fg_rslow_data {
+ u8 rslow_cfg;
+ u8 rslow_thr;
+ u8 rs_to_rslow[2];
+ u8 rslow_comp[4];
+ uint32_t chg_rs_to_rslow;
+ uint32_t chg_rslow_comp_c1;
+ uint32_t chg_rslow_comp_c2;
+ uint32_t chg_rslow_comp_thr;
+ bool active;
+ struct mutex lock;
+};
+
+struct fg_cyc_ctr_data {
+ bool en;
+ bool started[BUCKET_COUNT];
+ u16 count[BUCKET_COUNT];
+ u8 last_soc[BUCKET_COUNT];
+ int id;
+ struct mutex lock;
+};
+
+struct fg_iadc_comp_data {
+ u8 dfl_gain_reg[2];
+ bool gain_active;
+ int64_t dfl_gain;
+};
+
+struct fg_cc_soc_data {
+ int init_sys_soc;
+ int init_cc_soc;
+ int full_capacity;
+ int delta_soc;
+};
+
+/* FG_MEMIF setting index */
+enum fg_mem_setting_index {
+ FG_MEM_SOFT_COLD = 0,
+ FG_MEM_SOFT_HOT,
+ FG_MEM_HARD_COLD,
+ FG_MEM_HARD_HOT,
+ FG_MEM_RESUME_SOC,
+ FG_MEM_BCL_LM_THRESHOLD,
+ FG_MEM_BCL_MH_THRESHOLD,
+ FG_MEM_TERM_CURRENT,
+ FG_MEM_CHG_TERM_CURRENT,
+ FG_MEM_IRQ_VOLT_EMPTY,
+ FG_MEM_CUTOFF_VOLTAGE,
+ FG_MEM_VBAT_EST_DIFF,
+ FG_MEM_DELTA_SOC,
+ FG_MEM_BATT_LOW,
+ FG_MEM_THERM_DELAY,
+ FG_MEM_SETTING_MAX,
+};
+
+/* FG_MEMIF data index */
+enum fg_mem_data_index {
+ FG_DATA_BATT_TEMP = 0,
+ FG_DATA_OCV,
+ FG_DATA_VOLTAGE,
+ FG_DATA_CURRENT,
+ FG_DATA_BATT_ESR,
+ FG_DATA_BATT_ESR_COUNT,
+ FG_DATA_BATT_SOC,
+ FG_DATA_CC_CHARGE,
+ FG_DATA_VINT_ERR,
+ FG_DATA_CPRED_VOLTAGE,
+ /* values below this only gets read once per profile reload */
+ FG_DATA_BATT_ID,
+ FG_DATA_BATT_ID_INFO,
+ FG_DATA_MAX,
+};
+
+#define SETTING(_idx, _address, _offset, _value) \
+ [FG_MEM_##_idx] = { \
+ .address = _address, \
+ .offset = _offset, \
+ .value = _value, \
+ } \
+
+static struct fg_mem_setting settings[FG_MEM_SETTING_MAX] = {
+ /* ID Address, Offset, Value*/
+ SETTING(SOFT_COLD, 0x454, 0, 100),
+ SETTING(SOFT_HOT, 0x454, 1, 400),
+ SETTING(HARD_COLD, 0x454, 2, 50),
+ SETTING(HARD_HOT, 0x454, 3, 450),
+ SETTING(RESUME_SOC, 0x45C, 1, 0),
+ SETTING(BCL_LM_THRESHOLD, 0x47C, 2, 50),
+ SETTING(BCL_MH_THRESHOLD, 0x47C, 3, 752),
+ SETTING(TERM_CURRENT, 0x40C, 2, 250),
+ SETTING(CHG_TERM_CURRENT, 0x4F8, 2, 250),
+ SETTING(IRQ_VOLT_EMPTY, 0x458, 3, 3100),
+ SETTING(CUTOFF_VOLTAGE, 0x40C, 0, 3200),
+ SETTING(VBAT_EST_DIFF, 0x000, 0, 30),
+ SETTING(DELTA_SOC, 0x450, 3, 1),
+ SETTING(BATT_LOW, 0x458, 0, 4200),
+ SETTING(THERM_DELAY, 0x4AC, 3, 0),
+};
+
+#define DATA(_idx, _address, _offset, _length, _value) \
+ [FG_DATA_##_idx] = { \
+ .address = _address, \
+ .offset = _offset, \
+ .len = _length, \
+ .value = _value, \
+ } \
+
+static struct fg_mem_data fg_data[FG_DATA_MAX] = {
+ /* ID Address, Offset, Length, Value*/
+ DATA(BATT_TEMP, 0x550, 2, 2, -EINVAL),
+ DATA(OCV, 0x588, 3, 2, -EINVAL),
+ DATA(VOLTAGE, 0x5CC, 1, 2, -EINVAL),
+ DATA(CURRENT, 0x5CC, 3, 2, -EINVAL),
+ DATA(BATT_ESR, 0x554, 2, 2, -EINVAL),
+ DATA(BATT_ESR_COUNT, 0x558, 2, 2, -EINVAL),
+ DATA(BATT_SOC, 0x56C, 1, 3, -EINVAL),
+ DATA(CC_CHARGE, 0x570, 0, 4, -EINVAL),
+ DATA(VINT_ERR, 0x560, 0, 4, -EINVAL),
+ DATA(CPRED_VOLTAGE, 0x540, 0, 2, -EINVAL),
+ DATA(BATT_ID, 0x594, 1, 1, -EINVAL),
+ DATA(BATT_ID_INFO, 0x594, 3, 1, -EINVAL),
+};
+
+static int fg_debug_mask;
+module_param_named(
+ debug_mask, fg_debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+static int fg_sense_type = -EINVAL;
+static int fg_restart;
+
+static int fg_est_dump;
+module_param_named(
+ first_est_dump, fg_est_dump, int, S_IRUSR | S_IWUSR
+);
+
+static char *fg_batt_type;
+module_param_named(
+ battery_type, fg_batt_type, charp, S_IRUSR | S_IWUSR
+);
+
+static int fg_sram_update_period_ms = 30000;
+module_param_named(
+ sram_update_period_ms, fg_sram_update_period_ms, int, S_IRUSR | S_IWUSR
+);
+
+struct fg_irq {
+ int irq;
+ unsigned long disabled;
+};
+
+enum fg_soc_irq {
+ HIGH_SOC,
+ LOW_SOC,
+ FULL_SOC,
+ EMPTY_SOC,
+ DELTA_SOC,
+ FIRST_EST_DONE,
+ SW_FALLBK_OCV,
+ SW_FALLBK_NEW_BATT,
+ FG_SOC_IRQ_COUNT,
+};
+
+enum fg_batt_irq {
+ JEITA_SOFT_COLD,
+ JEITA_SOFT_HOT,
+ VBATT_LOW,
+ BATT_IDENTIFIED,
+ BATT_ID_REQ,
+ BATTERY_UNKNOWN,
+ BATT_MISSING,
+ BATT_MATCH,
+ FG_BATT_IRQ_COUNT,
+};
+
+enum fg_mem_if_irq {
+ FG_MEM_AVAIL,
+ TA_RCVRY_SUG,
+ FG_MEM_IF_IRQ_COUNT,
+};
+
+enum fg_batt_aging_mode {
+ FG_AGING_NONE,
+ FG_AGING_ESR,
+ FG_AGING_CC,
+};
+
+enum register_type {
+ MEM_INTF_CFG,
+ MEM_INTF_CTL,
+ MEM_INTF_ADDR_LSB,
+ MEM_INTF_RD_DATA0,
+ MEM_INTF_WR_DATA0,
+ MAX_ADDRESS,
+};
+
+struct register_offset {
+ u16 address[MAX_ADDRESS];
+};
+
+static struct register_offset offset[] = {
+ [0] = {
+ /* CFG CTL LSB RD0 WD0 */
+ .address = {0x40, 0x41, 0x42, 0x4C, 0x48},
+ },
+ [1] = {
+ /* CFG CTL LSB RD0 WD0 */
+ .address = {0x50, 0x51, 0x61, 0x67, 0x63},
+ },
+};
+
+#define MEM_INTF_CFG(chip) \
+ ((chip)->mem_base + (chip)->offset[MEM_INTF_CFG])
+#define MEM_INTF_CTL(chip) \
+ ((chip)->mem_base + (chip)->offset[MEM_INTF_CTL])
+#define MEM_INTF_ADDR_LSB(chip) \
+ ((chip)->mem_base + (chip)->offset[MEM_INTF_ADDR_LSB])
+#define MEM_INTF_RD_DATA0(chip) \
+ ((chip)->mem_base + (chip)->offset[MEM_INTF_RD_DATA0])
+#define MEM_INTF_WR_DATA0(chip) \
+ ((chip)->mem_base + (chip)->offset[MEM_INTF_WR_DATA0])
+
+struct fg_wakeup_source {
+ struct wakeup_source source;
+ unsigned long enabled;
+};
+
+static void fg_stay_awake(struct fg_wakeup_source *source)
+{
+ if (!__test_and_set_bit(0, &source->enabled)) {
+ __pm_stay_awake(&source->source);
+ pr_debug("enabled source %s\n", source->source.name);
+ }
+}
+
+static void fg_relax(struct fg_wakeup_source *source)
+{
+ if (__test_and_clear_bit(0, &source->enabled)) {
+ __pm_relax(&source->source);
+ pr_debug("disabled source %s\n", source->source.name);
+ }
+}
+
+#define THERMAL_COEFF_N_BYTES 6
+struct fg_chip {
+ struct device *dev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ u8 pmic_subtype;
+ u8 pmic_revision[4];
+ u8 revision[4];
+ u16 soc_base;
+ u16 batt_base;
+ u16 mem_base;
+ u16 vbat_adc_addr;
+ u16 ibat_adc_addr;
+ u16 tp_rev_addr;
+ u32 wa_flag;
+ atomic_t memif_user_cnt;
+ struct fg_irq soc_irq[FG_SOC_IRQ_COUNT];
+ struct fg_irq batt_irq[FG_BATT_IRQ_COUNT];
+ struct fg_irq mem_irq[FG_MEM_IF_IRQ_COUNT];
+ struct completion sram_access_granted;
+ struct completion sram_access_revoked;
+ struct completion batt_id_avail;
+ struct completion first_soc_done;
+ struct power_supply *bms_psy;
+ struct power_supply_desc bms_psy_d;
+ struct mutex rw_lock;
+ struct mutex sysfs_restart_lock;
+ struct delayed_work batt_profile_init;
+ struct work_struct dump_sram;
+ struct work_struct status_change_work;
+ struct work_struct cycle_count_work;
+ struct work_struct battery_age_work;
+ struct work_struct update_esr_work;
+ struct work_struct set_resume_soc_work;
+ struct work_struct rslow_comp_work;
+ struct work_struct sysfs_restart_work;
+ struct work_struct init_work;
+ struct work_struct charge_full_work;
+ struct work_struct gain_comp_work;
+ struct work_struct bcl_hi_power_work;
+ struct power_supply *batt_psy;
+ struct power_supply *usb_psy;
+ struct power_supply *dc_psy;
+ struct fg_wakeup_source memif_wakeup_source;
+ struct fg_wakeup_source profile_wakeup_source;
+ struct fg_wakeup_source empty_check_wakeup_source;
+ struct fg_wakeup_source resume_soc_wakeup_source;
+ struct fg_wakeup_source gain_comp_wakeup_source;
+ struct fg_wakeup_source capacity_learning_wakeup_source;
+ bool first_profile_loaded;
+ struct fg_wakeup_source update_temp_wakeup_source;
+ struct fg_wakeup_source update_sram_wakeup_source;
+ bool fg_restarting;
+ bool profile_loaded;
+ bool use_otp_profile;
+ bool battery_missing;
+ bool power_supply_registered;
+ bool sw_rbias_ctrl;
+ bool use_thermal_coefficients;
+ bool esr_strict_filter;
+ bool soc_empty;
+ bool charge_done;
+ bool resume_soc_lowered;
+ bool vbat_low_irq_enabled;
+ bool charge_full;
+ bool hold_soc_while_full;
+ bool input_present;
+ bool otg_present;
+ bool safety_timer_expired;
+ bool bad_batt_detection_en;
+ bool bcl_lpm_disabled;
+ bool charging_disabled;
+ struct delayed_work update_jeita_setting;
+ struct delayed_work update_sram_data;
+ struct delayed_work update_temp_work;
+ struct delayed_work check_empty_work;
+ char *batt_profile;
+ u8 thermal_coefficients[THERMAL_COEFF_N_BYTES];
+ u32 cc_cv_threshold_mv;
+ unsigned int batt_profile_len;
+ unsigned int batt_max_voltage_uv;
+ const char *batt_type;
+ const char *batt_psy_name;
+ unsigned long last_sram_update_time;
+ unsigned long last_temp_update_time;
+ int64_t ocv_coeffs[12];
+ int64_t cutoff_voltage;
+ int evaluation_current;
+ int ocv_junction_p1p2;
+ int ocv_junction_p2p3;
+ int nom_cap_uah;
+ int actual_cap_uah;
+ int status;
+ int prev_status;
+ int health;
+ enum fg_batt_aging_mode batt_aging_mode;
+ /* capacity learning */
+ struct fg_learning_data learning_data;
+ struct alarm fg_cap_learning_alarm;
+ struct work_struct fg_cap_learning_work;
+ struct fg_cc_soc_data sw_cc_soc_data;
+ /* rslow compensation */
+ struct fg_rslow_data rslow_comp;
+ /* cycle counter */
+ struct fg_cyc_ctr_data cyc_ctr;
+ /* iadc compensation */
+ struct fg_iadc_comp_data iadc_comp_data;
+ /* interleaved memory access */
+ u16 *offset;
+ bool ima_supported;
+ bool init_done;
+ /* jeita hysteresis */
+ bool jeita_hysteresis_support;
+ bool batt_hot;
+ bool batt_cold;
+ int cold_hysteresis;
+ int hot_hysteresis;
+ /* ESR pulse tuning */
+ struct fg_wakeup_source esr_extract_wakeup_source;
+ struct work_struct esr_extract_config_work;
+ bool esr_extract_disabled;
+ bool imptr_pulse_slow_en;
+ bool esr_pulse_tune_en;
+};
+
+/* FG_MEMIF DEBUGFS structures */
+#define ADDR_LEN 4 /* 3 byte address + 1 space character */
+#define CHARS_PER_ITEM 3 /* Format is 'XX ' */
+#define ITEMS_PER_LINE 4 /* 4 data items per line */
+#define MAX_LINE_LENGTH (ADDR_LEN + (ITEMS_PER_LINE * CHARS_PER_ITEM) + 1)
+#define MAX_REG_PER_TRANSACTION (8)
+
+static const char *DFS_ROOT_NAME = "fg_memif";
+static const mode_t DFS_MODE = S_IRUSR | S_IWUSR;
+static const char *default_batt_type = "Unknown Battery";
+static const char *loading_batt_type = "Loading Battery Data";
+static const char *missing_batt_type = "Disconnected Battery";
+
+/* Log buffer */
+struct fg_log_buffer {
+ size_t rpos; /* Current 'read' position in buffer */
+ size_t wpos; /* Current 'write' position in buffer */
+ size_t len; /* Length of the buffer */
+ char data[0]; /* Log buffer */
+};
+
+/* transaction parameters */
+struct fg_trans {
+ u32 cnt; /* Number of bytes to read */
+ u16 addr; /* 12-bit address in SRAM */
+ u32 offset; /* Offset of last read data + byte offset */
+ struct fg_chip *chip;
+ struct fg_log_buffer *log; /* log buffer */
+ u8 *data; /* fg data that is read */
+ struct mutex memif_dfs_lock; /* Prevent thread concurrency */
+};
+
+struct fg_dbgfs {
+ u32 cnt;
+ u32 addr;
+ struct fg_chip *chip;
+ struct dentry *root;
+ struct mutex lock;
+ struct debugfs_blob_wrapper help_msg;
+};
+
+static struct fg_dbgfs dbgfs_data = {
+ .lock = __MUTEX_INITIALIZER(dbgfs_data.lock),
+ .help_msg = {
+ .data =
+"FG Debug-FS support\n"
+"\n"
+"Hierarchy schema:\n"
+"/sys/kernel/debug/fg_memif\n"
+" /help -- Static help text\n"
+" /address -- Starting register address for reads or writes\n"
+" /count -- Number of registers to read (only used for reads)\n"
+" /data -- Initiates the SRAM read (formatted output)\n"
+"\n",
+ },
+};
+
+static const struct of_device_id fg_match_table[] = {
+ { .compatible = QPNP_FG_DEV_NAME, },
+ {}
+};
+
+static char *fg_supplicants[] = {
+ "battery",
+ "bcl",
+ "fg_adc"
+};
+
+#define DEBUG_PRINT_BUFFER_SIZE 64
+static void fill_string(char *str, size_t str_len, u8 *buf, int buf_len)
+{
+ int pos = 0;
+ int i;
+
+ for (i = 0; i < buf_len; i++) {
+ pos += scnprintf(str + pos, str_len - pos, "%02X", buf[i]);
+ if (i < buf_len - 1)
+ pos += scnprintf(str + pos, str_len - pos, " ");
+ }
+}
+
+static int fg_write(struct fg_chip *chip, u8 *val, u16 addr, int len)
+{
+ int rc = 0;
+ struct platform_device *pdev = chip->pdev;
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ if ((addr & 0xff00) == 0) {
+ pr_err("addr cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, rc);
+ return -EINVAL;
+ }
+
+ rc = regmap_bulk_write(chip->regmap, addr, val, len);
+ if (rc) {
+ pr_err("write failed addr=0x%02x sid=0x%02x rc=%d\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, rc);
+ return rc;
+ }
+
+ if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_WRITES)) {
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len);
+ pr_info("write(0x%04X), sid=%d, len=%d; %s\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, len,
+ str);
+ }
+
+ return rc;
+}
+
+static int fg_read(struct fg_chip *chip, u8 *val, u16 addr, int len)
+{
+ int rc = 0;
+ struct platform_device *pdev = chip->pdev;
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ if ((addr & 0xff00) == 0) {
+ pr_err("base cannot be zero base=0x%02x sid=0x%02x rc=%d\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, rc);
+ return -EINVAL;
+ }
+
+ rc = regmap_bulk_read(chip->regmap, addr, val, len);
+ if (rc) {
+ pr_err("SPMI read failed base=0x%02x sid=0x%02x rc=%d\n", addr,
+ to_spmi_device(pdev->dev.parent)->usid, rc);
+ return rc;
+ }
+
+ if (!rc && (fg_debug_mask & FG_SPMI_DEBUG_READS)) {
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, len);
+ pr_info("read(0x%04x), sid=%d, len=%d; %s\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, len,
+ str);
+ }
+
+ return rc;
+}
+
+static int fg_masked_write(struct fg_chip *chip, u16 addr,
+ u8 mask, u8 val, int len)
+{
+ int rc;
+
+ rc = regmap_update_bits(chip->regmap, addr, mask, val);
+ if (rc) {
+ pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define RIF_MEM_ACCESS_REQ BIT(7)
+static int fg_check_rif_mem_access(struct fg_chip *chip, bool *status)
+{
+ int rc;
+ u8 mem_if_sts;
+
+ rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1);
+ if (rc) {
+ pr_err("failed to read rif_mem status rc=%d\n", rc);
+ return rc;
+ }
+
+ *status = mem_if_sts & RIF_MEM_ACCESS_REQ;
+ return 0;
+}
+
+static bool fg_check_sram_access(struct fg_chip *chip)
+{
+ int rc;
+ u8 mem_if_sts;
+ bool rif_mem_sts = false;
+
+ rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1);
+ if (rc) {
+ pr_err("failed to read mem status rc=%d\n", rc);
+ return false;
+ }
+
+ if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0)
+ return false;
+
+ rc = fg_check_rif_mem_access(chip, &rif_mem_sts);
+ if (rc)
+ return false;
+
+ return rif_mem_sts;
+}
+
+static inline int fg_assert_sram_access(struct fg_chip *chip)
+{
+ int rc;
+ u8 mem_if_sts;
+
+ rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1);
+ if (rc) {
+ pr_err("failed to read mem status rc=%d\n", rc);
+ return rc;
+ }
+
+ if ((mem_if_sts & BIT(FG_MEM_AVAIL)) == 0) {
+ pr_err("mem_avail not high: %02x\n", mem_if_sts);
+ return -EINVAL;
+ }
+
+ rc = fg_read(chip, &mem_if_sts, MEM_INTF_CFG(chip), 1);
+ if (rc) {
+ pr_err("failed to read mem status rc=%d\n", rc);
+ return rc;
+ }
+
+ if ((mem_if_sts & RIF_MEM_ACCESS_REQ) == 0) {
+ pr_err("mem_avail not high: %02x\n", mem_if_sts);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define INTF_CTL_BURST BIT(7)
+#define INTF_CTL_WR_EN BIT(6)
+static int fg_config_access(struct fg_chip *chip, bool write,
+ bool burst)
+{
+ int rc;
+ u8 intf_ctl = 0;
+
+ intf_ctl = (write ? INTF_CTL_WR_EN : 0) | (burst ? INTF_CTL_BURST : 0);
+
+ rc = fg_write(chip, &intf_ctl, MEM_INTF_CTL(chip), 1);
+ if (rc) {
+ pr_err("failed to set mem access bit\n");
+ return -EIO;
+ }
+
+ return rc;
+}
+
+static int fg_req_and_wait_access(struct fg_chip *chip, int timeout)
+{
+ int rc = 0, ret = 0;
+ bool tried_again = false;
+
+ if (!fg_check_sram_access(chip)) {
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip),
+ RIF_MEM_ACCESS_REQ, RIF_MEM_ACCESS_REQ, 1);
+ if (rc) {
+ pr_err("failed to set mem access bit\n");
+ return -EIO;
+ }
+ fg_stay_awake(&chip->memif_wakeup_source);
+ }
+
+wait:
+ /* Wait for MEM_AVAIL IRQ. */
+ ret = wait_for_completion_interruptible_timeout(
+ &chip->sram_access_granted,
+ msecs_to_jiffies(timeout));
+ /* If we were interrupted wait again one more time. */
+ if (ret == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ goto wait;
+ } else if (ret <= 0) {
+ rc = -ETIMEDOUT;
+ pr_err("transaction timed out rc=%d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int fg_release_access(struct fg_chip *chip)
+{
+ int rc;
+
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip),
+ RIF_MEM_ACCESS_REQ, 0, 1);
+ fg_relax(&chip->memif_wakeup_source);
+ reinit_completion(&chip->sram_access_granted);
+
+ return rc;
+}
+
+static void fg_release_access_if_necessary(struct fg_chip *chip)
+{
+ mutex_lock(&chip->rw_lock);
+ if (atomic_sub_return(1, &chip->memif_user_cnt) <= 0) {
+ fg_release_access(chip);
+ }
+ mutex_unlock(&chip->rw_lock);
+}
+
+/*
+ * fg_mem_lock disallows the fuel gauge to release access until it has been
+ * released.
+ *
+ * an equal number of calls must be made to fg_mem_release for the fuel gauge
+ * driver to release the sram access.
+ */
+static void fg_mem_lock(struct fg_chip *chip)
+{
+ mutex_lock(&chip->rw_lock);
+ atomic_add_return(1, &chip->memif_user_cnt);
+ mutex_unlock(&chip->rw_lock);
+}
+
+static void fg_mem_release(struct fg_chip *chip)
+{
+ fg_release_access_if_necessary(chip);
+}
+
+static int fg_set_ram_addr(struct fg_chip *chip, u16 *address)
+{
+ int rc;
+
+ rc = fg_write(chip, (u8 *) address,
+ chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], 2);
+ if (rc) {
+ pr_err("spmi write failed: addr=%03X, rc=%d\n",
+ chip->mem_base + chip->offset[MEM_INTF_ADDR_LSB], rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define BUF_LEN 4
+static int fg_sub_mem_read(struct fg_chip *chip, u8 *val, u16 address, int len,
+ int offset)
+{
+ int rc, total_len;
+ u8 *rd_data = val;
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ rc = fg_config_access(chip, 0, (len > 4));
+ if (rc)
+ return rc;
+
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc)
+ return rc;
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("length %d addr=%02X\n", len, address);
+
+ total_len = len;
+ while (len > 0) {
+ if (!offset) {
+ rc = fg_read(chip, rd_data, MEM_INTF_RD_DATA0(chip),
+ min(len, BUF_LEN));
+ } else {
+ rc = fg_read(chip, rd_data,
+ MEM_INTF_RD_DATA0(chip) + offset,
+ min(len, BUF_LEN - offset));
+
+ /* manually set address to allow continous reads */
+ address += BUF_LEN;
+
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc)
+ return rc;
+ }
+ if (rc) {
+ pr_err("spmi read failed: addr=%03x, rc=%d\n",
+ MEM_INTF_RD_DATA0(chip) + offset, rc);
+ return rc;
+ }
+ rd_data += (BUF_LEN - offset);
+ len -= (BUF_LEN - offset);
+ offset = 0;
+ }
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS) {
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len);
+ pr_info("data: %s\n", str);
+ }
+ return rc;
+}
+
+static int fg_conventional_mem_read(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset, bool keep_access)
+{
+ int rc = 0, user_cnt = 0, orig_address = address;
+
+ if (offset > 3) {
+ pr_err("offset too large %d\n", offset);
+ return -EINVAL;
+ }
+
+ address = ((orig_address + offset) / 4) * 4;
+ offset = (orig_address + offset) % 4;
+
+ user_cnt = atomic_add_return(1, &chip->memif_user_cnt);
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("user_cnt %d\n", user_cnt);
+ mutex_lock(&chip->rw_lock);
+ if (!fg_check_sram_access(chip)) {
+ rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS);
+ if (rc)
+ goto out;
+ }
+
+ rc = fg_sub_mem_read(chip, val, address, len, offset);
+
+out:
+ user_cnt = atomic_sub_return(1, &chip->memif_user_cnt);
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("user_cnt %d\n", user_cnt);
+
+ fg_assert_sram_access(chip);
+
+ if (!keep_access && (user_cnt == 0) && !rc) {
+ rc = fg_release_access(chip);
+ if (rc) {
+ pr_err("failed to set mem access bit\n");
+ rc = -EIO;
+ }
+ }
+
+ mutex_unlock(&chip->rw_lock);
+ return rc;
+}
+
+static int fg_conventional_mem_write(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset, bool keep_access)
+{
+ int rc = 0, user_cnt = 0, sublen;
+ bool access_configured = false;
+ u8 *wr_data = val, word[4];
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ if (address < RAM_OFFSET)
+ return -EINVAL;
+
+ if (offset > 3)
+ return -EINVAL;
+
+ address = ((address + offset) / 4) * 4;
+ offset = (address + offset) % 4;
+
+ user_cnt = atomic_add_return(1, &chip->memif_user_cnt);
+ if (fg_debug_mask & FG_MEM_DEBUG_WRITES)
+ pr_info("user_cnt %d\n", user_cnt);
+ mutex_lock(&chip->rw_lock);
+ if (!fg_check_sram_access(chip)) {
+ rc = fg_req_and_wait_access(chip, MEM_IF_TIMEOUT_MS);
+ if (rc)
+ goto out;
+ }
+
+ if (fg_debug_mask & FG_MEM_DEBUG_WRITES) {
+ pr_info("length %d addr=%02X offset=%d\n",
+ len, address, offset);
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, wr_data, len);
+ pr_info("writing: %s\n", str);
+ }
+
+ while (len > 0) {
+ if (offset != 0) {
+ sublen = min(4 - offset, len);
+ rc = fg_sub_mem_read(chip, word, address, 4, 0);
+ if (rc)
+ goto out;
+ memcpy(word + offset, wr_data, sublen);
+ /* configure access as burst if more to write */
+ rc = fg_config_access(chip, 1, (len - sublen) > 0);
+ if (rc)
+ goto out;
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc)
+ goto out;
+ offset = 0;
+ access_configured = true;
+ } else if (len >= 4) {
+ if (!access_configured) {
+ rc = fg_config_access(chip, 1, len > 4);
+ if (rc)
+ goto out;
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc)
+ goto out;
+ access_configured = true;
+ }
+ sublen = 4;
+ memcpy(word, wr_data, 4);
+ } else if (len > 0 && len < 4) {
+ sublen = len;
+ rc = fg_sub_mem_read(chip, word, address, 4, 0);
+ if (rc)
+ goto out;
+ memcpy(word, wr_data, sublen);
+ rc = fg_config_access(chip, 1, 0);
+ if (rc)
+ goto out;
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc)
+ goto out;
+ access_configured = true;
+ } else {
+ pr_err("Invalid length: %d\n", len);
+ break;
+ }
+ rc = fg_write(chip, word, MEM_INTF_WR_DATA0(chip), 4);
+ if (rc) {
+ pr_err("spmi write failed: addr=%03x, rc=%d\n",
+ MEM_INTF_WR_DATA0(chip), rc);
+ goto out;
+ }
+ len -= sublen;
+ wr_data += sublen;
+ address += 4;
+ }
+
+out:
+ user_cnt = atomic_sub_return(1, &chip->memif_user_cnt);
+ if (fg_debug_mask & FG_MEM_DEBUG_WRITES)
+ pr_info("user_cnt %d\n", user_cnt);
+
+ fg_assert_sram_access(chip);
+
+ if (!keep_access && (user_cnt == 0) && !rc) {
+ rc = fg_release_access(chip);
+ if (rc) {
+ pr_err("failed to set mem access bit\n");
+ rc = -EIO;
+ }
+ }
+
+ mutex_unlock(&chip->rw_lock);
+ return rc;
+}
+
+#define MEM_INTF_IMA_CFG 0x52
+#define MEM_INTF_IMA_OPR_STS 0x54
+#define MEM_INTF_IMA_ERR_STS 0x5F
+#define MEM_INTF_IMA_EXP_STS 0x55
+#define MEM_INTF_IMA_HW_STS 0x56
+#define MEM_INTF_IMA_BYTE_EN 0x60
+#define IMA_ADDR_STBL_ERR BIT(7)
+#define IMA_WR_ACS_ERR BIT(6)
+#define IMA_RD_ACS_ERR BIT(5)
+#define IMA_IACS_CLR BIT(2)
+#define IMA_IACS_RDY BIT(1)
+static int fg_check_ima_exception(struct fg_chip *chip)
+{
+ int rc = 0, ret = 0;
+ u8 err_sts, exp_sts = 0, hw_sts = 0;
+
+ rc = fg_read(chip, &err_sts,
+ chip->mem_base + MEM_INTF_IMA_ERR_STS, 1);
+ if (rc) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ return rc;
+ }
+
+ if (err_sts & (IMA_ADDR_STBL_ERR | IMA_WR_ACS_ERR | IMA_RD_ACS_ERR)) {
+ u8 temp;
+
+ fg_read(chip, &exp_sts,
+ chip->mem_base + MEM_INTF_IMA_EXP_STS, 1);
+ fg_read(chip, &hw_sts,
+ chip->mem_base + MEM_INTF_IMA_HW_STS, 1);
+ pr_err("IMA access failed ima_err_sts=%x ima_exp_sts=%x ima_hw_sts=%x\n",
+ err_sts, exp_sts, hw_sts);
+ rc = err_sts;
+
+ /* clear the error */
+ ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG,
+ IMA_IACS_CLR, IMA_IACS_CLR, 1);
+ temp = 0x4;
+ ret |= fg_write(chip, &temp, MEM_INTF_ADDR_LSB(chip) + 1, 1);
+ temp = 0x0;
+ ret |= fg_write(chip, &temp, MEM_INTF_WR_DATA0(chip) + 3, 1);
+ ret |= fg_read(chip, &temp, MEM_INTF_RD_DATA0(chip) + 3, 1);
+ ret |= fg_masked_write(chip, chip->mem_base + MEM_INTF_IMA_CFG,
+ IMA_IACS_CLR, 0, 1);
+ if (!ret)
+ return -EAGAIN;
+ else
+ pr_err("Error clearing IMA exception ret=%d\n", ret);
+ }
+
+ return rc;
+}
+
+static int fg_check_iacs_ready(struct fg_chip *chip)
+{
+ int rc = 0, timeout = 250;
+ u8 ima_opr_sts = 0;
+
+ /*
+ * Additional delay to make sure IACS ready bit is set after
+ * Read/Write operation.
+ */
+
+ usleep_range(30, 35);
+ while (1) {
+ rc = fg_read(chip, &ima_opr_sts,
+ chip->mem_base + MEM_INTF_IMA_OPR_STS, 1);
+ if (!rc && (ima_opr_sts & IMA_IACS_RDY)) {
+ break;
+ } else {
+ if (!(--timeout) || rc)
+ break;
+ /* delay for iacs_ready to be asserted */
+ usleep_range(5000, 7000);
+ }
+ }
+
+ if (!timeout || rc) {
+ pr_err("IACS_RDY not set\n");
+ /* perform IACS_CLR sequence */
+ fg_check_ima_exception(chip);
+ return -EBUSY;
+ }
+
+ return 0;
+}
+
+#define IACS_SLCT BIT(5)
+static int __fg_interleaved_mem_write(struct fg_chip *chip, u8 *val,
+ u16 address, int offset, int len)
+{
+ int rc = 0, i;
+ u8 *word = val, byte_enable = 0, num_bytes = 0;
+
+ if (fg_debug_mask & FG_MEM_DEBUG_WRITES)
+ pr_info("length %d addr=%02X offset=%d\n",
+ len, address, offset);
+
+ while (len > 0) {
+ num_bytes = (offset + len) > BUF_LEN ?
+ (BUF_LEN - offset) : len;
+ /* write to byte_enable */
+ for (i = offset; i < (offset + num_bytes); i++)
+ byte_enable |= BIT(i);
+
+ rc = fg_write(chip, &byte_enable,
+ chip->mem_base + MEM_INTF_IMA_BYTE_EN, 1);
+ if (rc) {
+ pr_err("Unable to write to byte_en_reg rc=%d\n",
+ rc);
+ return rc;
+ }
+ /* write data */
+ rc = fg_write(chip, word, MEM_INTF_WR_DATA0(chip) + offset,
+ num_bytes);
+ if (rc) {
+ pr_err("spmi write failed: addr=%03x, rc=%d\n",
+ MEM_INTF_WR_DATA0(chip) + offset, rc);
+ return rc;
+ }
+ /*
+ * The last-byte WR_DATA3 starts the write transaction.
+ * Write a dummy value to WR_DATA3 if it does not have
+ * valid data. This dummy data is not written to the
+ * SRAM as byte_en for WR_DATA3 is not set.
+ */
+ if (!(byte_enable & BIT(3))) {
+ u8 dummy_byte = 0x0;
+ rc = fg_write(chip, &dummy_byte,
+ MEM_INTF_WR_DATA0(chip) + 3, 1);
+ if (rc) {
+ pr_err("Unable to write dummy-data to WR_DATA3 rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+
+ /* check for error condition */
+ rc = fg_check_ima_exception(chip);
+ if (rc) {
+ pr_err("IMA transaction failed rc=%d", rc);
+ return rc;
+ }
+
+ word += num_bytes;
+ len -= num_bytes;
+ offset = byte_enable = 0;
+ }
+
+ return rc;
+}
+
+static int __fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address,
+ int offset, int len)
+{
+ int rc = 0, total_len;
+ u8 *rd_data = val, num_bytes;
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("length %d addr=%02X\n", len, address);
+
+ total_len = len;
+ while (len > 0) {
+ num_bytes = (offset + len) > BUF_LEN ? (BUF_LEN - offset) : len;
+ rc = fg_read(chip, rd_data, MEM_INTF_RD_DATA0(chip) + offset,
+ num_bytes);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03x, rc=%d\n",
+ MEM_INTF_RD_DATA0(chip) + offset, rc);
+ return rc;
+ }
+
+ rd_data += num_bytes;
+ len -= num_bytes;
+ offset = 0;
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+
+ /* check for error condition */
+ rc = fg_check_ima_exception(chip);
+ if (rc) {
+ pr_err("IMA transaction failed rc=%d", rc);
+ return rc;
+ }
+
+ if (len && (len + offset) < BUF_LEN) {
+ /* move to single mode */
+ u8 intr_ctl = 0;
+
+ rc = fg_write(chip, &intr_ctl, MEM_INTF_CTL(chip), 1);
+ if (rc) {
+ pr_err("failed to move to single mode rc=%d\n",
+ rc);
+ return -EIO;
+ }
+ }
+ }
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS) {
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, val, total_len);
+ pr_info("data: %s\n", str);
+ }
+
+ return rc;
+}
+
+#define IMA_REQ_ACCESS (IACS_SLCT | RIF_MEM_ACCESS_REQ)
+static int fg_interleaved_mem_config(struct fg_chip *chip, u8 *val,
+ u16 address, int len, int offset, int op)
+{
+ int rc = 0;
+ bool rif_mem_sts = true;
+ int time_count = 0;
+
+ while (1) {
+ rc = fg_check_rif_mem_access(chip, &rif_mem_sts);
+ if (rc)
+ return rc;
+
+ if (!rif_mem_sts)
+ break;
+
+ if (fg_debug_mask & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES))
+ pr_info("RIF_MEM_ACCESS_REQ is not clear yet for IMA_%s\n",
+ op ? "write" : "read");
+
+ /*
+ * Try this no more than 4 times. If RIF_MEM_ACCESS_REQ is not
+ * clear, then return an error instead of waiting for it again.
+ */
+ if (time_count > 4) {
+ pr_err("Waited for 1.5 seconds polling RIF_MEM_ACCESS_REQ\n");
+ return -ETIMEDOUT;
+ }
+
+ /* Wait for 4ms before reading RIF_MEM_ACCESS_REQ again */
+ usleep_range(4000, 4100);
+ time_count++;
+ }
+
+ /* configure for IMA access */
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip),
+ IMA_REQ_ACCESS, IMA_REQ_ACCESS, 1);
+ if (rc) {
+ pr_err("failed to set mem access bit rc = %d\n", rc);
+ return rc;
+ }
+
+ /* configure for the read/write single/burst mode */
+ rc = fg_config_access(chip, op, (offset + len) > 4);
+ if (rc) {
+ pr_err("failed to set configure memory access rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc) {
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+ return rc;
+ }
+
+ /* write addresses to the register */
+ rc = fg_set_ram_addr(chip, &address);
+ if (rc) {
+ pr_err("failed to set SRAM address rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = fg_check_iacs_ready(chip);
+ if (rc)
+ pr_debug("IACS_RDY failed rc=%d\n", rc);
+
+ return rc;
+}
+
+#define MEM_INTF_FG_BEAT_COUNT 0x57
+#define BEAT_COUNT_MASK 0x0F
+#define RETRY_COUNT 3
+static int fg_interleaved_mem_read(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset)
+{
+ int rc = 0, orig_address = address;
+ u8 start_beat_count, end_beat_count, count = 0;
+ bool retry = false;
+
+ if (offset > 3) {
+ pr_err("offset too large %d\n", offset);
+ return -EINVAL;
+ }
+
+ fg_stay_awake(&chip->memif_wakeup_source);
+ address = ((orig_address + offset) / 4) * 4;
+ offset = (orig_address + offset) % 4;
+
+ if (address < RAM_OFFSET) {
+ /*
+ * OTP memory reads need a conventional memory access, do a
+ * conventional read when SRAM offset < RAM_OFFSET.
+ */
+ rc = fg_conventional_mem_read(chip, val, address, len, offset,
+ 0);
+ if (rc)
+ pr_err("Failed to read OTP memory %d\n", rc);
+ goto exit;
+ }
+
+ mutex_lock(&chip->rw_lock);
+
+retry:
+ rc = fg_interleaved_mem_config(chip, val, address, offset, len, 0);
+ if (rc) {
+ pr_err("failed to configure SRAM for IMA rc = %d\n", rc);
+ goto out;
+ }
+
+ /* read the start beat count */
+ rc = fg_read(chip, &start_beat_count,
+ chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1);
+ if (rc) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ goto out;
+ }
+
+ /* read data */
+ rc = __fg_interleaved_mem_read(chip, val, address, offset, len);
+ if (rc) {
+ if ((rc == -EAGAIN) && (count < RETRY_COUNT)) {
+ count++;
+ pr_err("IMA access failed retry_count = %d\n", count);
+ goto retry;
+ } else {
+ pr_err("failed to read SRAM address rc = %d\n", rc);
+ goto out;
+ }
+ }
+
+ /* read the end beat count */
+ rc = fg_read(chip, &end_beat_count,
+ chip->mem_base + MEM_INTF_FG_BEAT_COUNT, 1);
+ if (rc) {
+ pr_err("failed to read beat count rc=%d\n", rc);
+ goto out;
+ }
+
+ start_beat_count &= BEAT_COUNT_MASK;
+ end_beat_count &= BEAT_COUNT_MASK;
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("Start beat_count = %x End beat_count = %x\n",
+ start_beat_count, end_beat_count);
+ if (start_beat_count != end_beat_count) {
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("Beat count do not match - retry transaction\n");
+ retry = true;
+ }
+out:
+ /* Release IMA access */
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1);
+ if (rc)
+ pr_err("failed to reset IMA access bit rc = %d\n", rc);
+
+ if (retry) {
+ retry = false;
+ goto retry;
+ }
+ mutex_unlock(&chip->rw_lock);
+
+exit:
+ fg_relax(&chip->memif_wakeup_source);
+ return rc;
+}
+
+static int fg_interleaved_mem_write(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset)
+{
+ int rc = 0, orig_address = address;
+ u8 count = 0;
+
+ if (address < RAM_OFFSET)
+ return -EINVAL;
+
+ if (offset > 3) {
+ pr_err("offset too large %d\n", offset);
+ return -EINVAL;
+ }
+
+ fg_stay_awake(&chip->memif_wakeup_source);
+ address = ((orig_address + offset) / 4) * 4;
+ offset = (orig_address + offset) % 4;
+
+ mutex_lock(&chip->rw_lock);
+
+retry:
+ rc = fg_interleaved_mem_config(chip, val, address, offset, len, 1);
+ if (rc) {
+ pr_err("failed to xonfigure SRAM for IMA rc = %d\n", rc);
+ goto out;
+ }
+
+ /* write data */
+ rc = __fg_interleaved_mem_write(chip, val, address, offset, len);
+ if (rc) {
+ if ((rc == -EAGAIN) && (count < RETRY_COUNT)) {
+ count++;
+ pr_err("IMA access failed retry_count = %d\n", count);
+ goto retry;
+ } else {
+ pr_err("failed to write SRAM address rc = %d\n", rc);
+ goto out;
+ }
+ }
+
+out:
+ /* Release IMA access */
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip), IMA_REQ_ACCESS, 0, 1);
+ if (rc)
+ pr_err("failed to reset IMA access bit rc = %d\n", rc);
+
+ mutex_unlock(&chip->rw_lock);
+ fg_relax(&chip->memif_wakeup_source);
+ return rc;
+}
+
+static int fg_mem_read(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset, bool keep_access)
+{
+ if (chip->ima_supported)
+ return fg_interleaved_mem_read(chip, val, address,
+ len, offset);
+ else
+ return fg_conventional_mem_read(chip, val, address,
+ len, offset, keep_access);
+}
+
+static int fg_mem_write(struct fg_chip *chip, u8 *val, u16 address,
+ int len, int offset, bool keep_access)
+{
+ if (chip->ima_supported)
+ return fg_interleaved_mem_write(chip, val, address,
+ len, offset);
+ else
+ return fg_conventional_mem_write(chip, val, address,
+ len, offset, keep_access);
+}
+
+static int fg_mem_masked_write(struct fg_chip *chip, u16 addr,
+ u8 mask, u8 val, u8 offset)
+{
+ int rc = 0;
+ u8 reg[4];
+ char str[DEBUG_PRINT_BUFFER_SIZE];
+
+ rc = fg_mem_read(chip, reg, addr, 4, 0, 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+
+ reg[offset] &= ~mask;
+ reg[offset] |= val & mask;
+
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, reg, 4);
+ pr_debug("Writing %s address %03x, offset %d\n", str, addr, offset);
+
+ rc = fg_mem_write(chip, reg, addr, 4, 0, 0);
+ if (rc) {
+ pr_err("spmi write failed: addr=%03X, rc=%d\n", addr, rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int soc_to_setpoint(int soc)
+{
+ return DIV_ROUND_CLOSEST(soc * 255, 100);
+}
+
+static void batt_to_setpoint_adc(int vbatt_mv, u8 *data)
+{
+ int val;
+ /* Battery voltage is an offset from 0 V and LSB is 1/2^15. */
+ val = DIV_ROUND_CLOSEST(vbatt_mv * 32768, 5000);
+ data[0] = val & 0xFF;
+ data[1] = val >> 8;
+ return;
+}
+
+static u8 batt_to_setpoint_8b(int vbatt_mv)
+{
+ int val;
+ /* Battery voltage is an offset from 2.5 V and LSB is 5/2^9. */
+ val = (vbatt_mv - 2500) * 512 / 1000;
+ return DIV_ROUND_CLOSEST(val, 5);
+}
+
+static u8 therm_delay_to_setpoint(u32 delay_us)
+{
+ u8 val;
+
+ if (delay_us < 2560)
+ val = 0;
+ else if (delay_us > 163840)
+ val = 7;
+ else
+ val = ilog2(delay_us / 10) - 7;
+ return val << 5;
+}
+
+static int get_current_time(unsigned long *now_tm_sec)
+{
+ struct rtc_time tm;
+ struct rtc_device *rtc;
+ int rc;
+
+ rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
+ if (rtc == NULL) {
+ pr_err("%s: unable to open rtc device (%s)\n",
+ __FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
+ return -EINVAL;
+ }
+
+ rc = rtc_read_time(rtc, &tm);
+ if (rc) {
+ pr_err("Error reading rtc device (%s) : %d\n",
+ CONFIG_RTC_HCTOSYS_DEVICE, rc);
+ goto close_time;
+ }
+
+ rc = rtc_valid_tm(&tm);
+ if (rc) {
+ pr_err("Invalid RTC time (%s): %d\n",
+ CONFIG_RTC_HCTOSYS_DEVICE, rc);
+ goto close_time;
+ }
+ rtc_tm_to_time(&tm, now_tm_sec);
+
+close_time:
+ rtc_class_close(rtc);
+ return rc;
+}
+
+#define BATTERY_SOC_REG 0x56C
+#define BATTERY_SOC_OFFSET 1
+#define FULL_PERCENT_3B 0xFFFFFF
+static int get_battery_soc_raw(struct fg_chip *chip)
+{
+ int rc;
+ u8 buffer[3];
+
+ rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0);
+ if (rc) {
+ pr_err("Unable to read battery soc: %d\n", rc);
+ return 0;
+ }
+ return (int)(buffer[2] << 16 | buffer[1] << 8 | buffer[0]);
+}
+
+#define COUNTER_IMPTR_REG 0X558
+#define COUNTER_PULSE_REG 0X55C
+#define SOC_FULL_REG 0x564
+#define COUNTER_IMPTR_OFFSET 2
+#define COUNTER_PULSE_OFFSET 0
+#define SOC_FULL_OFFSET 3
+#define ESR_PULSE_RECONFIG_SOC 0xFFF971
+static int fg_configure_soc(struct fg_chip *chip)
+{
+ u32 batt_soc;
+ u8 cntr[2] = {0, 0};
+ int rc = 0;
+
+ mutex_lock(&chip->rw_lock);
+ atomic_add_return(1, &chip->memif_user_cnt);
+ mutex_unlock(&chip->rw_lock);
+
+ /* Read Battery SOC */
+ batt_soc = get_battery_soc_raw(chip);
+
+ if (batt_soc > ESR_PULSE_RECONFIG_SOC) {
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info("Configuring soc registers batt_soc: %x\n",
+ batt_soc);
+ batt_soc = ESR_PULSE_RECONFIG_SOC;
+ rc = fg_mem_write(chip, (u8 *)&batt_soc, BATTERY_SOC_REG, 3,
+ BATTERY_SOC_OFFSET, 1);
+ if (rc) {
+ pr_err("failed to write BATT_SOC rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = fg_mem_write(chip, (u8 *)&batt_soc, SOC_FULL_REG, 3,
+ SOC_FULL_OFFSET, 1);
+ if (rc) {
+ pr_err("failed to write SOC_FULL rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 2,
+ COUNTER_IMPTR_OFFSET, 1);
+ if (rc) {
+ pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc);
+ goto out;
+ }
+
+ rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2,
+ COUNTER_PULSE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc);
+ }
+out:
+ fg_release_access_if_necessary(chip);
+ return rc;
+}
+
+#define SOC_EMPTY BIT(3)
+static bool fg_is_batt_empty(struct fg_chip *chip)
+{
+ u8 fg_soc_sts;
+ int rc;
+
+ rc = fg_read(chip, &fg_soc_sts,
+ INT_RT_STS(chip->soc_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ return false;
+ }
+
+ return (fg_soc_sts & SOC_EMPTY) != 0;
+}
+
+static int get_monotonic_soc_raw(struct fg_chip *chip)
+{
+ u8 cap[2];
+ int rc, tries = 0;
+
+ while (tries < MAX_TRIES_SOC) {
+ rc = fg_read(chip, cap,
+ chip->soc_base + SOC_MONOTONIC_SOC, 2);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03x, rc=%d\n",
+ chip->soc_base + SOC_MONOTONIC_SOC, rc);
+ return rc;
+ }
+
+ if (cap[0] == cap[1])
+ break;
+
+ tries++;
+ }
+
+ if (tries == MAX_TRIES_SOC) {
+ pr_err("shadow registers do not match\n");
+ return -EINVAL;
+ }
+
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info_ratelimited("raw: 0x%02x\n", cap[0]);
+ return cap[0];
+}
+
+#define EMPTY_CAPACITY 0
+#define DEFAULT_CAPACITY 50
+#define MISSING_CAPACITY 100
+#define FULL_CAPACITY 100
+#define FULL_SOC_RAW 0xFF
+static int get_prop_capacity(struct fg_chip *chip)
+{
+ int msoc;
+
+ if (chip->battery_missing)
+ return MISSING_CAPACITY;
+ if (!chip->profile_loaded && !chip->use_otp_profile)
+ return DEFAULT_CAPACITY;
+ if (chip->charge_full)
+ return FULL_CAPACITY;
+ if (chip->soc_empty) {
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info_ratelimited("capacity: %d, EMPTY\n",
+ EMPTY_CAPACITY);
+ return EMPTY_CAPACITY;
+ }
+ msoc = get_monotonic_soc_raw(chip);
+ if (msoc == 0)
+ return EMPTY_CAPACITY;
+ else if (msoc == FULL_SOC_RAW)
+ return FULL_CAPACITY;
+ return DIV_ROUND_CLOSEST((msoc - 1) * (FULL_CAPACITY - 2),
+ FULL_SOC_RAW - 2) + 1;
+}
+
+#define HIGH_BIAS 3
+#define MED_BIAS BIT(1)
+#define LOW_BIAS BIT(0)
+static u8 bias_ua[] = {
+ [HIGH_BIAS] = 150,
+ [MED_BIAS] = 15,
+ [LOW_BIAS] = 5,
+};
+
+static int64_t get_batt_id(unsigned int battery_id_uv, u8 bid_info)
+{
+ u64 battery_id_ohm;
+
+ if ((bid_info & 0x3) == 0) {
+ pr_err("can't determine battery id 0x%02x\n", bid_info);
+ return -EINVAL;
+ }
+
+ battery_id_ohm = div_u64(battery_id_uv, bias_ua[bid_info & 0x3]);
+
+ return battery_id_ohm;
+}
+
+#define DEFAULT_TEMP_DEGC 250
+static int get_sram_prop_now(struct fg_chip *chip, unsigned int type)
+{
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info("addr 0x%02X, offset %d value %d\n",
+ fg_data[type].address, fg_data[type].offset,
+ fg_data[type].value);
+
+ if (type == FG_DATA_BATT_ID)
+ return get_batt_id(fg_data[type].value,
+ fg_data[FG_DATA_BATT_ID_INFO].value);
+
+ return fg_data[type].value;
+}
+
+#define MIN_TEMP_DEGC -300
+#define MAX_TEMP_DEGC 970
+static int get_prop_jeita_temp(struct fg_chip *chip, unsigned int type)
+{
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info("addr 0x%02X, offset %d\n", settings[type].address,
+ settings[type].offset);
+
+ return settings[type].value;
+}
+
+static int set_prop_jeita_temp(struct fg_chip *chip,
+ unsigned int type, int decidegc)
+{
+ int rc = 0;
+
+ if (fg_debug_mask & FG_POWER_SUPPLY)
+ pr_info("addr 0x%02X, offset %d temp%d\n",
+ settings[type].address,
+ settings[type].offset, decidegc);
+
+ settings[type].value = decidegc;
+
+ cancel_delayed_work_sync(
+ &chip->update_jeita_setting);
+ schedule_delayed_work(
+ &chip->update_jeita_setting, 0);
+
+ return rc;
+}
+
+#define EXTERNAL_SENSE_SELECT 0x4AC
+#define EXTERNAL_SENSE_OFFSET 0x2
+#define EXTERNAL_SENSE_BIT BIT(2)
+static int set_prop_sense_type(struct fg_chip *chip, int ext_sense_type)
+{
+ int rc;
+
+ rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT,
+ EXTERNAL_SENSE_BIT,
+ ext_sense_type ? EXTERNAL_SENSE_BIT : 0,
+ EXTERNAL_SENSE_OFFSET);
+ if (rc) {
+ pr_err("failed to write profile rc=%d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+#define EXPONENT_MASK 0xF800
+#define MANTISSA_MASK 0x3FF
+#define SIGN BIT(10)
+#define EXPONENT_SHIFT 11
+#define MICRO_UNIT 1000000ULL
+static int64_t float_decode(u16 reg)
+{
+ int64_t final_val, exponent_val, mantissa_val;
+ int exponent, mantissa, n;
+ bool sign;
+
+ exponent = (reg & EXPONENT_MASK) >> EXPONENT_SHIFT;
+ mantissa = (reg & MANTISSA_MASK);
+ sign = !!(reg & SIGN);
+
+ pr_debug("exponent=%d mantissa=%d sign=%d\n", exponent, mantissa, sign);
+
+ mantissa_val = mantissa * MICRO_UNIT;
+
+ n = exponent - 15;
+ if (n < 0)
+ exponent_val = MICRO_UNIT >> -n;
+ else
+ exponent_val = MICRO_UNIT << n;
+
+ n = n - 10;
+ if (n < 0)
+ mantissa_val >>= -n;
+ else
+ mantissa_val <<= n;
+
+ final_val = exponent_val + mantissa_val;
+
+ if (sign)
+ final_val *= -1;
+
+ return final_val;
+}
+
+#define MIN_HALFFLOAT_EXP_N -15
+#define MAX_HALFFLOAT_EXP_N 16
+static int log2_floor(int64_t uval)
+{
+ int n = 0;
+ int64_t i = MICRO_UNIT;
+
+ if (uval > i) {
+ while (uval > i && n > MIN_HALFFLOAT_EXP_N) {
+ i <<= 1;
+ n += 1;
+ }
+ if (uval < i)
+ n -= 1;
+ } else if (uval < i) {
+ while (uval < i && n < MAX_HALFFLOAT_EXP_N) {
+ i >>= 1;
+ n -= 1;
+ }
+ }
+
+ return n;
+}
+
+static int64_t exp2_int(int64_t n)
+{
+ int p = n - 1;
+
+ if (p > 0)
+ return (2 * MICRO_UNIT) << p;
+ else
+ return (2 * MICRO_UNIT) >> abs(p);
+}
+
+static u16 float_encode(int64_t uval)
+{
+ int sign = 0, n, exp, mantissa;
+ u16 half = 0;
+
+ if (uval < 0) {
+ sign = 1;
+ uval = abs(uval);
+ }
+ n = log2_floor(uval);
+ exp = n + 15;
+ mantissa = div_s64(div_s64((uval - exp2_int(n)) * exp2_int(10 - n),
+ MICRO_UNIT) + MICRO_UNIT / 2, MICRO_UNIT);
+
+ half = (mantissa & MANTISSA_MASK) | ((sign << 10) & SIGN)
+ | ((exp << 11) & EXPONENT_MASK);
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("uval = %lld, m = 0x%02x, sign = 0x%02x, exp = 0x%02x, half = 0x%04x\n",
+ uval, mantissa, sign, exp, half);
+ return half;
+}
+
+#define BATT_IDED BIT(3)
+static int fg_is_batt_id_valid(struct fg_chip *chip)
+{
+ u8 fg_batt_sts;
+ int rc;
+
+ rc = fg_read(chip, &fg_batt_sts,
+ INT_RT_STS(chip->batt_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->batt_base), rc);
+ return rc;
+ }
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("fg batt sts 0x%x\n", fg_batt_sts);
+
+ return (fg_batt_sts & BATT_IDED) ? 1 : 0;
+}
+
+static int64_t twos_compliment_extend(int64_t val, int nbytes)
+{
+ int i;
+ int64_t mask;
+
+ mask = 0x80LL << ((nbytes - 1) * 8);
+ if (val & mask) {
+ for (i = 8; i > nbytes; i--) {
+ mask = 0xFFLL << ((i - 1) * 8);
+ val |= mask;
+ }
+ }
+
+ return val;
+}
+
+#define LSB_24B_NUMRTR 596046
+#define LSB_24B_DENMTR 1000000
+#define LSB_16B_NUMRTR 152587
+#define LSB_16B_DENMTR 1000
+#define LSB_8B 9800
+#define TEMP_LSB_16B 625
+#define DECIKELVIN 2730
+#define SRAM_PERIOD_NO_ID_UPDATE_MS 100
+#define FULL_PERCENT_28BIT 0xFFFFFFF
+static void update_sram_data(struct fg_chip *chip, int *resched_ms)
+{
+ int i, j, rc = 0;
+ u8 reg[4];
+ int64_t temp;
+ int battid_valid = fg_is_batt_id_valid(chip);
+
+ fg_stay_awake(&chip->update_sram_wakeup_source);
+ if (chip->fg_restarting)
+ goto resched;
+
+ fg_mem_lock(chip);
+ for (i = 1; i < FG_DATA_MAX; i++) {
+ if (chip->profile_loaded && i >= FG_DATA_BATT_ID)
+ continue;
+ rc = fg_mem_read(chip, reg, fg_data[i].address,
+ fg_data[i].len, fg_data[i].offset, 0);
+ if (rc) {
+ pr_err("Failed to update sram data\n");
+ break;
+ }
+
+ temp = 0;
+ for (j = 0; j < fg_data[i].len; j++)
+ temp |= reg[j] << (8 * j);
+
+ switch (i) {
+ case FG_DATA_OCV:
+ case FG_DATA_VOLTAGE:
+ case FG_DATA_CPRED_VOLTAGE:
+ fg_data[i].value = div_u64(
+ (u64)(u16)temp * LSB_16B_NUMRTR,
+ LSB_16B_DENMTR);
+ break;
+ case FG_DATA_CURRENT:
+ temp = twos_compliment_extend(temp, fg_data[i].len);
+ fg_data[i].value = div_s64(
+ (s64)temp * LSB_16B_NUMRTR,
+ LSB_16B_DENMTR);
+ break;
+ case FG_DATA_BATT_ESR:
+ fg_data[i].value = float_decode((u16) temp);
+ break;
+ case FG_DATA_BATT_ESR_COUNT:
+ fg_data[i].value = (u16)temp;
+ break;
+ case FG_DATA_BATT_ID:
+ if (battid_valid)
+ fg_data[i].value = reg[0] * LSB_8B;
+ break;
+ case FG_DATA_BATT_ID_INFO:
+ if (battid_valid)
+ fg_data[i].value = reg[0];
+ break;
+ case FG_DATA_BATT_SOC:
+ fg_data[i].value = div64_s64((temp * 10000),
+ FULL_PERCENT_3B);
+ break;
+ case FG_DATA_CC_CHARGE:
+ temp = twos_compliment_extend(temp, fg_data[i].len);
+ fg_data[i].value = div64_s64(
+ temp * (int64_t)chip->nom_cap_uah,
+ FULL_PERCENT_28BIT);
+ break;
+ case FG_DATA_VINT_ERR:
+ temp = twos_compliment_extend(temp, fg_data[i].len);
+ fg_data[i].value = div64_s64(temp * chip->nom_cap_uah,
+ FULL_PERCENT_3B);
+ break;
+ };
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("%d %lld %d\n", i, temp, fg_data[i].value);
+ }
+ fg_mem_release(chip);
+
+ if (!rc)
+ get_current_time(&chip->last_sram_update_time);
+
+resched:
+ if (battid_valid) {
+ complete_all(&chip->batt_id_avail);
+ *resched_ms = fg_sram_update_period_ms;
+ } else {
+ *resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS;
+ }
+ fg_relax(&chip->update_sram_wakeup_source);
+}
+
+#define SRAM_TIMEOUT_MS 3000
+static void update_sram_data_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ update_sram_data.work);
+ int resched_ms = SRAM_PERIOD_NO_ID_UPDATE_MS, ret;
+ bool tried_again = false;
+
+wait:
+ /* Wait for MEMIF access revoked */
+ ret = wait_for_completion_interruptible_timeout(
+ &chip->sram_access_revoked,
+ msecs_to_jiffies(SRAM_TIMEOUT_MS));
+
+ /* If we were interrupted wait again one more time. */
+ if (ret == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ goto wait;
+ } else if (ret <= 0) {
+ pr_err("transaction timed out ret=%d\n", ret);
+ goto out;
+ }
+ update_sram_data(chip, &resched_ms);
+
+out:
+ schedule_delayed_work(
+ &chip->update_sram_data,
+ msecs_to_jiffies(resched_ms));
+}
+
+#define BATT_TEMP_OFFSET 3
+#define BATT_TEMP_CNTRL_MASK 0x17
+#define DISABLE_THERM_BIT BIT(0)
+#define TEMP_SENSE_ALWAYS_BIT BIT(1)
+#define TEMP_SENSE_CHARGE_BIT BIT(2)
+#define FORCE_RBIAS_ON_BIT BIT(4)
+#define BATT_TEMP_OFF DISABLE_THERM_BIT
+#define BATT_TEMP_ON (FORCE_RBIAS_ON_BIT | TEMP_SENSE_ALWAYS_BIT | \
+ TEMP_SENSE_CHARGE_BIT)
+#define TEMP_PERIOD_UPDATE_MS 10000
+#define TEMP_PERIOD_TIMEOUT_MS 3000
+static void update_temp_data(struct work_struct *work)
+{
+ s16 temp;
+ u8 reg[2];
+ bool tried_again = false;
+ int rc, ret, timeout = TEMP_PERIOD_TIMEOUT_MS;
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ update_temp_work.work);
+
+ if (chip->fg_restarting)
+ goto resched;
+
+ fg_stay_awake(&chip->update_temp_wakeup_source);
+ if (chip->sw_rbias_ctrl) {
+ rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT,
+ BATT_TEMP_CNTRL_MASK,
+ BATT_TEMP_ON,
+ BATT_TEMP_OFFSET);
+ if (rc) {
+ pr_err("failed to write BATT_TEMP_ON rc=%d\n", rc);
+ goto out;
+ }
+
+wait:
+ /* Wait for MEMIF access revoked */
+ ret = wait_for_completion_interruptible_timeout(
+ &chip->sram_access_revoked,
+ msecs_to_jiffies(timeout));
+
+ /* If we were interrupted wait again one more time. */
+ if (ret == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ goto wait;
+ } else if (ret <= 0) {
+ rc = -ETIMEDOUT;
+ pr_err("transaction timed out ret=%d\n", ret);
+ goto out;
+ }
+ }
+
+ /* Read FG_DATA_BATT_TEMP now */
+ rc = fg_mem_read(chip, reg, fg_data[0].address,
+ fg_data[0].len, fg_data[0].offset,
+ chip->sw_rbias_ctrl ? 1 : 0);
+ if (rc) {
+ pr_err("Failed to update temp data\n");
+ goto out;
+ }
+
+ temp = reg[0] | (reg[1] << 8);
+ fg_data[0].value = (temp * TEMP_LSB_16B / 1000)
+ - DECIKELVIN;
+
+ if (fg_debug_mask & FG_MEM_DEBUG_READS)
+ pr_info("BATT_TEMP %d %d\n", temp, fg_data[0].value);
+
+ get_current_time(&chip->last_temp_update_time);
+
+out:
+ if (chip->sw_rbias_ctrl) {
+ rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT,
+ BATT_TEMP_CNTRL_MASK,
+ BATT_TEMP_OFF,
+ BATT_TEMP_OFFSET);
+ if (rc)
+ pr_err("failed to write BATT_TEMP_OFF rc=%d\n", rc);
+ }
+ fg_relax(&chip->update_temp_wakeup_source);
+
+resched:
+ schedule_delayed_work(
+ &chip->update_temp_work,
+ msecs_to_jiffies(TEMP_PERIOD_UPDATE_MS));
+}
+
+static void update_jeita_setting(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ update_jeita_setting.work);
+ u8 reg[4];
+ int i, rc;
+
+ for (i = 0; i < 4; i++)
+ reg[i] = (settings[FG_MEM_SOFT_COLD + i].value / 10) + 30;
+
+ rc = fg_mem_write(chip, reg, settings[FG_MEM_SOFT_COLD].address,
+ 4, settings[FG_MEM_SOFT_COLD].offset, 0);
+ if (rc)
+ pr_err("failed to update JEITA setting rc=%d\n", rc);
+}
+
+static int fg_set_resume_soc(struct fg_chip *chip, u8 threshold)
+{
+ u16 address;
+ int offset, rc;
+
+ address = settings[FG_MEM_RESUME_SOC].address;
+ offset = settings[FG_MEM_RESUME_SOC].offset;
+
+ rc = fg_mem_masked_write(chip, address, 0xFF, threshold, offset);
+
+ if (rc)
+ pr_err("write failed rc=%d\n", rc);
+ else
+ pr_debug("setting resume-soc to %x\n", threshold);
+
+ return rc;
+}
+
+#define VBATT_LOW_STS_BIT BIT(2)
+static int fg_get_vbatt_status(struct fg_chip *chip, bool *vbatt_low_sts)
+{
+ int rc = 0;
+ u8 fg_batt_sts;
+
+ rc = fg_read(chip, &fg_batt_sts, INT_RT_STS(chip->batt_base), 1);
+ if (!rc)
+ *vbatt_low_sts = !!(fg_batt_sts & VBATT_LOW_STS_BIT);
+ return rc;
+}
+
+#define BATT_CYCLE_NUMBER_REG 0x5E8
+#define BATT_CYCLE_OFFSET 0
+static void restore_cycle_counter(struct fg_chip *chip)
+{
+ int rc = 0, i, address;
+ u8 data[2];
+
+ fg_mem_lock(chip);
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ address = BATT_CYCLE_NUMBER_REG + i * 2;
+ rc = fg_mem_read(chip, (u8 *)&data, address, 2,
+ BATT_CYCLE_OFFSET, 0);
+ if (rc)
+ pr_err("Failed to read BATT_CYCLE_NUMBER[%d] rc: %d\n",
+ i, rc);
+ else
+ chip->cyc_ctr.count[i] = data[0] | data[1] << 8;
+ }
+ fg_mem_release(chip);
+}
+
+static void clear_cycle_counter(struct fg_chip *chip)
+{
+ int rc = 0, len, i;
+
+ if (!chip->cyc_ctr.en)
+ return;
+
+ len = sizeof(chip->cyc_ctr.count);
+ memset(chip->cyc_ctr.count, 0, len);
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ chip->cyc_ctr.started[i] = false;
+ chip->cyc_ctr.last_soc[i] = 0;
+ }
+ rc = fg_mem_write(chip, (u8 *)&chip->cyc_ctr.count,
+ BATT_CYCLE_NUMBER_REG, len,
+ BATT_CYCLE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write BATT_CYCLE_NUMBER rc=%d\n", rc);
+}
+
+static int fg_inc_store_cycle_ctr(struct fg_chip *chip, int bucket)
+{
+ int rc = 0, address;
+ u16 cyc_count;
+ u8 data[2];
+
+ if (bucket < 0 || (bucket > BUCKET_COUNT - 1))
+ return 0;
+
+ cyc_count = chip->cyc_ctr.count[bucket];
+ cyc_count++;
+ data[0] = cyc_count & 0xFF;
+ data[1] = cyc_count >> 8;
+
+ address = BATT_CYCLE_NUMBER_REG + bucket * 2;
+
+ rc = fg_mem_write(chip, data, address, 2, BATT_CYCLE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write BATT_CYCLE_NUMBER[%d] rc=%d\n",
+ bucket, rc);
+ else
+ chip->cyc_ctr.count[bucket] = cyc_count;
+ return rc;
+}
+
+static void update_cycle_count(struct work_struct *work)
+{
+ int rc = 0, bucket, i;
+ u8 reg[3], batt_soc;
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ cycle_count_work);
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ rc = fg_mem_read(chip, reg, BATTERY_SOC_REG, 3,
+ BATTERY_SOC_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read battery soc rc: %d\n", rc);
+ goto out;
+ }
+ batt_soc = reg[2];
+
+ if (chip->status == POWER_SUPPLY_STATUS_CHARGING) {
+ /* Find out which bucket the SOC falls in */
+ bucket = batt_soc / BUCKET_SOC_PCT;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("batt_soc: %x bucket: %d\n", reg[2], bucket);
+
+ /*
+ * If we've started counting for the previous bucket,
+ * then store the counter for that bucket if the
+ * counter for current bucket is getting started.
+ */
+ if (bucket > 0 && chip->cyc_ctr.started[bucket - 1] &&
+ !chip->cyc_ctr.started[bucket]) {
+ rc = fg_inc_store_cycle_ctr(chip, bucket - 1);
+ if (rc) {
+ pr_err("Error in storing cycle_ctr rc: %d\n",
+ rc);
+ goto out;
+ } else {
+ chip->cyc_ctr.started[bucket - 1] = false;
+ chip->cyc_ctr.last_soc[bucket - 1] = 0;
+ }
+ }
+ if (!chip->cyc_ctr.started[bucket]) {
+ chip->cyc_ctr.started[bucket] = true;
+ chip->cyc_ctr.last_soc[bucket] = batt_soc;
+ }
+ } else {
+ for (i = 0; i < BUCKET_COUNT; i++) {
+ if (chip->cyc_ctr.started[i] &&
+ batt_soc > chip->cyc_ctr.last_soc[i]) {
+ rc = fg_inc_store_cycle_ctr(chip, i);
+ if (rc)
+ pr_err("Error in storing cycle_ctr rc: %d\n",
+ rc);
+ chip->cyc_ctr.last_soc[i] = 0;
+ }
+ chip->cyc_ctr.started[i] = false;
+ }
+ }
+out:
+ mutex_unlock(&chip->cyc_ctr.lock);
+}
+
+static int fg_get_cycle_count(struct fg_chip *chip)
+{
+ int count;
+
+ if (!chip->cyc_ctr.en)
+ return 0;
+
+ if ((chip->cyc_ctr.id <= 0) || (chip->cyc_ctr.id > BUCKET_COUNT))
+ return -EINVAL;
+
+ mutex_lock(&chip->cyc_ctr.lock);
+ count = chip->cyc_ctr.count[chip->cyc_ctr.id - 1];
+ mutex_unlock(&chip->cyc_ctr.lock);
+ return count;
+}
+
+static void half_float_to_buffer(int64_t uval, u8 *buffer)
+{
+ u16 raw;
+
+ raw = float_encode(uval);
+ buffer[0] = (u8)(raw & 0xFF);
+ buffer[1] = (u8)((raw >> 8) & 0xFF);
+}
+
+static int64_t half_float(u8 *buffer)
+{
+ u16 val;
+
+ val = buffer[1] << 8 | buffer[0];
+ return float_decode(val);
+}
+
+static int voltage_2b(u8 *buffer)
+{
+ u16 val;
+
+ val = buffer[1] << 8 | buffer[0];
+ /* the range of voltage 2b is [-5V, 5V], so it will fit in an int */
+ return (int)div_u64(((u64)val) * LSB_16B_NUMRTR, LSB_16B_DENMTR);
+}
+
+static int bcap_uah_2b(u8 *buffer)
+{
+ u16 val;
+
+ val = buffer[1] << 8 | buffer[0];
+ return ((int)val) * 1000;
+}
+
+static int lookup_ocv_for_soc(struct fg_chip *chip, int soc)
+{
+ int64_t *coeffs;
+
+ if (soc > chip->ocv_junction_p1p2 * 10)
+ coeffs = chip->ocv_coeffs;
+ else if (soc > chip->ocv_junction_p2p3 * 10)
+ coeffs = chip->ocv_coeffs + 4;
+ else
+ coeffs = chip->ocv_coeffs + 8;
+ /* the range of ocv will fit in a 32 bit int */
+ return (int)(coeffs[0]
+ + div_s64(coeffs[1] * soc, 1000LL)
+ + div_s64(coeffs[2] * soc * soc, 1000000LL)
+ + div_s64(coeffs[3] * soc * soc * soc, 1000000000LL));
+}
+
+static int lookup_soc_for_ocv(struct fg_chip *chip, int ocv)
+{
+ int64_t val;
+ int soc = -EINVAL;
+ /*
+ * binary search variables representing the valid start and end
+ * percentages to search
+ */
+ int start = 0, end = 1000, mid;
+
+ if (fg_debug_mask & FG_AGING)
+ pr_info("target_ocv = %d\n", ocv);
+ /* do a binary search for the closest soc to match the ocv */
+ while (end - start > 1) {
+ mid = (start + end) / 2;
+ val = lookup_ocv_for_soc(chip, mid);
+ if (fg_debug_mask & FG_AGING)
+ pr_info("start = %d, mid = %d, end = %d, ocv = %lld\n",
+ start, mid, end, val);
+ if (ocv < val) {
+ end = mid;
+ } else if (ocv > val) {
+ start = mid;
+ } else {
+ soc = mid;
+ break;
+ }
+ }
+ /*
+ * if the exact soc was not found and there are two or less values
+ * remaining, just compare them and see which one is closest to the ocv
+ */
+ if (soc == -EINVAL) {
+ if (abs(ocv - lookup_ocv_for_soc(chip, start))
+ > abs(ocv - lookup_ocv_for_soc(chip, end)))
+ soc = end;
+ else
+ soc = start;
+ }
+ if (fg_debug_mask & FG_AGING)
+ pr_info("closest = %d, target_ocv = %d, ocv_found = %d\n",
+ soc, ocv, lookup_ocv_for_soc(chip, soc));
+ return soc;
+}
+
+#define ESR_ACTUAL_REG 0x554
+#define BATTERY_ESR_REG 0x4F4
+#define TEMP_RS_TO_RSLOW_REG 0x514
+static int estimate_battery_age(struct fg_chip *chip, int *actual_capacity)
+{
+ int64_t ocv_cutoff_new, ocv_cutoff_aged, temp_rs_to_rslow;
+ int64_t esr_actual, battery_esr, val;
+ int soc_cutoff_aged, soc_cutoff_new, rc;
+ int battery_soc, unusable_soc, batt_temp;
+ u8 buffer[3];
+
+ if (chip->batt_aging_mode != FG_AGING_ESR)
+ return 0;
+
+ if (chip->nom_cap_uah == 0) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("ocv coefficients not loaded, aborting\n");
+ return 0;
+ }
+ fg_mem_lock(chip);
+
+ batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP);
+ if (batt_temp < 150 || batt_temp > 400) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("Battery temp (%d) out of range, aborting\n",
+ (int)batt_temp);
+ rc = 0;
+ goto done;
+ }
+
+ battery_soc = get_battery_soc_raw(chip) * 100 / FULL_PERCENT_3B;
+ if (battery_soc < 25 || battery_soc > 75) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("Battery SoC (%d) out of range, aborting\n",
+ (int)battery_soc);
+ rc = 0;
+ goto done;
+ }
+
+ rc = fg_mem_read(chip, buffer, ESR_ACTUAL_REG, 2, 2, 0);
+ esr_actual = half_float(buffer);
+ rc |= fg_mem_read(chip, buffer, BATTERY_ESR_REG, 2, 2, 0);
+ battery_esr = half_float(buffer);
+
+ if (rc) {
+ goto error_done;
+ } else if (esr_actual < battery_esr) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("Batt ESR lower than ESR actual, aborting\n");
+ rc = 0;
+ goto done;
+ }
+ rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, 0, 0);
+ temp_rs_to_rslow = half_float(buffer);
+
+ if (rc)
+ goto error_done;
+
+ fg_mem_release(chip);
+
+ if (fg_debug_mask & FG_AGING) {
+ pr_info("batt_soc = %d, cutoff_voltage = %lld, eval current = %d\n",
+ battery_soc, chip->cutoff_voltage,
+ chip->evaluation_current);
+ pr_info("temp_rs_to_rslow = %lld, batt_esr = %lld, esr_actual = %lld\n",
+ temp_rs_to_rslow, battery_esr, esr_actual);
+ }
+
+ /* calculate soc_cutoff_new */
+ val = (1000000LL + temp_rs_to_rslow) * battery_esr;
+ do_div(val, 1000000);
+ ocv_cutoff_new = div64_s64(chip->evaluation_current * val, 1000)
+ + chip->cutoff_voltage;
+
+ /* calculate soc_cutoff_aged */
+ val = (1000000LL + temp_rs_to_rslow) * esr_actual;
+ do_div(val, 1000000);
+ ocv_cutoff_aged = div64_s64(chip->evaluation_current * val, 1000)
+ + chip->cutoff_voltage;
+
+ if (fg_debug_mask & FG_AGING)
+ pr_info("ocv_cutoff_new = %lld, ocv_cutoff_aged = %lld\n",
+ ocv_cutoff_new, ocv_cutoff_aged);
+
+ soc_cutoff_new = lookup_soc_for_ocv(chip, ocv_cutoff_new);
+ soc_cutoff_aged = lookup_soc_for_ocv(chip, ocv_cutoff_aged);
+
+ if (fg_debug_mask & FG_AGING)
+ pr_info("aged soc = %d, new soc = %d\n",
+ soc_cutoff_aged, soc_cutoff_new);
+ unusable_soc = soc_cutoff_aged - soc_cutoff_new;
+
+ *actual_capacity = div64_s64(((int64_t)chip->nom_cap_uah)
+ * (1000 - unusable_soc), 1000);
+ if (fg_debug_mask & FG_AGING)
+ pr_info("nom cap = %d, actual cap = %d\n",
+ chip->nom_cap_uah, *actual_capacity);
+
+ return rc;
+
+error_done:
+ pr_err("some register reads failed: %d\n", rc);
+done:
+ fg_mem_release(chip);
+ return rc;
+}
+
+static void battery_age_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ battery_age_work);
+
+ estimate_battery_age(chip, &chip->actual_cap_uah);
+}
+
+static enum power_supply_property fg_power_props[] = {
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_CAPACITY_RAW,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_VOLTAGE_OCV,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CHARGE_NOW,
+ POWER_SUPPLY_PROP_CHARGE_NOW_RAW,
+ POWER_SUPPLY_PROP_CHARGE_NOW_ERROR,
+ POWER_SUPPLY_PROP_CHARGE_FULL,
+ POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_COOL_TEMP,
+ POWER_SUPPLY_PROP_WARM_TEMP,
+ POWER_SUPPLY_PROP_RESISTANCE,
+ POWER_SUPPLY_PROP_RESISTANCE_ID,
+ POWER_SUPPLY_PROP_BATTERY_TYPE,
+ POWER_SUPPLY_PROP_UPDATE_NOW,
+ POWER_SUPPLY_PROP_ESR_COUNT,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN,
+ POWER_SUPPLY_PROP_CYCLE_COUNT,
+ POWER_SUPPLY_PROP_CYCLE_COUNT_ID,
+ POWER_SUPPLY_PROP_HI_POWER,
+};
+
+static int fg_power_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct fg_chip *chip = power_supply_get_drvdata(psy);
+ bool vbatt_low_sts;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_BATTERY_TYPE:
+ if (chip->battery_missing)
+ val->strval = missing_batt_type;
+ else if (chip->fg_restarting)
+ val->strval = loading_batt_type;
+ else
+ val->strval = chip->batt_type;
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_prop_capacity(chip);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_RAW:
+ val->intval = get_sram_prop_now(chip, FG_DATA_BATT_SOC);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW_ERROR:
+ val->intval = get_sram_prop_now(chip, FG_DATA_VINT_ERR);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = get_sram_prop_now(chip, FG_DATA_CURRENT);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_sram_prop_now(chip, FG_DATA_VOLTAGE);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_OCV:
+ val->intval = get_sram_prop_now(chip, FG_DATA_OCV);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = chip->batt_max_voltage_uv;
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = get_sram_prop_now(chip, FG_DATA_BATT_TEMP);
+ break;
+ case POWER_SUPPLY_PROP_COOL_TEMP:
+ val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_COLD);
+ break;
+ case POWER_SUPPLY_PROP_WARM_TEMP:
+ val->intval = get_prop_jeita_temp(chip, FG_MEM_SOFT_HOT);
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE:
+ val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR);
+ break;
+ case POWER_SUPPLY_PROP_ESR_COUNT:
+ val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ESR_COUNT);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT:
+ val->intval = fg_get_cycle_count(chip);
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ val->intval = chip->cyc_ctr.id;
+ break;
+ case POWER_SUPPLY_PROP_RESISTANCE_ID:
+ val->intval = get_sram_prop_now(chip, FG_DATA_BATT_ID);
+ break;
+ case POWER_SUPPLY_PROP_UPDATE_NOW:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MIN:
+ if (!fg_get_vbatt_status(chip, &vbatt_low_sts))
+ val->intval = (int)vbatt_low_sts;
+ else
+ val->intval = 1;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL_DESIGN:
+ val->intval = chip->nom_cap_uah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_FULL:
+ val->intval = chip->learning_data.learned_cc_uah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW:
+ val->intval = chip->learning_data.cc_uah;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_NOW_RAW:
+ val->intval = get_sram_prop_now(chip, FG_DATA_CC_CHARGE);
+ break;
+ case POWER_SUPPLY_PROP_HI_POWER:
+ val->intval = !!chip->bcl_lpm_disabled;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int correction_times[] = {
+ 1470,
+ 2940,
+ 4410,
+ 5880,
+ 7350,
+ 8820,
+ 10290,
+ 11760,
+ 13230,
+ 14700,
+ 16170,
+ 17640,
+ 19110,
+ 20580,
+ 22050,
+ 23520,
+ 24990,
+ 26460,
+ 27930,
+ 29400,
+ 30870,
+ 32340,
+ 33810,
+ 35280,
+ 36750,
+ 38220,
+ 39690,
+ 41160,
+ 42630,
+ 44100,
+ 45570,
+ 47040,
+};
+
+static int correction_factors[] = {
+ 1000000,
+ 1007874,
+ 1015789,
+ 1023745,
+ 1031742,
+ 1039780,
+ 1047859,
+ 1055979,
+ 1064140,
+ 1072342,
+ 1080584,
+ 1088868,
+ 1097193,
+ 1105558,
+ 1113964,
+ 1122411,
+ 1130899,
+ 1139427,
+ 1147996,
+ 1156606,
+ 1165256,
+ 1173947,
+ 1182678,
+ 1191450,
+ 1200263,
+ 1209115,
+ 1218008,
+ 1226942,
+ 1235915,
+ 1244929,
+ 1253983,
+ 1263076,
+};
+
+#define FG_CONVERSION_FACTOR (64198531LL)
+static int iavg_3b_to_uah(u8 *buffer, int delta_ms)
+{
+ int64_t val, i_filtered;
+ int i, correction_factor;
+
+ for (i = 0; i < ARRAY_SIZE(correction_times); i++) {
+ if (correction_times[i] > delta_ms)
+ break;
+ }
+ if (i >= ARRAY_SIZE(correction_times)) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("fuel gauge took more than 32 cycles\n");
+ i = ARRAY_SIZE(correction_times) - 1;
+ }
+ correction_factor = correction_factors[i];
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("delta_ms = %d, cycles = %d, correction = %d\n",
+ delta_ms, i, correction_factor);
+ val = buffer[2] << 16 | buffer[1] << 8 | buffer[0];
+ /* convert val from signed 24b to signed 64b */
+ i_filtered = (val << 40) >> 40;
+ val = i_filtered * correction_factor;
+ val = div64_s64(val + FG_CONVERSION_FACTOR / 2, FG_CONVERSION_FACTOR);
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("i_filtered = 0x%llx/%lld, cc_uah = %lld\n",
+ i_filtered, i_filtered, val);
+
+ return val;
+}
+
+static bool fg_is_temperature_ok_for_learning(struct fg_chip *chip)
+{
+ int batt_temp = get_sram_prop_now(chip, FG_DATA_BATT_TEMP);
+
+ if (batt_temp > chip->learning_data.max_temp
+ || batt_temp < chip->learning_data.min_temp) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("temp (%d) out of range [%d, %d], aborting\n",
+ batt_temp,
+ chip->learning_data.min_temp,
+ chip->learning_data.max_temp);
+ return false;
+ }
+ return true;
+}
+
+static void fg_cap_learning_stop(struct fg_chip *chip)
+{
+ chip->learning_data.cc_uah = 0;
+ chip->learning_data.active = false;
+}
+
+#define I_FILTERED_REG 0x584
+static void fg_cap_learning_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ fg_cap_learning_work);
+ u8 i_filtered[3], data[3];
+ int rc, cc_uah, delta_ms;
+ ktime_t now_kt, delta_kt;
+
+ mutex_lock(&chip->learning_data.learning_lock);
+ if (!chip->learning_data.active)
+ goto fail;
+ if (!fg_is_temperature_ok_for_learning(chip)) {
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ if (chip->wa_flag & USE_CC_SOC_REG) {
+ mutex_unlock(&chip->learning_data.learning_lock);
+ fg_relax(&chip->capacity_learning_wakeup_source);
+ return;
+ }
+
+ fg_mem_lock(chip);
+
+ rc = fg_mem_read(chip, i_filtered, I_FILTERED_REG, 3, 0, 0);
+ if (rc) {
+ pr_err("Failed to read i_filtered: %d\n", rc);
+ fg_mem_release(chip);
+ goto fail;
+ }
+ memset(data, 0, 3);
+ rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0);
+ if (rc) {
+ pr_err("Failed to clear i_filtered: %d\n", rc);
+ fg_mem_release(chip);
+ goto fail;
+ }
+ fg_mem_release(chip);
+
+ now_kt = ktime_get_boottime();
+ delta_kt = ktime_sub(now_kt, chip->learning_data.time_stamp);
+ chip->learning_data.time_stamp = now_kt;
+
+ delta_ms = (int)div64_s64(ktime_to_ns(delta_kt), 1000000);
+
+ cc_uah = iavg_3b_to_uah(i_filtered, delta_ms);
+ chip->learning_data.cc_uah -= cc_uah;
+ if (fg_debug_mask & FG_AGING)
+ pr_info("total_cc_uah = %lld\n", chip->learning_data.cc_uah);
+
+fail:
+ mutex_unlock(&chip->learning_data.learning_lock);
+ return;
+
+}
+
+#define CC_SOC_BASE_REG 0x5BC
+#define CC_SOC_OFFSET 3
+#define CC_SOC_MAGNITUDE_MASK 0x1FFFFFFF
+#define CC_SOC_NEGATIVE_BIT BIT(29)
+static int fg_get_cc_soc(struct fg_chip *chip, int *cc_soc)
+{
+ int rc;
+ u8 reg[4];
+ unsigned int temp, magnitude;
+
+ rc = fg_mem_read(chip, reg, CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read CC_SOC_REG rc=%d\n", rc);
+ return rc;
+ }
+
+ temp = reg[3] << 24 | reg[2] << 16 | reg[1] << 8 | reg[0];
+ magnitude = temp & CC_SOC_MAGNITUDE_MASK;
+ if (temp & CC_SOC_NEGATIVE_BIT)
+ *cc_soc = -1 * (~magnitude + 1);
+ else
+ *cc_soc = magnitude;
+
+ return 0;
+}
+
+static int fg_cap_learning_process_full_data(struct fg_chip *chip)
+{
+ int cc_pc_val, rc = -EINVAL;
+ unsigned int cc_soc_delta_pc;
+ int64_t delta_cc_uah;
+
+ if (!chip->learning_data.active)
+ goto fail;
+
+ if (!fg_is_temperature_ok_for_learning(chip)) {
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ rc = fg_get_cc_soc(chip, &cc_pc_val);
+ if (rc) {
+ pr_err("failed to get CC_SOC, stopping capacity learning\n");
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ cc_soc_delta_pc = DIV_ROUND_CLOSEST(
+ abs(cc_pc_val - chip->learning_data.init_cc_pc_val)
+ * 100, FULL_PERCENT_28BIT);
+
+ delta_cc_uah = div64_s64(
+ chip->learning_data.learned_cc_uah * cc_soc_delta_pc,
+ 100);
+ chip->learning_data.cc_uah = delta_cc_uah + chip->learning_data.cc_uah;
+
+ if (fg_debug_mask & FG_AGING)
+ pr_info("current cc_soc=%d cc_soc_pc=%d total_cc_uah = %lld\n",
+ cc_pc_val, cc_soc_delta_pc,
+ chip->learning_data.cc_uah);
+
+ return 0;
+
+fail:
+ return rc;
+}
+
+#define FG_CAP_LEARNING_INTERVAL_NS 30000000000
+static enum alarmtimer_restart fg_cap_learning_alarm_cb(struct alarm *alarm,
+ ktime_t now)
+{
+ struct fg_chip *chip = container_of(alarm, struct fg_chip,
+ fg_cap_learning_alarm);
+
+ if (chip->learning_data.active) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("alarm fired\n");
+ schedule_work(&chip->fg_cap_learning_work);
+ alarm_forward_now(alarm,
+ ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS));
+ return ALARMTIMER_RESTART;
+ }
+ if (fg_debug_mask & FG_AGING)
+ pr_info("alarm misfired\n");
+ return ALARMTIMER_NORESTART;
+}
+
+#define FG_AGING_STORAGE_REG 0x5E4
+#define ACTUAL_CAPACITY_REG 0x578
+#define MAH_TO_SOC_CONV_REG 0x4A0
+#define CC_SOC_COEFF_OFFSET 0
+#define ACTUAL_CAPACITY_OFFSET 2
+#define MAH_TO_SOC_CONV_CS_OFFSET 0
+static int fg_calc_and_store_cc_soc_coeff(struct fg_chip *chip, int16_t cc_mah)
+{
+ int rc;
+ int64_t cc_to_soc_coeff, mah_to_soc;
+ u8 data[2];
+
+ rc = fg_mem_write(chip, (u8 *)&cc_mah, ACTUAL_CAPACITY_REG, 2,
+ ACTUAL_CAPACITY_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to store actual capacity: %d\n", rc);
+ return rc;
+ }
+
+ rc = fg_mem_read(chip, (u8 *)&data, MAH_TO_SOC_CONV_REG, 2,
+ MAH_TO_SOC_CONV_CS_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read mah_to_soc_conv_cs: %d\n", rc);
+ } else {
+ mah_to_soc = data[1] << 8 | data[0];
+ mah_to_soc *= MICRO_UNIT;
+ cc_to_soc_coeff = div64_s64(mah_to_soc, cc_mah);
+ half_float_to_buffer(cc_to_soc_coeff, data);
+ rc = fg_mem_write(chip, (u8 *)data,
+ ACTUAL_CAPACITY_REG, 2,
+ CC_SOC_COEFF_OFFSET, 0);
+ if (rc)
+ pr_err("Failed to write cc_soc_coeff_offset: %d\n",
+ rc);
+ else if (fg_debug_mask & FG_AGING)
+ pr_info("new cc_soc_coeff %lld [%x %x] saved to sram\n",
+ cc_to_soc_coeff, data[0], data[1]);
+ }
+ return rc;
+}
+
+static void fg_cap_learning_load_data(struct fg_chip *chip)
+{
+ int16_t cc_mah;
+ int64_t old_cap = chip->learning_data.learned_cc_uah;
+ int rc;
+
+ rc = fg_mem_read(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0);
+ if (rc) {
+ pr_err("Failed to load aged capacity: %d\n", rc);
+ } else {
+ chip->learning_data.learned_cc_uah = cc_mah * 1000;
+ if (fg_debug_mask & FG_AGING)
+ pr_info("learned capacity %lld-> %lld/%x uah\n",
+ old_cap,
+ chip->learning_data.learned_cc_uah,
+ cc_mah);
+ }
+}
+
+static void fg_cap_learning_save_data(struct fg_chip *chip)
+{
+ int16_t cc_mah;
+ int rc;
+
+ cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000);
+
+ rc = fg_mem_write(chip, (u8 *)&cc_mah, FG_AGING_STORAGE_REG, 2, 0, 0);
+ if (rc)
+ pr_err("Failed to store aged capacity: %d\n", rc);
+ else if (fg_debug_mask & FG_AGING)
+ pr_info("learned capacity %lld uah (%d/0x%x uah) saved to sram\n",
+ chip->learning_data.learned_cc_uah,
+ cc_mah, cc_mah);
+
+ if (chip->learning_data.feedback_on) {
+ rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah);
+ if (rc)
+ pr_err("Error in storing cc_soc_coeff, rc:%d\n", rc);
+ }
+}
+
+static void fg_cap_learning_post_process(struct fg_chip *chip)
+{
+ int64_t max_inc_val, min_dec_val, old_cap;
+
+ max_inc_val = chip->learning_data.learned_cc_uah
+ * (1000 + chip->learning_data.max_increment);
+ do_div(max_inc_val, 1000);
+
+ min_dec_val = chip->learning_data.learned_cc_uah
+ * (1000 - chip->learning_data.max_decrement);
+ do_div(min_dec_val, 1000);
+
+ old_cap = chip->learning_data.learned_cc_uah;
+ if (chip->learning_data.cc_uah > max_inc_val)
+ chip->learning_data.learned_cc_uah = max_inc_val;
+ else if (chip->learning_data.cc_uah < min_dec_val)
+ chip->learning_data.learned_cc_uah = min_dec_val;
+ else
+ chip->learning_data.learned_cc_uah =
+ chip->learning_data.cc_uah;
+
+ fg_cap_learning_save_data(chip);
+ if (fg_debug_mask & FG_AGING)
+ pr_info("final cc_uah = %lld, learned capacity %lld -> %lld uah\n",
+ chip->learning_data.cc_uah,
+ old_cap, chip->learning_data.learned_cc_uah);
+}
+
+static int get_vbat_est_diff(struct fg_chip *chip)
+{
+ return abs(fg_data[FG_DATA_VOLTAGE].value
+ - fg_data[FG_DATA_CPRED_VOLTAGE].value);
+}
+
+#define CBITS_INPUT_FILTER_REG 0x4B4
+#define IBATTF_TAU_MASK 0x38
+#define IBATTF_TAU_99_S 0x30
+static int fg_cap_learning_check(struct fg_chip *chip)
+{
+ u8 data[4];
+ int rc = 0, battery_soc, cc_pc_val;
+ int vbat_est_diff, vbat_est_thr_uv;
+ unsigned int cc_pc_100 = FULL_PERCENT_28BIT;
+
+ mutex_lock(&chip->learning_data.learning_lock);
+ if (chip->status == POWER_SUPPLY_STATUS_CHARGING
+ && !chip->learning_data.active
+ && chip->batt_aging_mode == FG_AGING_CC) {
+ if (chip->learning_data.learned_cc_uah == 0) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("no capacity, aborting\n");
+ goto fail;
+ }
+
+ if (!fg_is_temperature_ok_for_learning(chip))
+ goto fail;
+
+ fg_mem_lock(chip);
+ if (!chip->learning_data.feedback_on) {
+ vbat_est_diff = get_vbat_est_diff(chip);
+ vbat_est_thr_uv = chip->learning_data.vbat_est_thr_uv;
+ if (vbat_est_diff >= vbat_est_thr_uv &&
+ vbat_est_thr_uv > 0) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("vbat_est_diff (%d) < threshold (%d)\n",
+ vbat_est_diff, vbat_est_thr_uv);
+ fg_mem_release(chip);
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+ }
+ battery_soc = get_battery_soc_raw(chip);
+ if (fg_debug_mask & FG_AGING)
+ pr_info("checking battery soc (%d vs %d)\n",
+ battery_soc * 100 / FULL_PERCENT_3B,
+ chip->learning_data.max_start_soc);
+ /* check if the battery is low enough to start soc learning */
+ if (battery_soc * 100 / FULL_PERCENT_3B
+ > chip->learning_data.max_start_soc) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("battery soc too low (%d < %d), aborting\n",
+ battery_soc * 100 / FULL_PERCENT_3B,
+ chip->learning_data.max_start_soc);
+ fg_mem_release(chip);
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ /* set the coulomb counter to a percentage of the capacity */
+ chip->learning_data.cc_uah = div64_s64(
+ (chip->learning_data.learned_cc_uah * battery_soc),
+ FULL_PERCENT_3B);
+
+ /* Use CC_SOC_REG based capacity learning */
+ if (chip->wa_flag & USE_CC_SOC_REG) {
+ fg_mem_release(chip);
+ /* SW_CC_SOC based capacity learning */
+ if (fg_get_cc_soc(chip, &cc_pc_val)) {
+ pr_err("failed to get CC_SOC, stop capacity learning\n");
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ chip->learning_data.init_cc_pc_val = cc_pc_val;
+ chip->learning_data.active = true;
+ if (fg_debug_mask & FG_AGING)
+ pr_info("SW_CC_SOC based learning init_CC_SOC=%d\n",
+ chip->learning_data.init_cc_pc_val);
+ } else {
+ rc = fg_mem_masked_write(chip, CBITS_INPUT_FILTER_REG,
+ IBATTF_TAU_MASK, IBATTF_TAU_99_S, 0);
+ if (rc) {
+ pr_err("Failed to write IF IBAT Tau: %d\n",
+ rc);
+ fg_mem_release(chip);
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+
+ /* clear the i_filtered register */
+ memset(data, 0, 4);
+ rc = fg_mem_write(chip, data, I_FILTERED_REG, 3, 0, 0);
+ if (rc) {
+ pr_err("Failed to clear i_filtered: %d\n", rc);
+ fg_mem_release(chip);
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+ fg_mem_release(chip);
+ chip->learning_data.time_stamp = ktime_get_boottime();
+ chip->learning_data.active = true;
+
+ if (fg_debug_mask & FG_AGING)
+ pr_info("cap learning started, soc = %d cc_uah = %lld\n",
+ battery_soc * 100 / FULL_PERCENT_3B,
+ chip->learning_data.cc_uah);
+ alarm_start_relative(&chip->fg_cap_learning_alarm,
+ ns_to_ktime(FG_CAP_LEARNING_INTERVAL_NS));
+ }
+ } else if ((chip->status != POWER_SUPPLY_STATUS_CHARGING)
+ && chip->learning_data.active) {
+ if (fg_debug_mask & FG_AGING)
+ pr_info("capacity learning stopped\n");
+ if (!(chip->wa_flag & USE_CC_SOC_REG))
+ alarm_try_to_cancel(&chip->fg_cap_learning_alarm);
+
+ if (chip->status == POWER_SUPPLY_STATUS_FULL) {
+ if (chip->wa_flag & USE_CC_SOC_REG) {
+ rc = fg_cap_learning_process_full_data(chip);
+ if (rc) {
+ fg_cap_learning_stop(chip);
+ goto fail;
+ }
+ /* reset SW_CC_SOC register to 100% */
+ rc = fg_mem_write(chip, (u8 *)&cc_pc_100,
+ CC_SOC_BASE_REG, 4, CC_SOC_OFFSET, 0);
+ if (rc)
+ pr_err("Failed to reset CC_SOC_REG rc=%d\n",
+ rc);
+ }
+ fg_cap_learning_post_process(chip);
+ }
+
+ fg_cap_learning_stop(chip);
+ }
+
+fail:
+ mutex_unlock(&chip->learning_data.learning_lock);
+ return rc;
+}
+
+static bool is_usb_present(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0,};
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+
+ if (chip->usb_psy)
+ power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_PRESENT, &prop);
+ return prop.intval != 0;
+}
+
+static bool is_dc_present(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0,};
+ if (!chip->dc_psy)
+ chip->dc_psy = power_supply_get_by_name("dc");
+
+ if (chip->dc_psy)
+ power_supply_get_property(chip->dc_psy,
+ POWER_SUPPLY_PROP_PRESENT, &prop);
+ return prop.intval != 0;
+}
+
+static bool is_input_present(struct fg_chip *chip)
+{
+ return is_usb_present(chip) || is_dc_present(chip);
+}
+
+static bool is_otg_present(struct fg_chip *chip)
+{
+ union power_supply_propval prop = {0,};
+
+ if (!chip->usb_psy)
+ chip->usb_psy = power_supply_get_by_name("usb");
+
+ if (chip->usb_psy)
+ power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_USB_OTG, &prop);
+ return prop.intval != 0;
+}
+
+static bool is_charger_available(struct fg_chip *chip)
+{
+ if (!chip->batt_psy_name)
+ return false;
+
+ if (!chip->batt_psy)
+ chip->batt_psy = power_supply_get_by_name(chip->batt_psy_name);
+
+ if (!chip->batt_psy)
+ return false;
+
+ return true;
+}
+
+static int set_prop_enable_charging(struct fg_chip *chip, bool enable)
+{
+ int rc = 0;
+ union power_supply_propval ret = {enable, };
+
+ if (!is_charger_available(chip)) {
+ pr_err("Charger not available yet!\n");
+ return -EINVAL;
+ }
+
+ rc = power_supply_set_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ &ret);
+ if (rc) {
+ pr_err("couldn't configure batt chg %d\n", rc);
+ return rc;
+ }
+
+ chip->charging_disabled = !enable;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("%sabling charging\n", enable ? "en" : "dis");
+
+ return rc;
+}
+
+#define MAX_BATTERY_CC_SOC_CAPACITY 150
+static void status_change_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ status_change_work);
+ unsigned long current_time = 0;
+ int cc_soc, rc, capacity = get_prop_capacity(chip);
+
+ if (chip->esr_pulse_tune_en) {
+ fg_stay_awake(&chip->esr_extract_wakeup_source);
+ schedule_work(&chip->esr_extract_config_work);
+ }
+
+ if (chip->status == POWER_SUPPLY_STATUS_FULL) {
+ if (capacity >= 99 && chip->hold_soc_while_full
+ && chip->health == POWER_SUPPLY_HEALTH_GOOD) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("holding soc at 100\n");
+ chip->charge_full = true;
+ } else if (fg_debug_mask & FG_STATUS) {
+ pr_info("terminated charging at %d/0x%02x\n",
+ capacity, get_monotonic_soc_raw(chip));
+ }
+ }
+ if (chip->status == POWER_SUPPLY_STATUS_FULL ||
+ chip->status == POWER_SUPPLY_STATUS_CHARGING) {
+ if (!chip->vbat_low_irq_enabled) {
+ enable_irq(chip->batt_irq[VBATT_LOW].irq);
+ enable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
+ chip->vbat_low_irq_enabled = true;
+ }
+ if (!!(chip->wa_flag & PULSE_REQUEST_WA) && capacity == 100)
+ fg_configure_soc(chip);
+ } else if (chip->status == POWER_SUPPLY_STATUS_DISCHARGING) {
+ if (chip->vbat_low_irq_enabled) {
+ disable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
+ disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
+ chip->vbat_low_irq_enabled = false;
+ }
+ }
+ fg_cap_learning_check(chip);
+ schedule_work(&chip->update_esr_work);
+
+ if (chip->wa_flag & USE_CC_SOC_REG) {
+ if (fg_get_cc_soc(chip, &cc_soc)) {
+ pr_err("failed to get CC_SOC\n");
+ return;
+ }
+ }
+
+ if (chip->prev_status != chip->status && chip->last_sram_update_time) {
+ get_current_time(&current_time);
+ /*
+ * When charging status changes, update SRAM parameters if it
+ * was not updated before 5 seconds from now.
+ */
+ if (chip->last_sram_update_time + 5 < current_time) {
+ cancel_delayed_work(&chip->update_sram_data);
+ schedule_delayed_work(&chip->update_sram_data,
+ msecs_to_jiffies(0));
+ }
+ if (chip->cyc_ctr.en)
+ schedule_work(&chip->cycle_count_work);
+ if ((chip->wa_flag & USE_CC_SOC_REG) &&
+ chip->bad_batt_detection_en &&
+ chip->status == POWER_SUPPLY_STATUS_CHARGING) {
+ chip->sw_cc_soc_data.init_sys_soc = capacity;
+ chip->sw_cc_soc_data.init_cc_soc = cc_soc;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info(" Init_sys_soc %d init_cc_soc %d\n",
+ chip->sw_cc_soc_data.init_sys_soc,
+ chip->sw_cc_soc_data.init_cc_soc);
+ }
+ }
+ if ((chip->wa_flag & USE_CC_SOC_REG) && chip->bad_batt_detection_en
+ && chip->safety_timer_expired) {
+ chip->sw_cc_soc_data.delta_soc =
+ DIV_ROUND_CLOSEST(abs(cc_soc -
+ chip->sw_cc_soc_data.init_cc_soc)
+ * 100, FULL_PERCENT_28BIT);
+ chip->sw_cc_soc_data.full_capacity =
+ chip->sw_cc_soc_data.delta_soc +
+ chip->sw_cc_soc_data.init_sys_soc;
+ pr_info("Init_sys_soc %d init_cc_soc %d cc_soc %d delta_soc %d full_capacity %d\n",
+ chip->sw_cc_soc_data.init_sys_soc,
+ chip->sw_cc_soc_data.init_cc_soc, cc_soc,
+ chip->sw_cc_soc_data.delta_soc,
+ chip->sw_cc_soc_data.full_capacity);
+ /*
+ * If sw_cc_soc capacity greater than 150, then it's a bad
+ * battery. else, reset timer and restart charging.
+ */
+ if (chip->sw_cc_soc_data.full_capacity >
+ MAX_BATTERY_CC_SOC_CAPACITY) {
+ pr_info("Battery possibly damaged, do not restart charging\n");
+ } else {
+ pr_info("Reset safety-timer and restart charging\n");
+ rc = set_prop_enable_charging(chip, false);
+ if (rc) {
+ pr_err("failed to disable charging %d\n", rc);
+ return;
+ }
+
+ chip->safety_timer_expired = false;
+ msleep(200);
+
+ rc = set_prop_enable_charging(chip, true);
+ if (rc) {
+ pr_err("failed to enable charging %d\n", rc);
+ return;
+ }
+ }
+ }
+}
+
+/*
+ * Check for change in the status of input or OTG and schedule
+ * IADC gain compensation work.
+ */
+static void check_gain_compensation(struct fg_chip *chip)
+{
+ bool input_present = is_input_present(chip);
+ bool otg_present = is_otg_present(chip);
+
+ if ((chip->wa_flag & IADC_GAIN_COMP_WA)
+ && ((chip->input_present ^ input_present)
+ || (chip->otg_present ^ otg_present))) {
+ fg_stay_awake(&chip->gain_comp_wakeup_source);
+ chip->input_present = input_present;
+ chip->otg_present = otg_present;
+ cancel_work_sync(&chip->gain_comp_work);
+ schedule_work(&chip->gain_comp_work);
+ }
+}
+
+static void fg_hysteresis_config(struct fg_chip *chip)
+{
+ int hard_hot = 0, hard_cold = 0;
+
+ hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT);
+ hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD);
+ if (chip->health == POWER_SUPPLY_HEALTH_OVERHEAT && !chip->batt_hot) {
+ /* turn down the hard hot threshold */
+ chip->batt_hot = true;
+ set_prop_jeita_temp(chip, FG_MEM_HARD_HOT,
+ hard_hot - chip->hot_hysteresis);
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("hard hot hysteresis: old hot=%d, new hot=%d\n",
+ hard_hot, hard_hot - chip->hot_hysteresis);
+ } else if (chip->health == POWER_SUPPLY_HEALTH_COLD &&
+ !chip->batt_cold) {
+ /* turn up the hard cold threshold */
+ chip->batt_cold = true;
+ set_prop_jeita_temp(chip, FG_MEM_HARD_COLD,
+ hard_cold + chip->cold_hysteresis);
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("hard cold hysteresis: old cold=%d, new cold=%d\n",
+ hard_cold, hard_cold + chip->hot_hysteresis);
+ } else if (chip->health != POWER_SUPPLY_HEALTH_OVERHEAT &&
+ chip->batt_hot) {
+ /* restore the hard hot threshold */
+ set_prop_jeita_temp(chip, FG_MEM_HARD_HOT,
+ hard_hot + chip->hot_hysteresis);
+ chip->batt_hot = !chip->batt_hot;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("restore hard hot threshold: old hot=%d, new hot=%d\n",
+ hard_hot,
+ hard_hot + chip->hot_hysteresis);
+ } else if (chip->health != POWER_SUPPLY_HEALTH_COLD &&
+ chip->batt_cold) {
+ /* restore the hard cold threshold */
+ set_prop_jeita_temp(chip, FG_MEM_HARD_COLD,
+ hard_cold - chip->cold_hysteresis);
+ chip->batt_cold = !chip->batt_cold;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("restore hard cold threshold: old cold=%d, new cold=%d\n",
+ hard_cold,
+ hard_cold - chip->cold_hysteresis);
+ }
+}
+
+#define BATT_INFO_STS(base) (base + 0x09)
+#define JEITA_HARD_HOT_RT_STS BIT(6)
+#define JEITA_HARD_COLD_RT_STS BIT(5)
+static int fg_init_batt_temp_state(struct fg_chip *chip)
+{
+ int rc = 0;
+ u8 batt_info_sts;
+ int hard_hot = 0, hard_cold = 0;
+
+ /*
+ * read the batt_info_sts register to parse battery's
+ * initial status and do hysteresis config accordingly.
+ */
+ rc = fg_read(chip, &batt_info_sts,
+ BATT_INFO_STS(chip->batt_base), 1);
+ if (rc) {
+ pr_err("failed to read batt info sts, rc=%d\n", rc);
+ return rc;
+ }
+
+ hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT);
+ hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD);
+ chip->batt_hot =
+ (batt_info_sts & JEITA_HARD_HOT_RT_STS) ? true : false;
+ chip->batt_cold =
+ (batt_info_sts & JEITA_HARD_COLD_RT_STS) ? true : false;
+ if (chip->batt_hot || chip->batt_cold) {
+ if (chip->batt_hot) {
+ chip->health = POWER_SUPPLY_HEALTH_OVERHEAT;
+ set_prop_jeita_temp(chip, FG_MEM_HARD_HOT,
+ hard_hot - chip->hot_hysteresis);
+ } else {
+ chip->health = POWER_SUPPLY_HEALTH_COLD;
+ set_prop_jeita_temp(chip, FG_MEM_HARD_COLD,
+ hard_cold + chip->cold_hysteresis);
+ }
+ }
+
+ return rc;
+}
+
+static int fg_power_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct fg_chip *chip = power_supply_get_drvdata(psy);
+ int rc = 0, unused;
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_COOL_TEMP:
+ rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_COLD, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_WARM_TEMP:
+ rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_HOT, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_UPDATE_NOW:
+ if (val->intval)
+ update_sram_data(chip, &unused);
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ chip->prev_status = chip->status;
+ chip->status = val->intval;
+ schedule_work(&chip->status_change_work);
+ check_gain_compensation(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ chip->health = val->intval;
+ if (chip->health == POWER_SUPPLY_HEALTH_GOOD) {
+ fg_stay_awake(&chip->resume_soc_wakeup_source);
+ schedule_work(&chip->set_resume_soc_work);
+ }
+
+ if (chip->jeita_hysteresis_support)
+ fg_hysteresis_config(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_DONE:
+ chip->charge_done = val->intval;
+ if (!chip->resume_soc_lowered) {
+ fg_stay_awake(&chip->resume_soc_wakeup_source);
+ schedule_work(&chip->set_resume_soc_work);
+ }
+ break;
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ if ((val->intval > 0) && (val->intval <= BUCKET_COUNT)) {
+ chip->cyc_ctr.id = val->intval;
+ } else {
+ pr_err("rejecting invalid cycle_count_id = %d\n",
+ val->intval);
+ rc = -EINVAL;
+ }
+ break;
+ case POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED:
+ chip->safety_timer_expired = val->intval;
+ schedule_work(&chip->status_change_work);
+ break;
+ case POWER_SUPPLY_PROP_HI_POWER:
+ if (chip->wa_flag & BCL_HI_POWER_FOR_CHGLED_WA) {
+ chip->bcl_lpm_disabled = !!val->intval;
+ schedule_work(&chip->bcl_hi_power_work);
+ }
+ break;
+ default:
+ return -EINVAL;
+ };
+
+ return rc;
+};
+
+static int fg_property_is_writeable(struct power_supply *psy,
+ enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_COOL_TEMP:
+ case POWER_SUPPLY_PROP_WARM_TEMP:
+ case POWER_SUPPLY_PROP_CYCLE_COUNT_ID:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+#define SRAM_DUMP_START 0x400
+#define SRAM_DUMP_LEN 0x200
+static void dump_sram(struct work_struct *work)
+{
+ int i, rc;
+ u8 *buffer, rt_sts;
+ char str[16];
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ dump_sram);
+
+ buffer = devm_kzalloc(chip->dev, SRAM_DUMP_LEN, GFP_KERNEL);
+ if (buffer == NULL) {
+ pr_err("Can't allocate buffer\n");
+ return;
+ }
+
+ rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->soc_base), 1);
+ if (rc)
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ else
+ pr_info("soc rt_sts: 0x%x\n", rt_sts);
+
+ rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->batt_base), 1);
+ if (rc)
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->batt_base), rc);
+ else
+ pr_info("batt rt_sts: 0x%x\n", rt_sts);
+
+ rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->mem_base), 1);
+ if (rc)
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->mem_base), rc);
+ else
+ pr_info("memif rt_sts: 0x%x\n", rt_sts);
+
+ rc = fg_mem_read(chip, buffer, SRAM_DUMP_START, SRAM_DUMP_LEN, 0, 0);
+ if (rc) {
+ pr_err("dump failed: rc = %d\n", rc);
+ return;
+ }
+
+ for (i = 0; i < SRAM_DUMP_LEN; i += 4) {
+ str[0] = '\0';
+ fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buffer + i, 4);
+ pr_info("%03X %s\n", SRAM_DUMP_START + i, str);
+ }
+ devm_kfree(chip->dev, buffer);
+}
+
+#define MAXRSCHANGE_REG 0x434
+#define ESR_VALUE_OFFSET 1
+#define ESR_STRICT_VALUE 0x4120391F391F3019
+#define ESR_DEFAULT_VALUE 0x58CD4A6761C34A67
+static void update_esr_value(struct work_struct *work)
+{
+ union power_supply_propval prop = {0, };
+ u64 esr_value;
+ int rc = 0;
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ update_esr_work);
+
+ if (!is_charger_available(chip))
+ return;
+
+ power_supply_get_property(chip->batt_psy,
+ POWER_SUPPLY_PROP_CHARGE_TYPE, &prop);
+
+ if (!chip->esr_strict_filter) {
+ if ((prop.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER &&
+ chip->status == POWER_SUPPLY_STATUS_CHARGING) ||
+ (chip->status == POWER_SUPPLY_STATUS_FULL)) {
+ esr_value = ESR_STRICT_VALUE;
+ rc = fg_mem_write(chip, (u8 *)&esr_value,
+ MAXRSCHANGE_REG, 8,
+ ESR_VALUE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write strict ESR value rc=%d\n",
+ rc);
+ else
+ chip->esr_strict_filter = true;
+ }
+ } else if ((prop.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER &&
+ chip->status == POWER_SUPPLY_STATUS_CHARGING) ||
+ (chip->status == POWER_SUPPLY_STATUS_DISCHARGING)) {
+ esr_value = ESR_DEFAULT_VALUE;
+ rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8,
+ ESR_VALUE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write default ESR value rc=%d\n", rc);
+ else
+ chip->esr_strict_filter = false;
+ }
+}
+
+#define TEMP_COUNTER_REG 0x580
+#define VBAT_FILTERED_OFFSET 1
+#define GAIN_REG 0x424
+#define GAIN_OFFSET 1
+#define K_VCOR_REG 0x484
+#define DEF_GAIN_OFFSET 2
+#define PICO_UNIT 0xE8D4A51000LL
+#define ATTO_UNIT 0xDE0B6B3A7640000LL
+#define VBAT_REF 3800000
+
+/*
+ * IADC Gain compensation steps:
+ * If Input/OTG absent:
+ * - read VBAT_FILTERED, KVCOR, GAIN
+ * - calculate the gain compensation using following formula:
+ * gain = (1 + gain) * (1 + kvcor * (vbat_filtered - 3800000)) - 1;
+ * else
+ * - reset to the default gain compensation
+ */
+static void iadc_gain_comp_work(struct work_struct *work)
+{
+ u8 reg[4];
+ int rc;
+ uint64_t vbat_filtered;
+ int64_t gain, kvcor, temp, numerator;
+ struct fg_chip *chip = container_of(work, struct fg_chip,
+ gain_comp_work);
+ bool input_present = is_input_present(chip);
+ bool otg_present = is_otg_present(chip);
+
+ if (!chip->init_done)
+ goto done;
+
+ if (!input_present && !otg_present) {
+ /* read VBAT_FILTERED */
+ rc = fg_mem_read(chip, reg, TEMP_COUNTER_REG, 3,
+ VBAT_FILTERED_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read VBAT: rc=%d\n", rc);
+ goto done;
+ }
+ temp = (reg[2] << 16) | (reg[1] << 8) | reg[0];
+ vbat_filtered = div_u64((u64)temp * LSB_24B_NUMRTR,
+ LSB_24B_DENMTR);
+
+ /* read K_VCOR */
+ rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, 0, 0);
+ if (rc) {
+ pr_err("Failed to KVCOR rc=%d\n", rc);
+ goto done;
+ }
+ kvcor = half_float(reg);
+
+ /* calculate gain */
+ numerator = (MICRO_UNIT + chip->iadc_comp_data.dfl_gain)
+ * (PICO_UNIT + kvcor * (vbat_filtered - VBAT_REF))
+ - ATTO_UNIT;
+ gain = div64_s64(numerator, PICO_UNIT);
+
+ /* write back gain */
+ half_float_to_buffer(gain, reg);
+ rc = fg_mem_write(chip, reg, GAIN_REG, 2, GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to write gain reg rc=%d\n", rc);
+ goto done;
+ }
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("IADC gain update [%x %x]\n", reg[1], reg[0]);
+ chip->iadc_comp_data.gain_active = true;
+ } else {
+ /* reset gain register */
+ rc = fg_mem_write(chip, chip->iadc_comp_data.dfl_gain_reg,
+ GAIN_REG, 2, GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write gain comp: %d\n", rc);
+ goto done;
+ }
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("IADC gain reset [%x %x]\n",
+ chip->iadc_comp_data.dfl_gain_reg[1],
+ chip->iadc_comp_data.dfl_gain_reg[0]);
+ chip->iadc_comp_data.gain_active = false;
+ }
+
+done:
+ fg_relax(&chip->gain_comp_wakeup_source);
+}
+
+#define BATT_MISSING_STS BIT(6)
+static bool is_battery_missing(struct fg_chip *chip)
+{
+ int rc;
+ u8 fg_batt_sts;
+
+ rc = fg_read(chip, &fg_batt_sts,
+ INT_RT_STS(chip->batt_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->batt_base), rc);
+ return false;
+ }
+
+ return (fg_batt_sts & BATT_MISSING_STS) ? true : false;
+}
+
+#define SOC_FIRST_EST_DONE BIT(5)
+static bool is_first_est_done(struct fg_chip *chip)
+{
+ int rc;
+ u8 fg_soc_sts;
+
+ rc = fg_read(chip, &fg_soc_sts,
+ INT_RT_STS(chip->soc_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ return false;
+ }
+
+ return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false;
+}
+
+static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+ int rc;
+ bool vbatt_low_sts;
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("vbatt-low triggered\n");
+
+ if (chip->status == POWER_SUPPLY_STATUS_CHARGING) {
+ rc = fg_get_vbatt_status(chip, &vbatt_low_sts);
+ if (rc) {
+ pr_err("error in reading vbatt_status, rc:%d\n", rc);
+ goto out;
+ }
+ if (!vbatt_low_sts && chip->vbat_low_irq_enabled) {
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("disabling vbatt_low irq\n");
+ disable_irq_wake(chip->batt_irq[VBATT_LOW].irq);
+ disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
+ chip->vbat_low_irq_enabled = false;
+ }
+ }
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+out:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+ bool batt_missing = is_battery_missing(chip);
+
+ if (batt_missing) {
+ chip->battery_missing = true;
+ chip->profile_loaded = false;
+ chip->batt_type = default_batt_type;
+ mutex_lock(&chip->cyc_ctr.lock);
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("battery missing, clearing cycle counters\n");
+ clear_cycle_counter(chip);
+ mutex_unlock(&chip->cyc_ctr.lock);
+ } else {
+ if (!chip->use_otp_profile) {
+ reinit_completion(&chip->batt_id_avail);
+ reinit_completion(&chip->first_soc_done);
+ schedule_delayed_work(&chip->batt_profile_init, 0);
+ cancel_delayed_work(&chip->update_sram_data);
+ schedule_delayed_work(
+ &chip->update_sram_data,
+ msecs_to_jiffies(0));
+ } else {
+ chip->battery_missing = false;
+ }
+ }
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("batt-missing triggered: %s\n",
+ batt_missing ? "missing" : "present");
+
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+ u8 mem_if_sts;
+ int rc;
+
+ rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1);
+ if (rc) {
+ pr_err("failed to read mem status rc=%d\n", rc);
+ return IRQ_HANDLED;
+ }
+
+ if (fg_check_sram_access(chip)) {
+ if ((fg_debug_mask & FG_IRQS)
+ & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES))
+ pr_info("sram access granted\n");
+ reinit_completion(&chip->sram_access_revoked);
+ complete_all(&chip->sram_access_granted);
+ } else {
+ if ((fg_debug_mask & FG_IRQS)
+ & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES))
+ pr_info("sram access revoked\n");
+ complete_all(&chip->sram_access_revoked);
+ }
+
+ if (!rc && (fg_debug_mask & FG_IRQS)
+ & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES))
+ pr_info("mem_if sts 0x%02x\n", mem_if_sts);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_soc_irq_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+ u8 soc_rt_sts;
+ int rc;
+
+ rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ }
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("triggered 0x%x\n", soc_rt_sts);
+
+ schedule_work(&chip->battery_age_work);
+
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+
+ if (chip->rslow_comp.chg_rs_to_rslow > 0 &&
+ chip->rslow_comp.chg_rslow_comp_c1 > 0 &&
+ chip->rslow_comp.chg_rslow_comp_c2 > 0)
+ schedule_work(&chip->rslow_comp_work);
+ if (chip->cyc_ctr.en)
+ schedule_work(&chip->cycle_count_work);
+ schedule_work(&chip->update_esr_work);
+ if (chip->charge_full)
+ schedule_work(&chip->charge_full_work);
+ if (chip->wa_flag & IADC_GAIN_COMP_WA
+ && chip->iadc_comp_data.gain_active) {
+ fg_stay_awake(&chip->gain_comp_wakeup_source);
+ schedule_work(&chip->gain_comp_work);
+ }
+
+ if (chip->wa_flag & USE_CC_SOC_REG
+ && chip->learning_data.active) {
+ fg_stay_awake(&chip->capacity_learning_wakeup_source);
+ schedule_work(&chip->fg_cap_learning_work);
+ }
+
+ if (chip->esr_pulse_tune_en) {
+ fg_stay_awake(&chip->esr_extract_wakeup_source);
+ schedule_work(&chip->esr_extract_config_work);
+ }
+
+ return IRQ_HANDLED;
+}
+
+#define FG_EMPTY_DEBOUNCE_MS 1500
+static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+ u8 soc_rt_sts;
+ int rc;
+
+ rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ goto done;
+ }
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("triggered 0x%x\n", soc_rt_sts);
+ if (fg_is_batt_empty(chip)) {
+ fg_stay_awake(&chip->empty_check_wakeup_source);
+ schedule_delayed_work(&chip->check_empty_work,
+ msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS));
+ } else {
+ chip->soc_empty = false;
+ }
+
+done:
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fg_first_soc_irq_handler(int irq, void *_chip)
+{
+ struct fg_chip *chip = _chip;
+
+ if (fg_debug_mask & FG_IRQS)
+ pr_info("triggered\n");
+
+ if (fg_est_dump)
+ schedule_work(&chip->dump_sram);
+
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+
+ complete_all(&chip->first_soc_done);
+
+ return IRQ_HANDLED;
+}
+
+static void fg_external_power_changed(struct power_supply *psy)
+{
+ struct fg_chip *chip = power_supply_get_drvdata(psy);
+
+ if (is_input_present(chip) && chip->rslow_comp.active &&
+ chip->rslow_comp.chg_rs_to_rslow > 0 &&
+ chip->rslow_comp.chg_rslow_comp_c1 > 0 &&
+ chip->rslow_comp.chg_rslow_comp_c2 > 0)
+ schedule_work(&chip->rslow_comp_work);
+ if (!is_input_present(chip) && chip->resume_soc_lowered) {
+ fg_stay_awake(&chip->resume_soc_wakeup_source);
+ schedule_work(&chip->set_resume_soc_work);
+ }
+ if (!is_input_present(chip) && chip->charge_full)
+ schedule_work(&chip->charge_full_work);
+}
+
+static void set_resume_soc_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ set_resume_soc_work);
+ int rc, resume_soc_raw;
+
+ if (is_input_present(chip) && !chip->resume_soc_lowered) {
+ if (!chip->charge_done)
+ goto done;
+ resume_soc_raw = get_monotonic_soc_raw(chip)
+ - (0xFF - settings[FG_MEM_RESUME_SOC].value);
+ if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) {
+ rc = fg_set_resume_soc(chip, resume_soc_raw);
+ if (rc) {
+ pr_err("Couldn't set resume SOC for FG\n");
+ goto done;
+ }
+ if (fg_debug_mask & FG_STATUS) {
+ pr_info("resume soc lowered to 0x%02x\n",
+ resume_soc_raw);
+ }
+ } else if (settings[FG_MEM_RESUME_SOC].value > 0) {
+ pr_err("bad resume soc 0x%02x\n", resume_soc_raw);
+ }
+ chip->charge_done = false;
+ chip->resume_soc_lowered = true;
+ } else if (chip->resume_soc_lowered && (!is_input_present(chip)
+ || chip->health == POWER_SUPPLY_HEALTH_GOOD)) {
+ resume_soc_raw = settings[FG_MEM_RESUME_SOC].value;
+ if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) {
+ rc = fg_set_resume_soc(chip, resume_soc_raw);
+ if (rc) {
+ pr_err("Couldn't set resume SOC for FG\n");
+ goto done;
+ }
+ if (fg_debug_mask & FG_STATUS) {
+ pr_info("resume soc set to 0x%02x\n",
+ resume_soc_raw);
+ }
+ } else if (settings[FG_MEM_RESUME_SOC].value > 0) {
+ pr_err("bad resume soc 0x%02x\n", resume_soc_raw);
+ }
+ chip->resume_soc_lowered = false;
+ }
+done:
+ fg_relax(&chip->resume_soc_wakeup_source);
+}
+
+
+#define OCV_COEFFS_START_REG 0x4C0
+#define OCV_JUNCTION_REG 0x4D8
+#define NOM_CAP_REG 0x4F4
+#define CUTOFF_VOLTAGE_REG 0x40C
+#define RSLOW_CFG_REG 0x538
+#define RSLOW_CFG_OFFSET 2
+#define RSLOW_THRESH_REG 0x52C
+#define RSLOW_THRESH_OFFSET 0
+#define TEMP_RS_TO_RSLOW_OFFSET 2
+#define RSLOW_COMP_REG 0x528
+#define RSLOW_COMP_C1_OFFSET 0
+#define RSLOW_COMP_C2_OFFSET 2
+static int populate_system_data(struct fg_chip *chip)
+{
+ u8 buffer[24];
+ int rc, i;
+ int16_t cc_mah;
+
+ fg_mem_lock(chip);
+ rc = fg_mem_read(chip, buffer, OCV_COEFFS_START_REG, 24, 0, 0);
+ if (rc) {
+ pr_err("Failed to read ocv coefficients: %d\n", rc);
+ goto done;
+ }
+ for (i = 0; i < 12; i += 1)
+ chip->ocv_coeffs[i] = half_float(buffer + (i * 2));
+ if (fg_debug_mask & FG_AGING) {
+ pr_info("coeffs1 = %lld %lld %lld %lld\n",
+ chip->ocv_coeffs[0], chip->ocv_coeffs[1],
+ chip->ocv_coeffs[2], chip->ocv_coeffs[3]);
+ pr_info("coeffs2 = %lld %lld %lld %lld\n",
+ chip->ocv_coeffs[4], chip->ocv_coeffs[5],
+ chip->ocv_coeffs[6], chip->ocv_coeffs[7]);
+ pr_info("coeffs3 = %lld %lld %lld %lld\n",
+ chip->ocv_coeffs[8], chip->ocv_coeffs[9],
+ chip->ocv_coeffs[10], chip->ocv_coeffs[11]);
+ }
+ rc = fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 0, 0);
+ chip->ocv_junction_p1p2 = buffer[0] * 100 / 255;
+ rc |= fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 1, 0);
+ chip->ocv_junction_p2p3 = buffer[0] * 100 / 255;
+ if (rc) {
+ pr_err("Failed to read ocv junctions: %d\n", rc);
+ goto done;
+ }
+ rc = fg_mem_read(chip, buffer, NOM_CAP_REG, 2, 0, 0);
+ if (rc) {
+ pr_err("Failed to read nominal capacitance: %d\n", rc);
+ goto done;
+ }
+ chip->nom_cap_uah = bcap_uah_2b(buffer);
+ chip->actual_cap_uah = chip->nom_cap_uah;
+ if (chip->learning_data.learned_cc_uah == 0) {
+ chip->learning_data.learned_cc_uah = chip->nom_cap_uah;
+ fg_cap_learning_save_data(chip);
+ } else if (chip->learning_data.feedback_on) {
+ cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000);
+ rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah);
+ if (rc)
+ pr_err("Error in restoring cc_soc_coeff, rc:%d\n", rc);
+ }
+ rc = fg_mem_read(chip, buffer, CUTOFF_VOLTAGE_REG, 2, 0, 0);
+ if (rc) {
+ pr_err("Failed to read cutoff voltage: %d\n", rc);
+ goto done;
+ }
+ chip->cutoff_voltage = voltage_2b(buffer);
+ if (fg_debug_mask & FG_AGING)
+ pr_info("cutoff_voltage = %lld, nom_cap_uah = %d p1p2 = %d, p2p3 = %d\n",
+ chip->cutoff_voltage, chip->nom_cap_uah,
+ chip->ocv_junction_p1p2,
+ chip->ocv_junction_p2p3);
+
+ rc = fg_mem_read(chip, buffer, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to read rslow cfg: %d\n", rc);
+ goto done;
+ }
+ chip->rslow_comp.rslow_cfg = buffer[0];
+ rc = fg_mem_read(chip, buffer, RSLOW_THRESH_REG, 1,
+ RSLOW_THRESH_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to read rslow thresh: %d\n", rc);
+ goto done;
+ }
+ chip->rslow_comp.rslow_thr = buffer[0];
+ rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2,
+ RSLOW_THRESH_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to read rs to rslow: %d\n", rc);
+ goto done;
+ }
+ memcpy(chip->rslow_comp.rs_to_rslow, buffer, 2);
+ rc = fg_mem_read(chip, buffer, RSLOW_COMP_REG, 4,
+ RSLOW_COMP_C1_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to read rslow comp: %d\n", rc);
+ goto done;
+ }
+ memcpy(chip->rslow_comp.rslow_comp, buffer, 4);
+
+done:
+ fg_mem_release(chip);
+ return rc;
+}
+
+#define RSLOW_CFG_MASK (BIT(2) | BIT(3) | BIT(4) | BIT(5))
+#define RSLOW_CFG_ON_VAL (BIT(2) | BIT(3))
+#define RSLOW_THRESH_FULL_VAL 0xFF
+static int fg_rslow_charge_comp_set(struct fg_chip *chip)
+{
+ int rc;
+ u8 buffer[2];
+
+ mutex_lock(&chip->rslow_comp.lock);
+ fg_mem_lock(chip);
+
+ rc = fg_mem_masked_write(chip, RSLOW_CFG_REG,
+ RSLOW_CFG_MASK, RSLOW_CFG_ON_VAL, RSLOW_CFG_OFFSET);
+ if (rc) {
+ pr_err("unable to write rslow cfg: %d\n", rc);
+ goto done;
+ }
+ rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG,
+ 0xFF, RSLOW_THRESH_FULL_VAL, RSLOW_THRESH_OFFSET);
+ if (rc) {
+ pr_err("unable to write rslow thresh: %d\n", rc);
+ goto done;
+ }
+
+ half_float_to_buffer(chip->rslow_comp.chg_rs_to_rslow, buffer);
+ rc = fg_mem_write(chip, buffer,
+ TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write rs to rslow: %d\n", rc);
+ goto done;
+ }
+ half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c1, buffer);
+ rc = fg_mem_write(chip, buffer,
+ RSLOW_COMP_REG, 2, RSLOW_COMP_C1_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write rslow comp: %d\n", rc);
+ goto done;
+ }
+ half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c2, buffer);
+ rc = fg_mem_write(chip, buffer,
+ RSLOW_COMP_REG, 2, RSLOW_COMP_C2_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write rslow comp: %d\n", rc);
+ goto done;
+ }
+ chip->rslow_comp.active = true;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("Activated rslow charge comp values\n");
+
+done:
+ fg_mem_release(chip);
+ mutex_unlock(&chip->rslow_comp.lock);
+ return rc;
+}
+
+#define RSLOW_CFG_ORIG_MASK (BIT(4) | BIT(5))
+static int fg_rslow_charge_comp_clear(struct fg_chip *chip)
+{
+ u8 reg;
+ int rc;
+
+ mutex_lock(&chip->rslow_comp.lock);
+ fg_mem_lock(chip);
+
+ reg = chip->rslow_comp.rslow_cfg & RSLOW_CFG_ORIG_MASK;
+ rc = fg_mem_masked_write(chip, RSLOW_CFG_REG,
+ RSLOW_CFG_MASK, reg, RSLOW_CFG_OFFSET);
+ if (rc) {
+ pr_err("unable to write rslow cfg: %d\n", rc);
+ goto done;
+ }
+ rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG,
+ 0xFF, chip->rslow_comp.rslow_thr, RSLOW_THRESH_OFFSET);
+ if (rc) {
+ pr_err("unable to write rslow thresh: %d\n", rc);
+ goto done;
+ }
+
+ rc = fg_mem_write(chip, chip->rslow_comp.rs_to_rslow,
+ TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write rs to rslow: %d\n", rc);
+ goto done;
+ }
+ rc = fg_mem_write(chip, chip->rslow_comp.rslow_comp,
+ RSLOW_COMP_REG, 4, RSLOW_COMP_C1_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write rslow comp: %d\n", rc);
+ goto done;
+ }
+ chip->rslow_comp.active = false;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("Cleared rslow charge comp values\n");
+
+done:
+ fg_mem_release(chip);
+ mutex_unlock(&chip->rslow_comp.lock);
+ return rc;
+}
+
+static void rslow_comp_work(struct work_struct *work)
+{
+ int battery_soc_1b;
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ rslow_comp_work);
+
+ battery_soc_1b = get_battery_soc_raw(chip) >> 16;
+ if (battery_soc_1b > chip->rslow_comp.chg_rslow_comp_thr
+ && chip->status == POWER_SUPPLY_STATUS_CHARGING) {
+ if (!chip->rslow_comp.active)
+ fg_rslow_charge_comp_set(chip);
+ } else {
+ if (chip->rslow_comp.active)
+ fg_rslow_charge_comp_clear(chip);
+ }
+}
+
+#define MICROUNITS_TO_ADC_RAW(units) \
+ div64_s64(units * LSB_16B_DENMTR, LSB_16B_NUMRTR)
+static int update_chg_iterm(struct fg_chip *chip)
+{
+ u8 data[2];
+ u16 converted_current_raw;
+ s64 current_ma = -settings[FG_MEM_CHG_TERM_CURRENT].value;
+
+ converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000);
+ data[0] = cpu_to_le16(converted_current_raw) & 0xFF;
+ data[1] = cpu_to_le16(converted_current_raw) >> 8;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n",
+ current_ma, converted_current_raw, data[0], data[1]);
+ return fg_mem_write(chip, data,
+ settings[FG_MEM_CHG_TERM_CURRENT].address,
+ 2, settings[FG_MEM_CHG_TERM_CURRENT].offset, 0);
+}
+
+#define CC_CV_SETPOINT_REG 0x4F8
+#define CC_CV_SETPOINT_OFFSET 0
+static void update_cc_cv_setpoint(struct fg_chip *chip)
+{
+ int rc;
+ u8 tmp[2];
+
+ if (!chip->cc_cv_threshold_mv)
+ return;
+ batt_to_setpoint_adc(chip->cc_cv_threshold_mv, tmp);
+ rc = fg_mem_write(chip, tmp, CC_CV_SETPOINT_REG, 2,
+ CC_CV_SETPOINT_OFFSET, 0);
+ if (rc) {
+ pr_err("failed to write CC_CV_VOLT rc=%d\n", rc);
+ return;
+ }
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("Wrote %x %x to address %x for CC_CV setpoint\n",
+ tmp[0], tmp[1], CC_CV_SETPOINT_REG);
+}
+
+#define CBITS_INPUT_FILTER_REG 0x4B4
+#define CBITS_RMEAS1_OFFSET 1
+#define CBITS_RMEAS2_OFFSET 2
+#define CBITS_RMEAS1_DEFAULT_VAL 0x65
+#define CBITS_RMEAS2_DEFAULT_VAL 0x65
+#define IMPTR_FAST_TIME_SHIFT 1
+#define IMPTR_LONG_TIME_SHIFT (1 << 4)
+#define IMPTR_PULSE_CTR_CHG 1
+#define IMPTR_PULSE_CTR_DISCHG (1 << 4)
+static int fg_config_imptr_pulse(struct fg_chip *chip, bool slow)
+{
+ int rc;
+ u8 cntr[2] = {0, 0};
+ u8 val;
+
+ if (slow == chip->imptr_pulse_slow_en) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("imptr_pulse_slow is %sabled already\n",
+ slow ? "en" : "dis");
+ return 0;
+ }
+
+ fg_mem_lock(chip);
+
+ val = slow ? (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT) :
+ CBITS_RMEAS1_DEFAULT_VAL;
+ rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1,
+ CBITS_RMEAS1_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write cbits_rmeas1_offset rc=%d\n", rc);
+ goto done;
+ }
+
+ val = slow ? (IMPTR_PULSE_CTR_CHG | IMPTR_PULSE_CTR_DISCHG) :
+ CBITS_RMEAS2_DEFAULT_VAL;
+ rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1,
+ CBITS_RMEAS2_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write cbits_rmeas2_offset rc=%d\n", rc);
+ goto done;
+ }
+
+ if (slow) {
+ rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 4,
+ COUNTER_IMPTR_OFFSET, 0);
+ if (rc) {
+ pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc);
+ goto done;
+ }
+
+ rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2,
+ COUNTER_PULSE_OFFSET, 0);
+ if (rc) {
+ pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc);
+ goto done;
+ }
+ }
+
+ chip->imptr_pulse_slow_en = slow;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("imptr_pulse_slow is %sabled\n", slow ? "en" : "dis");
+done:
+ fg_mem_release(chip);
+ return rc;
+}
+
+#define CURRENT_DELTA_MIN_REG 0x42C
+#define CURRENT_DELTA_MIN_OFFSET 1
+#define SYS_CFG_1_REG 0x4AC
+#define SYS_CFG_1_OFFSET 0
+#define CURRENT_DELTA_MIN_DEFAULT 0x16
+#define CURRENT_DELTA_MIN_500MA 0xCD
+#define RSLOW_CFG_USE_FIX_RSER_VAL BIT(7)
+#define ENABLE_ESR_PULSE_VAL BIT(3)
+static int fg_config_esr_extract(struct fg_chip *chip, bool disable)
+{
+ int rc;
+ u8 val;
+
+ if (disable == chip->esr_extract_disabled) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("ESR extract already %sabled\n",
+ disable ? "dis" : "en");
+ return 0;
+ }
+
+ fg_mem_lock(chip);
+
+ val = disable ? CURRENT_DELTA_MIN_500MA :
+ CURRENT_DELTA_MIN_DEFAULT;
+ rc = fg_mem_write(chip, &val, CURRENT_DELTA_MIN_REG, 1,
+ CURRENT_DELTA_MIN_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to write curr_delta_min rc=%d\n", rc);
+ goto done;
+ }
+
+ val = disable ? RSLOW_CFG_USE_FIX_RSER_VAL : 0;
+ rc = fg_mem_masked_write(chip, RSLOW_CFG_REG,
+ RSLOW_CFG_USE_FIX_RSER_VAL, val, RSLOW_CFG_OFFSET);
+ if (rc) {
+ pr_err("unable to write rslow cfg rc= %d\n", rc);
+ goto done;
+ }
+
+ val = disable ? 0 : ENABLE_ESR_PULSE_VAL;
+ rc = fg_mem_masked_write(chip, SYS_CFG_1_REG,
+ ENABLE_ESR_PULSE_VAL, val, SYS_CFG_1_OFFSET);
+ if (rc) {
+ pr_err("unable to write sys_cfg_1 rc= %d\n", rc);
+ goto done;
+ }
+
+ chip->esr_extract_disabled = disable;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("ESR extract is %sabled\n", disable ? "dis" : "en");
+done:
+ fg_mem_release(chip);
+ return rc;
+}
+
+#define ESR_EXTRACT_STOP_SOC 2
+#define IMPTR_PULSE_CONFIG_SOC 5
+static void esr_extract_config_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work, struct fg_chip,
+ esr_extract_config_work);
+ bool input_present = is_input_present(chip);
+ int capacity = get_prop_capacity(chip);
+
+ if (input_present && capacity <= ESR_EXTRACT_STOP_SOC) {
+ fg_config_esr_extract(chip, true);
+ } else if (capacity > ESR_EXTRACT_STOP_SOC) {
+ fg_config_esr_extract(chip, false);
+
+ if (capacity <= IMPTR_PULSE_CONFIG_SOC)
+ fg_config_imptr_pulse(chip, true);
+ else
+ fg_config_imptr_pulse(chip, false);
+ }
+
+ fg_relax(&chip->esr_extract_wakeup_source);
+}
+
+#define LOW_LATENCY BIT(6)
+#define BATT_PROFILE_OFFSET 0x4C0
+#define PROFILE_INTEGRITY_REG 0x53C
+#define PROFILE_INTEGRITY_BIT BIT(0)
+#define FIRST_EST_DONE_BIT BIT(5)
+#define MAX_TRIES_FIRST_EST 3
+#define FIRST_EST_WAIT_MS 2000
+#define PROFILE_LOAD_TIMEOUT_MS 5000
+static int fg_do_restart(struct fg_chip *chip, bool write_profile)
+{
+ int rc, ibat_ua;
+ u8 reg = 0;
+ u8 buf[2];
+ bool tried_once = false;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("restarting fuel gauge...\n");
+
+try_again:
+ if (write_profile) {
+ if (!chip->charging_disabled) {
+ pr_err("Charging not yet disabled!\n");
+ return -EINVAL;
+ }
+
+ ibat_ua = get_sram_prop_now(chip, FG_DATA_CURRENT);
+ if (ibat_ua == -EINVAL) {
+ pr_err("SRAM not updated yet!\n");
+ return ibat_ua;
+ }
+
+ if (ibat_ua < 0) {
+ pr_warn("Charging enabled?, ibat_ua: %d\n", ibat_ua);
+
+ if (!tried_once) {
+ cancel_delayed_work(&chip->update_sram_data);
+ schedule_delayed_work(&chip->update_sram_data,
+ msecs_to_jiffies(0));
+ msleep(1000);
+ tried_once = true;
+ goto try_again;
+ }
+ }
+ }
+
+ chip->fg_restarting = true;
+ /*
+ * save the temperature if the sw rbias control is active so that there
+ * is no gap of time when there is no valid temperature read after the
+ * restart
+ */
+ if (chip->sw_rbias_ctrl) {
+ rc = fg_mem_read(chip, buf,
+ fg_data[FG_DATA_BATT_TEMP].address,
+ fg_data[FG_DATA_BATT_TEMP].len,
+ fg_data[FG_DATA_BATT_TEMP].offset, 0);
+ if (rc) {
+ pr_err("failed to read batt temp rc=%d\n", rc);
+ goto sub_and_fail;
+ }
+ }
+ /*
+ * release the sram access and configure the correct settings
+ * before re-requesting access.
+ */
+ mutex_lock(&chip->rw_lock);
+ fg_release_access(chip);
+
+ rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD,
+ NO_OTP_PROF_RELOAD, 0, 1);
+ if (rc) {
+ pr_err("failed to set no otp reload bit\n");
+ goto unlock_and_fail;
+ }
+
+ /* unset the restart bits so the fg doesn't continuously restart */
+ reg = REDO_FIRST_ESTIMATE | RESTART_GO;
+ rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART,
+ reg, 0, 1);
+ if (rc) {
+ pr_err("failed to unset fg restart: %d\n", rc);
+ goto unlock_and_fail;
+ }
+
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip),
+ LOW_LATENCY, LOW_LATENCY, 1);
+ if (rc) {
+ pr_err("failed to set low latency access bit\n");
+ goto unlock_and_fail;
+ }
+ mutex_unlock(&chip->rw_lock);
+
+ /* read once to get a fg cycle in */
+ rc = fg_mem_read(chip, &reg, PROFILE_INTEGRITY_REG, 1, 0, 0);
+ if (rc) {
+ pr_err("failed to read profile integrity rc=%d\n", rc);
+ goto fail;
+ }
+
+ /*
+ * If this is not the first time a profile has been loaded, sleep for
+ * 3 seconds to make sure the NO_OTP_RELOAD is cleared in memory
+ */
+ if (chip->first_profile_loaded)
+ msleep(3000);
+
+ mutex_lock(&chip->rw_lock);
+ fg_release_access(chip);
+ rc = fg_masked_write(chip, MEM_INTF_CFG(chip), LOW_LATENCY, 0, 1);
+ if (rc) {
+ pr_err("failed to set low latency access bit\n");
+ goto unlock_and_fail;
+ }
+
+ atomic_add_return(1, &chip->memif_user_cnt);
+ mutex_unlock(&chip->rw_lock);
+
+ if (write_profile) {
+ /* write the battery profile */
+ rc = fg_mem_write(chip, chip->batt_profile, BATT_PROFILE_OFFSET,
+ chip->batt_profile_len, 0, 1);
+ if (rc) {
+ pr_err("failed to write profile rc=%d\n", rc);
+ goto sub_and_fail;
+ }
+ /* write the integrity bits and release access */
+ rc = fg_mem_masked_write(chip, PROFILE_INTEGRITY_REG,
+ PROFILE_INTEGRITY_BIT,
+ PROFILE_INTEGRITY_BIT, 0);
+ if (rc) {
+ pr_err("failed to write profile rc=%d\n", rc);
+ goto sub_and_fail;
+ }
+ }
+
+ /* decrement the user count so that memory access can be released */
+ fg_release_access_if_necessary(chip);
+
+ /*
+ * make sure that the first estimate has completed
+ * in case of a hotswap
+ */
+ rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done,
+ msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS));
+ if (rc <= 0) {
+ pr_err("transaction timed out rc=%d\n", rc);
+ rc = -ETIMEDOUT;
+ goto fail;
+ }
+
+ /*
+ * reinitialize the completion so that the driver knows when the restart
+ * finishes
+ */
+ reinit_completion(&chip->first_soc_done);
+
+ if (chip->esr_pulse_tune_en) {
+ fg_stay_awake(&chip->esr_extract_wakeup_source);
+ schedule_work(&chip->esr_extract_config_work);
+ }
+
+ /*
+ * set the restart bits so that the next fg cycle will not reload
+ * the profile
+ */
+ rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD,
+ NO_OTP_PROF_RELOAD, NO_OTP_PROF_RELOAD, 1);
+ if (rc) {
+ pr_err("failed to set no otp reload bit\n");
+ goto fail;
+ }
+
+ reg = REDO_FIRST_ESTIMATE | RESTART_GO;
+ rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART,
+ reg, reg, 1);
+ if (rc) {
+ pr_err("failed to set fg restart: %d\n", rc);
+ goto fail;
+ }
+
+ /* wait for the first estimate to complete */
+ rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done,
+ msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS));
+ if (rc <= 0) {
+ pr_err("transaction timed out rc=%d\n", rc);
+ rc = -ETIMEDOUT;
+ goto fail;
+ }
+ rc = fg_read(chip, &reg, INT_RT_STS(chip->soc_base), 1);
+ if (rc) {
+ pr_err("spmi read failed: addr=%03X, rc=%d\n",
+ INT_RT_STS(chip->soc_base), rc);
+ goto fail;
+ }
+ if ((reg & FIRST_EST_DONE_BIT) == 0)
+ pr_err("Battery profile reloading failed, no first estimate\n");
+
+ rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD,
+ NO_OTP_PROF_RELOAD, 0, 1);
+ if (rc) {
+ pr_err("failed to set no otp reload bit\n");
+ goto fail;
+ }
+ /* unset the restart bits so the fg doesn't continuously restart */
+ reg = REDO_FIRST_ESTIMATE | RESTART_GO;
+ rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART,
+ reg, 0, 1);
+ if (rc) {
+ pr_err("failed to unset fg restart: %d\n", rc);
+ goto fail;
+ }
+
+ /* restore the battery temperature reading here */
+ if (chip->sw_rbias_ctrl) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("reloaded 0x%02x%02x into batt temp",
+ buf[0], buf[1]);
+ rc = fg_mem_write(chip, buf,
+ fg_data[FG_DATA_BATT_TEMP].address,
+ fg_data[FG_DATA_BATT_TEMP].len,
+ fg_data[FG_DATA_BATT_TEMP].offset, 0);
+ if (rc) {
+ pr_err("failed to write batt temp rc=%d\n", rc);
+ goto fail;
+ }
+ }
+
+ /* Enable charging now as the first estimate is done now */
+ if (chip->charging_disabled) {
+ rc = set_prop_enable_charging(chip, true);
+ if (rc)
+ pr_err("Failed to enable charging, rc=%d\n", rc);
+ else
+ chip->charging_disabled = false;
+ }
+
+ chip->fg_restarting = false;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("done!\n");
+ return 0;
+
+unlock_and_fail:
+ mutex_unlock(&chip->rw_lock);
+ goto fail;
+sub_and_fail:
+ fg_release_access_if_necessary(chip);
+ goto fail;
+fail:
+ chip->fg_restarting = false;
+ return -EINVAL;
+}
+
+#define FG_PROFILE_LEN 128
+#define PROFILE_COMPARE_LEN 32
+#define THERMAL_COEFF_ADDR 0x444
+#define THERMAL_COEFF_OFFSET 0x2
+#define BATTERY_PSY_WAIT_MS 2000
+static int fg_batt_profile_init(struct fg_chip *chip)
+{
+ int rc = 0, ret, len, batt_id;
+ struct device_node *node = chip->pdev->dev.of_node;
+ struct device_node *batt_node, *profile_node;
+ const char *data, *batt_type_str;
+ bool tried_again = false, vbat_in_range, profiles_same;
+ u8 reg = 0;
+
+wait:
+ fg_stay_awake(&chip->profile_wakeup_source);
+ ret = wait_for_completion_interruptible_timeout(&chip->batt_id_avail,
+ msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS));
+ /* If we were interrupted wait again one more time. */
+ if (ret == -ERESTARTSYS && !tried_again) {
+ tried_again = true;
+ pr_debug("interrupted, waiting again\n");
+ goto wait;
+ } else if (ret <= 0) {
+ rc = -ETIMEDOUT;
+ pr_err("profile loading timed out rc=%d\n", rc);
+ goto no_profile;
+ }
+
+ batt_node = of_find_node_by_name(node, "qcom,battery-data");
+ if (!batt_node) {
+ pr_warn("No available batterydata, using OTP defaults\n");
+ rc = 0;
+ goto no_profile;
+ }
+
+ batt_id = get_sram_prop_now(chip, FG_DATA_BATT_ID);
+ batt_id /= 1000;
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("battery id = %dKOhms\n", batt_id);
+
+ profile_node = of_batterydata_get_best_profile(batt_node, batt_id,
+ fg_batt_type);
+ if (IS_ERR_OR_NULL(profile_node)) {
+ rc = PTR_ERR(profile_node);
+ pr_err("couldn't find profile handle %d\n", rc);
+ goto no_profile;
+ }
+
+ /* read rslow compensation values if they're available */
+ rc = of_property_read_u32(profile_node, "qcom,chg-rs-to-rslow",
+ &chip->rslow_comp.chg_rs_to_rslow);
+ if (rc) {
+ chip->rslow_comp.chg_rs_to_rslow = -EINVAL;
+ if (rc != -EINVAL)
+ pr_err("Could not read rs to rslow: %d\n", rc);
+ }
+ rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-c1",
+ &chip->rslow_comp.chg_rslow_comp_c1);
+ if (rc) {
+ chip->rslow_comp.chg_rslow_comp_c1 = -EINVAL;
+ if (rc != -EINVAL)
+ pr_err("Could not read rslow comp c1: %d\n", rc);
+ }
+ rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-c2",
+ &chip->rslow_comp.chg_rslow_comp_c2);
+ if (rc) {
+ chip->rslow_comp.chg_rslow_comp_c2 = -EINVAL;
+ if (rc != -EINVAL)
+ pr_err("Could not read rslow comp c2: %d\n", rc);
+ }
+ rc = of_property_read_u32(profile_node, "qcom,chg-rslow-comp-thr",
+ &chip->rslow_comp.chg_rslow_comp_thr);
+ if (rc) {
+ chip->rslow_comp.chg_rslow_comp_thr = -EINVAL;
+ if (rc != -EINVAL)
+ pr_err("Could not read rslow comp thr: %d\n", rc);
+ }
+
+ rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
+ &chip->batt_max_voltage_uv);
+
+ if (rc)
+ pr_warn("couldn't find battery max voltage\n");
+
+ /*
+ * Only configure from profile if fg-cc-cv-threshold-mv is not
+ * defined in the charger device node.
+ */
+ if (!of_find_property(chip->pdev->dev.of_node,
+ "qcom,fg-cc-cv-threshold-mv", NULL)) {
+ of_property_read_u32(profile_node,
+ "qcom,fg-cc-cv-threshold-mv",
+ &chip->cc_cv_threshold_mv);
+ }
+
+ data = of_get_property(profile_node, "qcom,fg-profile-data", &len);
+ if (!data) {
+ pr_err("no battery profile loaded\n");
+ rc = 0;
+ goto no_profile;
+ }
+
+ if (len != FG_PROFILE_LEN) {
+ pr_err("battery profile incorrect size: %d\n", len);
+ rc = -EINVAL;
+ goto no_profile;
+ }
+
+ rc = of_property_read_string(profile_node, "qcom,battery-type",
+ &batt_type_str);
+ if (rc) {
+ pr_err("Could not find battery data type: %d\n", rc);
+ rc = 0;
+ goto no_profile;
+ }
+
+ if (!chip->batt_profile)
+ chip->batt_profile = devm_kzalloc(chip->dev,
+ sizeof(char) * len, GFP_KERNEL);
+
+ if (!chip->batt_profile) {
+ pr_err("out of memory\n");
+ rc = -ENOMEM;
+ goto no_profile;
+ }
+
+ rc = fg_mem_read(chip, &reg, PROFILE_INTEGRITY_REG, 1, 0, 1);
+ if (rc) {
+ pr_err("failed to read profile integrity rc=%d\n", rc);
+ goto no_profile;
+ }
+
+ rc = fg_mem_read(chip, chip->batt_profile, BATT_PROFILE_OFFSET,
+ len, 0, 1);
+ if (rc) {
+ pr_err("failed to read profile rc=%d\n", rc);
+ goto no_profile;
+ }
+
+ /* Check whether the charger is ready */
+ if (!is_charger_available(chip))
+ goto reschedule;
+
+ /* Disable charging for a FG cycle before calculating vbat_in_range */
+ if (!chip->charging_disabled) {
+ rc = set_prop_enable_charging(chip, false);
+ if (rc)
+ pr_err("Failed to disable charging, rc=%d\n", rc);
+
+ goto reschedule;
+ }
+
+ vbat_in_range = get_vbat_est_diff(chip)
+ < settings[FG_MEM_VBAT_EST_DIFF].value * 1000;
+ profiles_same = memcmp(chip->batt_profile, data,
+ PROFILE_COMPARE_LEN) == 0;
+ if (reg & PROFILE_INTEGRITY_BIT) {
+ fg_cap_learning_load_data(chip);
+ if (vbat_in_range && !fg_is_batt_empty(chip) && profiles_same) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("Battery profiles same, using default\n");
+ if (fg_est_dump)
+ schedule_work(&chip->dump_sram);
+ goto done;
+ }
+ } else {
+ pr_info("Battery profile not same, clearing data\n");
+ clear_cycle_counter(chip);
+ chip->learning_data.learned_cc_uah = 0;
+ }
+
+ if (fg_est_dump)
+ dump_sram(&chip->dump_sram);
+
+ if ((fg_debug_mask & FG_STATUS) && !vbat_in_range)
+ pr_info("Vbat out of range: v_current_pred: %d, v:%d\n",
+ fg_data[FG_DATA_CPRED_VOLTAGE].value,
+ fg_data[FG_DATA_VOLTAGE].value);
+
+ if ((fg_debug_mask & FG_STATUS) && fg_is_batt_empty(chip))
+ pr_info("battery empty\n");
+
+ if ((fg_debug_mask & FG_STATUS) && !profiles_same)
+ pr_info("profiles differ\n");
+
+ if (fg_debug_mask & FG_STATUS) {
+ pr_info("Using new profile\n");
+ print_hex_dump(KERN_INFO, "FG: loaded profile: ",
+ DUMP_PREFIX_NONE, 16, 1,
+ chip->batt_profile, len, false);
+ }
+
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+
+ memcpy(chip->batt_profile, data, len);
+
+ chip->batt_profile_len = len;
+
+ if (fg_debug_mask & FG_STATUS)
+ print_hex_dump(KERN_INFO, "FG: new profile: ",
+ DUMP_PREFIX_NONE, 16, 1, chip->batt_profile,
+ chip->batt_profile_len, false);
+
+ rc = fg_do_restart(chip, true);
+ if (rc) {
+ pr_err("restart failed: %d\n", rc);
+ goto no_profile;
+ }
+
+ /*
+ * Only configure from profile if thermal-coefficients is not
+ * defined in the FG device node.
+ */
+ if (!of_find_property(chip->pdev->dev.of_node,
+ "qcom,thermal-coefficients", NULL)) {
+ data = of_get_property(profile_node,
+ "qcom,thermal-coefficients", &len);
+ if (data && len == THERMAL_COEFF_N_BYTES) {
+ memcpy(chip->thermal_coefficients, data, len);
+ rc = fg_mem_write(chip, chip->thermal_coefficients,
+ THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES,
+ THERMAL_COEFF_OFFSET, 0);
+ if (rc)
+ pr_err("spmi write failed addr:%03x, ret:%d\n",
+ THERMAL_COEFF_ADDR, rc);
+ else if (fg_debug_mask & FG_STATUS)
+ pr_info("Battery thermal coefficients changed\n");
+ }
+ }
+
+done:
+ if (chip->charging_disabled) {
+ rc = set_prop_enable_charging(chip, true);
+ if (rc)
+ pr_err("Failed to enable charging, rc=%d\n", rc);
+ else
+ chip->charging_disabled = false;
+ }
+
+ if (fg_batt_type)
+ chip->batt_type = fg_batt_type;
+ else
+ chip->batt_type = batt_type_str;
+ chip->first_profile_loaded = true;
+ chip->profile_loaded = true;
+ chip->battery_missing = is_battery_missing(chip);
+ update_chg_iterm(chip);
+ update_cc_cv_setpoint(chip);
+ rc = populate_system_data(chip);
+ if (rc) {
+ pr_err("failed to read ocv properties=%d\n", rc);
+ return rc;
+ }
+ estimate_battery_age(chip, &chip->actual_cap_uah);
+ schedule_work(&chip->status_change_work);
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+ fg_relax(&chip->profile_wakeup_source);
+ pr_info("Battery SOC: %d, V: %duV\n", get_prop_capacity(chip),
+ fg_data[FG_DATA_VOLTAGE].value);
+ return rc;
+no_profile:
+ if (chip->charging_disabled) {
+ rc = set_prop_enable_charging(chip, true);
+ if (rc)
+ pr_err("Failed to enable charging, rc=%d\n", rc);
+ else
+ chip->charging_disabled = false;
+ }
+
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+ fg_relax(&chip->profile_wakeup_source);
+ return rc;
+reschedule:
+ schedule_delayed_work(
+ &chip->batt_profile_init,
+ msecs_to_jiffies(BATTERY_PSY_WAIT_MS));
+ cancel_delayed_work(&chip->update_sram_data);
+ schedule_delayed_work(
+ &chip->update_sram_data,
+ msecs_to_jiffies(0));
+ fg_relax(&chip->profile_wakeup_source);
+ return 0;
+}
+
+static void check_empty_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ check_empty_work.work);
+
+ if (fg_is_batt_empty(chip)) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("EMPTY SOC high\n");
+ chip->soc_empty = true;
+ if (chip->power_supply_registered)
+ power_supply_changed(chip->bms_psy);
+ }
+ fg_relax(&chip->empty_check_wakeup_source);
+}
+
+static void batt_profile_init(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ batt_profile_init.work);
+
+ if (fg_batt_profile_init(chip))
+ pr_err("failed to initialize profile\n");
+}
+
+static void sysfs_restart_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ sysfs_restart_work);
+ int rc;
+
+ rc = fg_do_restart(chip, false);
+ if (rc)
+ pr_err("fg restart failed: %d\n", rc);
+ mutex_lock(&chip->sysfs_restart_lock);
+ fg_restart = 0;
+ mutex_unlock(&chip->sysfs_restart_lock);
+}
+
+#define SRAM_MONOTONIC_SOC_REG 0x574
+#define SRAM_MONOTONIC_SOC_OFFSET 2
+#define SRAM_RELEASE_TIMEOUT_MS 500
+static void charge_full_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ charge_full_work);
+ int rc;
+ u8 buffer[3];
+ int bsoc;
+ int resume_soc_raw = FULL_SOC_RAW - settings[FG_MEM_RESUME_SOC].value;
+ bool disable = false;
+ u8 reg;
+
+ if (chip->status != POWER_SUPPLY_STATUS_FULL) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("battery not full: %d\n", chip->status);
+ disable = true;
+ }
+
+ fg_mem_lock(chip);
+ rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0);
+ if (rc) {
+ pr_err("Unable to read battery soc: %d\n", rc);
+ goto out;
+ }
+ if (buffer[2] <= resume_soc_raw) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("bsoc = 0x%02x <= resume = 0x%02x\n",
+ buffer[2], resume_soc_raw);
+ disable = true;
+ }
+ if (!disable)
+ goto out;
+
+ rc = fg_mem_write(chip, buffer, SOC_FULL_REG, 3,
+ SOC_FULL_OFFSET, 0);
+ if (rc) {
+ pr_err("failed to write SOC_FULL rc=%d\n", rc);
+ goto out;
+ }
+ /* force a full soc value into the monotonic in order to display 100 */
+ buffer[0] = 0xFF;
+ buffer[1] = 0xFF;
+ rc = fg_mem_write(chip, buffer, SRAM_MONOTONIC_SOC_REG, 2,
+ SRAM_MONOTONIC_SOC_OFFSET, 0);
+ if (rc) {
+ pr_err("failed to write SOC_FULL rc=%d\n", rc);
+ goto out;
+ }
+ if (fg_debug_mask & FG_STATUS) {
+ bsoc = buffer[0] | buffer[1] << 8 | buffer[2] << 16;
+ pr_info("wrote %06x into soc full\n", bsoc);
+ }
+ fg_mem_release(chip);
+ /*
+ * wait one cycle to make sure the soc is updated before clearing
+ * the soc mask bit
+ */
+ fg_mem_lock(chip);
+ fg_mem_read(chip, &reg, PROFILE_INTEGRITY_REG, 1, 0, 0);
+out:
+ fg_mem_release(chip);
+ if (disable)
+ chip->charge_full = false;
+}
+
+static void update_bcl_thresholds(struct fg_chip *chip)
+{
+ u8 data[4];
+ u8 mh_offset = 0, lm_offset = 0;
+ u16 address = 0;
+ int ret = 0;
+
+ address = settings[FG_MEM_BCL_MH_THRESHOLD].address;
+ mh_offset = settings[FG_MEM_BCL_MH_THRESHOLD].offset;
+ lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset;
+ ret = fg_mem_read(chip, data, address, 4, 0, 1);
+ if (ret)
+ pr_err("Error reading BCL LM & MH threshold rc:%d\n", ret);
+ else
+ pr_debug("Old BCL LM threshold:%x MH threshold:%x\n",
+ data[lm_offset], data[mh_offset]);
+ BCL_MA_TO_ADC(settings[FG_MEM_BCL_MH_THRESHOLD].value, data[mh_offset]);
+ BCL_MA_TO_ADC(settings[FG_MEM_BCL_LM_THRESHOLD].value, data[lm_offset]);
+
+ ret = fg_mem_write(chip, data, address, 4, 0, 0);
+ if (ret)
+ pr_err("spmi write failed. addr:%03x, ret:%d\n",
+ address, ret);
+ else
+ pr_debug("New BCL LM threshold:%x MH threshold:%x\n",
+ data[lm_offset], data[mh_offset]);
+}
+
+static int disable_bcl_lpm(struct fg_chip *chip)
+{
+ u8 data[4];
+ u8 lm_offset = 0;
+ u16 address = 0;
+ int rc = 0;
+
+ address = settings[FG_MEM_BCL_LM_THRESHOLD].address;
+ lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset;
+ rc = fg_mem_read(chip, data, address, 4, 0, 1);
+ if (rc) {
+ pr_err("Error reading BCL LM & MH threshold rc:%d\n", rc);
+ return rc;
+ }
+ pr_debug("Old BCL LM threshold:%x\n", data[lm_offset]);
+
+ /* Put BCL always above LPM */
+ BCL_MA_TO_ADC(0, data[lm_offset]);
+
+ rc = fg_mem_write(chip, data, address, 4, 0, 0);
+ if (rc)
+ pr_err("spmi write failed. addr:%03x, rc:%d\n",
+ address, rc);
+ else
+ pr_debug("New BCL LM threshold:%x\n", data[lm_offset]);
+
+ return rc;
+}
+
+static void bcl_hi_power_work(struct work_struct *work)
+{
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ bcl_hi_power_work);
+ int rc;
+
+ if (chip->bcl_lpm_disabled) {
+ rc = disable_bcl_lpm(chip);
+ if (rc)
+ pr_err("failed to disable bcl low mode %d\n",
+ rc);
+ } else {
+ update_bcl_thresholds(chip);
+ }
+}
+
+#define VOLT_UV_TO_VOLTCMP8(volt_uv) \
+ ((volt_uv - 2500000) / 9766)
+static int update_irq_volt_empty(struct fg_chip *chip)
+{
+ u8 data;
+ int volt_mv = settings[FG_MEM_IRQ_VOLT_EMPTY].value;
+
+ data = (u8)VOLT_UV_TO_VOLTCMP8(volt_mv * 1000);
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("voltage = %d, converted_raw = %04x\n", volt_mv, data);
+ return fg_mem_write(chip, &data,
+ settings[FG_MEM_IRQ_VOLT_EMPTY].address, 1,
+ settings[FG_MEM_IRQ_VOLT_EMPTY].offset, 0);
+}
+
+static int update_cutoff_voltage(struct fg_chip *chip)
+{
+ u8 data[2];
+ u16 converted_voltage_raw;
+ s64 voltage_mv = settings[FG_MEM_CUTOFF_VOLTAGE].value;
+
+ converted_voltage_raw = (s16)MICROUNITS_TO_ADC_RAW(voltage_mv * 1000);
+ data[0] = cpu_to_le16(converted_voltage_raw) & 0xFF;
+ data[1] = cpu_to_le16(converted_voltage_raw) >> 8;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("voltage = %lld, converted_raw = %04x, data = %02x %02x\n",
+ voltage_mv, converted_voltage_raw, data[0], data[1]);
+ return fg_mem_write(chip, data, settings[FG_MEM_CUTOFF_VOLTAGE].address,
+ 2, settings[FG_MEM_CUTOFF_VOLTAGE].offset, 0);
+}
+
+static int update_iterm(struct fg_chip *chip)
+{
+ u8 data[2];
+ u16 converted_current_raw;
+ s64 current_ma = -settings[FG_MEM_TERM_CURRENT].value;
+
+ converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000);
+ data[0] = cpu_to_le16(converted_current_raw) & 0xFF;
+ data[1] = cpu_to_le16(converted_current_raw) >> 8;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n",
+ current_ma, converted_current_raw, data[0], data[1]);
+ return fg_mem_write(chip, data, settings[FG_MEM_TERM_CURRENT].address,
+ 2, settings[FG_MEM_TERM_CURRENT].offset, 0);
+}
+
+#define OF_READ_SETTING(type, qpnp_dt_property, retval, optional) \
+do { \
+ if (retval) \
+ break; \
+ \
+ retval = of_property_read_u32(chip->pdev->dev.of_node, \
+ "qcom," qpnp_dt_property, \
+ &settings[type].value); \
+ \
+ if ((retval == -EINVAL) && optional) \
+ retval = 0; \
+ else if (retval) \
+ pr_err("Error reading " #qpnp_dt_property \
+ " property rc = %d\n", rc); \
+} while (0)
+
+#define OF_READ_PROPERTY(store, qpnp_dt_property, retval, default_val) \
+do { \
+ if (retval) \
+ break; \
+ \
+ retval = of_property_read_u32(chip->pdev->dev.of_node, \
+ "qcom," qpnp_dt_property, \
+ &store); \
+ \
+ if (retval == -EINVAL) { \
+ retval = 0; \
+ store = default_val; \
+ } else if (retval) { \
+ pr_err("Error reading " #qpnp_dt_property \
+ " property rc = %d\n", rc); \
+ } \
+} while (0)
+
+#define DEFAULT_EVALUATION_CURRENT_MA 1000
+static int fg_of_init(struct fg_chip *chip)
+{
+ int rc = 0, sense_type, len = 0;
+ const char *data;
+ struct device_node *node = chip->pdev->dev.of_node;
+ u32 temp[2] = {0};
+
+ OF_READ_SETTING(FG_MEM_SOFT_HOT, "warm-bat-decidegc", rc, 1);
+ OF_READ_SETTING(FG_MEM_SOFT_COLD, "cool-bat-decidegc", rc, 1);
+ OF_READ_SETTING(FG_MEM_HARD_HOT, "hot-bat-decidegc", rc, 1);
+ OF_READ_SETTING(FG_MEM_HARD_COLD, "cold-bat-decidegc", rc, 1);
+
+ if (of_find_property(node, "qcom,cold-hot-jeita-hysteresis", NULL)) {
+ int hard_hot = 0, soft_hot = 0, hard_cold = 0, soft_cold = 0;
+
+ rc = of_property_read_u32_array(node,
+ "qcom,cold-hot-jeita-hysteresis", temp, 2);
+ if (rc) {
+ pr_err("Error reading cold-hot-jeita-hysteresis rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chip->jeita_hysteresis_support = true;
+ chip->cold_hysteresis = temp[0];
+ chip->hot_hysteresis = temp[1];
+ hard_hot = settings[FG_MEM_HARD_HOT].value;
+ soft_hot = settings[FG_MEM_SOFT_HOT].value;
+ hard_cold = settings[FG_MEM_HARD_COLD].value;
+ soft_cold = settings[FG_MEM_SOFT_COLD].value;
+ if (((hard_hot - chip->hot_hysteresis) < soft_hot) ||
+ ((hard_cold + chip->cold_hysteresis) > soft_cold)) {
+ chip->jeita_hysteresis_support = false;
+ pr_err("invalid hysteresis: hot_hysterresis = %d cold_hysteresis = %d\n",
+ chip->hot_hysteresis, chip->cold_hysteresis);
+ } else {
+ pr_debug("cold_hysteresis = %d, hot_hysteresis = %d\n",
+ chip->cold_hysteresis, chip->hot_hysteresis);
+ }
+ }
+
+ OF_READ_SETTING(FG_MEM_BCL_LM_THRESHOLD, "bcl-lm-threshold-ma",
+ rc, 1);
+ OF_READ_SETTING(FG_MEM_BCL_MH_THRESHOLD, "bcl-mh-threshold-ma",
+ rc, 1);
+ OF_READ_SETTING(FG_MEM_TERM_CURRENT, "fg-iterm-ma", rc, 1);
+ OF_READ_SETTING(FG_MEM_CHG_TERM_CURRENT, "fg-chg-iterm-ma", rc, 1);
+ OF_READ_SETTING(FG_MEM_CUTOFF_VOLTAGE, "fg-cutoff-voltage-mv", rc, 1);
+ data = of_get_property(chip->pdev->dev.of_node,
+ "qcom,thermal-coefficients", &len);
+ if (data && len == THERMAL_COEFF_N_BYTES) {
+ memcpy(chip->thermal_coefficients, data, len);
+ chip->use_thermal_coefficients = true;
+ }
+ OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc", rc, 1);
+ settings[FG_MEM_RESUME_SOC].value =
+ DIV_ROUND_CLOSEST(settings[FG_MEM_RESUME_SOC].value
+ * FULL_SOC_RAW, FULL_CAPACITY);
+ OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc-raw", rc, 1);
+ OF_READ_SETTING(FG_MEM_IRQ_VOLT_EMPTY, "irq-volt-empty-mv", rc, 1);
+ OF_READ_SETTING(FG_MEM_VBAT_EST_DIFF, "vbat-estimate-diff-mv", rc, 1);
+ OF_READ_SETTING(FG_MEM_DELTA_SOC, "fg-delta-soc", rc, 1);
+ OF_READ_SETTING(FG_MEM_BATT_LOW, "fg-vbatt-low-threshold", rc, 1);
+ OF_READ_SETTING(FG_MEM_THERM_DELAY, "fg-therm-delay-us", rc, 1);
+ OF_READ_PROPERTY(chip->learning_data.max_increment,
+ "cl-max-increment-deciperc", rc, 5);
+ OF_READ_PROPERTY(chip->learning_data.max_decrement,
+ "cl-max-decrement-deciperc", rc, 100);
+ OF_READ_PROPERTY(chip->learning_data.max_temp,
+ "cl-max-temp-decidegc", rc, 450);
+ OF_READ_PROPERTY(chip->learning_data.min_temp,
+ "cl-min-temp-decidegc", rc, 150);
+ OF_READ_PROPERTY(chip->learning_data.max_start_soc,
+ "cl-max-start-capacity", rc, 15);
+ OF_READ_PROPERTY(chip->learning_data.vbat_est_thr_uv,
+ "cl-vbat-est-thr-uv", rc, 40000);
+ OF_READ_PROPERTY(chip->evaluation_current,
+ "aging-eval-current-ma", rc,
+ DEFAULT_EVALUATION_CURRENT_MA);
+ OF_READ_PROPERTY(chip->cc_cv_threshold_mv,
+ "fg-cc-cv-threshold-mv", rc, 0);
+ if (of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,capacity-learning-on"))
+ chip->batt_aging_mode = FG_AGING_CC;
+ else if (of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,capacity-estimation-on"))
+ chip->batt_aging_mode = FG_AGING_ESR;
+ else
+ chip->batt_aging_mode = FG_AGING_NONE;
+ if (chip->batt_aging_mode == FG_AGING_CC) {
+ chip->learning_data.feedback_on
+ = of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,capacity-learning-feedback");
+ }
+ if (fg_debug_mask & FG_AGING)
+ pr_info("battery aging mode: %d\n", chip->batt_aging_mode);
+
+ /* Get the use-otp-profile property */
+ chip->use_otp_profile = of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,use-otp-profile");
+ chip->hold_soc_while_full
+ = of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,hold-soc-while-full");
+
+ sense_type = of_property_read_bool(chip->pdev->dev.of_node,
+ "qcom,ext-sense-type");
+ if (rc == 0) {
+ if (fg_sense_type < 0)
+ fg_sense_type = sense_type;
+
+ if (fg_debug_mask & FG_STATUS) {
+ if (fg_sense_type == INTERNAL_CURRENT_SENSE)
+ pr_info("Using internal sense\n");
+ else if (fg_sense_type == EXTERNAL_CURRENT_SENSE)
+ pr_info("Using external sense\n");
+ else
+ pr_info("Using default sense\n");
+ }
+ } else {
+ rc = 0;
+ }
+
+ chip->bad_batt_detection_en = of_property_read_bool(node,
+ "qcom,bad-battery-detection-enable");
+
+ chip->sw_rbias_ctrl = of_property_read_bool(node,
+ "qcom,sw-rbias-control");
+
+ chip->cyc_ctr.en = of_property_read_bool(node,
+ "qcom,cycle-counter-en");
+ if (chip->cyc_ctr.en)
+ chip->cyc_ctr.id = 1;
+
+ chip->esr_pulse_tune_en = of_property_read_bool(node,
+ "qcom,esr-pulse-tuning-en");
+
+ return rc;
+}
+
+static int fg_init_irqs(struct fg_chip *chip)
+{
+ int rc = 0;
+ unsigned int base;
+ struct device_node *child;
+ u8 subtype;
+ struct platform_device *pdev = chip->pdev;
+
+ if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ return -ENXIO;
+ }
+
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ return rc;
+ }
+
+ if ((base == chip->vbat_adc_addr) ||
+ (base == chip->ibat_adc_addr) ||
+ (base == chip->tp_rev_addr))
+ continue;
+
+ rc = fg_read(chip, &subtype,
+ base + REG_OFFSET_PERP_SUBTYPE, 1);
+ if (rc) {
+ pr_err("Peripheral subtype read failed rc=%d\n", rc);
+ return rc;
+ }
+
+ switch (subtype) {
+ case FG_SOC:
+ chip->soc_irq[FULL_SOC].irq = of_irq_get_byname(child,
+ "full-soc");
+ if (chip->soc_irq[FULL_SOC].irq < 0) {
+ pr_err("Unable to get full-soc irq\n");
+ return rc;
+ }
+ chip->soc_irq[EMPTY_SOC].irq = of_irq_get_byname(child,
+ "empty-soc");
+ if (chip->soc_irq[EMPTY_SOC].irq < 0) {
+ pr_err("Unable to get low-soc irq\n");
+ return rc;
+ }
+ chip->soc_irq[DELTA_SOC].irq = of_irq_get_byname(child,
+ "delta-soc");
+ if (chip->soc_irq[DELTA_SOC].irq < 0) {
+ pr_err("Unable to get delta-soc irq\n");
+ return rc;
+ }
+ chip->soc_irq[FIRST_EST_DONE].irq
+ = of_irq_get_byname(child, "first-est-done");
+ if (chip->soc_irq[FIRST_EST_DONE].irq < 0) {
+ pr_err("Unable to get first-est-done irq\n");
+ return rc;
+ }
+
+ rc = devm_request_irq(chip->dev,
+ chip->soc_irq[FULL_SOC].irq,
+ fg_soc_irq_handler, IRQF_TRIGGER_RISING,
+ "full-soc", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d full-soc: %d\n",
+ chip->soc_irq[FULL_SOC].irq, rc);
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev,
+ chip->soc_irq[EMPTY_SOC].irq,
+ fg_empty_soc_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
+ "empty-soc", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d empty-soc: %d\n",
+ chip->soc_irq[EMPTY_SOC].irq, rc);
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev,
+ chip->soc_irq[DELTA_SOC].irq,
+ fg_soc_irq_handler, IRQF_TRIGGER_RISING,
+ "delta-soc", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d delta-soc: %d\n",
+ chip->soc_irq[DELTA_SOC].irq, rc);
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev,
+ chip->soc_irq[FIRST_EST_DONE].irq,
+ fg_first_soc_irq_handler, IRQF_TRIGGER_RISING,
+ "first-est-done", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d delta-soc: %d\n",
+ chip->soc_irq[FIRST_EST_DONE].irq, rc);
+ return rc;
+ }
+
+ enable_irq_wake(chip->soc_irq[DELTA_SOC].irq);
+ enable_irq_wake(chip->soc_irq[FULL_SOC].irq);
+ enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq);
+ break;
+ case FG_MEMIF:
+ chip->mem_irq[FG_MEM_AVAIL].irq
+ = of_irq_get_byname(child, "mem-avail");
+ if (chip->mem_irq[FG_MEM_AVAIL].irq < 0) {
+ pr_err("Unable to get mem-avail irq\n");
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev,
+ chip->mem_irq[FG_MEM_AVAIL].irq,
+ fg_mem_avail_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "mem-avail", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d mem-avail: %d\n",
+ chip->mem_irq[FG_MEM_AVAIL].irq, rc);
+ return rc;
+ }
+ break;
+ case FG_BATT:
+ chip->batt_irq[BATT_MISSING].irq
+ = of_irq_get_byname(child, "batt-missing");
+ if (chip->batt_irq[BATT_MISSING].irq < 0) {
+ pr_err("Unable to get batt-missing irq\n");
+ rc = -EINVAL;
+ return rc;
+ }
+ rc = devm_request_threaded_irq(chip->dev,
+ chip->batt_irq[BATT_MISSING].irq,
+ NULL,
+ fg_batt_missing_irq_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING |
+ IRQF_ONESHOT,
+ "batt-missing", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d batt-missing: %d\n",
+ chip->batt_irq[BATT_MISSING].irq, rc);
+ return rc;
+ }
+ chip->batt_irq[VBATT_LOW].irq
+ = of_irq_get_byname(child, "vbatt-low");
+ if (chip->batt_irq[VBATT_LOW].irq < 0) {
+ pr_err("Unable to get vbatt-low irq\n");
+ rc = -EINVAL;
+ return rc;
+ }
+ rc = devm_request_irq(chip->dev,
+ chip->batt_irq[VBATT_LOW].irq,
+ fg_vbatt_low_handler,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "vbatt-low", chip);
+ if (rc < 0) {
+ pr_err("Can't request %d vbatt-low: %d\n",
+ chip->batt_irq[VBATT_LOW].irq, rc);
+ return rc;
+ }
+ disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq);
+ chip->vbat_low_irq_enabled = false;
+ break;
+ case FG_ADC:
+ break;
+ default:
+ pr_err("subtype %d\n", subtype);
+ return -EINVAL;
+ }
+ }
+
+ return rc;
+}
+
+static void fg_cleanup(struct fg_chip *chip)
+{
+ cancel_delayed_work_sync(&chip->update_sram_data);
+ cancel_delayed_work_sync(&chip->update_temp_work);
+ cancel_delayed_work_sync(&chip->update_jeita_setting);
+ cancel_delayed_work_sync(&chip->check_empty_work);
+ cancel_delayed_work_sync(&chip->batt_profile_init);
+ alarm_try_to_cancel(&chip->fg_cap_learning_alarm);
+ cancel_work_sync(&chip->rslow_comp_work);
+ cancel_work_sync(&chip->set_resume_soc_work);
+ cancel_work_sync(&chip->fg_cap_learning_work);
+ cancel_work_sync(&chip->dump_sram);
+ cancel_work_sync(&chip->status_change_work);
+ cancel_work_sync(&chip->cycle_count_work);
+ cancel_work_sync(&chip->update_esr_work);
+ cancel_work_sync(&chip->sysfs_restart_work);
+ cancel_work_sync(&chip->gain_comp_work);
+ cancel_work_sync(&chip->init_work);
+ cancel_work_sync(&chip->charge_full_work);
+ cancel_work_sync(&chip->esr_extract_config_work);
+ mutex_destroy(&chip->rslow_comp.lock);
+ mutex_destroy(&chip->rw_lock);
+ mutex_destroy(&chip->cyc_ctr.lock);
+ mutex_destroy(&chip->learning_data.learning_lock);
+ mutex_destroy(&chip->sysfs_restart_lock);
+ wakeup_source_trash(&chip->resume_soc_wakeup_source.source);
+ wakeup_source_trash(&chip->empty_check_wakeup_source.source);
+ wakeup_source_trash(&chip->memif_wakeup_source.source);
+ wakeup_source_trash(&chip->profile_wakeup_source.source);
+ wakeup_source_trash(&chip->update_temp_wakeup_source.source);
+ wakeup_source_trash(&chip->update_sram_wakeup_source.source);
+ wakeup_source_trash(&chip->gain_comp_wakeup_source.source);
+ wakeup_source_trash(&chip->capacity_learning_wakeup_source.source);
+ wakeup_source_trash(&chip->esr_extract_wakeup_source.source);
+}
+
+static int fg_remove(struct platform_device *pdev)
+{
+ struct fg_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ fg_cleanup(chip);
+ dev_set_drvdata(&pdev->dev, NULL);
+ return 0;
+}
+
+static int fg_memif_data_open(struct inode *inode, struct file *file)
+{
+ struct fg_log_buffer *log;
+ struct fg_trans *trans;
+ u8 *data_buf;
+
+ size_t logbufsize = SZ_4K;
+ size_t databufsize = SZ_4K;
+
+ if (!dbgfs_data.chip) {
+ pr_err("Not initialized data\n");
+ return -EINVAL;
+ }
+
+ /* Per file "transaction" data */
+ trans = kzalloc(sizeof(*trans), GFP_KERNEL);
+ if (!trans) {
+ pr_err("Unable to allocate memory for transaction data\n");
+ return -ENOMEM;
+ }
+
+ /* Allocate log buffer */
+ log = kzalloc(logbufsize, GFP_KERNEL);
+
+ if (!log) {
+ kfree(trans);
+ pr_err("Unable to allocate memory for log buffer\n");
+ return -ENOMEM;
+ }
+
+ log->rpos = 0;
+ log->wpos = 0;
+ log->len = logbufsize - sizeof(*log);
+
+ /* Allocate data buffer */
+ data_buf = kzalloc(databufsize, GFP_KERNEL);
+
+ if (!data_buf) {
+ kfree(trans);
+ kfree(log);
+ pr_err("Unable to allocate memory for data buffer\n");
+ return -ENOMEM;
+ }
+
+ trans->log = log;
+ trans->data = data_buf;
+ trans->cnt = dbgfs_data.cnt;
+ trans->addr = dbgfs_data.addr;
+ trans->chip = dbgfs_data.chip;
+ trans->offset = trans->addr;
+ mutex_init(&trans->memif_dfs_lock);
+
+ file->private_data = trans;
+ return 0;
+}
+
+static int fg_memif_dfs_close(struct inode *inode, struct file *file)
+{
+ struct fg_trans *trans = file->private_data;
+
+ if (trans && trans->log && trans->data) {
+ file->private_data = NULL;
+ mutex_destroy(&trans->memif_dfs_lock);
+ kfree(trans->log);
+ kfree(trans->data);
+ kfree(trans);
+ }
+
+ return 0;
+}
+
+/**
+ * print_to_log: format a string and place into the log buffer
+ * @log: The log buffer to place the result into.
+ * @fmt: The format string to use.
+ * @...: The arguments for the format string.
+ *
+ * The return value is the number of characters written to @log buffer
+ * not including the trailing '\0'.
+ */
+static int print_to_log(struct fg_log_buffer *log, const char *fmt, ...)
+{
+ va_list args;
+ int cnt;
+ char *buf = &log->data[log->wpos];
+ size_t size = log->len - log->wpos;
+
+ va_start(args, fmt);
+ cnt = vscnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ log->wpos += cnt;
+ return cnt;
+}
+
+/**
+ * write_next_line_to_log: Writes a single "line" of data into the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ * @offset: SRAM address offset to start reading from.
+ * @pcnt: Pointer to 'cnt' variable. Indicates the number of bytes to read.
+ *
+ * The 'offset' is a 12-bit SRAM address.
+ *
+ * On a successful read, the pcnt is decremented by the number of data
+ * bytes read from the SRAM. When the cnt reaches 0, all requested bytes have
+ * been read.
+ */
+static int
+write_next_line_to_log(struct fg_trans *trans, int offset, size_t *pcnt)
+{
+ int i, j;
+ u8 data[ITEMS_PER_LINE];
+ struct fg_log_buffer *log = trans->log;
+
+ int cnt = 0;
+ int padding = offset % ITEMS_PER_LINE;
+ int items_to_read = min(ARRAY_SIZE(data) - padding, *pcnt);
+ int items_to_log = min(ITEMS_PER_LINE, padding + items_to_read);
+
+ /* Buffer needs enough space for an entire line */
+ if ((log->len - log->wpos) < MAX_LINE_LENGTH)
+ goto done;
+
+ memcpy(data, trans->data + (offset - trans->addr), items_to_read);
+
+ *pcnt -= items_to_read;
+
+ /* Each line starts with the aligned offset (12-bit address) */
+ cnt = print_to_log(log, "%3.3X ", offset & 0xfff);
+ if (cnt == 0)
+ goto done;
+
+ /* If the offset is unaligned, add padding to right justify items */
+ for (i = 0; i < padding; ++i) {
+ cnt = print_to_log(log, "-- ");
+ if (cnt == 0)
+ goto done;
+ }
+
+ /* Log the data items */
+ for (j = 0; i < items_to_log; ++i, ++j) {
+ cnt = print_to_log(log, "%2.2X ", data[j]);
+ if (cnt == 0)
+ goto done;
+ }
+
+ /* If the last character was a space, then replace it with a newline */
+ if (log->wpos > 0 && log->data[log->wpos - 1] == ' ')
+ log->data[log->wpos - 1] = '\n';
+
+done:
+ return cnt;
+}
+
+/**
+ * get_log_data - reads data from SRAM and saves to the log buffer
+ * @trans: Pointer to SRAM transaction data.
+ *
+ * Returns the number of "items" read or SPMI error code for read failures.
+ */
+static int get_log_data(struct fg_trans *trans)
+{
+ int cnt, rc;
+ int last_cnt;
+ int items_read;
+ int total_items_read = 0;
+ u32 offset = trans->offset;
+ size_t item_cnt = trans->cnt;
+ struct fg_log_buffer *log = trans->log;
+
+ if (item_cnt == 0)
+ return 0;
+
+ if (item_cnt > SZ_4K) {
+ pr_err("Reading too many bytes\n");
+ return -EINVAL;
+ }
+
+ rc = fg_mem_read(trans->chip, trans->data,
+ trans->addr, trans->cnt, 0, 0);
+ if (rc) {
+ pr_err("dump failed: rc = %d\n", rc);
+ return rc;
+ }
+ /* Reset the log buffer 'pointers' */
+ log->wpos = log->rpos = 0;
+
+ /* Keep reading data until the log is full */
+ do {
+ last_cnt = item_cnt;
+ cnt = write_next_line_to_log(trans, offset, &item_cnt);
+ items_read = last_cnt - item_cnt;
+ offset += items_read;
+ total_items_read += items_read;
+ } while (cnt && item_cnt > 0);
+
+ /* Adjust the transaction offset and count */
+ trans->cnt = item_cnt;
+ trans->offset += total_items_read;
+
+ return total_items_read;
+}
+
+/**
+ * fg_memif_dfs_reg_read: reads value(s) from SRAM and fills user's buffer a
+ * byte array (coded as string)
+ * @file: file pointer
+ * @buf: where to put the result
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user bytes read, or negative error value
+ */
+static ssize_t fg_memif_dfs_reg_read(struct file *file, char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ struct fg_trans *trans = file->private_data;
+ struct fg_log_buffer *log = trans->log;
+ size_t ret;
+ size_t len;
+
+ mutex_lock(&trans->memif_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->memif_dfs_lock);
+ return len;
+}
+
+/**
+ * fg_memif_dfs_reg_write: write user's byte array (coded as string) to SRAM.
+ * @file: file pointer
+ * @buf: user data to be written.
+ * @count: maximum space available in @buf
+ * @ppos: starting position
+ * @return number of user byte written, or negative error value
+ */
+static ssize_t fg_memif_dfs_reg_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *ppos)
+{
+ int bytes_read;
+ int data;
+ int pos = 0;
+ int cnt = 0;
+ u8 *values;
+ size_t ret = 0;
+ char *kbuf;
+ u32 offset;
+
+ struct fg_trans *trans = file->private_data;
+
+ mutex_lock(&trans->memif_dfs_lock);
+ offset = trans->offset;
+
+ /* 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_info("address %x, count %d\n", offset, cnt);
+ /* Perform the write(s) */
+
+ ret = fg_mem_write(trans->chip, values, offset,
+ cnt, 0, 0);
+ if (ret) {
+ pr_err("SPMI write failed, err = %zu\n", ret);
+ } else {
+ ret = count;
+ trans->offset += cnt > 4 ? 4 : cnt;
+ }
+
+free_buf:
+ kfree(kbuf);
+unlock_mutex:
+ mutex_unlock(&trans->memif_dfs_lock);
+ return ret;
+}
+
+static const struct file_operations fg_memif_dfs_reg_fops = {
+ .open = fg_memif_data_open,
+ .release = fg_memif_dfs_close,
+ .read = fg_memif_dfs_reg_read,
+ .write = fg_memif_dfs_reg_write,
+};
+
+/**
+ * fg_dfs_create_fs: create debugfs file system.
+ * @return pointer to root directory or NULL if failed to create fs
+ */
+static struct dentry *fg_dfs_create_fs(void)
+{
+ struct dentry *root, *file;
+
+ pr_debug("Creating FG_MEM debugfs file-system\n");
+ root = debugfs_create_dir(DFS_ROOT_NAME, NULL);
+ if (IS_ERR_OR_NULL(root)) {
+ pr_err("Error creating top level directory err:%ld",
+ (long)root);
+ if (PTR_ERR(root) == -ENODEV)
+ pr_err("debugfs is not enabled in the kernel");
+ return NULL;
+ }
+
+ dbgfs_data.help_msg.size = strlen(dbgfs_data.help_msg.data);
+
+ file = debugfs_create_blob("help", S_IRUGO, root, &dbgfs_data.help_msg);
+ if (!file) {
+ pr_err("error creating help entry\n");
+ goto err_remove_fs;
+ }
+ return root;
+
+err_remove_fs:
+ debugfs_remove_recursive(root);
+ return NULL;
+}
+
+/**
+ * fg_dfs_get_root: return a pointer to FG debugfs root directory.
+ * @return a pointer to the existing directory, or if no root
+ * directory exists then create one. Directory is created with file that
+ * configures SRAM transaction, namely: address, and count.
+ * @returns valid pointer on success or NULL
+ */
+struct dentry *fg_dfs_get_root(void)
+{
+ if (dbgfs_data.root)
+ return dbgfs_data.root;
+
+ if (mutex_lock_interruptible(&dbgfs_data.lock) < 0)
+ return NULL;
+ /* critical section */
+ if (!dbgfs_data.root) { /* double checking idiom */
+ dbgfs_data.root = fg_dfs_create_fs();
+ }
+ mutex_unlock(&dbgfs_data.lock);
+ return dbgfs_data.root;
+}
+
+/*
+ * fg_dfs_create: adds new fg_mem if debugfs entry
+ * @return zero on success
+ */
+int fg_dfs_create(struct fg_chip *chip)
+{
+ struct dentry *root;
+ struct dentry *file;
+
+ root = fg_dfs_get_root();
+ if (!root)
+ return -ENOENT;
+
+ dbgfs_data.chip = chip;
+
+ file = debugfs_create_u32("count", DFS_MODE, root, &(dbgfs_data.cnt));
+ if (!file) {
+ pr_err("error creating 'count' entry\n");
+ goto err_remove_fs;
+ }
+
+ file = debugfs_create_x32("address", DFS_MODE,
+ root, &(dbgfs_data.addr));
+ if (!file) {
+ pr_err("error creating 'address' entry\n");
+ goto err_remove_fs;
+ }
+
+ file = debugfs_create_file("data", DFS_MODE, root, &dbgfs_data,
+ &fg_memif_dfs_reg_fops);
+ if (!file) {
+ pr_err("error creating 'data' entry\n");
+ goto err_remove_fs;
+ }
+
+ return 0;
+
+err_remove_fs:
+ debugfs_remove_recursive(root);
+ return -ENOMEM;
+}
+
+#define EXTERNAL_SENSE_OFFSET_REG 0x41C
+#define EXT_OFFSET_TRIM_REG 0xF8
+#define SEC_ACCESS_REG 0xD0
+#define SEC_ACCESS_UNLOCK 0xA5
+#define BCL_TRIM_REV_FIXED 12
+static int bcl_trim_workaround(struct fg_chip *chip)
+{
+ u8 reg, rc;
+
+ if (chip->tp_rev_addr == 0)
+ return 0;
+
+ rc = fg_read(chip, &reg, chip->tp_rev_addr, 1);
+ if (rc) {
+ pr_err("Failed to read tp reg, rc = %d\n", rc);
+ return rc;
+ }
+ if (reg >= BCL_TRIM_REV_FIXED) {
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("workaround not applied, tp_rev = %d\n", reg);
+ return 0;
+ }
+
+ rc = fg_mem_read(chip, &reg, EXTERNAL_SENSE_OFFSET_REG, 1, 2, 0);
+ if (rc) {
+ pr_err("Failed to read ext sense offset trim, rc = %d\n", rc);
+ return rc;
+ }
+ rc = fg_masked_write(chip, chip->soc_base + SEC_ACCESS_REG,
+ SEC_ACCESS_UNLOCK, SEC_ACCESS_UNLOCK, 1);
+
+ rc |= fg_masked_write(chip, chip->soc_base + EXT_OFFSET_TRIM_REG,
+ 0xFF, reg, 1);
+ if (rc) {
+ pr_err("Failed to write ext sense offset trim, rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+#define FG_ALG_SYSCTL_1 0x4B0
+#define SOC_CNFG 0x450
+#define SOC_DELTA_OFFSET 3
+#define DELTA_SOC_PERCENT 1
+#define I_TERM_QUAL_BIT BIT(1)
+#define PATCH_NEG_CURRENT_BIT BIT(3)
+#define KI_COEFF_PRED_FULL_ADDR 0x408
+#define KI_COEFF_PRED_FULL_4_0_MSB 0x88
+#define KI_COEFF_PRED_FULL_4_0_LSB 0x00
+#define TEMP_FRAC_SHIFT_REG 0x4A4
+#define FG_ADC_CONFIG_REG 0x4B8
+#define FG_BCL_CONFIG_OFFSET 0x3
+#define BCL_FORCED_HPM_IN_CHARGE BIT(2)
+static int fg_common_hw_init(struct fg_chip *chip)
+{
+ int rc;
+ int resume_soc_raw;
+ u8 val;
+
+ update_iterm(chip);
+ update_cutoff_voltage(chip);
+ update_irq_volt_empty(chip);
+ update_bcl_thresholds(chip);
+
+ resume_soc_raw = settings[FG_MEM_RESUME_SOC].value;
+ if (resume_soc_raw > 0) {
+ rc = fg_set_resume_soc(chip, resume_soc_raw);
+ if (rc) {
+ pr_err("Couldn't set resume SOC for FG\n");
+ return rc;
+ }
+ } else {
+ pr_info("FG auto recharge threshold not specified in DT\n");
+ }
+
+ if (fg_sense_type >= 0) {
+ rc = set_prop_sense_type(chip, fg_sense_type);
+ if (rc) {
+ pr_err("failed to config sense type %d rc=%d\n",
+ fg_sense_type, rc);
+ return rc;
+ }
+ }
+
+ rc = fg_mem_masked_write(chip, settings[FG_MEM_DELTA_SOC].address, 0xFF,
+ soc_to_setpoint(settings[FG_MEM_DELTA_SOC].value),
+ settings[FG_MEM_DELTA_SOC].offset);
+ if (rc) {
+ pr_err("failed to write delta soc rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF,
+ batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value),
+ settings[FG_MEM_BATT_LOW].offset);
+ if (rc) {
+ pr_err("failed to write Vbatt_low rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_mem_masked_write(chip, settings[FG_MEM_THERM_DELAY].address,
+ THERM_DELAY_MASK,
+ therm_delay_to_setpoint(settings[FG_MEM_THERM_DELAY].value),
+ settings[FG_MEM_THERM_DELAY].offset);
+ if (rc) {
+ pr_err("failed to write therm_delay rc=%d\n", rc);
+ return rc;
+ }
+
+ if (chip->use_thermal_coefficients) {
+ fg_mem_write(chip, chip->thermal_coefficients,
+ THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES,
+ THERMAL_COEFF_OFFSET, 0);
+ }
+
+ if (!chip->sw_rbias_ctrl) {
+ rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT,
+ BATT_TEMP_CNTRL_MASK,
+ TEMP_SENSE_ALWAYS_BIT,
+ BATT_TEMP_OFFSET);
+ if (rc) {
+ pr_err("failed to write BATT_TEMP_OFFSET rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ /* Read the cycle counter back from FG SRAM */
+ if (chip->cyc_ctr.en)
+ restore_cycle_counter(chip);
+
+ if (chip->esr_pulse_tune_en) {
+ rc = fg_mem_read(chip, &val, SYS_CFG_1_REG, 1, SYS_CFG_1_OFFSET,
+ 0);
+ if (rc) {
+ pr_err("unable to read sys_cfg_1: %d\n", rc);
+ return rc;
+ }
+
+ if (!(val & ENABLE_ESR_PULSE_VAL))
+ chip->esr_extract_disabled = true;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("ESR extract is %sabled\n",
+ chip->esr_extract_disabled ? "dis" : "en");
+
+ rc = fg_mem_read(chip, &val, CBITS_INPUT_FILTER_REG, 1,
+ CBITS_RMEAS1_OFFSET, 0);
+ if (rc) {
+ pr_err("unable to read cbits_input_filter_reg: %d\n",
+ rc);
+ return rc;
+ }
+
+ if (val & (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT))
+ chip->imptr_pulse_slow_en = true;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("imptr_pulse_slow is %sabled\n",
+ chip->imptr_pulse_slow_en ? "en" : "dis");
+
+ rc = fg_mem_read(chip, &val, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET,
+ 0);
+ if (rc) {
+ pr_err("unable to read rslow cfg: %d\n", rc);
+ return rc;
+ }
+
+ if (val & RSLOW_CFG_ON_VAL)
+ chip->rslow_comp.active = true;
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("rslow_comp active is %sabled\n",
+ chip->rslow_comp.active ? "en" : "dis");
+ }
+
+ return 0;
+}
+
+static int fg_8994_hw_init(struct fg_chip *chip)
+{
+ int rc = 0;
+ u8 data[4];
+ u64 esr_value;
+
+ rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT,
+ PATCH_NEG_CURRENT_BIT,
+ PATCH_NEG_CURRENT_BIT,
+ EXTERNAL_SENSE_OFFSET);
+ if (rc) {
+ pr_err("failed to write patch current bit rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = bcl_trim_workaround(chip);
+ if (rc) {
+ pr_err("failed to redo bcl trim rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG,
+ BCL_FORCED_HPM_IN_CHARGE,
+ BCL_FORCED_HPM_IN_CHARGE,
+ FG_BCL_CONFIG_OFFSET);
+ if (rc) {
+ pr_err("failed to force hpm in charge rc=%d\n", rc);
+ return rc;
+ }
+
+ fg_mem_masked_write(chip, FG_ALG_SYSCTL_1, I_TERM_QUAL_BIT, 0, 0);
+
+ data[0] = 0xA2;
+ data[1] = 0x12;
+
+ rc = fg_mem_write(chip, data, TEMP_FRAC_SHIFT_REG, 2, 2, 0);
+ if (rc) {
+ pr_err("failed to write temp ocv constants rc=%d\n", rc);
+ return rc;
+ }
+
+ data[0] = KI_COEFF_PRED_FULL_4_0_LSB;
+ data[1] = KI_COEFF_PRED_FULL_4_0_MSB;
+ fg_mem_write(chip, data, KI_COEFF_PRED_FULL_ADDR, 2, 2, 0);
+
+ esr_value = ESR_DEFAULT_VALUE;
+ rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8,
+ ESR_VALUE_OFFSET, 0);
+ if (rc)
+ pr_err("failed to write default ESR value rc=%d\n", rc);
+ else
+ pr_debug("set default value to esr filter\n");
+
+ return 0;
+}
+
+#define FG_USBID_CONFIG_OFFSET 0x2
+#define DISABLE_USBID_DETECT_BIT BIT(0)
+static int fg_8996_hw_init(struct fg_chip *chip)
+{
+ int rc;
+
+ rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG,
+ BCL_FORCED_HPM_IN_CHARGE,
+ BCL_FORCED_HPM_IN_CHARGE,
+ FG_BCL_CONFIG_OFFSET);
+ if (rc) {
+ pr_err("failed to force hpm in charge rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable usbid conversions for PMi8996 V1.0 */
+ if (chip->pmic_revision[REVID_DIG_MAJOR] == 1
+ && chip->pmic_revision[REVID_ANA_MAJOR] == 0) {
+ rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG,
+ DISABLE_USBID_DETECT_BIT,
+ 0, FG_USBID_CONFIG_OFFSET);
+ if (rc) {
+ pr_err("failed to enable usbid conversions: %d\n", rc);
+ return rc;
+ }
+ }
+
+ return rc;
+}
+
+static int fg_8950_hw_init(struct fg_chip *chip)
+{
+ int rc;
+
+ rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG,
+ BCL_FORCED_HPM_IN_CHARGE,
+ BCL_FORCED_HPM_IN_CHARGE,
+ FG_BCL_CONFIG_OFFSET);
+ if (rc)
+ pr_err("failed to force hpm in charge rc=%d\n", rc);
+
+ return rc;
+}
+
+static int fg_hw_init(struct fg_chip *chip)
+{
+ int rc = 0;
+
+ rc = fg_common_hw_init(chip);
+ if (rc) {
+ pr_err("Unable to initialize FG HW rc=%d\n", rc);
+ return rc;
+ }
+
+ /* add PMIC specific hw init */
+ switch (chip->pmic_subtype) {
+ case PMI8994:
+ rc = fg_8994_hw_init(chip);
+ chip->wa_flag |= PULSE_REQUEST_WA;
+ break;
+ case PMI8996:
+ rc = fg_8996_hw_init(chip);
+ /* Setup workaround flag based on PMIC type */
+ if (fg_sense_type == INTERNAL_CURRENT_SENSE)
+ chip->wa_flag |= IADC_GAIN_COMP_WA;
+ if (chip->pmic_revision[REVID_DIG_MAJOR] > 1)
+ chip->wa_flag |= USE_CC_SOC_REG;
+
+ break;
+ case PMI8950:
+ case PMI8937:
+ rc = fg_8950_hw_init(chip);
+ /* Setup workaround flag based on PMIC type */
+ chip->wa_flag |= BCL_HI_POWER_FOR_CHGLED_WA;
+ if (fg_sense_type == INTERNAL_CURRENT_SENSE)
+ chip->wa_flag |= IADC_GAIN_COMP_WA;
+ if (chip->pmic_revision[REVID_DIG_MAJOR] > 1)
+ chip->wa_flag |= USE_CC_SOC_REG;
+
+ break;
+ }
+ if (rc)
+ pr_err("Unable to initialize PMIC specific FG HW rc=%d\n", rc);
+
+ pr_debug("wa_flag=0x%x\n", chip->wa_flag);
+
+ return rc;
+}
+
+#define DIG_MINOR 0x0
+#define DIG_MAJOR 0x1
+#define ANA_MINOR 0x2
+#define ANA_MAJOR 0x3
+#define IACS_INTR_SRC_SLCT BIT(3)
+static int fg_setup_memif_offset(struct fg_chip *chip)
+{
+ int rc;
+
+ rc = fg_read(chip, chip->revision, chip->mem_base + DIG_MINOR, 4);
+ if (rc) {
+ pr_err("Unable to read FG revision rc=%d\n", rc);
+ return rc;
+ }
+
+ switch (chip->revision[DIG_MAJOR]) {
+ case DIG_REV_1:
+ case DIG_REV_2:
+ chip->offset = offset[0].address;
+ break;
+ case DIG_REV_3:
+ chip->offset = offset[1].address;
+ chip->ima_supported = true;
+ break;
+ default:
+ pr_err("Digital Major rev=%d not supported\n",
+ chip->revision[DIG_MAJOR]);
+ return -EINVAL;
+ }
+
+ if (chip->ima_supported) {
+ /*
+ * Change the FG_MEM_INT interrupt to track IACS_READY
+ * condition instead of end-of-transaction. This makes sure
+ * that the next transaction starts only after the hw is ready.
+ */
+ rc = fg_masked_write(chip,
+ chip->mem_base + MEM_INTF_IMA_CFG, IACS_INTR_SRC_SLCT,
+ IACS_INTR_SRC_SLCT, 1);
+ if (rc) {
+ pr_err("failed to configure interrupt source %d\n", rc);
+ return rc;
+ }
+ }
+
+ return 0;
+}
+
+static int fg_detect_pmic_type(struct fg_chip *chip)
+{
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+
+ revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR_OR_NULL(pmic_rev_id)) {
+ pr_err("Unable to get pmic_revid rc=%ld\n",
+ PTR_ERR(pmic_rev_id));
+ /*
+ * the revid peripheral must be registered, any failure
+ * here only indicates that the rev-id module has not
+ * probed yet.
+ */
+ return -EPROBE_DEFER;
+ }
+
+ switch (pmic_rev_id->pmic_subtype) {
+ case PMI8994:
+ case PMI8950:
+ case PMI8937:
+ case PMI8996:
+ chip->pmic_subtype = pmic_rev_id->pmic_subtype;
+ chip->pmic_revision[REVID_RESERVED] = pmic_rev_id->rev1;
+ chip->pmic_revision[REVID_VARIANT] = pmic_rev_id->rev2;
+ chip->pmic_revision[REVID_ANA_MAJOR] = pmic_rev_id->rev3;
+ chip->pmic_revision[REVID_DIG_MAJOR] = pmic_rev_id->rev4;
+ break;
+ default:
+ pr_err("PMIC subtype %d not supported\n",
+ pmic_rev_id->pmic_subtype);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+#define INIT_JEITA_DELAY_MS 1000
+
+static void delayed_init_work(struct work_struct *work)
+{
+ u8 reg[2];
+ int rc;
+ struct fg_chip *chip = container_of(work,
+ struct fg_chip,
+ init_work);
+
+ /* hold memory access until initialization finishes */
+ fg_mem_lock(chip);
+
+ rc = fg_hw_init(chip);
+ if (rc) {
+ pr_err("failed to hw init rc = %d\n", rc);
+ fg_mem_release(chip);
+ fg_cleanup(chip);
+ return;
+ }
+ /* release memory access before update_sram_data is called */
+ fg_mem_release(chip);
+
+ schedule_delayed_work(
+ &chip->update_jeita_setting,
+ msecs_to_jiffies(INIT_JEITA_DELAY_MS));
+
+ if (chip->last_sram_update_time == 0)
+ update_sram_data_work(&chip->update_sram_data.work);
+
+ if (chip->last_temp_update_time == 0)
+ update_temp_data(&chip->update_temp_work.work);
+
+ if (!chip->use_otp_profile)
+ schedule_delayed_work(&chip->batt_profile_init, 0);
+
+ if (chip->wa_flag & IADC_GAIN_COMP_WA) {
+ /* read default gain config */
+ rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, DEF_GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read default gain rc=%d\n", rc);
+ goto done;
+ }
+
+ if (reg[1] || reg[0]) {
+ /*
+ * Default gain register has valid value:
+ * - write to gain register.
+ */
+ rc = fg_mem_write(chip, reg, GAIN_REG, 2,
+ GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to write gain rc=%d\n", rc);
+ goto done;
+ }
+ } else {
+ /*
+ * Default gain register is invalid:
+ * - read gain register for default gain value
+ * - write to default gain register.
+ */
+ rc = fg_mem_read(chip, reg, GAIN_REG, 2,
+ GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to read gain rc=%d\n", rc);
+ goto done;
+ }
+ rc = fg_mem_write(chip, reg, K_VCOR_REG, 2,
+ DEF_GAIN_OFFSET, 0);
+ if (rc) {
+ pr_err("Failed to write default gain rc=%d\n",
+ rc);
+ goto done;
+ }
+ }
+
+ chip->iadc_comp_data.dfl_gain_reg[0] = reg[0];
+ chip->iadc_comp_data.dfl_gain_reg[1] = reg[1];
+ chip->iadc_comp_data.dfl_gain = half_float(reg);
+ chip->input_present = is_input_present(chip);
+ chip->otg_present = is_otg_present(chip);
+ chip->init_done = true;
+
+ pr_debug("IADC gain initial config reg_val 0x%x%x gain %lld\n",
+ reg[1], reg[0], chip->iadc_comp_data.dfl_gain);
+ }
+
+ pr_debug("FG: HW_init success\n");
+
+ return;
+done:
+ fg_cleanup(chip);
+}
+
+static int fg_probe(struct platform_device *pdev)
+{
+ struct device *dev = &(pdev->dev);
+ struct fg_chip *chip;
+ struct device_node *child;
+ unsigned int base;
+ u8 subtype, reg;
+ int rc = 0;
+ struct power_supply_config bms_psy_cfg;
+
+ if (!pdev) {
+ pr_err("no valid spmi pointer\n");
+ return -ENODEV;
+ }
+
+ if (!pdev->dev.of_node) {
+ pr_err("device node missing\n");
+ return -ENODEV;
+ }
+
+ chip = devm_kzalloc(dev, sizeof(struct fg_chip), GFP_KERNEL);
+ if (chip == NULL) {
+ pr_err("Can't allocate fg_chip\n");
+ return -ENOMEM;
+ }
+ chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ chip->pdev = pdev;
+ chip->dev = &(pdev->dev);
+
+ wakeup_source_init(&chip->empty_check_wakeup_source.source,
+ "qpnp_fg_empty_check");
+ wakeup_source_init(&chip->memif_wakeup_source.source,
+ "qpnp_fg_memaccess");
+ wakeup_source_init(&chip->profile_wakeup_source.source,
+ "qpnp_fg_profile");
+ wakeup_source_init(&chip->update_temp_wakeup_source.source,
+ "qpnp_fg_update_temp");
+ wakeup_source_init(&chip->update_sram_wakeup_source.source,
+ "qpnp_fg_update_sram");
+ wakeup_source_init(&chip->resume_soc_wakeup_source.source,
+ "qpnp_fg_set_resume_soc");
+ wakeup_source_init(&chip->gain_comp_wakeup_source.source,
+ "qpnp_fg_gain_comp");
+ wakeup_source_init(&chip->capacity_learning_wakeup_source.source,
+ "qpnp_fg_cap_learning");
+ wakeup_source_init(&chip->esr_extract_wakeup_source.source,
+ "qpnp_fg_esr_extract");
+ mutex_init(&chip->rw_lock);
+ mutex_init(&chip->cyc_ctr.lock);
+ mutex_init(&chip->learning_data.learning_lock);
+ mutex_init(&chip->rslow_comp.lock);
+ mutex_init(&chip->sysfs_restart_lock);
+ INIT_DELAYED_WORK(&chip->update_jeita_setting, update_jeita_setting);
+ INIT_DELAYED_WORK(&chip->update_sram_data, update_sram_data_work);
+ INIT_DELAYED_WORK(&chip->update_temp_work, update_temp_data);
+ INIT_DELAYED_WORK(&chip->check_empty_work, check_empty_work);
+ INIT_DELAYED_WORK(&chip->batt_profile_init, batt_profile_init);
+ INIT_WORK(&chip->rslow_comp_work, rslow_comp_work);
+ INIT_WORK(&chip->fg_cap_learning_work, fg_cap_learning_work);
+ INIT_WORK(&chip->dump_sram, dump_sram);
+ INIT_WORK(&chip->status_change_work, status_change_work);
+ INIT_WORK(&chip->cycle_count_work, update_cycle_count);
+ INIT_WORK(&chip->battery_age_work, battery_age_work);
+ INIT_WORK(&chip->update_esr_work, update_esr_value);
+ INIT_WORK(&chip->set_resume_soc_work, set_resume_soc_work);
+ INIT_WORK(&chip->sysfs_restart_work, sysfs_restart_work);
+ INIT_WORK(&chip->init_work, delayed_init_work);
+ INIT_WORK(&chip->charge_full_work, charge_full_work);
+ INIT_WORK(&chip->gain_comp_work, iadc_gain_comp_work);
+ INIT_WORK(&chip->bcl_hi_power_work, bcl_hi_power_work);
+ INIT_WORK(&chip->esr_extract_config_work, esr_extract_config_work);
+ alarm_init(&chip->fg_cap_learning_alarm, ALARM_BOOTTIME,
+ fg_cap_learning_alarm_cb);
+ init_completion(&chip->sram_access_granted);
+ init_completion(&chip->sram_access_revoked);
+ complete_all(&chip->sram_access_revoked);
+ init_completion(&chip->batt_id_avail);
+ init_completion(&chip->first_soc_done);
+ dev_set_drvdata(&pdev->dev, chip);
+
+ if (of_get_available_child_count(pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ rc = -ENXIO;
+ goto of_init_fail;
+ }
+
+ for_each_available_child_of_node(pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Couldn't find reg in node = %s rc = %d\n",
+ child->full_name, rc);
+ goto of_init_fail;
+ }
+
+ if (strcmp("qcom,fg-adc-vbat", child->name) == 0) {
+ chip->vbat_adc_addr = base;
+ continue;
+ } else if (strcmp("qcom,fg-adc-ibat", child->name) == 0) {
+ chip->ibat_adc_addr = base;
+ continue;
+ } else if (strcmp("qcom,revid-tp-rev", child->name) == 0) {
+ chip->tp_rev_addr = base;
+ continue;
+ }
+
+ rc = fg_read(chip, &subtype,
+ base + REG_OFFSET_PERP_SUBTYPE, 1);
+ if (rc) {
+ pr_err("Peripheral subtype read failed rc=%d\n", rc);
+ goto of_init_fail;
+ }
+
+ switch (subtype) {
+ case FG_SOC:
+ chip->soc_base = base;
+ break;
+ case FG_MEMIF:
+ chip->mem_base = base;
+ break;
+ case FG_BATT:
+ chip->batt_base = base;
+ break;
+ default:
+ pr_err("Invalid peripheral subtype=0x%x\n", subtype);
+ rc = -EINVAL;
+ }
+ }
+
+ rc = fg_detect_pmic_type(chip);
+ if (rc) {
+ pr_err("Unable to detect PMIC type rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = fg_setup_memif_offset(chip);
+ if (rc) {
+ pr_err("Unable to setup mem_if offsets rc=%d\n", rc);
+ goto of_init_fail;
+ }
+
+ rc = fg_of_init(chip);
+ if (rc) {
+ pr_err("failed to parse devicetree rc%d\n", rc);
+ goto of_init_fail;
+ }
+
+ if (chip->jeita_hysteresis_support) {
+ rc = fg_init_batt_temp_state(chip);
+ if (rc) {
+ pr_err("failed to get battery status rc%d\n", rc);
+ goto of_init_fail;
+ }
+ }
+
+ /* check if the first estimate is already finished at this time */
+ if (is_first_est_done(chip))
+ complete_all(&chip->first_soc_done);
+
+ reg = 0xFF;
+ rc = fg_write(chip, &reg, INT_EN_CLR(chip->mem_base), 1);
+ if (rc) {
+ pr_err("failed to clear interrupts %d\n", rc);
+ goto of_init_fail;
+ }
+
+ rc = fg_init_irqs(chip);
+ if (rc) {
+ pr_err("failed to request interrupts %d\n", rc);
+ goto cancel_work;
+ }
+
+ chip->batt_type = default_batt_type;
+
+ chip->bms_psy_d.name = "bms";
+ chip->bms_psy_d.type = POWER_SUPPLY_TYPE_BMS;
+ chip->bms_psy_d.properties = fg_power_props;
+ chip->bms_psy_d.num_properties = ARRAY_SIZE(fg_power_props);
+ chip->bms_psy_d.get_property = fg_power_get_property;
+ chip->bms_psy_d.set_property = fg_power_set_property;
+ chip->bms_psy_d.external_power_changed = fg_external_power_changed;
+ chip->bms_psy_d.property_is_writeable = fg_property_is_writeable;
+
+ bms_psy_cfg.drv_data = chip;
+ bms_psy_cfg.supplied_to = fg_supplicants;
+ bms_psy_cfg.num_supplicants = ARRAY_SIZE(fg_supplicants);
+ bms_psy_cfg.of_node = NULL;
+ chip->bms_psy = devm_power_supply_register(chip->dev,
+ &chip->bms_psy_d,
+ &bms_psy_cfg);
+ if (IS_ERR(chip->bms_psy)) {
+ pr_err("batt failed to register rc = %ld\n",
+ PTR_ERR(chip->bms_psy));
+ goto of_init_fail;
+ }
+ chip->power_supply_registered = true;
+ /*
+ * Just initialize the batt_psy_name here. Power supply
+ * will be obtained later.
+ */
+ chip->batt_psy_name = "battery";
+
+ if (chip->mem_base) {
+ rc = fg_dfs_create(chip);
+ if (rc < 0) {
+ pr_err("failed to create debugfs rc = %d\n", rc);
+ goto cancel_work;
+ }
+ }
+
+ schedule_work(&chip->init_work);
+
+ pr_info("FG Probe success - FG Revision DIG:%d.%d ANA:%d.%d PMIC subtype=%d\n",
+ chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
+ chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR],
+ chip->pmic_subtype);
+
+ return rc;
+
+cancel_work:
+ cancel_delayed_work_sync(&chip->update_jeita_setting);
+ cancel_delayed_work_sync(&chip->update_sram_data);
+ cancel_delayed_work_sync(&chip->update_temp_work);
+ cancel_delayed_work_sync(&chip->check_empty_work);
+ cancel_delayed_work_sync(&chip->batt_profile_init);
+ alarm_try_to_cancel(&chip->fg_cap_learning_alarm);
+ cancel_work_sync(&chip->set_resume_soc_work);
+ cancel_work_sync(&chip->fg_cap_learning_work);
+ cancel_work_sync(&chip->dump_sram);
+ cancel_work_sync(&chip->status_change_work);
+ cancel_work_sync(&chip->cycle_count_work);
+ cancel_work_sync(&chip->update_esr_work);
+ cancel_work_sync(&chip->rslow_comp_work);
+ cancel_work_sync(&chip->sysfs_restart_work);
+ cancel_work_sync(&chip->gain_comp_work);
+ cancel_work_sync(&chip->init_work);
+ cancel_work_sync(&chip->charge_full_work);
+ cancel_work_sync(&chip->bcl_hi_power_work);
+ cancel_work_sync(&chip->esr_extract_config_work);
+of_init_fail:
+ mutex_destroy(&chip->rslow_comp.lock);
+ mutex_destroy(&chip->rw_lock);
+ mutex_destroy(&chip->cyc_ctr.lock);
+ mutex_destroy(&chip->learning_data.learning_lock);
+ mutex_destroy(&chip->sysfs_restart_lock);
+ wakeup_source_trash(&chip->resume_soc_wakeup_source.source);
+ wakeup_source_trash(&chip->empty_check_wakeup_source.source);
+ wakeup_source_trash(&chip->memif_wakeup_source.source);
+ wakeup_source_trash(&chip->profile_wakeup_source.source);
+ wakeup_source_trash(&chip->update_temp_wakeup_source.source);
+ wakeup_source_trash(&chip->update_sram_wakeup_source.source);
+ wakeup_source_trash(&chip->gain_comp_wakeup_source.source);
+ wakeup_source_trash(&chip->capacity_learning_wakeup_source.source);
+ wakeup_source_trash(&chip->esr_extract_wakeup_source.source);
+ return rc;
+}
+
+static void check_and_update_sram_data(struct fg_chip *chip)
+{
+ unsigned long current_time = 0, next_update_time, time_left;
+
+ get_current_time(&current_time);
+
+ next_update_time = chip->last_temp_update_time
+ + (TEMP_PERIOD_UPDATE_MS / 1000);
+
+ if (next_update_time > current_time)
+ time_left = next_update_time - current_time;
+ else
+ time_left = 0;
+
+ schedule_delayed_work(
+ &chip->update_temp_work, msecs_to_jiffies(time_left * 1000));
+
+ next_update_time = chip->last_sram_update_time
+ + (fg_sram_update_period_ms / 1000);
+
+ if (next_update_time > current_time)
+ time_left = next_update_time - current_time;
+ else
+ time_left = 0;
+
+ schedule_delayed_work(
+ &chip->update_sram_data, msecs_to_jiffies(time_left * 1000));
+}
+
+static int fg_suspend(struct device *dev)
+{
+ struct fg_chip *chip = dev_get_drvdata(dev);
+
+ if (!chip->sw_rbias_ctrl)
+ return 0;
+
+ cancel_delayed_work(&chip->update_temp_work);
+ cancel_delayed_work(&chip->update_sram_data);
+
+ return 0;
+}
+
+static int fg_resume(struct device *dev)
+{
+ struct fg_chip *chip = dev_get_drvdata(dev);
+
+ if (!chip->sw_rbias_ctrl)
+ return 0;
+
+ check_and_update_sram_data(chip);
+ return 0;
+}
+
+static const struct dev_pm_ops qpnp_fg_pm_ops = {
+ .suspend = fg_suspend,
+ .resume = fg_resume,
+};
+
+static int fg_sense_type_set(const char *val, const struct kernel_param *kp)
+{
+ int rc;
+ struct power_supply *bms_psy;
+ struct fg_chip *chip;
+ int old_fg_sense_type = fg_sense_type;
+
+ rc = param_set_int(val, kp);
+ if (rc) {
+ pr_err("Unable to set fg_sense_type: %d\n", rc);
+ return rc;
+ }
+
+ if (fg_sense_type != 0 && fg_sense_type != 1) {
+ pr_err("Bad value %d\n", fg_sense_type);
+ fg_sense_type = old_fg_sense_type;
+ return -EINVAL;
+ }
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("fg_sense_type set to %d\n", fg_sense_type);
+
+ bms_psy = power_supply_get_by_name("bms");
+ if (!bms_psy) {
+ pr_err("bms psy not found\n");
+ return 0;
+ }
+
+ chip = power_supply_get_drvdata(bms_psy);
+ rc = set_prop_sense_type(chip, fg_sense_type);
+ return rc;
+}
+
+static struct kernel_param_ops fg_sense_type_ops = {
+ .set = fg_sense_type_set,
+ .get = param_get_int,
+};
+
+module_param_cb(sense_type, &fg_sense_type_ops, &fg_sense_type, 0644);
+
+static int fg_restart_set(const char *val, const struct kernel_param *kp)
+{
+ struct power_supply *bms_psy;
+ struct fg_chip *chip;
+
+ bms_psy = power_supply_get_by_name("bms");
+ if (!bms_psy) {
+ pr_err("bms psy not found\n");
+ return 0;
+ }
+ chip = power_supply_get_drvdata(bms_psy);
+
+ mutex_lock(&chip->sysfs_restart_lock);
+ if (fg_restart != 0) {
+ mutex_unlock(&chip->sysfs_restart_lock);
+ return 0;
+ }
+ fg_restart = 1;
+ mutex_unlock(&chip->sysfs_restart_lock);
+
+ if (fg_debug_mask & FG_STATUS)
+ pr_info("fuel gauge restart initiated from sysfs...\n");
+
+ schedule_work(&chip->sysfs_restart_work);
+ return 0;
+}
+
+static struct kernel_param_ops fg_restart_ops = {
+ .set = fg_restart_set,
+ .get = param_get_int,
+};
+
+module_param_cb(restart, &fg_restart_ops, &fg_restart, 0644);
+
+static struct platform_driver fg_driver = {
+ .driver = {
+ .name = QPNP_FG_DEV_NAME,
+ .of_match_table = fg_match_table,
+ .pm = &qpnp_fg_pm_ops,
+ },
+ .probe = fg_probe,
+ .remove = fg_remove,
+};
+
+static int __init fg_init(void)
+{
+ return platform_driver_register(&fg_driver);
+}
+
+static void __exit fg_exit(void)
+{
+ return platform_driver_unregister(&fg_driver);
+}
+
+module_init(fg_init);
+module_exit(fg_exit);
+
+MODULE_DESCRIPTION("QPNP Fuel Gauge Driver");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:" QPNP_FG_DEV_NAME);
diff --git a/drivers/power/supply/qcom/qpnp-smb2.c b/drivers/power/supply/qcom/qpnp-smb2.c
index ea205100644d..4beaddff47b3 100644
--- a/drivers/power/supply/qcom/qpnp-smb2.c
+++ b/drivers/power/supply/qcom/qpnp-smb2.c
@@ -188,6 +188,11 @@ static int __weak_chg_icl_ua = 500000;
module_param_named(
weak_chg_icl_ua, __weak_chg_icl_ua, int, S_IRUSR | S_IWUSR);
+static int __try_sink_enabled = 1;
+module_param_named(
+ try_sink_enabled, __try_sink_enabled, int, 0600
+);
+
#define MICRO_1P5A 1500000
#define MICRO_P1A 100000
#define OTG_DEFAULT_DEGLITCH_TIME_MS 50
@@ -1658,6 +1663,18 @@ static int smb2_init_hw(struct smb2 *chip)
return rc;
}
+ /*
+ * allow DRP.DFP time to exceed by tPDdebounce time.
+ */
+ rc = smblib_masked_write(chg, TAPER_TIMER_SEL_CFG_REG,
+ TYPEC_DRP_DFP_TIME_CFG_BIT,
+ TYPEC_DRP_DFP_TIME_CFG_BIT);
+ if (rc < 0) {
+ dev_err(chg->dev, "Couldn't configure DRP.DFP time rc=%d\n",
+ rc);
+ return rc;
+ }
+
/* configure float charger options */
switch (chip->dt.float_option) {
case 1:
@@ -2236,6 +2253,7 @@ static int smb2_probe(struct platform_device *pdev)
chg->dev = &pdev->dev;
chg->param = v1_params;
chg->debug_mask = &__debug_mask;
+ chg->try_sink_enabled = &__try_sink_enabled;
chg->weak_chg_icl_ua = &__weak_chg_icl_ua;
chg->mode = PARALLEL_MASTER;
chg->irq_info = smb2_irqs;
diff --git a/drivers/power/supply/qcom/qpnp-smbcharger.c b/drivers/power/supply/qcom/qpnp-smbcharger.c
new file mode 100644
index 000000000000..a2863dcf7389
--- /dev/null
+++ b/drivers/power/supply/qcom/qpnp-smbcharger.c
@@ -0,0 +1,8472 @@
+/* Copyright (c) 2014-2016 The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+#define pr_fmt(fmt) "SMBCHG: %s: " fmt, __func__
+
+#include <linux/regmap.h>
+#include <linux/spinlock.h>
+#include <linux/gpio.h>
+#include <linux/errno.h>
+#include <linux/delay.h>
+#include <linux/module.h>
+#include <linux/interrupt.h>
+#include <linux/slab.h>
+#include <linux/sched.h>
+#include <linux/power_supply.h>
+#include <linux/of.h>
+#include <linux/of_gpio.h>
+#include <linux/of_irq.h>
+#include <linux/bitops.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/of_regulator.h>
+#include <linux/regulator/machine.h>
+#include <linux/spmi.h>
+#include <linux/platform_device.h>
+#include <linux/printk.h>
+#include <linux/ratelimit.h>
+#include <linux/debugfs.h>
+#include <linux/leds.h>
+#include <linux/rtc.h>
+#include <linux/qpnp/qpnp-adc.h>
+#include <linux/batterydata-lib.h>
+#include <linux/of_batterydata.h>
+#include <linux/msm_bcl.h>
+#include <linux/ktime.h>
+#include <linux/extcon.h>
+#include <linux/pmic-voter.h>
+
+/* Mask/Bit helpers */
+#define _SMB_MASK(BITS, POS) \
+ ((unsigned char)(((1 << (BITS)) - 1) << (POS)))
+#define SMB_MASK(LEFT_BIT_POS, RIGHT_BIT_POS) \
+ _SMB_MASK((LEFT_BIT_POS) - (RIGHT_BIT_POS) + 1, \
+ (RIGHT_BIT_POS))
+/* Config registers */
+struct smbchg_regulator {
+ struct regulator_desc rdesc;
+ struct regulator_dev *rdev;
+};
+
+struct parallel_usb_cfg {
+ struct power_supply *psy;
+ int min_current_thr_ma;
+ int min_9v_current_thr_ma;
+ int allowed_lowering_ma;
+ int current_max_ma;
+ bool avail;
+ struct mutex lock;
+ int initial_aicl_ma;
+ ktime_t last_disabled;
+ bool enabled_once;
+};
+
+struct ilim_entry {
+ int vmin_uv;
+ int vmax_uv;
+ int icl_pt_ma;
+ int icl_lv_ma;
+ int icl_hv_ma;
+};
+
+struct ilim_map {
+ int num;
+ struct ilim_entry *entries;
+};
+
+struct smbchg_version_tables {
+ const int *dc_ilim_ma_table;
+ int dc_ilim_ma_len;
+ const int *usb_ilim_ma_table;
+ int usb_ilim_ma_len;
+ const int *iterm_ma_table;
+ int iterm_ma_len;
+ const int *fcc_comp_table;
+ int fcc_comp_len;
+ const int *aicl_rerun_period_table;
+ int aicl_rerun_period_len;
+ int rchg_thr_mv;
+};
+
+struct smbchg_chip {
+ struct device *dev;
+ struct platform_device *pdev;
+ struct regmap *regmap;
+ int schg_version;
+
+ /* peripheral register address bases */
+ u16 chgr_base;
+ u16 bat_if_base;
+ u16 usb_chgpth_base;
+ u16 dc_chgpth_base;
+ u16 otg_base;
+ u16 misc_base;
+
+ int fake_battery_soc;
+ u8 revision[4];
+
+ /* configuration parameters */
+ int iterm_ma;
+ int usb_max_current_ma;
+ int typec_current_ma;
+ int dc_max_current_ma;
+ int dc_target_current_ma;
+ int cfg_fastchg_current_ma;
+ int fastchg_current_ma;
+ int vfloat_mv;
+ int fastchg_current_comp;
+ int float_voltage_comp;
+ int resume_delta_mv;
+ int safety_time;
+ int prechg_safety_time;
+ int bmd_pin_src;
+ int jeita_temp_hard_limit;
+ int aicl_rerun_period_s;
+ bool use_vfloat_adjustments;
+ bool iterm_disabled;
+ bool bmd_algo_disabled;
+ bool soft_vfloat_comp_disabled;
+ bool chg_enabled;
+ bool charge_unknown_battery;
+ bool chg_inhibit_en;
+ bool chg_inhibit_source_fg;
+ bool low_volt_dcin;
+ bool cfg_chg_led_support;
+ bool cfg_chg_led_sw_ctrl;
+ bool vbat_above_headroom;
+ bool force_aicl_rerun;
+ bool hvdcp3_supported;
+ bool restricted_charging;
+ bool skip_usb_suspend_for_fake_battery;
+ bool hvdcp_not_supported;
+ bool otg_pinctrl;
+ u8 original_usbin_allowance;
+ struct parallel_usb_cfg parallel;
+ struct delayed_work parallel_en_work;
+ struct dentry *debug_root;
+ struct smbchg_version_tables tables;
+
+ /* wipower params */
+ struct ilim_map wipower_default;
+ struct ilim_map wipower_pt;
+ struct ilim_map wipower_div2;
+ struct qpnp_vadc_chip *vadc_dev;
+ bool wipower_dyn_icl_avail;
+ struct ilim_entry current_ilim;
+ struct mutex wipower_config;
+ bool wipower_configured;
+ struct qpnp_adc_tm_btm_param param;
+
+ /* flash current prediction */
+ int rpara_uohm;
+ int rslow_uohm;
+ int vled_max_uv;
+
+ /* vfloat adjustment */
+ int max_vbat_sample;
+ int n_vbat_samples;
+
+ /* status variables */
+ int wake_reasons;
+ int previous_soc;
+ int usb_online;
+ bool dc_present;
+ bool usb_present;
+ bool batt_present;
+ int otg_retries;
+ ktime_t otg_enable_time;
+ bool aicl_deglitch_short;
+ bool safety_timer_en;
+ bool aicl_complete;
+ bool usb_ov_det;
+ bool otg_pulse_skip_dis;
+ const char *battery_type;
+ enum power_supply_type usb_supply_type;
+ bool very_weak_charger;
+ bool parallel_charger_detected;
+ bool chg_otg_enabled;
+ bool flash_triggered;
+ bool flash_active;
+ bool icl_disabled;
+ u32 wa_flags;
+ int usb_icl_delta;
+ bool typec_dfp;
+ unsigned int usb_current_max;
+ unsigned int usb_health;
+
+ /* jeita and temperature */
+ bool batt_hot;
+ bool batt_cold;
+ bool batt_warm;
+ bool batt_cool;
+ unsigned int thermal_levels;
+ unsigned int therm_lvl_sel;
+ unsigned int *thermal_mitigation;
+
+ /* irqs */
+ int batt_hot_irq;
+ int batt_warm_irq;
+ int batt_cool_irq;
+ int batt_cold_irq;
+ int batt_missing_irq;
+ int vbat_low_irq;
+ int chg_hot_irq;
+ int chg_term_irq;
+ int taper_irq;
+ bool taper_irq_enabled;
+ struct mutex taper_irq_lock;
+ int recharge_irq;
+ int fastchg_irq;
+ int wdog_timeout_irq;
+ int power_ok_irq;
+ int dcin_uv_irq;
+ int usbin_uv_irq;
+ int usbin_ov_irq;
+ int src_detect_irq;
+ int otg_fail_irq;
+ int otg_oc_irq;
+ int aicl_done_irq;
+ int usbid_change_irq;
+ int chg_error_irq;
+ bool enable_aicl_wake;
+
+ /* psy */
+ struct power_supply_desc usb_psy_d;
+ struct power_supply *usb_psy;
+ struct power_supply_desc batt_psy_d;
+ struct power_supply *batt_psy;
+ struct power_supply_desc dc_psy_d;
+ struct power_supply *dc_psy;
+ struct power_supply *bms_psy;
+ struct power_supply *typec_psy;
+ int dc_psy_type;
+ const char *bms_psy_name;
+ const char *battery_psy_name;
+
+ struct regulator *dpdm_reg;
+ struct smbchg_regulator otg_vreg;
+ struct smbchg_regulator ext_otg_vreg;
+ struct work_struct usb_set_online_work;
+ struct delayed_work vfloat_adjust_work;
+ struct delayed_work hvdcp_det_work;
+ spinlock_t sec_access_lock;
+ struct mutex therm_lvl_lock;
+ struct mutex usb_set_online_lock;
+ struct mutex pm_lock;
+ /* aicl deglitch workaround */
+ unsigned long first_aicl_seconds;
+ int aicl_irq_count;
+ struct mutex usb_status_lock;
+ bool hvdcp_3_det_ignore_uv;
+ struct completion src_det_lowered;
+ struct completion src_det_raised;
+ struct completion usbin_uv_lowered;
+ struct completion usbin_uv_raised;
+ int pulse_cnt;
+ struct led_classdev led_cdev;
+ bool skip_usb_notification;
+ u32 vchg_adc_channel;
+ struct qpnp_vadc_chip *vchg_vadc_dev;
+
+ /* voters */
+ struct votable *fcc_votable;
+ struct votable *usb_icl_votable;
+ struct votable *dc_icl_votable;
+ struct votable *usb_suspend_votable;
+ struct votable *dc_suspend_votable;
+ struct votable *battchg_suspend_votable;
+ struct votable *hw_aicl_rerun_disable_votable;
+ struct votable *hw_aicl_rerun_enable_indirect_votable;
+ struct votable *aicl_deglitch_short_votable;
+
+ /* extcon for VBUS / ID notification to USB */
+ struct extcon_dev *extcon;
+};
+
+enum qpnp_schg {
+ QPNP_SCHG,
+ QPNP_SCHG_LITE,
+};
+
+static char *version_str[] = {
+ [QPNP_SCHG] = "SCHG",
+ [QPNP_SCHG_LITE] = "SCHG_LITE",
+};
+
+enum pmic_subtype {
+ PMI8994 = 10,
+ PMI8950 = 17,
+ PMI8996 = 19,
+ PMI8937 = 55,
+};
+
+enum smbchg_wa {
+ SMBCHG_AICL_DEGLITCH_WA = BIT(0),
+ SMBCHG_HVDCP_9V_EN_WA = BIT(1),
+ SMBCHG_USB100_WA = BIT(2),
+ SMBCHG_BATT_OV_WA = BIT(3),
+ SMBCHG_CC_ESR_WA = BIT(4),
+ SMBCHG_FLASH_ICL_DISABLE_WA = BIT(5),
+ SMBCHG_RESTART_WA = BIT(6),
+ SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA = BIT(7),
+};
+
+enum print_reason {
+ PR_REGISTER = BIT(0),
+ PR_INTERRUPT = BIT(1),
+ PR_STATUS = BIT(2),
+ PR_DUMP = BIT(3),
+ PR_PM = BIT(4),
+ PR_MISC = BIT(5),
+ PR_WIPOWER = BIT(6),
+ PR_TYPEC = BIT(7),
+};
+
+enum wake_reason {
+ PM_PARALLEL_CHECK = BIT(0),
+ PM_REASON_VFLOAT_ADJUST = BIT(1),
+ PM_ESR_PULSE = BIT(2),
+ PM_PARALLEL_TAPER = BIT(3),
+ PM_DETECT_HVDCP = BIT(4),
+};
+
+/* fcc_voters */
+#define ESR_PULSE_FCC_VOTER "ESR_PULSE_FCC_VOTER"
+#define BATT_TYPE_FCC_VOTER "BATT_TYPE_FCC_VOTER"
+#define RESTRICTED_CHG_FCC_VOTER "RESTRICTED_CHG_FCC_VOTER"
+
+/* ICL VOTERS */
+#define PSY_ICL_VOTER "PSY_ICL_VOTER"
+#define THERMAL_ICL_VOTER "THERMAL_ICL_VOTER"
+#define HVDCP_ICL_VOTER "HVDCP_ICL_VOTER"
+#define USER_ICL_VOTER "USER_ICL_VOTER"
+#define WEAK_CHARGER_ICL_VOTER "WEAK_CHARGER_ICL_VOTER"
+#define SW_AICL_ICL_VOTER "SW_AICL_ICL_VOTER"
+#define CHG_SUSPEND_WORKAROUND_ICL_VOTER "CHG_SUSPEND_WORKAROUND_ICL_VOTER"
+
+/* USB SUSPEND VOTERS */
+/* userspace has suspended charging altogether */
+#define USER_EN_VOTER "USER_EN_VOTER"
+/*
+ * this specific path has been suspended through the power supply
+ * framework
+ */
+#define POWER_SUPPLY_EN_VOTER "POWER_SUPPLY_EN_VOTER"
+/*
+ * the usb driver has suspended this path by setting a current limit
+ * of < 2MA
+ */
+#define USB_EN_VOTER "USB_EN_VOTER"
+/*
+ * the thermal daemon can suspend a charge path when the system
+ * temperature levels rise
+ */
+#define THERMAL_EN_VOTER "THERMAL_EN_VOTER"
+/*
+ * an external OTG supply is being used, suspend charge path so the
+ * charger does not accidentally try to charge from the external supply.
+ */
+#define OTG_EN_VOTER "OTG_EN_VOTER"
+/*
+ * the charger is very weak, do not draw any current from it
+ */
+#define WEAK_CHARGER_EN_VOTER "WEAK_CHARGER_EN_VOTER"
+/*
+ * fake battery voter, if battery id-resistance around 7.5 Kohm
+ */
+#define FAKE_BATTERY_EN_VOTER "FAKE_BATTERY_EN_VOTER"
+
+/* battchg_enable_voters */
+ /* userspace has disabled battery charging */
+#define BATTCHG_USER_EN_VOTER "BATTCHG_USER_EN_VOTER"
+ /* battery charging disabled while loading battery profiles */
+#define BATTCHG_UNKNOWN_BATTERY_EN_VOTER "BATTCHG_UNKNOWN_BATTERY_EN_VOTER"
+
+/* hw_aicl_rerun_enable_indirect_voters */
+/* enabled via device tree */
+#define DEFAULT_CONFIG_HW_AICL_VOTER "DEFAULT_CONFIG_HW_AICL_VOTER"
+/* Varb workaround voter */
+#define VARB_WORKAROUND_VOTER "VARB_WORKAROUND_VOTER"
+/* SHUTDOWN workaround voter */
+#define SHUTDOWN_WORKAROUND_VOTER "SHUTDOWN_WORKAROUND_VOTER"
+
+/* hw_aicl_rerun_disable_voters */
+/* the results from enabling clients */
+#define HW_AICL_RERUN_ENABLE_INDIRECT_VOTER \
+ "HW_AICL_RERUN_ENABLE_INDIRECT_VOTER"
+/* Weak charger voter */
+#define WEAK_CHARGER_HW_AICL_VOTER "WEAK_CHARGER_HW_AICL_VOTER"
+
+/* aicl_short_deglitch_voters */
+/* Varb workaround voter */
+#define VARB_WORKAROUND_SHORT_DEGLITCH_VOTER \
+ "VARB_WRKARND_SHORT_DEGLITCH_VOTER"
+/* QC 2.0 */
+#define HVDCP_SHORT_DEGLITCH_VOTER "HVDCP_SHORT_DEGLITCH_VOTER"
+
+static const unsigned int smbchg_extcon_cable[] = {
+ EXTCON_USB,
+ EXTCON_USB_HOST,
+ EXTCON_NONE,
+};
+
+static int smbchg_debug_mask;
+module_param_named(
+ debug_mask, smbchg_debug_mask, int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_parallel_en = 1;
+module_param_named(
+ parallel_en, smbchg_parallel_en, int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_main_chg_fcc_percent = 50;
+module_param_named(
+ main_chg_fcc_percent, smbchg_main_chg_fcc_percent,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_main_chg_icl_percent = 60;
+module_param_named(
+ main_chg_icl_percent, smbchg_main_chg_icl_percent,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_default_hvdcp_icl_ma = 1800;
+module_param_named(
+ default_hvdcp_icl_ma, smbchg_default_hvdcp_icl_ma,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_default_hvdcp3_icl_ma = 3000;
+module_param_named(
+ default_hvdcp3_icl_ma, smbchg_default_hvdcp3_icl_ma,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int smbchg_default_dcp_icl_ma = 1800;
+module_param_named(
+ default_dcp_icl_ma, smbchg_default_dcp_icl_ma,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int wipower_dyn_icl_en;
+module_param_named(
+ dynamic_icl_wipower_en, wipower_dyn_icl_en,
+ int, S_IRUSR | S_IWUSR
+);
+
+static int wipower_dcin_interval = ADC_MEAS1_INTERVAL_2P0MS;
+module_param_named(
+ wipower_dcin_interval, wipower_dcin_interval,
+ int, S_IRUSR | S_IWUSR
+);
+
+#define WIPOWER_DEFAULT_HYSTERISIS_UV 250000
+static int wipower_dcin_hyst_uv = WIPOWER_DEFAULT_HYSTERISIS_UV;
+module_param_named(
+ wipower_dcin_hyst_uv, wipower_dcin_hyst_uv,
+ int, S_IRUSR | S_IWUSR
+);
+
+#define pr_smb(reason, fmt, ...) \
+ do { \
+ if (smbchg_debug_mask & (reason)) \
+ pr_info(fmt, ##__VA_ARGS__); \
+ else \
+ pr_debug(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define pr_smb_rt(reason, fmt, ...) \
+ do { \
+ if (smbchg_debug_mask & (reason)) \
+ pr_info_ratelimited(fmt, ##__VA_ARGS__); \
+ else \
+ pr_debug(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+static int smbchg_read(struct smbchg_chip *chip, u8 *val,
+ u16 addr, int count)
+{
+ int rc = 0;
+ struct platform_device *pdev = chip->pdev;
+
+ if (addr == 0) {
+ dev_err(chip->dev, "addr cannot be zero addr=0x%02x sid=0x%02x rc=%d\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid, rc);
+ return -EINVAL;
+ }
+
+ rc = regmap_bulk_read(chip->regmap, addr, val, count);
+ if (rc) {
+ dev_err(chip->dev, "spmi read failed addr=0x%02x sid=0x%02x rc=%d\n",
+ addr, to_spmi_device(pdev->dev.parent)->usid,
+ rc);
+ return rc;
+ }
+ return 0;
+}
+
+/*
+ * Writes a register to the specified by the base and limited by the bit mask
+ *
+ * Do not use this function for register writes if possible. Instead use the
+ * smbchg_masked_write function.
+ *
+ * The sec_access_lock must be held for all register writes and this function
+ * does not do that. If this function is used, please hold the spinlock or
+ * random secure access writes may fail.
+ */
+static int smbchg_masked_write_raw(struct smbchg_chip *chip, u16 base, u8 mask,
+ u8 val)
+{
+ int rc;
+
+ rc = regmap_update_bits(chip->regmap, base, mask, val);
+ if (rc) {
+ dev_err(chip->dev, "spmi write failed: addr=%03X, rc=%d\n",
+ base, rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+/*
+ * Writes a register to the specified by the base and limited by the bit mask
+ *
+ * This function holds a spin lock to ensure secure access register writes goes
+ * through. If the secure access unlock register is armed, any old register
+ * write can unarm the secure access unlock, causing the next write to fail.
+ *
+ * Note: do not use this for sec_access registers. Instead use the function
+ * below: smbchg_sec_masked_write
+ */
+static int smbchg_masked_write(struct smbchg_chip *chip, u16 base, u8 mask,
+ u8 val)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_irqsave(&chip->sec_access_lock, flags);
+ rc = smbchg_masked_write_raw(chip, base, mask, val);
+ spin_unlock_irqrestore(&chip->sec_access_lock, flags);
+
+ return rc;
+}
+
+/*
+ * Unlocks sec access and writes to the register specified.
+ *
+ * This function holds a spin lock to exclude other register writes while
+ * the two writes are taking place.
+ */
+#define SEC_ACCESS_OFFSET 0xD0
+#define SEC_ACCESS_VALUE 0xA5
+#define PERIPHERAL_MASK 0xFF
+static int smbchg_sec_masked_write(struct smbchg_chip *chip, u16 base, u8 mask,
+ u8 val)
+{
+ unsigned long flags;
+ int rc;
+ u16 peripheral_base = base & (~PERIPHERAL_MASK);
+
+ spin_lock_irqsave(&chip->sec_access_lock, flags);
+
+ rc = smbchg_masked_write_raw(chip, peripheral_base + SEC_ACCESS_OFFSET,
+ SEC_ACCESS_VALUE, SEC_ACCESS_VALUE);
+ if (rc) {
+ dev_err(chip->dev, "Unable to unlock sec_access: %d", rc);
+ goto out;
+ }
+
+ rc = smbchg_masked_write_raw(chip, base, mask, val);
+
+out:
+ spin_unlock_irqrestore(&chip->sec_access_lock, flags);
+ return rc;
+}
+
+static void smbchg_stay_awake(struct smbchg_chip *chip, int reason)
+{
+ int reasons;
+
+ mutex_lock(&chip->pm_lock);
+ reasons = chip->wake_reasons | reason;
+ if (reasons != 0 && chip->wake_reasons == 0) {
+ pr_smb(PR_PM, "staying awake: 0x%02x (bit %d)\n",
+ reasons, reason);
+ pm_stay_awake(chip->dev);
+ }
+ chip->wake_reasons = reasons;
+ mutex_unlock(&chip->pm_lock);
+}
+
+static void smbchg_relax(struct smbchg_chip *chip, int reason)
+{
+ int reasons;
+
+ mutex_lock(&chip->pm_lock);
+ reasons = chip->wake_reasons & (~reason);
+ if (reasons == 0 && chip->wake_reasons != 0) {
+ pr_smb(PR_PM, "relaxing: 0x%02x (bit %d)\n",
+ reasons, reason);
+ pm_relax(chip->dev);
+ }
+ chip->wake_reasons = reasons;
+ mutex_unlock(&chip->pm_lock);
+};
+
+enum pwr_path_type {
+ UNKNOWN = 0,
+ PWR_PATH_BATTERY = 1,
+ PWR_PATH_USB = 2,
+ PWR_PATH_DC = 3,
+};
+
+#define PWR_PATH 0x08
+#define PWR_PATH_MASK 0x03
+static enum pwr_path_type smbchg_get_pwr_path(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + PWR_PATH, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read PWR_PATH rc = %d\n", rc);
+ return PWR_PATH_BATTERY;
+ }
+
+ return reg & PWR_PATH_MASK;
+}
+
+#define RID_STS 0xB
+#define RID_MASK 0xF
+#define IDEV_STS 0x8
+#define RT_STS 0x10
+#define USBID_MSB 0xE
+#define USBIN_UV_BIT BIT(0)
+#define USBIN_OV_BIT BIT(1)
+#define USBIN_SRC_DET_BIT BIT(2)
+#define FMB_STS_MASK SMB_MASK(3, 0)
+#define USBID_GND_THRESHOLD 0x495
+static bool is_otg_present_schg(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+ u8 usbid_reg[2];
+ u16 usbid_val;
+ /*
+ * After the falling edge of the usbid change interrupt occurs,
+ * there may still be some time before the ADC conversion for USB RID
+ * finishes in the fuel gauge. In the worst case, this could be up to
+ * 15 ms.
+ *
+ * Sleep for 20 ms (minimum msleep time) to wait for the conversion to
+ * finish and the USB RID status register to be updated before trying
+ * to detect OTG insertions.
+ */
+
+ msleep(20);
+
+ /*
+ * There is a problem with USBID conversions on PMI8994 revisions
+ * 2.0.0. As a workaround, check that the cable is not
+ * detected as factory test before enabling OTG.
+ */
+ rc = smbchg_read(chip, &reg, chip->misc_base + IDEV_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read IDEV_STS rc = %d\n", rc);
+ return false;
+ }
+
+ if ((reg & FMB_STS_MASK) != 0) {
+ pr_smb(PR_STATUS, "IDEV_STS = %02x, not ground\n", reg);
+ return false;
+ }
+
+ rc = smbchg_read(chip, usbid_reg, chip->usb_chgpth_base + USBID_MSB, 2);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read USBID rc = %d\n", rc);
+ return false;
+ }
+ usbid_val = (usbid_reg[0] << 8) | usbid_reg[1];
+
+ if (usbid_val > USBID_GND_THRESHOLD) {
+ pr_smb(PR_STATUS, "USBID = 0x%04x, too high to be ground\n",
+ usbid_val);
+ return false;
+ }
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RID_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read usb rid status rc = %d\n", rc);
+ return false;
+ }
+
+ pr_smb(PR_STATUS, "RID_STS = %02x\n", reg);
+
+ return (reg & RID_MASK) == 0;
+}
+
+#define RID_GND_DET_STS BIT(2)
+static bool is_otg_present_schg_lite(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->otg_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read otg RT status rc = %d\n", rc);
+ return false;
+ }
+
+ return !!(reg & RID_GND_DET_STS);
+}
+
+static bool is_otg_present(struct smbchg_chip *chip)
+{
+ if (chip->schg_version == QPNP_SCHG_LITE)
+ return is_otg_present_schg_lite(chip);
+
+ return is_otg_present_schg(chip);
+}
+
+#define USBIN_9V BIT(5)
+#define USBIN_UNREG BIT(4)
+#define USBIN_LV BIT(3)
+#define DCIN_9V BIT(2)
+#define DCIN_UNREG BIT(1)
+#define DCIN_LV BIT(0)
+#define INPUT_STS 0x0D
+#define DCIN_UV_BIT BIT(0)
+#define DCIN_OV_BIT BIT(1)
+static bool is_dc_present(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->dc_chgpth_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read dc status rc = %d\n", rc);
+ return false;
+ }
+
+ if ((reg & DCIN_UV_BIT) || (reg & DCIN_OV_BIT))
+ return false;
+
+ return true;
+}
+
+static bool is_usb_present(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc);
+ return false;
+ }
+ if (!(reg & USBIN_SRC_DET_BIT) || (reg & USBIN_OV_BIT))
+ return false;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + INPUT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc);
+ return false;
+ }
+
+ return !!(reg & (USBIN_9V | USBIN_UNREG | USBIN_LV));
+}
+
+static char *usb_type_str[] = {
+ "SDP", /* bit 0 */
+ "OTHER", /* bit 1 */
+ "DCP", /* bit 2 */
+ "CDP", /* bit 3 */
+ "NONE", /* bit 4 error case */
+};
+
+#define N_TYPE_BITS 4
+#define TYPE_BITS_OFFSET 4
+
+static int get_type(u8 type_reg)
+{
+ unsigned long type = type_reg;
+ type >>= TYPE_BITS_OFFSET;
+ return find_first_bit(&type, N_TYPE_BITS);
+}
+
+/* helper to return the string of USB type */
+static inline char *get_usb_type_name(int type)
+{
+ return usb_type_str[type];
+}
+
+static enum power_supply_type usb_type_enum[] = {
+ POWER_SUPPLY_TYPE_USB, /* bit 0 */
+ POWER_SUPPLY_TYPE_USB_DCP, /* bit 1 */
+ POWER_SUPPLY_TYPE_USB_DCP, /* bit 2 */
+ POWER_SUPPLY_TYPE_USB_CDP, /* bit 3 */
+ POWER_SUPPLY_TYPE_USB_DCP, /* bit 4 error case, report DCP */
+};
+
+/* helper to return enum power_supply_type of USB type */
+static inline enum power_supply_type get_usb_supply_type(int type)
+{
+ return usb_type_enum[type];
+}
+
+static bool is_src_detect_high(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc);
+ return false;
+ }
+ return reg &= USBIN_SRC_DET_BIT;
+}
+
+static void read_usb_type(struct smbchg_chip *chip, char **usb_type_name,
+ enum power_supply_type *usb_supply_type)
+{
+ int rc, type;
+ u8 reg;
+
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low\n");
+ *usb_type_name = "Absent";
+ *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ return;
+ }
+
+ rc = smbchg_read(chip, &reg, chip->misc_base + IDEV_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
+ *usb_type_name = "Other";
+ *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN;
+ return;
+ }
+ type = get_type(reg);
+ *usb_type_name = get_usb_type_name(type);
+ *usb_supply_type = get_usb_supply_type(type);
+}
+
+#define CHGR_STS 0x0E
+#define BATT_LESS_THAN_2V BIT(4)
+#define CHG_HOLD_OFF_BIT BIT(3)
+#define CHG_TYPE_MASK SMB_MASK(2, 1)
+#define CHG_TYPE_SHIFT 1
+#define BATT_NOT_CHG_VAL 0x0
+#define BATT_PRE_CHG_VAL 0x1
+#define BATT_FAST_CHG_VAL 0x2
+#define BATT_TAPER_CHG_VAL 0x3
+#define CHG_INHIBIT_BIT BIT(1)
+#define BAT_TCC_REACHED_BIT BIT(7)
+static int get_prop_batt_status(struct smbchg_chip *chip)
+{
+ int rc, status = POWER_SUPPLY_STATUS_DISCHARGING;
+ u8 reg = 0, chg_type;
+ bool charger_present, chg_inhibit;
+
+ charger_present = is_usb_present(chip) | is_dc_present(chip) |
+ chip->hvdcp_3_det_ignore_uv;
+ if (!charger_present)
+ return POWER_SUPPLY_STATUS_DISCHARGING;
+
+ rc = smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ if (reg & BAT_TCC_REACHED_BIT)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ chg_inhibit = reg & CHG_INHIBIT_BIT;
+ if (chg_inhibit)
+ return POWER_SUPPLY_STATUS_FULL;
+
+ rc = smbchg_read(chip, &reg, chip->chgr_base + CHGR_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
+ return POWER_SUPPLY_STATUS_UNKNOWN;
+ }
+
+ if (reg & CHG_HOLD_OFF_BIT) {
+ /*
+ * when chg hold off happens the battery is
+ * not charging
+ */
+ status = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ goto out;
+ }
+
+ chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+
+ if (chg_type == BATT_NOT_CHG_VAL && !chip->hvdcp_3_det_ignore_uv)
+ status = POWER_SUPPLY_STATUS_DISCHARGING;
+ else
+ status = POWER_SUPPLY_STATUS_CHARGING;
+out:
+ pr_smb_rt(PR_MISC, "CHGR_STS = 0x%02x\n", reg);
+ return status;
+}
+
+#define BAT_PRES_STATUS 0x08
+#define BAT_PRES_BIT BIT(7)
+static int get_prop_batt_present(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->bat_if_base + BAT_PRES_STATUS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
+ return 0;
+ }
+
+ return !!(reg & BAT_PRES_BIT);
+}
+
+static int get_prop_charge_type(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg, chg_type;
+
+ rc = smbchg_read(chip, &reg, chip->chgr_base + CHGR_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc);
+ return 0;
+ }
+
+ chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT;
+ if (chg_type == BATT_NOT_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+ else if (chg_type == BATT_TAPER_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_TAPER;
+ else if (chg_type == BATT_FAST_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_FAST;
+ else if (chg_type == BATT_PRE_CHG_VAL)
+ return POWER_SUPPLY_CHARGE_TYPE_TRICKLE;
+
+ return POWER_SUPPLY_CHARGE_TYPE_NONE;
+}
+
+static int set_property_on_fg(struct smbchg_chip *chip,
+ enum power_supply_property prop, int val)
+{
+ int rc;
+ union power_supply_propval ret = {0, };
+
+ if (!chip->bms_psy && chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+ if (!chip->bms_psy) {
+ pr_smb(PR_STATUS, "no bms psy found\n");
+ return -EINVAL;
+ }
+
+ ret.intval = val;
+ rc = power_supply_set_property(chip->bms_psy, prop, &ret);
+ if (rc)
+ pr_smb(PR_STATUS,
+ "bms psy does not allow updating prop %d rc = %d\n",
+ prop, rc);
+
+ return rc;
+}
+
+static int get_property_from_fg(struct smbchg_chip *chip,
+ enum power_supply_property prop, int *val)
+{
+ int rc;
+ union power_supply_propval ret = {0, };
+
+ if (!chip->bms_psy && chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+ if (!chip->bms_psy) {
+ pr_smb(PR_STATUS, "no bms psy found\n");
+ return -EINVAL;
+ }
+
+ rc = power_supply_get_property(chip->bms_psy, prop, &ret);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "bms psy doesn't support reading prop %d rc = %d\n",
+ prop, rc);
+ return rc;
+ }
+
+ *val = ret.intval;
+ return rc;
+}
+
+#define DEFAULT_BATT_CAPACITY 50
+static int get_prop_batt_capacity(struct smbchg_chip *chip)
+{
+ int capacity, rc;
+
+ if (chip->fake_battery_soc >= 0)
+ return chip->fake_battery_soc;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CAPACITY, &capacity);
+ if (rc) {
+ pr_smb(PR_STATUS, "Couldn't get capacity rc = %d\n", rc);
+ capacity = DEFAULT_BATT_CAPACITY;
+ }
+ return capacity;
+}
+
+#define DEFAULT_BATT_TEMP 200
+static int get_prop_batt_temp(struct smbchg_chip *chip)
+{
+ int temp, rc;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_TEMP, &temp);
+ if (rc) {
+ pr_smb(PR_STATUS, "Couldn't get temperature rc = %d\n", rc);
+ temp = DEFAULT_BATT_TEMP;
+ }
+ return temp;
+}
+
+#define DEFAULT_BATT_CURRENT_NOW 0
+static int get_prop_batt_current_now(struct smbchg_chip *chip)
+{
+ int ua, rc;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, &ua);
+ if (rc) {
+ pr_smb(PR_STATUS, "Couldn't get current rc = %d\n", rc);
+ ua = DEFAULT_BATT_CURRENT_NOW;
+ }
+ return ua;
+}
+
+#define DEFAULT_BATT_VOLTAGE_NOW 0
+static int get_prop_batt_voltage_now(struct smbchg_chip *chip)
+{
+ int uv, rc;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_NOW, &uv);
+ if (rc) {
+ pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc);
+ uv = DEFAULT_BATT_VOLTAGE_NOW;
+ }
+ return uv;
+}
+
+#define DEFAULT_BATT_VOLTAGE_MAX_DESIGN 4200000
+static int get_prop_batt_voltage_max_design(struct smbchg_chip *chip)
+{
+ int uv, rc;
+
+ rc = get_property_from_fg(chip,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &uv);
+ if (rc) {
+ pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc);
+ uv = DEFAULT_BATT_VOLTAGE_MAX_DESIGN;
+ }
+ return uv;
+}
+
+static int get_prop_batt_health(struct smbchg_chip *chip)
+{
+ if (chip->batt_hot)
+ return POWER_SUPPLY_HEALTH_OVERHEAT;
+ else if (chip->batt_cold)
+ return POWER_SUPPLY_HEALTH_COLD;
+ else if (chip->batt_warm)
+ return POWER_SUPPLY_HEALTH_WARM;
+ else if (chip->batt_cool)
+ return POWER_SUPPLY_HEALTH_COOL;
+ else
+ return POWER_SUPPLY_HEALTH_GOOD;
+}
+
+static void get_property_from_typec(struct smbchg_chip *chip,
+ enum power_supply_property property,
+ union power_supply_propval *prop)
+{
+ int rc;
+
+ rc = power_supply_get_property(chip->typec_psy,
+ property, prop);
+ if (rc)
+ pr_smb(PR_TYPEC,
+ "typec psy doesn't support reading prop %d rc = %d\n",
+ property, rc);
+}
+
+static void update_typec_status(struct smbchg_chip *chip)
+{
+ union power_supply_propval type = {0, };
+ union power_supply_propval capability = {0, };
+
+ get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type);
+ if (type.intval != POWER_SUPPLY_TYPE_UNKNOWN) {
+ get_property_from_typec(chip,
+ POWER_SUPPLY_PROP_CURRENT_CAPABILITY,
+ &capability);
+ chip->typec_current_ma = capability.intval;
+ pr_smb(PR_TYPEC, "SMB Type-C mode = %d, current=%d\n",
+ type.intval, capability.intval);
+ } else {
+ pr_smb(PR_TYPEC,
+ "typec detection not completed continuing with USB update\n");
+ }
+}
+
+/*
+ * finds the index of the closest value in the array. If there are two that
+ * are equally close, the lower index will be returned
+ */
+static int find_closest_in_array(const int *arr, int len, int val)
+{
+ int i, closest = 0;
+
+ if (len == 0)
+ return closest;
+ for (i = 0; i < len; i++)
+ if (abs(val - arr[i]) < abs(val - arr[closest]))
+ closest = i;
+
+ return closest;
+}
+
+/* finds the index of the closest smaller value in the array. */
+static int find_smaller_in_array(const int *table, int val, int len)
+{
+ int i;
+
+ for (i = len - 1; i >= 0; i--) {
+ if (val >= table[i])
+ break;
+ }
+
+ return i;
+}
+
+static const int iterm_ma_table_8994[] = {
+ 300,
+ 50,
+ 100,
+ 150,
+ 200,
+ 250,
+ 500,
+ 600
+};
+
+static const int iterm_ma_table_8996[] = {
+ 300,
+ 50,
+ 100,
+ 150,
+ 200,
+ 250,
+ 400,
+ 500
+};
+
+static const int usb_ilim_ma_table_8994[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 1450,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 1930,
+ 1950,
+ 1970,
+ 2000,
+ 2050,
+ 2100,
+ 2300,
+ 2400,
+ 2500,
+ 3000
+};
+
+static const int usb_ilim_ma_table_8996[] = {
+ 300,
+ 400,
+ 500,
+ 600,
+ 700,
+ 800,
+ 900,
+ 1000,
+ 1100,
+ 1200,
+ 1300,
+ 1400,
+ 1450,
+ 1500,
+ 1550,
+ 1600,
+ 1700,
+ 1800,
+ 1900,
+ 1950,
+ 2000,
+ 2050,
+ 2100,
+ 2200,
+ 2300,
+ 2400,
+ 2500,
+ 2600,
+ 2700,
+ 2800,
+ 2900,
+ 3000
+};
+
+static int dc_ilim_ma_table_8994[] = {
+ 300,
+ 400,
+ 450,
+ 475,
+ 500,
+ 550,
+ 600,
+ 650,
+ 700,
+ 900,
+ 950,
+ 1000,
+ 1100,
+ 1200,
+ 1400,
+ 1450,
+ 1500,
+ 1600,
+ 1800,
+ 1850,
+ 1880,
+ 1910,
+ 1930,
+ 1950,
+ 1970,
+ 2000,
+};
+
+static int dc_ilim_ma_table_8996[] = {
+ 300,
+ 400,
+ 500,
+ 600,
+ 700,
+ 800,
+ 900,
+ 1000,
+ 1100,
+ 1200,
+ 1300,
+ 1400,
+ 1450,
+ 1500,
+ 1550,
+ 1600,
+ 1700,
+ 1800,
+ 1900,
+ 1950,
+ 2000,
+ 2050,
+ 2100,
+ 2200,
+ 2300,
+ 2400,
+};
+
+static const int fcc_comp_table_8994[] = {
+ 250,
+ 700,
+ 900,
+ 1200,
+};
+
+static const int fcc_comp_table_8996[] = {
+ 250,
+ 1100,
+ 1200,
+ 1500,
+};
+
+static const int aicl_rerun_period[] = {
+ 45,
+ 90,
+ 180,
+ 360,
+};
+
+static const int aicl_rerun_period_schg_lite[] = {
+ 3, /* 2.8s */
+ 6, /* 5.6s */
+ 11, /* 11.3s */
+ 23, /* 22.5s */
+ 45,
+ 90,
+ 180,
+ 360,
+};
+
+static void use_pmi8994_tables(struct smbchg_chip *chip)
+{
+ chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8994;
+ chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8994);
+ chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8994;
+ chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8994);
+ chip->tables.iterm_ma_table = iterm_ma_table_8994;
+ chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8994);
+ chip->tables.fcc_comp_table = fcc_comp_table_8994;
+ chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8994);
+ chip->tables.rchg_thr_mv = 200;
+ chip->tables.aicl_rerun_period_table = aicl_rerun_period;
+ chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period);
+}
+
+static void use_pmi8996_tables(struct smbchg_chip *chip)
+{
+ chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8996;
+ chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8996);
+ chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8996;
+ chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8996);
+ chip->tables.iterm_ma_table = iterm_ma_table_8996;
+ chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8996);
+ chip->tables.fcc_comp_table = fcc_comp_table_8996;
+ chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8996);
+ chip->tables.rchg_thr_mv = 150;
+ chip->tables.aicl_rerun_period_table = aicl_rerun_period;
+ chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period);
+}
+
+#define CMD_CHG_REG 0x42
+#define EN_BAT_CHG_BIT BIT(1)
+static int smbchg_charging_en(struct smbchg_chip *chip, bool en)
+{
+ /* The en bit is configured active low */
+ return smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ EN_BAT_CHG_BIT, en ? 0 : EN_BAT_CHG_BIT);
+}
+
+#define CMD_IL 0x40
+#define USBIN_SUSPEND_BIT BIT(4)
+#define CURRENT_100_MA 100
+#define CURRENT_150_MA 150
+#define CURRENT_500_MA 500
+#define CURRENT_900_MA 900
+#define CURRENT_1500_MA 1500
+#define SUSPEND_CURRENT_MA 2
+#define ICL_OVERRIDE_BIT BIT(2)
+static int smbchg_usb_suspend(struct smbchg_chip *chip, bool suspend)
+{
+ int rc;
+
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set usb suspend rc = %d\n", rc);
+ return rc;
+}
+
+#define DCIN_SUSPEND_BIT BIT(3)
+static int smbchg_dc_suspend(struct smbchg_chip *chip, bool suspend)
+{
+ int rc = 0;
+
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set dc suspend rc = %d\n", rc);
+ return rc;
+}
+
+#define IL_CFG 0xF2
+#define DCIN_INPUT_MASK SMB_MASK(4, 0)
+static int smbchg_set_dc_current_max(struct smbchg_chip *chip, int current_ma)
+{
+ int i;
+ u8 dc_cur_val;
+
+ i = find_smaller_in_array(chip->tables.dc_ilim_ma_table,
+ current_ma, chip->tables.dc_ilim_ma_len);
+
+ if (i < 0) {
+ dev_err(chip->dev, "Cannot find %dma current_table\n",
+ current_ma);
+ return -EINVAL;
+ }
+
+ chip->dc_max_current_ma = chip->tables.dc_ilim_ma_table[i];
+ dc_cur_val = i & DCIN_INPUT_MASK;
+
+ pr_smb(PR_STATUS, "dc current set to %d mA\n",
+ chip->dc_max_current_ma);
+ return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG,
+ DCIN_INPUT_MASK, dc_cur_val);
+}
+
+#define AICL_WL_SEL_CFG 0xF5
+#define AICL_WL_SEL_MASK SMB_MASK(1, 0)
+#define AICL_WL_SEL_SCHG_LITE_MASK SMB_MASK(2, 0)
+static int smbchg_set_aicl_rerun_period_s(struct smbchg_chip *chip,
+ int period_s)
+{
+ int i;
+ u8 reg, mask;
+
+ i = find_smaller_in_array(chip->tables.aicl_rerun_period_table,
+ period_s, chip->tables.aicl_rerun_period_len);
+
+ if (i < 0) {
+ dev_err(chip->dev, "Cannot find %ds in aicl rerun period\n",
+ period_s);
+ return -EINVAL;
+ }
+
+ if (chip->schg_version == QPNP_SCHG_LITE)
+ mask = AICL_WL_SEL_SCHG_LITE_MASK;
+ else
+ mask = AICL_WL_SEL_MASK;
+
+ reg = i & mask;
+
+ pr_smb(PR_STATUS, "aicl rerun period set to %ds\n",
+ chip->tables.aicl_rerun_period_table[i]);
+ return smbchg_sec_masked_write(chip,
+ chip->dc_chgpth_base + AICL_WL_SEL_CFG,
+ mask, reg);
+}
+
+static struct power_supply *get_parallel_psy(struct smbchg_chip *chip)
+{
+ if (!chip->parallel.avail)
+ return NULL;
+ if (chip->parallel.psy)
+ return chip->parallel.psy;
+ chip->parallel.psy = power_supply_get_by_name("usb-parallel");
+ if (!chip->parallel.psy)
+ pr_smb(PR_STATUS, "parallel charger not found\n");
+ return chip->parallel.psy;
+}
+
+static void smbchg_usb_update_online_work(struct work_struct *work)
+{
+ struct smbchg_chip *chip = container_of(work,
+ struct smbchg_chip,
+ usb_set_online_work);
+ bool user_enabled = !get_client_vote(chip->usb_suspend_votable,
+ USER_EN_VOTER);
+ int online;
+
+ online = user_enabled && chip->usb_present && !chip->very_weak_charger;
+
+ mutex_lock(&chip->usb_set_online_lock);
+ if (chip->usb_online != online) {
+ pr_smb(PR_MISC, "setting usb psy online = %d\n", online);
+ chip->usb_online = online;
+ power_supply_changed(chip->usb_psy);
+ }
+ mutex_unlock(&chip->usb_set_online_lock);
+}
+
+#define CHGPTH_CFG 0xF4
+#define CFG_USB_2_3_SEL_BIT BIT(7)
+#define CFG_USB_2 0
+#define CFG_USB_3 BIT(7)
+#define USBIN_INPUT_MASK SMB_MASK(4, 0)
+#define USBIN_MODE_CHG_BIT BIT(0)
+#define USBIN_LIMITED_MODE 0
+#define USBIN_HC_MODE BIT(0)
+#define USB51_MODE_BIT BIT(1)
+#define USB51_100MA 0
+#define USB51_500MA BIT(1)
+static int smbchg_set_high_usb_chg_current(struct smbchg_chip *chip,
+ int current_ma)
+{
+ int i, rc;
+ u8 usb_cur_val;
+
+ if (current_ma == CURRENT_100_MA) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_2);
+ if (rc < 0) {
+ pr_err("Couldn't set CFG_USB_2 rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT | ICL_OVERRIDE_BIT,
+ USBIN_LIMITED_MODE | USB51_100MA | ICL_OVERRIDE_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't set ICL_OVERRIDE rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_STATUS,
+ "Forcing 100mA current limit\n");
+ chip->usb_max_current_ma = CURRENT_100_MA;
+ return rc;
+ }
+
+ i = find_smaller_in_array(chip->tables.usb_ilim_ma_table,
+ current_ma, chip->tables.usb_ilim_ma_len);
+ if (i < 0) {
+ dev_err(chip->dev,
+ "Cannot find %dma current_table using %d\n",
+ current_ma, CURRENT_150_MA);
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_3);
+ rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
+ USBIN_LIMITED_MODE | USB51_100MA);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
+ CURRENT_150_MA, rc);
+ else
+ chip->usb_max_current_ma = 150;
+ return rc;
+ }
+
+ usb_cur_val = i & USBIN_INPUT_MASK;
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + IL_CFG,
+ USBIN_INPUT_MASK, usb_cur_val);
+ if (rc < 0) {
+ dev_err(chip->dev, "cannot write to config c rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT, USBIN_HC_MODE);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc);
+ chip->usb_max_current_ma = chip->tables.usb_ilim_ma_table[i];
+ return rc;
+}
+
+/* if APSD results are used
+ * if SDP is detected it will look at 500mA setting
+ * if set it will draw 500mA
+ * if unset it will draw 100mA
+ * if CDP/DCP it will look at 0x0C setting
+ * i.e. values in 0x41[1, 0] does not matter
+ */
+static int smbchg_set_usb_current_max(struct smbchg_chip *chip,
+ int current_ma)
+{
+ int rc = 0;
+
+ /*
+ * if the battery is not present, do not allow the usb ICL to lower in
+ * order to avoid browning out the device during a hotswap.
+ */
+ if (!chip->batt_present && current_ma < chip->usb_max_current_ma) {
+ pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n",
+ current_ma);
+ return 0;
+ }
+ pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma);
+
+ if (current_ma <= SUSPEND_CURRENT_MA) {
+ /* suspend the usb if current <= 2mA */
+ rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, true, 0);
+ chip->usb_max_current_ma = 0;
+ goto out;
+ } else {
+ rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, false, 0);
+ }
+
+ switch (chip->usb_supply_type) {
+ case POWER_SUPPLY_TYPE_USB:
+ if ((current_ma < CURRENT_150_MA) &&
+ (chip->wa_flags & SMBCHG_USB100_WA))
+ current_ma = CURRENT_150_MA;
+
+ if (current_ma < CURRENT_150_MA) {
+ /* force 100mA */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_2);
+ if (rc < 0) {
+ pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc);
+ goto out;
+ }
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
+ USBIN_LIMITED_MODE | USB51_100MA);
+ if (rc < 0) {
+ pr_err("Couldn't set CMD_IL rc = %d\n", rc);
+ goto out;
+ }
+ chip->usb_max_current_ma = 100;
+ }
+ /* specific current values */
+ if (current_ma == CURRENT_150_MA) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_3);
+ if (rc < 0) {
+ pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc);
+ goto out;
+ }
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
+ USBIN_LIMITED_MODE | USB51_100MA);
+ if (rc < 0) {
+ pr_err("Couldn't set CMD_IL rc = %d\n", rc);
+ goto out;
+ }
+ chip->usb_max_current_ma = 150;
+ }
+ if (current_ma == CURRENT_500_MA) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_2);
+ if (rc < 0) {
+ pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc);
+ goto out;
+ }
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
+ USBIN_LIMITED_MODE | USB51_500MA);
+ if (rc < 0) {
+ pr_err("Couldn't set CMD_IL rc = %d\n", rc);
+ goto out;
+ }
+ chip->usb_max_current_ma = 500;
+ }
+ if (current_ma == CURRENT_900_MA) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ CFG_USB_2_3_SEL_BIT, CFG_USB_3);
+ if (rc < 0) {
+ pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc);
+ goto out;
+ }
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + CMD_IL,
+ USBIN_MODE_CHG_BIT | USB51_MODE_BIT,
+ USBIN_LIMITED_MODE | USB51_500MA);
+ if (rc < 0) {
+ pr_err("Couldn't set CMD_IL rc = %d\n", rc);
+ goto out;
+ }
+ chip->usb_max_current_ma = 900;
+ }
+ break;
+ case POWER_SUPPLY_TYPE_USB_CDP:
+ if (current_ma < CURRENT_1500_MA) {
+ /* use override for CDP */
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + CMD_IL,
+ ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT);
+ if (rc < 0)
+ pr_err("Couldn't set override rc = %d\n", rc);
+ }
+ /* fall through */
+ default:
+ rc = smbchg_set_high_usb_chg_current(chip, current_ma);
+ if (rc < 0)
+ pr_err("Couldn't set %dmA rc = %d\n", current_ma, rc);
+ break;
+ }
+
+out:
+ pr_smb(PR_STATUS, "usb type = %d current set to %d mA\n",
+ chip->usb_supply_type, chip->usb_max_current_ma);
+ return rc;
+}
+
+#define USBIN_HVDCP_STS 0x0C
+#define USBIN_HVDCP_SEL_BIT BIT(4)
+#define USBIN_HVDCP_SEL_9V_BIT BIT(1)
+#define SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT BIT(2)
+#define SCHG_LITE_USBIN_HVDCP_SEL_BIT BIT(0)
+static int smbchg_get_min_parallel_current_ma(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg, hvdcp_sel, hvdcp_sel_9v;
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc);
+ return 0;
+ }
+ if (chip->schg_version == QPNP_SCHG_LITE) {
+ hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT;
+ hvdcp_sel_9v = SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT;
+ } else {
+ hvdcp_sel = USBIN_HVDCP_SEL_BIT;
+ hvdcp_sel_9v = USBIN_HVDCP_SEL_9V_BIT;
+ }
+
+ if ((reg & hvdcp_sel) && (reg & hvdcp_sel_9v))
+ return chip->parallel.min_9v_current_thr_ma;
+ return chip->parallel.min_current_thr_ma;
+}
+
+static bool is_hvdcp_present(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg, hvdcp_sel;
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
+ if (rc < 0) {
+ pr_err("Couldn't read hvdcp status rc = %d\n", rc);
+ return false;
+ }
+
+ pr_smb(PR_STATUS, "HVDCP_STS = 0x%02x\n", reg);
+ /*
+ * If a valid HVDCP is detected, notify it to the usb_psy only
+ * if USB is still present.
+ */
+ if (chip->schg_version == QPNP_SCHG_LITE)
+ hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT;
+ else
+ hvdcp_sel = USBIN_HVDCP_SEL_BIT;
+
+ if ((reg & hvdcp_sel) && is_usb_present(chip))
+ return true;
+
+ return false;
+}
+
+#define FCC_CFG 0xF2
+#define FCC_500MA_VAL 0x4
+#define FCC_MASK SMB_MASK(4, 0)
+static int smbchg_set_fastchg_current_raw(struct smbchg_chip *chip,
+ int current_ma)
+{
+ int i, rc;
+ u8 cur_val;
+
+ /* the fcc enumerations are the same as the usb currents */
+ i = find_smaller_in_array(chip->tables.usb_ilim_ma_table,
+ current_ma, chip->tables.usb_ilim_ma_len);
+ if (i < 0) {
+ dev_err(chip->dev,
+ "Cannot find %dma current_table using %d\n",
+ current_ma, CURRENT_500_MA);
+
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG,
+ FCC_MASK,
+ FCC_500MA_VAL);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set %dmA rc=%d\n",
+ CURRENT_500_MA, rc);
+ else
+ chip->fastchg_current_ma = 500;
+ return rc;
+ }
+
+ if (chip->tables.usb_ilim_ma_table[i] == chip->fastchg_current_ma) {
+ pr_smb(PR_STATUS, "skipping fastchg current request: %d\n",
+ chip->fastchg_current_ma);
+ return 0;
+ }
+
+ cur_val = i & FCC_MASK;
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG,
+ FCC_MASK, cur_val);
+ if (rc < 0) {
+ dev_err(chip->dev, "cannot write to fcc cfg rc = %d\n", rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "fastcharge current requested %d, set to %d\n",
+ current_ma, chip->tables.usb_ilim_ma_table[cur_val]);
+
+ chip->fastchg_current_ma = chip->tables.usb_ilim_ma_table[cur_val];
+ return rc;
+}
+
+#define ICL_STS_1_REG 0x7
+#define ICL_STS_2_REG 0x9
+#define ICL_STS_MASK 0x1F
+#define AICL_SUSP_BIT BIT(6)
+#define AICL_STS_BIT BIT(5)
+#define USBIN_SUSPEND_STS_BIT BIT(3)
+#define USBIN_ACTIVE_PWR_SRC_BIT BIT(1)
+#define DCIN_ACTIVE_PWR_SRC_BIT BIT(0)
+#define PARALLEL_REENABLE_TIMER_MS 1000
+#define PARALLEL_CHG_THRESHOLD_CURRENT 1800
+static bool smbchg_is_usbin_active_pwr_src(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + ICL_STS_2_REG, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc);
+ return false;
+ }
+
+ return !(reg & USBIN_SUSPEND_STS_BIT)
+ && (reg & USBIN_ACTIVE_PWR_SRC_BIT);
+}
+
+static int smbchg_parallel_usb_charging_en(struct smbchg_chip *chip, bool en)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+
+ if (!parallel_psy || !chip->parallel_charger_detected)
+ return 0;
+
+ pval.intval = en;
+ return power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval);
+}
+
+#define ESR_PULSE_CURRENT_DELTA_MA 200
+static int smbchg_sw_esr_pulse_en(struct smbchg_chip *chip, bool en)
+{
+ int rc, fg_current_now, icl_ma;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW,
+ &fg_current_now);
+ if (rc) {
+ pr_smb(PR_STATUS, "bms psy does not support OCV\n");
+ return 0;
+ }
+
+ icl_ma = max(chip->iterm_ma + ESR_PULSE_CURRENT_DELTA_MA,
+ fg_current_now - ESR_PULSE_CURRENT_DELTA_MA);
+ rc = vote(chip->fcc_votable, ESR_PULSE_FCC_VOTER, en, icl_ma);
+ if (rc < 0) {
+ pr_err("Couldn't Vote FCC en = %d rc = %d\n", en, rc);
+ return rc;
+ }
+ rc = smbchg_parallel_usb_charging_en(chip, !en);
+ return rc;
+}
+
+#define USB_AICL_CFG 0xF3
+#define AICL_EN_BIT BIT(2)
+static void smbchg_rerun_aicl(struct smbchg_chip *chip)
+{
+ pr_smb(PR_STATUS, "Rerunning AICL...\n");
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, 0);
+ /* Add a delay so that AICL successfully clears */
+ msleep(50);
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, AICL_EN_BIT);
+}
+
+static void taper_irq_en(struct smbchg_chip *chip, bool en)
+{
+ mutex_lock(&chip->taper_irq_lock);
+ if (en != chip->taper_irq_enabled) {
+ if (en) {
+ enable_irq(chip->taper_irq);
+ enable_irq_wake(chip->taper_irq);
+ } else {
+ disable_irq_wake(chip->taper_irq);
+ disable_irq_nosync(chip->taper_irq);
+ }
+ chip->taper_irq_enabled = en;
+ }
+ mutex_unlock(&chip->taper_irq_lock);
+}
+
+static int smbchg_get_aicl_level_ma(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + ICL_STS_1_REG, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc);
+ return 0;
+ }
+ if (reg & AICL_SUSP_BIT) {
+ pr_warn("AICL suspended: %02x\n", reg);
+ return 0;
+ }
+ reg &= ICL_STS_MASK;
+ if (reg >= chip->tables.usb_ilim_ma_len) {
+ pr_warn("invalid AICL value: %02x\n", reg);
+ return 0;
+ }
+ return chip->tables.usb_ilim_ma_table[reg];
+}
+
+static void smbchg_parallel_usb_disable(struct smbchg_chip *chip)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ int fcc_ma, usb_icl_ma;
+
+ if (!parallel_psy || !chip->parallel_charger_detected)
+ return;
+ pr_smb(PR_STATUS, "disabling parallel charger\n");
+ chip->parallel.last_disabled = ktime_get_boottime();
+ taper_irq_en(chip, false);
+ chip->parallel.initial_aicl_ma = 0;
+ chip->parallel.current_max_ma = 0;
+ pval.intval = SUSPEND_CURRENT_MA * 1000;
+ power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pval);
+
+ pval.intval = false;
+ power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+
+ fcc_ma = get_effective_result_locked(chip->fcc_votable);
+ usb_icl_ma = get_effective_result_locked(chip->usb_icl_votable);
+ if (fcc_ma < 0)
+ pr_err("no voters for fcc, skip it\n");
+ else
+ smbchg_set_fastchg_current_raw(chip, fcc_ma);
+
+ if (usb_icl_ma < 0)
+ pr_err("no voters for usb_icl, skip it\n");
+ else
+ smbchg_set_usb_current_max(chip, usb_icl_ma);
+
+ smbchg_rerun_aicl(chip);
+}
+
+#define PARALLEL_TAPER_MAX_TRIES 3
+#define PARALLEL_FCC_PERCENT_REDUCTION 75
+#define MINIMUM_PARALLEL_FCC_MA 500
+#define CHG_ERROR_BIT BIT(0)
+#define BAT_TAPER_MODE_BIT BIT(6)
+static void smbchg_parallel_usb_taper(struct smbchg_chip *chip)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ int parallel_fcc_ma, tries = 0;
+ u8 reg = 0;
+
+ if (!parallel_psy || !chip->parallel_charger_detected)
+ return;
+
+ smbchg_stay_awake(chip, PM_PARALLEL_TAPER);
+try_again:
+ mutex_lock(&chip->parallel.lock);
+ if (chip->parallel.current_max_ma == 0) {
+ pr_smb(PR_STATUS, "Not parallel charging, skipping\n");
+ goto done;
+ }
+ power_supply_get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ tries += 1;
+ parallel_fcc_ma = pval.intval / 1000;
+ pr_smb(PR_STATUS, "try #%d parallel charger fcc = %d\n",
+ tries, parallel_fcc_ma);
+ if (parallel_fcc_ma < MINIMUM_PARALLEL_FCC_MA
+ || tries > PARALLEL_TAPER_MAX_TRIES) {
+ smbchg_parallel_usb_disable(chip);
+ goto done;
+ }
+ pval.intval = ((parallel_fcc_ma
+ * PARALLEL_FCC_PERCENT_REDUCTION) / 100);
+ pr_smb(PR_STATUS, "reducing FCC of parallel charger to %d\n",
+ pval.intval);
+ /* Change it to uA */
+ pval.intval *= 1000;
+ power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ /*
+ * sleep here for 100 ms in order to make sure the charger has a chance
+ * to go back into constant current charging
+ */
+ mutex_unlock(&chip->parallel.lock);
+ msleep(100);
+
+ mutex_lock(&chip->parallel.lock);
+ if (chip->parallel.current_max_ma == 0) {
+ pr_smb(PR_STATUS, "Not parallel charging, skipping\n");
+ goto done;
+ }
+ smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
+ if (reg & BAT_TAPER_MODE_BIT) {
+ mutex_unlock(&chip->parallel.lock);
+ goto try_again;
+ }
+ taper_irq_en(chip, true);
+done:
+ mutex_unlock(&chip->parallel.lock);
+ smbchg_relax(chip, PM_PARALLEL_TAPER);
+}
+
+static void smbchg_parallel_usb_enable(struct smbchg_chip *chip,
+ int total_current_ma)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ int new_parallel_cl_ma, set_parallel_cl_ma, new_pmi_cl_ma, rc;
+ int current_table_index, target_icl_ma;
+ int fcc_ma, main_fastchg_current_ma;
+ int target_parallel_fcc_ma, supplied_parallel_fcc_ma;
+ int parallel_chg_fcc_percent;
+
+ if (!parallel_psy || !chip->parallel_charger_detected)
+ return;
+
+ pr_smb(PR_STATUS, "Attempting to enable parallel charger\n");
+ pval.intval = chip->vfloat_mv + 50;
+ rc = power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &pval);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set Vflt on parallel psy rc: %d\n", rc);
+ return;
+ }
+ /* Set USB ICL */
+ target_icl_ma = get_effective_result_locked(chip->usb_icl_votable);
+ if (target_icl_ma < 0) {
+ pr_err("no voters for usb_icl, skip it\n");
+ return;
+ }
+ new_parallel_cl_ma = total_current_ma
+ * (100 - smbchg_main_chg_icl_percent) / 100;
+ taper_irq_en(chip, true);
+
+ pval.intval = true;
+ power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_PRESENT,
+ &pval);
+
+ pval.intval = new_parallel_cl_ma * 1000;
+ power_supply_set_property(parallel_psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pval);
+
+ /* read back the real amount of current we are getting */
+ power_supply_get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ set_parallel_cl_ma = pval.intval / 1000;
+ chip->parallel.current_max_ma = new_parallel_cl_ma;
+ pr_smb(PR_MISC, "Requested ICL = %d from parallel, got %d\n",
+ new_parallel_cl_ma, set_parallel_cl_ma);
+ new_pmi_cl_ma = max(0, target_icl_ma - set_parallel_cl_ma);
+ pr_smb(PR_STATUS, "New Total USB current = %d[%d, %d]\n",
+ total_current_ma, new_pmi_cl_ma,
+ set_parallel_cl_ma);
+ smbchg_set_usb_current_max(chip, new_pmi_cl_ma);
+
+ /* begin splitting the fast charge current */
+ fcc_ma = get_effective_result_locked(chip->fcc_votable);
+ if (fcc_ma < 0) {
+ pr_err("no voters for fcc, skip it\n");
+ return;
+ }
+ parallel_chg_fcc_percent = 100 - smbchg_main_chg_fcc_percent;
+ target_parallel_fcc_ma = (fcc_ma * parallel_chg_fcc_percent) / 100;
+ pval.intval = target_parallel_fcc_ma * 1000;
+ power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ /* check how much actual current is supplied by the parallel charger */
+ power_supply_get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, &pval);
+ supplied_parallel_fcc_ma = pval.intval / 1000;
+ pr_smb(PR_MISC, "Requested FCC = %d from parallel, got %d\n",
+ target_parallel_fcc_ma, supplied_parallel_fcc_ma);
+
+ /* then for the main charger, use the left over FCC */
+ current_table_index = find_smaller_in_array(
+ chip->tables.usb_ilim_ma_table,
+ fcc_ma - supplied_parallel_fcc_ma,
+ chip->tables.usb_ilim_ma_len);
+ main_fastchg_current_ma =
+ chip->tables.usb_ilim_ma_table[current_table_index];
+ smbchg_set_fastchg_current_raw(chip, main_fastchg_current_ma);
+ pr_smb(PR_STATUS, "FCC = %d[%d, %d]\n", fcc_ma, main_fastchg_current_ma,
+ supplied_parallel_fcc_ma);
+
+ chip->parallel.enabled_once = true;
+
+ return;
+}
+
+static bool smbchg_is_parallel_usb_ok(struct smbchg_chip *chip,
+ int *ret_total_current_ma)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ int min_current_thr_ma, rc, type;
+ int total_current_ma, current_limit_ma, parallel_cl_ma;
+ ktime_t kt_since_last_disable;
+ u8 reg;
+ int fcc_ma = get_effective_result_locked(chip->fcc_votable);
+ const char *fcc_voter
+ = get_effective_client_locked(chip->fcc_votable);
+ int usb_icl_ma = get_effective_result_locked(chip->usb_icl_votable);
+
+ if (!parallel_psy || !smbchg_parallel_en
+ || !chip->parallel_charger_detected) {
+ pr_smb(PR_STATUS, "Parallel charging not enabled\n");
+ return false;
+ }
+
+ if (fcc_ma < 0) {
+ pr_err("no voters for fcc! Can't enable parallel\n");
+ return false;
+ }
+ if (usb_icl_ma < 0) {
+ pr_err("no voters for usb_icl, Can't enable parallel\n");
+ return false;
+ }
+
+ kt_since_last_disable = ktime_sub(ktime_get_boottime(),
+ chip->parallel.last_disabled);
+ if (chip->parallel.current_max_ma == 0
+ && chip->parallel.enabled_once
+ && ktime_to_ms(kt_since_last_disable)
+ < PARALLEL_REENABLE_TIMER_MS) {
+ pr_smb(PR_STATUS, "Only been %lld since disable, skipping\n",
+ ktime_to_ms(kt_since_last_disable));
+ return false;
+ }
+
+ /*
+ * If the battery is not present, try not to change parallel charging
+ * from OFF to ON or from ON to OFF, as it could cause the device to
+ * brown out in the instant that the USB settings are changed.
+ *
+ * Only allow parallel charging check to report false (thereby turnin
+ * off parallel charging) if the battery is still there, or if parallel
+ * charging is disabled in the first place.
+ */
+ if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST
+ && (get_prop_batt_present(chip)
+ || chip->parallel.current_max_ma == 0)) {
+ pr_smb(PR_STATUS, "Not in fast charge, skipping\n");
+ return false;
+ }
+
+ if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) {
+ pr_smb(PR_STATUS, "JEITA active, skipping\n");
+ return false;
+ }
+
+ rc = smbchg_read(chip, &reg, chip->misc_base + IDEV_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc);
+ return false;
+ }
+
+ type = get_type(reg);
+ if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB_CDP) {
+ pr_smb(PR_STATUS, "CDP adapter, skipping\n");
+ return false;
+ }
+
+ if (get_usb_supply_type(type) == POWER_SUPPLY_TYPE_USB) {
+ pr_smb(PR_STATUS, "SDP adapter, skipping\n");
+ return false;
+ }
+
+ /*
+ * If USBIN is suspended or not the active power source, do not enable
+ * parallel charging. The device may be charging off of DCIN.
+ */
+ if (!smbchg_is_usbin_active_pwr_src(chip)) {
+ pr_smb(PR_STATUS, "USB not active power source: %02x\n", reg);
+ return false;
+ }
+
+ min_current_thr_ma = smbchg_get_min_parallel_current_ma(chip);
+ if (min_current_thr_ma <= 0) {
+ pr_smb(PR_STATUS, "parallel charger unavailable for thr: %d\n",
+ min_current_thr_ma);
+ return false;
+ }
+
+ if (usb_icl_ma < min_current_thr_ma) {
+ pr_smb(PR_STATUS, "Weak USB chg skip enable: %d < %d\n",
+ usb_icl_ma, min_current_thr_ma);
+ return false;
+ }
+
+ if (!fcc_voter)
+ return false;
+ /*
+ * Suspend the parallel charger if the charging current is < 1800 mA
+ * and is not because of an ESR pulse.
+ */
+ if ((strcmp(fcc_voter, ESR_PULSE_FCC_VOTER) == 0)
+ && fcc_ma < PARALLEL_CHG_THRESHOLD_CURRENT) {
+ pr_smb(PR_STATUS, "FCC %d lower than %d\n",
+ fcc_ma,
+ PARALLEL_CHG_THRESHOLD_CURRENT);
+ return false;
+ }
+
+ current_limit_ma = smbchg_get_aicl_level_ma(chip);
+ if (current_limit_ma <= 0)
+ return false;
+
+ if (chip->parallel.initial_aicl_ma == 0) {
+ if (current_limit_ma < min_current_thr_ma) {
+ pr_smb(PR_STATUS, "Initial AICL very low: %d < %d\n",
+ current_limit_ma, min_current_thr_ma);
+ return false;
+ }
+ chip->parallel.initial_aicl_ma = current_limit_ma;
+ }
+
+ power_supply_get_property(parallel_psy,
+ POWER_SUPPLY_PROP_CURRENT_MAX, &pval);
+ parallel_cl_ma = pval.intval / 1000;
+ /*
+ * Read back the real amount of current we are getting
+ * Treat 2mA as 0 because that is the suspend current setting
+ */
+ if (parallel_cl_ma <= SUSPEND_CURRENT_MA)
+ parallel_cl_ma = 0;
+
+ /*
+ * Set the parallel charge path's input current limit (ICL)
+ * to the total current / 2
+ */
+ total_current_ma = min(current_limit_ma + parallel_cl_ma, usb_icl_ma);
+
+ if (total_current_ma < chip->parallel.initial_aicl_ma
+ - chip->parallel.allowed_lowering_ma) {
+ pr_smb(PR_STATUS,
+ "Total current reduced a lot: %d (%d + %d) < %d - %d\n",
+ total_current_ma,
+ current_limit_ma, parallel_cl_ma,
+ chip->parallel.initial_aicl_ma,
+ chip->parallel.allowed_lowering_ma);
+ return false;
+ }
+
+ *ret_total_current_ma = total_current_ma;
+ return true;
+}
+
+#define PARALLEL_CHARGER_EN_DELAY_MS 500
+static void smbchg_parallel_usb_en_work(struct work_struct *work)
+{
+ struct smbchg_chip *chip = container_of(work,
+ struct smbchg_chip,
+ parallel_en_work.work);
+ int previous_aicl_ma, total_current_ma, aicl_ma;
+ bool in_progress;
+
+ /* do a check to see if the aicl is stable */
+ previous_aicl_ma = smbchg_get_aicl_level_ma(chip);
+ msleep(PARALLEL_CHARGER_EN_DELAY_MS);
+ aicl_ma = smbchg_get_aicl_level_ma(chip);
+ if (previous_aicl_ma == aicl_ma) {
+ pr_smb(PR_STATUS, "AICL at %d\n", aicl_ma);
+ } else {
+ pr_smb(PR_STATUS,
+ "AICL changed [%d -> %d], recheck %d ms\n",
+ previous_aicl_ma, aicl_ma,
+ PARALLEL_CHARGER_EN_DELAY_MS);
+ goto recheck;
+ }
+
+ mutex_lock(&chip->parallel.lock);
+ in_progress = (chip->parallel.current_max_ma != 0);
+ if (smbchg_is_parallel_usb_ok(chip, &total_current_ma)) {
+ smbchg_parallel_usb_enable(chip, total_current_ma);
+ } else {
+ if (in_progress) {
+ pr_smb(PR_STATUS, "parallel charging unavailable\n");
+ smbchg_parallel_usb_disable(chip);
+ }
+ }
+ mutex_unlock(&chip->parallel.lock);
+ smbchg_relax(chip, PM_PARALLEL_CHECK);
+ return;
+
+recheck:
+ schedule_delayed_work(&chip->parallel_en_work, 0);
+}
+
+static void smbchg_parallel_usb_check_ok(struct smbchg_chip *chip)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+
+ if (!parallel_psy || !chip->parallel_charger_detected)
+ return;
+
+ smbchg_stay_awake(chip, PM_PARALLEL_CHECK);
+ schedule_delayed_work(&chip->parallel_en_work, 0);
+}
+
+static int charging_suspend_vote_cb(struct votable *votable, void *data,
+ int suspend,
+ const char *client)
+{
+ int rc;
+ struct smbchg_chip *chip = data;
+
+ if (suspend < 0) {
+ pr_err("No voters\n");
+ suspend = false;
+ }
+
+ rc = smbchg_charging_en(chip, !suspend);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't configure batt chg: 0x%x rc = %d\n",
+ !suspend, rc);
+ }
+
+ return rc;
+}
+
+static int usb_suspend_vote_cb(struct votable *votable,
+ void *data,
+ int suspend,
+ const char *client)
+{
+ int rc;
+ struct smbchg_chip *chip = data;
+
+ if (suspend < 0) {
+ pr_err("No voters\n");
+ suspend = false;
+ }
+
+ rc = smbchg_usb_suspend(chip, suspend);
+ if (rc < 0)
+ return rc;
+
+ if ((strcmp(client, THERMAL_EN_VOTER) == 0)
+ || (strcmp(client, POWER_SUPPLY_EN_VOTER) == 0)
+ || (strcmp(client, USER_EN_VOTER) == 0)
+ || (strcmp(client, FAKE_BATTERY_EN_VOTER) == 0))
+ smbchg_parallel_usb_check_ok(chip);
+
+ return rc;
+}
+
+static int dc_suspend_vote_cb(struct votable *votable,
+ void *data,
+ int suspend,
+ const char *client)
+{
+ int rc;
+ struct smbchg_chip *chip = data;
+
+ if (suspend < 0) {
+ pr_err("No voters\n");
+ suspend = false;
+ }
+
+ rc = smbchg_dc_suspend(chip, suspend);
+ if (rc < 0)
+ return rc;
+
+ if (chip->dc_psy_type != -EINVAL && chip->dc_psy)
+ power_supply_changed(chip->dc_psy);
+
+ return rc;
+}
+
+static int set_fastchg_current_vote_cb(struct votable *votable,
+ void *data,
+ int fcc_ma,
+ const char *client)
+{
+ struct smbchg_chip *chip = data;
+ int rc;
+
+ if (fcc_ma < 0) {
+ pr_err("No voters\n");
+ return 0;
+ }
+
+ if (chip->parallel.current_max_ma == 0) {
+ rc = smbchg_set_fastchg_current_raw(chip, fcc_ma);
+ if (rc < 0) {
+ pr_err("Can't set FCC fcc_ma=%d rc=%d\n", fcc_ma, rc);
+ return rc;
+ }
+ }
+ /*
+ * check if parallel charging can be enabled, and if enabled,
+ * distribute the fcc
+ */
+ smbchg_parallel_usb_check_ok(chip);
+ return 0;
+}
+
+static int smbchg_set_fastchg_current_user(struct smbchg_chip *chip,
+ int current_ma)
+{
+ int rc = 0;
+
+ pr_smb(PR_STATUS, "User setting FCC to %d\n", current_ma);
+
+ rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, current_ma);
+ if (rc < 0)
+ pr_err("Couldn't vote en rc %d\n", rc);
+ return rc;
+}
+
+static struct ilim_entry *smbchg_wipower_find_entry(struct smbchg_chip *chip,
+ struct ilim_map *map, int uv)
+{
+ int i;
+ struct ilim_entry *ret = &(chip->wipower_default.entries[0]);
+
+ for (i = 0; i < map->num; i++) {
+ if (is_between(map->entries[i].vmin_uv, map->entries[i].vmax_uv,
+ uv))
+ ret = &map->entries[i];
+ }
+ return ret;
+}
+
+#define ZIN_ICL_PT 0xFC
+#define ZIN_ICL_LV 0xFD
+#define ZIN_ICL_HV 0xFE
+#define ZIN_ICL_MASK SMB_MASK(4, 0)
+static int smbchg_dcin_ilim_config(struct smbchg_chip *chip, int offset, int ma)
+{
+ int i, rc;
+
+ i = find_smaller_in_array(chip->tables.dc_ilim_ma_table,
+ ma, chip->tables.dc_ilim_ma_len);
+
+ if (i < 0)
+ i = 0;
+
+ rc = smbchg_sec_masked_write(chip, chip->bat_if_base + offset,
+ ZIN_ICL_MASK, i);
+ if (rc)
+ dev_err(chip->dev, "Couldn't write bat if offset %d value = %d rc = %d\n",
+ offset, i, rc);
+ return rc;
+}
+
+static int smbchg_wipower_ilim_config(struct smbchg_chip *chip,
+ struct ilim_entry *ilim)
+{
+ int rc = 0;
+
+ if (chip->current_ilim.icl_pt_ma != ilim->icl_pt_ma) {
+ rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_PT, ilim->icl_pt_ma);
+ if (rc)
+ dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n",
+ ZIN_ICL_PT, ilim->icl_pt_ma, rc);
+ else
+ chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma;
+ }
+
+ if (chip->current_ilim.icl_lv_ma != ilim->icl_lv_ma) {
+ rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_LV, ilim->icl_lv_ma);
+ if (rc)
+ dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n",
+ ZIN_ICL_LV, ilim->icl_lv_ma, rc);
+ else
+ chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma;
+ }
+
+ if (chip->current_ilim.icl_hv_ma != ilim->icl_hv_ma) {
+ rc = smbchg_dcin_ilim_config(chip, ZIN_ICL_HV, ilim->icl_hv_ma);
+ if (rc)
+ dev_err(chip->dev, "failed to write batif offset %d %dma rc = %d\n",
+ ZIN_ICL_HV, ilim->icl_hv_ma, rc);
+ else
+ chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma;
+ }
+ return rc;
+}
+
+static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx);
+static int smbchg_wipower_dcin_btm_configure(struct smbchg_chip *chip,
+ struct ilim_entry *ilim)
+{
+ int rc;
+
+ if (ilim->vmin_uv == chip->current_ilim.vmin_uv
+ && ilim->vmax_uv == chip->current_ilim.vmax_uv)
+ return 0;
+
+ chip->param.channel = DCIN;
+ chip->param.btm_ctx = chip;
+ if (wipower_dcin_interval < ADC_MEAS1_INTERVAL_0MS)
+ wipower_dcin_interval = ADC_MEAS1_INTERVAL_0MS;
+
+ if (wipower_dcin_interval > ADC_MEAS1_INTERVAL_16S)
+ wipower_dcin_interval = ADC_MEAS1_INTERVAL_16S;
+
+ chip->param.timer_interval = wipower_dcin_interval;
+ chip->param.threshold_notification = &btm_notify_dcin;
+ chip->param.high_thr = ilim->vmax_uv + wipower_dcin_hyst_uv;
+ chip->param.low_thr = ilim->vmin_uv - wipower_dcin_hyst_uv;
+ chip->param.state_request = ADC_TM_HIGH_LOW_THR_ENABLE;
+ rc = qpnp_vadc_channel_monitor(chip->vadc_dev, &chip->param);
+ if (rc) {
+ dev_err(chip->dev, "Couldn't configure btm for dcin rc = %d\n",
+ rc);
+ } else {
+ chip->current_ilim.vmin_uv = ilim->vmin_uv;
+ chip->current_ilim.vmax_uv = ilim->vmax_uv;
+ pr_smb(PR_STATUS, "btm ilim = (%duV %duV %dmA %dmA %dmA)\n",
+ ilim->vmin_uv, ilim->vmax_uv,
+ ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma);
+ }
+ return rc;
+}
+
+static int smbchg_wipower_icl_configure(struct smbchg_chip *chip,
+ int dcin_uv, bool div2)
+{
+ int rc = 0;
+ struct ilim_map *map = div2 ? &chip->wipower_div2 : &chip->wipower_pt;
+ struct ilim_entry *ilim = smbchg_wipower_find_entry(chip, map, dcin_uv);
+
+ rc = smbchg_wipower_ilim_config(chip, ilim);
+ if (rc) {
+ dev_err(chip->dev, "failed to config ilim rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n",
+ rc, dcin_uv, div2,
+ ilim->vmin_uv, ilim->vmax_uv,
+ ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma);
+ return rc;
+ }
+
+ rc = smbchg_wipower_dcin_btm_configure(chip, ilim);
+ if (rc) {
+ dev_err(chip->dev, "failed to config btm rc = %d, dcin_uv = %d , div2 = %d, ilim = (%duV %duV %dmA %dmA %dmA)\n",
+ rc, dcin_uv, div2,
+ ilim->vmin_uv, ilim->vmax_uv,
+ ilim->icl_pt_ma, ilim->icl_lv_ma, ilim->icl_hv_ma);
+ return rc;
+ }
+ chip->wipower_configured = true;
+ return 0;
+}
+
+static void smbchg_wipower_icl_deconfigure(struct smbchg_chip *chip)
+{
+ int rc;
+ struct ilim_entry *ilim = &(chip->wipower_default.entries[0]);
+
+ if (!chip->wipower_configured)
+ return;
+
+ rc = smbchg_wipower_ilim_config(chip, ilim);
+ if (rc)
+ dev_err(chip->dev, "Couldn't config default ilim rc = %d\n",
+ rc);
+
+ rc = qpnp_vadc_end_channel_monitor(chip->vadc_dev);
+ if (rc)
+ dev_err(chip->dev, "Couldn't de configure btm for dcin rc = %d\n",
+ rc);
+
+ chip->wipower_configured = false;
+ chip->current_ilim.vmin_uv = 0;
+ chip->current_ilim.vmax_uv = 0;
+ chip->current_ilim.icl_pt_ma = ilim->icl_pt_ma;
+ chip->current_ilim.icl_lv_ma = ilim->icl_lv_ma;
+ chip->current_ilim.icl_hv_ma = ilim->icl_hv_ma;
+ pr_smb(PR_WIPOWER, "De config btm\n");
+}
+
+#define FV_STS 0x0C
+#define DIV2_ACTIVE BIT(7)
+static void __smbchg_wipower_check(struct smbchg_chip *chip)
+{
+ int chg_type;
+ bool usb_present, dc_present;
+ int rc;
+ int dcin_uv;
+ bool div2;
+ struct qpnp_vadc_result adc_result;
+ u8 reg;
+
+ if (!wipower_dyn_icl_en) {
+ smbchg_wipower_icl_deconfigure(chip);
+ return;
+ }
+
+ chg_type = get_prop_charge_type(chip);
+ usb_present = is_usb_present(chip);
+ dc_present = is_dc_present(chip);
+ if (chg_type != POWER_SUPPLY_CHARGE_TYPE_NONE
+ && !usb_present
+ && dc_present
+ && chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) {
+ rc = qpnp_vadc_read(chip->vadc_dev, DCIN, &adc_result);
+ if (rc) {
+ pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc);
+ return;
+ }
+ dcin_uv = adc_result.physical;
+
+ /* check div_by_2 */
+ rc = smbchg_read(chip, &reg, chip->chgr_base + FV_STS, 1);
+ if (rc) {
+ pr_smb(PR_STATUS, "error DCIN read rc = %d\n", rc);
+ return;
+ }
+ div2 = !!(reg & DIV2_ACTIVE);
+
+ pr_smb(PR_WIPOWER,
+ "config ICL chg_type = %d usb = %d dc = %d dcin_uv(adc_code) = %d (0x%x) div2 = %d\n",
+ chg_type, usb_present, dc_present, dcin_uv,
+ adc_result.adc_code, div2);
+ smbchg_wipower_icl_configure(chip, dcin_uv, div2);
+ } else {
+ pr_smb(PR_WIPOWER,
+ "deconfig ICL chg_type = %d usb = %d dc = %d\n",
+ chg_type, usb_present, dc_present);
+ smbchg_wipower_icl_deconfigure(chip);
+ }
+}
+
+static void smbchg_wipower_check(struct smbchg_chip *chip)
+{
+ if (!chip->wipower_dyn_icl_avail)
+ return;
+
+ mutex_lock(&chip->wipower_config);
+ __smbchg_wipower_check(chip);
+ mutex_unlock(&chip->wipower_config);
+}
+
+static void btm_notify_dcin(enum qpnp_tm_state state, void *ctx)
+{
+ struct smbchg_chip *chip = ctx;
+
+ mutex_lock(&chip->wipower_config);
+ pr_smb(PR_WIPOWER, "%s state\n",
+ state == ADC_TM_LOW_STATE ? "low" : "high");
+ chip->current_ilim.vmin_uv = 0;
+ chip->current_ilim.vmax_uv = 0;
+ __smbchg_wipower_check(chip);
+ mutex_unlock(&chip->wipower_config);
+}
+
+static int force_dcin_icl_write(void *data, u64 val)
+{
+ struct smbchg_chip *chip = data;
+
+ smbchg_wipower_check(chip);
+ return 0;
+}
+DEFINE_SIMPLE_ATTRIBUTE(force_dcin_icl_ops, NULL,
+ force_dcin_icl_write, "0x%02llx\n");
+
+/*
+ * set the dc charge path's maximum allowed current draw
+ * that may be limited by the system's thermal level
+ */
+static int set_dc_current_limit_vote_cb(struct votable *votable,
+ void *data,
+ int icl_ma,
+ const char *client)
+{
+ struct smbchg_chip *chip = data;
+
+ if (icl_ma < 0) {
+ pr_err("No voters\n");
+ return 0;
+ }
+
+ return smbchg_set_dc_current_max(chip, icl_ma);
+}
+
+/*
+ * set the usb charge path's maximum allowed current draw
+ * that may be limited by the system's thermal level
+ */
+static int set_usb_current_limit_vote_cb(struct votable *votable,
+ void *data,
+ int icl_ma,
+ const char *client)
+{
+ struct smbchg_chip *chip = data;
+ int rc, aicl_ma;
+ const char *effective_id;
+
+ if (icl_ma < 0) {
+ pr_err("No voters\n");
+ return 0;
+ }
+ effective_id = get_effective_client_locked(chip->usb_icl_votable);
+
+ if (!effective_id)
+ return 0;
+
+ /* disable parallel charging if HVDCP is voting for 300mA */
+ if (strcmp(effective_id, HVDCP_ICL_VOTER) == 0)
+ smbchg_parallel_usb_disable(chip);
+
+ if (chip->parallel.current_max_ma == 0) {
+ rc = smbchg_set_usb_current_max(chip, icl_ma);
+ if (rc) {
+ pr_err("Failed to set usb current max: %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* skip the aicl rerun if hvdcp icl voter is active */
+ if (strcmp(effective_id, HVDCP_ICL_VOTER) == 0)
+ return 0;
+
+ aicl_ma = smbchg_get_aicl_level_ma(chip);
+ if (icl_ma > aicl_ma)
+ smbchg_rerun_aicl(chip);
+ smbchg_parallel_usb_check_ok(chip);
+ return 0;
+}
+
+static int smbchg_system_temp_level_set(struct smbchg_chip *chip,
+ int lvl_sel)
+{
+ int rc = 0;
+ int prev_therm_lvl;
+ int thermal_icl_ma;
+
+ if (!chip->thermal_mitigation) {
+ dev_err(chip->dev, "Thermal mitigation not supported\n");
+ return -EINVAL;
+ }
+
+ if (lvl_sel < 0) {
+ dev_err(chip->dev, "Unsupported level selected %d\n", lvl_sel);
+ return -EINVAL;
+ }
+
+ if (lvl_sel >= chip->thermal_levels) {
+ dev_err(chip->dev, "Unsupported level selected %d forcing %d\n",
+ lvl_sel, chip->thermal_levels - 1);
+ lvl_sel = chip->thermal_levels - 1;
+ }
+
+ if (lvl_sel == chip->therm_lvl_sel)
+ return 0;
+
+ mutex_lock(&chip->therm_lvl_lock);
+ prev_therm_lvl = chip->therm_lvl_sel;
+ chip->therm_lvl_sel = lvl_sel;
+ if (chip->therm_lvl_sel == (chip->thermal_levels - 1)) {
+ /*
+ * Disable charging if highest value selected by
+ * setting the DC and USB path in suspend
+ */
+ rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, true, 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set dc suspend rc %d\n", rc);
+ goto out;
+ }
+ rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER, true, 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend rc %d\n", rc);
+ goto out;
+ }
+ goto out;
+ }
+
+ if (chip->therm_lvl_sel == 0) {
+ rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't disable USB thermal ICL vote rc=%d\n",
+ rc);
+
+ rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't disable DC thermal ICL vote rc=%d\n",
+ rc);
+ } else {
+ thermal_icl_ma =
+ (int)chip->thermal_mitigation[chip->therm_lvl_sel];
+ rc = vote(chip->usb_icl_votable, THERMAL_ICL_VOTER, true,
+ thermal_icl_ma);
+ if (rc < 0)
+ pr_err("Couldn't vote for USB thermal ICL rc=%d\n", rc);
+
+ rc = vote(chip->dc_icl_votable, THERMAL_ICL_VOTER, true,
+ thermal_icl_ma);
+ if (rc < 0)
+ pr_err("Couldn't vote for DC thermal ICL rc=%d\n", rc);
+ }
+
+ if (prev_therm_lvl == chip->thermal_levels - 1) {
+ /*
+ * If previously highest value was selected charging must have
+ * been disabed. Enable charging by taking the DC and USB path
+ * out of suspend.
+ */
+ rc = vote(chip->dc_suspend_votable, THERMAL_EN_VOTER, false, 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set dc suspend rc %d\n", rc);
+ goto out;
+ }
+ rc = vote(chip->usb_suspend_votable, THERMAL_EN_VOTER,
+ false, 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set usb suspend rc %d\n", rc);
+ goto out;
+ }
+ }
+out:
+ mutex_unlock(&chip->therm_lvl_lock);
+ return rc;
+}
+
+static int smbchg_ibat_ocp_threshold_ua = 4500000;
+module_param(smbchg_ibat_ocp_threshold_ua, int, 0644);
+
+#define UCONV 1000000LL
+#define MCONV 1000LL
+#define FLASH_V_THRESHOLD 3000000
+#define FLASH_VDIP_MARGIN 100000
+#define VPH_FLASH_VDIP (FLASH_V_THRESHOLD + FLASH_VDIP_MARGIN)
+#define BUCK_EFFICIENCY 800LL
+static int smbchg_calc_max_flash_current(struct smbchg_chip *chip)
+{
+ int ocv_uv, esr_uohm, rbatt_uohm, ibat_now, rc;
+ int64_t ibat_flash_ua, avail_flash_ua, avail_flash_power_fw;
+ int64_t ibat_safe_ua, vin_flash_uv, vph_flash_uv;
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_OCV, &ocv_uv);
+ if (rc) {
+ pr_smb(PR_STATUS, "bms psy does not support OCV\n");
+ return 0;
+ }
+
+ rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_RESISTANCE,
+ &esr_uohm);
+ if (rc) {
+ pr_smb(PR_STATUS, "bms psy does not support resistance\n");
+ return 0;
+ }
+
+ rc = msm_bcl_read(BCL_PARAM_CURRENT, &ibat_now);
+ if (rc) {
+ pr_smb(PR_STATUS, "BCL current read failed: %d\n", rc);
+ return 0;
+ }
+
+ rbatt_uohm = esr_uohm + chip->rpara_uohm + chip->rslow_uohm;
+ /*
+ * Calculate the maximum current that can pulled out of the battery
+ * before the battery voltage dips below a safe threshold.
+ */
+ ibat_safe_ua = div_s64((ocv_uv - VPH_FLASH_VDIP) * UCONV,
+ rbatt_uohm);
+
+ if (ibat_safe_ua <= smbchg_ibat_ocp_threshold_ua) {
+ /*
+ * If the calculated current is below the OCP threshold, then
+ * use it as the possible flash current.
+ */
+ ibat_flash_ua = ibat_safe_ua - ibat_now;
+ vph_flash_uv = VPH_FLASH_VDIP;
+ } else {
+ /*
+ * If the calculated current is above the OCP threshold, then
+ * use the ocp threshold instead.
+ *
+ * Any higher current will be tripping the battery OCP.
+ */
+ ibat_flash_ua = smbchg_ibat_ocp_threshold_ua - ibat_now;
+ vph_flash_uv = ocv_uv - div64_s64((int64_t)rbatt_uohm
+ * smbchg_ibat_ocp_threshold_ua, UCONV);
+ }
+ /* Calculate the input voltage of the flash module. */
+ vin_flash_uv = max((chip->vled_max_uv + 500000LL),
+ div64_s64((vph_flash_uv * 1200), 1000));
+ /* Calculate the available power for the flash module. */
+ avail_flash_power_fw = BUCK_EFFICIENCY * vph_flash_uv * ibat_flash_ua;
+ /*
+ * Calculate the available amount of current the flash module can draw
+ * before collapsing the battery. (available power/ flash input voltage)
+ */
+ avail_flash_ua = div64_s64(avail_flash_power_fw, vin_flash_uv * MCONV);
+ pr_smb(PR_MISC,
+ "avail_iflash=%lld, ocv=%d, ibat=%d, rbatt=%d\n",
+ avail_flash_ua, ocv_uv, ibat_now, rbatt_uohm);
+ return (int)avail_flash_ua;
+}
+
+#define FCC_CMP_CFG 0xF3
+#define FCC_COMP_MASK SMB_MASK(1, 0)
+static int smbchg_fastchg_current_comp_set(struct smbchg_chip *chip,
+ int comp_current)
+{
+ int rc;
+ u8 i;
+
+ for (i = 0; i < chip->tables.fcc_comp_len; i++)
+ if (comp_current == chip->tables.fcc_comp_table[i])
+ break;
+
+ if (i >= chip->tables.fcc_comp_len)
+ return -EINVAL;
+
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CMP_CFG,
+ FCC_COMP_MASK, i);
+
+ if (rc)
+ dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n",
+ rc);
+
+ return rc;
+}
+
+#define CFG_TCC_REG 0xF9
+#define CHG_ITERM_MASK SMB_MASK(2, 0)
+static int smbchg_iterm_set(struct smbchg_chip *chip, int iterm_ma)
+{
+ int rc;
+ u8 reg;
+
+ reg = find_closest_in_array(
+ chip->tables.iterm_ma_table,
+ chip->tables.iterm_ma_len,
+ iterm_ma);
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->chgr_base + CFG_TCC_REG,
+ CHG_ITERM_MASK, reg);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "set tcc (%d) to 0x%02x\n",
+ iterm_ma, reg);
+ chip->iterm_ma = iterm_ma;
+
+ return 0;
+}
+
+#define FV_CMP_CFG 0xF5
+#define FV_COMP_MASK SMB_MASK(5, 0)
+static int smbchg_float_voltage_comp_set(struct smbchg_chip *chip, int code)
+{
+ int rc;
+ u8 val;
+
+ val = code & FV_COMP_MASK;
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + FV_CMP_CFG,
+ FV_COMP_MASK, val);
+
+ if (rc)
+ dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n",
+ rc);
+
+ return rc;
+}
+
+#define VFLOAT_CFG_REG 0xF4
+#define MIN_FLOAT_MV 3600
+#define MAX_FLOAT_MV 4500
+#define VFLOAT_MASK SMB_MASK(5, 0)
+
+#define MID_RANGE_FLOAT_MV_MIN 3600
+#define MID_RANGE_FLOAT_MIN_VAL 0x05
+#define MID_RANGE_FLOAT_STEP_MV 20
+
+#define HIGH_RANGE_FLOAT_MIN_MV 4340
+#define HIGH_RANGE_FLOAT_MIN_VAL 0x2A
+#define HIGH_RANGE_FLOAT_STEP_MV 10
+
+#define VHIGH_RANGE_FLOAT_MIN_MV 4360
+#define VHIGH_RANGE_FLOAT_MIN_VAL 0x2C
+#define VHIGH_RANGE_FLOAT_STEP_MV 20
+static int smbchg_float_voltage_set(struct smbchg_chip *chip, int vfloat_mv)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval prop;
+ int rc, delta;
+ u8 temp;
+
+ if ((vfloat_mv < MIN_FLOAT_MV) || (vfloat_mv > MAX_FLOAT_MV)) {
+ dev_err(chip->dev, "bad float voltage mv =%d asked to set\n",
+ vfloat_mv);
+ return -EINVAL;
+ }
+
+ if (vfloat_mv <= HIGH_RANGE_FLOAT_MIN_MV) {
+ /* mid range */
+ delta = vfloat_mv - MID_RANGE_FLOAT_MV_MIN;
+ temp = MID_RANGE_FLOAT_MIN_VAL + delta
+ / MID_RANGE_FLOAT_STEP_MV;
+ vfloat_mv -= delta % MID_RANGE_FLOAT_STEP_MV;
+ } else if (vfloat_mv <= VHIGH_RANGE_FLOAT_MIN_MV) {
+ /* high range */
+ delta = vfloat_mv - HIGH_RANGE_FLOAT_MIN_MV;
+ temp = HIGH_RANGE_FLOAT_MIN_VAL + delta
+ / HIGH_RANGE_FLOAT_STEP_MV;
+ vfloat_mv -= delta % HIGH_RANGE_FLOAT_STEP_MV;
+ } else {
+ /* very high range */
+ delta = vfloat_mv - VHIGH_RANGE_FLOAT_MIN_MV;
+ temp = VHIGH_RANGE_FLOAT_MIN_VAL + delta
+ / VHIGH_RANGE_FLOAT_STEP_MV;
+ vfloat_mv -= delta % VHIGH_RANGE_FLOAT_STEP_MV;
+ }
+
+ if (parallel_psy) {
+ prop.intval = vfloat_mv + 50;
+ rc = power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX, &prop);
+ if (rc)
+ dev_err(chip->dev, "Couldn't set float voltage on parallel psy rc: %d\n",
+ rc);
+ }
+
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + VFLOAT_CFG_REG,
+ VFLOAT_MASK, temp);
+
+ if (rc)
+ dev_err(chip->dev, "Couldn't set float voltage rc = %d\n", rc);
+ else
+ chip->vfloat_mv = vfloat_mv;
+
+ return rc;
+}
+
+static int smbchg_float_voltage_get(struct smbchg_chip *chip)
+{
+ return chip->vfloat_mv;
+}
+
+#define SFT_CFG 0xFD
+#define SFT_EN_MASK SMB_MASK(5, 4)
+#define SFT_TO_MASK SMB_MASK(3, 2)
+#define PRECHG_SFT_TO_MASK SMB_MASK(1, 0)
+#define SFT_TIMER_DISABLE_BIT BIT(5)
+#define PRECHG_SFT_TIMER_DISABLE_BIT BIT(4)
+#define SAFETY_TIME_MINUTES_SHIFT 2
+static int smbchg_safety_timer_enable(struct smbchg_chip *chip, bool enable)
+{
+ int rc;
+ u8 reg;
+
+ if (enable == chip->safety_timer_en)
+ return 0;
+
+ if (enable)
+ reg = 0;
+ else
+ reg = SFT_TIMER_DISABLE_BIT | PRECHG_SFT_TIMER_DISABLE_BIT;
+
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + SFT_CFG,
+ SFT_EN_MASK, reg);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't %s safety timer rc = %d\n",
+ enable ? "enable" : "disable", rc);
+ return rc;
+ }
+ chip->safety_timer_en = enable;
+ return 0;
+}
+
+enum skip_reason {
+ REASON_OTG_ENABLED = BIT(0),
+ REASON_FLASH_ENABLED = BIT(1)
+};
+
+#define BAT_IF_TRIM7_REG 0xF7
+#define CFG_750KHZ_BIT BIT(1)
+#define MISC_CFG_NTC_VOUT_REG 0xF3
+#define CFG_NTC_VOUT_FSW_BIT BIT(0)
+static int smbchg_switch_buck_frequency(struct smbchg_chip *chip,
+ bool flash_active)
+{
+ int rc;
+
+ if (!(chip->wa_flags & SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA))
+ return 0;
+
+ if (chip->flash_active == flash_active) {
+ pr_smb(PR_STATUS, "Fsw not changed, flash_active: %d\n",
+ flash_active);
+ return 0;
+ }
+
+ /*
+ * As per the systems team recommendation, before the flash fires,
+ * buck switching frequency(Fsw) needs to be increased to 1MHz. Once the
+ * flash is disabled, Fsw needs to be set back to 750KHz.
+ */
+ rc = smbchg_sec_masked_write(chip, chip->misc_base +
+ MISC_CFG_NTC_VOUT_REG, CFG_NTC_VOUT_FSW_BIT,
+ flash_active ? CFG_NTC_VOUT_FSW_BIT : 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set switching frequency multiplier rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BAT_IF_TRIM7_REG,
+ CFG_750KHZ_BIT, flash_active ? 0 : CFG_750KHZ_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Cannot set switching freq: %d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_STATUS, "Fsw @ %sHz\n", flash_active ? "1M" : "750K");
+ chip->flash_active = flash_active;
+ return 0;
+}
+
+#define OTG_TRIM6 0xF6
+#define TR_ENB_SKIP_BIT BIT(2)
+#define OTG_EN_BIT BIT(0)
+static int smbchg_otg_pulse_skip_disable(struct smbchg_chip *chip,
+ enum skip_reason reason, bool disable)
+{
+ int rc;
+ bool disabled;
+
+ disabled = !!chip->otg_pulse_skip_dis;
+ pr_smb(PR_STATUS, "%s pulse skip, reason %d\n",
+ disable ? "disabling" : "enabling", reason);
+ if (disable)
+ chip->otg_pulse_skip_dis |= reason;
+ else
+ chip->otg_pulse_skip_dis &= ~reason;
+ if (disabled == !!chip->otg_pulse_skip_dis)
+ return 0;
+ disabled = !!chip->otg_pulse_skip_dis;
+
+ rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_TRIM6,
+ TR_ENB_SKIP_BIT, disabled ? TR_ENB_SKIP_BIT : 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't %s otg pulse skip rc = %d\n",
+ disabled ? "disable" : "enable", rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "%s pulse skip\n", disabled ? "disabled" : "enabled");
+ return 0;
+}
+
+#define LOW_PWR_OPTIONS_REG 0xFF
+#define FORCE_TLIM_BIT BIT(4)
+static int smbchg_force_tlim_en(struct smbchg_chip *chip, bool enable)
+{
+ int rc;
+
+ rc = smbchg_sec_masked_write(chip, chip->otg_base + LOW_PWR_OPTIONS_REG,
+ FORCE_TLIM_BIT, enable ? FORCE_TLIM_BIT : 0);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't %s otg force tlim rc = %d\n",
+ enable ? "enable" : "disable", rc);
+ return rc;
+ }
+ return rc;
+}
+
+static void smbchg_vfloat_adjust_check(struct smbchg_chip *chip)
+{
+ if (!chip->use_vfloat_adjustments)
+ return;
+
+ smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST);
+ pr_smb(PR_STATUS, "Starting vfloat adjustments\n");
+ schedule_delayed_work(&chip->vfloat_adjust_work, 0);
+}
+
+#define FV_STS_REG 0xC
+#define AICL_INPUT_STS_BIT BIT(6)
+static bool smbchg_is_input_current_limited(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->chgr_base + FV_STS_REG, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read FV_STS rc=%d\n", rc);
+ return false;
+ }
+
+ return !!(reg & AICL_INPUT_STS_BIT);
+}
+
+#define SW_ESR_PULSE_MS 1500
+static void smbchg_cc_esr_wa_check(struct smbchg_chip *chip)
+{
+ int rc, esr_count;
+
+ if (!(chip->wa_flags & SMBCHG_CC_ESR_WA))
+ return;
+
+ if (!is_usb_present(chip) && !is_dc_present(chip)) {
+ pr_smb(PR_STATUS, "No inputs present, skipping\n");
+ return;
+ }
+
+ if (get_prop_charge_type(chip) != POWER_SUPPLY_CHARGE_TYPE_FAST) {
+ pr_smb(PR_STATUS, "Not in fast charge, skipping\n");
+ return;
+ }
+
+ if (!smbchg_is_input_current_limited(chip)) {
+ pr_smb(PR_STATUS, "Not input current limited, skipping\n");
+ return;
+ }
+
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1);
+ rc = get_property_from_fg(chip,
+ POWER_SUPPLY_PROP_ESR_COUNT, &esr_count);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "could not read ESR counter rc = %d\n", rc);
+ return;
+ }
+
+ /*
+ * The esr_count is counting down the number of fuel gauge cycles
+ * before a ESR pulse is needed.
+ *
+ * After a successful ESR pulse, this count is reset to some
+ * high number like 28. If this reaches 0, then the fuel gauge
+ * hardware should force a ESR pulse.
+ *
+ * However, if the device is in constant current charge mode while
+ * being input current limited, the ESR pulse will not affect the
+ * battery current, so the measurement will fail.
+ *
+ * As a failsafe, force a manual ESR pulse if this value is read as
+ * 0.
+ */
+ if (esr_count != 0) {
+ pr_smb(PR_STATUS, "ESR count is not zero, skipping\n");
+ return;
+ }
+
+ pr_smb(PR_STATUS, "Lowering charge current for ESR pulse\n");
+ smbchg_stay_awake(chip, PM_ESR_PULSE);
+ smbchg_sw_esr_pulse_en(chip, true);
+ msleep(SW_ESR_PULSE_MS);
+ pr_smb(PR_STATUS, "Raising charge current for ESR pulse\n");
+ smbchg_relax(chip, PM_ESR_PULSE);
+ smbchg_sw_esr_pulse_en(chip, false);
+}
+
+static void smbchg_soc_changed(struct smbchg_chip *chip)
+{
+ smbchg_cc_esr_wa_check(chip);
+}
+
+#define DC_AICL_CFG 0xF3
+#define MISC_TRIM_OPT_15_8 0xF5
+#define USB_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3))
+#define USB_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3))
+#define USB_AICL_DEGLITCH_LONG 0
+#define DC_AICL_DEGLITCH_MASK (BIT(5) | BIT(4) | BIT(3))
+#define DC_AICL_DEGLITCH_SHORT (BIT(5) | BIT(4) | BIT(3))
+#define DC_AICL_DEGLITCH_LONG 0
+#define AICL_RERUN_MASK (BIT(5) | BIT(4))
+#define AICL_RERUN_ON (BIT(5) | BIT(4))
+#define AICL_RERUN_OFF 0
+
+static int smbchg_hw_aicl_rerun_enable_indirect_cb(struct votable *votable,
+ void *data,
+ int enable,
+ const char *client)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = data;
+
+ if (enable < 0) {
+ pr_err("No voters\n");
+ enable = 0;
+ }
+ /*
+ * If the indirect voting result of all the clients is to enable hw aicl
+ * rerun, then remove our vote to disable hw aicl rerun
+ */
+ rc = vote(chip->hw_aicl_rerun_disable_votable,
+ HW_AICL_RERUN_ENABLE_INDIRECT_VOTER, !enable, 0);
+ if (rc < 0) {
+ pr_err("Couldn't vote for hw rerun rc= %d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int smbchg_hw_aicl_rerun_disable_cb(struct votable *votable, void *data,
+ int disable,
+ const char *client)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = data;
+
+ if (disable < 0) {
+ pr_err("No voters\n");
+ disable = 0;
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->misc_base + MISC_TRIM_OPT_15_8,
+ AICL_RERUN_MASK, disable ? AICL_RERUN_OFF : AICL_RERUN_ON);
+ if (rc < 0)
+ pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smbchg_aicl_deglitch_config_cb(struct votable *votable, void *data,
+ int shorter,
+ const char *client)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = data;
+
+ if (shorter < 0) {
+ pr_err("No voters\n");
+ shorter = 0;
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + USB_AICL_CFG,
+ USB_AICL_DEGLITCH_MASK,
+ shorter ? USB_AICL_DEGLITCH_SHORT : USB_AICL_DEGLITCH_LONG);
+ if (rc < 0) {
+ pr_err("Couldn't write to USB_AICL_CFG rc=%d\n", rc);
+ return rc;
+ }
+ rc = smbchg_sec_masked_write(chip,
+ chip->dc_chgpth_base + DC_AICL_CFG,
+ DC_AICL_DEGLITCH_MASK,
+ shorter ? DC_AICL_DEGLITCH_SHORT : DC_AICL_DEGLITCH_LONG);
+ if (rc < 0) {
+ pr_err("Couldn't write to DC_AICL_CFG rc=%d\n", rc);
+ return rc;
+ }
+ return rc;
+}
+
+static void smbchg_aicl_deglitch_wa_en(struct smbchg_chip *chip, bool en)
+{
+ int rc;
+
+ rc = vote(chip->aicl_deglitch_short_votable,
+ VARB_WORKAROUND_VOTER, en, 0);
+ if (rc < 0) {
+ pr_err("Couldn't vote %s deglitch rc=%d\n",
+ en ? "short" : "long", rc);
+ return;
+ }
+ pr_smb(PR_STATUS, "AICL deglitch set to %s\n", en ? "short" : "long");
+
+ rc = vote(chip->hw_aicl_rerun_enable_indirect_votable,
+ VARB_WORKAROUND_VOTER, en, 0);
+ if (rc < 0) {
+ pr_err("Couldn't vote hw aicl rerun rc= %d\n", rc);
+ return;
+ }
+ chip->aicl_deglitch_short = en;
+}
+
+static void smbchg_aicl_deglitch_wa_check(struct smbchg_chip *chip)
+{
+ union power_supply_propval prop = {0,};
+ int rc;
+ bool low_volt_chgr = true;
+
+ if (!(chip->wa_flags & SMBCHG_AICL_DEGLITCH_WA))
+ return;
+
+ if (!is_usb_present(chip) && !is_dc_present(chip)) {
+ pr_smb(PR_STATUS, "Charger removed\n");
+ smbchg_aicl_deglitch_wa_en(chip, false);
+ return;
+ }
+
+ if (!chip->bms_psy)
+ return;
+
+ if (is_usb_present(chip)) {
+ if (is_hvdcp_present(chip))
+ low_volt_chgr = false;
+ } else if (is_dc_present(chip)) {
+ if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER)
+ low_volt_chgr = false;
+ else
+ low_volt_chgr = chip->low_volt_dcin;
+ }
+
+ if (!low_volt_chgr) {
+ pr_smb(PR_STATUS, "High volt charger! Don't set deglitch\n");
+ smbchg_aicl_deglitch_wa_en(chip, false);
+ return;
+ }
+
+ /* It is possible that battery voltage went high above threshold
+ * when the charger is inserted and can go low because of system
+ * load. We shouldn't be reconfiguring AICL deglitch when this
+ * happens as it will lead to oscillation again which is being
+ * fixed here. Do it once when the battery voltage crosses the
+ * threshold (e.g. 4.2 V) and clear it only when the charger
+ * is removed.
+ */
+ if (!chip->vbat_above_headroom) {
+ rc = power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_VOLTAGE_MIN, &prop);
+ if (rc < 0) {
+ pr_err("could not read voltage_min, rc=%d\n", rc);
+ return;
+ }
+ chip->vbat_above_headroom = !prop.intval;
+ }
+ smbchg_aicl_deglitch_wa_en(chip, chip->vbat_above_headroom);
+}
+
+#define MISC_TEST_REG 0xE2
+#define BB_LOOP_DISABLE_ICL BIT(2)
+static int smbchg_icl_loop_disable_check(struct smbchg_chip *chip)
+{
+ bool icl_disabled = !chip->chg_otg_enabled && chip->flash_triggered;
+ int rc = 0;
+
+ if ((chip->wa_flags & SMBCHG_FLASH_ICL_DISABLE_WA)
+ && icl_disabled != chip->icl_disabled) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->misc_base + MISC_TEST_REG,
+ BB_LOOP_DISABLE_ICL,
+ icl_disabled ? BB_LOOP_DISABLE_ICL : 0);
+ chip->icl_disabled = icl_disabled;
+ }
+
+ return rc;
+}
+
+#define UNKNOWN_BATT_TYPE "Unknown Battery"
+#define LOADING_BATT_TYPE "Loading Battery Data"
+static int smbchg_config_chg_battery_type(struct smbchg_chip *chip)
+{
+ int rc = 0, max_voltage_uv = 0, fastchg_ma = 0, ret = 0, iterm_ua = 0;
+ struct device_node *batt_node, *profile_node;
+ struct device_node *node = chip->pdev->dev.of_node;
+ union power_supply_propval prop = {0,};
+
+ rc = power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_BATTERY_TYPE, &prop);
+ if (rc) {
+ pr_smb(PR_STATUS, "Unable to read battery-type rc=%d\n", rc);
+ return 0;
+ }
+ if (!strcmp(prop.strval, UNKNOWN_BATT_TYPE) ||
+ !strcmp(prop.strval, LOADING_BATT_TYPE)) {
+ pr_smb(PR_MISC, "Battery-type not identified\n");
+ return 0;
+ }
+ /* quit if there is no change in the battery-type from previous */
+ if (chip->battery_type && !strcmp(prop.strval, chip->battery_type))
+ return 0;
+
+ chip->battery_type = prop.strval;
+ batt_node = of_parse_phandle(node, "qcom,battery-data", 0);
+ if (!batt_node) {
+ pr_smb(PR_MISC, "No batterydata available\n");
+ return 0;
+ }
+
+ rc = power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_RESISTANCE_ID, &prop);
+ if (rc < 0) {
+ pr_smb(PR_STATUS, "Unable to read battery-id rc=%d\n", rc);
+ return 0;
+ }
+
+ profile_node = of_batterydata_get_best_profile(batt_node,
+ prop.intval / 1000, NULL);
+ if (IS_ERR_OR_NULL(profile_node)) {
+ rc = PTR_ERR(profile_node);
+ pr_err("couldn't find profile handle %d\n", rc);
+ return rc;
+ }
+
+ /* change vfloat */
+ rc = of_property_read_u32(profile_node, "qcom,max-voltage-uv",
+ &max_voltage_uv);
+ if (rc) {
+ pr_warn("couldn't find battery max voltage rc=%d\n", rc);
+ ret = rc;
+ } else {
+ if (chip->vfloat_mv != (max_voltage_uv / 1000)) {
+ pr_info("Vfloat changed from %dmV to %dmV for battery-type %s\n",
+ chip->vfloat_mv, (max_voltage_uv / 1000),
+ chip->battery_type);
+ rc = smbchg_float_voltage_set(chip,
+ (max_voltage_uv / 1000));
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set float voltage rc = %d\n", rc);
+ return rc;
+ }
+ }
+ }
+
+ /* change chg term */
+ rc = of_property_read_u32(profile_node, "qcom,chg-term-ua",
+ &iterm_ua);
+ if (rc && rc != -EINVAL) {
+ pr_warn("couldn't read battery term current=%d\n", rc);
+ ret = rc;
+ } else if (!rc) {
+ if (chip->iterm_ma != (iterm_ua / 1000)
+ && !chip->iterm_disabled) {
+ pr_info("Term current changed from %dmA to %dmA for battery-type %s\n",
+ chip->iterm_ma, (iterm_ua / 1000),
+ chip->battery_type);
+ rc = smbchg_iterm_set(chip,
+ (iterm_ua / 1000));
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set iterm rc = %d\n", rc);
+ return rc;
+ }
+ }
+ chip->iterm_ma = iterm_ua / 1000;
+ }
+
+ /*
+ * Only configure from profile if fastchg-ma is not defined in the
+ * charger device node.
+ */
+ if (!of_find_property(chip->pdev->dev.of_node,
+ "qcom,fastchg-current-ma", NULL)) {
+ rc = of_property_read_u32(profile_node,
+ "qcom,fastchg-current-ma", &fastchg_ma);
+ if (rc) {
+ ret = rc;
+ } else {
+ pr_smb(PR_MISC,
+ "fastchg-ma changed from to %dma for battery-type %s\n",
+ fastchg_ma, chip->battery_type);
+ rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true,
+ fastchg_ma);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't vote for fastchg current rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+ }
+
+ return ret;
+}
+
+#define MAX_INV_BATT_ID 7700
+#define MIN_INV_BATT_ID 7300
+static void check_battery_type(struct smbchg_chip *chip)
+{
+ union power_supply_propval prop = {0,};
+ bool en;
+
+ if (!chip->bms_psy && chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+ if (chip->bms_psy) {
+ power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_BATTERY_TYPE, &prop);
+ en = (strcmp(prop.strval, UNKNOWN_BATT_TYPE) != 0
+ || chip->charge_unknown_battery)
+ && (strcmp(prop.strval, LOADING_BATT_TYPE) != 0);
+ vote(chip->battchg_suspend_votable,
+ BATTCHG_UNKNOWN_BATTERY_EN_VOTER, !en, 0);
+
+ if (!chip->skip_usb_suspend_for_fake_battery) {
+ power_supply_get_property(chip->bms_psy,
+ POWER_SUPPLY_PROP_RESISTANCE_ID, &prop);
+ /* suspend USB path for invalid battery-id */
+ en = (prop.intval <= MAX_INV_BATT_ID &&
+ prop.intval >= MIN_INV_BATT_ID) ? 1 : 0;
+ vote(chip->usb_suspend_votable, FAKE_BATTERY_EN_VOTER,
+ en, 0);
+ }
+ }
+}
+
+static void smbchg_external_power_changed(struct power_supply *psy)
+{
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+ union power_supply_propval prop = {0,};
+ int rc, current_limit = 0, soc;
+ enum power_supply_type usb_supply_type;
+ char *usb_type_name = "null";
+
+ if (chip->bms_psy_name)
+ chip->bms_psy =
+ power_supply_get_by_name((char *)chip->bms_psy_name);
+
+ smbchg_aicl_deglitch_wa_check(chip);
+ if (chip->bms_psy) {
+ check_battery_type(chip);
+ soc = get_prop_batt_capacity(chip);
+ if (chip->previous_soc != soc) {
+ chip->previous_soc = soc;
+ smbchg_soc_changed(chip);
+ }
+
+ rc = smbchg_config_chg_battery_type(chip);
+ if (rc)
+ pr_smb(PR_MISC,
+ "Couldn't update charger configuration rc=%d\n",
+ rc);
+ }
+
+ rc = power_supply_get_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED, &prop);
+ if (rc == 0)
+ vote(chip->usb_suspend_votable, POWER_SUPPLY_EN_VOTER,
+ !prop.intval, 0);
+
+ current_limit = chip->usb_current_max / 1000;
+
+ /* Override if type-c charger used */
+ if (chip->typec_current_ma > 500 &&
+ current_limit < chip->typec_current_ma)
+ current_limit = chip->typec_current_ma;
+
+ read_usb_type(chip, &usb_type_name, &usb_supply_type);
+
+ if (usb_supply_type != POWER_SUPPLY_TYPE_USB)
+ goto skip_current_for_non_sdp;
+
+ pr_smb(PR_MISC, "usb type = %s current_limit = %d\n",
+ usb_type_name, current_limit);
+
+ rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true,
+ current_limit);
+ if (rc < 0)
+ pr_err("Couldn't update USB PSY ICL vote rc=%d\n", rc);
+
+skip_current_for_non_sdp:
+ smbchg_vfloat_adjust_check(chip);
+
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+}
+
+static int smbchg_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ chip->otg_retries = 0;
+ chip->chg_otg_enabled = true;
+ smbchg_icl_loop_disable_check(chip);
+ smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, true);
+
+ /* If pin control mode then return from here */
+ if (chip->otg_pinctrl)
+ return rc;
+
+ /* sleep to make sure the pulse skip is actually disabled */
+ msleep(20);
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ OTG_EN_BIT, OTG_EN_BIT);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't enable OTG mode rc=%d\n", rc);
+ else
+ chip->otg_enable_time = ktime_get();
+ pr_smb(PR_STATUS, "Enabling OTG Boost\n");
+ return rc;
+}
+
+static int smbchg_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ if (!chip->otg_pinctrl) {
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ OTG_EN_BIT, 0);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't disable OTG mode rc=%d\n",
+ rc);
+ }
+
+ chip->chg_otg_enabled = false;
+ smbchg_otg_pulse_skip_disable(chip, REASON_OTG_ENABLED, false);
+ smbchg_icl_loop_disable_check(chip);
+ pr_smb(PR_STATUS, "Disabling OTG Boost\n");
+ return rc;
+}
+
+static int smbchg_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ u8 reg = 0;
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ rc = smbchg_read(chip, &reg, chip->bat_if_base + CMD_CHG_REG, 1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read OTG enable bit rc=%d\n", rc);
+ return rc;
+ }
+
+ return (reg & OTG_EN_BIT) ? 1 : 0;
+}
+
+struct regulator_ops smbchg_otg_reg_ops = {
+ .enable = smbchg_otg_regulator_enable,
+ .disable = smbchg_otg_regulator_disable,
+ .is_enabled = smbchg_otg_regulator_is_enable,
+};
+
+#define USBIN_CHGR_CFG 0xF1
+#define ADAPTER_ALLOWANCE_MASK 0x7
+#define USBIN_ADAPTER_9V 0x3
+#define USBIN_ADAPTER_5V_9V_CONT 0x2
+#define USBIN_ADAPTER_5V_UNREGULATED_9V 0x5
+#define HVDCP_EN_BIT BIT(3)
+static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, true, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't suspend charger rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_read(chip, &chip->original_usbin_allowance,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * To disallow source detect and usbin_uv interrupts, set the adapter
+ * allowance to 9V, so that the audio boost operating in reverse never
+ * gets detected as a valid input
+ */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG,
+ 0xFF, USBIN_ADAPTER_9V);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_STATUS, "Enabling OTG Boost\n");
+ return rc;
+}
+
+static int smbchg_external_otg_regulator_disable(struct regulator_dev *rdev)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, false, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't unsuspend charger rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Reenable HVDCP and set the adapter allowance back to the original
+ * value in order to allow normal USBs to be recognized as a valid
+ * input.
+ */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, HVDCP_EN_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable HVDCP rc=%d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG,
+ 0xFF, chip->original_usbin_allowance);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_STATUS, "Disabling OTG Boost\n");
+ return rc;
+}
+
+static int smbchg_external_otg_regulator_is_enable(struct regulator_dev *rdev)
+{
+ struct smbchg_chip *chip = rdev_get_drvdata(rdev);
+
+ return get_client_vote(chip->usb_suspend_votable, OTG_EN_VOTER);
+}
+
+struct regulator_ops smbchg_external_otg_reg_ops = {
+ .enable = smbchg_external_otg_regulator_enable,
+ .disable = smbchg_external_otg_regulator_disable,
+ .is_enabled = smbchg_external_otg_regulator_is_enable,
+};
+
+static int smbchg_regulator_init(struct smbchg_chip *chip)
+{
+ int rc = 0;
+ struct regulator_config cfg = {};
+ struct device_node *regulator_node;
+
+ cfg.dev = chip->dev;
+ cfg.driver_data = chip;
+
+ chip->otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->otg_vreg.rdesc.ops = &smbchg_otg_reg_ops;
+ chip->otg_vreg.rdesc.of_match = "qcom,smbcharger-boost-otg";
+ chip->otg_vreg.rdesc.name = "qcom,smbcharger-boost-otg";
+
+ chip->otg_vreg.rdev = devm_regulator_register(chip->dev,
+ &chip->otg_vreg.rdesc, &cfg);
+ if (IS_ERR(chip->otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->otg_vreg.rdev);
+ chip->otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ dev_err(chip->dev,
+ "OTG reg failed, rc=%d\n", rc);
+ }
+ if (rc)
+ return rc;
+
+ regulator_node = of_get_child_by_name(chip->dev->of_node,
+ "qcom,smbcharger-external-otg");
+ if (!regulator_node) {
+ dev_dbg(chip->dev, "external-otg node absent\n");
+ return 0;
+ }
+
+ chip->ext_otg_vreg.rdesc.owner = THIS_MODULE;
+ chip->ext_otg_vreg.rdesc.type = REGULATOR_VOLTAGE;
+ chip->ext_otg_vreg.rdesc.ops = &smbchg_external_otg_reg_ops;
+ chip->ext_otg_vreg.rdesc.of_match = "qcom,smbcharger-external-otg";
+ chip->ext_otg_vreg.rdesc.name = "qcom,smbcharger-external-otg";
+ if (of_get_property(chip->dev->of_node, "otg-parent-supply", NULL))
+ chip->ext_otg_vreg.rdesc.supply_name = "otg-parent";
+ cfg.dev = chip->dev;
+ cfg.driver_data = chip;
+
+ chip->ext_otg_vreg.rdev = devm_regulator_register(chip->dev,
+ &chip->ext_otg_vreg.rdesc,
+ &cfg);
+ if (IS_ERR(chip->ext_otg_vreg.rdev)) {
+ rc = PTR_ERR(chip->ext_otg_vreg.rdev);
+ chip->ext_otg_vreg.rdev = NULL;
+ if (rc != -EPROBE_DEFER)
+ dev_err(chip->dev,
+ "external OTG reg failed, rc=%d\n", rc);
+ }
+
+ return rc;
+}
+
+#define CMD_CHG_LED_REG 0x43
+#define CHG_LED_CTRL_BIT BIT(0)
+#define LED_SW_CTRL_BIT 0x1
+#define LED_CHG_CTRL_BIT 0x0
+#define CHG_LED_ON 0x03
+#define CHG_LED_OFF 0x00
+#define LED_BLINKING_PATTERN1 0x01
+#define LED_BLINKING_PATTERN2 0x02
+#define LED_BLINKING_CFG_MASK SMB_MASK(2, 1)
+#define CHG_LED_SHIFT 1
+static int smbchg_chg_led_controls(struct smbchg_chip *chip)
+{
+ u8 reg, mask;
+ int rc;
+
+ if (chip->cfg_chg_led_sw_ctrl) {
+ /* turn-off LED by default for software control */
+ mask = CHG_LED_CTRL_BIT | LED_BLINKING_CFG_MASK;
+ reg = LED_SW_CTRL_BIT;
+ } else {
+ mask = CHG_LED_CTRL_BIT;
+ reg = LED_CHG_CTRL_BIT;
+ }
+
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_LED_REG,
+ mask, reg);
+ if (rc < 0)
+ dev_err(chip->dev,
+ "Couldn't write LED_CTRL_BIT rc=%d\n", rc);
+ return rc;
+}
+
+static void smbchg_chg_led_brightness_set(struct led_classdev *cdev,
+ enum led_brightness value)
+{
+ struct smbchg_chip *chip = container_of(cdev,
+ struct smbchg_chip, led_cdev);
+ union power_supply_propval pval = {0, };
+ u8 reg;
+ int rc;
+
+ reg = (value > LED_OFF) ? CHG_LED_ON << CHG_LED_SHIFT :
+ CHG_LED_OFF << CHG_LED_SHIFT;
+ pval.intval = value > LED_OFF ? 1 : 0;
+ power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER,
+ &pval);
+ pr_smb(PR_STATUS,
+ "set the charger led brightness to value=%d\n",
+ value);
+ rc = smbchg_sec_masked_write(chip,
+ chip->bat_if_base + CMD_CHG_LED_REG,
+ LED_BLINKING_CFG_MASK, reg);
+ if (rc)
+ dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n",
+ rc);
+}
+
+static enum
+led_brightness smbchg_chg_led_brightness_get(struct led_classdev *cdev)
+{
+ struct smbchg_chip *chip = container_of(cdev,
+ struct smbchg_chip, led_cdev);
+ u8 reg_val, chg_led_sts;
+ int rc;
+
+ rc = smbchg_read(chip, &reg_val, chip->bat_if_base + CMD_CHG_LED_REG,
+ 1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read CHG_LED_REG sts rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ chg_led_sts = (reg_val & LED_BLINKING_CFG_MASK) >> CHG_LED_SHIFT;
+
+ pr_smb(PR_STATUS, "chg_led_sts = %02x\n", chg_led_sts);
+
+ return (chg_led_sts == CHG_LED_OFF) ? LED_OFF : LED_FULL;
+}
+
+static void smbchg_chg_led_blink_set(struct smbchg_chip *chip,
+ unsigned long blinking)
+{
+ union power_supply_propval pval = {0, };
+ u8 reg;
+ int rc;
+
+ pval.intval = (blinking == 0) ? 0 : 1;
+ power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER,
+ &pval);
+
+ if (blinking == 0) {
+ reg = CHG_LED_OFF << CHG_LED_SHIFT;
+ } else {
+ if (blinking == 1)
+ reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT;
+ else if (blinking == 2)
+ reg = LED_BLINKING_PATTERN2 << CHG_LED_SHIFT;
+ else
+ reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT;
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->bat_if_base + CMD_CHG_LED_REG,
+ LED_BLINKING_CFG_MASK, reg);
+ if (rc)
+ dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n",
+ rc);
+}
+
+static ssize_t smbchg_chg_led_blink_store(struct device *dev,
+ struct device_attribute *attr, const char *buf, size_t len)
+{
+ struct led_classdev *cdev = dev_get_drvdata(dev);
+ struct smbchg_chip *chip = container_of(cdev, struct smbchg_chip,
+ led_cdev);
+ unsigned long blinking;
+ ssize_t rc = -EINVAL;
+
+ rc = kstrtoul(buf, 10, &blinking);
+ if (rc)
+ return rc;
+
+ smbchg_chg_led_blink_set(chip, blinking);
+
+ return len;
+}
+
+static DEVICE_ATTR(blink, 0664, NULL, smbchg_chg_led_blink_store);
+
+static struct attribute *led_blink_attributes[] = {
+ &dev_attr_blink.attr,
+ NULL,
+};
+
+static struct attribute_group smbchg_led_attr_group = {
+ .attrs = led_blink_attributes
+};
+
+static int smbchg_register_chg_led(struct smbchg_chip *chip)
+{
+ int rc;
+
+ chip->led_cdev.name = "red";
+ chip->led_cdev.brightness_set = smbchg_chg_led_brightness_set;
+ chip->led_cdev.brightness_get = smbchg_chg_led_brightness_get;
+
+ rc = led_classdev_register(chip->dev, &chip->led_cdev);
+ if (rc) {
+ dev_err(chip->dev, "unable to register charger led, rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = sysfs_create_group(&chip->led_cdev.dev->kobj,
+ &smbchg_led_attr_group);
+ if (rc) {
+ dev_err(chip->dev, "led sysfs rc: %d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+static int vf_adjust_low_threshold = 5;
+module_param(vf_adjust_low_threshold, int, 0644);
+
+static int vf_adjust_high_threshold = 7;
+module_param(vf_adjust_high_threshold, int, 0644);
+
+static int vf_adjust_n_samples = 10;
+module_param(vf_adjust_n_samples, int, 0644);
+
+static int vf_adjust_max_delta_mv = 40;
+module_param(vf_adjust_max_delta_mv, int, 0644);
+
+static int vf_adjust_trim_steps_per_adjust = 1;
+module_param(vf_adjust_trim_steps_per_adjust, int, 0644);
+
+#define CENTER_TRIM_CODE 7
+#define MAX_LIN_CODE 14
+#define MAX_TRIM_CODE 15
+#define SCALE_SHIFT 4
+#define VF_TRIM_OFFSET_MASK SMB_MASK(3, 0)
+#define VF_STEP_SIZE_MV 10
+#define SCALE_LSB_MV 17
+static int smbchg_trim_add_steps(int prev_trim, int delta_steps)
+{
+ int scale_steps;
+ int linear_offset, linear_scale;
+ int offset_code = prev_trim & VF_TRIM_OFFSET_MASK;
+ int scale_code = (prev_trim & ~VF_TRIM_OFFSET_MASK) >> SCALE_SHIFT;
+
+ if (abs(delta_steps) > 1) {
+ pr_smb(PR_STATUS,
+ "Cant trim multiple steps delta_steps = %d\n",
+ delta_steps);
+ return prev_trim;
+ }
+ if (offset_code <= CENTER_TRIM_CODE)
+ linear_offset = offset_code + CENTER_TRIM_CODE;
+ else if (offset_code > CENTER_TRIM_CODE)
+ linear_offset = MAX_TRIM_CODE - offset_code;
+
+ if (scale_code <= CENTER_TRIM_CODE)
+ linear_scale = scale_code + CENTER_TRIM_CODE;
+ else if (scale_code > CENTER_TRIM_CODE)
+ linear_scale = scale_code - (CENTER_TRIM_CODE + 1);
+
+ /* check if we can accomodate delta steps with just the offset */
+ if (linear_offset + delta_steps >= 0
+ && linear_offset + delta_steps <= MAX_LIN_CODE) {
+ linear_offset += delta_steps;
+
+ if (linear_offset > CENTER_TRIM_CODE)
+ offset_code = linear_offset - CENTER_TRIM_CODE;
+ else
+ offset_code = MAX_TRIM_CODE - linear_offset;
+
+ return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code;
+ }
+
+ /* changing offset cannot satisfy delta steps, change the scale bits */
+ scale_steps = delta_steps > 0 ? 1 : -1;
+
+ if (linear_scale + scale_steps < 0
+ || linear_scale + scale_steps > MAX_LIN_CODE) {
+ pr_smb(PR_STATUS,
+ "Cant trim scale_steps = %d delta_steps = %d\n",
+ scale_steps, delta_steps);
+ return prev_trim;
+ }
+
+ linear_scale += scale_steps;
+
+ if (linear_scale > CENTER_TRIM_CODE)
+ scale_code = linear_scale - CENTER_TRIM_CODE;
+ else
+ scale_code = linear_scale + (CENTER_TRIM_CODE + 1);
+ prev_trim = (prev_trim & VF_TRIM_OFFSET_MASK)
+ | scale_code << SCALE_SHIFT;
+
+ /*
+ * now that we have changed scale which is a 17mV jump, change the
+ * offset bits (10mV) too so the effective change is just 7mV
+ */
+ delta_steps = -1 * delta_steps;
+
+ linear_offset = clamp(linear_offset + delta_steps, 0, MAX_LIN_CODE);
+ if (linear_offset > CENTER_TRIM_CODE)
+ offset_code = linear_offset - CENTER_TRIM_CODE;
+ else
+ offset_code = MAX_TRIM_CODE - linear_offset;
+
+ return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code;
+}
+
+#define TRIM_14 0xFE
+#define VF_TRIM_MASK 0xFF
+static int smbchg_adjust_vfloat_mv_trim(struct smbchg_chip *chip,
+ int delta_mv)
+{
+ int sign, delta_steps, rc = 0;
+ u8 prev_trim, new_trim;
+ int i;
+
+ sign = delta_mv > 0 ? 1 : -1;
+ delta_steps = (delta_mv + sign * VF_STEP_SIZE_MV / 2)
+ / VF_STEP_SIZE_MV;
+
+ rc = smbchg_read(chip, &prev_trim, chip->misc_base + TRIM_14, 1);
+ if (rc) {
+ dev_err(chip->dev, "Unable to read trim 14: %d\n", rc);
+ return rc;
+ }
+
+ for (i = 1; i <= abs(delta_steps)
+ && i <= vf_adjust_trim_steps_per_adjust; i++) {
+ new_trim = (u8)smbchg_trim_add_steps(prev_trim,
+ delta_steps > 0 ? 1 : -1);
+ if (new_trim == prev_trim) {
+ pr_smb(PR_STATUS,
+ "VFloat trim unchanged from %02x\n", prev_trim);
+ /* treat no trim change as an error */
+ return -EINVAL;
+ }
+
+ rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_14,
+ VF_TRIM_MASK, new_trim);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't change vfloat trim rc=%d\n", rc);
+ }
+ pr_smb(PR_STATUS,
+ "VFlt trim %02x to %02x, delta steps: %d\n",
+ prev_trim, new_trim, delta_steps);
+ prev_trim = new_trim;
+ }
+
+ return rc;
+}
+
+#define VFLOAT_RESAMPLE_DELAY_MS 10000
+static void smbchg_vfloat_adjust_work(struct work_struct *work)
+{
+ struct smbchg_chip *chip = container_of(work,
+ struct smbchg_chip,
+ vfloat_adjust_work.work);
+ int vbat_uv, vbat_mv, ibat_ua, rc, delta_vfloat_mv;
+ bool taper, enable;
+
+ smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST);
+ taper = (get_prop_charge_type(chip)
+ == POWER_SUPPLY_CHARGE_TYPE_TAPER);
+ enable = taper && (chip->parallel.current_max_ma == 0);
+
+ if (!enable) {
+ pr_smb(PR_MISC,
+ "Stopping vfloat adj taper=%d parallel_ma = %d\n",
+ taper, chip->parallel.current_max_ma);
+ goto stop;
+ }
+
+ if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) {
+ pr_smb(PR_STATUS, "JEITA active, skipping\n");
+ goto stop;
+ }
+
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1);
+ rc = get_property_from_fg(chip,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW, &vbat_uv);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "bms psy does not support voltage rc = %d\n", rc);
+ goto stop;
+ }
+ vbat_mv = vbat_uv / 1000;
+
+ if ((vbat_mv - chip->vfloat_mv) < -1 * vf_adjust_max_delta_mv) {
+ pr_smb(PR_STATUS, "Skip vbat out of range: %d\n", vbat_mv);
+ goto reschedule;
+ }
+
+ rc = get_property_from_fg(chip,
+ POWER_SUPPLY_PROP_CURRENT_NOW, &ibat_ua);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "bms psy does not support current_now rc = %d\n", rc);
+ goto stop;
+ }
+
+ if (ibat_ua / 1000 > -chip->iterm_ma) {
+ pr_smb(PR_STATUS, "Skip ibat too high: %d\n", ibat_ua);
+ goto reschedule;
+ }
+
+ pr_smb(PR_STATUS, "sample number = %d vbat_mv = %d ibat_ua = %d\n",
+ chip->n_vbat_samples,
+ vbat_mv,
+ ibat_ua);
+
+ chip->max_vbat_sample = max(chip->max_vbat_sample, vbat_mv);
+ chip->n_vbat_samples += 1;
+ if (chip->n_vbat_samples < vf_adjust_n_samples) {
+ pr_smb(PR_STATUS, "Skip %d samples; max = %d\n",
+ chip->n_vbat_samples, chip->max_vbat_sample);
+ goto reschedule;
+ }
+ /* if max vbat > target vfloat, delta_vfloat_mv could be negative */
+ delta_vfloat_mv = chip->vfloat_mv - chip->max_vbat_sample;
+ pr_smb(PR_STATUS, "delta_vfloat_mv = %d, samples = %d, mvbat = %d\n",
+ delta_vfloat_mv, chip->n_vbat_samples, chip->max_vbat_sample);
+ /*
+ * enough valid samples has been collected, adjust trim codes
+ * based on maximum of collected vbat samples if necessary
+ */
+ if (delta_vfloat_mv > vf_adjust_high_threshold
+ || delta_vfloat_mv < -1 * vf_adjust_low_threshold) {
+ rc = smbchg_adjust_vfloat_mv_trim(chip, delta_vfloat_mv);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "Stopping vfloat adj after trim adj rc = %d\n",
+ rc);
+ goto stop;
+ }
+ chip->max_vbat_sample = 0;
+ chip->n_vbat_samples = 0;
+ goto reschedule;
+ }
+
+stop:
+ chip->max_vbat_sample = 0;
+ chip->n_vbat_samples = 0;
+ smbchg_relax(chip, PM_REASON_VFLOAT_ADJUST);
+ return;
+
+reschedule:
+ schedule_delayed_work(&chip->vfloat_adjust_work,
+ msecs_to_jiffies(VFLOAT_RESAMPLE_DELAY_MS));
+ return;
+}
+
+static int smbchg_charging_status_change(struct smbchg_chip *chip)
+{
+ smbchg_vfloat_adjust_check(chip);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
+ get_prop_batt_status(chip));
+ return 0;
+}
+
+#define BB_CLMP_SEL 0xF8
+#define BB_CLMP_MASK SMB_MASK(1, 0)
+#define BB_CLMP_VFIX_3338MV 0x1
+#define BB_CLMP_VFIX_3512MV 0x2
+static int smbchg_set_optimal_charging_mode(struct smbchg_chip *chip, int type)
+{
+ int rc;
+ bool hvdcp2 = (type == POWER_SUPPLY_TYPE_USB_HVDCP
+ && smbchg_is_usbin_active_pwr_src(chip));
+
+ /*
+ * Set the charger switching freq to 1MHZ if HVDCP 2.0,
+ * or 750KHZ otherwise
+ */
+ rc = smbchg_sec_masked_write(chip,
+ chip->bat_if_base + BAT_IF_TRIM7_REG,
+ CFG_750KHZ_BIT, hvdcp2 ? 0 : CFG_750KHZ_BIT);
+ if (rc) {
+ dev_err(chip->dev, "Cannot set switching freq: %d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Set the charger switch frequency clamp voltage threshold to 3.338V
+ * if HVDCP 2.0, or 3.512V otherwise.
+ */
+ rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BB_CLMP_SEL,
+ BB_CLMP_MASK,
+ hvdcp2 ? BB_CLMP_VFIX_3338MV : BB_CLMP_VFIX_3512MV);
+ if (rc) {
+ dev_err(chip->dev, "Cannot set switching freq: %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+#define DEFAULT_SDP_MA 100
+#define DEFAULT_CDP_MA 1500
+static int smbchg_change_usb_supply_type(struct smbchg_chip *chip,
+ enum power_supply_type type)
+{
+ int rc, current_limit_ma;
+
+ /*
+ * if the type is not unknown, set the type before changing ICL vote
+ * in order to ensure that the correct current limit registers are
+ * used
+ */
+ if (type != POWER_SUPPLY_TYPE_UNKNOWN)
+ chip->usb_supply_type = type;
+
+ /*
+ * Type-C only supports STD(900), MEDIUM(1500) and HIGH(3000) current
+ * modes, skip all BC 1.2 current if external typec is supported.
+ * Note: for SDP supporting current based on USB notifications.
+ */
+ if (chip->typec_psy && (type != POWER_SUPPLY_TYPE_USB))
+ current_limit_ma = chip->typec_current_ma;
+ else if (type == POWER_SUPPLY_TYPE_USB)
+ current_limit_ma = DEFAULT_SDP_MA;
+ else if (type == POWER_SUPPLY_TYPE_USB_CDP)
+ current_limit_ma = DEFAULT_CDP_MA;
+ else if (type == POWER_SUPPLY_TYPE_USB_HVDCP)
+ current_limit_ma = smbchg_default_hvdcp_icl_ma;
+ else if (type == POWER_SUPPLY_TYPE_USB_HVDCP_3)
+ current_limit_ma = smbchg_default_hvdcp3_icl_ma;
+ else
+ current_limit_ma = smbchg_default_dcp_icl_ma;
+
+ pr_smb(PR_STATUS, "Type %d: setting mA = %d\n",
+ type, current_limit_ma);
+ rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true,
+ current_limit_ma);
+ if (rc < 0) {
+ pr_err("Couldn't vote for new USB ICL rc=%d\n", rc);
+ goto out;
+ }
+
+ /* otherwise if it is unknown, set type after the vote */
+ if (type == POWER_SUPPLY_TYPE_UNKNOWN)
+ chip->usb_supply_type = type;
+
+ if (!chip->skip_usb_notification)
+ power_supply_changed(chip->usb_psy);
+
+ /* set the correct buck switching frequency */
+ rc = smbchg_set_optimal_charging_mode(chip, type);
+ if (rc < 0)
+ pr_err("Couldn't set charger optimal mode rc=%d\n", rc);
+
+out:
+ return rc;
+}
+
+#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4)
+#define HVDCP_5V 0x00
+#define HVDCP_9V 0x10
+#define USB_CMD_HVDCP_1 0x42
+#define FORCE_HVDCP_2p0 BIT(3)
+
+static int force_9v_hvdcp(struct smbchg_chip *chip)
+{
+ int rc;
+
+ /* Force 5V HVDCP */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
+ if (rc) {
+ pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Force QC2.0 */
+ rc = smbchg_masked_write(chip,
+ chip->usb_chgpth_base + USB_CMD_HVDCP_1,
+ FORCE_HVDCP_2p0, FORCE_HVDCP_2p0);
+ rc |= smbchg_masked_write(chip,
+ chip->usb_chgpth_base + USB_CMD_HVDCP_1,
+ FORCE_HVDCP_2p0, 0);
+ if (rc < 0) {
+ pr_err("Couldn't force QC2.0 rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Delay to switch into HVDCP 2.0 and avoid UV */
+ msleep(500);
+
+ /* Force 9V HVDCP */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
+ if (rc)
+ pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc);
+
+ return rc;
+}
+
+static void smbchg_hvdcp_det_work(struct work_struct *work)
+{
+ struct smbchg_chip *chip = container_of(work,
+ struct smbchg_chip,
+ hvdcp_det_work.work);
+ int rc;
+
+ if (is_hvdcp_present(chip)) {
+ if (!chip->hvdcp3_supported &&
+ (chip->wa_flags & SMBCHG_HVDCP_9V_EN_WA)) {
+ /* force HVDCP 2.0 */
+ rc = force_9v_hvdcp(chip);
+ if (rc)
+ pr_err("could not force 9V HVDCP continuing rc=%d\n",
+ rc);
+ }
+ smbchg_change_usb_supply_type(chip,
+ POWER_SUPPLY_TYPE_USB_HVDCP);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_aicl_deglitch_wa_check(chip);
+ }
+ smbchg_relax(chip, PM_DETECT_HVDCP);
+}
+
+static int set_usb_psy_dp_dm(struct smbchg_chip *chip, int state)
+{
+ int rc;
+ u8 reg;
+ union power_supply_propval pval = {0, };
+
+ /*
+ * ensure that we are not in the middle of an insertion where usbin_uv
+ * is low and src_detect hasnt gone high. If so force dp=F dm=F
+ * which guarantees proper type detection
+ */
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (!rc && !(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) {
+ pr_smb(PR_MISC, "overwriting state = %d with %d\n",
+ state, POWER_SUPPLY_DP_DM_DPF_DMF);
+ if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg))
+ return regulator_enable(chip->dpdm_reg);
+ }
+ pr_smb(PR_MISC, "setting usb psy dp dm = %d\n", state);
+ pval.intval = state;
+ return power_supply_set_property(chip->usb_psy,
+ POWER_SUPPLY_PROP_DP_DM, &pval);
+}
+
+#define APSD_CFG 0xF5
+#define AUTO_SRC_DETECT_EN_BIT BIT(0)
+#define APSD_TIMEOUT_MS 1500
+static void restore_from_hvdcp_detection(struct smbchg_chip *chip)
+{
+ int rc;
+
+ pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
+
+ /* switch to 9V HVDCP */
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
+ if (rc < 0)
+ pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc);
+
+ /* enable HVDCP */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, HVDCP_EN_BIT);
+ if (rc < 0)
+ pr_err("Couldn't enable HVDCP rc=%d\n", rc);
+
+ /* enable APSD */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + APSD_CFG,
+ AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT);
+ if (rc < 0)
+ pr_err("Couldn't enable APSD rc=%d\n", rc);
+
+ /* Reset back to 5V unregulated */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG,
+ ADAPTER_ALLOWANCE_MASK, USBIN_ADAPTER_5V_UNREGULATED_9V);
+ if (rc < 0)
+ pr_err("Couldn't write usb allowance rc=%d\n", rc);
+
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, AICL_EN_BIT);
+ if (rc < 0)
+ pr_err("Couldn't enable AICL rc=%d\n", rc);
+
+ chip->hvdcp_3_det_ignore_uv = false;
+ chip->pulse_cnt = 0;
+}
+
+#define RESTRICTED_CHG_FCC_PERCENT 50
+static int smbchg_restricted_charging(struct smbchg_chip *chip, bool enable)
+{
+ int current_table_index, fastchg_current;
+ int rc = 0;
+
+ /* If enable, set the fcc to the set point closest
+ * to 50% of the configured fcc while remaining below it
+ */
+ current_table_index = find_smaller_in_array(
+ chip->tables.usb_ilim_ma_table,
+ chip->cfg_fastchg_current_ma
+ * RESTRICTED_CHG_FCC_PERCENT / 100,
+ chip->tables.usb_ilim_ma_len);
+ fastchg_current =
+ chip->tables.usb_ilim_ma_table[current_table_index];
+ rc = vote(chip->fcc_votable, RESTRICTED_CHG_FCC_VOTER, enable,
+ fastchg_current);
+
+ pr_smb(PR_STATUS, "restricted_charging set to %d\n", enable);
+ chip->restricted_charging = enable;
+
+ return rc;
+}
+
+static void handle_usb_removal(struct smbchg_chip *chip)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ int rc;
+
+ pr_smb(PR_STATUS, "triggered\n");
+ smbchg_aicl_deglitch_wa_check(chip);
+ /* Clear the OV detected status set before */
+ if (chip->usb_ov_det)
+ chip->usb_ov_det = false;
+ /* Clear typec current status */
+ if (chip->typec_psy)
+ chip->typec_current_ma = 0;
+ smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_UNKNOWN);
+ extcon_set_cable_state_(chip->extcon, EXTCON_USB, chip->usb_present);
+ if (chip->dpdm_reg)
+ regulator_disable(chip->dpdm_reg);
+ schedule_work(&chip->usb_set_online_work);
+
+ pr_smb(PR_MISC, "setting usb psy health UNKNOWN\n");
+ chip->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN;
+ power_supply_changed(chip->usb_psy);
+
+ if (parallel_psy && chip->parallel_charger_detected) {
+ pval.intval = false;
+ power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_PRESENT, &pval);
+ }
+ if (chip->parallel.avail && chip->aicl_done_irq
+ && chip->enable_aicl_wake) {
+ disable_irq_wake(chip->aicl_done_irq);
+ chip->enable_aicl_wake = false;
+ }
+ chip->parallel.enabled_once = false;
+ chip->vbat_above_headroom = false;
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ ICL_OVERRIDE_BIT, 0);
+ if (rc < 0)
+ pr_err("Couldn't set override rc = %d\n", rc);
+
+ vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, false, 0);
+ chip->usb_icl_delta = 0;
+ vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, false, 0);
+ vote(chip->aicl_deglitch_short_votable,
+ HVDCP_SHORT_DEGLITCH_VOTER, false, 0);
+ if (!chip->hvdcp_not_supported)
+ restore_from_hvdcp_detection(chip);
+}
+
+static bool is_usbin_uv_high(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc);
+ return false;
+ }
+ return reg &= USBIN_UV_BIT;
+}
+
+#define HVDCP_NOTIFY_MS 2500
+static void handle_usb_insertion(struct smbchg_chip *chip)
+{
+ struct power_supply *parallel_psy = get_parallel_psy(chip);
+ union power_supply_propval pval = {0, };
+ enum power_supply_type usb_supply_type;
+ int rc;
+ char *usb_type_name = "null";
+
+ pr_smb(PR_STATUS, "triggered\n");
+ /* usb inserted */
+ read_usb_type(chip, &usb_type_name, &usb_supply_type);
+ pr_smb(PR_STATUS,
+ "inserted type = %d (%s)", usb_supply_type, usb_type_name);
+
+ smbchg_aicl_deglitch_wa_check(chip);
+ if (chip->typec_psy)
+ update_typec_status(chip);
+ smbchg_change_usb_supply_type(chip, usb_supply_type);
+
+ /* Only notify USB if it's not a charger */
+ if (usb_supply_type == POWER_SUPPLY_TYPE_USB ||
+ usb_supply_type == POWER_SUPPLY_TYPE_USB_CDP)
+ extcon_set_cable_state_(chip->extcon, EXTCON_USB,
+ chip->usb_present);
+
+ /* Notify the USB psy if OV condition is not present */
+ if (!chip->usb_ov_det) {
+ /*
+ * Note that this could still be a very weak charger
+ * if the handle_usb_insertion was triggered from
+ * the falling edge of an USBIN_OV interrupt
+ */
+ pr_smb(PR_MISC, "setting usb psy health %s\n",
+ chip->very_weak_charger
+ ? "UNSPEC_FAILURE" : "GOOD");
+ chip->usb_health = chip->very_weak_charger
+ ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE
+ : POWER_SUPPLY_HEALTH_GOOD;
+ power_supply_changed(chip->usb_psy);
+ }
+ schedule_work(&chip->usb_set_online_work);
+
+ if (!chip->hvdcp_not_supported &&
+ (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP)) {
+ cancel_delayed_work_sync(&chip->hvdcp_det_work);
+ smbchg_stay_awake(chip, PM_DETECT_HVDCP);
+ schedule_delayed_work(&chip->hvdcp_det_work,
+ msecs_to_jiffies(HVDCP_NOTIFY_MS));
+ }
+
+ if (parallel_psy) {
+ pval.intval = true;
+ rc = power_supply_set_property(parallel_psy,
+ POWER_SUPPLY_PROP_PRESENT, &pval);
+ chip->parallel_charger_detected = rc ? false : true;
+ if (rc)
+ pr_debug("parallel-charger absent rc=%d\n", rc);
+ }
+
+ if (chip->parallel.avail && chip->aicl_done_irq
+ && !chip->enable_aicl_wake) {
+ rc = enable_irq_wake(chip->aicl_done_irq);
+ chip->enable_aicl_wake = true;
+ }
+}
+
+void update_usb_status(struct smbchg_chip *chip, bool usb_present, bool force)
+{
+ mutex_lock(&chip->usb_status_lock);
+ if (force) {
+ chip->usb_present = usb_present;
+ chip->usb_present ? handle_usb_insertion(chip)
+ : handle_usb_removal(chip);
+ goto unlock;
+ }
+ if (!chip->usb_present && usb_present) {
+ chip->usb_present = usb_present;
+ handle_usb_insertion(chip);
+ } else if (chip->usb_present && !usb_present) {
+ chip->usb_present = usb_present;
+ handle_usb_removal(chip);
+ }
+
+ /* update FG */
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
+ get_prop_batt_status(chip));
+unlock:
+ mutex_unlock(&chip->usb_status_lock);
+}
+
+static int otg_oc_reset(struct smbchg_chip *chip)
+{
+ int rc;
+
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ OTG_EN_BIT, 0);
+ if (rc)
+ pr_err("Failed to disable OTG rc=%d\n", rc);
+
+ msleep(20);
+
+ /*
+ * There is a possibility that an USBID interrupt might have
+ * occurred notifying USB power supply to disable OTG. We
+ * should not enable OTG in such cases.
+ */
+ if (!is_otg_present(chip)) {
+ pr_smb(PR_STATUS,
+ "OTG is not present, not enabling OTG_EN_BIT\n");
+ goto out;
+ }
+
+ rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG,
+ OTG_EN_BIT, OTG_EN_BIT);
+ if (rc)
+ pr_err("Failed to re-enable OTG rc=%d\n", rc);
+
+out:
+ return rc;
+}
+
+static int get_current_time(unsigned long *now_tm_sec)
+{
+ struct rtc_time tm;
+ struct rtc_device *rtc;
+ int rc;
+
+ rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE);
+ if (rtc == NULL) {
+ pr_err("%s: unable to open rtc device (%s)\n",
+ __FILE__, CONFIG_RTC_HCTOSYS_DEVICE);
+ return -EINVAL;
+ }
+
+ rc = rtc_read_time(rtc, &tm);
+ if (rc) {
+ pr_err("Error reading rtc device (%s) : %d\n",
+ CONFIG_RTC_HCTOSYS_DEVICE, rc);
+ goto close_time;
+ }
+
+ rc = rtc_valid_tm(&tm);
+ if (rc) {
+ pr_err("Invalid RTC time (%s): %d\n",
+ CONFIG_RTC_HCTOSYS_DEVICE, rc);
+ goto close_time;
+ }
+ rtc_tm_to_time(&tm, now_tm_sec);
+
+close_time:
+ rtc_class_close(rtc);
+ return rc;
+}
+
+#define AICL_IRQ_LIMIT_SECONDS 60
+#define AICL_IRQ_LIMIT_COUNT 25
+static void increment_aicl_count(struct smbchg_chip *chip)
+{
+ bool bad_charger = false;
+ int max_aicl_count, rc;
+ u8 reg;
+ long elapsed_seconds;
+ unsigned long now_seconds;
+
+ pr_smb(PR_INTERRUPT, "aicl count c:%d dgltch:%d first:%ld\n",
+ chip->aicl_irq_count, chip->aicl_deglitch_short,
+ chip->first_aicl_seconds);
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + ICL_STS_1_REG, 1);
+ if (!rc)
+ chip->aicl_complete = reg & AICL_STS_BIT;
+ else
+ chip->aicl_complete = false;
+
+ if (chip->aicl_deglitch_short || chip->force_aicl_rerun) {
+ if (!chip->aicl_irq_count)
+ get_current_time(&chip->first_aicl_seconds);
+ get_current_time(&now_seconds);
+ elapsed_seconds = now_seconds
+ - chip->first_aicl_seconds;
+
+ if (elapsed_seconds > AICL_IRQ_LIMIT_SECONDS) {
+ pr_smb(PR_INTERRUPT,
+ "resetting: elp:%ld first:%ld now:%ld c=%d\n",
+ elapsed_seconds, chip->first_aicl_seconds,
+ now_seconds, chip->aicl_irq_count);
+ chip->aicl_irq_count = 1;
+ get_current_time(&chip->first_aicl_seconds);
+ return;
+ }
+ /*
+ * Double the amount of AICLs allowed if parallel charging is
+ * enabled.
+ */
+ max_aicl_count = AICL_IRQ_LIMIT_COUNT
+ * (chip->parallel.avail ? 2 : 1);
+ chip->aicl_irq_count++;
+
+ if (chip->aicl_irq_count > max_aicl_count) {
+ pr_smb(PR_INTERRUPT, "elp:%ld first:%ld now:%ld c=%d\n",
+ elapsed_seconds, chip->first_aicl_seconds,
+ now_seconds, chip->aicl_irq_count);
+ pr_smb(PR_INTERRUPT, "Disable AICL rerun\n");
+ chip->very_weak_charger = true;
+ bad_charger = true;
+
+ /*
+ * Disable AICL rerun since many interrupts were
+ * triggered in a short time
+ */
+ /* disable hw aicl */
+ rc = vote(chip->hw_aicl_rerun_disable_votable,
+ WEAK_CHARGER_HW_AICL_VOTER, true, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable hw aicl rerun rc=%d\n",
+ rc);
+ return;
+ }
+
+ /* Vote 100mA current limit */
+ rc = vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER,
+ true, CURRENT_100_MA);
+ if (rc < 0) {
+ pr_err("Can't vote %d current limit rc=%d\n",
+ CURRENT_100_MA, rc);
+ }
+
+ chip->aicl_irq_count = 0;
+ } else if ((get_prop_charge_type(chip) ==
+ POWER_SUPPLY_CHARGE_TYPE_FAST) &&
+ (reg & AICL_SUSP_BIT)) {
+ /*
+ * If the AICL_SUSP_BIT is on, then AICL reruns have
+ * already been disabled. Set the very weak charger
+ * flag so that the driver reports a bad charger
+ * and does not reenable AICL reruns.
+ */
+ chip->very_weak_charger = true;
+ bad_charger = true;
+ }
+ if (bad_charger) {
+ pr_smb(PR_MISC,
+ "setting usb psy health UNSPEC_FAILURE\n");
+ chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ power_supply_changed(chip->usb_psy);
+ schedule_work(&chip->usb_set_online_work);
+ }
+ }
+}
+
+static int wait_for_usbin_uv(struct smbchg_chip *chip, bool high)
+{
+ int rc;
+ int tries = 3;
+ struct completion *completion = &chip->usbin_uv_lowered;
+ bool usbin_uv;
+
+ if (high)
+ completion = &chip->usbin_uv_raised;
+
+ while (tries--) {
+ rc = wait_for_completion_interruptible_timeout(
+ completion,
+ msecs_to_jiffies(APSD_TIMEOUT_MS));
+ if (rc >= 0)
+ break;
+ }
+
+ usbin_uv = is_usbin_uv_high(chip);
+
+ if (high == usbin_uv)
+ return 0;
+
+ pr_err("usbin uv didnt go to a %s state, still at %s, tries = %d, rc = %d\n",
+ high ? "risen" : "lowered",
+ usbin_uv ? "high" : "low",
+ tries, rc);
+ return -EINVAL;
+}
+
+static int wait_for_src_detect(struct smbchg_chip *chip, bool high)
+{
+ int rc;
+ int tries = 3;
+ struct completion *completion = &chip->src_det_lowered;
+ bool src_detect;
+
+ if (high)
+ completion = &chip->src_det_raised;
+
+ while (tries--) {
+ rc = wait_for_completion_interruptible_timeout(
+ completion,
+ msecs_to_jiffies(APSD_TIMEOUT_MS));
+ if (rc >= 0)
+ break;
+ }
+
+ src_detect = is_src_detect_high(chip);
+
+ if (high == src_detect)
+ return 0;
+
+ pr_err("src detect didnt go to a %s state, still at %s, tries = %d, rc = %d\n",
+ high ? "risen" : "lowered",
+ src_detect ? "high" : "low",
+ tries, rc);
+ return -EINVAL;
+}
+
+static int fake_insertion_removal(struct smbchg_chip *chip, bool insertion)
+{
+ int rc;
+ bool src_detect;
+ bool usbin_uv;
+
+ if (insertion) {
+ reinit_completion(&chip->src_det_raised);
+ reinit_completion(&chip->usbin_uv_lowered);
+ } else {
+ reinit_completion(&chip->src_det_lowered);
+ reinit_completion(&chip->usbin_uv_raised);
+ }
+
+ /* ensure that usbin uv real time status is in the right state */
+ usbin_uv = is_usbin_uv_high(chip);
+ if (usbin_uv != insertion) {
+ pr_err("Skip faking, usbin uv is already %d\n", usbin_uv);
+ return -EINVAL;
+ }
+
+ /* ensure that src_detect real time status is in the right state */
+ src_detect = is_src_detect_high(chip);
+ if (src_detect == insertion) {
+ pr_err("Skip faking, src detect is already %d\n", src_detect);
+ return -EINVAL;
+ }
+
+ pr_smb(PR_MISC, "Allow only %s charger\n",
+ insertion ? "5-9V" : "9V only");
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG,
+ ADAPTER_ALLOWANCE_MASK,
+ insertion ?
+ USBIN_ADAPTER_5V_9V_CONT : USBIN_ADAPTER_9V);
+ if (rc < 0) {
+ pr_err("Couldn't write usb allowance rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on %s usbin uv\n",
+ insertion ? "falling" : "rising");
+ rc = wait_for_usbin_uv(chip, !insertion);
+ if (rc < 0) {
+ pr_err("wait for usbin uv failed rc = %d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on %s src det\n",
+ insertion ? "rising" : "falling");
+ rc = wait_for_src_detect(chip, insertion);
+ if (rc < 0) {
+ pr_err("wait for src detect failed rc = %d\n", rc);
+ return rc;
+ }
+
+ return 0;
+}
+
+static int smbchg_prepare_for_pulsing(struct smbchg_chip *chip)
+{
+ int rc = 0;
+ u8 reg;
+
+ /* switch to 5V HVDCP */
+ pr_smb(PR_MISC, "Switch to 5V HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
+ if (rc < 0) {
+ pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc);
+ goto out;
+ }
+
+ /* wait for HVDCP to lower to 5V */
+ msleep(500);
+ /*
+ * Check if the same hvdcp session is in progress. src_det should be
+ * high and that we are still in 5V hvdcp
+ */
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low after 500mS sleep\n");
+ goto out;
+ }
+
+ /* disable HVDCP */
+ pr_smb(PR_MISC, "Disable HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable HVDCP rc=%d\n", rc);
+ goto out;
+ }
+
+ pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300);
+ if (rc < 0) {
+ pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc);
+ goto out;
+ }
+
+ pr_smb(PR_MISC, "Disable AICL\n");
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, 0);
+
+ chip->hvdcp_3_det_ignore_uv = true;
+ /* fake a removal */
+ pr_smb(PR_MISC, "Faking Removal\n");
+ rc = fake_insertion_removal(chip, false);
+ if (rc < 0) {
+ pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc);
+ goto handle_removal;
+ }
+
+ /* disable APSD */
+ pr_smb(PR_MISC, "Disabling APSD\n");
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + APSD_CFG,
+ AUTO_SRC_DETECT_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable APSD rc=%d\n", rc);
+ goto out;
+ }
+
+ /* fake an insertion */
+ pr_smb(PR_MISC, "Faking Insertion\n");
+ rc = fake_insertion_removal(chip, true);
+ if (rc < 0) {
+ pr_err("Couldn't fake insertion rc=%d\n", rc);
+ goto handle_removal;
+ }
+ chip->hvdcp_3_det_ignore_uv = false;
+
+ pr_smb(PR_MISC, "Enable AICL\n");
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, AICL_EN_BIT);
+
+ set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DMF);
+ /*
+ * DCP will switch to HVDCP in this time by removing the short
+ * between DP DM
+ */
+ msleep(HVDCP_NOTIFY_MS);
+ /*
+ * Check if the same hvdcp session is in progress. src_det should be
+ * high and the usb type should be none since APSD was disabled
+ */
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low after 2s sleep\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ smbchg_read(chip, &reg, chip->misc_base + IDEV_STS, 1);
+ if ((reg >> TYPE_BITS_OFFSET) != 0) {
+ pr_smb(PR_MISC, "type bits set after 2s sleep - abort\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DM3P3);
+ /* Wait 60mS after entering continuous mode */
+ msleep(60);
+
+ return 0;
+out:
+ chip->hvdcp_3_det_ignore_uv = false;
+ restore_from_hvdcp_detection(chip);
+ return rc;
+handle_removal:
+ chip->hvdcp_3_det_ignore_uv = false;
+ update_usb_status(chip, 0, 0);
+ return rc;
+}
+
+static int smbchg_unprepare_for_pulsing(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg))
+ rc = regulator_enable(chip->dpdm_reg);
+ if (rc < 0) {
+ pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc);
+ return rc;
+ }
+
+ /* switch to 9V HVDCP */
+ pr_smb(PR_MISC, "Switch to 9V HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
+ if (rc < 0) {
+ pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable HVDCP */
+ pr_smb(PR_MISC, "Enable HVDCP\n");
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, HVDCP_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't enable HVDCP rc=%d\n", rc);
+ return rc;
+ }
+
+ /* enable APSD */
+ pr_smb(PR_MISC, "Enabling APSD\n");
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + APSD_CFG,
+ AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT);
+ if (rc < 0) {
+ pr_err("Couldn't enable APSD rc=%d\n", rc);
+ return rc;
+ }
+
+ /* Disable AICL */
+ pr_smb(PR_MISC, "Disable AICL\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't disable AICL rc=%d\n", rc);
+ return rc;
+ }
+
+ /* fake a removal */
+ chip->hvdcp_3_det_ignore_uv = true;
+ pr_smb(PR_MISC, "Faking Removal\n");
+ rc = fake_insertion_removal(chip, false);
+ if (rc < 0) {
+ pr_err("Couldn't fake removal rc=%d\n", rc);
+ goto out;
+ }
+
+ /*
+ * reset the enabled once flag for parallel charging so
+ * parallel charging can immediately restart after the HVDCP pulsing
+ * is complete
+ */
+ chip->parallel.enabled_once = false;
+
+ /* fake an insertion */
+ pr_smb(PR_MISC, "Faking Insertion\n");
+ rc = fake_insertion_removal(chip, true);
+ if (rc < 0) {
+ pr_err("Couldn't fake insertion rc=%d\n", rc);
+ goto out;
+ }
+ chip->hvdcp_3_det_ignore_uv = false;
+
+ /* Enable AICL */
+ pr_smb(PR_MISC, "Enable AICL\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, 0);
+ if (rc < 0) {
+ pr_err("Couldn't enable AICL rc=%d\n", rc);
+ return rc;
+ }
+
+out:
+ /*
+ * There are many QC 2.0 chargers that collapse before the aicl deglitch
+ * timer can mitigate. Hence set the aicl deglitch time to a shorter
+ * period.
+ */
+
+ rc = vote(chip->aicl_deglitch_short_votable,
+ HVDCP_SHORT_DEGLITCH_VOTER, true, 0);
+ if (rc < 0)
+ pr_err("Couldn't reduce aicl deglitch rc=%d\n", rc);
+
+ pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
+
+ chip->hvdcp_3_det_ignore_uv = false;
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "HVDCP removed\n");
+ update_usb_status(chip, 0, 0);
+ }
+ return rc;
+}
+
+#define USB_CMD_APSD 0x41
+#define APSD_RERUN BIT(0)
+static int rerun_apsd(struct smbchg_chip *chip)
+{
+ int rc;
+
+ reinit_completion(&chip->src_det_raised);
+ reinit_completion(&chip->usbin_uv_lowered);
+ reinit_completion(&chip->src_det_lowered);
+ reinit_completion(&chip->usbin_uv_raised);
+
+ /* re-run APSD */
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + USB_CMD_APSD,
+ APSD_RERUN, APSD_RERUN);
+ if (rc) {
+ pr_err("Couldn't re-run APSD rc=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on rising usbin uv\n");
+ rc = wait_for_usbin_uv(chip, true);
+ if (rc < 0) {
+ pr_err("wait for usbin uv failed rc = %d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on falling src det\n");
+ rc = wait_for_src_detect(chip, false);
+ if (rc < 0) {
+ pr_err("wait for src detect failed rc = %d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on falling usbin uv\n");
+ rc = wait_for_usbin_uv(chip, false);
+ if (rc < 0) {
+ pr_err("wait for usbin uv failed rc = %d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Waiting on rising src det\n");
+ rc = wait_for_src_detect(chip, true);
+ if (rc < 0) {
+ pr_err("wait for src detect failed rc = %d\n", rc);
+ return rc;
+ }
+
+ return rc;
+}
+
+#define SCHG_LITE_USBIN_HVDCP_5_9V 0x8
+#define SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK 0x38
+#define SCHG_LITE_USBIN_HVDCP_SEL_IDLE BIT(3)
+static bool is_hvdcp_5v_cont_mode(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg = 0;
+
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + USBIN_HVDCP_STS, 1);
+ if (rc) {
+ pr_err("Unable to read HVDCP status rc=%d\n", rc);
+ return false;
+ }
+
+ pr_smb(PR_STATUS, "HVDCP status = %x\n", reg);
+
+ if (reg & SCHG_LITE_USBIN_HVDCP_SEL_IDLE) {
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + INPUT_STS, 1);
+ if (rc) {
+ pr_err("Unable to read INPUT status rc=%d\n", rc);
+ return false;
+ }
+ pr_smb(PR_STATUS, "INPUT status = %x\n", reg);
+ if ((reg & SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK) ==
+ SCHG_LITE_USBIN_HVDCP_5_9V)
+ return true;
+ }
+ return false;
+}
+
+static int smbchg_prepare_for_pulsing_lite(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ /* check if HVDCP is already in 5V continuous mode */
+ if (is_hvdcp_5v_cont_mode(chip)) {
+ pr_smb(PR_MISC, "HVDCP by default is in 5V continuous mode\n");
+ return 0;
+ }
+
+ /* switch to 5V HVDCP */
+ pr_smb(PR_MISC, "Switch to 5V HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
+ if (rc < 0) {
+ pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc);
+ goto out;
+ }
+
+ /* wait for HVDCP to lower to 5V */
+ msleep(500);
+ /*
+ * Check if the same hvdcp session is in progress. src_det should be
+ * high and that we are still in 5V hvdcp
+ */
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low after 500mS sleep\n");
+ goto out;
+ }
+
+ pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300);
+ if (rc < 0) {
+ pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc);
+ goto out;
+ }
+
+ pr_smb(PR_MISC, "Disable AICL\n");
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, 0);
+
+ chip->hvdcp_3_det_ignore_uv = true;
+
+ /* re-run APSD */
+ rc = rerun_apsd(chip);
+ if (rc) {
+ pr_err("APSD rerun failed\n");
+ goto out;
+ }
+
+ chip->hvdcp_3_det_ignore_uv = false;
+
+ pr_smb(PR_MISC, "Enable AICL\n");
+ smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG,
+ AICL_EN_BIT, AICL_EN_BIT);
+ /*
+ * DCP will switch to HVDCP in this time by removing the short
+ * between DP DM
+ */
+ msleep(HVDCP_NOTIFY_MS);
+ /*
+ * Check if the same hvdcp session is in progress. src_det should be
+ * high and the usb type should be none since APSD was disabled
+ */
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low after 2s sleep\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ /* We are set if HVDCP in 5V continuous mode */
+ if (!is_hvdcp_5v_cont_mode(chip)) {
+ pr_err("HVDCP could not be set in 5V continuous mode\n");
+ goto out;
+ }
+
+ return 0;
+out:
+ chip->hvdcp_3_det_ignore_uv = false;
+ restore_from_hvdcp_detection(chip);
+ return rc;
+}
+
+static int smbchg_unprepare_for_pulsing_lite(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ pr_smb(PR_MISC, "Forcing 9V HVDCP 2.0\n");
+ rc = force_9v_hvdcp(chip);
+ if (rc) {
+ pr_err("Failed to force 9V HVDCP=%d\n", rc);
+ return rc;
+ }
+
+ pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
+
+ return rc;
+}
+
+#define CMD_HVDCP_2 0x43
+#define SINGLE_INCREMENT BIT(0)
+#define SINGLE_DECREMENT BIT(1)
+static int smbchg_dp_pulse_lite(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ pr_smb(PR_MISC, "Increment DP\n");
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2,
+ SINGLE_INCREMENT, SINGLE_INCREMENT);
+ if (rc)
+ pr_err("Single-increment failed rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smbchg_dm_pulse_lite(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ pr_smb(PR_MISC, "Decrement DM\n");
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2,
+ SINGLE_DECREMENT, SINGLE_DECREMENT);
+ if (rc)
+ pr_err("Single-decrement failed rc=%d\n", rc);
+
+ return rc;
+}
+
+static int smbchg_hvdcp3_confirmed(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ /*
+ * reset the enabled once flag for parallel charging because this is
+ * effectively a new insertion.
+ */
+ chip->parallel.enabled_once = false;
+
+ pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n");
+ rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc);
+
+ smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_USB_HVDCP_3);
+
+ return rc;
+}
+
+static int smbchg_dp_dm(struct smbchg_chip *chip, int val)
+{
+ int rc = 0;
+ int target_icl_vote_ma;
+
+ switch (val) {
+ case POWER_SUPPLY_DP_DM_PREPARE:
+ if (!is_hvdcp_present(chip)) {
+ pr_err("No pulsing unless HVDCP\n");
+ return -ENODEV;
+ }
+ if (chip->schg_version == QPNP_SCHG_LITE)
+ rc = smbchg_prepare_for_pulsing_lite(chip);
+ else
+ rc = smbchg_prepare_for_pulsing(chip);
+ break;
+ case POWER_SUPPLY_DP_DM_UNPREPARE:
+ if (chip->schg_version == QPNP_SCHG_LITE)
+ rc = smbchg_unprepare_for_pulsing_lite(chip);
+ else
+ rc = smbchg_unprepare_for_pulsing(chip);
+ break;
+ case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3:
+ rc = smbchg_hvdcp3_confirmed(chip);
+ break;
+ case POWER_SUPPLY_DP_DM_DP_PULSE:
+ if (chip->schg_version == QPNP_SCHG)
+ rc = set_usb_psy_dp_dm(chip,
+ POWER_SUPPLY_DP_DM_DP_PULSE);
+ else
+ rc = smbchg_dp_pulse_lite(chip);
+ if (!rc)
+ chip->pulse_cnt++;
+ pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt);
+ break;
+ case POWER_SUPPLY_DP_DM_DM_PULSE:
+ if (chip->schg_version == QPNP_SCHG)
+ rc = set_usb_psy_dp_dm(chip,
+ POWER_SUPPLY_DP_DM_DM_PULSE);
+ else
+ rc = smbchg_dm_pulse_lite(chip);
+ if (!rc && chip->pulse_cnt)
+ chip->pulse_cnt--;
+ pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt);
+ break;
+ case POWER_SUPPLY_DP_DM_HVDCP3_SUPPORTED:
+ chip->hvdcp3_supported = true;
+ pr_smb(PR_MISC, "HVDCP3 supported\n");
+ break;
+ case POWER_SUPPLY_DP_DM_ICL_DOWN:
+ chip->usb_icl_delta -= 100;
+ target_icl_vote_ma = get_client_vote(chip->usb_icl_votable,
+ PSY_ICL_VOTER);
+ vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true,
+ target_icl_vote_ma + chip->usb_icl_delta);
+ break;
+ case POWER_SUPPLY_DP_DM_ICL_UP:
+ chip->usb_icl_delta += 100;
+ target_icl_vote_ma = get_client_vote(chip->usb_icl_votable,
+ PSY_ICL_VOTER);
+ vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true,
+ target_icl_vote_ma + chip->usb_icl_delta);
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+static void update_typec_capability_status(struct smbchg_chip *chip,
+ const union power_supply_propval *val)
+{
+ pr_smb(PR_TYPEC, "typec capability = %dma\n", val->intval);
+
+ pr_debug("changing ICL from %dma to %dma\n", chip->typec_current_ma,
+ val->intval);
+ chip->typec_current_ma = val->intval;
+ smbchg_change_usb_supply_type(chip, chip->usb_supply_type);
+}
+
+static void update_typec_otg_status(struct smbchg_chip *chip, int mode,
+ bool force)
+{
+ union power_supply_propval pval = {0, };
+ pr_smb(PR_TYPEC, "typec mode = %d\n", mode);
+
+ if (mode == POWER_SUPPLY_TYPE_DFP) {
+ chip->typec_dfp = true;
+ pval.intval = 1;
+ extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST,
+ chip->typec_dfp);
+ /* update FG */
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
+ get_prop_batt_status(chip));
+ } else if (force || chip->typec_dfp) {
+ chip->typec_dfp = false;
+ pval.intval = 0;
+ extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST,
+ chip->typec_dfp);
+ /* update FG */
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
+ get_prop_batt_status(chip));
+ }
+}
+
+static int smbchg_usb_get_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ union power_supply_propval *val)
+{
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = chip->usb_current_max;
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = chip->usb_present;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ val->intval = chip->usb_online;
+ break;
+ case POWER_SUPPLY_PROP_TYPE:
+ val->intval = chip->usb_supply_type;
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = chip->usb_health;
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int smbchg_usb_set_property(struct power_supply *psy,
+ enum power_supply_property psp,
+ const union power_supply_propval *val)
+{
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ chip->usb_current_max = val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ chip->usb_online = val->intval;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ power_supply_changed(psy);
+ return 0;
+}
+
+static int
+smbchg_usb_is_writeable(struct power_supply *psy, enum power_supply_property psp)
+{
+ switch (psp) {
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ return 1;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+
+static char *smbchg_usb_supplicants[] = {
+ "battery",
+ "bms",
+};
+
+static enum power_supply_property smbchg_usb_properties[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+ POWER_SUPPLY_PROP_TYPE,
+ POWER_SUPPLY_PROP_HEALTH,
+};
+
+#define CHARGE_OUTPUT_VTG_RATIO 840
+static int smbchg_get_iusb(struct smbchg_chip *chip)
+{
+ int rc, iusb_ua = -EINVAL;
+ struct qpnp_vadc_result adc_result;
+
+ if (!is_usb_present(chip) && !is_dc_present(chip))
+ return 0;
+
+ if (chip->vchg_vadc_dev && chip->vchg_adc_channel != -EINVAL) {
+ rc = qpnp_vadc_read(chip->vchg_vadc_dev,
+ chip->vchg_adc_channel, &adc_result);
+ if (rc) {
+ pr_smb(PR_STATUS,
+ "error in VCHG (channel-%d) read rc = %d\n",
+ chip->vchg_adc_channel, rc);
+ return 0;
+ }
+ iusb_ua = div_s64(adc_result.physical * 1000,
+ CHARGE_OUTPUT_VTG_RATIO);
+ }
+
+ return iusb_ua;
+}
+
+static enum power_supply_property smbchg_battery_properties[] = {
+ POWER_SUPPLY_PROP_STATUS,
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CHARGE_TYPE,
+ POWER_SUPPLY_PROP_CAPACITY,
+ POWER_SUPPLY_PROP_HEALTH,
+ POWER_SUPPLY_PROP_TECHNOLOGY,
+ POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL,
+ POWER_SUPPLY_PROP_FLASH_CURRENT_MAX,
+ POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX,
+ POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN,
+ POWER_SUPPLY_PROP_CURRENT_NOW,
+ POWER_SUPPLY_PROP_TEMP,
+ POWER_SUPPLY_PROP_VOLTAGE_NOW,
+ POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_MAX,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_NOW,
+ POWER_SUPPLY_PROP_FLASH_ACTIVE,
+ POWER_SUPPLY_PROP_FLASH_TRIGGER,
+ POWER_SUPPLY_PROP_DP_DM,
+ POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED,
+ POWER_SUPPLY_PROP_RERUN_AICL,
+ POWER_SUPPLY_PROP_RESTRICTED_CHARGING,
+};
+
+static int smbchg_battery_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ vote(chip->battchg_suspend_votable, BATTCHG_USER_EN_VOTER,
+ !val->intval, 0);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ rc = vote(chip->usb_suspend_votable, USER_EN_VOTER,
+ !val->intval, 0);
+ rc = vote(chip->dc_suspend_votable, USER_EN_VOTER,
+ !val->intval, 0);
+ chip->chg_enabled = val->intval;
+ schedule_work(&chip->usb_set_online_work);
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY:
+ chip->fake_battery_soc = val->intval;
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ smbchg_system_temp_level_set(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ rc = smbchg_set_fastchg_current_user(chip, val->intval / 1000);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ rc = smbchg_float_voltage_set(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE:
+ rc = smbchg_safety_timer_enable(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ rc = smbchg_switch_buck_frequency(chip, val->intval);
+ if (rc) {
+ pr_err("Couldn't switch buck frequency, rc=%d\n", rc);
+ /*
+ * Trigger a panic if there is an error while switching
+ * buck frequency. This will prevent LS FET damage.
+ */
+ BUG_ON(1);
+ }
+
+ rc = smbchg_otg_pulse_skip_disable(chip,
+ REASON_FLASH_ENABLED, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_FLASH_TRIGGER:
+ chip->flash_triggered = !!val->intval;
+ smbchg_icl_loop_disable_check(chip);
+ break;
+ case POWER_SUPPLY_PROP_FORCE_TLIM:
+ rc = smbchg_force_tlim_en(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_DP_DM:
+ rc = smbchg_dp_dm(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ smbchg_rerun_aicl(chip);
+ break;
+ case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
+ rc = smbchg_restricted_charging(chip, val->intval);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_CAPABILITY:
+ if (chip->typec_psy)
+ update_typec_capability_status(chip, val);
+ break;
+ case POWER_SUPPLY_PROP_TYPEC_MODE:
+ if (chip->typec_psy)
+ update_typec_otg_status(chip, val->intval, false);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smbchg_battery_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ int rc;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CAPACITY:
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE:
+ case POWER_SUPPLY_PROP_DP_DM:
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+
+static int smbchg_battery_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_STATUS:
+ val->intval = get_prop_batt_status(chip);
+ break;
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = get_prop_batt_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED:
+ val->intval
+ = get_effective_result(chip->battchg_suspend_votable);
+ if (val->intval < 0) /* no votes */
+ val->intval = 1;
+ else
+ val->intval = !val->intval;
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = chip->chg_enabled;
+ break;
+ case POWER_SUPPLY_PROP_CHARGE_TYPE:
+ val->intval = get_prop_charge_type(chip);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX:
+ val->intval = smbchg_float_voltage_get(chip);
+ break;
+ case POWER_SUPPLY_PROP_HEALTH:
+ val->intval = get_prop_batt_health(chip);
+ break;
+ case POWER_SUPPLY_PROP_TECHNOLOGY:
+ val->intval = POWER_SUPPLY_TECHNOLOGY_LION;
+ break;
+ case POWER_SUPPLY_PROP_FLASH_CURRENT_MAX:
+ val->intval = smbchg_calc_max_flash_current(chip);
+ break;
+ case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX:
+ val->intval = chip->fastchg_current_ma * 1000;
+ break;
+ case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL:
+ val->intval = chip->therm_lvl_sel;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_MAX:
+ val->intval = smbchg_get_aicl_level_ma(chip) * 1000;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED:
+ val->intval = (int)chip->aicl_complete;
+ break;
+ case POWER_SUPPLY_PROP_RESTRICTED_CHARGING:
+ val->intval = (int)chip->restricted_charging;
+ break;
+ /* properties from fg */
+ case POWER_SUPPLY_PROP_CAPACITY:
+ val->intval = get_prop_batt_capacity(chip);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_NOW:
+ val->intval = get_prop_batt_current_now(chip);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_NOW:
+ val->intval = get_prop_batt_voltage_now(chip);
+ break;
+ case POWER_SUPPLY_PROP_TEMP:
+ val->intval = get_prop_batt_temp(chip);
+ break;
+ case POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN:
+ val->intval = get_prop_batt_voltage_max_design(chip);
+ break;
+ case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE:
+ val->intval = chip->safety_timer_en;
+ break;
+ case POWER_SUPPLY_PROP_FLASH_ACTIVE:
+ val->intval = chip->otg_pulse_skip_dis;
+ break;
+ case POWER_SUPPLY_PROP_FLASH_TRIGGER:
+ val->intval = chip->flash_triggered;
+ break;
+ case POWER_SUPPLY_PROP_DP_DM:
+ val->intval = chip->pulse_cnt;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED:
+ val->intval = smbchg_is_input_current_limited(chip);
+ break;
+ case POWER_SUPPLY_PROP_RERUN_AICL:
+ val->intval = 0;
+ break;
+ case POWER_SUPPLY_PROP_INPUT_CURRENT_NOW:
+ val->intval = smbchg_get_iusb(chip);
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static char *smbchg_dc_supplicants[] = {
+ "bms",
+};
+
+static enum power_supply_property smbchg_dc_properties[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_ONLINE,
+ POWER_SUPPLY_PROP_CHARGING_ENABLED,
+ POWER_SUPPLY_PROP_CURRENT_MAX,
+};
+
+static int smbchg_dc_set_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ const union power_supply_propval *val)
+{
+ int rc = 0;
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ rc = vote(chip->dc_suspend_votable, POWER_SUPPLY_EN_VOTER,
+ !val->intval, 0);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = vote(chip->dc_icl_votable, USER_ICL_VOTER, true,
+ val->intval / 1000);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return rc;
+}
+
+static int smbchg_dc_get_property(struct power_supply *psy,
+ enum power_supply_property prop,
+ union power_supply_propval *val)
+{
+ struct smbchg_chip *chip = power_supply_get_drvdata(psy);
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = is_dc_present(chip);
+ break;
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ val->intval = get_effective_result(chip->dc_suspend_votable);
+ if (val->intval < 0) /* no votes */
+ val->intval = 1;
+ else
+ val->intval = !val->intval;
+ break;
+ case POWER_SUPPLY_PROP_ONLINE:
+ /* return if dc is charging the battery */
+ val->intval = (smbchg_get_pwr_path(chip) == PWR_PATH_DC)
+ && (get_prop_batt_status(chip)
+ == POWER_SUPPLY_STATUS_CHARGING);
+ break;
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ val->intval = chip->dc_max_current_ma * 1000;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int smbchg_dc_is_writeable(struct power_supply *psy,
+ enum power_supply_property prop)
+{
+ int rc;
+
+ switch (prop) {
+ case POWER_SUPPLY_PROP_CHARGING_ENABLED:
+ case POWER_SUPPLY_PROP_CURRENT_MAX:
+ rc = 1;
+ break;
+ default:
+ rc = 0;
+ break;
+ }
+ return rc;
+}
+
+#define HOT_BAT_HARD_BIT BIT(0)
+#define HOT_BAT_SOFT_BIT BIT(1)
+#define COLD_BAT_HARD_BIT BIT(2)
+#define COLD_BAT_SOFT_BIT BIT(3)
+#define BAT_OV_BIT BIT(4)
+#define BAT_LOW_BIT BIT(5)
+#define BAT_MISSING_BIT BIT(6)
+#define BAT_TERM_MISSING_BIT BIT(7)
+static irqreturn_t batt_hot_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_wipower_check(chip);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
+ get_prop_batt_health(chip));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_cold_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_wipower_check(chip);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
+ get_prop_batt_health(chip));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_warm_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ chip->batt_warm = !!(reg & HOT_BAT_SOFT_BIT);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
+ get_prop_batt_health(chip));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_cool_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ chip->batt_cool = !!(reg & COLD_BAT_SOFT_BIT);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
+ get_prop_batt_health(chip));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t batt_pres_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ chip->batt_present = !(reg & BAT_MISSING_BIT);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH,
+ get_prop_batt_health(chip));
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t vbat_low_handler(int irq, void *_chip)
+{
+ pr_warn_ratelimited("vbat low\n");
+ return IRQ_HANDLED;
+}
+
+#define CHG_COMP_SFT_BIT BIT(3)
+static irqreturn_t chg_error_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ int rc = 0;
+ u8 reg;
+
+ pr_smb(PR_INTERRUPT, "chg-error triggered\n");
+
+ rc = smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc);
+ } else {
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ if (reg & CHG_COMP_SFT_BIT)
+ set_property_on_fg(chip,
+ POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED,
+ 1);
+ }
+
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_wipower_check(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t fastchg_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+
+ pr_smb(PR_INTERRUPT, "p2f triggered\n");
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_wipower_check(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chg_hot_handler(int irq, void *_chip)
+{
+ pr_warn_ratelimited("chg hot\n");
+ smbchg_wipower_check(_chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t chg_term_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+
+ pr_smb(PR_INTERRUPT, "tcc triggered\n");
+ /*
+ * Charge termination is a pulse and not level triggered. That means,
+ * TCC bit in RT_STS can get cleared by the time this interrupt is
+ * handled. Instead of relying on that to determine whether the
+ * charge termination had happened, we've to simply notify the FG
+ * about this as long as the interrupt is handled.
+ */
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_CHARGE_DONE, 1);
+
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t taper_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ taper_irq_en(chip, false);
+ smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_taper(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_wipower_check(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t recharge_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->chgr_base + RT_STS, 1);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ smbchg_parallel_usb_check_ok(chip);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ return IRQ_HANDLED;
+}
+
+static irqreturn_t wdog_timeout_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->misc_base + RT_STS, 1);
+ pr_warn_ratelimited("wdog timeout rt_stat = 0x%02x\n", reg);
+ if (chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+ smbchg_charging_status_change(chip);
+ return IRQ_HANDLED;
+}
+
+/**
+ * power_ok_handler() - called when the switcher turns on or turns off
+ * @chip: pointer to smbchg_chip
+ * @rt_stat: the status bit indicating switcher turning on or off
+ */
+static irqreturn_t power_ok_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ u8 reg = 0;
+
+ smbchg_read(chip, &reg, chip->misc_base + RT_STS, 1);
+ pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg);
+ return IRQ_HANDLED;
+}
+
+/**
+ * dcin_uv_handler() - called when the dc voltage crosses the uv threshold
+ * @chip: pointer to smbchg_chip
+ * @rt_stat: the status bit indicating whether dc voltage is uv
+ */
+#define DCIN_UNSUSPEND_DELAY_MS 1000
+static irqreturn_t dcin_uv_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ bool dc_present = is_dc_present(chip);
+
+ pr_smb(PR_STATUS, "chip->dc_present = %d dc_present = %d\n",
+ chip->dc_present, dc_present);
+
+ if (chip->dc_present != dc_present) {
+ /* dc changed */
+ chip->dc_present = dc_present;
+ if (chip->dc_psy_type != -EINVAL && chip->batt_psy)
+ power_supply_changed(chip->dc_psy);
+ smbchg_charging_status_change(chip);
+ smbchg_aicl_deglitch_wa_check(chip);
+ chip->vbat_above_headroom = false;
+ }
+
+ smbchg_wipower_check(chip);
+ return IRQ_HANDLED;
+}
+
+/**
+ * usbin_ov_handler() - this is called when an overvoltage condition occurs
+ * @chip: pointer to smbchg_chip chip
+ */
+static irqreturn_t usbin_ov_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ int rc;
+ u8 reg;
+ bool usb_present;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc);
+ goto out;
+ }
+
+ /* OV condition is detected. Notify it to USB psy */
+ if (reg & USBIN_OV_BIT) {
+ chip->usb_ov_det = true;
+ pr_smb(PR_MISC, "setting usb psy health OV\n");
+ chip->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE;
+ power_supply_changed(chip->usb_psy);
+ } else {
+ chip->usb_ov_det = false;
+ /* If USB is present, then handle the USB insertion */
+ usb_present = is_usb_present(chip);
+ if (usb_present)
+ update_usb_status(chip, usb_present, false);
+ }
+out:
+ return IRQ_HANDLED;
+}
+
+/**
+ * usbin_uv_handler() - this is called when USB charger is removed
+ * @chip: pointer to smbchg_chip chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+#define ICL_MODE_MASK SMB_MASK(5, 4)
+#define ICL_MODE_HIGH_CURRENT 0
+static irqreturn_t usbin_uv_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ int aicl_level = smbchg_get_aicl_level_ma(chip);
+ int rc;
+ u8 reg;
+
+ rc = smbchg_read(chip, &reg, chip->usb_chgpth_base + RT_STS, 1);
+ if (rc) {
+ pr_err("could not read rt sts: %d", rc);
+ goto out;
+ }
+
+ pr_smb(PR_STATUS,
+ "%s chip->usb_present = %d rt_sts = 0x%02x hvdcp_3_det_ignore_uv = %d aicl = %d\n",
+ chip->hvdcp_3_det_ignore_uv ? "Ignoring":"",
+ chip->usb_present, reg, chip->hvdcp_3_det_ignore_uv,
+ aicl_level);
+
+ /*
+ * set usb_psy's dp=f dm=f if this is a new insertion, i.e. it is
+ * not already src_detected and usbin_uv is seen falling
+ */
+ if (!(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) {
+ pr_smb(PR_MISC, "setting usb dp=f dm=f\n");
+ if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg))
+ rc = regulator_enable(chip->dpdm_reg);
+ if (rc < 0) {
+ pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+ if (reg & USBIN_UV_BIT)
+ complete_all(&chip->usbin_uv_raised);
+ else
+ complete_all(&chip->usbin_uv_lowered);
+
+ if (chip->hvdcp_3_det_ignore_uv)
+ goto out;
+
+ if ((reg & USBIN_UV_BIT) && (reg & USBIN_SRC_DET_BIT)) {
+ pr_smb(PR_STATUS, "Very weak charger detected\n");
+ chip->very_weak_charger = true;
+ rc = smbchg_read(chip, &reg,
+ chip->usb_chgpth_base + ICL_STS_2_REG, 1);
+ if (rc) {
+ dev_err(chip->dev, "Could not read usb icl sts 2: %d\n",
+ rc);
+ goto out;
+ }
+ if ((reg & ICL_MODE_MASK) != ICL_MODE_HIGH_CURRENT) {
+ /*
+ * If AICL is not even enabled, this is either an
+ * SDP or a grossly out of spec charger. Do not
+ * draw any current from it.
+ */
+ rc = vote(chip->usb_suspend_votable,
+ WEAK_CHARGER_EN_VOTER, true, 0);
+ if (rc < 0)
+ pr_err("could not disable charger: %d", rc);
+ } else if (aicl_level == chip->tables.usb_ilim_ma_table[0]) {
+ /*
+ * we are in a situation where the adapter is not able
+ * to supply even 300mA. Disable hw aicl reruns else it
+ * is only a matter of time when we get back here again
+ */
+ rc = vote(chip->hw_aicl_rerun_disable_votable,
+ WEAK_CHARGER_HW_AICL_VOTER, true, 0);
+ if (rc < 0)
+ pr_err("Couldn't disable hw aicl rerun rc=%d\n",
+ rc);
+ }
+ pr_smb(PR_MISC, "setting usb psy health UNSPEC_FAILURE\n");
+ chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE;
+ power_supply_changed(chip->usb_psy);
+ schedule_work(&chip->usb_set_online_work);
+ }
+
+ smbchg_wipower_check(chip);
+out:
+ return IRQ_HANDLED;
+}
+
+/**
+ * src_detect_handler() - this is called on rising edge when USB charger type
+ * is detected and on falling edge when USB voltage falls
+ * below the coarse detect voltage(1V), use it for
+ * handling USB charger insertion and removal.
+ * @chip: pointer to smbchg_chip
+ * @rt_stat: the status bit indicating chg insertion/removal
+ */
+static irqreturn_t src_detect_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ bool usb_present = is_usb_present(chip);
+ bool src_detect = is_src_detect_high(chip);
+ int rc;
+
+ pr_smb(PR_STATUS,
+ "%s chip->usb_present = %d usb_present = %d src_detect = %d hvdcp_3_det_ignore_uv=%d\n",
+ chip->hvdcp_3_det_ignore_uv ? "Ignoring":"",
+ chip->usb_present, usb_present, src_detect,
+ chip->hvdcp_3_det_ignore_uv);
+
+ if (src_detect)
+ complete_all(&chip->src_det_raised);
+ else
+ complete_all(&chip->src_det_lowered);
+
+ if (chip->hvdcp_3_det_ignore_uv)
+ goto out;
+
+ /*
+ * When VBAT is above the AICL threshold (4.25V) - 180mV (4.07V),
+ * an input collapse due to AICL will actually cause an USBIN_UV
+ * interrupt to fire as well.
+ *
+ * Handle USB insertions and removals in the source detect handler
+ * instead of the USBIN_UV handler since the latter is untrustworthy
+ * when the battery voltage is high.
+ */
+ chip->very_weak_charger = false;
+ /*
+ * a src detect marks a new insertion or a real removal,
+ * vote for enable aicl hw reruns
+ */
+ rc = vote(chip->hw_aicl_rerun_disable_votable,
+ WEAK_CHARGER_HW_AICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't enable hw aicl rerun rc=%d\n", rc);
+
+ rc = vote(chip->usb_suspend_votable, WEAK_CHARGER_EN_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("could not enable charger: %d\n", rc);
+
+ if (src_detect) {
+ update_usb_status(chip, usb_present, 0);
+ } else {
+ update_usb_status(chip, 0, false);
+ chip->aicl_irq_count = 0;
+ }
+out:
+ return IRQ_HANDLED;
+}
+
+/**
+ * otg_oc_handler() - called when the usb otg goes over current
+ */
+#define NUM_OTG_RETRIES 5
+#define OTG_OC_RETRY_DELAY_US 50000
+static irqreturn_t otg_oc_handler(int irq, void *_chip)
+{
+ int rc;
+ struct smbchg_chip *chip = _chip;
+ s64 elapsed_us = ktime_us_delta(ktime_get(), chip->otg_enable_time);
+
+ pr_smb(PR_INTERRUPT, "triggered\n");
+
+ if (chip->schg_version == QPNP_SCHG_LITE) {
+ pr_warn("OTG OC triggered - OTG disabled\n");
+ return IRQ_HANDLED;
+ }
+
+ if (elapsed_us > OTG_OC_RETRY_DELAY_US)
+ chip->otg_retries = 0;
+
+ /*
+ * Due to a HW bug in the PMI8994 charger, the current inrush that
+ * occurs when connecting certain OTG devices can cause the OTG
+ * overcurrent protection to trip.
+ *
+ * The work around is to try reenabling the OTG when getting an
+ * overcurrent interrupt once.
+ */
+ if (chip->otg_retries < NUM_OTG_RETRIES) {
+ chip->otg_retries += 1;
+ pr_smb(PR_STATUS,
+ "Retrying OTG enable. Try #%d, elapsed_us %lld\n",
+ chip->otg_retries, elapsed_us);
+ rc = otg_oc_reset(chip);
+ if (rc)
+ pr_err("Failed to reset OTG OC state rc=%d\n", rc);
+ chip->otg_enable_time = ktime_get();
+ }
+ return IRQ_HANDLED;
+}
+
+/**
+ * otg_fail_handler() - called when the usb otg fails
+ * (when vbat < OTG UVLO threshold)
+ */
+static irqreturn_t otg_fail_handler(int irq, void *_chip)
+{
+ pr_smb(PR_INTERRUPT, "triggered\n");
+ return IRQ_HANDLED;
+}
+
+/**
+ * aicl_done_handler() - called when the usb AICL algorithm is finished
+ * and a current is set.
+ */
+static irqreturn_t aicl_done_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ bool usb_present = is_usb_present(chip);
+ int aicl_level = smbchg_get_aicl_level_ma(chip);
+
+ pr_smb(PR_INTERRUPT, "triggered, aicl: %d\n", aicl_level);
+
+ increment_aicl_count(chip);
+
+ if (usb_present)
+ smbchg_parallel_usb_check_ok(chip);
+
+ if (chip->aicl_complete && chip->batt_psy)
+ power_supply_changed(chip->batt_psy);
+
+ return IRQ_HANDLED;
+}
+
+/**
+ * usbid_change_handler() - called when the usb RID changes.
+ * This is used mostly for detecting OTG
+ */
+static irqreturn_t usbid_change_handler(int irq, void *_chip)
+{
+ struct smbchg_chip *chip = _chip;
+ bool otg_present;
+
+ pr_smb(PR_INTERRUPT, "triggered\n");
+
+ otg_present = is_otg_present(chip);
+ pr_smb(PR_MISC, "setting usb psy OTG = %d\n",
+ otg_present ? 1 : 0);
+
+ extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, otg_present);
+
+ if (otg_present)
+ pr_smb(PR_STATUS, "OTG detected\n");
+
+ /* update FG */
+ set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS,
+ get_prop_batt_status(chip));
+
+ return IRQ_HANDLED;
+}
+
+static int determine_initial_status(struct smbchg_chip *chip)
+{
+ union power_supply_propval type = {0, };
+
+ /*
+ * It is okay to read the interrupt status here since
+ * interrupts aren't requested. reading interrupt status
+ * clears the interrupt so be careful to read interrupt
+ * status only in interrupt handling code
+ */
+
+ batt_pres_handler(0, chip);
+ batt_hot_handler(0, chip);
+ batt_warm_handler(0, chip);
+ batt_cool_handler(0, chip);
+ batt_cold_handler(0, chip);
+ if (chip->typec_psy) {
+ get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type);
+ update_typec_otg_status(chip, type.intval, true);
+ } else {
+ usbid_change_handler(0, chip);
+ }
+ src_detect_handler(0, chip);
+
+ chip->usb_present = is_usb_present(chip);
+ chip->dc_present = is_dc_present(chip);
+
+ if (chip->usb_present) {
+ int rc = 0;
+ pr_smb(PR_MISC, "setting usb dp=f dm=f\n");
+ if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg))
+ rc = regulator_enable(chip->dpdm_reg);
+ if (rc < 0) {
+ pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc);
+ return rc;
+ }
+ handle_usb_insertion(chip);
+ } else {
+ handle_usb_removal(chip);
+ }
+
+ return 0;
+}
+
+static int prechg_time[] = {
+ 24,
+ 48,
+ 96,
+ 192,
+};
+static int chg_time[] = {
+ 192,
+ 384,
+ 768,
+ 1536,
+};
+
+enum bpd_type {
+ BPD_TYPE_BAT_NONE,
+ BPD_TYPE_BAT_ID,
+ BPD_TYPE_BAT_THM,
+ BPD_TYPE_BAT_THM_BAT_ID,
+ BPD_TYPE_DEFAULT,
+};
+
+static const char * const bpd_label[] = {
+ [BPD_TYPE_BAT_NONE] = "bpd_none",
+ [BPD_TYPE_BAT_ID] = "bpd_id",
+ [BPD_TYPE_BAT_THM] = "bpd_thm",
+ [BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id",
+};
+
+static inline int get_bpd(const char *name)
+{
+ int i = 0;
+ for (i = 0; i < ARRAY_SIZE(bpd_label); i++) {
+ if (strcmp(bpd_label[i], name) == 0)
+ return i;
+ }
+ return -EINVAL;
+}
+
+#define REVISION1_REG 0x0
+#define DIG_MINOR 0
+#define DIG_MAJOR 1
+#define ANA_MINOR 2
+#define ANA_MAJOR 3
+#define CHGR_CFG1 0xFB
+#define RECHG_THRESHOLD_SRC_BIT BIT(1)
+#define TERM_I_SRC_BIT BIT(2)
+#define TERM_SRC_FG BIT(2)
+#define CHG_INHIB_CFG_REG 0xF7
+#define CHG_INHIBIT_50MV_VAL 0x00
+#define CHG_INHIBIT_100MV_VAL 0x01
+#define CHG_INHIBIT_200MV_VAL 0x02
+#define CHG_INHIBIT_300MV_VAL 0x03
+#define CHG_INHIBIT_MASK 0x03
+#define USE_REGISTER_FOR_CURRENT BIT(2)
+#define CHGR_CFG2 0xFC
+#define CHG_EN_SRC_BIT BIT(7)
+#define CHG_EN_POLARITY_BIT BIT(6)
+#define P2F_CHG_TRAN BIT(5)
+#define CHG_BAT_OV_ECC BIT(4)
+#define I_TERM_BIT BIT(3)
+#define AUTO_RECHG_BIT BIT(2)
+#define CHARGER_INHIBIT_BIT BIT(0)
+#define USB51_COMMAND_POL BIT(2)
+#define USB51AC_CTRL BIT(1)
+#define TR_8OR32B 0xFE
+#define BUCK_8_16_FREQ_BIT BIT(0)
+#define BM_CFG 0xF3
+#define BATT_MISSING_ALGO_BIT BIT(2)
+#define BMD_PIN_SRC_MASK SMB_MASK(1, 0)
+#define PIN_SRC_SHIFT 0
+#define CHGR_CFG 0xFF
+#define RCHG_LVL_BIT BIT(0)
+#define VCHG_EN_BIT BIT(1)
+#define VCHG_INPUT_CURRENT_BIT BIT(3)
+#define CFG_AFVC 0xF6
+#define VFLOAT_COMP_ENABLE_MASK SMB_MASK(2, 0)
+#define TR_RID_REG 0xFA
+#define FG_INPUT_FET_DELAY_BIT BIT(3)
+#define TRIM_OPTIONS_7_0 0xF6
+#define INPUT_MISSING_POLLER_EN_BIT BIT(3)
+#define CHGR_CCMP_CFG 0xFA
+#define JEITA_TEMP_HARD_LIMIT_BIT BIT(5)
+#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4)
+#define HVDCP_ADAPTER_SEL_9V_BIT BIT(4)
+#define HVDCP_AUTH_ALG_EN_BIT BIT(6)
+#define CMD_APSD 0x41
+#define APSD_RERUN_BIT BIT(0)
+#define OTG_CFG 0xF1
+#define HICCUP_ENABLED_BIT BIT(6)
+#define OTG_PIN_POLARITY_BIT BIT(4)
+#define OTG_PIN_ACTIVE_LOW BIT(4)
+#define OTG_EN_CTRL_MASK SMB_MASK(3, 2)
+#define OTG_PIN_CTRL_RID_DIS 0x04
+#define OTG_CMD_CTRL_RID_EN 0x08
+#define AICL_ADC_BIT BIT(6)
+static void batt_ov_wa_check(struct smbchg_chip *chip)
+{
+ int rc;
+ u8 reg;
+
+ /* disable-'battery OV disables charging' feature */
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
+ CHG_BAT_OV_ECC, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
+ return;
+ }
+
+ /*
+ * if battery OV is set:
+ * restart charging by disable/enable charging
+ */
+ rc = smbchg_read(chip, &reg, chip->bat_if_base + RT_STS, 1);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't read Battery RT status rc = %d\n", rc);
+ return;
+ }
+
+ if (reg & BAT_OV_BIT) {
+ rc = smbchg_charging_en(chip, false);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't disable charging: rc = %d\n", rc);
+ return;
+ }
+
+ /* delay for charging-disable to take affect */
+ msleep(200);
+
+ rc = smbchg_charging_en(chip, true);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't enable charging: rc = %d\n", rc);
+ return;
+ }
+ }
+}
+
+static int smbchg_hw_init(struct smbchg_chip *chip)
+{
+ int rc, i;
+ u8 reg, mask;
+
+ rc = smbchg_read(chip, chip->revision,
+ chip->misc_base + REVISION1_REG, 4);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't read revision rc=%d\n",
+ rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "Charger Revision DIG: %d.%d; ANA: %d.%d\n",
+ chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
+ chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]);
+
+ /* Setup 9V HVDCP */
+ if (!chip->hvdcp_not_supported) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_9V);
+ if (rc < 0) {
+ pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chip->aicl_rerun_period_s > 0) {
+ rc = smbchg_set_aicl_rerun_period_s(chip,
+ chip->aicl_rerun_period_s);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set AICL rerun timer rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG,
+ FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable fg input fet delay rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0,
+ INPUT_MISSING_POLLER_EN_BIT,
+ INPUT_MISSING_POLLER_EN_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable input missing poller rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ /*
+ * Do not force using current from the register i.e. use auto
+ * power source detect (APSD) mA ratings for the initial current values.
+ *
+ * If this is set, AICL will not rerun at 9V for HVDCPs
+ */
+ rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL,
+ USE_REGISTER_FOR_CURRENT, 0);
+
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * set chg en by cmd register, set chg en by writing bit 1,
+ * enable auto pre to fast, enable auto recharge by default.
+ * enable current termination and charge inhibition based on
+ * the device tree configuration.
+ */
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2,
+ CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT | P2F_CHG_TRAN
+ | I_TERM_BIT | AUTO_RECHG_BIT | CHARGER_INHIBIT_BIT,
+ CHG_EN_POLARITY_BIT
+ | (chip->chg_inhibit_en ? CHARGER_INHIBIT_BIT : 0)
+ | (chip->iterm_disabled ? I_TERM_BIT : 0));
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * enable battery charging to make sure it hasn't been changed earlier
+ * by the bootloader.
+ */
+ rc = smbchg_charging_en(chip, true);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't enable battery charging=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * Based on the configuration, use the analog sensors or the fuelgauge
+ * adc for recharge threshold source.
+ */
+
+ if (chip->chg_inhibit_source_fg)
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1,
+ TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT,
+ TERM_SRC_FG | RECHG_THRESHOLD_SRC_BIT);
+ else
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1,
+ TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, 0);
+
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc);
+ return rc;
+ }
+
+ /*
+ * control USB suspend via command bits and set correct 100/500mA
+ * polarity on the usb current
+ */
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ USB51_COMMAND_POL | USB51AC_CTRL, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set usb_chgpth cfg rc=%d\n", rc);
+ return rc;
+ }
+
+ check_battery_type(chip);
+
+ /* set the float voltage */
+ if (chip->vfloat_mv != -EINVAL) {
+ rc = smbchg_float_voltage_set(chip, chip->vfloat_mv);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set float voltage rc = %d\n", rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "set vfloat to %d\n", chip->vfloat_mv);
+ }
+
+ /* set the fast charge current compensation */
+ if (chip->fastchg_current_comp != -EINVAL) {
+ rc = smbchg_fastchg_current_comp_set(chip,
+ chip->fastchg_current_comp);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n",
+ rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "set fastchg current comp to %d\n",
+ chip->fastchg_current_comp);
+ }
+
+ /* set the float voltage compensation */
+ if (chip->float_voltage_comp != -EINVAL) {
+ rc = smbchg_float_voltage_comp_set(chip,
+ chip->float_voltage_comp);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n",
+ rc);
+ return rc;
+ }
+ pr_smb(PR_STATUS, "set float voltage comp to %d\n",
+ chip->float_voltage_comp);
+ }
+
+ /* set iterm */
+ if (chip->iterm_ma != -EINVAL) {
+ if (chip->iterm_disabled) {
+ dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n");
+ return -EINVAL;
+ } else {
+ smbchg_iterm_set(chip, chip->iterm_ma);
+ }
+ }
+
+ /* set the safety time voltage */
+ if (chip->safety_time != -EINVAL) {
+ reg = (chip->safety_time > 0 ? 0 : SFT_TIMER_DISABLE_BIT) |
+ (chip->prechg_safety_time > 0
+ ? 0 : PRECHG_SFT_TIMER_DISABLE_BIT);
+
+ for (i = 0; i < ARRAY_SIZE(chg_time); i++) {
+ if (chip->safety_time <= chg_time[i]) {
+ reg |= i << SAFETY_TIME_MINUTES_SHIFT;
+ break;
+ }
+ }
+ for (i = 0; i < ARRAY_SIZE(prechg_time); i++) {
+ if (chip->prechg_safety_time <= prechg_time[i]) {
+ reg |= i;
+ break;
+ }
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->chgr_base + SFT_CFG,
+ SFT_EN_MASK | SFT_TO_MASK |
+ (chip->prechg_safety_time > 0
+ ? PRECHG_SFT_TO_MASK : 0), reg);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set safety timer rc = %d\n",
+ rc);
+ return rc;
+ }
+ chip->safety_timer_en = true;
+ } else {
+ rc = smbchg_read(chip, &reg, chip->chgr_base + SFT_CFG, 1);
+ if (rc < 0)
+ dev_err(chip->dev, "Unable to read SFT_CFG rc = %d\n",
+ rc);
+ else if (!(reg & SFT_EN_MASK))
+ chip->safety_timer_en = true;
+ }
+
+ /* configure jeita temperature hard limit */
+ if (chip->jeita_temp_hard_limit >= 0) {
+ rc = smbchg_sec_masked_write(chip,
+ chip->chgr_base + CHGR_CCMP_CFG,
+ JEITA_TEMP_HARD_LIMIT_BIT,
+ chip->jeita_temp_hard_limit
+ ? 0 : JEITA_TEMP_HARD_LIMIT_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't set jeita temp hard limit rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* make the buck switch faster to prevent some vbus oscillation */
+ rc = smbchg_sec_masked_write(chip,
+ chip->usb_chgpth_base + TR_8OR32B,
+ BUCK_8_16_FREQ_BIT, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set buck frequency rc = %d\n", rc);
+ return rc;
+ }
+
+ /* battery missing detection */
+ mask = BATT_MISSING_ALGO_BIT;
+ reg = chip->bmd_algo_disabled ? 0 : BATT_MISSING_ALGO_BIT;
+ if (chip->bmd_pin_src < BPD_TYPE_DEFAULT) {
+ mask |= BMD_PIN_SRC_MASK;
+ reg |= chip->bmd_pin_src << PIN_SRC_SHIFT;
+ }
+ rc = smbchg_sec_masked_write(chip,
+ chip->bat_if_base + BM_CFG, mask, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set batt_missing config = %d\n",
+ rc);
+ return rc;
+ }
+
+ if (chip->vchg_adc_channel != -EINVAL) {
+ /* configure and enable VCHG */
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG,
+ VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT,
+ VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set recharge rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ smbchg_charging_status_change(chip);
+
+ vote(chip->usb_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0);
+ vote(chip->dc_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0);
+ /* resume threshold */
+ if (chip->resume_delta_mv != -EINVAL) {
+
+ /*
+ * Configure only if the recharge threshold source is not
+ * fuel gauge ADC.
+ */
+ if (!chip->chg_inhibit_source_fg) {
+ if (chip->resume_delta_mv < 100)
+ reg = CHG_INHIBIT_50MV_VAL;
+ else if (chip->resume_delta_mv < 200)
+ reg = CHG_INHIBIT_100MV_VAL;
+ else if (chip->resume_delta_mv < 300)
+ reg = CHG_INHIBIT_200MV_VAL;
+ else
+ reg = CHG_INHIBIT_300MV_VAL;
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->chgr_base + CHG_INHIB_CFG_REG,
+ CHG_INHIBIT_MASK, reg);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = smbchg_sec_masked_write(chip,
+ chip->chgr_base + CHGR_CFG,
+ RCHG_LVL_BIT,
+ (chip->resume_delta_mv
+ < chip->tables.rchg_thr_mv)
+ ? 0 : RCHG_LVL_BIT);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set recharge rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ /* DC path current settings */
+ if (chip->dc_psy_type != -EINVAL) {
+ rc = vote(chip->dc_icl_votable, PSY_ICL_VOTER, true,
+ chip->dc_target_current_ma);
+ if (rc < 0) {
+ dev_err(chip->dev,
+ "Couldn't vote for initial DC ICL rc=%d\n", rc);
+ return rc;
+ }
+ }
+
+
+ /*
+ * on some devices the battery is powered via external sources which
+ * could raise its voltage above the float voltage. smbchargers go
+ * in to reverse boost in such a situation and the workaround is to
+ * disable float voltage compensation (note that the battery will appear
+ * hot/cold when powered via external source).
+ */
+ if (chip->soft_vfloat_comp_disabled) {
+ rc = smbchg_sec_masked_write(chip, chip->chgr_base + CFG_AFVC,
+ VFLOAT_COMP_ENABLE_MASK, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true,
+ chip->cfg_fastchg_current_ma);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't vote fastchg ma rc = %d\n", rc);
+ return rc;
+ }
+
+ rc = smbchg_read(chip, &chip->original_usbin_allowance,
+ chip->usb_chgpth_base + USBIN_CHGR_CFG, 1);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc);
+
+ if (chip->wipower_dyn_icl_avail) {
+ rc = smbchg_wipower_ilim_config(chip,
+ &(chip->wipower_default.entries[0]));
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set default wipower ilim = %d\n",
+ rc);
+ return rc;
+ }
+ }
+ /* unsuspend dc path, it could be suspended by the bootloader */
+ rc = smbchg_dc_suspend(chip, 0);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't unsuspend dc path= %d\n", rc);
+ return rc;
+ }
+
+ if (chip->force_aicl_rerun) {
+ /* vote to enable hw aicl */
+ rc = vote(chip->hw_aicl_rerun_enable_indirect_votable,
+ DEFAULT_CONFIG_HW_AICL_VOTER, true, 0);
+ if (rc < 0) {
+ pr_err("Couldn't vote enable hw aicl rerun rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chip->schg_version == QPNP_SCHG_LITE) {
+ /* enable OTG hiccup mode */
+ rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG,
+ HICCUP_ENABLED_BIT, HICCUP_ENABLED_BIT);
+ if (rc < 0)
+ dev_err(chip->dev, "Couldn't set OTG OC config rc = %d\n",
+ rc);
+ }
+
+ if (chip->otg_pinctrl) {
+ /* configure OTG enable to pin control active low */
+ rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG,
+ OTG_PIN_POLARITY_BIT | OTG_EN_CTRL_MASK,
+ OTG_PIN_ACTIVE_LOW | OTG_PIN_CTRL_RID_DIS);
+ if (rc < 0) {
+ dev_err(chip->dev, "Couldn't set OTG EN config rc = %d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ if (chip->wa_flags & SMBCHG_BATT_OV_WA)
+ batt_ov_wa_check(chip);
+
+ /* turn off AICL adc for improved accuracy */
+ rc = smbchg_sec_masked_write(chip,
+ chip->misc_base + MISC_TRIM_OPT_15_8, AICL_ADC_BIT, 0);
+ if (rc)
+ pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n",
+ rc);
+
+ return rc;
+}
+
+static struct of_device_id smbchg_match_table[] = {
+ {
+ .compatible = "qcom,qpnp-smbcharger",
+ },
+ { },
+};
+
+#define DC_MA_MIN 300
+#define DC_MA_MAX 2000
+#define OF_PROP_READ(chip, prop, dt_property, retval, optional) \
+do { \
+ if (retval) \
+ break; \
+ if (optional) \
+ prop = -EINVAL; \
+ \
+ retval = of_property_read_u32(chip->pdev->dev.of_node, \
+ "qcom," dt_property , \
+ &prop); \
+ \
+ if ((retval == -EINVAL) && optional) \
+ retval = 0; \
+ else if (retval) \
+ dev_err(chip->dev, "Error reading " #dt_property \
+ " property rc = %d\n", rc); \
+} while (0)
+
+#define ILIM_ENTRIES 3
+#define VOLTAGE_RANGE_ENTRIES 2
+#define RANGE_ENTRY (ILIM_ENTRIES + VOLTAGE_RANGE_ENTRIES)
+static int smb_parse_wipower_map_dt(struct smbchg_chip *chip,
+ struct ilim_map *map, char *property)
+{
+ struct device_node *node = chip->dev->of_node;
+ int total_elements, size;
+ struct property *prop;
+ const __be32 *data;
+ int num, i;
+
+ prop = of_find_property(node, property, &size);
+ if (!prop) {
+ dev_err(chip->dev, "%s missing\n", property);
+ return -EINVAL;
+ }
+
+ total_elements = size / sizeof(int);
+ if (total_elements % RANGE_ENTRY) {
+ dev_err(chip->dev, "%s table not in multiple of %d, total elements = %d\n",
+ property, RANGE_ENTRY, total_elements);
+ return -EINVAL;
+ }
+
+ data = prop->value;
+ num = total_elements / RANGE_ENTRY;
+ map->entries = devm_kzalloc(chip->dev,
+ num * sizeof(struct ilim_entry), GFP_KERNEL);
+ if (!map->entries) {
+ dev_err(chip->dev, "kzalloc failed for default ilim\n");
+ return -ENOMEM;
+ }
+ for (i = 0; i < num; i++) {
+ map->entries[i].vmin_uv = be32_to_cpup(data++);
+ map->entries[i].vmax_uv = be32_to_cpup(data++);
+ map->entries[i].icl_pt_ma = be32_to_cpup(data++);
+ map->entries[i].icl_lv_ma = be32_to_cpup(data++);
+ map->entries[i].icl_hv_ma = be32_to_cpup(data++);
+ }
+ map->num = num;
+ return 0;
+}
+
+static int smb_parse_wipower_dt(struct smbchg_chip *chip)
+{
+ int rc = 0;
+
+ chip->wipower_dyn_icl_avail = false;
+
+ if (!chip->vadc_dev)
+ goto err;
+
+ rc = smb_parse_wipower_map_dt(chip, &chip->wipower_default,
+ "qcom,wipower-default-ilim-map");
+ if (rc) {
+ dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n",
+ rc);
+ goto err;
+ }
+
+ rc = smb_parse_wipower_map_dt(chip, &chip->wipower_pt,
+ "qcom,wipower-pt-ilim-map");
+ if (rc) {
+ dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n",
+ rc);
+ goto err;
+ }
+
+ rc = smb_parse_wipower_map_dt(chip, &chip->wipower_div2,
+ "qcom,wipower-div2-ilim-map");
+ if (rc) {
+ dev_err(chip->dev, "failed to parse wipower-div2-ilim-map rc = %d\n",
+ rc);
+ goto err;
+ }
+ chip->wipower_dyn_icl_avail = true;
+ return 0;
+err:
+ chip->wipower_default.num = 0;
+ chip->wipower_pt.num = 0;
+ chip->wipower_default.num = 0;
+ if (chip->wipower_default.entries)
+ devm_kfree(chip->dev, chip->wipower_default.entries);
+ if (chip->wipower_pt.entries)
+ devm_kfree(chip->dev, chip->wipower_pt.entries);
+ if (chip->wipower_div2.entries)
+ devm_kfree(chip->dev, chip->wipower_div2.entries);
+ chip->wipower_default.entries = NULL;
+ chip->wipower_pt.entries = NULL;
+ chip->wipower_div2.entries = NULL;
+ chip->vadc_dev = NULL;
+ return rc;
+}
+
+#define DEFAULT_VLED_MAX_UV 3500000
+#define DEFAULT_FCC_MA 2000
+static int smb_parse_dt(struct smbchg_chip *chip)
+{
+ int rc = 0, ocp_thresh = -EINVAL;
+ struct device_node *node = chip->dev->of_node;
+ const char *dc_psy_type, *bpd;
+
+ if (!node) {
+ dev_err(chip->dev, "device tree info. missing\n");
+ return -EINVAL;
+ }
+
+ /* read optional u32 properties */
+ OF_PROP_READ(chip, ocp_thresh,
+ "ibat-ocp-threshold-ua", rc, 1);
+ if (ocp_thresh >= 0)
+ smbchg_ibat_ocp_threshold_ua = ocp_thresh;
+ OF_PROP_READ(chip, chip->iterm_ma, "iterm-ma", rc, 1);
+ OF_PROP_READ(chip, chip->cfg_fastchg_current_ma,
+ "fastchg-current-ma", rc, 1);
+ if (chip->cfg_fastchg_current_ma == -EINVAL)
+ chip->cfg_fastchg_current_ma = DEFAULT_FCC_MA;
+ OF_PROP_READ(chip, chip->vfloat_mv, "float-voltage-mv", rc, 1);
+ OF_PROP_READ(chip, chip->safety_time, "charging-timeout-mins", rc, 1);
+ OF_PROP_READ(chip, chip->vled_max_uv, "vled-max-uv", rc, 1);
+ if (chip->vled_max_uv < 0)
+ chip->vled_max_uv = DEFAULT_VLED_MAX_UV;
+ OF_PROP_READ(chip, chip->rpara_uohm, "rparasitic-uohm", rc, 1);
+ if (chip->rpara_uohm < 0)
+ chip->rpara_uohm = 0;
+ OF_PROP_READ(chip, chip->prechg_safety_time, "precharging-timeout-mins",
+ rc, 1);
+ OF_PROP_READ(chip, chip->fastchg_current_comp, "fastchg-current-comp",
+ rc, 1);
+ OF_PROP_READ(chip, chip->float_voltage_comp, "float-voltage-comp",
+ rc, 1);
+ if (chip->safety_time != -EINVAL &&
+ (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) {
+ dev_err(chip->dev, "Bad charging-timeout-mins %d\n",
+ chip->safety_time);
+ return -EINVAL;
+ }
+ if (chip->prechg_safety_time != -EINVAL &&
+ (chip->prechg_safety_time >
+ prechg_time[ARRAY_SIZE(prechg_time) - 1])) {
+ dev_err(chip->dev, "Bad precharging-timeout-mins %d\n",
+ chip->prechg_safety_time);
+ return -EINVAL;
+ }
+ OF_PROP_READ(chip, chip->resume_delta_mv, "resume-delta-mv", rc, 1);
+ OF_PROP_READ(chip, chip->parallel.min_current_thr_ma,
+ "parallel-usb-min-current-ma", rc, 1);
+ OF_PROP_READ(chip, chip->parallel.min_9v_current_thr_ma,
+ "parallel-usb-9v-min-current-ma", rc, 1);
+ OF_PROP_READ(chip, chip->parallel.allowed_lowering_ma,
+ "parallel-allowed-lowering-ma", rc, 1);
+ if (chip->parallel.min_current_thr_ma != -EINVAL
+ && chip->parallel.min_9v_current_thr_ma != -EINVAL)
+ chip->parallel.avail = true;
+ /*
+ * use the dt values if they exist, otherwise do not touch the params
+ */
+ of_property_read_u32(node, "qcom,parallel-main-chg-fcc-percent",
+ &smbchg_main_chg_fcc_percent);
+ of_property_read_u32(node, "qcom,parallel-main-chg-icl-percent",
+ &smbchg_main_chg_icl_percent);
+ pr_smb(PR_STATUS, "parallel usb thr: %d, 9v thr: %d\n",
+ chip->parallel.min_current_thr_ma,
+ chip->parallel.min_9v_current_thr_ma);
+ OF_PROP_READ(chip, chip->jeita_temp_hard_limit,
+ "jeita-temp-hard-limit", rc, 1);
+ OF_PROP_READ(chip, chip->aicl_rerun_period_s,
+ "aicl-rerun-period-s", rc, 1);
+ OF_PROP_READ(chip, chip->vchg_adc_channel,
+ "vchg-adc-channel-id", rc, 1);
+
+ /* read boolean configuration properties */
+ chip->use_vfloat_adjustments = of_property_read_bool(node,
+ "qcom,autoadjust-vfloat");
+ chip->bmd_algo_disabled = of_property_read_bool(node,
+ "qcom,bmd-algo-disabled");
+ chip->iterm_disabled = of_property_read_bool(node,
+ "qcom,iterm-disabled");
+ chip->soft_vfloat_comp_disabled = of_property_read_bool(node,
+ "qcom,soft-vfloat-comp-disabled");
+ chip->chg_enabled = !(of_property_read_bool(node,
+ "qcom,charging-disabled"));
+ chip->charge_unknown_battery = of_property_read_bool(node,
+ "qcom,charge-unknown-battery");
+ chip->chg_inhibit_en = of_property_read_bool(node,
+ "qcom,chg-inhibit-en");
+ chip->chg_inhibit_source_fg = of_property_read_bool(node,
+ "qcom,chg-inhibit-fg");
+ chip->low_volt_dcin = of_property_read_bool(node,
+ "qcom,low-volt-dcin");
+ chip->force_aicl_rerun = of_property_read_bool(node,
+ "qcom,force-aicl-rerun");
+ chip->skip_usb_suspend_for_fake_battery = of_property_read_bool(node,
+ "qcom,skip-usb-suspend-for-fake-battery");
+
+ /* parse the battery missing detection pin source */
+ rc = of_property_read_string(chip->pdev->dev.of_node,
+ "qcom,bmd-pin-src", &bpd);
+ if (rc) {
+ /* Select BAT_THM as default BPD scheme */
+ chip->bmd_pin_src = BPD_TYPE_DEFAULT;
+ rc = 0;
+ } else {
+ chip->bmd_pin_src = get_bpd(bpd);
+ if (chip->bmd_pin_src < 0) {
+ dev_err(chip->dev,
+ "failed to determine bpd schema %d\n", rc);
+ return rc;
+ }
+ }
+
+ /* parse the dc power supply configuration */
+ rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type);
+ if (rc) {
+ chip->dc_psy_type = -EINVAL;
+ rc = 0;
+ } else {
+ if (strcmp(dc_psy_type, "Mains") == 0)
+ chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS;
+ else if (strcmp(dc_psy_type, "Wireless") == 0)
+ chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS;
+ else if (strcmp(dc_psy_type, "Wipower") == 0)
+ chip->dc_psy_type = POWER_SUPPLY_TYPE_WIPOWER;
+ }
+ if (chip->dc_psy_type != -EINVAL) {
+ OF_PROP_READ(chip, chip->dc_target_current_ma,
+ "dc-psy-ma", rc, 0);
+ if (rc)
+ return rc;
+ if (chip->dc_target_current_ma < DC_MA_MIN
+ || chip->dc_target_current_ma > DC_MA_MAX) {
+ dev_err(chip->dev, "Bad dc mA %d\n",
+ chip->dc_target_current_ma);
+ return -EINVAL;
+ }
+ }
+
+ if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER)
+ smb_parse_wipower_dt(chip);
+
+ /* read the bms power supply name */
+ rc = of_property_read_string(node, "qcom,bms-psy-name",
+ &chip->bms_psy_name);
+ if (rc)
+ chip->bms_psy_name = NULL;
+
+ /* read the battery power supply name */
+ rc = of_property_read_string(node, "qcom,battery-psy-name",
+ &chip->battery_psy_name);
+ if (rc)
+ chip->battery_psy_name = "battery";
+
+ /* Get the charger led support property */
+ chip->cfg_chg_led_sw_ctrl =
+ of_property_read_bool(node, "qcom,chg-led-sw-controls");
+ chip->cfg_chg_led_support =
+ of_property_read_bool(node, "qcom,chg-led-support");
+
+ if (of_find_property(node, "qcom,thermal-mitigation",
+ &chip->thermal_levels)) {
+ chip->thermal_mitigation = devm_kzalloc(chip->dev,
+ chip->thermal_levels,
+ GFP_KERNEL);
+
+ if (chip->thermal_mitigation == NULL) {
+ dev_err(chip->dev, "thermal mitigation kzalloc() failed.\n");
+ return -ENOMEM;
+ }
+
+ chip->thermal_levels /= sizeof(int);
+ rc = of_property_read_u32_array(node,
+ "qcom,thermal-mitigation",
+ chip->thermal_mitigation, chip->thermal_levels);
+ if (rc) {
+ dev_err(chip->dev,
+ "Couldn't read threm limits rc = %d\n", rc);
+ return rc;
+ }
+ }
+
+ chip->skip_usb_notification
+ = of_property_read_bool(node,
+ "qcom,skip-usb-notification");
+
+ chip->otg_pinctrl = of_property_read_bool(node, "qcom,otg-pinctrl");
+
+ return 0;
+}
+
+#define SUBTYPE_REG 0x5
+#define SMBCHG_CHGR_SUBTYPE 0x1
+#define SMBCHG_OTG_SUBTYPE 0x8
+#define SMBCHG_BAT_IF_SUBTYPE 0x3
+#define SMBCHG_USB_CHGPTH_SUBTYPE 0x4
+#define SMBCHG_DC_CHGPTH_SUBTYPE 0x5
+#define SMBCHG_MISC_SUBTYPE 0x7
+#define SMBCHG_LITE_CHGR_SUBTYPE 0x51
+#define SMBCHG_LITE_OTG_SUBTYPE 0x58
+#define SMBCHG_LITE_BAT_IF_SUBTYPE 0x53
+#define SMBCHG_LITE_USB_CHGPTH_SUBTYPE 0x54
+#define SMBCHG_LITE_DC_CHGPTH_SUBTYPE 0x55
+#define SMBCHG_LITE_MISC_SUBTYPE 0x57
+static int smbchg_request_irq(struct smbchg_chip *chip,
+ struct device_node *child,
+ int irq_num, char *irq_name,
+ irqreturn_t (irq_handler)(int irq, void *_chip),
+ int flags)
+{
+ int rc;
+
+ irq_num = of_irq_get_byname(child, irq_name);
+ if (irq_num < 0) {
+ dev_err(chip->dev, "Unable to get %s irqn", irq_name);
+ rc = -ENXIO;
+ }
+ rc = devm_request_threaded_irq(chip->dev,
+ irq_num, NULL, irq_handler, flags, irq_name,
+ chip);
+ if (rc < 0) {
+ dev_err(chip->dev, "Unable to request %s irq: %dn",
+ irq_name, rc);
+ rc = -ENXIO;
+ }
+ return 0;
+}
+
+static int smbchg_request_irqs(struct smbchg_chip *chip)
+{
+ int rc = 0;
+ unsigned int base;
+ struct device_node *child;
+ u8 subtype;
+ unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING
+ | IRQF_ONESHOT;
+
+ if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ return -ENXIO;
+ }
+
+ for_each_available_child_of_node(chip->pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ rc = 0;
+ continue;
+ }
+
+ rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1);
+ if (rc) {
+ dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ switch (subtype) {
+ case SMBCHG_CHGR_SUBTYPE:
+ case SMBCHG_LITE_CHGR_SUBTYPE:
+ rc = smbchg_request_irq(chip, child,
+ chip->chg_error_irq, "chg-error",
+ chg_error_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child, chip->taper_irq,
+ "chg-taper-thr", taper_handler,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ disable_irq_nosync(chip->taper_irq);
+ rc = smbchg_request_irq(chip, child, chip->chg_term_irq,
+ "chg-tcc-thr", chg_term_handler,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child, chip->recharge_irq,
+ "chg-rechg-thr", recharge_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child, chip->fastchg_irq,
+ "chg-p2f-thr", fastchg_handler, flags);
+ if (rc < 0)
+ return rc;
+ enable_irq_wake(chip->chg_term_irq);
+ enable_irq_wake(chip->chg_error_irq);
+ enable_irq_wake(chip->fastchg_irq);
+ break;
+ case SMBCHG_BAT_IF_SUBTYPE:
+ case SMBCHG_LITE_BAT_IF_SUBTYPE:
+ rc = smbchg_request_irq(chip, child, chip->batt_hot_irq,
+ "batt-hot", batt_hot_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->batt_warm_irq,
+ "batt-warm", batt_warm_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->batt_cool_irq,
+ "batt-cool", batt_cool_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->batt_cold_irq,
+ "batt-cold", batt_cold_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->batt_missing_irq,
+ "batt-missing", batt_pres_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->vbat_low_irq,
+ "batt-low", vbat_low_handler, flags);
+ if (rc < 0)
+ return rc;
+
+ enable_irq_wake(chip->batt_hot_irq);
+ enable_irq_wake(chip->batt_warm_irq);
+ enable_irq_wake(chip->batt_cool_irq);
+ enable_irq_wake(chip->batt_cold_irq);
+ enable_irq_wake(chip->batt_missing_irq);
+ enable_irq_wake(chip->vbat_low_irq);
+ break;
+ case SMBCHG_USB_CHGPTH_SUBTYPE:
+ case SMBCHG_LITE_USB_CHGPTH_SUBTYPE:
+ rc = smbchg_request_irq(chip, child,
+ chip->usbin_uv_irq,
+ "usbin-uv", usbin_uv_handler,
+ flags | IRQF_EARLY_RESUME);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->usbin_ov_irq,
+ "usbin-ov", usbin_ov_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->src_detect_irq,
+ "usbin-src-det",
+ src_detect_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->aicl_done_irq,
+ "aicl-done",
+ aicl_done_handler,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+
+ if (chip->schg_version != QPNP_SCHG_LITE) {
+ rc = smbchg_request_irq(chip, child,
+ chip->otg_fail_irq, "otg-fail",
+ otg_fail_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->otg_oc_irq, "otg-oc",
+ otg_oc_handler,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->usbid_change_irq, "usbid-change",
+ usbid_change_handler,
+ (IRQF_TRIGGER_FALLING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ enable_irq_wake(chip->otg_oc_irq);
+ enable_irq_wake(chip->usbid_change_irq);
+ enable_irq_wake(chip->otg_fail_irq);
+ }
+ enable_irq_wake(chip->usbin_uv_irq);
+ enable_irq_wake(chip->usbin_ov_irq);
+ enable_irq_wake(chip->src_detect_irq);
+ if (chip->parallel.avail && chip->usb_present) {
+ rc = enable_irq_wake(chip->aicl_done_irq);
+ chip->enable_aicl_wake = true;
+ }
+ break;
+ case SMBCHG_DC_CHGPTH_SUBTYPE:
+ case SMBCHG_LITE_DC_CHGPTH_SUBTYPE:
+ rc = smbchg_request_irq(chip, child, chip->dcin_uv_irq,
+ "dcin-uv", dcin_uv_handler, flags);
+ if (rc < 0)
+ return rc;
+ enable_irq_wake(chip->dcin_uv_irq);
+ break;
+ case SMBCHG_MISC_SUBTYPE:
+ case SMBCHG_LITE_MISC_SUBTYPE:
+ rc = smbchg_request_irq(chip, child, chip->power_ok_irq,
+ "power-ok", power_ok_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child, chip->chg_hot_irq,
+ "temp-shutdown", chg_hot_handler, flags);
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child, chip->wdog_timeout_irq,
+ "wdog-timeout",
+ wdog_timeout_handler, flags);
+ if (rc < 0)
+ return rc;
+ enable_irq_wake(chip->chg_hot_irq);
+ enable_irq_wake(chip->wdog_timeout_irq);
+ break;
+ case SMBCHG_OTG_SUBTYPE:
+ break;
+ case SMBCHG_LITE_OTG_SUBTYPE:
+ rc = smbchg_request_irq(chip, child,
+ chip->usbid_change_irq, "usbid-change",
+ usbid_change_handler,
+ (IRQF_TRIGGER_FALLING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->otg_oc_irq, "otg-oc",
+ otg_oc_handler,
+ (IRQF_TRIGGER_RISING | IRQF_ONESHOT));
+ if (rc < 0)
+ return rc;
+ rc = smbchg_request_irq(chip, child,
+ chip->otg_fail_irq, "otg-fail",
+ otg_fail_handler, flags);
+ if (rc < 0)
+ return rc;
+ enable_irq_wake(chip->usbid_change_irq);
+ enable_irq_wake(chip->otg_oc_irq);
+ enable_irq_wake(chip->otg_fail_irq);
+ break;
+ }
+ }
+
+ return rc;
+}
+
+#define REQUIRE_BASE(chip, base, rc) \
+do { \
+ if (!rc && !chip->base) { \
+ dev_err(chip->dev, "Missing " #base "\n"); \
+ rc = -EINVAL; \
+ } \
+} while (0)
+
+static int smbchg_parse_peripherals(struct smbchg_chip *chip)
+{
+ int rc = 0;
+ unsigned int base;
+ struct device_node *child;
+ u8 subtype;
+
+ if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) {
+ pr_err("no child nodes\n");
+ return -ENXIO;
+ }
+
+ for_each_available_child_of_node(chip->pdev->dev.of_node, child) {
+ rc = of_property_read_u32(child, "reg", &base);
+ if (rc < 0) {
+ rc = 0;
+ continue;
+ }
+
+ rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1);
+ if (rc) {
+ dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ switch (subtype) {
+ case SMBCHG_CHGR_SUBTYPE:
+ case SMBCHG_LITE_CHGR_SUBTYPE:
+ chip->chgr_base = base;
+ break;
+ case SMBCHG_BAT_IF_SUBTYPE:
+ case SMBCHG_LITE_BAT_IF_SUBTYPE:
+ chip->bat_if_base = base;
+ break;
+ case SMBCHG_USB_CHGPTH_SUBTYPE:
+ case SMBCHG_LITE_USB_CHGPTH_SUBTYPE:
+ chip->usb_chgpth_base = base;
+ break;
+ case SMBCHG_DC_CHGPTH_SUBTYPE:
+ case SMBCHG_LITE_DC_CHGPTH_SUBTYPE:
+ chip->dc_chgpth_base = base;
+ break;
+ case SMBCHG_MISC_SUBTYPE:
+ case SMBCHG_LITE_MISC_SUBTYPE:
+ chip->misc_base = base;
+ break;
+ case SMBCHG_OTG_SUBTYPE:
+ case SMBCHG_LITE_OTG_SUBTYPE:
+ chip->otg_base = base;
+ break;
+ }
+ }
+
+ REQUIRE_BASE(chip, chgr_base, rc);
+ REQUIRE_BASE(chip, bat_if_base, rc);
+ REQUIRE_BASE(chip, usb_chgpth_base, rc);
+ REQUIRE_BASE(chip, dc_chgpth_base, rc);
+ REQUIRE_BASE(chip, misc_base, rc);
+
+ return rc;
+}
+
+static inline void dump_reg(struct smbchg_chip *chip, u16 addr,
+ const char *name)
+{
+ u8 reg;
+
+ smbchg_read(chip, &reg, addr, 1);
+ pr_smb(PR_DUMP, "%s - %04X = %02X\n", name, addr, reg);
+}
+
+/* dumps useful registers for debug */
+static void dump_regs(struct smbchg_chip *chip)
+{
+ u16 addr;
+
+ /* charger peripheral */
+ for (addr = 0xB; addr <= 0x10; addr++)
+ dump_reg(chip, chip->chgr_base + addr, "CHGR Status");
+ for (addr = 0xF0; addr <= 0xFF; addr++)
+ dump_reg(chip, chip->chgr_base + addr, "CHGR Config");
+ /* battery interface peripheral */
+ dump_reg(chip, chip->bat_if_base + RT_STS, "BAT_IF Status");
+ dump_reg(chip, chip->bat_if_base + CMD_CHG_REG, "BAT_IF Command");
+ for (addr = 0xF0; addr <= 0xFB; addr++)
+ dump_reg(chip, chip->bat_if_base + addr, "BAT_IF Config");
+ /* usb charge path peripheral */
+ for (addr = 0x7; addr <= 0x10; addr++)
+ dump_reg(chip, chip->usb_chgpth_base + addr, "USB Status");
+ dump_reg(chip, chip->usb_chgpth_base + CMD_IL, "USB Command");
+ for (addr = 0xF0; addr <= 0xF5; addr++)
+ dump_reg(chip, chip->usb_chgpth_base + addr, "USB Config");
+ /* dc charge path peripheral */
+ dump_reg(chip, chip->dc_chgpth_base + RT_STS, "DC Status");
+ for (addr = 0xF0; addr <= 0xF6; addr++)
+ dump_reg(chip, chip->dc_chgpth_base + addr, "DC Config");
+ /* misc peripheral */
+ dump_reg(chip, chip->misc_base + IDEV_STS, "MISC Status");
+ dump_reg(chip, chip->misc_base + RT_STS, "MISC Status");
+ for (addr = 0xF0; addr <= 0xF3; addr++)
+ dump_reg(chip, chip->misc_base + addr, "MISC CFG");
+}
+
+static int create_debugfs_entries(struct smbchg_chip *chip)
+{
+ struct dentry *ent;
+
+ chip->debug_root = debugfs_create_dir("qpnp-smbcharger", NULL);
+ if (!chip->debug_root) {
+ dev_err(chip->dev, "Couldn't create debug dir\n");
+ return -EINVAL;
+ }
+
+ ent = debugfs_create_file("force_dcin_icl_check",
+ S_IFREG | S_IWUSR | S_IRUGO,
+ chip->debug_root, chip,
+ &force_dcin_icl_ops);
+ if (!ent) {
+ dev_err(chip->dev,
+ "Couldn't create force dcin icl check file\n");
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int smbchg_check_chg_version(struct smbchg_chip *chip)
+{
+ struct pmic_revid_data *pmic_rev_id;
+ struct device_node *revid_dev_node;
+ int rc;
+
+ revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node,
+ "qcom,pmic-revid", 0);
+ if (!revid_dev_node) {
+ pr_err("Missing qcom,pmic-revid property - driver failed\n");
+ return -EINVAL;
+ }
+
+ pmic_rev_id = get_revid_data(revid_dev_node);
+ if (IS_ERR(pmic_rev_id)) {
+ rc = PTR_ERR(revid_dev_node);
+ if (rc != -EPROBE_DEFER)
+ pr_err("Unable to get pmic_revid rc=%d\n", rc);
+ return rc;
+ }
+
+ switch (pmic_rev_id->pmic_subtype) {
+ case PMI8994:
+ chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA
+ | SMBCHG_BATT_OV_WA
+ | SMBCHG_CC_ESR_WA
+ | SMBCHG_RESTART_WA;
+ use_pmi8994_tables(chip);
+ chip->schg_version = QPNP_SCHG;
+ break;
+ case PMI8950:
+ case PMI8937:
+ chip->wa_flags |= SMBCHG_BATT_OV_WA;
+ if (pmic_rev_id->rev4 < 2) /* PMI8950 1.0 */ {
+ chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA;
+ } else { /* rev > PMI8950 v1.0 */
+ chip->wa_flags |= SMBCHG_HVDCP_9V_EN_WA
+ | SMBCHG_USB100_WA;
+ }
+ use_pmi8994_tables(chip);
+ chip->tables.aicl_rerun_period_table =
+ aicl_rerun_period_schg_lite;
+ chip->tables.aicl_rerun_period_len =
+ ARRAY_SIZE(aicl_rerun_period_schg_lite);
+
+ chip->schg_version = QPNP_SCHG_LITE;
+ if (pmic_rev_id->pmic_subtype == PMI8937)
+ chip->hvdcp_not_supported = true;
+ break;
+ case PMI8996:
+ chip->wa_flags |= SMBCHG_CC_ESR_WA
+ | SMBCHG_FLASH_ICL_DISABLE_WA
+ | SMBCHG_RESTART_WA
+ | SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA;
+ use_pmi8996_tables(chip);
+ chip->schg_version = QPNP_SCHG;
+ break;
+ default:
+ pr_err("PMIC subtype %d not supported, WA flags not set\n",
+ pmic_rev_id->pmic_subtype);
+ }
+
+ pr_smb(PR_STATUS, "pmic=%s, wa_flags=0x%x, hvdcp_supported=%s\n",
+ pmic_rev_id->pmic_name, chip->wa_flags,
+ chip->hvdcp_not_supported ? "false" : "true");
+
+ return 0;
+}
+
+static void rerun_hvdcp_det_if_necessary(struct smbchg_chip *chip)
+{
+ enum power_supply_type usb_supply_type;
+ char *usb_type_name;
+ int rc;
+
+ if (!(chip->wa_flags & SMBCHG_RESTART_WA))
+ return;
+
+ read_usb_type(chip, &usb_type_name, &usb_supply_type);
+ if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP
+ && !is_hvdcp_present(chip)) {
+ pr_smb(PR_STATUS, "DCP found rerunning APSD\n");
+ rc = vote(chip->usb_icl_votable,
+ CHG_SUSPEND_WORKAROUND_ICL_VOTER, true, 300);
+ if (rc < 0)
+ pr_err("Couldn't vote for 300mA for suspend wa, going ahead rc=%d\n",
+ rc);
+
+ pr_smb(PR_STATUS, "Faking Removal\n");
+ fake_insertion_removal(chip, false);
+ msleep(500);
+ pr_smb(PR_STATUS, "Faking Insertion\n");
+ fake_insertion_removal(chip, true);
+
+ read_usb_type(chip, &usb_type_name, &usb_supply_type);
+ if (usb_supply_type != POWER_SUPPLY_TYPE_USB_DCP) {
+ msleep(500);
+ pr_smb(PR_STATUS, "Fake Removal again as type!=DCP\n");
+ fake_insertion_removal(chip, false);
+ msleep(500);
+ pr_smb(PR_STATUS, "Fake Insert again as type!=DCP\n");
+ fake_insertion_removal(chip, true);
+ }
+
+ rc = vote(chip->usb_icl_votable,
+ CHG_SUSPEND_WORKAROUND_ICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't vote for 0 for suspend wa, going ahead rc=%d\n",
+ rc);
+ }
+}
+
+static int smbchg_probe(struct platform_device *pdev)
+{
+ int rc;
+ struct smbchg_chip *chip;
+ struct power_supply *typec_psy = NULL;
+ struct qpnp_vadc_chip *vadc_dev, *vchg_vadc_dev;
+ const char *typec_psy_name;
+ struct power_supply_config usb_psy_cfg = {};
+ struct power_supply_config batt_psy_cfg = {};
+ struct power_supply_config dc_psy_cfg = {};
+
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,external-typec")) {
+ /* read the type power supply name */
+ rc = of_property_read_string(pdev->dev.of_node,
+ "qcom,typec-psy-name", &typec_psy_name);
+ if (rc) {
+ pr_err("failed to get prop typec-psy-name rc=%d\n",
+ rc);
+ return rc;
+ }
+
+ typec_psy = power_supply_get_by_name(typec_psy_name);
+ if (!typec_psy) {
+ pr_smb(PR_STATUS,
+ "Type-C supply not found, deferring probe\n");
+ return -EPROBE_DEFER;
+ }
+ }
+
+ vadc_dev = NULL;
+ if (of_find_property(pdev->dev.of_node, "qcom,dcin-vadc", NULL)) {
+ vadc_dev = qpnp_get_vadc(&pdev->dev, "dcin");
+ if (IS_ERR(vadc_dev)) {
+ rc = PTR_ERR(vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ dev_err(&pdev->dev,
+ "Couldn't get vadc rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+ vchg_vadc_dev = NULL;
+ if (of_find_property(pdev->dev.of_node, "qcom,vchg_sns-vadc", NULL)) {
+ vchg_vadc_dev = qpnp_get_vadc(&pdev->dev, "vchg_sns");
+ if (IS_ERR(vchg_vadc_dev)) {
+ rc = PTR_ERR(vchg_vadc_dev);
+ if (rc != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "Couldn't get vadc 'vchg' rc=%d\n",
+ rc);
+ return rc;
+ }
+ }
+
+
+ chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL);
+ if (!chip)
+ return -ENOMEM;
+
+ chip->regmap = dev_get_regmap(pdev->dev.parent, NULL);
+ if (!chip->regmap) {
+ dev_err(&pdev->dev, "Couldn't get parent's regmap\n");
+ return -EINVAL;
+ }
+
+ chip->fcc_votable = create_votable("BATT_FCC",
+ VOTE_MIN,
+ set_fastchg_current_vote_cb, chip);
+ if (IS_ERR(chip->fcc_votable)) {
+ rc = PTR_ERR(chip->fcc_votable);
+ goto votables_cleanup;
+ }
+
+ chip->usb_icl_votable = create_votable("USB_ICL",
+ VOTE_MIN,
+ set_usb_current_limit_vote_cb, chip);
+ if (IS_ERR(chip->usb_icl_votable)) {
+ rc = PTR_ERR(chip->usb_icl_votable);
+ goto votables_cleanup;
+ }
+
+ chip->dc_icl_votable = create_votable("DCIN_ICL",
+ VOTE_MIN,
+ set_dc_current_limit_vote_cb, chip);
+ if (IS_ERR(chip->dc_icl_votable)) {
+ rc = PTR_ERR(chip->dc_icl_votable);
+ goto votables_cleanup;
+ }
+
+ chip->usb_suspend_votable = create_votable("USB_SUSPEND",
+ VOTE_SET_ANY,
+ usb_suspend_vote_cb, chip);
+ if (IS_ERR(chip->usb_suspend_votable)) {
+ rc = PTR_ERR(chip->usb_suspend_votable);
+ goto votables_cleanup;
+ }
+
+ chip->dc_suspend_votable = create_votable("DC_SUSPEND",
+ VOTE_SET_ANY,
+ dc_suspend_vote_cb, chip);
+ if (IS_ERR(chip->dc_suspend_votable)) {
+ rc = PTR_ERR(chip->dc_suspend_votable);
+ goto votables_cleanup;
+ }
+
+ chip->battchg_suspend_votable = create_votable("BATTCHG_SUSPEND",
+ VOTE_SET_ANY,
+ charging_suspend_vote_cb, chip);
+ if (IS_ERR(chip->battchg_suspend_votable)) {
+ rc = PTR_ERR(chip->battchg_suspend_votable);
+ goto votables_cleanup;
+ }
+
+ chip->hw_aicl_rerun_disable_votable = create_votable("HWAICL_DISABLE",
+ VOTE_SET_ANY,
+ smbchg_hw_aicl_rerun_disable_cb, chip);
+ if (IS_ERR(chip->hw_aicl_rerun_disable_votable)) {
+ rc = PTR_ERR(chip->hw_aicl_rerun_disable_votable);
+ goto votables_cleanup;
+ }
+
+ chip->hw_aicl_rerun_enable_indirect_votable = create_votable(
+ "HWAICL_ENABLE_INDIRECT",
+ VOTE_SET_ANY,
+ smbchg_hw_aicl_rerun_enable_indirect_cb, chip);
+ if (IS_ERR(chip->hw_aicl_rerun_enable_indirect_votable)) {
+ rc = PTR_ERR(chip->hw_aicl_rerun_enable_indirect_votable);
+ goto votables_cleanup;
+ }
+
+ chip->aicl_deglitch_short_votable = create_votable(
+ "HWAICL_SHORT_DEGLITCH",
+ VOTE_SET_ANY,
+ smbchg_aicl_deglitch_config_cb, chip);
+ if (IS_ERR(chip->aicl_deglitch_short_votable)) {
+ rc = PTR_ERR(chip->aicl_deglitch_short_votable);
+ goto votables_cleanup;
+ }
+
+ INIT_WORK(&chip->usb_set_online_work, smbchg_usb_update_online_work);
+ INIT_DELAYED_WORK(&chip->parallel_en_work,
+ smbchg_parallel_usb_en_work);
+ INIT_DELAYED_WORK(&chip->vfloat_adjust_work, smbchg_vfloat_adjust_work);
+ INIT_DELAYED_WORK(&chip->hvdcp_det_work, smbchg_hvdcp_det_work);
+ init_completion(&chip->src_det_lowered);
+ init_completion(&chip->src_det_raised);
+ init_completion(&chip->usbin_uv_lowered);
+ init_completion(&chip->usbin_uv_raised);
+ chip->vadc_dev = vadc_dev;
+ chip->vchg_vadc_dev = vchg_vadc_dev;
+ chip->pdev = pdev;
+ chip->dev = &pdev->dev;
+
+ chip->typec_psy = typec_psy;
+ chip->fake_battery_soc = -EINVAL;
+ chip->usb_online = -EINVAL;
+ dev_set_drvdata(&pdev->dev, chip);
+
+ spin_lock_init(&chip->sec_access_lock);
+ mutex_init(&chip->therm_lvl_lock);
+ mutex_init(&chip->usb_set_online_lock);
+ mutex_init(&chip->parallel.lock);
+ mutex_init(&chip->taper_irq_lock);
+ mutex_init(&chip->pm_lock);
+ mutex_init(&chip->wipower_config);
+ mutex_init(&chip->usb_status_lock);
+ device_init_wakeup(chip->dev, true);
+
+ rc = smbchg_parse_peripherals(chip);
+ if (rc) {
+ dev_err(chip->dev, "Error parsing DT peripherals: %d\n", rc);
+ goto votables_cleanup;
+ }
+
+ rc = smbchg_check_chg_version(chip);
+ if (rc) {
+ pr_err("Unable to check schg version rc=%d\n", rc);
+ goto votables_cleanup;
+ }
+
+ rc = smb_parse_dt(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Unable to parse DT nodes: %d\n", rc);
+ goto votables_cleanup;
+ }
+
+ rc = smbchg_regulator_init(chip);
+ if (rc) {
+ dev_err(&pdev->dev,
+ "Couldn't initialize regulator rc=%d\n", rc);
+ goto votables_cleanup;
+ }
+
+ chip->extcon = devm_extcon_dev_allocate(chip->dev, smbchg_extcon_cable);
+ if (IS_ERR(chip->extcon)) {
+ dev_err(chip->dev, "failed to allocate extcon device\n");
+ rc = PTR_ERR(chip->extcon);
+ goto votables_cleanup;
+ }
+
+ rc = devm_extcon_dev_register(chip->dev, chip->extcon);
+ if (rc) {
+ dev_err(chip->dev, "failed to register extcon device\n");
+ goto votables_cleanup;
+ }
+
+ chip->usb_psy_d.name = "usb";
+ chip->usb_psy_d.type = POWER_SUPPLY_TYPE_USB;
+ chip->usb_psy_d.get_property = smbchg_usb_get_property;
+ chip->usb_psy_d.set_property = smbchg_usb_set_property;
+ chip->usb_psy_d.properties = smbchg_usb_properties;
+ chip->usb_psy_d.num_properties = ARRAY_SIZE(smbchg_usb_properties);
+ chip->usb_psy_d.property_is_writeable = smbchg_usb_is_writeable;
+
+ usb_psy_cfg.drv_data = chip;
+ usb_psy_cfg.supplied_to = smbchg_usb_supplicants;
+ usb_psy_cfg.num_supplicants = ARRAY_SIZE(smbchg_usb_supplicants);
+
+ chip->usb_psy = devm_power_supply_register(chip->dev,
+ &chip->usb_psy_d, &usb_psy_cfg);
+ if (IS_ERR(chip->usb_psy)) {
+ dev_err(&pdev->dev, "Unable to register usb_psy rc = %ld\n",
+ PTR_ERR(chip->usb_psy));
+ rc = PTR_ERR(chip->usb_psy);
+ goto votables_cleanup;
+ }
+
+ if (of_find_property(chip->dev->of_node, "dpdm-supply", NULL)) {
+ chip->dpdm_reg = devm_regulator_get(chip->dev, "dpdm");
+ if (IS_ERR(chip->dpdm_reg)) {
+ rc = PTR_ERR(chip->dpdm_reg);
+ goto votables_cleanup;
+ }
+ }
+
+ rc = smbchg_hw_init(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Unable to intialize hardware rc = %d\n", rc);
+ goto out;
+ }
+
+ rc = determine_initial_status(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev,
+ "Unable to determine init status rc = %d\n", rc);
+ goto out;
+ }
+
+ chip->previous_soc = -EINVAL;
+ chip->batt_psy_d.name = chip->battery_psy_name;
+ chip->batt_psy_d.type = POWER_SUPPLY_TYPE_BATTERY;
+ chip->batt_psy_d.get_property = smbchg_battery_get_property;
+ chip->batt_psy_d.set_property = smbchg_battery_set_property;
+ chip->batt_psy_d.properties = smbchg_battery_properties;
+ chip->batt_psy_d.num_properties = ARRAY_SIZE(smbchg_battery_properties);
+ chip->batt_psy_d.external_power_changed = smbchg_external_power_changed;
+ chip->batt_psy_d.property_is_writeable = smbchg_battery_is_writeable;
+
+ batt_psy_cfg.drv_data = chip;
+ batt_psy_cfg.num_supplicants = 0;
+ chip->batt_psy = devm_power_supply_register(chip->dev,
+ &chip->batt_psy_d,
+ &batt_psy_cfg);
+ if (IS_ERR(chip->batt_psy)) {
+ dev_err(&pdev->dev,
+ "Unable to register batt_psy rc = %ld\n",
+ PTR_ERR(chip->batt_psy));
+ goto out;
+ }
+
+ if (chip->dc_psy_type != -EINVAL) {
+ chip->dc_psy_d.name = "dc";
+ chip->dc_psy_d.type = chip->dc_psy_type;
+ chip->dc_psy_d.get_property = smbchg_dc_get_property;
+ chip->dc_psy_d.set_property = smbchg_dc_set_property;
+ chip->dc_psy_d.property_is_writeable = smbchg_dc_is_writeable;
+ chip->dc_psy_d.properties = smbchg_dc_properties;
+ chip->dc_psy_d.num_properties
+ = ARRAY_SIZE(smbchg_dc_properties);
+
+ dc_psy_cfg.drv_data = chip;
+ dc_psy_cfg.num_supplicants
+ = ARRAY_SIZE(smbchg_dc_supplicants);
+ dc_psy_cfg.supplied_to = smbchg_dc_supplicants;
+
+ chip->dc_psy = devm_power_supply_register(chip->dev,
+ &chip->dc_psy_d,
+ &dc_psy_cfg);
+ if (IS_ERR(chip->dc_psy)) {
+ dev_err(&pdev->dev,
+ "Unable to register dc_psy rc = %ld\n",
+ PTR_ERR(chip->dc_psy));
+ goto out;
+ }
+ }
+
+ if (chip->cfg_chg_led_support &&
+ chip->schg_version == QPNP_SCHG_LITE) {
+ rc = smbchg_register_chg_led(chip);
+ if (rc) {
+ dev_err(chip->dev,
+ "Unable to register charger led: %d\n",
+ rc);
+ goto out;
+ }
+
+ rc = smbchg_chg_led_controls(chip);
+ if (rc) {
+ dev_err(chip->dev,
+ "Failed to set charger led controld bit: %d\n",
+ rc);
+ goto unregister_led_class;
+ }
+ }
+
+ rc = smbchg_request_irqs(chip);
+ if (rc < 0) {
+ dev_err(&pdev->dev, "Unable to request irqs rc = %d\n", rc);
+ goto unregister_led_class;
+ }
+
+ rerun_hvdcp_det_if_necessary(chip);
+
+ dump_regs(chip);
+ create_debugfs_entries(chip);
+ dev_info(chip->dev,
+ "SMBCHG successfully probe Charger version=%s Revision DIG:%d.%d ANA:%d.%d batt=%d dc=%d usb=%d\n",
+ version_str[chip->schg_version],
+ chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR],
+ chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR],
+ get_prop_batt_present(chip),
+ chip->dc_present, chip->usb_present);
+ return 0;
+
+unregister_led_class:
+ if (chip->cfg_chg_led_support && chip->schg_version == QPNP_SCHG_LITE)
+ led_classdev_unregister(&chip->led_cdev);
+out:
+ handle_usb_removal(chip);
+votables_cleanup:
+ if (chip->aicl_deglitch_short_votable)
+ destroy_votable(chip->aicl_deglitch_short_votable);
+ if (chip->hw_aicl_rerun_enable_indirect_votable)
+ destroy_votable(chip->hw_aicl_rerun_enable_indirect_votable);
+ if (chip->hw_aicl_rerun_disable_votable)
+ destroy_votable(chip->hw_aicl_rerun_disable_votable);
+ if (chip->battchg_suspend_votable)
+ destroy_votable(chip->battchg_suspend_votable);
+ if (chip->dc_suspend_votable)
+ destroy_votable(chip->dc_suspend_votable);
+ if (chip->usb_suspend_votable)
+ destroy_votable(chip->usb_suspend_votable);
+ if (chip->dc_icl_votable)
+ destroy_votable(chip->dc_icl_votable);
+ if (chip->usb_icl_votable)
+ destroy_votable(chip->usb_icl_votable);
+ if (chip->fcc_votable)
+ destroy_votable(chip->fcc_votable);
+ return rc;
+}
+
+static int smbchg_remove(struct platform_device *pdev)
+{
+ struct smbchg_chip *chip = dev_get_drvdata(&pdev->dev);
+
+ debugfs_remove_recursive(chip->debug_root);
+
+ destroy_votable(chip->aicl_deglitch_short_votable);
+ destroy_votable(chip->hw_aicl_rerun_enable_indirect_votable);
+ destroy_votable(chip->hw_aicl_rerun_disable_votable);
+ destroy_votable(chip->battchg_suspend_votable);
+ destroy_votable(chip->dc_suspend_votable);
+ destroy_votable(chip->usb_suspend_votable);
+ destroy_votable(chip->dc_icl_votable);
+ destroy_votable(chip->usb_icl_votable);
+ destroy_votable(chip->fcc_votable);
+
+ return 0;
+}
+
+static void smbchg_shutdown(struct platform_device *pdev)
+{
+ struct smbchg_chip *chip = dev_get_drvdata(&pdev->dev);
+ int rc;
+
+ if (!(chip->wa_flags & SMBCHG_RESTART_WA))
+ return;
+
+ if (!is_hvdcp_present(chip))
+ return;
+
+ pr_smb(PR_MISC, "Disable Parallel\n");
+ mutex_lock(&chip->parallel.lock);
+ smbchg_parallel_en = 0;
+ smbchg_parallel_usb_disable(chip);
+ mutex_unlock(&chip->parallel.lock);
+
+ pr_smb(PR_MISC, "Disable all interrupts\n");
+ disable_irq(chip->aicl_done_irq);
+ disable_irq(chip->batt_cold_irq);
+ disable_irq(chip->batt_cool_irq);
+ disable_irq(chip->batt_hot_irq);
+ disable_irq(chip->batt_missing_irq);
+ disable_irq(chip->batt_warm_irq);
+ disable_irq(chip->chg_error_irq);
+ disable_irq(chip->chg_hot_irq);
+ disable_irq(chip->chg_term_irq);
+ disable_irq(chip->dcin_uv_irq);
+ disable_irq(chip->fastchg_irq);
+ disable_irq(chip->otg_fail_irq);
+ disable_irq(chip->otg_oc_irq);
+ disable_irq(chip->power_ok_irq);
+ disable_irq(chip->recharge_irq);
+ disable_irq(chip->src_detect_irq);
+ disable_irq(chip->taper_irq);
+ disable_irq(chip->usbid_change_irq);
+ disable_irq(chip->usbin_ov_irq);
+ disable_irq(chip->usbin_uv_irq);
+ disable_irq(chip->vbat_low_irq);
+ disable_irq(chip->wdog_timeout_irq);
+
+ /* remove all votes for short deglitch */
+ vote(chip->aicl_deglitch_short_votable,
+ VARB_WORKAROUND_SHORT_DEGLITCH_VOTER, false, 0);
+ vote(chip->aicl_deglitch_short_votable,
+ HVDCP_SHORT_DEGLITCH_VOTER, false, 0);
+
+ /* vote to ensure AICL rerun is enabled */
+ rc = vote(chip->hw_aicl_rerun_enable_indirect_votable,
+ SHUTDOWN_WORKAROUND_VOTER, true, 0);
+ if (rc < 0)
+ pr_err("Couldn't vote to enable indirect AICL rerun\n");
+ rc = vote(chip->hw_aicl_rerun_disable_votable,
+ WEAK_CHARGER_HW_AICL_VOTER, false, 0);
+ if (rc < 0)
+ pr_err("Couldn't vote to enable AICL rerun\n");
+
+ /* switch to 5V HVDCP */
+ pr_smb(PR_MISC, "Switch to 5V HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_ADAPTER_SEL_MASK, HVDCP_5V);
+ if (rc < 0) {
+ pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc);
+ return;
+ }
+
+ pr_smb(PR_MISC, "Wait 500mS to lower to 5V\n");
+ /* wait for HVDCP to lower to 5V */
+ msleep(500);
+ /*
+ * Check if the same hvdcp session is in progress. src_det should be
+ * high and that we are still in 5V hvdcp
+ */
+ if (!is_src_detect_high(chip)) {
+ pr_smb(PR_MISC, "src det low after 500mS sleep\n");
+ return;
+ }
+
+ /* disable HVDCP */
+ pr_smb(PR_MISC, "Disable HVDCP\n");
+ rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG,
+ HVDCP_EN_BIT, 0);
+ if (rc < 0)
+ pr_err("Couldn't disable HVDCP rc=%d\n", rc);
+
+ chip->hvdcp_3_det_ignore_uv = true;
+ /* fake a removal */
+ pr_smb(PR_MISC, "Faking Removal\n");
+ rc = fake_insertion_removal(chip, false);
+ if (rc < 0)
+ pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc);
+
+ /* fake an insertion */
+ pr_smb(PR_MISC, "Faking Insertion\n");
+ rc = fake_insertion_removal(chip, true);
+ if (rc < 0)
+ pr_err("Couldn't fake insertion rc=%d\n", rc);
+
+ pr_smb(PR_MISC, "Wait 1S to settle\n");
+ msleep(1000);
+ chip->hvdcp_3_det_ignore_uv = false;
+
+ pr_smb(PR_STATUS, "wrote power off configurations\n");
+}
+
+static const struct dev_pm_ops smbchg_pm_ops = {
+};
+
+MODULE_DEVICE_TABLE(spmi, smbchg_id);
+
+static struct platform_driver smbchg_driver = {
+ .driver = {
+ .name = "qpnp-smbcharger",
+ .owner = THIS_MODULE,
+ .of_match_table = smbchg_match_table,
+ .pm = &smbchg_pm_ops,
+ },
+ .probe = smbchg_probe,
+ .remove = smbchg_remove,
+ .shutdown = smbchg_shutdown,
+};
+
+static int __init smbchg_init(void)
+{
+ return platform_driver_register(&smbchg_driver);
+}
+
+static void __exit smbchg_exit(void)
+{
+ return platform_driver_unregister(&smbchg_driver);
+}
+
+module_init(smbchg_init);
+module_exit(smbchg_exit);
+
+MODULE_DESCRIPTION("QPNP SMB Charger");
+MODULE_LICENSE("GPL v2");
+MODULE_ALIAS("platform:qpnp-smbcharger");
diff --git a/drivers/power/supply/qcom/smb-lib.c b/drivers/power/supply/qcom/smb-lib.c
index df7aabfd7e2e..93512f155c52 100644
--- a/drivers/power/supply/qcom/smb-lib.c
+++ b/drivers/power/supply/qcom/smb-lib.c
@@ -3734,8 +3734,165 @@ irqreturn_t smblib_handle_usb_source_change(int irq, void *data)
return IRQ_HANDLED;
}
+static int typec_try_sink(struct smb_charger *chg)
+{
+ union power_supply_propval val;
+ bool debounce_done, vbus_detected, sink;
+ u8 stat;
+ int exit_mode = ATTACHED_SRC, rc;
+
+ /* ignore typec interrupt while try.snk WIP */
+ chg->try_sink_active = true;
+
+ /* force SNK mode */
+ val.intval = POWER_SUPPLY_TYPEC_PR_SINK;
+ rc = smblib_set_prop_typec_power_role(chg, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set UFP mode rc=%d\n", rc);
+ goto try_sink_exit;
+ }
+
+ /* reduce Tccdebounce time to ~20ms */
+ rc = smblib_masked_write(chg, MISC_CFG_REG,
+ TCC_DEBOUNCE_20MS_BIT, TCC_DEBOUNCE_20MS_BIT);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set MISC_CFG_REG rc=%d\n", rc);
+ goto try_sink_exit;
+ }
+
+ /*
+ * give opportunity to the other side to be a SRC,
+ * for tDRPTRY + Tccdebounce time
+ */
+ msleep(120);
+
+ 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);
+ goto try_sink_exit;
+ }
+
+ debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT;
+
+ if (!debounce_done)
+ /*
+ * The other side didn't switch to source, either it
+ * is an adamant sink or is removed go back to showing Rp
+ */
+ goto try_wait_src;
+
+ /*
+ * We are in force sink mode and the other side has switched to
+ * showing Rp. Config DRP in case the other side removes Rp so we
+ * can quickly (20ms) switch to showing our Rp. Note that the spec
+ * needs us to show Rp for 80mS while the drp DFP residency is just
+ * 54mS. But 54mS is plenty time for us to react and force Rp for
+ * the remaining 26mS.
+ */
+ val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+ rc = smblib_set_prop_typec_power_role(chg, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set DFP mode rc=%d\n",
+ rc);
+ goto try_sink_exit;
+ }
+
+ /*
+ * while other side is Rp, wait for VBUS from it; exit if other side
+ * removes Rp
+ */
+ do {
+ 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);
+ goto try_sink_exit;
+ }
+
+ debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT;
+ vbus_detected = stat & TYPEC_VBUS_STATUS_BIT;
+
+ /* Successfully transitioned to ATTACHED.SNK */
+ if (vbus_detected && debounce_done) {
+ exit_mode = ATTACHED_SINK;
+ goto try_sink_exit;
+ }
+
+ /*
+ * Ensure sink since drp may put us in source if other
+ * side switches back to Rd
+ */
+ sink = !(stat & UFP_DFP_MODE_STATUS_BIT);
+
+ usleep_range(1000, 2000);
+ } while (debounce_done && sink);
+
+try_wait_src:
+ /*
+ * Transition to trywait.SRC state. check if other side still wants
+ * to be SNK or has been removed.
+ */
+ val.intval = POWER_SUPPLY_TYPEC_PR_SOURCE;
+ rc = smblib_set_prop_typec_power_role(chg, &val);
+ if (rc < 0) {
+ smblib_err(chg, "Couldn't set UFP mode rc=%d\n", rc);
+ goto try_sink_exit;
+ }
+
+ /* Need to be in this state for tDRPTRY time, 75ms~150ms */
+ msleep(80);
+
+ 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);
+ goto try_sink_exit;
+ }
+
+ debounce_done = stat & TYPEC_DEBOUNCE_DONE_STATUS_BIT;
+
+ if (debounce_done)
+ /* the other side wants to be a sink */
+ exit_mode = ATTACHED_SRC;
+ else
+ /* the other side is detached */
+ exit_mode = UNATTACHED_SINK;
+
+try_sink_exit:
+ /* release forcing of SRC/SNK mode */
+ val.intval = POWER_SUPPLY_TYPEC_PR_DUAL;
+ rc = smblib_set_prop_typec_power_role(chg, &val);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set DFP mode rc=%d\n", rc);
+
+ /* revert Tccdebounce time back to ~120ms */
+ rc = smblib_masked_write(chg, MISC_CFG_REG, TCC_DEBOUNCE_20MS_BIT, 0);
+ if (rc < 0)
+ smblib_err(chg, "Couldn't set MISC_CFG_REG rc=%d\n", rc);
+
+ chg->try_sink_active = false;
+
+ return exit_mode;
+}
+
static void typec_sink_insertion(struct smb_charger *chg)
{
+ int exit_mode;
+
+ /*
+ * Try.SNK entry status - ATTACHWAIT.SRC state and detected Rd-open
+ * or RD-Ra for TccDebounce time.
+ */
+
+ if (*chg->try_sink_enabled) {
+ exit_mode = typec_try_sink(chg);
+
+ if (exit_mode != ATTACHED_SRC) {
+ smblib_usb_typec_change(chg);
+ return;
+ }
+ }
+
/* when a sink is inserted we should not wait on hvdcp timeout to
* enable pd
*/
@@ -3993,7 +4150,7 @@ static void smblib_handle_typec_cc_state_change(struct smb_charger *chg)
smblib_typec_mode_name[chg->typec_mode]);
}
-static void smblib_usb_typec_change(struct smb_charger *chg)
+void smblib_usb_typec_change(struct smb_charger *chg)
{
int rc;
@@ -4029,7 +4186,8 @@ irqreturn_t smblib_handle_usb_typec_change(int irq, void *data)
return IRQ_HANDLED;
}
- if (chg->cc2_detach_wa_active || chg->typec_en_dis_active) {
+ if (chg->cc2_detach_wa_active || chg->typec_en_dis_active ||
+ chg->try_sink_active) {
smblib_dbg(chg, PR_INTERRUPT, "Ignoring since %s active\n",
chg->cc2_detach_wa_active ?
"cc2_detach_wa" : "typec_en_dis");
diff --git a/drivers/power/supply/qcom/smb-lib.h b/drivers/power/supply/qcom/smb-lib.h
index 19c0d19106d6..f292ca09f532 100644
--- a/drivers/power/supply/qcom/smb-lib.h
+++ b/drivers/power/supply/qcom/smb-lib.h
@@ -128,6 +128,12 @@ enum smb_irq_index {
SMB_IRQ_MAX,
};
+enum try_sink_exit_mode {
+ ATTACHED_SRC = 0,
+ ATTACHED_SINK,
+ UNATTACHED_SINK,
+};
+
struct smb_irq_info {
const char *name;
const irq_handler_t handler;
@@ -232,6 +238,7 @@ struct smb_charger {
struct smb_params param;
struct smb_iio iio;
int *debug_mask;
+ int *try_sink_enabled;
enum smb_mode mode;
struct smb_chg_freq chg_freq;
int smb_version;
@@ -341,6 +348,7 @@ struct smb_charger {
u32 wa_flags;
bool cc2_detach_wa_active;
bool typec_en_dis_active;
+ bool try_sink_active;
int boost_current_ua;
int temp_speed_reading_count;
@@ -518,6 +526,7 @@ int smblib_get_prop_pr_swap_in_progress(struct smb_charger *chg,
union power_supply_propval *val);
int smblib_set_prop_pr_swap_in_progress(struct smb_charger *chg,
const union power_supply_propval *val);
+void smblib_usb_typec_change(struct smb_charger *chg);
int smblib_init(struct smb_charger *chg);
int smblib_deinit(struct smb_charger *chg);
diff --git a/drivers/power/supply/qcom/smb-reg.h b/drivers/power/supply/qcom/smb-reg.h
index d8671ab1fd06..4ddb085e9300 100644
--- a/drivers/power/supply/qcom/smb-reg.h
+++ b/drivers/power/supply/qcom/smb-reg.h
@@ -624,6 +624,7 @@ enum {
#define TAPER_TIMER_SEL_CFG_REG (USBIN_BASE + 0x64)
#define TYPEC_SPARE_CFG_BIT BIT(7)
+#define TYPEC_DRP_DFP_TIME_CFG_BIT BIT(5)
#define TAPER_TIMER_SEL_MASK GENMASK(1, 0)
#define USBIN_LOAD_CFG_REG (USBIN_BASE + 0x65)
diff --git a/fs/block_dev.c b/fs/block_dev.c
index 26bbaaefdff4..d3c296d4eb25 100644
--- a/fs/block_dev.c
+++ b/fs/block_dev.c
@@ -532,6 +532,7 @@ static void init_once(void *foo)
#ifdef CONFIG_SYSFS
INIT_LIST_HEAD(&bdev->bd_holder_disks);
#endif
+ bdev->bd_bdi = &noop_backing_dev_info;
inode_init_once(&ei->vfs_inode);
/* Initialize mutex for freeze. */
mutex_init(&bdev->bd_fsfreeze_mutex);
@@ -557,6 +558,10 @@ static void bdev_evict_inode(struct inode *inode)
}
list_del_init(&bdev->bd_list);
spin_unlock(&bdev_lock);
+ if (bdev->bd_bdi != &noop_backing_dev_info) {
+ bdi_put(bdev->bd_bdi);
+ bdev->bd_bdi = &noop_backing_dev_info;
+ }
}
static const struct super_operations bdev_sops = {
@@ -623,6 +628,21 @@ static int bdev_set(struct inode *inode, void *data)
static LIST_HEAD(all_bdevs);
+/*
+ * If there is a bdev inode for this device, unhash it so that it gets evicted
+ * as soon as last inode reference is dropped.
+ */
+void bdev_unhash_inode(dev_t dev)
+{
+ struct inode *inode;
+
+ inode = ilookup5(blockdev_superblock, hash(dev), bdev_test, &dev);
+ if (inode) {
+ remove_inode_hash(inode);
+ iput(inode);
+ }
+}
+
struct block_device *bdget(dev_t dev)
{
struct block_device *bdev;
@@ -1201,6 +1221,9 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
bdev->bd_disk = disk;
bdev->bd_queue = disk->queue;
bdev->bd_contains = bdev;
+ if (bdev->bd_bdi == &noop_backing_dev_info)
+ bdev->bd_bdi = bdi_get(disk->queue->backing_dev_info);
+
bdev->bd_inode->i_flags = disk->fops->direct_access ? S_DAX : 0;
if (!partno) {
ret = -ENXIO;
@@ -1302,6 +1325,8 @@ static int __blkdev_get(struct block_device *bdev, fmode_t mode, int for_part)
bdev->bd_disk = NULL;
bdev->bd_part = NULL;
bdev->bd_queue = NULL;
+ bdi_put(bdev->bd_bdi);
+ bdev->bd_bdi = &noop_backing_dev_info;
if (bdev != bdev->bd_contains)
__blkdev_put(bdev->bd_contains, mode, 1);
bdev->bd_contains = NULL;
diff --git a/fs/gfs2/ops_fstype.c b/fs/gfs2/ops_fstype.c
index baab99b69d8a..de3e91817228 100644
--- a/fs/gfs2/ops_fstype.c
+++ b/fs/gfs2/ops_fstype.c
@@ -1222,7 +1222,7 @@ static int set_gfs2_super(struct super_block *s, void *data)
* We set the bdi here to the queue backing, file systems can
* overwrite this in ->fill_super()
*/
- s->s_bdi = &bdev_get_queue(s->s_bdev)->backing_dev_info;
+ s->s_bdi = bdev_get_queue(s->s_bdev)->backing_dev_info;
return 0;
}
diff --git a/fs/nilfs2/super.c b/fs/nilfs2/super.c
index 354013ea22ec..67c6c650b21e 100644
--- a/fs/nilfs2/super.c
+++ b/fs/nilfs2/super.c
@@ -1079,7 +1079,7 @@ nilfs_fill_super(struct super_block *sb, void *data, int silent)
sb->s_time_gran = 1;
sb->s_max_links = NILFS_LINK_MAX;
- sb->s_bdi = &bdev_get_queue(sb->s_bdev)->backing_dev_info;
+ sb->s_bdi = bdev_get_queue(sb->s_bdev)->backing_dev_info;
err = load_nilfs(nilfs, sb);
if (err)
diff --git a/fs/super.c b/fs/super.c
index c96434ea71e2..cbd4fab271d4 100644
--- a/fs/super.c
+++ b/fs/super.c
@@ -968,7 +968,7 @@ static int set_bdev_super(struct super_block *s, void *data)
* We set the bdi here to the queue backing, file systems can
* overwrite this in ->fill_super()
*/
- s->s_bdi = &bdev_get_queue(s->s_bdev)->backing_dev_info;
+ s->s_bdi = bdev_get_queue(s->s_bdev)->backing_dev_info;
return 0;
}
diff --git a/include/linux/backing-dev-defs.h b/include/linux/backing-dev-defs.h
index 140c29635069..c104d4aed62a 100644
--- a/include/linux/backing-dev-defs.h
+++ b/include/linux/backing-dev-defs.h
@@ -10,6 +10,7 @@
#include <linux/flex_proportions.h>
#include <linux/timer.h>
#include <linux/workqueue.h>
+#include <linux/kref.h>
struct page;
struct device;
@@ -141,6 +142,7 @@ struct backing_dev_info {
void *congested_data; /* Pointer to aux data for congested func */
char *name;
+ struct kref refcnt; /* Reference counter for the structure */
unsigned int min_ratio;
unsigned int max_ratio, max_prop_frac;
diff --git a/include/linux/backing-dev.h b/include/linux/backing-dev.h
index 89d3de3e096b..125bc67319b4 100644
--- a/include/linux/backing-dev.h
+++ b/include/linux/backing-dev.h
@@ -18,7 +18,14 @@
#include <linux/slab.h>
int __must_check bdi_init(struct backing_dev_info *bdi);
-void bdi_exit(struct backing_dev_info *bdi);
+
+static inline struct backing_dev_info *bdi_get(struct backing_dev_info *bdi)
+{
+ kref_get(&bdi->refcnt);
+ return bdi;
+}
+
+void bdi_put(struct backing_dev_info *bdi);
__printf(3, 4)
int bdi_register(struct backing_dev_info *bdi, struct device *parent,
@@ -29,6 +36,7 @@ void bdi_unregister(struct backing_dev_info *bdi);
int __must_check bdi_setup_and_register(struct backing_dev_info *, char *);
void bdi_destroy(struct backing_dev_info *bdi);
+struct backing_dev_info *bdi_alloc_node(gfp_t gfp_mask, int node_id);
void wb_start_writeback(struct bdi_writeback *wb, long nr_pages,
bool range_cyclic, enum wb_reason reason);
diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h
index ae64a897622c..8150e164385c 100644
--- a/include/linux/blkdev.h
+++ b/include/linux/blkdev.h
@@ -332,7 +332,7 @@ struct request_queue {
*/
struct delayed_work delay_work;
- struct backing_dev_info backing_dev_info;
+ struct backing_dev_info *backing_dev_info;
/*
* The queue owner gets to use this for whatever they like.
diff --git a/include/linux/fs.h b/include/linux/fs.h
index c6de40668e08..d5264dcaaa26 100644
--- a/include/linux/fs.h
+++ b/include/linux/fs.h
@@ -481,6 +481,7 @@ struct block_device {
int bd_invalidated;
struct gendisk * bd_disk;
struct request_queue * bd_queue;
+ struct backing_dev_info *bd_bdi;
struct list_head bd_list;
/*
* Private data. You must have bd_claim'ed the block_device
@@ -2331,6 +2332,7 @@ extern struct kmem_cache *names_cachep;
#ifdef CONFIG_BLOCK
extern int register_blkdev(unsigned int, const char *);
extern void unregister_blkdev(unsigned int, const char *);
+extern void bdev_unhash_inode(dev_t dev);
extern struct block_device *bdget(dev_t);
extern struct block_device *bdgrab(struct block_device *bdev);
extern void bd_set_size(struct block_device *, loff_t size);
diff --git a/mm/backing-dev.c b/mm/backing-dev.c
index a988d4ef39da..4c480a20d76c 100644
--- a/mm/backing-dev.c
+++ b/mm/backing-dev.c
@@ -237,6 +237,7 @@ static __init int bdi_class_init(void)
bdi_class->dev_groups = bdi_dev_groups;
bdi_debug_init();
+
return 0;
}
postcore_initcall(bdi_class_init);
@@ -780,6 +781,7 @@ int bdi_init(struct backing_dev_info *bdi)
bdi->dev = NULL;
+ kref_init(&bdi->refcnt);
bdi->min_ratio = 0;
bdi->max_ratio = 100;
bdi->max_prop_frac = FPROP_FRAC_BASE;
@@ -795,6 +797,22 @@ int bdi_init(struct backing_dev_info *bdi)
}
EXPORT_SYMBOL(bdi_init);
+struct backing_dev_info *bdi_alloc_node(gfp_t gfp_mask, int node_id)
+{
+ struct backing_dev_info *bdi;
+
+ bdi = kmalloc_node(sizeof(struct backing_dev_info),
+ gfp_mask | __GFP_ZERO, node_id);
+ if (!bdi)
+ return NULL;
+
+ if (bdi_init(bdi)) {
+ kfree(bdi);
+ return NULL;
+ }
+ return bdi;
+}
+
int bdi_register(struct backing_dev_info *bdi, struct device *parent,
const char *fmt, ...)
{
@@ -875,12 +893,26 @@ void bdi_unregister(struct backing_dev_info *bdi)
}
}
-void bdi_exit(struct backing_dev_info *bdi)
+static void bdi_exit(struct backing_dev_info *bdi)
{
WARN_ON_ONCE(bdi->dev);
wb_exit(&bdi->wb);
}
+static void release_bdi(struct kref *ref)
+{
+ struct backing_dev_info *bdi =
+ container_of(ref, struct backing_dev_info, refcnt);
+
+ bdi_exit(bdi);
+ kfree(bdi);
+}
+
+void bdi_put(struct backing_dev_info *bdi)
+{
+ kref_put(&bdi->refcnt, release_bdi);
+}
+
void bdi_destroy(struct backing_dev_info *bdi)
{
bdi_unregister(bdi);
diff --git a/mm/page-writeback.c b/mm/page-writeback.c
index 15cb026ef807..29597fe6bb35 100644
--- a/mm/page-writeback.c
+++ b/mm/page-writeback.c
@@ -1993,11 +1993,11 @@ void laptop_mode_timer_fn(unsigned long data)
* We want to write everything out, not just down to the dirty
* threshold
*/
- if (!bdi_has_dirty_io(&q->backing_dev_info))
+ if (!bdi_has_dirty_io(q->backing_dev_info))
return;
rcu_read_lock();
- list_for_each_entry_rcu(wb, &q->backing_dev_info.wb_list, bdi_node)
+ list_for_each_entry_rcu(wb, &q->backing_dev_info->wb_list, bdi_node)
if (wb_has_dirty_io(wb))
wb_start_writeback(wb, nr_pages, true,
WB_REASON_LAPTOP_TIMER);
diff --git a/sound/soc/msm/Kconfig b/sound/soc/msm/Kconfig
index 629a9c3d91db..18a5d924e282 100644
--- a/sound/soc/msm/Kconfig
+++ b/sound/soc/msm/Kconfig
@@ -22,6 +22,17 @@ config SND_SOC_QDSP6V2
audio drivers. This includes q6asm, q6adm,
q6afe interfaces to DSP using apr.
+config SND_SOC_QDSP6V2_VM
+ tristate "SoC ALSA audio driver for QDSP6V2 virtualization"
+ depends on MSM_QDSP6_APRV2_VM
+ select SND_SOC_COMPRESS
+ help
+ To add support for MSM QDSP6V2 virtualization
+ Soc Audio.
+ This will enable sound soc platform specific
+ audio drivers. This includes q6asm, q6adm,
+ q6afe interfaces to DSP using virtualized apr.
+
config SND_SOC_QDSP_DEBUG
bool "QDSP Audio Driver Debug Feature"
help
@@ -70,7 +81,7 @@ config DTS_SRS_TM
config QTI_PP
bool "Enable QTI PP"
- depends on SND_SOC_MSM_QDSP6V2_INTF
+ depends on SND_SOC_MSM_QDSP6V2_INTF || SND_SOC_QDSP6V2_VM
help
To add support for default QTI post processing.
This support is to configure the post processing
@@ -192,6 +203,22 @@ config SND_SOC_MSM8996
the machine driver and the corresponding
DAI-links
+config SND_SOC_MSM8996_VM
+ tristate "SoC Machine driver for MSM8996 virtualization"
+ select SND_SOC_QDSP6V2_VM
+ select SND_SOC_MSM_STUB
+ select SND_SOC_MSM_HOSTLESS_PCM
+ select SND_DYNAMIC_MINORS
+ select MSM_QDSP6_APRV2_VM
+ select QTI_PP
+ help
+ To add support for SoC audio on MSM8996
+ virtualization platform.
+ This will enable sound soc drivers which
+ interfaces with DSP using virtualized apr,
+ also it will enable the machine driver and
+ the corresponding DAI-links
+
config SND_SOC_MSM8998
tristate "SoC Machine driver for MSM8998 boards"
depends on ARCH_QCOM
diff --git a/sound/soc/msm/Makefile b/sound/soc/msm/Makefile
index 63c4e61430c4..4d2a360ad1aa 100644
--- a/sound/soc/msm/Makefile
+++ b/sound/soc/msm/Makefile
@@ -4,9 +4,11 @@ snd-soc-hostless-pcm-objs := msm-pcm-hostless.o
obj-$(CONFIG_SND_SOC_MSM_HOSTLESS_PCM) += snd-soc-hostless-pcm.o
obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += qdsp6v2/
+obj-$(CONFIG_SND_SOC_QDSP6V2_VM) += qdsp6v2/
snd-soc-qdsp6v2-objs := msm-dai-fe.o
obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o
+obj-$(CONFIG_SND_SOC_QDSP6V2_VM) += snd-soc-qdsp6v2.o
#for CPE drivers
snd-soc-cpe-objs := msm-cpe-lsm.o
@@ -16,6 +18,10 @@ obj-$(CONFIG_SND_SOC_CPE) += snd-soc-cpe.o
snd-soc-msm8996-objs := msm8996.o apq8096-auto.o
obj-$(CONFIG_SND_SOC_MSM8996) += snd-soc-msm8996.o
+# for MSM8996 virtualization sound card driver
+snd-soc-msm8996-vm-objs := apq8096-auto.o
+obj-$(CONFIG_SND_SOC_MSM8996_VM) += snd-soc-msm8996-vm.o
+
# for MSM8998 sound card driver
snd-soc-msm8998-objs := msm8998.o
obj-$(CONFIG_SND_SOC_MSM8998) += snd-soc-msm8998.o
diff --git a/sound/soc/msm/qdsp6v2/Makefile b/sound/soc/msm/qdsp6v2/Makefile
index 98c52a4db51f..4116f79890a3 100644
--- a/sound/soc/msm/qdsp6v2/Makefile
+++ b/sound/soc/msm/qdsp6v2/Makefile
@@ -4,10 +4,13 @@ snd-soc-qdsp6v2-objs += msm-dai-q6-v2.o msm-pcm-q6-v2.o msm-pcm-routing-v2.o \
msm-pcm-voice-v2.o msm-dai-q6-hdmi-v2.o \
msm-lsm-client.o msm-pcm-host-voice-v2.o \
msm-audio-effects-q6-v2.o msm-pcm-loopback-v2.o \
- msm-dai-slim.o msm-transcode-loopback-q6-v2.o \
+ msm-transcode-loopback-q6-v2.o \
adsp_err.o
+obj-$(CONFIG_SLIMBUS) += msm-dai-slim.o audio_slimslave.o
obj-$(CONFIG_SND_SOC_QDSP6V2) += snd-soc-qdsp6v2.o msm-pcm-dtmf-v2.o \
msm-dai-stub-v2.o
+obj-$(CONFIG_SND_SOC_QDSP6V2_VM) += snd-soc-qdsp6v2.o msm-pcm-dtmf-v2.o \
+ msm-dai-stub-v2.o
obj-$(CONFIG_SND_HWDEP) += msm-pcm-routing-devdep.o
obj-$(CONFIG_DOLBY_DAP) += msm-dolby-dap-config.o
obj-$(CONFIG_DOLBY_DS2) += msm-ds2-dap-config.o
@@ -15,7 +18,7 @@ obj-$(CONFIG_DOLBY_LICENSE) += msm-ds2-dap-config.o
obj-$(CONFIG_DTS_SRS_TM) += msm-dts-srs-tm-config.o
obj-$(CONFIG_QTI_PP) += msm-qti-pp-config.o
obj-y += audio_calibration.o audio_cal_utils.o q6adm.o q6afe.o q6asm.o \
- q6audio-v2.o q6voice.o q6core.o rtac.o q6lsm.o audio_slimslave.o \
+ q6audio-v2.o q6voice.o q6core.o rtac.o q6lsm.o \
msm-pcm-q6-noirq.o
ocmem-audio-objs += audio_ocmem.o
obj-$(CONFIG_AUDIO_OCMEM) += ocmem-audio.o
diff --git a/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c b/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c
index 276270258771..ce9091b1ca16 100644
--- a/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c
+++ b/sound/soc/msm/qdsp6v2/msm-pcm-q6-noirq.c
@@ -424,7 +424,7 @@ static int msm_pcm_trigger(struct snd_pcm_substream *substream, int cmd)
return ret;
}
-
+#ifdef CONFIG_SND_HWDEP
static int msm_pcm_mmap_fd(struct snd_pcm_substream *substream,
struct snd_pcm_mmap_fd *mmap_fd)
{
@@ -459,6 +459,7 @@ static int msm_pcm_mmap_fd(struct snd_pcm_substream *substream,
}
return mmap_fd->fd < 0 ? -EFAULT : 0;
}
+#endif
static int msm_pcm_ioctl(struct snd_pcm_substream *substream,
unsigned int cmd, void *arg)
@@ -1042,6 +1043,7 @@ static int msm_pcm_add_app_type_controls(struct snd_soc_pcm_runtime *rtd)
return 0;
}
+#ifdef CONFIG_SND_HWDEP
static int msm_pcm_hwdep_ioctl(struct snd_hwdep *hw, struct file *file,
unsigned int cmd, unsigned long arg)
{
@@ -1137,6 +1139,7 @@ static int msm_pcm_add_hwdep_dev(struct snd_soc_pcm_runtime *runtime)
hwdep->ops.ioctl_compat = msm_pcm_hwdep_compat_ioctl;
return 0;
}
+#endif
static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd)
{
@@ -1170,9 +1173,11 @@ static int msm_asoc_pcm_new(struct snd_soc_pcm_runtime *rtd)
pr_err("%s: Could not add app type controls failed %d\n",
__func__, ret);
}
+#ifdef CONFIG_SND_HWDEP
ret = msm_pcm_add_hwdep_dev(rtd);
if (ret)
pr_err("%s: Could not add hw dep node\n", __func__);
+#endif
pcm->nonatomic = true;
exit:
return ret;
diff --git a/sound/usb/mixer.c b/sound/usb/mixer.c
index ad139d45f5b2..b62cc921695a 100644
--- a/sound/usb/mixer.c
+++ b/sound/usb/mixer.c
@@ -215,7 +215,6 @@ static int snd_usb_copy_string_desc(struct mixer_build *state,
int index, char *buf, int maxlen)
{
int len = usb_string(state->chip->dev, index, buf, maxlen - 1);
- buf[len] = 0;
return len;
}