Merge "ARM: dts: msm: Add support for venus PIL on msm8953"
diff --git a/Documentation/devicetree/bindings/pil/subsys-pil-tz.txt b/Documentation/devicetree/bindings/pil/subsys-pil-tz.txt
index 4a69e03..f8329a9 100644
--- a/Documentation/devicetree/bindings/pil/subsys-pil-tz.txt
+++ b/Documentation/devicetree/bindings/pil/subsys-pil-tz.txt
@@ -68,6 +68,7 @@
first segment to end address of last segment will be collected without
leaving any hole in between.
- qcom,ignore-ssr-failure: Boolean. If set, SSR failures are not considered fatal.
+- qcom,mas-crypto: Reference to the bus master of crypto core.
Example:
qcom,venus@fdce0000 {
diff --git a/Documentation/devicetree/bindings/usb/msm-hsusb.txt b/Documentation/devicetree/bindings/usb/msm-hsusb.txt
index 8654a3e..89c817e 100644
--- a/Documentation/devicetree/bindings/usb/msm-hsusb.txt
+++ b/Documentation/devicetree/bindings/usb/msm-hsusb.txt
@@ -1,110 +1,315 @@
MSM SoC HSUSB controllers
-EHCI
+OTG:
-Required properties:
-- compatible: Should contain "qcom,ehci-host"
-- regs: offset and length of the register set in the memory map
-- usb-phy: phandle for the PHY device
+Required properties :
+- compatible : should be "qcom,hsusb-otg"
+- regs : Array of offset and length of the register sets in the memory map
+- reg-names : indicates various iomem resources passed by name. The possible
+ strings in this field are:
+ "core": USB controller register space. (Required)
+ "tcsr": TCSR register for routing USB Controller signals to
+ either picoPHY0 or picoPHY1. (Optional)
+ "phy_csr": PHY Wrapper CSR register space. Provides register level
+ interface through AHB2PHY for performing PHY related operations
+ like retention and HV interrupts management.
+- interrupts: IRQ line
+- interrupt-names: OTG interrupt name(s) referenced in interrupts above
+ HSUSB OTG expects "core_irq" which is IRQ line from CORE and
+ "async_irq" from HSPHY for asynchronous wakeup events in LPM.
+ optional ones are described in next section.
+- qcom,hsusb-otg-phy-type: PHY type can be one of
+ 1 - Chipidea PHY (obsolete)
+ 2 - Synopsis Pico PHY
+ 3 - Synopsis Femto PHY
+ 4 - QUSB ULPI PHY
+- qcom,hsusb-otg-mode: Operational mode. Can be one of
+ 1 - Peripheral only mode
+ 2 - Host only mode
+ 3 - OTG mode
+ Based on the mode, OTG driver registers platform devices for
+ gadget and host.
+- qcom,hsusb-otg-otg-control: OTG control (VBUS and ID notifications)
+ can be one of
+ 1 - PHY control
+ 2 - PMIC control
+ 3 - User control (via debugfs)
+- <supply-name>-supply: handle to the regulator device tree node
+ Required "supply-name" is "HSUSB_VDDCX" (when voting for VDDCX) or
+ "hsusb_vdd_dig" (when voting for VDDCX Corner voltage),
+ "HSUSB_1p8-supply" and "HSUSB_3p3-supply".
+- qcom,vdd-voltage-level: This property must be a list of three integer
+ values (none, min, max) where each value represents either a voltage
+ in microvolts or a value corresponding to voltage corner. If usb core
+ supports svs, min value will have absolute SVS or SVS corner otherwise
+ min value will have absolute nominal or nominal corner.
+- clocks: a list of phandles to the USB clocks. Usage is as per
+ Documentation/devicetree/bindings/clock/clock-bindings.txt
+- clock-names: Names of the clocks in 1-1 correspondence with the "clocks"
+ property.
-Example EHCI controller device node:
+ Required clocks:
+ "core_clk": USB core clock that is required for data transfers.
+ "iface_clk": USB core clock that is required for register access.
- ehci: ehci@f9a55000 {
- compatible = "qcom,ehci-host";
- reg = <0xf9a55000 0x400>;
- usb-phy = <&usb_otg>;
+ Optional clocks:
+ "sleep_clk": PHY sleep clock. Required for interrupts.
+ "phy_reset_clk": PHY blocks asynchronous reset clock. Required
+ for the USB block reset. It is a reset only clock.
+ "phy_por_clk": Reset only clock for asserting/de-asserting
+ PHY POR signal. Required for overriding PHY parameters.
+ "phy_csr_clk": Required for accessing PHY CSR registers through
+ AHB2PHY interface.
+ "phy_ref_clk": Required when PHY have referance clock,
+ "xo": XO clock. The source clock that is used as a reference clock
+ to the PHY.
+ "bimc_clk", "snoc_clk", "pcnoc_clk": bus voting clocks. Used to
+ keep buses at a nominal frequency during USB peripheral
+ mode for achieving max throughput.
+- qcom,max-nominal-sysclk-rate: Indicates maximum nominal frequency for which
+ system clock should be voted whenever streaming mode is enabled.
+- resets: reset specifier pair consists of phandle for the reset provider
+ and reset lines used by this controller.
+- reset-names: reset signal name strings sorted in the same order as the resets
+ property.
+
+Optional properties :
+- interrupt-names : Optional interrupt resource entries are:
+ "pmic_id_irq" : Interrupt from PMIC for external ID pin notification.
+ "phy_irq" : Interrupt from PHY. Used for ID detection.
+- qcom,hsusb-otg-disable-reset: If present then core is RESET only during
+ init, otherwise core is RESET for every cable disconnect as well
+- qcom,hsusb-otg-pnoc-errata-fix: If present then workaround for PNOC
+ performance issue is applied which requires changing the mem-type
+ attribute via VMIDMT.
+- qcom,hsusb-otg-default-mode: The default USB mode after boot-up.
+ Applicable only when OTG is controlled by user. Can be one of
+ 0 - None. Low power mode
+ 1 - Peripheral
+ 2 - Host
+- qcom,hsusb-otg-phy-init-seq: PHY configuration sequence. val, reg pairs
+ terminate with -1
+- qcom,hsusb-otg-power-budget: VBUS power budget in mA
+ 0 will be treated as 500mA
+- qcom,hsusb-otg-pclk-src-name: The source of pclk
+- Refer to "Documentation/devicetree/bindings/arm/msm/msm-bus.txt" for
+ below optional properties:
+ - qcom,msm-bus,name
+ - qcom,msm-bus,num_cases - There are three valid cases for this: NONE, MAX
+ and MIN bandwidth votes. Minimum two cases must be defined for
+ both NONE and MAX votes. If MIN vote is different from NONE VOTE
+ then specify third case for MIN VOTE. If explicit NOC clock rates
+ are not specified then MAX value should be large enough to get
+ desired BUS frequencies. In case explicit NOC clock rates are
+ specified, peripheral mode bus bandwidth vote should be defined
+ to vote for arbitrated bandwidth so that 60MHz frequency is met.
+
+ - qcom,msm-bus,num_paths
+ - qcom,msm-bus,vectors
+- qcom,hsusb-otg-lpm-on-dev-suspend: If present then USB enter to
+ low power mode upon receiving bus suspend.
+- qcom,hsusb-otg-clk-always-on-workaround: If present then USB core clocks
+ remain active upon receiving bus suspend and USB cable is connected.
+ Used for allowing USB to respond for remote wakup.
+- qcom,hsusb-otg-delay-lpm: If present then USB core will wait one second
+ after disconnect before entering low power mode.
+- <supply-name>-supply: handle to the regulator device tree node.
+ Optional "supply-name" is "vbus_otg" to supply vbus in host mode.
+- qcom,dp-manual-pullup: If present, vbus is not routed to USB controller/phy
+ and controller driver therefore enables pull-up explicitly before
+ starting controller using usbcmd run/stop bit.
+- qcom,usb2-enable-hsphy2: If present then USB2 controller is connected to 2nd
+ HSPHY.
+- qcom,hsusb-log2-itc: value of 2^(log2_itc-1) will be used as the
+ interrupt threshold (ITC), when log2_itc is between 1 to 7.
+- qcom,hsusb-l1-supported: If present, the device supports l1 (Link power
+ management).
+- qcom,no-selective-suspend: If present selective suspend is disabled on hub ports.
+- qcom,hsusb-otg-mpm-dpsehv-int: If present, indicates mpm interrupt to be
+ configured for detection of dp line transition during VDD minimization.
+- qcom,hsusb-otg-mpm-dmsehv-int: If present, indicates mpm interrupt to be
+ configured for detection of dm line transition during VDD minimization.
+- pinctrl-names : This should be defined if a target uses gpio and pinctrl framework.
+ See "pinctrl" in Documentation/devicetree/bindings/pinctrl/msm-pinctrl.txt.
+ It should specify the names of the configs that pinctrl can install in driver
+ Following are the pinctrl config that can be installed
+ "hsusb_active" : Active configuration of pins, this should specify active
+ config of vddmin gpio (if used) defined in their pin groups.
+ "hsusb_sleep" : Disabled configuration of pins, this should specify sleep
+ config of vddmin gpio (if used) defined in their pin groups.
+- qcom,hsusb-otg-vddmin-gpio = If present, indicates a gpio that will be used
+ to supply voltage to the D+ line during VDD minimization and peripheral
+ bus suspend. If not exists, then VDD minimization will not be allowed
+ during peripheral bus suspend.
+- qcom,ahb-async-bridge-bypass: If present, indicates that enable AHB2AHB By Pass
+ mode with device controller for better throughput. With this mode, USB Core
+ runs using PNOC clock and synchronous to it. Hence it is must to have proper
+ "qcom,msm-bus,vectors" to have high bus frequency. User shouldn't try to
+ enable this feature without proper bus voting. When this feature is enabled,
+ it is required to do HW reset during cable disconnect for host mode functionality
+ working and hence need to disable qcom,hsusb-otg-disable-reset. With this feature
+ enabled, USB HW has to vote for maximum PNOC frequency as USB HW cannot tolerate
+ changes in PNOC frequency which results in USB functionality failure.
+- qcom,disable-retention-with-vdd-min: If present don't allow phy retention but allow
+ vdd min.
+- qcom,usbin-vadc: Corresponding vadc device's phandle to read usbin voltage using VADC.
+ This will be used to get value of usb power supply's VOLTAGE_NOW property.
+- qcom,usbid-gpio: This corresponds to gpio which is used for USB ID detection.
+- qcom,hub-reset-gpio: This corresponds to gpio which is used for HUB reset.
+- qcom,sw-sel-gpio: This corresponds to gpio which is used for switch select routing
+ of D+/D- between the USB HUB and type B USB jack for peripheral mode.
+- qcom,bus-clk-rate: If present, indicates nominal bus frequency to be voted for
+ bimc/snoc/pcnoc clock with usb cable connected. If AHB2AHB bypass is enabled,
+ pcnoc value should be defined to very large number so that PNOC runs at max
+ frequency. If 'qcom,default-mode-svs' is also set then two set of frequencies
+ must be specified for SVS and NOM modes which user can change using sysfs node.
+- qcom,phy-dvdd-always-on: If present PHY DVDD is supplied by a always-on
+ regulator unlike vddcx/vddmx. PHY can keep D+ pull-up and D+/D-
+ pull-down resistors during peripheral and host bus suspend without
+ any re-work.
+- qcom,emulation: Indicates that we are running on emulation platform.
+- qcom,boost-sysclk-with-streaming: If present, enable controller specific
+ streaming feature. Also this flag can bump up usb system clock to max in streaming
+ mode. This flag enables streaming mode for all compositions and is different from
+ streaming-func property defined in android device node. Please refer Doumentation/
+ devicetree/bindings/usb/android-dev.txt for details about "streaming-func" property.
+- qcom,axi-prefetch-enable: If present, AXI64 interface will be used for transferring data
+ to/from DDR by controller.
+- qcom,enable-sdp-typec-current-limit: Indicates whether type-c current for SDP CHARGER to
+ be limited.
+- qcom,enable-phy-id-pullup: If present, PHY can keep D+ pull-up resistor on USB ID line
+ during cable disconnect.
+- qcom,max-svs-sysclk-rate: Indicates system clock frequency voted by driver in
+ non-perf mode. In perf mode driver uses qcom,max-nominal-sysclk-rate.
+- qcom,pm-qos-latency: This represents max tolerable CPU latency in microsecs,
+ which is used as a vote by driver to get max performance in perf mode.
+- qcom,default-mode-svs: Indicates USB system clock should run at SVS frequency.
+ User can bump it up using 'perf_mode' sysfs attribute for gadget.
+- qcom,vbus-low-as-hostmode: If present, specifies USB_VBUS to switch to host mode
+ if USB_VBUS is low or device mode if USB_VBUS is high.
+- qcom,usbeth-reset-gpio: If present then an external usb-to-eth is connected to
+ the USB host controller and its RESET_N signal is connected to this
+ usbeth-reset-gpio GPIO. It should be driven LOW to RESET the usb-to-eth.
+- extcon: phandles to external connector devices. First phandle should point to
+ external connector, which provide "USB" cable events, the second should
+ point to external connector device, which provide "USB-HOST" cable events.
+ A single phandle may be specified if a single connector device provides
+ both "USB" and "USB-HOST" events.
+
+Example HSUSB OTG controller device node :
+ usb@f9690000 {
+ compatible = "qcom,hsusb-otg";
+ reg = <0xf9690000 0x400>;
+ reg-names = "core";
+ interrupts = <134>;
+ interrupt-names = "core_irq";
+
+ qcom,hsusb-otg-phy-type = <2>;
+ qcom,hsusb-otg-mode = <1>;
+ qcom,hsusb-otg-otg-control = <1>;
+ qcom,hsusb-otg-disable-reset;
+ qcom,hsusb-otg-pnoc-errata-fix;
+ qcom,hsusb-otg-default-mode = <2>;
+ qcom,hsusb-otg-phy-init-seq = <0x01 0x90 0xffffffff>;
+ qcom,hsusb-otg-power-budget = <500>;
+ qcom,hsusb-otg-pclk-src-name = "dfab_usb_clk";
+ qcom,hsusb-otg-lpm-on-dev-suspend;
+ qcom,hsusb-otg-clk-always-on-workaround;
+ hsusb_vdd_dig-supply = <&pm8226_s1_corner>;
+ HSUSB_1p8-supply = <&pm8226_l10>;
+ HSUSB_3p3-supply = <&pm8226_l20>;
+ qcom,vdd-voltage-level = <1 5 7>;
+ qcom,dp-manual-pullup;
+ qcom,hsusb-otg-mpm-dpsehv-int = <49>;
+ qcom,hsusb-otg-mpm-dmsehv-int = <58>;
+ qcom,max-nominal-sysclk-rate = <133330000>;
+ qcom,max-svs-sysclk-rate = <100000000>;
+ qcom,pm-qos-latency = <59>;
+
+ qcom,msm-bus,name = "usb2";
+ qcom,msm-bus,num_cases = <2>;
+ qcom,msm-bus,num_paths = <1>;
+ qcom,msm-bus,vectors =
+ <87 512 0 0>,
+ <87 512 60000000 960000000>;
+ pinctrl-names = "hsusb_active","hsusb_sleep";
+ pinctrl-0 = <&vddmin_act>;
+ pinctrl-0 = <&vddmin_sus>;
+ qcom,hsusb-otg-vddmin-gpio = <&pm8019_mpps 6 0>;
+ qcom,disable-retention-with-vdd-min;
+ qcom,usbin-vadc = <&pm8226_vadc>;
+ qcom,usbid-gpio = <&msm_gpio 110 0>;
};
-USB PHY with optional OTG:
+MSM HSUSB EHCI controller
-Required properties:
-- compatible: Should contain:
- "qcom,usb-otg-ci" for chipsets with ChipIdea 45nm PHY
- "qcom,usb-otg-snps" for chipsets with Synopsys 28nm PHY
+Required properties :
+- compatible : should be "qcom,ehci-host"
+- reg : offset and length of the register set in the memory map
+- interrupts: IRQ lines used by this controller
+- interrupt-names : Required interrupt resource entries are:
+ HSUSB EHCI expects "core_irq" and optionally "async_irq".
+- <supply-name>-supply: handle to the regulator device tree node
+ Required "supply-name" is either "hsusb_vdd_dig" or "HSUSB_VDDCX"
+ "HSUSB_1p8-supply" "HSUSB_3p3-supply".
+- qcom,usb2-power-budget: maximum vbus power (in mA) that can be provided.
+- qcom,vdd-voltage-level: This property must be a list of five integer
+ values (no, 0.5vsuspend, 0.75suspend, min, max) where each value respresents
+ either a voltage in microvolts or a value corresponding to voltage corner.
+ First value represents value to vote when USB is not at all active, second
+ value represents value to vote when target is not connected to dock during low
+ power mode, third value represents vlaue to vote when target is connected to dock
+ and no peripheral connected over dock during low power mode, fourth value represents
+ minimum value to vote when USB is operational, fifth item represents maximum value
+ to vote for USB is operational.
-- regs: Offset and length of the register set in the memory map
-- interrupts: interrupt-specifier for the OTG interrupt.
+Optional properties :
+- qcom,usb2-enable-hsphy2: If present, select second PHY for USB operation.
+- pinctrl-names : This should be defined if a target uses pinctrl framework.
+ See "pinctrl" in Documentation/devicetree/bindings/pinctrl/msm-pinctrl.txt.
+ It should specify the names of the configs that pinctrl can install in driver
+ Following are the pinctrl configs that can be installed
+ "ehci_active" : Active configuration of pins, this should specify active
+ config defined in pin groups of used gpio's from resume and
+ ext-hub-reset.
+ "ehci_sleep" : Disabled configuration of pins, this should specify sleep
+ config defined in pin groups of used gpio's from resume and
+ ext-hub-reset.
+- qcom,resume-gpio: if present then peripheral connected to usb controller
+ cannot wakeup from XO shutdown using in-band usb bus resume. Use resume
+ gpio to wakeup peripheral.
+- qcom,ext-hub-reset-gpio: If present then an external HUB is connected to
+ the USB host controller and its RESET_N signal is connected to this
+ ext-hub-reset-gpio GPIO. It should be driven LOW to RESET the HUB.
+- qcom,usb2-enable-uicc: If present, usb2 port will be used for uicc card connection.
+- usb-phy: phandle for the PHY device, if described as a separate device tree node
+- qcom,pm-qos-latency: This property represents the maximum tolerable CPU latency in
+ microsecs, which is used as a vote to keep the CPUs in a high enough power state when
+ USB bus is in use (not suspended).
+- Refer to "Documentation/devicetree/bindings/arm/msm/msm-bus.txt" for
+ below optional properties:
+ - qcom,msm-bus,name
+ - qcom,msm-bus,num_cases - Two cases (NONE and MAX) for voting are supported.
+ - qcom,msm-bus,num_paths
+ - qcom,msm-bus,vectors
-- clocks: A list of phandle + clock-specifier pairs for the
- clocks listed in clock-names
-- clock-names: Should contain the following:
- "phy" USB PHY reference clock
- "core" Protocol engine clock
- "iface" Interface bus clock
- "alt_core" Protocol engine clock for targets with asynchronous
- reset methodology. (optional)
-
-- vdccx-supply: phandle to the regulator for the vdd supply for
- digital circuit operation.
-- v1p8-supply: phandle to the regulator for the 1.8V supply
-- v3p3-supply: phandle to the regulator for the 3.3V supply
-
-- resets: A list of phandle + reset-specifier pairs for the
- resets listed in reset-names
-- reset-names: Should contain the following:
- "phy" USB PHY controller reset
- "link" USB LINK controller reset
-
-- qcom,otg-control: OTG control (VBUS and ID notifications) can be one of
- 1 - PHY control
- 2 - PMIC control
-
-Optional properties:
-- dr_mode: One of "host", "peripheral" or "otg". Defaults to "otg"
-
-- switch-gpio: A phandle + gpio-specifier pair. Some boards are using Dual
- SPDT USB Switch, witch is cotrolled by GPIO to de/multiplex
- D+/D- USB lines between connectors.
-
-- qcom,phy-init-sequence: PHY configuration sequence values. This is related to Device
- Mode Eye Diagram test. Start address at which these values will be
- written is ULPI_EXT_VENDOR_SPECIFIC. Value of -1 is reserved as
- "do not overwrite default value at this address".
- For example: qcom,phy-init-sequence = < -1 0x63 >;
- Will update only value at address ULPI_EXT_VENDOR_SPECIFIC + 1.
-
-- qcom,phy-num: Select number of pyco-phy to use, can be one of
- 0 - PHY one, default
- 1 - Second PHY
- Some platforms may have configuration to allow USB
- controller work with any of the two HSPHYs present.
-
-- qcom,vdd-levels: This property must be a list of three integer values
- (no, min, max) where each value represents either a voltage
- in microvolts or a value corresponding to voltage corner.
-
-- qcom,manual-pullup: If present, vbus is not routed to USB controller/phy
- and controller driver therefore enables pull-up explicitly
- before starting controller using usbcmd run/stop bit.
-
-- extcon: phandles to external connector devices. First phandle
- should point to external connector, which provide "USB"
- cable events, the second should point to external connector
- device, which provide "USB-HOST" cable events. If one of
- the external connector devices is not required empty <0>
- phandle should be specified.
-
-Example HSUSB OTG controller device node:
-
- usb@f9a55000 {
- compatible = "qcom,usb-otg-snps";
- reg = <0xf9a55000 0x400>;
- interrupts = <0 134 0>;
- dr_mode = "peripheral";
-
- clocks = <&gcc GCC_XO_CLK>, <&gcc GCC_USB_HS_SYSTEM_CLK>,
- <&gcc GCC_USB_HS_AHB_CLK>;
-
- clock-names = "phy", "core", "iface";
-
- vddcx-supply = <&pm8841_s2_corner>;
- v1p8-supply = <&pm8941_l6>;
- v3p3-supply = <&pm8941_l24>;
-
- resets = <&gcc GCC_USB2A_PHY_BCR>, <&gcc GCC_USB_HS_BCR>;
- reset-names = "phy", "link";
-
- qcom,otg-control = <1>;
- qcom,phy-init-sequence = < -1 0x63 >;
- qcom,vdd-levels = <1 5 7>;
+Example MSM HSUSB EHCI controller device node :
+ ehci: qcom,ehci-host@f9a55000 {
+ compatible = "qcom,ehci-host";
+ reg = <0xf9a55000 0x400>;
+ interrupts = <0 134 0>, <0 140 0>;
+ interrupt-names = "core_irq", "async_irq";
+ /* If pinctrl is used and ext-hub-reset and resume gpio's are present*/
+ pinctrl-names = "ehci_active","ehci_sleep";
+ pinctrl-0 = <&ehci_reset_act &resume_act>;
+ pinctrl-1 = <&ehci_reset_sus &resume_sus>;
+ qcom,resume-gpio = <&msm_gpio 80 0>;
+ qcom,ext-hub-reset-gpio = <&msm_gpio 0 0>;
+ hsusb_vdd_dig-supply = <&pm8841_s2_corner>;
+ HSUSB_1p8-supply = <&pm8941_l6>;
+ HSUSB_3p3-supply = <&pm8941_l24>;
+ qcom,usb2-enable-hsphy2;
+ qcom,usb2-power-budget = <500>;
+ qcom,vdd-voltage-level = <1 2 3 5 7>;
+ qcom,usb2-enable-uicc;
};
diff --git a/arch/arm/boot/dts/qcom/sdxpoorwills-audio-overlay.dtsi b/arch/arm/boot/dts/qcom/sdxpoorwills-audio-overlay.dtsi
index f90bd7f..e51d54b 100644
--- a/arch/arm/boot/dts/qcom/sdxpoorwills-audio-overlay.dtsi
+++ b/arch/arm/boot/dts/qcom/sdxpoorwills-audio-overlay.dtsi
@@ -17,7 +17,6 @@
&snd_934x {
qcom,audio-routing =
- "AIF4 VI", "MCLK",
"RX_BIAS", "MCLK",
"MADINPUT", "MCLK",
"AMIC2", "MIC BIAS2",
@@ -28,8 +27,6 @@
"MIC BIAS2", "ANCLeft Headset Mic",
"AMIC5", "MIC BIAS3",
"MIC BIAS3", "Handset Mic",
- "DMIC0", "MIC BIAS1",
- "MIC BIAS1", "Digital Mic0",
"DMIC1", "MIC BIAS1",
"MIC BIAS1", "Digital Mic1",
"DMIC2", "MIC BIAS3",
@@ -40,14 +37,13 @@
"MIC BIAS4", "Digital Mic4",
"DMIC5", "MIC BIAS4",
"MIC BIAS4", "Digital Mic5",
- "SpkrLeft IN", "SPK1 OUT",
"SpkrRight IN", "SPK2 OUT";
qcom,msm-mbhc-hphl-swh = <1>;
qcom,msm-mbhc-gnd-swh = <1>;
qcom,msm-mbhc-hs-mic-max-threshold-mv = <1700>;
qcom,msm-mbhc-hs-mic-min-threshold-mv = <50>;
- qcom,tavil-mclk-clk-freq = <12288000>;
+ qcom,tavil-mclk-clk-freq = <9600000>;
asoc-codec = <&stub_codec>;
asoc-codec-names = "msm-stub-codec.1";
@@ -66,23 +62,24 @@
interrupt-controller;
#interrupt-cells = <1>;
interrupt-parent = <&tlmm>;
- qcom,gpio-connect = <&tlmm 71 0>;
+ qcom,gpio-connect = <&tlmm 90 0>;
pinctrl-names = "default";
pinctrl-0 = <&wcd_intr_default>;
};
clock_audio_up: audio_ext_clk_up {
compatible = "qcom,audio-ref-clk";
- qcom,codec-mclk-clk-freq = <12288000>;
+ qcom,audio-ref-clk-gpio = <&tlmm 62 0>;
+ qcom,codec-mclk-clk-freq = <9600000>;
pinctrl-names = "sleep", "active";
pinctrl-0 = <&i2s_mclk_sleep>;
pinctrl-1 = <&i2s_mclk_active>;
#clock-cells = <1>;
};
- wcd_rst_gpio: msm_cdc_pinctrl@77 {
+ wcd_rst_gpio: msm_cdc_pinctrl@86 {
compatible = "qcom,msm-cdc-pinctrl";
- qcom,cdc-rst-n-gpio = <&tlmm 77 0>;
+ qcom,cdc-rst-n-gpio = <&tlmm 86 0>;
pinctrl-names = "aud_active", "aud_sleep";
pinctrl-0 = <&cdc_reset_active>;
pinctrl-1 = <&cdc_reset_sleep>;
@@ -91,8 +88,8 @@
&i2c_3 {
wcd934x_cdc: tavil_codec {
- compatible = "qcom,tavil-i2c-pgd";
- elemental-addr = [00 01 50 02 17 02];
+ compatible = "qcom,tavil-i2c";
+ reg = <0x0d>;
interrupt-parent = <&wcd9xxx_intc>;
interrupts = <0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@@ -135,7 +132,7 @@
qcom,cdc-micbias3-mv = <1800>;
qcom,cdc-micbias4-mv = <1800>;
- qcom,cdc-mclk-clk-rate = <12288000>;
+ qcom,cdc-mclk-clk-rate = <9600000>;
qcom,cdc-dmic-sample-rate = <4800000>;
qcom,wdsp-cmpnt-dev-name = "tavil_codec";
diff --git a/arch/arm/boot/dts/qcom/sdxpoorwills-blsp.dtsi b/arch/arm/boot/dts/qcom/sdxpoorwills-blsp.dtsi
index 4fe2d1e..13e1fc3 100644
--- a/arch/arm/boot/dts/qcom/sdxpoorwills-blsp.dtsi
+++ b/arch/arm/boot/dts/qcom/sdxpoorwills-blsp.dtsi
@@ -103,7 +103,6 @@
pinctrl-names = "i2c_active", "i2c_sleep";
pinctrl-0 = <&i2c_3_active>;
pinctrl-1 = <&i2c_3_sleep>;
- status = "disabled";
};
i2c_4: i2c@838000 { /* BLSP1 QUP4: GPIO: 76,77 */
diff --git a/arch/arm/boot/dts/qcom/sdxpoorwills-cdp.dts b/arch/arm/boot/dts/qcom/sdxpoorwills-cdp.dts
index 89945e3..94ccf9c 100644
--- a/arch/arm/boot/dts/qcom/sdxpoorwills-cdp.dts
+++ b/arch/arm/boot/dts/qcom/sdxpoorwills-cdp.dts
@@ -15,6 +15,7 @@
#include "sdxpoorwills.dtsi"
#include "sdxpoorwills-pinctrl.dtsi"
+#include "sdxpoorwills-cdp-audio-overlay.dtsi"
/ {
model = "Qualcomm Technologies, Inc. SDXPOORWILLS CDP";
diff --git a/arch/arm/boot/dts/qcom/sdxpoorwills-ion.dtsi b/arch/arm/boot/dts/qcom/sdxpoorwills-ion.dtsi
index afc88969..a09b149 100644
--- a/arch/arm/boot/dts/qcom/sdxpoorwills-ion.dtsi
+++ b/arch/arm/boot/dts/qcom/sdxpoorwills-ion.dtsi
@@ -20,5 +20,11 @@
reg = <25>;
qcom,ion-heap-type = "SYSTEM";
};
+
+ qcom,ion-heap@28 { /* AUDIO HEAP */
+ reg = <28>;
+ memory-region = <&audio_mem>;
+ qcom,ion-heap-type = "DMA";
+ };
};
};
diff --git a/arch/arm/boot/dts/qcom/sdxpoorwills-pinctrl.dtsi b/arch/arm/boot/dts/qcom/sdxpoorwills-pinctrl.dtsi
index 9b8e751..82b65e2 100644
--- a/arch/arm/boot/dts/qcom/sdxpoorwills-pinctrl.dtsi
+++ b/arch/arm/boot/dts/qcom/sdxpoorwills-pinctrl.dtsi
@@ -947,12 +947,12 @@
wcd9xxx_intr {
wcd_intr_default: wcd_intr_default{
mux {
- pins = "gpio71";
+ pins = "gpio90";
function = "gpio";
};
config {
- pins = "gpio71";
+ pins = "gpio90";
drive-strength = <2>; /* 2 mA */
bias-pull-down; /* pull down */
input-enable;
@@ -963,11 +963,11 @@
cdc_reset_ctrl {
cdc_reset_sleep: cdc_reset_sleep {
mux {
- pins = "gpio77";
+ pins = "gpio86";
function = "gpio";
};
config {
- pins = "gpio77";
+ pins = "gpio86";
drive-strength = <2>;
bias-disable;
output-low;
@@ -976,11 +976,11 @@
cdc_reset_active:cdc_reset_active {
mux {
- pins = "gpio77";
+ pins = "gpio86";
function = "gpio";
};
config {
- pins = "gpio77";
+ pins = "gpio86";
drive-strength = <8>;
bias-pull-down;
output-high;
@@ -1063,7 +1063,7 @@
pri_ws_active_master: pri_ws_active_master {
mux {
pins = "gpio12";
- function = "pri_mi2s_ws_a";
+ function = "pri_mi2s";
};
config {
@@ -1077,7 +1077,7 @@
pri_sck_active_master: pri_sck_active_master {
mux {
pins = "gpio15";
- function = "pri_mi2s_sck_a";
+ function = "pri_mi2s";
};
config {
@@ -1091,7 +1091,7 @@
pri_ws_active_slave: pri_ws_active_slave {
mux {
pins = "gpio12";
- function = "pri_mi2s_ws_a";
+ function = "pri_mi2s";
};
config {
@@ -1104,7 +1104,7 @@
pri_sck_active_slave: pri_sck_active_slave {
mux {
pins = "gpio15";
- function = "pri_mi2s_sck_a";
+ function = "pri_mi2s";
};
config {
@@ -1117,7 +1117,7 @@
pri_dout_active: pri_dout_active {
mux {
pins = "gpio14";
- function = "pri_mi2s_data1_a";
+ function = "pri_mi2s";
};
config {
@@ -1147,7 +1147,7 @@
pri_din_active: pri_din_active {
mux {
pins = "gpio13";
- function = "pri_mi2s_data0_a";
+ function = "pri_mi2s";
};
config {
diff --git a/arch/arm64/boot/dts/qcom/Makefile b/arch/arm64/boot/dts/qcom/Makefile
index 5a7e17e..eaa15ce 100644
--- a/arch/arm64/boot/dts/qcom/Makefile
+++ b/arch/arm64/boot/dts/qcom/Makefile
@@ -126,6 +126,7 @@
sda670-pm660a-mtp-overlay.dtbo \
qcs605-cdp-overlay.dtbo \
qcs605-mtp-overlay.dtbo \
+ qcs605-360camera-overlay.dtbo \
qcs605-external-codec-mtp-overlay.dtbo \
qcs605-lc-mtp-overlay.dtbo
@@ -156,6 +157,7 @@
qcs605-mtp-overlay.dtbo-base := qcs605.dtb
qcs605-external-codec-mtp-overlay.dtbo-base := qcs605.dtb
qcs605-lc-mtp-overlay.dtbo-base := qcs605.dtb
+qcs605-360camera-overlay.dtbo-base := qcs605.dtb
else
dtb-$(CONFIG_ARCH_SDM670) += sdm670-rumi.dtb \
diff --git a/arch/arm64/boot/dts/qcom/msm-arm-smmu-8953.dtsi b/arch/arm64/boot/dts/qcom/msm-arm-smmu-8953.dtsi
new file mode 100644
index 0000000..e794472
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/msm-arm-smmu-8953.dtsi
@@ -0,0 +1,103 @@
+/*
+ *Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+#include <dt-bindings/clock/msm-clocks-8953.h>
+
+&soc {
+ kgsl_smmu: arm,smmu-kgsl@1c40000 {
+ status = "ok";
+ compatible = "qcom,smmu-v2";
+ qcom,tz-device-id = "GPU";
+ reg = <0x1c40000 0x10000>;
+ #iommu-cells = <1>;
+ #global-interrupts = <0>;
+ interrupts = <GIC_SPI 225 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 232 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 233 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 234 IRQ_TYPE_LEVEL_HIGH>;
+ qcom,dynamic;
+ qcom,use-3-lvl-tables;
+ qcom,enable-static-cb;
+ qcom,enable-smmu-halt;
+ qcom,skip-init;
+ vdd-supply = <&gdsc_oxili_cx>;
+ qcom,regulator-names = "vdd";
+ clocks = <&clock_gcc_gfx clk_gcc_oxili_ahb_clk>,
+ <&clock_gcc_gfx clk_gcc_bimc_gfx_clk>;
+ clock-names = "gpu_ahb_clk", "gcc_bimc_gfx_clk";
+ };
+
+ /* A test device to test the SMMU operation */
+ kgsl_iommu_test_device0 {
+ compatible = "iommu-debug-test";
+ /* The SID should be valid one to get the proper
+ *SMR,S2CR indices.
+ */
+ iommus = <&kgsl_smmu 0x0>;
+ };
+
+ apps_iommu: qcom,iommu@1e00000 {
+ status = "disabled";
+ compatible = "qcom,qsmmu-v500";
+ reg = <0x1e00000 0x40000>,
+ <0x1ee2000 0x20>;
+ reg-names = "base", "tcu-base";
+ #iommu-cells = <2>;
+ qcom,tz-device-id = "APPS";
+ qcom,skip-init;
+ qcom,enable-static-cb;
+ qcom,use-3-lvl-tables;
+ qcom,disable-atos;
+ #global-interrupts = <0>;
+ #size-cells = <1>;
+ #address-cells = <1>;
+ ranges;
+ interrupts = <GIC_SPI 253 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 254 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 255 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 53 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 54 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 58 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 60 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 61 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 76 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 77 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 94 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 102 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 103 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 104 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 111 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 112 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 119 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>,
+ <GIC_SPI 122 IRQ_TYPE_LEVEL_HIGH>;
+ clocks = <&clock_gcc clk_gcc_smmu_cfg_clk>,
+ <&clock_gcc clk_gcc_apss_tcu_async_clk>;
+ clock-names = "iface_clk", "core_clk";
+ };
+};
+
+#include "msm-arm-smmu-impl-defs-8953.dtsi"
diff --git a/arch/arm64/boot/dts/qcom/msm-arm-smmu-impl-defs-8953.dtsi b/arch/arm64/boot/dts/qcom/msm-arm-smmu-impl-defs-8953.dtsi
new file mode 100644
index 0000000..2122db9
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/msm-arm-smmu-impl-defs-8953.dtsi
@@ -0,0 +1,25 @@
+/*
+ * Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+&kgsl_smmu {
+ attach-impl-defs = <0x6000 0x270>,
+ <0x6060 0x1055>,
+ <0x6800 0x6>,
+ <0x6900 0x3ff>,
+ <0x6924 0x204>,
+ <0x6928 0x10800>,
+ <0x6930 0x400>,
+ <0x6960 0xffffffff>,
+ <0x6b64 0xa0000>,
+ <0x6b68 0xaaab92a>;
+};
diff --git a/arch/arm64/boot/dts/qcom/msm8953-ion.dtsi b/arch/arm64/boot/dts/qcom/msm8953-ion.dtsi
new file mode 100644
index 0000000..34004b0
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/msm8953-ion.dtsi
@@ -0,0 +1,36 @@
+/* Copyright (c) 2015-2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+&soc {
+ qcom,ion {
+ compatible = "qcom,msm-ion";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ qcom,ion-heap@25 {
+ reg = <25>;
+ qcom,ion-heap-type = "SYSTEM";
+ };
+
+ qcom,ion-heap@8 { /* CP_MM HEAP */
+ reg = <8>;
+ memory-region = <&secure_mem>;
+ qcom,ion-heap-type = "SECURE_DMA";
+ };
+
+ qcom,ion-heap@27 { /* QSEECOM HEAP */
+ reg = <27>;
+ memory-region = <&qseecom_mem>;
+ qcom,ion-heap-type = "DMA";
+ };
+ };
+};
diff --git a/arch/arm64/boot/dts/qcom/msm8953.dtsi b/arch/arm64/boot/dts/qcom/msm8953.dtsi
index 50708aa..b4631e9 100644
--- a/arch/arm64/boot/dts/qcom/msm8953.dtsi
+++ b/arch/arm64/boot/dts/qcom/msm8953.dtsi
@@ -134,6 +134,8 @@
#include "msm8953-pm.dtsi"
#include "msm8953-bus.dtsi"
#include "msm8953-coresight.dtsi"
+#include "msm8953-ion.dtsi"
+#include "msm-arm-smmu-8953.dtsi"
&soc {
#address-cells = <1>;
@@ -522,13 +524,13 @@
clock-names = "core_clk_src", "core_clk",
"iface_clk", "bus_clk";
qcom,ce-opp-freq = <100000000>;
- status = "disabled";
+ status = "okay";
};
qcom_tzlog: tz-log@08600720 {
compatible = "qcom,tz-log";
reg = <0x08600720 0x2000>;
- status = "disabled";
+ status = "okay";
};
qcom_rng: qrng@e3000 {
@@ -544,7 +546,7 @@
<1 618 0 800>; /* 100 MB/s */
clocks = <&clock_gcc clk_gcc_prng_ahb_clk>;
clock-names = "iface_clk";
- status = "disabled";
+ status = "okay";
};
qcom_crypto: qcrypto@720000 {
@@ -577,7 +579,7 @@
qcom,use-sw-hmac-algo;
qcom,use-sw-aead-algo;
qcom,ce-opp-freq = <100000000>;
- status = "disabled";
+ status = "okay";
};
qcom_cedev: qcedev@720000 {
@@ -603,7 +605,7 @@
clock-names = "core_clk_src", "core_clk",
"iface_clk", "bus_clk";
qcom,ce-opp-freq = <100000000>;
- status = "disabled";
+ status = "okay";
};
blsp1_uart0: serial@78af000 {
@@ -1794,6 +1796,7 @@
vdd_cx-supply = <&pm8953_s2_level>;
qcom,proxy-reg-names = "vdd_cx";
qcom,vdd_cx-uV-uA = <RPM_SMD_REGULATOR_LEVEL_TURBO 100000>;
+ qcom,mas-crypto = <&mas_crypto>;
clocks = <&clock_gcc clk_xo_pil_lpass_clk>,
<&clock_gcc clk_gcc_crypto_clk>,
@@ -1825,6 +1828,8 @@
vdd_pronto_pll-supply = <&pm8953_l7>;
proxy-reg-names = "vdd_pronto_pll";
vdd_pronto_pll-uV-uA = <1800000 18000>;
+ qcom,mas-crypto = <&mas_crypto>;
+
clocks = <&clock_gcc clk_xo_pil_pronto_clk>,
<&clock_gcc clk_gcc_crypto_clk>,
<&clock_gcc clk_gcc_crypto_ahb_clk>,
diff --git a/arch/arm64/boot/dts/qcom/qcs605-360camera-overlay.dts b/arch/arm64/boot/dts/qcom/qcs605-360camera-overlay.dts
new file mode 100644
index 0000000..e7a2197
--- /dev/null
+++ b/arch/arm64/boot/dts/qcom/qcs605-360camera-overlay.dts
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2017, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ */
+
+/dts-v1/;
+/plugin/;
+
+#include <dt-bindings/clock/qcom,gcc-sdm845.h>
+#include <dt-bindings/clock/qcom,camcc-sdm845.h>
+#include <dt-bindings/clock/qcom,dispcc-sdm845.h>
+#include <dt-bindings/clock/qcom,rpmh.h>
+#include <dt-bindings/interrupt-controller/arm-gic.h>
+
+#include "qcs605-360camera.dtsi"
+
+/ {
+ model = "Qualcomm Technologies, Inc. QCS605 PM660+PM660L 360camera";
+ compatible = "qcom,qcs605-mtp", "qcom,qcs605", "qcom,mtp";
+ qcom,msm-id = <347 0x0>;
+ qcom,board-id = <0x0000000b 1>;
+ qcom,pmic-id = <0x0001001b 0x0101011a 0x0 0x0>,
+ <0x0001001b 0x0102001a 0x0 0x0>,
+ <0x0001001b 0x0201011a 0x0 0x0>;
+};
+
diff --git a/arch/arm64/boot/dts/qcom/sdm670-camera-sensor-360camera.dtsi b/arch/arm64/boot/dts/qcom/sdm670-camera-sensor-360camera.dtsi
index 18b0cd8..c40fff6 100644
--- a/arch/arm64/boot/dts/qcom/sdm670-camera-sensor-360camera.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm670-camera-sensor-360camera.dtsi
@@ -11,247 +11,245 @@
* GNU General Public License for more details.
*/
-&soc {
- led_flash_rear: qcom,camera-flash@0 {
- cell-index = <0>;
- reg = <0x00 0x00>;
- compatible = "qcom,camera-flash";
- flash-source = <&pm660l_flash0 &pm660l_flash1>;
- torch-source = <&pm660l_torch0 &pm660l_torch1>;
- switch-source = <&pm660l_switch0>;
- status = "ok";
- };
+&led_flash_rear {
+ cell-index = <0>;
+ reg = <0x00 0x00>;
+ compatible = "qcom,camera-flash";
+ flash-source = <&pm660l_flash0 &pm660l_flash1>;
+ torch-source = <&pm660l_torch0 &pm660l_torch1>;
+ switch-source = <&pm660l_switch0>;
+ status = "ok";
+};
- led_flash_front: qcom,camera-flash@1 {
- cell-index = <1>;
- reg = <0x01 0x00>;
- compatible = "qcom,camera-flash";
- flash-source = <&pm660l_flash2>;
- torch-source = <&pm660l_torch2>;
- switch-source = <&pm660l_switch1>;
- status = "ok";
- };
+&led_flash_front {
+ cell-index = <1>;
+ reg = <0x01 0x00>;
+ compatible = "qcom,camera-flash";
+ flash-source = <&pm660l_flash2>;
+ torch-source = <&pm660l_torch2>;
+ switch-source = <&pm660l_switch1>;
+ status = "ok";
+};
- actuator_regulator: gpio-regulator@0 {
- compatible = "regulator-fixed";
- reg = <0x00 0x00>;
- regulator-name = "actuator_regulator";
- regulator-min-microvolt = <2800000>;
- regulator-max-microvolt = <2800000>;
- regulator-enable-ramp-delay = <100>;
- enable-active-high;
- gpio = <&tlmm 27 0>;
- };
+&actuator_regulator {
+ compatible = "regulator-fixed";
+ reg = <0x00 0x00>;
+ regulator-name = "actuator_regulator";
+ regulator-min-microvolt = <2800000>;
+ regulator-max-microvolt = <2800000>;
+ regulator-enable-ramp-delay = <100>;
+ enable-active-high;
+ gpio = <&tlmm 27 0>;
+};
- camera_ldo: gpio-regulator@2 {
- compatible = "regulator-fixed";
- reg = <0x02 0x00>;
- regulator-name = "camera_ldo";
- regulator-min-microvolt = <1352000>;
- regulator-max-microvolt = <1352000>;
- regulator-enable-ramp-delay = <233>;
- enable-active-high;
- gpio = <&pm660l_gpios 4 0>;
- pinctrl-names = "default";
- pinctrl-0 = <&camera_dvdd_en_default>;
- vin-supply = <&pm660_s6>;
- };
+&camera_ldo {
+ compatible = "regulator-fixed";
+ reg = <0x02 0x00>;
+ regulator-name = "camera_ldo";
+ regulator-min-microvolt = <1352000>;
+ regulator-max-microvolt = <1352000>;
+ regulator-enable-ramp-delay = <233>;
+ enable-active-high;
+ gpio = <&pm660l_gpios 4 0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&camera_dvdd_en_default>;
+ vin-supply = <&pm660_s6>;
+};
- camera_rear_ldo: gpio-regulator@1 {
- compatible = "regulator-fixed";
- reg = <0x01 0x00>;
- regulator-name = "camera_rear_ldo";
- regulator-min-microvolt = <1352000>;
- regulator-max-microvolt = <1352000>;
- regulator-enable-ramp-delay = <135>;
- enable-active-high;
- gpio = <&pm660l_gpios 4 0>;
- pinctrl-names = "default";
- pinctrl-0 = <&camera_rear_dvdd_en_default>;
- vin-supply = <&pm660_s6>;
- };
+&camera_rear_ldo {
+ compatible = "regulator-fixed";
+ reg = <0x01 0x00>;
+ regulator-name = "camera_rear_ldo";
+ regulator-min-microvolt = <1352000>;
+ regulator-max-microvolt = <1352000>;
+ regulator-enable-ramp-delay = <135>;
+ enable-active-high;
+ gpio = <&pm660l_gpios 4 0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&camera_rear_dvdd_en_default>;
+ vin-supply = <&pm660_s6>;
+};
- camera_vio_ldo: gpio-regulator@3 {
- compatible = "regulator-fixed";
- reg = <0x03 0x00>;
- regulator-name = "camera_vio_ldo";
- regulator-min-microvolt = <1800000>;
- regulator-max-microvolt = <1800000>;
- regulator-enable-ramp-delay = <233>;
- enable-active-high;
- gpio = <&tlmm 29 0>;
- pinctrl-names = "default";
- pinctrl-0 = <&cam_sensor_rear_vio>;
- vin-supply = <&pm660_s4>;
- };
+&camera_vio_ldo {
+ compatible = "regulator-fixed";
+ reg = <0x03 0x00>;
+ regulator-name = "camera_vio_ldo";
+ regulator-min-microvolt = <1800000>;
+ regulator-max-microvolt = <1800000>;
+ regulator-enable-ramp-delay = <233>;
+ enable-active-high;
+ gpio = <&tlmm 29 0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cam_sensor_rear_vio>;
+ vin-supply = <&pm660_s4>;
+};
- camera_vana_ldo: gpio-regulator@4 {
- compatible = "regulator-fixed";
- reg = <0x04 0x00>;
- regulator-name = "camera_vana_ldo";
- regulator-min-microvolt = <2850000>;
- regulator-max-microvolt = <2850000>;
- regulator-enable-ramp-delay = <233>;
- enable-active-high;
- gpio = <&tlmm 8 0>;
- pinctrl-names = "default";
- pinctrl-0 = <&cam_sensor_rear_vana>;
- vin-supply = <&pm660l_bob>;
- };
+&camera_vana_ldo {
+ compatible = "regulator-fixed";
+ reg = <0x04 0x00>;
+ regulator-name = "camera_vana_ldo";
+ regulator-min-microvolt = <2850000>;
+ regulator-max-microvolt = <2850000>;
+ regulator-enable-ramp-delay = <233>;
+ enable-active-high;
+ gpio = <&tlmm 8 0>;
+ pinctrl-names = "default";
+ pinctrl-0 = <&cam_sensor_rear_vana>;
+ vin-supply = <&pm660l_bob>;
+};
+
+&actuator_rear {
+ cell-index = <0>;
+ reg = <0x0>;
+ compatible = "qcom,actuator";
+ cci-master = <0>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <2800000>;
+ rgltr-max-voltage = <2800000>;
+ rgltr-load-current = <0>;
+};
+
+&actuator_front {
+ cell-index = <1>;
+ reg = <0x1>;
+ compatible = "qcom,actuator";
+ cci-master = <1>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <2800000>;
+ rgltr-max-voltage = <2800000>;
+ rgltr-load-current = <0>;
+};
+
+&ois_rear {
+ cell-index = <0>;
+ reg = <0x0>;
+ compatible = "qcom,ois";
+ cci-master = <0>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <2800000>;
+ rgltr-max-voltage = <2800000>;
+ rgltr-load-current = <0>;
+ status = "disabled";
+};
+
+&eeprom_rear {
+ cell-index = <0>;
+ reg = <0>;
+ compatible = "qcom,eeprom";
+ cam_vio-supply = <&camera_vio_ldo>;
+ cam_vana-supply = <&camera_vana_ldo>;
+ cam_vdig-supply = <&camera_rear_ldo>;
+ cam_clk-supply = <&titan_top_gdsc>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vio", "cam_vana", "cam_vdig",
+ "cam_clk", "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <1800000 2850000 1352000 0 2800000>;
+ rgltr-max-voltage = <1800000 2850000 1352000 0 2800000>;
+ rgltr-load-current = <0 80000 105000 0 0>;
+ gpio-no-mux = <0>;
+ pinctrl-names = "cam_default", "cam_suspend";
+ pinctrl-0 = <&cam_sensor_mclk0_active
+ &cam_sensor_rear_active>;
+ pinctrl-1 = <&cam_sensor_mclk0_suspend
+ &cam_sensor_rear_suspend>;
+ gpios = <&tlmm 13 0>,
+ <&tlmm 30 0>;
+ gpio-reset = <1>;
+ gpio-req-tbl-num = <0 1>;
+ gpio-req-tbl-flags = <1 0>;
+ gpio-req-tbl-label = "CAMIF_MCLK0",
+ "CAM_RESET0";
+ sensor-mode = <0>;
+ cci-master = <0>;
+ status = "ok";
+ clocks = <&clock_camcc CAM_CC_MCLK0_CLK>;
+ clock-names = "cam_clk";
+ clock-cntl-level = "turbo";
+ clock-rates = <24000000>;
+};
+
+&eeprom_rear_aux {
+ cell-index = <1>;
+ reg = <0x1>;
+ compatible = "qcom,eeprom";
+ cam_vio-supply = <&camera_vio_ldo>;
+ cam_vana-supply = <&camera_vana_ldo>;
+ cam_vdig-supply = <&camera_ldo>;
+ cam_clk-supply = <&titan_top_gdsc>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vdig", "cam_vio", "cam_vana",
+ "cam_clk", "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <1352000 1800000 2850000 0 2800000>;
+ rgltr-max-voltage = <1352000 1800000 2850000 0 2800000>;
+ rgltr-load-current = <105000 0 80000 0>;
+ gpio-no-mux = <0>;
+ pinctrl-names = "cam_default", "cam_suspend";
+ pinctrl-0 = <&cam_sensor_mclk1_active
+ &cam_sensor_rear2_active>;
+ pinctrl-1 = <&cam_sensor_mclk1_suspend
+ &cam_sensor_rear2_suspend>;
+ gpios = <&tlmm 14 0>,
+ <&tlmm 28 0>;
+ gpio-reset = <1>;
+ gpio-req-tbl-num = <0 1>;
+ gpio-req-tbl-flags = <1 0>;
+ gpio-req-tbl-label = "CAMIF_MCLK1",
+ "CAM_RESET1";
+ sensor-position = <0>;
+ sensor-mode = <0>;
+ cci-master = <1>;
+ status = "ok";
+ clock-names = "cam_clk";
+ clock-cntl-level = "turbo";
+ clock-rates = <24000000>;
+};
+
+&eeprom_front {
+ cell-index = <2>;
+ reg = <0x2>;
+ compatible = "qcom,eeprom";
+ cam_vio-supply = <&camera_vio_ldo>;
+ cam_vana-supply = <&camera_vana_ldo>;
+ cam_vdig-supply = <&camera_ldo>;
+ cam_clk-supply = <&titan_top_gdsc>;
+ cam_vaf-supply = <&actuator_regulator>;
+ regulator-names = "cam_vio", "cam_vana", "cam_vdig",
+ "cam_clk", "cam_vaf";
+ rgltr-cntrl-support;
+ rgltr-min-voltage = <1800000 2850000 1352000 0 2800000>;
+ rgltr-max-voltage = <1800000 2850000 1352000 0 2800000>;
+ rgltr-load-current = <0 80000 105000 0>;
+ gpio-no-mux = <0>;
+ pinctrl-names = "cam_default", "cam_suspend";
+ pinctrl-0 = <&cam_sensor_mclk2_active
+ &cam_sensor_front_active>;
+ pinctrl-1 = <&cam_sensor_mclk2_suspend
+ &cam_sensor_front_suspend>;
+ gpios = <&tlmm 15 0>,
+ <&tlmm 9 0>;
+ gpio-reset = <1>;
+ gpio-req-tbl-num = <0 1>;
+ gpio-req-tbl-flags = <1 0>;
+ gpio-req-tbl-label = "CAMIF_MCLK2",
+ "CAM_RESET2";
+ sensor-mode = <0>;
+ cci-master = <1>;
+ status = "ok";
+ clocks = <&clock_camcc CAM_CC_MCLK2_CLK>;
+ clock-names = "cam_clk";
+ clock-cntl-level = "turbo";
+ clock-rates = <24000000>;
};
&cam_cci {
- actuator_rear: qcom,actuator@0 {
- cell-index = <0>;
- reg = <0x0>;
- compatible = "qcom,actuator";
- cci-master = <0>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <2800000>;
- rgltr-max-voltage = <2800000>;
- rgltr-load-current = <0>;
- };
-
- actuator_front: qcom,actuator@1 {
- cell-index = <1>;
- reg = <0x1>;
- compatible = "qcom,actuator";
- cci-master = <1>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <2800000>;
- rgltr-max-voltage = <2800000>;
- rgltr-load-current = <0>;
- };
-
- ois_rear: qcom,ois@0 {
- cell-index = <0>;
- reg = <0x0>;
- compatible = "qcom,ois";
- cci-master = <0>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <2800000>;
- rgltr-max-voltage = <2800000>;
- rgltr-load-current = <0>;
- status = "disabled";
- };
-
- eeprom_rear: qcom,eeprom@0 {
- cell-index = <0>;
- reg = <0>;
- compatible = "qcom,eeprom";
- cam_vio-supply = <&camera_vio_ldo>;
- cam_vana-supply = <&camera_vana_ldo>;
- cam_vdig-supply = <&camera_rear_ldo>;
- cam_clk-supply = <&titan_top_gdsc>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vio", "cam_vana", "cam_vdig",
- "cam_clk", "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <1800000 2850000 1352000 0 2800000>;
- rgltr-max-voltage = <1800000 2850000 1352000 0 2800000>;
- rgltr-load-current = <0 80000 105000 0 0>;
- gpio-no-mux = <0>;
- pinctrl-names = "cam_default", "cam_suspend";
- pinctrl-0 = <&cam_sensor_mclk0_active
- &cam_sensor_rear_active>;
- pinctrl-1 = <&cam_sensor_mclk0_suspend
- &cam_sensor_rear_suspend>;
- gpios = <&tlmm 13 0>,
- <&tlmm 30 0>;
- gpio-reset = <1>;
- gpio-req-tbl-num = <0 1>;
- gpio-req-tbl-flags = <1 0>;
- gpio-req-tbl-label = "CAMIF_MCLK0",
- "CAM_RESET0";
- sensor-mode = <0>;
- cci-master = <0>;
- status = "ok";
- clocks = <&clock_camcc CAM_CC_MCLK0_CLK>;
- clock-names = "cam_clk";
- clock-cntl-level = "turbo";
- clock-rates = <24000000>;
- };
-
- eeprom_rear_aux: qcom,eeprom@1 {
- cell-index = <1>;
- reg = <0x1>;
- compatible = "qcom,eeprom";
- cam_vio-supply = <&camera_vio_ldo>;
- cam_vana-supply = <&camera_vana_ldo>;
- cam_vdig-supply = <&camera_ldo>;
- cam_clk-supply = <&titan_top_gdsc>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vdig", "cam_vio", "cam_vana",
- "cam_clk", "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <1352000 1800000 2850000 0 2800000>;
- rgltr-max-voltage = <1352000 1800000 2850000 0 2800000>;
- rgltr-load-current = <105000 0 80000 0>;
- gpio-no-mux = <0>;
- pinctrl-names = "cam_default", "cam_suspend";
- pinctrl-0 = <&cam_sensor_mclk1_active
- &cam_sensor_rear2_active>;
- pinctrl-1 = <&cam_sensor_mclk1_suspend
- &cam_sensor_rear2_suspend>;
- gpios = <&tlmm 14 0>,
- <&tlmm 28 0>;
- gpio-reset = <1>;
- gpio-req-tbl-num = <0 1>;
- gpio-req-tbl-flags = <1 0>;
- gpio-req-tbl-label = "CAMIF_MCLK1",
- "CAM_RESET1";
- sensor-position = <0>;
- sensor-mode = <0>;
- cci-master = <1>;
- status = "ok";
- clock-names = "cam_clk";
- clock-cntl-level = "turbo";
- clock-rates = <24000000>;
- };
-
- eeprom_front: qcom,eeprom@2 {
- cell-index = <2>;
- reg = <0x2>;
- compatible = "qcom,eeprom";
- cam_vio-supply = <&camera_vio_ldo>;
- cam_vana-supply = <&camera_vana_ldo>;
- cam_vdig-supply = <&camera_ldo>;
- cam_clk-supply = <&titan_top_gdsc>;
- cam_vaf-supply = <&actuator_regulator>;
- regulator-names = "cam_vio", "cam_vana", "cam_vdig",
- "cam_clk", "cam_vaf";
- rgltr-cntrl-support;
- rgltr-min-voltage = <1800000 2850000 1352000 0 2800000>;
- rgltr-max-voltage = <1800000 2850000 1352000 0 2800000>;
- rgltr-load-current = <0 80000 105000 0>;
- gpio-no-mux = <0>;
- pinctrl-names = "cam_default", "cam_suspend";
- pinctrl-0 = <&cam_sensor_mclk2_active
- &cam_sensor_front_active>;
- pinctrl-1 = <&cam_sensor_mclk2_suspend
- &cam_sensor_front_suspend>;
- gpios = <&tlmm 15 0>,
- <&tlmm 9 0>;
- gpio-reset = <1>;
- gpio-req-tbl-num = <0 1>;
- gpio-req-tbl-flags = <1 0>;
- gpio-req-tbl-label = "CAMIF_MCLK2",
- "CAM_RESET2";
- sensor-mode = <0>;
- cci-master = <1>;
- status = "ok";
- clocks = <&clock_camcc CAM_CC_MCLK2_CLK>;
- clock-names = "cam_clk";
- clock-cntl-level = "turbo";
- clock-rates = <24000000>;
- };
-
qcom,cam-sensor@0 {
cell-index = <0>;
compatible = "qcom,cam-sensor";
diff --git a/arch/arm64/boot/dts/qcom/sdm670-ion.dtsi b/arch/arm64/boot/dts/qcom/sdm670-ion.dtsi
index 61ef7ff..46de412 100644
--- a/arch/arm64/boot/dts/qcom/sdm670-ion.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm670-ion.dtsi
@@ -21,12 +21,6 @@
qcom,ion-heap-type = "SYSTEM";
};
- qcom,ion-heap@22 { /* ADSP HEAP */
- reg = <22>;
- memory-region = <&adsp_mem>;
- qcom,ion-heap-type = "DMA";
- };
-
qcom,ion-heap@27 { /* QSEECOM HEAP */
reg = <27>;
memory-region = <&qseecom_mem>;
diff --git a/arch/arm64/boot/dts/qcom/sdm670-pinctrl.dtsi b/arch/arm64/boot/dts/qcom/sdm670-pinctrl.dtsi
index ffed74c..9025d6b 100644
--- a/arch/arm64/boot/dts/qcom/sdm670-pinctrl.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm670-pinctrl.dtsi
@@ -1979,6 +1979,19 @@
drive-strength = <2>; /* 2 MA */
};
};
+
+ nx30p6093_intr_default: nx30p6093_intr_default {
+ mux {
+ pins = "gpio5";
+ function = "gpio";
+ };
+
+ config {
+ pins = "gpio5";
+ bias-disable;
+ input-enable;
+ };
+ };
};
};
diff --git a/arch/arm64/boot/dts/qcom/sdm670-sde-display.dtsi b/arch/arm64/boot/dts/qcom/sdm670-sde-display.dtsi
index de125e2..7e426cf 100644
--- a/arch/arm64/boot/dts/qcom/sdm670-sde-display.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm670-sde-display.dtsi
@@ -625,15 +625,35 @@
};
&dsi_sim_cmd {
- qcom,mdss-dsi-t-clk-post = <0x0d>;
- qcom,mdss-dsi-t-clk-pre = <0x2d>;
+ qcom,mdss-dsi-t-clk-post = <0x0c>;
+ qcom,mdss-dsi-t-clk-pre = <0x29>;
qcom,mdss-dsi-display-timings {
timing@0{
- qcom,mdss-dsi-panel-phy-timings = [00 1c 07 07 23 21 07
- 07 05 03 04 00];
qcom,display-topology = <1 0 1>,
- <2 0 1>;
- qcom,default-topology-index = <0>;
+ <2 2 1>;
+ qcom,default-topology-index = <1>;
+ qcom,panel-roi-alignment = <720 40 720 40 720 40>;
+ qcom,partial-update-enabled = "single_roi";
+ qcom,mdss-dsi-panel-phy-timings = [00 1a 06 06 22 20 07
+ 07 04 03 04 00];
+ };
+ timing@1{
+ qcom,display-topology = <1 0 1>,
+ <2 2 1>;
+ qcom,default-topology-index = <1>;
+ qcom,panel-roi-alignment = <540 40 540 40 540 40>;
+ qcom,partial-update-enabled = "single_roi";
+ qcom,mdss-dsi-panel-phy-timings = [00 1a 06 06 22 20 07
+ 07 04 03 04 00];
+ };
+ timing@2{
+ qcom,display-topology = <1 0 1>,
+ <2 2 1>;
+ qcom,default-topology-index = <1>;
+ qcom,panel-roi-alignment = <360 40 360 40 360 40>;
+ qcom,partial-update-enabled = "single_roi";
+ qcom,mdss-dsi-panel-phy-timings = [00 1a 06 06 22 20 07
+ 07 04 03 04 00];
};
};
};
@@ -709,8 +729,8 @@
qcom,mdss-dsi-t-clk-pre = <0x2d>;
qcom,mdss-dsi-display-timings {
timing@0 {
- qcom,mdss-dsi-panel-timings = [00 1c 08 07 23 22 07 07
- 05 03 04 00];
+ qcom,mdss-dsi-panel-phy-timings = [00 1c 07 07 23 21 07
+ 07 05 03 04 00];
qcom,display-topology = <2 0 2>,
<1 0 2>;
qcom,default-topology-index = <0>;
@@ -724,8 +744,8 @@
qcom,ulps-enabled;
qcom,mdss-dsi-display-timings {
timing@0 {
- qcom,mdss-dsi-panel-timings = [00 1c 08 07 23 22 07 07
- 05 03 04 00];
+ qcom,mdss-dsi-panel-phy-timings = [00 1c 07 07 23 21 07
+ 07 05 03 04 00];
qcom,display-topology = <2 0 2>,
<1 0 2>;
qcom,default-topology-index = <0>;
diff --git a/arch/arm64/boot/dts/qcom/sdm670.dtsi b/arch/arm64/boot/dts/qcom/sdm670.dtsi
index e321329..4d38f954 100644
--- a/arch/arm64/boot/dts/qcom/sdm670.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm670.dtsi
@@ -2932,3 +2932,17 @@
&pm660_div_clk {
status = "ok";
};
+
+&qupv3_se10_i2c {
+ nx30p6093: nx30p6093@36 {
+ status = "disabled";
+ compatible = "nxp,nx30p6093";
+ reg = <0x36>;
+ interrupt-parent = <&tlmm>;
+ interrupts = <5 IRQ_TYPE_NONE>;
+ nxp,long-wakeup-sec = <28800>; /* 8 hours */
+ nxp,short-wakeup-ms = <180000>; /* 3 mins */
+ pinctrl-names = "default";
+ pinctrl-0 = <&nx30p6093_intr_default>;
+ };
+};
diff --git a/arch/arm64/boot/dts/qcom/sdm845-qvr.dtsi b/arch/arm64/boot/dts/qcom/sdm845-qvr.dtsi
index 00f0650..0c1f097 100644
--- a/arch/arm64/boot/dts/qcom/sdm845-qvr.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845-qvr.dtsi
@@ -132,6 +132,7 @@
vdd-hba-supply = <&ufs_phy_gdsc>;
vdd-hba-fixed-regulator;
vcc-supply = <&pm8998_l20>;
+ vcc-voltage-level = <2950000 2960000>;
vccq2-supply = <&pm8998_s4>;
vcc-max-microamp = <600000>;
vccq2-max-microamp = <600000>;
diff --git a/arch/arm64/boot/dts/qcom/sdm845.dtsi b/arch/arm64/boot/dts/qcom/sdm845.dtsi
index 91bab54..1b83977 100644
--- a/arch/arm64/boot/dts/qcom/sdm845.dtsi
+++ b/arch/arm64/boot/dts/qcom/sdm845.dtsi
@@ -2800,6 +2800,7 @@
qcom,pas-id = <0xf>;
qcom,firmware-name = "ipa_fws";
qcom,pil-force-shutdown;
+ memory-region = <&pil_ipa_fw_mem>;
};
qcom,chd_sliver {
diff --git a/arch/arm64/configs/msm8953-perf_defconfig b/arch/arm64/configs/msm8953-perf_defconfig
index f862f576..de90d43 100644
--- a/arch/arm64/configs/msm8953-perf_defconfig
+++ b/arch/arm64/configs/msm8953-perf_defconfig
@@ -400,9 +400,20 @@
CONFIG_USB_GADGET_DEBUG_FS=y
CONFIG_USB_GADGET_VBUS_DRAW=500
CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_QCRNDIS=y
+CONFIG_USB_CONFIGFS_RMNET_BAM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_MTP=y
+CONFIG_USB_CONFIGFS_F_PTP=y
+CONFIG_USB_CONFIGFS_F_ACC=y
+CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y
CONFIG_USB_CONFIGFS_UEVENT=y
+CONFIG_USB_CONFIGFS_F_HID=y
CONFIG_USB_CONFIGFS_F_DIAG=y
+CONFIG_USB_CONFIGFS_F_QDSS=y
CONFIG_MMC=y
CONFIG_MMC_PERF_PROFILING=y
CONFIG_MMC_PARANOID_SD_INIT=y
@@ -459,6 +470,7 @@
CONFIG_MSM_SMEM=y
CONFIG_MSM_SMD=y
CONFIG_MSM_SMD_DEBUG=y
+CONFIG_MSM_TZ_SMMU=y
CONFIG_MSM_SMP2P=y
CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y
CONFIG_MSM_QMI_INTERFACE=y
diff --git a/arch/arm64/configs/msm8953_defconfig b/arch/arm64/configs/msm8953_defconfig
index ef6e00f..8145f47 100644
--- a/arch/arm64/configs/msm8953_defconfig
+++ b/arch/arm64/configs/msm8953_defconfig
@@ -411,9 +411,20 @@
CONFIG_USB_GADGET_DEBUG_FS=y
CONFIG_USB_GADGET_VBUS_DRAW=500
CONFIG_USB_CONFIGFS=y
+CONFIG_USB_CONFIGFS_SERIAL=y
+CONFIG_USB_CONFIGFS_NCM=y
+CONFIG_USB_CONFIGFS_QCRNDIS=y
+CONFIG_USB_CONFIGFS_RMNET_BAM=y
+CONFIG_USB_CONFIGFS_MASS_STORAGE=y
CONFIG_USB_CONFIGFS_F_FS=y
+CONFIG_USB_CONFIGFS_F_MTP=y
+CONFIG_USB_CONFIGFS_F_PTP=y
+CONFIG_USB_CONFIGFS_F_ACC=y
+CONFIG_USB_CONFIGFS_F_AUDIO_SRC=y
CONFIG_USB_CONFIGFS_UEVENT=y
+CONFIG_USB_CONFIGFS_F_HID=y
CONFIG_USB_CONFIGFS_F_DIAG=y
+CONFIG_USB_CONFIGFS_F_QDSS=y
CONFIG_MMC=y
CONFIG_MMC_PERF_PROFILING=y
CONFIG_MMC_RING_BUFFER=y
@@ -476,6 +487,7 @@
CONFIG_MSM_SMEM=y
CONFIG_MSM_SMD=y
CONFIG_MSM_SMD_DEBUG=y
+CONFIG_MSM_TZ_SMMU=y
CONFIG_TRACER_PKT=y
CONFIG_MSM_SMP2P=y
CONFIG_MSM_IPC_ROUTER_SMD_XPRT=y
diff --git a/arch/arm64/configs/sdm670-perf_defconfig b/arch/arm64/configs/sdm670-perf_defconfig
index ee6135a..1904209 100644
--- a/arch/arm64/configs/sdm670-perf_defconfig
+++ b/arch/arm64/configs/sdm670-perf_defconfig
@@ -21,8 +21,6 @@
CONFIG_CPUSETS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_SCHEDTUNE=y
-CONFIG_MEMCG=y
-CONFIG_MEMCG_SWAP=y
CONFIG_BLK_CGROUP=y
CONFIG_RT_GROUP_SCHED=y
CONFIG_CGROUP_BPF=y
diff --git a/arch/arm64/configs/sdm670_defconfig b/arch/arm64/configs/sdm670_defconfig
index 995e658..670627d 100644
--- a/arch/arm64/configs/sdm670_defconfig
+++ b/arch/arm64/configs/sdm670_defconfig
@@ -22,8 +22,6 @@
CONFIG_CPUSETS=y
CONFIG_CGROUP_CPUACCT=y
CONFIG_CGROUP_SCHEDTUNE=y
-CONFIG_MEMCG=y
-CONFIG_MEMCG_SWAP=y
CONFIG_BLK_CGROUP=y
CONFIG_DEBUG_BLK_CGROUP=y
CONFIG_RT_GROUP_SCHED=y
diff --git a/drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c b/drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c
index 5635346..c1ea8dd 100644
--- a/drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c
+++ b/drivers/gpu/drm/msm/dsi-staging/dsi_pwr.c
@@ -111,13 +111,13 @@
regs->vregs[i].post_off_sleep = tmp;
}
- ++i;
pr_debug("[%s] minv=%d maxv=%d, en_load=%d, dis_load=%d\n",
regs->vregs[i].vreg_name,
regs->vregs[i].min_voltage,
regs->vregs[i].max_voltage,
regs->vregs[i].enable_load,
regs->vregs[i].disable_load);
+ ++i;
}
error:
diff --git a/drivers/gpu/drm/msm/sde/sde_ad4.h b/drivers/gpu/drm/msm/sde/sde_ad4.h
index 5a646e9..06f004c 100644
--- a/drivers/gpu/drm/msm/sde/sde_ad4.h
+++ b/drivers/gpu/drm/msm/sde/sde_ad4.h
@@ -58,7 +58,7 @@
* enum ad_intr_resp_property - ad4 interrupt response enum
*/
enum ad_intr_resp_property {
- AD4_BACKLIGHT,
+ AD4_IN_OUT_BACKLIGHT,
AD4_RESPMAX,
};
@@ -92,8 +92,10 @@
* sde_read_intr_resp_ad4 - api to get ad4 interrupt status for event
* @dspp: pointer to dspp object
* @event: event for which response is needed
- * @resp: value of event requested
+ * @resp_in: read ad4 input value of event requested
+ * @resp_out: read ad4 output value of event requested
*/
-void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event, u32 *resp);
+void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event,
+ u32 *resp_in, u32 *resp_out);
#endif /* _SDE_AD4_H_ */
diff --git a/drivers/gpu/drm/msm/sde/sde_color_processing.c b/drivers/gpu/drm/msm/sde/sde_color_processing.c
index 07b5536..42aea7e 100644
--- a/drivers/gpu/drm/msm/sde/sde_color_processing.c
+++ b/drivers/gpu/drm/msm/sde/sde_color_processing.c
@@ -24,6 +24,7 @@
#include "sde_ad4.h"
#include "sde_hw_interrupts.h"
#include "sde_core_irq.h"
+#include "dsi_panel.h"
struct sde_cp_node {
u32 property_id;
@@ -1575,7 +1576,8 @@
static void sde_cp_notify_ad_event(struct drm_crtc *crtc_drm, void *arg)
{
- uint32_t bl = 0;
+ uint32_t input_bl = 0, output_bl = 0;
+ uint32_t scale = MAX_AD_BL_SCALE_LEVEL;
struct sde_hw_mixer *hw_lm = NULL;
struct sde_hw_dspp *hw_dspp = NULL;
u32 num_mixers;
@@ -1598,11 +1600,17 @@
if (!hw_dspp)
return;
- hw_dspp->ops.ad_read_intr_resp(hw_dspp, AD4_BACKLIGHT, &bl);
+ hw_dspp->ops.ad_read_intr_resp(hw_dspp, AD4_IN_OUT_BACKLIGHT,
+ &input_bl, &output_bl);
+
+ if (!input_bl || input_bl < output_bl)
+ return;
+
+ scale = (output_bl * MAX_AD_BL_SCALE_LEVEL) / input_bl;
event.length = sizeof(u32);
event.type = DRM_EVENT_AD_BACKLIGHT;
msm_mode_object_event_notify(&crtc_drm->base, crtc_drm->dev,
- &event, (u8 *)&bl);
+ &event, (u8 *)&scale);
}
int sde_cp_ad_interrupt(struct drm_crtc *crtc_drm, bool en,
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.c b/drivers/gpu/drm/msm/sde/sde_crtc.c
index 43e6aaa..52e291d 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.c
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.c
@@ -1404,7 +1404,8 @@
}
static void _sde_crtc_blend_setup_mixer(struct drm_crtc *crtc,
- struct sde_crtc *sde_crtc, struct sde_crtc_mixer *mixer)
+ struct drm_crtc_state *old_state, struct sde_crtc *sde_crtc,
+ struct sde_crtc_mixer *mixer)
{
struct drm_plane *plane;
struct drm_framebuffer *fb;
@@ -1424,7 +1425,7 @@
bool bg_alpha_enable = false;
u32 prefill = 0;
- if (!sde_crtc || !mixer) {
+ if (!sde_crtc || !crtc->state || !mixer) {
SDE_ERROR("invalid sde_crtc or mixer\n");
return;
}
@@ -1435,7 +1436,9 @@
cstate = to_sde_crtc_state(crtc->state);
cstate->sbuf_prefill_line = 0;
- sde_crtc->sbuf_flush_mask = 0x0;
+ sde_crtc->sbuf_flush_mask_old = sde_crtc->sbuf_flush_mask_all;
+ sde_crtc->sbuf_flush_mask_all = 0x0;
+ sde_crtc->sbuf_flush_mask_delta = 0x0;
drm_atomic_crtc_for_each_plane(plane, crtc) {
state = plane->state;
@@ -1457,7 +1460,10 @@
sde_plane_get_ctl_flush(plane, ctl, &flush_mask, &flush_sbuf);
/* save sbuf flush value for later */
- sde_crtc->sbuf_flush_mask |= flush_sbuf;
+ if (old_state && drm_atomic_get_existing_plane_state(
+ old_state->state, plane))
+ sde_crtc->sbuf_flush_mask_delta |= flush_sbuf;
+ sde_crtc->sbuf_flush_mask_all |= flush_sbuf;
SDE_DEBUG("crtc %d stage:%d - plane %d sspp %d fb %d\n",
crtc->base.id,
@@ -1584,8 +1590,11 @@
/**
* _sde_crtc_blend_setup - configure crtc mixers
* @crtc: Pointer to drm crtc structure
+ * @old_state: Pointer to old crtc state
+ * @add_planes: Whether or not to add planes to mixers
*/
-static void _sde_crtc_blend_setup(struct drm_crtc *crtc, bool add_planes)
+static void _sde_crtc_blend_setup(struct drm_crtc *crtc,
+ struct drm_crtc_state *old_state, bool add_planes)
{
struct sde_crtc *sde_crtc;
struct sde_crtc_state *sde_crtc_state;
@@ -1632,7 +1641,7 @@
memset(&sde_crtc->stage_cfg, 0, sizeof(struct sde_hw_stage_cfg));
if (add_planes)
- _sde_crtc_blend_setup_mixer(crtc, sde_crtc, mixer);
+ _sde_crtc_blend_setup_mixer(crtc, old_state, sde_crtc, mixer);
for (i = 0; i < sde_crtc->num_mixers; i++) {
const struct sde_rect *lm_roi = &sde_crtc_state->lm_roi[i];
@@ -3198,7 +3207,7 @@
if (unlikely(!sde_crtc->num_mixers))
return;
- _sde_crtc_blend_setup(crtc, true);
+ _sde_crtc_blend_setup(crtc, old_state, true);
_sde_crtc_dest_scaler_setup(crtc);
/* cancel the idle notify delayed work */
@@ -3422,23 +3431,29 @@
sde_crtc = to_sde_crtc(crtc);
/*
- * Update sbuf configuration and flush bits if a flush
- * mask has been defined for either the current or
- * previous commit.
+ * Update sbuf configuration and flush bits if either the rot_op_mode
+ * is different or a rotator commit was performed.
*
- * Updates are also required for the first commit after
- * sbuf_flush_mask becomes 0x0, to properly transition
- * the hardware out of sbuf mode.
+ * In the case where the rot_op_mode has changed, further require that
+ * the transition is either to or from offline mode unless
+ * sbuf_flush_mask_delta is also non-zero (i.e., a corresponding plane
+ * update was provided to the current commit).
*/
- if (!sde_crtc->sbuf_flush_mask_old && !sde_crtc->sbuf_flush_mask)
- return 0;
+ flush_mask = sde_crtc->sbuf_flush_mask_delta;
+ if ((sde_crtc->sbuf_op_mode_old != cstate->sbuf_cfg.rot_op_mode) &&
+ (sde_crtc->sbuf_op_mode_old == SDE_CTL_ROT_OP_MODE_OFFLINE ||
+ cstate->sbuf_cfg.rot_op_mode == SDE_CTL_ROT_OP_MODE_OFFLINE))
+ flush_mask |= sde_crtc->sbuf_flush_mask_all |
+ sde_crtc->sbuf_flush_mask_old;
- flush_mask = sde_crtc->sbuf_flush_mask_old | sde_crtc->sbuf_flush_mask;
- sde_crtc->sbuf_flush_mask_old = sde_crtc->sbuf_flush_mask;
+ if (!flush_mask &&
+ cstate->sbuf_cfg.rot_op_mode == SDE_CTL_ROT_OP_MODE_OFFLINE)
+ return 0;
SDE_ATRACE_BEGIN("crtc_kickoff_rot");
- if (cstate->sbuf_cfg.rot_op_mode != SDE_CTL_ROT_OP_MODE_OFFLINE) {
+ if (cstate->sbuf_cfg.rot_op_mode != SDE_CTL_ROT_OP_MODE_OFFLINE &&
+ sde_crtc->sbuf_flush_mask_delta) {
drm_atomic_crtc_for_each_plane(plane, crtc) {
rc = sde_plane_kickoff_rot(plane);
if (rc) {
@@ -3474,12 +3489,16 @@
/* explicitly trigger rotator for async modes */
if (cstate->sbuf_cfg.rot_op_mode ==
SDE_CTL_ROT_OP_MODE_INLINE_ASYNC &&
- master_ctl->ops.trigger_rot_start) {
+ master_ctl->ops.trigger_rot_start)
master_ctl->ops.trigger_rot_start(master_ctl);
- SDE_EVT32(DRMID(crtc), master_ctl->idx - CTL_0);
- }
+ SDE_EVT32(DRMID(crtc), master_ctl->idx - CTL_0,
+ sde_crtc->sbuf_flush_mask_all,
+ sde_crtc->sbuf_flush_mask_delta);
}
+ /* save this in sde_crtc for next commit cycle */
+ sde_crtc->sbuf_op_mode_old = cstate->sbuf_cfg.rot_op_mode;
+
SDE_ATRACE_END("crtc_kickoff_rot");
return rc;
}
@@ -3492,13 +3511,14 @@
{
struct sde_crtc_mixer *mixer;
struct sde_hw_ctl *ctl;
- u32 i, flush_mask;
+ u32 i, n, flush_mask;
if (!sde_crtc)
return;
mixer = sde_crtc->mixers;
- for (i = 0; i < sde_crtc->num_mixers; i++) {
+ n = min_t(size_t, sde_crtc->num_mixers, ARRAY_SIZE(sde_crtc->mixers));
+ for (i = 0; i < n; i++) {
ctl = mixer[i].hw_ctl;
if (!ctl || !ctl->ops.get_pending_flush ||
!ctl->ops.clear_pending_flush ||
@@ -3524,16 +3544,19 @@
{
struct drm_plane *plane_halt[MAX_PLANES];
struct drm_plane *plane;
+ struct drm_encoder *encoder;
const struct drm_plane_state *pstate;
struct sde_crtc *sde_crtc;
+ struct sde_crtc_state *cstate;
struct sde_hw_ctl *ctl;
enum sde_ctl_rot_op_mode old_rot_op_mode;
- signed int i, plane_count;
+ signed int i, n, plane_count;
int rc;
- if (!crtc || !old_state)
+ if (!crtc || !crtc->dev || !old_state || !crtc->state)
return -EINVAL;
sde_crtc = to_sde_crtc(crtc);
+ cstate = to_sde_crtc_state(crtc->state);
old_rot_op_mode = to_sde_crtc_state(old_state)->sbuf_cfg.rot_op_mode;
SDE_EVT32(DRMID(crtc), old_rot_op_mode,
@@ -3545,7 +3568,8 @@
/* optionally generate a panic instead of performing a h/w reset */
SDE_DBG_CTRL("stop_ftrace", "reset_hw_panic");
- for (i = 0; i < sde_crtc->num_mixers; ++i) {
+ n = min_t(size_t, sde_crtc->num_mixers, ARRAY_SIZE(sde_crtc->mixers));
+ for (i = 0; i < n; ++i) {
ctl = sde_crtc->mixers[i].hw_ctl;
if (!ctl || !ctl->ops.reset)
continue;
@@ -3570,14 +3594,13 @@
* depending on the rotation mode; don't handle this for now
* and just force a hard reset in those cases.
*/
- if (i == sde_crtc->num_mixers &&
- old_rot_op_mode == SDE_CTL_ROT_OP_MODE_OFFLINE)
+ if (i == n && old_rot_op_mode == SDE_CTL_ROT_OP_MODE_OFFLINE)
return false;
SDE_DEBUG("crtc%d: issuing hard reset\n", DRMID(crtc));
/* force all components in the system into reset at the same time */
- for (i = 0; i < sde_crtc->num_mixers; ++i) {
+ for (i = 0; i < n; ++i) {
ctl = sde_crtc->mixers[i].hw_ctl;
if (!ctl || !ctl->ops.hard_reset)
continue;
@@ -3613,11 +3636,26 @@
sde_plane_reset_rot(plane, (struct drm_plane_state *)pstate);
}
+ /* provide safe "border color only" commit configuration for later */
+ cstate->sbuf_cfg.rot_op_mode = SDE_CTL_ROT_OP_MODE_OFFLINE;
+ _sde_crtc_commit_kickoff_rot(crtc, cstate);
+ _sde_crtc_remove_pipe_flush(sde_crtc);
+ _sde_crtc_blend_setup(crtc, old_state, false);
+
/* take h/w components out of reset */
for (i = plane_count - 1; i >= 0; --i)
sde_plane_halt_requests(plane_halt[i], false);
- for (i = 0; i < sde_crtc->num_mixers; ++i) {
+ /* attempt to poll for start of frame cycle before reset release */
+ list_for_each_entry(encoder,
+ &crtc->dev->mode_config.encoder_list, head) {
+ if (encoder->crtc != crtc)
+ continue;
+ if (sde_encoder_get_intf_mode(encoder) == INTF_MODE_VIDEO)
+ sde_encoder_poll_line_counts(encoder);
+ }
+
+ for (i = 0; i < n; ++i) {
ctl = sde_crtc->mixers[i].hw_ctl;
if (!ctl || !ctl->ops.hard_reset)
continue;
@@ -3625,6 +3663,15 @@
ctl->ops.hard_reset(ctl, false);
}
+ list_for_each_entry(encoder,
+ &crtc->dev->mode_config.encoder_list, head) {
+ if (encoder->crtc != crtc)
+ continue;
+
+ if (sde_encoder_get_intf_mode(encoder) == INTF_MODE_VIDEO)
+ sde_encoder_kickoff(encoder, false);
+ }
+
return -EAGAIN;
}
@@ -3649,7 +3696,7 @@
cstate = to_sde_crtc_state(crtc->state);
/* default to ASYNC mode for inline rotation */
- cstate->sbuf_cfg.rot_op_mode = sde_crtc->sbuf_flush_mask ?
+ cstate->sbuf_cfg.rot_op_mode = sde_crtc->sbuf_flush_mask_all ?
SDE_CTL_ROT_OP_MODE_INLINE_ASYNC : SDE_CTL_ROT_OP_MODE_OFFLINE;
if (cstate->sbuf_cfg.rot_op_mode == SDE_CTL_ROT_OP_MODE_OFFLINE)
@@ -3749,11 +3796,6 @@
if (_sde_crtc_reset_hw(crtc, old_state,
!sde_crtc->reset_request))
is_error = true;
-
- /* force offline rotation mode since the commit has no pipes */
- if (is_error)
- cstate->sbuf_cfg.rot_op_mode =
- SDE_CTL_ROT_OP_MODE_OFFLINE;
}
sde_crtc->reset_request = reset_req;
@@ -3799,7 +3841,7 @@
if (is_error) {
_sde_crtc_remove_pipe_flush(sde_crtc);
- _sde_crtc_blend_setup(crtc, false);
+ _sde_crtc_blend_setup(crtc, old_state, false);
}
list_for_each_entry(encoder, &dev->mode_config.encoder_list, head) {
diff --git a/drivers/gpu/drm/msm/sde/sde_crtc.h b/drivers/gpu/drm/msm/sde/sde_crtc.h
index 589a667..1de3675 100644
--- a/drivers/gpu/drm/msm/sde/sde_crtc.h
+++ b/drivers/gpu/drm/msm/sde/sde_crtc.h
@@ -215,8 +215,10 @@
* @misr_enable : boolean entry indicates misr enable/disable status.
* @misr_frame_count : misr frame count provided by client
* @misr_data : store misr data before turning off the clocks.
- * @sbuf_flush_mask: flush mask for inline rotator
+ * @sbuf_op_mode_old : inline rotator op mode for previous commit cycle
* @sbuf_flush_mask_old: inline rotator flush mask for previous commit
+ * @sbuf_flush_mask_all: inline rotator flush mask for all attached planes
+ * @sbuf_flush_mask_delta: inline rotator flush mask for current delta state
* @idle_notify_work: delayed worker to notify idle timeout to user space
* @power_event : registered power event handle
* @cur_perf : current performance committed to clock/bandwidth driver
@@ -284,8 +286,10 @@
u32 misr_frame_count;
u32 misr_data[CRTC_DUAL_MIXERS];
- u32 sbuf_flush_mask;
+ u32 sbuf_op_mode_old;
u32 sbuf_flush_mask_old;
+ u32 sbuf_flush_mask_all;
+ u32 sbuf_flush_mask_delta;
struct kthread_delayed_work idle_notify_work;
struct sde_power_event *power_event;
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.c b/drivers/gpu/drm/msm/sde/sde_encoder.c
index 4008115..7162b06 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.c
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.c
@@ -3443,6 +3443,51 @@
_sde_encoder_power_enable(sde_enc, false);
}
+int sde_encoder_poll_line_counts(struct drm_encoder *drm_enc)
+{
+ static const uint64_t timeout_us = 50000;
+ static const uint64_t sleep_us = 20;
+ struct sde_encoder_virt *sde_enc;
+ ktime_t cur_ktime, exp_ktime;
+ uint32_t line_count, tmp, i;
+
+ if (!drm_enc) {
+ SDE_ERROR("invalid encoder\n");
+ return -EINVAL;
+ }
+ sde_enc = to_sde_encoder_virt(drm_enc);
+ if (!sde_enc->cur_master ||
+ !sde_enc->cur_master->ops.get_line_count) {
+ SDE_DEBUG_ENC(sde_enc, "can't get master line count\n");
+ SDE_EVT32(DRMID(drm_enc), SDE_EVTLOG_ERROR);
+ return -EINVAL;
+ }
+
+ exp_ktime = ktime_add_ms(ktime_get(), timeout_us / 1000);
+
+ line_count = sde_enc->cur_master->ops.get_line_count(
+ sde_enc->cur_master);
+
+ for (i = 0; i < (timeout_us * 2 / sleep_us); ++i) {
+ tmp = line_count;
+ line_count = sde_enc->cur_master->ops.get_line_count(
+ sde_enc->cur_master);
+ if (line_count < tmp) {
+ SDE_EVT32(DRMID(drm_enc), line_count);
+ return 0;
+ }
+
+ cur_ktime = ktime_get();
+ if (ktime_compare_safe(exp_ktime, cur_ktime) <= 0)
+ break;
+
+ usleep_range(sleep_us / 2, sleep_us);
+ }
+
+ SDE_EVT32(DRMID(drm_enc), line_count, SDE_EVTLOG_ERROR);
+ return -ETIMEDOUT;
+}
+
int sde_encoder_prepare_for_kickoff(struct drm_encoder *drm_enc,
struct sde_encoder_kickoff_params *params)
{
diff --git a/drivers/gpu/drm/msm/sde/sde_encoder.h b/drivers/gpu/drm/msm/sde/sde_encoder.h
index 937bd18..8038eb6 100644
--- a/drivers/gpu/drm/msm/sde/sde_encoder.h
+++ b/drivers/gpu/drm/msm/sde/sde_encoder.h
@@ -108,6 +108,13 @@
struct sde_rsc_client *sde_encoder_get_rsc_client(struct drm_encoder *encoder);
/**
+ * sde_encoder_poll_line_counts - poll encoder line counts for start of frame
+ * @encoder: encoder pointer
+ * @Returns: zero on success
+ */
+int sde_encoder_poll_line_counts(struct drm_encoder *encoder);
+
+/**
* sde_encoder_prepare_for_kickoff - schedule double buffer flip of the ctl
* path (i.e. ctl flush and start) at next appropriate time.
* Immediately: if no previous commit is outstanding.
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_ad4.c b/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
index bf48271..994bf3d 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
+++ b/drivers/gpu/drm/msm/sde/sde_hw_ad4.c
@@ -1248,16 +1248,20 @@
return 0;
}
-void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event, u32 *resp)
+void sde_read_intr_resp_ad4(struct sde_hw_dspp *dspp, u32 event,
+ u32 *resp_in, u32 *resp_out)
{
- if (!dspp || !resp) {
- DRM_ERROR("invalid params dspp %pK resp %pK\n", dspp, resp);
+ if (!dspp || !resp_in || !resp_out) {
+ DRM_ERROR("invalid params dspp %pK resp_in %pK resp_out %pK\n",
+ dspp, resp_in, resp_out);
return;
}
switch (event) {
- case AD4_BACKLIGHT:
- *resp = SDE_REG_READ(&dspp->hw,
+ case AD4_IN_OUT_BACKLIGHT:
+ *resp_in = SDE_REG_READ(&dspp->hw,
+ dspp->cap->sblk->ad.base + 0x2c);
+ *resp_out = SDE_REG_READ(&dspp->hw,
dspp->cap->sblk->ad.base + 0x48);
break;
default:
diff --git a/drivers/gpu/drm/msm/sde/sde_hw_dspp.h b/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
index 2b64165..2d2ac5b 100644
--- a/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
+++ b/drivers/gpu/drm/msm/sde/sde_hw_dspp.h
@@ -159,10 +159,11 @@
/**
* ad_read_intr_resp - function to get interrupt response for ad
* @event: Event for which response needs to be read
- * @resp: Pointer to u32 where response value is dumped.
+ * @resp_in: Pointer to u32 where resp ad4 input value is dumped.
+ * @resp_out: Pointer to u32 where resp ad4 output value is dumped.
*/
void (*ad_read_intr_resp)(struct sde_hw_dspp *ctx, u32 event,
- u32 *resp);
+ u32 *resp_in, u32 *resp_out);
};
diff --git a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
index fd031d7..fa5eb49 100644
--- a/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
+++ b/drivers/media/platform/msm/sde/rotator/sde_rotator_core.c
@@ -588,7 +588,7 @@
{
struct sde_rot_data_type *mdata = sde_rot_get_mdata();
uint32_t sid_info;
- struct scm_desc desc;
+ struct scm_desc desc = {0};
unsigned int resp = 0;
int ret = 0;
diff --git a/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c b/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c
index d53b1b5..4ffbd55 100644
--- a/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c
+++ b/drivers/platform/msm/ipa/ipa_v2/ipa_qmi_service.c
@@ -706,7 +706,9 @@
req->filter_index_list[i].filter_handle,
req->filter_index_list[i].filter_index);
return -EINVAL;
- } else if (req->install_status != IPA_QMI_RESULT_SUCCESS_V01) {
+ }
+
+ if (req->install_status != IPA_QMI_RESULT_SUCCESS_V01) {
IPAWANERR(" UL filter rule for pipe %d install_status = %d\n",
req->source_pipe_index, req->install_status);
return -EINVAL;
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
index ee312c7..a812891 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_dp.c
@@ -2327,6 +2327,7 @@
IPA_STATS_DEC_CNT(ipa3_ctx->stats.rx_excp_pkts
[IPAHAL_PKT_STATUS_EXCEPTION_NONE]);
}
+ tx_pkt = NULL;
};
return rc;
diff --git a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c
index 1c8715a..c158c94 100644
--- a/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c
+++ b/drivers/platform/msm/ipa/ipa_v3/ipa_qmi_service.c
@@ -610,7 +610,7 @@
IPAWANDBG("IPACM pass zero rules to Q6\n");
} else {
IPAWANDBG("IPACM pass %u rules to Q6\n",
- req->filter_spec_ex_list_len);
+ req->filter_spec_list_len);
}
if (req->filter_spec_list_len >= QMI_IPA_MAX_FILTERS_V01) {
@@ -919,7 +919,9 @@
req->source_pipe_index,
req->rule_id_len);
return -EINVAL;
- } else if (req->install_status != IPA_QMI_RESULT_SUCCESS_V01) {
+ }
+
+ if (req->install_status != IPA_QMI_RESULT_SUCCESS_V01) {
IPAWANERR(" UL filter rule for pipe %d install_status = %d\n",
req->source_pipe_index, req->install_status);
return -EINVAL;
diff --git a/drivers/soc/qcom/dcc_v2.c b/drivers/soc/qcom/dcc_v2.c
index 457dc5f..41a1a79 100644
--- a/drivers/soc/qcom/dcc_v2.c
+++ b/drivers/soc/qcom/dcc_v2.c
@@ -671,13 +671,14 @@
if (sscanf(buf, "%s", str) != 1)
return -EINVAL;
+ mutex_lock(&drvdata->mutex);
if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
dev_err(dev,
"Select link list to program using curr_list\n");
- return -EINVAL;
+ ret = -EINVAL;
+ goto out;
}
- mutex_lock(&drvdata->mutex);
if (drvdata->enable[drvdata->curr_list]) {
ret = -EBUSY;
goto out;
@@ -771,10 +772,21 @@
static ssize_t dcc_show_enable(struct device *dev,
struct device_attribute *attr, char *buf)
{
+ int ret;
struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
- return scnprintf(buf, PAGE_SIZE, "%u\n",
+ mutex_lock(&drvdata->mutex);
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
(unsigned int)drvdata->enable[drvdata->curr_list]);
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
}
static ssize_t dcc_store_enable(struct device *dev,
@@ -812,10 +824,13 @@
buf[0] = '\0';
- if (drvdata->curr_list >= DCC_MAX_LINK_LIST)
- return -EINVAL;
-
mutex_lock(&drvdata->mutex);
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ count = -EINVAL;
+ goto err;
+ }
+
list_for_each_entry(entry,
&drvdata->cfg_head[drvdata->curr_list], list) {
switch (entry->desc_type) {
@@ -852,8 +867,8 @@
count += len;
}
+err:
mutex_unlock(&drvdata->mutex);
-
return count;
}
@@ -866,6 +881,12 @@
mutex_lock(&drvdata->mutex);
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(drvdata->dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
if (!len) {
dev_err(drvdata->dev, "DCC: Invalid length\n");
ret = -EINVAL;
@@ -959,11 +980,6 @@
if (nval <= 0 || nval > 3)
return -EINVAL;
- if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
- dev_err(dev, "Select link list to program using curr_list\n");
- return -EINVAL;
- }
-
if (nval == 1) {
len = 1;
apb_bus = 0;
@@ -1028,6 +1044,12 @@
struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
mutex_lock(&drvdata->mutex);
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
if (!drvdata->enable[drvdata->curr_list]) {
ret = -EINVAL;
goto err;
@@ -1049,6 +1071,13 @@
struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
mutex_lock(&drvdata->mutex);
+
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
if (!drvdata->enable[drvdata->curr_list]) {
ret = -EINVAL;
goto err;
@@ -1159,6 +1188,12 @@
goto err;
}
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
if (list_empty(&drvdata->cfg_head[drvdata->curr_list])) {
dev_err(drvdata->dev, "DCC: No read address programmed\n");
ret = -EPERM;
@@ -1266,6 +1301,12 @@
mutex_lock(&drvdata->mutex);
+ if (drvdata->curr_list >= DCC_MAX_LINK_LIST) {
+ dev_err(dev, "Select link list to program using curr_list\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
if (drvdata->enable[drvdata->curr_list]) {
ret = -EBUSY;
goto out;
diff --git a/drivers/soc/qcom/secure_buffer.c b/drivers/soc/qcom/secure_buffer.c
index 5289cd0..1c8bc51 100644
--- a/drivers/soc/qcom/secure_buffer.c
+++ b/drivers/soc/qcom/secure_buffer.c
@@ -25,12 +25,28 @@
DEFINE_MUTEX(secure_buffer_mutex);
+struct cp2_mem_chunks {
+ u32 chunk_list;
+ u32 chunk_list_size;
+ u32 chunk_size;
+} __attribute__ ((__packed__));
+
+struct cp2_lock_req {
+ struct cp2_mem_chunks chunks;
+ u32 mem_usage;
+ u32 lock;
+} __attribute__ ((__packed__));
+
struct mem_prot_info {
phys_addr_t addr;
u64 size;
};
#define MEM_PROT_ASSIGN_ID 0x16
+#define MEM_PROTECT_LOCK_ID2 0x0A
+#define MEM_PROTECT_LOCK_ID2_FLAT 0x11
+#define V2_CHUNK_SIZE SZ_1M
+#define FEATURE_ID_CP 12
struct dest_vm_and_perm_info {
u32 vm;
@@ -42,6 +58,134 @@
static void *qcom_secure_mem;
#define QCOM_SECURE_MEM_SIZE (512*1024)
+static int secure_buffer_change_chunk(u32 chunks,
+ u32 nchunks,
+ u32 chunk_size,
+ int lock)
+{
+ struct cp2_lock_req request;
+ u32 resp;
+ int ret;
+ struct scm_desc desc = {0};
+
+ desc.args[0] = request.chunks.chunk_list = chunks;
+ desc.args[1] = request.chunks.chunk_list_size = nchunks;
+ desc.args[2] = request.chunks.chunk_size = chunk_size;
+ /* Usage is now always 0 */
+ desc.args[3] = request.mem_usage = 0;
+ desc.args[4] = request.lock = lock;
+ desc.args[5] = 0;
+ desc.arginfo = SCM_ARGS(6, SCM_RW, SCM_VAL, SCM_VAL, SCM_VAL, SCM_VAL,
+ SCM_VAL);
+
+ kmap_flush_unused();
+ kmap_atomic_flush_unused();
+
+ if (!is_scm_armv8()) {
+ ret = scm_call(SCM_SVC_MP, MEM_PROTECT_LOCK_ID2,
+ &request, sizeof(request), &resp, sizeof(resp));
+ } else {
+ ret = scm_call2(SCM_SIP_FNID(SCM_SVC_MP,
+ MEM_PROTECT_LOCK_ID2_FLAT), &desc);
+ resp = desc.ret[0];
+ }
+
+ return ret;
+}
+
+static int secure_buffer_change_table(struct sg_table *table, int lock)
+{
+ int i, j;
+ int ret = -EINVAL;
+ u32 *chunk_list;
+ struct scatterlist *sg;
+
+ for_each_sg(table->sgl, sg, table->nents, i) {
+ int nchunks;
+ int size = sg->length;
+ int chunk_list_len;
+ phys_addr_t chunk_list_phys;
+
+ /*
+ * This should theoretically be a phys_addr_t but the protocol
+ * indicates this should be a u32.
+ */
+ u32 base;
+ u64 tmp = sg_dma_address(sg);
+
+ WARN((tmp >> 32) & 0xffffffff,
+ "%s: there are ones in the upper 32 bits of the sg at %p! They will be truncated! Address: 0x%llx\n",
+ __func__, sg, tmp);
+ if (unlikely(!size || (size % V2_CHUNK_SIZE))) {
+ WARN(1,
+ "%s: chunk %d has invalid size: 0x%x. Must be a multiple of 0x%x\n",
+ __func__, i, size, V2_CHUNK_SIZE);
+ return -EINVAL;
+ }
+
+ base = (u32)tmp;
+
+ nchunks = size / V2_CHUNK_SIZE;
+ chunk_list_len = sizeof(u32)*nchunks;
+
+ chunk_list = kzalloc(chunk_list_len, GFP_KERNEL);
+
+ if (!chunk_list)
+ return -ENOMEM;
+
+ chunk_list_phys = virt_to_phys(chunk_list);
+ for (j = 0; j < nchunks; j++)
+ chunk_list[j] = base + j * V2_CHUNK_SIZE;
+
+ /*
+ * Flush the chunk list before sending the memory to the
+ * secure environment to ensure the data is actually present
+ * in RAM
+ */
+ dmac_flush_range(chunk_list, chunk_list + chunk_list_len);
+
+ ret = secure_buffer_change_chunk(chunk_list_phys,
+ nchunks, V2_CHUNK_SIZE, lock);
+
+ if (!ret) {
+ /*
+ * Set or clear the private page flag to communicate the
+ * status of the chunk to other entities
+ */
+ if (lock)
+ SetPagePrivate(sg_page(sg));
+ else
+ ClearPagePrivate(sg_page(sg));
+ }
+
+ kfree(chunk_list);
+ }
+
+ return ret;
+}
+
+int msm_secure_table(struct sg_table *table)
+{
+ int ret;
+
+ mutex_lock(&secure_buffer_mutex);
+ ret = secure_buffer_change_table(table, 1);
+ mutex_unlock(&secure_buffer_mutex);
+
+ return ret;
+}
+
+int msm_unsecure_table(struct sg_table *table)
+{
+ int ret;
+
+ mutex_lock(&secure_buffer_mutex);
+ ret = secure_buffer_change_table(table, 0);
+ mutex_unlock(&secure_buffer_mutex);
+
+ return ret;
+}
+
static struct dest_vm_and_perm_info *
populate_dest_info(int *dest_vmids, int nelements, int *dest_perms,
size_t *size_in_bytes)
@@ -279,6 +423,19 @@
}
}
+#define MAKE_CP_VERSION(major, minor, patch) \
+ (((major & 0x3FF) << 22) | ((minor & 0x3FF) << 12) | (patch & 0xFFF))
+
+bool msm_secure_v2_is_supported(void)
+{
+ /*
+ * if the version is < 1.1.0 then dynamic buffer allocation is
+ * not supported
+ */
+ return (scm_get_feat_version(FEATURE_ID_CP) >=
+ MAKE_CP_VERSION(1, 1, 0));
+}
+
static int __init alloc_secure_shared_memory(void)
{
int ret = 0;
diff --git a/drivers/soc/qcom/spcom.c b/drivers/soc/qcom/spcom.c
index 876e176..9ff0c73 100644
--- a/drivers/soc/qcom/spcom.c
+++ b/drivers/soc/qcom/spcom.c
@@ -840,6 +840,8 @@
ch->glink_state = GLINK_LOCAL_DISCONNECTED;
ch->txn_id = INITIAL_TXN_ID; /* use non-zero nonce for debug */
ch->pid = 0;
+ ch->actual_rx_size = 0;
+ ch->glink_rx_buf = NULL;
pr_debug("Channel closed [%s].\n", ch->name);
@@ -940,8 +942,8 @@
/* check for already pending data */
if (ch->actual_rx_size) {
- pr_debug("already pending data size [%zu]\n",
- ch->actual_rx_size);
+ pr_debug("already pending data size [%zu] ch [%s]\n",
+ ch->actual_rx_size, ch->name);
goto copy_buf;
}
@@ -949,24 +951,27 @@
reinit_completion(&ch->rx_done);
/* Wait for Rx response */
- pr_debug("Wait for Rx done.\n");
+ pr_debug("Wait for Rx done, ch [%s].\n", ch->name);
if (timeout_msec)
timeleft = wait_for_completion_timeout(&ch->rx_done, jiffies);
else
wait_for_completion(&ch->rx_done);
if (timeleft == 0) {
- pr_err("rx_done timeout [%d] msec expired.\n", timeout_msec);
+ pr_err("rx_done timeout [%d] msec expired, ch [%s]\n",
+ timeout_msec, ch->name);
mutex_unlock(&ch->lock);
return -ETIMEDOUT;
} else if (ch->rx_abort) {
mutex_unlock(&ch->lock);
- pr_err("rx_abort, probably remote side reset (SSR).\n");
+ pr_err("rx_abort, probably remote side reset (SSR), ch [%s].\n",
+ ch->name);
return -ERESTART; /* probably SSR */
} else if (ch->actual_rx_size) {
- pr_debug("actual_rx_size is [%zu]\n", ch->actual_rx_size);
+ pr_debug("actual_rx_size is [%zu], ch [%s]\n",
+ ch->actual_rx_size, ch->name);
} else {
- pr_err("actual_rx_size is zero.\n");
+ pr_err("actual_rx_size is zero, ch [%s].\n", ch->name);
goto exit_err;
}
@@ -980,7 +985,7 @@
size = min_t(int, ch->actual_rx_size, size);
memcpy(buf, ch->glink_rx_buf, size);
- pr_debug("copy size [%d].\n", (int) size);
+ pr_debug("copy size [%d] , ch [%s].\n", (int) size, ch->name);
/* free glink buffer after copy to spcom buffer */
glink_rx_done(ch->glink_handle, ch->glink_rx_buf, false);
@@ -993,7 +998,8 @@
pr_err("glink_queue_rx_intent() failed, ret [%d]", ret);
goto exit_err;
} else {
- pr_debug("queue rx_buf, size [%zu]\n", ch->rx_buf_size);
+ pr_debug("queue rx_buf, size [%zu], ch [%s]\n",
+ ch->rx_buf_size, ch->name);
}
mutex_unlock(&ch->lock);
diff --git a/drivers/soc/qcom/subsys-pil-tz.c b/drivers/soc/qcom/subsys-pil-tz.c
index d3819b6..4b686e6 100644
--- a/drivers/soc/qcom/subsys-pil-tz.c
+++ b/drivers/soc/qcom/subsys-pil-tz.c
@@ -1065,7 +1065,8 @@
{
struct pil_tz_data *d;
struct resource *res;
- u32 proxy_timeout;
+ struct device_node *crypto_node;
+ u32 proxy_timeout, crypto_id;
int len, rc;
d = devm_kzalloc(&pdev->dev, sizeof(*d), GFP_KERNEL);
@@ -1128,7 +1129,17 @@
rc);
return rc;
}
- scm_pas_init(MSM_BUS_MASTER_CRYPTO_CORE_0);
+
+ crypto_id = MSM_BUS_MASTER_CRYPTO_CORE_0;
+ crypto_node = of_parse_phandle(pdev->dev.of_node,
+ "qcom,mas-crypto", 0);
+ if (!IS_ERR_OR_NULL(crypto_node)) {
+ of_property_read_u32(crypto_node, "cell-id",
+ &crypto_id);
+ of_node_put(crypto_node);
+ }
+
+ scm_pas_init((int)crypto_id);
}
rc = pil_desc_init(&d->desc);
diff --git a/drivers/staging/android/ion/Makefile b/drivers/staging/android/ion/Makefile
index 309b9cc..f3c8d51 100644
--- a/drivers/staging/android/ion/Makefile
+++ b/drivers/staging/android/ion/Makefile
@@ -1,6 +1,8 @@
obj-$(CONFIG_ION) += ion.o ion_heap.o ion_page_pool.o ion_system_heap.o \
- ion_carveout_heap.o ion_chunk_heap.o ion_cma_heap.o \
- ion_system_secure_heap.o
+ ion_carveout_heap.o ion_chunk_heap.o ion_system_secure_heap.o
+ifdef CONFIG_ION_MSM
+obj-$(CONFIG_CMA) += ion_cma_heap.o ion_cma_secure_heap.o
+endif
obj-$(CONFIG_ION_TEST) += ion_test.o
ifdef CONFIG_COMPAT
obj-$(CONFIG_ION) += compat_ion.o
diff --git a/drivers/staging/android/ion/ion_cma_secure_heap.c b/drivers/staging/android/ion/ion_cma_secure_heap.c
new file mode 100644
index 0000000..b2eac28
--- /dev/null
+++ b/drivers/staging/android/ion/ion_cma_secure_heap.c
@@ -0,0 +1,902 @@
+/*
+ * drivers/staging/android/ion/ion_cma_secure_heap.c
+ *
+ * Copyright (C) Linaro 2012
+ * Author: <benjamin.gaignard@linaro.org> for ST-Ericsson.
+ * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#include <linux/device.h>
+#include <linux/ion.h>
+#include <linux/slab.h>
+#include <linux/errno.h>
+#include <linux/err.h>
+#include <linux/dma-mapping.h>
+#include <linux/msm_ion.h>
+#include <trace/events/kmem.h>
+
+#include <soc/qcom/secure_buffer.h>
+#include <asm/cacheflush.h>
+
+/* for ion_heap_ops structure */
+#include "ion_priv.h"
+
+#define ION_CMA_ALLOCATE_FAILED NULL
+
+struct ion_secure_cma_non_contig_info {
+ dma_addr_t phys;
+ int len;
+ struct list_head entry;
+};
+
+struct ion_secure_cma_buffer_info {
+ dma_addr_t phys;
+ struct sg_table *table;
+ bool is_cached;
+ int len;
+ struct list_head non_contig_list;
+ unsigned long ncelems;
+};
+
+struct ion_cma_alloc_chunk {
+ void *cpu_addr;
+ struct list_head entry;
+ dma_addr_t handle;
+ unsigned long chunk_size;
+ atomic_t cnt;
+};
+
+struct ion_cma_secure_heap {
+ struct device *dev;
+ /*
+ * Protects against races between threads allocating memory/adding to
+ * pool at the same time. (e.g. thread 1 adds to pool, thread 2
+ * allocates thread 1's memory before thread 1 knows it needs to
+ * allocate more.
+ * Admittedly this is fairly coarse grained right now but the chance for
+ * contention on this lock is unlikely right now. This can be changed if
+ * this ever changes in the future
+ */
+ struct mutex alloc_lock;
+ /*
+ * protects the list of memory chunks in this pool
+ */
+ struct mutex chunk_lock;
+ struct ion_heap heap;
+ /*
+ * Bitmap for allocation. This contains the aggregate of all chunks.
+ */
+ unsigned long *bitmap;
+ /*
+ * List of all allocated chunks
+ *
+ * This is where things get 'clever'. Individual allocations from
+ * dma_alloc_coherent must be allocated and freed in one chunk.
+ * We don't just want to limit the allocations to those confined
+ * within a single chunk (if clients allocate n small chunks we would
+ * never be able to use the combined size). The bitmap allocator is
+ * used to find the contiguous region and the parts of the chunks are
+ * marked off as used. The chunks won't be freed in the shrinker until
+ * the usage is actually zero.
+ */
+ struct list_head chunks;
+ int npages;
+ ion_phys_addr_t base;
+ struct work_struct work;
+ unsigned long last_alloc;
+ struct shrinker shrinker;
+ atomic_t total_allocated;
+ atomic_t total_pool_size;
+ atomic_t total_leaked;
+ unsigned long heap_size;
+ unsigned long default_prefetch_size;
+};
+
+static void ion_secure_pool_pages(struct work_struct *work);
+
+/*
+ * Create scatter-list for the already allocated DMA buffer.
+ * This function could be replace by dma_common_get_sgtable
+ * as soon as it will avalaible.
+ */
+static int ion_secure_cma_get_sgtable(struct device *dev, struct sg_table *sgt,
+ dma_addr_t handle, size_t size)
+{
+ struct page *page = pfn_to_page(PFN_DOWN(handle));
+ int ret;
+
+ ret = sg_alloc_table(sgt, 1, GFP_KERNEL);
+ if (unlikely(ret))
+ return ret;
+
+ sg_set_page(sgt->sgl, page, PAGE_ALIGN(size), 0);
+ sg_dma_address(sgt->sgl) = handle;
+ return 0;
+}
+
+static int ion_secure_cma_add_to_pool(
+ struct ion_cma_secure_heap *sheap,
+ unsigned long len,
+ bool prefetch)
+{
+ void *cpu_addr;
+ dma_addr_t handle;
+ unsigned long attrs = 0;
+ int ret = 0;
+ struct ion_cma_alloc_chunk *chunk;
+
+ trace_ion_secure_cma_add_to_pool_start(len, atomic_read(&sheap->
+ total_pool_size), prefetch);
+ mutex_lock(&sheap->chunk_lock);
+
+ chunk = kzalloc(sizeof(*chunk), GFP_KERNEL);
+ if (!chunk) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ attrs = DMA_ATTR_NO_KERNEL_MAPPING | DMA_ATTR_SKIP_ZEROING;
+
+ cpu_addr = dma_alloc_attrs(sheap->dev, len, &handle, GFP_KERNEL,
+ attrs);
+
+ if (!cpu_addr) {
+ ret = -ENOMEM;
+ goto out_free;
+ }
+
+ chunk->cpu_addr = cpu_addr;
+ chunk->handle = handle;
+ chunk->chunk_size = len;
+ atomic_set(&chunk->cnt, 0);
+ list_add(&chunk->entry, &sheap->chunks);
+ atomic_add(len, &sheap->total_pool_size);
+ /* clear the bitmap to indicate this region can be allocated from */
+ bitmap_clear(sheap->bitmap, (handle - sheap->base) >> PAGE_SHIFT,
+ len >> PAGE_SHIFT);
+ goto out;
+
+out_free:
+ kfree(chunk);
+out:
+ mutex_unlock(&sheap->chunk_lock);
+
+ trace_ion_secure_cma_add_to_pool_end(len, atomic_read(&sheap->
+ total_pool_size), prefetch);
+
+ return ret;
+}
+
+static void ion_secure_pool_pages(struct work_struct *work)
+{
+ struct ion_cma_secure_heap *sheap = container_of(work,
+ struct ion_cma_secure_heap, work);
+
+ ion_secure_cma_add_to_pool(sheap, sheap->last_alloc, true);
+}
+
+/*
+ * @s1: start of the first region
+ * @l1: length of the first region
+ * @s2: start of the second region
+ * @l2: length of the second region
+ *
+ * Returns the total number of bytes that intersect.
+ *
+ * s1 is the region we are trying to clear so s2 may be subsumed by s1 but the
+ * maximum size to clear should only ever be l1
+ *
+ */
+static unsigned int intersect(unsigned long s1, unsigned long l1,
+ unsigned long s2, unsigned long l2)
+{
+ unsigned long base1 = s1;
+ unsigned long end1 = s1 + l1;
+ unsigned long base2 = s2;
+ unsigned long end2 = s2 + l2;
+
+ /* Case 0: The regions don't overlap at all */
+ if (!(base1 < end2 && base2 < end1))
+ return 0;
+
+ /* Case 1: region 2 is subsumed by region 1 */
+ if (base1 <= base2 && end2 <= end1)
+ return l2;
+
+ /* case 2: region 1 is subsumed by region 2 */
+ if (base2 <= base1 && end1 <= end2)
+ return l1;
+
+ /* case 3: region1 overlaps region2 on the bottom */
+ if (base2 < end1 && base2 > base1)
+ return end1 - base2;
+
+ /* case 4: region 2 overlaps region1 on the bottom */
+ if (base1 < end2 && base1 > base2)
+ return end2 - base1;
+
+ pr_err("Bad math! Did not detect chunks correctly! %lx %lx %lx %lx\n",
+ s1, l1, s2, l2);
+ WARN_ON(1);
+ /* retrun max intersection value, so that it will fail later*/
+ return (unsigned int)(~0);
+}
+
+int ion_secure_cma_prefetch(struct ion_heap *heap, void *data)
+{
+ unsigned long len = (unsigned long)data;
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+ unsigned long diff;
+
+ if ((int)heap->type != ION_HEAP_TYPE_SECURE_DMA)
+ return -EINVAL;
+
+ if (len == 0)
+ len = sheap->default_prefetch_size;
+
+ /*
+ * Only prefetch as much space as there is left in the pool so
+ * check against the current free size of the heap.
+ * This is slightly racy if someone else is allocating at the same
+ * time. CMA has a restricted size for the heap so worst case
+ * the prefetch doesn't work because the allocation fails.
+ */
+ diff = sheap->heap_size - atomic_read(&sheap->total_pool_size);
+
+ if (len > diff)
+ len = diff;
+
+ sheap->last_alloc = len;
+ trace_ion_prefetching(sheap->last_alloc);
+ schedule_work(&sheap->work);
+
+ return 0;
+}
+
+static void bad_math_dump(unsigned long len, int total_overlap,
+ struct ion_cma_secure_heap *sheap,
+ bool alloc, dma_addr_t paddr)
+{
+ struct list_head *entry;
+
+ pr_err("Bad math! expected total was %lx actual was %x\n",
+ len, total_overlap);
+ pr_err("attempted %s address was %pa len %lx\n",
+ alloc ? "allocation" : "free", &paddr, len);
+ pr_err("chunks:\n");
+ list_for_each(entry, &sheap->chunks) {
+ struct ion_cma_alloc_chunk *chunk =
+ container_of(entry,
+ struct ion_cma_alloc_chunk, entry);
+ pr_info("--- pa %pa len %lx\n",
+ &chunk->handle, chunk->chunk_size);
+ }
+ WARN(1, "mismatch in the sizes of secure cma chunks\n");
+}
+
+static int ion_secure_cma_alloc_from_pool(
+ struct ion_cma_secure_heap *sheap,
+ dma_addr_t *phys,
+ unsigned long len)
+{
+ dma_addr_t paddr;
+ unsigned long page_no;
+ int ret = 0;
+ int total_overlap = 0;
+ struct list_head *entry;
+
+ mutex_lock(&sheap->chunk_lock);
+
+ page_no = bitmap_find_next_zero_area(sheap->bitmap,
+ sheap->npages, 0,
+ len >> PAGE_SHIFT, 0);
+ if (page_no >= sheap->npages) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ bitmap_set(sheap->bitmap, page_no, len >> PAGE_SHIFT);
+ paddr = sheap->base + (page_no << PAGE_SHIFT);
+
+ list_for_each(entry, &sheap->chunks) {
+ struct ion_cma_alloc_chunk *chunk = container_of(entry,
+ struct ion_cma_alloc_chunk, entry);
+ int overlap = intersect(chunk->handle,
+ chunk->chunk_size, paddr, len);
+
+ atomic_add(overlap, &chunk->cnt);
+ total_overlap += overlap;
+ }
+
+ if (total_overlap != len) {
+ bad_math_dump(len, total_overlap, sheap, 1, paddr);
+ ret = -EINVAL;
+ goto out;
+ }
+
+ *phys = paddr;
+out:
+ mutex_unlock(&sheap->chunk_lock);
+ return ret;
+}
+
+static void ion_secure_cma_free_chunk(struct ion_cma_secure_heap *sheap,
+ struct ion_cma_alloc_chunk *chunk)
+{
+ unsigned long attrs = 0;
+
+ attrs = DMA_ATTR_NO_KERNEL_MAPPING;
+ /* This region is 'allocated' and not available to allocate from */
+ bitmap_set(sheap->bitmap, (chunk->handle - sheap->base) >> PAGE_SHIFT,
+ chunk->chunk_size >> PAGE_SHIFT);
+ dma_free_attrs(sheap->dev, chunk->chunk_size, chunk->cpu_addr,
+ chunk->handle, attrs);
+ atomic_sub(chunk->chunk_size, &sheap->total_pool_size);
+ list_del(&chunk->entry);
+ kfree(chunk);
+}
+
+static void __ion_secure_cma_shrink_pool(struct ion_cma_secure_heap *sheap,
+ int max_nr)
+{
+ struct list_head *entry, *_n;
+ unsigned long drained_size = 0, skipped_size = 0;
+
+ trace_ion_secure_cma_shrink_pool_start(drained_size, skipped_size);
+
+ list_for_each_safe(entry, _n, &sheap->chunks) {
+ struct ion_cma_alloc_chunk *chunk = container_of(entry,
+ struct ion_cma_alloc_chunk, entry);
+
+ if (max_nr < 0)
+ break;
+
+ if (atomic_read(&chunk->cnt) == 0) {
+ max_nr -= chunk->chunk_size;
+ drained_size += chunk->chunk_size;
+ ion_secure_cma_free_chunk(sheap, chunk);
+ } else {
+ skipped_size += chunk->chunk_size;
+ }
+ }
+
+ trace_ion_secure_cma_shrink_pool_end(drained_size, skipped_size);
+}
+
+int ion_secure_cma_drain_pool(struct ion_heap *heap, void *unused)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+
+ mutex_lock(&sheap->chunk_lock);
+ __ion_secure_cma_shrink_pool(sheap, INT_MAX);
+ mutex_unlock(&sheap->chunk_lock);
+
+ return 0;
+}
+
+static unsigned long ion_secure_cma_shrinker(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct ion_cma_secure_heap *sheap = container_of(shrinker,
+ struct ion_cma_secure_heap, shrinker);
+ int nr_to_scan = sc->nr_to_scan;
+
+ /*
+ * Allocation path may invoke the shrinker. Proceeding any further
+ * would cause a deadlock in several places so don't shrink if that
+ * happens.
+ */
+ if (!mutex_trylock(&sheap->chunk_lock))
+ return -EAGAIN;
+
+ __ion_secure_cma_shrink_pool(sheap, nr_to_scan);
+
+ mutex_unlock(&sheap->chunk_lock);
+
+ return atomic_read(&sheap->total_pool_size);
+}
+
+static unsigned long ion_secure_cma_shrinker_count(struct shrinker *shrinker,
+ struct shrink_control *sc)
+{
+ struct ion_cma_secure_heap *sheap = container_of(shrinker,
+ struct ion_cma_secure_heap, shrinker);
+ return atomic_read(&sheap->total_pool_size);
+}
+
+static void ion_secure_cma_free_from_pool(struct ion_cma_secure_heap *sheap,
+ dma_addr_t handle,
+ unsigned long len)
+{
+ struct list_head *entry, *_n;
+ int total_overlap = 0;
+
+ mutex_lock(&sheap->chunk_lock);
+ bitmap_clear(sheap->bitmap, (handle - sheap->base) >> PAGE_SHIFT,
+ len >> PAGE_SHIFT);
+
+ list_for_each_safe(entry, _n, &sheap->chunks) {
+ struct ion_cma_alloc_chunk *chunk = container_of(entry,
+ struct ion_cma_alloc_chunk, entry);
+ int overlap = intersect(chunk->handle,
+ chunk->chunk_size, handle, len);
+
+ /*
+ * Don't actually free this from the pool list yet, let either
+ * an explicit drain call or the shrinkers take care of the
+ * pool.
+ */
+ atomic_sub_return(overlap, &chunk->cnt);
+ if (atomic_read(&chunk->cnt) < 0) {
+ WARN(1, "Invalid chunk size of %d\n",
+ atomic_read(&chunk->cnt));
+ goto out;
+ }
+
+ total_overlap += overlap;
+ }
+
+ if (atomic_read(&sheap->total_pool_size) < 0) {
+ WARN(1, "total pool size of %d is unexpected\n",
+ atomic_read(&sheap->total_pool_size));
+ goto out;
+ }
+
+ if (total_overlap != len)
+ bad_math_dump(len, total_overlap, sheap, 0, handle);
+out:
+ mutex_unlock(&sheap->chunk_lock);
+}
+
+/* ION CMA heap operations functions */
+static struct ion_secure_cma_buffer_info *__ion_secure_cma_allocate(
+ struct ion_heap *heap, struct ion_buffer *buffer,
+ unsigned long len, unsigned long align,
+ unsigned long flags)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+ struct ion_secure_cma_buffer_info *info;
+ int ret;
+
+ dev_dbg(sheap->dev, "Request buffer allocation len %ld\n", len);
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return ION_CMA_ALLOCATE_FAILED;
+
+ mutex_lock(&sheap->alloc_lock);
+ ret = ion_secure_cma_alloc_from_pool(sheap, &info->phys, len);
+
+ if (ret) {
+retry:
+ ret = ion_secure_cma_add_to_pool(sheap, len, false);
+ if (ret) {
+ mutex_unlock(&sheap->alloc_lock);
+ dev_err(sheap->dev, "Fail to allocate buffer\n");
+ goto err;
+ }
+ ret = ion_secure_cma_alloc_from_pool(sheap, &info->phys, len);
+ if (ret) {
+ /*
+ * Lost the race with the shrinker, try again
+ */
+ goto retry;
+ }
+ }
+ mutex_unlock(&sheap->alloc_lock);
+
+ atomic_add(len, &sheap->total_allocated);
+ info->table = kmalloc(sizeof(*info->table), GFP_KERNEL);
+ if (!info->table) {
+ dev_err(sheap->dev, "Fail to allocate sg table\n");
+ goto err;
+ }
+
+ info->len = len;
+ ion_secure_cma_get_sgtable(sheap->dev,
+ info->table, info->phys, len);
+
+ /* keep this for memory release */
+ buffer->priv_virt = info;
+ dev_dbg(sheap->dev, "Allocate buffer %pK\n", buffer);
+ return info;
+
+err:
+ kfree(info);
+ return ION_CMA_ALLOCATE_FAILED;
+}
+
+static void __ion_secure_cma_free_non_contig(struct ion_cma_secure_heap *sheap,
+ struct ion_secure_cma_buffer_info
+ *info)
+{
+ struct ion_secure_cma_non_contig_info *nc_info, *temp;
+
+ list_for_each_entry_safe(nc_info, temp, &info->non_contig_list, entry) {
+ ion_secure_cma_free_from_pool(sheap, nc_info->phys,
+ nc_info->len);
+ list_del(&nc_info->entry);
+ kfree(nc_info);
+ }
+}
+
+static void __ion_secure_cma_free(struct ion_cma_secure_heap *sheap,
+ struct ion_secure_cma_buffer_info *info,
+ bool release_memory)
+{
+ if (release_memory) {
+ if (info->ncelems)
+ __ion_secure_cma_free_non_contig(sheap, info);
+ else
+ ion_secure_cma_free_from_pool(sheap, info->phys,
+ info->len);
+ }
+ sg_free_table(info->table);
+ kfree(info->table);
+ kfree(info);
+}
+
+static struct ion_secure_cma_buffer_info *__ion_secure_cma_allocate_non_contig(
+ struct ion_heap *heap, struct ion_buffer *buffer,
+ unsigned long len, unsigned long align,
+ unsigned long flags)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+ struct ion_secure_cma_buffer_info *info;
+ int ret;
+ unsigned long alloc_size = len;
+ struct ion_secure_cma_non_contig_info *nc_info, *temp;
+ unsigned long ncelems = 0;
+ struct scatterlist *sg;
+ unsigned long total_allocated = 0;
+
+ dev_dbg(sheap->dev, "Request buffer allocation len %ld\n", len);
+
+ info = kzalloc(sizeof(*info), GFP_KERNEL);
+ if (!info)
+ return ION_CMA_ALLOCATE_FAILED;
+
+ INIT_LIST_HEAD(&info->non_contig_list);
+ info->table = kmalloc(sizeof(*info->table), GFP_KERNEL);
+ if (!info->table) {
+ dev_err(sheap->dev, "Fail to allocate sg table\n");
+ goto err;
+ }
+ mutex_lock(&sheap->alloc_lock);
+ while (total_allocated < len) {
+ if (alloc_size < SZ_1M) {
+ pr_err("Cannot allocate less than 1MB\n");
+ goto err2;
+ }
+ nc_info = kzalloc(sizeof(*nc_info), GFP_KERNEL);
+ if (!nc_info)
+ goto err2;
+
+ ret = ion_secure_cma_alloc_from_pool(sheap, &nc_info->phys,
+ alloc_size);
+ if (ret) {
+retry:
+ ret = ion_secure_cma_add_to_pool(sheap, alloc_size,
+ false);
+ if (ret) {
+ alloc_size = alloc_size / 2;
+ if (!IS_ALIGNED(alloc_size, SZ_1M))
+ alloc_size = round_down(alloc_size,
+ SZ_1M);
+ kfree(nc_info);
+ continue;
+ }
+ ret = ion_secure_cma_alloc_from_pool(sheap,
+ &nc_info->phys,
+ alloc_size);
+ if (ret) {
+ /*
+ * Lost the race with the shrinker, try again
+ */
+ goto retry;
+ }
+ }
+ nc_info->len = alloc_size;
+ list_add_tail(&nc_info->entry, &info->non_contig_list);
+ ncelems++;
+ total_allocated += alloc_size;
+ alloc_size = min(alloc_size, len - total_allocated);
+ }
+ mutex_unlock(&sheap->alloc_lock);
+ atomic_add(total_allocated, &sheap->total_allocated);
+
+ nc_info = list_first_entry_or_null(&info->non_contig_list,
+ struct
+ ion_secure_cma_non_contig_info,
+ entry);
+ if (!nc_info) {
+ pr_err("%s: Unable to find first entry of non contig list\n",
+ __func__);
+ goto err1;
+ }
+ info->phys = nc_info->phys;
+ info->len = total_allocated;
+ info->ncelems = ncelems;
+
+ ret = sg_alloc_table(info->table, ncelems, GFP_KERNEL);
+ if (unlikely(ret))
+ goto err1;
+
+ sg = info->table->sgl;
+ list_for_each_entry(nc_info, &info->non_contig_list, entry) {
+ sg_set_page(sg, phys_to_page(nc_info->phys), nc_info->len, 0);
+ sg_dma_address(sg) = nc_info->phys;
+ sg = sg_next(sg);
+ }
+ buffer->priv_virt = info;
+ dev_dbg(sheap->dev, "Allocate buffer %pK\n", buffer);
+ return info;
+
+err2:
+ mutex_unlock(&sheap->alloc_lock);
+err1:
+ list_for_each_entry_safe(nc_info, temp, &info->non_contig_list,
+ entry) {
+ list_del(&nc_info->entry);
+ kfree(nc_info);
+ }
+ kfree(info->table);
+err:
+ kfree(info);
+ return ION_CMA_ALLOCATE_FAILED;
+}
+
+static int ion_secure_cma_allocate(struct ion_heap *heap,
+ struct ion_buffer *buffer,
+ unsigned long len, unsigned long align,
+ unsigned long flags)
+{
+ unsigned long secure_allocation = flags & ION_FLAG_SECURE;
+ struct ion_secure_cma_buffer_info *buf = NULL;
+ unsigned long allow_non_contig = flags & ION_FLAG_ALLOW_NON_CONTIG;
+
+ if (!secure_allocation &&
+ !ion_heap_allow_secure_allocation(heap->type)) {
+ pr_err("%s: non-secure allocation disallowed from heap %s %lx\n",
+ __func__, heap->name, flags);
+ return -ENOMEM;
+ }
+
+ if (ION_IS_CACHED(flags)) {
+ pr_err("%s: cannot allocate cached memory from secure heap %s\n",
+ __func__, heap->name);
+ return -ENOMEM;
+ }
+
+ if (!IS_ALIGNED(len, SZ_1M)) {
+ pr_err("%s: length of allocation from %s must be a multiple of 1MB\n",
+ __func__, heap->name);
+ return -ENOMEM;
+ }
+ trace_ion_secure_cma_allocate_start(heap->name, len, align, flags);
+ if (!allow_non_contig)
+ buf = __ion_secure_cma_allocate(heap, buffer, len, align,
+ flags);
+ else
+ buf = __ion_secure_cma_allocate_non_contig(heap, buffer, len,
+ align, flags);
+ trace_ion_secure_cma_allocate_end(heap->name, len, align, flags);
+ if (buf) {
+ int ret;
+
+ if (!msm_secure_v2_is_supported()) {
+ pr_err("%s: securing buffers from clients is not supported on this platform\n",
+ __func__);
+ ret = 1;
+ } else {
+ trace_ion_cp_secure_buffer_start(heap->name, len, align,
+ flags);
+ ret = msm_secure_table(buf->table);
+ trace_ion_cp_secure_buffer_end(heap->name, len, align,
+ flags);
+ }
+ if (ret) {
+ struct ion_cma_secure_heap *sheap =
+ container_of(buffer->heap,
+ struct ion_cma_secure_heap, heap);
+
+ pr_err("%s: failed to secure buffer\n", __func__);
+ __ion_secure_cma_free(sheap, buf, true);
+ }
+ return ret;
+ } else {
+ return -ENOMEM;
+ }
+}
+
+static void ion_secure_cma_free(struct ion_buffer *buffer)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(buffer->heap, struct ion_cma_secure_heap, heap);
+ struct ion_secure_cma_buffer_info *info = buffer->priv_virt;
+ int ret = 0;
+
+ dev_dbg(sheap->dev, "Release buffer %pK\n", buffer);
+ if (msm_secure_v2_is_supported())
+ ret = msm_unsecure_table(info->table);
+ atomic_sub(buffer->size, &sheap->total_allocated);
+ if (atomic_read(&sheap->total_allocated) < 0) {
+ WARN(1, "no memory is allocated from this pool\n");
+ return;
+ }
+
+ /* release memory */
+ if (ret) {
+ WARN(1, "Unsecure failed, can't free the memory. Leaking it!");
+ atomic_add(buffer->size, &sheap->total_leaked);
+ }
+
+ __ion_secure_cma_free(sheap, info, ret ? false : true);
+}
+
+static int ion_secure_cma_phys(struct ion_heap *heap, struct ion_buffer *buffer,
+ ion_phys_addr_t *addr, size_t *len)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+ struct ion_secure_cma_buffer_info *info = buffer->priv_virt;
+
+ dev_dbg(sheap->dev, "Return buffer %pK physical address 0x%pa\n",
+ buffer, &info->phys);
+
+ *addr = info->phys;
+ *len = buffer->size;
+
+ return 0;
+}
+
+static struct sg_table *ion_secure_cma_heap_map_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ struct ion_secure_cma_buffer_info *info = buffer->priv_virt;
+
+ return info->table;
+}
+
+static void ion_secure_cma_heap_unmap_dma(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+}
+
+static int ion_secure_cma_mmap(struct ion_heap *mapper,
+ struct ion_buffer *buffer,
+ struct vm_area_struct *vma)
+{
+ pr_info("%s: mmaping from secure heap %s disallowed\n",
+ __func__, mapper->name);
+ return -EINVAL;
+}
+
+static void *ion_secure_cma_map_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+ pr_info("%s: kernel mapping from secure heap %s disallowed\n",
+ __func__, heap->name);
+ return ERR_PTR(-EINVAL);
+}
+
+static void ion_secure_cma_unmap_kernel(struct ion_heap *heap,
+ struct ion_buffer *buffer)
+{
+}
+
+static int ion_secure_cma_print_debug(struct ion_heap *heap, struct seq_file *s,
+ const struct list_head *mem_map)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+
+ if (mem_map) {
+ struct mem_map_data *data;
+
+ seq_puts(s, "\nMemory Map\n");
+ seq_printf(s, "%16.s %14.s %14.s %14.s\n",
+ "client", "start address", "end address",
+ "size");
+
+ list_for_each_entry(data, mem_map, node) {
+ const char *client_name = "(null)";
+
+ if (data->client_name)
+ client_name = data->client_name;
+
+ seq_printf(s, "%16.s 0x%14pa 0x%14pa %14lu (0x%lx)\n",
+ client_name, &data->addr,
+ &data->addr_end,
+ data->size, data->size);
+ }
+ }
+ seq_printf(s, "Total allocated: 0x%x\n",
+ atomic_read(&sheap->total_allocated));
+ seq_printf(s, "Total pool size: 0x%x\n",
+ atomic_read(&sheap->total_pool_size));
+ seq_printf(s, "Total memory leaked due to unlock failures: 0x%x\n",
+ atomic_read(&sheap->total_leaked));
+
+ return 0;
+}
+
+static struct ion_heap_ops ion_secure_cma_ops = {
+ .allocate = ion_secure_cma_allocate,
+ .free = ion_secure_cma_free,
+ .map_dma = ion_secure_cma_heap_map_dma,
+ .unmap_dma = ion_secure_cma_heap_unmap_dma,
+ .phys = ion_secure_cma_phys,
+ .map_user = ion_secure_cma_mmap,
+ .map_kernel = ion_secure_cma_map_kernel,
+ .unmap_kernel = ion_secure_cma_unmap_kernel,
+ .print_debug = ion_secure_cma_print_debug,
+};
+
+struct ion_heap *ion_secure_cma_heap_create(struct ion_platform_heap *data)
+{
+ struct ion_cma_secure_heap *sheap;
+ int map_size = BITS_TO_LONGS(data->size >> PAGE_SHIFT) * sizeof(long);
+
+ sheap = kzalloc(sizeof(*sheap), GFP_KERNEL);
+ if (!sheap)
+ return ERR_PTR(-ENOMEM);
+
+ sheap->dev = data->priv;
+ mutex_init(&sheap->chunk_lock);
+ mutex_init(&sheap->alloc_lock);
+ sheap->heap.ops = &ion_secure_cma_ops;
+ sheap->heap.type = ION_HEAP_TYPE_SECURE_DMA;
+ sheap->npages = data->size >> PAGE_SHIFT;
+ sheap->base = data->base;
+ sheap->heap_size = data->size;
+ sheap->bitmap = kmalloc(map_size, GFP_KERNEL);
+ INIT_LIST_HEAD(&sheap->chunks);
+ INIT_WORK(&sheap->work, ion_secure_pool_pages);
+ sheap->shrinker.seeks = DEFAULT_SEEKS;
+ sheap->shrinker.batch = 0;
+ sheap->shrinker.scan_objects = ion_secure_cma_shrinker;
+ sheap->shrinker.count_objects = ion_secure_cma_shrinker_count;
+ sheap->default_prefetch_size = sheap->heap_size;
+ register_shrinker(&sheap->shrinker);
+
+ if (!sheap->bitmap) {
+ kfree(sheap);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ if (data->extra_data) {
+ struct ion_cma_pdata *extra = data->extra_data;
+
+ sheap->default_prefetch_size = extra->default_prefetch_size;
+ }
+
+ /*
+ * we initially mark everything in the allocator as being free so that
+ * allocations can come in later
+ */
+ bitmap_fill(sheap->bitmap, sheap->npages);
+
+ return &sheap->heap;
+}
+
+void ion_secure_cma_heap_destroy(struct ion_heap *heap)
+{
+ struct ion_cma_secure_heap *sheap =
+ container_of(heap, struct ion_cma_secure_heap, heap);
+
+ kfree(sheap);
+}
diff --git a/drivers/staging/android/ion/ion_priv.h b/drivers/staging/android/ion/ion_priv.h
index bb119cc..775c666 100644
--- a/drivers/staging/android/ion/ion_priv.h
+++ b/drivers/staging/android/ion/ion_priv.h
@@ -26,6 +26,7 @@
#include <linux/rbtree.h>
#include <linux/seq_file.h>
+#include "msm_ion_priv.h"
#include <linux/sched.h>
#include <linux/shrinker.h>
#include <linux/types.h>
diff --git a/drivers/staging/android/ion/msm/msm_ion.c b/drivers/staging/android/ion/msm/msm_ion.c
index 9d53391..3771726 100644
--- a/drivers/staging/android/ion/msm/msm_ion.c
+++ b/drivers/staging/android/ion/msm/msm_ion.c
@@ -404,6 +404,28 @@
ret = -ENOMEM;
break;
}
+ case ION_HEAP_TYPE_SECURE_DMA:
+ {
+ unsigned int val;
+ struct ion_cma_pdata *extra = NULL;
+
+ ret = of_property_read_u32(node,
+ "qcom,default-prefetch-size", &val);
+ if (!ret) {
+ heap->extra_data = kzalloc(sizeof(*extra),
+ GFP_KERNEL);
+
+ if (!heap->extra_data) {
+ ret = -ENOMEM;
+ } else {
+ extra = heap->extra_data;
+ extra->default_prefetch_size = val;
+ }
+ } else {
+ ret = 0;
+ }
+ break;
+ }
default:
heap->extra_data = 0;
break;
@@ -423,6 +445,7 @@
MAKE_HEAP_TYPE_MAPPING(CARVEOUT),
MAKE_HEAP_TYPE_MAPPING(CHUNK),
MAKE_HEAP_TYPE_MAPPING(DMA),
+ MAKE_HEAP_TYPE_MAPPING(SECURE_DMA),
MAKE_HEAP_TYPE_MAPPING(SYSTEM_SECURE),
MAKE_HEAP_TYPE_MAPPING(HYP_CMA),
};
@@ -609,6 +632,16 @@
return type == ((enum ion_heap_type)ION_HEAP_TYPE_SYSTEM_SECURE);
}
+int ion_heap_allow_secure_allocation(enum ion_heap_type type)
+{
+ return type == ((enum ion_heap_type)ION_HEAP_TYPE_SECURE_DMA);
+}
+
+int ion_heap_allow_handle_secure(enum ion_heap_type type)
+{
+ return type == ((enum ion_heap_type)ION_HEAP_TYPE_SECURE_DMA);
+}
+
int ion_heap_allow_heap_secure(enum ion_heap_type type)
{
return false;
@@ -796,6 +829,13 @@
int ret;
ret = ion_walk_heaps(client, data.prefetch_data.heap_id,
+ ION_HEAP_TYPE_SECURE_DMA,
+ (void *)data.prefetch_data.len,
+ ion_secure_cma_prefetch);
+ if (ret)
+ return ret;
+
+ ret = ion_walk_heaps(client, data.prefetch_data.heap_id,
ION_HEAP_TYPE_SYSTEM_SECURE,
(void *)&data.prefetch_data,
ion_system_secure_heap_prefetch);
@@ -806,6 +846,13 @@
case ION_IOC_DRAIN:
{
int ret;
+ ret = ion_walk_heaps(client, data.prefetch_data.heap_id,
+ ION_HEAP_TYPE_SECURE_DMA,
+ (void *)data.prefetch_data.len,
+ ion_secure_cma_drain_pool);
+
+ if (ret)
+ return ret;
ret = ion_walk_heaps(client, data.prefetch_data.heap_id,
ION_HEAP_TYPE_SYSTEM_SECURE,
@@ -959,6 +1006,11 @@
struct ion_heap *heap = NULL;
switch ((int)heap_data->type) {
+#ifdef CONFIG_CMA
+ case ION_HEAP_TYPE_SECURE_DMA:
+ heap = ion_secure_cma_heap_create(heap_data);
+ break;
+#endif
case ION_HEAP_TYPE_SYSTEM_SECURE:
heap = ion_system_secure_heap_create(heap_data);
break;
@@ -988,6 +1040,11 @@
return;
switch ((int)heap->type) {
+#ifdef CONFIG_CMA
+ case ION_HEAP_TYPE_SECURE_DMA:
+ ion_secure_cma_heap_destroy(heap);
+ break;
+#endif
case ION_HEAP_TYPE_SYSTEM_SECURE:
ion_system_secure_heap_destroy(heap);
break;
diff --git a/drivers/staging/android/ion/msm_ion_priv.h b/drivers/staging/android/ion/msm_ion_priv.h
new file mode 100644
index 0000000..bbf2e8b
--- /dev/null
+++ b/drivers/staging/android/ion/msm_ion_priv.h
@@ -0,0 +1,112 @@
+/*
+ * drivers/staging/android/ion/msm_ion_priv.h
+ *
+ * Copyright (C) 2011 Google, Inc.
+ * Copyright (c) 2013-2017, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * 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 _MSM_ION_PRIV_H
+#define _MSM_ION_PRIV_H
+
+#include <linux/kref.h>
+#include <linux/mm_types.h>
+#include <linux/mutex.h>
+#include <linux/types.h>
+#include <linux/ion.h>
+#include <linux/iommu.h>
+#include <linux/seq_file.h>
+
+struct ion_heap *ion_iommu_heap_create(struct ion_platform_heap *heap);
+void ion_iommu_heap_destroy(struct ion_heap *heap);
+
+struct ion_heap *ion_cp_heap_create(struct ion_platform_heap *heap);
+void ion_cp_heap_destroy(struct ion_heap *heap);
+
+struct ion_heap *ion_system_secure_heap_create(struct ion_platform_heap *heap);
+void ion_system_secure_heap_destroy(struct ion_heap *heap);
+int ion_system_secure_heap_prefetch(struct ion_heap *heap, void *data);
+int ion_system_secure_heap_drain(struct ion_heap *heap, void *data);
+
+struct ion_heap *ion_cma_secure_heap_create(struct ion_platform_heap *heap);
+void ion_cma_secure_heap_destroy(struct ion_heap *heap);
+
+long msm_ion_custom_ioctl(struct ion_client *client,
+ unsigned int cmd,
+ unsigned long arg);
+
+#ifdef CONFIG_CMA
+struct ion_heap *ion_secure_cma_heap_create(struct ion_platform_heap *heap);
+void ion_secure_cma_heap_destroy(struct ion_heap *heap);
+
+int ion_secure_cma_prefetch(struct ion_heap *heap, void *data);
+
+int ion_secure_cma_drain_pool(struct ion_heap *heap, void *unused);
+
+#else
+static inline int ion_secure_cma_prefetch(struct ion_heap *heap, void *data)
+{
+ return -ENODEV;
+}
+
+static inline int ion_secure_cma_drain_pool(struct ion_heap *heap, void *unused)
+{
+ return -ENODEV;
+}
+
+#endif
+
+struct ion_heap *ion_removed_heap_create(struct ion_platform_heap *pheap);
+void ion_removed_heap_destroy(struct ion_heap *heap);
+
+#define ION_CP_ALLOCATE_FAIL -1
+#define ION_RESERVED_ALLOCATE_FAIL -1
+
+void ion_cp_heap_get_base(struct ion_heap *heap, unsigned long *base,
+ unsigned long *size);
+
+void ion_mem_map_show(struct ion_heap *heap);
+
+int ion_heap_is_system_secure_heap_type(enum ion_heap_type type);
+
+int ion_heap_allow_secure_allocation(enum ion_heap_type type);
+
+int ion_heap_allow_heap_secure(enum ion_heap_type type);
+
+int ion_heap_allow_handle_secure(enum ion_heap_type type);
+
+int get_secure_vmid(unsigned long flags);
+
+bool is_secure_vmid_valid(int vmid);
+
+/**
+ * Functions to help assign/unassign sg_table for System Secure Heap
+ */
+
+int ion_system_secure_heap_unassign_sg(struct sg_table *sgt, int source_vmid);
+int ion_system_secure_heap_assign_sg(struct sg_table *sgt, int dest_vmid);
+
+/**
+ * ion_create_chunked_sg_table - helper function to create sg table
+ * with specified chunk size
+ * @buffer_base: The starting address used for the sg dma address
+ * @chunk_size: The size of each entry in the sg table
+ * @total_size: The total size of the sg table (i.e. the sum of the
+ * entries). This will be rounded up to the nearest
+ * multiple of `chunk_size'
+ */
+struct sg_table *ion_create_chunked_sg_table(phys_addr_t buffer_base,
+ size_t chunk_size,
+ size_t total_size);
+
+void show_ion_usage(struct ion_device *dev);
+#endif /* _MSM_ION_PRIV_H */
diff --git a/drivers/staging/android/uapi/msm_ion.h b/drivers/staging/android/uapi/msm_ion.h
index 4f9dd73..d510fda 100644
--- a/drivers/staging/android/uapi/msm_ion.h
+++ b/drivers/staging/android/uapi/msm_ion.h
@@ -92,6 +92,11 @@
#define ION_FLAG_CP_CAMERA_PREVIEW ION_BIT(27)
#define ION_FLAG_CP_SPSS_HLOS_SHARED ION_BIT(30)
+/**
+ * Flag to allow non continguous allocation of memory from secure
+ * heap
+ */
+#define ION_FLAG_ALLOW_NON_CONTIG ION_BIT(28)
/**
* Flag to use when allocating to indicate that a heap is secure.
diff --git a/drivers/usb/phy/Kconfig b/drivers/usb/phy/Kconfig
index 17e8edb..f76d347 100644
--- a/drivers/usb/phy/Kconfig
+++ b/drivers/usb/phy/Kconfig
@@ -253,4 +253,17 @@
the high-speed PHY which is usually paired with either the ChipIdea or
Synopsys DWC3 USB IPs on MSM SOCs. This driver expects to configure the
PHY with a dedicated register I/O memory region.
+
+config USB_MSM_OTG
+ tristate "Qualcomm on-chip USB OTG controller support"
+ depends on (USB || USB_GADGET) && (ARCH_QCOM || COMPILE_TEST)
+ select USB_PHY
+ help
+ Enable this to support the USB OTG transceiver on Qualcomm chips. It
+ handles PHY initialization, clock management, and workarounds
+ required after resetting the hardware and power management.
+ This driver is required even for peripheral only or host only
+ mode configurations.
+ This driver is not supported on boards like trout which
+ has an external PHY.
endmenu
diff --git a/drivers/usb/phy/Makefile b/drivers/usb/phy/Makefile
index 285659d..7e9ffa0 100644
--- a/drivers/usb/phy/Makefile
+++ b/drivers/usb/phy/Makefile
@@ -31,3 +31,4 @@
obj-$(CONFIG_USB_MSM_SSPHY_QMP) += phy-msm-ssusb-qmp.o
obj-$(CONFIG_MSM_QUSB_PHY) += phy-msm-qusb.o phy-msm-qusb-v2.o
obj-$(CONFIG_MSM_HSUSB_PHY) += phy-msm-snps-hs.o
+obj-$(CONFIG_USB_MSM_OTG) += phy-msm-usb.o
diff --git a/drivers/usb/phy/phy-msm-usb.c b/drivers/usb/phy/phy-msm-usb.c
new file mode 100644
index 0000000..6170656
--- /dev/null
+++ b/drivers/usb/phy/phy-msm-usb.c
@@ -0,0 +1,5471 @@
+/* Copyright (c) 2009-2017, 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/device.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/err.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/ioport.h>
+#include <linux/gpio.h>
+#include <linux/of_gpio.h>
+#include <linux/of_platform.h>
+#include <linux/uaccess.h>
+#include <linux/debugfs.h>
+#include <linux/seq_file.h>
+#include <linux/pm_runtime.h>
+#include <linux/suspend.h>
+#include <linux/of.h>
+#include <linux/dma-mapping.h>
+#include <linux/pinctrl/consumer.h>
+#include <linux/irqchip/msm-mpm-irq.h>
+#include <linux/pm_wakeup.h>
+#include <linux/reset.h>
+#include <linux/extcon.h>
+#include <soc/qcom/scm.h>
+
+#include <linux/usb.h>
+#include <linux/usb/otg.h>
+#include <linux/usb/ulpi.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/hcd.h>
+#include <linux/usb/msm_hsusb.h>
+#include <linux/usb/msm_hsusb_hw.h>
+#include <linux/regulator/consumer.h>
+#include <linux/regulator/driver.h>
+#include <linux/regulator/machine.h>
+#include <linux/qpnp/qpnp-adc.h>
+
+#include <linux/msm-bus.h>
+
+/**
+ * Requested USB votes for BUS bandwidth
+ *
+ * USB_NO_PERF_VOTE BUS Vote for inactive USB session or disconnect
+ * USB_MAX_PERF_VOTE Maximum BUS bandwidth vote
+ * USB_MIN_PERF_VOTE Minimum BUS bandwidth vote (for some hw same as NO_PERF)
+ *
+ */
+enum usb_bus_vote {
+ USB_NO_PERF_VOTE = 0,
+ USB_MAX_PERF_VOTE,
+ USB_MIN_PERF_VOTE,
+};
+
+/**
+ * Supported USB modes
+ *
+ * USB_PERIPHERAL Only peripheral mode is supported.
+ * USB_HOST Only host mode is supported.
+ * USB_OTG OTG mode is supported.
+ *
+ */
+enum usb_mode_type {
+ USB_NONE = 0,
+ USB_PERIPHERAL,
+ USB_HOST,
+ USB_OTG,
+};
+
+/**
+ * OTG control
+ *
+ * OTG_NO_CONTROL Id/VBUS notifications not required. Useful in host
+ * only configuration.
+ * OTG_PHY_CONTROL Id/VBUS notifications comes form USB PHY.
+ * OTG_PMIC_CONTROL Id/VBUS notifications comes from PMIC hardware.
+ * OTG_USER_CONTROL Id/VBUS notifcations comes from User via sysfs.
+ *
+ */
+enum otg_control_type {
+ OTG_NO_CONTROL = 0,
+ OTG_PHY_CONTROL,
+ OTG_PMIC_CONTROL,
+ OTG_USER_CONTROL,
+};
+
+/**
+ * PHY used in
+ *
+ * INVALID_PHY Unsupported PHY
+ * CI_PHY Chipidea PHY
+ * SNPS_PICO_PHY Synopsis Pico PHY
+ * SNPS_FEMTO_PHY Synopsis Femto PHY
+ * QUSB_ULPI_PHY
+ *
+ */
+enum msm_usb_phy_type {
+ INVALID_PHY = 0,
+ CI_PHY, /* not supported */
+ SNPS_PICO_PHY,
+ SNPS_FEMTO_PHY,
+ QUSB_ULPI_PHY,
+};
+
+#define IDEV_CHG_MAX 1500
+#define IUNIT 100
+#define IDEV_HVDCP_CHG_MAX 1800
+
+/**
+ * Used different VDDCX voltage values
+ */
+enum usb_vdd_value {
+ VDD_NONE = 0,
+ VDD_MIN,
+ VDD_MAX,
+ VDD_VAL_MAX,
+};
+
+/**
+ * struct msm_otg_platform_data - platform device data
+ * for msm_otg driver.
+ * @phy_init_seq: PHY configuration sequence values. Value of -1 is reserved as
+ * "do not overwrite default value at this address".
+ * @vbus_power: VBUS power on/off routine.It should return result
+ * as success(zero value) or failure(non-zero value).
+ * @power_budget: VBUS power budget in mA (0 will be treated as 500mA).
+ * @mode: Supported mode (OTG/peripheral/host).
+ * @otg_control: OTG switch controlled by user/Id pin
+ * @default_mode: Default operational mode. Applicable only if
+ * OTG switch is controller by user.
+ * @pmic_id_irq: IRQ number assigned for PMIC USB ID line.
+ * @mpm_otgsessvld_int: MPM wakeup pin assigned for OTG SESSVLD
+ * interrupt. Used when .otg_control == OTG_PHY_CONTROL.
+ * @mpm_dpshv_int: MPM wakeup pin assigned for DP SHV interrupt.
+ * Used during host bus suspend.
+ * @mpm_dmshv_int: MPM wakeup pin assigned for DM SHV interrupt.
+ * Used during host bus suspend.
+ * @disable_reset_on_disconnect: perform USB PHY and LINK reset
+ * on USB cable disconnection.
+ * @pnoc_errata_fix: workaround needed for PNOC hardware bug that
+ * affects USB performance.
+ * @enable_lpm_on_suspend: Enable the USB core to go into Low
+ * Power Mode, when USB bus is suspended but cable
+ * is connected.
+ * @core_clk_always_on_workaround: Don't disable core_clk when
+ * USB enters LPM.
+ * @delay_lpm_on_disconnect: Use a delay before entering LPM
+ * upon USB cable disconnection.
+ * @enable_sec_phy: Use second HSPHY with USB2 core
+ * @bus_scale_table: parameters for bus bandwidth requirements
+ * @log2_itc: value of 2^(log2_itc-1) will be used as the
+ * interrupt threshold (ITC), when log2_itc is
+ * between 1 to 7.
+ * @l1_supported: enable link power management support.
+ * @dpdm_pulldown_added: Indicates whether pull down resistors are
+ * connected on data lines or not.
+ * @vddmin_gpio: dedictaed gpio in the platform that is used for
+ * pullup the D+ line in case of bus suspend with
+ * phy retention.
+ * @enable_ahb2ahb_bypass: Indicates whether enable AHB2AHB BYPASS
+ * mode with controller in device mode.
+ * @bool disable_retention_with_vdd_min: Indicates whether to enable
+ allowing VDDmin without putting PHY into retention.
+ * @bool enable_phy_id_pullup: Indicates whether phy id pullup is
+ enabled or not.
+ * @usb_id_gpio: Gpio used for USB ID detection.
+ * @hub_reset_gpio: Gpio used for hub reset.
+ * @switch_sel_gpio: Gpio used for controlling switch that
+ routing D+/D- from the USB HUB to the USB jack type B
+ for peripheral mode.
+ * @bool phy_dvdd_always_on: PHY DVDD is supplied by always on PMIC LDO.
+ * @bool emulation: Indicates whether we are running on emulation platform.
+ * @bool enable_streaming: Indicates whether streaming to be enabled by default.
+ * @bool enable_axi_prefetch: Indicates whether AXI Prefetch interface is used
+ for improving data performance.
+ * @bool enable_sdp_typec_current_limit: Indicates whether type-c current for
+ sdp charger to be limited.
+ * @usbeth_reset_gpio: Gpio used for external usb-to-eth reset.
+ */
+struct msm_otg_platform_data {
+ int *phy_init_seq;
+ int phy_init_sz;
+ int (*vbus_power)(bool on);
+ unsigned int power_budget;
+ enum usb_mode_type mode;
+ enum otg_control_type otg_control;
+ enum usb_mode_type default_mode;
+ enum msm_usb_phy_type phy_type;
+ int pmic_id_irq;
+ unsigned int mpm_otgsessvld_int;
+ unsigned int mpm_dpshv_int;
+ unsigned int mpm_dmshv_int;
+ bool disable_reset_on_disconnect;
+ bool pnoc_errata_fix;
+ bool enable_lpm_on_dev_suspend;
+ bool core_clk_always_on_workaround;
+ bool delay_lpm_on_disconnect;
+ bool dp_manual_pullup;
+ bool enable_sec_phy;
+ struct msm_bus_scale_pdata *bus_scale_table;
+ int log2_itc;
+ bool l1_supported;
+ bool dpdm_pulldown_added;
+ int vddmin_gpio;
+ bool enable_ahb2ahb_bypass;
+ bool disable_retention_with_vdd_min;
+ bool enable_phy_id_pullup;
+ int usb_id_gpio;
+ int hub_reset_gpio;
+ int usbeth_reset_gpio;
+ int switch_sel_gpio;
+ bool phy_dvdd_always_on;
+ bool emulation;
+ bool enable_streaming;
+ bool enable_axi_prefetch;
+ bool enable_sdp_typec_current_limit;
+ bool vbus_low_as_hostmode;
+};
+
+#define USB_CHG_BLOCK_ULPI 1
+
+#define USB_REQUEST_5V 1
+#define USB_REQUEST_9V 2
+/**
+ * struct msm_usb_chg_info - MSM USB charger block details.
+ * @chg_block_type: The type of charger block. QSCRATCH/ULPI.
+ * @page_offset: USB charger register base may not be aligned to
+ * PAGE_SIZE. The kernel driver aligns the base
+ * address and use it for memory mapping. This
+ * page_offset is used by user space to calaculate
+ * the corret charger register base address.
+ * @length: The length of the charger register address space.
+ */
+struct msm_usb_chg_info {
+ uint32_t chg_block_type;
+ __kernel_off_t page_offset;
+ size_t length;
+};
+
+/* Get the MSM USB charger block information */
+#define MSM_USB_EXT_CHG_INFO _IOW('M', 0, struct msm_usb_chg_info)
+
+/* Vote against USB hardware low power mode */
+#define MSM_USB_EXT_CHG_BLOCK_LPM _IOW('M', 1, int)
+
+/* To tell kernel about voltage being voted */
+#define MSM_USB_EXT_CHG_VOLTAGE_INFO _IOW('M', 2, int)
+
+/* To tell kernel about voltage request result */
+#define MSM_USB_EXT_CHG_RESULT _IOW('M', 3, int)
+
+/* To tell kernel whether charger connected is external charger or not */
+#define MSM_USB_EXT_CHG_TYPE _IOW('M', 4, int)
+
+#define MSM_USB_BASE (motg->regs)
+#define MSM_USB_PHY_CSR_BASE (motg->phy_csr_regs)
+
+#define DRIVER_NAME "msm_otg"
+
+#define CHG_RECHECK_DELAY (jiffies + msecs_to_jiffies(2000))
+#define ULPI_IO_TIMEOUT_USEC (10 * 1000)
+#define USB_PHY_3P3_VOL_MIN 3050000 /* uV */
+#define USB_PHY_3P3_VOL_MAX 3300000 /* uV */
+#define USB_PHY_3P3_HPM_LOAD 50000 /* uA */
+#define USB_PHY_3P3_LPM_LOAD 4000 /* uA */
+
+#define USB_PHY_1P8_VOL_MIN 1800000 /* uV */
+#define USB_PHY_1P8_VOL_MAX 1800000 /* uV */
+#define USB_PHY_1P8_HPM_LOAD 50000 /* uA */
+#define USB_PHY_1P8_LPM_LOAD 4000 /* uA */
+
+#define USB_PHY_VDD_DIG_VOL_NONE 0 /*uV */
+#define USB_PHY_VDD_DIG_VOL_MIN 1045000 /* uV */
+#define USB_PHY_VDD_DIG_VOL_MAX 1320000 /* uV */
+
+#define USB_SUSPEND_DELAY_TIME (500 * HZ/1000) /* 500 msec */
+
+#define USB_DEFAULT_SYSTEM_CLOCK 80000000 /* 80 MHz */
+
+#define PM_QOS_SAMPLE_SEC 2
+#define PM_QOS_THRESHOLD 400
+
+#define MICRO_5V 5000000
+#define MICRO_9V 9000000
+
+#define SDP_CURRENT_UA 500000
+#define CDP_CURRENT_UA 1500000
+#define DCP_CURRENT_UA 1500000
+#define HVDCP_CURRENT_UA 3000000
+
+enum msm_otg_phy_reg_mode {
+ USB_PHY_REG_OFF,
+ USB_PHY_REG_ON,
+ USB_PHY_REG_LPM_ON,
+ USB_PHY_REG_LPM_OFF,
+ USB_PHY_REG_3P3_ON,
+ USB_PHY_REG_3P3_OFF,
+};
+
+static char *override_phy_init;
+module_param(override_phy_init, charp, 0644);
+MODULE_PARM_DESC(override_phy_init,
+ "Override HSUSB PHY Init Settings");
+
+unsigned int lpm_disconnect_thresh = 1000;
+module_param(lpm_disconnect_thresh, uint, 0644);
+MODULE_PARM_DESC(lpm_disconnect_thresh,
+ "Delay before entering LPM on USB disconnect");
+
+static bool floated_charger_enable;
+module_param(floated_charger_enable, bool, 0644);
+MODULE_PARM_DESC(floated_charger_enable,
+ "Whether to enable floated charger");
+
+/* by default debugging is enabled */
+static unsigned int enable_dbg_log = 1;
+module_param(enable_dbg_log, uint, 0644);
+MODULE_PARM_DESC(enable_dbg_log, "Debug buffer events");
+
+/* Max current to be drawn for HVDCP charger */
+static int hvdcp_max_current = IDEV_HVDCP_CHG_MAX;
+module_param(hvdcp_max_current, int, 0644);
+MODULE_PARM_DESC(hvdcp_max_current, "max current drawn for HVDCP charger");
+
+/* Max current to be drawn for DCP charger */
+static int dcp_max_current = IDEV_CHG_MAX;
+module_param(dcp_max_current, int, 0644);
+MODULE_PARM_DESC(dcp_max_current, "max current drawn for DCP charger");
+
+static DECLARE_COMPLETION(pmic_vbus_init);
+static struct msm_otg *the_msm_otg;
+static bool debug_bus_voting_enabled;
+
+static struct regulator *hsusb_3p3;
+static struct regulator *hsusb_1p8;
+static struct regulator *hsusb_vdd;
+static struct regulator *vbus_otg;
+static struct power_supply *psy;
+
+static int vdd_val[VDD_VAL_MAX];
+static u32 bus_freqs[USB_NOC_NUM_VOTE][USB_NUM_BUS_CLOCKS] /*bimc,snoc,pcnoc*/;
+static char bus_clkname[USB_NUM_BUS_CLOCKS][20] = {"bimc_clk", "snoc_clk",
+ "pcnoc_clk"};
+static bool bus_clk_rate_set;
+
+static void dbg_inc(unsigned int *idx)
+{
+ *idx = (*idx + 1) & (DEBUG_MAX_MSG-1);
+}
+
+static void
+msm_otg_dbg_log_event(struct usb_phy *phy, char *event, int d1, int d2)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+ unsigned long flags;
+ unsigned long long t;
+ unsigned long nanosec;
+
+ if (!enable_dbg_log)
+ return;
+
+ write_lock_irqsave(&motg->dbg_lock, flags);
+ t = cpu_clock(smp_processor_id());
+ nanosec = do_div(t, 1000000000)/1000;
+ scnprintf(motg->buf[motg->dbg_idx], DEBUG_MSG_LEN,
+ "[%5lu.%06lu]: %s :%d:%d",
+ (unsigned long)t, nanosec, event, d1, d2);
+
+ motg->dbg_idx++;
+ motg->dbg_idx = motg->dbg_idx % DEBUG_MAX_MSG;
+ write_unlock_irqrestore(&motg->dbg_lock, flags);
+}
+
+static int msm_hsusb_ldo_init(struct msm_otg *motg, int init)
+{
+ int rc = 0;
+
+ if (init) {
+ hsusb_3p3 = devm_regulator_get(motg->phy.dev, "HSUSB_3p3");
+ if (IS_ERR(hsusb_3p3)) {
+ dev_err(motg->phy.dev, "unable to get hsusb 3p3\n");
+ return PTR_ERR(hsusb_3p3);
+ }
+
+ rc = regulator_set_voltage(hsusb_3p3, USB_PHY_3P3_VOL_MIN,
+ USB_PHY_3P3_VOL_MAX);
+ if (rc) {
+ dev_err(motg->phy.dev, "unable to set voltage level for hsusb 3p3\n"
+ );
+ return rc;
+ }
+ hsusb_1p8 = devm_regulator_get(motg->phy.dev, "HSUSB_1p8");
+ if (IS_ERR(hsusb_1p8)) {
+ dev_err(motg->phy.dev, "unable to get hsusb 1p8\n");
+ rc = PTR_ERR(hsusb_1p8);
+ goto put_3p3_lpm;
+ }
+ rc = regulator_set_voltage(hsusb_1p8, USB_PHY_1P8_VOL_MIN,
+ USB_PHY_1P8_VOL_MAX);
+ if (rc) {
+ dev_err(motg->phy.dev, "unable to set voltage level for hsusb 1p8\n"
+ );
+ goto put_1p8;
+ }
+
+ return 0;
+ }
+
+put_1p8:
+ regulator_set_voltage(hsusb_1p8, 0, USB_PHY_1P8_VOL_MAX);
+put_3p3_lpm:
+ regulator_set_voltage(hsusb_3p3, 0, USB_PHY_3P3_VOL_MAX);
+ return rc;
+}
+
+static int msm_hsusb_config_vddcx(int high)
+{
+ struct msm_otg *motg = the_msm_otg;
+ int max_vol = vdd_val[VDD_MAX];
+ int min_vol;
+ int ret;
+
+ min_vol = vdd_val[!!high];
+ ret = regulator_set_voltage(hsusb_vdd, min_vol, max_vol);
+ if (ret) {
+ pr_err("%s: unable to set the voltage for regulator HSUSB_VDDCX\n",
+ __func__);
+ return ret;
+ }
+
+ pr_debug("%s: min_vol:%d max_vol:%d\n", __func__, min_vol, max_vol);
+ msm_otg_dbg_log_event(&motg->phy, "CONFIG VDDCX", min_vol, max_vol);
+
+ return ret;
+}
+
+static int msm_hsusb_ldo_enable(struct msm_otg *motg,
+ enum msm_otg_phy_reg_mode mode)
+{
+ int ret = 0;
+
+ if (IS_ERR(hsusb_1p8)) {
+ pr_err("%s: HSUSB_1p8 is not initialized\n", __func__);
+ return -ENODEV;
+ }
+
+ if (IS_ERR(hsusb_3p3)) {
+ pr_err("%s: HSUSB_3p3 is not initialized\n", __func__);
+ return -ENODEV;
+ }
+
+ switch (mode) {
+ case USB_PHY_REG_ON:
+ ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set HPM of the regulator HSUSB_1p8\n",
+ __func__);
+ return ret;
+ }
+
+ ret = regulator_enable(hsusb_1p8);
+ if (ret) {
+ dev_err(motg->phy.dev, "%s: unable to enable the hsusb 1p8\n",
+ __func__);
+ regulator_set_load(hsusb_1p8, 0);
+ return ret;
+ }
+
+ /* fall through */
+ case USB_PHY_REG_3P3_ON:
+ ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set HPM of the regulator HSUSB_3p3\n",
+ __func__);
+ if (mode == USB_PHY_REG_ON) {
+ regulator_set_load(hsusb_1p8, 0);
+ regulator_disable(hsusb_1p8);
+ }
+ return ret;
+ }
+
+ ret = regulator_enable(hsusb_3p3);
+ if (ret) {
+ dev_err(motg->phy.dev, "%s: unable to enable the hsusb 3p3\n",
+ __func__);
+ regulator_set_load(hsusb_3p3, 0);
+ if (mode == USB_PHY_REG_ON) {
+ regulator_set_load(hsusb_1p8, 0);
+ regulator_disable(hsusb_1p8);
+ }
+ return ret;
+ }
+
+ break;
+
+ case USB_PHY_REG_OFF:
+ ret = regulator_disable(hsusb_1p8);
+ if (ret) {
+ dev_err(motg->phy.dev, "%s: unable to disable the hsusb 1p8\n",
+ __func__);
+ return ret;
+ }
+
+ ret = regulator_set_load(hsusb_1p8, 0);
+ if (ret < 0)
+ pr_err("%s: Unable to set LPM of the regulator HSUSB_1p8\n",
+ __func__);
+
+ /* fall through */
+ case USB_PHY_REG_3P3_OFF:
+ ret = regulator_disable(hsusb_3p3);
+ if (ret) {
+ dev_err(motg->phy.dev, "%s: unable to disable the hsusb 3p3\n",
+ __func__);
+ return ret;
+ }
+ ret = regulator_set_load(hsusb_3p3, 0);
+ if (ret < 0)
+ pr_err("%s: Unable to set LPM of the regulator HSUSB_3p3\n",
+ __func__);
+
+ break;
+
+ case USB_PHY_REG_LPM_ON:
+ ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_LPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set LPM of the regulator: HSUSB_1p8\n",
+ __func__);
+ return ret;
+ }
+
+ ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_LPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set LPM of the regulator: HSUSB_3p3\n",
+ __func__);
+ regulator_set_load(hsusb_1p8, USB_PHY_REG_ON);
+ return ret;
+ }
+
+ break;
+
+ case USB_PHY_REG_LPM_OFF:
+ ret = regulator_set_load(hsusb_1p8, USB_PHY_1P8_HPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set HPM of the regulator: HSUSB_1p8\n",
+ __func__);
+ return ret;
+ }
+
+ ret = regulator_set_load(hsusb_3p3, USB_PHY_3P3_HPM_LOAD);
+ if (ret < 0) {
+ pr_err("%s: Unable to set HPM of the regulator: HSUSB_3p3\n",
+ __func__);
+ regulator_set_load(hsusb_1p8, USB_PHY_REG_ON);
+ return ret;
+ }
+
+ break;
+
+ default:
+ pr_err("%s: Unsupported mode (%d).", __func__, mode);
+ return -ENOTSUPP;
+ }
+
+ pr_debug("%s: USB reg mode (%d) (OFF/HPM/LPM)\n", __func__, mode);
+ msm_otg_dbg_log_event(&motg->phy, "USB REG MODE", mode, ret);
+ return ret < 0 ? ret : 0;
+}
+
+static int ulpi_read(struct usb_phy *phy, u32 reg)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+ int cnt = 0;
+
+ if (motg->pdata->emulation)
+ return 0;
+
+ if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) {
+ pr_debug("%s: ULPI vendor-specific reg 0x%02x not supported\n",
+ __func__, reg);
+ return 0;
+ }
+
+ /* initiate read operation */
+ writel_relaxed(ULPI_RUN | ULPI_READ | ULPI_ADDR(reg),
+ USB_ULPI_VIEWPORT);
+
+ /* wait for completion */
+ while (cnt < ULPI_IO_TIMEOUT_USEC) {
+ if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN))
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= ULPI_IO_TIMEOUT_USEC) {
+ dev_err(phy->dev, "ulpi_read: timeout %08x\n",
+ readl_relaxed(USB_ULPI_VIEWPORT));
+ dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n",
+ readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD));
+ return -ETIMEDOUT;
+ }
+ return ULPI_DATA_READ(readl_relaxed(USB_ULPI_VIEWPORT));
+}
+
+static int ulpi_write(struct usb_phy *phy, u32 val, u32 reg)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+ int cnt = 0;
+
+ if (motg->pdata->emulation)
+ return 0;
+
+ if (motg->pdata->phy_type == QUSB_ULPI_PHY && reg > 0x3F) {
+ pr_debug("%s: ULPI vendor-specific reg 0x%02x not supported\n",
+ __func__, reg);
+ return 0;
+ }
+
+ /* initiate write operation */
+ writel_relaxed(ULPI_RUN | ULPI_WRITE |
+ ULPI_ADDR(reg) | ULPI_DATA(val),
+ USB_ULPI_VIEWPORT);
+
+ /* wait for completion */
+ while (cnt < ULPI_IO_TIMEOUT_USEC) {
+ if (!(readl_relaxed(USB_ULPI_VIEWPORT) & ULPI_RUN))
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= ULPI_IO_TIMEOUT_USEC) {
+ dev_err(phy->dev, "ulpi_write: timeout\n");
+ dev_err(phy->dev, "PORTSC: %08x USBCMD: %08x\n",
+ readl_relaxed(USB_PORTSC), readl_relaxed(USB_USBCMD));
+ return -ETIMEDOUT;
+ }
+ return 0;
+}
+
+static struct usb_phy_io_ops msm_otg_io_ops = {
+ .read = ulpi_read,
+ .write = ulpi_write,
+};
+
+static void ulpi_init(struct msm_otg *motg)
+{
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ int aseq[10];
+ int *seq = NULL;
+
+ if (override_phy_init) {
+ pr_debug("%s(): HUSB PHY Init:%s\n", __func__,
+ override_phy_init);
+ get_options(override_phy_init, ARRAY_SIZE(aseq), aseq);
+ seq = &aseq[1];
+ } else {
+ seq = pdata->phy_init_seq;
+ }
+
+ if (!seq)
+ return;
+
+ while (seq[0] >= 0) {
+ if (override_phy_init)
+ pr_debug("ulpi: write 0x%02x to 0x%02x\n",
+ seq[0], seq[1]);
+
+ dev_vdbg(motg->phy.dev, "ulpi: write 0x%02x to 0x%02x\n",
+ seq[0], seq[1]);
+ msm_otg_dbg_log_event(&motg->phy, "ULPI WRITE", seq[0], seq[1]);
+ ulpi_write(&motg->phy, seq[0], seq[1]);
+ seq += 2;
+ }
+}
+
+static int msm_otg_phy_clk_reset(struct msm_otg *motg)
+{
+ int ret;
+
+ if (!motg->phy_reset_clk)
+ return 0;
+
+ if (motg->sleep_clk)
+ clk_disable_unprepare(motg->sleep_clk);
+ if (motg->phy_csr_clk)
+ clk_disable_unprepare(motg->phy_csr_clk);
+
+ ret = reset_control_assert(motg->phy_reset);
+ if (ret) {
+ pr_err("phy_reset_clk assert failed %d\n", ret);
+ return ret;
+ }
+ /*
+ * As per databook, 10 usec delay is required between
+ * PHY POR assert and de-assert.
+ */
+ usleep_range(10, 15);
+ ret = reset_control_deassert(motg->phy_reset);
+ if (ret) {
+ pr_err("phy_reset_clk de-assert failed %d\n", ret);
+ return ret;
+ }
+ /*
+ * As per databook, it takes 75 usec for PHY to stabilize
+ * after the reset.
+ */
+ usleep_range(80, 100);
+
+ if (motg->phy_csr_clk)
+ clk_prepare_enable(motg->phy_csr_clk);
+ if (motg->sleep_clk)
+ clk_prepare_enable(motg->sleep_clk);
+
+ return 0;
+}
+
+static int msm_otg_link_clk_reset(struct msm_otg *motg, bool assert)
+{
+ int ret;
+
+ if (assert) {
+ /* Using asynchronous block reset to the hardware */
+ dev_dbg(motg->phy.dev, "block_reset ASSERT\n");
+ clk_disable_unprepare(motg->pclk);
+ clk_disable_unprepare(motg->core_clk);
+ ret = reset_control_assert(motg->core_reset);
+ if (ret)
+ dev_err(motg->phy.dev, "usb hs_clk assert failed\n");
+ } else {
+ dev_dbg(motg->phy.dev, "block_reset DEASSERT\n");
+ ret = reset_control_deassert(motg->core_reset);
+ ndelay(200);
+ ret = clk_prepare_enable(motg->core_clk);
+ WARN(ret, "USB core_clk enable failed\n");
+ ret = clk_prepare_enable(motg->pclk);
+ WARN(ret, "USB pclk enable failed\n");
+ if (ret)
+ dev_err(motg->phy.dev, "usb hs_clk deassert failed\n");
+ }
+ return ret;
+}
+
+static int msm_otg_phy_reset(struct msm_otg *motg)
+{
+ u32 val;
+ int ret;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ /*
+ * AHB2AHB Bypass mode shouldn't be enable before doing
+ * async clock reset. If it is enable, disable the same.
+ */
+ val = readl_relaxed(USB_AHBMODE);
+ if (val & AHB2AHB_BYPASS) {
+ pr_err("%s(): AHB2AHB_BYPASS SET: AHBMODE:%x\n",
+ __func__, val);
+ val &= ~AHB2AHB_BYPASS_BIT_MASK;
+ writel_relaxed(val | AHB2AHB_BYPASS_CLEAR, USB_AHBMODE);
+ pr_err("%s(): AHBMODE: %x\n", __func__,
+ readl_relaxed(USB_AHBMODE));
+ }
+
+ ret = msm_otg_link_clk_reset(motg, 1);
+ if (ret)
+ return ret;
+
+ msm_otg_phy_clk_reset(motg);
+
+ /* wait for 1ms delay as suggested in HPG. */
+ usleep_range(1000, 1200);
+
+ ret = msm_otg_link_clk_reset(motg, 0);
+ if (ret)
+ return ret;
+
+ if (pdata && pdata->enable_sec_phy)
+ writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16),
+ USB_PHY_CTRL2);
+ val = readl_relaxed(USB_PORTSC) & ~PORTSC_PTS_MASK;
+ writel_relaxed(val | PORTSC_PTS_ULPI, USB_PORTSC);
+
+ dev_info(motg->phy.dev, "phy_reset: success\n");
+ msm_otg_dbg_log_event(&motg->phy, "PHY RESET SUCCESS",
+ motg->inputs, motg->phy.otg->state);
+ return 0;
+}
+
+#define LINK_RESET_TIMEOUT_USEC (250 * 1000)
+static int msm_otg_link_reset(struct msm_otg *motg)
+{
+ int cnt = 0;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ writel_relaxed(USBCMD_RESET, USB_USBCMD);
+ while (cnt < LINK_RESET_TIMEOUT_USEC) {
+ if (!(readl_relaxed(USB_USBCMD) & USBCMD_RESET))
+ break;
+ udelay(1);
+ cnt++;
+ }
+ if (cnt >= LINK_RESET_TIMEOUT_USEC)
+ return -ETIMEDOUT;
+
+ /* select ULPI phy */
+ writel_relaxed(0x80000000, USB_PORTSC);
+ writel_relaxed(0x0, USB_AHBBURST);
+ writel_relaxed(0x08, USB_AHBMODE);
+
+ if (pdata && pdata->enable_sec_phy)
+ writel_relaxed(readl_relaxed(USB_PHY_CTRL2) | (1<<16),
+ USB_PHY_CTRL2);
+ return 0;
+}
+
+#define QUSB2PHY_PORT_POWERDOWN 0xB4
+#define QUSB2PHY_PORT_UTMI_CTRL2 0xC4
+
+static void msm_usb_phy_reset(struct msm_otg *motg)
+{
+ u32 val;
+ int ret, *seq;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ /* Assert USB PHY_PON */
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ val &= ~PHY_POR_BIT_MASK;
+ val |= PHY_POR_ASSERT;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+
+ /* wait for minimum 10 microseconds as
+ * suggested in HPG.
+ */
+ usleep_range(10, 15);
+
+ /* Deassert USB PHY_PON */
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ val &= ~PHY_POR_BIT_MASK;
+ val |= PHY_POR_DEASSERT;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+ break;
+ case QUSB_ULPI_PHY:
+ ret = reset_control_assert(motg->phy_reset);
+ if (ret) {
+ pr_err("phy_reset_clk assert failed %d\n", ret);
+ break;
+ }
+
+ /* need to delay 10us for PHY to reset */
+ usleep_range(10, 20);
+
+ ret = reset_control_deassert(motg->phy_reset);
+ if (ret) {
+ pr_err("phy_reset_clk de-assert failed %d\n", ret);
+ break;
+ }
+
+ /* Ensure that RESET operation is completed. */
+ mb();
+
+ writel_relaxed(0x23,
+ motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN);
+ writel_relaxed(0x0,
+ motg->phy_csr_regs + QUSB2PHY_PORT_UTMI_CTRL2);
+
+ /* Program tuning parameters for PHY */
+ seq = motg->pdata->phy_init_seq;
+ if (seq) {
+ while (seq[0] >= 0) {
+ writel_relaxed(seq[1],
+ motg->phy_csr_regs + seq[0]);
+ seq += 2;
+ }
+ }
+
+ /* ensure above writes are completed before re-enabling PHY */
+ wmb();
+ writel_relaxed(0x22,
+ motg->phy_csr_regs + QUSB2PHY_PORT_POWERDOWN);
+ break;
+ case SNPS_FEMTO_PHY:
+ if (!motg->phy_por_clk) {
+ pr_err("phy_por_clk missing\n");
+ break;
+ }
+ ret = reset_control_assert(motg->phy_por_reset);
+ if (ret) {
+ pr_err("phy_por_clk assert failed %d\n", ret);
+ break;
+ }
+ /*
+ * The Femto PHY is POR reset in the following scenarios.
+ *
+ * 1. After overriding the parameter registers.
+ * 2. Low power mode exit from PHY retention.
+ *
+ * Ensure that SIDDQ is cleared before bringing the PHY
+ * out of reset.
+ *
+ */
+
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0);
+ val &= ~SIDDQ;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0);
+
+ /*
+ * As per databook, 10 usec delay is required between
+ * PHY POR assert and de-assert.
+ */
+ usleep_range(10, 20);
+ ret = reset_control_deassert(motg->phy_por_reset);
+ if (ret) {
+ pr_err("phy_por_clk de-assert failed %d\n", ret);
+ break;
+ }
+ /*
+ * As per databook, it takes 75 usec for PHY to stabilize
+ * after the reset.
+ */
+ usleep_range(80, 100);
+ break;
+ default:
+ break;
+ }
+ /* Ensure that RESET operation is completed. */
+ mb();
+}
+
+static int msm_otg_reset(struct usb_phy *phy)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ int ret;
+ u32 val = 0;
+ u32 ulpi_val = 0;
+
+ msm_otg_dbg_log_event(&motg->phy, "USB RESET", phy->otg->state,
+ get_pm_runtime_counter(phy->dev));
+ /*
+ * USB PHY and Link reset also reset the USB BAM.
+ * Thus perform reset operation only once to avoid
+ * USB BAM reset on other cases e.g. USB cable disconnections.
+ * If hardware reported error then it must be reset for recovery.
+ */
+ if (motg->err_event_seen)
+ dev_info(phy->dev, "performing USB h/w reset for recovery\n");
+ else if (pdata->disable_reset_on_disconnect && motg->reset_counter)
+ return 0;
+
+ motg->reset_counter++;
+
+ disable_irq(motg->irq);
+ if (motg->phy_irq)
+ disable_irq(motg->phy_irq);
+
+ ret = msm_otg_phy_reset(motg);
+ if (ret) {
+ dev_err(phy->dev, "phy_reset failed\n");
+ if (motg->phy_irq)
+ enable_irq(motg->phy_irq);
+
+ enable_irq(motg->irq);
+ return ret;
+ }
+
+ if (motg->phy_irq)
+ enable_irq(motg->phy_irq);
+
+ enable_irq(motg->irq);
+ ret = msm_otg_link_reset(motg);
+ if (ret) {
+ dev_err(phy->dev, "link reset failed\n");
+ return ret;
+ }
+
+ msleep(100);
+
+ /* Reset USB PHY after performing USB Link RESET */
+ msm_usb_phy_reset(motg);
+
+ /* Program USB PHY Override registers. */
+ ulpi_init(motg);
+
+ /*
+ * It is required to reset USB PHY after programming
+ * the USB PHY Override registers to get the new
+ * values into effect.
+ */
+ msm_usb_phy_reset(motg);
+
+ if (pdata->otg_control == OTG_PHY_CONTROL) {
+ val = readl_relaxed(USB_OTGSC);
+ if (pdata->mode == USB_OTG) {
+ ulpi_val = ULPI_INT_IDGRD | ULPI_INT_SESS_VALID;
+ val |= OTGSC_IDIE | OTGSC_BSVIE;
+ } else if (pdata->mode == USB_PERIPHERAL) {
+ ulpi_val = ULPI_INT_SESS_VALID;
+ val |= OTGSC_BSVIE;
+ }
+ writel_relaxed(val, USB_OTGSC);
+ ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_RISE);
+ ulpi_write(phy, ulpi_val, ULPI_USB_INT_EN_FALL);
+ } else if (pdata->otg_control == OTG_PMIC_CONTROL) {
+ ulpi_write(phy, OTG_COMP_DISABLE,
+ ULPI_SET(ULPI_PWR_CLK_MNG_REG));
+ if (motg->phy_irq)
+ writeb_relaxed(USB_PHY_ID_MASK,
+ USB2_PHY_USB_PHY_INTERRUPT_MASK1);
+ }
+
+ if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)
+ writel_relaxed(readl_relaxed(USB_OTGSC) & ~(OTGSC_IDPU),
+ USB_OTGSC);
+
+ msm_otg_dbg_log_event(&motg->phy, "USB RESET DONE", phy->otg->state,
+ get_pm_runtime_counter(phy->dev));
+
+ if (pdata->enable_axi_prefetch)
+ writel_relaxed(readl_relaxed(USB_HS_APF_CTRL) | (APF_CTRL_EN),
+ USB_HS_APF_CTRL);
+
+ /*
+ * Disable USB BAM as block reset resets USB BAM registers.
+ */
+ msm_usb_bam_enable(CI_CTRL, false);
+
+ return 0;
+}
+
+static void msm_otg_kick_sm_work(struct msm_otg *motg)
+{
+ if (atomic_read(&motg->in_lpm))
+ motg->resume_pending = true;
+
+ /* For device mode, resume now. Let pm_resume handle other cases */
+ if (atomic_read(&motg->pm_suspended) &&
+ motg->phy.otg->state != OTG_STATE_B_SUSPEND) {
+ motg->sm_work_pending = true;
+ } else if (!motg->sm_work_pending) {
+ /* process event only if previous one is not pending */
+ queue_work(motg->otg_wq, &motg->sm_work);
+ }
+}
+
+/*
+ * UDC calls usb_phy_set_suspend() to notify during bus suspend/resume.
+ * Update relevant state-machine inputs and queue sm_work.
+ * LPM enter/exit doesn't happen directly from this routine.
+ */
+
+static int msm_otg_set_suspend(struct usb_phy *phy, int suspend)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+
+ pr_debug("%s(%d) in %s state\n", __func__, suspend,
+ usb_otg_state_string(phy->otg->state));
+ msm_otg_dbg_log_event(phy, "SET SUSPEND", suspend, phy->otg->state);
+
+ if (!(motg->caps & ALLOW_LPM_ON_DEV_SUSPEND))
+ return 0;
+
+ if (suspend) {
+ /* called in suspend interrupt context */
+ pr_debug("peripheral bus suspend\n");
+ msm_otg_dbg_log_event(phy, "PERIPHERAL BUS SUSPEND",
+ motg->inputs, phy->otg->state);
+
+ set_bit(A_BUS_SUSPEND, &motg->inputs);
+ } else {
+ /* host resume or remote-wakeup */
+ pr_debug("peripheral bus resume\n");
+ msm_otg_dbg_log_event(phy, "PERIPHERAL BUS RESUME",
+ motg->inputs, phy->otg->state);
+
+ clear_bit(A_BUS_SUSPEND, &motg->inputs);
+ }
+ /* use kick_sm_work to handle race with pm_resume */
+ msm_otg_kick_sm_work(motg);
+
+ return 0;
+}
+
+static int msm_otg_bus_freq_set(struct msm_otg *motg, enum usb_noc_mode mode)
+{
+ int i, ret;
+ long rate;
+
+ for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) {
+ rate = bus_freqs[mode][i];
+ if (!rate) {
+ pr_debug("%s rate not available\n", bus_clkname[i]);
+ continue;
+ }
+
+ ret = clk_set_rate(motg->bus_clks[i], rate);
+ if (ret) {
+ pr_err("%s set rate failed: %d\n", bus_clkname[i], ret);
+ return ret;
+ }
+ pr_debug("%s set to %lu Hz\n", bus_clkname[i],
+ clk_get_rate(motg->bus_clks[i]));
+ msm_otg_dbg_log_event(&motg->phy, "OTG BUS FREQ SET", i, rate);
+ }
+
+ bus_clk_rate_set = true;
+
+ return 0;
+}
+
+static int msm_otg_bus_freq_get(struct msm_otg *motg)
+{
+ struct device *dev = motg->phy.dev;
+ struct device_node *np = dev->of_node;
+ int len = 0, i, count = USB_NUM_BUS_CLOCKS;
+
+ if (!np)
+ return -EINVAL;
+
+ of_find_property(np, "qcom,bus-clk-rate", &len);
+ /* SVS requires extra set of frequencies for perf_mode sysfs node */
+ if (motg->default_noc_mode == USB_NOC_SVS_VOTE)
+ count *= 2;
+
+ if (!len || (len / sizeof(u32) != count)) {
+ pr_err("Invalid bus rate:%d %u\n", len, motg->default_noc_mode);
+ return -EINVAL;
+ }
+ of_property_read_u32_array(np, "qcom,bus-clk-rate", bus_freqs[0],
+ count);
+ for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) {
+ if (bus_freqs[0][i] == 0) {
+ motg->bus_clks[i] = NULL;
+ pr_debug("%s not available\n", bus_clkname[i]);
+ continue;
+ }
+
+ motg->bus_clks[i] = devm_clk_get(dev, bus_clkname[i]);
+ if (IS_ERR(motg->bus_clks[i])) {
+ pr_err("%s get failed\n", bus_clkname[i]);
+ return PTR_ERR(motg->bus_clks[i]);
+ }
+ }
+ return 0;
+}
+
+static void msm_otg_bus_clks_enable(struct msm_otg *motg)
+{
+ int i;
+ int ret;
+
+ if (!bus_clk_rate_set || motg->bus_clks_enabled)
+ return;
+
+ for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) {
+ if (motg->bus_clks[i] == NULL)
+ continue;
+ ret = clk_prepare_enable(motg->bus_clks[i]);
+ if (ret) {
+ pr_err("%s enable rate failed: %d\n", bus_clkname[i],
+ ret);
+ goto err_clk_en;
+ }
+ }
+ motg->bus_clks_enabled = true;
+ return;
+err_clk_en:
+ for (--i; i >= 0; --i) {
+ if (motg->bus_clks[i] != NULL)
+ clk_disable_unprepare(motg->bus_clks[i]);
+ }
+}
+
+static void msm_otg_bus_clks_disable(struct msm_otg *motg)
+{
+ int i;
+
+ if (!bus_clk_rate_set || !motg->bus_clks_enabled)
+ return;
+
+ for (i = 0; i < USB_NUM_BUS_CLOCKS; i++) {
+ if (motg->bus_clks[i] != NULL)
+ clk_disable_unprepare(motg->bus_clks[i]);
+ }
+ motg->bus_clks_enabled = false;
+}
+
+static void msm_otg_bus_vote(struct msm_otg *motg, enum usb_bus_vote vote)
+{
+ int ret;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ msm_otg_dbg_log_event(&motg->phy, "BUS VOTE", vote,
+ motg->phy.otg->state);
+ /* Check if target allows min_vote to be same as no_vote */
+ if (pdata->bus_scale_table &&
+ vote >= pdata->bus_scale_table->num_usecases)
+ vote = USB_NO_PERF_VOTE;
+
+ if (motg->bus_perf_client) {
+ ret = msm_bus_scale_client_update_request(
+ motg->bus_perf_client, vote);
+ if (ret)
+ dev_err(motg->phy.dev, "%s: Failed to vote (%d)\n"
+ "for bus bw %d\n", __func__, vote, ret);
+ }
+
+ if (vote == USB_MAX_PERF_VOTE)
+ msm_otg_bus_clks_enable(motg);
+ else
+ msm_otg_bus_clks_disable(motg);
+}
+
+static void msm_otg_enable_phy_hv_int(struct msm_otg *motg)
+{
+ bool bsv_id_hv_int = false;
+ bool dp_dm_hv_int = false;
+ u32 val;
+
+ if (motg->pdata->otg_control == OTG_PHY_CONTROL ||
+ motg->phy_irq)
+ bsv_id_hv_int = true;
+ if (motg->host_bus_suspend || motg->device_bus_suspend)
+ dp_dm_hv_int = true;
+
+ if (!bsv_id_hv_int && !dp_dm_hv_int)
+ return;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ if (bsv_id_hv_int)
+ val |= (PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN);
+ if (dp_dm_hv_int)
+ val |= PHY_CLAMP_DPDMSE_EN;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+ break;
+ case SNPS_FEMTO_PHY:
+ if (bsv_id_hv_int) {
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1);
+ val |= ID_HV_CLAMP_EN_N;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1);
+ }
+
+ if (dp_dm_hv_int) {
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3);
+ val |= CLAMP_MPM_DPSE_DMSE_EN_N;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3);
+ }
+ break;
+ default:
+ break;
+ }
+ pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n",
+ __func__, bsv_id_hv_int, dp_dm_hv_int);
+ msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR ENABLED",
+ bsv_id_hv_int, dp_dm_hv_int);
+}
+
+static void msm_otg_disable_phy_hv_int(struct msm_otg *motg)
+{
+ bool bsv_id_hv_int = false;
+ bool dp_dm_hv_int = false;
+ u32 val;
+
+ if (motg->pdata->otg_control == OTG_PHY_CONTROL ||
+ motg->phy_irq)
+ bsv_id_hv_int = true;
+ if (motg->host_bus_suspend || motg->device_bus_suspend)
+ dp_dm_hv_int = true;
+
+ if (!bsv_id_hv_int && !dp_dm_hv_int)
+ return;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ if (bsv_id_hv_int)
+ val &= ~(PHY_IDHV_INTEN | PHY_OTGSESSVLDHV_INTEN);
+ if (dp_dm_hv_int)
+ val &= ~PHY_CLAMP_DPDMSE_EN;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+ break;
+ case SNPS_FEMTO_PHY:
+ if (bsv_id_hv_int) {
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL1);
+ val &= ~ID_HV_CLAMP_EN_N;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL1);
+ }
+
+ if (dp_dm_hv_int) {
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL3);
+ val &= ~CLAMP_MPM_DPSE_DMSE_EN_N;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL3);
+ }
+ break;
+ default:
+ break;
+ }
+ pr_debug("%s: bsv_id_hv = %d dp_dm_hv_int = %d\n",
+ __func__, bsv_id_hv_int, dp_dm_hv_int);
+ msm_otg_dbg_log_event(&motg->phy, "PHY HV INTR DISABLED",
+ bsv_id_hv_int, dp_dm_hv_int);
+}
+
+static void msm_otg_enter_phy_retention(struct msm_otg *motg)
+{
+ u32 val;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ val &= ~PHY_RETEN;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+ break;
+ case SNPS_FEMTO_PHY:
+ /* Retention is supported via SIDDQ */
+ val = readb_relaxed(USB_PHY_CSR_PHY_CTRL_COMMON0);
+ val |= SIDDQ;
+ writeb_relaxed(val, USB_PHY_CSR_PHY_CTRL_COMMON0);
+ break;
+ default:
+ break;
+ }
+ pr_debug("USB PHY is in retention\n");
+ msm_otg_dbg_log_event(&motg->phy, "USB PHY ENTER RETENTION",
+ motg->pdata->phy_type, 0);
+}
+
+static void msm_otg_exit_phy_retention(struct msm_otg *motg)
+{
+ int val;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ val = readl_relaxed(motg->usb_phy_ctrl_reg);
+ val |= PHY_RETEN;
+ writel_relaxed(val, motg->usb_phy_ctrl_reg);
+ break;
+ case SNPS_FEMTO_PHY:
+ /*
+ * It is required to do USB block reset to bring Femto PHY out
+ * of retention.
+ */
+ msm_otg_reset(&motg->phy);
+ break;
+ default:
+ break;
+ }
+ pr_debug("USB PHY is exited from retention\n");
+ msm_otg_dbg_log_event(&motg->phy, "USB PHY EXIT RETENTION",
+ motg->pdata->phy_type, 0);
+}
+
+static void msm_id_status_w(struct work_struct *w);
+static irqreturn_t msm_otg_phy_irq_handler(int irq, void *data)
+{
+ struct msm_otg *motg = data;
+
+ msm_otg_dbg_log_event(&motg->phy, "PHY ID IRQ",
+ atomic_read(&motg->in_lpm), motg->phy.otg->state);
+ if (atomic_read(&motg->in_lpm)) {
+ pr_debug("PHY ID IRQ in LPM\n");
+ motg->phy_irq_pending = true;
+ msm_otg_kick_sm_work(motg);
+ } else {
+ pr_debug("PHY ID IRQ outside LPM\n");
+ msm_id_status_w(&motg->id_status_work.work);
+ }
+
+ return IRQ_HANDLED;
+}
+
+#define PHY_SUSPEND_TIMEOUT_USEC (5 * 1000)
+#define PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC 100
+#define PHY_RESUME_TIMEOUT_USEC (100 * 1000)
+
+#define PHY_SUSPEND_RETRIES_MAX 3
+
+static void msm_otg_set_vbus_state(int online);
+static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode);
+
+#ifdef CONFIG_PM_SLEEP
+static int msm_otg_suspend(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ struct usb_bus *bus = phy->otg->host;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ int cnt;
+ bool host_bus_suspend, device_bus_suspend, dcp, prop_charger;
+ bool floated_charger, sm_work_busy;
+ u32 cmd_val;
+ u32 portsc, config2;
+ u32 func_ctrl;
+ int phcd_retry_cnt = 0, ret;
+ unsigned int phy_suspend_timeout;
+
+ cnt = 0;
+ msm_otg_dbg_log_event(phy, "LPM ENTER START",
+ motg->inputs, phy->otg->state);
+
+ if (atomic_read(&motg->in_lpm))
+ return 0;
+
+ cancel_delayed_work_sync(&motg->perf_vote_work);
+
+ disable_irq(motg->irq);
+ if (motg->phy_irq)
+ disable_irq(motg->phy_irq);
+lpm_start:
+ host_bus_suspend = phy->otg->host && !test_bit(ID, &motg->inputs);
+ device_bus_suspend = phy->otg->gadget && test_bit(ID, &motg->inputs) &&
+ test_bit(A_BUS_SUSPEND, &motg->inputs) &&
+ motg->caps & ALLOW_LPM_ON_DEV_SUSPEND;
+
+ if (host_bus_suspend)
+ msm_otg_perf_vote_update(motg, false);
+ /*
+ * Allow putting PHY into SIDDQ with wall charger connected in
+ * case of external charger detection.
+ */
+ dcp = (motg->chg_type == USB_DCP_CHARGER) && !motg->is_ext_chg_dcp;
+ prop_charger = motg->chg_type == USB_NONCOMPLIANT_CHARGER;
+ floated_charger = motg->chg_type == USB_FLOATED_CHARGER;
+
+ /* !BSV, but its handling is in progress by otg sm_work */
+ sm_work_busy = !test_bit(B_SESS_VLD, &motg->inputs) &&
+ phy->otg->state == OTG_STATE_B_PERIPHERAL;
+
+ /* Perform block reset to recover from UDC error events on disconnect */
+ if (motg->err_event_seen)
+ msm_otg_reset(phy);
+
+ /* Enable line state difference wakeup fix for only device and host
+ * bus suspend scenarios. Otherwise PHY can not be suspended when
+ * a charger that pulls DP/DM high is connected.
+ */
+ config2 = readl_relaxed(USB_GENCONFIG_2);
+ if (device_bus_suspend)
+ config2 |= GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN;
+ else
+ config2 &= ~GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN;
+ writel_relaxed(config2, USB_GENCONFIG_2);
+
+ /*
+ * Abort suspend when,
+ * 1. charging detection in progress due to cable plug-in
+ * 2. host mode activation in progress due to Micro-A cable insertion
+ * 3. !BSV, but its handling is in progress by otg sm_work
+ * Don't abort suspend in case of dcp detected by PMIC
+ */
+
+ if ((test_bit(B_SESS_VLD, &motg->inputs) && !device_bus_suspend &&
+ !dcp && !motg->is_ext_chg_dcp && !prop_charger &&
+ !floated_charger) || sm_work_busy) {
+ msm_otg_dbg_log_event(phy, "LPM ENTER ABORTED",
+ motg->inputs, motg->chg_type);
+ enable_irq(motg->irq);
+ if (motg->phy_irq)
+ enable_irq(motg->phy_irq);
+ return -EBUSY;
+ }
+
+ if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) {
+ /* put the controller in non-driving mode */
+ func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL);
+ func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
+ func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;
+ ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL);
+ ulpi_write(phy, ULPI_IFC_CTRL_AUTORESUME,
+ ULPI_CLR(ULPI_IFC_CTRL));
+ }
+
+ /*
+ * PHY suspend sequence as mentioned in the databook.
+ *
+ * Device bus suspend: The controller may abort PHY suspend if
+ * there is an incoming reset or resume from the host. If PHCD
+ * is not set within 100 usec. Abort the LPM sequence.
+ *
+ * Host bus suspend: If the peripheral is attached, PHY is already
+ * put into suspend along with the peripheral bus suspend. poll for
+ * PHCD upto 5 msec. If the peripheral is not attached i.e entering
+ * LPM with Micro-A cable, set the PHCD and poll for it for 5 msec.
+ *
+ * No cable connected: Set the PHCD to suspend the PHY. Poll for PHCD
+ * upto 5 msec.
+ *
+ * The controller aborts PHY suspend only in device bus suspend case.
+ * In other cases, it is observed that PHCD may not get set within
+ * the timeout. If so, set the PHCD again and poll for it before
+ * reset recovery.
+ */
+
+phcd_retry:
+ if (device_bus_suspend)
+ phy_suspend_timeout = PHY_DEVICE_BUS_SUSPEND_TIMEOUT_USEC;
+ else
+ phy_suspend_timeout = PHY_SUSPEND_TIMEOUT_USEC;
+
+ cnt = 0;
+ portsc = readl_relaxed(USB_PORTSC);
+ if (!(portsc & PORTSC_PHCD)) {
+ writel_relaxed(portsc | PORTSC_PHCD,
+ USB_PORTSC);
+ while (cnt < phy_suspend_timeout) {
+ if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD)
+ break;
+ udelay(1);
+ cnt++;
+ }
+ }
+
+ if (cnt >= phy_suspend_timeout) {
+ if (phcd_retry_cnt > PHY_SUSPEND_RETRIES_MAX) {
+ msm_otg_dbg_log_event(phy, "PHY SUSPEND FAILED",
+ phcd_retry_cnt, phy->otg->state);
+ dev_err(phy->dev, "PHY suspend failed\n");
+ ret = -EBUSY;
+ goto phy_suspend_fail;
+ }
+
+ if (device_bus_suspend) {
+ dev_dbg(phy->dev, "PHY suspend aborted\n");
+ ret = -EBUSY;
+ goto phy_suspend_fail;
+ } else {
+ if (phcd_retry_cnt++ < PHY_SUSPEND_RETRIES_MAX) {
+ dev_dbg(phy->dev, "PHY suspend retry\n");
+ goto phcd_retry;
+ } else {
+ dev_err(phy->dev, "reset attempt during PHY suspend\n");
+ phcd_retry_cnt++;
+ motg->reset_counter = 0;
+ msm_otg_reset(phy);
+ goto lpm_start;
+ }
+ }
+ }
+
+ /*
+ * PHY has capability to generate interrupt asynchronously in low
+ * power mode (LPM). This interrupt is level triggered. So USB IRQ
+ * line must be disabled till async interrupt enable bit is cleared
+ * in USBCMD register. Assert STP (ULPI interface STOP signal) to
+ * block data communication from PHY.
+ *
+ * PHY retention mode is disallowed while entering to LPM with wall
+ * charger connected. But PHY is put into suspend mode. Hence
+ * enable asynchronous interrupt to detect charger disconnection when
+ * PMIC notifications are unavailable.
+ */
+ cmd_val = readl_relaxed(USB_USBCMD);
+ if (host_bus_suspend || device_bus_suspend ||
+ (motg->pdata->otg_control == OTG_PHY_CONTROL))
+ cmd_val |= ASYNC_INTR_CTRL | ULPI_STP_CTRL;
+ else
+ cmd_val |= ULPI_STP_CTRL;
+ writel_relaxed(cmd_val, USB_USBCMD);
+
+ /*
+ * BC1.2 spec mandates PD to enable VDP_SRC when charging from DCP.
+ * PHY retention and collapse can not happen with VDP_SRC enabled.
+ */
+
+
+ /*
+ * We come here in 3 scenarios.
+ *
+ * (1) No cable connected (out of session):
+ * - BSV/ID HV interrupts are enabled for PHY based detection.
+ * - PHY is put in retention.
+ * - If allowed (PMIC based detection), PHY is power collapsed.
+ * - DVDD (CX/MX) minimization and XO shutdown are allowed.
+ * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user.
+ * (2) USB wall charger:
+ * - BSV/ID HV interrupts are enabled for PHY based detection.
+ * - For BC1.2 compliant charger, retention is not allowed to
+ * keep VDP_SRC on. XO shutdown is allowed.
+ * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user.
+ * (3) Device/Host Bus suspend (if LPM is enabled):
+ * - BSV/ID HV interrupts are enabled for PHY based detection.
+ * - D+/D- MPM pin are configured to wakeup from line state
+ * change through PHY HV interrupts. PHY HV interrupts are
+ * also enabled. If MPM pins are not available, retention and
+ * XO is not allowed.
+ * - PHY is put into retention only if a gpio is used to keep
+ * the D+ pull-up. ALLOW_BUS_SUSPEND_WITHOUT_REWORK capability
+ * is set means, PHY can enable D+ pull-up or D+/D- pull-down
+ * without any re-work and PHY should not be put into retention.
+ * - DVDD (CX/MX) minimization and XO shutdown is allowed if
+ * ALLOW_BUS_SUSPEND_WITHOUT_REWORK is set (PHY DVDD is supplied
+ * via PMIC LDO) or board level re-work is present.
+ * - The wakeup is through VBUS/ID interrupt from PHY/PMIC/user
+ * or USB link asynchronous interrupt for line state change.
+ *
+ */
+ motg->host_bus_suspend = host_bus_suspend;
+ motg->device_bus_suspend = device_bus_suspend;
+
+ if (motg->caps & ALLOW_PHY_RETENTION && !device_bus_suspend && !dcp &&
+ (!host_bus_suspend || (motg->caps &
+ ALLOW_BUS_SUSPEND_WITHOUT_REWORK) ||
+ ((motg->caps & ALLOW_HOST_PHY_RETENTION)
+ && (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS))))) {
+ msm_otg_enable_phy_hv_int(motg);
+ if ((!host_bus_suspend || !(motg->caps &
+ ALLOW_BUS_SUSPEND_WITHOUT_REWORK)) &&
+ !(motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) {
+ msm_otg_enter_phy_retention(motg);
+ motg->lpm_flags |= PHY_RETENTIONED;
+ }
+ } else if (device_bus_suspend && !dcp &&
+ (pdata->mpm_dpshv_int || pdata->mpm_dmshv_int)) {
+ /* DP DM HV interrupts are used for bus resume from XO off */
+ msm_otg_enable_phy_hv_int(motg);
+ if (motg->caps & ALLOW_PHY_RETENTION && pdata->vddmin_gpio) {
+
+ /*
+ * This is HW WA needed when PHY_CLAMP_DPDMSE_EN is
+ * enabled and we put the phy in retention mode.
+ * Without this WA, the async_irq will be fired right
+ * after suspending whithout any bus resume.
+ */
+ config2 = readl_relaxed(USB_GENCONFIG_2);
+ config2 &= ~GENCONFIG_2_DPSE_DMSE_HV_INTR_EN;
+ writel_relaxed(config2, USB_GENCONFIG_2);
+
+ msm_otg_enter_phy_retention(motg);
+ motg->lpm_flags |= PHY_RETENTIONED;
+ gpio_direction_output(pdata->vddmin_gpio, 1);
+ }
+ }
+
+ /* Ensure that above operation is completed before turning off clocks */
+ mb();
+ /* Consider clocks on workaround flag only in case of bus suspend */
+ if (!(phy->otg->state == OTG_STATE_B_PERIPHERAL &&
+ test_bit(A_BUS_SUSPEND, &motg->inputs)) ||
+ !motg->pdata->core_clk_always_on_workaround) {
+ clk_disable_unprepare(motg->pclk);
+ clk_disable_unprepare(motg->core_clk);
+ if (motg->phy_csr_clk)
+ clk_disable_unprepare(motg->phy_csr_clk);
+ motg->lpm_flags |= CLOCKS_DOWN;
+ }
+
+ /* usb phy no more require TCXO clock, hence vote for TCXO disable */
+ if (!host_bus_suspend || (motg->caps &
+ ALLOW_BUS_SUSPEND_WITHOUT_REWORK) ||
+ ((motg->caps & ALLOW_HOST_PHY_RETENTION) &&
+ (pdata->dpdm_pulldown_added || !(portsc & PORTSC_CCS)))) {
+ if (motg->xo_clk) {
+ clk_disable_unprepare(motg->xo_clk);
+ motg->lpm_flags |= XO_SHUTDOWN;
+ }
+ }
+
+ if (motg->caps & ALLOW_PHY_POWER_COLLAPSE &&
+ !host_bus_suspend && !dcp && !device_bus_suspend) {
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF);
+ motg->lpm_flags |= PHY_PWR_COLLAPSED;
+ } else if (motg->caps & ALLOW_PHY_REGULATORS_LPM &&
+ !host_bus_suspend && !device_bus_suspend && !dcp) {
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_ON);
+ motg->lpm_flags |= PHY_REGULATORS_LPM;
+ }
+
+ if (motg->lpm_flags & PHY_RETENTIONED ||
+ (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) {
+ regulator_disable(hsusb_vdd);
+ msm_hsusb_config_vddcx(0);
+ }
+
+ if (device_may_wakeup(phy->dev)) {
+ if (host_bus_suspend || device_bus_suspend) {
+ enable_irq_wake(motg->async_irq);
+ enable_irq_wake(motg->irq);
+ }
+
+ if (motg->phy_irq)
+ enable_irq_wake(motg->phy_irq);
+ if (motg->pdata->pmic_id_irq)
+ enable_irq_wake(motg->pdata->pmic_id_irq);
+ if (motg->ext_id_irq)
+ enable_irq_wake(motg->ext_id_irq);
+ if (pdata->otg_control == OTG_PHY_CONTROL &&
+ pdata->mpm_otgsessvld_int)
+ msm_mpm_set_pin_wake(pdata->mpm_otgsessvld_int, 1);
+ if ((host_bus_suspend || device_bus_suspend) &&
+ pdata->mpm_dpshv_int)
+ msm_mpm_set_pin_wake(pdata->mpm_dpshv_int, 1);
+ if ((host_bus_suspend || device_bus_suspend) &&
+ pdata->mpm_dmshv_int)
+ msm_mpm_set_pin_wake(pdata->mpm_dmshv_int, 1);
+ }
+ if (bus)
+ clear_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags);
+
+ msm_otg_bus_vote(motg, USB_NO_PERF_VOTE);
+
+ atomic_set(&motg->in_lpm, 1);
+
+ /* Enable ASYNC IRQ during LPM */
+ enable_irq(motg->async_irq);
+ if (motg->phy_irq)
+ enable_irq(motg->phy_irq);
+
+ enable_irq(motg->irq);
+ pm_relax(&motg->pdev->dev);
+
+ dev_dbg(phy->dev, "LPM caps = %lu flags = %lu\n",
+ motg->caps, motg->lpm_flags);
+ dev_info(phy->dev, "USB in low power mode\n");
+ msm_otg_dbg_log_event(phy, "LPM ENTER DONE",
+ motg->caps, motg->lpm_flags);
+
+ if (motg->err_event_seen) {
+ motg->err_event_seen = false;
+ if (motg->vbus_state != test_bit(B_SESS_VLD, &motg->inputs))
+ msm_otg_set_vbus_state(motg->vbus_state);
+ if (motg->id_state != test_bit(ID, &motg->inputs))
+ msm_id_status_w(&motg->id_status_work.work);
+ }
+
+ return 0;
+
+phy_suspend_fail:
+ enable_irq(motg->irq);
+ if (motg->phy_irq)
+ enable_irq(motg->phy_irq);
+ return ret;
+}
+
+static int msm_otg_resume(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ struct usb_bus *bus = phy->otg->host;
+ struct usb_hcd *hcd = bus_to_hcd(phy->otg->host);
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ int cnt = 0;
+ unsigned int temp;
+ unsigned int ret;
+ u32 func_ctrl;
+
+ msm_otg_dbg_log_event(phy, "LPM EXIT START", motg->inputs,
+ phy->otg->state);
+ if (!atomic_read(&motg->in_lpm)) {
+ msm_otg_dbg_log_event(phy, "USB NOT IN LPM",
+ atomic_read(&motg->in_lpm), phy->otg->state);
+ return 0;
+ }
+
+ disable_irq(motg->irq);
+ pm_stay_awake(&motg->pdev->dev);
+
+ /*
+ * If we are resuming from the device bus suspend, restore
+ * the max performance bus vote. Otherwise put a minimum
+ * bus vote to satisfy the requirement for enabling clocks.
+ */
+
+ if (motg->device_bus_suspend && debug_bus_voting_enabled)
+ msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE);
+ else
+ msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE);
+
+ /* Vote for TCXO when waking up the phy */
+ if (motg->lpm_flags & XO_SHUTDOWN) {
+ if (motg->xo_clk)
+ clk_prepare_enable(motg->xo_clk);
+ motg->lpm_flags &= ~XO_SHUTDOWN;
+ }
+
+ if (motg->lpm_flags & CLOCKS_DOWN) {
+ if (motg->phy_csr_clk) {
+ ret = clk_prepare_enable(motg->phy_csr_clk);
+ WARN(ret, "USB phy_csr_clk enable failed\n");
+ }
+ ret = clk_prepare_enable(motg->core_clk);
+ WARN(ret, "USB core_clk enable failed\n");
+ ret = clk_prepare_enable(motg->pclk);
+ WARN(ret, "USB pclk enable failed\n");
+ motg->lpm_flags &= ~CLOCKS_DOWN;
+ }
+
+ if (motg->lpm_flags & PHY_PWR_COLLAPSED) {
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON);
+ motg->lpm_flags &= ~PHY_PWR_COLLAPSED;
+ } else if (motg->lpm_flags & PHY_REGULATORS_LPM) {
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_LPM_OFF);
+ motg->lpm_flags &= ~PHY_REGULATORS_LPM;
+ }
+
+ if (motg->lpm_flags & PHY_RETENTIONED ||
+ (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED)) {
+ msm_hsusb_config_vddcx(1);
+ ret = regulator_enable(hsusb_vdd);
+ WARN(ret, "hsusb_vdd LDO enable failed\n");
+ msm_otg_disable_phy_hv_int(motg);
+ msm_otg_exit_phy_retention(motg);
+ motg->lpm_flags &= ~PHY_RETENTIONED;
+ if (pdata->vddmin_gpio && motg->device_bus_suspend)
+ gpio_direction_input(pdata->vddmin_gpio);
+ } else if (motg->device_bus_suspend) {
+ msm_otg_disable_phy_hv_int(motg);
+ }
+
+ temp = readl_relaxed(USB_USBCMD);
+ temp &= ~ASYNC_INTR_CTRL;
+ temp &= ~ULPI_STP_CTRL;
+ writel_relaxed(temp, USB_USBCMD);
+
+ /*
+ * PHY comes out of low power mode (LPM) in case of wakeup
+ * from asynchronous interrupt.
+ */
+ if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD))
+ goto skip_phy_resume;
+
+ writel_relaxed(readl_relaxed(USB_PORTSC) & ~PORTSC_PHCD, USB_PORTSC);
+
+ while (cnt < PHY_RESUME_TIMEOUT_USEC) {
+ if (!(readl_relaxed(USB_PORTSC) & PORTSC_PHCD))
+ break;
+ udelay(1);
+ cnt++;
+ }
+
+ if (cnt >= PHY_RESUME_TIMEOUT_USEC) {
+ /*
+ * This is a fatal error. Reset the link and
+ * PHY. USB state can not be restored. Re-insertion
+ * of USB cable is the only way to get USB working.
+ */
+ dev_err(phy->dev, "Unable to resume USB. Re-plugin the cable\n"
+ );
+ msm_otg_reset(phy);
+ }
+
+skip_phy_resume:
+ if (motg->caps & ALLOW_VDD_MIN_WITH_RETENTION_DISABLED) {
+ /* put the controller in normal mode */
+ func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL);
+ func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
+ func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL;
+ ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL);
+ }
+
+ if (device_may_wakeup(phy->dev)) {
+ if (motg->host_bus_suspend || motg->device_bus_suspend) {
+ disable_irq_wake(motg->async_irq);
+ disable_irq_wake(motg->irq);
+ }
+
+ if (motg->phy_irq)
+ disable_irq_wake(motg->phy_irq);
+ if (motg->pdata->pmic_id_irq)
+ disable_irq_wake(motg->pdata->pmic_id_irq);
+ if (motg->ext_id_irq)
+ disable_irq_wake(motg->ext_id_irq);
+ if (pdata->otg_control == OTG_PHY_CONTROL &&
+ pdata->mpm_otgsessvld_int)
+ msm_mpm_set_pin_wake(pdata->mpm_otgsessvld_int, 0);
+ if ((motg->host_bus_suspend || motg->device_bus_suspend) &&
+ pdata->mpm_dpshv_int)
+ msm_mpm_set_pin_wake(pdata->mpm_dpshv_int, 0);
+ if ((motg->host_bus_suspend || motg->device_bus_suspend) &&
+ pdata->mpm_dmshv_int)
+ msm_mpm_set_pin_wake(pdata->mpm_dmshv_int, 0);
+ }
+ if (bus)
+ set_bit(HCD_FLAG_HW_ACCESSIBLE, &(bus_to_hcd(bus))->flags);
+
+ atomic_set(&motg->in_lpm, 0);
+
+ if (motg->async_int) {
+ /* Match the disable_irq call from ISR */
+ enable_irq(motg->async_int);
+ motg->async_int = 0;
+ }
+ enable_irq(motg->irq);
+
+ /* Enable ASYNC_IRQ only during LPM */
+ disable_irq(motg->async_irq);
+
+ if (motg->phy_irq_pending) {
+ motg->phy_irq_pending = false;
+ msm_id_status_w(&motg->id_status_work.work);
+ }
+
+ if (motg->host_bus_suspend) {
+ usb_hcd_resume_root_hub(hcd);
+ schedule_delayed_work(&motg->perf_vote_work,
+ msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC));
+ }
+
+ dev_info(phy->dev, "USB exited from low power mode\n");
+ msm_otg_dbg_log_event(phy, "LPM EXIT DONE",
+ motg->caps, motg->lpm_flags);
+
+ return 0;
+}
+#endif
+
+static void msm_otg_notify_host_mode(struct msm_otg *motg, bool host_mode)
+{
+ if (!psy) {
+ pr_err("No USB power supply registered!\n");
+ return;
+ }
+
+ motg->host_mode = host_mode;
+ power_supply_changed(psy);
+}
+
+static int msm_otg_notify_chg_type(struct msm_otg *motg)
+{
+ static int charger_type;
+ union power_supply_propval pval = {0};
+
+ /*
+ * TODO
+ * Unify OTG driver charger types and power supply charger types
+ */
+ if (charger_type == motg->chg_type)
+ return 0;
+
+ if (motg->chg_type == USB_SDP_CHARGER)
+ charger_type = POWER_SUPPLY_TYPE_USB;
+ else if (motg->chg_type == USB_CDP_CHARGER)
+ charger_type = POWER_SUPPLY_TYPE_USB_CDP;
+ else if (motg->chg_type == USB_DCP_CHARGER ||
+ motg->chg_type == USB_NONCOMPLIANT_CHARGER ||
+ motg->chg_type == USB_FLOATED_CHARGER)
+ charger_type = POWER_SUPPLY_TYPE_USB_DCP;
+ else
+ charger_type = POWER_SUPPLY_TYPE_UNKNOWN;
+
+ if (!psy) {
+ pr_err("No USB power supply registered!\n");
+ return -EINVAL;
+ }
+
+ pr_debug("setting usb power supply type %d\n", charger_type);
+ msm_otg_dbg_log_event(&motg->phy, "SET USB PWR SUPPLY TYPE",
+ motg->chg_type, charger_type);
+ pval.intval = charger_type;
+ power_supply_set_property(psy, POWER_SUPPLY_PROP_TYPE, &pval);
+ return 0;
+}
+
+static int msm_otg_notify_power_supply(struct msm_otg *motg, unsigned int mA)
+{
+ union power_supply_propval pval = {0};
+ bool enable;
+ int limit;
+
+ if (!psy) {
+ dev_dbg(motg->phy.dev, "no usb power supply registered\n");
+ goto psy_error;
+ }
+
+ if (motg->cur_power == 0 && mA > 2) {
+ /* Enable charging */
+ enable = true;
+ limit = 1000 * mA;
+ } else if (motg->cur_power >= 0 && (mA == 0 || mA == 2)) {
+ /* Disable charging */
+ enable = false;
+ /* Set max current limit in uA */
+ limit = 1000 * mA;
+ } else {
+ enable = true;
+ /* Current has changed (100/2 --> 500) */
+ limit = 1000 * mA;
+ }
+
+ pval.intval = enable;
+ if (power_supply_set_property(psy, POWER_SUPPLY_PROP_ONLINE, &pval))
+ goto psy_error;
+
+ pval.intval = limit;
+ if (power_supply_set_property(psy, POWER_SUPPLY_PROP_CURRENT_MAX,
+ &pval))
+ goto psy_error;
+
+ power_supply_changed(psy);
+ return 0;
+
+psy_error:
+ dev_dbg(motg->phy.dev, "power supply error when setting property\n");
+ return -ENXIO;
+}
+
+static void msm_otg_set_online_status(struct msm_otg *motg)
+{
+ union power_supply_propval pval = {0};
+
+ if (!psy) {
+ dev_dbg(motg->phy.dev, "no usb power supply registered\n");
+ return;
+ }
+
+ /* Set power supply online status to false */
+ pval.intval = false;
+ if (power_supply_set_property(psy, POWER_SUPPLY_PROP_ONLINE, &pval))
+ dev_dbg(motg->phy.dev, "error setting power supply property\n");
+}
+
+static void msm_otg_notify_charger(struct msm_otg *motg, unsigned int mA)
+{
+ struct usb_gadget *g = motg->phy.otg->gadget;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ if (g && g->is_a_peripheral)
+ return;
+
+ dev_dbg(motg->phy.dev, "Requested curr from USB = %u, max-type-c:%u\n",
+ mA, motg->typec_current_max);
+ /* Save bc1.2 max_curr if type-c charger later moves to diff mode */
+ motg->bc1p2_current_max = mA;
+
+ /*
+ * Limit type-c charger current to 500 for SDP charger to avoid more
+ * current drawn than 500 with Hosts that don't support type C due to
+ * non compliant type-c to standard A cables.
+ */
+ if (pdata->enable_sdp_typec_current_limit &&
+ (motg->chg_type == USB_SDP_CHARGER) &&
+ motg->typec_current_max > 500)
+ motg->typec_current_max = 500;
+
+ /* Override mA if type-c charger used (use hvdcp/bc1.2 if it is 500) */
+ if (motg->typec_current_max > 500 && mA < motg->typec_current_max)
+ mA = motg->typec_current_max;
+
+ if (msm_otg_notify_chg_type(motg))
+ dev_err(motg->phy.dev,
+ "Failed notifying %d charger type to PMIC\n",
+ motg->chg_type);
+
+ /*
+ * This condition will be true when usb cable is disconnected
+ * during bootup before enumeration. Check charger type also
+ * to avoid clearing online flag in case of valid charger.
+ */
+ if (motg->online && motg->cur_power == 0 && mA == 0 &&
+ (motg->chg_type == USB_INVALID_CHARGER))
+ msm_otg_set_online_status(motg);
+
+ if (motg->cur_power == mA)
+ return;
+
+ dev_info(motg->phy.dev, "Avail curr from USB = %u\n", mA);
+ msm_otg_dbg_log_event(&motg->phy, "AVAIL CURR FROM USB",
+ mA, motg->chg_type);
+
+ msm_otg_notify_power_supply(motg, mA);
+
+ motg->cur_power = mA;
+}
+
+static int msm_otg_set_power(struct usb_phy *phy, unsigned int mA)
+{
+ struct msm_otg *motg = container_of(phy, struct msm_otg, phy);
+
+ /*
+ * Gadget driver uses set_power method to notify about the
+ * available current based on suspend/configured states.
+ *
+ * IDEV_CHG can be drawn irrespective of suspend/un-configured
+ * states when CDP/ACA is connected.
+ */
+ if (motg->chg_type == USB_SDP_CHARGER)
+ msm_otg_notify_charger(motg, mA);
+
+ return 0;
+}
+
+static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on);
+
+static void msm_otg_perf_vote_update(struct msm_otg *motg, bool perf_mode)
+{
+ static bool curr_perf_mode;
+ int ret, latency = motg->pm_qos_latency;
+ long clk_rate;
+
+ if (curr_perf_mode == perf_mode)
+ return;
+
+ if (perf_mode) {
+ if (latency)
+ pm_qos_update_request(&motg->pm_qos_req_dma, latency);
+ msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE);
+ clk_rate = motg->core_clk_rate;
+ } else {
+ if (latency)
+ pm_qos_update_request(&motg->pm_qos_req_dma,
+ PM_QOS_DEFAULT_VALUE);
+ msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE);
+ clk_rate = motg->core_clk_svs_rate;
+ }
+
+ if (clk_rate) {
+ ret = clk_set_rate(motg->core_clk, clk_rate);
+ if (ret)
+ dev_err(motg->phy.dev, "sys_clk set_rate fail:%d %ld\n",
+ ret, clk_rate);
+ }
+ curr_perf_mode = perf_mode;
+ pr_debug("%s: latency updated to: %d, core_freq to: %ld\n", __func__,
+ latency, clk_rate);
+}
+
+static void msm_otg_perf_vote_work(struct work_struct *w)
+{
+ struct msm_otg *motg = container_of(w, struct msm_otg,
+ perf_vote_work.work);
+ unsigned int curr_sample_int_count;
+ bool in_perf_mode = false;
+
+ curr_sample_int_count = motg->usb_irq_count;
+ motg->usb_irq_count = 0;
+
+ if (curr_sample_int_count >= PM_QOS_THRESHOLD)
+ in_perf_mode = true;
+
+ msm_otg_perf_vote_update(motg, in_perf_mode);
+ pr_debug("%s: in_perf_mode:%u, interrupts in last sample:%u\n",
+ __func__, in_perf_mode, curr_sample_int_count);
+
+ schedule_delayed_work(&motg->perf_vote_work,
+ msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC));
+}
+
+static void msm_otg_start_host(struct usb_otg *otg, int on)
+{
+ struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy);
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ struct usb_hcd *hcd;
+ u32 val;
+
+ if (!otg->host)
+ return;
+
+ hcd = bus_to_hcd(otg->host);
+
+ msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost GET",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ if (on) {
+ dev_dbg(otg->usb_phy->dev, "host on\n");
+ msm_otg_dbg_log_event(&motg->phy, "HOST ON",
+ motg->inputs, otg->state);
+ msm_hsusb_vbus_power(motg, 1);
+ msm_otg_reset(&motg->phy);
+
+ if (pdata->otg_control == OTG_PHY_CONTROL)
+ ulpi_write(otg->usb_phy, OTG_COMP_DISABLE,
+ ULPI_SET(ULPI_PWR_CLK_MNG_REG));
+
+ if (pdata->enable_axi_prefetch) {
+ val = readl_relaxed(USB_HS_APF_CTRL);
+ val &= ~APF_CTRL_EN;
+ writel_relaxed(val, USB_HS_APF_CTRL);
+ }
+ usb_add_hcd(hcd, hcd->irq, IRQF_SHARED);
+#ifdef CONFIG_SMP
+ motg->pm_qos_req_dma.type = PM_QOS_REQ_AFFINE_IRQ;
+ motg->pm_qos_req_dma.irq = motg->irq;
+#endif
+ pm_qos_add_request(&motg->pm_qos_req_dma,
+ PM_QOS_CPU_DMA_LATENCY, PM_QOS_DEFAULT_VALUE);
+ /* start in perf mode for better performance initially */
+ msm_otg_perf_vote_update(motg, true);
+ schedule_delayed_work(&motg->perf_vote_work,
+ msecs_to_jiffies(1000 * PM_QOS_SAMPLE_SEC));
+ } else {
+ dev_dbg(otg->usb_phy->dev, "host off\n");
+ msm_otg_dbg_log_event(&motg->phy, "HOST OFF",
+ motg->inputs, otg->state);
+ msm_hsusb_vbus_power(motg, 0);
+
+ cancel_delayed_work_sync(&motg->perf_vote_work);
+ msm_otg_perf_vote_update(motg, false);
+ pm_qos_remove_request(&motg->pm_qos_req_dma);
+
+ pm_runtime_disable(&hcd->self.root_hub->dev);
+ pm_runtime_barrier(&hcd->self.root_hub->dev);
+ usb_remove_hcd(hcd);
+ msm_otg_reset(&motg->phy);
+
+ if (pdata->enable_axi_prefetch)
+ writel_relaxed(readl_relaxed(USB_HS_APF_CTRL)
+ | (APF_CTRL_EN), USB_HS_APF_CTRL);
+
+ /* HCD core reset all bits of PORTSC. select ULPI phy */
+ writel_relaxed(0x80000000, USB_PORTSC);
+
+ if (pdata->otg_control == OTG_PHY_CONTROL)
+ ulpi_write(otg->usb_phy, OTG_COMP_DISABLE,
+ ULPI_CLR(ULPI_PWR_CLK_MNG_REG));
+ }
+ msm_otg_dbg_log_event(&motg->phy, "PM RT: StartHost PUT",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+
+ pm_runtime_mark_last_busy(otg->usb_phy->dev);
+ pm_runtime_put_autosuspend(otg->usb_phy->dev);
+}
+
+static void msm_hsusb_vbus_power(struct msm_otg *motg, bool on)
+{
+ int ret;
+ static bool vbus_is_on;
+
+ msm_otg_dbg_log_event(&motg->phy, "VBUS POWER", on, vbus_is_on);
+ if (vbus_is_on == on)
+ return;
+
+ if (motg->pdata->vbus_power) {
+ ret = motg->pdata->vbus_power(on);
+ if (!ret)
+ vbus_is_on = on;
+ return;
+ }
+
+ if (!vbus_otg) {
+ pr_err("vbus_otg is NULL.");
+ return;
+ }
+
+ /*
+ * if entering host mode tell the charger to not draw any current
+ * from usb before turning on the boost.
+ * if exiting host mode disable the boost before enabling to draw
+ * current from the source.
+ */
+ if (on) {
+ msm_otg_notify_host_mode(motg, on);
+ ret = regulator_enable(vbus_otg);
+ if (ret) {
+ pr_err("unable to enable vbus_otg\n");
+ return;
+ }
+ vbus_is_on = true;
+ } else {
+ ret = regulator_disable(vbus_otg);
+ if (ret) {
+ pr_err("unable to disable vbus_otg\n");
+ return;
+ }
+ msm_otg_notify_host_mode(motg, on);
+ vbus_is_on = false;
+ }
+}
+
+static int msm_otg_set_host(struct usb_otg *otg, struct usb_bus *host)
+{
+ struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy);
+ struct usb_hcd *hcd;
+
+ /*
+ * Fail host registration if this board can support
+ * only peripheral configuration.
+ */
+ if (motg->pdata->mode == USB_PERIPHERAL) {
+ dev_info(otg->usb_phy->dev, "Host mode is not supported\n");
+ return -ENODEV;
+ }
+
+ if (!motg->pdata->vbus_power && host) {
+ vbus_otg = devm_regulator_get(motg->phy.dev, "vbus_otg");
+ if (IS_ERR(vbus_otg)) {
+ msm_otg_dbg_log_event(&motg->phy,
+ "UNABLE TO GET VBUS_OTG",
+ otg->state, 0);
+ pr_err("Unable to get vbus_otg\n");
+ return PTR_ERR(vbus_otg);
+ }
+ }
+
+ if (!host) {
+ if (otg->state == OTG_STATE_A_HOST) {
+ msm_otg_start_host(otg, 0);
+ otg->host = NULL;
+ otg->state = OTG_STATE_UNDEFINED;
+ queue_work(motg->otg_wq, &motg->sm_work);
+ } else {
+ otg->host = NULL;
+ }
+
+ return 0;
+ }
+
+ hcd = bus_to_hcd(host);
+ hcd->power_budget = motg->pdata->power_budget;
+
+ otg->host = host;
+ dev_dbg(otg->usb_phy->dev, "host driver registered w/ tranceiver\n");
+ msm_otg_dbg_log_event(&motg->phy, "HOST DRIVER REGISTERED",
+ hcd->power_budget, motg->pdata->mode);
+
+ /*
+ * Kick the state machine work, if peripheral is not supported
+ * or peripheral is already registered with us.
+ */
+ if (motg->pdata->mode == USB_HOST || otg->gadget)
+ queue_work(motg->otg_wq, &motg->sm_work);
+
+ return 0;
+}
+
+static void msm_otg_start_peripheral(struct usb_otg *otg, int on)
+{
+ struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy);
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ struct pinctrl_state *set_state;
+ int ret;
+
+ if (!otg->gadget)
+ return;
+
+ msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri GET",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ if (on) {
+ dev_dbg(otg->usb_phy->dev, "gadget on\n");
+ msm_otg_dbg_log_event(&motg->phy, "GADGET ON",
+ motg->inputs, otg->state);
+
+ /* Configure BUS performance parameters for MAX bandwidth */
+ if (debug_bus_voting_enabled)
+ msm_otg_bus_vote(motg, USB_MAX_PERF_VOTE);
+ /* bump up usb core_clk to default */
+ clk_set_rate(motg->core_clk, motg->core_clk_rate);
+
+ usb_gadget_vbus_connect(otg->gadget);
+
+ /*
+ * Request VDD min gpio, if need to support VDD
+ * minimazation during peripheral bus suspend.
+ */
+ if (pdata->vddmin_gpio) {
+ if (motg->phy_pinctrl) {
+ set_state =
+ pinctrl_lookup_state(motg->phy_pinctrl,
+ "hsusb_active");
+ if (IS_ERR(set_state)) {
+ pr_err("cannot get phy pinctrl active state\n");
+ } else {
+ pinctrl_select_state(motg->phy_pinctrl,
+ set_state);
+ }
+ }
+
+ ret = gpio_request(pdata->vddmin_gpio,
+ "MSM_OTG_VDD_MIN_GPIO");
+ if (ret < 0) {
+ dev_err(otg->usb_phy->dev, "gpio req failed for vdd min:%d\n",
+ ret);
+ pdata->vddmin_gpio = 0;
+ }
+ }
+ } else {
+ dev_dbg(otg->usb_phy->dev, "gadget off\n");
+ msm_otg_dbg_log_event(&motg->phy, "GADGET OFF",
+ motg->inputs, otg->state);
+ usb_gadget_vbus_disconnect(otg->gadget);
+ clear_bit(A_BUS_SUSPEND, &motg->inputs);
+ /* Configure BUS performance parameters to default */
+ msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE);
+
+ if (pdata->vddmin_gpio) {
+ gpio_free(pdata->vddmin_gpio);
+ if (motg->phy_pinctrl) {
+ set_state =
+ pinctrl_lookup_state(motg->phy_pinctrl,
+ "hsusb_sleep");
+ if (IS_ERR(set_state))
+ pr_err("cannot get phy pinctrl sleep state\n");
+ else
+ pinctrl_select_state(motg->phy_pinctrl,
+ set_state);
+ }
+ }
+ }
+ msm_otg_dbg_log_event(&motg->phy, "PM RT: StartPeri PUT",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+ pm_runtime_mark_last_busy(otg->usb_phy->dev);
+ pm_runtime_put_autosuspend(otg->usb_phy->dev);
+}
+
+static int msm_otg_set_peripheral(struct usb_otg *otg,
+ struct usb_gadget *gadget)
+{
+ struct msm_otg *motg = container_of(otg->usb_phy, struct msm_otg, phy);
+
+ /*
+ * Fail peripheral registration if this board can support
+ * only host configuration.
+ */
+ if (motg->pdata->mode == USB_HOST) {
+ dev_info(otg->usb_phy->dev, "Peripheral mode is not supported\n");
+ return -ENODEV;
+ }
+
+ if (!gadget) {
+ if (otg->state == OTG_STATE_B_PERIPHERAL) {
+ msm_otg_dbg_log_event(&motg->phy,
+ "PM RUNTIME: PERIPHERAL GET1",
+ get_pm_runtime_counter(otg->usb_phy->dev), 0);
+ msm_otg_start_peripheral(otg, 0);
+ otg->gadget = NULL;
+ otg->state = OTG_STATE_UNDEFINED;
+ queue_work(motg->otg_wq, &motg->sm_work);
+ } else {
+ otg->gadget = NULL;
+ }
+
+ return 0;
+ }
+ otg->gadget = gadget;
+ dev_dbg(otg->usb_phy->dev, "peripheral driver registered w/ tranceiver\n");
+ msm_otg_dbg_log_event(&motg->phy, "PERIPHERAL DRIVER REGISTERED",
+ otg->state, motg->pdata->mode);
+
+ /*
+ * Kick the state machine work, if host is not supported
+ * or host is already registered with us.
+ */
+ if (motg->pdata->mode == USB_PERIPHERAL || otg->host)
+ queue_work(motg->otg_wq, &motg->sm_work);
+
+ return 0;
+}
+
+static bool msm_otg_read_pmic_id_state(struct msm_otg *motg)
+{
+ unsigned long flags;
+ bool id;
+ int ret;
+
+ if (!motg->pdata->pmic_id_irq)
+ return -ENODEV;
+
+ local_irq_save(flags);
+ ret = irq_get_irqchip_state(motg->pdata->pmic_id_irq,
+ IRQCHIP_STATE_LINE_LEVEL, &id);
+ local_irq_restore(flags);
+
+ /*
+ * If we can not read ID line state for some reason, treat
+ * it as float. This would prevent MHL discovery and kicking
+ * host mode unnecessarily.
+ */
+ if (ret < 0)
+ return true;
+
+ return !!id;
+}
+
+static bool msm_otg_read_phy_id_state(struct msm_otg *motg)
+{
+ u8 val;
+
+ /*
+ * clear the pending/outstanding interrupts and
+ * read the ID status from the SRC_STATUS register.
+ */
+ writeb_relaxed(USB_PHY_ID_MASK, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1);
+
+ writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD);
+ /*
+ * Databook says 200 usec delay is required for
+ * clearing the interrupts.
+ */
+ udelay(200);
+ writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD);
+
+ val = readb_relaxed(USB2_PHY_USB_PHY_INTERRUPT_SRC_STATUS);
+ if (val & USB_PHY_IDDIG_1_0)
+ return false; /* ID is grounded */
+ else
+ return true;
+}
+
+static void msm_otg_chg_check_timer_func(unsigned long data)
+{
+ struct msm_otg *motg = (struct msm_otg *) data;
+ struct usb_otg *otg = motg->phy.otg;
+
+ if (atomic_read(&motg->in_lpm) ||
+ !test_bit(B_SESS_VLD, &motg->inputs) ||
+ otg->state != OTG_STATE_B_PERIPHERAL ||
+ otg->gadget->speed != USB_SPEED_UNKNOWN) {
+ dev_dbg(otg->usb_phy->dev, "Nothing to do in chg_check_timer\n");
+ return;
+ }
+
+ if ((readl_relaxed(USB_PORTSC) & PORTSC_LS) == PORTSC_LS) {
+ dev_dbg(otg->usb_phy->dev, "DCP is detected as SDP\n");
+ msm_otg_dbg_log_event(&motg->phy, "DCP IS DETECTED AS SDP",
+ otg->state, 0);
+ set_bit(B_FALSE_SDP, &motg->inputs);
+ queue_work(motg->otg_wq, &motg->sm_work);
+ }
+}
+
+static bool msm_chg_check_secondary_det(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ u32 chg_det;
+ bool ret = false;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ chg_det = ulpi_read(phy, 0x87);
+ ret = chg_det & 1;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void msm_chg_enable_secondary_det(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ /*
+ * Configure DM as current source, DP as current sink
+ * and enable battery charging comparators.
+ */
+ ulpi_write(phy, 0x8, 0x85);
+ ulpi_write(phy, 0x2, 0x85);
+ ulpi_write(phy, 0x1, 0x85);
+ break;
+ default:
+ break;
+ }
+}
+
+static bool msm_chg_check_primary_det(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ u32 chg_det;
+ bool ret = false;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ chg_det = ulpi_read(phy, 0x87);
+ ret = chg_det & 1;
+ /* Turn off VDP_SRC */
+ ulpi_write(phy, 0x3, 0x86);
+ msleep(20);
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void msm_chg_enable_primary_det(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ /*
+ * Configure DP as current source, DM as current sink
+ * and enable battery charging comparators.
+ */
+ ulpi_write(phy, 0x2, 0x85);
+ ulpi_write(phy, 0x1, 0x85);
+ break;
+ default:
+ break;
+ }
+}
+
+static bool msm_chg_check_dcd(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ u32 line_state;
+ bool ret = false;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ line_state = ulpi_read(phy, 0x87);
+ ret = line_state & 2;
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+static void msm_chg_disable_dcd(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ ulpi_write(phy, 0x10, 0x86);
+ break;
+ case SNPS_FEMTO_PHY:
+ ulpi_write(phy, 0x10, 0x86);
+ /*
+ * Disable the Rdm_down after
+ * the DCD is completed.
+ */
+ ulpi_write(phy, 0x04, 0x0C);
+ break;
+ default:
+ break;
+ }
+}
+
+static void msm_chg_enable_dcd(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ /* Data contact detection enable */
+ ulpi_write(phy, 0x10, 0x85);
+ break;
+ case SNPS_FEMTO_PHY:
+ /*
+ * Idp_src and Rdm_down are de-coupled
+ * on Femto PHY. If Idp_src alone is
+ * enabled, DCD timeout is observed with
+ * wall charger. But a genuine DCD timeout
+ * may be incorrectly interpreted. Also
+ * BC1.2 compliance testers expect Rdm_down
+ * to enabled during DCD. Enable Rdm_down
+ * explicitly before enabling the DCD.
+ */
+ ulpi_write(phy, 0x04, 0x0B);
+ ulpi_write(phy, 0x10, 0x85);
+ break;
+ default:
+ break;
+ }
+}
+
+static void msm_chg_block_on(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ u32 func_ctrl;
+
+ /* put the controller in non-driving mode */
+ func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL);
+ func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
+ func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NONDRIVING;
+ ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL);
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ /* disable DP and DM pull down resistors */
+ ulpi_write(phy, 0x6, 0xC);
+ /* Clear charger detecting control bits */
+ ulpi_write(phy, 0x1F, 0x86);
+ /* Clear alt interrupt latch and enable bits */
+ ulpi_write(phy, 0x1F, 0x92);
+ ulpi_write(phy, 0x1F, 0x95);
+ udelay(100);
+ break;
+ default:
+ break;
+ }
+}
+
+static void msm_chg_block_off(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ u32 func_ctrl;
+
+ switch (motg->pdata->phy_type) {
+ case SNPS_PICO_PHY:
+ case SNPS_FEMTO_PHY:
+ /* Clear charger detecting control bits */
+ ulpi_write(phy, 0x3F, 0x86);
+ /* Clear alt interrupt latch and enable bits */
+ ulpi_write(phy, 0x1F, 0x92);
+ ulpi_write(phy, 0x1F, 0x95);
+ /* re-enable DP and DM pull down resistors */
+ ulpi_write(phy, 0x6, 0xB);
+ break;
+ default:
+ break;
+ }
+
+ /* put the controller in normal mode */
+ func_ctrl = ulpi_read(phy, ULPI_FUNC_CTRL);
+ func_ctrl &= ~ULPI_FUNC_CTRL_OPMODE_MASK;
+ func_ctrl |= ULPI_FUNC_CTRL_OPMODE_NORMAL;
+ ulpi_write(phy, func_ctrl, ULPI_FUNC_CTRL);
+}
+
+static const char *chg_to_string(enum usb_chg_type chg_type)
+{
+ switch (chg_type) {
+ case USB_SDP_CHARGER: return "USB_SDP_CHARGER";
+ case USB_DCP_CHARGER: return "USB_DCP_CHARGER";
+ case USB_CDP_CHARGER: return "USB_CDP_CHARGER";
+ case USB_NONCOMPLIANT_CHARGER: return "USB_NONCOMPLIANT_CHARGER";
+ case USB_FLOATED_CHARGER: return "USB_FLOATED_CHARGER";
+ default: return "INVALID_CHARGER";
+ }
+}
+
+#define MSM_CHG_DCD_TIMEOUT (750 * HZ/1000) /* 750 msec */
+#define MSM_CHG_DCD_POLL_TIME (50 * HZ/1000) /* 50 msec */
+#define MSM_CHG_PRIMARY_DET_TIME (50 * HZ/1000) /* TVDPSRC_ON */
+#define MSM_CHG_SECONDARY_DET_TIME (50 * HZ/1000) /* TVDMSRC_ON */
+static void msm_chg_detect_work(struct work_struct *w)
+{
+ struct msm_otg *motg = container_of(w, struct msm_otg, chg_work.work);
+ struct usb_phy *phy = &motg->phy;
+ bool is_dcd = false, tmout, vout;
+ static bool dcd;
+ u32 line_state, dm_vlgc;
+ unsigned long delay;
+
+ dev_dbg(phy->dev, "chg detection work\n");
+ msm_otg_dbg_log_event(phy, "CHG DETECTION WORK",
+ motg->chg_state, get_pm_runtime_counter(phy->dev));
+
+ switch (motg->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ case USB_CHG_STATE_IN_PROGRESS:
+ msm_chg_block_on(motg);
+ msm_chg_enable_dcd(motg);
+ motg->chg_state = USB_CHG_STATE_WAIT_FOR_DCD;
+ motg->dcd_time = 0;
+ delay = MSM_CHG_DCD_POLL_TIME;
+ break;
+ case USB_CHG_STATE_WAIT_FOR_DCD:
+ is_dcd = msm_chg_check_dcd(motg);
+ motg->dcd_time += MSM_CHG_DCD_POLL_TIME;
+ tmout = motg->dcd_time >= MSM_CHG_DCD_TIMEOUT;
+ if (is_dcd || tmout) {
+ if (is_dcd)
+ dcd = true;
+ else
+ dcd = false;
+ msm_chg_disable_dcd(motg);
+ msm_chg_enable_primary_det(motg);
+ delay = MSM_CHG_PRIMARY_DET_TIME;
+ motg->chg_state = USB_CHG_STATE_DCD_DONE;
+ } else {
+ delay = MSM_CHG_DCD_POLL_TIME;
+ }
+ break;
+ case USB_CHG_STATE_DCD_DONE:
+ vout = msm_chg_check_primary_det(motg);
+ line_state = readl_relaxed(USB_PORTSC) & PORTSC_LS;
+ dm_vlgc = line_state & PORTSC_LS_DM;
+ if (vout && !dm_vlgc) { /* VDAT_REF < DM < VLGC */
+ if (line_state) { /* DP > VLGC */
+ motg->chg_type = USB_NONCOMPLIANT_CHARGER;
+ motg->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ } else {
+ msm_chg_enable_secondary_det(motg);
+ delay = MSM_CHG_SECONDARY_DET_TIME;
+ motg->chg_state = USB_CHG_STATE_PRIMARY_DONE;
+ }
+ } else { /* DM < VDAT_REF || DM > VLGC */
+ if (line_state) /* DP > VLGC or/and DM > VLGC */
+ motg->chg_type = USB_NONCOMPLIANT_CHARGER;
+ else if (!dcd && floated_charger_enable)
+ motg->chg_type = USB_FLOATED_CHARGER;
+ else
+ motg->chg_type = USB_SDP_CHARGER;
+
+ motg->chg_state = USB_CHG_STATE_DETECTED;
+ delay = 0;
+ goto state_detected;
+ }
+ break;
+ case USB_CHG_STATE_PRIMARY_DONE:
+ vout = msm_chg_check_secondary_det(motg);
+ if (vout)
+ motg->chg_type = USB_DCP_CHARGER;
+ else
+ motg->chg_type = USB_CDP_CHARGER;
+ motg->chg_state = USB_CHG_STATE_SECONDARY_DONE;
+ /* fall through */
+ case USB_CHG_STATE_SECONDARY_DONE:
+ motg->chg_state = USB_CHG_STATE_DETECTED;
+ case USB_CHG_STATE_DETECTED:
+state_detected:
+ /*
+ * Notify the charger type to power supply
+ * owner as soon as we determine the charger.
+ */
+ if (motg->chg_type == USB_DCP_CHARGER && motg->ext_chg_opened) {
+ init_completion(&motg->ext_chg_wait);
+ motg->ext_chg_active = DEFAULT;
+ }
+ msm_otg_notify_chg_type(motg);
+ msm_chg_block_off(motg);
+
+ /* Enable VDP_SRC in case of DCP charger */
+ if (motg->chg_type == USB_DCP_CHARGER)
+ ulpi_write(phy, 0x2, 0x85);
+
+ dev_dbg(phy->dev, "chg_type = %s\n",
+ chg_to_string(motg->chg_type));
+ msm_otg_dbg_log_event(phy, "CHG WORK PUT: CHG_TYPE",
+ motg->chg_type, get_pm_runtime_counter(phy->dev));
+ /* to match _get from sm_work before starting chg_det_work */
+ pm_runtime_mark_last_busy(phy->dev);
+ pm_runtime_put_autosuspend(phy->dev);
+
+ queue_work(motg->otg_wq, &motg->sm_work);
+ return;
+ default:
+ return;
+ }
+
+ msm_otg_dbg_log_event(phy, "CHG WORK: QUEUE", motg->chg_type, delay);
+ queue_delayed_work(motg->otg_wq, &motg->chg_work, delay);
+}
+
+#define VBUS_INIT_TIMEOUT msecs_to_jiffies(5000)
+
+/*
+ * We support OTG, Peripheral only and Host only configurations. In case
+ * of OTG, mode switch (host-->peripheral/peripheral-->host) can happen
+ * via Id pin status or user request (debugfs). Id/BSV interrupts are not
+ * enabled when switch is controlled by user and default mode is supplied
+ * by board file, which can be changed by userspace later.
+ */
+static void msm_otg_init_sm(struct msm_otg *motg)
+{
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ u32 otgsc = readl_relaxed(USB_OTGSC);
+ int ret;
+
+ switch (pdata->mode) {
+ case USB_OTG:
+ if (pdata->otg_control == OTG_USER_CONTROL) {
+ if (pdata->default_mode == USB_HOST) {
+ clear_bit(ID, &motg->inputs);
+ } else if (pdata->default_mode == USB_PERIPHERAL) {
+ set_bit(ID, &motg->inputs);
+ set_bit(B_SESS_VLD, &motg->inputs);
+ } else {
+ set_bit(ID, &motg->inputs);
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ }
+ } else if (pdata->otg_control == OTG_PHY_CONTROL) {
+ if (otgsc & OTGSC_ID)
+ set_bit(ID, &motg->inputs);
+ else
+ clear_bit(ID, &motg->inputs);
+ if (otgsc & OTGSC_BSV)
+ set_bit(B_SESS_VLD, &motg->inputs);
+ else
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ } else if (pdata->otg_control == OTG_PMIC_CONTROL) {
+ if (pdata->pmic_id_irq) {
+ if (msm_otg_read_pmic_id_state(motg))
+ set_bit(ID, &motg->inputs);
+ else
+ clear_bit(ID, &motg->inputs);
+ } else if (motg->ext_id_irq) {
+ if (gpio_get_value(pdata->usb_id_gpio))
+ set_bit(ID, &motg->inputs);
+ else
+ clear_bit(ID, &motg->inputs);
+ } else if (motg->phy_irq) {
+ if (msm_otg_read_phy_id_state(motg))
+ set_bit(ID, &motg->inputs);
+ else
+ clear_bit(ID, &motg->inputs);
+ }
+ /*
+ * VBUS initial state is reported after PMIC
+ * driver initialization. Wait for it.
+ */
+ ret = wait_for_completion_timeout(&pmic_vbus_init,
+ VBUS_INIT_TIMEOUT);
+ if (!ret) {
+ dev_dbg(motg->phy.dev, "%s: timeout waiting for PMIC VBUS\n",
+ __func__);
+ msm_otg_dbg_log_event(&motg->phy,
+ "PMIC VBUS WAIT TMOUT", motg->inputs,
+ motg->phy.otg->state);
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ pmic_vbus_init.done = 1;
+ }
+ }
+ break;
+ case USB_HOST:
+ clear_bit(ID, &motg->inputs);
+ break;
+ case USB_PERIPHERAL:
+ set_bit(ID, &motg->inputs);
+ if (pdata->otg_control == OTG_PHY_CONTROL) {
+ if (otgsc & OTGSC_BSV)
+ set_bit(B_SESS_VLD, &motg->inputs);
+ else
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ } else if (pdata->otg_control == OTG_PMIC_CONTROL) {
+ /*
+ * VBUS initial state is reported after PMIC
+ * driver initialization. Wait for it.
+ */
+ ret = wait_for_completion_timeout(&pmic_vbus_init,
+ VBUS_INIT_TIMEOUT);
+ if (!ret) {
+ dev_dbg(motg->phy.dev, "%s: timeout waiting for PMIC VBUS\n",
+ __func__);
+ msm_otg_dbg_log_event(&motg->phy,
+ "PMIC VBUS WAIT TMOUT", motg->inputs,
+ motg->phy.otg->state);
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ pmic_vbus_init.done = 1;
+ }
+ } else if (pdata->otg_control == OTG_USER_CONTROL) {
+ set_bit(ID, &motg->inputs);
+ set_bit(B_SESS_VLD, &motg->inputs);
+ }
+ break;
+ default:
+ break;
+ }
+ msm_otg_dbg_log_event(&motg->phy, "SM INIT", pdata->mode, motg->inputs);
+ if (motg->id_state != USB_ID_GROUND)
+ motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT :
+ USB_ID_GROUND;
+}
+
+static void msm_otg_wait_for_ext_chg_done(struct msm_otg *motg)
+{
+ struct usb_phy *phy = &motg->phy;
+ unsigned long t;
+
+ /*
+ * Defer next cable connect event till external charger
+ * detection is completed.
+ */
+
+ if (motg->ext_chg_active == ACTIVE) {
+
+do_wait:
+ pr_debug("before msm_otg ext chg wait\n");
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: WAIT", 0, 0);
+
+ t = wait_for_completion_timeout(&motg->ext_chg_wait,
+ msecs_to_jiffies(3000));
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: DONE", t, 0);
+
+ if (!t)
+ pr_err("msm_otg ext chg wait timeout\n");
+ else if (motg->ext_chg_active == ACTIVE)
+ goto do_wait;
+ else
+ pr_debug("msm_otg ext chg wait done\n");
+ }
+
+ if (motg->ext_chg_opened) {
+ if (phy->flags & ENABLE_DP_MANUAL_PULLUP) {
+ ulpi_write(phy, ULPI_MISC_A_VBUSVLDEXT |
+ ULPI_MISC_A_VBUSVLDEXTSEL,
+ ULPI_CLR(ULPI_MISC_A));
+ }
+ /* clear charging register bits */
+ ulpi_write(phy, 0x3F, 0x86);
+ /* re-enable DP and DM pull-down resistors*/
+ ulpi_write(phy, 0x6, 0xB);
+ }
+}
+
+static void msm_otg_sm_work(struct work_struct *w)
+{
+ struct msm_otg *motg = container_of(w, struct msm_otg, sm_work);
+ struct usb_otg *otg = motg->phy.otg;
+ struct device *dev = otg->usb_phy->dev;
+ bool work = 0, dcp;
+ int ret;
+
+ pr_debug("%s work\n", usb_otg_state_string(otg->state));
+ msm_otg_dbg_log_event(&motg->phy, "SM WORK:",
+ otg->state, motg->inputs);
+
+ /* Just resume h/w if reqd, pm_count is handled based on state/inputs */
+ if (motg->resume_pending) {
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ if (atomic_read(&motg->in_lpm)) {
+ dev_err(dev, "SM WORK: USB is in LPM\n");
+ msm_otg_dbg_log_event(&motg->phy,
+ "SM WORK: USB IS IN LPM",
+ otg->state, motg->inputs);
+ msm_otg_resume(motg);
+ }
+ motg->resume_pending = false;
+ pm_runtime_put_noidle(otg->usb_phy->dev);
+ }
+
+ switch (otg->state) {
+ case OTG_STATE_UNDEFINED:
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ msm_otg_reset(otg->usb_phy);
+ /* Add child device only after block reset */
+ ret = of_platform_populate(motg->pdev->dev.of_node, NULL, NULL,
+ &motg->pdev->dev);
+ if (ret)
+ dev_dbg(&motg->pdev->dev, "failed to add BAM core\n");
+
+ msm_otg_init_sm(motg);
+ otg->state = OTG_STATE_B_IDLE;
+ if (!test_bit(B_SESS_VLD, &motg->inputs) &&
+ test_bit(ID, &motg->inputs)) {
+ msm_otg_dbg_log_event(&motg->phy,
+ "PM RUNTIME: UNDEF PUT",
+ get_pm_runtime_counter(otg->usb_phy->dev), 0);
+ pm_runtime_put_sync(otg->usb_phy->dev);
+ break;
+ }
+ pm_runtime_put(otg->usb_phy->dev);
+ /* FALL THROUGH */
+ case OTG_STATE_B_IDLE:
+ if (!test_bit(ID, &motg->inputs) && otg->host) {
+ pr_debug("!id\n");
+ msm_otg_dbg_log_event(&motg->phy, "!ID",
+ motg->inputs, otg->state);
+
+ msm_otg_start_host(otg, 1);
+ otg->state = OTG_STATE_A_HOST;
+ } else if (test_bit(B_SESS_VLD, &motg->inputs)) {
+ pr_debug("b_sess_vld\n");
+ msm_otg_dbg_log_event(&motg->phy, "B_SESS_VLD",
+ motg->inputs, otg->state);
+ switch (motg->chg_state) {
+ case USB_CHG_STATE_UNDEFINED:
+ /* put at the end of chg_det or disconnect */
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ msm_otg_dbg_log_event(&motg->phy, "PM CHG GET",
+ get_pm_runtime_counter(dev), 0);
+ motg->chg_state = USB_CHG_STATE_IN_PROGRESS;
+ msm_chg_detect_work(&motg->chg_work.work);
+ break;
+ case USB_CHG_STATE_DETECTED:
+ switch (motg->chg_type) {
+ case USB_DCP_CHARGER:
+ /* fall through */
+ case USB_NONCOMPLIANT_CHARGER:
+ msm_otg_notify_charger(motg,
+ dcp_max_current);
+ if (!motg->is_ext_chg_dcp)
+ otg->state =
+ OTG_STATE_B_CHARGER;
+ break;
+ case USB_FLOATED_CHARGER:
+ msm_otg_notify_charger(motg,
+ IDEV_CHG_MAX);
+ otg->state = OTG_STATE_B_CHARGER;
+ break;
+ case USB_CDP_CHARGER:
+ msm_otg_notify_charger(motg,
+ IDEV_CHG_MAX);
+ /* fall through */
+ case USB_SDP_CHARGER:
+ pm_runtime_get_sync(otg->usb_phy->dev);
+ msm_otg_start_peripheral(otg, 1);
+ otg->state =
+ OTG_STATE_B_PERIPHERAL;
+ mod_timer(&motg->chg_check_timer,
+ CHG_RECHECK_DELAY);
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ pr_debug("chg_work cancel");
+ msm_otg_dbg_log_event(&motg->phy, "CHG_WORK CANCEL",
+ motg->inputs, otg->state);
+ del_timer_sync(&motg->chg_check_timer);
+ clear_bit(B_FALSE_SDP, &motg->inputs);
+ cancel_delayed_work_sync(&motg->chg_work);
+ /*
+ * Find out whether chg_w couldn't start or finished.
+ * In both the cases, runtime ref_count vote is missing
+ */
+ if (motg->chg_state == USB_CHG_STATE_UNDEFINED ||
+ motg->chg_state == USB_CHG_STATE_DETECTED) {
+ msm_otg_dbg_log_event(&motg->phy, "RT !CHG GET",
+ get_pm_runtime_counter(otg->usb_phy->dev), 0);
+ pm_runtime_get_sync(dev);
+ }
+
+ dcp = (motg->chg_type == USB_DCP_CHARGER);
+ motg->chg_state = USB_CHG_STATE_UNDEFINED;
+ motg->chg_type = USB_INVALID_CHARGER;
+ msm_otg_notify_charger(motg, 0);
+ if (dcp) {
+ if (motg->ext_chg_active == DEFAULT)
+ motg->ext_chg_active = INACTIVE;
+ msm_otg_wait_for_ext_chg_done(motg);
+ /* Turn off VDP_SRC */
+ ulpi_write(otg->usb_phy, 0x2, 0x86);
+ }
+ msm_chg_block_off(motg);
+ msm_otg_dbg_log_event(&motg->phy, "RT: CHG A PUT",
+ get_pm_runtime_counter(otg->usb_phy->dev), 0);
+ /* Delay used only if autosuspend enabled */
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ }
+ break;
+ case OTG_STATE_B_PERIPHERAL:
+ if (test_bit(B_SESS_VLD, &motg->inputs) &&
+ test_bit(B_FALSE_SDP, &motg->inputs)) {
+ pr_debug("B_FALSE_SDP\n");
+ msm_otg_start_peripheral(otg, 0);
+ motg->chg_type = USB_DCP_CHARGER;
+ clear_bit(B_FALSE_SDP, &motg->inputs);
+ otg->state = OTG_STATE_B_IDLE;
+ msm_otg_dbg_log_event(&motg->phy, "B_FALSE_SDP PUT",
+ get_pm_runtime_counter(dev), motg->inputs);
+ pm_runtime_put_sync(dev);
+ /* schedule work to update charging current */
+ work = 1;
+ } else if (!test_bit(B_SESS_VLD, &motg->inputs)) {
+ msm_otg_start_peripheral(otg, 0);
+ msm_otg_dbg_log_event(&motg->phy, "RT PM: B_PERI A PUT",
+ get_pm_runtime_counter(dev), 0);
+ /* _put for _get done on cable connect in B_IDLE */
+ pm_runtime_put_noidle(dev);
+ /* Schedule work to finish cable disconnect processing*/
+ otg->state = OTG_STATE_B_IDLE;
+ work = 1;
+ } else if (test_bit(A_BUS_SUSPEND, &motg->inputs)) {
+ pr_debug("a_bus_suspend\n");
+ msm_otg_dbg_log_event(&motg->phy,
+ "BUS_SUSPEND: PM RT PUT",
+ get_pm_runtime_counter(dev), 0);
+ otg->state = OTG_STATE_B_SUSPEND;
+ /* _get on connect in B_IDLE or host resume in B_SUSP */
+ pm_runtime_mark_last_busy(dev);
+ pm_runtime_put_autosuspend(dev);
+ }
+ break;
+ case OTG_STATE_B_SUSPEND:
+ if (!test_bit(B_SESS_VLD, &motg->inputs)) {
+ msm_otg_start_peripheral(otg, 0);
+ otg->state = OTG_STATE_B_IDLE;
+ /* Schedule work to finish cable disconnect processing*/
+ work = 1;
+ } else if (!test_bit(A_BUS_SUSPEND, &motg->inputs)) {
+ pr_debug("!a_bus_suspend\n");
+ otg->state = OTG_STATE_B_PERIPHERAL;
+ msm_otg_dbg_log_event(&motg->phy,
+ "BUS_RESUME: PM RT GET",
+ get_pm_runtime_counter(dev), 0);
+ pm_runtime_get_sync(dev);
+ }
+ break;
+
+ case OTG_STATE_B_CHARGER:
+ if (test_bit(B_SESS_VLD, &motg->inputs)) {
+ pr_debug("BSV set again\n");
+ msm_otg_dbg_log_event(&motg->phy, "BSV SET AGAIN",
+ motg->inputs, otg->state);
+ } else if (!test_bit(B_SESS_VLD, &motg->inputs)) {
+ otg->state = OTG_STATE_B_IDLE;
+ work = 1;
+ }
+ break;
+ case OTG_STATE_A_HOST:
+ if (test_bit(ID, &motg->inputs)) {
+ msm_otg_start_host(otg, 0);
+ otg->state = OTG_STATE_B_IDLE;
+ work = 1;
+ }
+ break;
+ default:
+ break;
+ }
+
+ if (work)
+ queue_work(motg->otg_wq, &motg->sm_work);
+}
+
+static irqreturn_t msm_otg_irq(int irq, void *data)
+{
+ struct msm_otg *motg = data;
+ struct usb_otg *otg = motg->phy.otg;
+ u32 otgsc = 0;
+ bool work = 0;
+
+ if (atomic_read(&motg->in_lpm)) {
+ pr_debug("OTG IRQ: %d in LPM\n", irq);
+ msm_otg_dbg_log_event(&motg->phy, "OTG IRQ IS IN LPM",
+ irq, otg->state);
+ /*Ignore interrupt if one interrupt already seen in LPM*/
+ if (motg->async_int)
+ return IRQ_HANDLED;
+
+ disable_irq_nosync(irq);
+ motg->async_int = irq;
+ msm_otg_kick_sm_work(motg);
+
+ return IRQ_HANDLED;
+ }
+ motg->usb_irq_count++;
+
+ otgsc = readl_relaxed(USB_OTGSC);
+ if (!(otgsc & (OTGSC_IDIS | OTGSC_BSVIS)))
+ return IRQ_NONE;
+
+ if ((otgsc & OTGSC_IDIS) && (otgsc & OTGSC_IDIE)) {
+ if (otgsc & OTGSC_ID) {
+ dev_dbg(otg->usb_phy->dev, "ID set\n");
+ msm_otg_dbg_log_event(&motg->phy, "ID SET",
+ motg->inputs, otg->state);
+ set_bit(ID, &motg->inputs);
+ } else {
+ dev_dbg(otg->usb_phy->dev, "ID clear\n");
+ msm_otg_dbg_log_event(&motg->phy, "ID CLEAR",
+ motg->inputs, otg->state);
+ clear_bit(ID, &motg->inputs);
+ }
+ work = 1;
+ } else if ((otgsc & OTGSC_BSVIE) && (otgsc & OTGSC_BSVIS)) {
+ if (otgsc & OTGSC_BSV) {
+ dev_dbg(otg->usb_phy->dev, "BSV set\n");
+ msm_otg_dbg_log_event(&motg->phy, "BSV SET",
+ motg->inputs, otg->state);
+ set_bit(B_SESS_VLD, &motg->inputs);
+ } else {
+ dev_dbg(otg->usb_phy->dev, "BSV clear\n");
+ msm_otg_dbg_log_event(&motg->phy, "BSV CLEAR",
+ motg->inputs, otg->state);
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ clear_bit(A_BUS_SUSPEND, &motg->inputs);
+ }
+ work = 1;
+ }
+ if (work)
+ queue_work(motg->otg_wq, &motg->sm_work);
+
+ writel_relaxed(otgsc, USB_OTGSC);
+
+ return IRQ_HANDLED;
+}
+
+static void msm_otg_set_vbus_state(int online)
+{
+ struct msm_otg *motg = the_msm_otg;
+ static bool init;
+
+ motg->vbus_state = online;
+
+ if (motg->err_event_seen)
+ return;
+
+ if (online) {
+ pr_debug("PMIC: BSV set\n");
+ msm_otg_dbg_log_event(&motg->phy, "PMIC: BSV SET",
+ init, motg->inputs);
+ if (test_and_set_bit(B_SESS_VLD, &motg->inputs) && init)
+ return;
+ } else {
+ pr_debug("PMIC: BSV clear\n");
+ msm_otg_dbg_log_event(&motg->phy, "PMIC: BSV CLEAR",
+ init, motg->inputs);
+ motg->is_ext_chg_dcp = false;
+ if (!test_and_clear_bit(B_SESS_VLD, &motg->inputs) && init)
+ return;
+ }
+
+ /* do not queue state m/c work if id is grounded */
+ if (!test_bit(ID, &motg->inputs) &&
+ !motg->pdata->vbus_low_as_hostmode) {
+ /*
+ * state machine work waits for initial VBUS
+ * completion in UNDEFINED state. Process
+ * the initial VBUS event in ID_GND state.
+ */
+ if (init)
+ return;
+ }
+
+ if (!init) {
+ init = true;
+ if (pmic_vbus_init.done &&
+ test_bit(B_SESS_VLD, &motg->inputs)) {
+ pr_debug("PMIC: BSV came late\n");
+ msm_otg_dbg_log_event(&motg->phy, "PMIC: BSV CAME LATE",
+ init, motg->inputs);
+ goto out;
+ }
+
+ if (motg->pdata->vbus_low_as_hostmode &&
+ !test_bit(B_SESS_VLD, &motg->inputs)) {
+ motg->id_state = USB_ID_GROUND;
+ clear_bit(ID, &motg->inputs);
+ }
+ complete(&pmic_vbus_init);
+ pr_debug("PMIC: BSV init complete\n");
+ msm_otg_dbg_log_event(&motg->phy, "PMIC: BSV INIT COMPLETE",
+ init, motg->inputs);
+ return;
+ }
+
+out:
+ if (motg->is_ext_chg_dcp) {
+ if (test_bit(B_SESS_VLD, &motg->inputs)) {
+ msm_otg_notify_charger(motg, IDEV_CHG_MAX);
+ } else {
+ motg->is_ext_chg_dcp = false;
+ motg->chg_state = USB_CHG_STATE_UNDEFINED;
+ motg->chg_type = USB_INVALID_CHARGER;
+ msm_otg_notify_charger(motg, 0);
+ }
+ return;
+ }
+
+ msm_otg_dbg_log_event(&motg->phy, "CHECK VBUS EVENT DURING SUSPEND",
+ atomic_read(&motg->pm_suspended),
+ motg->sm_work_pending);
+
+ /* Move to host mode on vbus low if required */
+ if (motg->pdata->vbus_low_as_hostmode) {
+ if (!test_bit(B_SESS_VLD, &motg->inputs))
+ clear_bit(ID, &motg->inputs);
+ else
+ set_bit(ID, &motg->inputs);
+ }
+ msm_otg_kick_sm_work(motg);
+}
+
+static void msm_id_status_w(struct work_struct *w)
+{
+ struct msm_otg *motg = container_of(w, struct msm_otg,
+ id_status_work.work);
+ int work = 0;
+
+ dev_dbg(motg->phy.dev, "ID status_w\n");
+
+ if (motg->pdata->pmic_id_irq)
+ motg->id_state = msm_otg_read_pmic_id_state(motg);
+ else if (motg->ext_id_irq)
+ motg->id_state = gpio_get_value(motg->pdata->usb_id_gpio);
+ else if (motg->phy_irq)
+ motg->id_state = msm_otg_read_phy_id_state(motg);
+
+ if (motg->err_event_seen)
+ return;
+
+ if (motg->id_state) {
+ if (gpio_is_valid(motg->pdata->switch_sel_gpio))
+ gpio_direction_input(motg->pdata->switch_sel_gpio);
+ if (!test_and_set_bit(ID, &motg->inputs)) {
+ pr_debug("ID set\n");
+ msm_otg_dbg_log_event(&motg->phy, "ID SET",
+ motg->inputs, motg->phy.otg->state);
+ work = 1;
+ }
+ } else {
+ if (gpio_is_valid(motg->pdata->switch_sel_gpio))
+ gpio_direction_output(motg->pdata->switch_sel_gpio, 1);
+ if (test_and_clear_bit(ID, &motg->inputs)) {
+ pr_debug("ID clear\n");
+ msm_otg_dbg_log_event(&motg->phy, "ID CLEAR",
+ motg->inputs, motg->phy.otg->state);
+ work = 1;
+ }
+ }
+
+ if (work && (motg->phy.otg->state != OTG_STATE_UNDEFINED)) {
+ msm_otg_dbg_log_event(&motg->phy,
+ "CHECK ID EVENT DURING SUSPEND",
+ atomic_read(&motg->pm_suspended),
+ motg->sm_work_pending);
+ msm_otg_kick_sm_work(motg);
+ }
+}
+
+#define MSM_ID_STATUS_DELAY 5 /* 5msec */
+static irqreturn_t msm_id_irq(int irq, void *data)
+{
+ struct msm_otg *motg = data;
+
+ /*schedule delayed work for 5msec for ID line state to settle*/
+ queue_delayed_work(motg->otg_wq, &motg->id_status_work,
+ msecs_to_jiffies(MSM_ID_STATUS_DELAY));
+
+ return IRQ_HANDLED;
+}
+
+int msm_otg_pm_notify(struct notifier_block *notify_block,
+ unsigned long mode, void *unused)
+{
+ struct msm_otg *motg = container_of(
+ notify_block, struct msm_otg, pm_notify);
+
+ dev_dbg(motg->phy.dev, "OTG PM notify:%lx, sm_pending:%u\n", mode,
+ motg->sm_work_pending);
+ msm_otg_dbg_log_event(&motg->phy, "PM NOTIFY",
+ mode, motg->sm_work_pending);
+
+ switch (mode) {
+ case PM_POST_SUSPEND:
+ /* OTG sm_work can be armed now */
+ atomic_set(&motg->pm_suspended, 0);
+
+ /* Handle any deferred wakeup events from USB during suspend */
+ if (motg->sm_work_pending) {
+ motg->sm_work_pending = false;
+ queue_work(motg->otg_wq, &motg->sm_work);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ return NOTIFY_OK;
+}
+
+static int msm_otg_mode_show(struct seq_file *s, void *unused)
+{
+ struct msm_otg *motg = s->private;
+ struct usb_otg *otg = motg->phy.otg;
+
+ switch (otg->state) {
+ case OTG_STATE_A_HOST:
+ seq_puts(s, "host\n");
+ break;
+ case OTG_STATE_B_IDLE:
+ case OTG_STATE_B_PERIPHERAL:
+ case OTG_STATE_B_SUSPEND:
+ seq_puts(s, "peripheral\n");
+ break;
+ default:
+ seq_puts(s, "none\n");
+ break;
+ }
+
+ return 0;
+}
+
+static int msm_otg_mode_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_otg_mode_show, inode->i_private);
+}
+
+static ssize_t msm_otg_mode_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ struct seq_file *s = file->private_data;
+ struct msm_otg *motg = s->private;
+ char buf[16];
+ struct usb_phy *phy = &motg->phy;
+ int status = count;
+ enum usb_mode_type req_mode;
+
+ memset(buf, 0x00, sizeof(buf));
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count))) {
+ status = -EFAULT;
+ goto out;
+ }
+
+ if (!strncmp(buf, "host", 4)) {
+ req_mode = USB_HOST;
+ } else if (!strncmp(buf, "peripheral", 10)) {
+ req_mode = USB_PERIPHERAL;
+ } else if (!strncmp(buf, "none", 4)) {
+ req_mode = USB_NONE;
+ } else {
+ status = -EINVAL;
+ goto out;
+ }
+
+ switch (req_mode) {
+ case USB_NONE:
+ switch (phy->otg->state) {
+ case OTG_STATE_A_HOST:
+ case OTG_STATE_B_PERIPHERAL:
+ case OTG_STATE_B_SUSPEND:
+ set_bit(ID, &motg->inputs);
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ break;
+ default:
+ goto out;
+ }
+ break;
+ case USB_PERIPHERAL:
+ switch (phy->otg->state) {
+ case OTG_STATE_B_IDLE:
+ case OTG_STATE_A_HOST:
+ set_bit(ID, &motg->inputs);
+ set_bit(B_SESS_VLD, &motg->inputs);
+ break;
+ default:
+ goto out;
+ }
+ break;
+ case USB_HOST:
+ switch (phy->otg->state) {
+ case OTG_STATE_B_IDLE:
+ case OTG_STATE_B_PERIPHERAL:
+ case OTG_STATE_B_SUSPEND:
+ clear_bit(ID, &motg->inputs);
+ break;
+ default:
+ goto out;
+ }
+ break;
+ default:
+ goto out;
+ }
+
+ motg->id_state = (test_bit(ID, &motg->inputs)) ? USB_ID_FLOAT :
+ USB_ID_GROUND;
+ queue_work(motg->otg_wq, &motg->sm_work);
+out:
+ return status;
+}
+
+const struct file_operations msm_otg_mode_fops = {
+ .open = msm_otg_mode_open,
+ .read = seq_read,
+ .write = msm_otg_mode_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int msm_otg_show_otg_state(struct seq_file *s, void *unused)
+{
+ struct msm_otg *motg = s->private;
+ struct usb_phy *phy = &motg->phy;
+
+ seq_printf(s, "%s\n", usb_otg_state_string(phy->otg->state));
+ return 0;
+}
+
+static int msm_otg_otg_state_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_otg_show_otg_state, inode->i_private);
+}
+
+const struct file_operations msm_otg_state_fops = {
+ .open = msm_otg_otg_state_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int msm_otg_show_chg_type(struct seq_file *s, void *unused)
+{
+ struct msm_otg *motg = s->private;
+
+ seq_printf(s, "%s\n", chg_to_string(motg->chg_type));
+ return 0;
+}
+
+static int msm_otg_chg_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_otg_show_chg_type, inode->i_private);
+}
+
+const struct file_operations msm_otg_chg_fops = {
+ .open = msm_otg_chg_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int msm_otg_bus_show(struct seq_file *s, void *unused)
+{
+ if (debug_bus_voting_enabled)
+ seq_puts(s, "enabled\n");
+ else
+ seq_puts(s, "disabled\n");
+
+ return 0;
+}
+
+static int msm_otg_bus_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_otg_bus_show, inode->i_private);
+}
+
+static ssize_t msm_otg_bus_write(struct file *file, const char __user *ubuf,
+ size_t count, loff_t *ppos)
+{
+ char buf[8];
+ struct seq_file *s = file->private_data;
+ struct msm_otg *motg = s->private;
+
+ memset(buf, 0x00, sizeof(buf));
+
+ if (copy_from_user(&buf, ubuf, min_t(size_t, sizeof(buf) - 1, count)))
+ return -EFAULT;
+
+ if (!strncmp(buf, "enable", 6)) {
+ /* Do not vote here. Let OTG statemachine decide when to vote */
+ debug_bus_voting_enabled = true;
+ } else {
+ debug_bus_voting_enabled = false;
+ msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE);
+ }
+
+ return count;
+}
+
+static int msm_otg_dbg_buff_show(struct seq_file *s, void *unused)
+{
+ struct msm_otg *motg = s->private;
+ unsigned long flags;
+ unsigned int i;
+
+ read_lock_irqsave(&motg->dbg_lock, flags);
+
+ i = motg->dbg_idx;
+ if (strnlen(motg->buf[i], DEBUG_MSG_LEN))
+ seq_printf(s, "%s\n", motg->buf[i]);
+ for (dbg_inc(&i); i != motg->dbg_idx; dbg_inc(&i)) {
+ if (!strnlen(motg->buf[i], DEBUG_MSG_LEN))
+ continue;
+ seq_printf(s, "%s\n", motg->buf[i]);
+ }
+ read_unlock_irqrestore(&motg->dbg_lock, flags);
+
+ return 0;
+}
+
+static int msm_otg_dbg_buff_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, msm_otg_dbg_buff_show, inode->i_private);
+}
+
+const struct file_operations msm_otg_dbg_buff_fops = {
+ .open = msm_otg_dbg_buff_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static int msm_otg_dpdm_regulator_enable(struct regulator_dev *rdev)
+{
+ int ret = 0;
+ struct msm_otg *motg = rdev_get_drvdata(rdev);
+
+ if (!motg->rm_pulldown) {
+ ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_ON);
+ if (!ret) {
+ motg->rm_pulldown = true;
+ msm_otg_dbg_log_event(&motg->phy, "RM Pulldown",
+ motg->rm_pulldown, 0);
+ }
+ }
+
+ return ret;
+}
+
+static int msm_otg_dpdm_regulator_disable(struct regulator_dev *rdev)
+{
+ int ret = 0;
+ struct msm_otg *motg = rdev_get_drvdata(rdev);
+
+ if (motg->rm_pulldown) {
+ ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_3P3_OFF);
+ if (!ret) {
+ motg->rm_pulldown = false;
+ msm_otg_dbg_log_event(&motg->phy, "RM Pulldown",
+ motg->rm_pulldown, 0);
+ }
+ }
+
+ return ret;
+}
+
+static int msm_otg_dpdm_regulator_is_enabled(struct regulator_dev *rdev)
+{
+ struct msm_otg *motg = rdev_get_drvdata(rdev);
+
+ return motg->rm_pulldown;
+}
+
+static struct regulator_ops msm_otg_dpdm_regulator_ops = {
+ .enable = msm_otg_dpdm_regulator_enable,
+ .disable = msm_otg_dpdm_regulator_disable,
+ .is_enabled = msm_otg_dpdm_regulator_is_enabled,
+};
+
+static int usb_phy_regulator_init(struct msm_otg *motg)
+{
+ struct device *dev = motg->phy.dev;
+ struct regulator_config cfg = {};
+ struct regulator_init_data *init_data;
+
+ init_data = devm_kzalloc(dev, sizeof(*init_data), GFP_KERNEL);
+ if (!init_data)
+ return -ENOMEM;
+
+ init_data->constraints.valid_ops_mask |= REGULATOR_CHANGE_STATUS;
+ motg->dpdm_rdesc.owner = THIS_MODULE;
+ motg->dpdm_rdesc.type = REGULATOR_VOLTAGE;
+ motg->dpdm_rdesc.ops = &msm_otg_dpdm_regulator_ops;
+ motg->dpdm_rdesc.name = kbasename(dev->of_node->full_name);
+
+ cfg.dev = dev;
+ cfg.init_data = init_data;
+ cfg.driver_data = motg;
+ cfg.of_node = dev->of_node;
+
+ motg->dpdm_rdev = devm_regulator_register(dev, &motg->dpdm_rdesc, &cfg);
+ if (IS_ERR(motg->dpdm_rdev))
+ return PTR_ERR(motg->dpdm_rdev);
+
+ return 0;
+}
+
+const struct file_operations msm_otg_bus_fops = {
+ .open = msm_otg_bus_open,
+ .read = seq_read,
+ .write = msm_otg_bus_write,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static struct dentry *msm_otg_dbg_root;
+
+static int msm_otg_debugfs_init(struct msm_otg *motg)
+{
+ struct dentry *msm_otg_dentry;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ msm_otg_dbg_root = debugfs_create_dir("msm_otg", NULL);
+
+ if (!msm_otg_dbg_root || IS_ERR(msm_otg_dbg_root))
+ return -ENODEV;
+
+ if ((pdata->mode == USB_OTG || pdata->mode == USB_PERIPHERAL) &&
+ pdata->otg_control == OTG_USER_CONTROL) {
+
+ msm_otg_dentry = debugfs_create_file("mode", 0644,
+ msm_otg_dbg_root, motg, &msm_otg_mode_fops);
+
+ if (!msm_otg_dentry) {
+ debugfs_remove(msm_otg_dbg_root);
+ msm_otg_dbg_root = NULL;
+ return -ENODEV;
+ }
+ }
+
+ msm_otg_dentry = debugfs_create_file("chg_type", 0444, msm_otg_dbg_root,
+ motg, &msm_otg_chg_fops);
+
+ if (!msm_otg_dentry) {
+ debugfs_remove_recursive(msm_otg_dbg_root);
+ return -ENODEV;
+ }
+
+ msm_otg_dentry = debugfs_create_file("bus_voting", 0644,
+ msm_otg_dbg_root, motg, &msm_otg_bus_fops);
+
+ if (!msm_otg_dentry) {
+ debugfs_remove_recursive(msm_otg_dbg_root);
+ return -ENODEV;
+ }
+
+ msm_otg_dentry = debugfs_create_file("otg_state", 0444,
+ msm_otg_dbg_root, motg, &msm_otg_state_fops);
+
+ if (!msm_otg_dentry) {
+ debugfs_remove_recursive(msm_otg_dbg_root);
+ return -ENODEV;
+ }
+
+ msm_otg_dentry = debugfs_create_file("dbg_buff", 0444,
+ msm_otg_dbg_root, motg, &msm_otg_dbg_buff_fops);
+
+ if (!msm_otg_dentry) {
+ debugfs_remove_recursive(msm_otg_dbg_root);
+ return -ENODEV;
+ }
+ return 0;
+}
+
+static void msm_otg_debugfs_cleanup(void)
+{
+ debugfs_remove_recursive(msm_otg_dbg_root);
+}
+
+static ssize_t
+set_msm_otg_perf_mode(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t count)
+{
+ struct msm_otg *motg = the_msm_otg;
+ int ret;
+ long clk_rate;
+
+ pr_debug("%s: enable:%d\n", __func__, !strncasecmp(buf, "enable", 6));
+
+ if (!strncasecmp(buf, "enable", 6)) {
+ clk_rate = motg->core_clk_nominal_rate;
+ msm_otg_bus_freq_set(motg, USB_NOC_NOM_VOTE);
+ } else {
+ clk_rate = motg->core_clk_svs_rate;
+ msm_otg_bus_freq_set(motg, USB_NOC_SVS_VOTE);
+ }
+
+ if (clk_rate) {
+ pr_debug("Set usb sys_clk rate:%ld\n", clk_rate);
+ ret = clk_set_rate(motg->core_clk, clk_rate);
+ if (ret)
+ pr_err("sys_clk set_rate fail:%d %ld\n", ret, clk_rate);
+ msm_otg_dbg_log_event(&motg->phy, "OTG PERF SET",
+ clk_rate, ret);
+ } else {
+ pr_err("usb sys_clk rate is undefined\n");
+ }
+
+ return count;
+}
+
+static DEVICE_ATTR(perf_mode, 0200, NULL, set_msm_otg_perf_mode);
+
+#define MSM_OTG_CMD_ID 0x09
+#define MSM_OTG_DEVICE_ID 0x04
+#define MSM_OTG_VMID_IDX 0xFF
+#define MSM_OTG_MEM_TYPE 0x02
+struct msm_otg_scm_cmd_buf {
+ unsigned int device_id;
+ unsigned int vmid_idx;
+ unsigned int mem_type;
+} __attribute__ ((__packed__));
+
+static void msm_otg_pnoc_errata_fix(struct msm_otg *motg)
+{
+ int ret;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+ struct msm_otg_scm_cmd_buf cmd_buf;
+
+ if (!pdata->pnoc_errata_fix)
+ return;
+
+ dev_dbg(motg->phy.dev, "applying fix for pnoc h/w issue\n");
+
+ cmd_buf.device_id = MSM_OTG_DEVICE_ID;
+ cmd_buf.vmid_idx = MSM_OTG_VMID_IDX;
+ cmd_buf.mem_type = MSM_OTG_MEM_TYPE;
+
+ ret = scm_call(SCM_SVC_MP, MSM_OTG_CMD_ID, &cmd_buf,
+ sizeof(cmd_buf), NULL, 0);
+
+ if (ret)
+ dev_err(motg->phy.dev, "scm command failed to update VMIDMT\n");
+}
+
+static u64 msm_otg_dma_mask = DMA_BIT_MASK(32);
+static struct platform_device *msm_otg_add_pdev(
+ struct platform_device *ofdev, const char *name)
+{
+ struct platform_device *pdev;
+ const struct resource *res = ofdev->resource;
+ unsigned int num = ofdev->num_resources;
+ int retval;
+ struct ci13xxx_platform_data ci_pdata;
+ struct msm_otg_platform_data *otg_pdata;
+ struct msm_otg *motg;
+
+ pdev = platform_device_alloc(name, -1);
+ if (!pdev) {
+ retval = -ENOMEM;
+ goto error;
+ }
+
+ pdev->dev.coherent_dma_mask = DMA_BIT_MASK(32);
+ pdev->dev.dma_mask = &msm_otg_dma_mask;
+ pdev->dev.parent = &ofdev->dev;
+
+ if (num) {
+ retval = platform_device_add_resources(pdev, res, num);
+ if (retval)
+ goto error;
+ }
+
+ if (!strcmp(name, "msm_hsusb")) {
+ otg_pdata =
+ (struct msm_otg_platform_data *)
+ ofdev->dev.platform_data;
+ motg = platform_get_drvdata(ofdev);
+ ci_pdata.log2_itc = otg_pdata->log2_itc;
+ ci_pdata.usb_core_id = 0;
+ ci_pdata.l1_supported = otg_pdata->l1_supported;
+ ci_pdata.enable_ahb2ahb_bypass =
+ otg_pdata->enable_ahb2ahb_bypass;
+ ci_pdata.enable_streaming = otg_pdata->enable_streaming;
+ ci_pdata.enable_axi_prefetch = otg_pdata->enable_axi_prefetch;
+ retval = platform_device_add_data(pdev, &ci_pdata,
+ sizeof(ci_pdata));
+ if (retval)
+ goto error;
+ }
+
+ retval = platform_device_add(pdev);
+ if (retval)
+ goto error;
+
+ return pdev;
+
+error:
+ platform_device_put(pdev);
+ return ERR_PTR(retval);
+}
+
+static int msm_otg_setup_devices(struct platform_device *ofdev,
+ enum usb_mode_type mode, bool init)
+{
+ const char *gadget_name = "msm_hsusb";
+ const char *host_name = "msm_hsusb_host";
+ static struct platform_device *gadget_pdev;
+ static struct platform_device *host_pdev;
+ int retval = 0;
+
+ if (!init) {
+ if (gadget_pdev) {
+ platform_device_unregister(gadget_pdev);
+ device_remove_file(&gadget_pdev->dev,
+ &dev_attr_perf_mode);
+ }
+ if (host_pdev)
+ platform_device_unregister(host_pdev);
+ return 0;
+ }
+
+ switch (mode) {
+ case USB_OTG:
+ /* fall through */
+ case USB_PERIPHERAL:
+ gadget_pdev = msm_otg_add_pdev(ofdev, gadget_name);
+ if (IS_ERR(gadget_pdev)) {
+ retval = PTR_ERR(gadget_pdev);
+ break;
+ }
+ if (device_create_file(&gadget_pdev->dev, &dev_attr_perf_mode))
+ dev_err(&gadget_pdev->dev, "perf_mode file failed\n");
+ if (mode == USB_PERIPHERAL)
+ break;
+ /* fall through */
+ case USB_HOST:
+ host_pdev = msm_otg_add_pdev(ofdev, host_name);
+ if (IS_ERR(host_pdev)) {
+ retval = PTR_ERR(host_pdev);
+ if (mode == USB_OTG) {
+ platform_device_unregister(gadget_pdev);
+ device_remove_file(&gadget_pdev->dev,
+ &dev_attr_perf_mode);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return retval;
+}
+
+static int msm_otg_ext_chg_open(struct inode *inode, struct file *file)
+{
+ struct msm_otg *motg = the_msm_otg;
+
+ pr_debug("msm_otg ext chg open\n");
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: OPEN",
+ motg->inputs, motg->phy.otg->state);
+
+ motg->ext_chg_opened = true;
+ file->private_data = (void *)motg;
+ return 0;
+}
+
+static long
+msm_otg_ext_chg_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+ struct msm_otg *motg = file->private_data;
+ struct msm_usb_chg_info info = {0};
+ int ret = 0, val;
+
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: IOCTL", cmd, 0);
+ switch (cmd) {
+ case MSM_USB_EXT_CHG_INFO:
+ info.chg_block_type = USB_CHG_BLOCK_ULPI;
+ info.page_offset = motg->io_res->start & ~PAGE_MASK;
+ /* mmap() works on PAGE granularity */
+ info.length = PAGE_SIZE;
+
+ if (copy_to_user((void __user *)arg, &info, sizeof(info))) {
+ pr_err("%s: copy to user failed\n\n", __func__);
+ ret = -EFAULT;
+ }
+ break;
+ case MSM_USB_EXT_CHG_BLOCK_LPM:
+ if (get_user(val, (int __user *)arg)) {
+ pr_err("%s: get_user failed\n\n", __func__);
+ ret = -EFAULT;
+ break;
+ }
+ pr_debug("%s: LPM block request %d\n", __func__, val);
+ msm_otg_dbg_log_event(&motg->phy, "LPM BLOCK REQ", val, 0);
+ if (val) { /* block LPM */
+ if (motg->chg_type == USB_DCP_CHARGER) {
+ motg->ext_chg_active = ACTIVE;
+ msm_otg_dbg_log_event(&motg->phy,
+ "PM RUNTIME: EXT_CHG GET",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+ pm_runtime_get_sync(motg->phy.dev);
+ } else {
+ motg->ext_chg_active = INACTIVE;
+ complete(&motg->ext_chg_wait);
+ ret = -ENODEV;
+ }
+ } else {
+ motg->ext_chg_active = INACTIVE;
+ complete(&motg->ext_chg_wait);
+ /*
+ * If usb cable is disconnected and then userspace
+ * calls ioctl to unblock low power mode, make sure
+ * otg_sm work for usb disconnect is processed first
+ * followed by decrementing the PM usage counters.
+ */
+ flush_work(&motg->sm_work);
+ msm_otg_dbg_log_event(&motg->phy,
+ "PM RUNTIME: EXT_CHG PUT",
+ get_pm_runtime_counter(motg->phy.dev), 0);
+ pm_runtime_put_sync(motg->phy.dev);
+ }
+ break;
+ case MSM_USB_EXT_CHG_VOLTAGE_INFO:
+ if (get_user(val, (int __user *)arg)) {
+ pr_err("%s: get_user failed\n\n", __func__);
+ ret = -EFAULT;
+ break;
+ }
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: VOL REQ", cmd, val);
+
+ if (val == USB_REQUEST_5V)
+ pr_debug("%s:voting 5V voltage request\n", __func__);
+ else if (val == USB_REQUEST_9V)
+ pr_debug("%s:voting 9V voltage request\n", __func__);
+ break;
+ case MSM_USB_EXT_CHG_RESULT:
+ if (get_user(val, (int __user *)arg)) {
+ pr_err("%s: get_user failed\n\n", __func__);
+ ret = -EFAULT;
+ break;
+ }
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: VOL REQ", cmd, val);
+
+ if (!val)
+ pr_debug("%s:voltage request successful\n", __func__);
+ else
+ pr_debug("%s:voltage request failed\n", __func__);
+ break;
+ case MSM_USB_EXT_CHG_TYPE:
+ if (get_user(val, (int __user *)arg)) {
+ pr_err("%s: get_user failed\n\n", __func__);
+ ret = -EFAULT;
+ break;
+ }
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: VOL REQ", cmd, val);
+
+ if (val)
+ pr_debug("%s:charger is external charger\n", __func__);
+ else
+ pr_debug("%s:charger is not ext charger\n", __func__);
+ break;
+ default:
+ ret = -EINVAL;
+ }
+
+ return ret;
+}
+
+static int msm_otg_ext_chg_mmap(struct file *file, struct vm_area_struct *vma)
+{
+ struct msm_otg *motg = file->private_data;
+ unsigned long vsize = vma->vm_end - vma->vm_start;
+ int ret;
+
+ if (vma->vm_pgoff || vsize > PAGE_SIZE)
+ return -EINVAL;
+
+ vma->vm_pgoff = __phys_to_pfn(motg->io_res->start);
+ vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
+
+ ret = io_remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff,
+ vsize, vma->vm_page_prot);
+ if (ret < 0) {
+ pr_err("%s: failed with return val %d\n", __func__, ret);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int msm_otg_ext_chg_release(struct inode *inode, struct file *file)
+{
+ struct msm_otg *motg = file->private_data;
+
+ pr_debug("msm_otg ext chg release\n");
+ msm_otg_dbg_log_event(&motg->phy, "EXT CHG: RELEASE",
+ motg->inputs, motg->phy.otg->state);
+
+ motg->ext_chg_opened = false;
+
+ return 0;
+}
+
+static const struct file_operations msm_otg_ext_chg_fops = {
+ .owner = THIS_MODULE,
+ .open = msm_otg_ext_chg_open,
+ .unlocked_ioctl = msm_otg_ext_chg_ioctl,
+ .mmap = msm_otg_ext_chg_mmap,
+ .release = msm_otg_ext_chg_release,
+};
+
+static int msm_otg_setup_ext_chg_cdev(struct msm_otg *motg)
+{
+ int ret;
+
+ if (motg->pdata->enable_sec_phy || motg->pdata->mode == USB_HOST ||
+ motg->pdata->otg_control != OTG_PMIC_CONTROL) {
+ pr_debug("usb ext chg is not supported by msm otg\n");
+ return -ENODEV;
+ }
+
+ ret = alloc_chrdev_region(&motg->ext_chg_dev, 0, 1, "usb_ext_chg");
+ if (ret < 0) {
+ pr_err("Fail to allocate usb ext char dev region\n");
+ return ret;
+ }
+ motg->ext_chg_class = class_create(THIS_MODULE, "msm_ext_chg");
+ if (ret < 0) {
+ pr_err("Fail to create usb ext chg class\n");
+ goto unreg_chrdev;
+ }
+ cdev_init(&motg->ext_chg_cdev, &msm_otg_ext_chg_fops);
+ motg->ext_chg_cdev.owner = THIS_MODULE;
+
+ ret = cdev_add(&motg->ext_chg_cdev, motg->ext_chg_dev, 1);
+ if (ret < 0) {
+ pr_err("Fail to add usb ext chg cdev\n");
+ goto destroy_class;
+ }
+ motg->ext_chg_device = device_create(motg->ext_chg_class,
+ NULL, motg->ext_chg_dev, NULL,
+ "usb_ext_chg");
+ if (IS_ERR(motg->ext_chg_device)) {
+ pr_err("Fail to create usb ext chg device\n");
+ ret = PTR_ERR(motg->ext_chg_device);
+ motg->ext_chg_device = NULL;
+ goto del_cdev;
+ }
+
+ init_completion(&motg->ext_chg_wait);
+ pr_debug("msm otg ext chg cdev setup success\n");
+ return 0;
+
+del_cdev:
+ cdev_del(&motg->ext_chg_cdev);
+destroy_class:
+ class_destroy(motg->ext_chg_class);
+unreg_chrdev:
+ unregister_chrdev_region(motg->ext_chg_dev, 1);
+
+ return ret;
+}
+
+static ssize_t dpdm_pulldown_enable_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct msm_otg *motg = the_msm_otg;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ return snprintf(buf, PAGE_SIZE, "%s\n", pdata->dpdm_pulldown_added ?
+ "enabled" : "disabled");
+}
+
+static ssize_t dpdm_pulldown_enable_store(struct device *dev,
+ struct device_attribute *attr, const char
+ *buf, size_t size)
+{
+ struct msm_otg *motg = the_msm_otg;
+ struct msm_otg_platform_data *pdata = motg->pdata;
+
+ if (!strncasecmp(buf, "enable", 6)) {
+ pdata->dpdm_pulldown_added = true;
+ return size;
+ } else if (!strncasecmp(buf, "disable", 7)) {
+ pdata->dpdm_pulldown_added = false;
+ return size;
+ }
+
+ return -EINVAL;
+}
+
+static DEVICE_ATTR(dpdm_pulldown_enable, 0644,
+ dpdm_pulldown_enable_show, dpdm_pulldown_enable_store);
+
+static int msm_otg_vbus_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct msm_otg *motg = container_of(nb, struct msm_otg, vbus_nb);
+
+ if (event)
+ set_bit(B_SESS_VLD, &motg->inputs);
+ else
+ clear_bit(B_SESS_VLD, &motg->inputs);
+
+ queue_work(motg->otg_wq, &motg->sm_work);
+
+ return NOTIFY_DONE;
+}
+
+static int msm_otg_id_notifier(struct notifier_block *nb, unsigned long event,
+ void *ptr)
+{
+ struct msm_otg *motg = container_of(nb, struct msm_otg, id_nb);
+
+ if (event)
+ clear_bit(ID, &motg->inputs);
+ else
+ set_bit(ID, &motg->inputs);
+
+ queue_work(motg->otg_wq, &motg->sm_work);
+
+ return NOTIFY_DONE;
+}
+
+static int msm_otg_extcon_register(struct msm_otg *motg)
+{
+ struct device_node *node = motg->pdev->dev.of_node;
+ struct extcon_dev *edev;
+ int ret = 0;
+
+ if (!of_property_read_bool(node, "extcon"))
+ return 0;
+
+ edev = extcon_get_edev_by_phandle(&motg->pdev->dev, 0);
+ if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV)
+ return PTR_ERR(edev);
+
+ if (!IS_ERR(edev)) {
+ motg->extcon_vbus = edev;
+ motg->vbus_nb.notifier_call = msm_otg_vbus_notifier;
+ ret = extcon_register_notifier(edev, EXTCON_USB,
+ &motg->vbus_nb);
+ if (ret < 0) {
+ dev_err(&motg->pdev->dev, "failed to register notifier for USB\n");
+ return ret;
+ }
+ }
+
+ if (of_count_phandle_with_args(node, "extcon", NULL) > 1) {
+ edev = extcon_get_edev_by_phandle(&motg->pdev->dev, 1);
+ if (IS_ERR(edev) && PTR_ERR(edev) != -ENODEV) {
+ ret = PTR_ERR(edev);
+ goto err;
+ }
+ }
+
+ if (!IS_ERR(edev)) {
+ motg->extcon_id = edev;
+ motg->id_nb.notifier_call = msm_otg_id_notifier;
+ ret = extcon_register_notifier(edev, EXTCON_USB_HOST,
+ &motg->id_nb);
+ if (ret < 0) {
+ dev_err(&motg->pdev->dev, "failed to register notifier for USB-HOST\n");
+ goto err;
+ }
+ }
+
+ return 0;
+err:
+ if (motg->extcon_vbus)
+ extcon_unregister_notifier(motg->extcon_vbus, EXTCON_USB,
+ &motg->vbus_nb);
+
+ return ret;
+}
+
+struct msm_otg_platform_data *msm_otg_dt_to_pdata(struct platform_device *pdev)
+{
+ struct device_node *node = pdev->dev.of_node;
+ struct msm_otg_platform_data *pdata;
+ int len = 0;
+ int res_gpio;
+
+ pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
+ if (!pdata)
+ return NULL;
+
+ of_get_property(node, "qcom,hsusb-otg-phy-init-seq", &len);
+ if (len) {
+ pdata->phy_init_seq = devm_kzalloc(&pdev->dev, len, GFP_KERNEL);
+ if (!pdata->phy_init_seq)
+ return NULL;
+ of_property_read_u32_array(node, "qcom,hsusb-otg-phy-init-seq",
+ pdata->phy_init_seq,
+ len/sizeof(*pdata->phy_init_seq));
+ }
+ of_property_read_u32(node, "qcom,hsusb-otg-power-budget",
+ &pdata->power_budget);
+ of_property_read_u32(node, "qcom,hsusb-otg-mode",
+ &pdata->mode);
+ of_property_read_u32(node, "qcom,hsusb-otg-otg-control",
+ &pdata->otg_control);
+ of_property_read_u32(node, "qcom,hsusb-otg-default-mode",
+ &pdata->default_mode);
+ of_property_read_u32(node, "qcom,hsusb-otg-phy-type",
+ &pdata->phy_type);
+ pdata->disable_reset_on_disconnect = of_property_read_bool(node,
+ "qcom,hsusb-otg-disable-reset");
+ pdata->pnoc_errata_fix = of_property_read_bool(node,
+ "qcom,hsusb-otg-pnoc-errata-fix");
+ pdata->enable_lpm_on_dev_suspend = of_property_read_bool(node,
+ "qcom,hsusb-otg-lpm-on-dev-suspend");
+ pdata->core_clk_always_on_workaround = of_property_read_bool(node,
+ "qcom,hsusb-otg-clk-always-on-workaround");
+ pdata->delay_lpm_on_disconnect = of_property_read_bool(node,
+ "qcom,hsusb-otg-delay-lpm");
+ pdata->dp_manual_pullup = of_property_read_bool(node,
+ "qcom,dp-manual-pullup");
+ pdata->enable_sec_phy = of_property_read_bool(node,
+ "qcom,usb2-enable-hsphy2");
+ of_property_read_u32(node, "qcom,hsusb-log2-itc",
+ &pdata->log2_itc);
+
+ of_property_read_u32(node, "qcom,hsusb-otg-mpm-dpsehv-int",
+ &pdata->mpm_dpshv_int);
+ of_property_read_u32(node, "qcom,hsusb-otg-mpm-dmsehv-int",
+ &pdata->mpm_dmshv_int);
+ pdata->pmic_id_irq = platform_get_irq_byname(pdev, "pmic_id_irq");
+ if (pdata->pmic_id_irq < 0)
+ pdata->pmic_id_irq = 0;
+
+ pdata->hub_reset_gpio = of_get_named_gpio(
+ node, "qcom,hub-reset-gpio", 0);
+ if (pdata->hub_reset_gpio < 0)
+ pr_debug("hub_reset_gpio is not available\n");
+
+ pdata->usbeth_reset_gpio = of_get_named_gpio(
+ node, "qcom,usbeth-reset-gpio", 0);
+ if (pdata->usbeth_reset_gpio < 0)
+ pr_debug("usbeth_reset_gpio is not available\n");
+
+ pdata->switch_sel_gpio =
+ of_get_named_gpio(node, "qcom,sw-sel-gpio", 0);
+ if (pdata->switch_sel_gpio < 0)
+ pr_debug("switch_sel_gpio is not available\n");
+
+ pdata->usb_id_gpio =
+ of_get_named_gpio(node, "qcom,usbid-gpio", 0);
+ if (pdata->usb_id_gpio < 0)
+ pr_debug("usb_id_gpio is not available\n");
+
+ pdata->l1_supported = of_property_read_bool(node,
+ "qcom,hsusb-l1-supported");
+ pdata->enable_ahb2ahb_bypass = of_property_read_bool(node,
+ "qcom,ahb-async-bridge-bypass");
+ pdata->disable_retention_with_vdd_min = of_property_read_bool(node,
+ "qcom,disable-retention-with-vdd-min");
+ pdata->enable_phy_id_pullup = of_property_read_bool(node,
+ "qcom,enable-phy-id-pullup");
+ pdata->phy_dvdd_always_on = of_property_read_bool(node,
+ "qcom,phy-dvdd-always-on");
+
+ res_gpio = of_get_named_gpio(node, "qcom,hsusb-otg-vddmin-gpio", 0);
+ if (res_gpio < 0)
+ res_gpio = 0;
+ pdata->vddmin_gpio = res_gpio;
+
+ pdata->emulation = of_property_read_bool(node,
+ "qcom,emulation");
+
+ pdata->enable_streaming = of_property_read_bool(node,
+ "qcom,boost-sysclk-with-streaming");
+
+ pdata->enable_axi_prefetch = of_property_read_bool(node,
+ "qcom,axi-prefetch-enable");
+
+ pdata->enable_sdp_typec_current_limit = of_property_read_bool(node,
+ "qcom,enable-sdp-typec-current-limit");
+ pdata->vbus_low_as_hostmode = of_property_read_bool(node,
+ "qcom,vbus-low-as-hostmode");
+ return pdata;
+}
+
+static int msm_otg_probe(struct platform_device *pdev)
+{
+ int ret = 0;
+ int len = 0;
+ u32 tmp[3];
+ struct resource *res;
+ struct msm_otg *motg;
+ struct usb_phy *phy;
+ struct msm_otg_platform_data *pdata;
+ void __iomem *tcsr;
+ int id_irq = 0;
+
+ dev_info(&pdev->dev, "msm_otg probe\n");
+
+ motg = kzalloc(sizeof(struct msm_otg), GFP_KERNEL);
+ if (!motg) {
+ ret = -ENOMEM;
+ return ret;
+ }
+
+ /*
+ * USB Core is running its protocol engine based on CORE CLK,
+ * CORE CLK must be running at >55Mhz for correct HSUSB
+ * operation and USB core cannot tolerate frequency changes on
+ * CORE CLK. For such USB cores, vote for maximum clk frequency
+ * on pclk source
+ */
+ motg->core_clk = clk_get(&pdev->dev, "core_clk");
+ if (IS_ERR(motg->core_clk)) {
+ ret = PTR_ERR(motg->core_clk);
+ motg->core_clk = NULL;
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "failed to get core_clk\n");
+ goto free_motg;
+ }
+
+ motg->core_reset = devm_reset_control_get(&pdev->dev, "core_reset");
+ if (IS_ERR(motg->core_reset)) {
+ dev_err(&pdev->dev, "failed to get core_reset\n");
+ ret = PTR_ERR(motg->core_reset);
+ goto put_core_clk;
+ }
+
+ /*
+ * USB Core CLK can run at max freq if streaming is enabled. Hence,
+ * get Max supported clk frequency for USB Core CLK and request to set
+ * the same. Otherwise set USB Core CLK to defined default value.
+ */
+ if (of_property_read_u32(pdev->dev.of_node,
+ "qcom,max-nominal-sysclk-rate", &ret)) {
+ ret = -EINVAL;
+ goto put_core_clk;
+ } else {
+ motg->core_clk_nominal_rate = clk_round_rate(motg->core_clk,
+ ret);
+ }
+
+ if (of_property_read_u32(pdev->dev.of_node,
+ "qcom,max-svs-sysclk-rate", &ret)) {
+ dev_dbg(&pdev->dev, "core_clk svs freq not specified\n");
+ } else {
+ motg->core_clk_svs_rate = clk_round_rate(motg->core_clk, ret);
+ }
+
+ motg->default_noc_mode = USB_NOC_NOM_VOTE;
+ if (of_property_read_bool(pdev->dev.of_node, "qcom,default-mode-svs")) {
+ motg->core_clk_rate = motg->core_clk_svs_rate;
+ motg->default_noc_mode = USB_NOC_SVS_VOTE;
+ } else if (of_property_read_bool(pdev->dev.of_node,
+ "qcom,boost-sysclk-with-streaming")) {
+ motg->core_clk_rate = motg->core_clk_nominal_rate;
+ } else {
+ motg->core_clk_rate = clk_round_rate(motg->core_clk,
+ USB_DEFAULT_SYSTEM_CLOCK);
+ }
+
+ if (IS_ERR_VALUE(motg->core_clk_rate)) {
+ dev_err(&pdev->dev, "fail to get core clk max freq.\n");
+ } else {
+ ret = clk_set_rate(motg->core_clk, motg->core_clk_rate);
+ if (ret)
+ dev_err(&pdev->dev, "fail to set core_clk freq:%d\n",
+ ret);
+ }
+
+ motg->pclk = clk_get(&pdev->dev, "iface_clk");
+ if (IS_ERR(motg->pclk)) {
+ ret = PTR_ERR(motg->pclk);
+ motg->pclk = NULL;
+ if (ret != -EPROBE_DEFER)
+ dev_err(&pdev->dev, "failed to get iface_clk\n");
+ goto put_core_clk;
+ }
+
+ motg->xo_clk = clk_get(&pdev->dev, "xo");
+ if (IS_ERR(motg->xo_clk)) {
+ ret = PTR_ERR(motg->xo_clk);
+ motg->xo_clk = NULL;
+ if (ret == -EPROBE_DEFER)
+ goto put_pclk;
+ }
+
+ /*
+ * On few platforms USB PHY is fed with sleep clk.
+ * Hence don't fail probe.
+ */
+ motg->sleep_clk = devm_clk_get(&pdev->dev, "sleep_clk");
+ if (IS_ERR(motg->sleep_clk)) {
+ ret = PTR_ERR(motg->sleep_clk);
+ motg->sleep_clk = NULL;
+ if (ret == -EPROBE_DEFER)
+ goto put_xo_clk;
+ else
+ dev_dbg(&pdev->dev, "failed to get sleep_clk\n");
+ } else {
+ ret = clk_prepare_enable(motg->sleep_clk);
+ if (ret) {
+ dev_err(&pdev->dev, "%s failed to vote sleep_clk%d\n",
+ __func__, ret);
+ goto put_xo_clk;
+ }
+ }
+
+ /*
+ * If present, phy_reset_clk is used to reset the PHY, ULPI bridge
+ * and CSR Wrapper. This is a reset only clock.
+ */
+
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_reset_clk") >= 0) {
+ motg->phy_reset_clk = devm_clk_get(&pdev->dev, "phy_reset_clk");
+ if (IS_ERR(motg->phy_reset_clk)) {
+ ret = PTR_ERR(motg->phy_reset_clk);
+ goto disable_sleep_clk;
+ }
+
+ motg->phy_reset = devm_reset_control_get(&pdev->dev,
+ "phy_reset");
+ if (IS_ERR(motg->phy_reset)) {
+ dev_err(&pdev->dev, "failed to get phy_reset\n");
+ ret = PTR_ERR(motg->phy_reset);
+ goto disable_sleep_clk;
+ }
+ }
+
+ /*
+ * If present, phy_por_clk is used to assert/de-assert phy POR
+ * input. This is a reset only clock. phy POR must be asserted
+ * after overriding the parameter registers via CSR wrapper or
+ * ULPI bridge.
+ */
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_por_clk") >= 0) {
+ motg->phy_por_clk = devm_clk_get(&pdev->dev, "phy_por_clk");
+ if (IS_ERR(motg->phy_por_clk)) {
+ ret = PTR_ERR(motg->phy_por_clk);
+ goto disable_sleep_clk;
+ }
+
+ motg->phy_por_reset = devm_reset_control_get(&pdev->dev,
+ "phy_por_reset");
+ if (IS_ERR(motg->phy_por_reset)) {
+ dev_err(&pdev->dev, "failed to get phy_por_reset\n");
+ ret = PTR_ERR(motg->phy_por_reset);
+ goto disable_sleep_clk;
+ }
+ }
+
+ /*
+ * If present, phy_csr_clk is required for accessing PHY
+ * CSR registers via AHB2PHY interface.
+ */
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_csr_clk") >= 0) {
+ motg->phy_csr_clk = devm_clk_get(&pdev->dev, "phy_csr_clk");
+ if (IS_ERR(motg->phy_csr_clk)) {
+ ret = PTR_ERR(motg->phy_csr_clk);
+ goto disable_sleep_clk;
+ } else {
+ ret = clk_prepare_enable(motg->phy_csr_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "fail to enable phy csr clk %d\n", ret);
+ goto disable_sleep_clk;
+ }
+ }
+ }
+
+ of_property_read_u32(pdev->dev.of_node, "qcom,pm-qos-latency",
+ &motg->pm_qos_latency);
+
+ pdata = msm_otg_dt_to_pdata(pdev);
+ if (!pdata) {
+ ret = -ENOMEM;
+ goto disable_phy_csr_clk;
+ }
+ pdev->dev.platform_data = pdata;
+
+ pdata->bus_scale_table = msm_bus_cl_get_pdata(pdev);
+ if (!pdata->bus_scale_table)
+ dev_dbg(&pdev->dev, "bus scaling is disabled\n");
+
+ if (pdata->phy_type == QUSB_ULPI_PHY) {
+ if (of_property_match_string(pdev->dev.of_node,
+ "clock-names", "phy_ref_clk") >= 0) {
+ motg->phy_ref_clk = devm_clk_get(&pdev->dev,
+ "phy_ref_clk");
+ if (IS_ERR(motg->phy_ref_clk)) {
+ ret = PTR_ERR(motg->phy_ref_clk);
+ goto disable_phy_csr_clk;
+ } else {
+ ret = clk_prepare_enable(motg->phy_ref_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "fail to enable phy ref clk %d\n",
+ ret);
+ goto disable_phy_csr_clk;
+ }
+ }
+ }
+ }
+
+ motg->phy.otg = devm_kzalloc(&pdev->dev, sizeof(struct usb_otg),
+ GFP_KERNEL);
+ if (!motg->phy.otg) {
+ ret = -ENOMEM;
+ goto disable_phy_csr_clk;
+ }
+
+ the_msm_otg = motg;
+ motg->pdata = pdata;
+ phy = &motg->phy;
+ phy->dev = &pdev->dev;
+ motg->pdev = pdev;
+ motg->dbg_idx = 0;
+ motg->dbg_lock = __RW_LOCK_UNLOCKED(lck);
+
+ if (motg->pdata->bus_scale_table) {
+ motg->bus_perf_client =
+ msm_bus_scale_register_client(motg->pdata->bus_scale_table);
+ if (!motg->bus_perf_client) {
+ dev_err(motg->phy.dev, "%s: Failed to register BUS\n"
+ "scaling client!!\n", __func__);
+ } else {
+ debug_bus_voting_enabled = true;
+ /* Some platforms require BUS vote to control clocks */
+ msm_otg_bus_vote(motg, USB_MIN_PERF_VOTE);
+ }
+ }
+
+ ret = msm_otg_bus_freq_get(motg);
+ if (ret) {
+ pr_err("failed to get noc clocks: %d\n", ret);
+ } else {
+ ret = msm_otg_bus_freq_set(motg, motg->default_noc_mode);
+ if (ret)
+ pr_err("failed to vote explicit noc rates: %d\n", ret);
+ }
+
+ /* initialize reset counter */
+ motg->reset_counter = 0;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core");
+ if (!res) {
+ dev_err(&pdev->dev, "failed to get core iomem resource\n");
+ ret = -ENODEV;
+ goto devote_bus_bw;
+ }
+
+ motg->io_res = res;
+ motg->regs = ioremap(res->start, resource_size(res));
+ if (!motg->regs) {
+ dev_err(&pdev->dev, "core iomem ioremap failed\n");
+ ret = -ENOMEM;
+ goto devote_bus_bw;
+ }
+ dev_info(&pdev->dev, "OTG regs = %pK\n", motg->regs);
+
+ if (pdata->enable_sec_phy) {
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "tcsr");
+ if (!res) {
+ dev_dbg(&pdev->dev, "missing TCSR memory resource\n");
+ } else {
+ tcsr = devm_ioremap_nocache(&pdev->dev, res->start,
+ resource_size(res));
+ if (!tcsr) {
+ dev_dbg(&pdev->dev, "tcsr ioremap failed\n");
+ } else {
+ /* Enable USB2 on secondary HSPHY. */
+ writel_relaxed(0x1, tcsr);
+ /*
+ * Ensure that TCSR write is completed before
+ * USB registers initialization.
+ */
+ mb();
+ }
+ }
+ }
+
+ if (pdata->enable_sec_phy)
+ motg->usb_phy_ctrl_reg = USB_PHY_CTRL2;
+ else
+ motg->usb_phy_ctrl_reg = USB_PHY_CTRL;
+
+ /*
+ * The USB PHY wrapper provides a register interface
+ * through AHB2PHY for performing PHY related operations
+ * like retention, HV interrupts and overriding parameter
+ * registers etc. The registers start at 4 byte boundary
+ * but only the first byte is valid and remaining are not
+ * used. Relaxed versions of readl/writel should be used.
+ *
+ * The link does not have any PHY specific registers.
+ * Hence set motg->usb_phy_ctrl_reg to.
+ */
+ if (motg->pdata->phy_type == SNPS_FEMTO_PHY ||
+ pdata->phy_type == QUSB_ULPI_PHY) {
+ res = platform_get_resource_byname(pdev,
+ IORESOURCE_MEM, "phy_csr");
+ if (!res) {
+ dev_err(&pdev->dev, "PHY CSR IOMEM missing!\n");
+ ret = -ENODEV;
+ goto free_regs;
+ }
+ motg->phy_csr_regs = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(motg->phy_csr_regs)) {
+ ret = PTR_ERR(motg->phy_csr_regs);
+ dev_err(&pdev->dev, "PHY CSR ioremap failed!\n");
+ goto free_regs;
+ }
+ motg->usb_phy_ctrl_reg = 0;
+ }
+
+ motg->irq = platform_get_irq(pdev, 0);
+ if (!motg->irq) {
+ dev_err(&pdev->dev, "platform_get_irq failed\n");
+ ret = -ENODEV;
+ goto free_regs;
+ }
+
+ motg->async_irq = platform_get_irq_byname(pdev, "async_irq");
+ if (motg->async_irq < 0) {
+ dev_err(&pdev->dev, "platform_get_irq for async_int failed\n");
+ motg->async_irq = 0;
+ goto free_regs;
+ }
+
+ if (motg->xo_clk) {
+ ret = clk_prepare_enable(motg->xo_clk);
+ if (ret) {
+ dev_err(&pdev->dev,
+ "%s failed to vote for TCXO %d\n",
+ __func__, ret);
+ goto free_xo_handle;
+ }
+ }
+
+
+ clk_prepare_enable(motg->pclk);
+
+ hsusb_vdd = devm_regulator_get(motg->phy.dev, "hsusb_vdd_dig");
+ if (IS_ERR(hsusb_vdd)) {
+ hsusb_vdd = devm_regulator_get(motg->phy.dev, "HSUSB_VDDCX");
+ if (IS_ERR(hsusb_vdd)) {
+ dev_err(motg->phy.dev, "unable to get hsusb vddcx\n");
+ ret = PTR_ERR(hsusb_vdd);
+ goto devote_xo_handle;
+ }
+ }
+
+ if (of_get_property(pdev->dev.of_node,
+ "qcom,vdd-voltage-level",
+ &len)){
+ if (len == sizeof(tmp)) {
+ of_property_read_u32_array(pdev->dev.of_node,
+ "qcom,vdd-voltage-level",
+ tmp, len/sizeof(*tmp));
+ vdd_val[0] = tmp[0];
+ vdd_val[1] = tmp[1];
+ vdd_val[2] = tmp[2];
+ } else {
+ dev_dbg(&pdev->dev,
+ "Using default hsusb vdd config.\n");
+ goto devote_xo_handle;
+ }
+ } else {
+ goto devote_xo_handle;
+ }
+
+ ret = msm_hsusb_config_vddcx(1);
+ if (ret) {
+ dev_err(&pdev->dev, "hsusb vddcx configuration failed\n");
+ goto devote_xo_handle;
+ }
+
+ ret = regulator_enable(hsusb_vdd);
+ if (ret) {
+ dev_err(&pdev->dev, "unable to enable the hsusb vddcx\n");
+ goto free_config_vddcx;
+ }
+
+ ret = msm_hsusb_ldo_init(motg, 1);
+ if (ret) {
+ dev_err(&pdev->dev, "hsusb vreg configuration failed\n");
+ goto free_hsusb_vdd;
+ }
+
+ /* Get pinctrl if target uses pinctrl */
+ motg->phy_pinctrl = devm_pinctrl_get(&pdev->dev);
+ if (IS_ERR(motg->phy_pinctrl)) {
+ if (of_property_read_bool(pdev->dev.of_node, "pinctrl-names")) {
+ dev_err(&pdev->dev, "Error encountered while getting pinctrl");
+ ret = PTR_ERR(motg->phy_pinctrl);
+ goto free_ldo_init;
+ }
+ dev_dbg(&pdev->dev, "Target does not use pinctrl\n");
+ motg->phy_pinctrl = NULL;
+ }
+
+ ret = msm_hsusb_ldo_enable(motg, USB_PHY_REG_ON);
+ if (ret) {
+ dev_err(&pdev->dev, "hsusb vreg enable failed\n");
+ goto free_ldo_init;
+ }
+ clk_prepare_enable(motg->core_clk);
+
+ /* Check if USB mem_type change is needed to workaround PNOC hw issue */
+ msm_otg_pnoc_errata_fix(motg);
+
+ writel_relaxed(0, USB_USBINTR);
+ writel_relaxed(0, USB_OTGSC);
+ /* Ensure that above STOREs are completed before enabling interrupts */
+ mb();
+
+ motg->id_state = USB_ID_FLOAT;
+ set_bit(ID, &motg->inputs);
+ INIT_WORK(&motg->sm_work, msm_otg_sm_work);
+ INIT_DELAYED_WORK(&motg->chg_work, msm_chg_detect_work);
+ INIT_DELAYED_WORK(&motg->id_status_work, msm_id_status_w);
+ INIT_DELAYED_WORK(&motg->perf_vote_work, msm_otg_perf_vote_work);
+ setup_timer(&motg->chg_check_timer, msm_otg_chg_check_timer_func,
+ (unsigned long) motg);
+ motg->otg_wq = alloc_ordered_workqueue("k_otg", 0);
+ if (!motg->otg_wq) {
+ pr_err("%s: Unable to create workqueue otg_wq\n",
+ __func__);
+ goto disable_core_clk;
+ }
+
+ ret = request_irq(motg->irq, msm_otg_irq, IRQF_SHARED,
+ "msm_otg", motg);
+ if (ret) {
+ dev_err(&pdev->dev, "request irq failed\n");
+ goto destroy_wq;
+ }
+
+ motg->phy_irq = platform_get_irq_byname(pdev, "phy_irq");
+ if (motg->phy_irq < 0) {
+ dev_dbg(&pdev->dev, "phy_irq is not present\n");
+ motg->phy_irq = 0;
+ } else {
+
+ /* clear all interrupts before enabling the IRQ */
+ writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR0);
+ writeb_relaxed(0xFF, USB2_PHY_USB_PHY_INTERRUPT_CLEAR1);
+
+ writeb_relaxed(0x1, USB2_PHY_USB_PHY_IRQ_CMD);
+ /*
+ * Databook says 200 usec delay is required for
+ * clearing the interrupts.
+ */
+ udelay(200);
+ writeb_relaxed(0x0, USB2_PHY_USB_PHY_IRQ_CMD);
+
+ ret = request_irq(motg->phy_irq, msm_otg_phy_irq_handler,
+ IRQF_TRIGGER_RISING, "msm_otg_phy_irq", motg);
+ if (ret < 0) {
+ dev_err(&pdev->dev, "phy_irq request fail %d\n", ret);
+ goto free_irq;
+ }
+ }
+
+ ret = request_irq(motg->async_irq, msm_otg_irq,
+ IRQF_TRIGGER_RISING, "msm_otg", motg);
+ if (ret) {
+ dev_err(&pdev->dev, "request irq failed (ASYNC INT)\n");
+ goto free_phy_irq;
+ }
+ disable_irq(motg->async_irq);
+
+ if (pdata->otg_control == OTG_PHY_CONTROL && pdata->mpm_otgsessvld_int)
+ msm_mpm_enable_pin(pdata->mpm_otgsessvld_int, 1);
+
+ if (pdata->mpm_dpshv_int)
+ msm_mpm_enable_pin(pdata->mpm_dpshv_int, 1);
+ if (pdata->mpm_dmshv_int)
+ msm_mpm_enable_pin(pdata->mpm_dmshv_int, 1);
+
+ phy->init = msm_otg_reset;
+ phy->set_power = msm_otg_set_power;
+ phy->set_suspend = msm_otg_set_suspend;
+ phy->dbg_event = msm_otg_dbg_log_event;
+
+ phy->io_ops = &msm_otg_io_ops;
+
+ phy->otg->usb_phy = &motg->phy;
+ phy->otg->set_host = msm_otg_set_host;
+ phy->otg->set_peripheral = msm_otg_set_peripheral;
+ if (pdata->dp_manual_pullup)
+ phy->flags |= ENABLE_DP_MANUAL_PULLUP;
+
+ if (pdata->enable_sec_phy)
+ phy->flags |= ENABLE_SECONDARY_PHY;
+
+ ret = usb_add_phy(&motg->phy, USB_PHY_TYPE_USB2);
+ if (ret) {
+ dev_err(&pdev->dev, "usb_add_phy failed\n");
+ goto free_async_irq;
+ }
+
+ ret = usb_phy_regulator_init(motg);
+ if (ret) {
+ dev_err(&pdev->dev, "usb_phy_regulator_init failed\n");
+ goto remove_phy;
+ }
+
+ if (motg->pdata->mode == USB_OTG &&
+ motg->pdata->otg_control == OTG_PMIC_CONTROL &&
+ !motg->phy_irq) {
+
+ if (gpio_is_valid(motg->pdata->usb_id_gpio)) {
+ /* usb_id_gpio request */
+ ret = gpio_request(motg->pdata->usb_id_gpio,
+ "USB_ID_GPIO");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio req failed for id\n");
+ motg->pdata->usb_id_gpio = 0;
+ goto remove_phy;
+ }
+
+ /*
+ * The following code implements switch between the HOST
+ * mode to device mode when used different HW components
+ * on the same port: USB HUB and the usb jack type B
+ * for device mode In this case HUB should be gone
+ * only once out of reset at the boot time and after
+ * that always stay on
+ */
+ if (gpio_is_valid(motg->pdata->hub_reset_gpio)) {
+ ret = devm_gpio_request(&pdev->dev,
+ motg->pdata->hub_reset_gpio,
+ "qcom,hub-reset-gpio");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio req failed for hub reset\n");
+ goto remove_phy;
+ }
+ gpio_direction_output(
+ motg->pdata->hub_reset_gpio, 1);
+ }
+
+ if (gpio_is_valid(motg->pdata->switch_sel_gpio)) {
+ ret = devm_gpio_request(&pdev->dev,
+ motg->pdata->switch_sel_gpio,
+ "qcom,sw-sel-gpio");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio req failed for switch sel\n");
+ goto remove_phy;
+ }
+ if (gpio_get_value(motg->pdata->usb_id_gpio))
+ gpio_direction_input(
+ motg->pdata->switch_sel_gpio);
+
+ else
+ gpio_direction_output(
+ motg->pdata->switch_sel_gpio,
+ 1);
+ }
+
+ /* usb_id_gpio to irq */
+ id_irq = gpio_to_irq(motg->pdata->usb_id_gpio);
+ motg->ext_id_irq = id_irq;
+ } else if (motg->pdata->pmic_id_irq) {
+ id_irq = motg->pdata->pmic_id_irq;
+ }
+
+ if (id_irq) {
+ ret = request_irq(id_irq,
+ msm_id_irq,
+ IRQF_TRIGGER_RISING |
+ IRQF_TRIGGER_FALLING,
+ "msm_otg", motg);
+ if (ret) {
+ dev_err(&pdev->dev, "request irq failed for ID\n");
+ goto remove_phy;
+ }
+ } else {
+ /* PMIC does USB ID detection and notifies through
+ * USB_OTG property of USB powersupply.
+ */
+ dev_dbg(&pdev->dev, "PMIC does ID detection\n");
+ }
+ }
+
+ platform_set_drvdata(pdev, motg);
+ device_init_wakeup(&pdev->dev, 1);
+
+ ret = msm_otg_debugfs_init(motg);
+ if (ret)
+ dev_dbg(&pdev->dev, "mode debugfs file is not available\n");
+
+ if (motg->pdata->otg_control == OTG_PMIC_CONTROL &&
+ (!(motg->pdata->mode == USB_OTG) ||
+ motg->pdata->pmic_id_irq || motg->ext_id_irq ||
+ !motg->phy_irq))
+ motg->caps = ALLOW_PHY_POWER_COLLAPSE | ALLOW_PHY_RETENTION;
+
+ if (motg->pdata->otg_control == OTG_PHY_CONTROL || motg->phy_irq ||
+ motg->pdata->enable_phy_id_pullup)
+ motg->caps = ALLOW_PHY_RETENTION | ALLOW_PHY_REGULATORS_LPM;
+
+ if (motg->pdata->mpm_dpshv_int || motg->pdata->mpm_dmshv_int)
+ motg->caps |= ALLOW_HOST_PHY_RETENTION;
+
+ device_create_file(&pdev->dev, &dev_attr_dpdm_pulldown_enable);
+
+ if (motg->pdata->enable_lpm_on_dev_suspend)
+ motg->caps |= ALLOW_LPM_ON_DEV_SUSPEND;
+
+ if (motg->pdata->disable_retention_with_vdd_min)
+ motg->caps |= ALLOW_VDD_MIN_WITH_RETENTION_DISABLED;
+
+ /*
+ * PHY DVDD is supplied by a always on PMIC LDO (unlike
+ * vddcx/vddmx). PHY can keep D+ pull-up and D+/D-
+ * pull-down during suspend without any additional
+ * hardware re-work.
+ */
+ if (motg->pdata->phy_type == SNPS_FEMTO_PHY)
+ motg->caps |= ALLOW_BUS_SUSPEND_WITHOUT_REWORK;
+
+ pm_stay_awake(&pdev->dev);
+ pm_runtime_set_active(&pdev->dev);
+ pm_runtime_enable(&pdev->dev);
+
+ if (motg->pdata->delay_lpm_on_disconnect) {
+ pm_runtime_set_autosuspend_delay(&pdev->dev,
+ lpm_disconnect_thresh);
+ pm_runtime_use_autosuspend(&pdev->dev);
+ }
+
+ ret = msm_otg_setup_ext_chg_cdev(motg);
+ if (ret)
+ dev_dbg(&pdev->dev, "fail to setup cdev\n");
+
+ if (pdev->dev.of_node) {
+ ret = msm_otg_setup_devices(pdev, pdata->mode, true);
+ if (ret) {
+ dev_err(&pdev->dev, "devices setup failed\n");
+ goto remove_cdev;
+ }
+ }
+
+ psy = power_supply_get_by_name("usb");
+ if (!psy) {
+ dev_dbg(&pdev->dev, "Could not get usb power_supply\n");
+ ret = -EPROBE_DEFER;
+ goto otg_remove_devices;
+ }
+
+
+ ret = msm_otg_extcon_register(motg);
+ if (ret)
+ goto put_psy;
+
+ if (motg->extcon_vbus) {
+ ret = extcon_get_cable_state_(motg->extcon_vbus, EXTCON_USB);
+ if (ret)
+ set_bit(B_SESS_VLD, &motg->inputs);
+ else
+ clear_bit(B_SESS_VLD, &motg->inputs);
+ }
+
+ if (motg->extcon_id) {
+ ret = extcon_get_cable_state_(motg->extcon_id, EXTCON_USB_HOST);
+ if (ret)
+ clear_bit(ID, &motg->inputs);
+ else
+ set_bit(ID, &motg->inputs);
+ }
+
+ if (gpio_is_valid(motg->pdata->hub_reset_gpio)) {
+ ret = devm_gpio_request(&pdev->dev,
+ motg->pdata->hub_reset_gpio,
+ "HUB_RESET");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio req failed for hub_reset\n");
+ } else {
+ gpio_direction_output(
+ motg->pdata->hub_reset_gpio, 0);
+ /* 5 microsecs reset signaling to usb hub */
+ usleep_range(5, 10);
+ gpio_direction_output(
+ motg->pdata->hub_reset_gpio, 1);
+ }
+ }
+
+ if (gpio_is_valid(motg->pdata->usbeth_reset_gpio)) {
+ ret = devm_gpio_request(&pdev->dev,
+ motg->pdata->usbeth_reset_gpio,
+ "ETH_RESET");
+ if (ret < 0) {
+ dev_err(&pdev->dev, "gpio req failed for usbeth_reset\n");
+ } else {
+ gpio_direction_output(
+ motg->pdata->usbeth_reset_gpio, 0);
+ /* 100 microsecs reset signaling to usb-to-eth */
+ usleep_range(100, 110);
+ gpio_direction_output(
+ motg->pdata->usbeth_reset_gpio, 1);
+ }
+ }
+
+ motg->pm_notify.notifier_call = msm_otg_pm_notify;
+ register_pm_notifier(&motg->pm_notify);
+ msm_otg_dbg_log_event(phy, "OTG PROBE", motg->caps, motg->lpm_flags);
+
+ return 0;
+
+put_psy:
+ if (psy)
+ power_supply_put(psy);
+otg_remove_devices:
+ if (pdev->dev.of_node)
+ msm_otg_setup_devices(pdev, motg->pdata->mode, false);
+remove_cdev:
+ if (!motg->ext_chg_device) {
+ device_destroy(motg->ext_chg_class, motg->ext_chg_dev);
+ cdev_del(&motg->ext_chg_cdev);
+ class_destroy(motg->ext_chg_class);
+ unregister_chrdev_region(motg->ext_chg_dev, 1);
+ }
+remove_phy:
+ usb_remove_phy(&motg->phy);
+free_async_irq:
+ free_irq(motg->async_irq, motg);
+free_phy_irq:
+ if (motg->phy_irq)
+ free_irq(motg->phy_irq, motg);
+free_irq:
+ free_irq(motg->irq, motg);
+destroy_wq:
+ destroy_workqueue(motg->otg_wq);
+disable_core_clk:
+ clk_disable_unprepare(motg->core_clk);
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF);
+free_ldo_init:
+ msm_hsusb_ldo_init(motg, 0);
+free_hsusb_vdd:
+ regulator_disable(hsusb_vdd);
+free_config_vddcx:
+ regulator_set_voltage(hsusb_vdd,
+ vdd_val[VDD_NONE],
+ vdd_val[VDD_MAX]);
+devote_xo_handle:
+ clk_disable_unprepare(motg->pclk);
+ if (motg->xo_clk)
+ clk_disable_unprepare(motg->xo_clk);
+free_xo_handle:
+ if (motg->xo_clk) {
+ clk_put(motg->xo_clk);
+ motg->xo_clk = NULL;
+ }
+free_regs:
+ iounmap(motg->regs);
+devote_bus_bw:
+ if (motg->bus_perf_client) {
+ msm_otg_bus_vote(motg, USB_NO_PERF_VOTE);
+ msm_bus_scale_unregister_client(motg->bus_perf_client);
+ }
+disable_phy_csr_clk:
+ if (motg->phy_csr_clk)
+ clk_disable_unprepare(motg->phy_csr_clk);
+disable_sleep_clk:
+ if (motg->sleep_clk)
+ clk_disable_unprepare(motg->sleep_clk);
+put_xo_clk:
+ if (motg->xo_clk)
+ clk_put(motg->xo_clk);
+put_pclk:
+ if (motg->pclk)
+ clk_put(motg->pclk);
+put_core_clk:
+ if (motg->core_clk)
+ clk_put(motg->core_clk);
+free_motg:
+ kfree(motg);
+ return ret;
+}
+
+static int msm_otg_remove(struct platform_device *pdev)
+{
+ struct msm_otg *motg = platform_get_drvdata(pdev);
+ struct usb_phy *phy = &motg->phy;
+ int cnt = 0;
+
+ if (phy->otg->host || phy->otg->gadget)
+ return -EBUSY;
+
+ unregister_pm_notifier(&motg->pm_notify);
+
+ extcon_unregister_notifier(motg->extcon_id, EXTCON_USB_HOST,
+ &motg->id_nb);
+ extcon_unregister_notifier(motg->extcon_vbus, EXTCON_USB,
+ &motg->vbus_nb);
+
+ if (!motg->ext_chg_device) {
+ device_destroy(motg->ext_chg_class, motg->ext_chg_dev);
+ cdev_del(&motg->ext_chg_cdev);
+ class_destroy(motg->ext_chg_class);
+ unregister_chrdev_region(motg->ext_chg_dev, 1);
+ }
+
+ if (pdev->dev.of_node)
+ msm_otg_setup_devices(pdev, motg->pdata->mode, false);
+ if (psy)
+ power_supply_put(psy);
+ msm_otg_debugfs_cleanup();
+ cancel_delayed_work_sync(&motg->chg_work);
+ cancel_delayed_work_sync(&motg->id_status_work);
+ cancel_delayed_work_sync(&motg->perf_vote_work);
+ msm_otg_perf_vote_update(motg, false);
+ cancel_work_sync(&motg->sm_work);
+ destroy_workqueue(motg->otg_wq);
+
+ pm_runtime_resume(&pdev->dev);
+
+ device_init_wakeup(&pdev->dev, 0);
+ pm_runtime_disable(&pdev->dev);
+
+ if (motg->phy_irq)
+ free_irq(motg->phy_irq, motg);
+ if (motg->pdata->pmic_id_irq)
+ free_irq(motg->pdata->pmic_id_irq, motg);
+ usb_remove_phy(phy);
+ free_irq(motg->irq, motg);
+
+ if (motg->pdata->mpm_dpshv_int || motg->pdata->mpm_dmshv_int)
+ device_remove_file(&pdev->dev,
+ &dev_attr_dpdm_pulldown_enable);
+ if (motg->pdata->otg_control == OTG_PHY_CONTROL &&
+ motg->pdata->mpm_otgsessvld_int)
+ msm_mpm_enable_pin(motg->pdata->mpm_otgsessvld_int, 0);
+
+ if (motg->pdata->mpm_dpshv_int)
+ msm_mpm_enable_pin(motg->pdata->mpm_dpshv_int, 0);
+ if (motg->pdata->mpm_dmshv_int)
+ msm_mpm_enable_pin(motg->pdata->mpm_dmshv_int, 0);
+
+ /*
+ * Put PHY in low power mode.
+ */
+ ulpi_read(phy, 0x14);
+ ulpi_write(phy, 0x08, 0x09);
+
+ writel_relaxed(readl_relaxed(USB_PORTSC) | PORTSC_PHCD, USB_PORTSC);
+ while (cnt < PHY_SUSPEND_TIMEOUT_USEC) {
+ if (readl_relaxed(USB_PORTSC) & PORTSC_PHCD)
+ break;
+ udelay(1);
+ cnt++;
+ }
+ if (cnt >= PHY_SUSPEND_TIMEOUT_USEC)
+ dev_err(phy->dev, "Unable to suspend PHY\n");
+
+ clk_disable_unprepare(motg->pclk);
+ clk_disable_unprepare(motg->core_clk);
+ if (motg->phy_csr_clk)
+ clk_disable_unprepare(motg->phy_csr_clk);
+ if (motg->xo_clk) {
+ clk_disable_unprepare(motg->xo_clk);
+ clk_put(motg->xo_clk);
+ }
+
+ if (!IS_ERR(motg->sleep_clk))
+ clk_disable_unprepare(motg->sleep_clk);
+
+ msm_hsusb_ldo_enable(motg, USB_PHY_REG_OFF);
+ msm_hsusb_ldo_init(motg, 0);
+ regulator_disable(hsusb_vdd);
+ regulator_set_voltage(hsusb_vdd,
+ vdd_val[VDD_NONE],
+ vdd_val[VDD_MAX]);
+
+ iounmap(motg->regs);
+ pm_runtime_set_suspended(&pdev->dev);
+
+ clk_put(motg->pclk);
+ clk_put(motg->core_clk);
+
+ if (motg->bus_perf_client) {
+ msm_otg_bus_vote(motg, USB_NO_PERF_VOTE);
+ msm_bus_scale_unregister_client(motg->bus_perf_client);
+ }
+
+ return 0;
+}
+
+static void msm_otg_shutdown(struct platform_device *pdev)
+{
+ struct msm_otg *motg = platform_get_drvdata(pdev);
+
+ dev_dbg(&pdev->dev, "OTG shutdown\n");
+ msm_hsusb_vbus_power(motg, 0);
+}
+
+#ifdef CONFIG_PM
+static int msm_otg_runtime_idle(struct device *dev)
+{
+ struct msm_otg *motg = dev_get_drvdata(dev);
+ struct usb_phy *phy = &motg->phy;
+
+ dev_dbg(dev, "OTG runtime idle\n");
+ msm_otg_dbg_log_event(phy, "RUNTIME IDLE",
+ phy->otg->state, motg->ext_chg_active);
+
+ if (phy->otg->state == OTG_STATE_UNDEFINED)
+ return -EAGAIN;
+
+ if (motg->ext_chg_active == DEFAULT) {
+ dev_dbg(dev, "Deferring LPM\n");
+ /*
+ * Charger detection may happen in user space.
+ * Delay entering LPM by 3 sec. Otherwise we
+ * have to exit LPM when user space begins
+ * charger detection.
+ *
+ * This timer will be canceled when user space
+ * votes against LPM by incrementing PM usage
+ * counter. We enter low power mode when
+ * PM usage counter is decremented.
+ */
+ pm_schedule_suspend(dev, 3000);
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+static int msm_otg_runtime_suspend(struct device *dev)
+{
+ struct msm_otg *motg = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "OTG runtime suspend\n");
+ msm_otg_dbg_log_event(&motg->phy, "RUNTIME SUSPEND",
+ get_pm_runtime_counter(dev), 0);
+ return msm_otg_suspend(motg);
+}
+
+static int msm_otg_runtime_resume(struct device *dev)
+{
+ struct msm_otg *motg = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "OTG runtime resume\n");
+ msm_otg_dbg_log_event(&motg->phy, "RUNTIME RESUME",
+ get_pm_runtime_counter(dev), 0);
+
+ return msm_otg_resume(motg);
+}
+#endif
+
+#ifdef CONFIG_PM_SLEEP
+static int msm_otg_pm_suspend(struct device *dev)
+{
+ struct msm_otg *motg = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "OTG PM suspend\n");
+ msm_otg_dbg_log_event(&motg->phy, "PM SUSPEND START",
+ get_pm_runtime_counter(dev),
+ atomic_read(&motg->pm_suspended));
+
+ /* flush any pending sm_work first */
+ flush_work(&motg->sm_work);
+ if (!atomic_read(&motg->in_lpm)) {
+ dev_err(dev, "Abort PM suspend!! (USB is outside LPM)\n");
+ return -EBUSY;
+ }
+ atomic_set(&motg->pm_suspended, 1);
+
+ return 0;
+}
+
+static int msm_otg_pm_resume(struct device *dev)
+{
+ int ret = 0;
+ struct msm_otg *motg = dev_get_drvdata(dev);
+
+ dev_dbg(dev, "OTG PM resume\n");
+ msm_otg_dbg_log_event(&motg->phy, "PM RESUME START",
+ get_pm_runtime_counter(dev), pm_runtime_suspended(dev));
+
+ if (motg->resume_pending || motg->phy_irq_pending) {
+ msm_otg_dbg_log_event(&motg->phy, "PM RESUME BY USB",
+ motg->async_int, motg->resume_pending);
+ /* sm work if pending will start in pm notify to exit LPM */
+ }
+
+ return ret;
+}
+#endif
+
+#ifdef CONFIG_PM
+static const struct dev_pm_ops msm_otg_dev_pm_ops = {
+ SET_SYSTEM_SLEEP_PM_OPS(msm_otg_pm_suspend, msm_otg_pm_resume)
+ SET_RUNTIME_PM_OPS(msm_otg_runtime_suspend, msm_otg_runtime_resume,
+ msm_otg_runtime_idle)
+};
+#endif
+
+static const struct of_device_id msm_otg_dt_match[] = {
+ { .compatible = "qcom,hsusb-otg",
+ },
+ {}
+};
+
+static struct platform_driver msm_otg_driver = {
+ .probe = msm_otg_probe,
+ .remove = msm_otg_remove,
+ .shutdown = msm_otg_shutdown,
+ .driver = {
+ .name = DRIVER_NAME,
+ .owner = THIS_MODULE,
+#ifdef CONFIG_PM
+ .pm = &msm_otg_dev_pm_ops,
+#endif
+ .of_match_table = msm_otg_dt_match,
+ },
+};
+
+module_platform_driver(msm_otg_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM USB transceiver driver");
diff --git a/include/linux/oom.h b/include/linux/oom.h
index 5b5d4c7..34ed577 100644
--- a/include/linux/oom.h
+++ b/include/linux/oom.h
@@ -63,6 +63,15 @@
return tsk->signal->oom_mm;
}
+/*
+ * Use this helper if tsk->mm != mm and the victim mm needs a special
+ * handling. This is guaranteed to stay true after once set.
+ */
+static inline bool mm_is_oom_victim(struct mm_struct *mm)
+{
+ return test_bit(MMF_OOM_VICTIM, &mm->flags);
+}
+
extern unsigned long oom_badness(struct task_struct *p,
struct mem_cgroup *memcg, const nodemask_t *nodemask,
unsigned long totalpages);
diff --git a/include/linux/sched.h b/include/linux/sched.h
index 0d4035a..62c770d 100644
--- a/include/linux/sched.h
+++ b/include/linux/sched.h
@@ -636,6 +636,7 @@
#define MMF_OOM_SKIP 21 /* mm is of no interest for the OOM killer */
#define MMF_UNSTABLE 22 /* mm is unstable for copy_from_user */
#define MMF_HUGE_ZERO_PAGE 23 /* mm has ever used the global huge zero page */
+#define MMF_OOM_VICTIM 25 /* mm is the oom victim */
#define MMF_INIT_MASK (MMF_DUMPABLE_MASK | MMF_DUMP_FILTER_MASK)
diff --git a/include/linux/usb/msm_hsusb.h b/include/linux/usb/msm_hsusb.h
new file mode 100644
index 0000000..53d8458
--- /dev/null
+++ b/include/linux/usb/msm_hsusb.h
@@ -0,0 +1,357 @@
+/* include/linux/usb/msm_hsusb.h
+ *
+ * Copyright (C) 2008 Google, Inc.
+ * Author: Brian Swetland <swetland@google.com>
+ * Copyright (c) 2009-2017, The Linux Foundation. All rights reserved.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ */
+
+#ifndef __ASM_ARCH_MSM_HSUSB_H
+#define __ASM_ARCH_MSM_HSUSB_H
+
+#include <linux/types.h>
+#include <linux/usb/ch9.h>
+#include <linux/usb/gadget.h>
+#include <linux/usb/otg.h>
+#include <linux/clk.h>
+#include <linux/pm_qos.h>
+#include <linux/hrtimer.h>
+#include <linux/power_supply.h>
+#include <linux/cdev.h>
+#include <linux/usb_bam.h>
+#include <linux/extcon.h>
+#include <linux/regulator/driver.h>
+/**
+ * Requested USB votes for NOC frequency
+ *
+ * USB_NOC_NOM_VOTE Vote for NOM set of NOC frequencies
+ * USB_NOC_SVS_VOTE Vote for SVS set of NOC frequencies
+ *
+ */
+enum usb_noc_mode {
+ USB_NOC_NOM_VOTE = 0,
+ USB_NOC_SVS_VOTE,
+ USB_NOC_NUM_VOTE,
+};
+
+/**
+ * Different states involved in USB charger detection.
+ *
+ * USB_CHG_STATE_UNDEFINED USB charger is not connected or detection
+ * process is not yet started.
+ * USB_CHG_STATE_IN_PROGRESS Charger detection in progress
+ * USB_CHG_STATE_WAIT_FOR_DCD Waiting for Data pins contact.
+ * USB_CHG_STATE_DCD_DONE Data pin contact is detected.
+ * USB_CHG_STATE_PRIMARY_DONE Primary detection is completed (Detects
+ * between SDP and DCP/CDP).
+ * USB_CHG_STATE_SECONDARY_DONE Secondary detection is completed (Detects
+ * between DCP and CDP).
+ * USB_CHG_STATE_DETECTED USB charger type is determined.
+ *
+ */
+enum usb_chg_state {
+ USB_CHG_STATE_UNDEFINED = 0,
+ USB_CHG_STATE_IN_PROGRESS,
+ USB_CHG_STATE_WAIT_FOR_DCD,
+ USB_CHG_STATE_DCD_DONE,
+ USB_CHG_STATE_PRIMARY_DONE,
+ USB_CHG_STATE_SECONDARY_DONE,
+ USB_CHG_STATE_DETECTED,
+};
+
+/**
+ * USB charger types
+ *
+ * USB_INVALID_CHARGER Invalid USB charger.
+ * USB_SDP_CHARGER Standard downstream port. Refers to a downstream port
+ * on USB2.0 compliant host/hub.
+ * USB_DCP_CHARGER Dedicated charger port (AC charger/ Wall charger).
+ * USB_CDP_CHARGER Charging downstream port. Enumeration can happen and
+ * IDEV_CHG_MAX can be drawn irrespective of USB state.
+ * USB_NONCOMPLIANT_CHARGER A non-compliant charger pull DP and DM to specific
+ * voltages between 2.0-3.3v for identification.
+ *
+ */
+enum usb_chg_type {
+ USB_INVALID_CHARGER = 0,
+ USB_SDP_CHARGER,
+ USB_DCP_CHARGER,
+ USB_CDP_CHARGER,
+ USB_NONCOMPLIANT_CHARGER,
+ USB_FLOATED_CHARGER,
+};
+
+/**
+ * Maintain state for hvdcp external charger status
+ * DEFAULT This is used when DCP is detected
+ * ACTIVE This is used when ioctl is called to block LPM
+ * INACTIVE This is used when ioctl is called to unblock LPM
+ */
+
+enum usb_ext_chg_status {
+ DEFAULT = 1,
+ ACTIVE,
+ INACTIVE,
+};
+
+/**
+ * USB ID state
+ */
+enum usb_id_state {
+ USB_ID_GROUND = 0,
+ USB_ID_FLOAT,
+};
+
+#define USB_NUM_BUS_CLOCKS 3
+
+/**
+ * struct msm_otg: OTG driver data. Shared by HCD and DCD.
+ * @otg: USB OTG Transceiver structure.
+ * @pdata: otg device platform data.
+ * @irq: IRQ number assigned for HSUSB controller.
+ * @async_irq: IRQ number used by some controllers during low power state
+ * @phy_irq: IRQ number assigned for PHY to notify events like id and line
+ state changes.
+ * @pclk: clock struct of iface_clk.
+ * @core_clk: clock struct of core_bus_clk.
+ * @sleep_clk: clock struct of sleep_clk for USB PHY.
+ * @phy_reset_clk: clock struct of phy_reset_clk for USB PHY. This clock is
+ a reset only clock and resets the PHY, ULPI bridge and
+ CSR wrapper.
+ * @phy_por_clk: clock struct of phy_por_clk for USB PHY. This clock is
+ a reset only clock and resets only the PHY (POR).
+ * @phy_csr_clk: clock struct of phy_csr_clk for USB PHY. This clock is
+ required to access PHY CSR registers via AHB2PHY interface.
+ * @bus_clks: bimc/snoc/pcnoc clock struct.
+ * @core_reset: Reset control for core_clk
+ * @phy_reset: Reset control for phy_reset_clk
+ * @phy_por_reset: Reset control for phy_por_clk
+ * @default_noc_mode: default frequency for NOC clocks - SVS or NOM
+ * @core_clk_rate: core clk max frequency
+ * @regs: ioremapped register base address.
+ * @usb_phy_ctrl_reg: relevant PHY_CTRL_REG register base address.
+ * @inputs: OTG state machine inputs(Id, SessValid etc).
+ * @sm_work: OTG state machine work.
+ * @sm_work_pending: OTG state machine work is pending, queued post pm_resume
+ * @resume_pending: USB h/w lpm_exit pending. Done on next sm_work run
+ * @pm_suspended: OTG device is system(PM) suspended.
+ * @pm_notify: Notifier to receive system wide PM transition events.
+ It is used to defer wakeup events processing until
+ system is RESUMED.
+ * @in_lpm: indicates low power mode (LPM) state.
+ * @async_int: IRQ line on which ASYNC interrupt arrived in LPM.
+ * @cur_power: The amount of mA available from downstream port.
+ * @otg_wq: Strict order otg workqueue for OTG works (SM/ID/SUSPEND).
+ * @chg_work: Charger detection work.
+ * @chg_state: The state of charger detection process.
+ * @chg_type: The type of charger attached.
+ * @bus_perf_client: Bus performance client handle to request BUS bandwidth
+ * @host_bus_suspend: indicates host bus suspend or not.
+ * @device_bus_suspend: indicates device bus suspend or not.
+ * @bus_clks_enabled: indicates pcnoc/snoc/bimc clocks are on or not.
+ * @chg_check_timer: The timer used to implement the workaround to detect
+ * very slow plug in of wall charger.
+ * @bc1p2_current_max: Max charging current allowed as per bc1.2 chg detection
+ * @typec_current_max: Max charging current allowed as per type-c chg detection
+ * @is_ext_chg_dcp: To indicate whether charger detected by external entity
+ SMB hardware is DCP charger or not.
+ * @ext_id_irq: IRQ for ID interrupt.
+ * @phy_irq_pending: Gets set when PHY IRQ arrives in LPM.
+ * @id_state: Indicates USBID line status.
+ * @rm_pulldown: Indicates pulldown status on D+ and D- data lines.
+ * @extcon_vbus: Used for VBUS notification registration.
+ * @extcon_id: Used for ID notification registration.
+ * @vbus_nb: Notification callback for VBUS event.
+ * @id_nb: Notification callback for ID event.
+ * @dpdm_desc: Regulator descriptor for D+ and D- voting.
+ * @dpdm_rdev: Regulator class device for dpdm regulator.
+ * @dbg_idx: Dynamic debug buffer Index.
+ * @dbg_lock: Dynamic debug buffer Lock.
+ * @buf: Dynamic Debug Buffer.
+ * @max_nominal_system_clk_rate: max freq at which system clock can run in
+ nominal mode.
+ */
+struct msm_otg {
+ struct usb_phy phy;
+ struct msm_otg_platform_data *pdata;
+ struct platform_device *pdev;
+ int irq;
+ int async_irq;
+ int phy_irq;
+ struct clk *xo_clk;
+ struct clk *pclk;
+ struct clk *core_clk;
+ struct clk *sleep_clk;
+ struct clk *phy_reset_clk;
+ struct clk *phy_por_clk;
+ struct clk *phy_csr_clk;
+ struct clk *bus_clks[USB_NUM_BUS_CLOCKS];
+ struct clk *phy_ref_clk;
+ struct reset_control *core_reset;
+ struct reset_control *phy_reset;
+ struct reset_control *phy_por_reset;
+ long core_clk_rate;
+ long core_clk_svs_rate;
+ long core_clk_nominal_rate;
+ enum usb_noc_mode default_noc_mode;
+ struct resource *io_res;
+ void __iomem *regs;
+ void __iomem *phy_csr_regs;
+ void __iomem *usb_phy_ctrl_reg;
+#define ID 0
+#define B_SESS_VLD 1
+#define A_BUS_SUSPEND 14
+#define B_FALSE_SDP 18
+ unsigned long inputs;
+ struct work_struct sm_work;
+ bool sm_work_pending;
+ bool resume_pending;
+ atomic_t pm_suspended;
+ struct notifier_block pm_notify;
+ atomic_t in_lpm;
+ bool err_event_seen;
+ int async_int;
+ unsigned int cur_power;
+ struct workqueue_struct *otg_wq;
+ struct delayed_work chg_work;
+ struct delayed_work id_status_work;
+ enum usb_chg_state chg_state;
+ enum usb_chg_type chg_type;
+ unsigned int dcd_time;
+ unsigned long caps;
+ uint32_t bus_perf_client;
+ bool host_bus_suspend;
+ bool device_bus_suspend;
+ bool bus_clks_enabled;
+ struct timer_list chg_check_timer;
+ /*
+ * Allowing PHY power collpase turns off the HSUSB 3.3v and 1.8v
+ * analog regulators while going to low power mode.
+ * Currently only 28nm PHY has the support to allowing PHY
+ * power collapse since it doesn't have leakage currents while
+ * turning off the power rails.
+ */
+#define ALLOW_PHY_POWER_COLLAPSE BIT(0)
+ /*
+ * Allow PHY RETENTION mode before turning off the digital
+ * voltage regulator(VDDCX).
+ */
+#define ALLOW_PHY_RETENTION BIT(1)
+ /*
+ * Allow putting the core in Low Power mode, when
+ * USB bus is suspended but cable is connected.
+ */
+#define ALLOW_LPM_ON_DEV_SUSPEND BIT(2)
+ /*
+ * Allowing PHY regulators LPM puts the HSUSB 3.3v and 1.8v
+ * analog regulators into LPM while going to USB low power mode.
+ */
+#define ALLOW_PHY_REGULATORS_LPM BIT(3)
+ /*
+ * Allow PHY RETENTION mode before turning off the digital
+ * voltage regulator(VDDCX) during host mode.
+ */
+#define ALLOW_HOST_PHY_RETENTION BIT(4)
+ /*
+ * Allow VDD minimization without putting PHY into retention
+ * for fixing PHY current leakage issue when LDOs ar turned off.
+ */
+#define ALLOW_VDD_MIN_WITH_RETENTION_DISABLED BIT(5)
+
+ /*
+ * PHY can keep D+ pull-up during peripheral bus suspend and
+ * D+/D- pull-down during host bus suspend without any
+ * re-work. This is possible only when PHY DVDD is supplied
+ * by a PMIC LDO (unlike VDDCX/VDDMX).
+ */
+#define ALLOW_BUS_SUSPEND_WITHOUT_REWORK BIT(6)
+ unsigned long lpm_flags;
+#define PHY_PWR_COLLAPSED BIT(0)
+#define PHY_RETENTIONED BIT(1)
+#define XO_SHUTDOWN BIT(2)
+#define CLOCKS_DOWN BIT(3)
+#define PHY_REGULATORS_LPM BIT(4)
+ int reset_counter;
+ unsigned int online;
+ unsigned int host_mode;
+ unsigned int bc1p2_current_max;
+ unsigned int typec_current_max;
+
+ dev_t ext_chg_dev;
+ struct cdev ext_chg_cdev;
+ struct class *ext_chg_class;
+ struct device *ext_chg_device;
+ bool ext_chg_opened;
+ enum usb_ext_chg_status ext_chg_active;
+ struct completion ext_chg_wait;
+ struct pinctrl *phy_pinctrl;
+ bool is_ext_chg_dcp;
+ struct qpnp_vadc_chip *vadc_dev;
+ int ext_id_irq;
+ bool phy_irq_pending;
+ enum usb_id_state id_state;
+ bool rm_pulldown;
+ struct extcon_dev *extcon_vbus;
+ struct extcon_dev *extcon_id;
+ struct notifier_block vbus_nb;
+ struct notifier_block id_nb;
+ struct regulator_desc dpdm_rdesc;
+ struct regulator_dev *dpdm_rdev;
+/* Maximum debug message length */
+#define DEBUG_MSG_LEN 128UL
+/* Maximum number of messages */
+#define DEBUG_MAX_MSG 256UL
+ unsigned int dbg_idx;
+ rwlock_t dbg_lock;
+
+ char (buf[DEBUG_MAX_MSG])[DEBUG_MSG_LEN]; /* buffer */
+ unsigned int vbus_state;
+ unsigned int usb_irq_count;
+ int pm_qos_latency;
+ struct pm_qos_request pm_qos_req_dma;
+ struct delayed_work perf_vote_work;
+};
+
+struct ci13xxx_platform_data {
+ u8 usb_core_id;
+ /*
+ * value of 2^(log2_itc-1) will be used as the interrupt threshold
+ * (ITC), when log2_itc is between 1 to 7.
+ */
+ int log2_itc;
+ bool l1_supported;
+ bool enable_ahb2ahb_bypass;
+ bool enable_streaming;
+ bool enable_axi_prefetch;
+};
+
+#ifdef CONFIG_USB_BAM
+void msm_bam_set_usb_host_dev(struct device *dev);
+bool msm_usb_bam_enable(enum usb_ctrl ctrl, bool bam_enable);
+int msm_do_bam_disable_enable(enum usb_ctrl ctrl);
+#else
+static inline void msm_bam_set_usb_host_dev(struct device *dev) {}
+static inline bool msm_usb_bam_enable(enum usb_ctrl ctrl, bool bam_enable)
+{
+ return true;
+}
+int msm_do_bam_disable_enable(enum usb_ctrl ctrl) { return true; }
+#endif
+#ifdef CONFIG_USB_CI13XXX_MSM
+void msm_hw_soft_reset(void);
+#else
+static inline void msm_hw_soft_reset(void)
+{
+}
+#endif
+
+#endif
diff --git a/include/linux/usb/msm_hsusb_hw.h b/include/linux/usb/msm_hsusb_hw.h
index c32bc7e..b86f127 100644
--- a/include/linux/usb/msm_hsusb_hw.h
+++ b/include/linux/usb/msm_hsusb_hw.h
@@ -26,6 +26,9 @@
#define ULPI_TX_PKT_EN_CLR_FIX BIT(19)
#define USB_CAPLENGTH (MSM_USB_BASE + 0x0100) /* 8 bit */
+#define USB_HS_APF_CTRL (MSM_USB_BASE + 0x0380)
+
+#define APF_CTRL_EN BIT(0)
#define USB_USBCMD (MSM_USB_BASE + 0x0140)
#define USB_PORTSC (MSM_USB_BASE + 0x0184)
@@ -35,6 +38,8 @@
#define USB_PHY_CTRL2 (MSM_USB_BASE + 0x0278)
#define GENCONFIG_2_SESS_VLD_CTRL_EN BIT(7)
+#define GENCONFIG_2_LINESTATE_DIFF_WAKEUP_EN BIT(12)
+#define GENCONFIG_2_DPSE_DMSE_HV_INTR_EN BIT(15)
#define USBCMD_SESS_VLD_CTRL BIT(25)
#define USBCMD_RESET 2
@@ -48,11 +53,17 @@
#define L1_CONFIG_GATE_SYS_CLK BIT(7)
#define L1_CONFIG_PHY_LPM BIT(10)
#define L1_CONFIG_PLL BIT(11)
+#define AHB2AHB_BYPASS BIT(31)
+#define AHB2AHB_BYPASS_BIT_MASK BIT(31)
+#define AHB2AHB_BYPASS_CLEAR (0 << 31)
#define PORTSC_PHCD (1 << 23) /* phy suspend mode */
#define PORTSC_PTS_MASK (3 << 30)
#define PORTSC_PTS_ULPI (2 << 30)
#define PORTSC_PTS_SERIAL (3 << 30)
+#define PORTSC_LS (3 << 10)
+#define PORTSC_LS_DM (1 << 10)
+#define PORTSC_CCS (1 << 0)
#define USB_ULPI_VIEWPORT (MSM_USB_BASE + 0x0170)
#define ULPI_RUN (1 << 30)
@@ -77,10 +88,16 @@
#define ASYNC_INTR_CTRL (1 << 29) /* Enable async interrupt */
#define ULPI_STP_CTRL (1 << 30) /* Block communication with PHY */
#define PHY_RETEN (1 << 1) /* PHY retention enable/disable */
+#define PHY_IDHV_INTEN (1 << 8) /* PHY ID HV interrupt */
+#define PHY_OTGSESSVLDHV_INTEN (1 << 9) /* PHY Session Valid HV int. */
+#define PHY_CLAMP_DPDMSE_EN (1 << 21) /* PHY mpm DP DM clamp enable */
+#define PHY_POR_BIT_MASK BIT(0)
#define PHY_POR_ASSERT (1 << 0) /* USB2 28nm PHY POR ASSERT */
+#define PHY_POR_DEASSERT (0 << 0) /* USB2 28nm PHY POR DEASSERT */
/* OTG definitions */
#define OTGSC_INTSTS_MASK (0x7f << 16)
+#define OTGSC_IDPU (1 << 5)
#define OTGSC_ID (1 << 8)
#define OTGSC_BSV (1 << 11)
#define OTGSC_IDIS (1 << 16)
@@ -88,4 +105,29 @@
#define OTGSC_IDIE (1 << 24)
#define OTGSC_BSVIE (1 << 27)
+/* USB PHY CSR registers and bit definitions */
+
+#define USB_PHY_CSR_PHY_CTRL_COMMON0 (MSM_USB_PHY_CSR_BASE + 0x078)
+#define SIDDQ BIT(2)
+
+#define USB_PHY_CSR_PHY_CTRL1 (MSM_USB_PHY_CSR_BASE + 0x08C)
+#define ID_HV_CLAMP_EN_N BIT(1)
+
+#define USB_PHY_CSR_PHY_CTRL3 (MSM_USB_PHY_CSR_BASE + 0x094)
+#define CLAMP_MPM_DPSE_DMSE_EN_N BIT(2)
+
+#define USB2_PHY_USB_PHY_IRQ_CMD (MSM_USB_PHY_CSR_BASE + 0x0D0)
+#define USB2_PHY_USB_PHY_INTERRUPT_SRC_STATUS (MSM_USB_PHY_CSR_BASE + 0x05C)
+
+#define USB2_PHY_USB_PHY_INTERRUPT_CLEAR0 (MSM_USB_PHY_CSR_BASE + 0x0DC)
+#define USB2_PHY_USB_PHY_INTERRUPT_CLEAR1 (MSM_USB_PHY_CSR_BASE + 0x0E0)
+
+#define USB2_PHY_USB_PHY_INTERRUPT_MASK1 (MSM_USB_PHY_CSR_BASE + 0x0D8)
+
+#define USB_PHY_IDDIG_1_0 BIT(7)
+
+#define USB_PHY_IDDIG_RISE_MASK BIT(0)
+#define USB_PHY_IDDIG_FALL_MASK BIT(1)
+#define USB_PHY_ID_MASK (USB_PHY_IDDIG_RISE_MASK | USB_PHY_IDDIG_FALL_MASK)
+
#endif /* __LINUX_USB_GADGET_MSM72K_UDC_H__ */
diff --git a/include/linux/usb/phy.h b/include/linux/usb/phy.h
index 64aa52e..d999b3c 100644
--- a/include/linux/usb/phy.h
+++ b/include/linux/usb/phy.h
@@ -58,6 +58,7 @@
OTG_STATE_B_SRP_INIT,
OTG_STATE_B_PERIPHERAL,
OTG_STATE_B_SUSPEND,
+ OTG_STATE_B_CHARGER,
/* extra dual-role default-b states */
OTG_STATE_B_WAIT_ACON,
@@ -141,6 +142,10 @@
/* reset the PHY clocks */
int (*reset)(struct usb_phy *x);
+
+ /* for notification of usb_phy_dbg_events */
+ void (*dbg_event)(struct usb_phy *x,
+ char *event, int msg1, int msg2);
int (*disable_chirp)(struct usb_phy *x, bool disable);
};
diff --git a/include/soc/qcom/secure_buffer.h b/include/soc/qcom/secure_buffer.h
index 12fa374..d9a526d 100644
--- a/include/soc/qcom/secure_buffer.h
+++ b/include/soc/qcom/secure_buffer.h
@@ -50,6 +50,8 @@
#define PERM_EXEC 0x1
#ifdef CONFIG_QCOM_SECURE_BUFFER
+int msm_secure_table(struct sg_table *table);
+int msm_unsecure_table(struct sg_table *table);
int hyp_assign_table(struct sg_table *table,
u32 *source_vm_list, int source_nelems,
int *dest_vmids, int *dest_perms,
@@ -57,8 +59,19 @@
extern int hyp_assign_phys(phys_addr_t addr, u64 size,
u32 *source_vmlist, int source_nelems,
int *dest_vmids, int *dest_perms, int dest_nelems);
+bool msm_secure_v2_is_supported(void);
const char *msm_secure_vmid_to_string(int secure_vmid);
#else
+static inline int msm_secure_table(struct sg_table *table)
+{
+ return -EINVAL;
+}
+
+static inline int msm_unsecure_table(struct sg_table *table)
+{
+ return -EINVAL;
+}
+
static inline int hyp_assign_table(struct sg_table *table,
u32 *source_vm_list, int source_nelems,
int *dest_vmids, int *dest_perms,
@@ -74,6 +87,11 @@
return -EINVAL;
}
+static inline bool msm_secure_v2_is_supported(void)
+{
+ return false;
+}
+
static inline const char *msm_secure_vmid_to_string(int secure_vmid)
{
return "N/A";
diff --git a/mm/mmap.c b/mm/mmap.c
index 7e6c049..c30a61e 100644
--- a/mm/mmap.c
+++ b/mm/mmap.c
@@ -2984,20 +2984,20 @@
/* Use -1 here to ensure all VMAs in the mm are unmapped */
unmap_vmas(&tlb, vma, 0, -1);
- set_bit(MMF_OOM_SKIP, &mm->flags);
- if (unlikely(tsk_is_oom_victim(current))) {
+ if (unlikely(mm_is_oom_victim(mm))) {
/*
* Wait for oom_reap_task() to stop working on this
* mm. Because MMF_OOM_SKIP is already set before
* calling down_read(), oom_reap_task() will not run
* on this "mm" post up_write().
*
- * tsk_is_oom_victim() cannot be set from under us
- * either because current->mm is already set to NULL
+ * mm_is_oom_victim() cannot be set from under us
+ * either because victim->mm is already set to NULL
* under task_lock before calling mmput and oom_mm is
- * set not NULL by the OOM killer only if current->mm
+ * set not NULL by the OOM killer only if victim->mm
* is found not NULL while holding the task_lock.
*/
+ set_bit(MMF_OOM_SKIP, &mm->flags);
down_write(&mm->mmap_sem);
up_write(&mm->mmap_sem);
}
diff --git a/mm/oom_kill.c b/mm/oom_kill.c
index af9a8a6..6fd9773 100644
--- a/mm/oom_kill.c
+++ b/mm/oom_kill.c
@@ -677,8 +677,10 @@
return;
/* oom_mm is bound to the signal struct life time. */
- if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm))
+ if (!cmpxchg(&tsk->signal->oom_mm, NULL, mm)) {
atomic_inc(&tsk->signal->oom_mm->mm_count);
+ set_bit(MMF_OOM_VICTIM, &mm->flags);
+ }
/*
* Make sure that the task is woken up from uninterruptible sleep