diff options
29 files changed, 21601 insertions, 11 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/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt index db21a2b58c2b..7820562d17ae 100644 --- a/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt +++ b/Documentation/devicetree/bindings/sound/qcom-audio-dev.txt @@ -1096,6 +1096,26 @@ qcom,msm-audio-ion { qcom,smmu-enabled; }; +* msm-audio-ion-vm + +Required properties: + - compatible : "qcom,msm-audio-ion-vm" + +Optional properties: + - qcom,smmu-enabled: + It is possible that some MSM have SMMU in ADSP. While other + MSM use no SMMU. Audio lib introduce wrapper for ION APIs. + The wrapper needs presence of SMMU in ADSP to handle ION + APIs differently. Presence of this property means ADSP has + SMMU in it. + +Example: + +qcom,msm-audio-ion-vm { + compatible = "qcom,msm-audio-ion-vm; + qcom,smmu-enabled; +}; + * MSM8994 ASoC Machine driver Required properties: 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 970611f218c4..4a8cef21e90e 100644 --- a/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts +++ b/arch/arm/boot/dts/qcom/vplatform-lfv-msm8996.dts @@ -188,6 +188,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/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/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(¤t_time); + /* + * When charging status changes, update SRAM parameters if it + * was not updated before 5 seconds from now. + */ + if (chip->last_sram_update_time + 5 < current_time) { + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work(&chip->update_sram_data, + msecs_to_jiffies(0)); + } + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + if ((chip->wa_flag & USE_CC_SOC_REG) && + chip->bad_batt_detection_en && + chip->status == POWER_SUPPLY_STATUS_CHARGING) { + chip->sw_cc_soc_data.init_sys_soc = capacity; + chip->sw_cc_soc_data.init_cc_soc = cc_soc; + if (fg_debug_mask & FG_STATUS) + pr_info(" Init_sys_soc %d init_cc_soc %d\n", + chip->sw_cc_soc_data.init_sys_soc, + chip->sw_cc_soc_data.init_cc_soc); + } + } + if ((chip->wa_flag & USE_CC_SOC_REG) && chip->bad_batt_detection_en + && chip->safety_timer_expired) { + chip->sw_cc_soc_data.delta_soc = + DIV_ROUND_CLOSEST(abs(cc_soc - + chip->sw_cc_soc_data.init_cc_soc) + * 100, FULL_PERCENT_28BIT); + chip->sw_cc_soc_data.full_capacity = + chip->sw_cc_soc_data.delta_soc + + chip->sw_cc_soc_data.init_sys_soc; + pr_info("Init_sys_soc %d init_cc_soc %d cc_soc %d delta_soc %d full_capacity %d\n", + chip->sw_cc_soc_data.init_sys_soc, + chip->sw_cc_soc_data.init_cc_soc, cc_soc, + chip->sw_cc_soc_data.delta_soc, + chip->sw_cc_soc_data.full_capacity); + /* + * If sw_cc_soc capacity greater than 150, then it's a bad + * battery. else, reset timer and restart charging. + */ + if (chip->sw_cc_soc_data.full_capacity > + MAX_BATTERY_CC_SOC_CAPACITY) { + pr_info("Battery possibly damaged, do not restart charging\n"); + } else { + pr_info("Reset safety-timer and restart charging\n"); + rc = set_prop_enable_charging(chip, false); + if (rc) { + pr_err("failed to disable charging %d\n", rc); + return; + } + + chip->safety_timer_expired = false; + msleep(200); + + rc = set_prop_enable_charging(chip, true); + if (rc) { + pr_err("failed to enable charging %d\n", rc); + return; + } + } + } +} + +/* + * Check for change in the status of input or OTG and schedule + * IADC gain compensation work. + */ +static void check_gain_compensation(struct fg_chip *chip) +{ + bool input_present = is_input_present(chip); + bool otg_present = is_otg_present(chip); + + if ((chip->wa_flag & IADC_GAIN_COMP_WA) + && ((chip->input_present ^ input_present) + || (chip->otg_present ^ otg_present))) { + fg_stay_awake(&chip->gain_comp_wakeup_source); + chip->input_present = input_present; + chip->otg_present = otg_present; + cancel_work_sync(&chip->gain_comp_work); + schedule_work(&chip->gain_comp_work); + } +} + +static void fg_hysteresis_config(struct fg_chip *chip) +{ + int hard_hot = 0, hard_cold = 0; + + hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT); + hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD); + if (chip->health == POWER_SUPPLY_HEALTH_OVERHEAT && !chip->batt_hot) { + /* turn down the hard hot threshold */ + chip->batt_hot = true; + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot - chip->hot_hysteresis); + if (fg_debug_mask & FG_STATUS) + pr_info("hard hot hysteresis: old hot=%d, new hot=%d\n", + hard_hot, hard_hot - chip->hot_hysteresis); + } else if (chip->health == POWER_SUPPLY_HEALTH_COLD && + !chip->batt_cold) { + /* turn up the hard cold threshold */ + chip->batt_cold = true; + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold + chip->cold_hysteresis); + if (fg_debug_mask & FG_STATUS) + pr_info("hard cold hysteresis: old cold=%d, new cold=%d\n", + hard_cold, hard_cold + chip->hot_hysteresis); + } else if (chip->health != POWER_SUPPLY_HEALTH_OVERHEAT && + chip->batt_hot) { + /* restore the hard hot threshold */ + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot + chip->hot_hysteresis); + chip->batt_hot = !chip->batt_hot; + if (fg_debug_mask & FG_STATUS) + pr_info("restore hard hot threshold: old hot=%d, new hot=%d\n", + hard_hot, + hard_hot + chip->hot_hysteresis); + } else if (chip->health != POWER_SUPPLY_HEALTH_COLD && + chip->batt_cold) { + /* restore the hard cold threshold */ + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold - chip->cold_hysteresis); + chip->batt_cold = !chip->batt_cold; + if (fg_debug_mask & FG_STATUS) + pr_info("restore hard cold threshold: old cold=%d, new cold=%d\n", + hard_cold, + hard_cold - chip->cold_hysteresis); + } +} + +#define BATT_INFO_STS(base) (base + 0x09) +#define JEITA_HARD_HOT_RT_STS BIT(6) +#define JEITA_HARD_COLD_RT_STS BIT(5) +static int fg_init_batt_temp_state(struct fg_chip *chip) +{ + int rc = 0; + u8 batt_info_sts; + int hard_hot = 0, hard_cold = 0; + + /* + * read the batt_info_sts register to parse battery's + * initial status and do hysteresis config accordingly. + */ + rc = fg_read(chip, &batt_info_sts, + BATT_INFO_STS(chip->batt_base), 1); + if (rc) { + pr_err("failed to read batt info sts, rc=%d\n", rc); + return rc; + } + + hard_hot = get_prop_jeita_temp(chip, FG_MEM_HARD_HOT); + hard_cold = get_prop_jeita_temp(chip, FG_MEM_HARD_COLD); + chip->batt_hot = + (batt_info_sts & JEITA_HARD_HOT_RT_STS) ? true : false; + chip->batt_cold = + (batt_info_sts & JEITA_HARD_COLD_RT_STS) ? true : false; + if (chip->batt_hot || chip->batt_cold) { + if (chip->batt_hot) { + chip->health = POWER_SUPPLY_HEALTH_OVERHEAT; + set_prop_jeita_temp(chip, FG_MEM_HARD_HOT, + hard_hot - chip->hot_hysteresis); + } else { + chip->health = POWER_SUPPLY_HEALTH_COLD; + set_prop_jeita_temp(chip, FG_MEM_HARD_COLD, + hard_cold + chip->cold_hysteresis); + } + } + + return rc; +} + +static int fg_power_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + int rc = 0, unused; + + switch (psp) { + case POWER_SUPPLY_PROP_COOL_TEMP: + rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_COLD, val->intval); + break; + case POWER_SUPPLY_PROP_WARM_TEMP: + rc = set_prop_jeita_temp(chip, FG_MEM_SOFT_HOT, val->intval); + break; + case POWER_SUPPLY_PROP_UPDATE_NOW: + if (val->intval) + update_sram_data(chip, &unused); + break; + case POWER_SUPPLY_PROP_STATUS: + chip->prev_status = chip->status; + chip->status = val->intval; + schedule_work(&chip->status_change_work); + check_gain_compensation(chip); + break; + case POWER_SUPPLY_PROP_HEALTH: + chip->health = val->intval; + if (chip->health == POWER_SUPPLY_HEALTH_GOOD) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + + if (chip->jeita_hysteresis_support) + fg_hysteresis_config(chip); + break; + case POWER_SUPPLY_PROP_CHARGE_DONE: + chip->charge_done = val->intval; + if (!chip->resume_soc_lowered) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + break; + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + if ((val->intval > 0) && (val->intval <= BUCKET_COUNT)) { + chip->cyc_ctr.id = val->intval; + } else { + pr_err("rejecting invalid cycle_count_id = %d\n", + val->intval); + rc = -EINVAL; + } + break; + case POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED: + chip->safety_timer_expired = val->intval; + schedule_work(&chip->status_change_work); + break; + case POWER_SUPPLY_PROP_HI_POWER: + if (chip->wa_flag & BCL_HI_POWER_FOR_CHGLED_WA) { + chip->bcl_lpm_disabled = !!val->intval; + schedule_work(&chip->bcl_hi_power_work); + } + break; + default: + return -EINVAL; + }; + + return rc; +}; + +static int fg_property_is_writeable(struct power_supply *psy, + enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_COOL_TEMP: + case POWER_SUPPLY_PROP_WARM_TEMP: + case POWER_SUPPLY_PROP_CYCLE_COUNT_ID: + return 1; + default: + break; + } + + return 0; +} + +#define SRAM_DUMP_START 0x400 +#define SRAM_DUMP_LEN 0x200 +static void dump_sram(struct work_struct *work) +{ + int i, rc; + u8 *buffer, rt_sts; + char str[16]; + struct fg_chip *chip = container_of(work, + struct fg_chip, + dump_sram); + + buffer = devm_kzalloc(chip->dev, SRAM_DUMP_LEN, GFP_KERNEL); + if (buffer == NULL) { + pr_err("Can't allocate buffer\n"); + return; + } + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + else + pr_info("soc rt_sts: 0x%x\n", rt_sts); + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->batt_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + else + pr_info("batt rt_sts: 0x%x\n", rt_sts); + + rc = fg_read(chip, &rt_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->mem_base), rc); + else + pr_info("memif rt_sts: 0x%x\n", rt_sts); + + rc = fg_mem_read(chip, buffer, SRAM_DUMP_START, SRAM_DUMP_LEN, 0, 0); + if (rc) { + pr_err("dump failed: rc = %d\n", rc); + return; + } + + for (i = 0; i < SRAM_DUMP_LEN; i += 4) { + str[0] = '\0'; + fill_string(str, DEBUG_PRINT_BUFFER_SIZE, buffer + i, 4); + pr_info("%03X %s\n", SRAM_DUMP_START + i, str); + } + devm_kfree(chip->dev, buffer); +} + +#define MAXRSCHANGE_REG 0x434 +#define ESR_VALUE_OFFSET 1 +#define ESR_STRICT_VALUE 0x4120391F391F3019 +#define ESR_DEFAULT_VALUE 0x58CD4A6761C34A67 +static void update_esr_value(struct work_struct *work) +{ + union power_supply_propval prop = {0, }; + u64 esr_value; + int rc = 0; + struct fg_chip *chip = container_of(work, + struct fg_chip, + update_esr_work); + + if (!is_charger_available(chip)) + return; + + power_supply_get_property(chip->batt_psy, + POWER_SUPPLY_PROP_CHARGE_TYPE, &prop); + + if (!chip->esr_strict_filter) { + if ((prop.intval == POWER_SUPPLY_CHARGE_TYPE_TAPER && + chip->status == POWER_SUPPLY_STATUS_CHARGING) || + (chip->status == POWER_SUPPLY_STATUS_FULL)) { + esr_value = ESR_STRICT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, + MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write strict ESR value rc=%d\n", + rc); + else + chip->esr_strict_filter = true; + } + } else if ((prop.intval != POWER_SUPPLY_CHARGE_TYPE_TAPER && + chip->status == POWER_SUPPLY_STATUS_CHARGING) || + (chip->status == POWER_SUPPLY_STATUS_DISCHARGING)) { + esr_value = ESR_DEFAULT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write default ESR value rc=%d\n", rc); + else + chip->esr_strict_filter = false; + } +} + +#define TEMP_COUNTER_REG 0x580 +#define VBAT_FILTERED_OFFSET 1 +#define GAIN_REG 0x424 +#define GAIN_OFFSET 1 +#define K_VCOR_REG 0x484 +#define DEF_GAIN_OFFSET 2 +#define PICO_UNIT 0xE8D4A51000LL +#define ATTO_UNIT 0xDE0B6B3A7640000LL +#define VBAT_REF 3800000 + +/* + * IADC Gain compensation steps: + * If Input/OTG absent: + * - read VBAT_FILTERED, KVCOR, GAIN + * - calculate the gain compensation using following formula: + * gain = (1 + gain) * (1 + kvcor * (vbat_filtered - 3800000)) - 1; + * else + * - reset to the default gain compensation + */ +static void iadc_gain_comp_work(struct work_struct *work) +{ + u8 reg[4]; + int rc; + uint64_t vbat_filtered; + int64_t gain, kvcor, temp, numerator; + struct fg_chip *chip = container_of(work, struct fg_chip, + gain_comp_work); + bool input_present = is_input_present(chip); + bool otg_present = is_otg_present(chip); + + if (!chip->init_done) + goto done; + + if (!input_present && !otg_present) { + /* read VBAT_FILTERED */ + rc = fg_mem_read(chip, reg, TEMP_COUNTER_REG, 3, + VBAT_FILTERED_OFFSET, 0); + if (rc) { + pr_err("Failed to read VBAT: rc=%d\n", rc); + goto done; + } + temp = (reg[2] << 16) | (reg[1] << 8) | reg[0]; + vbat_filtered = div_u64((u64)temp * LSB_24B_NUMRTR, + LSB_24B_DENMTR); + + /* read K_VCOR */ + rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to KVCOR rc=%d\n", rc); + goto done; + } + kvcor = half_float(reg); + + /* calculate gain */ + numerator = (MICRO_UNIT + chip->iadc_comp_data.dfl_gain) + * (PICO_UNIT + kvcor * (vbat_filtered - VBAT_REF)) + - ATTO_UNIT; + gain = div64_s64(numerator, PICO_UNIT); + + /* write back gain */ + half_float_to_buffer(gain, reg); + rc = fg_mem_write(chip, reg, GAIN_REG, 2, GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write gain reg rc=%d\n", rc); + goto done; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("IADC gain update [%x %x]\n", reg[1], reg[0]); + chip->iadc_comp_data.gain_active = true; + } else { + /* reset gain register */ + rc = fg_mem_write(chip, chip->iadc_comp_data.dfl_gain_reg, + GAIN_REG, 2, GAIN_OFFSET, 0); + if (rc) { + pr_err("unable to write gain comp: %d\n", rc); + goto done; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("IADC gain reset [%x %x]\n", + chip->iadc_comp_data.dfl_gain_reg[1], + chip->iadc_comp_data.dfl_gain_reg[0]); + chip->iadc_comp_data.gain_active = false; + } + +done: + fg_relax(&chip->gain_comp_wakeup_source); +} + +#define BATT_MISSING_STS BIT(6) +static bool is_battery_missing(struct fg_chip *chip) +{ + int rc; + u8 fg_batt_sts; + + rc = fg_read(chip, &fg_batt_sts, + INT_RT_STS(chip->batt_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->batt_base), rc); + return false; + } + + return (fg_batt_sts & BATT_MISSING_STS) ? true : false; +} + +#define SOC_FIRST_EST_DONE BIT(5) +static bool is_first_est_done(struct fg_chip *chip) +{ + int rc; + u8 fg_soc_sts; + + rc = fg_read(chip, &fg_soc_sts, + INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + return false; + } + + return (fg_soc_sts & SOC_FIRST_EST_DONE) ? true : false; +} + +static irqreturn_t fg_vbatt_low_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + int rc; + bool vbatt_low_sts; + + if (fg_debug_mask & FG_IRQS) + pr_info("vbatt-low triggered\n"); + + if (chip->status == POWER_SUPPLY_STATUS_CHARGING) { + rc = fg_get_vbatt_status(chip, &vbatt_low_sts); + if (rc) { + pr_err("error in reading vbatt_status, rc:%d\n", rc); + goto out; + } + if (!vbatt_low_sts && chip->vbat_low_irq_enabled) { + if (fg_debug_mask & FG_IRQS) + pr_info("disabling vbatt_low irq\n"); + disable_irq_wake(chip->batt_irq[VBATT_LOW].irq); + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + } + } + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); +out: + return IRQ_HANDLED; +} + +static irqreturn_t fg_batt_missing_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + bool batt_missing = is_battery_missing(chip); + + if (batt_missing) { + chip->battery_missing = true; + chip->profile_loaded = false; + chip->batt_type = default_batt_type; + mutex_lock(&chip->cyc_ctr.lock); + if (fg_debug_mask & FG_IRQS) + pr_info("battery missing, clearing cycle counters\n"); + clear_cycle_counter(chip); + mutex_unlock(&chip->cyc_ctr.lock); + } else { + if (!chip->use_otp_profile) { + reinit_completion(&chip->batt_id_avail); + reinit_completion(&chip->first_soc_done); + schedule_delayed_work(&chip->batt_profile_init, 0); + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(0)); + } else { + chip->battery_missing = false; + } + } + + if (fg_debug_mask & FG_IRQS) + pr_info("batt-missing triggered: %s\n", + batt_missing ? "missing" : "present"); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + return IRQ_HANDLED; +} + +static irqreturn_t fg_mem_avail_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 mem_if_sts; + int rc; + + rc = fg_read(chip, &mem_if_sts, INT_RT_STS(chip->mem_base), 1); + if (rc) { + pr_err("failed to read mem status rc=%d\n", rc); + return IRQ_HANDLED; + } + + if (fg_check_sram_access(chip)) { + if ((fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("sram access granted\n"); + reinit_completion(&chip->sram_access_revoked); + complete_all(&chip->sram_access_granted); + } else { + if ((fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("sram access revoked\n"); + complete_all(&chip->sram_access_revoked); + } + + if (!rc && (fg_debug_mask & FG_IRQS) + & (FG_MEM_DEBUG_READS | FG_MEM_DEBUG_WRITES)) + pr_info("mem_if sts 0x%02x\n", mem_if_sts); + + return IRQ_HANDLED; +} + +static irqreturn_t fg_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 soc_rt_sts; + int rc; + + rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + } + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered 0x%x\n", soc_rt_sts); + + schedule_work(&chip->battery_age_work); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + if (chip->rslow_comp.chg_rs_to_rslow > 0 && + chip->rslow_comp.chg_rslow_comp_c1 > 0 && + chip->rslow_comp.chg_rslow_comp_c2 > 0) + schedule_work(&chip->rslow_comp_work); + if (chip->cyc_ctr.en) + schedule_work(&chip->cycle_count_work); + schedule_work(&chip->update_esr_work); + if (chip->charge_full) + schedule_work(&chip->charge_full_work); + if (chip->wa_flag & IADC_GAIN_COMP_WA + && chip->iadc_comp_data.gain_active) { + fg_stay_awake(&chip->gain_comp_wakeup_source); + schedule_work(&chip->gain_comp_work); + } + + if (chip->wa_flag & USE_CC_SOC_REG + && chip->learning_data.active) { + fg_stay_awake(&chip->capacity_learning_wakeup_source); + schedule_work(&chip->fg_cap_learning_work); + } + + if (chip->esr_pulse_tune_en) { + fg_stay_awake(&chip->esr_extract_wakeup_source); + schedule_work(&chip->esr_extract_config_work); + } + + return IRQ_HANDLED; +} + +#define FG_EMPTY_DEBOUNCE_MS 1500 +static irqreturn_t fg_empty_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + u8 soc_rt_sts; + int rc; + + rc = fg_read(chip, &soc_rt_sts, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + goto done; + } + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered 0x%x\n", soc_rt_sts); + if (fg_is_batt_empty(chip)) { + fg_stay_awake(&chip->empty_check_wakeup_source); + schedule_delayed_work(&chip->check_empty_work, + msecs_to_jiffies(FG_EMPTY_DEBOUNCE_MS)); + } else { + chip->soc_empty = false; + } + +done: + return IRQ_HANDLED; +} + +static irqreturn_t fg_first_soc_irq_handler(int irq, void *_chip) +{ + struct fg_chip *chip = _chip; + + if (fg_debug_mask & FG_IRQS) + pr_info("triggered\n"); + + if (fg_est_dump) + schedule_work(&chip->dump_sram); + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + complete_all(&chip->first_soc_done); + + return IRQ_HANDLED; +} + +static void fg_external_power_changed(struct power_supply *psy) +{ + struct fg_chip *chip = power_supply_get_drvdata(psy); + + if (is_input_present(chip) && chip->rslow_comp.active && + chip->rslow_comp.chg_rs_to_rslow > 0 && + chip->rslow_comp.chg_rslow_comp_c1 > 0 && + chip->rslow_comp.chg_rslow_comp_c2 > 0) + schedule_work(&chip->rslow_comp_work); + if (!is_input_present(chip) && chip->resume_soc_lowered) { + fg_stay_awake(&chip->resume_soc_wakeup_source); + schedule_work(&chip->set_resume_soc_work); + } + if (!is_input_present(chip) && chip->charge_full) + schedule_work(&chip->charge_full_work); +} + +static void set_resume_soc_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + set_resume_soc_work); + int rc, resume_soc_raw; + + if (is_input_present(chip) && !chip->resume_soc_lowered) { + if (!chip->charge_done) + goto done; + resume_soc_raw = get_monotonic_soc_raw(chip) + - (0xFF - settings[FG_MEM_RESUME_SOC].value); + if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + goto done; + } + if (fg_debug_mask & FG_STATUS) { + pr_info("resume soc lowered to 0x%02x\n", + resume_soc_raw); + } + } else if (settings[FG_MEM_RESUME_SOC].value > 0) { + pr_err("bad resume soc 0x%02x\n", resume_soc_raw); + } + chip->charge_done = false; + chip->resume_soc_lowered = true; + } else if (chip->resume_soc_lowered && (!is_input_present(chip) + || chip->health == POWER_SUPPLY_HEALTH_GOOD)) { + resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; + if (resume_soc_raw > 0 && resume_soc_raw < FULL_SOC_RAW) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + goto done; + } + if (fg_debug_mask & FG_STATUS) { + pr_info("resume soc set to 0x%02x\n", + resume_soc_raw); + } + } else if (settings[FG_MEM_RESUME_SOC].value > 0) { + pr_err("bad resume soc 0x%02x\n", resume_soc_raw); + } + chip->resume_soc_lowered = false; + } +done: + fg_relax(&chip->resume_soc_wakeup_source); +} + + +#define OCV_COEFFS_START_REG 0x4C0 +#define OCV_JUNCTION_REG 0x4D8 +#define NOM_CAP_REG 0x4F4 +#define CUTOFF_VOLTAGE_REG 0x40C +#define RSLOW_CFG_REG 0x538 +#define RSLOW_CFG_OFFSET 2 +#define RSLOW_THRESH_REG 0x52C +#define RSLOW_THRESH_OFFSET 0 +#define TEMP_RS_TO_RSLOW_OFFSET 2 +#define RSLOW_COMP_REG 0x528 +#define RSLOW_COMP_C1_OFFSET 0 +#define RSLOW_COMP_C2_OFFSET 2 +static int populate_system_data(struct fg_chip *chip) +{ + u8 buffer[24]; + int rc, i; + int16_t cc_mah; + + fg_mem_lock(chip); + rc = fg_mem_read(chip, buffer, OCV_COEFFS_START_REG, 24, 0, 0); + if (rc) { + pr_err("Failed to read ocv coefficients: %d\n", rc); + goto done; + } + for (i = 0; i < 12; i += 1) + chip->ocv_coeffs[i] = half_float(buffer + (i * 2)); + if (fg_debug_mask & FG_AGING) { + pr_info("coeffs1 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[0], chip->ocv_coeffs[1], + chip->ocv_coeffs[2], chip->ocv_coeffs[3]); + pr_info("coeffs2 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[4], chip->ocv_coeffs[5], + chip->ocv_coeffs[6], chip->ocv_coeffs[7]); + pr_info("coeffs3 = %lld %lld %lld %lld\n", + chip->ocv_coeffs[8], chip->ocv_coeffs[9], + chip->ocv_coeffs[10], chip->ocv_coeffs[11]); + } + rc = fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 0, 0); + chip->ocv_junction_p1p2 = buffer[0] * 100 / 255; + rc |= fg_mem_read(chip, buffer, OCV_JUNCTION_REG, 1, 1, 0); + chip->ocv_junction_p2p3 = buffer[0] * 100 / 255; + if (rc) { + pr_err("Failed to read ocv junctions: %d\n", rc); + goto done; + } + rc = fg_mem_read(chip, buffer, NOM_CAP_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to read nominal capacitance: %d\n", rc); + goto done; + } + chip->nom_cap_uah = bcap_uah_2b(buffer); + chip->actual_cap_uah = chip->nom_cap_uah; + if (chip->learning_data.learned_cc_uah == 0) { + chip->learning_data.learned_cc_uah = chip->nom_cap_uah; + fg_cap_learning_save_data(chip); + } else if (chip->learning_data.feedback_on) { + cc_mah = div64_s64(chip->learning_data.learned_cc_uah, 1000); + rc = fg_calc_and_store_cc_soc_coeff(chip, cc_mah); + if (rc) + pr_err("Error in restoring cc_soc_coeff, rc:%d\n", rc); + } + rc = fg_mem_read(chip, buffer, CUTOFF_VOLTAGE_REG, 2, 0, 0); + if (rc) { + pr_err("Failed to read cutoff voltage: %d\n", rc); + goto done; + } + chip->cutoff_voltage = voltage_2b(buffer); + if (fg_debug_mask & FG_AGING) + pr_info("cutoff_voltage = %lld, nom_cap_uah = %d p1p2 = %d, p2p3 = %d\n", + chip->cutoff_voltage, chip->nom_cap_uah, + chip->ocv_junction_p1p2, + chip->ocv_junction_p2p3); + + rc = fg_mem_read(chip, buffer, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow cfg: %d\n", rc); + goto done; + } + chip->rslow_comp.rslow_cfg = buffer[0]; + rc = fg_mem_read(chip, buffer, RSLOW_THRESH_REG, 1, + RSLOW_THRESH_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow thresh: %d\n", rc); + goto done; + } + chip->rslow_comp.rslow_thr = buffer[0]; + rc = fg_mem_read(chip, buffer, TEMP_RS_TO_RSLOW_REG, 2, + RSLOW_THRESH_OFFSET, 0); + if (rc) { + pr_err("unable to read rs to rslow: %d\n", rc); + goto done; + } + memcpy(chip->rslow_comp.rs_to_rslow, buffer, 2); + rc = fg_mem_read(chip, buffer, RSLOW_COMP_REG, 4, + RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to read rslow comp: %d\n", rc); + goto done; + } + memcpy(chip->rslow_comp.rslow_comp, buffer, 4); + +done: + fg_mem_release(chip); + return rc; +} + +#define RSLOW_CFG_MASK (BIT(2) | BIT(3) | BIT(4) | BIT(5)) +#define RSLOW_CFG_ON_VAL (BIT(2) | BIT(3)) +#define RSLOW_THRESH_FULL_VAL 0xFF +static int fg_rslow_charge_comp_set(struct fg_chip *chip) +{ + int rc; + u8 buffer[2]; + + mutex_lock(&chip->rslow_comp.lock); + fg_mem_lock(chip); + + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_MASK, RSLOW_CFG_ON_VAL, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg: %d\n", rc); + goto done; + } + rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG, + 0xFF, RSLOW_THRESH_FULL_VAL, RSLOW_THRESH_OFFSET); + if (rc) { + pr_err("unable to write rslow thresh: %d\n", rc); + goto done; + } + + half_float_to_buffer(chip->rslow_comp.chg_rs_to_rslow, buffer); + rc = fg_mem_write(chip, buffer, + TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + if (rc) { + pr_err("unable to write rs to rslow: %d\n", rc); + goto done; + } + half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c1, buffer); + rc = fg_mem_write(chip, buffer, + RSLOW_COMP_REG, 2, RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + half_float_to_buffer(chip->rslow_comp.chg_rslow_comp_c2, buffer); + rc = fg_mem_write(chip, buffer, + RSLOW_COMP_REG, 2, RSLOW_COMP_C2_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + chip->rslow_comp.active = true; + if (fg_debug_mask & FG_STATUS) + pr_info("Activated rslow charge comp values\n"); + +done: + fg_mem_release(chip); + mutex_unlock(&chip->rslow_comp.lock); + return rc; +} + +#define RSLOW_CFG_ORIG_MASK (BIT(4) | BIT(5)) +static int fg_rslow_charge_comp_clear(struct fg_chip *chip) +{ + u8 reg; + int rc; + + mutex_lock(&chip->rslow_comp.lock); + fg_mem_lock(chip); + + reg = chip->rslow_comp.rslow_cfg & RSLOW_CFG_ORIG_MASK; + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_MASK, reg, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg: %d\n", rc); + goto done; + } + rc = fg_mem_masked_write(chip, RSLOW_THRESH_REG, + 0xFF, chip->rslow_comp.rslow_thr, RSLOW_THRESH_OFFSET); + if (rc) { + pr_err("unable to write rslow thresh: %d\n", rc); + goto done; + } + + rc = fg_mem_write(chip, chip->rslow_comp.rs_to_rslow, + TEMP_RS_TO_RSLOW_REG, 2, TEMP_RS_TO_RSLOW_OFFSET, 0); + if (rc) { + pr_err("unable to write rs to rslow: %d\n", rc); + goto done; + } + rc = fg_mem_write(chip, chip->rslow_comp.rslow_comp, + RSLOW_COMP_REG, 4, RSLOW_COMP_C1_OFFSET, 0); + if (rc) { + pr_err("unable to write rslow comp: %d\n", rc); + goto done; + } + chip->rslow_comp.active = false; + if (fg_debug_mask & FG_STATUS) + pr_info("Cleared rslow charge comp values\n"); + +done: + fg_mem_release(chip); + mutex_unlock(&chip->rslow_comp.lock); + return rc; +} + +static void rslow_comp_work(struct work_struct *work) +{ + int battery_soc_1b; + struct fg_chip *chip = container_of(work, + struct fg_chip, + rslow_comp_work); + + battery_soc_1b = get_battery_soc_raw(chip) >> 16; + if (battery_soc_1b > chip->rslow_comp.chg_rslow_comp_thr + && chip->status == POWER_SUPPLY_STATUS_CHARGING) { + if (!chip->rslow_comp.active) + fg_rslow_charge_comp_set(chip); + } else { + if (chip->rslow_comp.active) + fg_rslow_charge_comp_clear(chip); + } +} + +#define MICROUNITS_TO_ADC_RAW(units) \ + div64_s64(units * LSB_16B_DENMTR, LSB_16B_NUMRTR) +static int update_chg_iterm(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_current_raw; + s64 current_ma = -settings[FG_MEM_CHG_TERM_CURRENT].value; + + converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000); + data[0] = cpu_to_le16(converted_current_raw) & 0xFF; + data[1] = cpu_to_le16(converted_current_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n", + current_ma, converted_current_raw, data[0], data[1]); + return fg_mem_write(chip, data, + settings[FG_MEM_CHG_TERM_CURRENT].address, + 2, settings[FG_MEM_CHG_TERM_CURRENT].offset, 0); +} + +#define CC_CV_SETPOINT_REG 0x4F8 +#define CC_CV_SETPOINT_OFFSET 0 +static void update_cc_cv_setpoint(struct fg_chip *chip) +{ + int rc; + u8 tmp[2]; + + if (!chip->cc_cv_threshold_mv) + return; + batt_to_setpoint_adc(chip->cc_cv_threshold_mv, tmp); + rc = fg_mem_write(chip, tmp, CC_CV_SETPOINT_REG, 2, + CC_CV_SETPOINT_OFFSET, 0); + if (rc) { + pr_err("failed to write CC_CV_VOLT rc=%d\n", rc); + return; + } + if (fg_debug_mask & FG_STATUS) + pr_info("Wrote %x %x to address %x for CC_CV setpoint\n", + tmp[0], tmp[1], CC_CV_SETPOINT_REG); +} + +#define CBITS_INPUT_FILTER_REG 0x4B4 +#define CBITS_RMEAS1_OFFSET 1 +#define CBITS_RMEAS2_OFFSET 2 +#define CBITS_RMEAS1_DEFAULT_VAL 0x65 +#define CBITS_RMEAS2_DEFAULT_VAL 0x65 +#define IMPTR_FAST_TIME_SHIFT 1 +#define IMPTR_LONG_TIME_SHIFT (1 << 4) +#define IMPTR_PULSE_CTR_CHG 1 +#define IMPTR_PULSE_CTR_DISCHG (1 << 4) +static int fg_config_imptr_pulse(struct fg_chip *chip, bool slow) +{ + int rc; + u8 cntr[2] = {0, 0}; + u8 val; + + if (slow == chip->imptr_pulse_slow_en) { + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled already\n", + slow ? "en" : "dis"); + return 0; + } + + fg_mem_lock(chip); + + val = slow ? (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT) : + CBITS_RMEAS1_DEFAULT_VAL; + rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS1_OFFSET, 0); + if (rc) { + pr_err("unable to write cbits_rmeas1_offset rc=%d\n", rc); + goto done; + } + + val = slow ? (IMPTR_PULSE_CTR_CHG | IMPTR_PULSE_CTR_DISCHG) : + CBITS_RMEAS2_DEFAULT_VAL; + rc = fg_mem_write(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS2_OFFSET, 0); + if (rc) { + pr_err("unable to write cbits_rmeas2_offset rc=%d\n", rc); + goto done; + } + + if (slow) { + rc = fg_mem_write(chip, cntr, COUNTER_IMPTR_REG, 4, + COUNTER_IMPTR_OFFSET, 0); + if (rc) { + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + goto done; + } + + rc = fg_mem_write(chip, cntr, COUNTER_PULSE_REG, 2, + COUNTER_PULSE_OFFSET, 0); + if (rc) { + pr_err("failed to write COUNTER_IMPTR rc=%d\n", rc); + goto done; + } + } + + chip->imptr_pulse_slow_en = slow; + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled\n", slow ? "en" : "dis"); +done: + fg_mem_release(chip); + return rc; +} + +#define CURRENT_DELTA_MIN_REG 0x42C +#define CURRENT_DELTA_MIN_OFFSET 1 +#define SYS_CFG_1_REG 0x4AC +#define SYS_CFG_1_OFFSET 0 +#define CURRENT_DELTA_MIN_DEFAULT 0x16 +#define CURRENT_DELTA_MIN_500MA 0xCD +#define RSLOW_CFG_USE_FIX_RSER_VAL BIT(7) +#define ENABLE_ESR_PULSE_VAL BIT(3) +static int fg_config_esr_extract(struct fg_chip *chip, bool disable) +{ + int rc; + u8 val; + + if (disable == chip->esr_extract_disabled) { + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract already %sabled\n", + disable ? "dis" : "en"); + return 0; + } + + fg_mem_lock(chip); + + val = disable ? CURRENT_DELTA_MIN_500MA : + CURRENT_DELTA_MIN_DEFAULT; + rc = fg_mem_write(chip, &val, CURRENT_DELTA_MIN_REG, 1, + CURRENT_DELTA_MIN_OFFSET, 0); + if (rc) { + pr_err("unable to write curr_delta_min rc=%d\n", rc); + goto done; + } + + val = disable ? RSLOW_CFG_USE_FIX_RSER_VAL : 0; + rc = fg_mem_masked_write(chip, RSLOW_CFG_REG, + RSLOW_CFG_USE_FIX_RSER_VAL, val, RSLOW_CFG_OFFSET); + if (rc) { + pr_err("unable to write rslow cfg rc= %d\n", rc); + goto done; + } + + val = disable ? 0 : ENABLE_ESR_PULSE_VAL; + rc = fg_mem_masked_write(chip, SYS_CFG_1_REG, + ENABLE_ESR_PULSE_VAL, val, SYS_CFG_1_OFFSET); + if (rc) { + pr_err("unable to write sys_cfg_1 rc= %d\n", rc); + goto done; + } + + chip->esr_extract_disabled = disable; + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract is %sabled\n", disable ? "dis" : "en"); +done: + fg_mem_release(chip); + return rc; +} + +#define ESR_EXTRACT_STOP_SOC 2 +#define IMPTR_PULSE_CONFIG_SOC 5 +static void esr_extract_config_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, struct fg_chip, + esr_extract_config_work); + bool input_present = is_input_present(chip); + int capacity = get_prop_capacity(chip); + + if (input_present && capacity <= ESR_EXTRACT_STOP_SOC) { + fg_config_esr_extract(chip, true); + } else if (capacity > ESR_EXTRACT_STOP_SOC) { + fg_config_esr_extract(chip, false); + + if (capacity <= IMPTR_PULSE_CONFIG_SOC) + fg_config_imptr_pulse(chip, true); + else + fg_config_imptr_pulse(chip, false); + } + + fg_relax(&chip->esr_extract_wakeup_source); +} + +#define LOW_LATENCY BIT(6) +#define BATT_PROFILE_OFFSET 0x4C0 +#define PROFILE_INTEGRITY_REG 0x53C +#define PROFILE_INTEGRITY_BIT BIT(0) +#define FIRST_EST_DONE_BIT BIT(5) +#define MAX_TRIES_FIRST_EST 3 +#define FIRST_EST_WAIT_MS 2000 +#define PROFILE_LOAD_TIMEOUT_MS 5000 +static int fg_do_restart(struct fg_chip *chip, bool write_profile) +{ + int rc, ibat_ua; + u8 reg = 0; + u8 buf[2]; + bool tried_once = false; + + if (fg_debug_mask & FG_STATUS) + pr_info("restarting fuel gauge...\n"); + +try_again: + if (write_profile) { + if (!chip->charging_disabled) { + pr_err("Charging not yet disabled!\n"); + return -EINVAL; + } + + ibat_ua = get_sram_prop_now(chip, FG_DATA_CURRENT); + if (ibat_ua == -EINVAL) { + pr_err("SRAM not updated yet!\n"); + return ibat_ua; + } + + if (ibat_ua < 0) { + pr_warn("Charging enabled?, ibat_ua: %d\n", ibat_ua); + + if (!tried_once) { + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work(&chip->update_sram_data, + msecs_to_jiffies(0)); + msleep(1000); + tried_once = true; + goto try_again; + } + } + } + + chip->fg_restarting = true; + /* + * save the temperature if the sw rbias control is active so that there + * is no gap of time when there is no valid temperature read after the + * restart + */ + if (chip->sw_rbias_ctrl) { + rc = fg_mem_read(chip, buf, + fg_data[FG_DATA_BATT_TEMP].address, + fg_data[FG_DATA_BATT_TEMP].len, + fg_data[FG_DATA_BATT_TEMP].offset, 0); + if (rc) { + pr_err("failed to read batt temp rc=%d\n", rc); + goto sub_and_fail; + } + } + /* + * release the sram access and configure the correct settings + * before re-requesting access. + */ + mutex_lock(&chip->rw_lock); + fg_release_access(chip); + + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, 0, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto unlock_and_fail; + } + + /* unset the restart bits so the fg doesn't continuously restart */ + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, 0, 1); + if (rc) { + pr_err("failed to unset fg restart: %d\n", rc); + goto unlock_and_fail; + } + + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), + LOW_LATENCY, LOW_LATENCY, 1); + if (rc) { + pr_err("failed to set low latency access bit\n"); + goto unlock_and_fail; + } + mutex_unlock(&chip->rw_lock); + + /* read once to get a fg cycle in */ + rc = fg_mem_read(chip, ®, PROFILE_INTEGRITY_REG, 1, 0, 0); + if (rc) { + pr_err("failed to read profile integrity rc=%d\n", rc); + goto fail; + } + + /* + * If this is not the first time a profile has been loaded, sleep for + * 3 seconds to make sure the NO_OTP_RELOAD is cleared in memory + */ + if (chip->first_profile_loaded) + msleep(3000); + + mutex_lock(&chip->rw_lock); + fg_release_access(chip); + rc = fg_masked_write(chip, MEM_INTF_CFG(chip), LOW_LATENCY, 0, 1); + if (rc) { + pr_err("failed to set low latency access bit\n"); + goto unlock_and_fail; + } + + atomic_add_return(1, &chip->memif_user_cnt); + mutex_unlock(&chip->rw_lock); + + if (write_profile) { + /* write the battery profile */ + rc = fg_mem_write(chip, chip->batt_profile, BATT_PROFILE_OFFSET, + chip->batt_profile_len, 0, 1); + if (rc) { + pr_err("failed to write profile rc=%d\n", rc); + goto sub_and_fail; + } + /* write the integrity bits and release access */ + rc = fg_mem_masked_write(chip, PROFILE_INTEGRITY_REG, + PROFILE_INTEGRITY_BIT, + PROFILE_INTEGRITY_BIT, 0); + if (rc) { + pr_err("failed to write profile rc=%d\n", rc); + goto sub_and_fail; + } + } + + /* decrement the user count so that memory access can be released */ + fg_release_access_if_necessary(chip); + + /* + * make sure that the first estimate has completed + * in case of a hotswap + */ + rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done, + msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS)); + if (rc <= 0) { + pr_err("transaction timed out rc=%d\n", rc); + rc = -ETIMEDOUT; + goto fail; + } + + /* + * reinitialize the completion so that the driver knows when the restart + * finishes + */ + reinit_completion(&chip->first_soc_done); + + if (chip->esr_pulse_tune_en) { + fg_stay_awake(&chip->esr_extract_wakeup_source); + schedule_work(&chip->esr_extract_config_work); + } + + /* + * set the restart bits so that the next fg cycle will not reload + * the profile + */ + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, NO_OTP_PROF_RELOAD, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto fail; + } + + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, reg, 1); + if (rc) { + pr_err("failed to set fg restart: %d\n", rc); + goto fail; + } + + /* wait for the first estimate to complete */ + rc = wait_for_completion_interruptible_timeout(&chip->first_soc_done, + msecs_to_jiffies(PROFILE_LOAD_TIMEOUT_MS)); + if (rc <= 0) { + pr_err("transaction timed out rc=%d\n", rc); + rc = -ETIMEDOUT; + goto fail; + } + rc = fg_read(chip, ®, INT_RT_STS(chip->soc_base), 1); + if (rc) { + pr_err("spmi read failed: addr=%03X, rc=%d\n", + INT_RT_STS(chip->soc_base), rc); + goto fail; + } + if ((reg & FIRST_EST_DONE_BIT) == 0) + pr_err("Battery profile reloading failed, no first estimate\n"); + + rc = fg_masked_write(chip, chip->soc_base + SOC_BOOT_MOD, + NO_OTP_PROF_RELOAD, 0, 1); + if (rc) { + pr_err("failed to set no otp reload bit\n"); + goto fail; + } + /* unset the restart bits so the fg doesn't continuously restart */ + reg = REDO_FIRST_ESTIMATE | RESTART_GO; + rc = fg_masked_write(chip, chip->soc_base + SOC_RESTART, + reg, 0, 1); + if (rc) { + pr_err("failed to unset fg restart: %d\n", rc); + goto fail; + } + + /* restore the battery temperature reading here */ + if (chip->sw_rbias_ctrl) { + if (fg_debug_mask & FG_STATUS) + pr_info("reloaded 0x%02x%02x into batt temp", + buf[0], buf[1]); + rc = fg_mem_write(chip, buf, + fg_data[FG_DATA_BATT_TEMP].address, + fg_data[FG_DATA_BATT_TEMP].len, + fg_data[FG_DATA_BATT_TEMP].offset, 0); + if (rc) { + pr_err("failed to write batt temp rc=%d\n", rc); + goto fail; + } + } + + /* Enable charging now as the first estimate is done now */ + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + chip->fg_restarting = false; + + if (fg_debug_mask & FG_STATUS) + pr_info("done!\n"); + return 0; + +unlock_and_fail: + mutex_unlock(&chip->rw_lock); + goto fail; +sub_and_fail: + fg_release_access_if_necessary(chip); + goto fail; +fail: + chip->fg_restarting = false; + return -EINVAL; +} + +#define FG_PROFILE_LEN 128 +#define PROFILE_COMPARE_LEN 32 +#define THERMAL_COEFF_ADDR 0x444 +#define THERMAL_COEFF_OFFSET 0x2 +#define BATTERY_PSY_WAIT_MS 2000 +static int fg_batt_profile_init(struct fg_chip *chip) +{ + int rc = 0, ret, 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, ®, PROFILE_INTEGRITY_REG, 1, 0, 1); + if (rc) { + pr_err("failed to read profile integrity rc=%d\n", rc); + goto no_profile; + } + + rc = fg_mem_read(chip, chip->batt_profile, BATT_PROFILE_OFFSET, + len, 0, 1); + if (rc) { + pr_err("failed to read profile rc=%d\n", rc); + goto no_profile; + } + + /* Check whether the charger is ready */ + if (!is_charger_available(chip)) + goto reschedule; + + /* Disable charging for a FG cycle before calculating vbat_in_range */ + if (!chip->charging_disabled) { + rc = set_prop_enable_charging(chip, false); + if (rc) + pr_err("Failed to disable charging, rc=%d\n", rc); + + goto reschedule; + } + + vbat_in_range = get_vbat_est_diff(chip) + < settings[FG_MEM_VBAT_EST_DIFF].value * 1000; + profiles_same = memcmp(chip->batt_profile, data, + PROFILE_COMPARE_LEN) == 0; + if (reg & PROFILE_INTEGRITY_BIT) { + fg_cap_learning_load_data(chip); + if (vbat_in_range && !fg_is_batt_empty(chip) && profiles_same) { + if (fg_debug_mask & FG_STATUS) + pr_info("Battery profiles same, using default\n"); + if (fg_est_dump) + schedule_work(&chip->dump_sram); + goto done; + } + } else { + pr_info("Battery profile not same, clearing data\n"); + clear_cycle_counter(chip); + chip->learning_data.learned_cc_uah = 0; + } + + if (fg_est_dump) + dump_sram(&chip->dump_sram); + + if ((fg_debug_mask & FG_STATUS) && !vbat_in_range) + pr_info("Vbat out of range: v_current_pred: %d, v:%d\n", + fg_data[FG_DATA_CPRED_VOLTAGE].value, + fg_data[FG_DATA_VOLTAGE].value); + + if ((fg_debug_mask & FG_STATUS) && fg_is_batt_empty(chip)) + pr_info("battery empty\n"); + + if ((fg_debug_mask & FG_STATUS) && !profiles_same) + pr_info("profiles differ\n"); + + if (fg_debug_mask & FG_STATUS) { + pr_info("Using new profile\n"); + print_hex_dump(KERN_INFO, "FG: loaded profile: ", + DUMP_PREFIX_NONE, 16, 1, + chip->batt_profile, len, false); + } + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + + memcpy(chip->batt_profile, data, len); + + chip->batt_profile_len = len; + + if (fg_debug_mask & FG_STATUS) + print_hex_dump(KERN_INFO, "FG: new profile: ", + DUMP_PREFIX_NONE, 16, 1, chip->batt_profile, + chip->batt_profile_len, false); + + rc = fg_do_restart(chip, true); + if (rc) { + pr_err("restart failed: %d\n", rc); + goto no_profile; + } + + /* + * Only configure from profile if thermal-coefficients is not + * defined in the FG device node. + */ + if (!of_find_property(chip->pdev->dev.of_node, + "qcom,thermal-coefficients", NULL)) { + data = of_get_property(profile_node, + "qcom,thermal-coefficients", &len); + if (data && len == THERMAL_COEFF_N_BYTES) { + memcpy(chip->thermal_coefficients, data, len); + rc = fg_mem_write(chip, chip->thermal_coefficients, + THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES, + THERMAL_COEFF_OFFSET, 0); + if (rc) + pr_err("spmi write failed addr:%03x, ret:%d\n", + THERMAL_COEFF_ADDR, rc); + else if (fg_debug_mask & FG_STATUS) + pr_info("Battery thermal coefficients changed\n"); + } + } + +done: + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + if (fg_batt_type) + chip->batt_type = fg_batt_type; + else + chip->batt_type = batt_type_str; + chip->first_profile_loaded = true; + chip->profile_loaded = true; + chip->battery_missing = is_battery_missing(chip); + update_chg_iterm(chip); + update_cc_cv_setpoint(chip); + rc = populate_system_data(chip); + if (rc) { + pr_err("failed to read ocv properties=%d\n", rc); + return rc; + } + estimate_battery_age(chip, &chip->actual_cap_uah); + schedule_work(&chip->status_change_work); + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + fg_relax(&chip->profile_wakeup_source); + pr_info("Battery SOC: %d, V: %duV\n", get_prop_capacity(chip), + fg_data[FG_DATA_VOLTAGE].value); + return rc; +no_profile: + if (chip->charging_disabled) { + rc = set_prop_enable_charging(chip, true); + if (rc) + pr_err("Failed to enable charging, rc=%d\n", rc); + else + chip->charging_disabled = false; + } + + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + fg_relax(&chip->profile_wakeup_source); + return rc; +reschedule: + schedule_delayed_work( + &chip->batt_profile_init, + msecs_to_jiffies(BATTERY_PSY_WAIT_MS)); + cancel_delayed_work(&chip->update_sram_data); + schedule_delayed_work( + &chip->update_sram_data, + msecs_to_jiffies(0)); + fg_relax(&chip->profile_wakeup_source); + return 0; +} + +static void check_empty_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + check_empty_work.work); + + if (fg_is_batt_empty(chip)) { + if (fg_debug_mask & FG_STATUS) + pr_info("EMPTY SOC high\n"); + chip->soc_empty = true; + if (chip->power_supply_registered) + power_supply_changed(chip->bms_psy); + } + fg_relax(&chip->empty_check_wakeup_source); +} + +static void batt_profile_init(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + batt_profile_init.work); + + if (fg_batt_profile_init(chip)) + pr_err("failed to initialize profile\n"); +} + +static void sysfs_restart_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + sysfs_restart_work); + int rc; + + rc = fg_do_restart(chip, false); + if (rc) + pr_err("fg restart failed: %d\n", rc); + mutex_lock(&chip->sysfs_restart_lock); + fg_restart = 0; + mutex_unlock(&chip->sysfs_restart_lock); +} + +#define SRAM_MONOTONIC_SOC_REG 0x574 +#define SRAM_MONOTONIC_SOC_OFFSET 2 +#define SRAM_RELEASE_TIMEOUT_MS 500 +static void charge_full_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + charge_full_work); + int rc; + u8 buffer[3]; + int bsoc; + int resume_soc_raw = FULL_SOC_RAW - settings[FG_MEM_RESUME_SOC].value; + bool disable = false; + u8 reg; + + if (chip->status != POWER_SUPPLY_STATUS_FULL) { + if (fg_debug_mask & FG_STATUS) + pr_info("battery not full: %d\n", chip->status); + disable = true; + } + + fg_mem_lock(chip); + rc = fg_mem_read(chip, buffer, BATTERY_SOC_REG, 3, 1, 0); + if (rc) { + pr_err("Unable to read battery soc: %d\n", rc); + goto out; + } + if (buffer[2] <= resume_soc_raw) { + if (fg_debug_mask & FG_STATUS) + pr_info("bsoc = 0x%02x <= resume = 0x%02x\n", + buffer[2], resume_soc_raw); + disable = true; + } + if (!disable) + goto out; + + rc = fg_mem_write(chip, buffer, SOC_FULL_REG, 3, + SOC_FULL_OFFSET, 0); + if (rc) { + pr_err("failed to write SOC_FULL rc=%d\n", rc); + goto out; + } + /* force a full soc value into the monotonic in order to display 100 */ + buffer[0] = 0xFF; + buffer[1] = 0xFF; + rc = fg_mem_write(chip, buffer, SRAM_MONOTONIC_SOC_REG, 2, + SRAM_MONOTONIC_SOC_OFFSET, 0); + if (rc) { + pr_err("failed to write SOC_FULL rc=%d\n", rc); + goto out; + } + if (fg_debug_mask & FG_STATUS) { + bsoc = buffer[0] | buffer[1] << 8 | buffer[2] << 16; + pr_info("wrote %06x into soc full\n", bsoc); + } + fg_mem_release(chip); + /* + * wait one cycle to make sure the soc is updated before clearing + * the soc mask bit + */ + fg_mem_lock(chip); + fg_mem_read(chip, ®, PROFILE_INTEGRITY_REG, 1, 0, 0); +out: + fg_mem_release(chip); + if (disable) + chip->charge_full = false; +} + +static void update_bcl_thresholds(struct fg_chip *chip) +{ + u8 data[4]; + u8 mh_offset = 0, lm_offset = 0; + u16 address = 0; + int ret = 0; + + address = settings[FG_MEM_BCL_MH_THRESHOLD].address; + mh_offset = settings[FG_MEM_BCL_MH_THRESHOLD].offset; + lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset; + ret = fg_mem_read(chip, data, address, 4, 0, 1); + if (ret) + pr_err("Error reading BCL LM & MH threshold rc:%d\n", ret); + else + pr_debug("Old BCL LM threshold:%x MH threshold:%x\n", + data[lm_offset], data[mh_offset]); + BCL_MA_TO_ADC(settings[FG_MEM_BCL_MH_THRESHOLD].value, data[mh_offset]); + BCL_MA_TO_ADC(settings[FG_MEM_BCL_LM_THRESHOLD].value, data[lm_offset]); + + ret = fg_mem_write(chip, data, address, 4, 0, 0); + if (ret) + pr_err("spmi write failed. addr:%03x, ret:%d\n", + address, ret); + else + pr_debug("New BCL LM threshold:%x MH threshold:%x\n", + data[lm_offset], data[mh_offset]); +} + +static int disable_bcl_lpm(struct fg_chip *chip) +{ + u8 data[4]; + u8 lm_offset = 0; + u16 address = 0; + int rc = 0; + + address = settings[FG_MEM_BCL_LM_THRESHOLD].address; + lm_offset = settings[FG_MEM_BCL_LM_THRESHOLD].offset; + rc = fg_mem_read(chip, data, address, 4, 0, 1); + if (rc) { + pr_err("Error reading BCL LM & MH threshold rc:%d\n", rc); + return rc; + } + pr_debug("Old BCL LM threshold:%x\n", data[lm_offset]); + + /* Put BCL always above LPM */ + BCL_MA_TO_ADC(0, data[lm_offset]); + + rc = fg_mem_write(chip, data, address, 4, 0, 0); + if (rc) + pr_err("spmi write failed. addr:%03x, rc:%d\n", + address, rc); + else + pr_debug("New BCL LM threshold:%x\n", data[lm_offset]); + + return rc; +} + +static void bcl_hi_power_work(struct work_struct *work) +{ + struct fg_chip *chip = container_of(work, + struct fg_chip, + bcl_hi_power_work); + int rc; + + if (chip->bcl_lpm_disabled) { + rc = disable_bcl_lpm(chip); + if (rc) + pr_err("failed to disable bcl low mode %d\n", + rc); + } else { + update_bcl_thresholds(chip); + } +} + +#define VOLT_UV_TO_VOLTCMP8(volt_uv) \ + ((volt_uv - 2500000) / 9766) +static int update_irq_volt_empty(struct fg_chip *chip) +{ + u8 data; + int volt_mv = settings[FG_MEM_IRQ_VOLT_EMPTY].value; + + data = (u8)VOLT_UV_TO_VOLTCMP8(volt_mv * 1000); + + if (fg_debug_mask & FG_STATUS) + pr_info("voltage = %d, converted_raw = %04x\n", volt_mv, data); + return fg_mem_write(chip, &data, + settings[FG_MEM_IRQ_VOLT_EMPTY].address, 1, + settings[FG_MEM_IRQ_VOLT_EMPTY].offset, 0); +} + +static int update_cutoff_voltage(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_voltage_raw; + s64 voltage_mv = settings[FG_MEM_CUTOFF_VOLTAGE].value; + + converted_voltage_raw = (s16)MICROUNITS_TO_ADC_RAW(voltage_mv * 1000); + data[0] = cpu_to_le16(converted_voltage_raw) & 0xFF; + data[1] = cpu_to_le16(converted_voltage_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("voltage = %lld, converted_raw = %04x, data = %02x %02x\n", + voltage_mv, converted_voltage_raw, data[0], data[1]); + return fg_mem_write(chip, data, settings[FG_MEM_CUTOFF_VOLTAGE].address, + 2, settings[FG_MEM_CUTOFF_VOLTAGE].offset, 0); +} + +static int update_iterm(struct fg_chip *chip) +{ + u8 data[2]; + u16 converted_current_raw; + s64 current_ma = -settings[FG_MEM_TERM_CURRENT].value; + + converted_current_raw = (s16)MICROUNITS_TO_ADC_RAW(current_ma * 1000); + data[0] = cpu_to_le16(converted_current_raw) & 0xFF; + data[1] = cpu_to_le16(converted_current_raw) >> 8; + + if (fg_debug_mask & FG_STATUS) + pr_info("current = %lld, converted_raw = %04x, data = %02x %02x\n", + current_ma, converted_current_raw, data[0], data[1]); + return fg_mem_write(chip, data, settings[FG_MEM_TERM_CURRENT].address, + 2, settings[FG_MEM_TERM_CURRENT].offset, 0); +} + +#define OF_READ_SETTING(type, qpnp_dt_property, retval, optional) \ +do { \ + if (retval) \ + break; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_dt_property, \ + &settings[type].value); \ + \ + if ((retval == -EINVAL) && optional) \ + retval = 0; \ + else if (retval) \ + pr_err("Error reading " #qpnp_dt_property \ + " property rc = %d\n", rc); \ +} while (0) + +#define OF_READ_PROPERTY(store, qpnp_dt_property, retval, default_val) \ +do { \ + if (retval) \ + break; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," qpnp_dt_property, \ + &store); \ + \ + if (retval == -EINVAL) { \ + retval = 0; \ + store = default_val; \ + } else if (retval) { \ + pr_err("Error reading " #qpnp_dt_property \ + " property rc = %d\n", rc); \ + } \ +} while (0) + +#define DEFAULT_EVALUATION_CURRENT_MA 1000 +static int fg_of_init(struct fg_chip *chip) +{ + int rc = 0, sense_type, len = 0; + const char *data; + struct device_node *node = chip->pdev->dev.of_node; + u32 temp[2] = {0}; + + OF_READ_SETTING(FG_MEM_SOFT_HOT, "warm-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_SOFT_COLD, "cool-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_HARD_HOT, "hot-bat-decidegc", rc, 1); + OF_READ_SETTING(FG_MEM_HARD_COLD, "cold-bat-decidegc", rc, 1); + + if (of_find_property(node, "qcom,cold-hot-jeita-hysteresis", NULL)) { + int hard_hot = 0, soft_hot = 0, hard_cold = 0, soft_cold = 0; + + rc = of_property_read_u32_array(node, + "qcom,cold-hot-jeita-hysteresis", temp, 2); + if (rc) { + pr_err("Error reading cold-hot-jeita-hysteresis rc=%d\n", + rc); + return rc; + } + + chip->jeita_hysteresis_support = true; + chip->cold_hysteresis = temp[0]; + chip->hot_hysteresis = temp[1]; + hard_hot = settings[FG_MEM_HARD_HOT].value; + soft_hot = settings[FG_MEM_SOFT_HOT].value; + hard_cold = settings[FG_MEM_HARD_COLD].value; + soft_cold = settings[FG_MEM_SOFT_COLD].value; + if (((hard_hot - chip->hot_hysteresis) < soft_hot) || + ((hard_cold + chip->cold_hysteresis) > soft_cold)) { + chip->jeita_hysteresis_support = false; + pr_err("invalid hysteresis: hot_hysterresis = %d cold_hysteresis = %d\n", + chip->hot_hysteresis, chip->cold_hysteresis); + } else { + pr_debug("cold_hysteresis = %d, hot_hysteresis = %d\n", + chip->cold_hysteresis, chip->hot_hysteresis); + } + } + + OF_READ_SETTING(FG_MEM_BCL_LM_THRESHOLD, "bcl-lm-threshold-ma", + rc, 1); + OF_READ_SETTING(FG_MEM_BCL_MH_THRESHOLD, "bcl-mh-threshold-ma", + rc, 1); + OF_READ_SETTING(FG_MEM_TERM_CURRENT, "fg-iterm-ma", rc, 1); + OF_READ_SETTING(FG_MEM_CHG_TERM_CURRENT, "fg-chg-iterm-ma", rc, 1); + OF_READ_SETTING(FG_MEM_CUTOFF_VOLTAGE, "fg-cutoff-voltage-mv", rc, 1); + data = of_get_property(chip->pdev->dev.of_node, + "qcom,thermal-coefficients", &len); + if (data && len == THERMAL_COEFF_N_BYTES) { + memcpy(chip->thermal_coefficients, data, len); + chip->use_thermal_coefficients = true; + } + OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc", rc, 1); + settings[FG_MEM_RESUME_SOC].value = + DIV_ROUND_CLOSEST(settings[FG_MEM_RESUME_SOC].value + * FULL_SOC_RAW, FULL_CAPACITY); + OF_READ_SETTING(FG_MEM_RESUME_SOC, "resume-soc-raw", rc, 1); + OF_READ_SETTING(FG_MEM_IRQ_VOLT_EMPTY, "irq-volt-empty-mv", rc, 1); + OF_READ_SETTING(FG_MEM_VBAT_EST_DIFF, "vbat-estimate-diff-mv", rc, 1); + OF_READ_SETTING(FG_MEM_DELTA_SOC, "fg-delta-soc", rc, 1); + OF_READ_SETTING(FG_MEM_BATT_LOW, "fg-vbatt-low-threshold", rc, 1); + OF_READ_SETTING(FG_MEM_THERM_DELAY, "fg-therm-delay-us", rc, 1); + OF_READ_PROPERTY(chip->learning_data.max_increment, + "cl-max-increment-deciperc", rc, 5); + OF_READ_PROPERTY(chip->learning_data.max_decrement, + "cl-max-decrement-deciperc", rc, 100); + OF_READ_PROPERTY(chip->learning_data.max_temp, + "cl-max-temp-decidegc", rc, 450); + OF_READ_PROPERTY(chip->learning_data.min_temp, + "cl-min-temp-decidegc", rc, 150); + OF_READ_PROPERTY(chip->learning_data.max_start_soc, + "cl-max-start-capacity", rc, 15); + OF_READ_PROPERTY(chip->learning_data.vbat_est_thr_uv, + "cl-vbat-est-thr-uv", rc, 40000); + OF_READ_PROPERTY(chip->evaluation_current, + "aging-eval-current-ma", rc, + DEFAULT_EVALUATION_CURRENT_MA); + OF_READ_PROPERTY(chip->cc_cv_threshold_mv, + "fg-cc-cv-threshold-mv", rc, 0); + if (of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-learning-on")) + chip->batt_aging_mode = FG_AGING_CC; + else if (of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-estimation-on")) + chip->batt_aging_mode = FG_AGING_ESR; + else + chip->batt_aging_mode = FG_AGING_NONE; + if (chip->batt_aging_mode == FG_AGING_CC) { + chip->learning_data.feedback_on + = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,capacity-learning-feedback"); + } + if (fg_debug_mask & FG_AGING) + pr_info("battery aging mode: %d\n", chip->batt_aging_mode); + + /* Get the use-otp-profile property */ + chip->use_otp_profile = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,use-otp-profile"); + chip->hold_soc_while_full + = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,hold-soc-while-full"); + + sense_type = of_property_read_bool(chip->pdev->dev.of_node, + "qcom,ext-sense-type"); + if (rc == 0) { + if (fg_sense_type < 0) + fg_sense_type = sense_type; + + if (fg_debug_mask & FG_STATUS) { + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + pr_info("Using internal sense\n"); + else if (fg_sense_type == EXTERNAL_CURRENT_SENSE) + pr_info("Using external sense\n"); + else + pr_info("Using default sense\n"); + } + } else { + rc = 0; + } + + chip->bad_batt_detection_en = of_property_read_bool(node, + "qcom,bad-battery-detection-enable"); + + chip->sw_rbias_ctrl = of_property_read_bool(node, + "qcom,sw-rbias-control"); + + chip->cyc_ctr.en = of_property_read_bool(node, + "qcom,cycle-counter-en"); + if (chip->cyc_ctr.en) + chip->cyc_ctr.id = 1; + + chip->esr_pulse_tune_en = of_property_read_bool(node, + "qcom,esr-pulse-tuning-en"); + + return rc; +} + +static int fg_init_irqs(struct fg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + struct platform_device *pdev = chip->pdev; + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + child->full_name, rc); + return rc; + } + + if ((base == chip->vbat_adc_addr) || + (base == chip->ibat_adc_addr) || + (base == chip->tp_rev_addr)) + continue; + + rc = fg_read(chip, &subtype, + base + REG_OFFSET_PERP_SUBTYPE, 1); + if (rc) { + pr_err("Peripheral subtype read failed rc=%d\n", rc); + return rc; + } + + switch (subtype) { + case FG_SOC: + chip->soc_irq[FULL_SOC].irq = of_irq_get_byname(child, + "full-soc"); + if (chip->soc_irq[FULL_SOC].irq < 0) { + pr_err("Unable to get full-soc irq\n"); + return rc; + } + chip->soc_irq[EMPTY_SOC].irq = of_irq_get_byname(child, + "empty-soc"); + if (chip->soc_irq[EMPTY_SOC].irq < 0) { + pr_err("Unable to get low-soc irq\n"); + return rc; + } + chip->soc_irq[DELTA_SOC].irq = of_irq_get_byname(child, + "delta-soc"); + if (chip->soc_irq[DELTA_SOC].irq < 0) { + pr_err("Unable to get delta-soc irq\n"); + return rc; + } + chip->soc_irq[FIRST_EST_DONE].irq + = of_irq_get_byname(child, "first-est-done"); + if (chip->soc_irq[FIRST_EST_DONE].irq < 0) { + pr_err("Unable to get first-est-done irq\n"); + return rc; + } + + rc = devm_request_irq(chip->dev, + chip->soc_irq[FULL_SOC].irq, + fg_soc_irq_handler, IRQF_TRIGGER_RISING, + "full-soc", chip); + if (rc < 0) { + pr_err("Can't request %d full-soc: %d\n", + chip->soc_irq[FULL_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[EMPTY_SOC].irq, + fg_empty_soc_irq_handler, + IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, + "empty-soc", chip); + if (rc < 0) { + pr_err("Can't request %d empty-soc: %d\n", + chip->soc_irq[EMPTY_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[DELTA_SOC].irq, + fg_soc_irq_handler, IRQF_TRIGGER_RISING, + "delta-soc", chip); + if (rc < 0) { + pr_err("Can't request %d delta-soc: %d\n", + chip->soc_irq[DELTA_SOC].irq, rc); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->soc_irq[FIRST_EST_DONE].irq, + fg_first_soc_irq_handler, IRQF_TRIGGER_RISING, + "first-est-done", chip); + if (rc < 0) { + pr_err("Can't request %d delta-soc: %d\n", + chip->soc_irq[FIRST_EST_DONE].irq, rc); + return rc; + } + + enable_irq_wake(chip->soc_irq[DELTA_SOC].irq); + enable_irq_wake(chip->soc_irq[FULL_SOC].irq); + enable_irq_wake(chip->soc_irq[EMPTY_SOC].irq); + break; + case FG_MEMIF: + chip->mem_irq[FG_MEM_AVAIL].irq + = of_irq_get_byname(child, "mem-avail"); + if (chip->mem_irq[FG_MEM_AVAIL].irq < 0) { + pr_err("Unable to get mem-avail irq\n"); + return rc; + } + rc = devm_request_irq(chip->dev, + chip->mem_irq[FG_MEM_AVAIL].irq, + fg_mem_avail_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "mem-avail", chip); + if (rc < 0) { + pr_err("Can't request %d mem-avail: %d\n", + chip->mem_irq[FG_MEM_AVAIL].irq, rc); + return rc; + } + break; + case FG_BATT: + chip->batt_irq[BATT_MISSING].irq + = of_irq_get_byname(child, "batt-missing"); + if (chip->batt_irq[BATT_MISSING].irq < 0) { + pr_err("Unable to get batt-missing irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_threaded_irq(chip->dev, + chip->batt_irq[BATT_MISSING].irq, + NULL, + fg_batt_missing_irq_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING | + IRQF_ONESHOT, + "batt-missing", chip); + if (rc < 0) { + pr_err("Can't request %d batt-missing: %d\n", + chip->batt_irq[BATT_MISSING].irq, rc); + return rc; + } + chip->batt_irq[VBATT_LOW].irq + = of_irq_get_byname(child, "vbatt-low"); + if (chip->batt_irq[VBATT_LOW].irq < 0) { + pr_err("Unable to get vbatt-low irq\n"); + rc = -EINVAL; + return rc; + } + rc = devm_request_irq(chip->dev, + chip->batt_irq[VBATT_LOW].irq, + fg_vbatt_low_handler, + IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + "vbatt-low", chip); + if (rc < 0) { + pr_err("Can't request %d vbatt-low: %d\n", + chip->batt_irq[VBATT_LOW].irq, rc); + return rc; + } + disable_irq_nosync(chip->batt_irq[VBATT_LOW].irq); + chip->vbat_low_irq_enabled = false; + break; + case FG_ADC: + break; + default: + pr_err("subtype %d\n", subtype); + return -EINVAL; + } + } + + return rc; +} + +static void fg_cleanup(struct fg_chip *chip) +{ + cancel_delayed_work_sync(&chip->update_sram_data); + cancel_delayed_work_sync(&chip->update_temp_work); + cancel_delayed_work_sync(&chip->update_jeita_setting); + cancel_delayed_work_sync(&chip->check_empty_work); + cancel_delayed_work_sync(&chip->batt_profile_init); + alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + cancel_work_sync(&chip->rslow_comp_work); + cancel_work_sync(&chip->set_resume_soc_work); + cancel_work_sync(&chip->fg_cap_learning_work); + cancel_work_sync(&chip->dump_sram); + cancel_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->cycle_count_work); + cancel_work_sync(&chip->update_esr_work); + cancel_work_sync(&chip->sysfs_restart_work); + cancel_work_sync(&chip->gain_comp_work); + cancel_work_sync(&chip->init_work); + cancel_work_sync(&chip->charge_full_work); + cancel_work_sync(&chip->esr_extract_config_work); + mutex_destroy(&chip->rslow_comp.lock); + mutex_destroy(&chip->rw_lock); + mutex_destroy(&chip->cyc_ctr.lock); + mutex_destroy(&chip->learning_data.learning_lock); + mutex_destroy(&chip->sysfs_restart_lock); + wakeup_source_trash(&chip->resume_soc_wakeup_source.source); + wakeup_source_trash(&chip->empty_check_wakeup_source.source); + wakeup_source_trash(&chip->memif_wakeup_source.source); + wakeup_source_trash(&chip->profile_wakeup_source.source); + wakeup_source_trash(&chip->update_temp_wakeup_source.source); + wakeup_source_trash(&chip->update_sram_wakeup_source.source); + wakeup_source_trash(&chip->gain_comp_wakeup_source.source); + wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); + wakeup_source_trash(&chip->esr_extract_wakeup_source.source); +} + +static int fg_remove(struct platform_device *pdev) +{ + struct fg_chip *chip = dev_get_drvdata(&pdev->dev); + + fg_cleanup(chip); + dev_set_drvdata(&pdev->dev, NULL); + return 0; +} + +static int fg_memif_data_open(struct inode *inode, struct file *file) +{ + struct fg_log_buffer *log; + struct fg_trans *trans; + u8 *data_buf; + + size_t logbufsize = SZ_4K; + size_t databufsize = SZ_4K; + + if (!dbgfs_data.chip) { + pr_err("Not initialized data\n"); + return -EINVAL; + } + + /* Per file "transaction" data */ + trans = kzalloc(sizeof(*trans), GFP_KERNEL); + if (!trans) { + pr_err("Unable to allocate memory for transaction data\n"); + return -ENOMEM; + } + + /* Allocate log buffer */ + log = kzalloc(logbufsize, GFP_KERNEL); + + if (!log) { + kfree(trans); + pr_err("Unable to allocate memory for log buffer\n"); + return -ENOMEM; + } + + log->rpos = 0; + log->wpos = 0; + log->len = logbufsize - sizeof(*log); + + /* Allocate data buffer */ + data_buf = kzalloc(databufsize, GFP_KERNEL); + + if (!data_buf) { + kfree(trans); + kfree(log); + pr_err("Unable to allocate memory for data buffer\n"); + return -ENOMEM; + } + + trans->log = log; + trans->data = data_buf; + trans->cnt = dbgfs_data.cnt; + trans->addr = dbgfs_data.addr; + trans->chip = dbgfs_data.chip; + trans->offset = trans->addr; + 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, ®, chip->tp_rev_addr, 1); + if (rc) { + pr_err("Failed to read tp reg, rc = %d\n", rc); + return rc; + } + if (reg >= BCL_TRIM_REV_FIXED) { + if (fg_debug_mask & FG_STATUS) + pr_info("workaround not applied, tp_rev = %d\n", reg); + return 0; + } + + rc = fg_mem_read(chip, ®, EXTERNAL_SENSE_OFFSET_REG, 1, 2, 0); + if (rc) { + pr_err("Failed to read ext sense offset trim, rc = %d\n", rc); + return rc; + } + rc = fg_masked_write(chip, chip->soc_base + SEC_ACCESS_REG, + SEC_ACCESS_UNLOCK, SEC_ACCESS_UNLOCK, 1); + + rc |= fg_masked_write(chip, chip->soc_base + EXT_OFFSET_TRIM_REG, + 0xFF, reg, 1); + if (rc) { + pr_err("Failed to write ext sense offset trim, rc = %d\n", rc); + return rc; + } + + return 0; +} + +#define FG_ALG_SYSCTL_1 0x4B0 +#define SOC_CNFG 0x450 +#define SOC_DELTA_OFFSET 3 +#define DELTA_SOC_PERCENT 1 +#define I_TERM_QUAL_BIT BIT(1) +#define PATCH_NEG_CURRENT_BIT BIT(3) +#define KI_COEFF_PRED_FULL_ADDR 0x408 +#define KI_COEFF_PRED_FULL_4_0_MSB 0x88 +#define KI_COEFF_PRED_FULL_4_0_LSB 0x00 +#define TEMP_FRAC_SHIFT_REG 0x4A4 +#define FG_ADC_CONFIG_REG 0x4B8 +#define FG_BCL_CONFIG_OFFSET 0x3 +#define BCL_FORCED_HPM_IN_CHARGE BIT(2) +static int fg_common_hw_init(struct fg_chip *chip) +{ + int rc; + int resume_soc_raw; + u8 val; + + update_iterm(chip); + update_cutoff_voltage(chip); + update_irq_volt_empty(chip); + update_bcl_thresholds(chip); + + resume_soc_raw = settings[FG_MEM_RESUME_SOC].value; + if (resume_soc_raw > 0) { + rc = fg_set_resume_soc(chip, resume_soc_raw); + if (rc) { + pr_err("Couldn't set resume SOC for FG\n"); + return rc; + } + } else { + pr_info("FG auto recharge threshold not specified in DT\n"); + } + + if (fg_sense_type >= 0) { + rc = set_prop_sense_type(chip, fg_sense_type); + if (rc) { + pr_err("failed to config sense type %d rc=%d\n", + fg_sense_type, rc); + return rc; + } + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_DELTA_SOC].address, 0xFF, + soc_to_setpoint(settings[FG_MEM_DELTA_SOC].value), + settings[FG_MEM_DELTA_SOC].offset); + if (rc) { + pr_err("failed to write delta soc rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_BATT_LOW].address, 0xFF, + batt_to_setpoint_8b(settings[FG_MEM_BATT_LOW].value), + settings[FG_MEM_BATT_LOW].offset); + if (rc) { + pr_err("failed to write Vbatt_low rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, settings[FG_MEM_THERM_DELAY].address, + THERM_DELAY_MASK, + therm_delay_to_setpoint(settings[FG_MEM_THERM_DELAY].value), + settings[FG_MEM_THERM_DELAY].offset); + if (rc) { + pr_err("failed to write therm_delay rc=%d\n", rc); + return rc; + } + + if (chip->use_thermal_coefficients) { + fg_mem_write(chip, chip->thermal_coefficients, + THERMAL_COEFF_ADDR, THERMAL_COEFF_N_BYTES, + THERMAL_COEFF_OFFSET, 0); + } + + if (!chip->sw_rbias_ctrl) { + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + BATT_TEMP_CNTRL_MASK, + TEMP_SENSE_ALWAYS_BIT, + BATT_TEMP_OFFSET); + if (rc) { + pr_err("failed to write BATT_TEMP_OFFSET rc=%d\n", rc); + return rc; + } + } + + /* Read the cycle counter back from FG SRAM */ + if (chip->cyc_ctr.en) + restore_cycle_counter(chip); + + if (chip->esr_pulse_tune_en) { + rc = fg_mem_read(chip, &val, SYS_CFG_1_REG, 1, SYS_CFG_1_OFFSET, + 0); + if (rc) { + pr_err("unable to read sys_cfg_1: %d\n", rc); + return rc; + } + + if (!(val & ENABLE_ESR_PULSE_VAL)) + chip->esr_extract_disabled = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("ESR extract is %sabled\n", + chip->esr_extract_disabled ? "dis" : "en"); + + rc = fg_mem_read(chip, &val, CBITS_INPUT_FILTER_REG, 1, + CBITS_RMEAS1_OFFSET, 0); + if (rc) { + pr_err("unable to read cbits_input_filter_reg: %d\n", + rc); + return rc; + } + + if (val & (IMPTR_FAST_TIME_SHIFT | IMPTR_LONG_TIME_SHIFT)) + chip->imptr_pulse_slow_en = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("imptr_pulse_slow is %sabled\n", + chip->imptr_pulse_slow_en ? "en" : "dis"); + + rc = fg_mem_read(chip, &val, RSLOW_CFG_REG, 1, RSLOW_CFG_OFFSET, + 0); + if (rc) { + pr_err("unable to read rslow cfg: %d\n", rc); + return rc; + } + + if (val & RSLOW_CFG_ON_VAL) + chip->rslow_comp.active = true; + + if (fg_debug_mask & FG_STATUS) + pr_info("rslow_comp active is %sabled\n", + chip->rslow_comp.active ? "en" : "dis"); + } + + return 0; +} + +static int fg_8994_hw_init(struct fg_chip *chip) +{ + int rc = 0; + u8 data[4]; + u64 esr_value; + + rc = fg_mem_masked_write(chip, EXTERNAL_SENSE_SELECT, + PATCH_NEG_CURRENT_BIT, + PATCH_NEG_CURRENT_BIT, + EXTERNAL_SENSE_OFFSET); + if (rc) { + pr_err("failed to write patch current bit rc=%d\n", rc); + return rc; + } + + rc = bcl_trim_workaround(chip); + if (rc) { + pr_err("failed to redo bcl trim rc=%d\n", rc); + return rc; + } + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) { + pr_err("failed to force hpm in charge rc=%d\n", rc); + return rc; + } + + fg_mem_masked_write(chip, FG_ALG_SYSCTL_1, I_TERM_QUAL_BIT, 0, 0); + + data[0] = 0xA2; + data[1] = 0x12; + + rc = fg_mem_write(chip, data, TEMP_FRAC_SHIFT_REG, 2, 2, 0); + if (rc) { + pr_err("failed to write temp ocv constants rc=%d\n", rc); + return rc; + } + + data[0] = KI_COEFF_PRED_FULL_4_0_LSB; + data[1] = KI_COEFF_PRED_FULL_4_0_MSB; + fg_mem_write(chip, data, KI_COEFF_PRED_FULL_ADDR, 2, 2, 0); + + esr_value = ESR_DEFAULT_VALUE; + rc = fg_mem_write(chip, (u8 *)&esr_value, MAXRSCHANGE_REG, 8, + ESR_VALUE_OFFSET, 0); + if (rc) + pr_err("failed to write default ESR value rc=%d\n", rc); + else + pr_debug("set default value to esr filter\n"); + + return 0; +} + +#define FG_USBID_CONFIG_OFFSET 0x2 +#define DISABLE_USBID_DETECT_BIT BIT(0) +static int fg_8996_hw_init(struct fg_chip *chip) +{ + int rc; + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) { + pr_err("failed to force hpm in charge rc=%d\n", rc); + return rc; + } + + /* enable usbid conversions for PMi8996 V1.0 */ + if (chip->pmic_revision[REVID_DIG_MAJOR] == 1 + && chip->pmic_revision[REVID_ANA_MAJOR] == 0) { + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + DISABLE_USBID_DETECT_BIT, + 0, FG_USBID_CONFIG_OFFSET); + if (rc) { + pr_err("failed to enable usbid conversions: %d\n", rc); + return rc; + } + } + + return rc; +} + +static int fg_8950_hw_init(struct fg_chip *chip) +{ + int rc; + + rc = fg_mem_masked_write(chip, FG_ADC_CONFIG_REG, + BCL_FORCED_HPM_IN_CHARGE, + BCL_FORCED_HPM_IN_CHARGE, + FG_BCL_CONFIG_OFFSET); + if (rc) + pr_err("failed to force hpm in charge rc=%d\n", rc); + + return rc; +} + +static int fg_hw_init(struct fg_chip *chip) +{ + int rc = 0; + + rc = fg_common_hw_init(chip); + if (rc) { + pr_err("Unable to initialize FG HW rc=%d\n", rc); + return rc; + } + + /* add PMIC specific hw init */ + switch (chip->pmic_subtype) { + case PMI8994: + rc = fg_8994_hw_init(chip); + chip->wa_flag |= PULSE_REQUEST_WA; + break; + case PMI8996: + rc = fg_8996_hw_init(chip); + /* Setup workaround flag based on PMIC type */ + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + chip->wa_flag |= IADC_GAIN_COMP_WA; + if (chip->pmic_revision[REVID_DIG_MAJOR] > 1) + chip->wa_flag |= USE_CC_SOC_REG; + + break; + case PMI8950: + case PMI8937: + rc = fg_8950_hw_init(chip); + /* Setup workaround flag based on PMIC type */ + chip->wa_flag |= BCL_HI_POWER_FOR_CHGLED_WA; + if (fg_sense_type == INTERNAL_CURRENT_SENSE) + chip->wa_flag |= IADC_GAIN_COMP_WA; + if (chip->pmic_revision[REVID_DIG_MAJOR] > 1) + chip->wa_flag |= USE_CC_SOC_REG; + + break; + } + if (rc) + pr_err("Unable to initialize PMIC specific FG HW rc=%d\n", rc); + + pr_debug("wa_flag=0x%x\n", chip->wa_flag); + + return rc; +} + +#define DIG_MINOR 0x0 +#define DIG_MAJOR 0x1 +#define ANA_MINOR 0x2 +#define ANA_MAJOR 0x3 +#define IACS_INTR_SRC_SLCT BIT(3) +static int fg_setup_memif_offset(struct fg_chip *chip) +{ + int rc; + + rc = fg_read(chip, chip->revision, chip->mem_base + DIG_MINOR, 4); + if (rc) { + pr_err("Unable to read FG revision rc=%d\n", rc); + return rc; + } + + switch (chip->revision[DIG_MAJOR]) { + case DIG_REV_1: + case DIG_REV_2: + chip->offset = offset[0].address; + break; + case DIG_REV_3: + chip->offset = offset[1].address; + chip->ima_supported = true; + break; + default: + pr_err("Digital Major rev=%d not supported\n", + chip->revision[DIG_MAJOR]); + return -EINVAL; + } + + if (chip->ima_supported) { + /* + * Change the FG_MEM_INT interrupt to track IACS_READY + * condition instead of end-of-transaction. This makes sure + * that the next transaction starts only after the hw is ready. + */ + rc = fg_masked_write(chip, + chip->mem_base + MEM_INTF_IMA_CFG, IACS_INTR_SRC_SLCT, + IACS_INTR_SRC_SLCT, 1); + if (rc) { + pr_err("failed to configure interrupt source %d\n", rc); + return rc; + } + } + + return 0; +} + +static int fg_detect_pmic_type(struct fg_chip *chip) +{ + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + + revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR_OR_NULL(pmic_rev_id)) { + pr_err("Unable to get pmic_revid rc=%ld\n", + PTR_ERR(pmic_rev_id)); + /* + * the revid peripheral must be registered, any failure + * here only indicates that the rev-id module has not + * probed yet. + */ + return -EPROBE_DEFER; + } + + switch (pmic_rev_id->pmic_subtype) { + case PMI8994: + case PMI8950: + case PMI8937: + case PMI8996: + chip->pmic_subtype = pmic_rev_id->pmic_subtype; + chip->pmic_revision[REVID_RESERVED] = pmic_rev_id->rev1; + chip->pmic_revision[REVID_VARIANT] = pmic_rev_id->rev2; + chip->pmic_revision[REVID_ANA_MAJOR] = pmic_rev_id->rev3; + chip->pmic_revision[REVID_DIG_MAJOR] = pmic_rev_id->rev4; + break; + default: + pr_err("PMIC subtype %d not supported\n", + pmic_rev_id->pmic_subtype); + return -EINVAL; + } + + return 0; +} + +#define INIT_JEITA_DELAY_MS 1000 + +static void delayed_init_work(struct work_struct *work) +{ + u8 reg[2]; + int rc; + struct fg_chip *chip = container_of(work, + struct fg_chip, + init_work); + + /* hold memory access until initialization finishes */ + fg_mem_lock(chip); + + rc = fg_hw_init(chip); + if (rc) { + pr_err("failed to hw init rc = %d\n", rc); + fg_mem_release(chip); + fg_cleanup(chip); + return; + } + /* release memory access before update_sram_data is called */ + fg_mem_release(chip); + + schedule_delayed_work( + &chip->update_jeita_setting, + msecs_to_jiffies(INIT_JEITA_DELAY_MS)); + + if (chip->last_sram_update_time == 0) + update_sram_data_work(&chip->update_sram_data.work); + + if (chip->last_temp_update_time == 0) + update_temp_data(&chip->update_temp_work.work); + + if (!chip->use_otp_profile) + schedule_delayed_work(&chip->batt_profile_init, 0); + + if (chip->wa_flag & IADC_GAIN_COMP_WA) { + /* read default gain config */ + rc = fg_mem_read(chip, reg, K_VCOR_REG, 2, DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read default gain rc=%d\n", rc); + goto done; + } + + if (reg[1] || reg[0]) { + /* + * Default gain register has valid value: + * - write to gain register. + */ + rc = fg_mem_write(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write gain rc=%d\n", rc); + goto done; + } + } else { + /* + * Default gain register is invalid: + * - read gain register for default gain value + * - write to default gain register. + */ + rc = fg_mem_read(chip, reg, GAIN_REG, 2, + GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to read gain rc=%d\n", rc); + goto done; + } + rc = fg_mem_write(chip, reg, K_VCOR_REG, 2, + DEF_GAIN_OFFSET, 0); + if (rc) { + pr_err("Failed to write default gain rc=%d\n", + rc); + goto done; + } + } + + chip->iadc_comp_data.dfl_gain_reg[0] = reg[0]; + chip->iadc_comp_data.dfl_gain_reg[1] = reg[1]; + chip->iadc_comp_data.dfl_gain = half_float(reg); + chip->input_present = is_input_present(chip); + chip->otg_present = is_otg_present(chip); + chip->init_done = true; + + pr_debug("IADC gain initial config reg_val 0x%x%x gain %lld\n", + reg[1], reg[0], chip->iadc_comp_data.dfl_gain); + } + + pr_debug("FG: HW_init success\n"); + + return; +done: + fg_cleanup(chip); +} + +static int fg_probe(struct platform_device *pdev) +{ + struct device *dev = &(pdev->dev); + struct fg_chip *chip; + struct device_node *child; + unsigned int base; + u8 subtype, reg; + int rc = 0; + struct power_supply_config bms_psy_cfg; + + if (!pdev) { + pr_err("no valid spmi pointer\n"); + return -ENODEV; + } + + if (!pdev->dev.of_node) { + pr_err("device node missing\n"); + return -ENODEV; + } + + chip = devm_kzalloc(dev, sizeof(struct fg_chip), GFP_KERNEL); + if (chip == NULL) { + pr_err("Can't allocate fg_chip\n"); + return -ENOMEM; + } + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->pdev = pdev; + chip->dev = &(pdev->dev); + + wakeup_source_init(&chip->empty_check_wakeup_source.source, + "qpnp_fg_empty_check"); + wakeup_source_init(&chip->memif_wakeup_source.source, + "qpnp_fg_memaccess"); + wakeup_source_init(&chip->profile_wakeup_source.source, + "qpnp_fg_profile"); + wakeup_source_init(&chip->update_temp_wakeup_source.source, + "qpnp_fg_update_temp"); + wakeup_source_init(&chip->update_sram_wakeup_source.source, + "qpnp_fg_update_sram"); + wakeup_source_init(&chip->resume_soc_wakeup_source.source, + "qpnp_fg_set_resume_soc"); + wakeup_source_init(&chip->gain_comp_wakeup_source.source, + "qpnp_fg_gain_comp"); + wakeup_source_init(&chip->capacity_learning_wakeup_source.source, + "qpnp_fg_cap_learning"); + wakeup_source_init(&chip->esr_extract_wakeup_source.source, + "qpnp_fg_esr_extract"); + mutex_init(&chip->rw_lock); + mutex_init(&chip->cyc_ctr.lock); + mutex_init(&chip->learning_data.learning_lock); + mutex_init(&chip->rslow_comp.lock); + mutex_init(&chip->sysfs_restart_lock); + INIT_DELAYED_WORK(&chip->update_jeita_setting, update_jeita_setting); + INIT_DELAYED_WORK(&chip->update_sram_data, update_sram_data_work); + INIT_DELAYED_WORK(&chip->update_temp_work, update_temp_data); + INIT_DELAYED_WORK(&chip->check_empty_work, check_empty_work); + INIT_DELAYED_WORK(&chip->batt_profile_init, batt_profile_init); + INIT_WORK(&chip->rslow_comp_work, rslow_comp_work); + INIT_WORK(&chip->fg_cap_learning_work, fg_cap_learning_work); + INIT_WORK(&chip->dump_sram, dump_sram); + INIT_WORK(&chip->status_change_work, status_change_work); + INIT_WORK(&chip->cycle_count_work, update_cycle_count); + INIT_WORK(&chip->battery_age_work, battery_age_work); + INIT_WORK(&chip->update_esr_work, update_esr_value); + INIT_WORK(&chip->set_resume_soc_work, set_resume_soc_work); + INIT_WORK(&chip->sysfs_restart_work, sysfs_restart_work); + INIT_WORK(&chip->init_work, delayed_init_work); + INIT_WORK(&chip->charge_full_work, charge_full_work); + INIT_WORK(&chip->gain_comp_work, iadc_gain_comp_work); + INIT_WORK(&chip->bcl_hi_power_work, bcl_hi_power_work); + INIT_WORK(&chip->esr_extract_config_work, esr_extract_config_work); + alarm_init(&chip->fg_cap_learning_alarm, ALARM_BOOTTIME, + fg_cap_learning_alarm_cb); + init_completion(&chip->sram_access_granted); + init_completion(&chip->sram_access_revoked); + complete_all(&chip->sram_access_revoked); + init_completion(&chip->batt_id_avail); + init_completion(&chip->first_soc_done); + dev_set_drvdata(&pdev->dev, chip); + + if (of_get_available_child_count(pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + rc = -ENXIO; + goto of_init_fail; + } + + for_each_available_child_of_node(pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + dev_err(&pdev->dev, + "Couldn't find reg in node = %s rc = %d\n", + child->full_name, rc); + goto of_init_fail; + } + + if (strcmp("qcom,fg-adc-vbat", child->name) == 0) { + chip->vbat_adc_addr = base; + continue; + } else if (strcmp("qcom,fg-adc-ibat", child->name) == 0) { + chip->ibat_adc_addr = base; + continue; + } else if (strcmp("qcom,revid-tp-rev", child->name) == 0) { + chip->tp_rev_addr = base; + continue; + } + + rc = fg_read(chip, &subtype, + base + REG_OFFSET_PERP_SUBTYPE, 1); + if (rc) { + pr_err("Peripheral subtype read failed rc=%d\n", rc); + goto of_init_fail; + } + + switch (subtype) { + case FG_SOC: + chip->soc_base = base; + break; + case FG_MEMIF: + chip->mem_base = base; + break; + case FG_BATT: + chip->batt_base = base; + break; + default: + pr_err("Invalid peripheral subtype=0x%x\n", subtype); + rc = -EINVAL; + } + } + + rc = fg_detect_pmic_type(chip); + if (rc) { + pr_err("Unable to detect PMIC type rc=%d\n", rc); + return rc; + } + + rc = fg_setup_memif_offset(chip); + if (rc) { + pr_err("Unable to setup mem_if offsets rc=%d\n", rc); + goto of_init_fail; + } + + rc = fg_of_init(chip); + if (rc) { + pr_err("failed to parse devicetree rc%d\n", rc); + goto of_init_fail; + } + + if (chip->jeita_hysteresis_support) { + rc = fg_init_batt_temp_state(chip); + if (rc) { + pr_err("failed to get battery status rc%d\n", rc); + goto of_init_fail; + } + } + + /* check if the first estimate is already finished at this time */ + if (is_first_est_done(chip)) + complete_all(&chip->first_soc_done); + + reg = 0xFF; + rc = fg_write(chip, ®, INT_EN_CLR(chip->mem_base), 1); + if (rc) { + pr_err("failed to clear interrupts %d\n", rc); + goto of_init_fail; + } + + rc = fg_init_irqs(chip); + if (rc) { + pr_err("failed to request interrupts %d\n", rc); + goto cancel_work; + } + + chip->batt_type = default_batt_type; + + chip->bms_psy_d.name = "bms"; + chip->bms_psy_d.type = POWER_SUPPLY_TYPE_BMS; + chip->bms_psy_d.properties = fg_power_props; + chip->bms_psy_d.num_properties = ARRAY_SIZE(fg_power_props); + chip->bms_psy_d.get_property = fg_power_get_property; + chip->bms_psy_d.set_property = fg_power_set_property; + chip->bms_psy_d.external_power_changed = fg_external_power_changed; + chip->bms_psy_d.property_is_writeable = fg_property_is_writeable; + + bms_psy_cfg.drv_data = chip; + bms_psy_cfg.supplied_to = fg_supplicants; + bms_psy_cfg.num_supplicants = ARRAY_SIZE(fg_supplicants); + bms_psy_cfg.of_node = NULL; + chip->bms_psy = devm_power_supply_register(chip->dev, + &chip->bms_psy_d, + &bms_psy_cfg); + if (IS_ERR(chip->bms_psy)) { + pr_err("batt failed to register rc = %ld\n", + PTR_ERR(chip->bms_psy)); + goto of_init_fail; + } + chip->power_supply_registered = true; + /* + * Just initialize the batt_psy_name here. Power supply + * will be obtained later. + */ + chip->batt_psy_name = "battery"; + + if (chip->mem_base) { + rc = fg_dfs_create(chip); + if (rc < 0) { + pr_err("failed to create debugfs rc = %d\n", rc); + goto cancel_work; + } + } + + schedule_work(&chip->init_work); + + pr_info("FG Probe success - FG Revision DIG:%d.%d ANA:%d.%d PMIC subtype=%d\n", + chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR], + chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR], + chip->pmic_subtype); + + return rc; + +cancel_work: + cancel_delayed_work_sync(&chip->update_jeita_setting); + cancel_delayed_work_sync(&chip->update_sram_data); + cancel_delayed_work_sync(&chip->update_temp_work); + cancel_delayed_work_sync(&chip->check_empty_work); + cancel_delayed_work_sync(&chip->batt_profile_init); + alarm_try_to_cancel(&chip->fg_cap_learning_alarm); + cancel_work_sync(&chip->set_resume_soc_work); + cancel_work_sync(&chip->fg_cap_learning_work); + cancel_work_sync(&chip->dump_sram); + cancel_work_sync(&chip->status_change_work); + cancel_work_sync(&chip->cycle_count_work); + cancel_work_sync(&chip->update_esr_work); + cancel_work_sync(&chip->rslow_comp_work); + cancel_work_sync(&chip->sysfs_restart_work); + cancel_work_sync(&chip->gain_comp_work); + cancel_work_sync(&chip->init_work); + cancel_work_sync(&chip->charge_full_work); + cancel_work_sync(&chip->bcl_hi_power_work); + cancel_work_sync(&chip->esr_extract_config_work); +of_init_fail: + mutex_destroy(&chip->rslow_comp.lock); + mutex_destroy(&chip->rw_lock); + mutex_destroy(&chip->cyc_ctr.lock); + mutex_destroy(&chip->learning_data.learning_lock); + mutex_destroy(&chip->sysfs_restart_lock); + wakeup_source_trash(&chip->resume_soc_wakeup_source.source); + wakeup_source_trash(&chip->empty_check_wakeup_source.source); + wakeup_source_trash(&chip->memif_wakeup_source.source); + wakeup_source_trash(&chip->profile_wakeup_source.source); + wakeup_source_trash(&chip->update_temp_wakeup_source.source); + wakeup_source_trash(&chip->update_sram_wakeup_source.source); + wakeup_source_trash(&chip->gain_comp_wakeup_source.source); + wakeup_source_trash(&chip->capacity_learning_wakeup_source.source); + wakeup_source_trash(&chip->esr_extract_wakeup_source.source); + return rc; +} + +static void check_and_update_sram_data(struct fg_chip *chip) +{ + unsigned long current_time = 0, next_update_time, time_left; + + get_current_time(¤t_time); + + next_update_time = chip->last_temp_update_time + + (TEMP_PERIOD_UPDATE_MS / 1000); + + if (next_update_time > current_time) + time_left = next_update_time - current_time; + else + time_left = 0; + + schedule_delayed_work( + &chip->update_temp_work, msecs_to_jiffies(time_left * 1000)); + + next_update_time = chip->last_sram_update_time + + (fg_sram_update_period_ms / 1000); + + if (next_update_time > current_time) + time_left = next_update_time - current_time; + else + time_left = 0; + + schedule_delayed_work( + &chip->update_sram_data, msecs_to_jiffies(time_left * 1000)); +} + +static int fg_suspend(struct device *dev) +{ + struct fg_chip *chip = dev_get_drvdata(dev); + + if (!chip->sw_rbias_ctrl) + return 0; + + cancel_delayed_work(&chip->update_temp_work); + cancel_delayed_work(&chip->update_sram_data); + + return 0; +} + +static int fg_resume(struct device *dev) +{ + struct fg_chip *chip = dev_get_drvdata(dev); + + if (!chip->sw_rbias_ctrl) + return 0; + + check_and_update_sram_data(chip); + return 0; +} + +static const struct dev_pm_ops qpnp_fg_pm_ops = { + .suspend = fg_suspend, + .resume = fg_resume, +}; + +static int fg_sense_type_set(const char *val, const struct kernel_param *kp) +{ + int rc; + struct power_supply *bms_psy; + struct fg_chip *chip; + int old_fg_sense_type = fg_sense_type; + + rc = param_set_int(val, kp); + if (rc) { + pr_err("Unable to set fg_sense_type: %d\n", rc); + return rc; + } + + if (fg_sense_type != 0 && fg_sense_type != 1) { + pr_err("Bad value %d\n", fg_sense_type); + fg_sense_type = old_fg_sense_type; + return -EINVAL; + } + + if (fg_debug_mask & FG_STATUS) + pr_info("fg_sense_type set to %d\n", fg_sense_type); + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return 0; + } + + chip = power_supply_get_drvdata(bms_psy); + rc = set_prop_sense_type(chip, fg_sense_type); + return rc; +} + +static struct kernel_param_ops fg_sense_type_ops = { + .set = fg_sense_type_set, + .get = param_get_int, +}; + +module_param_cb(sense_type, &fg_sense_type_ops, &fg_sense_type, 0644); + +static int fg_restart_set(const char *val, const struct kernel_param *kp) +{ + struct power_supply *bms_psy; + struct fg_chip *chip; + + bms_psy = power_supply_get_by_name("bms"); + if (!bms_psy) { + pr_err("bms psy not found\n"); + return 0; + } + chip = power_supply_get_drvdata(bms_psy); + + mutex_lock(&chip->sysfs_restart_lock); + if (fg_restart != 0) { + mutex_unlock(&chip->sysfs_restart_lock); + return 0; + } + fg_restart = 1; + mutex_unlock(&chip->sysfs_restart_lock); + + if (fg_debug_mask & FG_STATUS) + pr_info("fuel gauge restart initiated from sysfs...\n"); + + schedule_work(&chip->sysfs_restart_work); + return 0; +} + +static struct kernel_param_ops fg_restart_ops = { + .set = fg_restart_set, + .get = param_get_int, +}; + +module_param_cb(restart, &fg_restart_ops, &fg_restart, 0644); + +static struct platform_driver fg_driver = { + .driver = { + .name = QPNP_FG_DEV_NAME, + .of_match_table = fg_match_table, + .pm = &qpnp_fg_pm_ops, + }, + .probe = fg_probe, + .remove = fg_remove, +}; + +static int __init fg_init(void) +{ + return platform_driver_register(&fg_driver); +} + +static void __exit fg_exit(void) +{ + return platform_driver_unregister(&fg_driver); +} + +module_init(fg_init); +module_exit(fg_exit); + +MODULE_DESCRIPTION("QPNP Fuel Gauge Driver"); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:" QPNP_FG_DEV_NAME); diff --git a/drivers/power/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, ®, chip->usb_chgpth_base + PWR_PATH, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read PWR_PATH rc = %d\n", rc); + return PWR_PATH_BATTERY; + } + + return reg & PWR_PATH_MASK; +} + +#define RID_STS 0xB +#define RID_MASK 0xF +#define IDEV_STS 0x8 +#define RT_STS 0x10 +#define USBID_MSB 0xE +#define USBIN_UV_BIT BIT(0) +#define USBIN_OV_BIT BIT(1) +#define USBIN_SRC_DET_BIT BIT(2) +#define FMB_STS_MASK SMB_MASK(3, 0) +#define USBID_GND_THRESHOLD 0x495 +static bool is_otg_present_schg(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + u8 usbid_reg[2]; + u16 usbid_val; + /* + * After the falling edge of the usbid change interrupt occurs, + * there may still be some time before the ADC conversion for USB RID + * finishes in the fuel gauge. In the worst case, this could be up to + * 15 ms. + * + * Sleep for 20 ms (minimum msleep time) to wait for the conversion to + * finish and the USB RID status register to be updated before trying + * to detect OTG insertions. + */ + + msleep(20); + + /* + * There is a problem with USBID conversions on PMI8994 revisions + * 2.0.0. As a workaround, check that the cable is not + * detected as factory test before enabling OTG. + */ + rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read IDEV_STS rc = %d\n", rc); + return false; + } + + if ((reg & FMB_STS_MASK) != 0) { + pr_smb(PR_STATUS, "IDEV_STS = %02x, not ground\n", reg); + return false; + } + + rc = smbchg_read(chip, usbid_reg, chip->usb_chgpth_base + USBID_MSB, 2); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read USBID rc = %d\n", rc); + return false; + } + usbid_val = (usbid_reg[0] << 8) | usbid_reg[1]; + + if (usbid_val > USBID_GND_THRESHOLD) { + pr_smb(PR_STATUS, "USBID = 0x%04x, too high to be ground\n", + usbid_val); + return false; + } + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RID_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read usb rid status rc = %d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "RID_STS = %02x\n", reg); + + return (reg & RID_MASK) == 0; +} + +#define RID_GND_DET_STS BIT(2) +static bool is_otg_present_schg_lite(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->otg_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read otg RT status rc = %d\n", rc); + return false; + } + + return !!(reg & RID_GND_DET_STS); +} + +static bool is_otg_present(struct smbchg_chip *chip) +{ + if (chip->schg_version == QPNP_SCHG_LITE) + return is_otg_present_schg_lite(chip); + + return is_otg_present_schg(chip); +} + +#define USBIN_9V BIT(5) +#define USBIN_UNREG BIT(4) +#define USBIN_LV BIT(3) +#define DCIN_9V BIT(2) +#define DCIN_UNREG BIT(1) +#define DCIN_LV BIT(0) +#define INPUT_STS 0x0D +#define DCIN_UV_BIT BIT(0) +#define DCIN_OV_BIT BIT(1) +static bool is_dc_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->dc_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read dc status rc = %d\n", rc); + return false; + } + + if ((reg & DCIN_UV_BIT) || (reg & DCIN_OV_BIT)) + return false; + + return true; +} + +static bool is_usb_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + if (!(reg & USBIN_SRC_DET_BIT) || (reg & USBIN_OV_BIT)) + return false; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + INPUT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); + return false; + } + + return !!(reg & (USBIN_9V | USBIN_UNREG | USBIN_LV)); +} + +static char *usb_type_str[] = { + "SDP", /* bit 0 */ + "OTHER", /* bit 1 */ + "DCP", /* bit 2 */ + "CDP", /* bit 3 */ + "NONE", /* bit 4 error case */ +}; + +#define N_TYPE_BITS 4 +#define TYPE_BITS_OFFSET 4 + +static int get_type(u8 type_reg) +{ + unsigned long type = type_reg; + type >>= TYPE_BITS_OFFSET; + return find_first_bit(&type, N_TYPE_BITS); +} + +/* helper to return the string of USB type */ +static inline char *get_usb_type_name(int type) +{ + return usb_type_str[type]; +} + +static enum power_supply_type usb_type_enum[] = { + POWER_SUPPLY_TYPE_USB, /* bit 0 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 1 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 2 */ + POWER_SUPPLY_TYPE_USB_CDP, /* bit 3 */ + POWER_SUPPLY_TYPE_USB_DCP, /* bit 4 error case, report DCP */ +}; + +/* helper to return enum power_supply_type of USB type */ +static inline enum power_supply_type get_usb_supply_type(int type) +{ + return usb_type_enum[type]; +} + +static bool is_src_detect_high(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + return reg &= USBIN_SRC_DET_BIT; +} + +static void read_usb_type(struct smbchg_chip *chip, char **usb_type_name, + enum power_supply_type *usb_supply_type) +{ + int rc, type; + u8 reg; + + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low\n"); + *usb_type_name = "Absent"; + *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + return; + } + + rc = smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read status 5 rc = %d\n", rc); + *usb_type_name = "Other"; + *usb_supply_type = POWER_SUPPLY_TYPE_UNKNOWN; + return; + } + type = get_type(reg); + *usb_type_name = get_usb_type_name(type); + *usb_supply_type = get_usb_supply_type(type); +} + +#define CHGR_STS 0x0E +#define BATT_LESS_THAN_2V BIT(4) +#define CHG_HOLD_OFF_BIT BIT(3) +#define CHG_TYPE_MASK SMB_MASK(2, 1) +#define CHG_TYPE_SHIFT 1 +#define BATT_NOT_CHG_VAL 0x0 +#define BATT_PRE_CHG_VAL 0x1 +#define BATT_FAST_CHG_VAL 0x2 +#define BATT_TAPER_CHG_VAL 0x3 +#define CHG_INHIBIT_BIT BIT(1) +#define BAT_TCC_REACHED_BIT BIT(7) +static int get_prop_batt_status(struct smbchg_chip *chip) +{ + int rc, status = POWER_SUPPLY_STATUS_DISCHARGING; + u8 reg = 0, chg_type; + bool charger_present, chg_inhibit; + + charger_present = is_usb_present(chip) | is_dc_present(chip) | + chip->hvdcp_3_det_ignore_uv; + if (!charger_present) + return POWER_SUPPLY_STATUS_DISCHARGING; + + rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + if (reg & BAT_TCC_REACHED_BIT) + return POWER_SUPPLY_STATUS_FULL; + + chg_inhibit = reg & CHG_INHIBIT_BIT; + if (chg_inhibit) + return POWER_SUPPLY_STATUS_FULL; + + rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return POWER_SUPPLY_STATUS_UNKNOWN; + } + + if (reg & CHG_HOLD_OFF_BIT) { + /* + * when chg hold off happens the battery is + * not charging + */ + status = POWER_SUPPLY_STATUS_NOT_CHARGING; + goto out; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + + if (chg_type == BATT_NOT_CHG_VAL && !chip->hvdcp_3_det_ignore_uv) + status = POWER_SUPPLY_STATUS_DISCHARGING; + else + status = POWER_SUPPLY_STATUS_CHARGING; +out: + pr_smb_rt(PR_MISC, "CHGR_STS = 0x%02x\n", reg); + return status; +} + +#define BAT_PRES_STATUS 0x08 +#define BAT_PRES_BIT BIT(7) +static int get_prop_batt_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->bat_if_base + BAT_PRES_STATUS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return 0; + } + + return !!(reg & BAT_PRES_BIT); +} + +static int get_prop_charge_type(struct smbchg_chip *chip) +{ + int rc; + u8 reg, chg_type; + + rc = smbchg_read(chip, ®, chip->chgr_base + CHGR_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read CHGR_STS rc = %d\n", rc); + return 0; + } + + chg_type = (reg & CHG_TYPE_MASK) >> CHG_TYPE_SHIFT; + if (chg_type == BATT_NOT_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_NONE; + else if (chg_type == BATT_TAPER_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TAPER; + else if (chg_type == BATT_FAST_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_FAST; + else if (chg_type == BATT_PRE_CHG_VAL) + return POWER_SUPPLY_CHARGE_TYPE_TRICKLE; + + return POWER_SUPPLY_CHARGE_TYPE_NONE; +} + +static int set_property_on_fg(struct smbchg_chip *chip, + enum power_supply_property prop, int val) +{ + int rc; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + if (!chip->bms_psy) { + pr_smb(PR_STATUS, "no bms psy found\n"); + return -EINVAL; + } + + ret.intval = val; + rc = power_supply_set_property(chip->bms_psy, prop, &ret); + if (rc) + pr_smb(PR_STATUS, + "bms psy does not allow updating prop %d rc = %d\n", + prop, rc); + + return rc; +} + +static int get_property_from_fg(struct smbchg_chip *chip, + enum power_supply_property prop, int *val) +{ + int rc; + union power_supply_propval ret = {0, }; + + if (!chip->bms_psy && chip->bms_psy_name) + chip->bms_psy = + power_supply_get_by_name((char *)chip->bms_psy_name); + if (!chip->bms_psy) { + pr_smb(PR_STATUS, "no bms psy found\n"); + return -EINVAL; + } + + rc = power_supply_get_property(chip->bms_psy, prop, &ret); + if (rc) { + pr_smb(PR_STATUS, + "bms psy doesn't support reading prop %d rc = %d\n", + prop, rc); + return rc; + } + + *val = ret.intval; + return rc; +} + +#define DEFAULT_BATT_CAPACITY 50 +static int get_prop_batt_capacity(struct smbchg_chip *chip) +{ + int capacity, rc; + + if (chip->fake_battery_soc >= 0) + return chip->fake_battery_soc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CAPACITY, &capacity); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get capacity rc = %d\n", rc); + capacity = DEFAULT_BATT_CAPACITY; + } + return capacity; +} + +#define DEFAULT_BATT_TEMP 200 +static int get_prop_batt_temp(struct smbchg_chip *chip) +{ + int temp, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_TEMP, &temp); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get temperature rc = %d\n", rc); + temp = DEFAULT_BATT_TEMP; + } + return temp; +} + +#define DEFAULT_BATT_CURRENT_NOW 0 +static int get_prop_batt_current_now(struct smbchg_chip *chip) +{ + int ua, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, &ua); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get current rc = %d\n", rc); + ua = DEFAULT_BATT_CURRENT_NOW; + } + return ua; +} + +#define DEFAULT_BATT_VOLTAGE_NOW 0 +static int get_prop_batt_voltage_now(struct smbchg_chip *chip) +{ + int uv, rc; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_VOLTAGE_NOW, &uv); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); + uv = DEFAULT_BATT_VOLTAGE_NOW; + } + return uv; +} + +#define DEFAULT_BATT_VOLTAGE_MAX_DESIGN 4200000 +static int get_prop_batt_voltage_max_design(struct smbchg_chip *chip) +{ + int uv, rc; + + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, &uv); + if (rc) { + pr_smb(PR_STATUS, "Couldn't get voltage rc = %d\n", rc); + uv = DEFAULT_BATT_VOLTAGE_MAX_DESIGN; + } + return uv; +} + +static int get_prop_batt_health(struct smbchg_chip *chip) +{ + if (chip->batt_hot) + return POWER_SUPPLY_HEALTH_OVERHEAT; + else if (chip->batt_cold) + return POWER_SUPPLY_HEALTH_COLD; + else if (chip->batt_warm) + return POWER_SUPPLY_HEALTH_WARM; + else if (chip->batt_cool) + return POWER_SUPPLY_HEALTH_COOL; + else + return POWER_SUPPLY_HEALTH_GOOD; +} + +static void get_property_from_typec(struct smbchg_chip *chip, + enum power_supply_property property, + union power_supply_propval *prop) +{ + int rc; + + rc = power_supply_get_property(chip->typec_psy, + property, prop); + if (rc) + pr_smb(PR_TYPEC, + "typec psy doesn't support reading prop %d rc = %d\n", + property, rc); +} + +static void update_typec_status(struct smbchg_chip *chip) +{ + union power_supply_propval type = {0, }; + union power_supply_propval capability = {0, }; + + get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type); + if (type.intval != POWER_SUPPLY_TYPE_UNKNOWN) { + get_property_from_typec(chip, + POWER_SUPPLY_PROP_CURRENT_CAPABILITY, + &capability); + chip->typec_current_ma = capability.intval; + pr_smb(PR_TYPEC, "SMB Type-C mode = %d, current=%d\n", + type.intval, capability.intval); + } else { + pr_smb(PR_TYPEC, + "typec detection not completed continuing with USB update\n"); + } +} + +/* + * finds the index of the closest value in the array. If there are two that + * are equally close, the lower index will be returned + */ +static int find_closest_in_array(const int *arr, int len, int val) +{ + int i, closest = 0; + + if (len == 0) + return closest; + for (i = 0; i < len; i++) + if (abs(val - arr[i]) < abs(val - arr[closest])) + closest = i; + + return closest; +} + +/* finds the index of the closest smaller value in the array. */ +static int find_smaller_in_array(const int *table, int val, int len) +{ + int i; + + for (i = len - 1; i >= 0; i--) { + if (val >= table[i]) + break; + } + + return i; +} + +static const int iterm_ma_table_8994[] = { + 300, + 50, + 100, + 150, + 200, + 250, + 500, + 600 +}; + +static const int iterm_ma_table_8996[] = { + 300, + 50, + 100, + 150, + 200, + 250, + 400, + 500 +}; + +static const int usb_ilim_ma_table_8994[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, + 2050, + 2100, + 2300, + 2400, + 2500, + 3000 +}; + +static const int usb_ilim_ma_table_8996[] = { + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1100, + 1200, + 1300, + 1400, + 1450, + 1500, + 1550, + 1600, + 1700, + 1800, + 1900, + 1950, + 2000, + 2050, + 2100, + 2200, + 2300, + 2400, + 2500, + 2600, + 2700, + 2800, + 2900, + 3000 +}; + +static int dc_ilim_ma_table_8994[] = { + 300, + 400, + 450, + 475, + 500, + 550, + 600, + 650, + 700, + 900, + 950, + 1000, + 1100, + 1200, + 1400, + 1450, + 1500, + 1600, + 1800, + 1850, + 1880, + 1910, + 1930, + 1950, + 1970, + 2000, +}; + +static int dc_ilim_ma_table_8996[] = { + 300, + 400, + 500, + 600, + 700, + 800, + 900, + 1000, + 1100, + 1200, + 1300, + 1400, + 1450, + 1500, + 1550, + 1600, + 1700, + 1800, + 1900, + 1950, + 2000, + 2050, + 2100, + 2200, + 2300, + 2400, +}; + +static const int fcc_comp_table_8994[] = { + 250, + 700, + 900, + 1200, +}; + +static const int fcc_comp_table_8996[] = { + 250, + 1100, + 1200, + 1500, +}; + +static const int aicl_rerun_period[] = { + 45, + 90, + 180, + 360, +}; + +static const int aicl_rerun_period_schg_lite[] = { + 3, /* 2.8s */ + 6, /* 5.6s */ + 11, /* 11.3s */ + 23, /* 22.5s */ + 45, + 90, + 180, + 360, +}; + +static void use_pmi8994_tables(struct smbchg_chip *chip) +{ + chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8994; + chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8994); + chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8994; + chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8994); + chip->tables.iterm_ma_table = iterm_ma_table_8994; + chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8994); + chip->tables.fcc_comp_table = fcc_comp_table_8994; + chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8994); + chip->tables.rchg_thr_mv = 200; + chip->tables.aicl_rerun_period_table = aicl_rerun_period; + chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period); +} + +static void use_pmi8996_tables(struct smbchg_chip *chip) +{ + chip->tables.usb_ilim_ma_table = usb_ilim_ma_table_8996; + chip->tables.usb_ilim_ma_len = ARRAY_SIZE(usb_ilim_ma_table_8996); + chip->tables.dc_ilim_ma_table = dc_ilim_ma_table_8996; + chip->tables.dc_ilim_ma_len = ARRAY_SIZE(dc_ilim_ma_table_8996); + chip->tables.iterm_ma_table = iterm_ma_table_8996; + chip->tables.iterm_ma_len = ARRAY_SIZE(iterm_ma_table_8996); + chip->tables.fcc_comp_table = fcc_comp_table_8996; + chip->tables.fcc_comp_len = ARRAY_SIZE(fcc_comp_table_8996); + chip->tables.rchg_thr_mv = 150; + chip->tables.aicl_rerun_period_table = aicl_rerun_period; + chip->tables.aicl_rerun_period_len = ARRAY_SIZE(aicl_rerun_period); +} + +#define CMD_CHG_REG 0x42 +#define EN_BAT_CHG_BIT BIT(1) +static int smbchg_charging_en(struct smbchg_chip *chip, bool en) +{ + /* The en bit is configured active low */ + return smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + EN_BAT_CHG_BIT, en ? 0 : EN_BAT_CHG_BIT); +} + +#define CMD_IL 0x40 +#define USBIN_SUSPEND_BIT BIT(4) +#define CURRENT_100_MA 100 +#define CURRENT_150_MA 150 +#define CURRENT_500_MA 500 +#define CURRENT_900_MA 900 +#define CURRENT_1500_MA 1500 +#define SUSPEND_CURRENT_MA 2 +#define ICL_OVERRIDE_BIT BIT(2) +static int smbchg_usb_suspend(struct smbchg_chip *chip, bool suspend) +{ + int rc; + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_SUSPEND_BIT, suspend ? USBIN_SUSPEND_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set usb suspend rc = %d\n", rc); + return rc; +} + +#define DCIN_SUSPEND_BIT BIT(3) +static int smbchg_dc_suspend(struct smbchg_chip *chip, bool suspend) +{ + int rc = 0; + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + DCIN_SUSPEND_BIT, suspend ? DCIN_SUSPEND_BIT : 0); + if (rc < 0) + dev_err(chip->dev, "Couldn't set dc suspend rc = %d\n", rc); + return rc; +} + +#define IL_CFG 0xF2 +#define DCIN_INPUT_MASK SMB_MASK(4, 0) +static int smbchg_set_dc_current_max(struct smbchg_chip *chip, int current_ma) +{ + int i; + u8 dc_cur_val; + + i = find_smaller_in_array(chip->tables.dc_ilim_ma_table, + current_ma, chip->tables.dc_ilim_ma_len); + + if (i < 0) { + dev_err(chip->dev, "Cannot find %dma current_table\n", + current_ma); + return -EINVAL; + } + + chip->dc_max_current_ma = chip->tables.dc_ilim_ma_table[i]; + dc_cur_val = i & DCIN_INPUT_MASK; + + pr_smb(PR_STATUS, "dc current set to %d mA\n", + chip->dc_max_current_ma); + return smbchg_sec_masked_write(chip, chip->dc_chgpth_base + IL_CFG, + DCIN_INPUT_MASK, dc_cur_val); +} + +#define AICL_WL_SEL_CFG 0xF5 +#define AICL_WL_SEL_MASK SMB_MASK(1, 0) +#define AICL_WL_SEL_SCHG_LITE_MASK SMB_MASK(2, 0) +static int smbchg_set_aicl_rerun_period_s(struct smbchg_chip *chip, + int period_s) +{ + int i; + u8 reg, mask; + + i = find_smaller_in_array(chip->tables.aicl_rerun_period_table, + period_s, chip->tables.aicl_rerun_period_len); + + if (i < 0) { + dev_err(chip->dev, "Cannot find %ds in aicl rerun period\n", + period_s); + return -EINVAL; + } + + if (chip->schg_version == QPNP_SCHG_LITE) + mask = AICL_WL_SEL_SCHG_LITE_MASK; + else + mask = AICL_WL_SEL_MASK; + + reg = i & mask; + + pr_smb(PR_STATUS, "aicl rerun period set to %ds\n", + chip->tables.aicl_rerun_period_table[i]); + return smbchg_sec_masked_write(chip, + chip->dc_chgpth_base + AICL_WL_SEL_CFG, + mask, reg); +} + +static struct power_supply *get_parallel_psy(struct smbchg_chip *chip) +{ + if (!chip->parallel.avail) + return NULL; + if (chip->parallel.psy) + return chip->parallel.psy; + chip->parallel.psy = power_supply_get_by_name("usb-parallel"); + if (!chip->parallel.psy) + pr_smb(PR_STATUS, "parallel charger not found\n"); + return chip->parallel.psy; +} + +static void smbchg_usb_update_online_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + usb_set_online_work); + bool user_enabled = !get_client_vote(chip->usb_suspend_votable, + USER_EN_VOTER); + int online; + + online = user_enabled && chip->usb_present && !chip->very_weak_charger; + + mutex_lock(&chip->usb_set_online_lock); + if (chip->usb_online != online) { + pr_smb(PR_MISC, "setting usb psy online = %d\n", online); + chip->usb_online = online; + power_supply_changed(chip->usb_psy); + } + mutex_unlock(&chip->usb_set_online_lock); +} + +#define CHGPTH_CFG 0xF4 +#define CFG_USB_2_3_SEL_BIT BIT(7) +#define CFG_USB_2 0 +#define CFG_USB_3 BIT(7) +#define USBIN_INPUT_MASK SMB_MASK(4, 0) +#define USBIN_MODE_CHG_BIT BIT(0) +#define USBIN_LIMITED_MODE 0 +#define USBIN_HC_MODE BIT(0) +#define USB51_MODE_BIT BIT(1) +#define USB51_100MA 0 +#define USB51_500MA BIT(1) +static int smbchg_set_high_usb_chg_current(struct smbchg_chip *chip, + int current_ma) +{ + int i, rc; + u8 usb_cur_val; + + if (current_ma == CURRENT_100_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CFG_USB_2 rc=%d\n", rc); + return rc; + } + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT | ICL_OVERRIDE_BIT, + USBIN_LIMITED_MODE | USB51_100MA | ICL_OVERRIDE_BIT); + if (rc < 0) { + pr_err("Couldn't set ICL_OVERRIDE rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, + "Forcing 100mA current limit\n"); + chip->usb_max_current_ma = CURRENT_100_MA; + return rc; + } + + i = find_smaller_in_array(chip->tables.usb_ilim_ma_table, + current_ma, chip->tables.usb_ilim_ma_len); + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_150_MA); + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + rc |= smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_150_MA, rc); + else + chip->usb_max_current_ma = 150; + return rc; + } + + usb_cur_val = i & USBIN_INPUT_MASK; + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + IL_CFG, + USBIN_INPUT_MASK, usb_cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to config c rc = %d\n", rc); + return rc; + } + + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT, USBIN_HC_MODE); + if (rc < 0) + dev_err(chip->dev, "Couldn't write cfg 5 rc = %d\n", rc); + chip->usb_max_current_ma = chip->tables.usb_ilim_ma_table[i]; + return rc; +} + +/* if APSD results are used + * if SDP is detected it will look at 500mA setting + * if set it will draw 500mA + * if unset it will draw 100mA + * if CDP/DCP it will look at 0x0C setting + * i.e. values in 0x41[1, 0] does not matter + */ +static int smbchg_set_usb_current_max(struct smbchg_chip *chip, + int current_ma) +{ + int rc = 0; + + /* + * if the battery is not present, do not allow the usb ICL to lower in + * order to avoid browning out the device during a hotswap. + */ + if (!chip->batt_present && current_ma < chip->usb_max_current_ma) { + pr_info_ratelimited("Ignoring usb current->%d, battery is absent\n", + current_ma); + return 0; + } + pr_smb(PR_STATUS, "USB current_ma = %d\n", current_ma); + + if (current_ma <= SUSPEND_CURRENT_MA) { + /* suspend the usb if current <= 2mA */ + rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, true, 0); + chip->usb_max_current_ma = 0; + goto out; + } else { + rc = vote(chip->usb_suspend_votable, USB_EN_VOTER, false, 0); + } + + switch (chip->usb_supply_type) { + case POWER_SUPPLY_TYPE_USB: + if ((current_ma < CURRENT_150_MA) && + (chip->wa_flags & SMBCHG_USB100_WA)) + current_ma = CURRENT_150_MA; + + if (current_ma < CURRENT_150_MA) { + /* force 100mA */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 100; + } + /* specific current values */ + if (current_ma == CURRENT_150_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_100MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 150; + } + if (current_ma == CURRENT_500_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_2); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_500MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 500; + } + if (current_ma == CURRENT_900_MA) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + CFG_USB_2_3_SEL_BIT, CFG_USB_3); + if (rc < 0) { + pr_err("Couldn't set CHGPTH_CFG rc = %d\n", rc); + goto out; + } + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + USBIN_MODE_CHG_BIT | USB51_MODE_BIT, + USBIN_LIMITED_MODE | USB51_500MA); + if (rc < 0) { + pr_err("Couldn't set CMD_IL rc = %d\n", rc); + goto out; + } + chip->usb_max_current_ma = 900; + } + break; + case POWER_SUPPLY_TYPE_USB_CDP: + if (current_ma < CURRENT_1500_MA) { + /* use override for CDP */ + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + CMD_IL, + ICL_OVERRIDE_BIT, ICL_OVERRIDE_BIT); + if (rc < 0) + pr_err("Couldn't set override rc = %d\n", rc); + } + /* fall through */ + default: + rc = smbchg_set_high_usb_chg_current(chip, current_ma); + if (rc < 0) + pr_err("Couldn't set %dmA rc = %d\n", current_ma, rc); + break; + } + +out: + pr_smb(PR_STATUS, "usb type = %d current set to %d mA\n", + chip->usb_supply_type, chip->usb_max_current_ma); + return rc; +} + +#define USBIN_HVDCP_STS 0x0C +#define USBIN_HVDCP_SEL_BIT BIT(4) +#define USBIN_HVDCP_SEL_9V_BIT BIT(1) +#define SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT BIT(2) +#define SCHG_LITE_USBIN_HVDCP_SEL_BIT BIT(0) +static int smbchg_get_min_parallel_current_ma(struct smbchg_chip *chip) +{ + int rc; + u8 reg, hvdcp_sel, hvdcp_sel_9v; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb status rc = %d\n", rc); + return 0; + } + if (chip->schg_version == QPNP_SCHG_LITE) { + hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT; + hvdcp_sel_9v = SCHG_LITE_USBIN_HVDCP_SEL_9V_BIT; + } else { + hvdcp_sel = USBIN_HVDCP_SEL_BIT; + hvdcp_sel_9v = USBIN_HVDCP_SEL_9V_BIT; + } + + if ((reg & hvdcp_sel) && (reg & hvdcp_sel_9v)) + return chip->parallel.min_9v_current_thr_ma; + return chip->parallel.min_current_thr_ma; +} + +static bool is_hvdcp_present(struct smbchg_chip *chip) +{ + int rc; + u8 reg, hvdcp_sel; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc < 0) { + pr_err("Couldn't read hvdcp status rc = %d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "HVDCP_STS = 0x%02x\n", reg); + /* + * If a valid HVDCP is detected, notify it to the usb_psy only + * if USB is still present. + */ + if (chip->schg_version == QPNP_SCHG_LITE) + hvdcp_sel = SCHG_LITE_USBIN_HVDCP_SEL_BIT; + else + hvdcp_sel = USBIN_HVDCP_SEL_BIT; + + if ((reg & hvdcp_sel) && is_usb_present(chip)) + return true; + + return false; +} + +#define FCC_CFG 0xF2 +#define FCC_500MA_VAL 0x4 +#define FCC_MASK SMB_MASK(4, 0) +static int smbchg_set_fastchg_current_raw(struct smbchg_chip *chip, + int current_ma) +{ + int i, rc; + u8 cur_val; + + /* the fcc enumerations are the same as the usb currents */ + i = find_smaller_in_array(chip->tables.usb_ilim_ma_table, + current_ma, chip->tables.usb_ilim_ma_len); + if (i < 0) { + dev_err(chip->dev, + "Cannot find %dma current_table using %d\n", + current_ma, CURRENT_500_MA); + + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, + FCC_MASK, + FCC_500MA_VAL); + if (rc < 0) + dev_err(chip->dev, "Couldn't set %dmA rc=%d\n", + CURRENT_500_MA, rc); + else + chip->fastchg_current_ma = 500; + return rc; + } + + if (chip->tables.usb_ilim_ma_table[i] == chip->fastchg_current_ma) { + pr_smb(PR_STATUS, "skipping fastchg current request: %d\n", + chip->fastchg_current_ma); + return 0; + } + + cur_val = i & FCC_MASK; + rc = smbchg_sec_masked_write(chip, chip->chgr_base + FCC_CFG, + FCC_MASK, cur_val); + if (rc < 0) { + dev_err(chip->dev, "cannot write to fcc cfg rc = %d\n", rc); + return rc; + } + pr_smb(PR_STATUS, "fastcharge current requested %d, set to %d\n", + current_ma, chip->tables.usb_ilim_ma_table[cur_val]); + + chip->fastchg_current_ma = chip->tables.usb_ilim_ma_table[cur_val]; + return rc; +} + +#define ICL_STS_1_REG 0x7 +#define ICL_STS_2_REG 0x9 +#define ICL_STS_MASK 0x1F +#define AICL_SUSP_BIT BIT(6) +#define AICL_STS_BIT BIT(5) +#define USBIN_SUSPEND_STS_BIT BIT(3) +#define USBIN_ACTIVE_PWR_SRC_BIT BIT(1) +#define DCIN_ACTIVE_PWR_SRC_BIT BIT(0) +#define PARALLEL_REENABLE_TIMER_MS 1000 +#define PARALLEL_CHG_THRESHOLD_CURRENT 1800 +static bool smbchg_is_usbin_active_pwr_src(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_2_REG, 1); + if (rc < 0) { + dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", rc); + return false; + } + + return !(reg & USBIN_SUSPEND_STS_BIT) + && (reg & USBIN_ACTIVE_PWR_SRC_BIT); +} + +static int smbchg_parallel_usb_charging_en(struct smbchg_chip *chip, bool en) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + + if (!parallel_psy || !chip->parallel_charger_detected) + return 0; + + pval.intval = en; + return power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_CHARGING_ENABLED, &pval); +} + +#define ESR_PULSE_CURRENT_DELTA_MA 200 +static int smbchg_sw_esr_pulse_en(struct smbchg_chip *chip, bool en) +{ + int rc, fg_current_now, icl_ma; + + rc = get_property_from_fg(chip, POWER_SUPPLY_PROP_CURRENT_NOW, + &fg_current_now); + if (rc) { + pr_smb(PR_STATUS, "bms psy does not support OCV\n"); + return 0; + } + + icl_ma = max(chip->iterm_ma + ESR_PULSE_CURRENT_DELTA_MA, + fg_current_now - ESR_PULSE_CURRENT_DELTA_MA); + rc = vote(chip->fcc_votable, ESR_PULSE_FCC_VOTER, en, icl_ma); + if (rc < 0) { + pr_err("Couldn't Vote FCC en = %d rc = %d\n", en, rc); + return rc; + } + rc = smbchg_parallel_usb_charging_en(chip, !en); + return rc; +} + +#define USB_AICL_CFG 0xF3 +#define AICL_EN_BIT BIT(2) +static void smbchg_rerun_aicl(struct smbchg_chip *chip) +{ + pr_smb(PR_STATUS, "Rerunning AICL...\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + /* Add a delay so that AICL successfully clears */ + msleep(50); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); +} + +static void taper_irq_en(struct smbchg_chip *chip, bool en) +{ + mutex_lock(&chip->taper_irq_lock); + if (en != chip->taper_irq_enabled) { + if (en) { + enable_irq(chip->taper_irq); + enable_irq_wake(chip->taper_irq); + } else { + disable_irq_wake(chip->taper_irq); + disable_irq_nosync(chip->taper_irq); + } + chip->taper_irq_enabled = en; + } + mutex_unlock(&chip->taper_irq_lock); +} + +static int smbchg_get_aicl_level_ma(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_1_REG, 1); + if (rc < 0) { + dev_err(chip->dev, "Could not read usb icl sts 1: %d\n", rc); + return 0; + } + if (reg & AICL_SUSP_BIT) { + pr_warn("AICL suspended: %02x\n", reg); + return 0; + } + reg &= ICL_STS_MASK; + if (reg >= chip->tables.usb_ilim_ma_len) { + pr_warn("invalid AICL value: %02x\n", reg); + return 0; + } + return chip->tables.usb_ilim_ma_table[reg]; +} + +static void smbchg_parallel_usb_disable(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + 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, ®, 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, ®, 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, ®, 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, ®, 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, ®, chip->bat_if_base + CMD_CHG_REG, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read OTG enable bit rc=%d\n", rc); + return rc; + } + + return (reg & OTG_EN_BIT) ? 1 : 0; +} + +struct regulator_ops smbchg_otg_reg_ops = { + .enable = smbchg_otg_regulator_enable, + .disable = smbchg_otg_regulator_disable, + .is_enabled = smbchg_otg_regulator_is_enable, +}; + +#define USBIN_CHGR_CFG 0xF1 +#define ADAPTER_ALLOWANCE_MASK 0x7 +#define USBIN_ADAPTER_9V 0x3 +#define USBIN_ADAPTER_5V_9V_CONT 0x2 +#define USBIN_ADAPTER_5V_UNREGULATED_9V 0x5 +#define HVDCP_EN_BIT BIT(3) +static int smbchg_external_otg_regulator_enable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, true, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't suspend charger rc=%d\n", rc); + return rc; + } + + rc = smbchg_read(chip, &chip->original_usbin_allowance, + chip->usb_chgpth_base + USBIN_CHGR_CFG, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc); + return rc; + } + + /* + * To disallow source detect and usbin_uv interrupts, set the adapter + * allowance to 9V, so that the audio boost operating in reverse never + * gets detected as a valid input + */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable HVDCP rc=%d\n", rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + 0xFF, USBIN_ADAPTER_9V); + if (rc < 0) { + dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, "Enabling OTG Boost\n"); + return rc; +} + +static int smbchg_external_otg_regulator_disable(struct regulator_dev *rdev) +{ + int rc = 0; + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + rc = vote(chip->usb_suspend_votable, OTG_EN_VOTER, false, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't unsuspend charger rc=%d\n", rc); + return rc; + } + + /* + * Reenable HVDCP and set the adapter allowance back to the original + * value in order to allow normal USBs to be recognized as a valid + * input. + */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable HVDCP rc=%d\n", rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + 0xFF, chip->original_usbin_allowance); + if (rc < 0) { + dev_err(chip->dev, "Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_STATUS, "Disabling OTG Boost\n"); + return rc; +} + +static int smbchg_external_otg_regulator_is_enable(struct regulator_dev *rdev) +{ + struct smbchg_chip *chip = rdev_get_drvdata(rdev); + + return get_client_vote(chip->usb_suspend_votable, OTG_EN_VOTER); +} + +struct regulator_ops smbchg_external_otg_reg_ops = { + .enable = smbchg_external_otg_regulator_enable, + .disable = smbchg_external_otg_regulator_disable, + .is_enabled = smbchg_external_otg_regulator_is_enable, +}; + +static int smbchg_regulator_init(struct smbchg_chip *chip) +{ + int rc = 0; + struct regulator_config cfg = {}; + struct device_node *regulator_node; + + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->otg_vreg.rdesc.owner = THIS_MODULE; + chip->otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->otg_vreg.rdesc.ops = &smbchg_otg_reg_ops; + chip->otg_vreg.rdesc.of_match = "qcom,smbcharger-boost-otg"; + chip->otg_vreg.rdesc.name = "qcom,smbcharger-boost-otg"; + + chip->otg_vreg.rdev = devm_regulator_register(chip->dev, + &chip->otg_vreg.rdesc, &cfg); + if (IS_ERR(chip->otg_vreg.rdev)) { + rc = PTR_ERR(chip->otg_vreg.rdev); + chip->otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "OTG reg failed, rc=%d\n", rc); + } + if (rc) + return rc; + + regulator_node = of_get_child_by_name(chip->dev->of_node, + "qcom,smbcharger-external-otg"); + if (!regulator_node) { + dev_dbg(chip->dev, "external-otg node absent\n"); + return 0; + } + + chip->ext_otg_vreg.rdesc.owner = THIS_MODULE; + chip->ext_otg_vreg.rdesc.type = REGULATOR_VOLTAGE; + chip->ext_otg_vreg.rdesc.ops = &smbchg_external_otg_reg_ops; + chip->ext_otg_vreg.rdesc.of_match = "qcom,smbcharger-external-otg"; + chip->ext_otg_vreg.rdesc.name = "qcom,smbcharger-external-otg"; + if (of_get_property(chip->dev->of_node, "otg-parent-supply", NULL)) + chip->ext_otg_vreg.rdesc.supply_name = "otg-parent"; + cfg.dev = chip->dev; + cfg.driver_data = chip; + + chip->ext_otg_vreg.rdev = devm_regulator_register(chip->dev, + &chip->ext_otg_vreg.rdesc, + &cfg); + if (IS_ERR(chip->ext_otg_vreg.rdev)) { + rc = PTR_ERR(chip->ext_otg_vreg.rdev); + chip->ext_otg_vreg.rdev = NULL; + if (rc != -EPROBE_DEFER) + dev_err(chip->dev, + "external OTG reg failed, rc=%d\n", rc); + } + + return rc; +} + +#define CMD_CHG_LED_REG 0x43 +#define CHG_LED_CTRL_BIT BIT(0) +#define LED_SW_CTRL_BIT 0x1 +#define LED_CHG_CTRL_BIT 0x0 +#define CHG_LED_ON 0x03 +#define CHG_LED_OFF 0x00 +#define LED_BLINKING_PATTERN1 0x01 +#define LED_BLINKING_PATTERN2 0x02 +#define LED_BLINKING_CFG_MASK SMB_MASK(2, 1) +#define CHG_LED_SHIFT 1 +static int smbchg_chg_led_controls(struct smbchg_chip *chip) +{ + u8 reg, mask; + int rc; + + if (chip->cfg_chg_led_sw_ctrl) { + /* turn-off LED by default for software control */ + mask = CHG_LED_CTRL_BIT | LED_BLINKING_CFG_MASK; + reg = LED_SW_CTRL_BIT; + } else { + mask = CHG_LED_CTRL_BIT; + reg = LED_CHG_CTRL_BIT; + } + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_LED_REG, + mask, reg); + if (rc < 0) + dev_err(chip->dev, + "Couldn't write LED_CTRL_BIT rc=%d\n", rc); + return rc; +} + +static void smbchg_chg_led_brightness_set(struct led_classdev *cdev, + enum led_brightness value) +{ + struct smbchg_chip *chip = container_of(cdev, + struct smbchg_chip, led_cdev); + union power_supply_propval pval = {0, }; + u8 reg; + int rc; + + reg = (value > LED_OFF) ? CHG_LED_ON << CHG_LED_SHIFT : + CHG_LED_OFF << CHG_LED_SHIFT; + pval.intval = value > LED_OFF ? 1 : 0; + power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER, + &pval); + pr_smb(PR_STATUS, + "set the charger led brightness to value=%d\n", + value); + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + CMD_CHG_LED_REG, + LED_BLINKING_CFG_MASK, reg); + if (rc) + dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n", + rc); +} + +static enum +led_brightness smbchg_chg_led_brightness_get(struct led_classdev *cdev) +{ + struct smbchg_chip *chip = container_of(cdev, + struct smbchg_chip, led_cdev); + u8 reg_val, chg_led_sts; + int rc; + + rc = smbchg_read(chip, ®_val, chip->bat_if_base + CMD_CHG_LED_REG, + 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read CHG_LED_REG sts rc=%d\n", + rc); + return rc; + } + + chg_led_sts = (reg_val & LED_BLINKING_CFG_MASK) >> CHG_LED_SHIFT; + + pr_smb(PR_STATUS, "chg_led_sts = %02x\n", chg_led_sts); + + return (chg_led_sts == CHG_LED_OFF) ? LED_OFF : LED_FULL; +} + +static void smbchg_chg_led_blink_set(struct smbchg_chip *chip, + unsigned long blinking) +{ + union power_supply_propval pval = {0, }; + u8 reg; + int rc; + + pval.intval = (blinking == 0) ? 0 : 1; + power_supply_set_property(chip->bms_psy, POWER_SUPPLY_PROP_HI_POWER, + &pval); + + if (blinking == 0) { + reg = CHG_LED_OFF << CHG_LED_SHIFT; + } else { + if (blinking == 1) + reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT; + else if (blinking == 2) + reg = LED_BLINKING_PATTERN2 << CHG_LED_SHIFT; + else + reg = LED_BLINKING_PATTERN1 << CHG_LED_SHIFT; + } + + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + CMD_CHG_LED_REG, + LED_BLINKING_CFG_MASK, reg); + if (rc) + dev_err(chip->dev, "Couldn't write CHG_LED rc=%d\n", + rc); +} + +static ssize_t smbchg_chg_led_blink_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct led_classdev *cdev = dev_get_drvdata(dev); + struct smbchg_chip *chip = container_of(cdev, struct smbchg_chip, + led_cdev); + unsigned long blinking; + ssize_t rc = -EINVAL; + + rc = kstrtoul(buf, 10, &blinking); + if (rc) + return rc; + + smbchg_chg_led_blink_set(chip, blinking); + + return len; +} + +static DEVICE_ATTR(blink, 0664, NULL, smbchg_chg_led_blink_store); + +static struct attribute *led_blink_attributes[] = { + &dev_attr_blink.attr, + NULL, +}; + +static struct attribute_group smbchg_led_attr_group = { + .attrs = led_blink_attributes +}; + +static int smbchg_register_chg_led(struct smbchg_chip *chip) +{ + int rc; + + chip->led_cdev.name = "red"; + chip->led_cdev.brightness_set = smbchg_chg_led_brightness_set; + chip->led_cdev.brightness_get = smbchg_chg_led_brightness_get; + + rc = led_classdev_register(chip->dev, &chip->led_cdev); + if (rc) { + dev_err(chip->dev, "unable to register charger led, rc=%d\n", + rc); + return rc; + } + + rc = sysfs_create_group(&chip->led_cdev.dev->kobj, + &smbchg_led_attr_group); + if (rc) { + dev_err(chip->dev, "led sysfs rc: %d\n", rc); + return rc; + } + + return rc; +} + +static int vf_adjust_low_threshold = 5; +module_param(vf_adjust_low_threshold, int, 0644); + +static int vf_adjust_high_threshold = 7; +module_param(vf_adjust_high_threshold, int, 0644); + +static int vf_adjust_n_samples = 10; +module_param(vf_adjust_n_samples, int, 0644); + +static int vf_adjust_max_delta_mv = 40; +module_param(vf_adjust_max_delta_mv, int, 0644); + +static int vf_adjust_trim_steps_per_adjust = 1; +module_param(vf_adjust_trim_steps_per_adjust, int, 0644); + +#define CENTER_TRIM_CODE 7 +#define MAX_LIN_CODE 14 +#define MAX_TRIM_CODE 15 +#define SCALE_SHIFT 4 +#define VF_TRIM_OFFSET_MASK SMB_MASK(3, 0) +#define VF_STEP_SIZE_MV 10 +#define SCALE_LSB_MV 17 +static int smbchg_trim_add_steps(int prev_trim, int delta_steps) +{ + int scale_steps; + int linear_offset, linear_scale; + int offset_code = prev_trim & VF_TRIM_OFFSET_MASK; + int scale_code = (prev_trim & ~VF_TRIM_OFFSET_MASK) >> SCALE_SHIFT; + + if (abs(delta_steps) > 1) { + pr_smb(PR_STATUS, + "Cant trim multiple steps delta_steps = %d\n", + delta_steps); + return prev_trim; + } + if (offset_code <= CENTER_TRIM_CODE) + linear_offset = offset_code + CENTER_TRIM_CODE; + else if (offset_code > CENTER_TRIM_CODE) + linear_offset = MAX_TRIM_CODE - offset_code; + + if (scale_code <= CENTER_TRIM_CODE) + linear_scale = scale_code + CENTER_TRIM_CODE; + else if (scale_code > CENTER_TRIM_CODE) + linear_scale = scale_code - (CENTER_TRIM_CODE + 1); + + /* check if we can accomodate delta steps with just the offset */ + if (linear_offset + delta_steps >= 0 + && linear_offset + delta_steps <= MAX_LIN_CODE) { + linear_offset += delta_steps; + + if (linear_offset > CENTER_TRIM_CODE) + offset_code = linear_offset - CENTER_TRIM_CODE; + else + offset_code = MAX_TRIM_CODE - linear_offset; + + return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code; + } + + /* changing offset cannot satisfy delta steps, change the scale bits */ + scale_steps = delta_steps > 0 ? 1 : -1; + + if (linear_scale + scale_steps < 0 + || linear_scale + scale_steps > MAX_LIN_CODE) { + pr_smb(PR_STATUS, + "Cant trim scale_steps = %d delta_steps = %d\n", + scale_steps, delta_steps); + return prev_trim; + } + + linear_scale += scale_steps; + + if (linear_scale > CENTER_TRIM_CODE) + scale_code = linear_scale - CENTER_TRIM_CODE; + else + scale_code = linear_scale + (CENTER_TRIM_CODE + 1); + prev_trim = (prev_trim & VF_TRIM_OFFSET_MASK) + | scale_code << SCALE_SHIFT; + + /* + * now that we have changed scale which is a 17mV jump, change the + * offset bits (10mV) too so the effective change is just 7mV + */ + delta_steps = -1 * delta_steps; + + linear_offset = clamp(linear_offset + delta_steps, 0, MAX_LIN_CODE); + if (linear_offset > CENTER_TRIM_CODE) + offset_code = linear_offset - CENTER_TRIM_CODE; + else + offset_code = MAX_TRIM_CODE - linear_offset; + + return (prev_trim & ~VF_TRIM_OFFSET_MASK) | offset_code; +} + +#define TRIM_14 0xFE +#define VF_TRIM_MASK 0xFF +static int smbchg_adjust_vfloat_mv_trim(struct smbchg_chip *chip, + int delta_mv) +{ + int sign, delta_steps, rc = 0; + u8 prev_trim, new_trim; + int i; + + sign = delta_mv > 0 ? 1 : -1; + delta_steps = (delta_mv + sign * VF_STEP_SIZE_MV / 2) + / VF_STEP_SIZE_MV; + + rc = smbchg_read(chip, &prev_trim, chip->misc_base + TRIM_14, 1); + if (rc) { + dev_err(chip->dev, "Unable to read trim 14: %d\n", rc); + return rc; + } + + for (i = 1; i <= abs(delta_steps) + && i <= vf_adjust_trim_steps_per_adjust; i++) { + new_trim = (u8)smbchg_trim_add_steps(prev_trim, + delta_steps > 0 ? 1 : -1); + if (new_trim == prev_trim) { + pr_smb(PR_STATUS, + "VFloat trim unchanged from %02x\n", prev_trim); + /* treat no trim change as an error */ + return -EINVAL; + } + + rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_14, + VF_TRIM_MASK, new_trim); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't change vfloat trim rc=%d\n", rc); + } + pr_smb(PR_STATUS, + "VFlt trim %02x to %02x, delta steps: %d\n", + prev_trim, new_trim, delta_steps); + prev_trim = new_trim; + } + + return rc; +} + +#define VFLOAT_RESAMPLE_DELAY_MS 10000 +static void smbchg_vfloat_adjust_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + vfloat_adjust_work.work); + int vbat_uv, vbat_mv, ibat_ua, rc, delta_vfloat_mv; + bool taper, enable; + + smbchg_stay_awake(chip, PM_REASON_VFLOAT_ADJUST); + taper = (get_prop_charge_type(chip) + == POWER_SUPPLY_CHARGE_TYPE_TAPER); + enable = taper && (chip->parallel.current_max_ma == 0); + + if (!enable) { + pr_smb(PR_MISC, + "Stopping vfloat adj taper=%d parallel_ma = %d\n", + taper, chip->parallel.current_max_ma); + goto stop; + } + + if (get_prop_batt_health(chip) != POWER_SUPPLY_HEALTH_GOOD) { + pr_smb(PR_STATUS, "JEITA active, skipping\n"); + goto stop; + } + + set_property_on_fg(chip, POWER_SUPPLY_PROP_UPDATE_NOW, 1); + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_VOLTAGE_NOW, &vbat_uv); + if (rc) { + pr_smb(PR_STATUS, + "bms psy does not support voltage rc = %d\n", rc); + goto stop; + } + vbat_mv = vbat_uv / 1000; + + if ((vbat_mv - chip->vfloat_mv) < -1 * vf_adjust_max_delta_mv) { + pr_smb(PR_STATUS, "Skip vbat out of range: %d\n", vbat_mv); + goto reschedule; + } + + rc = get_property_from_fg(chip, + POWER_SUPPLY_PROP_CURRENT_NOW, &ibat_ua); + if (rc) { + pr_smb(PR_STATUS, + "bms psy does not support current_now rc = %d\n", rc); + goto stop; + } + + if (ibat_ua / 1000 > -chip->iterm_ma) { + pr_smb(PR_STATUS, "Skip ibat too high: %d\n", ibat_ua); + goto reschedule; + } + + pr_smb(PR_STATUS, "sample number = %d vbat_mv = %d ibat_ua = %d\n", + chip->n_vbat_samples, + vbat_mv, + ibat_ua); + + chip->max_vbat_sample = max(chip->max_vbat_sample, vbat_mv); + chip->n_vbat_samples += 1; + if (chip->n_vbat_samples < vf_adjust_n_samples) { + pr_smb(PR_STATUS, "Skip %d samples; max = %d\n", + chip->n_vbat_samples, chip->max_vbat_sample); + goto reschedule; + } + /* if max vbat > target vfloat, delta_vfloat_mv could be negative */ + delta_vfloat_mv = chip->vfloat_mv - chip->max_vbat_sample; + pr_smb(PR_STATUS, "delta_vfloat_mv = %d, samples = %d, mvbat = %d\n", + delta_vfloat_mv, chip->n_vbat_samples, chip->max_vbat_sample); + /* + * enough valid samples has been collected, adjust trim codes + * based on maximum of collected vbat samples if necessary + */ + if (delta_vfloat_mv > vf_adjust_high_threshold + || delta_vfloat_mv < -1 * vf_adjust_low_threshold) { + rc = smbchg_adjust_vfloat_mv_trim(chip, delta_vfloat_mv); + if (rc) { + pr_smb(PR_STATUS, + "Stopping vfloat adj after trim adj rc = %d\n", + rc); + goto stop; + } + chip->max_vbat_sample = 0; + chip->n_vbat_samples = 0; + goto reschedule; + } + +stop: + chip->max_vbat_sample = 0; + chip->n_vbat_samples = 0; + smbchg_relax(chip, PM_REASON_VFLOAT_ADJUST); + return; + +reschedule: + schedule_delayed_work(&chip->vfloat_adjust_work, + msecs_to_jiffies(VFLOAT_RESAMPLE_DELAY_MS)); + return; +} + +static int smbchg_charging_status_change(struct smbchg_chip *chip) +{ + smbchg_vfloat_adjust_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + return 0; +} + +#define BB_CLMP_SEL 0xF8 +#define BB_CLMP_MASK SMB_MASK(1, 0) +#define BB_CLMP_VFIX_3338MV 0x1 +#define BB_CLMP_VFIX_3512MV 0x2 +static int smbchg_set_optimal_charging_mode(struct smbchg_chip *chip, int type) +{ + int rc; + bool hvdcp2 = (type == POWER_SUPPLY_TYPE_USB_HVDCP + && smbchg_is_usbin_active_pwr_src(chip)); + + /* + * Set the charger switching freq to 1MHZ if HVDCP 2.0, + * or 750KHZ otherwise + */ + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + BAT_IF_TRIM7_REG, + CFG_750KHZ_BIT, hvdcp2 ? 0 : CFG_750KHZ_BIT); + if (rc) { + dev_err(chip->dev, "Cannot set switching freq: %d\n", rc); + return rc; + } + + /* + * Set the charger switch frequency clamp voltage threshold to 3.338V + * if HVDCP 2.0, or 3.512V otherwise. + */ + rc = smbchg_sec_masked_write(chip, chip->bat_if_base + BB_CLMP_SEL, + BB_CLMP_MASK, + hvdcp2 ? BB_CLMP_VFIX_3338MV : BB_CLMP_VFIX_3512MV); + if (rc) { + dev_err(chip->dev, "Cannot set switching freq: %d\n", rc); + return rc; + } + + return 0; +} + +#define DEFAULT_SDP_MA 100 +#define DEFAULT_CDP_MA 1500 +static int smbchg_change_usb_supply_type(struct smbchg_chip *chip, + enum power_supply_type type) +{ + int rc, current_limit_ma; + + /* + * if the type is not unknown, set the type before changing ICL vote + * in order to ensure that the correct current limit registers are + * used + */ + if (type != POWER_SUPPLY_TYPE_UNKNOWN) + chip->usb_supply_type = type; + + /* + * Type-C only supports STD(900), MEDIUM(1500) and HIGH(3000) current + * modes, skip all BC 1.2 current if external typec is supported. + * Note: for SDP supporting current based on USB notifications. + */ + if (chip->typec_psy && (type != POWER_SUPPLY_TYPE_USB)) + current_limit_ma = chip->typec_current_ma; + else if (type == POWER_SUPPLY_TYPE_USB) + current_limit_ma = DEFAULT_SDP_MA; + else if (type == POWER_SUPPLY_TYPE_USB_CDP) + current_limit_ma = DEFAULT_CDP_MA; + else if (type == POWER_SUPPLY_TYPE_USB_HVDCP) + current_limit_ma = smbchg_default_hvdcp_icl_ma; + else if (type == POWER_SUPPLY_TYPE_USB_HVDCP_3) + current_limit_ma = smbchg_default_hvdcp3_icl_ma; + else + current_limit_ma = smbchg_default_dcp_icl_ma; + + pr_smb(PR_STATUS, "Type %d: setting mA = %d\n", + type, current_limit_ma); + rc = vote(chip->usb_icl_votable, PSY_ICL_VOTER, true, + current_limit_ma); + if (rc < 0) { + pr_err("Couldn't vote for new USB ICL rc=%d\n", rc); + goto out; + } + + /* otherwise if it is unknown, set type after the vote */ + if (type == POWER_SUPPLY_TYPE_UNKNOWN) + chip->usb_supply_type = type; + + if (!chip->skip_usb_notification) + power_supply_changed(chip->usb_psy); + + /* set the correct buck switching frequency */ + rc = smbchg_set_optimal_charging_mode(chip, type); + if (rc < 0) + pr_err("Couldn't set charger optimal mode rc=%d\n", rc); + +out: + return rc; +} + +#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4) +#define HVDCP_5V 0x00 +#define HVDCP_9V 0x10 +#define USB_CMD_HVDCP_1 0x42 +#define FORCE_HVDCP_2p0 BIT(3) + +static int force_9v_hvdcp(struct smbchg_chip *chip) +{ + int rc; + + /* Force 5V HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc) { + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc); + return rc; + } + + /* Force QC2.0 */ + rc = smbchg_masked_write(chip, + chip->usb_chgpth_base + USB_CMD_HVDCP_1, + FORCE_HVDCP_2p0, FORCE_HVDCP_2p0); + rc |= smbchg_masked_write(chip, + chip->usb_chgpth_base + USB_CMD_HVDCP_1, + FORCE_HVDCP_2p0, 0); + if (rc < 0) { + pr_err("Couldn't force QC2.0 rc=%d\n", rc); + return rc; + } + + /* Delay to switch into HVDCP 2.0 and avoid UV */ + msleep(500); + + /* Force 9V HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc) + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", rc); + + return rc; +} + +static void smbchg_hvdcp_det_work(struct work_struct *work) +{ + struct smbchg_chip *chip = container_of(work, + struct smbchg_chip, + hvdcp_det_work.work); + int rc; + + if (is_hvdcp_present(chip)) { + if (!chip->hvdcp3_supported && + (chip->wa_flags & SMBCHG_HVDCP_9V_EN_WA)) { + /* force HVDCP 2.0 */ + rc = force_9v_hvdcp(chip); + if (rc) + pr_err("could not force 9V HVDCP continuing rc=%d\n", + rc); + } + smbchg_change_usb_supply_type(chip, + POWER_SUPPLY_TYPE_USB_HVDCP); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_aicl_deglitch_wa_check(chip); + } + smbchg_relax(chip, PM_DETECT_HVDCP); +} + +static int set_usb_psy_dp_dm(struct smbchg_chip *chip, int state) +{ + int rc; + u8 reg; + union power_supply_propval pval = {0, }; + + /* + * ensure that we are not in the middle of an insertion where usbin_uv + * is low and src_detect hasnt gone high. If so force dp=F dm=F + * which guarantees proper type detection + */ + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (!rc && !(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_MISC, "overwriting state = %d with %d\n", + state, POWER_SUPPLY_DP_DM_DPF_DMF); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + return regulator_enable(chip->dpdm_reg); + } + pr_smb(PR_MISC, "setting usb psy dp dm = %d\n", state); + pval.intval = state; + return power_supply_set_property(chip->usb_psy, + POWER_SUPPLY_PROP_DP_DM, &pval); +} + +#define APSD_CFG 0xF5 +#define AUTO_SRC_DETECT_EN_BIT BIT(0) +#define APSD_TIMEOUT_MS 1500 +static void restore_from_hvdcp_detection(struct smbchg_chip *chip) +{ + int rc; + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + /* switch to 9V HVDCP */ + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) + pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc); + + /* enable HVDCP */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable HVDCP rc=%d\n", rc); + + /* enable APSD */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable APSD rc=%d\n", rc); + + /* Reset back to 5V unregulated */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + ADAPTER_ALLOWANCE_MASK, USBIN_ADAPTER_5V_UNREGULATED_9V); + if (rc < 0) + pr_err("Couldn't write usb allowance rc=%d\n", rc); + + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + if (rc < 0) + pr_err("Couldn't enable AICL rc=%d\n", rc); + + chip->hvdcp_3_det_ignore_uv = false; + chip->pulse_cnt = 0; +} + +#define RESTRICTED_CHG_FCC_PERCENT 50 +static int smbchg_restricted_charging(struct smbchg_chip *chip, bool enable) +{ + int current_table_index, fastchg_current; + int rc = 0; + + /* If enable, set the fcc to the set point closest + * to 50% of the configured fcc while remaining below it + */ + current_table_index = find_smaller_in_array( + chip->tables.usb_ilim_ma_table, + chip->cfg_fastchg_current_ma + * RESTRICTED_CHG_FCC_PERCENT / 100, + chip->tables.usb_ilim_ma_len); + fastchg_current = + chip->tables.usb_ilim_ma_table[current_table_index]; + rc = vote(chip->fcc_votable, RESTRICTED_CHG_FCC_VOTER, enable, + fastchg_current); + + pr_smb(PR_STATUS, "restricted_charging set to %d\n", enable); + chip->restricted_charging = enable; + + return rc; +} + +static void handle_usb_removal(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + int rc; + + pr_smb(PR_STATUS, "triggered\n"); + smbchg_aicl_deglitch_wa_check(chip); + /* Clear the OV detected status set before */ + if (chip->usb_ov_det) + chip->usb_ov_det = false; + /* Clear typec current status */ + if (chip->typec_psy) + chip->typec_current_ma = 0; + smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_UNKNOWN); + extcon_set_cable_state_(chip->extcon, EXTCON_USB, chip->usb_present); + if (chip->dpdm_reg) + regulator_disable(chip->dpdm_reg); + schedule_work(&chip->usb_set_online_work); + + pr_smb(PR_MISC, "setting usb psy health UNKNOWN\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNKNOWN; + power_supply_changed(chip->usb_psy); + + if (parallel_psy && chip->parallel_charger_detected) { + pval.intval = false; + power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + } + if (chip->parallel.avail && chip->aicl_done_irq + && chip->enable_aicl_wake) { + disable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = false; + } + chip->parallel.enabled_once = false; + chip->vbat_above_headroom = false; + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + ICL_OVERRIDE_BIT, 0); + if (rc < 0) + pr_err("Couldn't set override rc = %d\n", rc); + + vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, false, 0); + chip->usb_icl_delta = 0; + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, false, 0); + vote(chip->aicl_deglitch_short_votable, + HVDCP_SHORT_DEGLITCH_VOTER, false, 0); + if (!chip->hvdcp_not_supported) + restore_from_hvdcp_detection(chip); +} + +static bool is_usbin_uv_high(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + return false; + } + return reg &= USBIN_UV_BIT; +} + +#define HVDCP_NOTIFY_MS 2500 +static void handle_usb_insertion(struct smbchg_chip *chip) +{ + struct power_supply *parallel_psy = get_parallel_psy(chip); + union power_supply_propval pval = {0, }; + enum power_supply_type usb_supply_type; + int rc; + char *usb_type_name = "null"; + + pr_smb(PR_STATUS, "triggered\n"); + /* usb inserted */ + read_usb_type(chip, &usb_type_name, &usb_supply_type); + pr_smb(PR_STATUS, + "inserted type = %d (%s)", usb_supply_type, usb_type_name); + + smbchg_aicl_deglitch_wa_check(chip); + if (chip->typec_psy) + update_typec_status(chip); + smbchg_change_usb_supply_type(chip, usb_supply_type); + + /* Only notify USB if it's not a charger */ + if (usb_supply_type == POWER_SUPPLY_TYPE_USB || + usb_supply_type == POWER_SUPPLY_TYPE_USB_CDP) + extcon_set_cable_state_(chip->extcon, EXTCON_USB, + chip->usb_present); + + /* Notify the USB psy if OV condition is not present */ + if (!chip->usb_ov_det) { + /* + * Note that this could still be a very weak charger + * if the handle_usb_insertion was triggered from + * the falling edge of an USBIN_OV interrupt + */ + pr_smb(PR_MISC, "setting usb psy health %s\n", + chip->very_weak_charger + ? "UNSPEC_FAILURE" : "GOOD"); + chip->usb_health = chip->very_weak_charger + ? POWER_SUPPLY_HEALTH_UNSPEC_FAILURE + : POWER_SUPPLY_HEALTH_GOOD; + power_supply_changed(chip->usb_psy); + } + schedule_work(&chip->usb_set_online_work); + + if (!chip->hvdcp_not_supported && + (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP)) { + cancel_delayed_work_sync(&chip->hvdcp_det_work); + smbchg_stay_awake(chip, PM_DETECT_HVDCP); + schedule_delayed_work(&chip->hvdcp_det_work, + msecs_to_jiffies(HVDCP_NOTIFY_MS)); + } + + if (parallel_psy) { + pval.intval = true; + rc = power_supply_set_property(parallel_psy, + POWER_SUPPLY_PROP_PRESENT, &pval); + chip->parallel_charger_detected = rc ? false : true; + if (rc) + pr_debug("parallel-charger absent rc=%d\n", rc); + } + + if (chip->parallel.avail && chip->aicl_done_irq + && !chip->enable_aicl_wake) { + rc = enable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = true; + } +} + +void update_usb_status(struct smbchg_chip *chip, bool usb_present, bool force) +{ + mutex_lock(&chip->usb_status_lock); + if (force) { + chip->usb_present = usb_present; + chip->usb_present ? handle_usb_insertion(chip) + : handle_usb_removal(chip); + goto unlock; + } + if (!chip->usb_present && usb_present) { + chip->usb_present = usb_present; + handle_usb_insertion(chip); + } else if (chip->usb_present && !usb_present) { + chip->usb_present = usb_present; + handle_usb_removal(chip); + } + + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); +unlock: + mutex_unlock(&chip->usb_status_lock); +} + +static int otg_oc_reset(struct smbchg_chip *chip) +{ + int rc; + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, 0); + if (rc) + pr_err("Failed to disable OTG rc=%d\n", rc); + + msleep(20); + + /* + * There is a possibility that an USBID interrupt might have + * occurred notifying USB power supply to disable OTG. We + * should not enable OTG in such cases. + */ + if (!is_otg_present(chip)) { + pr_smb(PR_STATUS, + "OTG is not present, not enabling OTG_EN_BIT\n"); + goto out; + } + + rc = smbchg_masked_write(chip, chip->bat_if_base + CMD_CHG_REG, + OTG_EN_BIT, OTG_EN_BIT); + if (rc) + pr_err("Failed to re-enable OTG rc=%d\n", rc); + +out: + return rc; +} + +static int get_current_time(unsigned long *now_tm_sec) +{ + struct rtc_time tm; + struct rtc_device *rtc; + int rc; + + rtc = rtc_class_open(CONFIG_RTC_HCTOSYS_DEVICE); + if (rtc == NULL) { + pr_err("%s: unable to open rtc device (%s)\n", + __FILE__, CONFIG_RTC_HCTOSYS_DEVICE); + return -EINVAL; + } + + rc = rtc_read_time(rtc, &tm); + if (rc) { + pr_err("Error reading rtc device (%s) : %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + + rc = rtc_valid_tm(&tm); + if (rc) { + pr_err("Invalid RTC time (%s): %d\n", + CONFIG_RTC_HCTOSYS_DEVICE, rc); + goto close_time; + } + rtc_tm_to_time(&tm, now_tm_sec); + +close_time: + rtc_class_close(rtc); + return rc; +} + +#define AICL_IRQ_LIMIT_SECONDS 60 +#define AICL_IRQ_LIMIT_COUNT 25 +static void increment_aicl_count(struct smbchg_chip *chip) +{ + bool bad_charger = false; + int max_aicl_count, rc; + u8 reg; + long elapsed_seconds; + unsigned long now_seconds; + + pr_smb(PR_INTERRUPT, "aicl count c:%d dgltch:%d first:%ld\n", + chip->aicl_irq_count, chip->aicl_deglitch_short, + chip->first_aicl_seconds); + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_1_REG, 1); + if (!rc) + chip->aicl_complete = reg & AICL_STS_BIT; + else + chip->aicl_complete = false; + + if (chip->aicl_deglitch_short || chip->force_aicl_rerun) { + if (!chip->aicl_irq_count) + get_current_time(&chip->first_aicl_seconds); + get_current_time(&now_seconds); + elapsed_seconds = now_seconds + - chip->first_aicl_seconds; + + if (elapsed_seconds > AICL_IRQ_LIMIT_SECONDS) { + pr_smb(PR_INTERRUPT, + "resetting: elp:%ld first:%ld now:%ld c=%d\n", + elapsed_seconds, chip->first_aicl_seconds, + now_seconds, chip->aicl_irq_count); + chip->aicl_irq_count = 1; + get_current_time(&chip->first_aicl_seconds); + return; + } + /* + * Double the amount of AICLs allowed if parallel charging is + * enabled. + */ + max_aicl_count = AICL_IRQ_LIMIT_COUNT + * (chip->parallel.avail ? 2 : 1); + chip->aicl_irq_count++; + + if (chip->aicl_irq_count > max_aicl_count) { + pr_smb(PR_INTERRUPT, "elp:%ld first:%ld now:%ld c=%d\n", + elapsed_seconds, chip->first_aicl_seconds, + now_seconds, chip->aicl_irq_count); + pr_smb(PR_INTERRUPT, "Disable AICL rerun\n"); + chip->very_weak_charger = true; + bad_charger = true; + + /* + * Disable AICL rerun since many interrupts were + * triggered in a short time + */ + /* disable hw aicl */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, true, 0); + if (rc < 0) { + pr_err("Couldn't disable hw aicl rerun rc=%d\n", + rc); + return; + } + + /* Vote 100mA current limit */ + rc = vote(chip->usb_icl_votable, WEAK_CHARGER_ICL_VOTER, + true, CURRENT_100_MA); + if (rc < 0) { + pr_err("Can't vote %d current limit rc=%d\n", + CURRENT_100_MA, rc); + } + + chip->aicl_irq_count = 0; + } else if ((get_prop_charge_type(chip) == + POWER_SUPPLY_CHARGE_TYPE_FAST) && + (reg & AICL_SUSP_BIT)) { + /* + * If the AICL_SUSP_BIT is on, then AICL reruns have + * already been disabled. Set the very weak charger + * flag so that the driver reports a bad charger + * and does not reenable AICL reruns. + */ + chip->very_weak_charger = true; + bad_charger = true; + } + if (bad_charger) { + pr_smb(PR_MISC, + "setting usb psy health UNSPEC_FAILURE\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + power_supply_changed(chip->usb_psy); + schedule_work(&chip->usb_set_online_work); + } + } +} + +static int wait_for_usbin_uv(struct smbchg_chip *chip, bool high) +{ + int rc; + int tries = 3; + struct completion *completion = &chip->usbin_uv_lowered; + bool usbin_uv; + + if (high) + completion = &chip->usbin_uv_raised; + + while (tries--) { + rc = wait_for_completion_interruptible_timeout( + completion, + msecs_to_jiffies(APSD_TIMEOUT_MS)); + if (rc >= 0) + break; + } + + usbin_uv = is_usbin_uv_high(chip); + + if (high == usbin_uv) + return 0; + + pr_err("usbin uv didnt go to a %s state, still at %s, tries = %d, rc = %d\n", + high ? "risen" : "lowered", + usbin_uv ? "high" : "low", + tries, rc); + return -EINVAL; +} + +static int wait_for_src_detect(struct smbchg_chip *chip, bool high) +{ + int rc; + int tries = 3; + struct completion *completion = &chip->src_det_lowered; + bool src_detect; + + if (high) + completion = &chip->src_det_raised; + + while (tries--) { + rc = wait_for_completion_interruptible_timeout( + completion, + msecs_to_jiffies(APSD_TIMEOUT_MS)); + if (rc >= 0) + break; + } + + src_detect = is_src_detect_high(chip); + + if (high == src_detect) + return 0; + + pr_err("src detect didnt go to a %s state, still at %s, tries = %d, rc = %d\n", + high ? "risen" : "lowered", + src_detect ? "high" : "low", + tries, rc); + return -EINVAL; +} + +static int fake_insertion_removal(struct smbchg_chip *chip, bool insertion) +{ + int rc; + bool src_detect; + bool usbin_uv; + + if (insertion) { + reinit_completion(&chip->src_det_raised); + reinit_completion(&chip->usbin_uv_lowered); + } else { + reinit_completion(&chip->src_det_lowered); + reinit_completion(&chip->usbin_uv_raised); + } + + /* ensure that usbin uv real time status is in the right state */ + usbin_uv = is_usbin_uv_high(chip); + if (usbin_uv != insertion) { + pr_err("Skip faking, usbin uv is already %d\n", usbin_uv); + return -EINVAL; + } + + /* ensure that src_detect real time status is in the right state */ + src_detect = is_src_detect_high(chip); + if (src_detect == insertion) { + pr_err("Skip faking, src detect is already %d\n", src_detect); + return -EINVAL; + } + + pr_smb(PR_MISC, "Allow only %s charger\n", + insertion ? "5-9V" : "9V only"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + USBIN_CHGR_CFG, + ADAPTER_ALLOWANCE_MASK, + insertion ? + USBIN_ADAPTER_5V_9V_CONT : USBIN_ADAPTER_9V); + if (rc < 0) { + pr_err("Couldn't write usb allowance rc=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on %s usbin uv\n", + insertion ? "falling" : "rising"); + rc = wait_for_usbin_uv(chip, !insertion); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on %s src det\n", + insertion ? "rising" : "falling"); + rc = wait_for_src_detect(chip, insertion); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + return 0; +} + +static int smbchg_prepare_for_pulsing(struct smbchg_chip *chip) +{ + int rc = 0; + u8 reg; + + /* switch to 5V HVDCP */ + pr_smb(PR_MISC, "Switch to 5V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc); + goto out; + } + + /* wait for HVDCP to lower to 5V */ + msleep(500); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and that we are still in 5V hvdcp + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 500mS sleep\n"); + goto out; + } + + /* disable HVDCP */ + pr_smb(PR_MISC, "Disable HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable HVDCP rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300); + if (rc < 0) { + pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "Disable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + + chip->hvdcp_3_det_ignore_uv = true; + /* fake a removal */ + pr_smb(PR_MISC, "Faking Removal\n"); + rc = fake_insertion_removal(chip, false); + if (rc < 0) { + pr_err("Couldn't fake removal HVDCP Removed rc=%d\n", rc); + goto handle_removal; + } + + /* disable APSD */ + pr_smb(PR_MISC, "Disabling APSD\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable APSD rc=%d\n", rc); + goto out; + } + + /* fake an insertion */ + pr_smb(PR_MISC, "Faking Insertion\n"); + rc = fake_insertion_removal(chip, true); + if (rc < 0) { + pr_err("Couldn't fake insertion rc=%d\n", rc); + goto handle_removal; + } + chip->hvdcp_3_det_ignore_uv = false; + + pr_smb(PR_MISC, "Enable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + + set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DMF); + /* + * DCP will switch to HVDCP in this time by removing the short + * between DP DM + */ + msleep(HVDCP_NOTIFY_MS); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and the usb type should be none since APSD was disabled + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 2s sleep\n"); + rc = -EINVAL; + goto out; + } + + smbchg_read(chip, ®, chip->misc_base + IDEV_STS, 1); + if ((reg >> TYPE_BITS_OFFSET) != 0) { + pr_smb(PR_MISC, "type bits set after 2s sleep - abort\n"); + rc = -EINVAL; + goto out; + } + + set_usb_psy_dp_dm(chip, POWER_SUPPLY_DP_DM_DP0P6_DM3P3); + /* Wait 60mS after entering continuous mode */ + msleep(60); + + return 0; +out: + chip->hvdcp_3_det_ignore_uv = false; + restore_from_hvdcp_detection(chip); + return rc; +handle_removal: + chip->hvdcp_3_det_ignore_uv = false; + update_usb_status(chip, 0, 0); + return rc; +} + +static int smbchg_unprepare_for_pulsing(struct smbchg_chip *chip) +{ + int rc = 0; + + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + + /* switch to 9V HVDCP */ + pr_smb(PR_MISC, "Switch to 9V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 9V rc=%d\n", rc); + return rc; + } + + /* enable HVDCP */ + pr_smb(PR_MISC, "Enable HVDCP\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_EN_BIT, HVDCP_EN_BIT); + if (rc < 0) { + pr_err("Couldn't enable HVDCP rc=%d\n", rc); + return rc; + } + + /* enable APSD */ + pr_smb(PR_MISC, "Enabling APSD\n"); + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + APSD_CFG, + AUTO_SRC_DETECT_EN_BIT, AUTO_SRC_DETECT_EN_BIT); + if (rc < 0) { + pr_err("Couldn't enable APSD rc=%d\n", rc); + return rc; + } + + /* Disable AICL */ + pr_smb(PR_MISC, "Disable AICL\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't disable AICL rc=%d\n", rc); + return rc; + } + + /* fake a removal */ + chip->hvdcp_3_det_ignore_uv = true; + pr_smb(PR_MISC, "Faking Removal\n"); + rc = fake_insertion_removal(chip, false); + if (rc < 0) { + pr_err("Couldn't fake removal rc=%d\n", rc); + goto out; + } + + /* + * reset the enabled once flag for parallel charging so + * parallel charging can immediately restart after the HVDCP pulsing + * is complete + */ + chip->parallel.enabled_once = false; + + /* fake an insertion */ + pr_smb(PR_MISC, "Faking Insertion\n"); + rc = fake_insertion_removal(chip, true); + if (rc < 0) { + pr_err("Couldn't fake insertion rc=%d\n", rc); + goto out; + } + chip->hvdcp_3_det_ignore_uv = false; + + /* Enable AICL */ + pr_smb(PR_MISC, "Enable AICL\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + if (rc < 0) { + pr_err("Couldn't enable AICL rc=%d\n", rc); + return rc; + } + +out: + /* + * There are many QC 2.0 chargers that collapse before the aicl deglitch + * timer can mitigate. Hence set the aicl deglitch time to a shorter + * period. + */ + + rc = vote(chip->aicl_deglitch_short_votable, + HVDCP_SHORT_DEGLITCH_VOTER, true, 0); + if (rc < 0) + pr_err("Couldn't reduce aicl deglitch rc=%d\n", rc); + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + chip->hvdcp_3_det_ignore_uv = false; + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "HVDCP removed\n"); + update_usb_status(chip, 0, 0); + } + return rc; +} + +#define USB_CMD_APSD 0x41 +#define APSD_RERUN BIT(0) +static int rerun_apsd(struct smbchg_chip *chip) +{ + int rc; + + reinit_completion(&chip->src_det_raised); + reinit_completion(&chip->usbin_uv_lowered); + reinit_completion(&chip->src_det_lowered); + reinit_completion(&chip->usbin_uv_raised); + + /* re-run APSD */ + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + USB_CMD_APSD, + APSD_RERUN, APSD_RERUN); + if (rc) { + pr_err("Couldn't re-run APSD rc=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on rising usbin uv\n"); + rc = wait_for_usbin_uv(chip, true); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on falling src det\n"); + rc = wait_for_src_detect(chip, false); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on falling usbin uv\n"); + rc = wait_for_usbin_uv(chip, false); + if (rc < 0) { + pr_err("wait for usbin uv failed rc = %d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Waiting on rising src det\n"); + rc = wait_for_src_detect(chip, true); + if (rc < 0) { + pr_err("wait for src detect failed rc = %d\n", rc); + return rc; + } + + return rc; +} + +#define SCHG_LITE_USBIN_HVDCP_5_9V 0x8 +#define SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK 0x38 +#define SCHG_LITE_USBIN_HVDCP_SEL_IDLE BIT(3) +static bool is_hvdcp_5v_cont_mode(struct smbchg_chip *chip) +{ + int rc; + u8 reg = 0; + + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + USBIN_HVDCP_STS, 1); + if (rc) { + pr_err("Unable to read HVDCP status rc=%d\n", rc); + return false; + } + + pr_smb(PR_STATUS, "HVDCP status = %x\n", reg); + + if (reg & SCHG_LITE_USBIN_HVDCP_SEL_IDLE) { + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + INPUT_STS, 1); + if (rc) { + pr_err("Unable to read INPUT status rc=%d\n", rc); + return false; + } + pr_smb(PR_STATUS, "INPUT status = %x\n", reg); + if ((reg & SCHG_LITE_USBIN_HVDCP_5_9V_SEL_MASK) == + SCHG_LITE_USBIN_HVDCP_5_9V) + return true; + } + return false; +} + +static int smbchg_prepare_for_pulsing_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + /* check if HVDCP is already in 5V continuous mode */ + if (is_hvdcp_5v_cont_mode(chip)) { + pr_smb(PR_MISC, "HVDCP by default is in 5V continuous mode\n"); + return 0; + } + + /* switch to 5V HVDCP */ + pr_smb(PR_MISC, "Switch to 5V HVDCP\n"); + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_5V); + if (rc < 0) { + pr_err("Couldn't configure HVDCP 5V rc=%d\n", rc); + goto out; + } + + /* wait for HVDCP to lower to 5V */ + msleep(500); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and that we are still in 5V hvdcp + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 500mS sleep\n"); + goto out; + } + + pr_smb(PR_MISC, "HVDCP voting for 300mA ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, true, 300); + if (rc < 0) { + pr_err("Couldn't vote for 300mA HVDCP ICL rc=%d\n", rc); + goto out; + } + + pr_smb(PR_MISC, "Disable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, 0); + + chip->hvdcp_3_det_ignore_uv = true; + + /* re-run APSD */ + rc = rerun_apsd(chip); + if (rc) { + pr_err("APSD rerun failed\n"); + goto out; + } + + chip->hvdcp_3_det_ignore_uv = false; + + pr_smb(PR_MISC, "Enable AICL\n"); + smbchg_sec_masked_write(chip, chip->usb_chgpth_base + USB_AICL_CFG, + AICL_EN_BIT, AICL_EN_BIT); + /* + * DCP will switch to HVDCP in this time by removing the short + * between DP DM + */ + msleep(HVDCP_NOTIFY_MS); + /* + * Check if the same hvdcp session is in progress. src_det should be + * high and the usb type should be none since APSD was disabled + */ + if (!is_src_detect_high(chip)) { + pr_smb(PR_MISC, "src det low after 2s sleep\n"); + rc = -EINVAL; + goto out; + } + + /* We are set if HVDCP in 5V continuous mode */ + if (!is_hvdcp_5v_cont_mode(chip)) { + pr_err("HVDCP could not be set in 5V continuous mode\n"); + goto out; + } + + return 0; +out: + chip->hvdcp_3_det_ignore_uv = false; + restore_from_hvdcp_detection(chip); + return rc; +} + +static int smbchg_unprepare_for_pulsing_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Forcing 9V HVDCP 2.0\n"); + rc = force_9v_hvdcp(chip); + if (rc) { + pr_err("Failed to force 9V HVDCP=%d\n", rc); + return rc; + } + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + return rc; +} + +#define CMD_HVDCP_2 0x43 +#define SINGLE_INCREMENT BIT(0) +#define SINGLE_DECREMENT BIT(1) +static int smbchg_dp_pulse_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Increment DP\n"); + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2, + SINGLE_INCREMENT, SINGLE_INCREMENT); + if (rc) + pr_err("Single-increment failed rc=%d\n", rc); + + return rc; +} + +static int smbchg_dm_pulse_lite(struct smbchg_chip *chip) +{ + int rc = 0; + + pr_smb(PR_MISC, "Decrement DM\n"); + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_HVDCP_2, + SINGLE_DECREMENT, SINGLE_DECREMENT); + if (rc) + pr_err("Single-decrement failed rc=%d\n", rc); + + return rc; +} + +static int smbchg_hvdcp3_confirmed(struct smbchg_chip *chip) +{ + int rc = 0; + + /* + * reset the enabled once flag for parallel charging because this is + * effectively a new insertion. + */ + chip->parallel.enabled_once = false; + + pr_smb(PR_MISC, "Retracting HVDCP vote for ICL\n"); + rc = vote(chip->usb_icl_votable, HVDCP_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't retract HVDCP ICL vote rc=%d\n", rc); + + smbchg_change_usb_supply_type(chip, POWER_SUPPLY_TYPE_USB_HVDCP_3); + + return rc; +} + +static int smbchg_dp_dm(struct smbchg_chip *chip, int val) +{ + int rc = 0; + int target_icl_vote_ma; + + switch (val) { + case POWER_SUPPLY_DP_DM_PREPARE: + if (!is_hvdcp_present(chip)) { + pr_err("No pulsing unless HVDCP\n"); + return -ENODEV; + } + if (chip->schg_version == QPNP_SCHG_LITE) + rc = smbchg_prepare_for_pulsing_lite(chip); + else + rc = smbchg_prepare_for_pulsing(chip); + break; + case POWER_SUPPLY_DP_DM_UNPREPARE: + if (chip->schg_version == QPNP_SCHG_LITE) + rc = smbchg_unprepare_for_pulsing_lite(chip); + else + rc = smbchg_unprepare_for_pulsing(chip); + break; + case POWER_SUPPLY_DP_DM_CONFIRMED_HVDCP3: + rc = smbchg_hvdcp3_confirmed(chip); + break; + case POWER_SUPPLY_DP_DM_DP_PULSE: + if (chip->schg_version == QPNP_SCHG) + rc = set_usb_psy_dp_dm(chip, + POWER_SUPPLY_DP_DM_DP_PULSE); + else + rc = smbchg_dp_pulse_lite(chip); + if (!rc) + chip->pulse_cnt++; + pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_DM_PULSE: + if (chip->schg_version == QPNP_SCHG) + rc = set_usb_psy_dp_dm(chip, + POWER_SUPPLY_DP_DM_DM_PULSE); + else + rc = smbchg_dm_pulse_lite(chip); + if (!rc && chip->pulse_cnt) + chip->pulse_cnt--; + pr_smb(PR_MISC, "pulse_cnt = %d\n", chip->pulse_cnt); + break; + case POWER_SUPPLY_DP_DM_HVDCP3_SUPPORTED: + chip->hvdcp3_supported = true; + pr_smb(PR_MISC, "HVDCP3 supported\n"); + break; + case POWER_SUPPLY_DP_DM_ICL_DOWN: + chip->usb_icl_delta -= 100; + target_icl_vote_ma = get_client_vote(chip->usb_icl_votable, + PSY_ICL_VOTER); + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true, + target_icl_vote_ma + chip->usb_icl_delta); + break; + case POWER_SUPPLY_DP_DM_ICL_UP: + chip->usb_icl_delta += 100; + target_icl_vote_ma = get_client_vote(chip->usb_icl_votable, + PSY_ICL_VOTER); + vote(chip->usb_icl_votable, SW_AICL_ICL_VOTER, true, + target_icl_vote_ma + chip->usb_icl_delta); + break; + default: + break; + } + + return rc; +} + +static void update_typec_capability_status(struct smbchg_chip *chip, + const union power_supply_propval *val) +{ + pr_smb(PR_TYPEC, "typec capability = %dma\n", val->intval); + + pr_debug("changing ICL from %dma to %dma\n", chip->typec_current_ma, + val->intval); + chip->typec_current_ma = val->intval; + smbchg_change_usb_supply_type(chip, chip->usb_supply_type); +} + +static void update_typec_otg_status(struct smbchg_chip *chip, int mode, + bool force) +{ + union power_supply_propval pval = {0, }; + pr_smb(PR_TYPEC, "typec mode = %d\n", mode); + + if (mode == POWER_SUPPLY_TYPE_DFP) { + chip->typec_dfp = true; + pval.intval = 1; + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, + chip->typec_dfp); + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + } else if (force || chip->typec_dfp) { + chip->typec_dfp = false; + pval.intval = 0; + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, + chip->typec_dfp); + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + } +} + +static int smbchg_usb_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + val->intval = chip->usb_current_max; + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = chip->usb_present; + break; + case POWER_SUPPLY_PROP_ONLINE: + val->intval = chip->usb_online; + break; + case POWER_SUPPLY_PROP_TYPE: + val->intval = chip->usb_supply_type; + break; + case POWER_SUPPLY_PROP_HEALTH: + val->intval = chip->usb_health; + break; + default: + return -EINVAL; + } + return 0; +} + +static int smbchg_usb_set_property(struct power_supply *psy, + enum power_supply_property psp, + const union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + chip->usb_current_max = val->intval; + break; + case POWER_SUPPLY_PROP_ONLINE: + chip->usb_online = val->intval; + break; + default: + return -EINVAL; + } + + power_supply_changed(psy); + return 0; +} + +static int +smbchg_usb_is_writeable(struct power_supply *psy, enum power_supply_property psp) +{ + switch (psp) { + case POWER_SUPPLY_PROP_CURRENT_MAX: + return 1; + default: + break; + } + + return 0; +} + + +static char *smbchg_usb_supplicants[] = { + "battery", + "bms", +}; + +static enum power_supply_property smbchg_usb_properties[] = { + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_ONLINE, + POWER_SUPPLY_PROP_CURRENT_MAX, + POWER_SUPPLY_PROP_TYPE, + POWER_SUPPLY_PROP_HEALTH, +}; + +#define CHARGE_OUTPUT_VTG_RATIO 840 +static int smbchg_get_iusb(struct smbchg_chip *chip) +{ + int rc, iusb_ua = -EINVAL; + struct qpnp_vadc_result adc_result; + + if (!is_usb_present(chip) && !is_dc_present(chip)) + return 0; + + if (chip->vchg_vadc_dev && chip->vchg_adc_channel != -EINVAL) { + rc = qpnp_vadc_read(chip->vchg_vadc_dev, + chip->vchg_adc_channel, &adc_result); + if (rc) { + pr_smb(PR_STATUS, + "error in VCHG (channel-%d) read rc = %d\n", + chip->vchg_adc_channel, rc); + return 0; + } + iusb_ua = div_s64(adc_result.physical * 1000, + CHARGE_OUTPUT_VTG_RATIO); + } + + return iusb_ua; +} + +static enum power_supply_property smbchg_battery_properties[] = { + POWER_SUPPLY_PROP_STATUS, + POWER_SUPPLY_PROP_PRESENT, + POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGING_ENABLED, + POWER_SUPPLY_PROP_CHARGE_TYPE, + POWER_SUPPLY_PROP_CAPACITY, + POWER_SUPPLY_PROP_HEALTH, + POWER_SUPPLY_PROP_TECHNOLOGY, + POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL, + POWER_SUPPLY_PROP_FLASH_CURRENT_MAX, + POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX, + POWER_SUPPLY_PROP_VOLTAGE_MAX_DESIGN, + POWER_SUPPLY_PROP_CURRENT_NOW, + POWER_SUPPLY_PROP_TEMP, + POWER_SUPPLY_PROP_VOLTAGE_NOW, + POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE, + POWER_SUPPLY_PROP_INPUT_CURRENT_MAX, + POWER_SUPPLY_PROP_INPUT_CURRENT_SETTLED, + POWER_SUPPLY_PROP_INPUT_CURRENT_NOW, + POWER_SUPPLY_PROP_FLASH_ACTIVE, + POWER_SUPPLY_PROP_FLASH_TRIGGER, + POWER_SUPPLY_PROP_DP_DM, + POWER_SUPPLY_PROP_INPUT_CURRENT_LIMITED, + POWER_SUPPLY_PROP_RERUN_AICL, + POWER_SUPPLY_PROP_RESTRICTED_CHARGING, +}; + +static int smbchg_battery_set_property(struct power_supply *psy, + enum power_supply_property prop, + const union power_supply_propval *val) +{ + int rc = 0; + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + vote(chip->battchg_suspend_votable, BATTCHG_USER_EN_VOTER, + !val->intval, 0); + break; + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + rc = vote(chip->usb_suspend_votable, USER_EN_VOTER, + !val->intval, 0); + rc = vote(chip->dc_suspend_votable, USER_EN_VOTER, + !val->intval, 0); + chip->chg_enabled = val->intval; + schedule_work(&chip->usb_set_online_work); + break; + case POWER_SUPPLY_PROP_CAPACITY: + chip->fake_battery_soc = val->intval; + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + break; + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + smbchg_system_temp_level_set(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + rc = smbchg_set_fastchg_current_user(chip, val->intval / 1000); + break; + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + rc = smbchg_float_voltage_set(chip, val->intval); + break; + case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: + rc = smbchg_safety_timer_enable(chip, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_ACTIVE: + rc = smbchg_switch_buck_frequency(chip, val->intval); + if (rc) { + pr_err("Couldn't switch buck frequency, rc=%d\n", rc); + /* + * Trigger a panic if there is an error while switching + * buck frequency. This will prevent LS FET damage. + */ + BUG_ON(1); + } + + rc = smbchg_otg_pulse_skip_disable(chip, + REASON_FLASH_ENABLED, val->intval); + break; + case POWER_SUPPLY_PROP_FLASH_TRIGGER: + chip->flash_triggered = !!val->intval; + smbchg_icl_loop_disable_check(chip); + break; + case POWER_SUPPLY_PROP_FORCE_TLIM: + rc = smbchg_force_tlim_en(chip, val->intval); + break; + case POWER_SUPPLY_PROP_DP_DM: + rc = smbchg_dp_dm(chip, val->intval); + break; + case POWER_SUPPLY_PROP_RERUN_AICL: + smbchg_rerun_aicl(chip); + break; + case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: + rc = smbchg_restricted_charging(chip, val->intval); + break; + case POWER_SUPPLY_PROP_CURRENT_CAPABILITY: + if (chip->typec_psy) + update_typec_capability_status(chip, val); + break; + case POWER_SUPPLY_PROP_TYPEC_MODE: + if (chip->typec_psy) + update_typec_otg_status(chip, val->intval, false); + break; + default: + return -EINVAL; + } + + return rc; +} + +static int smbchg_battery_is_writeable(struct power_supply *psy, + enum power_supply_property prop) +{ + int rc; + + switch (prop) { + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CHARGING_ENABLED: + case POWER_SUPPLY_PROP_CAPACITY: + case POWER_SUPPLY_PROP_SYSTEM_TEMP_LEVEL: + case POWER_SUPPLY_PROP_CONSTANT_CHARGE_CURRENT_MAX: + case POWER_SUPPLY_PROP_VOLTAGE_MAX: + case POWER_SUPPLY_PROP_SAFETY_TIMER_ENABLE: + case POWER_SUPPLY_PROP_DP_DM: + case POWER_SUPPLY_PROP_RERUN_AICL: + case POWER_SUPPLY_PROP_RESTRICTED_CHARGING: + rc = 1; + break; + default: + rc = 0; + break; + } + return rc; +} + +static int smbchg_battery_get_property(struct power_supply *psy, + enum power_supply_property prop, + union power_supply_propval *val) +{ + struct smbchg_chip *chip = power_supply_get_drvdata(psy); + + switch (prop) { + case POWER_SUPPLY_PROP_STATUS: + val->intval = get_prop_batt_status(chip); + break; + case POWER_SUPPLY_PROP_PRESENT: + val->intval = get_prop_batt_present(chip); + break; + case POWER_SUPPLY_PROP_BATTERY_CHARGING_ENABLED: + val->intval + = get_effective_result(chip->battchg_suspend_votable); + 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, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_hot = !!(reg & HOT_BAT_HARD_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_cold_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_cold = !!(reg & COLD_BAT_HARD_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_warm_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_warm = !!(reg & HOT_BAT_SOFT_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_cool_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_cool = !!(reg & COLD_BAT_SOFT_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t batt_pres_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + chip->batt_present = !(reg & BAT_MISSING_BIT); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + set_property_on_fg(chip, POWER_SUPPLY_PROP_HEALTH, + get_prop_batt_health(chip)); + return IRQ_HANDLED; +} + +static irqreturn_t vbat_low_handler(int irq, void *_chip) +{ + pr_warn_ratelimited("vbat low\n"); + return IRQ_HANDLED; +} + +#define CHG_COMP_SFT_BIT BIT(3) +static irqreturn_t chg_error_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int rc = 0; + u8 reg; + + pr_smb(PR_INTERRUPT, "chg-error triggered\n"); + + rc = smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Unable to read RT_STS rc = %d\n", rc); + } else { + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + if (reg & CHG_COMP_SFT_BIT) + set_property_on_fg(chip, + POWER_SUPPLY_PROP_SAFETY_TIMER_EXPIRED, + 1); + } + + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t fastchg_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + + pr_smb(PR_INTERRUPT, "p2f triggered\n"); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t chg_hot_handler(int irq, void *_chip) +{ + pr_warn_ratelimited("chg hot\n"); + smbchg_wipower_check(_chip); + return IRQ_HANDLED; +} + +static irqreturn_t chg_term_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + + pr_smb(PR_INTERRUPT, "tcc triggered\n"); + /* + * Charge termination is a pulse and not level triggered. That means, + * TCC bit in RT_STS can get cleared by the time this interrupt is + * handled. Instead of relying on that to determine whether the + * charge termination had happened, we've to simply notify the FG + * about this as long as the interrupt is handled. + */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_CHARGE_DONE, 1); + + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + + return IRQ_HANDLED; +} + +static irqreturn_t taper_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + taper_irq_en(chip, false); + smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_taper(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +static irqreturn_t recharge_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->chgr_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + smbchg_parallel_usb_check_ok(chip); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + return IRQ_HANDLED; +} + +static irqreturn_t wdog_timeout_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->misc_base + RT_STS, 1); + pr_warn_ratelimited("wdog timeout rt_stat = 0x%02x\n", reg); + if (chip->batt_psy) + power_supply_changed(chip->batt_psy); + smbchg_charging_status_change(chip); + return IRQ_HANDLED; +} + +/** + * power_ok_handler() - called when the switcher turns on or turns off + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating switcher turning on or off + */ +static irqreturn_t power_ok_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + u8 reg = 0; + + smbchg_read(chip, ®, chip->misc_base + RT_STS, 1); + pr_smb(PR_INTERRUPT, "triggered: 0x%02x\n", reg); + return IRQ_HANDLED; +} + +/** + * dcin_uv_handler() - called when the dc voltage crosses the uv threshold + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating whether dc voltage is uv + */ +#define DCIN_UNSUSPEND_DELAY_MS 1000 +static irqreturn_t dcin_uv_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool dc_present = is_dc_present(chip); + + pr_smb(PR_STATUS, "chip->dc_present = %d dc_present = %d\n", + chip->dc_present, dc_present); + + if (chip->dc_present != dc_present) { + /* dc changed */ + chip->dc_present = dc_present; + if (chip->dc_psy_type != -EINVAL && chip->batt_psy) + power_supply_changed(chip->dc_psy); + smbchg_charging_status_change(chip); + smbchg_aicl_deglitch_wa_check(chip); + chip->vbat_above_headroom = false; + } + + smbchg_wipower_check(chip); + return IRQ_HANDLED; +} + +/** + * usbin_ov_handler() - this is called when an overvoltage condition occurs + * @chip: pointer to smbchg_chip chip + */ +static irqreturn_t usbin_ov_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int rc; + u8 reg; + bool usb_present; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read usb rt status rc = %d\n", rc); + goto out; + } + + /* OV condition is detected. Notify it to USB psy */ + if (reg & USBIN_OV_BIT) { + chip->usb_ov_det = true; + pr_smb(PR_MISC, "setting usb psy health OV\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_OVERVOLTAGE; + power_supply_changed(chip->usb_psy); + } else { + chip->usb_ov_det = false; + /* If USB is present, then handle the USB insertion */ + usb_present = is_usb_present(chip); + if (usb_present) + update_usb_status(chip, usb_present, false); + } +out: + return IRQ_HANDLED; +} + +/** + * usbin_uv_handler() - this is called when USB charger is removed + * @chip: pointer to smbchg_chip chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +#define ICL_MODE_MASK SMB_MASK(5, 4) +#define ICL_MODE_HIGH_CURRENT 0 +static irqreturn_t usbin_uv_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + int aicl_level = smbchg_get_aicl_level_ma(chip); + int rc; + u8 reg; + + rc = smbchg_read(chip, ®, chip->usb_chgpth_base + RT_STS, 1); + if (rc) { + pr_err("could not read rt sts: %d", rc); + goto out; + } + + pr_smb(PR_STATUS, + "%s chip->usb_present = %d rt_sts = 0x%02x hvdcp_3_det_ignore_uv = %d aicl = %d\n", + chip->hvdcp_3_det_ignore_uv ? "Ignoring":"", + chip->usb_present, reg, chip->hvdcp_3_det_ignore_uv, + aicl_level); + + /* + * set usb_psy's dp=f dm=f if this is a new insertion, i.e. it is + * not already src_detected and usbin_uv is seen falling + */ + if (!(reg & USBIN_UV_BIT) && !(reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_MISC, "setting usb dp=f dm=f\n"); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + } + + if (reg & USBIN_UV_BIT) + complete_all(&chip->usbin_uv_raised); + else + complete_all(&chip->usbin_uv_lowered); + + if (chip->hvdcp_3_det_ignore_uv) + goto out; + + if ((reg & USBIN_UV_BIT) && (reg & USBIN_SRC_DET_BIT)) { + pr_smb(PR_STATUS, "Very weak charger detected\n"); + chip->very_weak_charger = true; + rc = smbchg_read(chip, ®, + chip->usb_chgpth_base + ICL_STS_2_REG, 1); + if (rc) { + dev_err(chip->dev, "Could not read usb icl sts 2: %d\n", + rc); + goto out; + } + if ((reg & ICL_MODE_MASK) != ICL_MODE_HIGH_CURRENT) { + /* + * If AICL is not even enabled, this is either an + * SDP or a grossly out of spec charger. Do not + * draw any current from it. + */ + rc = vote(chip->usb_suspend_votable, + WEAK_CHARGER_EN_VOTER, true, 0); + if (rc < 0) + pr_err("could not disable charger: %d", rc); + } else if (aicl_level == chip->tables.usb_ilim_ma_table[0]) { + /* + * we are in a situation where the adapter is not able + * to supply even 300mA. Disable hw aicl reruns else it + * is only a matter of time when we get back here again + */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, true, 0); + if (rc < 0) + pr_err("Couldn't disable hw aicl rerun rc=%d\n", + rc); + } + pr_smb(PR_MISC, "setting usb psy health UNSPEC_FAILURE\n"); + chip->usb_health = POWER_SUPPLY_HEALTH_UNSPEC_FAILURE; + power_supply_changed(chip->usb_psy); + schedule_work(&chip->usb_set_online_work); + } + + smbchg_wipower_check(chip); +out: + return IRQ_HANDLED; +} + +/** + * src_detect_handler() - this is called on rising edge when USB charger type + * is detected and on falling edge when USB voltage falls + * below the coarse detect voltage(1V), use it for + * handling USB charger insertion and removal. + * @chip: pointer to smbchg_chip + * @rt_stat: the status bit indicating chg insertion/removal + */ +static irqreturn_t src_detect_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool usb_present = is_usb_present(chip); + bool src_detect = is_src_detect_high(chip); + int rc; + + pr_smb(PR_STATUS, + "%s chip->usb_present = %d usb_present = %d src_detect = %d hvdcp_3_det_ignore_uv=%d\n", + chip->hvdcp_3_det_ignore_uv ? "Ignoring":"", + chip->usb_present, usb_present, src_detect, + chip->hvdcp_3_det_ignore_uv); + + if (src_detect) + complete_all(&chip->src_det_raised); + else + complete_all(&chip->src_det_lowered); + + if (chip->hvdcp_3_det_ignore_uv) + goto out; + + /* + * When VBAT is above the AICL threshold (4.25V) - 180mV (4.07V), + * an input collapse due to AICL will actually cause an USBIN_UV + * interrupt to fire as well. + * + * Handle USB insertions and removals in the source detect handler + * instead of the USBIN_UV handler since the latter is untrustworthy + * when the battery voltage is high. + */ + chip->very_weak_charger = false; + /* + * a src detect marks a new insertion or a real removal, + * vote for enable aicl hw reruns + */ + rc = vote(chip->hw_aicl_rerun_disable_votable, + WEAK_CHARGER_HW_AICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't enable hw aicl rerun rc=%d\n", rc); + + rc = vote(chip->usb_suspend_votable, WEAK_CHARGER_EN_VOTER, false, 0); + if (rc < 0) + pr_err("could not enable charger: %d\n", rc); + + if (src_detect) { + update_usb_status(chip, usb_present, 0); + } else { + update_usb_status(chip, 0, false); + chip->aicl_irq_count = 0; + } +out: + return IRQ_HANDLED; +} + +/** + * otg_oc_handler() - called when the usb otg goes over current + */ +#define NUM_OTG_RETRIES 5 +#define OTG_OC_RETRY_DELAY_US 50000 +static irqreturn_t otg_oc_handler(int irq, void *_chip) +{ + int rc; + struct smbchg_chip *chip = _chip; + s64 elapsed_us = ktime_us_delta(ktime_get(), chip->otg_enable_time); + + pr_smb(PR_INTERRUPT, "triggered\n"); + + if (chip->schg_version == QPNP_SCHG_LITE) { + pr_warn("OTG OC triggered - OTG disabled\n"); + return IRQ_HANDLED; + } + + if (elapsed_us > OTG_OC_RETRY_DELAY_US) + chip->otg_retries = 0; + + /* + * Due to a HW bug in the PMI8994 charger, the current inrush that + * occurs when connecting certain OTG devices can cause the OTG + * overcurrent protection to trip. + * + * The work around is to try reenabling the OTG when getting an + * overcurrent interrupt once. + */ + if (chip->otg_retries < NUM_OTG_RETRIES) { + chip->otg_retries += 1; + pr_smb(PR_STATUS, + "Retrying OTG enable. Try #%d, elapsed_us %lld\n", + chip->otg_retries, elapsed_us); + rc = otg_oc_reset(chip); + if (rc) + pr_err("Failed to reset OTG OC state rc=%d\n", rc); + chip->otg_enable_time = ktime_get(); + } + return IRQ_HANDLED; +} + +/** + * otg_fail_handler() - called when the usb otg fails + * (when vbat < OTG UVLO threshold) + */ +static irqreturn_t otg_fail_handler(int irq, void *_chip) +{ + pr_smb(PR_INTERRUPT, "triggered\n"); + return IRQ_HANDLED; +} + +/** + * aicl_done_handler() - called when the usb AICL algorithm is finished + * and a current is set. + */ +static irqreturn_t aicl_done_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool usb_present = is_usb_present(chip); + int aicl_level = smbchg_get_aicl_level_ma(chip); + + pr_smb(PR_INTERRUPT, "triggered, aicl: %d\n", aicl_level); + + increment_aicl_count(chip); + + if (usb_present) + smbchg_parallel_usb_check_ok(chip); + + if (chip->aicl_complete && chip->batt_psy) + power_supply_changed(chip->batt_psy); + + return IRQ_HANDLED; +} + +/** + * usbid_change_handler() - called when the usb RID changes. + * This is used mostly for detecting OTG + */ +static irqreturn_t usbid_change_handler(int irq, void *_chip) +{ + struct smbchg_chip *chip = _chip; + bool otg_present; + + pr_smb(PR_INTERRUPT, "triggered\n"); + + otg_present = is_otg_present(chip); + pr_smb(PR_MISC, "setting usb psy OTG = %d\n", + otg_present ? 1 : 0); + + extcon_set_cable_state_(chip->extcon, EXTCON_USB_HOST, otg_present); + + if (otg_present) + pr_smb(PR_STATUS, "OTG detected\n"); + + /* update FG */ + set_property_on_fg(chip, POWER_SUPPLY_PROP_STATUS, + get_prop_batt_status(chip)); + + return IRQ_HANDLED; +} + +static int determine_initial_status(struct smbchg_chip *chip) +{ + union power_supply_propval type = {0, }; + + /* + * It is okay to read the interrupt status here since + * interrupts aren't requested. reading interrupt status + * clears the interrupt so be careful to read interrupt + * status only in interrupt handling code + */ + + batt_pres_handler(0, chip); + batt_hot_handler(0, chip); + batt_warm_handler(0, chip); + batt_cool_handler(0, chip); + batt_cold_handler(0, chip); + if (chip->typec_psy) { + get_property_from_typec(chip, POWER_SUPPLY_PROP_TYPE, &type); + update_typec_otg_status(chip, type.intval, true); + } else { + usbid_change_handler(0, chip); + } + src_detect_handler(0, chip); + + chip->usb_present = is_usb_present(chip); + chip->dc_present = is_dc_present(chip); + + if (chip->usb_present) { + int rc = 0; + pr_smb(PR_MISC, "setting usb dp=f dm=f\n"); + if (chip->dpdm_reg && !regulator_is_enabled(chip->dpdm_reg)) + rc = regulator_enable(chip->dpdm_reg); + if (rc < 0) { + pr_err("Couldn't enable DP/DM for pulsing rc=%d\n", rc); + return rc; + } + handle_usb_insertion(chip); + } else { + handle_usb_removal(chip); + } + + return 0; +} + +static int prechg_time[] = { + 24, + 48, + 96, + 192, +}; +static int chg_time[] = { + 192, + 384, + 768, + 1536, +}; + +enum bpd_type { + BPD_TYPE_BAT_NONE, + BPD_TYPE_BAT_ID, + BPD_TYPE_BAT_THM, + BPD_TYPE_BAT_THM_BAT_ID, + BPD_TYPE_DEFAULT, +}; + +static const char * const bpd_label[] = { + [BPD_TYPE_BAT_NONE] = "bpd_none", + [BPD_TYPE_BAT_ID] = "bpd_id", + [BPD_TYPE_BAT_THM] = "bpd_thm", + [BPD_TYPE_BAT_THM_BAT_ID] = "bpd_thm_id", +}; + +static inline int get_bpd(const char *name) +{ + int i = 0; + for (i = 0; i < ARRAY_SIZE(bpd_label); i++) { + if (strcmp(bpd_label[i], name) == 0) + return i; + } + return -EINVAL; +} + +#define REVISION1_REG 0x0 +#define DIG_MINOR 0 +#define DIG_MAJOR 1 +#define ANA_MINOR 2 +#define ANA_MAJOR 3 +#define CHGR_CFG1 0xFB +#define RECHG_THRESHOLD_SRC_BIT BIT(1) +#define TERM_I_SRC_BIT BIT(2) +#define TERM_SRC_FG BIT(2) +#define CHG_INHIB_CFG_REG 0xF7 +#define CHG_INHIBIT_50MV_VAL 0x00 +#define CHG_INHIBIT_100MV_VAL 0x01 +#define CHG_INHIBIT_200MV_VAL 0x02 +#define CHG_INHIBIT_300MV_VAL 0x03 +#define CHG_INHIBIT_MASK 0x03 +#define USE_REGISTER_FOR_CURRENT BIT(2) +#define CHGR_CFG2 0xFC +#define CHG_EN_SRC_BIT BIT(7) +#define CHG_EN_POLARITY_BIT BIT(6) +#define P2F_CHG_TRAN BIT(5) +#define CHG_BAT_OV_ECC BIT(4) +#define I_TERM_BIT BIT(3) +#define AUTO_RECHG_BIT BIT(2) +#define CHARGER_INHIBIT_BIT BIT(0) +#define USB51_COMMAND_POL BIT(2) +#define USB51AC_CTRL BIT(1) +#define TR_8OR32B 0xFE +#define BUCK_8_16_FREQ_BIT BIT(0) +#define BM_CFG 0xF3 +#define BATT_MISSING_ALGO_BIT BIT(2) +#define BMD_PIN_SRC_MASK SMB_MASK(1, 0) +#define PIN_SRC_SHIFT 0 +#define CHGR_CFG 0xFF +#define RCHG_LVL_BIT BIT(0) +#define VCHG_EN_BIT BIT(1) +#define VCHG_INPUT_CURRENT_BIT BIT(3) +#define CFG_AFVC 0xF6 +#define VFLOAT_COMP_ENABLE_MASK SMB_MASK(2, 0) +#define TR_RID_REG 0xFA +#define FG_INPUT_FET_DELAY_BIT BIT(3) +#define TRIM_OPTIONS_7_0 0xF6 +#define INPUT_MISSING_POLLER_EN_BIT BIT(3) +#define CHGR_CCMP_CFG 0xFA +#define JEITA_TEMP_HARD_LIMIT_BIT BIT(5) +#define HVDCP_ADAPTER_SEL_MASK SMB_MASK(5, 4) +#define HVDCP_ADAPTER_SEL_9V_BIT BIT(4) +#define HVDCP_AUTH_ALG_EN_BIT BIT(6) +#define CMD_APSD 0x41 +#define APSD_RERUN_BIT BIT(0) +#define OTG_CFG 0xF1 +#define HICCUP_ENABLED_BIT BIT(6) +#define OTG_PIN_POLARITY_BIT BIT(4) +#define OTG_PIN_ACTIVE_LOW BIT(4) +#define OTG_EN_CTRL_MASK SMB_MASK(3, 2) +#define OTG_PIN_CTRL_RID_DIS 0x04 +#define OTG_CMD_CTRL_RID_EN 0x08 +#define AICL_ADC_BIT BIT(6) +static void batt_ov_wa_check(struct smbchg_chip *chip) +{ + int rc; + u8 reg; + + /* disable-'battery OV disables charging' feature */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2, + CHG_BAT_OV_ECC, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return; + } + + /* + * if battery OV is set: + * restart charging by disable/enable charging + */ + rc = smbchg_read(chip, ®, chip->bat_if_base + RT_STS, 1); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't read Battery RT status rc = %d\n", rc); + return; + } + + if (reg & BAT_OV_BIT) { + rc = smbchg_charging_en(chip, false); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't disable charging: rc = %d\n", rc); + return; + } + + /* delay for charging-disable to take affect */ + msleep(200); + + rc = smbchg_charging_en(chip, true); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't enable charging: rc = %d\n", rc); + return; + } + } +} + +static int smbchg_hw_init(struct smbchg_chip *chip) +{ + int rc, i; + u8 reg, mask; + + rc = smbchg_read(chip, chip->revision, + chip->misc_base + REVISION1_REG, 4); + if (rc < 0) { + dev_err(chip->dev, "Couldn't read revision rc=%d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "Charger Revision DIG: %d.%d; ANA: %d.%d\n", + chip->revision[DIG_MAJOR], chip->revision[DIG_MINOR], + chip->revision[ANA_MAJOR], chip->revision[ANA_MINOR]); + + /* Setup 9V HVDCP */ + if (!chip->hvdcp_not_supported) { + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + CHGPTH_CFG, + HVDCP_ADAPTER_SEL_MASK, HVDCP_9V); + if (rc < 0) { + pr_err("Couldn't set hvdcp config in chgpath_chg rc=%d\n", + rc); + return rc; + } + } + + if (chip->aicl_rerun_period_s > 0) { + rc = smbchg_set_aicl_rerun_period_s(chip, + chip->aicl_rerun_period_s); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set AICL rerun timer rc=%d\n", + rc); + return rc; + } + } + + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + TR_RID_REG, + FG_INPUT_FET_DELAY_BIT, FG_INPUT_FET_DELAY_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable fg input fet delay rc=%d\n", + rc); + return rc; + } + + rc = smbchg_sec_masked_write(chip, chip->misc_base + TRIM_OPTIONS_7_0, + INPUT_MISSING_POLLER_EN_BIT, + INPUT_MISSING_POLLER_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable input missing poller rc=%d\n", + rc); + return rc; + } + + /* + * Do not force using current from the register i.e. use auto + * power source detect (APSD) mA ratings for the initial current values. + * + * If this is set, AICL will not rerun at 9V for HVDCPs + */ + rc = smbchg_masked_write(chip, chip->usb_chgpth_base + CMD_IL, + USE_REGISTER_FOR_CURRENT, 0); + + if (rc < 0) { + dev_err(chip->dev, "Couldn't set input limit cmd rc=%d\n", rc); + return rc; + } + + /* + * set chg en by cmd register, set chg en by writing bit 1, + * enable auto pre to fast, enable auto recharge by default. + * enable current termination and charge inhibition based on + * the device tree configuration. + */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG2, + CHG_EN_SRC_BIT | CHG_EN_POLARITY_BIT | P2F_CHG_TRAN + | I_TERM_BIT | AUTO_RECHG_BIT | CHARGER_INHIBIT_BIT, + CHG_EN_POLARITY_BIT + | (chip->chg_inhibit_en ? CHARGER_INHIBIT_BIT : 0) + | (chip->iterm_disabled ? I_TERM_BIT : 0)); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return rc; + } + + /* + * enable battery charging to make sure it hasn't been changed earlier + * by the bootloader. + */ + rc = smbchg_charging_en(chip, true); + if (rc < 0) { + dev_err(chip->dev, "Couldn't enable battery charging=%d\n", rc); + return rc; + } + + /* + * Based on the configuration, use the analog sensors or the fuelgauge + * adc for recharge threshold source. + */ + + if (chip->chg_inhibit_source_fg) + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1, + TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, + TERM_SRC_FG | RECHG_THRESHOLD_SRC_BIT); + else + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG1, + TERM_I_SRC_BIT | RECHG_THRESHOLD_SRC_BIT, 0); + + if (rc < 0) { + dev_err(chip->dev, "Couldn't set chgr_cfg2 rc=%d\n", rc); + return rc; + } + + /* + * control USB suspend via command bits and set correct 100/500mA + * polarity on the usb current + */ + rc = smbchg_sec_masked_write(chip, chip->usb_chgpth_base + CHGPTH_CFG, + USB51_COMMAND_POL | USB51AC_CTRL, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set usb_chgpth cfg rc=%d\n", rc); + return rc; + } + + check_battery_type(chip); + + /* set the float voltage */ + if (chip->vfloat_mv != -EINVAL) { + rc = smbchg_float_voltage_set(chip, chip->vfloat_mv); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set float voltage rc = %d\n", rc); + return rc; + } + pr_smb(PR_STATUS, "set vfloat to %d\n", chip->vfloat_mv); + } + + /* set the fast charge current compensation */ + if (chip->fastchg_current_comp != -EINVAL) { + rc = smbchg_fastchg_current_comp_set(chip, + chip->fastchg_current_comp); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set fastchg current comp rc = %d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "set fastchg current comp to %d\n", + chip->fastchg_current_comp); + } + + /* set the float voltage compensation */ + if (chip->float_voltage_comp != -EINVAL) { + rc = smbchg_float_voltage_comp_set(chip, + chip->float_voltage_comp); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set float voltage comp rc = %d\n", + rc); + return rc; + } + pr_smb(PR_STATUS, "set float voltage comp to %d\n", + chip->float_voltage_comp); + } + + /* set iterm */ + if (chip->iterm_ma != -EINVAL) { + if (chip->iterm_disabled) { + dev_err(chip->dev, "Error: Both iterm_disabled and iterm_ma set\n"); + return -EINVAL; + } else { + smbchg_iterm_set(chip, chip->iterm_ma); + } + } + + /* set the safety time voltage */ + if (chip->safety_time != -EINVAL) { + reg = (chip->safety_time > 0 ? 0 : SFT_TIMER_DISABLE_BIT) | + (chip->prechg_safety_time > 0 + ? 0 : PRECHG_SFT_TIMER_DISABLE_BIT); + + for (i = 0; i < ARRAY_SIZE(chg_time); i++) { + if (chip->safety_time <= chg_time[i]) { + reg |= i << SAFETY_TIME_MINUTES_SHIFT; + break; + } + } + for (i = 0; i < ARRAY_SIZE(prechg_time); i++) { + if (chip->prechg_safety_time <= prechg_time[i]) { + reg |= i; + break; + } + } + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + SFT_CFG, + SFT_EN_MASK | SFT_TO_MASK | + (chip->prechg_safety_time > 0 + ? PRECHG_SFT_TO_MASK : 0), reg); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set safety timer rc = %d\n", + rc); + return rc; + } + chip->safety_timer_en = true; + } else { + rc = smbchg_read(chip, ®, chip->chgr_base + SFT_CFG, 1); + if (rc < 0) + dev_err(chip->dev, "Unable to read SFT_CFG rc = %d\n", + rc); + else if (!(reg & SFT_EN_MASK)) + chip->safety_timer_en = true; + } + + /* configure jeita temperature hard limit */ + if (chip->jeita_temp_hard_limit >= 0) { + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHGR_CCMP_CFG, + JEITA_TEMP_HARD_LIMIT_BIT, + chip->jeita_temp_hard_limit + ? 0 : JEITA_TEMP_HARD_LIMIT_BIT); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't set jeita temp hard limit rc = %d\n", + rc); + return rc; + } + } + + /* make the buck switch faster to prevent some vbus oscillation */ + rc = smbchg_sec_masked_write(chip, + chip->usb_chgpth_base + TR_8OR32B, + BUCK_8_16_FREQ_BIT, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set buck frequency rc = %d\n", rc); + return rc; + } + + /* battery missing detection */ + mask = BATT_MISSING_ALGO_BIT; + reg = chip->bmd_algo_disabled ? 0 : BATT_MISSING_ALGO_BIT; + if (chip->bmd_pin_src < BPD_TYPE_DEFAULT) { + mask |= BMD_PIN_SRC_MASK; + reg |= chip->bmd_pin_src << PIN_SRC_SHIFT; + } + rc = smbchg_sec_masked_write(chip, + chip->bat_if_base + BM_CFG, mask, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set batt_missing config = %d\n", + rc); + return rc; + } + + if (chip->vchg_adc_channel != -EINVAL) { + /* configure and enable VCHG */ + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CHGR_CFG, + VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT, + VCHG_INPUT_CURRENT_BIT | VCHG_EN_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", + rc); + return rc; + } + } + + smbchg_charging_status_change(chip); + + vote(chip->usb_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0); + vote(chip->dc_suspend_votable, USER_EN_VOTER, !chip->chg_enabled, 0); + /* resume threshold */ + if (chip->resume_delta_mv != -EINVAL) { + + /* + * Configure only if the recharge threshold source is not + * fuel gauge ADC. + */ + if (!chip->chg_inhibit_source_fg) { + if (chip->resume_delta_mv < 100) + reg = CHG_INHIBIT_50MV_VAL; + else if (chip->resume_delta_mv < 200) + reg = CHG_INHIBIT_100MV_VAL; + else if (chip->resume_delta_mv < 300) + reg = CHG_INHIBIT_200MV_VAL; + else + reg = CHG_INHIBIT_300MV_VAL; + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHG_INHIB_CFG_REG, + CHG_INHIBIT_MASK, reg); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set inhibit val rc = %d\n", + rc); + return rc; + } + } + + rc = smbchg_sec_masked_write(chip, + chip->chgr_base + CHGR_CFG, + RCHG_LVL_BIT, + (chip->resume_delta_mv + < chip->tables.rchg_thr_mv) + ? 0 : RCHG_LVL_BIT); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set recharge rc = %d\n", + rc); + return rc; + } + } + + /* DC path current settings */ + if (chip->dc_psy_type != -EINVAL) { + rc = vote(chip->dc_icl_votable, PSY_ICL_VOTER, true, + chip->dc_target_current_ma); + if (rc < 0) { + dev_err(chip->dev, + "Couldn't vote for initial DC ICL rc=%d\n", rc); + return rc; + } + } + + + /* + * on some devices the battery is powered via external sources which + * could raise its voltage above the float voltage. smbchargers go + * in to reverse boost in such a situation and the workaround is to + * disable float voltage compensation (note that the battery will appear + * hot/cold when powered via external source). + */ + if (chip->soft_vfloat_comp_disabled) { + rc = smbchg_sec_masked_write(chip, chip->chgr_base + CFG_AFVC, + VFLOAT_COMP_ENABLE_MASK, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't disable soft vfloat rc = %d\n", + rc); + return rc; + } + } + + rc = vote(chip->fcc_votable, BATT_TYPE_FCC_VOTER, true, + chip->cfg_fastchg_current_ma); + if (rc < 0) { + dev_err(chip->dev, "Couldn't vote fastchg ma rc = %d\n", rc); + return rc; + } + + rc = smbchg_read(chip, &chip->original_usbin_allowance, + chip->usb_chgpth_base + USBIN_CHGR_CFG, 1); + if (rc < 0) + dev_err(chip->dev, "Couldn't read usb allowance rc=%d\n", rc); + + if (chip->wipower_dyn_icl_avail) { + rc = smbchg_wipower_ilim_config(chip, + &(chip->wipower_default.entries[0])); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set default wipower ilim = %d\n", + rc); + return rc; + } + } + /* unsuspend dc path, it could be suspended by the bootloader */ + rc = smbchg_dc_suspend(chip, 0); + if (rc < 0) { + dev_err(chip->dev, "Couldn't unsuspend dc path= %d\n", rc); + return rc; + } + + if (chip->force_aicl_rerun) { + /* vote to enable hw aicl */ + rc = vote(chip->hw_aicl_rerun_enable_indirect_votable, + DEFAULT_CONFIG_HW_AICL_VOTER, true, 0); + if (rc < 0) { + pr_err("Couldn't vote enable hw aicl rerun rc=%d\n", + rc); + return rc; + } + } + + if (chip->schg_version == QPNP_SCHG_LITE) { + /* enable OTG hiccup mode */ + rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG, + HICCUP_ENABLED_BIT, HICCUP_ENABLED_BIT); + if (rc < 0) + dev_err(chip->dev, "Couldn't set OTG OC config rc = %d\n", + rc); + } + + if (chip->otg_pinctrl) { + /* configure OTG enable to pin control active low */ + rc = smbchg_sec_masked_write(chip, chip->otg_base + OTG_CFG, + OTG_PIN_POLARITY_BIT | OTG_EN_CTRL_MASK, + OTG_PIN_ACTIVE_LOW | OTG_PIN_CTRL_RID_DIS); + if (rc < 0) { + dev_err(chip->dev, "Couldn't set OTG EN config rc = %d\n", + rc); + return rc; + } + } + + if (chip->wa_flags & SMBCHG_BATT_OV_WA) + batt_ov_wa_check(chip); + + /* turn off AICL adc for improved accuracy */ + rc = smbchg_sec_masked_write(chip, + chip->misc_base + MISC_TRIM_OPT_15_8, AICL_ADC_BIT, 0); + if (rc) + pr_err("Couldn't write to MISC_TRIM_OPTIONS_15_8 rc=%d\n", + rc); + + return rc; +} + +static struct of_device_id smbchg_match_table[] = { + { + .compatible = "qcom,qpnp-smbcharger", + }, + { }, +}; + +#define DC_MA_MIN 300 +#define DC_MA_MAX 2000 +#define OF_PROP_READ(chip, prop, dt_property, retval, optional) \ +do { \ + if (retval) \ + break; \ + if (optional) \ + prop = -EINVAL; \ + \ + retval = of_property_read_u32(chip->pdev->dev.of_node, \ + "qcom," dt_property , \ + &prop); \ + \ + if ((retval == -EINVAL) && optional) \ + retval = 0; \ + else if (retval) \ + dev_err(chip->dev, "Error reading " #dt_property \ + " property rc = %d\n", rc); \ +} while (0) + +#define ILIM_ENTRIES 3 +#define VOLTAGE_RANGE_ENTRIES 2 +#define RANGE_ENTRY (ILIM_ENTRIES + VOLTAGE_RANGE_ENTRIES) +static int smb_parse_wipower_map_dt(struct smbchg_chip *chip, + struct ilim_map *map, char *property) +{ + struct device_node *node = chip->dev->of_node; + int total_elements, size; + struct property *prop; + const __be32 *data; + int num, i; + + prop = of_find_property(node, property, &size); + if (!prop) { + dev_err(chip->dev, "%s missing\n", property); + return -EINVAL; + } + + total_elements = size / sizeof(int); + if (total_elements % RANGE_ENTRY) { + dev_err(chip->dev, "%s table not in multiple of %d, total elements = %d\n", + property, RANGE_ENTRY, total_elements); + return -EINVAL; + } + + data = prop->value; + num = total_elements / RANGE_ENTRY; + map->entries = devm_kzalloc(chip->dev, + num * sizeof(struct ilim_entry), GFP_KERNEL); + if (!map->entries) { + dev_err(chip->dev, "kzalloc failed for default ilim\n"); + return -ENOMEM; + } + for (i = 0; i < num; i++) { + map->entries[i].vmin_uv = be32_to_cpup(data++); + map->entries[i].vmax_uv = be32_to_cpup(data++); + map->entries[i].icl_pt_ma = be32_to_cpup(data++); + map->entries[i].icl_lv_ma = be32_to_cpup(data++); + map->entries[i].icl_hv_ma = be32_to_cpup(data++); + } + map->num = num; + return 0; +} + +static int smb_parse_wipower_dt(struct smbchg_chip *chip) +{ + int rc = 0; + + chip->wipower_dyn_icl_avail = false; + + if (!chip->vadc_dev) + goto err; + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_default, + "qcom,wipower-default-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n", + rc); + goto err; + } + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_pt, + "qcom,wipower-pt-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-pt-ilim-map rc = %d\n", + rc); + goto err; + } + + rc = smb_parse_wipower_map_dt(chip, &chip->wipower_div2, + "qcom,wipower-div2-ilim-map"); + if (rc) { + dev_err(chip->dev, "failed to parse wipower-div2-ilim-map rc = %d\n", + rc); + goto err; + } + chip->wipower_dyn_icl_avail = true; + return 0; +err: + chip->wipower_default.num = 0; + chip->wipower_pt.num = 0; + chip->wipower_default.num = 0; + if (chip->wipower_default.entries) + devm_kfree(chip->dev, chip->wipower_default.entries); + if (chip->wipower_pt.entries) + devm_kfree(chip->dev, chip->wipower_pt.entries); + if (chip->wipower_div2.entries) + devm_kfree(chip->dev, chip->wipower_div2.entries); + chip->wipower_default.entries = NULL; + chip->wipower_pt.entries = NULL; + chip->wipower_div2.entries = NULL; + chip->vadc_dev = NULL; + return rc; +} + +#define DEFAULT_VLED_MAX_UV 3500000 +#define DEFAULT_FCC_MA 2000 +static int smb_parse_dt(struct smbchg_chip *chip) +{ + int rc = 0, ocp_thresh = -EINVAL; + struct device_node *node = chip->dev->of_node; + const char *dc_psy_type, *bpd; + + if (!node) { + dev_err(chip->dev, "device tree info. missing\n"); + return -EINVAL; + } + + /* read optional u32 properties */ + OF_PROP_READ(chip, ocp_thresh, + "ibat-ocp-threshold-ua", rc, 1); + if (ocp_thresh >= 0) + smbchg_ibat_ocp_threshold_ua = ocp_thresh; + OF_PROP_READ(chip, chip->iterm_ma, "iterm-ma", rc, 1); + OF_PROP_READ(chip, chip->cfg_fastchg_current_ma, + "fastchg-current-ma", rc, 1); + if (chip->cfg_fastchg_current_ma == -EINVAL) + chip->cfg_fastchg_current_ma = DEFAULT_FCC_MA; + OF_PROP_READ(chip, chip->vfloat_mv, "float-voltage-mv", rc, 1); + OF_PROP_READ(chip, chip->safety_time, "charging-timeout-mins", rc, 1); + OF_PROP_READ(chip, chip->vled_max_uv, "vled-max-uv", rc, 1); + if (chip->vled_max_uv < 0) + chip->vled_max_uv = DEFAULT_VLED_MAX_UV; + OF_PROP_READ(chip, chip->rpara_uohm, "rparasitic-uohm", rc, 1); + if (chip->rpara_uohm < 0) + chip->rpara_uohm = 0; + OF_PROP_READ(chip, chip->prechg_safety_time, "precharging-timeout-mins", + rc, 1); + OF_PROP_READ(chip, chip->fastchg_current_comp, "fastchg-current-comp", + rc, 1); + OF_PROP_READ(chip, chip->float_voltage_comp, "float-voltage-comp", + rc, 1); + if (chip->safety_time != -EINVAL && + (chip->safety_time > chg_time[ARRAY_SIZE(chg_time) - 1])) { + dev_err(chip->dev, "Bad charging-timeout-mins %d\n", + chip->safety_time); + return -EINVAL; + } + if (chip->prechg_safety_time != -EINVAL && + (chip->prechg_safety_time > + prechg_time[ARRAY_SIZE(prechg_time) - 1])) { + dev_err(chip->dev, "Bad precharging-timeout-mins %d\n", + chip->prechg_safety_time); + return -EINVAL; + } + OF_PROP_READ(chip, chip->resume_delta_mv, "resume-delta-mv", rc, 1); + OF_PROP_READ(chip, chip->parallel.min_current_thr_ma, + "parallel-usb-min-current-ma", rc, 1); + OF_PROP_READ(chip, chip->parallel.min_9v_current_thr_ma, + "parallel-usb-9v-min-current-ma", rc, 1); + OF_PROP_READ(chip, chip->parallel.allowed_lowering_ma, + "parallel-allowed-lowering-ma", rc, 1); + if (chip->parallel.min_current_thr_ma != -EINVAL + && chip->parallel.min_9v_current_thr_ma != -EINVAL) + chip->parallel.avail = true; + /* + * use the dt values if they exist, otherwise do not touch the params + */ + of_property_read_u32(node, "qcom,parallel-main-chg-fcc-percent", + &smbchg_main_chg_fcc_percent); + of_property_read_u32(node, "qcom,parallel-main-chg-icl-percent", + &smbchg_main_chg_icl_percent); + pr_smb(PR_STATUS, "parallel usb thr: %d, 9v thr: %d\n", + chip->parallel.min_current_thr_ma, + chip->parallel.min_9v_current_thr_ma); + OF_PROP_READ(chip, chip->jeita_temp_hard_limit, + "jeita-temp-hard-limit", rc, 1); + OF_PROP_READ(chip, chip->aicl_rerun_period_s, + "aicl-rerun-period-s", rc, 1); + OF_PROP_READ(chip, chip->vchg_adc_channel, + "vchg-adc-channel-id", rc, 1); + + /* read boolean configuration properties */ + chip->use_vfloat_adjustments = of_property_read_bool(node, + "qcom,autoadjust-vfloat"); + chip->bmd_algo_disabled = of_property_read_bool(node, + "qcom,bmd-algo-disabled"); + chip->iterm_disabled = of_property_read_bool(node, + "qcom,iterm-disabled"); + chip->soft_vfloat_comp_disabled = of_property_read_bool(node, + "qcom,soft-vfloat-comp-disabled"); + chip->chg_enabled = !(of_property_read_bool(node, + "qcom,charging-disabled")); + chip->charge_unknown_battery = of_property_read_bool(node, + "qcom,charge-unknown-battery"); + chip->chg_inhibit_en = of_property_read_bool(node, + "qcom,chg-inhibit-en"); + chip->chg_inhibit_source_fg = of_property_read_bool(node, + "qcom,chg-inhibit-fg"); + chip->low_volt_dcin = of_property_read_bool(node, + "qcom,low-volt-dcin"); + chip->force_aicl_rerun = of_property_read_bool(node, + "qcom,force-aicl-rerun"); + chip->skip_usb_suspend_for_fake_battery = of_property_read_bool(node, + "qcom,skip-usb-suspend-for-fake-battery"); + + /* parse the battery missing detection pin source */ + rc = of_property_read_string(chip->pdev->dev.of_node, + "qcom,bmd-pin-src", &bpd); + if (rc) { + /* Select BAT_THM as default BPD scheme */ + chip->bmd_pin_src = BPD_TYPE_DEFAULT; + rc = 0; + } else { + chip->bmd_pin_src = get_bpd(bpd); + if (chip->bmd_pin_src < 0) { + dev_err(chip->dev, + "failed to determine bpd schema %d\n", rc); + return rc; + } + } + + /* parse the dc power supply configuration */ + rc = of_property_read_string(node, "qcom,dc-psy-type", &dc_psy_type); + if (rc) { + chip->dc_psy_type = -EINVAL; + rc = 0; + } else { + if (strcmp(dc_psy_type, "Mains") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_MAINS; + else if (strcmp(dc_psy_type, "Wireless") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIRELESS; + else if (strcmp(dc_psy_type, "Wipower") == 0) + chip->dc_psy_type = POWER_SUPPLY_TYPE_WIPOWER; + } + if (chip->dc_psy_type != -EINVAL) { + OF_PROP_READ(chip, chip->dc_target_current_ma, + "dc-psy-ma", rc, 0); + if (rc) + return rc; + if (chip->dc_target_current_ma < DC_MA_MIN + || chip->dc_target_current_ma > DC_MA_MAX) { + dev_err(chip->dev, "Bad dc mA %d\n", + chip->dc_target_current_ma); + return -EINVAL; + } + } + + if (chip->dc_psy_type == POWER_SUPPLY_TYPE_WIPOWER) + smb_parse_wipower_dt(chip); + + /* read the bms power supply name */ + rc = of_property_read_string(node, "qcom,bms-psy-name", + &chip->bms_psy_name); + if (rc) + chip->bms_psy_name = NULL; + + /* read the battery power supply name */ + rc = of_property_read_string(node, "qcom,battery-psy-name", + &chip->battery_psy_name); + if (rc) + chip->battery_psy_name = "battery"; + + /* Get the charger led support property */ + chip->cfg_chg_led_sw_ctrl = + of_property_read_bool(node, "qcom,chg-led-sw-controls"); + chip->cfg_chg_led_support = + of_property_read_bool(node, "qcom,chg-led-support"); + + if (of_find_property(node, "qcom,thermal-mitigation", + &chip->thermal_levels)) { + chip->thermal_mitigation = devm_kzalloc(chip->dev, + chip->thermal_levels, + GFP_KERNEL); + + if (chip->thermal_mitigation == NULL) { + dev_err(chip->dev, "thermal mitigation kzalloc() failed.\n"); + return -ENOMEM; + } + + chip->thermal_levels /= sizeof(int); + rc = of_property_read_u32_array(node, + "qcom,thermal-mitigation", + chip->thermal_mitigation, chip->thermal_levels); + if (rc) { + dev_err(chip->dev, + "Couldn't read threm limits rc = %d\n", rc); + return rc; + } + } + + chip->skip_usb_notification + = of_property_read_bool(node, + "qcom,skip-usb-notification"); + + chip->otg_pinctrl = of_property_read_bool(node, "qcom,otg-pinctrl"); + + return 0; +} + +#define SUBTYPE_REG 0x5 +#define SMBCHG_CHGR_SUBTYPE 0x1 +#define SMBCHG_OTG_SUBTYPE 0x8 +#define SMBCHG_BAT_IF_SUBTYPE 0x3 +#define SMBCHG_USB_CHGPTH_SUBTYPE 0x4 +#define SMBCHG_DC_CHGPTH_SUBTYPE 0x5 +#define SMBCHG_MISC_SUBTYPE 0x7 +#define SMBCHG_LITE_CHGR_SUBTYPE 0x51 +#define SMBCHG_LITE_OTG_SUBTYPE 0x58 +#define SMBCHG_LITE_BAT_IF_SUBTYPE 0x53 +#define SMBCHG_LITE_USB_CHGPTH_SUBTYPE 0x54 +#define SMBCHG_LITE_DC_CHGPTH_SUBTYPE 0x55 +#define SMBCHG_LITE_MISC_SUBTYPE 0x57 +static int smbchg_request_irq(struct smbchg_chip *chip, + struct device_node *child, + int irq_num, char *irq_name, + irqreturn_t (irq_handler)(int irq, void *_chip), + int flags) +{ + int rc; + + irq_num = of_irq_get_byname(child, irq_name); + if (irq_num < 0) { + dev_err(chip->dev, "Unable to get %s irqn", irq_name); + rc = -ENXIO; + } + rc = devm_request_threaded_irq(chip->dev, + irq_num, NULL, irq_handler, flags, irq_name, + chip); + if (rc < 0) { + dev_err(chip->dev, "Unable to request %s irq: %dn", + irq_name, rc); + rc = -ENXIO; + } + return 0; +} + +static int smbchg_request_irqs(struct smbchg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + unsigned long flags = IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING + | IRQF_ONESHOT; + + if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(chip->pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + rc = 0; + continue; + } + + rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1); + if (rc) { + dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n", + rc); + return rc; + } + + switch (subtype) { + case SMBCHG_CHGR_SUBTYPE: + case SMBCHG_LITE_CHGR_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->chg_error_irq, "chg-error", + chg_error_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->taper_irq, + "chg-taper-thr", taper_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + disable_irq_nosync(chip->taper_irq); + rc = smbchg_request_irq(chip, child, chip->chg_term_irq, + "chg-tcc-thr", chg_term_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->recharge_irq, + "chg-rechg-thr", recharge_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->fastchg_irq, + "chg-p2f-thr", fastchg_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->chg_term_irq); + enable_irq_wake(chip->chg_error_irq); + enable_irq_wake(chip->fastchg_irq); + break; + case SMBCHG_BAT_IF_SUBTYPE: + case SMBCHG_LITE_BAT_IF_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->batt_hot_irq, + "batt-hot", batt_hot_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_warm_irq, + "batt-warm", batt_warm_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_cool_irq, + "batt-cool", batt_cool_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_cold_irq, + "batt-cold", batt_cold_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->batt_missing_irq, + "batt-missing", batt_pres_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->vbat_low_irq, + "batt-low", vbat_low_handler, flags); + if (rc < 0) + return rc; + + enable_irq_wake(chip->batt_hot_irq); + enable_irq_wake(chip->batt_warm_irq); + enable_irq_wake(chip->batt_cool_irq); + enable_irq_wake(chip->batt_cold_irq); + enable_irq_wake(chip->batt_missing_irq); + enable_irq_wake(chip->vbat_low_irq); + break; + case SMBCHG_USB_CHGPTH_SUBTYPE: + case SMBCHG_LITE_USB_CHGPTH_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->usbin_uv_irq, + "usbin-uv", usbin_uv_handler, + flags | IRQF_EARLY_RESUME); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->usbin_ov_irq, + "usbin-ov", usbin_ov_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->src_detect_irq, + "usbin-src-det", + src_detect_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->aicl_done_irq, + "aicl-done", + aicl_done_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + + if (chip->schg_version != QPNP_SCHG_LITE) { + rc = smbchg_request_irq(chip, child, + chip->otg_fail_irq, "otg-fail", + otg_fail_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_oc_irq, "otg-oc", + otg_oc_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->usbid_change_irq, "usbid-change", + usbid_change_handler, + (IRQF_TRIGGER_FALLING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + enable_irq_wake(chip->otg_oc_irq); + enable_irq_wake(chip->usbid_change_irq); + enable_irq_wake(chip->otg_fail_irq); + } + enable_irq_wake(chip->usbin_uv_irq); + enable_irq_wake(chip->usbin_ov_irq); + enable_irq_wake(chip->src_detect_irq); + if (chip->parallel.avail && chip->usb_present) { + rc = enable_irq_wake(chip->aicl_done_irq); + chip->enable_aicl_wake = true; + } + break; + case SMBCHG_DC_CHGPTH_SUBTYPE: + case SMBCHG_LITE_DC_CHGPTH_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->dcin_uv_irq, + "dcin-uv", dcin_uv_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->dcin_uv_irq); + break; + case SMBCHG_MISC_SUBTYPE: + case SMBCHG_LITE_MISC_SUBTYPE: + rc = smbchg_request_irq(chip, child, chip->power_ok_irq, + "power-ok", power_ok_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->chg_hot_irq, + "temp-shutdown", chg_hot_handler, flags); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, chip->wdog_timeout_irq, + "wdog-timeout", + wdog_timeout_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->chg_hot_irq); + enable_irq_wake(chip->wdog_timeout_irq); + break; + case SMBCHG_OTG_SUBTYPE: + break; + case SMBCHG_LITE_OTG_SUBTYPE: + rc = smbchg_request_irq(chip, child, + chip->usbid_change_irq, "usbid-change", + usbid_change_handler, + (IRQF_TRIGGER_FALLING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_oc_irq, "otg-oc", + otg_oc_handler, + (IRQF_TRIGGER_RISING | IRQF_ONESHOT)); + if (rc < 0) + return rc; + rc = smbchg_request_irq(chip, child, + chip->otg_fail_irq, "otg-fail", + otg_fail_handler, flags); + if (rc < 0) + return rc; + enable_irq_wake(chip->usbid_change_irq); + enable_irq_wake(chip->otg_oc_irq); + enable_irq_wake(chip->otg_fail_irq); + break; + } + } + + return rc; +} + +#define REQUIRE_BASE(chip, base, rc) \ +do { \ + if (!rc && !chip->base) { \ + dev_err(chip->dev, "Missing " #base "\n"); \ + rc = -EINVAL; \ + } \ +} while (0) + +static int smbchg_parse_peripherals(struct smbchg_chip *chip) +{ + int rc = 0; + unsigned int base; + struct device_node *child; + u8 subtype; + + if (of_get_available_child_count(chip->pdev->dev.of_node) == 0) { + pr_err("no child nodes\n"); + return -ENXIO; + } + + for_each_available_child_of_node(chip->pdev->dev.of_node, child) { + rc = of_property_read_u32(child, "reg", &base); + if (rc < 0) { + rc = 0; + continue; + } + + rc = smbchg_read(chip, &subtype, base + SUBTYPE_REG, 1); + if (rc) { + dev_err(chip->dev, "Peripheral subtype read failed rc=%d\n", + rc); + return rc; + } + + switch (subtype) { + case SMBCHG_CHGR_SUBTYPE: + case SMBCHG_LITE_CHGR_SUBTYPE: + chip->chgr_base = base; + break; + case SMBCHG_BAT_IF_SUBTYPE: + case SMBCHG_LITE_BAT_IF_SUBTYPE: + chip->bat_if_base = base; + break; + case SMBCHG_USB_CHGPTH_SUBTYPE: + case SMBCHG_LITE_USB_CHGPTH_SUBTYPE: + chip->usb_chgpth_base = base; + break; + case SMBCHG_DC_CHGPTH_SUBTYPE: + case SMBCHG_LITE_DC_CHGPTH_SUBTYPE: + chip->dc_chgpth_base = base; + break; + case SMBCHG_MISC_SUBTYPE: + case SMBCHG_LITE_MISC_SUBTYPE: + chip->misc_base = base; + break; + case SMBCHG_OTG_SUBTYPE: + case SMBCHG_LITE_OTG_SUBTYPE: + chip->otg_base = base; + break; + } + } + + REQUIRE_BASE(chip, chgr_base, rc); + REQUIRE_BASE(chip, bat_if_base, rc); + REQUIRE_BASE(chip, usb_chgpth_base, rc); + REQUIRE_BASE(chip, dc_chgpth_base, rc); + REQUIRE_BASE(chip, misc_base, rc); + + return rc; +} + +static inline void dump_reg(struct smbchg_chip *chip, u16 addr, + const char *name) +{ + u8 reg; + + smbchg_read(chip, ®, addr, 1); + pr_smb(PR_DUMP, "%s - %04X = %02X\n", name, addr, reg); +} + +/* dumps useful registers for debug */ +static void dump_regs(struct smbchg_chip *chip) +{ + u16 addr; + + /* charger peripheral */ + for (addr = 0xB; addr <= 0x10; addr++) + dump_reg(chip, chip->chgr_base + addr, "CHGR Status"); + for (addr = 0xF0; addr <= 0xFF; addr++) + dump_reg(chip, chip->chgr_base + addr, "CHGR Config"); + /* battery interface peripheral */ + dump_reg(chip, chip->bat_if_base + RT_STS, "BAT_IF Status"); + dump_reg(chip, chip->bat_if_base + CMD_CHG_REG, "BAT_IF Command"); + for (addr = 0xF0; addr <= 0xFB; addr++) + dump_reg(chip, chip->bat_if_base + addr, "BAT_IF Config"); + /* usb charge path peripheral */ + for (addr = 0x7; addr <= 0x10; addr++) + dump_reg(chip, chip->usb_chgpth_base + addr, "USB Status"); + dump_reg(chip, chip->usb_chgpth_base + CMD_IL, "USB Command"); + for (addr = 0xF0; addr <= 0xF5; addr++) + dump_reg(chip, chip->usb_chgpth_base + addr, "USB Config"); + /* dc charge path peripheral */ + dump_reg(chip, chip->dc_chgpth_base + RT_STS, "DC Status"); + for (addr = 0xF0; addr <= 0xF6; addr++) + dump_reg(chip, chip->dc_chgpth_base + addr, "DC Config"); + /* misc peripheral */ + dump_reg(chip, chip->misc_base + IDEV_STS, "MISC Status"); + dump_reg(chip, chip->misc_base + RT_STS, "MISC Status"); + for (addr = 0xF0; addr <= 0xF3; addr++) + dump_reg(chip, chip->misc_base + addr, "MISC CFG"); +} + +static int create_debugfs_entries(struct smbchg_chip *chip) +{ + struct dentry *ent; + + chip->debug_root = debugfs_create_dir("qpnp-smbcharger", NULL); + if (!chip->debug_root) { + dev_err(chip->dev, "Couldn't create debug dir\n"); + return -EINVAL; + } + + ent = debugfs_create_file("force_dcin_icl_check", + S_IFREG | S_IWUSR | S_IRUGO, + chip->debug_root, chip, + &force_dcin_icl_ops); + if (!ent) { + dev_err(chip->dev, + "Couldn't create force dcin icl check file\n"); + return -EINVAL; + } + return 0; +} + +static int smbchg_check_chg_version(struct smbchg_chip *chip) +{ + struct pmic_revid_data *pmic_rev_id; + struct device_node *revid_dev_node; + int rc; + + revid_dev_node = of_parse_phandle(chip->pdev->dev.of_node, + "qcom,pmic-revid", 0); + if (!revid_dev_node) { + pr_err("Missing qcom,pmic-revid property - driver failed\n"); + return -EINVAL; + } + + pmic_rev_id = get_revid_data(revid_dev_node); + if (IS_ERR(pmic_rev_id)) { + rc = PTR_ERR(revid_dev_node); + if (rc != -EPROBE_DEFER) + pr_err("Unable to get pmic_revid rc=%d\n", rc); + return rc; + } + + switch (pmic_rev_id->pmic_subtype) { + case PMI8994: + chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA + | SMBCHG_BATT_OV_WA + | SMBCHG_CC_ESR_WA + | SMBCHG_RESTART_WA; + use_pmi8994_tables(chip); + chip->schg_version = QPNP_SCHG; + break; + case PMI8950: + case PMI8937: + chip->wa_flags |= SMBCHG_BATT_OV_WA; + if (pmic_rev_id->rev4 < 2) /* PMI8950 1.0 */ { + chip->wa_flags |= SMBCHG_AICL_DEGLITCH_WA; + } else { /* rev > PMI8950 v1.0 */ + chip->wa_flags |= SMBCHG_HVDCP_9V_EN_WA + | SMBCHG_USB100_WA; + } + use_pmi8994_tables(chip); + chip->tables.aicl_rerun_period_table = + aicl_rerun_period_schg_lite; + chip->tables.aicl_rerun_period_len = + ARRAY_SIZE(aicl_rerun_period_schg_lite); + + chip->schg_version = QPNP_SCHG_LITE; + if (pmic_rev_id->pmic_subtype == PMI8937) + chip->hvdcp_not_supported = true; + break; + case PMI8996: + chip->wa_flags |= SMBCHG_CC_ESR_WA + | SMBCHG_FLASH_ICL_DISABLE_WA + | SMBCHG_RESTART_WA + | SMBCHG_FLASH_BUCK_SWITCH_FREQ_WA; + use_pmi8996_tables(chip); + chip->schg_version = QPNP_SCHG; + break; + default: + pr_err("PMIC subtype %d not supported, WA flags not set\n", + pmic_rev_id->pmic_subtype); + } + + pr_smb(PR_STATUS, "pmic=%s, wa_flags=0x%x, hvdcp_supported=%s\n", + pmic_rev_id->pmic_name, chip->wa_flags, + chip->hvdcp_not_supported ? "false" : "true"); + + return 0; +} + +static void rerun_hvdcp_det_if_necessary(struct smbchg_chip *chip) +{ + enum power_supply_type usb_supply_type; + char *usb_type_name; + int rc; + + if (!(chip->wa_flags & SMBCHG_RESTART_WA)) + return; + + read_usb_type(chip, &usb_type_name, &usb_supply_type); + if (usb_supply_type == POWER_SUPPLY_TYPE_USB_DCP + && !is_hvdcp_present(chip)) { + pr_smb(PR_STATUS, "DCP found rerunning APSD\n"); + rc = vote(chip->usb_icl_votable, + CHG_SUSPEND_WORKAROUND_ICL_VOTER, true, 300); + if (rc < 0) + pr_err("Couldn't vote for 300mA for suspend wa, going ahead rc=%d\n", + rc); + + pr_smb(PR_STATUS, "Faking Removal\n"); + fake_insertion_removal(chip, false); + msleep(500); + pr_smb(PR_STATUS, "Faking Insertion\n"); + fake_insertion_removal(chip, true); + + read_usb_type(chip, &usb_type_name, &usb_supply_type); + if (usb_supply_type != POWER_SUPPLY_TYPE_USB_DCP) { + msleep(500); + pr_smb(PR_STATUS, "Fake Removal again as type!=DCP\n"); + fake_insertion_removal(chip, false); + msleep(500); + pr_smb(PR_STATUS, "Fake Insert again as type!=DCP\n"); + fake_insertion_removal(chip, true); + } + + rc = vote(chip->usb_icl_votable, + CHG_SUSPEND_WORKAROUND_ICL_VOTER, false, 0); + if (rc < 0) + pr_err("Couldn't vote for 0 for suspend wa, going ahead rc=%d\n", + rc); + } +} + +static int smbchg_probe(struct platform_device *pdev) +{ + int rc; + struct smbchg_chip *chip; + struct power_supply *typec_psy = NULL; + struct qpnp_vadc_chip *vadc_dev, *vchg_vadc_dev; + const char *typec_psy_name; + struct power_supply_config usb_psy_cfg = {}; + struct power_supply_config batt_psy_cfg = {}; + struct power_supply_config dc_psy_cfg = {}; + + if (of_property_read_bool(pdev->dev.of_node, "qcom,external-typec")) { + /* read the type power supply name */ + rc = of_property_read_string(pdev->dev.of_node, + "qcom,typec-psy-name", &typec_psy_name); + if (rc) { + pr_err("failed to get prop typec-psy-name rc=%d\n", + rc); + return rc; + } + + typec_psy = power_supply_get_by_name(typec_psy_name); + if (!typec_psy) { + pr_smb(PR_STATUS, + "Type-C supply not found, deferring probe\n"); + return -EPROBE_DEFER; + } + } + + vadc_dev = NULL; + if (of_find_property(pdev->dev.of_node, "qcom,dcin-vadc", NULL)) { + vadc_dev = qpnp_get_vadc(&pdev->dev, "dcin"); + if (IS_ERR(vadc_dev)) { + rc = PTR_ERR(vadc_dev); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, + "Couldn't get vadc rc=%d\n", + rc); + return rc; + } + } + + vchg_vadc_dev = NULL; + if (of_find_property(pdev->dev.of_node, "qcom,vchg_sns-vadc", NULL)) { + vchg_vadc_dev = qpnp_get_vadc(&pdev->dev, "vchg_sns"); + if (IS_ERR(vchg_vadc_dev)) { + rc = PTR_ERR(vchg_vadc_dev); + if (rc != -EPROBE_DEFER) + dev_err(&pdev->dev, "Couldn't get vadc 'vchg' rc=%d\n", + rc); + return rc; + } + } + + + chip = devm_kzalloc(&pdev->dev, sizeof(*chip), GFP_KERNEL); + if (!chip) + return -ENOMEM; + + chip->regmap = dev_get_regmap(pdev->dev.parent, NULL); + if (!chip->regmap) { + dev_err(&pdev->dev, "Couldn't get parent's regmap\n"); + return -EINVAL; + } + + chip->fcc_votable = create_votable("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/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 829876226689..a2136c6863d3 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -616,6 +616,15 @@ config MSM_QDSP6_APRV2 used by audio driver to configure QDSP6's ASM, ADM and AFE. +config MSM_QDSP6_APRV2_VM + bool "Audio QDSP6 APRv2 virtualization support" + depends on MSM_HAB + help + Enable APRv2 IPC protocol support over + HAB between application processor and + QDSP6. APR is used by audio driver to + configure QDSP6's ASM, ADM and AFE. + config MSM_QDSP6_APRV3 bool "Audio QDSP6 APRv3 support" depends on MSM_SMD @@ -800,7 +809,8 @@ config MSM_EVENT_TIMER config MSM_AVTIMER tristate "Avtimer Driver" - depends on MSM_QDSP6_APRV2 || MSM_QDSP6_APRV3 || MSM_QDSP6_APRV2_GLINK + depends on MSM_QDSP6_APRV2 || MSM_QDSP6_APRV3 || MSM_QDSP6_APRV2_GLINK || \ + MSM_QDSP6_APRV2_VM help This driver gets the Q6 out of power collapsed state and exposes ioctl control to read avtimer tick. diff --git a/drivers/soc/qcom/qdsp6v2/Makefile b/drivers/soc/qcom/qdsp6v2/Makefile index 8c5b0d0e81c8..90feb8b659d1 100644 --- a/drivers/soc/qcom/qdsp6v2/Makefile +++ b/drivers/soc/qcom/qdsp6v2/Makefile @@ -2,7 +2,9 @@ obj-$(CONFIG_MSM_QDSP6_APRV2) += apr.o apr_v2.o apr_tal.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV3) += apr.o apr_v3.o apr_tal.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV2_GLINK) += apr.o apr_v2.o apr_tal_glink.o voice_svc.o obj-$(CONFIG_MSM_QDSP6_APRV3_GLINK) += apr.o apr_v3.o apr_tal_glink.o voice_svc.o +obj-$(CONFIG_MSM_QDSP6_APRV2_VM) += apr_vm.o apr_v2.o voice_svc.o obj-$(CONFIG_SND_SOC_MSM_QDSP6V2_INTF) += msm_audio_ion.o +obj-$(CONFIG_SND_SOC_QDSP6V2_VM) += msm_audio_ion_vm.o obj-$(CONFIG_MSM_ADSP_LOADER) += adsp-loader.o obj-$(CONFIG_MSM_QDSP6_SSR) += audio_ssr.o obj-$(CONFIG_MSM_QDSP6_PDR) += audio_pdr.o diff --git a/drivers/soc/qcom/qdsp6v2/apr_vm.c b/drivers/soc/qcom/qdsp6v2/apr_vm.c new file mode 100644 index 000000000000..d0ea7b22717a --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/apr_vm.c @@ -0,0 +1,1270 @@ +/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/uaccess.h> +#include <linux/spinlock.h> +#include <linux/list.h> +#include <linux/sched.h> +#include <linux/wait.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <linux/debugfs.h> +#include <linux/platform_device.h> +#include <linux/sysfs.h> +#include <linux/device.h> +#include <linux/slab.h> +#include <soc/qcom/subsystem_restart.h> +#include <soc/qcom/scm.h> +#include <sound/apr_audio-v2.h> +#include <linux/qdsp6v2/apr.h> +#include <linux/qdsp6v2/apr_tal.h> +#include <linux/qdsp6v2/aprv2_vm.h> +#include <linux/qdsp6v2/dsp_debug.h> +#include <linux/qdsp6v2/audio_notifier.h> +#include <linux/ipc_logging.h> +#include <linux/habmm.h> + +#define APR_PKT_IPC_LOG_PAGE_CNT 2 +#define APR_VM_CB_THREAD_NAME "apr_vm_cb_thread" +#define APR_TX_BUF_SIZE 4096 +#define APR_RX_BUF_SIZE 4096 + +static struct apr_q6 q6; +static struct apr_client client[APR_DEST_MAX][APR_CLIENT_MAX]; +static void *apr_pkt_ctx; +static wait_queue_head_t dsp_wait; +static wait_queue_head_t modem_wait; +static bool is_modem_up; +static bool is_initial_boot; +/* Subsystem restart: QDSP6 data, functions */ +static struct workqueue_struct *apr_reset_workqueue; +static void apr_reset_deregister(struct work_struct *work); +static void dispatch_event(unsigned long code, uint16_t proc); +struct apr_reset_work { + void *handle; + struct work_struct work; +}; + +static bool apr_cf_debug; + +#ifdef CONFIG_DEBUG_FS +static struct dentry *debugfs_apr_debug; +static ssize_t apr_debug_write(struct file *filp, const char __user *ubuf, + size_t cnt, loff_t *ppos) +{ + char cmd; + + if (copy_from_user(&cmd, ubuf, 1)) + return -EFAULT; + + apr_cf_debug = (cmd == '1') ? true : false; + + return cnt; +} + +static const struct file_operations apr_debug_ops = { + .write = apr_debug_write, +}; +#endif + +#define APR_PKT_INFO(x...) \ +do { \ + if (apr_pkt_ctx) \ + ipc_log_string(apr_pkt_ctx, "<APR>: "x); \ +} while (0) + +/* hab handle */ +static uint32_t hab_handle_tx; +static uint32_t hab_handle_rx; +static char apr_tx_buf[APR_TX_BUF_SIZE]; +static char apr_rx_buf[APR_RX_BUF_SIZE]; + +/* apr callback thread task */ +static struct task_struct *apr_vm_cb_thread_task; +static int pid; + + +struct apr_svc_table { + char name[64]; + int idx; + int id; + int dest_svc; + int client_id; + int handle; +}; + +/* + * src svc should be assigned dynamically through apr registration: + * 1. replace with a proper string name for registration. + * e.g. "qcom.apps.lnx." + name + * 2. register apr BE, retrieve dynamic src svc address, + * apr handle and store in svc tbl. + */ + +static struct mutex m_lock_tbl_qdsp6; + +static struct apr_svc_table svc_tbl_qdsp6[] = { + { + .name = "AFE", + .idx = 0, + .id = 0, + .dest_svc = APR_SVC_AFE, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "ASM", + .idx = 1, + .id = 0, + .dest_svc = APR_SVC_ASM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "ADM", + .idx = 2, + .id = 0, + .dest_svc = APR_SVC_ADM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CORE", + .idx = 3, + .id = 0, + .dest_svc = APR_SVC_ADSP_CORE, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "TEST", + .idx = 4, + .id = 0, + .dest_svc = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "MVM", + .idx = 5, + .id = 0, + .dest_svc = APR_SVC_ADSP_MVM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CVS", + .idx = 6, + .id = 0, + .dest_svc = APR_SVC_ADSP_CVS, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "CVP", + .idx = 7, + .id = 0, + .dest_svc = APR_SVC_ADSP_CVP, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "USM", + .idx = 8, + .id = 0, + .dest_svc = APR_SVC_USM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, + { + .name = "VIDC", + .idx = 9, + .id = 0, + .dest_svc = APR_SVC_VIDC, + .handle = 0, + }, + { + .name = "LSM", + .idx = 10, + .id = 0, + .dest_svc = APR_SVC_LSM, + .client_id = APR_CLIENT_AUDIO, + .handle = 0, + }, +}; + +static struct mutex m_lock_tbl_voice; + +static struct apr_svc_table svc_tbl_voice[] = { + { + .name = "VSM", + .idx = 0, + .id = 0, + .dest_svc = APR_SVC_VSM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "VPM", + .idx = 1, + .id = 0, + .dest_svc = APR_SVC_VPM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "MVS", + .idx = 2, + .id = 0, + .dest_svc = APR_SVC_MVS, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "MVM", + .idx = 3, + .id = 0, + .dest_svc = APR_SVC_MVM, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "CVS", + .idx = 4, + .id = 0, + .dest_svc = APR_SVC_CVS, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "CVP", + .idx = 5, + .id = 0, + .dest_svc = APR_SVC_CVP, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "SRD", + .idx = 6, + .id = 0, + .dest_svc = APR_SVC_SRD, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, + { + .name = "TEST", + .idx = 7, + .id = 0, + .dest_svc = APR_SVC_TEST_CLIENT, + .client_id = APR_CLIENT_VOICE, + .handle = 0, + }, +}; + +enum apr_subsys_state apr_get_modem_state(void) +{ + return atomic_read(&q6.modem_state); +} + +void apr_set_modem_state(enum apr_subsys_state state) +{ + atomic_set(&q6.modem_state, state); +} + +enum apr_subsys_state apr_cmpxchg_modem_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.modem_state, prev, new); +} + +static void apr_modem_down(unsigned long opcode) +{ + apr_set_modem_state(APR_SUBSYS_DOWN); + dispatch_event(opcode, APR_DEST_MODEM); +} + +static void apr_modem_up(void) +{ + if (apr_cmpxchg_modem_state(APR_SUBSYS_DOWN, APR_SUBSYS_UP) == + APR_SUBSYS_DOWN) + wake_up(&modem_wait); + is_modem_up = 1; +} + +enum apr_subsys_state apr_get_q6_state(void) +{ + return atomic_read(&q6.q6_state); +} +EXPORT_SYMBOL(apr_get_q6_state); + +int apr_set_q6_state(enum apr_subsys_state state) +{ + pr_debug("%s: setting adsp state %d\n", __func__, state); + if (state < APR_SUBSYS_DOWN || state > APR_SUBSYS_LOADED) + return -EINVAL; + atomic_set(&q6.q6_state, state); + return 0; +} +EXPORT_SYMBOL(apr_set_q6_state); + +enum apr_subsys_state apr_cmpxchg_q6_state(enum apr_subsys_state prev, + enum apr_subsys_state new) +{ + return atomic_cmpxchg(&q6.q6_state, prev, new); +} + +static void apr_adsp_down(unsigned long opcode) +{ + apr_set_q6_state(APR_SUBSYS_DOWN); + dispatch_event(opcode, APR_DEST_QDSP6); +} + +static void apr_adsp_up(void) +{ + if (apr_cmpxchg_q6_state(APR_SUBSYS_DOWN, APR_SUBSYS_LOADED) == + APR_SUBSYS_DOWN) + wake_up(&dsp_wait); +} + +int apr_wait_for_device_up(int dest_id) +{ + int rc = -1; + + if (dest_id == APR_DEST_MODEM) + rc = wait_event_interruptible_timeout(modem_wait, + (apr_get_modem_state() == APR_SUBSYS_UP), + (1 * HZ)); + else if (dest_id == APR_DEST_QDSP6) + rc = wait_event_interruptible_timeout(dsp_wait, + (apr_get_q6_state() == APR_SUBSYS_UP), + (1 * HZ)); + else + pr_err("%s: unknown dest_id %d\n", __func__, dest_id); + /* returns left time */ + return rc; +} + +static int apr_vm_nb_receive(int32_t handle, void *dest_buff, + uint32_t *size_bytes, uint32_t timeout) +{ + int rc; + uint32_t dest_buff_bytes = *size_bytes; + unsigned long delay = jiffies + (HZ / 2); + + do { + *size_bytes = dest_buff_bytes; + rc = habmm_socket_recv(handle, + dest_buff, + size_bytes, + timeout, + HABMM_SOCKET_RECV_FLAGS_NON_BLOCKING); + } while (time_before(jiffies, delay) && (rc == -EAGAIN) && + (*size_bytes == 0)); + + return rc; +} + +static int apr_vm_cb_process_evt(char *buf, int len) +{ + struct apr_client_data data; + struct apr_client *apr_client; + struct apr_svc *c_svc; + struct apr_hdr *hdr; + uint16_t hdr_size; + uint16_t msg_type; + uint16_t ver; + uint16_t src; + uint16_t svc; + uint16_t clnt; + int i; + int temp_port = 0; + uint32_t *ptr; + uint32_t evt_id; + + pr_debug("APR: len = %d\n", len); + ptr = (uint32_t *)buf; + pr_debug("\n*****************\n"); + for (i = 0; i < len/4; i++) + pr_debug("%x ", ptr[i]); + pr_debug("\n"); + pr_debug("\n*****************\n"); + + if (!buf || len <= APR_HDR_SIZE + sizeof(uint32_t)) { + pr_err("APR: Improper apr pkt received: %p %d\n", buf, len); + return -EINVAL; + } + + evt_id = *((int32_t *)buf); + if (evt_id != APRV2_VM_EVT_RX_PKT_AVAILABLE) { + pr_err("APR: Wrong evt id: %d\n", evt_id); + return -EINVAL; + } + hdr = (struct apr_hdr *)(buf + sizeof(uint32_t)); + + ver = hdr->hdr_field; + ver = (ver & 0x000F); + if (ver > APR_PKT_VER + 1) { + pr_err("APR: Wrong version: %d\n", ver); + return -EINVAL; + } + + hdr_size = hdr->hdr_field; + hdr_size = ((hdr_size & 0x00F0) >> 0x4) * 4; + if (hdr_size < APR_HDR_SIZE) { + pr_err("APR: Wrong hdr size:%d\n", hdr_size); + return -EINVAL; + } + + if (hdr->pkt_size < APR_HDR_SIZE) { + pr_err("APR: Wrong paket size\n"); + return -EINVAL; + } + + msg_type = hdr->hdr_field; + msg_type = (msg_type >> 0x08) & 0x0003; + if (msg_type >= APR_MSG_TYPE_MAX && msg_type != APR_BASIC_RSP_RESULT) { + pr_err("APR: Wrong message type: %d\n", msg_type); + return -EINVAL; + } + + /* + * dest_svc is dynamic created by apr service + * no need to check the range of dest_svc + */ + if (hdr->src_domain >= APR_DOMAIN_MAX || + hdr->dest_domain >= APR_DOMAIN_MAX || + hdr->src_svc >= APR_SVC_MAX) { + pr_err("APR: Wrong APR header\n"); + return -EINVAL; + } + + svc = hdr->dest_svc; + if (hdr->src_domain == APR_DOMAIN_MODEM) + clnt = APR_CLIENT_VOICE; + else if (hdr->src_domain == APR_DOMAIN_ADSP) + clnt = APR_CLIENT_AUDIO; + else { + pr_err("APR: Pkt from wrong source: %d\n", hdr->src_domain); + return -EINVAL; + } + + src = apr_get_data_src(hdr); + if (src == APR_DEST_MAX) + return -EINVAL; + + pr_debug("src =%d clnt = %d\n", src, clnt); + apr_client = &client[src][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) + if (apr_client->svc[i].id == svc) { + pr_debug("svc_id = %d\n", apr_client->svc[i].id); + c_svc = &apr_client->svc[i]; + break; + } + + if (i == APR_SVC_MAX) { + pr_err("APR: service is not registered\n"); + return -ENXIO; + } + + pr_debug("svc_idx = %d\n", i); + pr_debug("%x %x %x %p %p\n", c_svc->id, c_svc->dest_id, + c_svc->client_id, c_svc->fn, c_svc->priv); + + data.payload_size = hdr->pkt_size - hdr_size; + data.opcode = hdr->opcode; + data.src = src; + data.src_port = hdr->src_port; + data.dest_port = hdr->dest_port; + data.token = hdr->token; + data.msg_type = msg_type; + if (data.payload_size > 0) + data.payload = (char *)hdr + hdr_size; + + if (unlikely(apr_cf_debug)) { + if (hdr->opcode == APR_BASIC_RSP_RESULT && data.payload) { + uint32_t *ptr = data.payload; + + APR_PKT_INFO( + "Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X] rc[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, + hdr->opcode, hdr->token, ptr[1]); + } else { + APR_PKT_INFO( + "Rx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode, + hdr->token); + } + } + + temp_port = ((data.dest_port >> 8) * 8) + (data.dest_port & 0xFF); + pr_debug("port = %d t_port = %d\n", data.src_port, temp_port); + if (c_svc->port_cnt && c_svc->port_fn[temp_port]) + c_svc->port_fn[temp_port](&data, c_svc->port_priv[temp_port]); + else if (c_svc->fn) + c_svc->fn(&data, c_svc->priv); + else + pr_err("APR: Rxed a packet for NULL callback\n"); + + return 0; +} + +static int apr_vm_cb_thread(void *data) +{ + uint32_t apr_rx_buf_len; + struct aprv2_vm_ack_rx_pkt_available_t apr_ack; + int status = 0; + int ret = 0; + + while (1) { + apr_rx_buf_len = sizeof(apr_rx_buf); + ret = habmm_socket_recv(hab_handle_rx, + (void *)&apr_rx_buf, + &apr_rx_buf_len, + 0xFFFFFFFF, + 0); + if (ret) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, ret); + /* + * TODO: depends on the HAB error code, + * may need to implement + * a retry mechanism. + * break if recv failed ? + */ + break; + } + + status = apr_vm_cb_process_evt(apr_rx_buf, apr_rx_buf_len); + + apr_ack.status = status; + ret = habmm_socket_send(hab_handle_rx, + (void *)&apr_ack, + sizeof(apr_ack), + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + /* TODO: break if send failed ? */ + break; + } + } + + return ret; +} + +static int apr_vm_get_svc(const char *svc_name, int domain_id, int *client_id, + int *svc_idx, int *svc_id, int *dest_svc, int *handle) +{ + int i; + int size; + struct apr_svc_table *tbl; + struct mutex *lock; + struct aprv2_vm_cmd_register_rsp_t apr_rsp; + uint32_t apr_len; + int ret = 0; + struct { + uint32_t cmd_id; + struct aprv2_vm_cmd_register_t reg_cmd; + } tx_data; + + if (domain_id == APR_DOMAIN_ADSP) { + tbl = svc_tbl_qdsp6; + size = ARRAY_SIZE(svc_tbl_qdsp6); + lock = &m_lock_tbl_qdsp6; + } else { + tbl = svc_tbl_voice; + size = ARRAY_SIZE(svc_tbl_voice); + lock = &m_lock_tbl_voice; + } + + mutex_lock(lock); + for (i = 0; i < size; i++) { + if (!strcmp(svc_name, tbl[i].name)) { + *client_id = tbl[i].client_id; + *svc_idx = tbl[i].idx; + if (!tbl[i].id && !tbl[i].handle) { + /* need to register a new service */ + memset((void *) &tx_data, 0, sizeof(tx_data)); + + apr_len = sizeof(tx_data); + tx_data.cmd_id = APRV2_VM_CMDID_REGISTER; + tx_data.reg_cmd.name_size = snprintf( + tx_data.reg_cmd.name, + APRV2_VM_MAX_DNS_SIZE, + "qcom.apps.lnx.%s", + svc_name); + tx_data.reg_cmd.addr = 0; + ret = habmm_socket_send(hab_handle_tx, + (void *) &tx_data, + apr_len, + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + mutex_unlock(lock); + return ret; + } + /* wait for response */ + apr_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_len, + 0xFFFFFFFF); + if (ret) { + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + mutex_unlock(lock); + return ret; + } + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + ret = apr_rsp.status; + mutex_unlock(lock); + return ret; + } + /* update svc table */ + tbl[i].handle = apr_rsp.handle; + tbl[i].id = apr_rsp.addr & + APRV2_VM_PKT_SERVICE_ID_MASK; + } + *svc_id = tbl[i].id; + *dest_svc = tbl[i].dest_svc; + *handle = tbl[i].handle; + break; + } + } + mutex_unlock(lock); + + pr_debug("%s: svc_name = %s client_id = %d domain_id = %d\n", + __func__, svc_name, *client_id, domain_id); + pr_debug("%s: src_svc = %d dest_svc = %d handle = %d\n", + __func__, *svc_id, *dest_svc, *handle); + + if (i == size) { + pr_err("%s: APR: Wrong svc name %s\n", __func__, svc_name); + ret = -EINVAL; + } + + return ret; +} + +static int apr_vm_rel_svc(int domain_id, int svc_id, int handle) +{ + int i; + int size; + struct apr_svc_table *tbl; + struct mutex *lock; + struct aprv2_vm_cmd_deregister_rsp_t apr_rsp; + uint32_t apr_len; + int ret = 0; + struct { + uint32_t cmd_id; + struct aprv2_vm_cmd_deregister_t dereg_cmd; + } tx_data; + + if (domain_id == APR_DOMAIN_ADSP) { + tbl = svc_tbl_qdsp6; + size = ARRAY_SIZE(svc_tbl_qdsp6); + lock = &m_lock_tbl_qdsp6; + } else { + tbl = svc_tbl_voice; + size = ARRAY_SIZE(svc_tbl_voice); + lock = &m_lock_tbl_voice; + } + + mutex_lock(lock); + for (i = 0; i < size; i++) { + if (tbl[i].id == svc_id && tbl[i].handle == handle) { + /* need to deregister a service */ + memset((void *) &tx_data, 0, sizeof(tx_data)); + + apr_len = sizeof(tx_data); + tx_data.cmd_id = APRV2_VM_CMDID_DEREGISTER; + tx_data.dereg_cmd.handle = handle; + ret = habmm_socket_send(hab_handle_tx, + (void *) &tx_data, + apr_len, + 0); + if (ret) + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + /* + * TODO: if send failed, should not wait for recv. + * should clear regardless? + */ + /* wait for response */ + apr_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_len, + 0xFFFFFFFF); + if (ret) + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + ret = apr_rsp.status; + } + /* clear svc table */ + tbl[i].handle = 0; + tbl[i].id = 0; + break; + } + } + mutex_unlock(lock); + + if (i == size) { + pr_err("%s: APR: Wrong svc id %d handle %d\n", + __func__, svc_id, handle); + ret = -EINVAL; + } + + return ret; +} + +int apr_send_pkt(void *handle, uint32_t *buf) +{ + struct apr_svc *svc = handle; + struct apr_hdr *hdr; + unsigned long flags; + uint32_t *cmd_id = (uint32_t *)apr_tx_buf; + struct aprv2_vm_cmd_async_send_t *apr_send = + (struct aprv2_vm_cmd_async_send_t *)(apr_tx_buf + + sizeof(uint32_t)); + uint32_t apr_send_len; + struct aprv2_vm_cmd_async_send_rsp_t apr_rsp; + uint32_t apr_rsp_len; + int ret = 0; + + if (!handle || !buf) { + pr_err("APR: Wrong parameters\n"); + return -EINVAL; + } + if (svc->need_reset) { + pr_err("APR: send_pkt service need reset\n"); + return -ENETRESET; + } + + if ((svc->dest_id == APR_DEST_QDSP6) && + (apr_get_q6_state() != APR_SUBSYS_LOADED)) { + pr_err("%s: Still dsp is not Up\n", __func__); + return -ENETRESET; + } else if ((svc->dest_id == APR_DEST_MODEM) && + (apr_get_modem_state() == APR_SUBSYS_DOWN)) { + pr_err("%s: Still Modem is not Up\n", __func__); + return -ENETRESET; + } + + spin_lock_irqsave(&svc->w_lock, flags); + if (!svc->id || !svc->vm_handle) { + pr_err("APR: Still service is not yet opened\n"); + ret = -EINVAL; + goto done; + } + hdr = (struct apr_hdr *)buf; + + hdr->src_domain = APR_DOMAIN_APPS; + hdr->src_svc = svc->id; + hdr->dest_domain = svc->dest_domain; + hdr->dest_svc = svc->vm_dest_svc; + + if (unlikely(apr_cf_debug)) { + APR_PKT_INFO( + "Tx: src_addr[0x%X] dest_addr[0x%X] opcode[0x%X] token[0x%X]", + (hdr->src_domain << 8) | hdr->src_svc, + (hdr->dest_domain << 8) | hdr->dest_svc, hdr->opcode, + hdr->token); + } + + memset((void *)&apr_tx_buf, 0, sizeof(apr_tx_buf)); + /* pkt_size + cmd_id + handle */ + apr_send_len = hdr->pkt_size + sizeof(uint32_t) * 2; + *cmd_id = APRV2_VM_CMDID_ASYNC_SEND; + apr_send->handle = svc->vm_handle; + + /* safe check */ + if (hdr->pkt_size > APR_TX_BUF_SIZE - (sizeof(uint32_t) * 2)) { + pr_err("APR: Wrong pkt size %d\n", hdr->pkt_size); + ret = -ENOMEM; + goto done; + } + memcpy(&apr_send->pkt_header, buf, hdr->pkt_size); + + ret = habmm_socket_send(hab_handle_tx, + (void *)&apr_tx_buf, + apr_send_len, + 0); + if (ret) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, ret); + goto done; + } + /* wait for response */ + apr_rsp_len = sizeof(apr_rsp); + ret = apr_vm_nb_receive(hab_handle_tx, + (void *)&apr_rsp, + &apr_rsp_len, + 0xFFFFFFFF); + if (ret) { + pr_err("%s: apr_vm_nb_receive failed %d\n", + __func__, ret); + goto done; + } + if (apr_rsp.status) { + pr_err("%s: apr_vm_nb_receive status %d\n", + __func__, apr_rsp.status); + /* should translate status properly */ + ret = -ECOMM; + goto done; + } + + /* upon successful send, return packet size */ + ret = hdr->pkt_size; + +done: + spin_unlock_irqrestore(&svc->w_lock, flags); + return ret; +} + +struct apr_svc *apr_register(char *dest, char *svc_name, apr_fn svc_fn, + uint32_t src_port, void *priv) +{ + struct apr_client *clnt; + int client_id = 0; + int svc_idx = 0; + int svc_id = 0; + int dest_id = 0; + int domain_id = 0; + int temp_port = 0; + struct apr_svc *svc = NULL; + int rc = 0; + bool can_open_channel = true; + int dest_svc = 0; + int handle = 0; + + if (!dest || !svc_name || !svc_fn) + return NULL; + + if (!strcmp(dest, "ADSP")) + domain_id = APR_DOMAIN_ADSP; + else if (!strcmp(dest, "MODEM")) { + /* Don't request for SMD channels if destination is MODEM, + * as these channels are no longer used and these clients + * are to listen only for MODEM SSR events + */ + can_open_channel = false; + domain_id = APR_DOMAIN_MODEM; + } else { + pr_err("APR: wrong destination\n"); + goto done; + } + + dest_id = apr_get_dest_id(dest); + + if (dest_id == APR_DEST_QDSP6) { + if (apr_get_q6_state() != APR_SUBSYS_LOADED) { + pr_err("%s: adsp not up\n", __func__); + return NULL; + } + pr_debug("%s: adsp Up\n", __func__); + } else if (dest_id == APR_DEST_MODEM) { + if (apr_get_modem_state() == APR_SUBSYS_DOWN) { + if (is_modem_up) { + pr_err("%s: modem shutdown due to SSR, ret", + __func__); + return NULL; + } + pr_debug("%s: Wait for modem to bootup\n", __func__); + rc = apr_wait_for_device_up(APR_DEST_MODEM); + if (rc == 0) { + pr_err("%s: Modem is not Up\n", __func__); + return NULL; + } + } + pr_debug("%s: modem Up\n", __func__); + } + + if (apr_vm_get_svc(svc_name, domain_id, &client_id, &svc_idx, &svc_id, + &dest_svc, &handle)) { + pr_err("%s: apr_vm_get_svc failed\n", __func__); + goto done; + } + + clnt = &client[dest_id][client_id]; + svc = &clnt->svc[svc_idx]; + mutex_lock(&svc->m_lock); + clnt->id = client_id; + if (svc->need_reset) { + mutex_unlock(&svc->m_lock); + pr_err("APR: Service needs reset\n"); + goto done; + } + svc->id = svc_id; + svc->vm_dest_svc = dest_svc; + svc->dest_id = dest_id; + svc->client_id = client_id; + svc->dest_domain = domain_id; + svc->pkt_owner = APR_PKT_OWNER_DRIVER; + svc->vm_handle = handle; + + if (src_port != 0xFFFFFFFF) { + temp_port = ((src_port >> 8) * 8) + (src_port & 0xFF); + pr_debug("port = %d t_port = %d\n", src_port, temp_port); + if (temp_port >= APR_MAX_PORTS || temp_port < 0) { + pr_err("APR: temp_port out of bounds\n"); + mutex_unlock(&svc->m_lock); + return NULL; + } + if (!svc->port_cnt && !svc->svc_cnt) + clnt->svc_cnt++; + svc->port_cnt++; + svc->port_fn[temp_port] = svc_fn; + svc->port_priv[temp_port] = priv; + } else { + if (!svc->fn) { + if (!svc->port_cnt && !svc->svc_cnt) + clnt->svc_cnt++; + svc->fn = svc_fn; + if (svc->port_cnt) + svc->svc_cnt++; + svc->priv = priv; + } + } + + mutex_unlock(&svc->m_lock); +done: + return svc; +} + +static void apr_reset_deregister(struct work_struct *work) +{ + struct apr_svc *handle = NULL; + struct apr_reset_work *apr_reset = + container_of(work, struct apr_reset_work, work); + + handle = apr_reset->handle; + pr_debug("%s:handle[%pK]\n", __func__, handle); + apr_deregister(handle); + kfree(apr_reset); +} + +int apr_deregister(void *handle) +{ + struct apr_svc *svc = handle; + struct apr_client *clnt; + uint16_t dest_id; + uint16_t client_id; + + if (!handle) + return -EINVAL; + + mutex_lock(&svc->m_lock); + dest_id = svc->dest_id; + client_id = svc->client_id; + clnt = &client[dest_id][client_id]; + + if (svc->port_cnt > 0 || svc->svc_cnt > 0) { + if (svc->port_cnt) + svc->port_cnt--; + else if (svc->svc_cnt) + svc->svc_cnt--; + if (!svc->port_cnt && !svc->svc_cnt) { + client[dest_id][client_id].svc_cnt--; + svc->need_reset = 0x0; + } + } else if (client[dest_id][client_id].svc_cnt > 0) { + client[dest_id][client_id].svc_cnt--; + if (!client[dest_id][client_id].svc_cnt) { + svc->need_reset = 0x0; + pr_debug("%s: service is reset %p\n", __func__, svc); + } + } + + if (!svc->port_cnt && !svc->svc_cnt) { + if (apr_vm_rel_svc(svc->dest_domain, svc->id, svc->vm_handle)) + pr_err("%s: apr_vm_rel_svc failed\n", __func__); + svc->priv = NULL; + svc->id = 0; + svc->vm_dest_svc = 0; + svc->fn = NULL; + svc->dest_id = 0; + svc->client_id = 0; + svc->need_reset = 0x0; + svc->vm_handle = 0; + } + mutex_unlock(&svc->m_lock); + + return 0; +} + +void apr_reset(void *handle) +{ + struct apr_reset_work *apr_reset_worker = NULL; + + if (!handle) + return; + pr_debug("%s: handle[%pK]\n", __func__, handle); + + if (apr_reset_workqueue == NULL) { + pr_err("%s: apr_reset_workqueue is NULL\n", __func__); + return; + } + + apr_reset_worker = kzalloc(sizeof(struct apr_reset_work), + GFP_ATOMIC); + + if (apr_reset_worker == NULL) { + pr_err("%s: mem failure\n", __func__); + return; + } + + apr_reset_worker->handle = handle; + INIT_WORK(&apr_reset_worker->work, apr_reset_deregister); + queue_work(apr_reset_workqueue, &apr_reset_worker->work); +} + +/* Dispatch the Reset events to Modem and audio clients */ +static void dispatch_event(unsigned long code, uint16_t proc) +{ + struct apr_client *apr_client; + struct apr_client_data data; + struct apr_svc *svc; + uint16_t clnt; + int i, j; + + memset(&data, 0, sizeof(data)); + data.opcode = RESET_EVENTS; + data.reset_event = code; + + /* Service domain can be different from the processor */ + data.reset_proc = apr_get_reset_domain(proc); + + clnt = APR_CLIENT_AUDIO; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } + + clnt = APR_CLIENT_VOICE; + apr_client = &client[proc][clnt]; + for (i = 0; i < APR_SVC_MAX; i++) { + mutex_lock(&apr_client->svc[i].m_lock); + if (apr_client->svc[i].fn) { + apr_client->svc[i].need_reset = 0x1; + apr_client->svc[i].fn(&data, apr_client->svc[i].priv); + } + if (apr_client->svc[i].port_cnt) { + svc = &(apr_client->svc[i]); + svc->need_reset = 0x1; + for (j = 0; j < APR_MAX_PORTS; j++) + if (svc->port_fn[j]) + svc->port_fn[j](&data, + svc->port_priv[j]); + } + mutex_unlock(&apr_client->svc[i].m_lock); + } +} + +static int apr_notifier_service_cb(struct notifier_block *this, + unsigned long opcode, void *data) +{ + struct audio_notifier_cb_data *cb_data = data; + + if (cb_data == NULL) { + pr_err("%s: Callback data is NULL!\n", __func__); + goto done; + } + + pr_debug("%s: Service opcode 0x%lx, domain %d\n", + __func__, opcode, cb_data->domain); + + switch (opcode) { + case AUDIO_NOTIFIER_SERVICE_DOWN: + /* + * Use flag to ignore down notifications during + * initial boot. There is no benefit from error + * recovery notifications during initial boot + * up since everything is expected to be down. + */ + if (is_initial_boot) { + is_initial_boot = false; + break; + } + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + apr_modem_down(opcode); + else + apr_adsp_down(opcode); + break; + case AUDIO_NOTIFIER_SERVICE_UP: + is_initial_boot = false; + if (cb_data->domain == AUDIO_NOTIFIER_MODEM_DOMAIN) + apr_modem_up(); + else + apr_adsp_up(); + break; + default: + break; + } +done: + return NOTIFY_OK; +} + +static struct notifier_block adsp_service_nb = { + .notifier_call = apr_notifier_service_cb, + .priority = 0, +}; + +static struct notifier_block modem_service_nb = { + .notifier_call = apr_notifier_service_cb, + .priority = 0, +}; + +static void apr_vm_set_subsys_state(void) +{ + /* set default subsys state in vm env. + * Both q6 and modem should be in LOADED state, + * since vm boots up at late stage after pm. + */ + apr_set_q6_state(APR_SUBSYS_LOADED); + apr_set_modem_state(APR_SUBSYS_LOADED); +} + +static int __init apr_init(void) +{ + int i, j, k; + int ret; + + /* open apr channel tx and rx, store as global */ + ret = habmm_socket_open(&hab_handle_tx, + MM_AUD_1, + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (ret) { + pr_err("%s: habmm_socket_open tx failed %d\n", __func__, ret); + return ret; + } + + ret = habmm_socket_open(&hab_handle_rx, + MM_AUD_2, + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (ret) { + pr_err("%s: habmm_socket_open rx failed %d\n", __func__, ret); + habmm_socket_close(hab_handle_tx); + return ret; + } + pr_info("%s: hab_handle_tx %x hab_handle_rx %x\n", + __func__, hab_handle_tx, hab_handle_rx); + + /* create apr ch rx cb thread */ + apr_vm_cb_thread_task = kthread_run(apr_vm_cb_thread, + NULL, + APR_VM_CB_THREAD_NAME); + if (IS_ERR(apr_vm_cb_thread_task)) { + ret = PTR_ERR(apr_vm_cb_thread_task); + pr_err("%s: kthread_run failed %d\n", __func__, ret); + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + return ret; + } + pid = apr_vm_cb_thread_task->pid; + pr_info("%s: apr_vm_cb_thread started pid %d\n", + __func__, pid); + + mutex_init(&m_lock_tbl_qdsp6); + mutex_init(&m_lock_tbl_voice); + + for (i = 0; i < APR_DEST_MAX; i++) + for (j = 0; j < APR_CLIENT_MAX; j++) { + mutex_init(&client[i][j].m_lock); + for (k = 0; k < APR_SVC_MAX; k++) { + mutex_init(&client[i][j].svc[k].m_lock); + spin_lock_init(&client[i][j].svc[k].w_lock); + } + } + + apr_vm_set_subsys_state(); + mutex_init(&q6.lock); + apr_reset_workqueue = create_singlethread_workqueue("apr_driver"); + if (!apr_reset_workqueue) { + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + kthread_stop(apr_vm_cb_thread_task); + return -ENOMEM; + } + + apr_pkt_ctx = ipc_log_context_create(APR_PKT_IPC_LOG_PAGE_CNT, + "apr", 0); + if (!apr_pkt_ctx) + pr_err("%s: Unable to create ipc log context\n", __func__); + + is_initial_boot = true; + subsys_notif_register("apr_adsp", AUDIO_NOTIFIER_ADSP_DOMAIN, + &adsp_service_nb); + subsys_notif_register("apr_modem", AUDIO_NOTIFIER_MODEM_DOMAIN, + &modem_service_nb); + + return 0; +} +device_initcall(apr_init); + +static int __init apr_late_init(void) +{ + int ret = 0; + + init_waitqueue_head(&dsp_wait); + init_waitqueue_head(&modem_wait); + + return ret; +} +late_initcall(apr_late_init); + +static void __exit apr_exit(void) +{ + habmm_socket_close(hab_handle_tx); + habmm_socket_close(hab_handle_rx); + kthread_stop(apr_vm_cb_thread_task); +} +__exitcall(apr_exit); + +#ifdef CONFIG_DEBUG_FS +static int __init apr_debug_init(void) +{ + debugfs_apr_debug = debugfs_create_file("msm_apr_debug", + S_IFREG | S_IRUGO, NULL, NULL, + &apr_debug_ops); + return 0; +} +device_initcall(apr_debug_init); +#endif diff --git a/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c b/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c new file mode 100644 index 000000000000..a3aa8823d8ce --- /dev/null +++ b/drivers/soc/qcom/qdsp6v2/msm_audio_ion_vm.c @@ -0,0 +1,838 @@ +/* + * Copyright (c) 2016-2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/platform_device.h> +#include <linux/of_device.h> +#include <linux/msm_audio_ion.h> +#include <linux/habmm.h> +#include "../../../staging/android/ion/ion_priv.h" +#include "../../../staging/android/ion/ion_hvenv_driver.h" + +#define MSM_AUDIO_ION_PROBED (1 << 0) + +#define MSM_AUDIO_SMMU_VM_CMD_MAP 0x00000001 +#define MSM_AUDIO_SMMU_VM_CMD_UNMAP 0x00000002 +#define MSM_AUDIO_SMMU_VM_HAB_MINOR_ID 1 + +struct msm_audio_ion_private { + bool smmu_enabled; + bool audioheap_enabled; + u8 device_status; + struct list_head smmu_map_list; + struct mutex smmu_map_mutex; +}; + +struct msm_audio_smmu_map_data { + struct ion_client *client; + struct ion_handle *handle; + u32 export_id; + struct list_head list; +}; + +struct msm_audio_smmu_vm_map_cmd { + int cmd_id; + u32 export_id; + u32 buf_size; +}; + +struct msm_audio_smmu_vm_map_cmd_rsp { + int status; + u64 addr; +}; + +struct msm_audio_smmu_vm_unmap_cmd { + int cmd_id; + u32 export_id; +}; + +struct msm_audio_smmu_vm_unmap_cmd_rsp { + int status; +}; + +static struct msm_audio_ion_private msm_audio_ion_data = {0,}; +static u32 msm_audio_ion_hab_handle; + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, + void *vaddr); + +static int msm_audio_ion_smmu_map(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, void *vaddr) +{ + int rc; + u32 export_id; + u32 cmd_rsp_size; + bool exported = false; + struct msm_audio_smmu_vm_map_cmd_rsp cmd_rsp; + struct msm_audio_smmu_map_data *map_data = NULL; + struct msm_audio_smmu_vm_map_cmd smmu_map_cmd; + + rc = ion_handle_get_size(client, handle, len); + if (rc) { + pr_err("%s: ion_handle_get_size failed, client = %pK, handle = %pK, rc = %d\n", + __func__, client, handle, rc); + goto err; + } + + /* Data required to track per buffer mapping */ + map_data = kzalloc(sizeof(*map_data), GFP_KERNEL); + if (!map_data) { + rc = -ENOMEM; + goto err; + } + + /* Export the buffer to physical VM */ + rc = habmm_export(msm_audio_ion_hab_handle, vaddr, *len, + &export_id, 0); + if (rc) { + pr_err("%s: habmm_export failed vaddr = %pK, len = %zd, rc = %d\n", + __func__, vaddr, *len, rc); + goto err; + } + + exported = true; + smmu_map_cmd.cmd_id = MSM_AUDIO_SMMU_VM_CMD_MAP; + smmu_map_cmd.export_id = export_id; + smmu_map_cmd.buf_size = *len; + + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + rc = habmm_socket_send(msm_audio_ion_hab_handle, + (void *)&smmu_map_cmd, sizeof(smmu_map_cmd), 0); + if (rc) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, rc); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + goto err; + } + + cmd_rsp_size = sizeof(cmd_rsp); + rc = habmm_socket_recv(msm_audio_ion_hab_handle, + (void *)&cmd_rsp, + &cmd_rsp_size, + 0xFFFFFFFF, + 0); + if (rc) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, rc); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + goto err; + } + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + if (cmd_rsp_size != sizeof(cmd_rsp)) { + pr_err("%s: invalid size for cmd rsp %lu, expected %lu\n", + __func__, cmd_rsp_size, sizeof(cmd_rsp)); + rc = -EIO; + goto err; + } + + if (cmd_rsp.status) { + pr_err("%s: SMMU map command failed %d\n", + __func__, cmd_rsp.status); + rc = cmd_rsp.status; + goto err; + } + + *addr = (ion_phys_addr_t)cmd_rsp.addr; + + map_data->client = client; + map_data->handle = handle; + map_data->export_id = export_id; + + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + list_add_tail(&(map_data->list), + &(msm_audio_ion_data.smmu_map_list)); + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + return 0; + +err: + if (exported) + (void)habmm_unexport(msm_audio_ion_hab_handle, export_id, 0); + + kfree(map_data); + + return rc; +} + +static int msm_audio_ion_smmu_unmap(struct ion_client *client, + struct ion_handle *handle) +{ + int rc; + bool found = false; + u32 cmd_rsp_size; + struct msm_audio_smmu_vm_unmap_cmd_rsp cmd_rsp; + struct msm_audio_smmu_map_data *map_data, *next; + struct msm_audio_smmu_vm_unmap_cmd smmu_unmap_cmd; + + /* + * Though list_for_each_entry_safe is delete safe, lock + * should be explicitly acquired to avoid race condition + * on adding elements to the list. + */ + mutex_lock(&(msm_audio_ion_data.smmu_map_mutex)); + list_for_each_entry_safe(map_data, next, + &(msm_audio_ion_data.smmu_map_list), list) { + + if (map_data->handle == handle && map_data->client == client) { + found = true; + smmu_unmap_cmd.cmd_id = MSM_AUDIO_SMMU_VM_CMD_UNMAP; + smmu_unmap_cmd.export_id = map_data->export_id; + + rc = habmm_socket_send(msm_audio_ion_hab_handle, + (void *)&smmu_unmap_cmd, + sizeof(smmu_unmap_cmd), 0); + if (rc) { + pr_err("%s: habmm_socket_send failed %d\n", + __func__, rc); + goto err; + } + + cmd_rsp_size = sizeof(cmd_rsp); + rc = habmm_socket_recv(msm_audio_ion_hab_handle, + (void *)&cmd_rsp, + &cmd_rsp_size, + 0xFFFFFFFF, + 0); + if (rc) { + pr_err("%s: habmm_socket_recv failed %d\n", + __func__, rc); + goto err; + } + + if (cmd_rsp_size != sizeof(cmd_rsp)) { + pr_err("%s: invalid size for cmd rsp %lu\n", + __func__, cmd_rsp_size); + rc = -EIO; + goto err; + } + + if (cmd_rsp.status) { + pr_err("%s: SMMU unmap command failed %d\n", + __func__, cmd_rsp.status); + rc = cmd_rsp.status; + goto err; + } + + rc = habmm_unexport(msm_audio_ion_hab_handle, + map_data->export_id, 0xFFFFFFFF); + if (rc) { + pr_err("%s: habmm_unexport failed export_id = %d, rc = %d\n", + __func__, map_data->export_id, rc); + } + + list_del(&(map_data->list)); + kfree(map_data); + break; + } + } + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + + if (!found) { + pr_err("%s: cannot find map_data ion_handle %pK, ion_client %pK\n", + __func__, handle, client); + rc = -EINVAL; + } + + return rc; + +err: + if (found) { + (void)habmm_unexport(msm_audio_ion_hab_handle, + map_data->export_id, 0xFFFFFFFF); + list_del(&(map_data->list)); + kfree(map_data); + } + + mutex_unlock(&(msm_audio_ion_data.smmu_map_mutex)); + return rc; +} + +int msm_audio_ion_alloc(const char *name, struct ion_client **client, + struct ion_handle **handle, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = -EINVAL; + unsigned long err_ion_ptr = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + if (!name || !client || !handle || !paddr || !vaddr + || !bufsz || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + goto err; + } + + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_AUDIO_HEAP_ID), 0); + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (msm_audio_ion_data.smmu_enabled == true) { + pr_debug("system heap is used"); + msm_audio_ion_data.audioheap_enabled = 0; + *handle = ion_alloc(*client, bufsz, SZ_4K, + ION_HEAP(ION_SYSTEM_HEAP_ID), 0); + } + if (IS_ERR_OR_NULL((void *) (*handle))) { + if (IS_ERR((void *)(*handle))) + err_ion_ptr = PTR_ERR((int *)(*handle)); + pr_err("%s:ION alloc fail err ptr=%ld, smmu_enabled=%d\n", + __func__, err_ion_ptr, msm_audio_ion_data.smmu_enabled); + rc = -ENOMEM; + goto err_ion_client; + } + } else { + pr_debug("audio heap is used"); + msm_audio_ion_data.audioheap_enabled = 1; + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + goto err_ion_handle; + } + pr_debug("%s: mapped address = %pK, size=%zd\n", __func__, + *vaddr, bufsz); + + if (bufsz != 0) { + pr_debug("%s: memset to 0 %pK %zd\n", __func__, *vaddr, bufsz); + memset((void *)*vaddr, 0, bufsz); + } + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return rc; + +err_get_phys: + ion_unmap_kernel(*client, *handle); +err_ion_handle: + ion_free(*client, *handle); +err_ion_client: + msm_audio_ion_client_destroy(*client); + *handle = NULL; + *client = NULL; +err: + return rc; +} +EXPORT_SYMBOL(msm_audio_ion_alloc); + +int msm_audio_ion_phys_free(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *paddr, + size_t *pa_len, u8 assign_type) +{ + if (!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!client || !handle || !paddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + /* hyp assign is not supported in VM */ + + ion_free(client, handle); + ion_client_destroy(client); + + return 0; +} + +int msm_audio_ion_phys_assign(const char *name, struct ion_client **client, + struct ion_handle **handle, int fd, + ion_phys_addr_t *paddr, + size_t *pa_len, u8 assign_type) +{ + int ret; + + if (!(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!name || !client || !handle || !paddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + return -EINVAL; + } + + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client failed\n", __func__); + return -EINVAL; + } + + *handle = ion_import_dma_buf(*client, fd); + if (IS_ERR_OR_NULL((void *) (*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + ret = -EINVAL; + goto err_destroy_client; + } + + ret = ion_phys(*client, *handle, paddr, pa_len); + if (ret) { + pr_err("%s: could not get physical address for handle, ret = %d\n", + __func__, ret); + goto err_ion_handle; + } + + /* hyp assign is not supported in VM */ + + return ret; + +err_ion_handle: + ion_free(*client, *handle); + +err_destroy_client: + ion_client_destroy(*client); + *client = NULL; + *handle = NULL; + + return ret; +} + +int msm_audio_ion_import(const char *name, struct ion_client **client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + + if ((msm_audio_ion_data.smmu_enabled == true) && + !(msm_audio_ion_data.device_status & MSM_AUDIO_ION_PROBED)) { + pr_debug("%s:probe is not done, deferred\n", __func__); + return -EPROBE_DEFER; + } + + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + + *client = msm_audio_ion_client_create(name); + if (IS_ERR_OR_NULL((void *)(*client))) { + pr_err("%s: ION create client for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err; + } + + /* name should be audio_acdb_client or Audio_Dec_Client, + * bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(*client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *) (*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err_destroy_client; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(*client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + goto err_ion_handle; + } + } + + *vaddr = ion_map_kernel(*client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -ENOMEM; + goto err_ion_handle; + } + pr_debug("%s: mapped address = %pK, size=%zd\n", __func__, + *vaddr, bufsz); + + rc = msm_audio_ion_get_phys(*client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return 0; + +err_get_phys: + ion_unmap_kernel(*client, *handle); +err_ion_handle: + ion_free(*client, *handle); +err_destroy_client: + msm_audio_ion_client_destroy(*client); + *client = NULL; + *handle = NULL; +err: + return rc; +} + +int msm_audio_ion_free(struct ion_client *client, struct ion_handle *handle) +{ + int ret = 0; + + if (!client || !handle) { + pr_err("%s Invalid params\n", __func__); + return -EINVAL; + } + + if (msm_audio_ion_data.smmu_enabled) { + ret = msm_audio_ion_smmu_unmap(client, handle); + if (ret) + pr_err("%s: smmu unmap failed with ret %d\n", + __func__, ret); + } + + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + msm_audio_ion_client_destroy(client); + return ret; +} +EXPORT_SYMBOL(msm_audio_ion_free); + +int msm_audio_ion_mmap(struct audio_buffer *ab, + struct vm_area_struct *vma) +{ + struct sg_table *table; + unsigned long addr = vma->vm_start; + unsigned long offset = vma->vm_pgoff * PAGE_SIZE; + struct scatterlist *sg; + unsigned int i; + struct page *page; + int ret; + + pr_debug("%s\n", __func__); + + table = ion_sg_table(ab->client, ab->handle); + + if (IS_ERR(table)) { + pr_err("%s: Unable to get sg_table from ion: %ld\n", + __func__, PTR_ERR(table)); + return PTR_ERR(table); + } else if (!table) { + pr_err("%s: sg_list is NULL\n", __func__); + return -EINVAL; + } + + /* uncached */ + vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot); + + /* We need to check if a page is associated with this sg list because: + * If the allocation came from a carveout we currently don't have + * pages associated with carved out memory. This might change in the + * future and we can remove this check and the else statement. + */ + page = sg_page(table->sgl); + if (page) { + pr_debug("%s: page is NOT null\n", __func__); + for_each_sg(table->sgl, sg, table->nents, i) { + unsigned long remainder = vma->vm_end - addr; + unsigned long len = sg->length; + + page = sg_page(sg); + + if (offset >= len) { + offset -= len; + continue; + } else if (offset) { + page += offset / PAGE_SIZE; + len -= offset; + offset = 0; + } + len = min(len, remainder); + pr_debug("vma=%pK, addr=%x len=%ld vm_start=%x vm_end=%x vm_page_prot=%ld\n", + vma, (unsigned int)addr, len, + (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, + (unsigned long int)vma->vm_page_prot); + remap_pfn_range(vma, addr, page_to_pfn(page), len, + vma->vm_page_prot); + addr += len; + if (addr >= vma->vm_end) + return 0; + } + } else { + ion_phys_addr_t phys_addr; + size_t phys_len; + size_t va_len = 0; + + pr_debug("%s: page is NULL\n", __func__); + + ret = ion_phys(ab->client, ab->handle, &phys_addr, &phys_len); + if (ret) { + pr_err("%s: Unable to get phys address from ION buffer: %d\n", + __func__, ret); + return ret; + } + pr_debug("phys=%pK len=%zd\n", &phys_addr, phys_len); + pr_debug("vma=%pK, vm_start=%x vm_end=%x vm_pgoff=%ld vm_page_prot=%ld\n", + vma, (unsigned int)vma->vm_start, + (unsigned int)vma->vm_end, vma->vm_pgoff, + (unsigned long int)vma->vm_page_prot); + va_len = vma->vm_end - vma->vm_start; + if ((offset > phys_len) || (va_len > phys_len-offset)) { + pr_err("wrong offset size %ld, lens= %zd, va_len=%zd\n", + offset, phys_len, va_len); + return -EINVAL; + } + ret = remap_pfn_range(vma, vma->vm_start, + __phys_to_pfn(phys_addr) + vma->vm_pgoff, + vma->vm_end - vma->vm_start, + vma->vm_page_prot); + } + return 0; +} + + +bool msm_audio_ion_is_smmu_available(void) +{ + return msm_audio_ion_data.smmu_enabled; +} + +/* move to static section again */ +struct ion_client *msm_audio_ion_client_create(const char *name) +{ + struct ion_client *pclient = NULL; + + pclient = hvenv_ion_client_create(name); + return pclient; +} + + +void msm_audio_ion_client_destroy(struct ion_client *client) +{ + pr_debug("%s: client = %pK smmu_enabled = %d\n", __func__, + client, msm_audio_ion_data.smmu_enabled); + + ion_client_destroy(client); +} + +int msm_audio_ion_import_legacy(const char *name, struct ion_client *client, + struct ion_handle **handle, int fd, + unsigned long *ionflag, size_t bufsz, + ion_phys_addr_t *paddr, size_t *pa_len, void **vaddr) +{ + int rc = 0; + + if (!name || !client || !handle || !paddr || !vaddr || !pa_len) { + pr_err("%s: Invalid params\n", __func__); + rc = -EINVAL; + goto err; + } + /* client is already created for legacy and given*/ + /* name should be audio_acdb_client or Audio_Dec_Client, + * bufsz should be 0 and fd shouldn't be 0 as of now + */ + *handle = ion_import_dma_buf(client, fd); + pr_debug("%s: DMA Buf name=%s, fd=%d handle=%pK\n", __func__, + name, fd, *handle); + if (IS_ERR_OR_NULL((void *)(*handle))) { + pr_err("%s: ion import dma buffer failed\n", + __func__); + rc = -EINVAL; + goto err; + } + + if (ionflag != NULL) { + rc = ion_handle_get_flags(client, *handle, ionflag); + if (rc) { + pr_err("%s: could not get flags for the handle\n", + __func__); + rc = -EINVAL; + goto err_ion_handle; + } + } + + /*Need to add condition SMMU enable or not */ + *vaddr = ion_map_kernel(client, *handle); + if (IS_ERR_OR_NULL((void *)*vaddr)) { + pr_err("%s: ION memory mapping for AUDIO failed\n", __func__); + rc = -EINVAL; + goto err_ion_handle; + } + + if (bufsz != 0) + memset((void *)*vaddr, 0, bufsz); + + rc = msm_audio_ion_get_phys(client, *handle, paddr, pa_len, *vaddr); + if (rc) { + pr_err("%s: ION Get Physical for AUDIO failed, rc = %d\n", + __func__, rc); + goto err_get_phys; + } + + return 0; + +err_get_phys: + ion_unmap_kernel(client, *handle); +err_ion_handle: + ion_free(client, *handle); +err: + return rc; +} + +int msm_audio_ion_free_legacy(struct ion_client *client, + struct ion_handle *handle) +{ + ion_unmap_kernel(client, handle); + + ion_free(client, handle); + /* no client_destrody in legacy*/ + return 0; +} + +static int msm_audio_ion_get_phys(struct ion_client *client, + struct ion_handle *handle, + ion_phys_addr_t *addr, size_t *len, void *vaddr) +{ + int rc = 0; + + pr_debug("%s: smmu_enabled = %d\n", __func__, + msm_audio_ion_data.smmu_enabled); + + if (msm_audio_ion_data.smmu_enabled) { + rc = msm_audio_ion_smmu_map(client, handle, addr, len, vaddr); + if (rc) { + pr_err("%s: failed to do smmu map, err = %d\n", + __func__, rc); + goto err; + } + } else { + rc = ion_phys(client, handle, addr, len); + } + + pr_debug("%s: phys=%pK, len=%zd, rc=%d\n", + __func__, &(*addr), *len, rc); +err: + return rc; +} + +static const struct of_device_id msm_audio_ion_dt_match[] = { + { .compatible = "qcom,msm-audio-ion-vm" }, + { } +}; +MODULE_DEVICE_TABLE(of, msm_audio_ion_dt_match); + +u32 msm_audio_populate_upper_32_bits(ion_phys_addr_t pa) +{ + return upper_32_bits(pa); +} + +static int msm_audio_ion_probe(struct platform_device *pdev) +{ + int rc = 0; + const char *msm_audio_ion_dt = "qcom,smmu-enabled"; + bool smmu_enabled; + struct device *dev = &pdev->dev; + + if (dev->of_node == NULL) { + pr_err("%s: device tree is not found\n", + __func__); + msm_audio_ion_data.smmu_enabled = 0; + return 0; + } + + smmu_enabled = of_property_read_bool(dev->of_node, + msm_audio_ion_dt); + msm_audio_ion_data.smmu_enabled = smmu_enabled; + + pr_info("%s: SMMU is %s\n", __func__, + (smmu_enabled) ? "Enabled" : "Disabled"); + + if (smmu_enabled) { + rc = habmm_socket_open(&msm_audio_ion_hab_handle, + HAB_MMID_CREATE(MM_AUD_3, + MSM_AUDIO_SMMU_VM_HAB_MINOR_ID), + 0xFFFFFFFF, + HABMM_SOCKET_OPEN_FLAGS_SINGLE_BE_SINGLE_FE); + if (rc) { + pr_err("%s: habmm_socket_open failed %d\n", + __func__, rc); + return rc; + } + + pr_info("%s: msm_audio_ion_hab_handle %x\n", + __func__, msm_audio_ion_hab_handle); + + INIT_LIST_HEAD(&msm_audio_ion_data.smmu_map_list); + mutex_init(&(msm_audio_ion_data.smmu_map_mutex)); + } + + if (!rc) + msm_audio_ion_data.device_status |= MSM_AUDIO_ION_PROBED; + + return rc; +} + +static int msm_audio_ion_remove(struct platform_device *pdev) +{ + if (msm_audio_ion_data.smmu_enabled) { + if (msm_audio_ion_hab_handle) + habmm_socket_close(msm_audio_ion_hab_handle); + + mutex_destroy(&(msm_audio_ion_data.smmu_map_mutex)); + } + msm_audio_ion_data.smmu_enabled = 0; + msm_audio_ion_data.device_status = 0; + + return 0; +} + +static struct platform_driver msm_audio_ion_driver = { + .driver = { + .name = "msm-audio-ion-vm", + .owner = THIS_MODULE, + .of_match_table = msm_audio_ion_dt_match, + }, + .probe = msm_audio_ion_probe, + .remove = msm_audio_ion_remove, +}; + +static int __init msm_audio_ion_init(void) +{ + return platform_driver_register(&msm_audio_ion_driver); +} +module_init(msm_audio_ion_init); + +static void __exit msm_audio_ion_exit(void) +{ + platform_driver_unregister(&msm_audio_ion_driver); +} +module_exit(msm_audio_ion_exit); + +MODULE_DESCRIPTION("MSM Audio ION VM module"); +MODULE_LICENSE("GPL v2"); diff --git a/include/linux/qdsp6v2/apr.h b/include/linux/qdsp6v2/apr.h index 29deb3ca5ac7..adcdbcbc5907 100644 --- a/include/linux/qdsp6v2/apr.h +++ b/include/linux/qdsp6v2/apr.h @@ -1,4 +1,4 @@ -/* Copyright (c) 2010-2016, The Linux Foundation. All rights reserved. +/* Copyright (c) 2010-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 @@ -137,6 +137,10 @@ struct apr_svc { struct mutex m_lock; spinlock_t w_lock; uint8_t pkt_owner; +#ifdef CONFIG_MSM_QDSP6_APRV2_VM + uint16_t vm_dest_svc; + uint32_t vm_handle; +#endif }; struct apr_client { diff --git a/include/linux/qdsp6v2/aprv2_vm.h b/include/linux/qdsp6v2/aprv2_vm.h new file mode 100644 index 000000000000..d16ea12d62b5 --- /dev/null +++ b/include/linux/qdsp6v2/aprv2_vm.h @@ -0,0 +1,116 @@ +/* Copyright (c) 2016-2017 The Linux Foundation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ +#ifndef __APRV2_VM_H__ +#define __APRV2_VM_H__ + +#define APRV2_VM_MAX_DNS_SIZE (31) + /* Includes NULL character. */ +#define APRV2_VM_PKT_SERVICE_ID_MASK (0x00FF) + /* Bitmask of the service ID field. */ + +/* Packet Structure Definition */ +struct aprv2_vm_packet_t { + uint32_t header; + uint16_t src_addr; + uint16_t src_port; + uint16_t dst_addr; + uint16_t dst_port; + uint32_t token; + uint32_t opcode; +}; + +/** + * In order to send command/event via MM HAB, the following buffer + * format shall be followed, where the buffer is provided to the + * HAB send API. + * |-----cmd_id or evt_id -----| <- 32 bit, e.g. APRV2_VM_CMDID_REGISTER + * |-----cmd payload ----------| e.g. aprv2_vm_cmd_register_t + * | ... | + * + * In order to receive a command response or event ack, the following + * buffer format shall be followed, where the buffer is provided to + * the HAB receive API. + * |-----cmd response ---------| e.g. aprv2_vm_cmd_register_rsp_t + * | ... | + */ + +/* Registers a service with the backend APR driver. */ +#define APRV2_VM_CMDID_REGISTER (0x00000001) + +struct aprv2_vm_cmd_register_t { + uint32_t name_size; + /**< The service name string size in bytes. */ + char name[APRV2_VM_MAX_DNS_SIZE]; + /**< + * The service name string to register. + * + * A NULL name means the service does not have a name. + */ + uint16_t addr; + /**< + * The address to register. + * + * A zero value means to auto-generate a free dynamic address. + * A non-zero value means to directly use the statically assigned address. + */ +}; + +struct aprv2_vm_cmd_register_rsp_t { + int32_t status; + /**< The status of registration. */ + uint32_t handle; + /**< The registered service handle. */ + uint16_t addr; + /**< The actual registered address. */ +}; + +#define APRV2_VM_CMDID_DEREGISTER (0x00000002) + +struct aprv2_vm_cmd_deregister_t { + uint32_t handle; + /**< The registered service handle. */ +}; + +struct aprv2_vm_cmd_deregister_rsp_t { + int32_t status; + /**< The status of de-registration. */ +}; + +#define APRV2_VM_CMDID_ASYNC_SEND (0x00000003) + +struct aprv2_vm_cmd_async_send_t { + uint32_t handle; + /**< The registered service handle. */ + struct aprv2_vm_packet_t pkt_header; + /**< The packet header. */ + /* The apr packet payload follows */ +}; + +struct aprv2_vm_cmd_async_send_rsp_t { + int32_t status; + /**< The status of send. */ +}; + +#define APRV2_VM_EVT_RX_PKT_AVAILABLE (0x00000004) + +struct aprv2_vm_evt_rx_pkt_available_t { + struct aprv2_vm_packet_t pkt_header; + /**< The packet header. */ + /* The apr packet payload follows */ +}; + +struct aprv2_vm_ack_rx_pkt_available_t { + int32_t status; +}; + +#endif /* __APRV2_VM_H__ */ 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; |
